logicaffeine_compile/codegen_sva/
caravel_rtl.rs1use super::dram::emit_dram_executor_verilog;
24
25fn dram_executor_wb() -> &'static str {
27 r#"// dram_executor_wb — the certified DRAM command player behind a Wishbone slave (Caravel user
28// macro). The management SoC writes the compile-time plan word + a launch strobe; the countdown
29// FSM (proven decode + issue-timing) fires the command, captured for readback + logic-analyzer.
30module dram_executor_wb(
31`ifdef USE_POWER_PINS
32 inout vccd1, // User area 1.8V supply — Caravel PDN hook
33 inout vssd1, // User area digital ground
34`endif
35 input wb_clk_i,
36 input wb_rst_i,
37 input wbs_stb_i,
38 input wbs_cyc_i,
39 input wbs_we_i,
40 input [3:0] wbs_sel_i,
41 input [31:0] wbs_adr_i,
42 input [31:0] wbs_dat_i,
43 output reg wbs_ack_o,
44 output reg [31:0] wbs_dat_o,
45 output [63:0] la_data_out
46);
47 wire valid = wbs_cyc_i & wbs_stb_i;
48 wire wr = valid & wbs_we_i;
49
50 reg [63:0] cmd_word_r;
51 reg cmd_valid_r;
52 wire cmd_ack, issue;
53 wire [2:0] op; wire [4:0] bank; wire [31:0] row;
54
55 // The certified countdown executor — instantiated unchanged.
56 dram_executor u_exec(
57 .clk(wb_clk_i), .rst(wb_rst_i),
58 .cmd_word(cmd_word_r), .cmd_valid(cmd_valid_r),
59 .cmd_ack(cmd_ack), .issue(issue), .op(op), .bank(bank), .row(row)
60 );
61
62 // Latch the last issued command for readback / observation.
63 reg [2:0] op_c; reg [4:0] bank_c; reg [31:0] row_c; reg issued_c;
64
65 // POST-FAB HANDSHAKE. `issued_c` alone is a STICKY flag: set on the first issue and cleared
66 // only by reset. Firmware that writes a command and polls the status register therefore reads
67 // `issued = 1` left over from the PREVIOUS command and cannot tell its own command has gone
68 // out — and two identical back-to-back commands are indistinguishable in every visible field.
69 // The user area's only observation path after fabrication is the management SoC reading these
70 // registers, so that made command sequencing on real silicon unreliable.
71 //
72 // `issue_count` is the fix: a monotonically increasing counter, one tick per issued command.
73 // The protocol is read-modify-poll, with no clear-on-read race and no lost event:
74 // n = read(0x0c)[15:8]; write cmd_word; write cmd_valid;
75 // poll 0x0c until [15:8] != n; then read op/bank from 0x0c and row from 0x10.
76 // It wraps at 256 by design — only INEQUALITY is ever tested, so wrapping is harmless unless
77 // firmware sleeps through 256 issues, which the single-command-at-a-time protocol prevents.
78 // `issued_c` is retained at its original bit position so existing readers keep working.
79 reg [7:0] issue_count;
80
81 always @(posedge wb_clk_i) begin
82 if (wb_rst_i) begin
83 cmd_word_r <= 64'd0; cmd_valid_r <= 1'b0; wbs_ack_o <= 1'b0;
84 op_c <= 3'd0; bank_c <= 5'd0; row_c <= 32'd0; issued_c <= 1'b0;
85 issue_count <= 8'd0;
86 end else begin
87 wbs_ack_o <= valid & ~wbs_ack_o; // single-cycle Wishbone ack
88 if (cmd_ack) cmd_valid_r <= 1'b0; // the executor consumed the word
89 if (issue) begin
90 op_c <= op; bank_c <= bank; row_c <= row; issued_c <= 1'b1;
91 issue_count <= issue_count + 8'd1; // the observable event firmware polls on
92 end
93 if (wr) begin
94 case (wbs_adr_i[7:0])
95 8'h00: cmd_word_r[31:0] <= wbs_dat_i;
96 8'h04: cmd_word_r[63:32] <= wbs_dat_i;
97 8'h08: cmd_valid_r <= wbs_dat_i[0];
98 endcase
99 end
100 end
101 end
102
103 // Registered read data mux.
104 always @(posedge wb_clk_i) begin
105 case (wbs_adr_i[7:0])
106 8'h0c: wbs_dat_o <= {issued_c, 15'd0, issue_count, bank_c, op_c};
107 8'h10: wbs_dat_o <= row_c;
108 default: wbs_dat_o <= 32'd0;
109 endcase
110 end
111
112 assign la_data_out = {issued_c, 15'd0, issue_count, bank_c, op_c, row_c};
113endmodule
114"#
115}
116
117pub fn emit_dram_executor_wb_verilog() -> String {
121 let mut v = String::new();
122 v.push_str(&emit_dram_executor_verilog());
123 v.push('\n');
124 v.push_str(dram_executor_wb());
125 v
126}
127
128pub fn emit_dram_executor_wb_testbench() -> String {
132 r#"// tb_dram_executor_wb — drive the command player over Wishbone; check it issues the command.
133`timescale 1ns/1ps
134module tb_dram_executor_wb;
135 reg clk = 0; always #5 clk = ~clk;
136 reg rst = 1;
137 reg stb = 0, cyc = 0, we = 0;
138 reg [3:0] sel = 4'hf;
139 reg [31:0] adr = 0, dat = 0;
140 wire ack; wire [31:0] rdat; wire [63:0] la;
141 integer errors = 0;
142
143 dram_executor_wb dut(
144 .wb_clk_i(clk), .wb_rst_i(rst), .wbs_stb_i(stb), .wbs_cyc_i(cyc), .wbs_we_i(we),
145 .wbs_sel_i(sel), .wbs_adr_i(adr), .wbs_dat_i(dat), .wbs_ack_o(ack), .wbs_dat_o(rdat),
146 .la_data_out(la)
147 );
148
149 task wb_write(input [31:0] a, input [31:0] d);
150 begin
151 @(posedge clk); adr <= a; dat <= d; we <= 1; stb <= 1; cyc <= 1;
152 @(posedge clk); while (!ack) @(posedge clk);
153 stb <= 0; cyc <= 0; we <= 0;
154 @(posedge clk);
155 end
156 endtask
157
158 initial begin
159 repeat (3) @(posedge clk); rst = 0; @(posedge clk);
160 // delta-0 command: cmd_word = {24'd0, op=1, bank=3, row=7}
161 wb_write(32'h00, 32'd7); // cmd_word[31:0] = row
162 wb_write(32'h04, 32'h23); // cmd_word[63:32] = {delta=0, op=1(<<5), bank=3}
163 wb_write(32'h08, 32'h1); // launch (cmd_valid)
164 repeat (8) @(posedge clk);
165 // la_data_out = {issued, 15'd0, issue_count, bank, op, row}
166 if (la[63] !== 1'b1) begin errors = errors + 1; $display("FAIL: never issued"); end
167 if (la[47:40] !== 8'd1) begin errors = errors + 1; $display("FAIL: issue_count %0d != 1 after one command", la[47:40]); end
168 if (la[39:35] !== 5'd3) begin errors = errors + 1; $display("FAIL: bank %0d != 3", la[39:35]); end
169 if (la[34:32] !== 3'd1) begin errors = errors + 1; $display("FAIL: op %0d != 1", la[34:32]); end
170 if (la[31:0] !== 32'd7) begin errors = errors + 1; $display("FAIL: row %0d != 7", la[31:0]); end
171 if (errors == 0) $display("PASS: Wishbone-driven command player issued op=1 bank=3 row=7");
172 else $display("FAIL: %0d Wishbone integration errors", errors);
173 $finish;
174 end
175endmodule
176"#
177 .to_string()
178}
179
180fn dram_user_project() -> &'static str {
186 r#"// dram_user_project — the full-interface Caravel user macro: the certified Wishbone command
187// player (dram_executor_wb) presented on the complete Caravel user interface so the wrapper wires
188// every pin (LVS-clean). The command player issues onto la_data_out[63:0]; other I/O is passthrough.
189module dram_user_project (
190`ifdef USE_POWER_PINS
191 inout vccd1,
192 inout vssd1,
193`endif
194 input wb_clk_i,
195 input wb_rst_i,
196 input wbs_stb_i,
197 input wbs_cyc_i,
198 input wbs_we_i,
199 input [3:0] wbs_sel_i,
200 input [31:0] wbs_dat_i,
201 input [31:0] wbs_adr_i,
202 output wbs_ack_o,
203 output [31:0] wbs_dat_o,
204 input [127:0] la_data_in,
205 output [127:0] la_data_out,
206 input [127:0] la_oenb,
207 input [37:0] io_in,
208 output [37:0] io_out,
209 output [37:0] io_oeb,
210 input user_clock2,
211 output [2:0] user_irq
212);
213 wire [63:0] la_status;
214 dram_executor_wb core (
215`ifdef USE_POWER_PINS
216 .vccd1(vccd1), .vssd1(vssd1),
217`endif
218 .wb_clk_i(wb_clk_i), .wb_rst_i(wb_rst_i),
219 .wbs_stb_i(wbs_stb_i), .wbs_cyc_i(wbs_cyc_i), .wbs_we_i(wbs_we_i),
220 .wbs_sel_i(wbs_sel_i), .wbs_adr_i(wbs_adr_i), .wbs_dat_i(wbs_dat_i),
221 .wbs_ack_o(wbs_ack_o), .wbs_dat_o(wbs_dat_o),
222 .la_data_out(la_status)
223 );
224 assign la_data_out = {64'd0, la_status};
225 assign io_out = 38'd0;
226 assign io_oeb = {38{1'b1}}; // all user GPIO as inputs (output-enable-bar high)
227 assign user_irq = 3'd0;
228 // The remaining Caravel inputs are part of the fixed interface (wired 1:1 by the wrapper for a
229 // clean LVS) and unused by the command player; reduce them so the ports are kept + connected.
230 wire _unused = &{1'b0, la_data_in, la_oenb, io_in, user_clock2};
231endmodule
232"#
233}
234
235pub fn emit_dram_user_project_verilog() -> String {
239 let mut v = emit_dram_executor_wb_verilog();
240 v.push('\n');
241 v.push_str(dram_user_project());
242 v
243}
244
245pub fn emit_user_project_wrapper_verilog() -> String {
248 emit_user_project_wrapper_around("dram_user_project", "certified command player")
249}
250
251pub fn emit_user_project_wrapper_around(macro_module: &str, carries: &str) -> String {
256 r#"// user_project_wrapper — the Caravel user area. Golden interface verbatim; the full-interface
257// user macro ({macro_module}, {carries} inside) is instantiated as `mprj` with
258// every pin connected 1:1 so the hardened wrapper is LVS-clean.
259`define MPRJ_IO_PADS 38
260module user_project_wrapper #(parameter BITS = 32) (
261`ifdef USE_POWER_PINS
262 inout vdda1, inout vdda2, inout vssa1, inout vssa2,
263 inout vccd1, inout vccd2, inout vssd1, inout vssd2,
264`endif
265 input wb_clk_i,
266 input wb_rst_i,
267 input wbs_stb_i,
268 input wbs_cyc_i,
269 input wbs_we_i,
270 input [3:0] wbs_sel_i,
271 input [31:0] wbs_dat_i,
272 input [31:0] wbs_adr_i,
273 output wbs_ack_o,
274 output [31:0] wbs_dat_o,
275 input [127:0] la_data_in,
276 output [127:0] la_data_out,
277 input [127:0] la_oenb,
278 input [`MPRJ_IO_PADS-1:0] io_in,
279 output [`MPRJ_IO_PADS-1:0] io_out,
280 output [`MPRJ_IO_PADS-1:0] io_oeb,
281 inout [`MPRJ_IO_PADS-10:0] analog_io,
282 input user_clock2,
283 output [2:0] user_irq
284);
285 // Full-interface user macro — the {carries} inside, EVERY pin wired 1:1.
286 {macro_module} mprj (
287`ifdef USE_POWER_PINS
288 .vccd1(vccd1), .vssd1(vssd1),
289`endif
290 .wb_clk_i(wb_clk_i), .wb_rst_i(wb_rst_i),
291 .wbs_stb_i(wbs_stb_i), .wbs_cyc_i(wbs_cyc_i), .wbs_we_i(wbs_we_i),
292 .wbs_sel_i(wbs_sel_i), .wbs_adr_i(wbs_adr_i), .wbs_dat_i(wbs_dat_i),
293 .wbs_ack_o(wbs_ack_o), .wbs_dat_o(wbs_dat_o),
294 .la_data_in(la_data_in), .la_data_out(la_data_out), .la_oenb(la_oenb),
295 .io_in(io_in), .io_out(io_out), .io_oeb(io_oeb),
296 .user_clock2(user_clock2), .user_irq(user_irq)
297 );
298 // analog_io has no digital use in this macro (left unconnected, as in the empty golden wrapper).
299endmodule
300"#
301 .replace("{macro_module}", macro_module)
302 .replace("{carries}", carries)
303}
304
305#[cfg(test)]
306#[path = "../../../logicaffeine_tests/unit/logicaffeine_compile/codegen_sva/caravel_rtl/tests.rs"]
307mod tests;