Skip to main content

logicaffeine_compile/codegen_sva/
ddr2_dfi.rs

1//! ddr2_dfi — `ddr2_dfi_ctrl`, the LOGOS DDR2 controller at the DFI 1:2 rate: a 64-bit native request port (a word a
2//! command, write data on its own stream, read data back in request order) and two DFI phases a system clock, the
3//! interface LiteDRAM's controller presents to its PHY, so either drives the same PHY.
4//!
5//! One BL4 burst of the x16 part is a 64-bit word and two DRAM clocks, one system clock: a column command every
6//! system clock is the bus's whole bandwidth. The controller keeps one in-order queue of requests and three
7//! pointers into it: the tail takes requests, `prep` opens the row of the next request (bank lookahead: a row opens
8//! while earlier requests in other banks are still being served), and the head issues column commands, in request
9//! order, so read data comes back in request order and no reorder buffer is needed. A row stays open only while a
10//! prepared request still needs it or the next request to prepare reads it; otherwise its last column command closes
11//! it (auto-precharge), so a request that finds the controller idle pays ACT and the column command, not a
12//! precharge first. A row is open only while a prepared request needs it, or for the clock in which the next request
13//! to prepare takes it as a hit, so no request ever finds its bank open on another row: the only PRECHARGE the
14//! controller issues is the precharge-all before a refresh.
15//!
16//! Column commands go on phase 0, row and refresh commands on phase 1. Every rule of the clocked part is a
17//! down-counter in DRAM clocks from the current system clock's phase 0, decremented by two a clock: a column command
18//! may issue when its counters are 0, a row command when they are at most 1. tRRD and tFAW are a window over the last
19//! system clocks' ACTs. Refresh is postponed while requests wait — up to the eight JESD79-2 allows — and repaid when
20//! the queue is empty or the debt reaches eight: then no new row opens, the prepared requests drain, every bank
21//! precharges and the REFRESH issues. A row is therefore never open longer than eight refresh intervals and the
22//! drain, inside tRAS max. Read data is delivered without back-pressure, as LiteDRAM's crossbar delivers it.
23
24use super::ddr2_jedec::{Ddr2Timing, REFRESH_POSTPONE_MAX};
25use super::ddr2_phy::{ddr2_pins, Ddr2Cmd};
26
27/// The requests `ddr2_dfi_ctrl` holds: the window its bank lookahead sees.
28pub const DFI_CTRL_QUEUE: u32 = 4;
29
30/// The fewest bits that hold `v`.
31fn bits_for(v: u64) -> u32 {
32    (u64::BITS - v.leading_zeros()).max(1)
33}
34
35/// `cs#, ras#, cas#, we#` of `cmd` as a Verilog constant.
36fn pins(cmd: Ddr2Cmd) -> String {
37    let (cs, ras, cas, we) = ddr2_pins(cmd);
38    format!("4'b{cs}{ras}{cas}{we}")
39}
40
41/// Emit `ddr2_dfi_ctrl` for the clocked part `t`: every rule of `t` baked in, a [`DFI_CTRL_QUEUE`]-deep queue.
42///
43/// The part must be the one the 1:2 rate and the port assume: x16, eight banks, 1 Kb columns, burst length 4 (one
44/// burst a 64-bit word, a system clock of data), column commands two DRAM clocks apart at most, and eight refresh
45/// intervals and the queue's drain inside tRAS max: a row opened just after a refresh stays open, at the longest,
46/// until the eighth refresh owed forces the queue to drain and every bank to precharge.
47pub fn emit_ddr2_dfi_controller_verilog(t: &Ddr2Timing) -> String {
48    let g = &t.grade;
49    assert_eq!(t.bl, 4, "{}: ddr2_dfi_ctrl carries a 64-bit word as one BL4 burst", g.part);
50    assert_eq!((g.dq_bits, g.banks, g.col_bits), (16, 8, 10), "{}: ddr2_dfi_ctrl drives an x16, eight-bank part with 1 Kb columns", g.part);
51    assert!(g.row_bits <= 13, "{}: the DDR2 address bus carries 13 row bits", g.part);
52    assert!(t.cas_to_cas() <= 2, "{}: a column command every system clock needs CAS-to-CAS of two DRAM clocks", g.part);
53    let drain = u64::from(DFI_CTRL_QUEUE) * (t.trc + t.write_to_read()) + t.trpa;
54    assert!(
55        REFRESH_POSTPONE_MAX * t.trefi + drain < t.tras_max,
56        "{}: eight postponed refreshes and a drain would hold a row past tRAS max",
57        g.part
58    );
59
60    let ap_ready = t.write_to_precharge().max(1 + t.tras) + t.trp;
61    let widest = [1 + t.trc, 1 + t.tras, ap_ready, 1 + t.trfc, 1 + t.trpa, t.write_to_read(), t.read_to_write(), t.cas_to_cas(), 1 + t.trcd]
62        .into_iter()
63        .max()
64        .unwrap();
65    let cw = bits_for(widest);
66    let rcw = bits_for(1 + t.trcd);
67    let refi = t.trefi / 2;
68    let faw_window = t.tfaw.div_ceil(2).saturating_sub(1);
69    let rrd_window = t.trrd.div_ceil(2).saturating_sub(1);
70    let history = faw_window.max(rrd_window).max(1);
71    let faw_ok = if faw_window >= 4 {
72        let w = bits_for(faw_window);
73        let ones: Vec<String> = (0..faw_window).map(|i| format!("{{{}'d0, acts[{i}]}}", w - 1)).collect();
74        format!("({}) < {w}'d4", ones.join(" + "))
75    } else {
76        "1'b1".to_string()
77    };
78    let rrd_ok = if rrd_window > 0 { format!("acts[{}:0] == 0", rrd_window - 1) } else { "1'b1".to_string() };
79    let qb = bits_for(u64::from(DFI_CTRL_QUEUE) - 1);
80
81    let v = r#"// ddr2_dfi_ctrl — LOGOS DDR2 controller, %PART% at tCK %TCK% ps, DFI 1:2: an in-order queue whose rows open ahead
82// in other banks, a row left open only while a waiting request reads it, refresh postponed while requests wait.
83module ddr2_dfi_ctrl(
84  input sys_clk, input sys_rst,
85  input cmd_valid, output cmd_ready, input cmd_payload_we, input [23:0] cmd_payload_addr,
86  input wdata_valid, output wdata_ready, input [63:0] wdata_payload_data, input [7:0] wdata_payload_we,
87  output reg rdata_valid, output reg [63:0] rdata_payload_data,
88  output reg [12:0] dfi_p0_address, output reg [2:0] dfi_p0_bank,
89  output reg dfi_p0_cs_n, output reg dfi_p0_ras_n, output reg dfi_p0_cas_n, output reg dfi_p0_we_n,
90  output dfi_p0_cke, output dfi_p0_odt, output dfi_p0_reset_n,
91  output reg [31:0] dfi_p0_wrdata, output reg dfi_p0_wrdata_en, output reg [3:0] dfi_p0_wrdata_mask,
92  output reg dfi_p0_rddata_en, input [31:0] dfi_p0_rddata, input dfi_p0_rddata_valid,
93  output reg [12:0] dfi_p1_address, output reg [2:0] dfi_p1_bank,
94  output reg dfi_p1_cs_n, output reg dfi_p1_ras_n, output reg dfi_p1_cas_n, output reg dfi_p1_we_n,
95  output dfi_p1_cke, output dfi_p1_odt, output dfi_p1_reset_n,
96  output reg [31:0] dfi_p1_wrdata, output reg dfi_p1_wrdata_en, output reg [3:0] dfi_p1_wrdata_mask,
97  output reg dfi_p1_rddata_en, input [31:0] dfi_p1_rddata, input dfi_p1_rddata_valid
98);
99  localparam QB = %QB%, QD = 1 << QB, CW = %CW%, RCW = %RCW%;
100  localparam [CW-1:0] T_RCD = %T_RCD%, T_RP = %T_RP%, T_RPA = %T_RPA%, T_RAS = %T_RAS%, T_RC = %T_RC%, T_RFC = %T_RFC%;
101  localparam [CW-1:0] RD2PRE = %RD2PRE%, WR2PRE = %WR2PRE%, WR2RD = %WR2RD%, RD2WR = %RD2WR%, CAS2CAS = %CAS2CAS%;
102  localparam [%RB%-1:0] REFI = %REFI%;
103  localparam [3:0] POSTPONE = %POSTPONE%;
104  localparam [3:0] NOP = %NOP%, ACT = %ACT%, PRE = %PRE%, REF = %REF%, RD = %RD%, WR = %WR%;
105
106  assign dfi_p0_cke = 1'b1; assign dfi_p1_cke = 1'b1;
107  assign dfi_p0_odt = 1'b1; assign dfi_p1_odt = 1'b1;
108  assign dfi_p0_reset_n = 1'b1; assign dfi_p1_reset_n = 1'b1;
109
110  function [CW-1:0] dec2(input [CW-1:0] v); dec2 = v > 2 ? v - 2'd2 : {CW{1'b0}}; endfunction
111  function [CW-1:0] mx(input [CW-1:0] a, input [CW-1:0] b); mx = a > b ? a : b; endfunction
112
113  // the requests: the tail takes them, prep opens their rows, the head issues their column commands
114  reg q_we [0:QD-1]; reg [2:0] q_bank [0:QD-1]; reg [12:0] q_row [0:QD-1]; reg [7:0] q_col [0:QD-1];
115  reg [QB:0] tail, prep, head;
116  reg [QD-1:0] ready;
117  wire full = tail[QB] != head[QB] && tail[QB-1:0] == head[QB-1:0];
118  wire idle = tail == head, has_prep = prep != tail, has_cas = head != prep;
119  assign cmd_ready = !full;
120  wire [2:0] pb = q_bank[prep[QB-1:0]]; wire [12:0] pr = q_row[prep[QB-1:0]];
121  wire [2:0] hb = q_bank[head[QB-1:0]]; wire hwe = q_we[head[QB-1:0]]; wire [7:0] hcol = q_col[head[QB-1:0]];
122
123  // the next write's data, refilled in the clock its WRITE issues
124  reg [63:0] w_data; reg [7:0] w_we; reg w_held;
125
126  // the banks: open row, and when each command may next come, in DRAM clocks from this clock's phase 0
127  wire [7:0] open;
128  wire [8*13-1:0] orow;
129  wire [8*CW-1:0] act_w, pre_w;
130  wire [8*RCW-1:0] cas_w;
131  reg [CW-1:0] ccd_w, rd_w, wr_w, ref_w;
132  reg [%H%-1:0] acts;
133  reg [%RB%-1:0] refi; reg [3:0] owed; reg ref_mode;
134
135  // a prepared request in the prep request's bank; another prepared request in the head's
136  reg p_busy, h_shared; integer i;
137  always @(*) begin
138    p_busy = 1'b0; h_shared = 1'b0;
139    for (i = 0; i < QD; i = i + 1) begin
140      if (ready[i] && q_bank[i] == pb) p_busy = 1'b1;
141      if (ready[i] && i != head[QB-1:0] && q_bank[i] == hb) h_shared = 1'b1;
142    end
143  end
144
145  wire force_ref = owed >= POSTPONE;
146  wire p_hit = open[pb] && orow[pb * 13 +: 13] == pr;
147  wire p_go = has_prep && !ref_mode && !force_ref;
148  wire do_hit = p_go && p_hit;
149  wire do_act = p_go && !open[pb] && !p_busy && act_w[pb * CW +: CW] <= 1 && %FAW_OK% && %RRD_OK%;
150
151  wire cas_go = has_cas && cas_w[hb * RCW +: RCW] == 0 && ccd_w == 0 && (hwe ? wr_w == 0 && w_held : rd_w == 0);
152  wire keep = has_prep && pb == hb && p_hit;
153  wire ap = !h_shared && !keep;
154  assign wdata_ready = !w_held || cas_go && hwe;
155
156  reg all_pre, all_ready; integer b;
157  always @(*) begin
158    all_pre = 1'b1; all_ready = 1'b1;
159    for (b = 0; b < 8; b = b + 1) begin
160      if (open[b] && pre_w[b * CW +: CW] > 1) all_pre = 1'b0;
161      if (act_w[b * CW +: CW] > 1) all_ready = 1'b0;
162    end
163  end
164  wire ref_go = !ref_mode && owed != 0 && !has_cas && (idle || force_ref);
165  wire pre_all = ref_mode && |open && all_pre;
166  wire refresh = ref_mode && !(|open) && all_ready && ref_w <= 1;
167  wire tick = refi == 0;
168  wire [CW-1:0] ap_ready = mx(hwe ? WR2PRE : RD2PRE, pre_w[hb * CW +: CW]) + T_RP;
169
170  genvar g;
171  generate for (g = 0; g < 8; g = g + 1) begin : bank
172    reg op; reg [12:0] row; reg [CW-1:0] aw, pw; reg [RCW-1:0] cw;
173    assign open[g] = op; assign orow[g * 13 +: 13] = row;
174    assign act_w[g * CW +: CW] = aw; assign pre_w[g * CW +: CW] = pw; assign cas_w[g * RCW +: RCW] = cw;
175    wire act_here = do_act && pb == g, cas_here = cas_go && hb == g;
176    wire [CW-1:0] a_n = act_here ? T_RC + 1'b1 : cas_here && ap ? ap_ready
177                      : pre_all && op ? T_RPA + 1'b1 : refresh ? T_RFC + 1'b1 : aw;
178    wire [CW-1:0] p_n = act_here ? T_RAS + 1'b1 : cas_here ? (hwe ? WR2PRE : mx(pw, RD2PRE)) : pw;
179    always @(posedge sys_clk)
180      if (sys_rst) begin op <= 1'b0; aw <= 0; pw <= 0; cw <= 0; end
181      else begin
182        aw <= dec2(a_n); pw <= dec2(p_n);
183        cw <= act_here ? T_RCD - 1'b1 : cw > 2 ? cw - 2'd2 : {RCW{1'b0}};
184        if (act_here) op <= 1'b1;
185        else if (cas_here && ap || pre_all) op <= 1'b0;
186      end
187    always @(posedge sys_clk) if (act_here) row <= pr;
188  end endgenerate
189
190  always @(posedge sys_clk) begin
191    if (sys_rst) begin
192      tail <= 0; prep <= 0; head <= 0; ready <= 0; w_held <= 1'b0;
193      ccd_w <= 0; rd_w <= 0; wr_w <= 0; ref_w <= 0; acts <= 0; refi <= REFI - 1'b1; owed <= 0; ref_mode <= 1'b0;
194      rdata_valid <= 1'b0;
195      {dfi_p0_cs_n, dfi_p0_ras_n, dfi_p0_cas_n, dfi_p0_we_n} <= NOP; {dfi_p1_cs_n, dfi_p1_ras_n, dfi_p1_cas_n, dfi_p1_we_n} <= NOP;
196      dfi_p0_wrdata_en <= 1'b0; dfi_p1_wrdata_en <= 1'b0; dfi_p0_rddata_en <= 1'b0; dfi_p1_rddata_en <= 1'b0;
197    end else begin
198      if (cmd_valid && !full) tail <= tail + 1'b1;
199      if (wdata_valid && wdata_ready) w_held <= 1'b1; else if (cas_go && hwe) w_held <= 1'b0;
200      if (do_hit || do_act) begin prep <= prep + 1'b1; ready[prep[QB-1:0]] <= 1'b1; end
201      if (cas_go) begin head <= head + 1'b1; ready[head[QB-1:0]] <= 1'b0; end
202      ccd_w <= dec2(cas_go ? CAS2CAS : ccd_w);
203      rd_w <= dec2(cas_go && hwe ? mx(rd_w, WR2RD) : rd_w);
204      wr_w <= dec2(cas_go && !hwe ? mx(wr_w, RD2WR) : wr_w);
205      ref_w <= dec2(pre_all ? T_RPA + 1'b1 : ref_w);
206      acts <= {acts, do_act};
207      refi <= tick ? REFI - 1'b1 : refi - 1'b1;
208      owed <= owed + tick - refresh;
209      if (ref_go) ref_mode <= 1'b1; else if (refresh) ref_mode <= 1'b0;
210      {dfi_p0_cs_n, dfi_p0_ras_n, dfi_p0_cas_n, dfi_p0_we_n} <= cas_go ? (hwe ? WR : RD) : NOP;
211      dfi_p0_wrdata_en <= cas_go && hwe; dfi_p1_wrdata_en <= cas_go && hwe;
212      dfi_p0_rddata_en <= cas_go && !hwe; dfi_p1_rddata_en <= 1'b0;
213      {dfi_p1_cs_n, dfi_p1_ras_n, dfi_p1_cas_n, dfi_p1_we_n} <= do_act ? ACT : pre_all ? PRE : refresh ? REF : NOP;
214      rdata_valid <= dfi_p0_rddata_valid;
215    end
216  end
217
218  always @(posedge sys_clk) begin
219    if (cmd_valid && !full) begin
220      q_we[tail[QB-1:0]] <= cmd_payload_we; q_col[tail[QB-1:0]] <= cmd_payload_addr[7:0];
221      q_bank[tail[QB-1:0]] <= cmd_payload_addr[10:8]; q_row[tail[QB-1:0]] <= cmd_payload_addr[23:11];
222    end
223    if (wdata_valid && wdata_ready) begin w_data <= wdata_payload_data; w_we <= wdata_payload_we; end
224    dfi_p0_bank <= hb; dfi_p0_address <= {2'b00, ap, hcol, 2'b00};
225    dfi_p0_wrdata <= w_data[31:0]; dfi_p1_wrdata <= w_data[63:32];
226    dfi_p0_wrdata_mask <= ~w_we[3:0]; dfi_p1_wrdata_mask <= ~w_we[7:4];
227    dfi_p1_bank <= pb; dfi_p1_address <= do_act ? pr : {2'b00, pre_all, 10'd0};
228    rdata_payload_data <= {dfi_p1_rddata, dfi_p0_rddata};
229  end
230endmodule
231"#;
232    let pairs: [(&str, String); 25] = [
233        ("%PART%", g.part.to_string()),
234        ("%TCK%", t.tck_ps.to_string()),
235        ("%QB%", qb.to_string()),
236        ("%CW%", cw.to_string()),
237        ("%RCW%", rcw.to_string()),
238        ("%T_RCD%", t.trcd.to_string()),
239        ("%T_RP%", t.trp.to_string()),
240        ("%T_RPA%", t.trpa.to_string()),
241        ("%T_RAS%", t.tras.to_string()),
242        ("%T_RC%", t.trc.to_string()),
243        ("%T_RFC%", t.trfc.to_string()),
244        ("%RD2PRE%", t.read_to_precharge().to_string()),
245        ("%WR2PRE%", t.write_to_precharge().to_string()),
246        ("%WR2RD%", t.write_to_read().to_string()),
247        ("%RD2WR%", t.read_to_write().to_string()),
248        ("%CAS2CAS%", t.cas_to_cas().to_string()),
249        ("%RB%", bits_for(refi).to_string()),
250        ("%REFI%", refi.to_string()),
251        ("%POSTPONE%", REFRESH_POSTPONE_MAX.to_string()),
252        ("%H%", history.to_string()),
253        ("%FAW_OK%", faw_ok),
254        ("%RRD_OK%", rrd_ok),
255        ("%NOP%", pins(Ddr2Cmd::Nop)),
256        ("%ACT%", pins(Ddr2Cmd::Active)),
257        ("%PRE%", pins(Ddr2Cmd::Precharge)),
258    ];
259    let mut out = v.to_string();
260    for (k, val) in pairs.iter().chain([("%REF%", pins(Ddr2Cmd::Refresh)), ("%RD%", pins(Ddr2Cmd::Read)), ("%WR%", pins(Ddr2Cmd::Write))].iter()) {
261        out = out.replace(k, val);
262    }
263    assert!(!out.contains('%'), "an unfilled field in ddr2_dfi_ctrl:\n{out}");
264    out
265}
266
267#[cfg(test)]
268#[path = "../../../logicaffeine_tests/unit/logicaffeine_compile/codegen_sva/ddr2_dfi/tests.rs"]
269mod tests;