summaryrefslogtreecommitdiffstats
path: root/sdram.v
blob: 2741d5fb118048d54cabb9378250aab08b4389a2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
// -----------------------------------------------------------------------
//
//   Copyright 2010-2011 H. Peter Anvin - All Rights Reserved
//
//   This program is free software; you can redistribute it and/or modify
//   it under the terms of the GNU General Public License as published by
//   the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
//   Boston MA 02110-1301, USA; either version 2 of the License, or
//   (at your option) any later version; incorporated herein by reference.
//
// -----------------------------------------------------------------------

//
// Simple SDRAM controller
//
// Very simple non-parallelizing SDRAM controller for the ABC8000 project.
// This is extremely inefficient in terms of bandwidth, using no more than
// 10% of the available bandwidth under even optimal conditions.
//

module sdram (
	      // System bus interface
	      input         rst_n,
	      input         sys_clk, // 25 MHz system clock
	      input [15:0]  cpu_do,
	      output [15:0] cpu_di,
	      input [22:1]  cpu_a,
	      input         msel,
	      input [1:0]   cpu_be_n,
	      input         cpu_r_wn,
	      output        cpu_wait_n,

	      // Video unit interface
	      input         vdu_start,  // VDU line start strobe
	      input [22:1]  vdu_a,      // VDU line address
	      input  [6:0]  vdu_cnt,    // VDU strobe count
	      output [15:0] vdu_d,      // VDU data out
	      input         vdu_ack,    // VDU data strobe

	      // SDRAM hardware interface
	      input         dram_clk,   // SDRAM clock (125 MHz)
	      output        dram_cke,   // SDRAM clock enable
	      output        dram_cs_n,  // SDRAM CS#
	      output        dram_ras_n,	// SDRAM RAS#
	      output        dram_cas_n,	// SDRAM CAS#
	      output        dram_we_n,  // SDRAM WE#
	      output  [1:0] dram_dqm,   // SDRAM DQM (per byte)
	      output  [1:0] dram_ba,    // SDRAM bank selects
	      output [11:0] dram_a,     // SDRAM address bus
	      inout  [15:0] dram_dq     // SDRAM data bus
	      );

   // Commands (CS#, RAS#, CAS#, WE#)
   parameter 		    cmd_desl = 4'b1111;
   parameter 		    cmd_nop  = 4'b0111;
   parameter 		    cmd_bst  = 4'b0110;
   parameter 		    cmd_rd   = 4'b0101;
   parameter 		    cmd_wr   = 4'b0100;
   parameter 		    cmd_act  = 4'b0011;
   parameter 		    cmd_pre  = 4'b0010;
   parameter 		    cmd_ref  = 4'b0001;
   parameter 		    cmd_mrs  = 4'b0000;

   // Output registers
   reg 			    sdram_cke;
   assign 		    dram_cke = sdram_cke;
   reg [3:0]		    sdram_cmd;
   assign 		    dram_cs_n  = sdram_cmd[3];
   assign 		    dram_ras_n = sdram_cmd[2];
   assign 		    dram_cas_n = sdram_cmd[1];
   assign 		    dram_we_n  = sdram_cmd[0];
   reg [11:0] 		    sdram_a;
   assign 		    dram_a = sdram_a;
   reg [1:0] 		    sdram_ba;
   assign 		    dram_ba = sdram_ba;
   reg [1:0] 		    sdram_dqm;
   assign 		    dram_dqm = sdram_dqm;
   reg [15:0] 		    sdram_q; // Data from SDRAM
   reg [15:0] 		    sdram_d; // Data to SDRAM
   reg 			    sdram_d_en; // Drive data out
   assign 		    dram_dq = sdram_d_en ? sdram_d : 16'hzzzz;

   //
   // VDU line FIFO
   //
   reg 		            vdu_rdy; // dram_dq contains data for VDU
   wire [4:0] 		    vdu_used;

   reg  [6:0] 		    vdu_ctr;
   reg  [1:0] 		    vdu_skip;

   reg [22:3] 		    vdu_a_ctr;

   reg [15:0] 		    sdram_vdu_q;

   // ~vdu_used gives the number of free slots in the FIFO.  Because
   // of delays in the reporting of the output count, we need to have space
   // for two bursts: one that is started before we notice we are almost
   // full, and one to deal with fractionals.
   wire 		    vdu_go = ~vdu_start & ~&vdu_used[4:3] & |vdu_ctr;

   vdufifo vdufifo (
		    .aclr	( ~rst_n | vdu_start ),
		    .data	( sdram_vdu_q ),
		    .rdclk	( sys_clk ),
		    .rdreq	( vdu_ack ),
		    .wrclk	( dram_clk ),
		    .wrreq	( vdu_rdy & ~|vdu_skip ),
		    .q		( vdu_d ),
		    .wrusedw	( vdu_used )
		    );

   // Data strobe skip counter
   always @(negedge rst_n or posedge dram_clk)
     if (~rst_n)
       vdu_skip <= 2'd0;
     else
       begin
	  if (vdu_start)
	    vdu_skip <= vdu_a[2:1];
	  else if (vdu_rdy & |vdu_skip)
	    vdu_skip <= vdu_skip - 1'b1;
       end

   // Data input latch
   always @(posedge dram_clk)
     sdram_vdu_q <= dram_dq;

   //
   // CPU request signals and private copies of input data
   //
   // Note: the timing of the 68000 bus for write requests is really
   // weird.  AS#, R/W# and address are available after S2,
   // data after S3, and byte enables(!) after S4; DTACK is sampled at S5.
   // We therefore need to delay the start of the write: we can issue
   // ACT based on address information alone, but we need the full data
   // and byte enables before WR.  Fortunately we don't have to wait for
   // the write cycle to actually complete once it has been started.
   //
   wire 		    cpu_req   = msel;
   wire 		    cpu_rdreq = cpu_req & cpu_r_wn;
   wire 		    cpu_wrreq = cpu_req & ~cpu_r_wn;

   reg [1:0] 		    cpu_req_s;  // Delay in the sys_clk domain

   reg 			    cpu_done;
   reg 			    cpu_wait_f; // CPU wait in the SDRAM clock domain
   reg 			    cpu_wait_s; // CPU wait in the sys_clk clock domain

   reg [15:0] 		    sdram_q_s;  // Output data in the sys_clk domain

   reg [15:0] 		    cpu_do_q;   // Private latched copy of cpu_do
   reg [1:0] 		    cpu_be_q;	// Private copy of cpu_be_n

   assign 		    cpu_wait_n = ~cpu_wait_s;
   assign 		    cpu_di     = sdram_q_s;

   always @(negedge rst_n or posedge sys_clk)
     if (~rst_n)
       begin
	  sdram_q_s  <= 16'hFFFF;
	  cpu_req_s  <= 2'b00;
	  cpu_wait_s <= 1'b0;
       end
     else
       begin
	  sdram_q_s  <= cpu_rdreq ? sdram_q : 16'hFFFF;
	  cpu_req_s  <= { cpu_req_s[0], cpu_req };
	  cpu_wait_s <= cpu_wait_f;
       end

   always @(negedge rst_n or posedge sys_clk)
     if (~rst_n)
       cpu_do_q <= 16'hxxxx;
     else if (cpu_wrreq)
       cpu_do_q <= cpu_do;

   // Because UDS#/LDS# come in late, we have to latch them on the fast clock
   always @(negedge rst_n or posedge dram_clk)
     if (~rst_n)
       cpu_be_q <= 2'bxx;
     else
       cpu_be_q <= cpu_be_n;

   // State machine and counters
   reg [15:0] 		    rfsh_ctr; // Refresh/initialization timer
   reg [3:0] 		    nop_ctr;  // Insert NOPs into a cycle
   reg [3:0] 		    state;

   always @(posedge dram_clk or negedge rst_n)
     if (~rst_n)
       rfsh_ctr <= 16'd0;
     else if (state == st_rfsh)
       rfsh_ctr <= 16'd0;
     else
       rfsh_ctr <= rfsh_ctr + 1;

   parameter st_reset       = 4'hC;
   parameter st_rfsh1       = 4'hD;	// 1st refresh during initialization
   parameter st_rfsh2       = 4'hE;	// 2st refresh during initialization
   parameter st_mrs         = 4'hF;
   parameter st_idle        = 4'h0;
   parameter st_cpu_act     = 4'h1;
   parameter st_cpu_rd_cmd  = 4'h2;
   parameter st_cpu_rd_pre  = 4'h3;
   parameter st_cpu_rd_data = 4'h4;
   parameter st_cpu_wr_cmd  = 4'h6;
   parameter st_rfsh        = 4'h7;
   parameter st_vdu_act     = 4'h8;
   parameter st_vdu_rd_cmd  = 4'h9;
   parameter st_vdu_rd_data = 4'hA;

   //
   // Careful with the timing here... there is one cycle between
   // registers and wires, and the SDRAM observes the clock 1/2
   // cycle from the internal logic.
   //
   always @(posedge dram_clk or negedge rst_n)
     if (~rst_n)
       begin
	  sdram_cke     <= 1'b0;
	  sdram_cmd     <= cmd_desl;
	  sdram_a       <= 12'h000;
	  sdram_ba      <= 2'bxx;
	  sdram_dqm     <= 2'b00;
	  sdram_d       <= 16'hxxxx;
	  sdram_d_en    <= 1'b0;
	  nop_ctr       <= 4'd0;
	  state         <= st_reset;
	  cpu_wait_f    <= 1'b0;
	  cpu_done      <= 1'b1;
	  vdu_rdy       <= 1'b0;
	  vdu_a_ctr     <= 20'hx_xxxx;
	  vdu_ctr       <= 5'd0;
       end
     else
       begin
	  sdram_cke <= 1'b1;	// Always true once out of reset

	  // Default values
	  sdram_a       <= 12'h000;
	  sdram_ba      <= 2'bxx;
	  sdram_dqm     <= 2'b00;
	  sdram_d       <= 16'hxxxx;
	  sdram_d_en    <= 1'b0;

	  cpu_done      <= cpu_done | ~cpu_req;
	  cpu_wait_f    <= cpu_wait_f | (cpu_done &
			   (cpu_rdreq | (cpu_wrreq & (&cpu_req_s))));

	  if (vdu_start)
	    begin
	       vdu_a_ctr   <= vdu_a[22:3];
	       vdu_ctr     <= vdu_cnt;
	    end

	  if (|nop_ctr)
	    begin
	       sdram_cmd <= cmd_nop;
	       nop_ctr   <= nop_ctr - 1;
	    end
	  else
	    begin
	       vdu_rdy       <= 1'b0;

	       case (state)
		 st_reset:
		   begin
		      sdram_cmd  <= cmd_desl;
		      sdram_d_en <= 1'b1; // Don't float indefinitely
		      sdram_d    <= 16'hffff;
		      if (rfsh_ctr[15])
			begin
			   sdram_cmd   <= cmd_pre;
			   sdram_a[10] <= 1'b1; // Precharge All Banks
			   state       <= st_rfsh1;
			   nop_ctr     <= 4'd2; // tRP = 3 cycles
			end
		   end
		 st_rfsh1:
		   begin
		      sdram_cmd <= cmd_ref;
		      state     <= st_rfsh2;
		      nop_ctr   <= 4'd8;	// tARFC = 9 cycles
		   end
		 st_rfsh2:
		   begin
		      sdram_cmd <= cmd_ref;
		      state     <= st_mrs;
		      nop_ctr   <= 4'd8;	// tARFC = 9 cycles
		   end
		 st_mrs:
		   begin
		      sdram_cmd <= cmd_mrs;
		      // Writes are single location
		      // CAS=3, burst 4, sequential
		      sdram_a   <= 12'b0010_0011_0010;
		      sdram_ba  <= 2'b00;
		      state     <= st_idle;
		      nop_ctr   <= 4'd2;
		   end
		 st_idle:
		   begin
		      sdram_cmd  <= cmd_desl;
		      sdram_d_en <= 1'b1; // Don't float indefinitely
		      sdram_d    <= 16'hffff;
		      if (rfsh_ctr[10])
			state <= st_rfsh; // High priority refresh
		      else if (cpu_wait_f)
			state <= st_cpu_act;
		      else if (vdu_go & ~cpu_req)
			state <= st_vdu_act;
		      else if (rfsh_ctr[9] & ~cpu_req)
			state <= st_rfsh; // Low priority refresh
		   end
		 st_rfsh:
		   begin
		      sdram_cmd <= cmd_ref;
		      state     <= st_idle;
		      nop_ctr   <= 4'd8;	// tARFC = 9 cycles
		   end
		 st_cpu_act:
		   begin
		      sdram_cmd    <= cmd_act;
		      sdram_a      <= cpu_a[22:11];
		      sdram_ba     <= cpu_a[10:9];
		      state        <= cpu_r_wn ? st_cpu_rd_cmd : st_cpu_wr_cmd;
		      nop_ctr      <= 4'd2; // tRCD = 3 cycles
		      //
		      // At this point, we know we will have the data
		      // within less than 1 CPU cycle = 10 SDRAM cycles,
		      // so we can assert DTACK# at this time.
		      //
		      cpu_wait_f   <= 1'b0;
		      cpu_done     <= 1'b0; // Don't re-issue this request
		   end
		 st_cpu_rd_cmd:
		   begin
		      sdram_cmd    <= cmd_rd;
		      sdram_a[7:0] <= cpu_a[8:1];
		      sdram_ba     <= cpu_a[10:9];
		      state        <= st_cpu_rd_pre;
		   end
		 st_cpu_rd_pre:
		   begin
		      // The PRE command issued in the cycle immediately
		      // after the read will terminate the burst after
		      // exactly one data strobe.  Note that the nop_ctr
		      // below is the CL, not CL-1, due to bus turnaround.
		      sdram_cmd    <= cmd_pre;
		      sdram_ba     <= cpu_a[10:9];
		      state        <= st_cpu_rd_data;
		      nop_ctr      <= 4'd3; // == CAS latency
		   end
		 st_cpu_rd_data:
		   begin
		      // Note: with one DESL from the idle state,
		      // tRC (8) and tRP (3) are both satisfied
		      sdram_cmd    <= cmd_nop;
		      sdram_q      <= dram_dq;
		      state        <= st_idle;
		   end
		 st_cpu_wr_cmd:
		   begin
		      // The nop_ctr needs to be set to the higher of:
		      // tRDL (2) + tRP (3) - 1 (st_idle) = 4
		      // tRC (8) - tRCD (3) - 1 = 4
		      sdram_cmd    <= cmd_wr;
		      sdram_a[7:0] <= cpu_a[8:1];
		      sdram_a[10]  <= 1'b1; // Auto precharge
		      sdram_a[9]   <= 1'b1; // Single write
		      sdram_ba     <= cpu_a[10:9];
		      sdram_dqm    <= cpu_be_q;
		      sdram_d      <= cpu_do_q;
		      sdram_d_en   <= 1'b1;
		      state        <= st_idle;
		      nop_ctr      <= 4'd4;
		   end // case: st_cpu_wr_cmd
		 st_vdu_act:
		   begin
		      sdram_cmd    <= cmd_act;
		      sdram_a      <= vdu_a_ctr[22:11];
		      sdram_ba     <= vdu_a_ctr[10:9];
		      state        <= st_vdu_rd_cmd;
		      nop_ctr      <= 4'd2; // tRCD = 3 cycles
		   end
		 st_vdu_rd_cmd:
		   begin
		      sdram_cmd    <= cmd_rd;
		      sdram_a[7:0] <= { vdu_a_ctr[8:3], 2'b00 };
		      sdram_a[10]  <= 1'b1; // Auto precharge
		      sdram_ba     <= vdu_a_ctr[10:9];
		      nop_ctr      <= 4'd4; // == CAS latency + 1
		      state        <= st_vdu_rd_data;
		      vdu_a_ctr    <= vdu_a_ctr + 1'b1;
		      vdu_ctr      <= vdu_ctr - 1'b1;
		   end
		 st_vdu_rd_data:
		   begin
		      // Note: with one DESL from the idle state,
		      // tRC (8) and tRP (3) are both satisfied
		      sdram_cmd    <= cmd_nop;
		      vdu_rdy      <= 1'b1;
		      nop_ctr      <= 4'd3; // Burst length - 1
		      state        <= st_idle;
		   end
	       endcase // case(state)
	    end // else: !if(|nop_ctr)
       end // else: !if(~rst_n)
endmodule // sdram