打印
[FPGA]

SDRAM读写控制器(1)

[复制链接]
492|3
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
guyu_1|  楼主 | 2020-11-7 17:07 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
第1节 SDRAM读写控制器

--作者:小黑同学
本文为明德扬原创及录用**,转载请注明出处

1.1 总体设计1.1.1 概述
同步动态随机存取内存(synchronousdynamic randon-access menory,简称SDRAM)是有一个同步接口的动态随机存取内存(DRAM)。通常DRAM是有一个异步接口的,这样它可以随时响应控制输入的变化。而SDRAM有一个同步接口,在响应控制输入前会等待一个时钟信号,这样就能和计算机的系统总线同步。时钟被用来驱动一个有限状态机,对进入的指令进行管线操作。这使得SDRAM与没有同步接口的异步DRAM相比,可以有一个更复杂的操作模式。

管线意味着芯片可以在处理完之前的指令前,接受一个新的指令。在一个写入的管线中,写入命令在另一个指令执行完之后可以立刻执行,而不需要等到数据写入存储队列的时间。在一个读取的流水线中,需要的数据在读取指令发出之后固定数量的时钟频率后到达,而这个等待的过程可以发出其他附加指令。这种延迟被称为等待时间(Latency),在为计算机购买内存时是一个很重要的参数。

SDRAM之所以称为DRAM就是因为他要不断进行刷新才能保留住数据,因为刷新是DRAM最重要的操作。那么要隔多长时间重复一次刷新,目前公认的标准是,存储体中电容的数据有效保存期上限是64ms,也就是每一行刷新的循环周期是64ms
SDRAM是多Bank结构,例如在一个具有两个BankSDRAM模组中,其中一个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.4模块功能
  按键检测模块实现功能
1、  将外来异步信号打两拍处理,将异步信号同步化。
2、  实现20ms按键消抖功能。
3、  实现矩阵键盘或者普通案件的检测功能,并输出有效按键信号。

锁相环
1、产生工程所需要的100M时钟。

  数据产生模块实现功能
1、  通过按键控制产生读/写请求。
2、  通过按键控制Bank地址选择。
3、  产生地址和写数据。

SDRAM接口模块实现功能
1、  接收上游模块发送的读/写请求、Bank地址、行地址和写数据,产生SDRAM的控制时序。

1.1.5顶层信号
  
信号名
  
I/O
位宽
定义
clk
I
1
系统工作时钟 50M
rst_n
I
1
系统复位信号,低电平有效
Key
I
4
4位按键信号,开发板按键为矩阵键盘时,不需要该信号
Key_col
I
4
4位矩阵键盘列信号,默认高电平,开发板按键为普通按键时,不需要该信号
Key_row
O
4
4位矩阵键盘行信号,默认低电平,开发板按键为普通按键时,不需要该信号
dq
I/O
16
SDRAM数据总线,既能作为数据输出,也能作为数据输入。
cke
O
1
SDRAM时钟使能信号,决定是否启用clk输入,为高电平时,时钟有效。
cs
O
1
SDRAM片选信号,决定设备内是否启用命令输入,当cs为低时启用,当cs为高时禁用命令输入。
ras
O
1
行地址选通信号,低电平有效
cas
O
1
列地址选通信号,低电平有效
we
O
1
写使能信号,低电平有效
dqm
O
2
数据掩码,控制I/O的高低字节,低电平有效。例如:2’b10,表示数据高字节无效,低字节有效。
sd_addr
O
13
SDRAM地址信号。
sd_bank
O
2
Bank地址选择信号,通过该信号决定哪个Bank正处于激活、读、写、预充电等命令期间。
sd_clk
O
1
SDRAM输入时钟,除cke外,SDRAM的所有输入与该引脚的上升沿同步获得。

1.1.6三态门
由于SDRAM只有一条数据总线,虽然可以既当作输入,又当作输出来用,但是输入和输出是不能同时进行的,因此需要在工程的顶层设计中采用三态门。关于三态门详细的介绍可以看明德扬《FPGA至简设计原理与应用》书中的第一篇第三章5.2.4高阻态一节。
【FPGA至简设计原理与应用】书籍连载03第一篇FPGA基础知识第三章硬件描述语言Verilog

