1use super::ddr2_jedec::{Ddr2Timing, REFRESH_POSTPONE_MAX};
25use super::ddr2_phy::{ddr2_pins, Ddr2Cmd};
26
27pub const DFI_CTRL_QUEUE: u32 = 4;
29
30fn bits_for(v: u64) -> u32 {
32 (u64::BITS - v.leading_zeros()).max(1)
33}
34
35fn pins(cmd: Ddr2Cmd) -> String {
37 let (cs, ras, cas, we) = ddr2_pins(cmd);
38 format!("4'b{cs}{ras}{cas}{we}")
39}
40
41pub 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;