← Evidence record

Privacy-sanitized source narrative

Production signer U280 synthesis at 300 MHz

This page publishes the producing narrative as escaped plaintext. It preserves repository provenance while withholding machine-local paths and private build-host labels.

Sanitization disclosure

This is a public projection, not a byte-identical copy when substitutions are listed. The pre-sanitization digest identifies the narrative bytes read from the documentation checkout; the cryptographic subject commit is recorded separately.

Provenance

Evidence record
production-signer-u280-synthesis-300
Repository-relative source
fpga/u280/README.md
Cryptographic subject/source commit
dbd18593527673be2a93d7067ee03667892c582a
Cryptographic source dirty
no
Narrative checkout commit
3810cf837ea79ca005f4ab227b0b5c2134fe6ad1
Narrative checkout dirty
no
Pre-sanitization narrative SHA-256
515df8be0bbc118672987967f18f5389c333561398825e0b658d0147e86eab19

The digest identifies the narrative bytes read by this documentation build before substitutions. It does not assert that those narrative bytes existed at the cryptographic subject/source commit.

Open the sanitized narrative as plain text.

Producing 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.