From e447bb8c05dc273de60b789312b58a8c42af9168 Mon Sep 17 00:00:00 2001 From: Makiras <22570332+Makiras@users.noreply.github.com> Date: Thu, 1 Oct 2026 13:56:38 +0800 Subject: [PATCH 1/6] ci: explicitly dispatch release after tagged builds Dispatch Release Picker after the release bundle is uploaded, and wait for the source Tagged CI run to finish successfully before publishing. Replace the workflow_run listener to support builds dispatched with GITHUB_TOKEN. --- .github/workflows/release-build.yml | 14 ++++ .github/workflows/release.yml | 27 ++----- doc/maintainer-guidance.md | 14 ++-- scripts/release/wait_for_source_run.py | 68 ++++++++++++++++++ test/release/test_wait_for_source_run.py | 89 ++++++++++++++++++++++++ 5 files changed, 187 insertions(+), 25 deletions(-) create mode 100644 scripts/release/wait_for_source_run.py create mode 100644 test/release/test_wait_for_source_run.py diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml index c1bd151..b8aa892 100644 --- a/.github/workflows/release-build.yml +++ b/.github/workflows/release-build.yml @@ -256,3 +256,17 @@ jobs: name: picker-release-bundle path: release-assets/* if-no-files-found: error + + dispatch-release: + needs: bundle + runs-on: ubuntu-24.04 + permissions: + actions: write + steps: + - name: Start release for this Tagged CI run + env: + GH_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + gh workflow run release.yml --repo "$GITHUB_REPOSITORY" --ref master \ + --raw-field "run_id=$GITHUB_RUN_ID" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 780b4d9..45dbcb9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,13 +1,10 @@ name: Release Picker on: - workflow_run: - workflows: [Tagged CI] - types: [completed] workflow_dispatch: inputs: run_id: - description: "Successful Tagged CI run ID whose exact artifacts should be published" + description: "Tagged CI run ID to wait for and publish its exact artifacts" required: true type: string @@ -17,36 +14,24 @@ permissions: jobs: publish: - if: github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' runs-on: ubuntu-24.04 permissions: contents: write actions: read pull-requests: read env: - SOURCE_RUN_ID: ${{ github.event.workflow_run.id || inputs.run_id }} + SOURCE_RUN_ID: ${{ inputs.run_id }} GH_TOKEN: ${{ github.token }} steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - - name: Validate source run + - name: Wait for successful Tagged CI completion + timeout-minutes: 65 run: | set -euo pipefail - [[ "$SOURCE_RUN_ID" =~ ^[0-9]+$ ]] - gh api "repos/${GITHUB_REPOSITORY}/actions/runs/${SOURCE_RUN_ID}" > source-run.json - python - <<'PY' - import json - import os - - with open("source-run.json") as source: - run = json.load(source) - assert run["repository"]["full_name"] == os.environ["GITHUB_REPOSITORY"] - assert run["path"].split("@", 1)[0].endswith("/.github/workflows/release-build.yml") or run["path"].split("@", 1)[0] == ".github/workflows/release-build.yml" - assert run["event"] in {"repository_dispatch", "workflow_dispatch"} - assert run["status"] == "completed" and run["conclusion"] == "success" - assert run["id"] == int(os.environ["SOURCE_RUN_ID"]) - PY + python3 scripts/release/wait_for_source_run.py \ + "$GITHUB_REPOSITORY" "$SOURCE_RUN_ID" --timeout 3600 - name: Download exact CI release bundle uses: actions/download-artifact@v4 with: diff --git a/doc/maintainer-guidance.md b/doc/maintainer-guidance.md index 74213d0..76ab65a 100644 --- a/doc/maintainer-guidance.md +++ b/doc/maintainer-guidance.md @@ -49,10 +49,16 @@ older run to fail its ancestry check. **Tagged CI** checks out that exact tag, tests the native builds and builds a CPython 3.12 Linux x86-64 wheel, Linux x86-64 and aarch64 AppImages, and Linux x86-64 and aarch64 native archives. It uploads one checked release bundle. -**Release Picker** runs only after Tagged CI succeeds, downloads that run's -bundle, checks its hashes and tag SHA, and publishes those exact files without -rebuilding. All five packages, `SHA256SUMS` and `RELEASE-MANIFEST.json` go into -one GitHub Release. The manifest records the Picker tag, commit, CI run ID and +After the bundle upload succeeds, Tagged CI explicitly dispatches **Release +Picker** with its run ID. A `workflow_run` listener is not used because Tagged +CI is itself dispatched using `GITHUB_TOKEN`. Release Picker waits up to 60 +minutes for the source run to complete successfully; a failed, cancelled or +unfinished run cannot publish. Tagged CI does not wait for Release Picker, +which would prevent the source run from completing. Release Picker downloads +that run's bundle, checks its hashes and tag SHA, and publishes those exact +files without rebuilding. All five packages, `SHA256SUMS` and +`RELEASE-MANIFEST.json` go into one GitHub Release. The manifest records the +Picker tag, commit, CI run ID and xcomm version used. The archives contain the installed `usr/bin/picker`, `usr/share/picker` templates and native libraries; they are not bare executables. AppImage is the self-contained desktop distribution; diff --git a/scripts/release/wait_for_source_run.py b/scripts/release/wait_for_source_run.py new file mode 100644 index 0000000..7e5ebdd --- /dev/null +++ b/scripts/release/wait_for_source_run.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python3 +"""Wait for a validated Tagged CI run to complete successfully before publishing.""" + +import argparse +import json +import subprocess +import sys +import time + + +def fetch_run(repository: str, run_id: int) -> dict: + response = subprocess.check_output( + ("gh", "api", f"repos/{repository}/actions/runs/{run_id}"), + text=True, + timeout=30, + ) + return json.loads(response) + + +def wait_for_success( + repository: str, run_id: int, timeout: int = 3600, interval: int = 10 +) -> dict: + if run_id <= 0 or timeout <= 0 or interval <= 0: + raise ValueError("Run ID, timeout and polling interval must be positive") + deadline = time.monotonic() + timeout + while True: + run = fetch_run(repository, run_id) + path = run["path"].split("@", 1)[0] + if ( + run["repository"]["full_name"] != repository + or run["id"] != run_id + or not ( + path == ".github/workflows/release-build.yml" + or path.endswith("/.github/workflows/release-build.yml") + ) + or run["event"] not in {"repository_dispatch", "workflow_dispatch"} + ): + raise ValueError(f"Run {run_id} is not a dispatched Tagged CI run in {repository}") + if run["status"] == "completed": + if run["conclusion"] != "success": + raise RuntimeError( + f"Tagged CI run {run_id} completed with {run['conclusion']}; refusing to publish" + ) + print(f"Verified successful Tagged CI run: {run_id}", flush=True) + return run + remaining = deadline - time.monotonic() + if remaining <= 0: + raise TimeoutError(f"Timed out waiting for Tagged CI run {run_id} after {timeout}s") + print(f"Tagged CI run {run_id} is {run['status']}; waiting for completion", flush=True) + time.sleep(min(interval, remaining)) + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("repository") + parser.add_argument("run_id", type=int) + parser.add_argument("--timeout", type=int, default=3600) + args = parser.parse_args() + try: + wait_for_success(args.repository, args.run_id, args.timeout) + except (ValueError, RuntimeError, TimeoutError, subprocess.SubprocessError) as error: + print(error, file=sys.stderr) + return 1 + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/test/release/test_wait_for_source_run.py b/test/release/test_wait_for_source_run.py new file mode 100644 index 0000000..c2a0ca0 --- /dev/null +++ b/test/release/test_wait_for_source_run.py @@ -0,0 +1,89 @@ +import importlib.util +from pathlib import Path +import unittest +from unittest.mock import patch + + +SCRIPT = Path(__file__).resolve().parents[2] / "scripts/release/wait_for_source_run.py" +spec = importlib.util.spec_from_file_location("wait_for_source_run", SCRIPT) +source_run = importlib.util.module_from_spec(spec) +spec.loader.exec_module(source_run) + + +def run(status="completed", conclusion="success", **overrides): + return { + "id": 123, + "repository": {"full_name": "owner/repo"}, + "path": ".github/workflows/release-build.yml", + "event": "repository_dispatch", + "status": status, + "conclusion": conclusion, + **overrides, + } + + +class SourceRunTests(unittest.TestCase): + def test_waits_through_pending_states_until_success(self): + snapshots = [run("queued", None), run("in_progress", None), run()] + with patch.object(source_run, "fetch_run", side_effect=snapshots), patch.object( + source_run.time, "sleep" + ) as sleep, patch.object(source_run.time, "monotonic", return_value=0): + self.assertEqual(source_run.wait_for_success("owner/repo", 123), snapshots[-1]) + self.assertEqual(sleep.call_count, 2) + + def test_completed_success_does_not_wait(self): + with patch.object(source_run, "fetch_run", return_value=run()), patch.object( + source_run.time, "sleep" + ) as sleep: + source_run.wait_for_success("owner/repo", 123) + sleep.assert_not_called() + + def test_failed_or_cancelled_source_never_passes(self): + for conclusion in ("failure", "cancelled", "timed_out", "skipped", "neutral", None): + with self.subTest(conclusion=conclusion), patch.object( + source_run, "fetch_run", return_value=run(conclusion=conclusion) + ), patch.object(source_run.time, "sleep") as sleep: + with self.assertRaisesRegex(RuntimeError, "refusing to publish"): + source_run.wait_for_success("owner/repo", 123) + sleep.assert_not_called() + + def test_wrong_source_is_rejected_before_waiting(self): + for overrides in ( + {"id": 456}, + {"repository": {"full_name": "other/repo"}}, + {"path": ".github/workflows/ci.yml"}, + {"event": "pull_request"}, + ): + with self.subTest(overrides=overrides), patch.object( + source_run, "fetch_run", return_value=run("in_progress", None, **overrides) + ), patch.object(source_run.time, "sleep") as sleep: + with self.assertRaisesRegex(ValueError, "not a dispatched Tagged CI"): + source_run.wait_for_success("owner/repo", 123) + sleep.assert_not_called() + + def test_manual_source_and_qualified_workflow_path_are_accepted(self): + snapshot = run( + event="workflow_dispatch", + path="owner/repo/.github/workflows/release-build.yml@refs/heads/master", + ) + with patch.object(source_run, "fetch_run", return_value=snapshot): + self.assertEqual(source_run.wait_for_success("owner/repo", 123), snapshot) + + def test_wait_is_bounded_and_last_sleep_is_clipped_to_deadline(self): + with patch.object(source_run, "fetch_run", return_value=run("in_progress", None)), patch.object( + source_run.time, "monotonic", side_effect=[0, 3595, 3600] + ), patch.object(source_run.time, "sleep") as sleep: + with self.assertRaisesRegex(TimeoutError, "after 3600s"): + source_run.wait_for_success("owner/repo", 123) + sleep.assert_called_once_with(5) + + def test_invalid_inputs_do_not_query_github(self): + for values in ({"run_id": 0}, {"run_id": 123, "timeout": 0}, {"run_id": 123, "interval": 0}): + with self.subTest(values=values), patch.object(source_run, "fetch_run") as fetch: + with self.assertRaises(ValueError): + source_run.wait_for_success("owner/repo", **values) + fetch.assert_not_called() + + +if __name__ == "__main__": + unittest.main() From 03f0f44000a29d9d99c0820416ca78a5724cecfb Mon Sep 17 00:00:00 2001 From: Makiras <22570332+Makiras@users.noreply.github.com> Date: Thu, 8 Oct 2026 10:26:44 +0800 Subject: [PATCH 2/6] feat!: require xcomm 0.2 and migrate generated signals to XData Require xspcomm>=0.2.0,<0.3 and pin development builds to the published v0.2.0 commit. Check native ABI 2 in wheel verification and require libxspcomm.so.2 in release archives so development, wheel and native release packages use the same supported runtime generation. Replace removed XPin wrappers with direct XData objects in Python and UVM DUTs, including internal signals and indexed port access. Update the MultiClock example and document the native rebuild and signal API migration. Add a generated Adder regression for ABI identity, direct XData leaves, port aliases, assignment protection and 128-bit arithmetic. Validation: - All 24 native CTest programs and 11 release-policy tests pass. - Generated Adder, VPI internal-signal and MultiClock simulations pass. - UVM transaction/from-RTL pack rendering passes; VCS runtime unavailable. - Built Picker and xcomm v0.2.0 wheels install and pass verification in a fresh offline environment; pip rejects pairing Picker with xcomm 0.1.1. - Installed native AppDir contains the ABI 2 SONAME and a working command. - Workflow and shell syntax checks pass (existing queue field excluded from actionlint v1.7.12). BREAKING CHANGE: generated Python and UVM signals expose XData directly. Replace dut.signal.xdata with dut.signal and rebuild existing generated DUT extensions against the xcomm 0.2 / ABI 2 headers and runtime. --- .build-config.yml | 2 +- .github/workflows/release-build.yml | 2 +- doc/maintainer-guidance.md | 16 +++++++- example/MultiClock/example.py | 12 +++--- pyproject.toml | 2 +- scripts/build/verify-wheel.sh | 2 +- src/codegen/python.cpp | 4 +- template/python/dut.py | 10 ++--- template/uvm/dut.py | 52 ++++++------------------ test/scripts/test_export_adder_python.sh | 32 +++++++++++++++ 10 files changed, 76 insertions(+), 58 deletions(-) diff --git a/.build-config.yml b/.build-config.yml index 95c100d..590de14 100644 --- a/.build-config.yml +++ b/.build-config.yml @@ -4,4 +4,4 @@ tools: node: 20.12.2 python: 3.11 dependencies: - xcomm: 18f3e60e9eb9d38127e3ecdccfacf91cf016b19c + xcomm: 3e4bccb73f08310c08382d3987ba2d98b83df4d6 diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml index b8aa892..eef7fea 100644 --- a/.github/workflows/release-build.yml +++ b/.github/workflows/release-build.yml @@ -201,7 +201,7 @@ jobs: test "$(uname -m)" = "$ARCH" test -x AppDir/usr/bin/picker test -d AppDir/usr/share/picker/template - test -e AppDir/usr/lib/libxspcomm.so.1 + test -e AppDir/usr/lib/libxspcomm.so.2 output="$(AppDir/usr/bin/picker --version)" [[ "$output" == *"$VERSION"* ]] mkdir -p release-assets diff --git a/doc/maintainer-guidance.md b/doc/maintainer-guidance.md index 76ab65a..b276d50 100644 --- a/doc/maintainer-guidance.md +++ b/doc/maintainer-guidance.md @@ -89,8 +89,10 @@ in `.build-config.yml` (an explicit `XCOMM_SOURCE_REF` commit can override it) and never become release assets. Tagged builds use the selected published xcomm tag instead. The build refuses to switch an xcomm checkout with local changes. -The `xspcomm>=0.1.0.dev0,<0.2` requirement in `pyproject.toml` is a deliberate -compatibility range, not an automatically generated version. Change it only +The `xspcomm>=0.2.0,<0.3` requirement in `pyproject.toml` selects the supported +xcomm 0.2 series with native ABI 2. Development CI pins the v0.2.0 commit in +`.build-config.yml`; wheel verification requires `xspcomm.abi_version() == 2`, +and native release archives must contain `libxspcomm.so.2`. Change the range only after testing the corresponding xcomm ABI and baseline APIs. Release metadata, the installed Picker command and CMake receive their version from the same tag. @@ -138,6 +140,16 @@ generated DUT extensions. A package patch release alone must not change the SONAME. A Python wheel is also tied to its CPython ABI and platform tag; the SWIG module is not declared `abi3`. +The xcomm 0.2 trigger engine changes the native layouts of `XClock`, `XData`, +and `ExprNode`. Rebuild existing generated DUT extensions against the ABI 2 +headers and runtime; changing a library symlink cannot make an ABI 1 binary +compatible. Keep each DUT extension and its xcomm runtime on the same native +ABI. The coverage snapshot protocol version is separate from this ABI number. + +Generated Python and UVM DUT signals are direct `XData` objects. Use +`dut.signal.value` for signal values and pass `dut.signal` to backend APIs; +the former `XPin` wrapper and `dut.signal.xdata` access are no longer used. + The wheel CI installs xcomm and Picker from built wheels in a clean virtual environment, checks metadata against native versions, exercises signal and clock APIs, and runs the installed Picker command. These checks do **not** yet prove that diff --git a/example/MultiClock/example.py b/example/MultiClock/example.py index 5234f27..d5aae87 100644 --- a/example/MultiClock/example.py +++ b/example/MultiClock/example.py @@ -17,12 +17,12 @@ def test_multi_clock(): clk1, clk2, clk3 = XClock(lambda x: 0), XClock(lambda x: 0), XClock(lambda x: 0) clk4, clk5, clk6 = XClock(lambda x: 0), XClock(lambda x: 0), XClock(lambda x: 0) - clk1.Add(dut.xport.SelectPins(["reg1"])).Add(dut.clk1.xdata) - clk2.Add(dut.xport.SelectPins(["reg2"])).Add(dut.clk2.xdata) - clk3.Add(dut.xport.SelectPins(["reg3"])).Add(dut.clk3.xdata) - clk4.Add(dut.xport.SelectPins(["reg4"])).Add(dut.clk4.xdata) - clk5.Add(dut.xport.SelectPins(["reg5"])).Add(dut.clk5.xdata) - clk6.Add(dut.xport.SelectPins(["reg6"])).Add(dut.clk6.xdata) + clk1.Add(dut.xport.SelectPins(["reg1"])).Add(dut.clk1) + clk2.Add(dut.xport.SelectPins(["reg2"])).Add(dut.clk2) + clk3.Add(dut.xport.SelectPins(["reg3"])).Add(dut.clk3) + clk4.Add(dut.xport.SelectPins(["reg4"])).Add(dut.clk4) + clk5.Add(dut.xport.SelectPins(["reg5"])).Add(dut.clk5) + clk6.Add(dut.xport.SelectPins(["reg6"])).Add(dut.clk6) main_clock.FreqDivWith(1, clk1) main_clock.FreqDivWith(2, clk2) diff --git a/pyproject.toml b/pyproject.toml index 81e6afc..6c0e622 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "scikit_build_core.build" name = "picker" dynamic = ["version"] dependencies = [ - "xspcomm>=0.1.0.dev0,<0.2", + "xspcomm>=0.2.0,<0.3", ] [[tool.dynamic-metadata]] diff --git a/scripts/build/verify-wheel.sh b/scripts/build/verify-wheel.sh index 21d8528..f0e3fc7 100644 --- a/scripts/build/verify-wheel.sh +++ b/scripts/build/verify-wheel.sh @@ -43,7 +43,7 @@ assert signal.U() == 0xA5 clock = xspcomm.XClock(lambda _: 0) clock.Step(2) assert clock.clk == 2 -assert xspcomm.abi_version() == 1 +assert xspcomm.abi_version() == 2 picker = os.path.join(os.path.dirname(sys.executable), "picker") output = subprocess.check_output([picker, "--version"], text=True) diff --git a/src/codegen/python.cpp b/src/codegen/python.cpp index e8b0094..a3986de 100644 --- a/src/codegen/python.cpp +++ b/src/codegen/python.cpp @@ -5,13 +5,13 @@ namespace picker { namespace codegen { namespace py { static const std::string xdata_init_template = - " self.{{pin_uniq_name}} = xsp.XPin(xsp.XData({{logic_pin_length}}, xsp.XData.{{logic_pin_type}}), self.event)\n"; + " self.{{pin_uniq_name}} = xsp.XData({{logic_pin_length}}, xsp.XData.{{logic_pin_type}})\n"; static const std::string xdata_binddpi_template = " self.{{pin_uniq_name}}.BindDPIPtr(self.dut.GetDPIHandle(\"{{pin_func_name}}\", 0), self.dut.GetDPIHandle(\"{{pin_func_name}}\", 1))\n"; static const std::string xdata_bindptr_template = " self.{{pin_uniq_name}}.BindNativeData(self.dut.NativeSignalAddr(\"{{logic_pin}}\"))\n"; static const std::string xport_add_template = - " self.xport.Add(\"{{pin_func_name}}\", self.{{pin_uniq_name}}.xdata)\n"; + " self.xport.Add(\"{{pin_func_name}}\", self.{{pin_uniq_name}})\n"; static const std::string xport_cascaded_template = " self.{{port_name}} = self.xport.NewSubPort(\"{{prefix_key}}_\")\n"; diff --git a/template/python/dut.py b/template/python/dut.py index fec4e10..35779fa 100644 --- a/template/python/dut.py +++ b/template/python/dut.py @@ -163,9 +163,9 @@ def GetInternalSignal(self, name: str, index=-1, is_array=False, use_vpi=False): if signal is None: return None if not isinstance(signal, xsp.XData): - self.internal_signals[name] = [xsp.XPin(s, self.event) for s in signal] + self.internal_signals[name] = list(signal) else: - self.internal_signals[name] = xsp.XPin(signal, self.event) + self.internal_signals[name] = signal return self.internal_signals[name] def GetInternalSignalList(self, prefix="", deep=99, use_vpi=False): @@ -192,7 +192,7 @@ def AtClone(self): ################################ def __getitem__(self, key): - return xsp.XPin(self.port[key], self.event) + return self.xport[key] # Async APIs wrapped from XClock async def AStep(self,i: int): @@ -206,8 +206,8 @@ def RunStep(self,i: int): def __setattr__(self, name, value): assert not isinstance(getattr(self, name, None), - (xsp.XPin, xsp.XData)), \ - f"XPin and XData of DUT are read-only, do you mean to set the value of the signal? please use `{name}.value = ` instead." + xsp.XData), \ + f"XData attributes of DUT are read-only; set the signal with `{name}.value = ...` instead." return super().__setattr__(name, value) diff --git a/template/uvm/dut.py b/template/uvm/dut.py index b821a3d..9cacc87 100644 --- a/template/uvm/dut.py +++ b/template/uvm/dut.py @@ -88,31 +88,6 @@ def fix_path(arg): # ==================== DUT Implementation ==================== -class _PinWrapper: - """Wrapper to provide dut.pin.value access instead of dut.pin.xdata.value""" - def __init__(self, xpin: xsp.XPin): - self._xpin = xpin - - @property - def value(self): - return self._xpin.xdata.value - - @value.setter - def value(self, val): - self._xpin.xdata.value = val - - @property - def xpin(self): - return self._xpin - - @property - def xdata(self): - return self._xpin.xdata - - @property - def event(self): - return self._xpin.event - class DUT{{package_name}}: """ DUT abstraction for {{package_name}} with pin-level interface. @@ -134,29 +109,28 @@ class DUT{{package_name}}: def __init__(self, **kwargs): """Initialize DUT with Agent and xspcomm infrastructure.""" - self._event = xsp.Event() self._xports: Dict[str, xsp.XPort] = {} self._user_callback: Optional[Callable[['DUT{{package_name}}'], None]] = None - self._xpins = {} + self._xdata = {} self._callback_pending = False # Monitor feedback extra cycles (configurable) self._monitor_extra_cycles: int = kwargs.get('monitor_extra_cycles', 1) - # Initialize XPins and XPorts per transaction + # Initialize direct XData leaves and XPorts per transaction {% for trans in transactions -%} # Transaction: {{trans.name}} self._xports['{{trans.name}}'] = xsp.XPort() {% for data in trans.variables -%} - self._xpins['{{data.name}}'] = xsp.XPin(xsp.XData({{data.bit_count}}), self._event) - self._xpins['{{data.name}}'].xdata.AsImmWrite() - self._xports['{{trans.name}}'].Add("{{data.name}}", self._xpins['{{data.name}}'].xdata) + self._xdata['{{data.name}}'] = xsp.XData({{data.bit_count}}) + self._xdata['{{data.name}}'].AsImmWrite() + self._xports['{{trans.name}}'].Add("{{data.name}}", self._xdata['{{data.name}}']) {% endfor -%} {% endfor -%} - # Create pin accessors + # Expose the same XData leaves as DUT attributes. {% for data in variables -%} - self.{{data.name}} = _PinWrapper(self._xpins['{{data.name}}']) + self.{{data.name}} = self._xdata['{{data.name}}'] {% endfor -%} # Monitor callback @@ -167,7 +141,7 @@ def _monitor_callback(trans_type: str, trans_obj): {% for trans in transactions %} if trans_type == '{{trans.name}}': {% for data in trans.variables -%} - self._xpins['{{data.name}}'].xdata.value = trans_obj.{{data.name}}.value + self._xdata['{{data.name}}'].value = trans_obj.{{data.name}}.value {% endfor -%} {% endfor %} self._callback_pending = True @@ -204,7 +178,7 @@ def Step(self, cycles: int = 1): {% for trans in transactions -%} tr = {{trans.name}}() {% for data in trans.variables -%} - tr.{{data.name}}.value = self._xpins['{{data.name}}'].xdata.value + tr.{{data.name}}.value = self._xdata['{{data.name}}'].value {% endfor -%} self.agent.drive(tr) {% endfor -%} @@ -240,7 +214,7 @@ def SetUpdateCallback(self, callback: Optional[Callable[['DUT{{package_name}}'], def SetZero(self): """Set all pins to zero.""" {% for data in variables -%} - self._xpins['{{data.name}}'].xdata.value = 0 + self._xdata['{{data.name}}'].value = 0 {% endfor %} def InitClock(self, clock_pin: Optional[str] = None, domain: str = "default", frequency: Optional[float] = None): @@ -258,8 +232,8 @@ def InitClock(self, clock_pin: Optional[str] = None, domain: str = "default", fr dut.InitClock("clk") # Validate clock pin exists dut.InitClock("clk", domain="sys_clk", frequency=100e6) """ - if clock_pin is not None and clock_pin not in self._xpins: - raise ValueError(f"Clock pin '{clock_pin}' not found. Available pins: {list(self._xpins.keys())}") + if clock_pin is not None and clock_pin not in self._xdata: + raise ValueError(f"Clock pin '{clock_pin}' not found. Available pins: {list(self._xdata.keys())}") # Delegate to Agent self.agent.InitClock(domain=domain, frequency=frequency) @@ -323,7 +297,7 @@ def GetAgent(self): def __repr__(self): fields = [] {% for data in variables -%} - fields.append(f"{{data.name}}={self._xpins['{{data.name}}'].xdata.value}") + fields.append(f"{{data.name}}={self._xdata['{{data.name}}'].value}") {% endfor -%} if self.agent._clock_enabled: fields.append(f"cycle={self.agent.GetCycleCount()}") diff --git a/test/scripts/test_export_adder_python.sh b/test/scripts/test_export_adder_python.sh index e54a32f..23e46eb 100644 --- a/test/scripts/test_export_adder_python.sh +++ b/test/scripts/test_export_adder_python.sh @@ -25,4 +25,36 @@ cp "${ROOT_DIR}/example/Adder/example.py" "${ROOT_DIR}/picker_out/Adder/python/" blue "[export-python] Building generated project" make -C "${ROOT_DIR}/picker_out/Adder" EXAMPLE=ON -j"$(nproc)" +blue "[export-python] Verifying generated signals against xcomm ABI 2" +( + cd "${ROOT_DIR}/picker_out" + python3 - <<'PY' +import Adder as generated + +assert generated.xsp.abi_version() == 2 +dut = generated.DUTAdder() +try: + for name in ("a", "b", "cin", "sum", "cout"): + signal = getattr(dut, name) + assert isinstance(signal, generated.xsp.XData), name + assert dut[name].CSelf() == signal.CSelf(), name + try: + dut.a = 0 + except AssertionError: + pass + else: + raise AssertionError("assigning a signal must use its value property") + + mask = (1 << 128) - 1 + for a, b, cin in ((0, 0, 0), (mask, 1, 0), (1 << 127, 1 << 127, 1)): + dut["a"].value, dut.b.value, dut.cin.value = a, b, cin + dut.Step(1) + expected = a + b + cin + assert dut.sum.value == expected & mask + assert dut.cout.value == expected >> 128 +finally: + dut.Finish() +PY +) + green "[export-python] OK" From b2d55e40212a15184711f34c718ae7becc3aa278 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 8 Oct 2026 10:29:08 +0000 Subject: [PATCH 3/6] Fix ARM wheel verification XData value assertion Co-authored-by: Makiras <22570332+Makiras@users.noreply.github.com> --- scripts/build/verify-wheel.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/build/verify-wheel.sh b/scripts/build/verify-wheel.sh index f0e3fc7..901272e 100644 --- a/scripts/build/verify-wheel.sh +++ b/scripts/build/verify-wheel.sh @@ -39,7 +39,7 @@ if os.environ.get("XCOMM_VERSION_EXPECTED"): assert xspcomm.version() == xcomm_version signal = xspcomm.XData(8, xspcomm.XData.InOut) signal.Set(0xA5) -assert signal.U() == 0xA5 +assert (signal.U() & 0xFF) == 0xA5 clock = xspcomm.XClock(lambda _: 0) clock.Step(2) assert clock.clk == 2 From 60ca8e82a88aaa8a1fc96284c26953b7329551c9 Mon Sep 17 00:00:00 2001 From: Makiras <22570332+Makiras@users.noreply.github.com> Date: Fri, 9 Oct 2026 21:42:52 +0800 Subject: [PATCH 4/6] feat!: integrate xcomm coverage-v2 ABI and source SDK Pin development builds to xcomm 83ab5b2, which shares native Trigger matching with Coverage bins and exports native ABI 3 with coverage descriptor protocol 4. Require the xspcomm 0.3 series, including its explicit 0.3.0.dev0 preview, and build that preview in Development CI until a compatible stable release exists. Tagged builds continue to require published stable xcomm releases. Align installed-wheel checks, generated Python signal regression checks and native release archive verification with ABI 3. Keep the existing ARM value assertion correction inherited from the current Picker PR branch. Apply -fno-strict-aliasing to the embedded xcomm target and its consumers, standalone xcomm wheel builds, and generated simulator libraries while retaining the existing optimization flags. Update the UVM template to consume python.i and pubsub.cpp from the installed TLM source SDK through XSP_COMM_TLM. Document dependency versions, preview builds, native compatibility and SDK paths. Validation: - Fresh combined Release build: all 27 Picker/xcomm C++ tests pass. - All 11 release-policy tests pass; modified shell scripts pass syntax checks. - Built Picker wheel installs with the xspcomm preview in a clean environment; pip check, metadata/native versions, ABI/protocol and CLI checks pass. - Installed wheels generate and compile a Python Adder DUT; direct XData access, 128-bit arithmetic and carry regression checks pass against native ABI 3. - Native installation exports libxspcomm.so.3 with SONAME 3. Native and wheel TLM SDK paths resolve, and SWIG wrapper generation passes. VCS commands were only dry-run because the simulator toolchain is unavailable. - Workflow lint introduces no diagnostics relative to the existing baseline. BREAKING CHANGE: native clients and generated DUT extensions must rebuild against xcomm ABI 3. Picker now selects xspcomm 0.3; formal releases require a published compatible xcomm release rather than a development preview. --- .build-config.yml | 2 +- .github/workflows/ci.yml | 3 ++ .github/workflows/release-build.yml | 2 +- CMakeLists.txt | 5 +++ Makefile | 2 +- doc/maintainer-guidance.md | 54 ++++++++++++++++++------ pyproject.toml | 2 +- scripts/build/verify-wheel.sh | 3 +- template/lib/CMakeLists.txt | 2 +- template/uvm/Makefile | 6 ++- test/scripts/test_export_adder_python.sh | 4 +- 11 files changed, 63 insertions(+), 22 deletions(-) diff --git a/.build-config.yml b/.build-config.yml index 590de14..ac7bae4 100644 --- a/.build-config.yml +++ b/.build-config.yml @@ -4,4 +4,4 @@ tools: node: 20.12.2 python: 3.11 dependencies: - xcomm: 3e4bccb73f08310c08382d3987ba2d98b83df4d6 + xcomm: 83ab5b2c466ad5ebfb0275b84830a41f9c06bb5e diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2948a8d..64e0467 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -155,6 +155,9 @@ jobs: - name: Build Python wheels if: matrix.full_test + env: + # ABI 3 is not released yet; keep its preview out of the ABI 2 series. + SETUPTOOLS_SCM_PRETEND_VERSION_FOR_XSPCOMM: 0.3.0.dev0 run: | python3 -m venv /tmp/picker-wheel-builder /tmp/picker-wheel-builder/bin/python -m pip install 'build>=1,<2' diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml index eef7fea..7ce2885 100644 --- a/.github/workflows/release-build.yml +++ b/.github/workflows/release-build.yml @@ -201,7 +201,7 @@ jobs: test "$(uname -m)" = "$ARCH" test -x AppDir/usr/bin/picker test -d AppDir/usr/share/picker/template - test -e AppDir/usr/lib/libxspcomm.so.2 + test -e AppDir/usr/lib/libxspcomm.so.3 output="$(AppDir/usr/bin/picker --version)" [[ "$output" == *"$VERSION"* ]] mkdir -p release-assets diff --git a/CMakeLists.txt b/CMakeLists.txt index 3461359..8080e09 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -42,6 +42,11 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/bin) #dependencies if ("$ENV{NO_BUILD_XSPCOMM}" STREQUAL "") add_subdirectory(dependence/xcomm) +# Cover pinned xcomm revisions as well as their SWIG consumers. Standalone +# xcomm wheels are built separately by the wheel target in the Makefile. +if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") + target_compile_options(xspcomm PUBLIC -fno-strict-aliasing) +endif() endif() # Verilog/SystemVerilog frontend diff --git a/Makefile b/Makefile index e729993..2a6fb65 100644 --- a/Makefile +++ b/Makefile @@ -78,7 +78,7 @@ clean: rm -rf temp build dist output app_image_build AppDir picker_out* picker_e203_ifu_ift2icb wheel: - cd dependence/xcomm && $(PYTHON) -m build --wheel + cd dependence/xcomm && CFLAGS="$(CFLAGS) -fno-strict-aliasing" $(PYTHON) -m build --wheel NO_BUILD_XSPCOMM=1 $(PYTHON) -m build --wheel mkdir -p dist && cp dependence/xcomm/dist/*.whl ./dist diff --git a/doc/maintainer-guidance.md b/doc/maintainer-guidance.md index b276d50..1cba353 100644 --- a/doc/maintainer-guidance.md +++ b/doc/maintainer-guidance.md @@ -89,12 +89,17 @@ in `.build-config.yml` (an explicit `XCOMM_SOURCE_REF` commit can override it) and never become release assets. Tagged builds use the selected published xcomm tag instead. The build refuses to switch an xcomm checkout with local changes. -The `xspcomm>=0.2.0,<0.3` requirement in `pyproject.toml` selects the supported -xcomm 0.2 series with native ABI 2. Development CI pins the v0.2.0 commit in -`.build-config.yml`; wheel verification requires `xspcomm.abi_version() == 2`, -and native release archives must contain `libxspcomm.so.2`. Change the range only -after testing the corresponding xcomm ABI and baseline APIs. Release metadata, -the installed Picker command and CMake receive their version from the same tag. +The `xspcomm>=0.3.0.dev0,<0.4` requirement in `pyproject.toml` selects the xcomm +0.3 series with native ABI 3 and coverage descriptor protocol 4. Development CI +pins the tested coverage-v2 commit in `.build-config.yml` and builds an explicit +`0.3.0.dev0` xcomm preview until the ABI 3 release is published. This avoids +labeling an ABI 3 wheel as a development patch of the ABI 2 release. Wheel +verification checks both versions, and native release archives must contain +`libxspcomm.so.3`. Tagged CI requires a compatible published xcomm 0.3 release; +it cannot use the development preview. +Change the range only after testing the corresponding ABI and baseline APIs. +Release metadata, the installed Picker command and CMake receive their version +from the same tag. ## Building and installing locally @@ -106,7 +111,8 @@ switch source repositories and does not remove existing packages. ```sh python3 -m venv /tmp/picker-package-build /tmp/picker-package-build/bin/python -m pip install build -make wheel PYTHON=/tmp/picker-package-build/bin/python +SETUPTOOLS_SCM_PRETEND_VERSION_FOR_XSPCOMM=0.3.0.dev0 \ + make wheel PYTHON=/tmp/picker-package-build/bin/python python3 -m venv /tmp/picker-package-test /tmp/picker-package-test/bin/python -m pip install --no-index --find-links=dist picker @@ -114,11 +120,29 @@ python3 -m venv /tmp/picker-package-test /tmp/picker-package-test/bin/picker --version ``` +Before xcomm 0.3 is tagged, set +`SETUPTOOLS_SCM_PRETEND_VERSION_FOR_XSPCOMM=0.3.0.dev0` when running `make wheel` +from this development branch. This affects only the xcomm preview; Picker +continues to derive its version from its own Git history. Formal releases use +the published xcomm wheel and its release version. + Use a fresh checkout or empty `dist` directory for release builds; pip selects the highest matching version when several old wheels are present. `make wheel_install` intentionally only prints an installation command and never uninstalls packages from the current interpreter. +Native xcomm builds and generated simulator libraries use +`-fno-strict-aliasing` to keep optimization settings consistent across signal +bindings and their consumers. Picker applies it to the pinned xcomm target, +and `make wheel` passes it when building xcomm separately. Generated simulator +libraries use the same option, including the C++ flags passed to +Verilator/VCS/UVS. Native memory bindings also use typed, fixed-size `memcpy` +accesses to respect object boundaries and alignment. +Keep `-O3` and the complete signal-value checks enabled. This option changes +type-based alias analysis; it does not fix out-of-bounds or unaligned accesses, +invalid shifts, object lifetime errors, or thread synchronization. Use +ASan/UBSan and explicit boundary tests when investigating those issues. + Picker contains a compiled executable and xspcomm contains a SWIG/CPython extension plus a C++ shared library. A matching wheel avoids compilation on the user's machine. Picker's current sdist omits its vendored slang/fmt/yaml-cpp @@ -140,11 +164,17 @@ generated DUT extensions. A package patch release alone must not change the SONAME. A Python wheel is also tied to its CPython ABI and platform tag; the SWIG module is not declared `abi3`. -The xcomm 0.2 trigger engine changes the native layouts of `XClock`, `XData`, -and `ExprNode`. Rebuild existing generated DUT extensions against the ABI 2 -headers and runtime; changing a library symlink cannot make an ABI 1 binary -compatible. Keep each DUT extension and its xcomm runtime on the same native -ABI. The coverage snapshot protocol version is separate from this ABI number. +The xcomm 0.2 trigger engine changed the native layouts of `XClock`, `XData`, +and `ExprNode` and required ABI 2. The coverage-v2 descriptors expand +`XCoverageBin`, requiring ABI 3 for the xcomm 0.3 series. Rebuild native clients +and generated DUT extensions against the matching headers and runtime; +changing a library symlink cannot make incompatible binaries compatible. Keep +each DUT extension and its xcomm runtime on the same native ABI. The coverage +descriptor protocol version is separate from this ABI number. + +The TLM template uses the source SDK installed under `share/xspcomm/tlm` for +its SWIG interface and bridge source. Set `XSP_COMM_TLM` to override that SDK +location; the public C++ headers remain under `include/xspcomm/tlm`. Generated Python and UVM DUT signals are direct `XData` objects. Use `dut.signal.value` for signal values and pass `dut.signal` to backend APIs; diff --git a/pyproject.toml b/pyproject.toml index 6c0e622..bb9e723 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "scikit_build_core.build" name = "picker" dynamic = ["version"] dependencies = [ - "xspcomm>=0.2.0,<0.3", + "xspcomm>=0.3.0.dev0,<0.4", ] [[tool.dynamic-metadata]] diff --git a/scripts/build/verify-wheel.sh b/scripts/build/verify-wheel.sh index 901272e..d1354a9 100644 --- a/scripts/build/verify-wheel.sh +++ b/scripts/build/verify-wheel.sh @@ -43,7 +43,8 @@ assert (signal.U() & 0xFF) == 0xA5 clock = xspcomm.XClock(lambda _: 0) clock.Step(2) assert clock.clk == 2 -assert xspcomm.abi_version() == 2 +assert xspcomm.abi_version() == 3 +assert xspcomm.XTriggerEngine.CoverageVersion() == 4 picker = os.path.join(os.path.dirname(sys.executable), "picker") output = subprocess.check_output([picker, "--version"], text=True) diff --git a/template/lib/CMakeLists.txt b/template/lib/CMakeLists.txt index ced3f19..5f0d84a 100644 --- a/template/lib/CMakeLists.txt +++ b/template/lib/CMakeLists.txt @@ -13,7 +13,7 @@ list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) # enable coroutines set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED) -set(CFLAGS "$ENV{CFLAGS} -ftls-model=global-dynamic -O3 -march=native") +set(CFLAGS "$ENV{CFLAGS} -ftls-model=global-dynamic -O3 -fno-strict-aliasing -march=native") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fcoroutines ${CFLAGS}") set(SIMULATOR_FLAGS "$ENV{SIMULATOR_FLAGS}") if(NOT "${SIMULATOR_FLAGS}" STREQUAL "") diff --git a/template/uvm/Makefile b/template/uvm/Makefile index cde9f57..0030413 100755 --- a/template/uvm/Makefile +++ b/template/uvm/Makefile @@ -1,3 +1,5 @@ +XSP_COMM_TLM ?= $(XSP_COMM_INCLUDE)/../share/xspcomm/tlm + PYTHON_INCLUDE:=$(firstword $(shell python3-config --includes)) XSP_COMM_INCLUDE:=$(shell picker --show_xcom_lib_location_cpp | awk '/Include:/ {print $$2}') XSP_PYTHON:=$(shell picker --show_xcom_lib_location_python | sed -n '$$p') @@ -79,9 +81,9 @@ comp: @mkdir -p $(BUILD_DIR) swig -D'MODULE_NAME="tlm_pbsb"' -python -c++ -DUSE_VCS -I${XSP_COMM_INCLUDE} \ -outdir $(PKG_DIR) -o $(BUILD_DIR)/tlmps.cpp \ - ${XSP_COMM_INCLUDE}/xspcomm/python_tlm_pbsb.i + ${XSP_COMM_TLM}/python.i $(SYSCAN) -Mdir=$(BUILD_DIR) \ - ${XSP_COMM_INCLUDE}/xspcomm/tlm_pbsb.cpp $(BUILD_DIR)/tlmps.cpp + ${XSP_COMM_TLM}/pubsub.cpp $(BUILD_DIR)/tlmps.cpp @if [ -f example-uvm.sv ]; then \ UVM_FILE=example-uvm.sv; \ elif [ -f example.sv ]; then \ diff --git a/test/scripts/test_export_adder_python.sh b/test/scripts/test_export_adder_python.sh index 23e46eb..a2fa4a5 100644 --- a/test/scripts/test_export_adder_python.sh +++ b/test/scripts/test_export_adder_python.sh @@ -25,13 +25,13 @@ cp "${ROOT_DIR}/example/Adder/example.py" "${ROOT_DIR}/picker_out/Adder/python/" blue "[export-python] Building generated project" make -C "${ROOT_DIR}/picker_out/Adder" EXAMPLE=ON -j"$(nproc)" -blue "[export-python] Verifying generated signals against xcomm ABI 2" +blue "[export-python] Verifying generated signals against xcomm ABI 3" ( cd "${ROOT_DIR}/picker_out" python3 - <<'PY' import Adder as generated -assert generated.xsp.abi_version() == 2 +assert generated.xsp.abi_version() == 3 dut = generated.DUTAdder() try: for name in ("a", "b", "cin", "sum", "cout"): From 4dd442c4a80c92549381e417c608836e3b78a8e9 Mon Sep 17 00:00:00 2001 From: Makiras <22570332+Makiras@users.noreply.github.com> Date: Mon, 12 Oct 2026 13:27:38 +0800 Subject: [PATCH 5/6] build: align Picker with xcomm coverage points and clock ownership Pin development builds to xcomm e64d862, which exposes XCoveragePoint, bin.point_id and explicit engine acquisition of XClock. Keep native ABI 3 relative to master's ABI 2 and require coverage descriptor protocol 5. Raise the minimum preview and the Development CI preview build version to 0.3.0.dev2 so older coverage descriptors and clock APIs cannot satisfy the updated dependency. Keep stable release selection within the xcomm 0.3 series. Extend clean-wheel verification to check the renamed descriptors and native clock ownership. Extend the generated Python Adder regression to reject DUT Step, raw/half stepping and competing engines while owned, exercise native cycle triggers and coverage counters, and verify manual stepping after release. Retain the existing direct XData and 128-bit arithmetic checks. Document the descriptor migration, ownership scope and phase-preserving release. Generated DUT Step already delegates to XClock, so the native guard protects it without another stepping implementation. --- .build-config.yml | 2 +- .github/workflows/ci.yml | 2 +- doc/maintainer-guidance.md | 23 ++++++++---- pyproject.toml | 2 +- scripts/build/verify-wheel.sh | 20 ++++++++++- test/scripts/test_export_adder_python.sh | 45 ++++++++++++++++++++++++ 6 files changed, 84 insertions(+), 10 deletions(-) diff --git a/.build-config.yml b/.build-config.yml index ac7bae4..37d989b 100644 --- a/.build-config.yml +++ b/.build-config.yml @@ -4,4 +4,4 @@ tools: node: 20.12.2 python: 3.11 dependencies: - xcomm: 83ab5b2c466ad5ebfb0275b84830a41f9c06bb5e + xcomm: e64d8622f7c50fc32a9f9447415672a470103496 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 64e0467..6d8217c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -157,7 +157,7 @@ jobs: if: matrix.full_test env: # ABI 3 is not released yet; keep its preview out of the ABI 2 series. - SETUPTOOLS_SCM_PRETEND_VERSION_FOR_XSPCOMM: 0.3.0.dev0 + SETUPTOOLS_SCM_PRETEND_VERSION_FOR_XSPCOMM: 0.3.0.dev2 run: | python3 -m venv /tmp/picker-wheel-builder /tmp/picker-wheel-builder/bin/python -m pip install 'build>=1,<2' diff --git a/doc/maintainer-guidance.md b/doc/maintainer-guidance.md index 1cba353..2723688 100644 --- a/doc/maintainer-guidance.md +++ b/doc/maintainer-guidance.md @@ -89,10 +89,10 @@ in `.build-config.yml` (an explicit `XCOMM_SOURCE_REF` commit can override it) and never become release assets. Tagged builds use the selected published xcomm tag instead. The build refuses to switch an xcomm checkout with local changes. -The `xspcomm>=0.3.0.dev0,<0.4` requirement in `pyproject.toml` selects the xcomm -0.3 series with native ABI 3 and coverage descriptor protocol 4. Development CI +The `xspcomm>=0.3.0.dev2,<0.4` requirement in `pyproject.toml` selects the xcomm +0.3 series with native ABI 3 and coverage descriptor protocol 5. Development CI pins the tested coverage-v2 commit in `.build-config.yml` and builds an explicit -`0.3.0.dev0` xcomm preview until the ABI 3 release is published. This avoids +`0.3.0.dev2` xcomm preview until the ABI 3 release is published. This avoids labeling an ABI 3 wheel as a development patch of the ABI 2 release. Wheel verification checks both versions, and native release archives must contain `libxspcomm.so.3`. Tagged CI requires a compatible published xcomm 0.3 release; @@ -111,7 +111,7 @@ switch source repositories and does not remove existing packages. ```sh python3 -m venv /tmp/picker-package-build /tmp/picker-package-build/bin/python -m pip install build -SETUPTOOLS_SCM_PRETEND_VERSION_FOR_XSPCOMM=0.3.0.dev0 \ +SETUPTOOLS_SCM_PRETEND_VERSION_FOR_XSPCOMM=0.3.0.dev2 \ make wheel PYTHON=/tmp/picker-package-build/bin/python python3 -m venv /tmp/picker-package-test @@ -121,7 +121,7 @@ python3 -m venv /tmp/picker-package-test ``` Before xcomm 0.3 is tagged, set -`SETUPTOOLS_SCM_PRETEND_VERSION_FOR_XSPCOMM=0.3.0.dev0` when running `make wheel` +`SETUPTOOLS_SCM_PRETEND_VERSION_FOR_XSPCOMM=0.3.0.dev2` when running `make wheel` from this development branch. This affects only the xcomm preview; Picker continues to derive its version from its own Git history. Formal releases use the published xcomm wheel and its release version. @@ -170,7 +170,18 @@ and `ExprNode` and required ABI 2. The coverage-v2 descriptors expand and generated DUT extensions against the matching headers and runtime; changing a library symlink cannot make incompatible binaries compatible. Keep each DUT extension and its xcomm runtime on the same native ABI. The coverage -descriptor protocol version is separate from this ABI number. +descriptor protocol version is separate from this ABI number. Unpublished +changes on this branch share ABI 3 relative to master's ABI 2; intermediate +branch commits do not each require another native ABI increment. + +The current coverage protocol is 5, with `XCoveragePoint` and `bin.point_id`. +`XTriggerEngine` acquires the clock through `AcquireClock()` before registering +or running work and releases it after disarming registrations. xreactor's +Execution manages this scope. Generated `dut.Step()` already delegates to +XClock and is rejected while an engine owns that clock; manual initialization +before acquisition and stepping after release remain available. Releasing an +engine preserves phase, so finish a remaining half-cycle with `StepHalf()` +before resuming full-cycle `Step()`. The TLM template uses the source SDK installed under `share/xspcomm/tlm` for its SWIG interface and bridge source. Set `XSP_COMM_TLM` to override that SDK diff --git a/pyproject.toml b/pyproject.toml index bb9e723..10b6a5a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "scikit_build_core.build" name = "picker" dynamic = ["version"] dependencies = [ - "xspcomm>=0.3.0.dev0,<0.4", + "xspcomm>=0.3.0.dev2,<0.4", ] [[tool.dynamic-metadata]] diff --git a/scripts/build/verify-wheel.sh b/scripts/build/verify-wheel.sh index d1354a9..1fc709b 100644 --- a/scripts/build/verify-wheel.sh +++ b/scripts/build/verify-wheel.sh @@ -44,7 +44,25 @@ clock = xspcomm.XClock(lambda _: 0) clock.Step(2) assert clock.clk == 2 assert xspcomm.abi_version() == 3 -assert xspcomm.XTriggerEngine.CoverageVersion() == 4 +assert xspcomm.XTriggerEngine.CoverageVersion() == 5 +assert xspcomm.XCoveragePoint().kind == xspcomm.XCoveragePointKind_Value +assert xspcomm.XCoverageBin().point_id == 0 +engine = xspcomm.XTriggerEngine(clock) +assert not clock.IsControlled() +engine.AcquireClock() +try: + try: + clock.Step(1) + except RuntimeError: + pass + else: + raise AssertionError("manual stepping bypassed native engine ownership") + assert clock.GetHalfTick() == 4 + assert engine.RunUntil(2).advanced_ticks == 2 +finally: + engine.ReleaseClock() +assert not clock.IsControlled() +clock.Step(1) picker = os.path.join(os.path.dirname(sys.executable), "picker") output = subprocess.check_output([picker, "--version"], text=True) diff --git a/test/scripts/test_export_adder_python.sh b/test/scripts/test_export_adder_python.sh index a2fa4a5..9e144bd 100644 --- a/test/scripts/test_export_adder_python.sh +++ b/test/scripts/test_export_adder_python.sh @@ -32,6 +32,7 @@ blue "[export-python] Verifying generated signals against xcomm ABI 3" import Adder as generated assert generated.xsp.abi_version() == 3 +assert generated.xsp.XTriggerEngine.CoverageVersion() == 5 dut = generated.DUTAdder() try: for name in ("a", "b", "cin", "sum", "cout"): @@ -52,6 +53,50 @@ try: expected = a + b + cin assert dut.sum.value == expected & mask assert dut.cout.value == expected >> 128 + clock = dut.GetXClock() + engine = generated.xsp.XTriggerEngine(clock) + competing = generated.xsp.XTriggerEngine(clock) + assert not clock.IsControlled() + tick = clock.GetHalfTick() + engine.AcquireClock() + try: + for operation in (lambda: dut.Step(1), lambda: clock.RawStep(1), + clock.StepHalf, competing.AcquireClock): + try: + operation() + except RuntimeError: + pass + else: + raise AssertionError("clock ownership was bypassed") + assert clock.GetHalfTick() == tick + + cycles = engine.ArmClockCycles(2) + result = engine.RunUntil(8) + assert result.advanced_ticks == 4 + assert len(result.hits) == 1 and result.hits[0].tick == tick + 4 + assert engine.Disarm(cycles) + + point = generated.xsp.XCoveragePoint() + point.signal = dut.cin + bin_spec = generated.xsp.XCoverageBin() + bin_spec.point_id = 0 + bin_spec.program.root = engine.ExprNewConst(1) + points = generated.xsp.XCoveragePointVector([point]) + bins = generated.xsp.XCoverageBinVector([bin_spec]) + sample = engine.ArmSample(generated.xsp.XPhase_RisingStable) + engine.AttachCoverage(sample, points, bins) + result = engine.RunUntil(4) + snapshot = engine.CoverageSnapshot(sample) + assert result.advanced_ticks == 4 and not result.hits + assert snapshot.counters[0] == snapshot.counters[-1] == 2 + assert engine.Disarm(sample) + finally: + engine.Clear() + engine.ReleaseClock() + assert not clock.IsControlled() + dut.Step(1) + assert dut.sum.value == expected & mask + assert dut.cout.value == expected >> 128 finally: dut.Finish() PY From fd6c9baa5643c728ab95234aabb22dc7fa489e60 Mon Sep 17 00:00:00 2001 From: Makiras <22570332+Makiras@users.noreply.github.com> Date: Mon, 12 Oct 2026 15:37:44 +0800 Subject: [PATCH 6/6] build: sync xcomm 1.x dependency and simplify maintainer guide Pin development builds to xcomm c39db66, including typed bit-vector expressions and live-program collection. Align the Python requirement with the planned 1.x release and build CI previews as 1.0.0.dev0 while retaining native ABI 3 and coverage descriptor protocol 5. Extend installed-wheel verification to exercise 128-bit arithmetic and expression collection. Condense the maintainer guide to version and dependency alignment, local validation, release sequencing and recovery. Validation: 28 native tests, 11 release-policy tests, generated Python Adder integration, and clean installation and verification of both wheels. --- .build-config.yml | 2 +- .github/workflows/ci.yml | 4 +- doc/maintainer-guidance.md | 260 ++++++++++------------------------ pyproject.toml | 2 +- scripts/build/verify-wheel.sh | 10 ++ 5 files changed, 85 insertions(+), 193 deletions(-) diff --git a/.build-config.yml b/.build-config.yml index 37d989b..54eead3 100644 --- a/.build-config.yml +++ b/.build-config.yml @@ -4,4 +4,4 @@ tools: node: 20.12.2 python: 3.11 dependencies: - xcomm: e64d8622f7c50fc32a9f9447415672a470103496 + xcomm: c39db6652cd08d46df05d08f0bb5a033b83e4d2f diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6d8217c..750cbe7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -156,8 +156,8 @@ jobs: - name: Build Python wheels if: matrix.full_test env: - # ABI 3 is not released yet; keep its preview out of the ABI 2 series. - SETUPTOOLS_SCM_PRETEND_VERSION_FOR_XSPCOMM: 0.3.0.dev2 + # Match the planned xcomm 1.x release while it is still in development. + SETUPTOOLS_SCM_PRETEND_VERSION_FOR_XSPCOMM: 1.0.0.dev0 run: | python3 -m venv /tmp/picker-wheel-builder /tmp/picker-wheel-builder/bin/python -m pip install 'build>=1,<2' diff --git a/doc/maintainer-guidance.md b/doc/maintainer-guidance.md index 2723688..0fc3af7 100644 --- a/doc/maintainer-guidance.md +++ b/doc/maintainer-guidance.md @@ -1,199 +1,81 @@ -# Maintaining Picker releases +# Maintaining Picker -Picker and xspcomm have independent version tags. Release xspcomm first; -Picker's wheel and native packages use a compatible xspcomm release. This page -records the maintainer procedure. +## Versions and dependencies -## Versions and releases +Picker and xcomm have independent `vX.Y.Z` package tags. Picker derives its +package, CLI and CMake release versions from its own tag using setuptools-scm. +Release a compatible xcomm version before releasing Picker. -Version tags are independent in the two Git repositories. Use `vX.Y.Z` tags, -for example `v2.0.0` for Picker and `v0.1.0` for xcomm. The wheel version is -derived from the tag by setuptools-scm. Commits after a tag yield development -versions such as `2.0.1.dev3`; ordinary CI uploads these wheels as artifacts -and does not create a stable release. Older date/SHA AppImage tags do not -participate in Python package version calculation; CI no longer creates them. +| Setting | Source | Current requirement | +| --- | --- | --- | +| Development xcomm source | `.build-config.yml` | Exact tested commit; `XCOMM_SOURCE_REF` can override it | +| Python dependency | `pyproject.toml` | `xspcomm>=1.0.0.dev0,<2` | +| Development xcomm wheel | Development CI | `1.0.0.dev0` preview until xcomm 1.0 is released | +| Native ABI | `xspcomm.abi_version()` | 3; packages contain `libxspcomm.so.3` | +| Coverage descriptors | `XTriggerEngine.CoverageVersion()` | 5 | -Every PR targeting `master` must have exactly one of `release:patch`, -`release:minor`, or `release:major`. Other labels are allowed. The **Version -policy / release-label** check verifies this. In GitHub Settings → Rules → -Rulesets (or the `master` branch protection rule), add this check to the -required status checks; the workflow alone does not prevent merging. Create -the three labels in each repository. Also require the relevant CI build checks -and require an up-to-date PR before merging; otherwise tagging after merge does -not imply the merged source has passed CI. Do not use merge queue until the -required checks also support `merge_group`. +Package version, native ABI and descriptor protocol are separate identifiers. +When updating xcomm, align the source pin, Python range and preview version, +then rebuild native clients and generated DUT extensions against matching +headers and runtime. ABI checks cannot be replaced by a package version range. -After a labeled PR merges, the `master` push starts **Version policy**. It -finds the merged PR, computes the next version, pushes its tag and dispatches -**Tagged CI** with the tag and commit SHA. A direct push without an associated -merged PR does not create a tag. Every merged PR, including a -documentation-only PR, creates a version tag. The tag push itself does not -start CI when it was made using `GITHUB_TOKEN`; the explicit dispatch does. -Both version calculation and Tagged CI apply the same master-tag rule: the tag -must point to a commit on master's first-parent line that GitHub records as the -result of a merged PR targeting master. A higher-numbered tag on a feature -branch is ignored when calculating the next version, and manually requesting a -build for a tag outside this rule fails before any assets are built. Tag names -remain repository-wide, so a conflicting name elsewhere still blocks creation -of the next tag rather than being overwritten. Git itself does not record the -branch on which a tag was created. To prevent users from creating, moving or -deleting `v*` tags outside this workflow, protect that tag pattern with a -GitHub tag ruleset; the workflow checks the target commit, not the tagger's -historical branch. -The version-tag job serializes its runs, but GitHub does not guarantee that -rapid consecutive merges enter that queue in commit order. Until version -allocation is made order-aware, avoid merging another PR while a previous -Version policy run is still tagging; a later commit tagged first can cause the -older run to fail its ancestry check. +## Local build and validation -**Tagged CI** checks out that exact tag, tests the native builds and builds a -CPython 3.12 Linux x86-64 wheel, Linux x86-64 and aarch64 AppImages, and Linux -x86-64 and aarch64 native archives. It uploads one checked release bundle. -After the bundle upload succeeds, Tagged CI explicitly dispatches **Release -Picker** with its run ID. A `workflow_run` listener is not used because Tagged -CI is itself dispatched using `GITHUB_TOKEN`. Release Picker waits up to 60 -minutes for the source run to complete successfully; a failed, cancelled or -unfinished run cannot publish. Tagged CI does not wait for Release Picker, -which would prevent the source run from completing. Release Picker downloads -that run's bundle, checks its hashes and tag SHA, and publishes those exact -files without rebuilding. All five packages, `SHA256SUMS` and -`RELEASE-MANIFEST.json` go into one GitHub Release. The manifest records the -Picker tag, commit, CI run ID and -xcomm version used. The archives contain the installed -`usr/bin/picker`, `usr/share/picker` templates and native libraries; they are -not bare executables. AppImage is the self-contained desktop distribution; -the archive is an installed tree for a compatible Linux system. The wheel is -for pip and depends on a separately published xspcomm wheel. Tagged CI selects -one compatible xspcomm version for all three package types. - -**Release Picker** never changes the tag. If the tagged build fails, rerun -**Tagged CI** manually with the existing tag and a compatible xcomm version. -If publication fails, rerun **Release Picker** with the successful Tagged CI -run ID; it publishes the same artifact. **Version policy** has a manual bump -input for recovering from a missed merge event. GitHub release wheels are not -automatically uploaded to PyPI; publishing there needs separate credentials -and platform-wheel policy. Development CI still builds AppImages and wheels, -but it no longer creates a date/SHA AppImage Release. The old `master`-push -Docker image publish is not part of this pipeline; Docker PR changes still -receive a build check. - -Development CI and Tagged CI use the same scripts under `scripts/build/` for -native toolchain setup, CMake build and install, AppImage packaging, and wheel -installation checks. A master PR runs full Development CI and the version-label -check. A non-master branch push also runs Development CI unless that branch -already has an open master PR; in that case the PR run owns the build. A merge -to master runs Version policy rather than a second development build. Tagged -CI uses the final tag and a published xcomm version; its checked artifacts are -the only inputs to Release Picker. PR and branch builds pin xcomm to the commit -in `.build-config.yml` (an explicit `XCOMM_SOURCE_REF` commit can override it) -and never become release assets. Tagged builds use the selected published xcomm -tag instead. The build refuses to switch an xcomm checkout with local changes. - -The `xspcomm>=0.3.0.dev2,<0.4` requirement in `pyproject.toml` selects the xcomm -0.3 series with native ABI 3 and coverage descriptor protocol 5. Development CI -pins the tested coverage-v2 commit in `.build-config.yml` and builds an explicit -`0.3.0.dev2` xcomm preview until the ABI 3 release is published. This avoids -labeling an ABI 3 wheel as a development patch of the ABI 2 release. Wheel -verification checks both versions, and native release archives must contain -`libxspcomm.so.3`. Tagged CI requires a compatible published xcomm 0.3 release; -it cannot use the development preview. -Change the range only after testing the corresponding ABI and baseline APIs. -Release metadata, the installed Picker command and CMake receive their version -from the same tag. - -## Building and installing locally - -Install a compiler, CMake, SWIG >= 4.2, Python development headers and the -Picker source dependencies. Run `make init` explicitly before building Picker -if those dependencies are missing. The wheel target itself does not fetch or -switch source repositories and does not remove existing packages. +Use a compiler, CMake, SWIG >= 4.2, Python development headers and a JDK +for Java tests. Run `make init` when source dependencies are missing. Build +from a clean checkout with a fresh wheel output directory; `make wheel` does +not align an existing xcomm checkout to the dependency pin or install packages +into the interpreter. ```sh -python3 -m venv /tmp/picker-package-build -/tmp/picker-package-build/bin/python -m pip install build -SETUPTOOLS_SCM_PRETEND_VERSION_FOR_XSPCOMM=0.3.0.dev2 \ - make wheel PYTHON=/tmp/picker-package-build/bin/python - -python3 -m venv /tmp/picker-package-test -/tmp/picker-package-test/bin/python -m pip install --no-index --find-links=dist picker -/tmp/picker-package-test/bin/python -m pip check -/tmp/picker-package-test/bin/picker --version +python3 -m venv /tmp/picker-build +/tmp/picker-build/bin/python -m pip install build +make unit_tests BUILD_XSPCOMM_SWIG=python,java +SETUPTOOLS_SCM_PRETEND_VERSION_FOR_XSPCOMM=1.0.0.dev0 \ + make wheel PYTHON=/tmp/picker-build/bin/python +bash scripts/build/verify-wheel.sh +make -C test all ``` -Before xcomm 0.3 is tagged, set -`SETUPTOOLS_SCM_PRETEND_VERSION_FOR_XSPCOMM=0.3.0.dev2` when running `make wheel` -from this development branch. This affects only the xcomm preview; Picker -continues to derive its version from its own Git history. Formal releases use -the published xcomm wheel and its release version. - -Use a fresh checkout or empty `dist` directory for release builds; pip selects -the highest matching version when several old wheels are present. `make -wheel_install` intentionally only prints an installation command and never -uninstalls packages from the current interpreter. - -Native xcomm builds and generated simulator libraries use -`-fno-strict-aliasing` to keep optimization settings consistent across signal -bindings and their consumers. Picker applies it to the pinned xcomm target, -and `make wheel` passes it when building xcomm separately. Generated simulator -libraries use the same option, including the C++ flags passed to -Verilator/VCS/UVS. Native memory bindings also use typed, fixed-size `memcpy` -accesses to respect object boundaries and alignment. -Keep `-O3` and the complete signal-value checks enabled. This option changes -type-based alias analysis; it does not fix out-of-bounds or unaligned accesses, -invalid shifts, object lifetime errors, or thread synchronization. Use -ASan/UBSan and explicit boundary tests when investigating those issues. - -Picker contains a compiled executable and xspcomm contains a SWIG/CPython -extension plus a C++ shared library. A matching wheel avoids compilation on -the user's machine. Picker's current sdist omits its vendored slang/fmt/yaml-cpp -sources, so **do not publish it as a pip fallback**. If no Picker wheel matches, -build from a full source checkout after `make init`; this does require local -compilation and the build toolchain. Use `--only-binary=:all:` when an -installation must fail instead of attempting a source build. -The wheel release currently targets CPython 3.12 on Linux x86-64. Native -archives and AppImages target Linux x86-64 and aarch64; expand these matrices -before claiming other platforms are supported. - -## Native compatibility - -xcomm's package version and C++ ABI version are different. The latter is -`XSPCOMM_ABI_VERSION` in xcomm's `CMakeLists.txt`; it controls the -`libxspcomm.so.N` SONAME and is exposed as `xspcomm.abi_version()`. Bump it -when a native ABI change is incompatible, then rebuild xcomm, Picker and any -generated DUT extensions. A package patch release alone must not change the -SONAME. A Python wheel is also tied to its CPython ABI and platform tag; -the SWIG module is not declared `abi3`. - -The xcomm 0.2 trigger engine changed the native layouts of `XClock`, `XData`, -and `ExprNode` and required ABI 2. The coverage-v2 descriptors expand -`XCoverageBin`, requiring ABI 3 for the xcomm 0.3 series. Rebuild native clients -and generated DUT extensions against the matching headers and runtime; -changing a library symlink cannot make incompatible binaries compatible. Keep -each DUT extension and its xcomm runtime on the same native ABI. The coverage -descriptor protocol version is separate from this ABI number. Unpublished -changes on this branch share ABI 3 relative to master's ABI 2; intermediate -branch commits do not each require another native ABI increment. - -The current coverage protocol is 5, with `XCoveragePoint` and `bin.point_id`. -`XTriggerEngine` acquires the clock through `AcquireClock()` before registering -or running work and releases it after disarming registrations. xreactor's -Execution manages this scope. Generated `dut.Step()` already delegates to -XClock and is rejected while an engine owns that clock; manual initialization -before acquisition and stepping after release remain available. Releasing an -engine preserves phase, so finish a remaining half-cycle with `StepHalf()` -before resuming full-cycle `Step()`. - -The TLM template uses the source SDK installed under `share/xspcomm/tlm` for -its SWIG interface and bridge source. Set `XSP_COMM_TLM` to override that SDK -location; the public C++ headers remain under `include/xspcomm/tlm`. - -Generated Python and UVM DUT signals are direct `XData` objects. Use -`dut.signal.value` for signal values and pass `dut.signal` to backend APIs; -the former `XPin` wrapper and `dut.signal.xdata` access are no longer used. - -The wheel CI installs xcomm and Picker from built wheels in a clean virtual -environment, checks metadata against native versions, exercises signal and clock APIs, -and runs the installed Picker command. These checks do **not** yet prove that -an independently packaged DUT extension uses the same `libxspcomm` instance: -generated Python DUTs can still copy xcomm. Keep DUT wheel/linker relocation -tests as a separate release gate before publishing a general-purpose DUT wheel. +The preview override affects only xcomm. Picker keeps its Git-derived version. +CI's native build script refuses to switch an xcomm checkout with local changes. +Keep `-O3`, the native `-fno-strict-aliasing` flags and signal-value regressions +enabled. Wheel verification installs both packages into a fresh environment and +checks dependency consistency, native APIs, clock ownership and CLI exit codes. + +## Release procedure + +1. Publish the compatible xcomm release. Tagged CI selects a published stable + release satisfying `pyproject.toml`; it never uses a development preview. +2. Give the Picker master PR exactly one label: `release:patch`, `release:minor` + or `release:major`. Require the version-label and build checks in branch + protection, and test the PR against current master. Merge queues need + `merge_group` checks before they can be used. +3. After merge, **Version policy** creates the next tag and explicitly dispatches + **Tagged CI**. Direct master pushes without an associated merged PR do not + create a tag. Wait for tagging to finish before merging the next release PR. +4. Tagged CI builds and validates the exact tag: CPython 3.12 Linux x86-64 wheel, + plus x86-64/aarch64 AppImages and native archives, using one xcomm release. +5. After bundle upload, Tagged CI dispatches **Release Picker** with its run ID. + The publisher waits up to 60 minutes for successful completion, verifies the + bundle hashes and tag SHA, and publishes those exact artifacts without + rebuilding. GitHub Releases are not automatically uploaded to PyPI. + +Only tags on master's first-parent line that identify merged PR results are +accepted by the tagging/build policy. Configure a `v*` tag ruleset to restrict +manual creation or replacement. Do not publish the current Picker sdist as a +pip fallback: it omits vendored build dependencies; use a full source checkout +when a matching wheel is unavailable. + +## Recovery + +- Build failed: rerun **Tagged CI** with the existing tag and a compatible + published xcomm version. Do not move the tag. +- Publication failed: rerun **Release Picker** with the successful Tagged CI + run ID; it downloads the same checked bundle. +- Merge did not create a tag: inspect **Version policy**, then use its manual + bump input if recovery is needed. + +Generated DUTs may copy xcomm libraries. The package checks do not establish +that independently packaged DUT extensions load the same runtime instance; +verify ABI compatibility and relocation separately before publishing DUT wheels. diff --git a/pyproject.toml b/pyproject.toml index 10b6a5a..bf49267 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "scikit_build_core.build" name = "picker" dynamic = ["version"] dependencies = [ - "xspcomm>=0.3.0.dev2,<0.4", + "xspcomm>=1.0.0.dev0,<2", ] [[tool.dynamic-metadata]] diff --git a/scripts/build/verify-wheel.sh b/scripts/build/verify-wheel.sh index 1fc709b..af87ad7 100644 --- a/scripts/build/verify-wheel.sh +++ b/scripts/build/verify-wheel.sh @@ -59,6 +59,16 @@ try: raise AssertionError("manual stepping bypassed native engine ownership") assert clock.GetHalfTick() == 4 assert engine.RunUntil(2).advanced_ticks == 2 + wide = xspcomm.XData(128, xspcomm.XData.InOut) + wide.Set((1 << 100) | 0xA5) + lhs = engine.ExprNewBitsSignal(wide, False) + rhs = engine.ExprNewBitsConst(128, False, (7).to_bytes(16, "little")) + total = engine.ExprNewBits(xspcomm.ExprOp_ADD, lhs, rhs, 128, False) + engine.ExprNewConst(0) # Collection must remove this unused node. + remap = engine.CollectExpressions(xspcomm.XIntVector([total])) + total = remap[total] + assert total >= 0 and engine.ExpressionNodeCount() == 3 + assert int.from_bytes(engine.ExprEvalBits(total), "little") == (1 << 100) | 0xAC finally: engine.ReleaseClock() assert not clock.IsControlled()