logicaffeine_compile/spatial/decode_engine/rtl/
stream.rs1use std::fmt::Write;
20
21use super::super::image::{crc32c, Image, ImageLayout, CHECK_BYTES};
22
23pub const BEAT_BYTES: u64 = 64;
25pub const AW: u32 = 27;
28pub const LW: u32 = 32;
29pub const BYW: u32 = 24;
30pub const TW: u32 = 16;
31pub const ROWW: u32 = 32;
32const GATHER_BYTES: u64 = 128;
33
34#[derive(Clone, Copy, Debug, PartialEq, Eq)]
36pub struct TileDescriptor {
37 pub line: u64,
38 pub lines: u64,
39 pub bytes: u64,
40 pub tensor: u32,
41 pub first_row: u64,
42}
43
44#[derive(Clone, Copy, Debug, PartialEq, Eq)]
47pub struct Beat {
48 pub data: [u8; BEAT_BYTES as usize],
49 pub keep: u64,
50 pub last: bool,
51 pub tensor: u32,
52 pub row: u64,
53 pub offset: u64,
54 pub crc_error: bool,
55}
56
57pub fn descriptors(image: &Image, channel: u32) -> Vec<TileDescriptor> {
59 image
60 .tiles
61 .iter()
62 .filter(|t| t.channel == channel)
63 .map(|t| TileDescriptor {
64 line: t.line,
65 lines: t.lines,
66 bytes: (t.end_row - t.first_row) * image.tensors[t.tensor].image_row_bytes(),
67 tensor: t.tensor as u32,
68 first_row: t.first_row,
69 })
70 .collect()
71}
72
73pub fn beats(channel: &[u8], d: &TileDescriptor, layout: &ImageLayout) -> Vec<Beat> {
75 let lb = layout.line_bytes as usize;
76 let span = &channel[d.line as usize * lb..(d.line + d.lines) as usize * lb];
77 let crc_error = layout.check && {
78 let body = span.len() - CHECK_BYTES as usize;
79 crc32c(&span[..body]).to_le_bytes() != span[body..]
80 };
81 let rows = &span[..d.bytes as usize];
82 let count = rows.len().div_ceil(BEAT_BYTES as usize);
83 rows.chunks(BEAT_BYTES as usize)
84 .enumerate()
85 .map(|(k, chunk)| {
86 let mut data = [0u8; BEAT_BYTES as usize];
87 data[..chunk.len()].copy_from_slice(chunk);
88 let last = k + 1 == count;
89 Beat {
90 data,
91 keep: chunk.len() as u64,
92 last,
93 tensor: d.tensor,
94 row: d.first_row,
95 offset: k as u64 * BEAT_BYTES,
96 crc_error: last && crc_error,
97 }
98 })
99 .collect()
100}
101
102pub fn crc32c_update(mut state: u32, bytes: &[u8]) -> u32 {
105 for &b in bytes {
106 state ^= b as u32;
107 for _ in 0..8 {
108 state = (state >> 1) ^ (0x82F6_3B78 & (state & 1).wrapping_neg());
109 }
110 }
111 state
112}
113
114pub fn crc_masks(len: usize) -> Vec<(u32, Vec<bool>)> {
117 let zeros = vec![0u8; len];
118 let from_state: Vec<u32> = (0..32).map(|j| crc32c_update(1 << j, &zeros)).collect();
119 let from_data: Vec<u32> = (0..8 * len)
120 .map(|k| {
121 let mut d = zeros.clone();
122 d[k / 8] = 1 << (k % 8);
123 crc32c_update(0, &d)
124 })
125 .collect();
126 (0..32)
127 .map(|i| {
128 let state = (0..32).filter(|&j| from_state[j] >> i & 1 == 1).fold(0u32, |m, j| m | 1 << j);
129 (state, from_data.iter().map(|&c| c >> i & 1 == 1).collect())
130 })
131 .collect()
132}
133
134fn hex_mask(bits: &[bool]) -> String {
135 let digits = bits.len().div_ceil(4);
136 (0..digits)
137 .rev()
138 .map(|d| {
139 let nibble = (0..4).fold(0u8, |v, b| v | (u8::from(bits.get(4 * d + b).copied().unwrap_or(false)) << b));
140 char::from_digit(nibble as u32, 16).unwrap()
141 })
142 .collect()
143}
144
145pub fn verilog(layout: &ImageLayout) -> String {
147 let lb = layout.line_bytes;
148 assert!(lb % 8 == 0 && lb >= BEAT_BYTES && lb + 56 <= GATHER_BYTES, "a line of {lb} bytes");
149 let dw = 8 * lb;
150 let mut v = String::new();
151 let _ = writeln!(
152 v,
153 "// The image stream engine for {lb}-byte lines{}: tile descriptors in, the tile's lines asked of the channel as one\n\
154 // read descriptor, its rows out as 64-byte beats tagged with tensor, first row and byte offset.",
155 if layout.check { " with a CRC-32C check word ending every tile" } else { "" }
156 );
157 let _ = writeln!(
158 v,
159 "module logos_image_stream (
160 input clk,
161 input rst_n,
162 input tile_valid,
163 output tile_ready,
164 input [{a}:0] tile_line,
165 input [{l}:0] tile_lines,
166 input [{b}:0] tile_bytes,
167 input [{t}:0] tile_tensor,
168 input [{r}:0] tile_row,
169 output md_valid,
170 input md_ready,
171 output [{m}:0] md_data,
172 input mr_valid,
173 output mr_ready,
174 input [{d}:0] mr_data,
175 output out_valid,
176 input out_ready,
177 output [511:0] out_data,
178 output [6:0] out_keep,
179 output out_last,
180 output [{t}:0] out_tensor,
181 output [{r}:0] out_row,
182 output [{b}:0] out_offset,
183 output out_crc_error
184);",
185 a = AW - 1,
186 l = LW - 1,
187 b = BYW - 1,
188 t = TW - 1,
189 r = ROWW - 1,
190 m = AW + LW,
191 d = dw - 1
192 );
193 let _ = writeln!(
194 v,
195 " // The tiles whose lines are asked for, oldest first, in registers.
196 (* ramstyle = \"logic\" *) reg [{l}:0] q_lines [0:3];
197 (* ramstyle = \"logic\" *) reg [{b}:0] q_bytes [0:3];
198 (* ramstyle = \"logic\" *) reg [{t}:0] q_tensor [0:3];
199 (* ramstyle = \"logic\" *) reg [{r}:0] q_row [0:3];
200 reg [2:0] qh, qt;
201 wire q_full = (qt - qh) == 3'd4;
202 wire q_some = qt != qh;
203 assign md_valid = tile_valid && !q_full;
204 assign tile_ready = md_ready && !q_full;
205 assign md_data = {{1'b0, tile_line, tile_lines}};
206 wire tile_take = tile_valid && tile_ready;
207
208 // The head tile: its lines still to take and row bytes still to send, each with its flag (none left, the last beat)
209 // in a register beside it; row bytes sent; the gather register and its bytes; the CRC register; the verdict. The head's
210 // counts are loaded from its queue entry, or from the descriptor taken the clock it becomes the head.
211 reg [{l}:0] lines_left;
212 reg [{b}:0] pay_left;
213 reg no_lines, final_beat;
214 reg [{b}:0] sent;
215 reg [7:0] n;
216 reg [1023:0] acc;
217 reg [31:0] crc;
218 reg bad;
219 wire can_emit = q_some && (final_beat ? no_lines : n[7] | n[6]);
220 wire emit = can_emit && out_ready;
221 wire shift = emit && !final_beat;
222 wire retire = emit && final_beat;
223 wire [7:0] n1 = shift ? n - 8'd64 : n;
224 // Shifting takes 64 from both the gathered bytes and the bytes left, so whether the register holds the rest is told
225 // before the shift; room for a line is told on the bytes after it.
226 wire holds_rest = {{{pad}'d0, n}} >= pay_left;
227 wire room = (shift ? n <= 8'd{room_shift} : n <= 8'd{room_hold}) || holds_rest;
228 wire take = q_some && !no_lines && mr_valid && room;
229 wire place = take && !holds_rest;
230 wire last_line = lines_left == {lw}'d1;
231 // The next head's counts: the entry after the head's when it is queued, else the descriptor taken now.
232 wire [2:0] qn = qh + 3'd1;
233 wire next_queued = qt != qn;
234 wire [{l}:0] next_lines = next_queued ? q_lines[qn[1:0]] : tile_lines;
235 wire [{b}:0] next_bytes = next_queued ? q_bytes[qn[1:0]] : tile_bytes;
236 wire load_next = retire && (next_queued || tile_take);
237 wire load_new = !q_some && tile_take;
238 assign mr_ready = take;
239 wire [1023:0] shifted = shift ? {{512'd0, acc[1023:512]}} : acc;
240 wire [1023:0] line_at = {{{rest}'d0, mr_data}} << {{n1[6:3], 6'd0}};",
241 l = LW - 1,
242 b = BYW - 1,
243 t = TW - 1,
244 r = ROWW - 1,
245 lw = LW,
246 pad = BYW - 8,
247 room_shift = 192 - lb,
248 room_hold = 128 - lb,
249 rest = 1024 - dw
250 );
251 if layout.check {
252 let full = crc_masks(lb as usize);
253 let last = crc_masks((lb - CHECK_BYTES) as usize);
254 let body = 8 * (lb - CHECK_BYTES);
255 let _ = writeln!(v, " // The CRC register after a whole line, and after a last line's bytes before its check word.");
256 let _ = writeln!(v, " wire [31:0] crc_full, crc_last;");
257 for (i, (state, data)) in full.iter().enumerate() {
258 let _ = writeln!(v, " assign crc_full[{i}] = ^(crc & 32'h{state:08x}) ^ ^(mr_data & {dw}'h{});", hex_mask(data));
259 }
260 for (i, (state, data)) in last.iter().enumerate() {
261 let _ = writeln!(
262 v,
263 " assign crc_last[{i}] = ^(crc & 32'h{state:08x}) ^ ^(mr_data[{}:0] & {body}'h{});",
264 body - 1,
265 hex_mask(data)
266 );
267 }
268 let _ = writeln!(v, " wire [31:0] check_word = mr_data[{}:{}];", dw - 1, body);
269 }
270 let _ = writeln!(
271 v,
272 " wire [511:0] keep_mask;
273 genvar i;
274 generate for (i = 0; i < 64; i = i + 1) begin : keep_bytes
275 assign keep_mask[8*i +: 8] = {{8{{i < out_keep}}}};
276 end endgenerate
277 assign out_valid = can_emit;
278 assign out_keep = final_beat ? pay_left[6:0] : 7'd64;
279 assign out_last = final_beat;
280 assign out_data = acc[511:0] & keep_mask;
281 assign out_tensor = q_tensor[qh[1:0]];
282 assign out_row = q_row[qh[1:0]];
283 assign out_offset = sent;
284 assign out_crc_error = {crc_error};
285
286 always @(posedge clk) begin
287 if (!rst_n) begin
288 qh <= 3'd0;
289 qt <= 3'd0;
290 lines_left <= {lw}'d0;
291 pay_left <= {byw}'d0;
292 no_lines <= 1'b1;
293 final_beat <= 1'b1;
294 sent <= {byw}'d0;
295 n <= 8'd0;
296 acc <= 1024'd0;
297 crc <= 32'hffffffff;
298 bad <= 1'b0;
299 end else begin
300 if (tile_take) begin
301 q_lines[qt[1:0]] <= tile_lines;
302 q_bytes[qt[1:0]] <= tile_bytes;
303 q_tensor[qt[1:0]] <= tile_tensor;
304 q_row[qt[1:0]] <= tile_row;
305 qt <= qt + 3'd1;
306 end
307 if (load_next || load_new) begin
308 lines_left <= load_next ? next_lines : tile_lines;
309 pay_left <= load_next ? next_bytes : tile_bytes;
310 no_lines <= (load_next ? next_lines : tile_lines) == {lw}'d0;
311 final_beat <= (load_next ? next_bytes : tile_bytes) <= {byw}'d64;
312 end else begin
313 if (take) begin lines_left <= lines_left - {lw}'d1; no_lines <= last_line; end
314 if (shift) begin pay_left <= pay_left - {byw}'d64; final_beat <= pay_left <= {byw}'d128; end
315 end
316 if (retire) begin
317 qh <= qh + 3'd1;
318 sent <= {byw}'d0;
319 n <= 8'd0;
320 acc <= 1024'd0;
321 crc <= 32'hffffffff;
322 bad <= 1'b0;
323 end else begin
324 if (shift) sent <= sent + {byw}'d64;
325 n <= n1 + (place ? 8'd{lb} : 8'd0);
326 acc <= place ? (shifted | line_at) : shifted;{update}
327 end
328 end
329 end
330endmodule",
331 lw = LW,
332 byw = BYW,
333 lb = lb,
334 crc_error = if layout.check { "final_beat && bad" } else { "1'b0" },
335 update = if layout.check {
336 "\n if (take) crc <= crc_full;\n if (take && last_line) bad <= check_word != ~crc_last;"
337 } else {
338 ""
339 }
340 );
341 v
342}