Skip to main content

rhdl_ubft/kv/
load.rs

1//! Bounded replacement for upstream `load/load.cpp`.
2//!
3//! The memory port transfers exactly one aligned eight-byte word per request,
4//! with at most one outstanding request. It is a single-beat AXI64 read subset
5//! with ARLEN=0, ARSIZE=3, and ARBURST=1 (INCR). Reset must also
6//! cancel/drain outstanding bus transactions before this component is restarted.
7//! `count` and `capacity` are word counts. Bit 31 of count is rejected to keep
8//! the upstream signed-int count domain explicit; negative counts are errors.
9//! Transfers occur on rising edges with valid and ready asserted. Producers
10//! hold valid and payload until transfer; reset suppresses handshakes. A
11//! malformed response without eventual LAST cannot finish draining and requires
12//! external recovery. Loader completion does not imply application commitment.
13use rhdl::prelude::*;
14
15/// Memory answers; response data is stable while valid and not ready.
16#[derive(Clone, Copy, Debug, PartialEq, Digital, Default)]
17pub struct LoadMemoryIn {
18    /// Memory accepts the offered read address this edge.
19    pub ar_ready: bool,
20    /// Memory offers a response beat.
21    pub r_valid: bool,
22    /// Exactly one 64-bit operation word.
23    pub r_data: b64,
24    /// AXI read response; only zero (OKAY) is accepted as successful.
25    pub r_resp: b2,
26    /// Required on the single response beat; a missing LAST triggers draining.
27    pub r_last: bool,
28}
29
30/// One aligned 64-bit read at a byte address.
31#[derive(Clone, Copy, Debug, PartialEq, Digital, Default)]
32pub struct LoadMemoryOut {
33    /// Read address offered; retained until ar_ready.
34    pub ar_valid: bool,
35    /// Eight-byte-aligned byte address of the next requested word.
36    pub ar_addr: b64,
37    /// Always zero: exactly one beat per request.
38    pub ar_len: b8,
39    /// Always three: eight bytes per beat.
40    pub ar_size: b3,
41    /// Always one: AXI INCR encoding.
42    pub ar_burst: b2,
43    /// Accepts a response beat, including while draining a malformed burst.
44    pub r_ready: bool,
45}
46
47/// Start is accepted only when `start_ready`; repeated asserted starts are
48/// separate commands. Capacity describes accessible words starting at base.
49#[derive(Clone, Copy, Debug, PartialEq, Digital, Default)]
50pub struct LoadIn {
51    /// Command offered; only start && start_ready admits it.
52    pub start: bool,
53    /// Eight-byte-aligned byte address of the operation buffer.
54    pub base: b64,
55    /// Words to emit; zero finishes without reads and bit31 is rejected.
56    pub count: b32,
57    /// Caller-asserted accessible extent in eight-byte words from base.
58    /// Checked against count, not against an allocator or protection domain.
59    pub capacity: b32,
60    /// AXI64 read responses and address acceptance.
61    pub memory: LoadMemoryIn,
62    /// Consumer accepts the held operation word this edge.
63    pub operation_ready: bool,
64}
65
66/// Done is a one-cycle pulse on success or failure. Fault stays high until
67/// the next accepted start or reset. Emitted counts accepted output words.
68#[derive(Clone, Copy, Debug, PartialEq, Digital, Default)]
69pub struct LoadOut {
70    /// A command can be accepted; false while busy or reset is asserted.
71    pub start_ready: bool,
72    /// An invocation is active, including its completion pulse and error drain.
73    pub busy: bool,
74    /// One-cycle completion pulse after successful emission or a completed error path.
75    pub done: bool,
76    /// Latched bounds/bus/framing error, cleared by the next accepted start or reset.
77    pub fault: bool,
78    /// Words accepted by the stream consumer in this invocation.
79    /// This does not count committed or completed KV operations.
80    pub emitted: b32,
81    /// Single-outstanding AXI64 read request and response readiness.
82    pub memory: LoadMemoryOut,
83    /// An operation is offered; retained until operation_ready.
84    pub operation_valid: bool,
85    /// Fetched word, stable throughout a stalled valid output.
86    pub operation: b64,
87}
88
89#[derive(Clone, Copy, Debug, PartialEq, Digital, Default)]
90/// Finite-transfer registers; output ownership ends only at the consumer handshake.
91pub struct LoadState {
92    /// 0 idle, 1 offer address, 2 await data, 3 hold operation, 4 report done,
93    /// 5 drain a malformed response through LAST.
94    pub phase: b3,
95    /// Byte address of the current read; advances by eight after word consumption.
96    pub address: b64,
97    /// Includes a word already fetched but not yet accepted by the consumer.
98    pub remaining: b32,
99    /// Number of operation-stream handshakes in this invocation.
100    pub emitted: b32,
101    /// Fetched operation retained while the consumer stalls.
102    pub word: b64,
103    /// Error state retained through completion until a new accepted command.
104    pub fault: bool,
105}
106
107/// Finite producer with explicit bounds and backpressure.
108#[derive(Clone, Debug, Synchronous, SynchronousDQ)]
109pub struct Load {
110    state: rhdl_fpga::core::dff::DFF<LoadState>,
111}
112
113impl Default for Load {
114    fn default() -> Self {
115        Self {
116            state: rhdl_fpga::core::dff::DFF::new(LoadState::default()),
117        }
118    }
119}
120
121impl SynchronousIO for Load {
122    type I = LoadIn;
123    type O = LoadOut;
124    type Kernel = load_kernel;
125}
126
127/// Move one word from memory to the consumer before requesting the next.
128/// This deliberately trades overlap for exact bounds and simple ownership:
129/// there is never a second response competing for the one-word output register.
130#[kernel]
131pub fn load_kernel(cr: ClockReset, i: LoadIn, q: LoadQ) -> (LoadOut, LoadD) {
132    let r = q.state;
133    let mut s = r;
134    let mut o = LoadOut {
135        start_ready: r.phase == b3(0),
136        busy: r.phase != b3(0),
137        done: r.phase == b3(4),
138        fault: r.fault,
139        emitted: r.emitted,
140        memory: LoadMemoryOut {
141            ar_valid: false,
142            ar_addr: r.address,
143            ar_len: b8(0),
144            ar_size: b3(3),
145            ar_burst: b2(1),
146            r_ready: false,
147        },
148        operation_valid: false,
149        operation: r.word,
150    };
151    if r.phase == b3(0) && i.start {
152        let bytes = i.count.resize::<64>() << b64(3);
153        // Checking the last accessed byte permits a word at u64::MAX - 7.
154        let last = i.base + bytes - b64(1);
155        let bad_count = (i.count & b32(0x8000_0000)) != b32(0) || i.count > i.capacity;
156        let bad_address = (i.base & b64(7)) != b64(0) || (i.count != b32(0) && last < i.base);
157        s.emitted = b32(0);
158        s.remaining = i.count;
159        s.address = i.base;
160        s.fault = bad_count || bad_address;
161        s.phase = if bad_count || bad_address || i.count == b32(0) {
162            b3(4)
163        } else {
164            b3(1)
165        };
166    }
167    if r.phase == b3(1) {
168        o.memory.ar_valid = true;
169        if i.memory.ar_ready {
170            s.phase = b3(2);
171        }
172    }
173    if r.phase == b3(2) {
174        o.memory.r_ready = true;
175        if i.memory.r_valid {
176            if i.memory.r_resp != b2(0) || !i.memory.r_last {
177                s.fault = true;
178                s.phase = if i.memory.r_last { b3(4) } else { b3(5) };
179            } else {
180                s.word = i.memory.r_data;
181                s.phase = b3(3);
182            }
183        }
184    }
185    // A missing LAST is a framing fault. Drain the malformed response before
186    // allowing another command, so trailing beats cannot become its data.
187    if r.phase == b3(5) {
188        o.memory.r_ready = true;
189        if i.memory.r_valid && i.memory.r_last {
190            s.phase = b3(4);
191        }
192    }
193    if r.phase == b3(3) {
194        // Fetching is not completion. Keep word and counters unchanged until
195        // the consumer accepts this operation, even if it stalls indefinitely.
196        o.operation_valid = true;
197        if i.operation_ready {
198            s.emitted = r.emitted + b32(1);
199            s.remaining = r.remaining - b32(1);
200            if r.remaining == b32(1) {
201                s.phase = b3(4);
202            } else {
203                s.address = r.address + b64(8);
204                s.phase = b3(1);
205            }
206        }
207    }
208    if r.phase == b3(4) {
209        s.phase = b3(0);
210    }
211    if cr.reset.any() {
212        // The DFF resets internal state. Gate visible handshakes separately so
213        // neighbours cannot accept an old request on the resetting edge.
214        o.start_ready = false;
215        o.memory.ar_valid = false;
216        o.memory.r_ready = false;
217        o.operation_valid = false;
218        o.done = false;
219    }
220    (o, LoadD { state: s })
221}
222
223#[cfg(test)]
224#[path = "load_tests.rs"]
225mod tests;