添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

数字IC实践项目(2)—高速SDRAM控制器的设计与综合(入门级工程项目)

写在前面的话

这个实践项目来源于研究生电子设计竞赛,在涉及到视频图像处理时需要用到DRAM存储数据 ;整个项目过程中先后学习了 小梅哥(AC620开发板资料) 开源骚客SDRAM控制器 正点原子FPGA教程 野火FPGA开发教程 等网络资料。

在此对上述提供学习资料的前辈表示真诚的感谢。

在整个工程项目中共涉及到多款SDRAM芯片手册,其分别是:

  1. 美光的SDR SDRAM MT48LC64M4A2,数据手册较为完全,RTL仿真采用的芯片模型也是来源于美光;
  2. 华邦电子的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个模块:

  1. 模式寄存器
  2. 命令解码器
  3. 控制逻辑
  4. 地址寄存器
  5. 刷新计数器
  6. 行/列地址计数锁存器
  7. bank控制逻辑
  8. 列解码器

SDRAM管脚说明:
在这里插入图片描述
sdram管脚说明如下表所示。
在这里插入图片描述

SDRAM控制器简介

  1. SDRAM控制器是一种用于控制同步动态随机存储器(SDRAM)的硬件设备。它能够实现高速、可靠的内存读写操作,并经常用于计算机、通讯系统和其他嵌入式应用中。SDRAM控制器有着复杂的内部工作机制,通过其内部的逻辑电路,实现对SDRAM存储器芯片的精细控制。

  2. 一个SDRAM控制器通常包含读写控制器、地址发生器、时钟控制器和数据缓冲等组件。它能够识别读取和写入命令,并控制存储器的读取和写入操作的执行时间。在操作过程中,SDRAM控制器先将外部指令和数据传输到数据缓冲区,然后再基于内部时钟更新SDRAM存储器中的数据。此外,SDRAM控制器还通过检测数据总线的情况,实现对数据的检查和纠错,以提高内存数据的可靠性。

  3. 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;
	else if(cnt_200us == cnt_pow) begin
		 cnt_200us	<= 	0 	;
		 pow_end	<=	1 	;
	else	begin
		cnt_200us	<=	cnt_200us + 1'b1	;
		pow_end		<=	0 					;
//cnt_clk:时钟周期计数,记录初始化各状态的等待时间
always @(posedge sys_clk or negedge sys_rst_n) begin 
	if(~sys_rst_n) begin
		cnt_clk <= 0 	;
	else if(pow_end == 1 || pre_end == 1 || aref_end == 1 ) begin
		 cnt_clk <=	0  	;
		cnt_clk 	<=	cnt_clk + 1;
//cnt_init_aref:初始化阶段的刷新次数
always @(posedge sys_clk or negedge sys_rst_n) begin 
	if(~sys_rst_n) begin
		 cnt_init_aref 		<= 	0 	;
	else if(init_state_cs == INIT_IDLE) begin		//这里为什么设置两次清零
		 cnt_init_aref 		<= 	0 	;
	else if (init_state_cs == INIT_AREF) begin
		cnt_init_aref		<=	cnt_init_aref	+ 1'b1	;
		cnt_init_aref 		<=	cnt_init_aref			;
//预充电结束标志
//pre_end
always@(*)	begin
	if(init_state_cs == INIT_TRP && cnt_clk == cnt_rp)
				pre_end 		= 			1 	;
				pre_end			=			0 	;
//刷新结束标志
//aref_end
always@(*)	begin
	if(init_state_cs == INIT_TRFC && cnt_clk == cnt_rfc)
				aref_end 		= 			1 	;
				aref_end			=			0 	;
//模式寄存器结束标志
//mrd_end
always@(*)	begin
	if(init_state_cs == INIT_TMRD && cnt_clk == cnt_mrd)
				mrd_end	 	 		= 			1 	;
				mrd_end				=			0 	;
//初始化状态机 三段式
//同步时序描述状态转移
always @(posedge sys_clk or negedge sys_rst_n) begin 
	if(~sys_rst_n) begin
		 init_state_cs <= INIT_IDLE;
	else begin
		 init_state_cs <= init_state_ns ;
