1use super::dram::{DramCmd, DramTimings, TimedCmd};
22
23#[derive(Clone, Copy, Debug, PartialEq, Eq)]
25pub enum Ddr2Cmd {
26 Deselect,
28 Nop,
30 Active,
32 Read,
34 Write,
36 Precharge,
38 Refresh,
40 LoadMode,
42}
43
44pub fn ddr2_pins(cmd: Ddr2Cmd) -> (u8, u8, u8, u8) {
47 match cmd {
48 Ddr2Cmd::Deselect => (1, 1, 1, 1),
50 Ddr2Cmd::Nop => (0, 1, 1, 1),
51 Ddr2Cmd::Active => (0, 0, 1, 1),
52 Ddr2Cmd::Read => (0, 1, 0, 1),
53 Ddr2Cmd::Write => (0, 1, 0, 0),
54 Ddr2Cmd::Precharge => (0, 0, 1, 0),
55 Ddr2Cmd::Refresh => (0, 0, 0, 1),
56 Ddr2Cmd::LoadMode => (0, 0, 0, 0),
57 }
58}
59
60pub fn executor_op_to_cmd(op: u8) -> Option<Ddr2Cmd> {
63 match op {
64 0 => Some(Ddr2Cmd::Active),
65 1 => Some(Ddr2Cmd::Read),
66 2 => Some(Ddr2Cmd::Write),
67 3 => Some(Ddr2Cmd::Precharge),
68 4 => Some(Ddr2Cmd::Refresh),
69 _ => None,
70 }
71}
72
73pub fn emit_ddr2_command_encoder_verilog() -> String {
77 let (ncs, nras, ncas, nwe) = ddr2_pins(Ddr2Cmd::Nop);
78 let mut arms = String::new();
79 for op in 0u8..5 {
80 let cmd = executor_op_to_cmd(op).expect("0..5 are executor ops");
81 let (cs, ras, cas, we) = ddr2_pins(cmd);
82 arms.push_str(&format!(
83 " 3'd{op}: begin cs_n=1'b{cs}; ras_n=1'b{ras}; cas_n=1'b{cas}; we_n=1'b{we}; end // {cmd:?}\n"
84 ));
85 }
86 format!(
87 r#"// ddr2_cmd_encoder — op field (0=ACT 1=RD 2=WR 3=PRE 4=REFpb) → DDR2 command pins.
88// GENERATED from codegen_sva::ddr2_phy::ddr2_pins (the JEDEC DDR2 command truth table); the
89// netlist and the Rust model share one source of truth. `issue` low holds NOP.
90module ddr2_cmd_encoder(
91 input [2:0] op,
92 input issue,
93 output reg cs_n,
94 output reg ras_n,
95 output reg cas_n,
96 output reg we_n
97);
98 always @(*) begin
99 if (!issue) begin
100 cs_n=1'b{ncs}; ras_n=1'b{nras}; cas_n=1'b{ncas}; we_n=1'b{nwe}; // NOP
101 end else begin
102 case (op)
103{arms} default: begin cs_n=1'b{ncs}; ras_n=1'b{nras}; cas_n=1'b{ncas}; we_n=1'b{nwe}; end // NOP
104 endcase
105 end
106 end
107endmodule
108"#
109 )
110}
111
112#[derive(Clone, Copy, Debug, PartialEq, Eq)]
123pub enum ModeReg {
124 Mr,
126 Emr1,
128 Emr2,
130 Emr3,
132}
133
134impl ModeReg {
135 pub fn ba(self) -> u8 {
137 match self {
138 ModeReg::Mr => 0,
139 ModeReg::Emr1 => 1,
140 ModeReg::Emr2 => 2,
141 ModeReg::Emr3 => 3,
142 }
143 }
144}
145
146#[derive(Clone, Copy, Debug, PartialEq, Eq)]
148pub enum InitStep {
149 CkeLowWait { cycles: u64 },
151 Wait { cycles: u64 },
153 PrechargeAll,
155 Refresh,
157 LoadMode { reg: ModeReg, a: u16 },
159}
160
161pub const EMR1_DLL_ENABLE: u16 = 0x0000;
163pub const EMR1_OCD_DEFAULT: u16 = 0b111 << 7;
165pub const EMR1_OCD_EXIT: u16 = 0x0000;
167
168pub fn mr_opcode(t: &DramTimings, dll_reset: bool) -> u16 {
172 let bl: u16 = if t.burst_bytes / 2 == 4 { 0b010 } else { 0b011 }; let cl: u16 = ((t.trcd as u16) & 0b111) << 4; let wr: u16 = ((t.twr as u16).saturating_sub(1) & 0b111) << 9; let dll: u16 = if dll_reset { 1 << 8 } else { 0 };
176 bl | cl | wr | dll
177}
178
179pub fn ddr2_init_sequence(t: &DramTimings, cke_low_cycles: u64) -> Vec<InitStep> {
182 use InitStep::*;
183 use ModeReg::*;
184 let tmrd = 2; let tdllk = 200; vec![
187 CkeLowWait {
188 cycles: cke_low_cycles,
189 }, Wait { cycles: 400 }, PrechargeAll,
192 Wait { cycles: t.trp },
193 LoadMode { reg: Emr2, a: 0 },
194 Wait { cycles: tmrd },
195 LoadMode { reg: Emr3, a: 0 },
196 Wait { cycles: tmrd },
197 LoadMode {
198 reg: Emr1,
199 a: EMR1_DLL_ENABLE,
200 }, Wait { cycles: tmrd },
202 LoadMode {
203 reg: Mr,
204 a: mr_opcode(t, true),
205 }, Wait { cycles: tmrd },
207 PrechargeAll,
208 Wait { cycles: t.trp },
209 Refresh,
210 Wait { cycles: t.trfc_pb },
211 Refresh,
212 Wait { cycles: t.trfc_pb },
213 LoadMode {
214 reg: Mr,
215 a: mr_opcode(t, false),
216 }, Wait { cycles: tdllk }, LoadMode {
219 reg: Emr1,
220 a: EMR1_OCD_DEFAULT,
221 }, Wait { cycles: tmrd },
223 LoadMode {
224 reg: Emr1,
225 a: EMR1_OCD_EXIT,
226 }, Wait { cycles: tmrd },
228 ]
229}
230
231fn init_step_fields(step: &InitStep) -> (u8, Ddr2Cmd, u8, u16, u64) {
233 match *step {
234 InitStep::CkeLowWait { cycles } => (0, Ddr2Cmd::Deselect, 0, 0, cycles),
235 InitStep::Wait { cycles } => (1, Ddr2Cmd::Nop, 0, 0, cycles),
236 InitStep::PrechargeAll => (1, Ddr2Cmd::Precharge, 0, 1 << 10, 0), InitStep::Refresh => (1, Ddr2Cmd::Refresh, 0, 0, 0),
238 InitStep::LoadMode { reg, a } => (1, Ddr2Cmd::LoadMode, reg.ba(), a, 0),
239 }
240}
241
242pub fn init_rom_word(step: &InitStep) -> u64 {
246 let (cke, cmd, ba, a, wait) = init_step_fields(step);
247 let (cs, ras, cas, we) = ddr2_pins(cmd);
248 ((cke as u64) << 43)
249 | ((cs as u64) << 42)
250 | ((ras as u64) << 41)
251 | ((cas as u64) << 40)
252 | ((we as u64) << 39)
253 | ((ba as u64) << 37)
254 | ((a as u64) << 24)
255 | (wait & 0xff_ffff)
256}
257
258pub fn emit_ddr2_init_fsm_verilog(t: &DramTimings, cke_low_cycles: u64) -> String {
263 let seq = ddr2_init_sequence(t, cke_low_cycles);
264 let n = seq.len().max(1);
265 let mut rom = String::new();
266 for (i, step) in seq.iter().enumerate() {
267 let word = init_rom_word(step);
268 let (_, cmd, ..) = init_step_fields(step);
269 rom.push_str(&format!(" rom[{i}] = 44'h{word:011x}; // {cmd:?}\n"));
270 }
271 format!(
272 r#"// ddr2_init_fsm — DDR2 power-up / mode-register sequencer. The step ROM is GENERATED from
273// codegen_sva::ddr2_phy::ddr2_init_sequence (the JEDEC init spec); each entry drives
274// {{cke, cs_n, ras_n, cas_n, we_n, ba, a}} for `wait` cycles, then advances. init_done latches high.
275module ddr2_init_fsm(
276 input clk,
277 input rst,
278 output reg cke,
279 output reg cs_n,
280 output reg ras_n,
281 output reg cas_n,
282 output reg we_n,
283 output reg [1:0] ba,
284 output reg [12:0] a,
285 output reg init_done
286);
287 localparam NSTEPS = {n};
288 reg [43:0] rom [0:NSTEPS-1];
289 initial begin
290{rom} end
291 integer step;
292 reg [43:0] cur;
293 reg [23:0] wait_left;
294 always @(posedge clk) begin
295 if (rst) begin
296 step <= 0; cur <= rom[0]; wait_left <= rom[0][23:0]; init_done <= 1'b0;
297 end else if (!init_done) begin
298 if (wait_left == 24'd0) begin
299 if (step == NSTEPS-1) init_done <= 1'b1;
300 else begin step <= step + 1; cur <= rom[step+1]; wait_left <= rom[step+1][23:0]; end
301 end else begin
302 wait_left <= wait_left - 24'd1;
303 end
304 end
305 end
306 always @(*) begin
307 if (init_done) begin
308 cke=1'b1; cs_n=1'b0; ras_n=1'b1; cas_n=1'b1; we_n=1'b1; ba=2'b0; a=13'b0; // NOP idle
309 end else begin
310 cke=cur[43]; cs_n=cur[42]; ras_n=cur[41]; cas_n=cur[40]; we_n=cur[39];
311 ba=cur[38:37]; a=cur[36:24];
312 end
313 end
314endmodule
315"#
316 )
317}
318
319pub fn emit_ddr2_cmd_encoder_testbench() -> String {
322 let (ncs, nras, ncas, nwe) = ddr2_pins(Ddr2Cmd::Nop);
323 let mut checks = String::new();
324 for op in 0u8..8 {
325 checks.push_str(&format!(
326 " chk(3'd{op}, 1'b0, 1'b{ncs}, 1'b{nras}, 1'b{ncas}, 1'b{nwe});\n"
327 ));
328 }
329 for op in 0u8..8 {
330 let (cs, ras, cas, we) = match executor_op_to_cmd(op) {
331 Some(cmd) => ddr2_pins(cmd),
332 None => (ncs, nras, ncas, nwe), };
334 checks.push_str(&format!(
335 " chk(3'd{op}, 1'b1, 1'b{cs}, 1'b{ras}, 1'b{cas}, 1'b{we});\n"
336 ));
337 }
338 format!(
339 r#"`timescale 1ns/1ps
340module tb_ddr2_cmd_encoder;
341 reg [2:0] op; reg issue;
342 wire cs_n, ras_n, cas_n, we_n;
343 integer errors = 0;
344 ddr2_cmd_encoder dut(.op(op), .issue(issue), .cs_n(cs_n), .ras_n(ras_n), .cas_n(cas_n), .we_n(we_n));
345 task chk(input [2:0] o, input i, input ecs, input eras, input ecas, input ewe);
346 begin
347 op=o; issue=i; #1;
348 if (cs_n!==ecs || ras_n!==eras || cas_n!==ecas || we_n!==ewe) begin
349 errors=errors+1;
350 $display("FAIL op=%0d issue=%0d got=%b%b%b%b exp=%b%b%b%b", o, i, cs_n, ras_n, cas_n, we_n, ecs, eras, ecas, ewe);
351 end
352 end
353 endtask
354 initial begin
355{checks} if (errors==0) $display("PASS: ddr2_cmd_encoder matches the JEDEC truth table for all ops");
356 $finish;
357 end
358endmodule
359"#
360 )
361}
362
363pub fn emit_ddr2_init_testbench(t: &DramTimings, cke_low_cycles: u64) -> String {
367 let seq = ddr2_init_sequence(t, cke_low_cycles);
368 let mut exp: Vec<(u8, u8)> = Vec::new();
369 for s in &seq {
370 match s {
371 InitStep::PrechargeAll => exp.push((4, 0)),
372 InitStep::Refresh => exp.push((5, 0)),
373 InitStep::LoadMode { reg, .. } => exp.push((6, reg.ba())),
374 _ => {}
375 }
376 }
377 let expn = exp.len();
378 let mut expinit = String::new();
379 for (i, (c, b)) in exp.iter().enumerate() {
380 expinit.push_str(&format!(" exp_code[{i}]=4'd{c}; exp_ba[{i}]=2'd{b};\n"));
381 }
382 format!(
383 r#"`timescale 1ns/1ps
384module tb_ddr2_init_fsm;
385 reg clk=0; always #5 clk=~clk;
386 reg rst=1;
387 wire cke, cs_n, ras_n, cas_n, we_n; wire [1:0] ba; wire [12:0] a; wire init_done;
388 ddr2_init_fsm dut(.clk(clk), .rst(rst), .cke(cke), .cs_n(cs_n), .ras_n(ras_n),
389 .cas_n(cas_n), .we_n(we_n), .ba(ba), .a(a), .init_done(init_done));
390 localparam EXP_N = {expn};
391 reg [3:0] exp_code [0:EXP_N-1];
392 reg [1:0] exp_ba [0:EXP_N-1];
393 reg [3:0] got_code [0:255];
394 reg [1:0] got_ba [0:255];
395 integer ngot; reg was_real; reg [3:0] cc; integer i; integer errors;
396 // Record each isolated real command (real commands are separated by NOPs in the sequence).
397 always @(posedge clk) begin
398 if (rst) begin ngot=0; was_real=0; end
399 else if (!init_done) begin
400 if (cs_n===1'b0 && {{ras_n,cas_n,we_n}} !== 3'b111) begin
401 case ({{ras_n,cas_n,we_n}})
402 3'b011: cc=4'd1; 3'b101: cc=4'd2; 3'b100: cc=4'd3;
403 3'b010: cc=4'd4; 3'b001: cc=4'd5; 3'b000: cc=4'd6; default: cc=4'd0;
404 endcase
405 if (!was_real) begin got_code[ngot]=cc; got_ba[ngot]=ba; ngot=ngot+1; end
406 was_real=1;
407 end else was_real=0;
408 end
409 end
410 initial begin
411{expinit} errors=0;
412 repeat (3) @(posedge clk); rst=0;
413 i=0;
414 while (i<20000 && !init_done) begin @(posedge clk); i=i+1; end
415 if (!init_done) begin errors=errors+1; $display("FAIL: init_done never asserted"); end
416 if (ngot !== EXP_N) begin errors=errors+1; $display("FAIL: recorded %0d cmds, expected %0d", ngot, EXP_N); end
417 for (i=0; i<EXP_N; i=i+1) begin
418 if (i<ngot) begin
419 if (got_code[i]!==exp_code[i] || (exp_code[i]==4'd6 && got_ba[i]!==exp_ba[i])) begin
420 errors=errors+1;
421 $display("FAIL: cmd %0d got code=%0d ba=%0d exp code=%0d ba=%0d", i, got_code[i], got_ba[i], exp_code[i], exp_ba[i]);
422 end
423 end
424 end
425 if (errors==0) $display("PASS: ddr2_init_fsm walks the JEDEC init sequence (%0d cmds) and asserts init_done", ngot);
426 $finish;
427 end
428endmodule
429"#
430 )
431}
432
433pub fn emit_transaction(t: &DramTimings, bank: u32, row: u64, write: bool) -> Vec<TimedCmd> {
443 let cas = t.trcd;
444 let cas_end = cas + t.data_cycles;
445 let cas_cmd = if write {
446 DramCmd::Wr { bank }
447 } else {
448 DramCmd::Rd { bank }
449 };
450 let mut pre = t.tras.max(cas_end); if write {
452 pre = pre.max(cas_end + t.twr); }
454 vec![
455 TimedCmd {
456 at: 0,
457 cmd: DramCmd::Act { bank, row },
458 },
459 TimedCmd {
460 at: cas,
461 cmd: cas_cmd,
462 },
463 TimedCmd {
464 at: pre,
465 cmd: DramCmd::Pre { bank },
466 },
467 ]
468}
469
470fn ddr2_bake(tpl: &str, t: &DramTimings) -> String {
481 tpl.replace("%TRCD%", &t.trcd.to_string())
482 .replace("%TRAS%", &t.tras.to_string())
483 .replace("%TRP%", &t.trp.to_string())
484 .replace("%TWR%", &t.twr.to_string())
485 .replace("%CL%", &t.trcd.to_string())
486 .replace("%BURST%", &t.data_cycles.to_string())
487 .replace("%TREFI%", &t.trefi_pb.to_string())
488 .replace("%TRFC%", &t.trfc_pb.to_string())
489}
490
491pub fn emit_ddr2_controller_verilog(t: &DramTimings) -> String {
493 ddr2_bake(
494 r#"module ddr2_ctrl(
495 input clk, input rst,
496 input req_valid, input req_we, input [25:0] req_addr, input [15:0] req_wdata,
497 output req_ready,
498 output reg resp_valid, output reg [15:0] resp_rdata,
499 output reg cke,
500 output cs_n, output ras_n, output cas_n, output we_n,
501 output [2:0] ba, output [12:0] a, output odt,
502 output [15:0] wr_data, output wr_en,
503 input [15:0] rd_data, input rd_valid
504);
505 localparam TRCD=%TRCD%, TRAS=%TRAS%, TRP=%TRP%, TWR=%TWR%, BURST=%BURST%;
506 localparam PRE_RD = (TRAS > (TRCD+BURST)) ? TRAS : (TRCD+BURST);
507 localparam PRE_WR = (TRAS > (TRCD+BURST+TWR)) ? TRAS : (TRCD+BURST+TWR);
508 localparam integer TREFI=%TREFI%;
509 localparam TRFC=%TRFC%;
510 localparam S_IDLE=2'd0, S_BUSY=2'd1, S_PREW=2'd2, S_REF=2'd3;
511
512 reg [1:0] state;
513 reg cur_we; reg [2:0] cur_bank; reg [12:0] cur_row; reg [9:0] cur_col; reg [15:0] cur_wd;
514 reg [15:0] t; reg [31:0] refi; reg refresh_pending; reg [15:0] reft;
515 wire [15:0] pre_t = cur_we ? PRE_WR : PRE_RD;
516
517 reg r_cs, r_ras, r_cas, r_we, r_wren, r_odt;
518 reg [2:0] r_ba; reg [12:0] r_a;
519 assign cs_n=r_cs; assign ras_n=r_ras; assign cas_n=r_cas; assign we_n=r_we;
520 assign ba=r_ba; assign a=r_a; assign odt=r_odt; assign wr_en=r_wren; assign wr_data=cur_wd;
521 assign req_ready = (state==S_IDLE) && !refresh_pending;
522
523 // Commands are a pure function of the REGISTERED state + counter (no req_valid dependence),
524 // so a missed accept can never leave a stray ACT on the bus.
525 always @(*) begin
526 r_cs=1'b0; r_ras=1'b1; r_cas=1'b1; r_we=1'b1; r_wren=1'b0; r_odt=1'b0; r_ba=cur_bank; r_a=13'b0;
527 case (state)
528 S_BUSY: begin
529 if (t==0) begin r_ras=1'b0; r_cas=1'b1; r_we=1'b1; r_a=cur_row; end // ACT
530 else if (t==TRCD) begin
531 r_ras=1'b1; r_cas=1'b0; r_we=cur_we?1'b0:1'b1; r_a={3'b000, cur_col};
532 if (cur_we) begin r_wren=1'b1; r_odt=1'b1; end
533 end else if (t==pre_t) begin
534 r_ras=1'b0; r_cas=1'b1; r_we=1'b0; // PRE
535 end
536 end
537 S_REF: if (reft==0) begin r_ras=1'b0; r_cas=1'b0; r_we=1'b1; end // REF (banks idle)
538 default: ;
539 endcase
540 end
541
542 always @(posedge clk) begin
543 if (rst) begin
544 state<=S_IDLE; t<=0; refi<=0; refresh_pending<=1'b0; reft<=0; cke<=1'b1;
545 resp_valid<=1'b0; resp_rdata<=0; cur_we<=0; cur_bank<=0; cur_row<=0; cur_col<=0; cur_wd<=0;
546 end else begin
547 cke<=1'b1; resp_valid<=1'b0;
548 if (refi >= TREFI-1) begin refresh_pending<=1'b1; refi<=0; end else refi<=refi+1;
549 case (state)
550 S_IDLE: begin
551 if (refresh_pending) begin reft<=0; refresh_pending<=1'b0; state<=S_REF; end
552 else if (req_valid) begin
553 cur_we<=req_we; cur_bank<=req_addr[12:10]; cur_row<=req_addr[25:13];
554 cur_col<=req_addr[9:0]; cur_wd<=req_wdata; t<=0; state<=S_BUSY;
555 end
556 end
557 S_BUSY: begin
558 if (!cur_we && rd_valid) begin resp_valid<=1'b1; resp_rdata<=rd_data; end
559 if (t==pre_t) state<=S_PREW;
560 t<=t+1;
561 end
562 S_PREW: begin
563 if (t >= pre_t + TRP) begin state<=S_IDLE; t<=0; end else t<=t+1;
564 end
565 S_REF: begin
566 if (reft >= TRFC-1) state<=S_IDLE; else reft<=reft+1;
567 end
568 endcase
569 end
570 end
571endmodule
572"#,
573 t,
574 )
575}
576
577pub fn emit_ddr2_bank_model_verilog(t: &DramTimings) -> String {
580 ddr2_bake(
581 r#"module ddr2_bank_model(
582 input clk, input rst,
583 input cke, input cs_n, input ras_n, input cas_n, input we_n,
584 input [2:0] ba, input [12:0] a, input odt,
585 input [15:0] wr_data, input wr_en,
586 output reg [15:0] rd_data, output reg rd_valid,
587 output reg model_error
588);
589 localparam TRCD=%TRCD%, TRAS=%TRAS%, TRP=%TRP%, TWR=%TWR%, CL=%CL%, BURST=%BURST%;
590 reg openb [0:7];
591 reg [12:0] open_row [0:7];
592 reg [31:0] last_act [0:7];
593 reg [31:0] last_pre [0:7];
594 reg had_pre [0:7];
595 reg [31:0] wr_end [0:7];
596 reg had_wr [0:7];
597 reg [31:0] mc;
598 reg rd_pending; reg [15:0] rd_cnt; reg [15:0] rd_val;
599 reg [15:0] mem [0:2047];
600 integer bk;
601
602 always @(posedge clk) begin
603 if (rst) begin
604 mc<=0; model_error<=1'b0; rd_valid<=1'b0; rd_data<=0; rd_pending<=1'b0; rd_cnt<=0; rd_val<=0;
605 for (bk=0;bk<8;bk=bk+1) begin
606 openb[bk]<=1'b0; had_pre[bk]<=1'b0; had_wr[bk]<=1'b0;
607 last_act[bk]<=0; last_pre[bk]<=0; wr_end[bk]<=0; open_row[bk]<=0;
608 end
609 end else begin
610 mc<=mc+1;
611 rd_valid<=1'b0;
612 if (rd_pending) begin
613 if (rd_cnt<=1) begin rd_valid<=1'b1; rd_data<=rd_val; rd_pending<=1'b0; end
614 else rd_cnt<=rd_cnt-1;
615 end
616 if (cke && !cs_n) begin
617 case ({ras_n,cas_n,we_n})
618 3'b011: begin // ACT
619 if (openb[ba]) begin model_error<=1'b1; $display("FAIL: ACT on open bank %0d @%0d", ba, mc); end
620 if (had_pre[ba] && (mc - last_pre[ba] < TRP)) begin model_error<=1'b1; $display("FAIL: tRP bank %0d @%0d", ba, mc); end
621 openb[ba]<=1'b1; open_row[ba]<=a; last_act[ba]<=mc;
622 end
623 3'b101: begin // READ
624 if (!openb[ba]) begin model_error<=1'b1; $display("FAIL: READ closed bank %0d @%0d", ba, mc); end
625 else if (mc - last_act[ba] < TRCD) begin model_error<=1'b1; $display("FAIL: tRCD read bank %0d @%0d", ba, mc); end
626 rd_pending<=1'b1; rd_cnt<=CL; rd_val<=mem[{ba, open_row[ba][3:0], a[3:0]}];
627 end
628 3'b100: begin // WRITE
629 if (!openb[ba]) begin model_error<=1'b1; $display("FAIL: WRITE closed bank %0d @%0d", ba, mc); end
630 else if (mc - last_act[ba] < TRCD) begin model_error<=1'b1; $display("FAIL: tRCD write bank %0d @%0d", ba, mc); end
631 mem[{ba, open_row[ba][3:0], a[3:0]}] <= wr_data;
632 wr_end[ba]<=mc+BURST; had_wr[ba]<=1'b1;
633 end
634 3'b010: begin // PRECHARGE
635 if (!openb[ba]) begin model_error<=1'b1; $display("FAIL: PRE closed bank %0d @%0d", ba, mc); end
636 else if (mc - last_act[ba] < TRAS) begin model_error<=1'b1; $display("FAIL: tRAS bank %0d @%0d", ba, mc); end
637 if (had_wr[ba] && (mc < wr_end[ba] + TWR)) begin model_error<=1'b1; $display("FAIL: tWR bank %0d @%0d", ba, mc); end
638 openb[ba]<=1'b0; last_pre[ba]<=mc; had_pre[ba]<=1'b1; had_wr[ba]<=1'b0;
639 end
640 3'b001: begin // REFRESH
641 for (bk=0;bk<8;bk=bk+1) if (openb[bk]) begin model_error<=1'b1; $display("FAIL: REF open bank %0d @%0d", bk, mc); end
642 end
643 default: ;
644 endcase
645 end
646 end
647 end
648endmodule
649"#,
650 t,
651 )
652}
653
654pub fn emit_ddr2_controller_testbench(t: &DramTimings, n_words: u32) -> String {
657 let mut init = String::new();
658 for i in 0..n_words {
659 let addr = (i << 10) | i; let data = 0xA000u32 + i;
661 init.push_str(&format!(
662 " addr[{i}]=26'h{addr:x}; data[{i}]=16'h{data:x};\n"
663 ));
664 }
665 let tpl = r#"`timescale 1ns/1ps
666module tb_ddr2_ctrl;
667 reg clk=0; always #5 clk=~clk;
668 reg rst=1;
669 reg req_valid; reg req_we; reg [25:0] req_addr; reg [15:0] req_wdata;
670 wire req_ready; wire resp_valid; wire [15:0] resp_rdata;
671 wire cke,cs_n,ras_n,cas_n,we_n,odt,wr_en,rd_valid;
672 wire [2:0] ba; wire [12:0] a; wire [15:0] wr_data; wire [15:0] rd_data; wire model_error;
673 ddr2_ctrl ctrl(.clk(clk),.rst(rst),.req_valid(req_valid),.req_we(req_we),.req_addr(req_addr),
674 .req_wdata(req_wdata),.req_ready(req_ready),.resp_valid(resp_valid),.resp_rdata(resp_rdata),
675 .cke(cke),.cs_n(cs_n),.ras_n(ras_n),.cas_n(cas_n),.we_n(we_n),.ba(ba),.a(a),.odt(odt),
676 .wr_data(wr_data),.wr_en(wr_en),.rd_data(rd_data),.rd_valid(rd_valid));
677 ddr2_bank_model memmodel(.clk(clk),.rst(rst),.cke(cke),.cs_n(cs_n),.ras_n(ras_n),.cas_n(cas_n),
678 .we_n(we_n),.ba(ba),.a(a),.odt(odt),.wr_data(wr_data),.wr_en(wr_en),.rd_data(rd_data),
679 .rd_valid(rd_valid),.model_error(model_error));
680 localparam N=%N%;
681 reg [25:0] addr [0:N-1];
682 reg [15:0] data [0:N-1];
683 reg [15:0] rdout;
684 integer i; integer errors; integer timeout;
685 task do_write(input [25:0] ad, input [15:0] d); begin
686 @(posedge clk);
687 req_valid <= 1'b1; req_we <= 1'b1; req_addr <= ad; req_wdata <= d;
688 @(posedge clk);
689 while (!req_ready) @(posedge clk);
690 req_valid <= 1'b0;
691 end endtask
692 task do_read(input [25:0] ad); begin
693 @(posedge clk);
694 req_valid <= 1'b1; req_we <= 1'b0; req_addr <= ad; req_wdata <= 0;
695 @(posedge clk);
696 while (!req_ready) @(posedge clk);
697 req_valid <= 1'b0;
698 timeout=0;
699 while (!resp_valid && timeout<500) begin @(posedge clk); timeout=timeout+1; end
700 rdout=resp_rdata;
701 end endtask
702 initial begin
703%INIT% req_valid=0; req_we=0; req_addr=0; req_wdata=0; errors=0;
704 repeat (4) @(posedge clk); rst=0;
705 for (i=0;i<N;i=i+1) do_write(addr[i], data[i]);
706 for (i=0;i<N;i=i+1) begin
707 do_read(addr[i]);
708 if (rdout !== data[i]) begin errors=errors+1; $display("FAIL: word %0d addr %h got %h exp %h", i, addr[i], rdout, data[i]); end
709 end
710 if (model_error) begin errors=errors+1; $display("FAIL: behavioral model flagged a timing violation"); end
711 if (errors==0) $display("PASS: ddr2_ctrl round-tripped %0d words through the behavioral DDR2 model, no timing violations", N);
712 $finish;
713 end
714endmodule
715"#;
716 ddr2_bake(tpl, t)
717 .replace("%N%", &n_words.to_string())
718 .replace("%INIT%", &init)
719}
720
721#[cfg(test)]
722#[path = "../../../logicaffeine_tests/unit/logicaffeine_compile/codegen_sva/ddr2_phy/tests.rs"]
723mod tests;