1use super::fabric_rtl::program_cfg_fabric;
11use super::pdk_target::{HarnessClock, SKY130A};
12use super::psram::{PsramCache, PsramEdge, APS6404L_3SQN};
13use super::wpe_floorplan::{WpeDemands, WpeProfile};
14use super::wpe_offchip::{
15 emit_wprog_engine_soc_offchip_testbench_clocked, emit_wprog_engine_soc_verilog_offchip,
16 OffchipMemory, OffchipUnplaceable,
17};
18use super::wprog_engine_rtl::{
19 eval_wprog_config_counted_heap_preloaded, eval_wprog_config_run, program_main_fabric,
20 serialize_program, wprog_config_image_unchecked, MemBackend, WProgConfig,
21};
22
23pub const CARAVEL_IO: u8 = 38;
25pub const CARAVEL_FIRST_USER_IO: u8 = 5;
26
27#[derive(Clone, Copy, Debug, PartialEq, Eq)]
29pub struct WpePins {
30 pub uart_rx: u8,
31 pub uart_tx: u8,
32 pub psram_ce_n: u8,
33 pub psram_sclk: u8,
34 pub psram_sio: [u8; 4],
35}
36
37pub const WPE_PINS: WpePins = WpePins {
40 uart_rx: 8,
41 uart_tx: 9,
42 psram_ce_n: 10,
43 psram_sclk: 11,
44 psram_sio: [12, 13, 14, 15],
45};
46
47#[derive(Clone, Copy, Debug, PartialEq, Eq)]
49pub enum GpioMode {
50 Input,
51 Output,
52 Bidirectional,
53}
54
55impl GpioMode {
56 pub fn define(&self) -> &'static str {
57 match self {
58 GpioMode::Input => "GPIO_MODE_USER_STD_INPUT_NOPULL",
59 GpioMode::Output => "GPIO_MODE_USER_STD_OUTPUT",
60 GpioMode::Bidirectional => "GPIO_MODE_USER_STD_BIDIRECTIONAL",
61 }
62 }
63}
64
65#[derive(Clone, Debug, PartialEq, Eq)]
67pub enum WpeCaravelUnplaceable {
68 PinNotUser { pin: u8 },
70 PinShared { pin: u8 },
72 BaudUnreachable {
74 baud: u32,
75 clks_per_bit: u64,
76 error_ppm: u64,
77 },
78 ClockOutsideEdgeRange {
80 engine_ps: u32,
81 fastest_ps: u32,
82 slowest_ps: u32,
83 },
84 ReadWindowShortOfHarness {
87 half_sclk_ps: u64,
88 round_trip_ps: u64,
89 },
90 Memory(OffchipUnplaceable),
92}
93
94pub const UART_MAX_ERROR_PPM: u64 = 10_000;
96
97#[derive(Clone, Copy, Debug, PartialEq, Eq)]
102pub struct HarnessDelay {
103 pub out_ps: u64,
104 pub oeb_ps: u64,
105 pub in_ps: u64,
106}
107
108pub const CARAVEL_IO_MAX: HarnessDelay = HarnessDelay {
109 out_ps: 9_120,
110 oeb_ps: 9_320,
111 in_ps: 4_550,
112};
113pub const CARAVEL_IO_MIN: HarnessDelay = HarnessDelay {
114 out_ps: 3_900,
115 oeb_ps: 2_340,
116 in_ps: 1_260,
117};
118
119impl HarnessDelay {
120 pub fn with_board(self, board_ps: u64) -> HarnessDelay {
122 HarnessDelay {
123 out_ps: self.out_ps + board_ps,
124 oeb_ps: self.oeb_ps + board_ps,
125 in_ps: self.in_ps + board_ps,
126 }
127 }
128}
129
130pub const BOARD_FLIGHT_MAX_PS: u64 = 1_000;
133
134impl WpePins {
135 pub fn used(&self) -> Vec<(u8, GpioMode)> {
137 let mut v = vec![
138 (self.uart_rx, GpioMode::Input),
139 (self.uart_tx, GpioMode::Output),
140 (self.psram_ce_n, GpioMode::Output),
141 (self.psram_sclk, GpioMode::Output),
142 ];
143 v.extend(self.psram_sio.iter().map(|&p| (p, GpioMode::Bidirectional)));
144 v
145 }
146
147 pub fn check(&self) -> Result<(), WpeCaravelUnplaceable> {
149 let used = self.used();
150 for (i, &(pin, _)) in used.iter().enumerate() {
151 if !(CARAVEL_FIRST_USER_IO..CARAVEL_IO).contains(&pin) {
152 return Err(WpeCaravelUnplaceable::PinNotUser { pin });
153 }
154 if used[..i].iter().any(|&(p, _)| p == pin) {
155 return Err(WpeCaravelUnplaceable::PinShared { pin });
156 }
157 }
158 Ok(())
159 }
160
161 pub fn gpio_modes(&self) -> Vec<(u8, GpioMode)> {
163 (CARAVEL_FIRST_USER_IO..CARAVEL_IO)
164 .map(|pin| {
165 (
166 pin,
167 self.used()
168 .iter()
169 .find(|(p, _)| *p == pin)
170 .map_or(GpioMode::Input, |&(_, m)| m),
171 )
172 })
173 .collect()
174 }
175}
176
177#[derive(Clone, Copy, Debug, PartialEq, Eq)]
179pub struct WpeCaravel {
180 pub clk_period_ps: u32,
181 pub baud: u32,
182 pub pins: WpePins,
183}
184
185impl WpeCaravel {
186 pub fn clks_per_bit(&self) -> u64 {
188 let cycles_per_second = 1_000_000_000_000u64 / self.clk_period_ps as u64;
189 (cycles_per_second + self.baud as u64 / 2) / self.baud as u64
190 }
191
192 pub fn baud_error_ppm(&self) -> u64 {
194 let ideal_ps = 1_000_000_000_000u64 / self.baud as u64;
195 let actual_ps = self.clks_per_bit() * self.clk_period_ps as u64;
196 ideal_ps.abs_diff(actual_ps) * 1_000_000 / ideal_ps
197 }
198
199 pub fn read_round_trip_ps(&self, memory: &OffchipMemory) -> u64 {
203 CARAVEL_IO_MAX.out_ps
204 + BOARD_FLIGHT_MAX_PS
205 + memory.edge.part.t_aclk_max_ps as u64
206 + BOARD_FLIGHT_MAX_PS
207 + CARAVEL_IO_MAX.in_ps
208 }
209
210 pub fn half_sclk_ps(&self, memory: &OffchipMemory) -> u64 {
212 memory.edge.half as u64 * self.clk_period_ps as u64
213 }
214
215 pub fn check(&self, memory: &OffchipMemory) -> Result<(), WpeCaravelUnplaceable> {
219 self.pins.check()?;
220 if self.baud_error_ppm() > UART_MAX_ERROR_PPM {
221 return Err(WpeCaravelUnplaceable::BaudUnreachable {
222 baud: self.baud,
223 clks_per_bit: self.clks_per_bit(),
224 error_ppm: self.baud_error_ppm(),
225 });
226 }
227 let (fastest_ps, slowest_ps) = (memory.edge.sys_period_ps, memory.edge.slowest_period_ps);
228 if !(fastest_ps..=slowest_ps).contains(&self.clk_period_ps) {
229 return Err(WpeCaravelUnplaceable::ClockOutsideEdgeRange {
230 engine_ps: self.clk_period_ps,
231 fastest_ps,
232 slowest_ps,
233 });
234 }
235 memory
236 .check_uart(self.clks_per_bit())
237 .map_err(WpeCaravelUnplaceable::Memory)
238 }
239
240 pub fn check_harness(&self, memory: &OffchipMemory) -> Result<(), WpeCaravelUnplaceable> {
243 self.check(memory)?;
244 let (half_sclk_ps, round_trip_ps) =
245 (self.half_sclk_ps(memory), self.read_round_trip_ps(memory));
246 if half_sclk_ps <= round_trip_ps {
247 return Err(WpeCaravelUnplaceable::ReadWindowShortOfHarness {
248 half_sclk_ps,
249 round_trip_ps,
250 });
251 }
252 Ok(())
253 }
254}
255
256pub const WPE_CARAVEL_NAME: &str = "caravel";
258
259pub const WPE_CARAVEL_CAPS: [usize; 10] = [48, 32, 1072, 112, 16, 56, 64, 32_768, 32, 64];
269
270pub const WPE_CARAVEL_BACKEND: MemBackend = MemBackend::ConfigBram;
272
273pub const WPE_CLOCK: HarnessClock = HarnessClock::User2;
277
278pub const WPE_CARAVEL: WpeCaravel = WpeCaravel {
290 clk_period_ps: 21_000,
291 baud: 115_200,
292 pins: WPE_PINS,
293};
294
295pub fn wpe_caravel_memory() -> OffchipMemory {
299 let edge = PsramEdge::new(APS6404L_3SQN, 16_000, 2)
300 .and_then(|e| e.clocked_down_to(40_000))
301 .expect("the part runs from 16 to 40 ns");
302 let cache = PsramCache::new(&edge, 3, 1).expect("eight two-word lines");
303 OffchipMemory {
304 edge,
305 heap: Some(cache),
306 frames: Some(cache),
307 cells: Some(cache),
308 }
309}
310
311pub fn emit_wpe_user_project_verilog(
323 name: &str,
324 caps: [usize; 10],
325 backend: MemBackend,
326 memory: &OffchipMemory,
327 caravel: &WpeCaravel,
328) -> Result<String, WpeCaravelUnplaceable> {
329 caravel.check(memory)?;
330 let engine = emit_wprog_engine_soc_verilog_offchip(name, caps, backend, memory)
333 .map_err(WpeCaravelUnplaceable::Memory)?
334 .replacen("parameter MUL_TREE = 0", "parameter MUL_TREE = 1", 1);
335 let p = &caravel.pins;
336 let (mut outs, mut oebs) = (Vec::new(), Vec::new());
337 for pin in (0..CARAVEL_IO).rev() {
338 let (out, oeb) = if pin == p.uart_tx {
339 ("uart_tx".to_string(), "1'b0".to_string())
340 } else if pin == p.psram_ce_n {
341 ("psram_ce_n".to_string(), "1'b0".to_string())
342 } else if pin == p.psram_sclk {
343 ("psram_sclk".to_string(), "1'b0".to_string())
344 } else if let Some(k) = p.psram_sio.iter().position(|&s| s == pin) {
345 (format!("psram_sio_out[{k}]"), format!("~psram_sio_oe[{k}]"))
346 } else {
347 ("1'b0".to_string(), "1'b1".to_string())
348 };
349 outs.push(out);
350 oebs.push(oeb);
351 }
352 let sio_in = p
353 .psram_sio
354 .iter()
355 .rev()
356 .map(|s| format!("io_in[{s}]"))
357 .collect::<Vec<_>>()
358 .join(", ");
359 Ok(format!(
360 "{engine}\n\
361 // wpe_user_project — the whole-program engine as a Caravel user macro (codegen_sva::wpe_caravel). UART rx/tx\n\
362 // on GPIO {rx}/{tx}; PSRAM CE# {ce}, SCLK {sclk}, SIO[3:0] {s0}..{s3}; engine clock {clk} ({period} ps), {baud}\n\
363 // baud ({cpb} cycles per bit); the Wishbone slave on wb_clk_i.\n\
364 module wpe_user_project (\n\
365 `ifdef USE_POWER_PINS\n\
366 \x20 inout vccd1,\n\
367 \x20 inout vssd1,\n\
368 `endif\n\
369 \x20 input wb_clk_i,\n\
370 \x20 input wb_rst_i,\n\
371 \x20 input wbs_stb_i,\n\
372 \x20 input wbs_cyc_i,\n\
373 \x20 input wbs_we_i,\n\
374 \x20 input [3:0] wbs_sel_i,\n\
375 \x20 input [31:0] wbs_dat_i,\n\
376 \x20 input [31:0] wbs_adr_i,\n\
377 \x20 output wbs_ack_o,\n\
378 \x20 output [31:0] wbs_dat_o,\n\
379 \x20 input [127:0] la_data_in,\n\
380 \x20 output [127:0] la_data_out,\n\
381 \x20 input [127:0] la_oenb,\n\
382 \x20 input [37:0] io_in,\n\
383 \x20 output [37:0] io_out,\n\
384 \x20 output [37:0] io_oeb,\n\
385 \x20 input user_clock2,\n\
386 \x20 output [2:0] user_irq\n\
387 );\n\
388 \x20 wire uart_tx, psram_ce_n, psram_sclk;\n\
389 \x20 wire [3:0] psram_sio_out, psram_sio_oe;\n\
390 \x20 // The engine's reset: wb_rst_i, or probe 0 while the management SoC drives it, into the engine's clock\n\
391 \x20 // domain through two flops, asserted at once and released on the second {clk} edge after it falls.\n\
392 \x20 wire engine_rst_req = wb_rst_i | (la_data_in[0] & ~la_oenb[0]);\n\
393 \x20 reg [1:0] engine_rst;\n\
394 \x20 always @(posedge {clk} or posedge engine_rst_req)\n\
395 \x20 if (engine_rst_req) engine_rst <= 2'b11;\n\
396 \x20 else engine_rst <= {{engine_rst[0], 1'b0}};\n\
397 \x20 wpe_soc_{name} #(.CLKS_PER_BIT({cpb})) engine (\n\
398 \x20 .clk({clk}), .rst(engine_rst[1]), .uart_rx(io_in[{rx}]), .uart_tx(uart_tx),\n\
399 \x20 .psram_ce_n(psram_ce_n), .psram_sclk(psram_sclk), .psram_sio_out(psram_sio_out), .psram_sio_oe(psram_sio_oe),\n\
400 \x20 .psram_sio_in({{{sio_in}}}));\n\
401 \x20 assign io_out = {{{outs}}};\n\
402 \x20 assign io_oeb = {{{oebs}}};\n\
403 \x20 // Every Wishbone access is acknowledged the cycle after it is presented and reads zero.\n\
404 \x20 reg ack;\n\
405 \x20 always @(posedge wb_clk_i) ack <= !wb_rst_i && wbs_cyc_i && wbs_stb_i && !ack;\n\
406 \x20 assign wbs_ack_o = ack;\n\
407 \x20 assign wbs_dat_o = 32'd0;\n\
408 \x20 assign la_data_out = 128'd0;\n\
409 \x20 assign user_irq = 3'd0;\n\
410 \x20 // The rest of the fixed interface is kept and connected so the wrapper wires every pin.\n\
411 \x20 wire _unused = &{{1'b0, la_data_in, la_oenb, wbs_we_i, wbs_sel_i, wbs_dat_i, wbs_adr_i, io_in}};\n\
412 endmodule\n",
413 rx = p.uart_rx,
414 tx = p.uart_tx,
415 ce = p.psram_ce_n,
416 sclk = p.psram_sclk,
417 s0 = p.psram_sio[0],
418 s3 = p.psram_sio[3],
419 period = caravel.clk_period_ps,
420 baud = caravel.baud,
421 cpb = caravel.clks_per_bit(),
422 clk = WPE_CLOCK.port(),
423 outs = outs.join(", "),
424 oebs = oebs.join(", "),
425 ))
426}
427
428fn core_clock_period_ns() -> f64 {
430 SKY130A
431 .user_area
432 .and_then(|ua| ua.harness)
433 .expect("Caravel's harness is characterized")
434 .core_clock_period_ns
435}
436
437pub fn emit_wpe_user_defines(pins: &WpePins) -> String {
439 let mut s = String::from(
440 "// user_defines.v — user GPIO power-on configuration for the LOGOS whole-program engine\n\
441 // (codegen_sva::wpe_caravel): the UART and PSRAM pins as the engine uses them, every other GPIO a\n\
442 // no-pull input.\n\
443 \n\
444 `default_nettype none\n\
445 \n\
446 `ifndef __USER_DEFINES_H\n\
447 `define __USER_DEFINES_H\n\
448 \n\
449 `define GPIO_MODE_INVALID 13'hXXXX\n\
450 \n\
451 `define GPIO_MODE_MGMT_STD_INPUT_NOPULL 13'h0403\n\
452 `define GPIO_MODE_MGMT_STD_INPUT_PULLDOWN 13'h0c01\n\
453 `define GPIO_MODE_MGMT_STD_INPUT_PULLUP 13'h0801\n\
454 `define GPIO_MODE_MGMT_STD_OUTPUT 13'h1809\n\
455 `define GPIO_MODE_MGMT_STD_BIDIRECTIONAL 13'h1801\n\
456 `define GPIO_MODE_MGMT_STD_ANALOG 13'h000b\n\
457 \n\
458 `define GPIO_MODE_USER_STD_INPUT_NOPULL 13'h0402\n\
459 `define GPIO_MODE_USER_STD_INPUT_PULLDOWN 13'h0c00\n\
460 `define GPIO_MODE_USER_STD_INPUT_PULLUP 13'h0800\n\
461 `define GPIO_MODE_USER_STD_OUTPUT 13'h1808\n\
462 `define GPIO_MODE_USER_STD_BIDIRECTIONAL 13'h1800\n\
463 `define GPIO_MODE_USER_STD_OUT_MONITORED 13'h1802\n\
464 `define GPIO_MODE_USER_STD_ANALOG 13'h000a\n\
465 \n",
466 );
467 for (pin, mode) in pins.gpio_modes() {
468 s.push_str(&format!(
469 "`define USER_CONFIG_GPIO_{pin}_INIT `{}\n",
470 mode.define()
471 ));
472 }
473 s.push_str("\n`endif // __USER_DEFINES_H\n`default_nettype wire\n");
474 s
475}
476
477pub fn emit_wpe_user_project_testbench(
483 name: &str,
484 n_vectors: usize,
485 max_bytes: usize,
486 memory: &OffchipMemory,
487 caravel: &WpeCaravel,
488 pads: HarnessDelay,
489) -> String {
490 let p = &caravel.pins;
491 let board = emit_wprog_engine_soc_offchip_testbench_clocked(
492 name,
493 n_vectors,
494 caravel.clks_per_bit() as usize,
495 max_bytes,
496 memory,
497 caravel.clk_period_ps,
498 );
499 let dut = format!(
500 " wpe_soc_{name} #(.CLKS_PER_BIT(CLKS)) dut(.clk(clk), .rst(rst), .uart_rx(uart_rx), .uart_tx(uart_tx),\n\
501 \x20 .psram_ce_n(psram_ce_n), .psram_sclk(psram_sclk), .psram_sio_out(psram_sio_out), .psram_sio_oe(psram_sio_oe),\n\
502 \x20 .psram_sio_in(psram_sio));\n"
503 );
504 assert_eq!(
505 board.matches(dut.as_str()).count(),
506 1,
507 "the board testbench instantiates the engine once"
508 );
509 let ns = |ps: u64| format!("{}.{:03}", ps / 1000, ps % 1000);
510 let (d_out, d_oeb, d_in) = (ns(pads.out_ps), ns(pads.oeb_ps), ns(pads.in_ps));
511 let sio_in = p
512 .psram_sio
513 .iter()
514 .enumerate()
515 .map(|(k, s)| format!(" assign #({d_in}) io_in[{s}] = psram_sio[{k}];\n"))
516 .collect::<String>();
517 let sio_out = p
518 .psram_sio
519 .iter()
520 .enumerate()
521 .map(|(k, s)| {
522 format!(" assign #({d_out}) psram_sio_out[{k}] = io_out[{s}]; assign #({d_oeb}) psram_sio_oe[{k}] = ~io_oeb[{s}];\n")
523 })
524 .collect::<String>();
525 let outputs = [p.uart_tx, p.psram_ce_n, p.psram_sclk];
526 let inputs: Vec<u8> = (0..CARAVEL_IO)
527 .filter(|pin| !outputs.contains(pin) && !p.psram_sio.contains(pin))
528 .collect();
529 let directions = format!(
530 " // After reset every output pin drives and every other non-SIO pin is an input.\n\
531 \x20 always @(posedge clk) if (!rst && ({outs_oeb} || ~&{{{ins_oeb}}})) begin\n\
532 \x20 $display(\"PIN-DIRECTION io_oeb=%b\", io_oeb);\n\
533 \x20 $display(\"FAIL: a Caravel pin is driven the wrong way\");\n\
534 \x20 $finish;\n\
535 \x20 end\n",
536 outs_oeb = outputs
537 .iter()
538 .map(|o| format!("io_oeb[{o}]"))
539 .collect::<Vec<_>>()
540 .join(" || "),
541 ins_oeb = inputs
542 .iter()
543 .map(|i| format!("io_oeb[{i}]"))
544 .collect::<Vec<_>>()
545 .join(", "),
546 );
547 let user = format!(
548 " wire [37:0] io_out, io_oeb; wire [37:0] io_in;\n\
549 \x20 genvar hk;\n\
550 \x20 generate for (hk = 0; hk < 38; hk = hk + 1) begin : idle_io\n\
551 \x20 if (hk != {rx} && hk != {s0} && hk != {s1} && hk != {s2} && hk != {s3}) begin : quiet\n\
552 \x20 assign io_in[hk] = 1'b0;\n\
553 \x20 end\n\
554 \x20 end endgenerate\n\
555 \x20 assign io_in[{rx}] = uart_rx;\n\
556 {sio_in}\
557 \x20 assign uart_tx = io_out[{tx}];\n\
558 \x20 assign #({d_out}) psram_ce_n = io_out[{ce}];\n\
559 \x20 assign #({d_out}) psram_sclk = io_out[{sclk}];\n\
560 {sio_out}\
561 \x20 wire wbs_ack_o; wire [31:0] wbs_dat_o; wire [127:0] la_data_out; wire [2:0] user_irq;\n\
562 \x20 // The management SoC's clock, at the period Caravel's chip signoff times it at; the engine's is clk.\n\
563 \x20 reg wb_clk = 0;\n\
564 \x20 always #{wb_half} wb_clk = ~wb_clk;\n\
565 \x20 wpe_user_project dut (\n\
566 \x20 .wb_clk_i(wb_clk), .wb_rst_i(rst), .wbs_stb_i(1'b0), .wbs_cyc_i(1'b0), .wbs_we_i(1'b0), .wbs_sel_i(4'd0),\n\
567 \x20 .wbs_dat_i(32'd0), .wbs_adr_i(32'd0), .wbs_ack_o(wbs_ack_o), .wbs_dat_o(wbs_dat_o),\n\
568 \x20 .la_data_in(128'd0), .la_data_out(la_data_out), .la_oenb({{128{{1'b1}}}}),\n\
569 \x20 .io_in(io_in), .io_out(io_out), .io_oeb(io_oeb), .{clk}(clk), .user_irq(user_irq));\n\
570 {directions}",
571 rx = p.uart_rx,
572 tx = p.uart_tx,
573 ce = p.psram_ce_n,
574 sclk = p.psram_sclk,
575 s0 = p.psram_sio[0],
576 s1 = p.psram_sio[1],
577 s2 = p.psram_sio[2],
578 s3 = p.psram_sio[3],
579 wb_half = core_clock_period_ns() / 2.0,
580 clk = WPE_CLOCK.port(),
581 );
582 board
583 .replace(&dut, &user)
584 .replace("dut.mstall", "dut.engine.mstall")
585 .replace("dut.rx_valid", "dut.engine.rx_valid")
586 .replace("dut.r_state", "dut.engine.r_state")
587 .replace("dut.r_cnt", "dut.engine.r_cnt")
588}
589
590pub fn emit_wpe_user_project_wrapper_testbench(
593 name: &str,
594 n_vectors: usize,
595 max_bytes: usize,
596 memory: &OffchipMemory,
597 caravel: &WpeCaravel,
598 pads: HarnessDelay,
599) -> String {
600 let tb = emit_wpe_user_project_testbench(name, n_vectors, max_bytes, memory, caravel, pads);
601 assert_eq!(
602 tb.matches(" wpe_user_project dut (").count(),
603 1,
604 "the macro testbench instantiates the macro once"
605 );
606 tb.replace(" wpe_user_project dut (", " user_project_wrapper dut (")
607 .replace("dut.engine.", "dut.mprj.engine.")
608}
609
610#[derive(Clone, Debug, PartialEq, Eq)]
612pub struct BringupVector {
613 pub bytes: Vec<u8>,
614 pub result: i64,
615}
616
617pub const BRINGUP_TEXT: &str = "## Main\n Let s be \"abc\".\n Show item 2 of s.\n Show s equals \"abc\".\n Show \"n=\" + 42.\n";
619
620pub const BRINGUP_PRELOAD: &str = "## To native loaded () -> Seq of Int\n## To sumw (n: Int) -> Int:\n Let s be 0.\n Let i be 0.\n While i is less than n:\n Set s to s + item (i + 1) of loaded().\n Set i to i + 1.\n Return s.\n";
623
624pub fn wpe_caravel_wire(
629 cfg: &WProgConfig,
630 params: &[i64],
631 heap_init: &[i64],
632) -> Result<Vec<u8>, Vec<(&'static str, usize, usize)>> {
633 let short = WpeProfile::from_caps(WPE_CARAVEL_CAPS).shortfalls(&WpeDemands::loaded(cfg));
634 if !short.is_empty() {
635 return Err(short);
636 }
637 Ok(wprog_config_image_unchecked(cfg).wire_bytes_preloaded(params, heap_init))
638}
639
640pub fn wpe_caravel_bringup_vectors() -> Vec<BringupVector> {
643 let wire = |cfg: &WProgConfig, params: &[i64], heap_init: &[i64]| {
644 wpe_caravel_wire(cfg, params, heap_init)
645 .unwrap_or_else(|short| panic!("a bring-up program overflows {short:?}"))
646 };
647 let text = program_main_fabric(BRINGUP_TEXT);
648 let mut v = vec![BringupVector {
649 bytes: wire(&text, &[], &[]),
650 result: eval_wprog_config_run(&text, &[], &[]).result,
651 }];
652 let sumw = serialize_program(&program_cfg_fabric(BRINGUP_PRELOAD, "sumw"));
653 let blob: Vec<i64> = std::iter::once(40)
654 .chain((1..=40).map(|k| 7 * k - 100))
655 .collect();
656 for n in [40i64, 17] {
657 v.push(BringupVector {
658 bytes: wire(&sumw, &[n], &blob),
659 result: eval_wprog_config_counted_heap_preloaded(&sumw, &[n], &blob).0,
660 });
661 }
662 v
663}
664
665const SPDX_V: &str = "// SPDX-FileCopyrightText: 2026 DatacenterDotDev, Inc.\n// SPDX-License-Identifier: MIT\n";
666
667const SCAFFOLD_COPYRIGHT_V: &str = "// SPDX-FileCopyrightText: 2022 Efabless Corporation\n";
669
670pub fn wpe_caravel_overlay_files() -> Vec<(String, String)> {
675 let memory = wpe_caravel_memory();
676 let vectors = wpe_caravel_bringup_vectors();
677 let bytes: Vec<u8> = vectors
678 .iter()
679 .flat_map(|v| v.bytes.iter().copied())
680 .collect();
681 let tb = emit_wpe_user_project_wrapper_testbench(
682 WPE_CARAVEL_NAME,
683 vectors.len(),
684 bytes.len(),
685 &memory,
686 &WPE_CARAVEL,
687 CARAVEL_IO_MAX.with_board(BOARD_FLIGHT_MAX_PS),
688 );
689 let dv = "verilog/dv/logos_bringup";
690 let spdx = |body: String| format!("{SPDX_V}{body}");
691 vec![
692 (
693 "verilog/rtl/user_defines.v".to_string(),
694 format!("{SCAFFOLD_COPYRIGHT_V}{}", spdx(emit_wpe_user_defines(&WPE_CARAVEL.pins))),
695 ),
696 (format!("{dv}/tb_user_project_wrapper.v"), spdx(tb)),
697 (
698 format!("{dv}/wire_{WPE_CARAVEL_NAME}.hex"),
699 spdx(bytes.iter().map(|b| format!("{b:02x}\n")).collect()),
700 ),
701 (
702 format!("{dv}/lens_{WPE_CARAVEL_NAME}.hex"),
703 spdx(
704 vectors
705 .iter()
706 .map(|v| format!("{:08x}\n", v.bytes.len()))
707 .collect(),
708 ),
709 ),
710 (
711 format!("{dv}/oracle_{WPE_CARAVEL_NAME}.hex"),
712 spdx(
713 vectors
714 .iter()
715 .map(|v| format!("{:016x}\n", v.result as u64))
716 .collect(),
717 ),
718 ),
719 ]
720}
721
722pub fn wpe_psram_read_sta_tcl(
729 caravel: &WpeCaravel,
730 memory: &OffchipMemory,
731 lib: &str,
732 netlist: &str,
733 spef: &str,
734) -> String {
735 let p = &caravel.pins;
736 let sio = p
737 .psram_sio
738 .iter()
739 .map(|s| format!("io_in[{s}]"))
740 .collect::<Vec<_>>()
741 .join(" ");
742 let half = memory.edge.half;
743 let multicycle = if half > 1 {
744 format!("set_multicycle_path -setup {half} -from $sio\n")
745 } else {
746 String::new()
747 };
748 format!(
749 "read_liberty {lib}\n\
750 read_verilog {netlist}\n\
751 link_design wpe_user_project\n\
752 read_spef {spef}\n\
753 create_clock -name clk -period {period} [get_ports {clk}]\n\
754 set_propagated_clock [get_clocks clk]\n\
755 set_output_delay 0 -clock clk [get_ports {{io_out[{sclk}]}}]\n\
756 set sclk_arrival [get_property [lindex [get_property [find_timing_paths -to [get_ports {{io_out[{sclk}]}}] -path_delay max] points] end] arrival]\n\
757 set sio [get_ports {{{sio}}}]\n\
758 set_input_delay [expr {{$sclk_arrival + {outside}}}] -clock clk $sio\n\
759 {multicycle}\
760 set slack [get_property [find_timing_paths -from $sio -path_delay max] slack]\n\
761 puts \"SCLK_ARRIVAL_NS [format %.3f $sclk_arrival]\"\n\
762 puts \"SIO_CAPTURE_SLACK_NS [format %.3f $slack]\"\n",
763 period = caravel.clk_period_ps as f64 / 1000.0,
764 clk = WPE_CLOCK.port(),
765 sclk = p.psram_sclk,
766 outside = caravel.read_round_trip_ps(memory) as f64 / 1000.0,
767 )
768}