rhdl_dsig_scalable_core/axi4.rs
1//! Packed 512-bit AXI4 memory-master channels for the U280 HBM ports.
2//!
3//! # The problem
4//!
5//! The signer lives on an FPGA card, but its inputs (messages) and outputs
6//! (signature frames) live in HBM, the card's stacked high-bandwidth DRAM.
7//! The only way an RTL kernel reaches HBM is through AXI4, the standard ARM
8//! bus protocol that Vitis (the AMD/Xilinx tool that links kernels into a
9//! card image) speaks. The host CPU, in turn, talks to the kernel through
10//! AXI4-Lite, a cut-down AXI with one 32-bit word per transfer and no bursts.
11//! This module defines those signals as plain RHDL structs so the rest of the
12//! crate can drive them like any other wires.
13//!
14//! # The idea: valid/ready handshakes
15//!
16//! Every AXI channel is one-way and carries a `valid` from the sender and a
17//! `ready` from the receiver. A transfer happens on the clock edge where
18//! *both* are high. Neither side may wait for the other before raising its
19//! own signal in a way that could deadlock, and a sender must hold its data
20//! steady until the transfer happens.
21//!
22//! ```text
23//! AXI4 write (kernel → HBM), used by hbm_writer
24//! AW kernel ──awvalid, awaddr, awlen──▶ memory ◀── awready
25//! W kernel ──wvalid, wdata, wlast────▶ memory ◀── wready
26//! B kernel ◀──bvalid, bresp────────── memory ──▶ bready
27//!
28//! AXI4 read (HBM → kernel), used by msg_reader
29//! AR kernel ──arvalid, araddr, arlen──▶ memory ◀── arready
30//! R kernel ◀──rvalid, rdata, rlast─── memory ──▶ rready
31//!
32//! AXI4-Lite (host → kernel registers), used by engine
33//! AW, W, B, AR, R as above, 32-bit data, one word per transfer
34//! ```
35//!
36//! # The decision: model only what we drive
37//!
38//! Vitis connects each `m_axi` port of an RTL kernel to an HBM pseudo-channel.
39//! Only the signals the DSig masters drive are modelled; every other AXI field
40//! is tied to its canonical value in the Verilog wrapper. Field order matches
41//! the packed layout the wrapper slices, so renaming a field is an ABI change.
42//! (ABI, "application binary interface", here means the exact bit layout the
43//! generated Verilog wrapper expects.)
44//!
45//! Burst rules the masters obey (AXI4, §A3.4): 64-byte beats (`size = 6`),
46//! incrementing bursts, at most 256 beats, and **no burst crosses a 4 KiB
47//! boundary**. DSig frames are 26 beats placed on a 2 KiB stride, and message
48//! reads are 16-beat (1 KiB) bursts from a 4 KiB-aligned base, so every burst
49//! is legal by construction.
50//!
51//! A *beat* is one data transfer on the W or R channel (64 bytes here). A
52//! *burst* is a run of beats that shares one address; `awlen`/`arlen` hold the
53//! beat count minus one, so `0` means one beat and `25` means 26 beats.
54//!
55//! # Where to go next
56//!
57//! [`crate::hbm_writer`] drives [`WriteRequest`] and reads [`WriteResponse`];
58//! [`crate::msg_reader`] drives [`ReadRequest`] and reads [`ReadResponse`];
59//! [`crate::engine`] serves [`AxiLiteIn`] and answers with [`AxiLiteOut`].
60
61use rhdl::prelude::*;
62
63/// Response code for success (`OKAY`, 2 bits, value 0).
64///
65/// AXI reports the outcome of every write burst (on B) and every read beat
66/// (on R) with a 2-bit code. Anything other than `OKAY` (for example
67/// `SLVERR` = 2 or `DECERR` = 3) means the memory refused or failed the
68/// access; the DSig masters compare against this constant and raise a sticky
69/// error flag, because a frame that did not land in HBM cannot be counted.
70pub const RESP_OKAY: b2 = b2(0);
71
72/// Memory-to-kernel signals of a write-only master (the inputs of
73/// [`crate::hbm_writer::HbmWriter`]).
74///
75/// These are the `ready` halves of the AW and W channels and the whole B
76/// (write response) channel. Deriving `Default` gives "memory idle, nothing
77/// accepted", which is a safe value for tests and for reset.
78#[derive(Clone, Copy, Debug, PartialEq, Digital, Default)]
79pub struct WriteResponse {
80 /// The memory accepts the write address (AW `ready`, 1 bit). The address
81 /// transfers on a cycle where this and `awvalid` are both high.
82 pub awready: bool,
83 /// The memory accepts one data beat (W `ready`, 1 bit). A beat transfers
84 /// on a cycle where this and `wvalid` are both high.
85 pub wready: bool,
86 /// A write response is present (B `valid`, 1 bit). One response arrives
87 /// per completed burst, i.e. one per DSig frame.
88 pub bvalid: bool,
89 /// The write response code (B `resp`, 2 bits); compare with
90 /// [`RESP_OKAY`].
91 pub bresp: b2,
92}
93
94/// Kernel-to-memory signals of a write-only master (the outputs of
95/// [`crate::hbm_writer::HbmWriter`]).
96#[derive(Clone, Copy, Debug, PartialEq, Digital)]
97pub struct WriteRequest {
98 /// Write address present (AW `valid`, 1 bit).
99 pub awvalid: bool,
100 /// Byte address of the burst's first beat (64 bits, the full HBM
101 /// address space Vitis assigns to the port).
102 pub awaddr: b64,
103 /// Beats in the burst minus one (8 bits, so up to 256 beats). DSig frames
104 /// always send 25, meaning 26 beats.
105 pub awlen: b8,
106 /// Data beat present (W `valid`, 1 bit).
107 pub wvalid: bool,
108 /// One 64-byte beat, byte 0 in the lowest bits (512 bits in total).
109 pub wdata: [b8; 64],
110 /// Last beat of the burst (W `last`, 1 bit). The memory uses it to know
111 /// the burst is over.
112 pub wlast: bool,
113 /// The kernel accepts a write response (B `ready`, 1 bit). The DSig
114 /// writer keeps it high: it can always count a response.
115 pub bready: bool,
116}
117
118/// Memory-to-kernel signals of a read-only master (the inputs of
119/// [`crate::msg_reader::MessageReader`]).
120#[derive(Clone, Copy, Debug, PartialEq, Digital)]
121pub struct ReadResponse {
122 /// The memory accepts the read address (AR `ready`, 1 bit).
123 pub arready: bool,
124 /// A read beat is present (R `valid`, 1 bit).
125 pub rvalid: bool,
126 /// The read beat (64 bytes = 512 bits, byte 0 in the lowest bits).
127 pub rdata: [b8; 64],
128 /// The read response code (R `resp`, 2 bits); compare with
129 /// [`RESP_OKAY`].
130 pub rresp: b2,
131 /// Last beat of the burst (R `last`, 1 bit). The DSig reader counts beats
132 /// itself and does not need this, but it is part of the packed layout.
133 pub rlast: bool,
134}
135
136/// Kernel-to-memory signals of a read-only master (the outputs of
137/// [`crate::msg_reader::MessageReader`]).
138#[derive(Clone, Copy, Debug, PartialEq, Digital, Default)]
139pub struct ReadRequest {
140 /// Read address present (AR `valid`, 1 bit).
141 pub arvalid: bool,
142 /// Byte address of the burst's first beat (64 bits).
143 pub araddr: b64,
144 /// Beats in the burst minus one (8 bits). The message reader asks for at
145 /// most 16 beats, so at most 15 here.
146 pub arlen: b8,
147 /// The kernel accepts a read beat (R `ready`, 1 bit). The message reader
148 /// keeps it high because it only asks for data it has room for.
149 pub rready: bool,
150}
151
152// ---------------------------------------------------------------- AXI4-Lite
153//
154// AXI4-Lite is how the host CPU reads and writes the kernel's 32-bit control
155// registers (see the register map in crate::engine). It has the same five
156// channels as AXI4 but no bursts: every transfer is one 32-bit word at one
157// 12-bit byte address inside the kernel's 4 KiB register window.
158
159/// Named-port-compatible input; every field is one AXI-Lite channel signal.
160///
161/// "Named-port-compatible" means the field names map one-to-one onto the
162/// `s_axi_control_*` ports Vitis expects, so the Verilog wrapper can connect
163/// them by name.
164#[derive(Clone, Copy, Debug, Default, Digital, Eq, PartialEq)]
165#[allow(clippy::struct_excessive_bools)]
166pub struct AxiLiteIn {
167 /// Write address validity (AW `valid`, 1 bit).
168 pub aw_valid: bool,
169 /// Byte address in the 4 KiB aperture (12 bits, so `0x000`..`0xfff`).
170 pub aw_addr: b12,
171 /// Write data validity (W `valid`, 1 bit).
172 pub w_valid: bool,
173 /// Write word, little-endian byte lanes (32 bits).
174 pub w_data: b32,
175 /// One enable per write byte (4 bits). The DSig register file treats
176 /// every write as a whole 32-bit word and ignores this field.
177 pub w_strb: b4,
178 /// Host accepts a write response (B `ready`, 1 bit).
179 pub b_ready: bool,
180 /// Read address validity (AR `valid`, 1 bit).
181 pub ar_valid: bool,
182 /// Read byte address (12 bits).
183 pub ar_addr: b12,
184 /// Host accepts a read response (R `ready`, 1 bit).
185 pub r_ready: bool,
186}
187
188/// Stable response channels plus the optional level interrupt.
189///
190/// "Stable" means every output here comes straight from a register in
191/// [`crate::engine::EngineControl`], so it does not change while the host
192/// holds `ready` low (AXI calls that "backpressure").
193#[derive(Clone, Copy, Debug, Default, Digital, Eq, PartialEq)]
194#[allow(clippy::struct_excessive_bools)]
195pub struct AxiLiteOut {
196 /// Write address acceptance (AW `ready`, 1 bit).
197 pub aw_ready: bool,
198 /// Write data acceptance (W `ready`, 1 bit).
199 pub w_ready: bool,
200 /// Registered write response validity (B `valid`, 1 bit).
201 pub b_valid: bool,
202 /// OKAY (0) or SLVERR (2) (2 bits).
203 pub b_resp: b2,
204 /// Read address acceptance (AR `ready`, 1 bit).
205 pub ar_ready: bool,
206 /// Registered read response validity (R `valid`, 1 bit).
207 pub r_valid: bool,
208 /// Registered, backpressure-stable read word (32 bits).
209 pub r_data: b32,
210 /// OKAY (0) or SLVERR (2) (2 bits).
211 pub r_resp: b2,
212 /// Enabled sticky done/fault event: a level (not a pulse) interrupt line
213 /// to the host, high while `GIE`, `IER` and `ISR` are all set.
214 pub interrupt: bool,
215}