搜索

[FPGA] SDRAM读写控制器(2)

[复制链接]
153|1
 楼主 | 2020-11-7 17:25 | 显示全部楼层 |阅读模式
1.4.2设计思路
该模块主要实现的功能是根据按键,产生读请求、写请求、Bank地址、写数据和SDRAM地址。下面是该模块主要信号的设计思路:
写请求wr_req:初始状态为低电平,表示没有往SDRAM里面写数据的请求;当按下按键key1的时候,写请求变为高电平,表示请求往SDRAM内部写入数据,因此写请求拉高的条件为key_vld[0]==1;当接收到接收到写响应为高电平的时候,表示同意往SDRAM写入数据,此时将写请求置为低电平,因此写请求的拉低条件为wr_ack==1
1.4.3参考代码
  • always  @(posedge clk or negedge rst_n)begin
  •     if(rst_n==1'b0)begin
  •         wr_req <= 0;
  •     end
  •     else if(key_vld[0]==1)begin
  •         wr_req <= 1;
  •     end
  •     else if(wr_ack)begin
  •         wr_req <= 0;
  •     end
  • end
  • endalways  @(*)begin
  •     if(flag_wr)begin
  •         wdata = {7'b0,cnt2};
  •     end
  •     else begin
  •         wdata = 0;
  •     end
  • end
  • assign add_cnt2 = flag_wr;
  • assign end_cnt2 = add_cnt2  && cnt2 == 512-1 ;
  • assign addr = 13'b0;
  • endmodule


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

1.5 SDRAM接口模块设计1.5.1接口信号
  
信号名
  
I/O
位宽
定义
clk
I
1
1工作时钟  100M
rst_n
I
1
系统复位信号,低电平有效
Wr_req
I
1
写请求
rd_req
I
1
读请求
bank
I
2
输入bank地址
addr
I
13
地址信号
Wdata
I
16
写数据
dq_in
I
16
SDRAM数据输入
dq_out
O
16
写SDRAM数据信号
dq_out_en
O
1
三态门使能
Wr_ack
O
1
写响应
rd_ack
O
1
读响应
rdata
O
16
读数据
rdata_vld
O
1
读数据有效指示信号
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.5.2SDRAM工作流程
SDRAM初始化
SDRAM内部有一个逻辑控制单元,并且有一个模式寄存器为其提供控制参数。每次开机时SDRAM都要先对这个控制逻辑核心进行初始化。SDRAM必须以预定义的方式启动和初始化。在电源同时作用于VddVddq后开始初始化SDRAM,此时的时钟稳定并且将数据掩码和时钟使能信号拉高。在向SDRAM发送命令之前需要有100us的延时,此时SDRAM不执行任何操作。在100us延时满足后,需要对Bank进行预充电,在此期间所有的Bank处于空闲状态。预充电之后会有至少两个自刷新操作,完成自刷新便可以对SDRAM进行模式寄存器配置。下面是初始化的时序图在初始化中的预充电期间,地址线A10定义自动预充电,以确定是否所有Bank都被预充电,也可以通过Bank地址选择信号BA0BA1来决定进行预充电的Bank地址。在加载模式寄存器期间,地址线A0A11一起组成命令码。
SDRAM行激活
初始化完成之后,在向SDRAM发送读或者写命令之前必须打开该Bank中的一行,通过ACTIVE命令来确定要激活的Bank和行。要想对一个Bank中的阵列进行寻址,首先要确定行(Row),然后确定列。片选信号与Bank选择信号与行有效同时进行,下面是激活的时序图
列选择
行地址确定后,就要对列地址进行寻址,地址线仍使用A0~A11,即行地址与列地址共用地址线。当列地址选通后,就需要对SDRAM进行读写,而给SDRAM读命令还是写命令由WE信号决定。当WE信号拉低时,SDRAM接收到的是写命令;当WE拉高,SDRAM接收读命令。列寻址信号与读写命令是同时发出的。虽然地址线与行寻址共用,但列地址选通脉冲CAS则可以区分开行与列寻址的不同,配合A0~A9A11来确定具体的地址。
在发送列读写命令时必须要与有效命令有一个时间间隔,这个时间间隔被定义为TRCD
读操作
读命令从输入信号BA0BA1中选取要进行读数据操作的BANK,并在已激活的行中进行突发读写操作。输入的A0~A7用来进行列寻址。
写操作
数据写入的操作也是在 tRCD 之后进行,但此时没有 CLCL 只出现在读取操作中),行寻址与列寻址的时序一样致,只是在列寻址时,WE#为有效状态。由于数据信号由控制端发出,输入时芯片无需做任何调校,只需直接传到数据输入寄存器中,然后再由写入驱动器进行对存储电容的充电操作,因此数据可以与 CAS 同时发送,也就是说写入延迟为 0。不过,数据并不是即时地写入存储电容,因为选通三极管(就如读取时一样)与电容的充电必须要有一段时间,所以数据的真正写入需要一定的周期
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_c==REF && 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,从最高位到最低位分别表示csrascaswe。在空操作阶段,指令为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参考代码
  •     parameter NOP       = 4'b0000 ;
  •     parameter PER       = 4'b0001 ;
  •     parameter REF       = 4'b0010 ;
  •     parameter MOD       = 4'b0100 ;
  •     parameter IDL       = 4'b1000 ;
  •     parameter ACT       = 4'b0011 ;
  •     parameter RED       = 4'b0110 ;
  •     parameter WIR       = 4'b1100 ;
  •     parameter NOP_CMD   = 4'b0111 ;
  •     parameter PER_CMD   = 4'b0010 ;
  •     parameter REF_CMD   = 4'b0001 ;
  •     parameter MOD_CMD   = 4'b0000 ;
  •     parameter ACT_CMD   = 4'b0011 ;
  •     parameter RED_CMD   = 4'b0101 ;
  •     parameter WIR_CMD   = 4'b0100 ;
  •     parameter ALL_BANK  = 13'b001_0_00_000_0_000;
  •     parameter CODE      = 13'b000_0_00_010_0_111;
  •     parameter TIME_780  = 780     ;
  •     parameter TIME_WAIT = 10000   ;
  •     parameter TIME_TRP  = 2       ;
  •     parameter TIME_TRC  = 7       ;
  •     parameter TIME_TMRD = 2       ;
  •     parameter TIME_TRCD = 2       ;
  •     parameter TIME_512  = 512     ;
  •     input            clk          ;
  •     input            rst_n        ;
  •     input            wr_req       ;
  •     input            rd_req       ;
  •     input  [1 :0]    bank         ;
  •     input  [12:0]    addr         ;
  •     input  [15:0]    wdata        ;
  •     input  [15:0]    dq_in        ;
  •     output [15:0]    dq_out       ;
  •     output           dq_out_en    ;
  •     output           sd_clk       ;
  •     reg    [15:0]    dq_out       ;
  •     reg              dq_out_en    ;
  •     reg              flag_syn     ;
  •     wire             wr_ack       ;
  •     wire             rd_ack       ;
  •     reg    [15:0]    rdata        ;
  •     reg              rdata_vld    ;
  •     reg              cke          ;
  •     wire             cs           ;
  •     wire             ras          ;
  •     wire             cas          ;
  •     wire             we           ;
  •     reg    [1 :0]    dqm          ;
  •     reg    [12:0]    sd_addr      ;
  •     reg    [1 :0]    sd_bank      ;
  •     wire             sd_clk       ;
  • always  @(posedge clk or negedge rst_n)begin
  •     if(rst_n==1'b0)begin
  •         state_c <= NOP;
  •     end
  •     else begin
  •         state_c <= state_n;
  •     end
  • end
  • always  @(posedge clk or negedge rst_n)begin
  •     if(rst_n==1'b0)begin
  •         flag_rd <= 0;
  •     end
  •     else if(state_c==RED)begin
  •         flag_rd <= 1;
  •     end
  •     else if(state_c==WIR)begin
  •         flag_rd <= 0;
  •     end
  • end
  • always  @(posedge clk or negedge rst_n)begin
  •     if(rst_n==1'b0)begin
  •         rdata_vld     <= 0;
  •         rdata_vld_ff1 <= 0;
  •         rdata_vld_ff2 <= 0;
  •     end
  •     else begin
  •         rdata_vld_ff1 <= rdata_vld_ff0;
  •         rdata_vld_ff2 <= rdata_vld_ff1;
  •         rdata_vld     <= rdata_vld_ff2;
  •     end
  • end
  • endmodule

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


使用特权

评论回复
| 2020-11-9 09:31 | 显示全部楼层
谢谢分享【SDRAM读写控制器】

使用特权

评论回复
扫描二维码,随时随地手机跟帖
您需要登录后才可以回帖 登录 | 注册

本版积分规则

我要发帖 我要提问 投诉建议 申请版主

快速回复

您需要登录后才可以回帖
登录 | 注册
高级模式

论坛热帖

关闭

热门推荐上一条 /5 下一条

在线客服 快速回复 返回顶部 返回列表