Pulse → UltraScale+ HLS Code Generation¶
SPDX-License-Identifier: AGPL-3.0-or-later
scpn_quantum_control.codegen.ultrascale_hls converts a quantum control pulse
waveform — the output of phase/pulse_shaping.py — into a manifest-bound
Vivado/Vitis HLS source artifact for AMD Xilinx Zynq UltraScale+ devices. The
artifact is a decoupled producer handoff for the designated SC-NEUROCORE
hdl_gen.hls_ingest boundary. This module emits source plus manifest.json;
it does not prove that downstream ingest is implemented, invoke Vivado,
prove timing closure, define board pin placement, or execute FPGA hardware.
The target devices are shared with SC-NEUROCORE NEU-C.1: zu3eg
(xczu3eg-sbva484-1-e) and zu9eg (xczu9eg-ffvb1156-2-e).
What is generated¶
emit_versioned_hls_artifact validates the pulse, writes a versioned artifact
directory, and records file hashes in manifest.json. The default runner writes
under ignored results/ultrascale_hls_artifacts/; pass an explicit output
directory when publishing a handoff artifact.
python scripts/export_ultrascale_hls_artifact.py \
--output-dir results/ultrascale_hls_artifacts \
--artifact-id ultrascale-hls-pulse-axi-v1 \
--target-sku zu3eg \
--sample-rate-hz 125000000 \
--n-samples 256
The manifest schema is
scpn-quantum-control.ultrascale-hls-artifact.v1, and the consumer contract is
sc-neurocore.hdl_gen.hls_ingest.v1. The payload records:
target: UltraScale+ SKU and part number.pulse: sample rate, sample count, and waveform SHA-256 over little-endian float64 bytes.fixed_point: word width, fractional bits, and integer bits.interfaces:pulse_stream, AXI4-Stream master output, FIFO depth, and one-sample-per-ap_clkcadence.files: relative paths plus SHA-256 and byte counts for the header, testbench, and XDC.claim_boundary: the explicit no-synthesis/no-timing/no-hardware boundary.
verify_hls_artifact_manifest validates schema identity and file integrity for
that artifact directory.
Status and ownership matrix¶
| Boundary | Current support | Evidence and owner |
|---|---|---|
| Pulse-to-HLS producer | Supported source-generation surface | This repository emits and verifies the versioned header, testbench, XDC, and manifest. |
| Host C++ co-simulation | Supported software evidence | This repository compiles the generated testbench against the packaged non-synthesis shim and records a hash-bound bit-true verdict. |
| Vivado/Vitis synthesis check | Toolchain-gated validation path | This repository defines an opt-in csim_design + csynth_design test. Ordinary local/CI evidence does not imply that the gated toolchain ran. |
| SC-NEUROCORE ingest | Contract designated; integration open | The producer names sc-neurocore.hdl_gen.hls_ingest.v1. A valid manifest is not proof of an accepted downstream consumer. |
| Timing closure, bitstream, and board execution | Unsupported by this repository | SC-NEUROCORE owns the RTL/PPA/board path; no timing, bitstream, or physical-FPGA claim is made here. |
These labels separate a supported software producer from toolchain and hardware boundaries. Fault-tolerant quantum resource estimates are a different scale regime; see Multiscale Quantum Error Correction. Neither that resource model nor this HLS producer is evidence for the other.
pulse_to_vivado_hls remains the lower-level generator: it quantises the
envelope to a signed Q-format ROM and renders three artefacts into an
HLSBundle:
| Artefact | File (via write_bundle) |
Role |
|---|---|---|
| HLS header | pulse_axi_stream.hpp |
synthesisable pulse_stream() — replays the ROM onto an AXI4-Stream master, one sample per cycle, TLAST on the final sample |
| Co-sim testbench | pulse_axi_stream_tb.cpp |
drives pulse_stream() and checks order, count, TLAST, and FIFO drain |
| Constraints | pulse_constraints.xdc |
clock-only timing baseline (no fabricated pin LOCs) |
from scpn_quantum_control.codegen import emit_versioned_hls_artifact
from scpn_quantum_control.phase.pulse_shaping import build_hypergeometric_pulse
pulse = build_hypergeometric_pulse(t_total=1.0, omega_0=1.0, alpha=1.0, beta=1.0, n_points=256)
manifest = emit_versioned_hls_artifact(
pulse.envelope,
"results/ultrascale_hls_artifacts",
artifact_id="ultrascale-hls-pulse-axi-v1",
sample_rate_hz=125e6,
target_sku="zu3eg",
fixed_point_width=16,
fixed_point_frac_bits=8,
)
print(manifest.consumer_contract_version)
Fixed-point quantisation¶
The envelope is quantised to a signed Q(width-frac-1).frac word by
floor(x · 2^frac + 0.5) (round half toward +∞) with saturation to the
two's-complement range. The default Q7.8 / 16-bit word maps a normalised
[-1, 1] envelope to [-256, 256] codes, well inside the ±32767 range. The
quantiser dispatches to a bit-true Rust kernel (quantise_q_format,
scpn_quantum_engine/src/hls_quantise.rs) and falls back to the pure-Python
reference; both evaluate identical IEEE-754 binary64 arithmetic.
Timing constraints¶
The XDC follows the NEU-C.1 discipline: a create_clock baseline and a reset
IOSTANDARD, with no fabricated pin assignments. The ap_clk period tracks the
requested sample rate (one sample per cycle) but is pinned at the 250 MHz
fabric-clock floor; a request above the floor emits a comment that the stream
must be paced or parallelised downstream.
Co-simulation without Vivado¶
The generated bundle uses the authentic AMD Xilinx HLS API surface (ap_int,
ap_axis, hls::stream). A packaged non-synthesis shim in src/scpn_quantum_control/codegen/hls_host_shim backs that
API with host C++ so the testbench compiles and runs under g++ for a bit-true
software co-simulation:
g++ -std=c++17 -Wno-unknown-pragmas -Isrc/scpn_quantum_control/codegen/hls_host_shim -Ibuild/pulse_player \
build/pulse_player/pulse_axi_stream_tb.cpp -o /tmp/tb && /tmp/tb # prints "PASS <n>"
Vivado/Vitis HLS supplies the real headers at synthesis time. The repository
defines a synthesis path (csim_design + csynth_design on the ZU3EG) in
tests/test_ultrascale_hls.py::test_vivado_hls_synthesis, gated behind
MIF_FPGA_VIVADO_CI=1 for a self-hosted runner. An ordinary test pass does not
assert that this opt-in path ran.
Co-simulation evidence artifact (RC-3)¶
benchmarks/hls_cosimulation_evidence.py elevates the host-compiler
co-simulation to a first-class, hash-bound evidence artifact, and
scripts/run_hls_cosimulation_evidence.py is its CLI:
The artifact records the bit-true PASS <n> verdict together with everything
that produced it: SHA-256 content digests of the generated header, testbench,
XDC, and each shim header; the exact compile command; the compiler identity;
provenance (git commit, command, dependency versions); and the host-isolation
timing grade. Its boundary is explicit — codegen + software co-simulation
only: no synthesis, no timing closure, no board execution. A compile or run
failure is recorded as passed=false with the captured output (failure
evidence is still evidence), and a missing host compiler raises instead of
fabricating a verdict. The handoff names the SC-NEUROCORE consumer contract
(sc-neurocore.hdl_gen.hls_ingest.v1); that identifier is a designated
boundary, not proof of a live consumer. The RTL path and any sub-50 ns latency
work stay in SC-NEUROCORE. A committed example artifact lives under
data/hls_cosimulation/.
Acceleration¶
Measured (release build, median of 21, scripts/bench_ultrascale_hls.py,
results/ultrascale_hls_benchmark.json, functional_non_isolated):
| operation (10⁴ samples) | time |
|---|---|
| Q-format quantise (Python) | 5.98 ms |
| Q-format quantise (Rust) | 0.20 ms (29.7×) |
end-to-end pulse_to_vivado_hls |
7.15 ms |
End-to-end codegen for a 10⁴-sample waveform is within the 10 ms acceptance target.
Consumers¶
The emitted directory targets the designated SC-NEUROCORE contract
sc-neurocore.hdl_gen.hls_ingest.v1. The handoff is file-system and manifest
based, so a future consumer does not need to import this Python package.
Downstream acceptance must be established in SC-NEUROCORE; producer evidence
alone does not establish it.