写在前面的话
这个实践项目来源于研究生电子设计竞赛,在涉及到视频图像处理时需要用到DRAM存储数据 ;整个项目过程中先后学习了小梅哥(AC620开发板资料)、开源骚客SDRAM控制器、正点原子FPGA教程、野火FPGA开发教程等网络资料。
在此对上述提供学习资料的前辈表示真诚的感谢。
在整个工程项目中共涉及到多款SDRAM芯片手册,其分别是:
- 美光的SDR SDRAM MT48LC64M4A2,数据手册较为完全,RTL仿真采用的芯片模型也是来源于美光;
- 华邦电子的W9825G6KH/W9825G6DH等型号,具体手册请查看开发板上的芯片;(受益于SDRAM统一的协议,不同型号间的Timing相差不大)
项目难度:⭐⭐⭐
项目推荐度:⭐⭐⭐⭐
项目推荐天数:3~5天
参考教材
1. 小梅哥AC620开发板资料;
2. 开源骚客第一季视频讲解;
3. 正点原子FPGA教程中SDRAM控制器部分;
4. 野火FPGA教程中SDRAM控制器部分。
个人比较推荐的是野火FPGA,视频讲解和开发文档非常齐全,也会先讲波形再讲代码,能培养较好的习惯。
项目简介和学习目的
本项目中SDRAM控制器相较于常用的时钟频率要高些,常见的SDRAM控制器为100MHz,有些甚至于50MHz。
- 为充分发挥SDRAM芯片的吞吐量,本次设计的时钟频率为166MHz,这也是开发板上SDRAM芯片支持的最高工作频率。
项目实践环境:
FPGA开发环境:
前仿: Modelsim SE-64 2019.2
综合: Quartus (Quartus Prime 17.1) Standard Edition
数字IC开发环境:
前仿: VCS 2016
综合:DC 2016
项目学习目的:
(1)熟练掌握项目中各文件的工程管理;
(2)熟悉Verilog HDL仿真、FPGA综合工具以及了解数字IC设计工具及流程;
(3)学习SDRAM基本结构和基础原理;
(4)学习SDRAM控制器基本结构和基础原理;
(5)熟练掌握Verilog语法和验证方法;
(6)熟练掌握Modelsim、VCS等开发工具。
SDRAM简介
-
SDRAM是一种同步动态随机存储器,被广泛应用于计算机、通信和嵌入式系统等领域。SDRAM具有高速读取和写入操作、高密度存储和高可靠性等优点,因此成为当前主流的内存存储器之一。
-
SDRAM存储器由多个单元字节组成。在每个字节单元中设置一个电容和一个访问晶体管,通过在电容上蓄电存储信息 0 或 1。除了字节内部的存储单元,SDRAM还包含行选择器和列选择器,以及控制线路等其他组件。
-
SDRAM的内部时钟配合控制线路实现了一种同步的读写操作。在读操作中,SDRAM控制器将要读数据的行和列地址发送给SDRAM,并根据某些参数读取数据。在写操作中,数据通过数据线送达SDRAM芯片,然后存储到指定的行和列地址内。
-
SDRAM存储器有多种种类,根据技术不同可分为SDR,DDR,DDR2,DDR3和DDR4等版本。各版本的主要区别在于内部时钟频率、内部传输速率和带宽等方面的提高,以满足不同应用场景的业务需求。
SDRAM存取原理:
SDRAM是利用电容充放电的特性来保存数据,由于电容存在电荷泄露的情况,因此需要定时刷新,其存储单元的组成如下图所示,包含行选通三极管、列选通三极管、电容以及刷新放大器组成。
通过将上述存储单元行列相连,得到一个存储数据的电容阵列,为了控制整个阵列的正常运转,需要搭配外围电路完成读写控制,主要组成部分如下图所示:
重点部分为8个模块:
- 模式寄存器
- 命令解码器
- 控制逻辑
- 地址寄存器
- 刷新计数器
- 行/列地址计数锁存器
- bank控制逻辑
- 列解码器
SDRAM管脚说明:
sdram管脚说明如下表所示。
SDRAM控制器简介
-
SDRAM控制器是一种用于控制同步动态随机存储器(SDRAM)的硬件设备。它能够实现高速、可靠的内存读写操作,并经常用于计算机、通讯系统和其他嵌入式应用中。SDRAM控制器有着复杂的内部工作机制,通过其内部的逻辑电路,实现对SDRAM存储器芯片的精细控制。
-
一个SDRAM控制器通常包含读写控制器、地址发生器、时钟控制器和数据缓冲等组件。它能够识别读取和写入命令,并控制存储器的读取和写入操作的执行时间。在操作过程中,SDRAM控制器先将外部指令和数据传输到数据缓冲区,然后再基于内部时钟更新SDRAM存储器中的数据。此外,SDRAM控制器还通过检测数据总线的情况,实现对数据的检查和纠错,以提高内存数据的可靠性。
-
SDRAM控制器的内部时钟频率非常高,通常达到百兆赫兹甚至更高。它能够对存储器按照指定的工作模式进行读写操作,如CAS时序、预充电和自刷新等。此外,SDRAM控制器还可以支持多通道、多控制器和多级缓存等高级特性,以大幅提高系统读写效率。
完整项目框图
完整项目说明:
开发一款针对MNIST数据集的实时监测系统,要求采用卷积神经网络加速器完成图像识别;其中在项目原型验证阶段采用两块独立开发板,AC620用来完成图像采集、缓存以及数据传输功能,AXU5EV-E用来完成CNN硬件实现,用来加速识别速度,同时开发显示驱动模块,将图片信息、识别结果等信息展现在显示器的OSD区域。
SDRAM控制器项目框图
SDRAM控制器项目说明:开发一款针对W9825GKH芯片的读写控制模块,要求采用页突发模式,时钟频率为166MHz;
本项目中的SDRAM控制器各模块具体如下:
(1)fifo_ctrl,读写FIFO控制模块;
包含两个异步FIFO模块,用来实现读写端口的数据缓冲。
(2)sdram_ctrl,SDRAM控制器主模块;
(1)sdram_init,sdram初始化模块;
(2)sdram_aref,sdram刷新控制模块;
(3)sdram_write,sdram写模块;
(4)sdram_read,sdram读模块;
(5)sdram_arbit,sdram仲裁模块,完成读、写、刷新仲裁。
在Modelsim中的电路图如下:
完整框图:
fifo控制模块:
sdram控制模块:
SDRAM初始化模块
模块图:
端口描述:
名称 | 备注 |
---|---|
sys_clk | 系统时钟,这里为166MHz |
sys_rst_n | 系统复位信号,低电平有效 |
init_cmd | SDRAM初始化阶段指令信号 |
init_addr | SDRAM初始化阶段地址总线 |
init_ba | SDRAM初始化阶段Bank地址 |
init_end | SDRAM初始化结束标志 |
工作时序:
备注:
在对SDRAM进行操作前,需要先完成初始化。上电和初始化需要按照预先定义的方式完成。
需要注意的地方:
(1)4个时间,SDRAM初始化模块涉及4个关键时间:
名称 | 时间 | 备注 |
---|---|---|
powup_T | 200us | 上电等待时间,最小100us |
T_RP | 18ns | 预充电等待时间 |
T_RFC | 66ns | 自动刷新周期 |
T_MRD | 2tck | 加载模式寄存器需要的时间 |
(2)8个状态,SDRAM初始化模块涉及8个状态,用于描述状态机:
名称 | 编码 | 备注 |
---|---|---|
INIT_IDLE | 3’b000 | 初始状态 |
INIT_PRE | 3’b001 | 预充电状态 |
INIT_TRP | 3’b011 | 预充电等待状态 |
INIT_AREF | 3’b010 | 自动刷新状态 |
INIT_TRFC | 3’b110 | 自动刷新等待状态 |
INIT_LMR | 3’b111 | 模式寄存器设置状态 |
INIT_TMRD | 3’b101 | 模式寄存器设置等待状态 |
INIT_END | 3’b100 | 初始化结束状态 |
(3)4个命令,SDRAM初始化模块涉及4个操作命令,这里需要查看芯片手册
名称 | 编码 | 备注 | 快速记忆码(10进制) |
---|---|---|---|
NOP | 4’b0111 | 空操作 | 7 |
PRE | 4’b0010 | 预充电 | 2 |
AREF | 4’b0001 | 自动刷新 | 1 |
LMR | 4’b0000 | 设置模式寄存器 | 0 |
初始化流程:
72717…1707
其中,17至少要两次,对应两次自动刷新操作。
仿真波形
Verilog代码:
`timescale 1ps/1ps
module sdram_init (
input sys_clk , // 系统时钟 167MHZ,period = 5.98ns
input sys_rst_n , // 系统复位信号
output reg [3:0] init_cmd , // 初始化命令 cs_n ras_n cas_n we_n
output reg [12:0] init_addr , // 初始化地址总线
output reg [1:0] init_ba , // 初始化bank地址
output reg init_end // 初始化结束标志
);
//parameter define
//sdram命令
localparam NOP = 4'b0111 , //空操作命令
PRE = 4'b0010 , //预充电命令,用于关闭当前的行
AREF = 4'b0001 , //自动刷新命令,用于维持当前数据
LMR = 4'b0000 , //设置模式寄存器命令,初始化阶段需要配置模式寄存器, 设置突发类型、突发长度、读写方式、列选通潜伏期
ACT = 4'b0011 , //激活命令,用于打开行,和行地址一起发送
RD = 4'b0101 , //读命令,发送读或写命令前必须激活
WR = 4'b0100 , //写命令,写命令和列地址一起发出,存在列选通潜伏期,就是写命令发出到数据出现在总线上的需要等待的时间,一般设为2或3
BR_T = 4'b0110 ; //突发终止命令
//计数器
localparam cnt_pow = 'd33445 , //200MHZ 'd40000 , //200us
cnt_rp = 'd4 , //200MHZ 'd4 , //20ns
cnt_rfc = 'd12 , //200MHZ 'd14 , //70ns
cnt_mrd = 'd6 ; //200MHZ 'd6 ; //30ns
//状态机 初始化过程的8个状态,格雷码定义,相邻两位只有一位发生变化,避免产生亚稳态
localparam INIT_IDLE = 3'b000 , //初始状态
INIT_PRE = 3'b001 , //预充电状态
INIT_TRP = 3'b011 , //预充电等待状态 trp
INIT_AREF = 3'b010 , //自动刷新状态
INIT_TRFC = 3'b110 , //自动刷新等待状态 trfc
INIT_LMR = 3'b111 , //模式寄存器设置状态
INIT_TMRD = 3'b101 , //模式寄存器设置等待状态 tmrd
INIT_END = 3'b100 ; //初始化结束状态
//刷新次数,适配不同器件,至少刷新2次
localparam aref_num = 6;
//地址辅助模式寄存器,参数不同,配置的模式不同
localparam init_lmrset = { 3'b000 , //A12-A10: 预留的模式寄存器
1'b0 , //A9 : 读写方式,0:突发读&突发写,1:突发读&单写
2'b00 , //{A8,A7}: 标准模式,默认
3'b011 , //{A6,A5,A4} CAS潜伏期; 010: 2 011: 3 ,其他:保留
1'b0 , //A3 突发传输方式; 0:顺序, 1: 隔行
3'b111 //{A2,A1,A0}=111:突发长度,000:单字节,001:2字节
//010:4字节,011:8字节,111:整页,其他:保留
};
//reg define
reg [15:0] cnt_200us ; //启动计数器
//状态机相关 三段式
reg [2:0] init_state_cs ; //初始化状态机 当前状态
reg [2:0] init_state_ns ; //初始化状态机 下一个状态
reg pow_end ; //上电结束标志
reg pre_end ; //预充电结束标志
reg aref_end ; //刷新结束标志
reg mrd_end ; //模式寄存器设置结束标志
reg [3:0] cnt_clk ; //各状态记录时间
//reg cnt_clk_rst_n ; //时钟周期复位信号 取消这个标志信号,直接判断是否复位
reg [3:0] cnt_init_aref ; //初始阶段刷新次数
//上电检测:SDRAM上电后计时200us
always @(posedge sys_clk or negedge sys_rst_n) begin
if(~sys_rst_n) begin
cnt_200us <= 0;
pow_end <= 0;
end
else if(cnt_200us == cnt_pow) begin
cnt_200us <= 0 ;
pow_end <= 1 ;
end
else begin
cnt_200us <= cnt_200us + 1'b1 ;
pow_end <= 0 ;
end
end
//cnt_clk:时钟周期计数,记录初始化各状态的等待时间
always @(posedge sys_clk or negedge sys_rst_n) begin
if(~sys_rst_n) begin
cnt_clk <= 0 ;
end
else if(pow_end == 1 || pre_end == 1 || aref_end == 1 ) begin
cnt_clk <= 0 ;
end
else
cnt_clk <= cnt_clk + 1;
end
//cnt_init_aref:初始化阶段的刷新次数
always @(posedge sys_clk or negedge sys_rst_n) begin
if(~sys_rst_n) begin
cnt_init_aref <= 0 ;
end
else if(init_state_cs == INIT_IDLE) begin //这里为什么设置两次清零
cnt_init_aref <= 0 ;
end
else if (init_state_cs == INIT_AREF) begin
cnt_init_aref <= cnt_init_aref + 1'b1 ;
end
else
cnt_init_aref <= cnt_init_aref ;
end
//预充电结束标志
//pre_end
always@(*) begin
if(init_state_cs == INIT_TRP && cnt_clk == cnt_rp)
pre_end = 1 ;
else
pre_end = 0 ;
end
//刷新结束标志
//aref_end
always@(*) begin
if(init_state_cs == INIT_TRFC && cnt_clk == cnt_rfc)
aref_end = 1 ;
else
aref_end = 0 ;
end
//模式寄存器结束标志
//mrd_end
always@(*) begin
if(init_state_cs == INIT_TMRD && cnt_clk == cnt_mrd)
mrd_end = 1 ;
else
mrd_end = 0 ;
end
//初始化状态机 三段式
//同步时序描述状态转移
always @(posedge sys_clk or negedge sys_rst_n) begin
if(~sys_rst_n) begin
init_state_cs <= INIT_IDLE;
end
else begin
init_state_cs <= init_state_ns ;
end
end
//组合逻辑描述状态转移条件
always@(*) begin
case(init_state_cs)
INIT_IDLE :
if(pow_end == 1)
init_state_ns = INIT_PRE ;
else
init_state_ns = INIT_IDLE ;
INIT_PRE :
init_state_ns = INIT_TRP ;
INIT_TRP :
if(pre_end == 1)
init_state_ns = INIT_AREF ;
else
init_state_ns = INIT_TRP ;
INIT_AREF: init_state_ns = INIT_TRFC ;
INIT_TRFC ://自动刷新等待状态,等待结束,自动跳转到模式寄存器,记录刷新次数
if(aref_end == 1) // 刷新结束,需要判断刷新次数
if(cnt_init_aref == aref_num)
init_state_ns = INIT_LMR ;
else
init_state_ns = INIT_AREF ;
else
init_state_ns = INIT_TRFC ;
INIT_LMR : init_state_ns = INIT_TMRD ;
INIT_TMRD :
if(mrd_end == 1)
init_state_ns = INIT_END ;
else
init_state_ns = INIT_TMRD ;
INIT_END :
init_state_ns = INIT_IDLE ;
default:
init_state_ns = INIT_IDLE ;
endcase // init_state_cs
end
//时序逻辑描述状态输出
always @(posedge sys_clk or negedge sys_rst_n) begin
if(~sys_rst_n) begin
init_cmd <= NOP ;
init_ba <= 2'b11 ;
init_addr <= 13'h1fff ;
init_end <= 1'b0 ;
end
else begin
case (init_state_cs)
INIT_IDLE,INIT_TRP,INIT_TRFC,INIT_TMRD: begin
init_cmd <= NOP ;
init_ba <= 2'b11 ;
init_addr <= 13'h1fff ;
end
INIT_PRE : begin
init_cmd <= PRE ;
init_ba <= 2'b11 ;
init_addr <= 13'h1fff ;
end
INIT_AREF : begin
init_cmd <= AREF ;
init_ba <= 2'b11 ;
init_addr <= 13'h1fff ;
end
INIT_LMR : begin
init_cmd <= LMR ;
init_ba <= 2'b00 ; //这里11和00有什么区别吗
init_addr <= init_lmrset ;
end
INIT_END : begin
init_cmd <= NOP ;
init_ba <= 2'b11 ;
init_addr <= 13'h1fff ;
init_end <= 1'b1 ;
end
default : /* default */begin
init_cmd <= NOP ;
init_ba <= 2'b11 ;
init_addr <= 13'h1fff ;
end
endcase
end
end
endmodule
SDRAM自动刷新模块
模块图:
端口描述:
名称 | 备注 |
---|---|
sys_clk | 系统时钟,166MHz |
sys_rst_n | 复位信号,低电平有效 |
init_end | 初始化模块结束标志 |
aref_en | 自动刷新使能 |
aref_cmd | 自动刷新阶段指令 |
aref_req | 自动刷新阶段请求 |
aref_ba | 自动刷新阶段Bank地址 |
aref_addr | 自动刷新地址总线 |
aref_end | 自动刷新结束标志 |
工作时序:
备注:
SDRAM中的电容是存在电荷泄露的,因此需要定期刷新,这是DRAM最重要的操作,也是保存数据留存的关键步骤。公认的标准是64ms完成一行的数据刷新,64ms/行数量,以8192行为例,需要7.8125us,这里选择7.5us是为了给仲裁机留出仲裁裕量。
需要注意的地方:
(1)3个时间,SDRAM自动刷新模块涉及3个关键时间:
名称 | 时间 | 备注 |
---|---|---|
T_RP | 18ns | 预充电等待时间 |
T_RFC | 66ns | 自动刷新周期 |
刷新时间 | 64ms/8192 = 7.8125us,取7.5us |
(2)6个状态,SDRAM自动刷新模块涉及6个状态,用于描述状态机:
名称 | 编码 | 备注 |
---|---|---|
AREF_IDLE | 3’b000 | 初始状态,等待自动刷新使能 |
AREF_PRE | 3’b001 | 预充电状态 |
AREF_TRP | 3’b011 | 预充电等待状态 |
AREF_AREF | 3’b010 | 自动刷新状态 |
AREF_TRFC | 3’b110 | 自动刷新等待状态 |
AREF_END | 3’b111 | 自动刷新结束状态 |
(3)3个命令,SDRAM自动刷新模块涉及3个操作命令,这里需要查看芯片手册
名称 | 编码 | 备注 | 快速记忆码(10进制) |
---|---|---|---|
NOP | 4’b0111 | 空操作 | 7 |
PRE | 4’b0010 | 预充电 | 2 |
AREF | 4’b0001 | 自动刷新 | 1 |
刷新流程:
72717…17
其中,17至少要一次,对应自动刷新操作。
仿真波形:
Verilog代码:
// -----------------------------------------------------------------------------
// Copyright (c) 2014-2022 All rights reserved
// File : sdram_aref.v
// Create : 2022-07-04 09:20:46
// -----------------------------------------------------------------------------
`timescale 1ns/1ns
module sdram_aref
(
input wire sys_clk , //系统时钟,频率166MHz
input wire sys_rst_n , //复位信号,低电平有效
input wire init_end , //初始化结束信号
input wire aref_en , //自动刷新使能
output reg aref_req , //自动刷新请求
output reg [3:0] aref_cmd , //自动刷新阶段写入sdram的指令,{cs_n,ras_n,cas_n,we_n}
output reg [1:0] aref_ba , //自动刷新阶段Bank地址
output reg [12:0] aref_addr , //地址数据,辅助预充电操作,A12-A0,13位地址
output wire aref_end //自动刷新结束标志
);
//********************************************************************//
//****************** Parameter and Internal Signal *******************//
//********************************************************************//
//parameter define
parameter CNT_REF_MAX = 11'd1248 ; //自动刷新等待时钟数(7.5us)
parameter TRP_CLK = 3'd2 , //预充电等待周期
TRC_CLK = 4'd6 ; //自动刷新等待周期
parameter P_CHARGE = 4'b0010 , //预充电指令
A_REF = 4'b0001 , //自动刷新指令
NOP = 4'b0111 ; //空操作指令
parameter AREF_IDLE = 3'b000 , //初始状态,等待自动刷新使能
AREF_PCHA = 3'b001 , //预充电状态
AREF_TRP = 3'b011 , //预充电等待 tRP
AUTO_REF = 3'b010 , //自动刷新状态
AREF_TRF = 3'b100 , //自动刷新等待 tRC
AREF_END = 3'b101 ; //自动刷新结束
//wire define
wire trp_end ; //预充电等待结束标志
wire trc_end ; //自动刷新等待结束标志
wire aref_ack ; //自动刷新应答信号
//reg define
reg [9:0] cnt_aref ; //自动刷新计数器
reg [2:0] aref_state ; //SDRAM自动刷新状态
reg [2:0] cnt_clk ; //时钟周期计数,记录自刷新阶段各状态等待时间
reg cnt_clk_rst ; //时钟周期计数复位标志
reg [1:0] cnt_aref_aref ; //自动刷新次数计数器
//********************************************************************//
//***************************** Main Code ****************************//
//********************************************************************//
//cnt_ref:刷新计数器
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_aref <= 10'd0;
else if(cnt_aref >= CNT_REF_MAX)
cnt_aref <= 10'd0;
else if(init_end == 1'b1)
cnt_aref <= cnt_aref + 1'b1;
//aref_req:自动刷新请求
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
aref_req <= 1'b0;
else if(cnt_aref == (CNT_REF_MAX - 1'b1))
aref_req <= 1'b1;
else if(aref_ack == 1'b1)
aref_req <= 1'b0;
//aref_ack:自动刷新应答信号
assign aref_ack = (aref_state == AREF_PCHA ) ? 1'b1 : 1'b0;
//aref_end:自动刷新结束标志
assign aref_end = (aref_state == AREF_END ) ? 1'b1 : 1'b0;
//cnt_clk:时钟周期计数,记录初始化各状态等待时间
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_clk <= 3'd0;
else if(cnt_clk_rst == 1'b1)
cnt_clk <= 3'd0;
else
cnt_clk <= cnt_clk + 1'b1;
//trp_end,trc_end,tmrd_end:等待结束标志
assign trp_end = ((aref_state == AREF_TRP)
&& (cnt_clk == TRP_CLK )) ? 1'b1 : 1'b0;
assign trc_end = ((aref_state == AREF_TRF)
&& (cnt_clk == TRC_CLK )) ? 1'b1 : 1'b0;
//cnt_aref_aref:初始化过程自动刷新次数计数器
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_aref_aref <= 2'd0;
else if(aref_state == AREF_IDLE)
cnt_aref_aref <= 2'd0;
else if(aref_state == AUTO_REF)
cnt_aref_aref <= cnt_aref_aref + 1'b1;
else
cnt_aref_aref <= cnt_aref_aref;
//SDRAM自动刷新状态机
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
aref_state <= AREF_IDLE;
else
case(aref_state)
AREF_IDLE:
if((aref_en == 1'b1) && (init_end == 1'b1))
aref_state <= AREF_PCHA;
else
aref_state <= aref_state;
AREF_PCHA:
aref_state <= AREF_TRP;
AREF_TRP:
if(trp_end == 1'b1)
aref_state <= AUTO_REF;
else
aref_state <= aref_state;
AUTO_REF:
aref_state <= AREF_TRF;
AREF_TRF:
if(trc_end == 1'b1)
if(cnt_aref_aref == 2'd2)
aref_state <= AREF_END;
else
aref_state <= AUTO_REF;
else
aref_state <= aref_state;
AREF_END:
aref_state <= AREF_IDLE;
default:
aref_state <= AREF_IDLE;
endcase
//cnt_clk_rst:时钟周期计数复位标志
always@(*)
begin
case (aref_state)
AREF_IDLE: cnt_clk_rst <= 1'b1; //时钟周期计数器清零
AREF_TRP: cnt_clk_rst <= (trp_end == 1'b1) ? 1'b1 : 1'b0;
//等待结束标志有效,计数器清零
AREF_TRF: cnt_clk_rst <= (trc_end == 1'b1) ? 1'b1 : 1'b0;
//等待结束标志有效,计数器清零
AREF_END: cnt_clk_rst <= 1'b1;
default: cnt_clk_rst <= 1'b0;
endcase
end
//SDRAM操作指令控制
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
begin
aref_cmd <= NOP;
aref_ba <= 2'b11;
aref_addr <= 13'h1fff;
end
else
case(aref_state)
AREF_IDLE,AREF_TRP,AREF_TRF: //执行空操作指令
begin
aref_cmd <= NOP;
aref_ba <= 2'b11;
aref_addr <= 13'h1fff;
end
AREF_PCHA: //预充电指令
begin
aref_cmd <= P_CHARGE;
aref_ba <= 2'b11;
aref_addr <= 13'h1fff;
end
AUTO_REF: //自动刷新指令
begin
aref_cmd <= A_REF;
aref_ba <= 2'b11;
aref_addr <= 13'h1fff;
end
AREF_END: //一次自动刷新完成
begin
aref_cmd <= NOP;
aref_ba <= 2'b11;
aref_addr <= 13'h1fff;
end
default:
begin
aref_cmd <= NOP;
aref_ba <= 2'b11;
aref_addr <= 13'h1fff;
end
endcase
endmodule
SDRAM写模块
模块图:
端口描述:
名称 | 备注 |
---|---|
sys_clk | 系统时钟,166MHz |
sys_rst_n | 复位信号,低电平有效 |
init_end | 初始化模块结束标志 |
wr_en | 写使能 |
wr_addr | 写地址 |
wr_data | 写数据 |
wr_burst_len | 写突发长度 |
wr_ack | 写响应 |
wr_end | 单次写结束 |
write_cmd | 写指令 |
write_ba | 写Bank地址 |
write_addr | 写地址 |
wr_sdram_en | 写SDRAM使能 |
wr_sdram_data | 写SDRAM数据 |
工作时序:
备注:
SDRAM写模块要和初始化模块模式寄存器设置的工作模式相匹配,这里采用的是页突发写模式。
需要注意的地方:
(1)3个时间,SDRAM写模块涉及3个关键时间:
名称 | 时间 | 备注 |
---|---|---|
T_RP | 18ns | 预充电等待时间 |
T_RCD | 18ns | 写入激活命令到数据开始读写,中间需要等待的时间 |
T_data | (n-1)*clk_period | 数据突发时间,按照突发长度设定 |
(2)8个状态,SDRAM写模块涉及8个状态,用于描述状态机:
名称 | 编码 | 备注 |
---|---|---|
WR_IDLE | 3’b000 | 写初始状态 |
WR_ACT | 3’b001 | 激活状态 |
WR_TRCD | 3’b011 | 激活等待状态 |
WR_WR | 3’b010 | 写操作状态 |
WR_DATA | 3’b110 | 写数据状态 |
WR_PRE | 3’b111 | 预充电状态 |
WR_TRD | 3’b101 | 预充电等待状态 |
WR_END | 3’b100 | 写结束状态 |
(3)5个命令,SDRAM写模块涉及5个操作命令,这里需要查看芯片手册
名称 | 编码 | 备注 | 快速记忆码(10进制) |
---|---|---|---|
ACT | 4’b0011 | 激活 | 3 |
NOP | 4’b0111 | 空操作 | 7 |
B_STOP | 4’b0110 | 突发停止 | 6 |
WR | 4’b0100 | SDRAM写 | 4 |
PRE | 4’b0010 | 预充电 | 2 |
写流程:
73747…762
其中,7至少要一次,对应突发写长度的周期。
仿真波形:
Verilog代码:
// -----------------------------------------------------------------------------
// File : sdram_write.v
// Create : 2022-07-04 09:15:24
// -----------------------------------------------------------------------------
`timescale 1ns/1ns
module sdram_write
(
input wire sys_clk , //系统时钟,频率166MHz
input wire sys_rst_n , //复位信号,低电平有效
input wire init_end , //初始化结束信号
input wire wr_en , //写使能
input wire [23:0] wr_addr , //写SDRAM地址
input wire [15:0] wr_data , //待写入SDRAM的数据(写FIFO传入)
input wire [9:0] wr_burst_len , //写突发SDRAM字节数
output wire wr_ack , //写SDRAM响应信号
output wire wr_end , //一次突发写结束
output reg [3:0] write_cmd , //写数据阶段写入sdram的指令,{cs_n,ras_n,cas_n,we_n}
output reg [1:0] write_ba , //写数据阶段Bank地址
output reg [12:0] write_addr , //地址数据,辅助预充电操作,行、列地址,A12-A0,13位地址
output reg wr_sdram_en , //数据总线输出使能
output wire [15:0] wr_sdram_data //写入SDRAM的数据
);
//********************************************************************//
//****************** Parameter and Internal Signal *******************//
//********************************************************************//
//parameter define
parameter TRCD_CLK = 'd2 , //激活周期
TRP_CLK = 'd2 ; //预充电周期
parameter WR_IDLE = 4'b0000 , //初始状态
WR_ACTIVE = 4'b0001 , //激活
WR_TRCD = 4'b0011 , //激活等待
WR_WRITE = 4'b0010 , //写操作
WR_DATA = 4'b0100 , //写数据
WR_PRE = 4'b0101 , //预充电
WR_TRP = 4'b0111 , //预充电等待
WR_END = 4'b0110 ; //一次突发写结束
parameter NOP = 4'b0111 , //空操作指令
ACTIVE = 4'b0011 , //激活指令
WRITE = 4'b0100 , //数据写指令
B_STOP = 4'b0110 , //突发停止指令
P_CHARGE = 4'b0010 ; //预充电指令
//wire define
wire trcd_end ; //激活等待周期结束
wire twrite_end ; //突发写结束
wire trp_end ; //预充电有效周期结束
//reg define
reg [3:0] write_state ; //SDRAM写状态
reg [9:0] cnt_clk ; //时钟周期计数,记录写数据阶段各状态等待时间
reg cnt_clk_rst ; //时钟周期计数复位标志
//********************************************************************//
//***************************** Main Code ****************************//
//********************************************************************//
//wr_end:一次突发写结束
assign wr_end = (write_state == WR_END) ? 1'b1 : 1'b0;
//wr_ack:写SDRAM响应信号
assign wr_ack = ( write_state == WR_WRITE)
|| ((write_state == WR_DATA)
&& (cnt_clk <= (wr_burst_len - 2'd2)));
//cnt_clk:时钟周期计数,记录初始化各状态等待时间
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_clk <= 10'd0;
else if(cnt_clk_rst == 1'b1)
cnt_clk <= 10'd0;
else
cnt_clk <= cnt_clk + 1'b1;
//trcd_end,twrite_end,trp_end:等待结束标志
assign trcd_end = ((write_state == WR_TRCD)
&&(cnt_clk == TRCD_CLK )) ? 1'b1 : 1'b0; //激活周期结束
assign twrite_end = ((write_state == WR_DATA)
&&(cnt_clk == wr_burst_len - 1)) ? 1'b1 : 1'b0; //突发写结束
assign trp_end = ((write_state == WR_TRP )
&&(cnt_clk == TRP_CLK )) ? 1'b1 : 1'b0; //预充电等待周期结束
//write_state:SDRAM的工作状态机
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
write_state <= WR_IDLE;
else
case(write_state)
WR_IDLE:
if((wr_en ==1'b1) && (init_end == 1'b1))
write_state <= WR_ACTIVE;
else
write_state <= write_state;
WR_ACTIVE:
write_state <= WR_TRCD;
WR_TRCD:
if(trcd_end == 1'b1)
write_state <= WR_WRITE;
else
write_state <= write_state;
WR_WRITE:
write_state <= WR_DATA;
WR_DATA:
if(twrite_end == 1'b1)
write_state <= WR_PRE;
else
write_state <= write_state;
WR_PRE:
write_state <= WR_TRP;
WR_TRP:
if(trp_end == 1'b1)
write_state <= WR_END;
else
write_state <= write_state;
WR_END:
write_state <= WR_IDLE;
default:
write_state <= WR_IDLE;
endcase
//计数器控制逻辑
always@(*)
begin
case(write_state)
WR_IDLE: cnt_clk_rst <= 1'b1;
WR_TRCD: cnt_clk_rst <= (trcd_end == 1'b1) ? 1'b1 : 1'b0;
WR_WRITE: cnt_clk_rst <= 1'b1;
WR_DATA: cnt_clk_rst <= (twrite_end == 1'b1) ? 1'b1 : 1'b0;
WR_TRP: cnt_clk_rst <= (trp_end == 1'b1) ? 1'b1 : 1'b0;
WR_END: cnt_clk_rst <= 1'b1;
default: cnt_clk_rst <= 1'b0;
endcase
end
//SDRAM操作指令控制
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
begin
write_cmd <= NOP;
write_ba <= 2'b11;
write_addr <= 13'h1fff;
end
else
case(write_state)
WR_IDLE,WR_TRCD,WR_TRP:
begin
write_cmd <= NOP;
write_ba <= 2'b11;
write_addr <= 13'h1fff;
end
WR_ACTIVE: //激活指令
begin
write_cmd <= ACTIVE;
write_ba <= wr_addr[23:22];
write_addr <= wr_addr[21:9];
end
WR_WRITE: //写操作指令
begin
write_cmd <= WRITE;
write_ba <= wr_addr[23:22];
write_addr <= {4'b0000,wr_addr[8:0]};
end
WR_DATA: //突发传输终止指令
begin
if(twrite_end == 1'b1)
write_cmd <= B_STOP;
else
begin
write_cmd <= NOP;
write_ba <= 2'b11;
write_addr <= 13'h1fff;
end
end
WR_PRE: //预充电指令
begin
write_cmd <= P_CHARGE;
write_ba <= wr_addr[23:22];
write_addr <= 13'h0400;
end
WR_END:
begin
write_cmd <= NOP;
write_ba <= 2'b11;
write_addr <= 13'h1fff;
end
default:
begin
write_cmd <= NOP;
write_ba <= 2'b11;
write_addr <= 13'h1fff;
end
endcase
//wr_sdram_en:数据总线输出使能
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
wr_sdram_en <= 1'b0;
else
wr_sdram_en <= wr_ack;
//wr_sdram_data:写入SDRAM的数据
assign wr_sdram_data = (wr_sdram_en == 1'b1) ? wr_data : 16'd0;
endmodule
SDRAM读模块
模块图:
端口描述:
名称 | 备注 |
---|---|
sys_clk | 系统时钟,166MHz |
sys_rst_n | 复位信号,低电平有效 |
init_end | 初始化模块结束标志 |
rd_en | 读使能 |
rd_addr | 读地址 |
rd_data | 读数据 |
rd_burst_len | 读突发长度 |
rd_ack | 读响应 |
rd_end | 单次读结束 |
read_cmd | 读SDRAM指令 |
read_ba | 读Bank地址 |
read_addr | 读地址 |
rd_sdram_data | 读SDRAM数据 |
工作时序:
备注:
SDRAM读模块模式也是同预设寄存器保持一致,这里使用的是不带自动预充电的页突发读模式。
需要注意的地方:
(1)3个时间,SDRAM读模块涉及3个关键时间:
名称 | 时间 | 备注 |
---|---|---|
T_RP | 18ns | 预充电等待时间 |
T_RCD | 18ns | 写入激活命令到数据开始读写,中间需要等待的时间 |
T_CL | 2clk | 潜伏期,读数据才有 |
(2)9个状态,SDRAM读模块涉及9个状态,用于描述状态机:
名称 | 编码 | 备注 |
---|---|---|
RD_IDLE | 4’b0000 | 空闲状态 |
RD_ACT | 4’b0001 | 激活状态 |
RD_TRCD | 4’b0011 | 激活等待状态 |
RD_RD | 4’b0010 | 读操作状态 |
RD_CL | 4’b0110 | 潜伏期状态 |
RD_DATA | 4’b0111 | 读数据状态 |
RD_PRE | 4’b0101 | 预充电状态 |
RD_TRP | 4’b0100 | 预充电等待状态 |
RD_END | 4’b1100 | 读结束状态 |
(3)5个命令,SDRAM读模块涉及5个操作命令,这里需要查看芯片手册
名称 | 编码 | 备注 | 快速记忆码(10进制) |
---|---|---|---|
ACT | 4’b0011 | 激活 | 3 |
NOP | 4’b0111 | 空操作 | 7 |
B_STOP | 4’b0110 | 突发停止 | 6 |
READ | 4’b0101 | SDRAM读 | 5 |
PRE | 4’b0010 | 预充电 | 2 |
读流程:
73757…76727
其中,5和6之间的7至少要一次,对应突发读长度的周期。
仿真波形:
Verilog代码:
// File : sdram_read.v
// Create : 2022-07-04 20:51:55
// -----------------------------------------------------------------------------
`timescale 1ns/1ns
module sdram_read
(
input wire sys_clk , //系统时钟,频率166.66MHz
input wire sys_rst_n , //复位信号,低电平有效
input wire init_end , //初始化结束信号
input wire rd_en , //读使能
input wire [23:0] rd_addr , //读SDRAM地址
input wire [15:0] rd_data , //自SDRAM中读出的数据
input wire [9:0] rd_burst_len , //读突发SDRAM字节数
output wire rd_ack , //读SDRAM响应信号
output wire rd_end , //一次突发读结束
output reg [3:0] read_cmd , //读数据阶段写入sdram的指令,{cs_n,ras_n,cas_n,we_n}
output reg [1:0] read_ba , //读数据阶段Bank地址
output reg [12:0] read_addr , //地址数据,辅助预充电操作,行、列地址,A12-A0,13位地址
output wire [15:0] rd_sdram_data //SDRAM读出的数据
);
//********************************************************************//
//****************** Parameter and Internal Signal *******************//
//********************************************************************//
//parameter define
parameter TRCD_CLK = 10'd2 , //激活等待周期
TCL_CLK = 10'd3 , //潜伏期
TRP_CLK = 10'd2 ; //预充电等待周期
parameter RD_IDLE = 4'b0000 , //空闲
RD_ACTIVE = 4'b0001 , //激活
RD_TRCD = 4'b0011 , //激活等待
RD_READ = 4'b0010 , //读操作
RD_CL = 4'b0100 , //潜伏期
RD_DATA = 4'b0101 , //读数据
RD_PRE = 4'b0111 , //预充电
RD_TRP = 4'b0110 , //预充电等待
RD_END = 4'b1100 ; //一次突发读结束
parameter NOP = 4'b0111 , //空操作指令
ACTIVE = 4'b0011 , //激活指令
READ = 4'b0101 , //数据读指令
B_STOP = 4'b0110 , //突发停止指令
P_CHARGE = 4'b0010 ; //预充电指令
//wire define
wire trcd_end ; //激活等待周期结束
wire trp_end ; //预充电等待周期结束
wire tcl_end ; //潜伏期结束标志
wire tread_end ; //突发读结束
wire rdburst_end ; //读突发终止
//reg define
reg [3:0] read_state ; //SDRAM写状态
reg [9:0] cnt_clk ; //时钟周期计数,记录初始化各状态等待时间
reg cnt_clk_rst ; //时钟周期计数复位标志
reg [15:0] rd_data_reg ;
//********************************************************************//
//***************************** Main Code ****************************//
//********************************************************************//
//rd_data_reg
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
rd_data_reg <= 16'd0;
else
rd_data_reg <= rd_data;
//rd_end:一次突发读结束
assign rd_end = (read_state == RD_END) ? 1'b1 : 1'b0;
//rd_ack:读SDRAM响应信号 //166m时钟对齐问题
assign rd_ack = (read_state == RD_DATA)
&& (cnt_clk >= 10'd2)
&& (cnt_clk < (rd_burst_len + 'd2));
//cnt_clk:时钟周期计数,记录初始化各状态等待时间
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_clk <= 10'd0;
else if(cnt_clk_rst == 1'b1)
cnt_clk <= 10'd0;
else
cnt_clk <= cnt_clk + 1'b1;
//trcd_end,trp_end,tcl_end,tread_end,rdburst_end:等待结束标志
assign trcd_end = ((read_state == RD_TRCD)
&& (cnt_clk == TRCD_CLK )) ? 1'b1 : 1'b0; //行选通周期结束
assign trp_end = ((read_state == RD_TRP )
&& (cnt_clk == TRP_CLK )) ? 1'b1 : 1'b0; //预充电有效周期结束
assign tcl_end = ((read_state == RD_CL )
&& (cnt_clk == TCL_CLK - 1 )) ? 1'b1 : 1'b0; //潜伏期结束
assign tread_end = ((read_state == RD_DATA)
&& (cnt_clk == rd_burst_len + 2)) ? 1'b1 : 1'b0; //突发读结束
assign rdburst_end = ((read_state == RD_DATA)
&& (cnt_clk == rd_burst_len - 4)) ? 1'b1 : 1'b0; //读突发终止
//read_state:SDRAM的工作状态机
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
read_state <= RD_IDLE;
else
case(read_state)
RD_IDLE:
if((rd_en ==1'b1) && (init_end == 1'b1))
read_state <= RD_ACTIVE;
else
read_state <= RD_IDLE;
RD_ACTIVE:
read_state <= RD_TRCD;
RD_TRCD:
if(trcd_end == 1'b1)
read_state <= RD_READ;
else
read_state <= RD_TRCD;
RD_READ:
read_state <= RD_CL;
RD_CL:
read_state <= (tcl_end == 1'b1) ? RD_DATA : RD_CL;
RD_DATA:
read_state <= (tread_end == 1'b1) ? RD_PRE : RD_DATA;
RD_PRE:
read_state <= RD_TRP;
RD_TRP:
read_state <= (trp_end == 1'b1) ? RD_END : RD_TRP;
RD_END:
read_state <= RD_IDLE;
default:
read_state <= RD_IDLE;
endcase
//计数器控制逻辑
always@(*)
begin
case(read_state)
RD_IDLE: cnt_clk_rst <= 1'b1;
RD_TRCD: cnt_clk_rst <= (trcd_end == 1'b1) ? 1'b1 : 1'b0;
RD_READ: cnt_clk_rst <= 1'b1;
RD_CL: cnt_clk_rst <= (tcl_end == 1'b1) ? 1'b1 : 1'b0;
RD_DATA: cnt_clk_rst <= (tread_end == 1'b1) ? 1'b1 : 1'b0;
RD_TRP: cnt_clk_rst <= (trp_end == 1'b1) ? 1'b1 : 1'b0;
RD_END: cnt_clk_rst <= 1'b1;
default: cnt_clk_rst <= 1'b0;
endcase
end
//SDRAM操作指令控制
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
begin
read_cmd <= NOP;
read_ba <= 2'b11;
read_addr <= 13'h1fff;
end
else
case(read_state)
RD_IDLE,RD_TRCD,RD_TRP:
begin
read_cmd <= NOP;
read_ba <= 2'b11;
read_addr <= 13'h1fff;
end
RD_ACTIVE: //激活指令
begin
read_cmd <= ACTIVE;
read_ba <= rd_addr[23:22];
read_addr <= rd_addr[21:9];
end
RD_READ: //读操作指令
begin
read_cmd <= READ;
read_ba <= rd_addr[23:22];
read_addr <= {4'b0000,rd_addr[8:0]};
end
RD_DATA: //突发传输终止指令
begin
if(rdburst_end == 1'b1)
read_cmd <= B_STOP;
else
begin
read_cmd <= NOP;
read_ba <= 2'b11;
read_addr <= 13'h1fff;
end
end
RD_PRE: //预充电指令
begin
read_cmd <= P_CHARGE;
read_ba <= rd_addr[23:22];
read_addr <= 13'h0400;
end
RD_END:
begin
read_cmd <= NOP;
read_ba <= 2'b11;
read_addr <= 13'h1fff;
end
default:
begin
read_cmd <= NOP;
read_ba <= 2'b11;
read_addr <= 13'h1fff;
end
endcase
//rd_sdram_data:SDRAM读出的数据
assign rd_sdram_data = (rd_ack == 1'b1) ? rd_data_reg : 16'b0;
endmodule
SDRAM仲裁机
模块图:
端口描述:
名称 | 备注 |
---|---|
sys_clk | 系统时钟,166MHz |
sys_rst_n | 复位信号,低电平有效 |
init_end | 初始化模块结束标志 |
init_cmd | 初始化模块指令 |
init_ba | 初始化模块bank地址 |
init_addr | 初始化模块地址总线 |
aref_req | 自动刷新阶段请求 |
aref_end | 自动刷新结束标志 |
aref_cmd | 自动刷新阶段指令 |
aref_ba | 自动刷新阶段Bank地址 |
aref_addr | 自动刷新地址总线 |
wr_req | 数据写请求 |
wr_end | 单次写结束 |
wr_cmd | 写指令 |
write_ba | 写Bank地址 |
wr_addr | 写地址 |
wr_data | 写数据 |
wr_sdram_en | 写SDRAM使能 |
rd_req | 读请求 |
rd_end | 单次读结束 |
rd_cmd | 读SDRAM指令 |
rd_ba | 读Bank地址 |
rd_addr | 读地址 |
aref_en | 自动刷新使能 |
wr_en | 数据写使能 |
rd_en | 数据读使能 |
sdram_cke | SDRAM时钟使能信号 |
sdram_cs_n | SDRAM片选信号 |
sdram_cas_n | SDRAM列选通信号 |
sdram_ras_n | SDRAM行选通信号 |
sdram_we_n | SDRAM写使能 |
sdram_ba | SDRAM Bank地址 |
sdram_addr | SDRAM地址总线 |
sdram_dq | SDRAM数据总线 |
工作时序:
备注:
仲裁机包含五个状态,默认优先级为自动刷新操作>数据写操作>数据读操作;一切都是以保证数据存留为前提。
仿真波形:
Verilog代码:
// -----------------------------------------------------------------------------
// Copyright (c) 2014-2022 All rights reserved
// -----------------------------------------------------------------------------
// File : sdram_arbit.v
// Create : 2022-06-07 15:32:59
// Revise : 2022-06-20 16:21:04
// Verdion:
// Description:
// -----------------------------------------------------------------------------
`timescale 1ps/1ps
module sdram_arbit (
//system signals
input sys_clk , //系统时钟,167M
input sys_rst_n , //系统复位信号,低电平有效
//init signals
input init_end , //初始化结束标志
input [3:0] init_cmd , //初始化阶段命令
input [1:0] init_ba , //初始化阶段bank地址
input [12:0] init_addr , //初始化阶段地址总线
//aref signals
input aref_req , //刷新请求信号
input aref_end , //刷新结束信号
input [3:0] aref_cmd , //刷新阶段命令
input [1:0] aref_ba , //刷新阶段bank地址
input [12:0] aref_addr , //刷新阶段地址
//write signals
input wr_req , //写数据请求
input wr_end , //一次写结束信号
input [3:0] wr_cmd , //写阶段命令
input [1:0] wr_ba , //写阶段BANK地址
input [12:0] wr_addr , //写阶段地址总线
input [15:0] wr_data , //写数据
input wr_sdram_en , //写sdram使能信号
//read signals
input rd_req , //读请求
input rd_end , //读数据结束
input [3:0] rd_cmd , //读阶段命令
input [1:0] rd_ba , //读阶段bank地址
input [12:0] rd_addr , //读地址总线
//output signals
output reg aref_en , //刷新请求
output reg wr_en , //写数据使能
output reg rd_en , //读数据使能
output wire sdram_cke , //sdram时钟有效信号
output wire sdram_cs_n , //sdram片选信号
output wire sdram_cas_n , //sdram行选通信号
output wire sdram_ras_n , //sdram列选通信号
output wire sdram_we_n , //sdram写使能信号
output reg [1:0] sdram_ba , //sdram的bank地址
output reg [12:0] sdram_addr , //sdram的地址总线
inout wire [15:0] sdram_dq //sdram的数据总线
);
//localparam
localparam IDLE = 3'b000 , //初始状态
ARBIT = 3'b001 , //仲裁状态
AREF = 3'b011 , //自动刷新
WRITE = 3'b010 , //写状态
READ = 3'b110 ; //读状态
//命令
localparam NOP = 4'b0111 ; //空操作命令
//reg define
reg [3:0] sdram_cmd ; //写入SDRAM 命令
reg [2:0] state_cs ; //当前状态
reg [2:0] state_ns ; //下一状态
reg [15:0] wr_data_reg ; //数据寄存
//状态机
always @(posedge sys_clk or negedge sys_rst_n) begin : proc_state_cs
if(~sys_rst_n) begin
state_cs <= IDLE ;
end else begin
state_cs <= state_ns ;
end
end
//组合逻辑,判断跳转
always@(*) begin
case(state_cs)
IDLE : begin
if(init_end == 1'b1)
state_ns = ARBIT ;
else
state_ns = IDLE ;
end
ARBIT : begin //刷新请求>写请求>读请求
if(aref_req == 1'b1)
state_ns = AREF ;
else if(wr_req == 1'b1 )
state_ns = WRITE ;
else if(rd_req == 1'b1)
state_ns = READ ;
else
state_ns = ARBIT ;
end
AREF : begin
if(aref_end == 1'b1)
state_ns = ARBIT ;
else
state_ns = AREF ;
end
WRITE : begin
if(wr_end == 1'b1)
state_ns = ARBIT ;
else
state_ns = WRITE ;
end
READ : begin
if(rd_end == 1'b1)
state_ns = ARBIT ;
else
state_ns = READ ;
end
default : state_ns = IDLE ;
endcase
end
//时序逻辑 输出错误,组合逻辑输出,可组合可时序
//sdram_ba sdram_addr sdram_cmd
always @(* ) begin
case(state_cs)
IDLE : begin
sdram_cmd = init_cmd ;
sdram_ba = init_ba ;
sdram_addr = init_addr ;
end
ARBIT : begin
sdram_cmd = NOP ;
sdram_ba = 2'b11 ;
sdram_addr = 13'h1fff ;
end
AREF : begin
sdram_cmd = aref_cmd ;
sdram_ba = aref_ba ;
sdram_addr = aref_addr ;
end
WRITE : begin
sdram_cmd = wr_cmd ;
sdram_ba = wr_ba ;
sdram_addr = wr_addr ;
end
READ : begin
sdram_cmd = rd_cmd ;
sdram_ba = rd_ba ;
sdram_addr = rd_addr ;
end
default : begin
sdram_cmd = NOP ;
sdram_ba = 2'b11 ;
sdram_addr = 13'h1fff ;
end
endcase // state_cs
end
//自动刷新使能
always @(posedge sys_clk or negedge sys_rst_n) begin : proc_
if(~sys_rst_n) begin
aref_en <= 1'b0 ;
end
else if ((state_cs == ARBIT) && (aref_req == 1'b1) )begin
aref_en <= 1'b1 ;
end
else if(aref_end == 1'b1 )
aref_en <= 1'b0 ;
end
//写数据使能
//wr_en
always @(posedge sys_clk or negedge sys_rst_n) begin : proc_wr_en
if(~sys_rst_n) begin
wr_en <= 1'b0 ;
end
else if((state_cs == ARBIT) && (aref_req == 1'b0) && (wr_req == 1'b1)) begin
wr_en <= 1'b1 ;
end
else if(wr_end == 1'b1)
wr_en <= 1'b0 ;
end
//读数据使能
//rd_en
always @(posedge sys_clk or negedge sys_rst_n) begin : proc_rd_en
if(~sys_rst_n) begin
rd_en <= 1'b0 ;
end
else if((state_cs == ARBIT) && (aref_req == 1'b0) && (rd_req == 1'b1) )begin
rd_en <= 1'b1 ;
end
else if(rd_end == 1'b1)
rd_en <= 1'b0 ;
end
//SDRAM 时钟使能
assign sdram_cke = 1'b1 ;
//SDRAM 数据总线
assign sdram_dq = (wr_sdram_en == 1'b1 )?wr_data: 16'bz ; //作为输出端口,延迟一拍?
always @(posedge sys_clk or negedge sys_rst_n) begin
if(~sys_rst_n) begin
wr_data_reg <= 0;
end else begin
wr_data_reg <= wr_data ;
end
end
//片选信号,行地址选通信号,列地址选通信号,写使能信号
assign {sdram_cs_n, sdram_ras_n, sdram_cas_n, sdram_we_n} = sdram_cmd ;
endmodule
SDRAM控制模块
模块图:
verlog代码:
// -----------------------------------------------------------------------------
// Copyright (c) 2014-2022 All rights reserved
// File : sdram_top.v
// Create : 2022-07-04 20:51:45
// -----------------------------------------------------------------------------
`timescale 1ns/1ns
module sdram_top
(
input wire sys_clk , //系统时钟
input wire clk_out , //相位偏移时钟
input wire sys_rst_n , //复位信号,低有效
//写FIFO信号
input wire wr_fifo_wr_clk , //写FIFO写时钟
input wire wr_fifo_wr_req , //写FIFO写请求
input wire [15:0] wr_fifo_wr_data , //写FIFO写数据
input wire [23:0] sdram_wr_b_addr , //写SDRAM首地址
input wire [23:0] sdram_wr_e_addr , //写SDRAM末地址
input wire [9:0] wr_burst_len , //写SDRAM数据突发长度
input wire wr_rst , //写复位信号
//读FIFO信号
input wire rd_fifo_rd_clk , //读FIFO读时钟
input wire rd_fifo_rd_req , //读FIFO读请求
input wire [23:0] sdram_rd_b_addr , //读SDRAM首地址
input wire [23:0] sdram_rd_e_addr , //读SDRAM末地址
input wire [9:0] rd_burst_len , //读SDRAM数据突发长度
input wire rd_rst , //读复位信号
output wire [15:0] rd_fifo_rd_data , //读FIFO读数据
output wire [9:0] rd_fifo_num , //读fifo中的数据量
input wire read_valid , //SDRAM读使能
output wire init_end , //SDRAM初始化完成标志
//SDRAM接口信号
output wire sdram_clk , //SDRAM芯片时钟
output wire sdram_cke , //SDRAM时钟有效信号
output wire sdram_cs_n , //SDRAM片选信号
output wire sdram_ras_n , //SDRAM行地址选通脉冲
output wire sdram_cas_n , //SDRAM列地址选通脉冲
output wire sdram_we_n , //SDRAM写允许位
output wire [1:0] sdram_ba , //SDRAM的L-Bank地址线
output wire [12:0] sdram_addr , //SDRAM地址总线
output wire [1:0] sdram_dqm , //SDRAM数据掩码
inout wire [15:0] sdram_dq //SDRAM数据总线
);
//********************************************************************//
//****************** Parameter and Internal Signal *******************//
//********************************************************************//
//wire define
wire sdram_wr_req ; //sdram 写请求
wire sdram_wr_ack ; //sdram 写响应
wire [23:0] sdram_wr_addr ; //sdram 写地址
wire [15:0] sdram_data_in ; //写入sdram中的数据
wire sdram_rd_req ; //sdram 读请求
wire sdram_rd_ack ; //sdram 读响应
wire [23:0] sdram_rd_addr ; //sdram 读地址
wire [15:0] sdram_data_out ; //从sdram中读出的数据
//sdram_clk:SDRAM芯片时钟
assign sdram_clk = clk_out;
//sdram_dqm:SDRAM数据掩码
assign sdram_dqm = 2'b00;
//********************************************************************//
//*************************** Instantiation **************************//
//********************************************************************//
//------------- fifo_ctrl_inst -------------
fifo_ctrl fifo_ctrl_inst(
//system signal
.sys_clk (sys_clk ), //SDRAM控制时钟
.sys_rst_n (sys_rst_n ), //复位信号
//write fifo signal
.wr_fifo_wr_clk (wr_fifo_wr_clk ), //写FIFO写时钟
.wr_fifo_wr_req (wr_fifo_wr_req ), //写FIFO写请求
.wr_fifo_wr_data(wr_fifo_wr_data), //写FIFO写数据
.sdram_wr_b_addr(sdram_wr_b_addr), //写SDRAM首地址
.sdram_wr_e_addr(sdram_wr_e_addr), //写SDRAM末地址
.wr_burst_len (wr_burst_len ), //写SDRAM数据突发长度
.wr_rst (wr_rst ), //写清零信号
//read fifo signal
.rd_fifo_rd_clk (rd_fifo_rd_clk ), //读FIFO读时钟
.rd_fifo_rd_req (rd_fifo_rd_req ), //读FIFO读请求
.rd_fifo_rd_data(rd_fifo_rd_data), //读FIFO读数据
.rd_fifo_num (rd_fifo_num ), //读FIFO中的数据量
.sdram_rd_b_addr(sdram_rd_b_addr), //读SDRAM首地址
.sdram_rd_e_addr(sdram_rd_e_addr), //读SDRAM末地址
.rd_burst_len (rd_burst_len ), //读SDRAM数据突发长度
.rd_rst (rd_rst ), //读清零信号
//USER ctrl signal
.read_valid (read_valid ), //SDRAM读使能
.init_end (init_end ), //SDRAM初始化完成标志
//SDRAM ctrl of write
.sdram_wr_ack (sdram_wr_ack ), //SDRAM写响应
.sdram_wr_req (sdram_wr_req ), //SDRAM写请求
.sdram_wr_addr (sdram_wr_addr ), //SDRAM写地址
.sdram_data_in (sdram_data_in ), //写入SDRAM的数据
//SDRAM ctrl of read
.sdram_rd_ack (sdram_rd_ack ), //SDRAM读请求
.sdram_data_out (sdram_data_out ), //SDRAM读响应
.sdram_rd_req (sdram_rd_req ), //SDRAM读地址
.sdram_rd_addr (sdram_rd_addr ) //读出SDRAM数据
);
//------------- sdram_ctrl_inst -------------
sdram_ctrl sdram_ctrl_inst(
.sys_clk (sys_clk ), //系统时钟
.sys_rst_n (sys_rst_n ), //复位信号,低电平有效
//SDRAM 控制器写端口
.sdram_wr_req (sdram_wr_req ), //写SDRAM请求信号
.sdram_wr_addr (sdram_wr_addr ), //SDRAM写操作的地址
.wr_burst_len (wr_burst_len ), //写sdram时数据突发长度
.sdram_data_in (sdram_data_in ), //写入SDRAM的数据
.sdram_wr_ack (sdram_wr_ack ), //写SDRAM响应信号
//SDRAM 控制器读端口
.sdram_rd_req (sdram_rd_req ), //读SDRAM请求信号
.sdram_rd_addr (sdram_rd_addr ), //SDRAM写操作的地址
.rd_burst_len (rd_burst_len ), //读sdram时数据突发长度
.sdram_data_out (sdram_data_out ), //从SDRAM读出的数据
.init_end (init_end ), //SDRAM 初始化完成标志
.sdram_rd_ack (sdram_rd_ack ), //读SDRAM响应信号
//FPGA与SDRAM硬件接口
.sdram_cke (sdram_cke ), // SDRAM 时钟有效信号
.sdram_cs_n (sdram_cs_n ), // SDRAM 片选信号
.sdram_ras_n (sdram_ras_n ), // SDRAM 行地址选通脉冲
.sdram_cas_n (sdram_cas_n ), // SDRAM 列地址选通脉冲
.sdram_we_n (sdram_we_n ), // SDRAM 写允许位
.sdram_ba (sdram_ba ), // SDRAM L-Bank地址线
.sdram_addr (sdram_addr ), // SDRAM 地址总线
.sdram_dq (sdram_dq ) // SDRAM 数据总线
);
endmodule
FIFO控制模块
模块图:
端口描述:
名称 | 备注 |
---|---|
sys_clk | 系统时钟,166MHz |
sys_rst_n | 复位信号,低电平有效 |
wr_fifo_wr_clk | 写fifo写时钟,测试按照50MHz |
wr_fifo_wr_req | 写fifo写请求 |
wr_fifo_wr_data | 写fifo写数据 |
sdram_wr_b_addr | 写SDRAM的首地址 |
sdram_wr_e_addr | 写SDRAM的末地址 |
wr_burst_len | 写SDRAM的突发长度 |
wr_rst | 写复位信号,写fifo清零 |
rd_fifo_rd_clk | 读fifo读时钟 |
rd_fifo_rd_req | 读fifo读请求 |
sdram_rd_b_addr | 读SDRAM的首地址 |
sdram_rd_e_addr | 读SDRAM的末地址 |
rd_burst_len | 读SDRAM的突发长度 |
rd_rst | 读复位信号,读fifo清零 |
rd_fifo_rd_data | 读fifo读数据 |
rd_fifo_num | 读FIFO中的数据量 /读FIFO中写入的数据量 |
read_valid | SDRAM读使能 |
init_end | SDRAM初始化结束信号 |
sdram_wr_ack | SDRAM写响应 |
sdram_wr_req | SDRAM写请求 |
sdram_wr_addr | SDRAM写地址 |
sdram_data_in | 写入SDRAM的数据 |
sdram_data_out | SDRAM读出的数据 |
sdram_rd_req | SDRAM读请求 |
sdram_rd_addr | SDRAM读地址 |
Verilog代码:
// -----------------------------------------------------------------------------
// Copyright (c) 2014-2022 All rights reserved
// -----------------------------------------------------------------------------
// File : fifo_ctrl.v
// Create : 2022-06-09 09:19:13
// Revise : 2022-06-20 09:14:11
// -----------------------------------------------------------------------------
`timescale 1ps/1ps
module fifo_ctrl (
//signal define
//system signals
input sys_clk , //系统时钟,167MHZ
input sys_rst_n , //系统复位信号,低电平有效
//写fifo信号 //
input wr_fifo_wr_clk , //写fifo写时钟
input wr_fifo_wr_req , //写fifo写请求
input [15:0] wr_fifo_wr_data , //写fifo写数据
input [23:0] sdram_wr_b_addr , //写SDRAM的首地址
input [23:0] sdram_wr_e_addr , //写SDRAM的末地址
input [9:0] wr_burst_len , //写SDRAM的突发长度
input wr_rst , //写复位信号,写fifo清零
//读fifo信号 //
input rd_fifo_rd_clk , //读fifo读时钟
input rd_fifo_rd_req , //读fifo读请求
input [23:0] sdram_rd_b_addr , //读SDRAM的首地址
input [23:0] sdram_rd_e_addr , //读SDRAM的末地址
input [9:0] rd_burst_len , //读SDRAM的突发长度
input rd_rst , //读复位信号,读fifo清零
output wire [15:0] rd_fifo_rd_data , //读fifo读数据
output wire [9:0] rd_fifo_num , //读FIFO中的数据量 /读FIFO中写入的数据量
//
input read_valid , //SDRAM读使能
input init_end , //SDRAM初始化结束信号
//
//SDRAM写信号 //
input sdram_wr_ack , //SDRAM写响应
output reg sdram_wr_req , //SDRAM写请求
output reg [23:0] sdram_wr_addr , //SDRAM写地址
output wire [15:0] sdram_data_in , //写入SDRAM的数据
//SDRAM读信号 //
input sdram_rd_ack , //SDRAM读响应
input [15:0] sdram_data_out , //SDRAM读出的数据
output reg sdram_rd_req , //SDRAM读请求
output reg [23:0] sdram_rd_addr //SDRAM读地址
);
//======================================
//param and internal signals
//======================================
//wire define
wire wr_ack_fall ; //写响应信号下降沿
wire rd_ack_fall ; //读相应信号下降沿
wire [9:0] wr_fifo_num ; //写fifo中的数据量
//reg define
reg wr_ack_dly ; //写响应打拍
reg rd_ack_dly ; //读响应打拍
//wr_ack_dly: 写响应信号打拍
always @(posedge sys_clk or negedge sys_rst_n) begin : proc_
if(~sys_rst_n) begin
wr_ack_dly <= 1'b0 ;
end
else begin
wr_ack_dly <= sdram_wr_ack ;
end
end
//rd_ack_dly:读响应信号打拍
always @(posedge sys_clk or negedge sys_rst_n) begin : proc_rd_ack_dly
if(~sys_rst_n) begin
rd_ack_dly <= 1'b0 ;
end
else begin
rd_ack_dly <= sdram_rd_ack ;
end
end
//wr_ack_fall,rd_ack_fall:检测读写响应信号下降沿
assign wr_ack_fall = (wr_ack_dly & ~sdram_wr_ack);
assign rd_ack_fall = (rd_ack_dly & ~sdram_rd_ack);
//sdram_wr_addr :sdram写地址
always @(posedge sys_clk or negedge sys_rst_n) begin : proc_sdram_wr_addr
if(~sys_rst_n) begin
sdram_wr_addr <= 24'd0 ;
end
else if(wr_rst == 1'b1) begin
sdram_wr_addr <= sdram_wr_b_addr ;
end
else if(wr_ack_fall == 1'b1 ) //一次突发写结束,更改写地址
begin
if(sdram_wr_addr < (sdram_wr_e_addr - wr_burst_len)) //不使用乒乓操作,一次突发写结束,更改写地址,未达到末地址,写地址累加
sdram_wr_addr <= sdram_wr_addr + wr_burst_len ;
else //不使用乒乓操作,到达末地址,回到写起始地址
sdram_wr_addr <= sdram_wr_b_addr ;
end
end
//sdram_rd_addr:sdram读地址
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
sdram_rd_addr <= 24'd0;
else if(rd_rst == 1'b1)
sdram_rd_addr <= sdram_rd_b_addr;
else if(rd_ack_fall == 1'b1) //一次突发读结束,更改读地址
begin
if(sdram_rd_addr < (sdram_rd_e_addr - rd_burst_len))
//读地址未达到末地址,读地址累加
sdram_rd_addr <= sdram_rd_addr + rd_burst_len;
else //到达末地址,回到首地址
sdram_rd_addr <= sdram_rd_b_addr;
end
//sdram_wr_req,sdram_rd_req:读写请求信号
always @(posedge sys_clk or negedge sys_rst_n) begin
if(~sys_rst_n) begin
sdram_rd_req <= 1'b0 ;
sdram_wr_req <= 1'b0 ;
end
else if (init_end == 1'b1 )begin //初始化完成,响应读写请求
//优先执行写操作,防止写入SDRAM中的数据丢失
if(wr_fifo_num >= wr_burst_len) begin //写FIFO中的数据量达到写突发长度,数据送出
sdram_wr_req <= 1'b1 ; //写请求有效,输出到仲裁机,仲裁机判断后输出写使能到写模块,写模块输出
sdram_rd_req <= 1'b0 ;
end
else if((rd_fifo_num < rd_burst_len ) && (read_valid == 1'b1 )) begin//读FIFO中的数据量小于读突发长度,且读使能信号有效
sdram_wr_req <= 1'b0 ;
sdram_rd_req <= 1'b1 ;
end
else begin
sdram_rd_req <= 1'b0 ;
sdram_wr_req <= 1'b0 ;
end
end
else begin
sdram_rd_req <= 1'b0 ;
sdram_wr_req <= 1'b0 ;
end
end
//读写fifo例化
//------------- wr_fifo_data -------------
/*
fifo_data wr_fifo_data(
//用户接口
.wrclk (wr_fifo_wr_clk ), //写时钟
.wrreq (wr_fifo_wr_req ), //写请求
.data (wr_fifo_wr_data), //写数据
//SDRAM接口
.rdclk (sys_clk ), //读时钟
.rdreq (sdram_wr_ack ), //读请求
.q (sdram_data_in ), //读数据
.rdusedw (wr_fifo_num ), //FIFO中的数据量,读时钟域的指针
.wrusedw ( ),
.aclr (~sys_rst_n || wr_rst) //清零信号
);
*/
//------------- rd_fifo_data -------------
/*
fifo_data rd_fifo_data(
//sdram接口
.wrclk (sys_clk ), //写时钟
.wrreq (sdram_rd_ack ), //写请求
.data (sdram_data_out ), //写数据
//用户接口
.rdclk (rd_fifo_rd_clk ), //读时钟
.rdreq (rd_fifo_rd_req ), //读请求
.q (rd_fifo_rd_data), //读数据
.rdusedw ( ),
.wrusedw (rd_fifo_num ), //FIFO中的数据量
.aclr (~sys_rst_n || rd_rst) //清零信号
);
*/
//写fifo例化
FIFO_async #(
.FIFO_data_size(16),
.FIFO_addr_size(10)
) inst_FIFO_async_wr (
.clk_w (wr_fifo_wr_clk ), //写时钟
.rst_w (~sys_rst_n || wr_rst ), //写复位
.w_en (wr_fifo_wr_req ), //写使能 / 写请求
.clk_r (sys_clk ), //读时钟
.rst_r (~sys_rst_n || wr_rst ), //读复位
.r_en (sdram_wr_ack ), //读使能 / 读请求
.data_in (wr_fifo_wr_data ), //写数据
.data_out (sdram_data_in ), //读数据
.empty ( ), //空信号
.full ( ), //满信号
.wrusedw ( ), //写指针
.rdusedw ( wr_fifo_num ) //读指针
);
//读fifo例化
FIFO_async #(
.FIFO_data_size(16),
.FIFO_addr_size(10)
) inst_FIFO_async_rd (
.clk_w (sys_clk ), //写时钟
.rst_w (~sys_rst_n || rd_rst ), //写复位
.w_en (sdram_rd_ack ), //写使能 / 写请求
.clk_r (rd_fifo_rd_clk ), //读时钟
.rst_r (~sys_rst_n || wr_rst ), //读复位
.r_en (rd_fifo_rd_req ), //读使能 / 读请求
.data_in (sdram_data_out ), //写数据
.data_out (rd_fifo_rd_data ), //读数据
.empty ( ), //空信号
.full ( ), //满信号
.wrusedw ( ), //写指针
.rdusedw ( rd_fifo_num ) //读指针
);
endmodule
异步FIFO模块
查看另一篇博客链接:
SDRAM控制器顶层模块
模块图:
Verilog代码:
// -----------------------------------------------------------------------------
// Copyright (c) 2014-2022 All rights reserved
// -----------------------------------------------------------------------------
// File : sdram_top.v
// Create : 2022-06-09 20:41:48
// Revise : 2022-06-10 15:24:19
// Verdion:
// Description:
// -----------------------------------------------------------------------------
`timescale 1ps/1ps
module sdram_top (
//system signals
input wire sys_clk , //系统时钟,167MHZ
input wire clk_out , //相位偏移时钟,传给SDRAM
input wire sys_rst_n , //系统复位信号,低电平有效
//写FIFO信号
input wire wr_fifo_wr_clk , //写FIFO写时钟
input wire wr_fifo_wr_req , //写FIFO写请求
input wire [15:0] wr_fifo_wr_data , //写FIFO写数据 /存入SDRAM的数据
input wire [23:0] sdram_wr_b_addr , //写SDRAM的首地址
input wire [23:0] sdram_wr_e_addr , //写SDRAM的末地址
input wire [9:0] wr_burst_len , //写突发长度
input wire wr_rst , //写复位 /清零
//读FIFO信号 //
input wire rd_fifo_rd_clk , //读FIFO读时钟
input wire rd_fifo_rd_req , //读FIFO读请求
input wire [23:0] sdram_rd_b_addr , //读SDRAM的首地址
input wire [23:0] sdram_rd_e_addr , //读SDRAM的末地址
input wire [9:0] rd_burst_len , //读突发长度
input wire rd_rst , //读复位
output wire [15:0] rd_fifo_rd_data , //读FIFO的读数据 / 读出SDRAM的数据
output wire [9:0] rd_fifo_num , //读fifo中的数据量 /读FIFO中写入的数量
//
input wire read_valid , //SDRAM读使能
output wire init_end , //SDRAM的初始化结束信号
//SDRAM接口信号
output wire sdram_clk , //SDRAM芯片时钟
output wire sdram_cke , //SDRAM时钟有效信号
output wire sdram_cs_n , //SDRAM片选信号
output wire sdram_ras_n , //SDRAM行选通信号
output wire sdram_cas_n , //SDRAM列选通信号
output wire sdram_we_n , //SDRAM写使能 低电平写 高电平读
output wire [1:0] sdram_ba , //SDRAM的bank地址
output wire [12:0] sdram_addr , //SDRAM的地址总线
output wire [1:0] sdram_dqm , //SDRAM的数据掩码
inout wire [15:0] sdram_dq //SDRAM的数据总线
);
//********************************************************************//
//****************** Parameter and Internal Signal *******************//
//********************************************************************//
//wire define
//wire define
wire sdram_wr_req ; //sdram 写请求
wire sdram_wr_ack ; //sdram 写响应
wire [23:0] sdram_wr_addr ; //sdram 写地址
wire [15:0] sdram_data_in ; //写入sdram中的数据
wire sdram_rd_req ; //sdram 读请求
wire sdram_rd_ack ; //sdram 读响应
wire [23:0] sdram_rd_addr ; //sdram 读地址
wire [15:0] sdram_data_out ; //从sdram中读出的数据
//sdram_clk:SDRAM芯片时钟
assign sdram_clk = clk_out;
//sdram_dqm:SDRAM数据掩码
assign sdram_dqm = 2'b00;
//模块例化
sdram_ctrl sdram_ctrl_inst
(
.sys_clk (sys_clk ), //系统时钟,167MHZ
.sys_rst_n (sys_rst_n ), //系统复位信号,低电平有效
.init_end (init_end ), //SDRAM初始化完成标志
//写FIFO模块
.sdram_wr_req (sdram_wr_req ), //写SDRAM请求信号
.sdram_wr_addr (sdram_wr_addr ), //SDRAM写操作的地址
.sdram_data_in (sdram_data_in ), //写SDRAM的数据
.wr_burst_len (wr_burst_len ), //写入SDRAM的突发长度
.sdram_wr_ack (sdram_wr_ack ), //写SDRAM响应信号
//读FIFO模块
.sdram_rd_req (sdram_rd_req ), //读SDRAM请求信号
.sdram_rd_addr (sdram_rd_addr ), //SDRAM读操作的地址
.rd_burst_len (rd_burst_len ), //读sdram时数据突发长度
.sdram_data_out (sdram_data_out ), //从SDRAM读出的数据
.sdram_rd_ack (sdram_rd_ack ), //读SDRAM响应信号
//SDRAM接口
.sdram_cke (sdram_cke ), //SDRAM 时钟有效信号
.sdram_cs_n (sdram_cs_n ), //SDRAM 片选信号
.sdram_ras_n (sdram_ras_n ), //SDRAM 行地址选通
.sdram_cas_n (sdram_cas_n ), //SDRAM 列地址选通
.sdram_we_n (sdram_we_n ), //SDRAM 写使能
.sdram_ba (sdram_ba ), //SDRAM Bank地址
.sdram_addr (sdram_addr ), //SDRAM 地址总线
.sdram_dq (sdram_dq ) //SDRAM 数据总线
);
fifo_ctrl fifo_ctrl_inst
(
.sys_clk (sys_clk ), //系统时钟,167MHZ
.sys_rst_n (sys_rst_n ), //系统复位信号,低电平有效
//写FIFO接口
.wr_fifo_wr_clk (wr_fifo_wr_clk ), //写fifo写时钟
.wr_fifo_wr_req (wr_fifo_wr_req ), //写fifo写请求
.wr_fifo_wr_data (wr_fifo_wr_data ), //写fifo写数据
.sdram_wr_b_addr (sdram_wr_b_addr ), //写SDRAM的首地址
.sdram_wr_e_addr (sdram_wr_e_addr ), //写SDRAM的末地址
.wr_burst_len (wr_burst_len ), //写SDRAM的突发长度
.wr_rst (wr_rst ), //写复位信号,写fifo清零
//读FIFO接口
.rd_fifo_rd_clk (rd_fifo_rd_clk ), //读fifo读时钟
.rd_fifo_rd_req (rd_fifo_rd_req ), //读fifo读请求
.sdram_rd_b_addr (sdram_rd_b_addr ), //读SDRAM的首地址
.sdram_rd_e_addr (sdram_rd_e_addr ), //读SDRAM的末地址
.rd_burst_len (rd_burst_len ), //读SDRAM的突发长度
.rd_rst (rd_rst ), //读复位信号,读fifo清零
.rd_fifo_rd_data (rd_fifo_rd_data ), //读fifo读数据
.rd_fifo_num (rd_fifo_num ), //读FIFO中的数据量
.read_valid (read_valid ), //SDRAM读使能
.init_end (init_end ), //SDRAM初始化结束信号
//SDRAM接口
.sdram_wr_ack (sdram_wr_ack ), //SDRAM写响应
.sdram_wr_req (sdram_wr_req ), //SDRAM写请求
.sdram_wr_addr (sdram_wr_addr ), //SDRAM写地址
.sdram_data_in (sdram_data_in ), //写入SDRAM的数据
.sdram_rd_ack (sdram_rd_ack ), //SDRAM读响应
.sdram_data_out (sdram_data_out ), //SDRAM读出的数据
.sdram_rd_req (sdram_rd_req ), //SDRAM读请求
.sdram_rd_addr (sdram_rd_addr ) //SDRAM读地址
);
endmodule
Modelsim前仿
对所有代码进行编译仿真
Quartus综合结果
使用Quartus (Quartus Prime 17.1) Standard Edition对RTL进行综合,对综合后的资源占用和电路图进行检查。
注意:在AC620综合时,FIFO采用IP核
RTL图
顶层模块
FIFO控制
SDRAM控制
Timing Slack
关键路径在于对FIFO读写端数据存量的判断
在FPGA综合时涉及到fifo内数据量的判断,从而实现突发读写,这里判断条件要采用相同时钟域下的信号,否则会出现timing error。在ac620下板验证时,速度100Mhz、133Mhz和166M均满足。
避免出现跨时钟域采样,以免造成时序不满足。
VCS仿真&DC综合结果
VCS仿真结果
DC综合结果
面积报告:
功耗报告:
layout window (standard cell):
Bug&Debug
在modelsim仿真&VCS仿真中,部分code存在风格问题,例如在两个always块中对同一信号进行赋值。在综合阶段会出现报错,需要进行修改。
这里将错误的部分列举出来:
1. sdram_aref 中的 cnt_clk
2. sdram_read 中的cnt_clk
3. sdram_write中的cnt_clk
此外,还需要额外注意SDRAM芯片在不同工作频率时对应的CL latency值
后续会上传bug版本和debug版本代码
总结
- 至此,整个入门级工程项目完成,从完成度和难度来讲,这个项目更偏向于工程实现。SDRAM控制器虽然看上去简单,但是通过这个小项目可以更好的培养工程文件的管理、项目实践习惯以及verilog综合语句理解。希望大家都能培养一个良好的工程习惯和coding style。
- SDRAM控制器是用于管理SDRAM存储器的硬件模块,其主要任务是协调CPU和SDRAM之间的数据传输。由于SDRAM存储器的特殊性质,SDRAM控制器需要具有复杂的时序控制和缓存管理功能,以确保数据的正确性和存取效率。
- Verilog综合是将Verilog代码转换为门级电路网络的过程,它可以生成与目标FPGA或ASIC芯片兼容的二进制文件。在SDRAM控制器的设计中,Verilog综合对于实现快速、准确、高效的控制器至关重要。因此,在进行SDRAM控制器设计时,必须考虑到Verilog综合的影响
注意以下问题:文章来源:https://www.toymoban.com/news/detail-534182.html
- Verilog代码的规范性:Verilog代码必须符合语言规范和硬件设计规范,以确保能够被正确地综合成目标电路。
- 时序约束的设置:时序约束是指定义逻辑操作的最小和最大时间限制,以确保电路能够在所需的时间内完成操作。在SDRAM控制器设计中,时序约束的设置非常重要,因为SDRAM存储器需要严格控制时序以确保数据的完整性。STA是数字IC设计中相当重要的组成部分。
- 确定优化策略:Verilog综合工具通常会提供一系列优化选项,以便在综合过程中最大限度地减小资源使用和功耗消耗。在SDRAM控制器设计中,需要根据具体情况选择合适的优化策略。
SDRAM控制器是一个对新手而言相对复杂的硬件模块,Verilog综合对于实现高效、准确、可靠的控制器至关重要。在进行SDRAM控制器设计时,必须考虑到Verilog综合工具的影响,并严格遵守规范和约束条件,以确保电路能够正确地综合成目标电路。文章来源地址https://www.toymoban.com/news/detail-534182.html
到了这里,关于数字IC实践项目(2)——高速SDRAM控制器的设计与综合(入门级工程项目)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!