1.1.7参考代码
下面是使用工程的顶层代码:
  • module sdram_top(
  •     clk      ,
  •     rst_n    ,
  •     key      ,
  •     dq       ,
  •     cke      ,
  •     cs       ,
  •     ras      ,
  •     cas      ,
  •     we       ,
  •     dqm      ,
  •     sd_addr  ,
  •     sd_bank  ,
  •     sd_clk
  •     );
  •     input              clk     ;
  •     input              rst_n   ;
  •     input  [3:0]       key     ;
  •     inout  [15:0]      dq      ;
  •     output             cke     ;
  •     output             cs      ;
  •     output             ras     ;
  •     output             cas     ;
  •     output             we      ;
  •     output [1 :0]      dqm     ;
  •     output [12:0]      sd_addr ;
  •     output [1 :0]      sd_bank ;
  •     output             sd_clk  ;
  •     wire               cke     ;
  •     wire               cs      ;
  •     wire               ras     ;
  •     wire               cas     ;
  •     wire               we      ;
  •     wire  [1 :0]       dqm     ;
  •     wire  [12:0]       sd_addr ;
  •     wire  [1 :0]       sd_bank ;
  •     wire               sd_clk  ;
  •     wire              clk_100m ;
  •     wire              wr_ack   ;
  •     wire              rd_ack   ;
  •     wire              wr_req   ;
  •     wire              rd_req   ;
  •     wire [1 :0]       bank     ;
  •     wire [12:0]       addr     ;
  •     wire [15:0]       wdata    ;
  •     wire [15:0]       rdata    ;
  •     wire              rdata_vld;
  •     wire [3:0 ]       key_vld  ;
  •     wire [15:0]       dq_in    ;
  •     wire [15:0]       dq_out   ;
  •     wire              dq_out_en;
  •     assign  dq_in = dq;
  •     assign  dq    = dq_out_en?dq_out:16'hzzzz;
  •     pll_100m uut_pll(
  •             .inclk0    (clk      ),
  •             .c0        (clk_100m )
  •     );
  •     key_module uut_key(
  •         .clk       (clk_100m ),
  •         .rst_n     (rst_n    ),
  •         .key_in    (key      ),
  •         .key_vld   (key_vld  ),
  •     );
  •     data_ctrl uut_ctrl(
  •         .clk       (clk_100m ),
  •         .rst_n     (rst_n    ),
  •         .key_vld   (key_vld  ),
  •         .wr_ack    (wr_ack   ),
  •         .rd_ack    (rd_ack   ),
  •         .wr_req    (wr_req   ),
  •         .rd_req    (rd_req   ),
  •         .bank      (bank     ),
  •         .addr      (addr     ),
  •         .wdata     (wdata    )
  •         );
  •     sdram_intf uut_sdram(
  •         .clk       (clk_100m ),
  •         .rst_n     (rst_n    ),
  •         .wr_req    (wr_req   ),
  •         .rd_req    (rd_req   ),
  •         .bank      (bank     ),
  •         .addr      (addr     ),
  •         .wdata     (wdata    ),
  •         .dq_in     (dq_in    ),
  •         .dq_out    (dq_out   ),
  •         .dq_out_en (dq_out_en),
  •         .wr_ack    (wr_ack   ),
  •         .rd_ack    (rd_ack   ),
  •         .rdata     (rdata    ),
  •         .rdata_vld (rdata_vld),
  •         .cke       (cke      ),
  •         .cs        (cs       ),
  •         .ras       (ras      ),
  •         .cas       (cas      ),
  •         .we        (we       ),
  •         .dqm       (dqm      ),
  •         .sd_addr   (sd_addr  ),
  •         .sd_bank   (sd_bank  ),
  •         .sd_clk    (sd_clk   )
  •     );
  •     endmodule