//组合逻辑描述状态转移条件
always@(*) begin
	case(init_state_cs)
		INIT_IDLE	:
						if(pow_end == 1)
							init_state_ns	=	INIT_PRE	;
							init_state_ns	=	INIT_IDLE	;
		INIT_PRE	:
							init_state_ns	=	INIT_TRP	;
		INIT_TRP	:
						if(pre_end == 1)
							init_state_ns	=	INIT_AREF	;
							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	;
									init_state_ns   = 	INIT_AREF 	;
							init_state_ns	=	INIT_TRFC	;
		INIT_LMR 	: 		init_state_ns	=	INIT_TMRD 	;
		INIT_TMRD	:
						if(mrd_end == 1)
							init_state_ns	=	INIT_END	;
							init_state_ns	=	INIT_TMRD	;
		INIT_END	:
							init_state_ns   =   INIT_IDLE	;
		default:
							init_state_ns 	= 	INIT_IDLE	;
	endcase // init_state_cs
//时序逻辑描述状态输出
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 	;	
	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		;
			INIT_PRE	: begin
						  	init_cmd	 <= PRE				;
			     			init_ba		 <= 2'b11			;
			     			init_addr 	 <= 13'h1fff		;
			INIT_AREF 	:	begin
							init_cmd	 <= AREF			;
			     			init_ba		 <= 2'b11			;
			     			init_addr 	 <= 13'h1fff		;
			INIT_LMR	: 	begin
							init_cmd	 <= LMR				;
			     			init_ba		 <= 2'b00			;	//这里11和00有什么区别吗
			     			init_addr 	 <= init_lmrset		;
			INIT_END	: begin
		 		 			init_cmd	 <= NOP				;
			     			init_ba		 <= 2'b11			;
			     			init_addr 	 <= 13'h1fff		;
			     			init_end	 <=	1'b1 			;
		 	default : /* default */begin
		 					init_cmd	 <= NOP				;
			     			init_ba		 <= 2'b11			;
			     			init_addr 	 <= 13'h1fff		;
		 endcase

端口描述:

名称备注
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_RP18ns预充电等待时间
T_RFC66ns自动刷新周期
刷新时间64ms/8192 = 7.8125us,取7.5us

(2)6个状态,SDRAM自动刷新模块涉及6个状态,用于描述状态机:

名称编码备注
AREF_IDLE3’b000初始状态,等待自动刷新使能
AREF_PRE3’b001预充电状态
AREF_TRP3’b011预充电等待状态
AREF_AREF3’b010自动刷新状态
AREF_TRFC3’b110自动刷新等待状态
AREF_END3’b111自动刷新结束状态

(3)3个命令,SDRAM自动刷新模块涉及3个操作命令,这里需要查看芯片手册

名称编码备注快速记忆码(10进制)
NOP4’b0111空操作7
PRE4’b0010预充电2
AREF4’b0001自动刷新1

刷新流程:
72717…17

其中,17至少要一次,对应自动刷新操作。

仿真波形:
在这里插入图片描述
在这里插入图片描述

Verilog代码:

// ----------------------------------------------------------------------------- // 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; 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; 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; case(aref_state) AREF_IDLE: if((aref_en == 1'b1) && (init_end == 1'b1)) aref_state <= AREF_PCHA; aref_state <= aref_state; AREF_PCHA: aref_state <= AREF_TRP; AREF_TRP: if(trp_end == 1'b1) aref_state <= AUTO_REF; 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; aref_state <= AUTO_REF; 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 //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; case(aref_state) AREF_IDLE,AREF_TRP,AREF_TRF: //执行空操作指令 begin aref_cmd <= NOP; aref_ba <= 2'b11; aref_addr <= 13'h1fff; AREF_PCHA: //预充电指令 begin aref_cmd <= P_CHARGE; aref_ba <= 2'b11; aref_addr <= 13'h1fff; AUTO_REF: //自动刷新指令 begin aref_cmd <= A_REF; aref_ba <= 2'b11; aref_addr <= 13'h1fff; AREF_END: //一次自动刷新完成 begin aref_cmd <= NOP; aref_ba <= 2'b11; aref_addr <= 13'h1fff; default: begin aref_cmd <= NOP; aref_ba <= 2'b11; aref_addr <= 13'h1fff; 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_RP18ns预充电等待时间
T_RCD18ns写入激活命令到数据开始读写,中间需要等待的时间
T_data(n-1)*clk_period数据突发时间,按照突发长度设定

(2)8个状态,SDRAM写模块涉及8个状态,用于描述状态机:

名称编码备注
WR_IDLE3’b000写初始状态
WR_ACT3’b001激活状态
WR_TRCD3’b011激活等待状态
WR_WR3’b010写操作状态
WR_DATA3’b110写数据状态
WR_PRE3’b111预充电状态
WR_TRD3’b101预充电等待状态
WR_END3’b100写结束状态

