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;