[color=rgb(51, 102, 153) !important]复制代码



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.    //矩阵键盘
  • always  @(posedge clk or negedge rst_n)begin
  •     if(rst_n==1'b0)begin
  •         key_col_ff0 <= 4'b1111;
  •         key_col_ff1 <= 4'b1111;
  •     end
  •     else begin
  •         key_col_ff0 <= key_col    ;
  •         key_col_ff1 <= key_col_ff0;
  •     end
  • end
  • always @(posedge clk or negedge rst_n) begin
  •     if (rst_n==0) begin
  •         shake_cnt <= 0;
  •     end
  •     else if(add_shake_cnt) begin
  •         if(end_shake_cnt)
  •             shake_cnt <= 0;
  •         else
  •             shake_cnt <= shake_cnt+1 ;
  •    end
  • end
  • assign add_shake_cnt = key_col_ff1!=4'hf;
  • assign end_shake_cnt = add_shake_cnt  && shake_cnt == TIME_20MS-1 ;
  • always  @(posedge clk or negedge rst_n)begin
  •     if(rst_n==1'b0)begin
  •         state_c <= CHK_COL;
  •     end
  •     else begin
  •         state_c <= state_n;
  •     end
  • end
  • always  @(*)begin
  •     case(state_c)
  •         CHK_COL: begin
  •                      if(col2row_start )begin
  •                          state_n = CHK_ROW;
  •                      end
  •                      else begin
  •                          state_n = CHK_COL;
  •                      end
  •                  end
  •         CHK_ROW: begin
  •                      if(row2del_start)begin
  •                          state_n = DELAY;
  •                      end
  •                      else begin
  •                          state_n = CHK_ROW;
  •                      end
  •                  end
  •         DELAY :  begin
  •                      if(del2wait_start)begin
  •                          state_n = WAIT_END;
  •                      end
  •                      else begin
  •                          state_n = DELAY;
  •                      end
  •                  end
  •         WAIT_END: begin
  •                      if(wait2col_start)begin
  •                          state_n = CHK_COL;
  •                      end
  •                      else begin
  •                          state_n = WAIT_END;
  •                      end
  •                   end
  •        default: state_n = CHK_COL;
  •     endcase
  • end
  • assign col2row_start = state_c==CHK_COL  && end_shake_cnt;
  • assign row2del_start = state_c==CHK_ROW  && row_index==3 && end_row_cnt;
  • assign del2wait_start= state_c==DELAY    && end_row_cnt;
  • assign wait2col_start= state_c==WAIT_END && key_col_ff1==4'hf;
  • always  @(posedge clk or negedge rst_n)begin
  •     if(rst_n==1'b0)begin
  •         key_row <= 4'b0;
  •     end
  •     else if(state_c==CHK_ROW)begin
  •         key_row <= ~(1'b1 << row_index);
  •     end
  •     else begin
  •         key_row <= 4'b0;
  •     end
  • end
  • 普通键盘
  • always @(posedge clk or negedge rst_n) begin
  •     if (rst_n==0) begin
  •         row_index <= 0;
  •     end
  •     else if(add_row_index) begin
  •         if(end_row_index)
  •             row_index <= 0;
  •         else
  •             row_index <= row_index+1 ;
  •    end
  •    else if(state_c!=CHK_ROW)begin
  •        row_index <= 0;
  •    end
  • end
  • assign add_row_index = state_c==CHK_ROW && end_row_cnt;
  • assign end_row_index = add_row_index  && row_index == 4-1 ;
  • always @(posedge clk or negedge rst_n) begin
  •     if (rst_n==0) begin
  •         row_cnt <= 0;
  •     end
  •     else if(add_row_cnt) begin
  •         if(end_row_cnt)
  •             row_cnt <= 0;
  •         else
  •             row_cnt <= row_cnt+1 ;
  •    end
  • end
  • assign add_row_cnt = state_c==CHK_ROW || state_c==DELAY;
  • assign end_row_cnt = add_row_cnt  && row_cnt == 16-1 ;
  • always  @(posedge clk or negedge rst_n)begin
  •     if(rst_n==1'b0)begin
  •         key_col_get <= 0;
  •     end
  •     else if(state_c==CHK_COL && end_shake_cnt ) begin
  •         if(key_col_ff1==4'b1110)
  •             key_col_get <= 0;
  •         else if(key_col_ff1==4'b1101)
  •             key_col_get <= 1;
  •         else if(key_col_ff1==4'b1011)
  •             key_col_get <= 2;
  •         else
  •             key_col_get <= 3;
  •     end
  • end
  • always  @(posedge clk or negedge rst_n)begin
  •     if(rst_n==1'b0)begin
  •         key_out <= 0;
  •     end
  •     else if(state_c==CHK_ROW && end_row_cnt)begin
  •         key_out <= {row_index,key_col_get};
  •     end
  •     else begin
  •         key_out <= 0;
  •     end
  • end
  • always  @(posedge clk or negedge rst_n)begin
  •     if(rst_n==1'b0)begin
  •         key_vld <= 1'b0;
  •     end
  •     else if(state_c==CHK_ROW && end_row_cnt && key_col_ff1[key_col_get]==1'b0)begin
  •         key_vld <= 1'b1;
  •     end
  •     else begin
  •         key_vld <= 1'b0;
  •     end
  • end
  • always  @(*)begin
  •     if(rst_n==1'b0)begin
  •         key_en = 0;
  •     end
  •     else if(key_vld && key_out==0)begin
  •         key_en = 4'b0001;
  •     end
  •     else if(key_vld && key_out==1)begin
  •         key_en = 4'b0010;
  •     end
  •     else if(key_vld && key_out==2)begin
  •         key_en = 4'b0100;
  •     end
  •     else begin
  •         key_en = 0;
  •     end
  • end
  • endmodule


[color=rgb(51, 102, 153) !important]复制代码





1.3 锁相环1.3.1 接口信号
  
信号名
  
I/O
位宽
定义
Inclk0
I
1
输入时钟50M
C0
O
1
输出时钟100M

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

1.4 数据产生模块设计1.4.1接口信号
  
信号名
  
I/O
位宽
定义
clk
I
1
工作时钟 100M
rst_n
I
1
系统复位信号,低电平有效
Key_vld
I
4
按键有效指示信号
Wr_ack
I
1
写数据响应
rd_ack
I
1
读数据响应
Wr_req
O
1
写数据请求
rd_req
O
1
读数据响应
bank
O
2
Bank地址选择信号
addr
O
13
SDRAM地址信号
Wdata
O
16
SDRAM写数据

使用特权

评论回复

相关帖子

沙发
zeshoufx| | 2020-11-9 09:26 | 只看该作者
谢谢分享【SDRAM读写控制器】

使用特权

评论回复
板凳
zeshoufx| | 2020-11-9 09:27 | 只看该作者
谢谢分享【SDRAM读写控制器】

使用特权

评论回复
地板
zeshoufx| | 2020-11-9 09:30 | 只看该作者
谢谢分享【SDRAM读写控制器】

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

53

主题

61

帖子

2

粉丝