HashSigs RHDL privacy-sanitized source narrative Evidence record: production-signer-u280-route-250 Repository-relative source: fpga/u280/README.md Cryptographic subject/source commit: dbd18593527673be2a93d7067ee03667892c582a Cryptographic source dirty: no Pre-sanitization narrative SHA-256: 515df8be0bbc118672987967f18f5389c333561398825e0b658d0147e86eab19 Narrative checkout commit: 3810cf837ea79ca005f4ab227b0b5c2134fe6ad1 Narrative checkout dirty: no Sanitization substitutions: home-directory path: 1 -> [local-home-path]; private build-host label: 1 -> [remote-build-host] This public projection is not byte-identical when substitutions are listed. The digest identifies the pre-sanitization narrative bytes read by this documentation build. It does not assert that those bytes existed at the cryptographic subject commit. --- sanitized narrative --- # U280 out-of-context flow This directory contains the reproducible Vivado gate for the exact generated `wots_sha256_signer_top`. It does not package a Vitis kernel and it does not prove card throughput. Its purpose is to measure the crypto hierarchy against the target device at both the 250 MHz performance basis and 300 MHz clock goal. The separate first card-kernel layer is documented in [`BENCHMARK_ABI.md`](BENCHMARK_ABI.md). `rhdl-gen benchmark-kernel` generates that Rust/RHDL AXI-Lite benchmark bundle, its metadata-derived wire wrapper, and packaging Tcl. Those generated files do not change this raw-crypto OOC flow and do not constitute Vitis link or hardware-execution evidence. Every HashSigs Vivado, Vitis, XRT, or card command must enter through the checked-in `tools/with-u280-lock.sh` wrapper. The wrapper requires the caller to assert, for that launch, that ownership is already recorded in both coordination ledgers. That acknowledgment is an operator assertion, not cryptographic proof that either entry exists. The descriptor helper ignores a caller-supplied `HOME`, derives the effective user's canonical passwd login home, and opens each lock-path component relative to retained directory descriptors with `O_NOFOLLOW`. It validates effective-user ownership and rejects group- or other-writable home, cache, lock-directory, or lock-file objects before taking `LOCK_EX|LOCK_NB` on the already-open lock fd. The helper reopens and identity-checks the complete canonical path immediately before launch, removes the human acknowledgment from the child environment, and remains as a supervisor retaining the locked fd until the foreground child exits. It also passes that fd to guarded Vitis and card entry points so they can recheck the canonical path identity; closing inherited non-stdio descriptors in a tool cannot release the supervisor's lock. Privileged Bash startup ignores `BASH_ENV` and imported functions. The helper also strips Bash/POSIX startup hooks, option injection, and exported Bash functions from the guarded child, validates its own file identity, descriptor-launches ELF commands, and checks script-launcher inode identity immediately before and after spawn. A lock conflict is a hard stop. The wrapper accepts only an absolute executable path and has no alternate namespace, waiting mode, or unlocked fallback. This remains an advisory same-UID coordination mechanism: it prevents accidental competing launches by cooperating processes, but a malicious process already running as the same user can deliberately violate the protocol. Approved tools must remain in the foreground and wait for every subprocess. Run [remote-build-host] flows in a dedicated systemd service with `KillMode=control-group`; do not background, double-fork, or daemonize an FPGA job beyond the supervising lock owner. ## Deterministic Phase-B probe inputs The first Phase-B shell link is deliberately a raw, permanently nonpromotable 250 MHz format probe. Generate its platform records and schema-2 request rather than hand-authoring JSON or FileSpecs: ```console bun tools/u280-vitis/cli.ts platforminfo-capture \ [local-home-path] \ /opt/xilinx/platforms/xilinx_u280_gen3x16_xdma_1_202211_1/xilinx_u280_gen3x16_xdma_1_202211_1.xpfm \ /new/private/capture/platforminfo.json \ /new/private/capture/platforminfo-version.txt bun tools/u280-vitis/cli.ts phase-b-probe-request \ /clean/hashsigs-rhdl \ /sealed/package/hashsigs_sha256_hbm_kernel.xo \ /sealed/artifacts/combined-link-graph.json \ /sealed/link/link_250.cfg \ /opt/xilinx/platforms/xilinx_u280_gen3x16_xdma_1_202211_1/xilinx_u280_gen3x16_xdma_1_202211_1.xpfm \ /new/private/capture \ /sealed/tools/vpp-version.txt \ /sealed/tools/vivado-version.txt \ /sealed/tools/xclbinutil-version.txt \ /sealed/tools/linkhook-steps.txt \ /sealed/artifacts/hbm-generation \ /sealed/artifacts/signer-generation/rhdl-gen-check-NNN-0 \ /new/private/phase-b-probe-request.json ``` Both actions require canonical absolute paths and new output files; the probe request output must also be outside the source repository. They use exclusive, non-symlink publication and read-only outputs. The request action revalidates the clean Git commit/tree, all 16 stable input files, AMD 2023.2 records, the exact link configuration, and the platform capture before and after publication; a failed post-publication revalidation removes the new request. Its clock, kernel ABI, HBM mapping, resource caps, and both routed identities are fixed in code: 250 MHz, `hashsigs_sha256_hbm_kernel_1`, the exact six-argument `inputs,outputs,summary,batch,abi_word,nonce` ABI, HBM[0] for inputs, HBM[1] for outputs and summary, 200k LUT, 300k FF, 4k DSP, and `ADAPTATION_REQUIRED`. It cannot emit a 300 MHz or promotable request. Continue with `stage`, `probe-plan`, `probe-run`, and `probe-check`. The platform capture and request generator do not invoke Vivado/Vitis linking, program a card, or constitute synthesis, timing, route, resource, hardware, or performance evidence. The examples below resolve the checked-in wrapper relative to the repository root. Start there; commands that later change directory retain its absolute path before doing so. ## Phase-A XO and Vitis link contract The generated packaging Tcl declares `user_managed` in both the IP metadata and the `package_xo` command. That control protocol is required by the Phase-A `xrt::ip` runner. The one linkable kernel/CU mapping is: ```text hashsigs_wots_sha256_benchmark_kernel:1:hashsigs_wots_sha256_benchmark_kernel_1 ``` Consequently, the only supported runtime identity is `hashsigs_wots_sha256_benchmark_kernel:{hashsigs_wots_sha256_benchmark_kernel_1}`. Do not rename or replicate the CU without changing and revalidating the host contract. Package from a clean, immutable `rhdl-gen benchmark-kernel` output. Select an existing private output directory containing neither the requested XO nor its per-XO work path: ```console : "${PHASE_A_ARTIFACT:?absolute generated benchmark directory}" : "${PHASE_A_XO_DIR:?existing new private XO directory}" : "${VIVADO_BIN:?Vivado 2023.2 build 4029153 executable}" test -d "$PHASE_A_XO_DIR" test -x "$VIVADO_BIN" test ! -e "$PHASE_A_XO_DIR/hashsigs_wots_sha256_benchmark_kernel.xo" test ! -e "$PHASE_A_XO_DIR/hashsigs_wots_sha256_benchmark_kernel.xo.package-work" package_console="$PHASE_A_XO_DIR/package.vivado.console.log" package_status_record="$PHASE_A_XO_DIR/package.vivado.exit-status" test ! -e "$package_console" test ! -e "$package_status_record" if HASHSIGS_U280_COORDINATION_ACK=both-ledgers-recorded \ tools/with-u280-lock.sh "$VIVADO_BIN" -mode batch -nojournal -nolog \ -source "$PHASE_A_ARTIFACT/package_hashsigs_benchmark_kernel.tcl" \ -tclargs \ "$PHASE_A_ARTIFACT/hashsigs_wots_sha256_benchmark.bundle.v" \ "$PHASE_A_XO_DIR/hashsigs_wots_sha256_benchmark_kernel.xo" \ > "$package_console" 2>&1; then package_status=0 else package_status=$? fi printf '%s\n' "$package_status" > "$package_status_record" test "$package_status" -eq 0 ``` The script refuses an input/output alias or existing output and atomically acquires a new, mode-`0700` per-XO work path. It hashes the original bundle, packages only a read-only private copy, embeds that digest and byte count in the IP description, and rehashes the original, staged, and IP-imported copies after `package_xo`. It publishes through a non-overwriting rename and never uses `package_xo -force`. Keep the per-XO work directory and package log with the XO until the link archive is sealed. Link 250 MHz first with the exact checked-in connectivity/clock contract. Use the 300 MHz file only after the 250 MHz shell route and card gate are understood: ```console : "${U280_PLATFORM:?exact U280 platform path or platform identifier}" : "${VPP_BIN:?Vitis 2023.2 v++ executable}" test -x "$VPP_BIN" HASHSIGS_U280_COORDINATION_ACK=both-ledgers-recorded \ tools/with-u280-lock.sh "$VPP_BIN" --link --target hw --platform "$U280_PLATFORM" \ --config fpga/u280/phase_a/link_250.cfg \ --output /new/path/hashsigs-wots-sha256-phase-a-250.xclbin \ "$PHASE_A_XO_DIR/hashsigs_wots_sha256_benchmark_kernel.xo" HASHSIGS_U280_COORDINATION_ACK=both-ledgers-recorded \ tools/with-u280-lock.sh "$VPP_BIN" --link --target hw --platform "$U280_PLATFORM" \ --config fpga/u280/phase_a/link_300.cfg \ --output /another/new/path/hashsigs-wots-sha256-phase-a-300.xclbin \ "$PHASE_A_XO_DIR/hashsigs_wots_sha256_benchmark_kernel.xo" ``` The configs pin `nk=...:1:..._1` and respectively `freqHz=250000000:hashsigs_wots_sha256_benchmark_kernel_1.ap_clk` or `freqHz=300000000:hashsigs_wots_sha256_benchmark_kernel_1.ap_clk`. A successful link is shell-build evidence only. Card performance still requires the exact xclbin, routed clock evidence, durable nonce/range reservations, complete raw register archive, and reserved-card execution described by the host guide. The checked-in Phase-A package and link inputs contain no `USE_DSP` or other DSP-placement directive. This Vitis staging/run contract identifies the flow with `profile=phase-a`, exact source and XO digests, and the selected link configuration; its current manifests do not carry a `flow_variant` field. The separate raw-RTL staging/checker schema records `flow_variant=baseline`, but that field is not evidence supplied by this Phase-A Vitis path. A DSP experiment therefore needs a later, separately staged and validated contract. The first OOC gate deliberately targets the raw generated top rather than an adapter. Packed `clock_reset` bit 0 is the clock and bit 1 is active-high synchronous reset. This preserves the locally simulated RTL checksum and adds no wrapper LUTs or registers. The XDC assigns 0.500 ns input/output boundary delays explicitly to synchronous `clock_reset[1]`, every `i` bit, and every `o` bit. Both XDCs are managed, declarative files: control flow, collection manipulation, and validation belong in `ooc.tcl`. A future shell adapter is a separate composition and simulation gate. ## Device and gates - Part: `xcu280-fsvh2892-2L-e` - 250 MHz constraint: 4.000 ns - 300 MHz constraint: 3.333 ns - Crypto hierarchy: at most 200,000 CLB LUTs, 300,000 registers, and 4,000 DSPs - Timing: completely routed, WNS/WHS/WPWS >= 0 and TNS/THS/TPWS = 0 The Tcl passes `-max_dsp 4000` to synthesis so even later `USE_DSP` A/B experiments cannot silently exceed the normal DSP cap. LUT and register limits remain explicit post-synthesis and post-route report gates. `-max_dsp` is a ceiling, not a request to consume DSPs. The baseline run must report the mapping Vivado actually chose. Any `USE_DSP` experiment is a separate RTL/XDC hash and run directory and wins only on routed timing and resources. The generated RTL must have passed Rust/RHDL and external Verilog simulation before this flow is run. Each architecture/profile variant uses a fresh run directory; never combine reports from different RTL hashes or constraints. ## Invocation > **Superseded stage:** `6801a66-41fe7ba3-20260715T1103PDT` is preserved as a > forensic artifact but is unlaunchable. Its two managed XDCs contain Tcl > control flow and `remove_from_collection`, so they cannot support a Vivado > result or evidence claim. Do not mutate, execute, or promote that directory. > Create a new schema-v2 stage from the corrected flow commit. First create a new checksum-validated staging directory from the preserved local gate. Run this from the final clean integration commit; the stage records that exact flow commit and tree, including the checker itself: ```console bun run stage:u280 -- \ /path/to/rhdl-gen-check-NNN-0 \ /path/to/new-staged-rtl ``` Keep the stage and every result directory outside the Git worktree. From a new directory that was empty before Vivado started, run 250 MHz synthesis only first. Vivado may create its process-local `.Xil` directory before the Tcl begins; every other pre-existing entry is rejected. Capture the console and exit status outside Vivado's disabled journal/log files. Export `VIVADO_BIN` as the executable for Vivado 2023.2 build 4029153 before entering the run directory: ```console : "${VIVADO_BIN:?set VIVADO_BIN to Vivado 2023.2 build 4029153}" test -x "$VIVADO_BIN" || { printf '%s\n' "VIVADO_BIN is not executable" >&2; exit 2; } u280_lock_wrapper="$PWD/tools/with-u280-lock.sh" test -x "$u280_lock_wrapper" || { printf '%s\n' "U280 lock wrapper is not executable" >&2; exit 2; } run_dir=/path/to/250-synth console_log="${run_dir}.vivado.console.log" mkdir -m 700 "$run_dir" test -z "$(find "$run_dir" -mindepth 1 -maxdepth 1 -print -quit)" cd "$run_dir" HASHSIGS_U280_COORDINATION_ACK=both-ledgers-recorded \ "$u280_lock_wrapper" "$VIVADO_BIN" -mode batch -nojournal -nolog \ -source /path/to/staged-rtl/ooc.tcl \ -tclargs /path/to/staged-rtl wots_sha256_signer_top \ /path/to/staged-rtl/constraints_250.xdc \ xcu280-fsvh2892-2L-e 2 synth \ > "$console_log" 2>&1 vivado_status=$? printf '%s\n' "$vivado_status" > vivado.exit-status mv "$console_log" vivado.console.log test "$vivado_status" -eq 0 ``` The part argument remains in the generic invocation shape but this evidence flow rejects every value except `xcu280-fsvh2892-2L-e`. It also reads the selected XDC and rejects a 250/300 filename whose actual `create_clock` period is not 4.000/3.333 ns. The thread cap is one through four; use two on the shared host. After copying the stage and result directory to a machine with this repository, run the fail-closed Bun checker. It supports a relocated archive: execution-time absolute paths remain bound by `rtl.sha256` and `staged-inputs.sha256`, while the supplied staged copy must match every recorded byte count and digest. The checker itself must execute from a completely clean checkout whose `HEAD` commit and tree exactly match the stage's `flow_git_commit` and `flow_git_tree`. It records its own source byte count and SHA-256 in the emitted evidence manifest and refuses a dirty, different, copied, or untracked checker. After changing this flow, create a fresh stage; an older stage cannot be promoted by a newer checker. ```console bun run tools/check-u280-reports.ts -- \ /path/to/staged-rtl /path/to/250-synth synth \ /path/to/250-synth/u280-synth-evidence.json ``` The checker emits the evidence manifest only on pass. For synthesis it requires the exact staged chain (including the embedded bundle/check manifests, pinned RHDL and HashSigs revisions, module graph, and exact simulation PASS line), complete report inventory, a post-synthesis DCP, no report `.error`, zero black boxes, and at most 200,000 CLB LUTs, 300,000 CLB registers, and 4,000 DSPs. The flow also exports Vivado's valid in-memory timing constraints to `post_synth_applied_constraints.xdc` and emits the deterministic `post_synth_constraints_observed.rpt`. The latter is accepted only when the post-synthesis design has exactly one `core_clk` on `clock_reset[0]`, the exact 1/547/677 reset-input/data-input/output port inventory, four valid and zero invalid constraints, the expected 0.500 ns boundary delays, and zero `no_*_delay` or `partial_*_delay` findings. Constraint evidence uses three deliberately different lifecycle terms: - **Declared** means the staged managed XDC passed the exact static grammar and checksum gate. No tool has applied it yet. - **Applied** means Vivado's `read_xdc` returned and the four constraints appear in its post-synthesis valid-constraint export. This is still a tool result, not hardware. - **Observed** means the post-synthesis Tcl gate checked the exact clock/ports, exported valid-versus-invalid constraint state, and timing-coverage counts; the Bun checker then independently validated and checksum-bound those files. The authoritative utilization/timing reports must identify Vivado 2023.2 build 4029153, this top, the U280/-2L device, and the expected synthesized state. The timing report must show the selected XDC period on `core_clk` and zero findings in all 12 verbose `check_timing` categories: `no_clock`, `constant_clock`, `pulse_width_clock`, `unconstrained_internal_endpoints`, `no_input_delay`, `no_output_delay`, `multiple_clock`, `generated_clocks`, `partial_input_delay`, `partial_output_delay`, `loops`, and `latch_loops`. It records post-synthesis WNS/TNS/WHS/THS/WPWS/TPWS but does not call an unplaced estimate timing closure. Promotion also requires `vivado.exit-status` to contain exactly zero and the captured console to contain exactly one Vivado 2023.2/build-4029153 banner and the phase's exact Tcl completion line. A DCP or nominal reports left behind by an interrupted process therefore cannot pass. Both phases name their Vivado DRC result and emit a no-waiver text report. The gate reads each Vivado 2023.2 `RULE#N Severity` detail header so an individual violation's effective `Error` or `Fatal` severity is fatal, resolves its rule as an enabled DRC check, and requires the complete sorted detail-ID list to equal the exact named `get_drc_violations` collection. A missing/changed report format or API disagreement fails closed. `Critical Warning` violations remain visible and checksum-bound in the DRC and gate reports but are not promoted to fatal policy by this flow. This follows Vivado 2023.2's documented [`report_drc`](https://docs.amd.com/r/2023.2-English/ug835-vivado-tcl-commands/report_drc) result naming, `IS_ENABLED` semantics, and [`get_drc_violations`](https://docs.amd.com/r/2023.2-English/ug835-vivado-tcl-commands/get_drc_violations) named-result query. DRC reports use `-no_waivers`, so a waiver cannot suppress this gate. Any severity-policy change is a new staged Tcl/XDC hash and cannot silently inherit this evidence record. Only after the 250 MHz synthesis checker passes, run `full` from another new empty directory with the same XDC and check it as `full`. Keep the same previously exported Vivado 2023.2 build 4029153 executable: ```console : "${VIVADO_BIN:?set VIVADO_BIN to Vivado 2023.2 build 4029153}" test -x "$VIVADO_BIN" || { printf '%s\n' "VIVADO_BIN is not executable" >&2; exit 2; } u280_lock_wrapper="$PWD/tools/with-u280-lock.sh" test -x "$u280_lock_wrapper" || { printf '%s\n' "U280 lock wrapper is not executable" >&2; exit 2; } run_dir=/path/to/250-full console_log="${run_dir}.vivado.console.log" mkdir -m 700 "$run_dir" test -z "$(find "$run_dir" -mindepth 1 -maxdepth 1 -print -quit)" cd "$run_dir" HASHSIGS_U280_COORDINATION_ACK=both-ledgers-recorded \ "$u280_lock_wrapper" "$VIVADO_BIN" -mode batch -nojournal -nolog \ -source /path/to/staged-rtl/ooc.tcl \ -tclargs /path/to/staged-rtl wots_sha256_signer_top \ /path/to/staged-rtl/constraints_250.xdc \ xcu280-fsvh2892-2L-e 2 full \ > "$console_log" 2>&1 vivado_status=$? printf '%s\n' "$vivado_status" > vivado.exit-status mv "$console_log" vivado.console.log test "$vivado_status" -eq 0 bun run tools/check-u280-reports.ts -- \ /path/to/staged-rtl /path/to/250-full full \ /path/to/250-full/u280-route-evidence.json ``` The full checker additionally requires the post-route DCP and every route report, equal routable/fully-routed net counts, zero routing errors/unrouted pins/resource conflicts, WNS/WHS/WPWS >= 0, TNS/THS/TPWS = 0, all 12 timing coverage categories at zero, the applied `core_clk` period equal to the selected XDC, routed-state Vivado report identity, and both post-synthesis and post-route resource totals within the caps. An exit code of zero or a DCP by itself is never a pass. Run the 3.333 ns synthesis and full phases in two further new directories only after the 4.000 ns full checker passes. Substitute `constraints_300.xdc` in the commands above and keep `synth` before `full`. The four runs are strictly sequential and must not overlap another large Vivado job on the shared host. The pristine-directory policy is intentionally exact for the documented Vivado 2023.2 non-project invocation: `.Xil` is the only process-local startup directory allowed before Tcl begins. `-nojournal -nolog` prevents journal/log files, and the command redirects its console outside the run directory until the Tcl emptiness gate has passed. Any other pre-existing entry is fatal. `RTL_DIR` must be a clean staging directory containing only the exact linked production bundle as Verilog. Do not point it at an `rhdl-gen check --keep` directory: that directory also contains self-checking testbenches and unrelated quick-check RTL, while `ooc.tcl` reads every `.v` and `.sv` in its input directory. Use the checksum-validating Bun staging command documented by `bun run stage:u280 --help`. ## Evidence discipline Archive the staging manifest, both source evidence manifests, `run_metadata.txt`, `rtl.sha256`, `staged-inputs.sha256`, `post_synth_applied_constraints.xdc`, `post_synth_constraints_observed.rpt`, every report, and the post-route DCP together. Also retain `vivado.console.log`, `vivado.exit-status`, and the checker-generated evidence manifest. The flow rejects Tcl or XDC outside the validated staging directory, exits nonzero when any required report command fails, and stops on unresolved black boxes. The evidence manifest carries the pinned RHDL revision `c99d5cc53269a247bbc675d0fbd766991d409f56`, pinned HashSigsRS revision `2d315dd4168804b7cbc51c51a1bf7ca27bf74140`, and clean checker commit/tree/hash. A passing OOC route is U280-part mapping/timing evidence for the raw crypto top; it is not a U280 shell result, card throughput, or hardware execution. Vitis link and reserved-card execution use separate evidence records and must identify shell resources.