SDRAM读写控制器

第1节

–作者:小黑同学

本文为明德扬原创及录用文章,转载请注明出处!

1.1 总体设计

1.1.1 概述

同步动态随机存取内存(synchronous dynamic randon-access menory,简称SDRAM)是有一个同步接口的动态随机存取内存(DRAM)。通常DRAM是有一个异步接口的,这样它可以随时响应控制输入的变化。而SDRAM有一个同步接口,在响应控制输入前会等待一个时钟信号,这样就能和计算机的系统总线同步。时钟被用来驱动一个有限状态机,对进入的指令进行管线操作。这使得SDRAM与没有同步接口的异步DRAM相比,可以有一个更复杂的操作模式。
管线意味着芯片可以在处理完之前的指令前,接受一个新的指令。在一个写入的管线中,写入命令在另一个指令执行完之后可以立刻执行,而不需要等到数据写入存储队列的时间。在一个读取的流水线中,需要的数据在读取指令发出之后固定数量的时钟频率后到达,而这个等待的过程可以发出其他附加指令。这种延迟被称为等待时间(Latency),在为计算机购买内存时是一个很重要的参数。
SDRAM之所以称为DRAM就是因为他要不断进行刷新才能保留住数据,因为刷新是DRAM最重要的操作。那么要隔多长时间重复一次刷新,目前公认的标准是,存储体中电容的数据有效保存期上限是64ms,也就是每一行刷新的循环周期是64ms。
SDRAM是多Bank结构,例如在一个具有两个Bank的SDRAM模组中,其中一个Bank在进行预充电期间,另一个Bank却马上可以被读取,这样当进行一次读取后,又马上读取已经预充电Bank的数据时,就无需等待而是可以直接读取了,这也就大大提高了存储器的访问速度

1.1.2 设计目标

设计SDRAM读写控制器来控制开发板上的一片SDRAM进行读写数据的操作,具体功能要求如下:
1.SDRAM的读写分别由两个按键进行控制,每按下一次,就会产生一个读使能或者写使能;
2.SDRAM读写模式为全页突发模式,每次写入某个Bank512个数据,在读此Bank的时候,也应该读出相同的512个数据;
3.SDRAM读写地址都是从地址0开始;
4.通过一个按键控制读写SDRAM的Bank地址,按键每按下一次,Bank地址加1。

1.1.3 系统结构框图

系统结构框图如下图一所示:
在这里插入图片描述

图一

1.1.4 模块功能

按键检测模块实现功能
1、将外来异步信号打两拍处理,将异步信号同步化。
2、实现20ms按键消抖功能。
3、实现矩阵键盘或者普通案件的检测功能,并输出有效按键信号。
锁相环
1、产生工程所需要的100M时钟。
数据产生模块实现功能
1、通过按键控制产生读/写请求。
2、通过按键控制Bank地址选择。
3、产生地址和写数据。
SDRAM接口模块实现功能
1、接收上游模块发送的读/写请求、Bank地址、行地址和写数据,产生SDRAM的控制时序。

1.1.5 顶层信号

信号名I/O位宽定义
clkI1系统工作时钟 50M
rst_nI1系统复位信号,低电平有效
KeyI44位按键信号,开发板按键为矩阵键盘时,不需要该信号
Key_colI44位矩阵键盘列信号,默认高电平,开发板按键为普通按键时,不需要该信号
Key_rowO44位矩阵键盘行信号,默认低电平,开发板按键为普通按键时,不需要该信号
dqI/O16SDRAM数据总线,既能作为数据输出,也能作为数据输入。
ckeO1SDRAM时钟使能信号,决定是否启用clk输入,为高电平时,时钟有效。
csO1SDRAM片选信号,决定设备内是否启用命令输入,当cs为低时启用,当cs为高时禁用命令输入。
rasO1行地址选通信号,低电平有效
casO1列地址选通信号,低电平有效
weO1写使能信号,低电平有效
dqmO2数据掩码,控制I/O的高低字节,低电平有效。例如:2’b10,表示数据高字节无效,低字节有效。
sd_addrO13SDRAM地址信号。
sd_bankO2Bank地址选择信号,通过该信号决定哪个Bank正处于激活、读、写、预充电等命令期间。
sd_clkO1SDRAM输入时钟,除cke外,SDRAM的所有输入与该引脚的上升沿同步获得。

1.1.6 三态门

由于SDRAM只有一条数据总线,虽然可以既当作输入,又当作输出来用,但是输入和输出是不能同时进行的,因此需要在工程的顶层设计中采用三态门。代码如下:
在这里插入图片描述

关于三态门详细的介绍可以看明德扬《FPGA至简设计原理与应用》书中的第一篇第三章5.2.4高阻态一节。
【FPGA至简设计原理与应用】书籍连载03 第一篇FPGA基础知识第三章硬件描述语言Verilog

1.1.7 参考代码

下面是使用工程的顶层代码:

1.module sdram_top(
2.    clk      ,
3.    rst_n    ,
4.    key      ,
5.    dq       ,
6.    cke      ,
7.    cs       ,  
8.    ras      ,
9.    cas      ,
10.    we       ,
11.    dqm      ,
12.    sd_addr  ,
13.    sd_bank  ,
14.    sd_clk     
15.    );
16.
17.    input              clk     ;
18.    input              rst_n   ;
19.    input  [3:0]       key     ;
20.    inout  [15:0]      dq      ;
21.    output             cke     ;    
22.    output             cs      ;
23.    output             ras     ;
24.    output             cas     ;
25.    output             we      ;
26.    output [1 :0]      dqm     ;
27.    output [12:0]      sd_addr ;
28.    output [1 :0]      sd_bank ;
29.    output             sd_clk  ;
30.
31.
32.
33.    wire               cke     ;
34.    wire               cs      ;
35.    wire               ras     ;
36.    wire               cas     ;
37.    wire               we      ;
38.    wire  [1 :0]       dqm     ;
39.    wire  [12:0]       sd_addr ;
40.    wire  [1 :0]       sd_bank ;
41.    wire               sd_clk  ;
42.
43.
44.
45.    wire              clk_100m ;
46.    wire              wr_ack   ;
47.    wire              rd_ack   ;
48.    wire              wr_req   ;
49.    wire              rd_req   ;
50.    wire [1 :0]       bank     ;
51.    wire [12:0]       addr     ;
52.    wire [15:0]       wdata    ;
53.    wire [15:0]       rdata    ;
54.    wire              rdata_vld;
55.    wire [3:0 ]       key_vld  ;
56.
57.    wire [15:0]       dq_in    ;
58.    wire [15:0]       dq_out   ;
59.    wire              dq_out_en;
60.
61.    assign  dq_in = dq;
62.    assign  dq    = dq_out_en?dq_out:16'hzzzz;
63.        
64.
65.
66.    pll_100m uut_pll(
67.    	.inclk0    (clk      ),
68.    	.c0        (clk_100m )
69.    );
70.
71.    key_module uut_key(
72.        .clk       (clk_100m ), 
73.        .rst_n     (rst_n    ), 
74.        .key_in    (key      ), 
75.        .key_vld   (key_vld  ),
76.    );
77.        
78.    data_ctrl uut_ctrl(
79.        .clk       (clk_100m ), 
80.        .rst_n     (rst_n    ),
81.        .key_vld   (key_vld  ),
82.        .wr_ack    (wr_ack   ),   
83.        .rd_ack    (rd_ack   ),   
84.        .wr_req    (wr_req   ),   
85.        .rd_req    (rd_req   ),   
86.        .bank      (bank     ),   
87.        .addr      (addr     ),    
88.        .wdata     (wdata    )    
89.        );
90.    
91.    sdram_intf uut_sdram(
92.        .clk       (clk_100m ),   
93.        .rst_n     (rst_n    ),
94.        .wr_req    (wr_req   ),
95.        .rd_req    (rd_req   ),
96.        .bank      (bank     ),
97.        .addr      (addr     ),
98.        .wdata     (wdata    ),
99.        .dq_in     (dq_in    ),
100.        .dq_out    (dq_out   ),
101.        .dq_out_en (dq_out_en),
102.        .wr_ack    (wr_ack   ),
103.        .rd_ack    (rd_ack   ),
104.        .rdata     (rdata    ),
105.        .rdata_vld (rdata_vld),
106.        .cke       (cke      ),
107.        .cs        (cs       ),
108.        .ras       (ras      ),
109.        .cas       (cas      ),
110.        .we        (we       ),
111.        .dqm       (dqm      ),
112.        .sd_addr   (sd_addr  ),
113.        .sd_bank   (sd_bank  ),
114.        .sd_clk    (sd_clk   )  
115.        
116.    );
117.
118.
119.
120.    endmodule

1.2 按键检测模块设计

1.2.1 接口信号

下面为使用矩阵键盘时的接口信号:

信号接口方向定义
clk输入系统时钟
rst_n输入低电平复位信号
key_col输入矩阵键盘列输入信号
Key_row输出矩阵键盘行输出信号
Key_en输出按键按下位置指示信号

下面是使用普通按键时的接口信号:

信号接口方向定义
clk输入系统时钟
rst_n输入低电平复位信号
Key_in输入按键输入信号
Key_vld输出按键按下指示信号

1.2.2 设计思路

在前面的按键控制数字时钟的案例中已经有介绍,所以这里不在过多介绍,详细介绍请看下方链接:
http://fpgabbs.com/forum.php?mod=viewthread&tid=310

1.2.3 参考代码

