1use super::SpatialLowerError;
22use crate::spatial::ir::SpatialProgram;
23use crate::spatial::lower::fabric::{host_word, VerilogArtifact};
24
25#[derive(Clone, Copy, Debug, PartialEq, Eq)]
27pub struct DecodeWords {
28 pub tok: (usize, usize),
29 pub pos: (usize, usize),
30 pub next: (usize, usize),
31}
32
33impl DecodeWords {
34 pub fn of(p: &SpatialProgram, tok: &str, pos: &str, next: &str) -> Result<DecodeWords, SpatialLowerError> {
36 let word = |name: &str| -> Result<(usize, usize), SpatialLowerError> {
37 let t = p
38 .tensors
39 .iter()
40 .find(|t| t.name == name)
41 .ok_or(SpatialLowerError::Unsupported("a decode step without its token, position or next-token tensor"))?;
42 Ok((t.id.idx(), host_word(p, t, 0, 0)))
43 };
44 Ok(DecodeWords { tok: word(tok)?, pos: word(pos)?, next: word(next)? })
45 }
46}
47
48pub fn generate_cycles(step_cycles: &[u64], lat: u64) -> u64 {
53 step_cycles.iter().map(|c| c + 13 + lat).sum()
54}
55
56#[derive(Clone, Copy, Debug, PartialEq, Eq)]
59pub struct CoreClock {
60 pub mult: u32,
61 pub div: u32,
62 pub out: u32,
63}
64
65impl CoreClock {
66 pub fn vco_mhz(self) -> f64 {
67 100.0 * self.mult as f64 / self.div as f64
68 }
69
70 pub fn mhz(self) -> f64 {
71 self.vco_mhz() / self.out as f64
72 }
73
74 pub fn legal(self) -> bool {
77 let pfd = 100.0 / self.div as f64;
78 (2..=64).contains(&self.mult)
79 && (1..=56).contains(&self.div)
80 && (1..=128).contains(&self.out)
81 && (19.0..=450.0).contains(&pfd)
82 && (800.0..=1600.0).contains(&self.vco_mhz())
83 }
84
85 pub fn fastest_within(period_ns: f64) -> Option<CoreClock> {
88 let mut best: Option<CoreClock> = None;
89 for div in 1..=56 {
90 for mult in 2..=64 {
91 for out in 1..=128 {
92 let c = CoreClock { mult, div, out };
93 if !c.legal() || 1000.0 / c.mhz() < period_ns {
94 continue;
95 }
96 let better = match best {
97 None => true,
98 Some(b) => c.mhz() > b.mhz() + 1e-9 || ((c.mhz() - b.mhz()).abs() <= 1e-9 && (c.div, c.vco_mhz() as u64) < (b.div, b.vco_mhz() as u64)),
99 };
100 if better {
101 best = Some(c);
102 }
103 }
104 }
105 }
106 best
107 }
108
109 pub const UNCERTAINTY_NS: f64 = 0.5;
111
112 pub fn for_route(fmax_mhz: f64, residual_ns: f64) -> Option<CoreClock> {
116 CoreClock::fastest_within(1000.0 / fmax_mhz + residual_ns + CoreClock::UNCERTAINTY_NS)
117 }
118}
119
120pub fn clks_per_bit(clock: CoreClock) -> u32 {
122 (clock.mhz() * 1e6 / 115_200.0).round() as u32
123}
124
125pub fn uart_verilog() -> String {
128 String::from(
129 "module uart_rx #(parameter CLKS_PER_BIT = 868) (input clk, input rst, input rx, output reg valid, output reg [7:0] data);
130 reg [1:0] sync = 2'b11;
131 always @(posedge clk) sync <= {sync[0], rx};
132 wire line = sync[1];
133 reg [1:0] st; reg [15:0] cnt; reg [2:0] bit_i; reg [7:0] sh;
134 always @(posedge clk) begin
135 valid <= 1'b0;
136 if (rst) begin st <= 2'd0; cnt <= 16'd0; bit_i <= 3'd0; end
137 else case (st)
138 2'd0: if (!line) begin st <= 2'd1; cnt <= 16'd0; end
139 2'd1: if (cnt == CLKS_PER_BIT / 2 - 1) begin cnt <= 16'd0; bit_i <= 3'd0; st <= line ? 2'd0 : 2'd2; end else cnt <= cnt + 16'd1;
140 2'd2: if (cnt == CLKS_PER_BIT - 1) begin cnt <= 16'd0; sh <= {line, sh[7:1]}; if (bit_i == 3'd7) st <= 2'd3; else bit_i <= bit_i + 3'd1; end else cnt <= cnt + 16'd1;
141 2'd3: if (cnt == CLKS_PER_BIT - 1) begin cnt <= 16'd0; st <= 2'd0; if (line) begin valid <= 1'b1; data <= sh; end end else cnt <= cnt + 16'd1;
142 endcase
143 end
144endmodule
145module uart_tx #(parameter CLKS_PER_BIT = 868) (input clk, input rst, input go, input [7:0] data, output reg tx, output busy);
146 reg [1:0] st; reg [15:0] cnt; reg [2:0] bit_i; reg [7:0] sh;
147 assign busy = st != 2'd0;
148 always @(posedge clk) begin
149 if (rst) begin st <= 2'd0; tx <= 1'b1; cnt <= 16'd0; bit_i <= 3'd0; end
150 else case (st)
151 2'd0: begin tx <= 1'b1; if (go) begin sh <= data; st <= 2'd1; cnt <= 16'd0; end end
152 2'd1: begin tx <= 1'b0; if (cnt == CLKS_PER_BIT - 1) begin cnt <= 16'd0; bit_i <= 3'd0; st <= 2'd2; end else cnt <= cnt + 16'd1; end
153 2'd2: begin tx <= sh[bit_i]; if (cnt == CLKS_PER_BIT - 1) begin cnt <= 16'd0; if (bit_i == 3'd7) st <= 2'd3; else bit_i <= bit_i + 3'd1; end else cnt <= cnt + 16'd1; end
154 2'd3: begin tx <= 1'b1; if (cnt == CLKS_PER_BIT - 1) begin cnt <= 16'd0; st <= 2'd0; end else cnt <= cnt + 16'd1; end
155 endcase
156 end
157endmodule
158",
159 )
160}
161
162const REPLY_QUEUE: &str = " // The serial line and the reply queue it drains.
165 wire rx_v; wire [7:0] rx_b; wire tx_busy; reg tx_go; reg [7:0] tx_byte;
166 uart_rx #(CLKS_PER_BIT) urx(.clk(clk), .rst(rst), .rx(uart_rx), .valid(rx_v), .data(rx_b));
167 uart_tx #(CLKS_PER_BIT) utx(.clk(clk), .rst(rst), .go(tx_go), .data(tx_byte), .tx(uart_tx), .busy(tx_busy));
168 (* ram_style = \"registers\" *) reg [7:0] txq [0:127];
169 reg [6:0] tq_w, tq_r; reg [7:0] tq_n; reg q_push; reg [7:0] q_byte;
170 wire q_room = tq_n < 8'd127;
171 wire q_pop = !tx_busy && !tx_go && tq_n != 8'd0;
172 always @(posedge clk) begin
173 tx_go <= 1'b0;
174 if (rst) begin tq_w <= 7'd0; tq_r <= 7'd0; tq_n <= 8'd0; end
175 else begin
176 if (q_push) begin txq[tq_w] <= q_byte; tq_w <= tq_w + 7'd1; end
177 if (q_pop) begin tx_go <= 1'b1; tx_byte <= txq[tq_r]; tq_r <= tq_r + 7'd1; end
178 tq_n <= tq_n + (q_push ? 8'd1 : 8'd0) - (q_pop ? 8'd1 : 8'd0);
179 end
180 end
181";
182
183pub fn decode_board_verilog(w: DecodeWords, lat: u64) -> String {
186 let mut s = uart_verilog();
187 s.push_str(&format!(
188 "module decode_board #(parameter CLKS_PER_BIT = 868) (input clk, input rst, input uart_rx, output uart_tx, output [7:0] led);
189 localparam LAT = {lat};
190 localparam [15:0] TOK_T = 16'd{tok_t}, POS_T = 16'd{pos_t}, NEXT_T = 16'd{next_t};
191 localparam [31:0] TOK_W = 32'd{tok_w}, POS_W = 32'd{pos_w}, NEXT_W = 32'd{next_w};
192 // The step.
193 reg f_rst, f_start; wire f_done; wire [31:0] f_cycles;
194 reg h_we, h_re; reg [15:0] h_tensor; reg [31:0] h_addr; reg [63:0] h_wdata; wire [63:0] h_rdata;
195 spatial_top fab(.clk(clk), .rst(rst | f_rst), .start(f_start), .done(f_done), .cycles(f_cycles), .host_we(h_we), .host_re(h_re),
196 .host_site(16'd0), .host_tensor(h_tensor), .host_addr(h_addr), .host_wdata(h_wdata), .host_rdata(h_rdata));
197",
198 tok_t = w.tok.0,
199 tok_w = w.tok.1,
200 pos_t = w.pos.0,
201 pos_w = w.pos.1,
202 next_t = w.next.0,
203 next_w = w.next.1,
204 ));
205 s.push_str(REPLY_QUEUE);
206 s.push_str(&format!(
207 " // The protocol.
208 localparam C_IDLE = 5'd0, C_ARGS = 5'd1, C_EXEC = 5'd2, C_REPLY = 5'd3, C_WROTE = 5'd4, C_READW = 5'd5,
209 ST_WTOK = 5'd6, ST_WPOS = 5'd7, ST_RST = 5'd8, ST_START = 5'd9, ST_WAIT = 5'd10, ST_READ = 5'd11, ST_READW = 5'd12,
210 S_DONE = 5'd13, G_LOOP = 5'd14, G_TOK = 5'd15;
211 reg [4:0] st, ret, rret; reg [7:0] cmd; reg [3:0] argi, argc; reg [7:0] args [0:13];
212 reg [7:0] rb [0:8]; reg [3:0] ri, rlen; reg [3:0] lcnt;
213 reg [15:0] tok_r, pos_r, next_r, gen_n; reg [31:0] step_cycles, g_cycles; reg g_on;
214 wire [15:0] arg16_0 = {{args[1], args[0]}}, arg16_2 = {{args[3], args[2]}};
215 wire [31:0] arg32_2 = {{args[5], args[4], args[3], args[2]}};
216 wire [63:0] arg64_6 = {{args[13], args[12], args[11], args[10], args[9], args[8], args[7], args[6]}};
217 function [3:0] nargs(input [7:0] c);
218 case (c) 8'h57: nargs = 4'd14; 8'h52: nargs = 4'd6; 8'h53: nargs = 4'd4; 8'h47: nargs = 4'd2; default: nargs = 4'd0; endcase
219 endfunction
220 assign led = {{g_on, st[4:0], tq_n != 8'd0, f_done}};
221 always @(posedge clk) begin
222 q_push <= 1'b0; h_we <= 1'b0; f_rst <= 1'b0; f_start <= 1'b0;
223 if (g_on) g_cycles <= g_cycles + 32'd1;
224 if (rst) begin st <= C_IDLE; h_re <= 1'b0; g_on <= 1'b0; tok_r <= 16'd0; pos_r <= 16'd0; end
225 else case (st)
226 C_IDLE: if (rx_v) begin
227 cmd <= rx_b; argi <= 4'd0; argc <= nargs(rx_b);
228 st <= (nargs(rx_b) == 4'd0) ? C_EXEC : C_ARGS;
229 end
230 C_ARGS: if (rx_v) begin
231 args[argi] <= rx_b; argi <= argi + 4'd1;
232 if (argi + 4'd1 == argc) st <= C_EXEC;
233 end
234 C_EXEC: case (cmd)
235 8'h49: begin rb[0] <= 8'h69; rb[1] <= 8'h4c; rb[2] <= 8'h4f; rb[3] <= 8'h47; rb[4] <= 8'h53; rb[5] <= LAT;
236 rlen <= 4'd6; ri <= 4'd0; rret <= C_IDLE; st <= C_REPLY; end
237 8'h57: begin h_we <= 1'b1; h_tensor <= arg16_0; h_addr <= arg32_2; h_wdata <= arg64_6; st <= C_WROTE; end
238 8'h52: begin h_re <= 1'b1; h_tensor <= arg16_0; h_addr <= arg32_2; lcnt <= LAT; st <= C_READW; end
239 8'h53: begin tok_r <= arg16_0; pos_r <= arg16_2; ret <= S_DONE; st <= ST_WTOK; end
240 8'h47: begin gen_n <= arg16_0; g_cycles <= 32'd0; g_on <= 1'b1; st <= G_LOOP; end
241 default: begin rb[0] <= 8'h3f; rlen <= 4'd1; ri <= 4'd0; rret <= C_IDLE; st <= C_REPLY; end
242 endcase
243 C_REPLY: if (ri == rlen) st <= rret;
244 else if (q_room && !q_push) begin q_push <= 1'b1; q_byte <= rb[ri]; ri <= ri + 4'd1; end
245 C_WROTE: begin rb[0] <= 8'h6b; rlen <= 4'd1; ri <= 4'd0; rret <= C_IDLE; st <= C_REPLY; end
246 C_READW: if (lcnt == 4'd0) begin
247 h_re <= 1'b0; rb[0] <= 8'h72;
248 rb[1] <= h_rdata[7:0]; rb[2] <= h_rdata[15:8]; rb[3] <= h_rdata[23:16]; rb[4] <= h_rdata[31:24];
249 rb[5] <= h_rdata[39:32]; rb[6] <= h_rdata[47:40]; rb[7] <= h_rdata[55:48]; rb[8] <= h_rdata[63:56];
250 rlen <= 4'd9; ri <= 4'd0; rret <= C_IDLE; st <= C_REPLY;
251 end else lcnt <= lcnt - 4'd1;
252 // One step: the token and the position in, a reset, a start, the run, the next token out.
253 ST_WTOK: begin h_we <= 1'b1; h_tensor <= TOK_T; h_addr <= TOK_W; h_wdata <= {{48'd0, tok_r}}; st <= ST_WPOS; end
254 ST_WPOS: begin h_we <= 1'b1; h_tensor <= POS_T; h_addr <= POS_W; h_wdata <= {{48'd0, pos_r}}; st <= ST_RST; end
255 ST_RST: begin f_rst <= 1'b1; st <= ST_START; end
256 ST_START: begin f_start <= 1'b1; st <= ST_WAIT; end
257 ST_WAIT: if (f_done && !f_start) begin step_cycles <= f_cycles; st <= ST_READ; end
258 ST_READ: begin h_re <= 1'b1; h_tensor <= NEXT_T; h_addr <= NEXT_W; lcnt <= LAT; st <= ST_READW; end
259 ST_READW: if (lcnt == 4'd0) begin h_re <= 1'b0; next_r <= h_rdata[15:0]; st <= ret; end else lcnt <= lcnt - 4'd1;
260 S_DONE: begin
261 tok_r <= next_r; pos_r <= pos_r + 16'd1;
262 rb[0] <= 8'h73; rb[1] <= next_r[7:0]; rb[2] <= next_r[15:8];
263 rb[3] <= step_cycles[7:0]; rb[4] <= step_cycles[15:8]; rb[5] <= step_cycles[23:16]; rb[6] <= step_cycles[31:24];
264 rlen <= 4'd7; ri <= 4'd0; rret <= C_IDLE; st <= C_REPLY;
265 end
266 // Generation: each step's token is the one before it produced, its position the next.
267 G_LOOP: if (gen_n == 16'd0) begin
268 g_on <= 1'b0; rb[0] <= 8'h65;
269 rb[1] <= g_cycles[7:0]; rb[2] <= g_cycles[15:8]; rb[3] <= g_cycles[23:16]; rb[4] <= g_cycles[31:24];
270 rlen <= 4'd5; ri <= 4'd0; rret <= C_IDLE; st <= C_REPLY;
271 end else begin ret <= G_TOK; st <= ST_WTOK; end
272 G_TOK: begin
273 rb[0] <= next_r[7:0]; rb[1] <= next_r[15:8]; rlen <= 4'd2; ri <= 4'd0;
274 tok_r <= next_r; pos_r <= pos_r + 16'd1; gen_n <= gen_n - 16'd1; rret <= G_LOOP; st <= C_REPLY;
275 end
276 default: st <= C_IDLE;
277 endcase
278 end
279endmodule
280"
281 ));
282 s
283}
284
285pub fn chip_board_verilog(lat: u64) -> String {
300 let mut s = uart_verilog();
301 s.push_str(&format!(
302 "module chip_board #(parameter CLKS_PER_BIT = 868) (input clk, input rst, input uart_rx, output uart_tx, output [7:0] led);
303 localparam LAT = {lat};
304 // The chip's program.
305 reg f_rst, f_start, f_go; wire f_done, f_halted; wire [15:0] f_halt_at; wire [31:0] f_cycles;
306 reg h_we, h_re; reg [15:0] h_tensor; reg [31:0] h_addr; reg [63:0] h_wdata; wire [63:0] h_rdata;
307 spatial_top fab(.clk(clk), .rst(rst | f_rst), .start(f_start), .done(f_done), .cycles(f_cycles), .host_we(h_we), .host_re(h_re),
308 .host_site(16'd0), .host_tensor(h_tensor), .host_addr(h_addr), .host_wdata(h_wdata), .host_rdata(h_rdata),
309 .link_go(f_go), .halted(f_halted), .halt_at(f_halt_at));
310"
311 ));
312 s.push_str(REPLY_QUEUE);
313 s.push_str(
314 " // The protocol.
315 localparam C_IDLE = 4'd0, C_ARGS = 4'd1, C_EXEC = 4'd2, C_REPLY = 4'd3, C_WROTE = 4'd4, C_READW = 4'd5,
316 X_START = 4'd6, X_LEAVE = 4'd7, X_WAIT = 4'd8;
317 reg [3:0] st, rret; reg [7:0] cmd; reg [3:0] argi, argc; reg [7:0] args [0:13];
318 reg [7:0] rb [0:8]; reg [3:0] ri, rlen; reg [3:0] lcnt; reg x_on;
319 wire [15:0] arg16_0 = {args[1], args[0]};
320 wire [31:0] arg32_2 = {args[5], args[4], args[3], args[2]};
321 wire [63:0] arg64_6 = {args[13], args[12], args[11], args[10], args[9], args[8], args[7], args[6]};
322 function [3:0] nargs(input [7:0] c);
323 case (c) 8'h57: nargs = 4'd14; 8'h52: nargs = 4'd6; default: nargs = 4'd0; endcase
324 endfunction
325 assign led = {x_on, f_halted, st[3:0], tq_n != 8'd0, f_done};
326 always @(posedge clk) begin
327 q_push <= 1'b0; h_we <= 1'b0; f_rst <= 1'b0; f_start <= 1'b0; f_go <= 1'b0;
328 if (rst) begin st <= C_IDLE; h_re <= 1'b0; x_on <= 1'b0; end
329 else case (st)
330 C_IDLE: if (rx_v) begin
331 cmd <= rx_b; argi <= 4'd0; argc <= nargs(rx_b);
332 st <= (nargs(rx_b) == 4'd0) ? C_EXEC : C_ARGS;
333 end
334 C_ARGS: if (rx_v) begin
335 args[argi] <= rx_b; argi <= argi + 4'd1;
336 if (argi + 4'd1 == argc) st <= C_EXEC;
337 end
338 C_EXEC: case (cmd)
339 8'h49: begin rb[0] <= 8'h69; rb[1] <= 8'h4c; rb[2] <= 8'h4f; rb[3] <= 8'h47; rb[4] <= 8'h53; rb[5] <= LAT;
340 rlen <= 4'd6; ri <= 4'd0; rret <= C_IDLE; st <= C_REPLY; end
341 8'h57: begin h_we <= 1'b1; h_tensor <= arg16_0; h_addr <= arg32_2; h_wdata <= arg64_6; st <= C_WROTE; end
342 8'h52: begin h_re <= 1'b1; h_tensor <= arg16_0; h_addr <= arg32_2; lcnt <= LAT; st <= C_READW; end
343 // A step starts with a reset and a start; a halted step continues past its halt and leaves it.
344 8'h58: if (!x_on) begin f_rst <= 1'b1; st <= X_START; end else begin f_go <= 1'b1; st <= X_LEAVE; end
345 default: begin rb[0] <= 8'h3f; rlen <= 4'd1; ri <= 4'd0; rret <= C_IDLE; st <= C_REPLY; end
346 endcase
347 C_REPLY: if (ri == rlen) st <= rret;
348 else if (q_room && !q_push) begin q_push <= 1'b1; q_byte <= rb[ri]; ri <= ri + 4'd1; end
349 C_WROTE: begin rb[0] <= 8'h6b; rlen <= 4'd1; ri <= 4'd0; rret <= C_IDLE; st <= C_REPLY; end
350 C_READW: if (lcnt == 4'd0) begin
351 h_re <= 1'b0; rb[0] <= 8'h72;
352 rb[1] <= h_rdata[7:0]; rb[2] <= h_rdata[15:8]; rb[3] <= h_rdata[23:16]; rb[4] <= h_rdata[31:24];
353 rb[5] <= h_rdata[39:32]; rb[6] <= h_rdata[47:40]; rb[7] <= h_rdata[55:48]; rb[8] <= h_rdata[63:56];
354 rlen <= 4'd9; ri <= 4'd0; rret <= C_IDLE; st <= C_REPLY;
355 end else lcnt <= lcnt - 4'd1;
356 X_START: begin f_start <= 1'b1; x_on <= 1'b1; st <= X_WAIT; end
357 X_LEAVE: if (!f_halted) st <= X_WAIT;
358 // Halted again, or finished: the link (0xFFFF at the end) and the cycles run.
359 X_WAIT: if (f_done && !f_start) begin
360 x_on <= 1'b0; rb[0] <= 8'h78; rb[1] <= 8'hff; rb[2] <= 8'hff;
361 rb[3] <= f_cycles[7:0]; rb[4] <= f_cycles[15:8]; rb[5] <= f_cycles[23:16]; rb[6] <= f_cycles[31:24];
362 rlen <= 4'd7; ri <= 4'd0; rret <= C_IDLE; st <= C_REPLY;
363 end else if (f_halted) begin
364 rb[0] <= 8'h78; rb[1] <= f_halt_at[7:0]; rb[2] <= f_halt_at[15:8];
365 rb[3] <= f_cycles[7:0]; rb[4] <= f_cycles[15:8]; rb[5] <= f_cycles[23:16]; rb[6] <= f_cycles[31:24];
366 rlen <= 4'd7; ri <= 4'd0; rret <= C_IDLE; st <= C_REPLY;
367 end
368 default: st <= C_IDLE;
369 endcase
370 end
371endmodule
372",
373 );
374 s
375}
376
377pub fn chip_record(p: &SpatialProgram, target: &crate::spatial::target::SpatialTarget) -> Option<serde_json::Value> {
383 let segments = super::fabric::fabric_cycle_segments(p, target)?;
384 let words = |name: &str| -> Option<serde_json::Value> {
385 let t = p.tensors.iter().find(|t| t.name == name)?;
386 let w: Vec<usize> = (0..t.elems.rows * t.elems.cols).map(|i| host_word(p, t, i / t.elems.cols, i % t.elems.cols)).collect();
387 Some(serde_json::json!({ "name": name, "tensor": t.id.idx(), "words": w }))
388 };
389 let named = |ids: &[crate::spatial::ir::TensorId]| -> Vec<serde_json::Value> {
390 ids.iter().filter_map(|t| words(&p.tensor(*t).name)).collect()
391 };
392 let links: Vec<serde_json::Value> = p
393 .links
394 .iter()
395 .map(|l| {
396 serde_json::json!({
397 "after": l.after,
398 "halts": l.after.is_some_and(|k| k + 1 < p.sections.len()),
399 "sends": named(&l.sends),
400 "receives": named(&l.receives),
401 })
402 })
403 .collect();
404 let inputs: Vec<serde_json::Value> = ["tok", "pos"].iter().filter_map(|n| words(n)).collect();
405 Some(serde_json::json!({
406 "segments": segments,
407 "links": links,
408 "inputs": inputs,
409 "next": words("next").unwrap_or(serde_json::Value::Null),
410 }))
411}
412
413pub fn relay_record(relay: &[crate::spatial::tensor_parallel::RelayStep]) -> serde_json::Value {
415 use crate::spatial::tensor_parallel::RelayStep;
416 serde_json::Value::Array(
417 relay
418 .iter()
419 .map(|s| match s {
420 RelayStep::Run { chip, to } => serde_json::json!({ "run": chip, "to": to }),
421 RelayStep::Copy { tensor, from, to } => serde_json::json!({ "copy": tensor, "from": from, "to": to }),
422 })
423 .collect(),
424 )
425}
426
427#[derive(Clone, Copy, Debug, PartialEq, Eq)]
430pub enum BoardProtocol {
431 Decode(DecodeWords),
432 Chip,
433}
434
435impl BoardProtocol {
436 pub fn module(self) -> &'static str {
438 match self {
439 BoardProtocol::Decode(_) => "decode_board",
440 BoardProtocol::Chip => "chip_board",
441 }
442 }
443
444 pub fn verilog(self, lat: u64) -> String {
446 match self {
447 BoardProtocol::Decode(w) => decode_board_verilog(w, lat),
448 BoardProtocol::Chip => chip_board_verilog(lat),
449 }
450 }
451
452 fn what(self) -> &'static str {
454 match self {
455 BoardProtocol::Decode(_) => "the decode step's board image",
456 BoardProtocol::Chip => "a chip's board image of a program split over chips",
457 }
458 }
459}
460
461#[derive(Clone, Copy, Debug, PartialEq, Eq)]
463pub enum NexysBoard {
464 A7,
466 Video,
469}
470
471pub const FT245_UART_BRIDGE_V: &str = include_str!("../../codegen_sva/ft245_uart_bridge.v");
474
475const BRIDGE_CPB: u32 = 8;
478
479impl NexysBoard {
480 pub fn part(self) -> &'static str {
482 match self {
483 NexysBoard::A7 => "xc7a100tcsg324-1",
484 NexysBoard::Video => "xc7a200tsbg484-1",
485 }
486 }
487
488 pub fn extra_sources(self) -> &'static [(&'static str, &'static str)] {
490 match self {
491 NexysBoard::A7 => &[],
492 NexysBoard::Video => &[("ft245_uart_bridge.v", FT245_UART_BRIDGE_V)],
493 }
494 }
495
496 pub fn top_verilog(self, top: &str, clock: CoreClock, protocol: BoardProtocol) -> String {
500 let module = protocol.module();
501 let core = format!(
502 " wire clkfb, core_raw, locked, coreclk;\n\
503 \x20 PLLE2_BASE #(.CLKIN1_PERIOD(10.0), .CLKFBOUT_MULT({}), .DIVCLK_DIVIDE({}), .CLKOUT0_DIVIDE({})) pll (\n\
504 \x20 .CLKIN1(clk), .CLKFBIN(clkfb), .CLKFBOUT(clkfb), .CLKOUT0(core_raw), .LOCKED(locked), .RST(1'b0), .PWRDWN(1'b0));\n\
505 \x20 BUFG bufg_core (.I(core_raw), .O(coreclk));\n",
506 clock.mult, clock.div, clock.out
507 );
508 let reset = " reg [3:0] por = 4'd0;\n always @(posedge coreclk) if (!locked) por <= 4'd0; else if (!por[3]) por <= por + 4'd1;\n";
509 match self {
510 NexysBoard::A7 => format!(
511 "module {top} (input clk, input uart_rx, output uart_tx, output [7:0] led);\n{core}{reset}\
512 \x20 {module} #(.CLKS_PER_BIT({cpb})) board(.clk(coreclk), .rst(!por[3]), .uart_rx(uart_rx), .uart_tx(uart_tx), .led(led));\n\
513 endmodule\n",
514 cpb = clks_per_bit(clock)
515 ),
516 NexysBoard::Video => format!(
517 "module {top} (input clk, inout [7:0] prog_d, input prog_rxen, input prog_txen, output prog_rdn, output prog_wrn,\n\
518 \x20 output prog_oen, output prog_siwun, output prog_spien, output [7:0] led);\n{core}{reset}\
519 \x20 wire to_board, from_board;\n\
520 \x20 ft245_uart_bridge #(.CLKS_PER_BIT({BRIDGE_CPB}), .W(4)) bridge(.clk(coreclk), .rst(!por[3]), .eng_rx(to_board), .eng_tx(from_board),\n\
521 \x20 .prog_d(prog_d), .prog_rxen(prog_rxen), .prog_txen(prog_txen), .prog_rdn(prog_rdn), .prog_wrn(prog_wrn), .prog_oen(prog_oen),\n\
522 \x20 .prog_siwun(prog_siwun), .prog_spien(prog_spien));\n\
523 \x20 {module} #(.CLKS_PER_BIT({BRIDGE_CPB})) board(.clk(coreclk), .rst(!por[3]), .uart_rx(to_board), .uart_tx(from_board), .led(led));\n\
524 endmodule\n"
525 ),
526 }
527 }
528
529 pub fn xdc(self, top: &str, protocol: BoardProtocol) -> String {
532 let pin = |p: &str, port: &str, io: &str| format!("set_property -dict {{ PACKAGE_PIN {p} IOSTANDARD {io} }} [get_ports {{ {port} }}]\n");
533 let mut s = format!("# {top}: {} on the {self:?} ({}).\n", protocol.what(), self.part());
534 let (clk, leds, io_led): (&str, [&str; 8], &str) = match self {
535 NexysBoard::A7 => ("E3", ["H17", "K15", "J13", "N14", "R18", "V17", "U17", "U16"], "LVCMOS33"),
536 NexysBoard::Video => ("R4", ["T14", "T15", "T16", "U16", "V15", "W16", "W15", "Y13"], "LVCMOS25"),
537 };
538 s.push_str(&pin(clk, "clk", "LVCMOS33"));
539 s.push_str("create_clock -add -name sys_clk_pin -period 10.00 -waveform {0 5} [get_ports { clk }]\n");
540 match self {
541 NexysBoard::A7 => {
542 s.push_str(&pin("C4", "uart_rx", "LVCMOS33"));
543 s.push_str(&pin("D4", "uart_tx", "LVCMOS33"));
544 }
545 NexysBoard::Video => {
546 for (i, p) in ["U20", "P14", "P15", "U17", "R17", "P16", "R18", "N14"].iter().enumerate() {
547 s.push_str(&pin(p, &format!("prog_d[{i}]"), "LVCMOS33"));
548 }
549 for (p, port) in [("N17", "prog_rxen"), ("Y19", "prog_txen"), ("P19", "prog_rdn"), ("R19", "prog_wrn"), ("V17", "prog_oen"), ("P17", "prog_siwun"), ("R14", "prog_spien")] {
550 s.push_str(&pin(p, port, "LVCMOS33"));
551 }
552 }
553 }
554 for (i, p) in leds.iter().enumerate() {
555 s.push_str(&pin(p, &format!("led[{i}]"), io_led));
556 }
557 s
558 }
559}
560
561pub fn board_image(
565 art: &VerilogArtifact,
566 images: &[(String, String)],
567 protocol: BoardProtocol,
568 lat: u64,
569 board: NexysBoard,
570 top: &str,
571 clock: CoreClock,
572) -> Vec<(String, String)> {
573 let mut files = vec![
574 ("spatial_top.v".to_string(), art.rtl.clone()),
575 (format!("{}.v", protocol.module()), protocol.verilog(lat)),
576 (format!("{top}.v"), board.top_verilog(top, clock, protocol)),
577 (format!("{top}.xdc"), board.xdc(top, protocol)),
578 ];
579 files.extend(art.hex.iter().cloned());
580 for (name, body) in images {
582 match files.iter_mut().find(|(n, _)| n == name) {
583 Some(slot) => slot.1 = body.clone(),
584 None => files.push((name.clone(), body.clone())),
585 }
586 }
587 files.extend(board.extra_sources().iter().map(|(name, text)| (name.to_string(), text.to_string())));
588 files
589}
590
591fn configured_memories(p: &SpatialProgram) -> Result<crate::spatial::eval::Inputs, BoardImageError> {
594 use crate::spatial::eval::{Inputs, Value};
595 use crate::spatial::ir::{TensorInit, TensorRole};
596 let mut inputs = Inputs::default();
597 for t in p.tensors.iter().filter(|t| t.role != TensorRole::Output) {
598 let n = t.elems.rows * t.elems.cols;
599 let value = if t.init == TensorInit::External {
600 Some(Value::Int(vec![0; n]))
601 } else if t.init.is_float() {
602 (0..n).map(|i| t.init.element_f64(i)).collect::<Option<Vec<f64>>>().map(Value::Float)
603 } else {
604 (0..n).map(|i| t.init.element(i)).collect::<Option<Vec<i64>>>().map(Value::Int)
605 };
606 let value = value.ok_or_else(|| BoardImageError::Fill(format!("`{}` has no deterministic fill", t.name)))?;
607 inputs.tensors.insert(t.id, value);
608 }
609 Ok(inputs)
610}
611
612#[derive(Clone, Debug, PartialEq)]
614pub enum BoardImageError {
615 Lower(SpatialLowerError),
617 Fill(String),
619 ReadLatency,
621}
622
623#[derive(Clone, Copy, Debug, PartialEq, Eq)]
626pub enum ImageRole {
627 DecodeStep,
628 Chip,
629}
630
631pub fn program_board_image(
635 p: &SpatialProgram,
636 target: &crate::spatial::target::SpatialTarget,
637 board: NexysBoard,
638 role: ImageRole,
639 top: &str,
640 clock: CoreClock,
641) -> Result<Vec<(String, String)>, BoardImageError> {
642 use crate::spatial::lower::SpatialBackend;
643 let inputs = configured_memories(p)?;
644 let art = match crate::spatial::lower::fabric::FabricBackend::new(target.clone()).lower(p) {
645 Ok(crate::spatial::lower::SpatialArtifact::Verilog(v)) => v,
646 Ok(_) => unreachable!("the fabric backend lowers to Verilog"),
647 Err(e) => return Err(BoardImageError::Lower(e)),
648 };
649 let lat = target.site.local_mem_read_latency.ok_or(BoardImageError::ReadLatency)? as u64;
650 let protocol = match role {
651 ImageRole::DecodeStep => BoardProtocol::Decode(DecodeWords::of(p, "tok", "pos", "next").map_err(BoardImageError::Lower)?),
652 ImageRole::Chip => BoardProtocol::Chip,
653 };
654 let images = crate::spatial::lower::fabric::input_hex_files(p, &inputs);
655 Ok(board_image(&art, &images, protocol, lat, board, top, clock))
656}
657
658pub fn image_digest(files: &[(String, String)]) -> String {
662 let mut sorted: Vec<&(String, String)> = files.iter().collect();
663 sorted.sort_by(|a, b| a.0.cmp(&b.0));
664 let mut msg = Vec::new();
665 for (name, body) in sorted {
666 msg.extend_from_slice(name.as_bytes());
667 msg.push(0);
668 let text = if name.ends_with(".v") { synthesized(body) } else { body.clone() };
669 msg.extend_from_slice(text.as_bytes());
670 msg.push(0);
671 }
672 crate::crypto_accel::sha256(&msg).iter().map(|b| format!("{b:02x}")).collect()
673}
674
675pub fn synthesized(verilog: &str) -> String {
677 let mut out = String::new();
678 let mut simulation_only = false;
679 for line in verilog.lines() {
680 if line.contains("synthesis translate_off") {
681 simulation_only = true;
682 } else if line.contains("synthesis translate_on") {
683 simulation_only = false;
684 } else if !simulation_only {
685 out.push_str(line);
686 out.push('\n');
687 }
688 }
689 out
690}