(3)5个命令,SDRAM写模块涉及5个操作命令,这里需要查看芯片手册

名称编码备注快速记忆码(10进制)
ACT4’b0011激活3
NOP4’b0111空操作7
B_STOP4’b0110突发停止6
WR4’b0100SDRAM写4
PRE4’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;
        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;
        case(write_state)
            WR_IDLE:
                if((wr_en ==1'b1) && (init_end == 1'b1))
                        write_state <=  WR_ACTIVE;
                        write_state <=  write_state;
            WR_ACTIVE:
                write_state <=  WR_TRCD;
            WR_TRCD:
                if(trcd_end == 1'b1)
                    write_state <=  WR_WRITE;
                    write_state <=  write_state;
            WR_WRITE:
                write_state <=  WR_DATA;
            WR_DATA:
                if(twrite_end == 1'b1)
                    write_state <=  WR_PRE;
                    write_state <=  write_state;
            WR_PRE:
                write_state <=  WR_TRP;
            WR_TRP:
                if(trp_end == 1'b1)
                    write_state <=  WR_END;
                    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
//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;
        case(write_state)
            WR_IDLE,WR_TRCD,WR_TRP:
                begin
                    write_cmd   <=  NOP;
                    write_ba    <=  2'b11;
                    write_addr  <=  13'h1fff;
            WR_ACTIVE:  //激活指令
                begin
                    write_cmd   <=  ACTIVE;
                    write_ba    <=  wr_addr[23:22];
                    write_addr  <=  wr_addr[21:9];
            WR_WRITE:   //写操作指令
                begin
                    write_cmd   <=  WRITE;
                    write_ba    <=  wr_addr[23:22];
                    write_addr  <=  {4'b0000,wr_addr[8:0]};
            WR_DATA:    //突发传输终止指令
                begin
                    if(twrite_end == 1'b1)
                        write_cmd <=  B_STOP;
                        begin
                            write_cmd   <=  NOP;
                            write_ba    <=  2'b11;
                            write_addr  <=  13'h1fff;
            WR_PRE:     //预充电指令
                begin
                    write_cmd   <= P_CHARGE;
                    write_ba    <= wr_addr[23:22];
                    write_addr  <= 13'h0400;
            WR_END:
                begin
                    write_cmd   <=  NOP;
                    write_ba    <=  2'b11;
                    write_addr  <=  13'h1fff;
            default:
                begin
                    write_cmd   <=  NOP;
                    write_ba    <=  2'b11;
                    write_addr  <=  13'h1fff;
        endcase
//wr_sdram_en:数据总线输出使能
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        wr_sdram_en <=  1'b0;
        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_RP18ns预充电等待时间
T_RCD18ns写入激活命令到数据开始读写,中间需要等待的时间
T_CL2clk潜伏期,读数据才有

(2)9个状态,SDRAM读模块涉及9个状态,用于描述状态机:

名称编码备注
RD_IDLE4’b0000空闲状态
RD_ACT4’b0001激活状态
RD_TRCD4’b0011激活等待状态
RD_RD4’b0010读操作状态
RD_CL4’b0110潜伏期状态
RD_DATA4’b0111读数据状态
RD_PRE4’b0101预充电状态
RD_TRP4’b0100预充电等待状态
RD_END4’b1100读结束状态

(3)5个命令,SDRAM读模块涉及5个操作命令,这里需要查看芯片手册

名称编码备注快速记忆码(10进制)
ACT4’b0011激活3
NOP4’b0111空操作7
B_STOP4’b0110突发停止6
READ4’b0101SDRAM读5
PRE4’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;
        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;
        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;
        case(read_state)
            RD_IDLE:
                if((rd_en ==1'b1) && (init_end == 1'b1))
                        read_state <=  RD_ACTIVE;
                        read_state <=  RD_IDLE;
            RD_ACTIVE:
                read_state <=  RD_TRCD;
            RD_TRCD:
                if(trcd_end == 1'b1)
                    read_state <=  RD_READ;
                    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
//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;
        case(read_state)
            RD_IDLE,RD_TRCD,RD_TRP:
                begin
                    read_cmd    <=  NOP;
                    read_ba     <=  2'b11;
                    read_addr   <=  13'h1fff;
            RD_ACTIVE:  //激活指令
                begin
                    read_cmd    <=  ACTIVE;
                    read_ba     <=  rd_addr[23:22];
                    read_addr   <=  rd_addr[21:9];
            RD_READ:    //读操作指令
                begin
                    read_cmd    <=  READ;
                    read_ba     <=  rd_addr[23:22];
                    read_addr   <=  {4'b0000,rd_addr[8:0]};
            RD_DATA:    //突发传输终止指令
                begin
                    if(rdburst_end == 1'b1)
                        read_cmd <=  B_STOP;
                        begin
                            read_cmd    <=  NOP;
                            read_ba     <=  2'b11;
                            read_addr   <=  13'h1fff;
            RD_PRE:     //预充电指令
                begin
                    read_cmd    <= P_CHARGE;
                    read_ba     <= rd_addr[23:22];
                    read_addr   <= 13'h0400;
            RD_END:
                begin
                    read_cmd    <=  NOP;
                    read_ba     <=  2'b11;
                    read_addr   <=  13'h1fff;
            default:
                begin
                    read_cmd    <=  NOP;
                    read_ba     <=  2'b11;
                    read_addr   <=  13'h1fff;
        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_ckeSDRAM时钟使能信号
sdram_cs_nSDRAM片选信号
sdram_cas_nSDRAM列选通信号
sdram_ras_nSDRAM行选通信号
sdram_we_nSDRAM写使能
sdram_baSDRAM Bank地址
sdram_addrSDRAM地址总线
sdram_dqSDRAM数据总线

工作时序:
在这里插入图片描述
备注:
仲裁机包含五个状态,默认优先级为自动刷新操作>数据写操作>数据读操作;一切都是以保证数据存留为前提。

仿真波形:
在这里插入图片描述

Verilog代码:

// ----------------------------------------------------------------------------- // ----------------------------------------------------------------------------- // 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 ; //组合逻辑,判断跳转 always@(*) begin case(state_cs) IDLE : begin if(init_end == 1'b1) state_ns = ARBIT ; state_ns = IDLE ; 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 ; state_ns = ARBIT ; AREF : begin if(aref_end == 1'b1) state_ns = ARBIT ; state_ns = AREF ; WRITE : begin if(wr_end == 1'b1) state_ns = ARBIT ; state_ns = WRITE ; READ : begin if(rd_end == 1'b1) state_ns = ARBIT ; state_ns = READ ; default : state_ns = IDLE ; endcase //时序逻辑 输出错误,组合逻辑输出,可组合可时序 //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 ; ARBIT : begin sdram_cmd = NOP ; sdram_ba = 2'b11 ; sdram_addr = 13'h1fff ; AREF : begin sdram_cmd = aref_cmd ; sdram_ba = aref_ba ; sdram_addr = aref_addr ; WRITE : begin sdram_cmd = wr_cmd ; sdram_ba = wr_ba ; sdram_addr = wr_addr ; READ : begin sdram_cmd = rd_cmd ; sdram_ba = rd_ba ; sdram_addr = rd_addr ; default : begin sdram_cmd = NOP ; sdram_ba = 2'b11 ; sdram_addr = 13'h1fff ; endcase // state_cs //自动刷新使能 always @(posedge sys_clk or negedge sys_rst_n) begin : proc_ if(~sys_rst_n) begin aref_en <= 1'b0 ; else if ((state_cs == ARBIT) && (aref_req == 1'b1) )begin aref_en <= 1'b1 ; else if(aref_end == 1'b1 ) aref_en <= 1'b0 ; //写数据使能 //wr_en always @(posedge sys_clk or negedge sys_rst_n) begin : proc_wr_en if(~sys_rst_n) begin wr_en <= 1'b0 ; else if((state_cs == ARBIT) && (aref_req == 1'b0) && (wr_req == 1'b1)) begin wr_en <= 1'b1 ; else if(wr_end == 1'b1) wr_en <= 1'b0 ; //读数据使能 //rd_en always @(posedge sys_clk or negedge sys_rst_n) begin : proc_rd_en if(~sys_rst_n) begin rd_en <= 1'b0 ; else if((state_cs == ARBIT) && (aref_req == 1'b0) && (rd_req == 1'b1) )begin rd_en <= 1'b1 ; else if(rd_end == 1'b1) rd_en <= 1'b0 ; //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 ; //片选信号,行地址选通信号,列地址选通信号,写使能信号 assign {sdram_cs_n, sdram_ras_n, sdram_cas_n, sdram_we_n} = sdram_cmd ; endmodule

模块图:
在这里插入图片描述
verlog代码:

// ----------------------------------------------------------------------------- // 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_validSDRAM读使能
init_endSDRAM初始化结束信号
sdram_wr_ackSDRAM写响应
sdram_wr_reqSDRAM写请求
sdram_wr_addrSDRAM写地址
sdram_data_in写入SDRAM的数据
sdram_data_outSDRAM读出的数据
sdram_rd_reqSDRAM读请求
sdram_rd_addrSDRAM读地址

Verilog代码:

// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------
// 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				;
	else begin
		 wr_ack_dly 	<= 	sdram_wr_ack	;
//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  	;
	else begin
		rd_ack_dly  <= 	 	sdram_rd_ack 	;
//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		;
	else if(wr_rst == 1'b1) begin
		sdram_wr_addr  <= sdram_wr_b_addr ;
	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 			;
//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;
//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 		;
	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 	;
		 	else if((rd_fifo_num < rd_burst_len ) && (read_valid == 1'b1 ))		begin//读FIFO中的数据量小于读突发长度,且读使能信号有效
		 		sdram_wr_req 		<= 		1'b0 	;	
				sdram_rd_req 		<= 		1'b1 	;
		 	else 	begin
		 		sdram_rd_req 	 	<= 	1'b0		;
				sdram_wr_req 		<= 	1'b0 		;
	else 	begin
		 		sdram_rd_req 	 	<= 	1'b0		;
				sdram_wr_req 		<= 	1'b0 		;
//读写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					)		//读指针

Verilog代码:

// ----------------------------------------------------------------------------- // ----------------------------------------------------------------------------- // 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; .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读地址

对所有代码进行编译仿真
在这里插入图片描述

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综合的影响

注意以下问题:

  1. Verilog代码的规范性:Verilog代码必须符合语言规范和硬件设计规范,以确保能够被正确地综合成目标电路。
  2. 时序约束的设置:时序约束是指定义逻辑操作的最小和最大时间限制,以确保电路能够在所需的时间内完成操作。在SDRAM控制器设计中,时序约束的设置非常重要,因为SDRAM存储器需要严格控制时序以确保数据的完整性。STA是数字IC设计中相当重要的组成部分。
  3. 确定优化策略:Verilog综合工具通常会提供一系列优化选项,以便在综合过程中最大限度地减小资源使用和功耗消耗。在SDRAM控制器设计中,需要根据具体情况选择合适的优化策略。

SDRAM控制器是一个对新手而言相对复杂的硬件模块,Verilog综合对于实现高效、准确、可靠的控制器至关重要。在进行SDRAM控制器设计时,必须考虑到Verilog综合工具的影响,并严格遵守规范和约束条件,以确保电路能够正确地综合成目标电路。

这个实践项目来源于研究生电子设计竞赛,在涉及到视频图像处理时需要用到DRAM存储数据 ;整个项目过程中先后学习了小梅哥(AC620开发板资料)、开源骚客SDRAM控制器、正点原子FPGA教程、野火FPGA开发教程等网络资料。在此对上述提供学习资料的前辈表示真诚的感谢。
一个优秀的Layout,一块好的板子,并不是随便布线连同就可以实现电路要求的,凡事都得谨慎,此处别处摘要,讲述SDRAM高速器件布线规则: 如果你没有信号完整性的知识和对传输线的认识,恐怕你很难看懂,如果你看不懂,那么请按这样一个通用的基本法则做: (1)DDR和主控芯片尽量靠近 (2)高速约束中设置所有信号、时钟线等长(最多允许50mils的冗余),所有信号、时钟线长度不超过1000mils 数字IC设计工程师编写RTL代码,实现具体功能。 4.前仿真(功能仿真) 数字IC验证工程师对设计进行验证。前仿默认为理想状态,不会有延迟等问题,主要验证模块逻辑功能的正确性,所以又叫功能仿真。编译跑仿真的EDA工具有VCS,Qustasim等,debug的工具一般是Verdi。 5.Lint和CDC 数字IC工程师写约束文件,对RTL代
正确的同步静态随机存取存储器(SRAM)的选择对于带宽要求更高,系统性能更好的网络应用至关重要。系统设计人员需要了解不同同步SRAM技术的特性和优势,以便为其应用选择正确的存储器。 决定正确的同步SRAM选择的一些关键因素是密度,等待时间,速度,读/写比和功率。通过了解这些因素如何影响性能,可靠性和成本,设计人员可以为其应用选择最佳的同步SRAM。宇芯电子专注代理销售SRAM,异步SRAM,同步SRAM,PSRAM等存储芯片,提供产品技术支持及解决方案。 同步高速SRAM有多种形式,具有不同的性能特征和优势