1.//矩阵键盘
2.always  @(posedge clk or negedge rst_n)begin
3.    if(rst_n==1'b0)begin
4.        key_col_ff0 <= 4'b1111;
5.        key_col_ff1 <= 4'b1111;
6.    end
7.    else begin
8.        key_col_ff0 <= key_col    ;
9.        key_col_ff1 <= key_col_ff0;
10.    end
11.end
12.
13.
14.always @(posedge clk or negedge rst_n) begin 
15.    if (rst_n==0) begin
16.        shake_cnt <= 0; 
17.    end
18.    else if(add_shake_cnt) begin
19.        if(end_shake_cnt)
20.            shake_cnt <= 0; 
21.        else
22.            shake_cnt <= shake_cnt+1 ;
23.   end
24.end
25.assign add_shake_cnt = key_col_ff1!=4'hf;
26.assign end_shake_cnt = add_shake_cnt  && shake_cnt == TIME_20MS-1 ;
27.
28.
29.always  @(posedge clk or negedge rst_n)begin
30.    if(rst_n==1'b0)begin
31.        state_c <= CHK_COL;
32.    end
33.    else begin
34.        state_c <= state_n;
35.    end
36.end
37.
1.always  @(*)begin
38.    case(state_c)
39.        CHK_COL: begin
40.                     if(col2row_start )begin
41.                         state_n = CHK_ROW;
42.                     end
43.                     else begin
44.                         state_n = CHK_COL;
45.                     end
46.                 end
47.        CHK_ROW: begin
48.                     if(row2del_start)begin
49.                         state_n = DELAY;
50.                     end
51.                     else begin
52.                         state_n = CHK_ROW;
53.                     end
54.                 end
55.        DELAY :  begin
56.                     if(del2wait_start)begin
57.                         state_n = WAIT_END;
58.                     end
59.                     else begin
60.                         state_n = DELAY;
61.                     end
62.                 end
63.        WAIT_END: begin
64.                     if(wait2col_start)begin
65.                         state_n = CHK_COL;
66.                     end
67.                     else begin
68.                         state_n = WAIT_END;
69.                     end
70.                  end
71.       default: state_n = CHK_COL;
72.    endcase
73.end
74.assign col2row_start = state_c==CHK_COL  && end_shake_cnt;
75.assign row2del_start = state_c==CHK_ROW  && row_index==3 && end_row_cnt;
76.assign del2wait_start= state_c==DELAY    && end_row_cnt;
77.assign wait2col_start= state_c==WAIT_END && key_col_ff1==4'hf;
78.
79.always  @(posedge clk or negedge rst_n)begin
80.    if(rst_n==1'b0)begin
81.        key_row <= 4'b0;
82.    end
83.    else if(state_c==CHK_ROW)begin
84.        key_row <= ~(1'b1 << row_index);
85.    end
86.    else begin
87.        key_row <= 4'b0;
88.    end
89.end
90.
91.
92.always @(posedge clk or negedge rst_n) begin 
93.    if (rst_n==0) begin
94.        row_index <= 0; 
95.    end
96.    else if(add_row_index) begin
97.        if(end_row_index)
98.            row_index <= 0; 
99.        else
100.            row_index <= row_index+1 ;
101.   end
102.   else if(state_c!=CHK_ROW)begin
103.       row_index <= 0;
104.   end
105.end
106.assign add_row_index = state_c==CHK_ROW && end_row_cnt;
107.assign end_row_index = add_row_index  && row_index == 4-1 ;
108.
109.
110.always @(posedge clk or negedge rst_n) begin 
111.    if (rst_n==0) begin
112.        row_cnt <= 0; 
113.    end
114.    else if(add_row_cnt) begin
115.        if(end_row_cnt)
116.            row_cnt <= 0; 
117.        else
118.            row_cnt <= row_cnt+1 ;
119.   end
120.end
121.assign add_row_cnt = state_c==CHK_ROW || state_c==DELAY;
122.assign end_row_cnt = add_row_cnt  && row_cnt == 16-1 ;
123.
124.
125.always  @(posedge clk or negedge rst_n)begin
126.    if(rst_n==1'b0)begin
127.        key_col_get <= 0;
128.    end
129.    else if(state_c==CHK_COL && end_shake_cnt ) begin
130.        if(key_col_ff1==4'b1110)
131.            key_col_get <= 0;
132.        else if(key_col_ff1==4'b1101)
133.            key_col_get <= 1;
134.        else if(key_col_ff1==4'b1011)
135.            key_col_get <= 2;
136.        else 
137.            key_col_get <= 3;
138.    end
139.end
140.
141.
142.always  @(posedge clk or negedge rst_n)begin
143.    if(rst_n==1'b0)begin
144.        key_out <= 0;
145.    end
146.    else if(state_c==CHK_ROW && end_row_cnt)begin
147.        key_out <= {row_index,key_col_get};
148.    end
149.    else begin
150.        key_out <= 0;
151.    end
152.end
153.
154.always  @(posedge clk or negedge rst_n)begin
155.    if(rst_n==1'b0)begin
156.        key_vld <= 1'b0;
157.    end
158.    else if(state_c==CHK_ROW && end_row_cnt && key_col_ff1[key_col_get]==1'b0)begin
159.        key_vld <= 1'b1;
160.    end
161.    else begin
162.        key_vld <= 1'b0;
163.    end
164.end
165.
166.
167.always  @(*)begin
168.    if(rst_n==1'b0)begin
169.        key_en = 0;
170.    end
171.    else if(key_vld && key_out==0)begin
172.        key_en = 4'b0001;
173.    end
174.    else if(key_vld && key_out==1)begin
175.        key_en = 4'b0010;
176.    end
177.    else if(key_vld && key_out==2)begin
178.        key_en = 4'b0100;
179.    end
180.    else begin
181.        key_en = 0;
182.    end
183.End
184.
185./*********************
186.普通按键
187.*********************/
188.always  @(posedge clk or negedge rst_n)begin
189.    if(rst_n==1'b0)begin
190.        cnt <= 20'b0;
191.    end
192.    else if(add_cnt)begin
193.        if(end_cnt)
194.            cnt <= 20'b0;
195.        else
196.            cnt <= cnt + 1'b1;
197.    end
198.    else begin
199.        cnt <= 0;
200.    end
201.end
202.
203.assign add_cnt = flag_add==1'b0 && (&key_in_ff1==0);
204.assign end_cnt = add_cnt && cnt == TIME_20MS - 1;
205.
206.
207.always  @(posedge clk or negedge rst_n)begin
208.    if(rst_n==1'b0)begin
209.        flag_add <= 1'b0;
210.    end
211.    else if(end_cnt)begin
212.        flag_add <= 1'b1;
213.    end
214.    else if(&key_in_ff1==1)begin
215.        flag_add <= 1'b0;
216.    end
217.end
218.
219.
220.always  @(posedge clk or negedge rst_n)begin
221.    if(rst_n==1'b0)begin
222.        key_in_ff0 <= {{KEY_W}{1'b1}};
223.        key_in_ff1 <= {{KEY_W}{1'b1}};
224.    end
225.    else begin
226.        key_in_ff0 <= key_in    ;
227.        key_in_ff1 <= key_in_ff0;
228.    end
229.end
230.
231.
232.always  @(posedge clk or negedge rst_n)begin
233.    if(rst_n==1'b0)begin
234.        key_vld <= 0;
235.    end
236.    else if(end_cnt)begin
237.        key_vld <= ~key_in_ff1;
238.    end
239.    else begin
240.        key_vld <= 0;
241.    end
242.end

1.3 锁相环

1.3.1 接口信号

信号名I/O位宽定义
Inclk0I1输入时钟50M
C0O1输出时钟100M

1.3.2 设计思路

此模块是使用Quartus生成的PLL IP核,相关的生成步骤、功能原理等可以看明德扬论坛中关于PLL的介绍。
IP核设计(PLL)

1.4 数据产生模块设计

1.4.1 接口信号

信号名I/O位宽定义
clkI1工作时钟 100M
rst_nI1系统复位信号,低电平有效
Key_vldI4按键有效指示信号
Wr_ackI1写数据响应
rd_ackI1读数据响应
Wr_reqO1写数据请求
rd_reqO1读数据响应
bankO2Bank地址选择信号
addrO13SDRAM地址信号
WdataO16SDRAM写数据

1.4.2 设计思路

该模块主要实现的功能是根据按键,产生读请求、写请求、Bank地址、写数据和SDRAM地址。下面是该模块主要信号的设计思路:
写请求wr_req:初始状态为低电平,表示没有往SDRAM里面写数据的请求;当按下按键key1的时候,写请求变为高电平,表示请求往SDRAM内部写入数据,因此写请求拉高的条件为key_vld[0]1;当接收到接收到写响应为高电平的时候,表示同意往SDRAM写入数据,此时将写请求置为低电平,因此写请求的拉低条件为wr_ack1。
读请求rd_req:初始状态为低电平,表示没有读出SDRAM中数据的请求;当按下按键key2的时候,读请求变为高电平,表示请求读出SDRAM内部数据,因此读请求拉高的条件为key_vld[1]1;当接收到接收到读响应为高电平的时候,表示SDRAM同意读出数据,此时将读请求置为低电平,因此读请求的拉低条件为rd_ack1。
读写Bank地址信号bank:初始状态为0,表示默认选择Bank0进行数据的读写操作;加一条件为key_vld[2]==1,表示按键key3每按下一次,Bank地址就加一;结束条件为数4个,因为一片SDRAM共有4个Bank,数完就清零。
在这里插入图片描述

SDRAM地址信号addr:固定为0即可。
写数据wdata:该信号表示要写入SDRAM中的数据,在接收到写响应后有效。设置时钟计数器cnt2,在收到写响应之后开始计数,由于本工程使用SDRAM全页突发模式一次写数据为512个,因此该计数器结束条件为数512个,将该计数器的值作为写数据。
在这里插入图片描述

1.4.3 参考代码

1.always  @(posedge clk or negedge rst_n)begin
2.    if(rst_n==1'b0)begin
3.        wr_req <= 0;
4.    end
5.    else if(key_vld[0]==1)begin
6.        wr_req <= 1;
7.    end
8.    else if(wr_ack)begin
9.        wr_req <= 0;
10.    end
11.end
12.
13.
14.always  @(posedge clk or negedge rst_n)begin
15.    if(rst_n==1'b0)begin
16.        rd_req <= 0;
17.    end
18.    else if(key_vld[1]==1)begin
19.        rd_req <= 1;
20.    end
21.    else if(rd_ack)begin
22.        rd_req <= 0;
23.    end
24.end
25.
26.
27.always @(posedge clk or negedge rst_n) begin 
28.    if (rst_n==0) begin
29.        bank <= 0; 
30.    end
31.    else if(add_bank) begin
32.        if(end_bank)
33.            bank <= 0; 
34.        else
35.            bank <= bank+1 ;
36.   end
37.end
38.assign add_bank = key_vld[2]==1;
39.assign end_bank = add_bank  && bank == 4-1 ;
40.
41.
42.always  @(*)begin
43.    if(flag_wr)begin
44.        wdata = {7'b0,cnt2};
45.    end
46.    else begin
47.        wdata = 0;
48.    end
49.end
50.
51.
52.always  @(posedge clk or negedge rst_n)begin
53.    if(rst_n==1'b0)begin
54.        flag_wr <= 0;
55.    end
56.    else if(wr_ack)begin
57.        flag_wr <= 1;
58.    end
59.    else if(end_cnt2)begin
60.        flag_wr <= 0;
61.    end
62.end
63.
64.
65.always @(posedge clk or negedge rst_n) begin 
66.    if (rst_n==0) begin
67.        cnt2 <= 0; 
68.    end
69.    else if(add_cnt2) begin
70.        if(end_cnt2)
71.            cnt2 <= 0; 
72.        else
73.            cnt2 <= cnt2+1 ;
74.   end
75.end
76.assign add_cnt2 = flag_wr;
77.assign end_cnt2 = add_cnt2  && cnt2 == 512-1 ;
78.
79.assign addr = 13'b0;

1.5 SDRAM接口模块设计

1.5.1 接口信号

信号名I/O位宽定义
clkI11工作时钟 100M
rst_nI1系统复位信号,低电平有效
Wr_reqI1写请求
rd_reqI1读请求
bankI2输入bank地址
addrI13地址信号
WdataI16写数据
dq_inI16SDRAM数据输入
dq_outO16写SDRAM数据信号
dq_out_enO1三态门使能
Wr_ackO1写响应
rd_ackO1读响应
rdataO16读数据
rdata_vldO1读数据有效指示信号
CkeO1SDRAM时钟使能信号,决定是否启用clk输入,为高电平时,时钟有效。
CsO1SDRAM片选信号,决定设备内是否启用命令输入,当cs为低时启用,当cs为高时禁用命令输入。
rasO1行地址选通信号,低电平有效
CasO1列地址选通信号,低电平有效
WeO1写使能信号,低电平有效
dqmO2数据掩码,控制I/O的高低字节,低电平有效。例如:2’b10,表示数据高字节无效,低字节有效。
Sd_addrO13SDRAM地址信号。
Sd_bankO2Bank地址选择信号,通过该信号决定哪个Bank正处于激活、读、写、预充电等命令期间。
Sd_clkO1SDRAM输入时钟,除cke外,SDRAM的所有输入与该引脚的上升沿同步获得。

1.5.2 SDRAM工作流程

SDRAM初始化
再SDRAM内部有一个逻辑控制单元,并且有一个模式寄存器为其提供控制参数。每次开机时SDRAM都要先对这个控制逻辑核心进行初始化。SDRAM必须以预定义的方式启动和初始化。在电源同时作用于Vdd和Vddq后开始初始化SDRAM,此时的时钟稳定并且将数据掩码和时钟使能信号拉高。在向SDRAM发送命令之前需要有100us的延时,此时SDRAM不执行任何操作。在100us延时满足后,需要对Bank进行预充电,在此期间所有的Bank处于空闲状态。预充电之后会有至少两个自刷新操作,完成自刷新便可以对SDRAM进行模式寄存器配置。下面是初始化的时序图
在这里插入图片描述

从上图中可以看出,上电后等待时间为T=100us,预充电操作需要的时间为TRP,一次自刷新需要的时间是TRC,加载模式寄存器需要的时间为TMRD。
在初始化中的预充电期间,地址线A10定义自动预充电,以确定是否所有Bank都被预充电,也可以通过Bank地址选择信号BA0和BA1来决定进行预充电的Bank地址。在加载模式寄存器期间,地址线A0到A11一起组成命令码。
SDRAM行激活
初始化完成之后,在向SDRAM发送读或者写命令之前必须打开该Bank中的一行,通过ACTIVE命令来确定要激活的Bank和行。要想对一个Bank中的阵列进行寻址,首先要确定行(Row),然后确定列。片选信号与Bank选择信号与行有效同时进行,下面是激活的时序图
在这里插入图片描述

从上图中可以看出,在片选信号、Bank地址选定的同时,行地址选通信号RAS也处于有效状态,此时An地址线发送具体的行地址。行地址位宽为12,共可以指示2^12=4096个具体的行地址。当行地址被激活后,相应的Bank也被激活,因此行激活又叫L-Bank激活。
列选择
行地址确定后,就要对列地址进行寻址,地址线仍使用A0A11,即行地址与列地址共用地址线。当列地址选通后,就需要对SDRAM进行读写,而给SDRAM读命令还是写命令由WE信号决定。当WE信号拉低时,SDRAM接收到的是写命令;当WE拉高,SDRAM接收读命令。列寻址信号与读写命令是同时发出的。虽然地址线与行寻址共用,但列地址选通脉冲CAS则可以区分开行与列寻址的不同,配合A0A9、A11来确定具体的地址。
在发送列读写命令时必须要与有效命令有一个时间间隔,这个时间间隔被定义为TRCD。
在这里插入图片描述

读操作
读命令从输入信号BA0、BA1中选取要进行读数据操作的BANK,并在已激活的行中进行突发读写操作。输入的A0~A7用来进行列寻址。在选定列地址后,就已经确定了具体的存储单元,剩下的事情就是数据通过dq输出到内存总线上了。但是再CAS发出之后,仍要经过一定的时间才能有数据输出,从CAS与读取命令发出到第一个数据输出的这段时间,被定义为CAL Latency(CAS潜伏期)。由于此现象只在读的时候出现,所以又称作读潜伏期
在这里插入图片描述

由于存储体中晶体管存在反应时间,从而造成数据不可能与 CAS 在同一上升沿触发,因此要延后至少一个时钟周期。考虑到芯片体积较小的因素,存储单元中的电容容量很小,所以信号要经过放大来保证其有效的识别性,这个放大/驱动工作由 S-AMP 负责,一个存储体对应一个 S-AMP 通道。但它要有一个准备时间才能保证信号的发送强度(事前还要进行电压比较以进行逻辑电平的判断),因此从数据 I/O 总线上有数据输出之前的一个时钟上升沿开始,数据即已传向 S-AMP,也就是说此时数据已经被触发,经过一定的驱动时间最终传向数据 I/O 总线进行输出,这段时间我们称之为 tAC(Access-Time-from-CLK,时钟触发后的访问时间),单位是 ns。在突发读操作完成后,如果选择了自动预充电模式,那么该行就会直接进入充电。如果没有选择此模式,那么该行将保持打开状态,供后续访问。自动预充电模式的选择与 A10的值有关,A10 为高时为自动预充电命令模式。
写操作
数据写入的操作也是在 tRCD 之后进行,但此时没有 CL(CL 只出现在读取操作中),行寻址与列寻址的时序一样致,只是在列寻址时,WE#为有效状态。由于数据信号由控制端发出,输入时芯片无需做任何调校,只需直接传到数据输入寄存器中,然后再由写入驱动器进行对存储电容的充电操作,因此数据可以与 CAS 同时发送,也就是说写入延迟为 0。不过,数据并不是即时地写入存储电容,因为选通三极管(就如读取时一样)与电容的充电必须要有一段时间,所以数据的真正写入需要一定的周期。
突发读写
突发(Burst)是指在同一行中相邻的存储单元连续进行数据传输的方式,连续传输所涉及到存储单元(列)的数量就是突发长度(Burst Lengths,简称 BL)。前文讲到的读/写操作,都是一次对一个存储单元进行寻址,如果想要连续的向 SDRAM 中读数据或者写数据,就需要对当前存储单元的下一个单元进行寻址,也即是需要不停给 SDRAM 列激活信号以及读/写命令(行地址不变,所以不用再对行寻址)。虽然由于读/写延迟相同可以让数据的传输在 I/O 端是连续的,但它占用了大量的内存控制资源,在数据进行连续传输时无法输入新的命令,效率很低。为此,人们开发了突发传输技术,只要指定起始列地址与突发长度,SDRAM 就会不再需要控制器连续地提供列地址,依次地自动对后面相应数量的存储单元进行读/写操作。这样,在突发模式读写中,除了第一个数据的传输需要若干个周期(主要是之前的延迟,一般的是tRCD+CL),其后每个数据只需一个周期的即可获得。至于突发长度 BL 的数值,也是不能随便设或在数据进行传输前临时决定,而是在上文讲到的 SDRAM 初始化过程中模式寄存器配置阶段就要对突发长度进行设置。目前可用的选项是 1、2、4、8、全页(FullPage),常见的突发长度设定是 BL=4、BL=8 或者全页突发模式。
在这里插入图片描述

1.5.3 设计思路

经过上面对SDRAM工作流程的介绍,可以采用状态机作为本工程的一个架构,根据指令的不同,划分为8个状态,分别为空操作(NOP)、预充电(PER)、自刷新(REF)、加载模式寄存器(MOD)、空闲(IDL)、激活(ACT)、读数据(RED)和写数据(WIR)。
由于每个操作需要的时间都不同,因此需要一个计数器来对每个操作需要的时间进行计数。
在这里插入图片描述

该计数器加一条件为state_c!=IDL,表示只要不是处于空闲状态,就进行计数;结束条件为数x个,x根据目前所处的状态的不同而不同,具体数据可以看下面的表格。

当前状态计数器数多少个
空操作(NOP)20000
预充电(PER)2
自刷新(REF)7
加载模式寄存器(MOD)2
激活(ACT)2
读/写数据(RED/WIR)512

由于再初始化阶段,自刷新需要连续进行两次,因此需要将初始化阶段区分出来,设计一个初始化指示信号init_flag:该信号初始状态为高电平,表示上电之后SDRAM处于初始化阶段;当初始化完成之后变为低电平,因此从高变低的条件为mod2idl_start。
自刷新计数器cnt1:该计数器表示初始化阶段进行自刷新的次数。加一条件为(init_flag && state_cREF && end__cnt),表示在初始化阶段,如果当前状态为自刷新,则时钟计数器数完一次就加一;结束条件为数两个,初始化阶段共进行两次自刷新,因此只需要数两个即可。
在初始化完成之后,需要进行自刷新、读数据和写数据等操作,由于自刷新是必须进行的,因此自刷新请求的优先级是最高的,那么读请求和写请求的优先级怎么确定呢?假设设置读请求的优先级高于写请求,读请求和写请求一起来的时候,总是先执行读请求,如果读请求一直有效的话,便不会执行写操作。反之设置写请求的优先级高于读请求,也会出现这样的问题,这当然是不可以的。因此我们设置为如果两个请求不是同时有效,则哪一个有效便执行哪一个。如果同时来的时候,第一次同时来,先执行写操作,第二次同时有效的时候在执行写操作,如此交替进行即可。通过两个信号进行控制:
读操作指示信号flag_rd:初始状态为低电平,表示上一次执行的写操作;从低变高的条件为state_c
RED,表示如果执行的是读操作,则置为高电平;当执行的是写操作的时候,该信号置为0,所以变0的条件是state_c==WIR。
读写同步指示信号flag_syn:初始状态为0,表示读写请求没有同时有效,如果当前处于激活状态,并且读写请求同时有效,则置为1,当激活状态结束,重新变为0。
设计中的辅助信号已经完成的差不多了,下面开始进行状态机的架构,架构图如下图所示:
在这里插入图片描述

下面介绍一个各个状态之间的跳转条件。
上电之后,先进入空操作状态,在空操作状态下:
1、延时100us之后,进入到预充电状态。
当处于预充电状态的时候:
1、如果处于初始化阶段,两个时钟周期之后,跳转到自刷新状态。
2、如果不是初始化阶段,两个时钟周期之后,跳转到空闲状态。
当处于自刷新状态时:
1、如果处于初始化状态,7个时钟周期之后,跳转到自刷新状态。
2、如果处于初始化状态,并且已经进行过一次初始化,7个时钟周期之后,跳转到加载模式寄存器状态。
3、如果不是初始化阶段,7个时钟周期之后,跳转到空闲状态。
当处于加载模式寄存器状态时:
1、2个时钟周期之后,进入到空闲状态。
当处于空闲状态时:
1、如果收到自刷新请求,则跳转到自刷新状态。
2、如果自刷新请求无效,收到读/写请求,则跳转到激活状态。
当处于处于激活状态时:
1、当读写请求不同时的时候,接收到读请求,则跳转到读状态。
2、当读写请求不同时的时候,接收到写请求,则跳转到写状态
3、当读写请求同时到达的时候,第一次来的时候,首先响应读请求,跳转到读状态
4、当读写请求同时到达,但不是第一次同时有效的时候,则根据上一次执行的操作进行判断,如果上一次执行的读操作,则这次执行写操作,跳转到写状态;如果上一次执行的写操作,则这次执行读操作,跳转到读状态。
当处于写状态的时候:
1、写数据完成,就进入到预充电状态。
当处于读状态的时候:
1、读数据完成,就进入到预充电状态。
指令集信号conmand:该信号共4bit,从最高位到最低位分别表示cs、ras、cas、we。在空操作阶段,指令为4’b0111;在预充电阶段,指令为4’b0010;在自刷新阶段,指令为4’b0001;在加载模式寄存器阶段,指令为4’b0000;在激活阶段,指令为4’b0011;在读数据阶段,指令为4’b0101;在写数据阶段,指令为4’b0100。这些操作对应的指令码都是从图中的表格中查找得来。
在这里插入图片描述

数据掩码dqm:初始状态为2’b11,表示输入得两个字节数据无效。当初始化完成之后,变为2’b00,表示输入得两个字节数据有效。
时钟使能cke:复位时为0,表示输入时钟无效,复位结束之后为1,表示输入时钟有效。
Bank选择信号sd_bank:初始状态为2’b00,表示选择Bank0;在激活阶段、读阶段和写阶段,该信号由输入得bank信号决定。
SDRAM地址选择信号sd_addr:由于本工程采用的预充电模式为全Bnak自动预充电,该模式由地址线A10控制,因此在预充电得时候,地址指令为13’b001_0_00_000_0_000;在激活的时候提供行地址;在加载模式寄存器得时候,地址线提供运算码,这时每个地址表示得意思入下图所示,A9决定读模式,A6、A5、A4决定读数据得潜伏期,A3决定突发类型,A2、A1、A0决定突发长度。
在这里插入图片描述

由于MP801开发板使用得SDRAM有两种型号,一种是W9812G6KH,共4096行,自刷新周期为1562,另一种是H57V2562GTR,共8192行,自刷新周期为780。在使用得时候需要注意开发板型号,这里我们以H57V2562GTR为例。自刷新需要以下信号:
时钟计数器cnt_780:该计数器主要得作用是初始化结束之后,数自刷新得周期;加一条件为init_flag==0,表示初始化结束就开始计数;结束条件为数780个,数完就清零。
自刷新请求ref_req:初始状态为0,表示不需要进行自刷新,当时钟计数器cnt_780数完得时候,ref_req拉高,请求进行自刷新,如果当前处于空闲状态,则进行自刷新,如果不是,则等待。
可能有人会想,如果不是空闲状态,就要等待,这样会不会对数据保存造成影响?其实不会得,存储器要求64ms全部刷新一遍,但不需要每一行刷新得间隔都一样。当时钟计数器cnt_780数完之后,产生自刷新请求,同时时钟计数器又会开始计数,所以可能自刷新得间隔不同,但每一行肯定能在64ms内刷新1次。
写SDRAM数据信号dq_out:该信号直接等于写数据wdata(注意,需要用组合逻辑实现)。
三态门使能信号dq_out_en:初始状态为0,表示使能无效,在写数据期间,变为高电平,表示使能有效。
读SDRAM数据信号rdata:直接将sdram输出数据dq_in连接即可。
读数据有效指示信号rdata_vld:由于存在读数据潜伏期,根据设置得潜伏期得长度,将rdata_vld进行相应得延时。

1.5.4 参考代码

1.    parameter NOP       = 4'b0000 ; 
2.    parameter PER       = 4'b0001 ; 
3.    parameter REF       = 4'b0010 ;
4.    parameter MOD       = 4'b0100 ;
5.    parameter IDL       = 4'b1000 ;
6.    parameter ACT       = 4'b0011 ;
7.    parameter RED       = 4'b0110 ;
8.    parameter WIR       = 4'b1100 ;
9.    
10.    parameter NOP_CMD   = 4'b0111 ; 
11.    parameter PER_CMD   = 4'b0010 ;
12.    parameter REF_CMD   = 4'b0001 ;
13.    parameter MOD_CMD   = 4'b0000 ;
14.    parameter ACT_CMD   = 4'b0011 ;
15.    parameter RED_CMD   = 4'b0101 ;
16.    parameter WIR_CMD   = 4'b0100 ;
17.    
18.    parameter ALL_BANK  = 13'b001_0_00_000_0_000;
19.    parameter CODE      = 13'b000_0_00_010_0_111;
20.    
21.    parameter TIME_780  = 780     ;      
22.    parameter TIME_WAIT = 20000   ;    
23.    parameter TIME_TRP  = 2       ;
24.    parameter TIME_TRC  = 7       ;
25.    parameter TIME_TMRD = 2       ;
26.    parameter TIME_TRCD = 2       ;
27.    parameter TIME_512  = 512     ;
28.
29.always  @(posedge clk or negedge rst_n)begin
30.    if(rst_n==1'b0)begin
31.        state_c <= NOP;
32.    end
33.    else begin
34.        state_c <= state_n;
35.    end
36.end
37.
38.always @(*)begin
39.    case(state_c)
40.        NOP:begin
41.            if(nop2per_start)begin
42.                state_n = PER;
43.            end
44.            else begin
45.                state_n = state_c;
46.            end
47.        end
48.        PER:begin
49.            if(per2ref_start)begin
50.                state_n = REF;
51.            end
52.            else if(per2idl_start)begin
53.                state_n = IDL;
54.            end
55.            else begin
56.                state_n = state_c;
57.            end
58.        end
59.        REF:begin
60.            if(ref2ref_start)begin
61.                state_n = REF;
62.            end
63.            else if(ref2mod_start)begin
64.                state_n = MOD;
65.            end
66.            else if(ref2idl_start)begin
67.                state_n = IDL;
68.            end
69.            else begin
70.                state_n = state_c;
71.            end
72.        end
73.        MOD:begin
74.            if(mod2idl_start)begin
75.                state_n = IDL;
76.            end
77.            else begin
78.                state_n = state_c;
79.            end
80.        end
81.        IDL:begin
82.            if(idl2ref_start)begin
83.                state_n = REF;
84.            end
85.            else if(idl2act_start)begin
86.                state_n = ACT;
87.            end
88.            else begin
89.                state_n = state_c;
90.            end
91.        end
92.        ACT:begin
93.            if(act2red_start)begin
94.                state_n = RED;
95.            end
96.            else if(act2wir_start)begin
97.                state_n = WIR;
98.            end
99.            else begin
100.                state_n = state_c;
101.            end
102.        end
103.        RED:begin
104.            if(red2per_start)begin
105.                state_n = PER;
106.            end
107.            else begin
108.                state_n = state_c;
109.            end
110.        end
111.        WIR:begin
112.            if(wir2per_start)begin
113.                state_n = PER;
114.            end
115.            else begin
116.                state_n = state_c;
117.            end
118.        end
119.        default:begin
120.            state_n = IDL;
121.        end
122.    endcase
123.end
124.
125.
126.assign nop2per_start = state_c==NOP && end_cnt;
127.assign per2ref_start = state_c==PER && init_flag==1 && end_cnt;
128.assign per2idl_start = state_c==PER && init_flag==0 && end_cnt;
129.assign ref2ref_start = state_c==REF && init_flag==1 && cnt1==0 && end_cnt;
130.assign ref2mod_start = state_c==REF && init_flag==1 && end_cnt1;
131.assign ref2idl_start = state_c==REF && init_flag==0 && end_cnt;
132.assign mod2idl_start = state_c==MOD && end_cnt;
133.assign idl2ref_start = state_c==IDL && ref_req;
134.assign idl2act_start = state_c==IDL && ref_req==0 && (wr_req || rd_req);
135.assign act2red_start = state_c==ACT && ((flag_syn==1 && flag_rd==0) || (flag_syn==0 && rd_req)) && end_cnt;
136.assign act2wir_start = state_c==ACT && ((flag_syn==1 && flag_rd==1) || (flag_syn==0 && wr_req)) && end_cnt;
137.assign red2per_start = state_c==RED && end_cnt;
138.assign wir2per_start = state_c==WIR && end_cnt;
139.
140.always @(posedge clk or negedge rst_n)begin
141.    if(!rst_n)begin
142.        cnt <= 0;
143.    end  
144.    else if(add_cnt)begin
145.        if(end_cnt)
146.            cnt <= 0;
147.        else
148.            cnt <= cnt + 1;
149.    end
150.end
151.
152.assign add_cnt = state_c!=IDL;       
153.assign end_cnt = add_cnt && cnt== x-1;   
154.
155.
156.always  @(posedge clk or negedge rst_n)begin
157.    if(rst_n==1'b0)begin
158.        init_flag <= 1;
159.    end
160.    else if(mod2idl_start)begin
161.        init_flag <= 0;
162.    end
163.end
164.
165.
166.always @(posedge clk or negedge rst_n) begin 
167.    if (rst_n==0) begin
168.        cnt1 <= 0; 
169.    end
170.    else if(add_cnt1) begin
171.        if(end_cnt1)
172.            cnt1 <= 0; 
173.        else
174.            cnt1 <= cnt1+1 ;
175.   end
176.end
177.assign add_cnt1 = init_flag && state_c==REF && end_cnt;
178.assign end_cnt1 = add_cnt1  && cnt1 == 2-1 ;
179.
180.
181.assign act2red_start = state_c==ACT && ((flag_syn==1 && flag_rd==0) || (flag_syn==0 && rd_req)) && end_cnt;
182.assign act2wir_start = state_c==ACT && ((flag_syn==1 && flag_rd==1) || (flag_syn==0 && wr_req)) && end_cnt;
183.always  @(posedge clk or negedge rst_n)begin
184.    if(rst_n==1'b0)begin
185.        flag_rd <= 0;
186.    end
187.    else if(state_c==RED)begin
188.        flag_rd <= 1;
189.    end
190.    else if(state_c==WIR)begin
191.        flag_rd <= 0;
192.    end
193.end
194.
195.
196.always  @(posedge clk or negedge rst_n)begin
197.    if(rst_n==1'b0)begin
198.        flag_syn <= 0;
199.    end
200.    else if(state_c==ACT && wr_req && rd_req)begin
201.        flag_syn <= 1;
202.    end
203.    else if(end_cnt)begin
204.        flag_syn <= 0;
205.    end
206.end
207.
208.assign rd_ack = act2red_start;
209.assign wr_ack = act2wir_start;
210.
211.always  @(posedge clk or negedge rst_n)begin
212.    if(rst_n==1'b0)begin
213.        conmand <= NOP_CMD;
214.    end
215.    else if(nop2per_start || red2per_start || wir2per_start)begin
216.        conmand <= PER_CMD;
217.    end
218.    else if(per2ref_start || ref2ref_start || idl2ref_start)begin
219.        conmand <= REF_CMD;
220.    end
221.    else if(ref2mod_start)begin
222.        conmand <= MOD_CMD;
223.    end
224.    else if(idl2act_start)begin
225.        conmand <= ACT_CMD;
226.    end
227.    else if(act2red_start)begin
228.        conmand <= RED_CMD;
229.    end
230.    else if(act2wir_start)begin
231.        conmand <= WIR_CMD;
232.    end
233.    else begin
234.        conmand <= NOP_CMD;
235.    end
236.end
237.
238.assign {cs,ras,cas,we} = conmand;
239.assign sd_clk = ~clk;
240.
241.always  @(posedge clk or negedge rst_n)begin
242.    if(rst_n==1'b0)begin
243.        dqm <= 2'b11;
244.    end
245.    else if(mod2idl_start)begin
246.        dqm <= 2'b00;
247.    end
248.end
249.
250.
251.always  @(posedge clk or negedge rst_n)begin
252.    if(rst_n==1'b0)begin
253.        cke <= 0;
254.    end
255.    else begin
256.        cke <= 1;
257.    end
258.end
259.
260.
261.always  @(posedge clk or negedge rst_n)begin
262.    if(rst_n==1'b0)begin
263.        sd_addr <= 13'b0;
264.    end
265.    else if(nop2per_start || red2per_start || wir2per_start)begin
266.        sd_addr <= ALL_BANK;
267.    end
268.    else if(ref2mod_start)begin
269.        sd_addr <= CODE;
270.    end
271.    else if(idl2act_start)begin
272.        sd_addr <= addr;
273.    end
274.    else begin
275.        sd_addr <= 13'b0;
276.    end
277.end
278.
279.
280.always  @(posedge clk or negedge rst_n)begin
281.    if(rst_n==1'b0)begin
282.        sd_bank <= 2'b00;
283.    end
284.    else if(idl2act_start || act2wir_start || act2red_start)begin
285.        sd_bank <= bank;
286.    end
287.    else begin
288.        sd_bank <= 0;
289.    end
290.end
291.
292.
293.always  @(*)begin
294.    if(state_c==NOP)begin
295.        x = TIME_WAIT;
296.    end
297.    else if(state_c==PER)begin
298.        x = TIME_TRP;
299.    end
300.    else if(state_c==REF)begin
301.        x = TIME_TRC;
302.    end
303.    else if(state_c==MOD)begin
304.        x = TIME_TMRD;
305.    end
306.    else if(state_c==ACT)begin
307.        x = TIME_TRCD;
308.    end
309.    else begin
310.        x = TIME_512;
311.    end
312.end
313.
314.
315.always @(posedge clk or negedge rst_n) begin 
316.    if (rst_n==0) begin
317.        cnt_780 <= 0; 
318.    end
319.    else if(add_cnt_780) begin
320.        if(end_cnt_780)
321.            cnt_780 <= 0; 
322.        else
323.            cnt_780 <= cnt_780+1 ;
324.   end
325.end
326.assign add_cnt_780 = init_flag==0;
327.assign end_cnt_780 = add_cnt_780  && cnt_780 == TIME_780-1 ;
328.
329.
330.always  @(posedge clk or negedge rst_n)begin
331.    if(rst_n==1'b0)begin
332.        ref_req <= 0;
333.    end
334.    else if(end_cnt_780)begin
335.        ref_req <= 1;
336.    end
337.    else if(ref_ack)begin
338.        ref_req <= 0;
339.    end
340.end
341.
342.
343.assign ref_ack = state_c==IDL && ref_req;
344.assign wr_ack  = act2wir_start;
345.assign rd_ack  = act2red_start;
346.
347.
348.always  @(*)begin
349.        dq_out <= wdata;
350.end
351.
352.
353.always  @(posedge clk or negedge rst_n)begin
354.    if(rst_n==1'b0)begin
355.        dq_out_en <= 1'b0;
356.    end
357.    else if(act2wir_start)begin
358.        dq_out_en <= 1'b1;
359.    end
360.    else if(end_cnt)begin
361.        dq_out_en <= 1'b0;
362.    end
363.end
364.
365.
366.always  @(posedge clk or negedge rst_n)begin
367.    if(rst_n==1'b0)begin
368.        rdata <= 16'b0;
369.    end
370.    else begin
371.        rdata <= dq_in;
372.    end
373.end
374.
375.
376.always  @(posedge clk or negedge rst_n)begin
377.    if(rst_n==1'b0)begin
378.        rdata_vld_ff0 <= 0;
379.    end
380.    else if(act2red_start)begin
381.        rdata_vld_ff0 <= 1;
382.    end
383.    else if(end_cnt)begin
384.        rdata_vld_ff0 <= 0;
385.    end
386.end
387.
388.
389.always  @(posedge clk or negedge rst_n)begin
390.    if(rst_n==1'b0)begin
391.        rdata_vld     <= 0;
392.        rdata_vld_ff1 <= 0;
393.        rdata_vld_ff2 <= 0;
394.    end
395.    else begin
396.        rdata_vld_ff1 <= rdata_vld_ff0;
397.        rdata_vld_ff2 <= rdata_vld_ff1;
398.        rdata_vld     <= rdata_vld_ff2;
399.    end
400.end

1.6 效果和总结

该工程得上板效果使用signaltap进行捕捉来观测的,因此不同开发板的上板效果是一样,那么下面就只介绍mp801的上板现象。
下图是写数据的情况,Bank地址为0。写入SDRAM的数据为0~255。
在这里插入图片描述

下图是读数据的情况,Bank地址为0,潜伏期为2,读出的数据为0~255。
在这里插入图片描述

下图是读数据的情况,Bank地址为1,潜伏期为2,读出的数据不是0~255,这是因为我们写数据的地址是Bank0,而不是Bank1,Bank1中是没有数据,所以读出的数据就不是确定的。
在这里插入图片描述

感兴趣的朋友也可以访问mdy论坛进行FPGA相关工程设计学习,也可以看一下我们往期的文章。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值