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

article/2025/10/26 11:58:41

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

  • 写在前面的话
  • 项目简介和学习目的
  • SDRAM简介
  • SDRAM控制器简介
  • 完整项目框图
  • SDRAM控制器项目框图
    • SDRAM初始化模块
    • SDRAM自动刷新模块
    • SDRAM写模块
    • SDRAM读模块
    • SDRAM仲裁机
    • SDRAM控制模块
    • FIFO控制模块
    • 异步FIFO模块
    • SDRAM控制器顶层模块
  • Modelsim前仿
  • Quartus综合结果
  • VCS仿真&DC综合结果
    • VCS仿真结果
    • DC综合结果
  • Bug&Debug
  • 总结

写在前面的话

这个实践项目来源于研究生电子设计竞赛,在涉及到视频图像处理时需要用到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_cmdSDRAM初始化阶段指令信号
init_addrSDRAM初始化阶段地址总线
init_baSDRAM初始化阶段Bank地址
init_endSDRAM初始化结束标志

工作时序:
在这里插入图片描述
备注:
在对SDRAM进行操作前,需要先完成初始化。上电和初始化需要按照预先定义的方式完成。
需要注意的地方:
(1)4个时间,SDRAM初始化模块涉及4个关键时间:

名称时间备注
powup_T200us上电等待时间,最小100us
T_RP18ns预充电等待时间
T_RFC66ns自动刷新周期
T_MRD2tck加载模式寄存器需要的时间

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

名称编码备注
INIT_IDLE3’b000初始状态
INIT_PRE3’b001预充电状态
INIT_TRP3’b011预充电等待状态
INIT_AREF3’b010自动刷新状态
INIT_TRFC3’b110自动刷新等待状态
INIT_LMR3’b111模式寄存器设置状态
INIT_TMRD3’b101模式寄存器设置等待状态
INIT_END3’b100初始化结束状态

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

名称编码备注快速记忆码(10进制)
NOP4’b0111空操作7
PRE4’b0010预充电2
AREF4’b0001自动刷新1
LMR4’b0000设置模式寄存器0

初始化流程:
72717…1707

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

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

Verilog代码:

`timescale 1ps/1ps
module sdram_init (input 					sys_clk		,    	//	系统时钟   167MHZ,period = 5.98nsinput 					sys_rst_n	,  		// 	系统复位信号	output	reg	[3:0]		init_cmd	,		//	初始化命令 cs_n ras_n cas_n we_noutput 	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或3BR_T	=	 4'b0110		;		//突发终止命令//计数器localparam		cnt_pow		=		'd33445	,	 	//200MHZ				'd40000		,		//200uscnt_rp 		=		'd4 	, 		//200MHZ				'd4			,		//20nscnt_rfc 	=		'd12	, 		//200MHZ				'd14		,		//70nscnt_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		,		//模式寄存器设置等待状态	tmrdINIT_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 definereg 	[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) begincnt_200us	<= 0;pow_end	<= 0;end else if(cnt_200us == cnt_pow) begincnt_200us	<= 	0 	;pow_end	<=	1 	;endelse	begincnt_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) begincnt_clk <= 0 	;end else if(pow_end == 1 || pre_end == 1 || aref_end == 1 ) begincnt_clk <=	0  	;endelsecnt_clk 	<=	cnt_clk + 1;
end//cnt_init_aref:初始化阶段的刷新次数
always @(posedge sys_clk or negedge sys_rst_n) begin if(~sys_rst_n) begincnt_init_aref 		<= 	0 	;end else if(init_state_cs == INIT_IDLE) begin		//这里为什么设置两次清零cnt_init_aref 		<= 	0 	;endelse if (init_state_cs == INIT_AREF) begincnt_init_aref		<=	cnt_init_aref	+ 1'b1	;endelsecnt_init_aref 		<=	cnt_init_aref			;
end//预充电结束标志
//pre_end
always@(*)	beginif(init_state_cs == INIT_TRP && cnt_clk == cnt_rp)pre_end 		= 			1 	;elsepre_end			=			0 	;
end//刷新结束标志
//aref_end
always@(*)	beginif(init_state_cs == INIT_TRFC && cnt_clk == cnt_rfc)aref_end 		= 			1 	;elsearef_end			=			0 	;
end//模式寄存器结束标志
//mrd_end
always@(*)	beginif(init_state_cs == INIT_TMRD && cnt_clk == cnt_mrd)mrd_end	 	 		= 			1 	;elsemrd_end				=			0 	;
end//初始化状态机 三段式
//同步时序描述状态转移
always @(posedge sys_clk or negedge sys_rst_n) begin if(~sys_rst_n) begininit_state_cs <= INIT_IDLE;end else begininit_state_cs <= init_state_ns ;end
end//组合逻辑描述状态转移条件
always@(*) begincase(init_state_cs)INIT_IDLE	:if(pow_end == 1)init_state_ns	=	INIT_PRE	;elseinit_state_ns	=	INIT_IDLE	;INIT_PRE	:init_state_ns	=	INIT_TRP	;INIT_TRP	:if(pre_end == 1)init_state_ns	=	INIT_AREF	;elseinit_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	;elseinit_state_ns   = 	INIT_AREF 	;elseinit_state_ns	=	INIT_TRFC	;INIT_LMR 	: 		init_state_ns	=	INIT_TMRD 	;INIT_TMRD	:if(mrd_end == 1)init_state_ns	=	INIT_END	;elseinit_state_ns	=	INIT_TMRD	;INIT_END	:init_state_ns   =   INIT_IDLE	;default:init_state_ns 	= 	INIT_IDLE	;endcase // init_state_csend//时序逻辑描述状态输出
always @(posedge sys_clk or negedge sys_rst_n) begin if(~sys_rst_n) begininit_cmd	 <= NOP				;init_ba		 <= 2'b11			;init_addr 	 <= 13'h1fff		;init_end 	<= 1'b0 	;	end else begincase (init_state_cs)INIT_IDLE,INIT_TRP,INIT_TRFC,INIT_TMRD: begininit_cmd	 <= NOP				;init_ba		 <= 2'b11			;init_addr 	 <= 13'h1fff		;endINIT_PRE	: begininit_cmd	 <= PRE				;init_ba		 <= 2'b11			;init_addr 	 <= 13'h1fff		;endINIT_AREF 	:	begininit_cmd	 <= AREF			;init_ba		 <= 2'b11			;init_addr 	 <= 13'h1fff		;end			INIT_LMR	: 	begininit_cmd	 <= LMR				;init_ba		 <= 2'b00			;	//这里11和00有什么区别吗init_addr 	 <= init_lmrset		;endINIT_END	: begininit_cmd	 <= NOP				;init_ba		 <= 2'b11			;init_addr 	 <= 13'h1fff		;init_end	 <=	1'b1 			;enddefault : /* default */begininit_cmd	 <= NOP				;init_ba		 <= 2'b11			;init_addr 	 <= 13'h1fff		;endendcaseend
endendmodule 

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_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代码:


// -----------------------------------------------------------------------------
// Copyright (c) 2014-2022 All rights reserved// File   : sdram_aref.v
// Create : 2022-07-04 09:20:46
// -----------------------------------------------------------------------------`timescale  1ns/1nsmodule  sdram_aref
(input   wire            sys_clk     ,   //系统时钟,频率166MHzinput   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      ,   //预充电等待          tRPAUTO_REF    =   3'b010      ,   //自动刷新状态AREF_TRF    =   3'b100      ,   //自动刷新等待        tRCAREF_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;elsecnt_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;elsecnt_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;elsecase(aref_state)AREF_IDLE:if((aref_en == 1'b1) && (init_end == 1'b1))aref_state  <=  AREF_PCHA;elsearef_state  <=  aref_state;AREF_PCHA:aref_state  <=  AREF_TRP;AREF_TRP:if(trp_end == 1'b1)aref_state  <=  AUTO_REF;elsearef_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;elsearef_state  <=  AUTO_REF;elsearef_state  <=  aref_state;AREF_END:aref_state  <=  AREF_IDLE;default:aref_state  <=  AREF_IDLE;endcase//cnt_clk_rst:时钟周期计数复位标志
always@(*)begincase (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;endcaseend//SDRAM操作指令控制
always@(posedge sys_clk or negedge sys_rst_n)if(sys_rst_n == 1'b0)beginaref_cmd    <=  NOP;aref_ba     <=  2'b11;aref_addr   <=  13'h1fff;endelsecase(aref_state)AREF_IDLE,AREF_TRP,AREF_TRF:    //执行空操作指令beginaref_cmd    <=  NOP;aref_ba     <=  2'b11;aref_addr   <=  13'h1fff;endAREF_PCHA:  //预充电指令beginaref_cmd    <=  P_CHARGE;aref_ba     <=  2'b11;aref_addr   <=  13'h1fff;end AUTO_REF:   //自动刷新指令beginaref_cmd    <=  A_REF;aref_ba     <=  2'b11;aref_addr   <=  13'h1fff;endAREF_END:   //一次自动刷新完成beginaref_cmd    <=  NOP;aref_ba     <=  2'b11;aref_addr   <=  13'h1fff;end    default:beginaref_cmd    <=  NOP;aref_ba     <=  2'b11;aref_addr   <=  13'h1fff;end    endcaseendmodule

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         ,   //系统时钟,频率166MHzinput   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;elsecnt_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;elsecase(write_state)WR_IDLE:if((wr_en ==1'b1) && (init_end == 1'b1))write_state <=  WR_ACTIVE;elsewrite_state <=  write_state;WR_ACTIVE:write_state <=  WR_TRCD;WR_TRCD:if(trcd_end == 1'b1)write_state <=  WR_WRITE;elsewrite_state <=  write_state;WR_WRITE:write_state <=  WR_DATA;WR_DATA:if(twrite_end == 1'b1)write_state <=  WR_PRE;elsewrite_state <=  write_state;WR_PRE:write_state <=  WR_TRP;WR_TRP:if(trp_end == 1'b1)write_state <=  WR_END;elsewrite_state <=  write_state;WR_END:write_state <=  WR_IDLE;default:write_state <=  WR_IDLE;endcase//计数器控制逻辑
always@(*)begincase(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;endcaseend//SDRAM操作指令控制
always@(posedge sys_clk or negedge sys_rst_n)if(sys_rst_n == 1'b0)beginwrite_cmd   <=  NOP;write_ba    <=  2'b11;write_addr  <=  13'h1fff;endelsecase(write_state)WR_IDLE,WR_TRCD,WR_TRP:beginwrite_cmd   <=  NOP;write_ba    <=  2'b11;write_addr  <=  13'h1fff;endWR_ACTIVE:  //激活指令beginwrite_cmd   <=  ACTIVE;write_ba    <=  wr_addr[23:22];write_addr  <=  wr_addr[21:9];endWR_WRITE:   //写操作指令beginwrite_cmd   <=  WRITE;write_ba    <=  wr_addr[23:22];write_addr  <=  {4'b0000,wr_addr[8:0]};end     WR_DATA:    //突发传输终止指令beginif(twrite_end == 1'b1)write_cmd <=  B_STOP;elsebeginwrite_cmd   <=  NOP;write_ba    <=  2'b11;write_addr  <=  13'h1fff;endendWR_PRE:     //预充电指令beginwrite_cmd   <= P_CHARGE;write_ba    <= wr_addr[23:22];write_addr  <= 13'h0400;endWR_END:beginwrite_cmd   <=  NOP;write_ba    <=  2'b11;write_addr  <=  13'h1fff;enddefault:beginwrite_cmd   <=  NOP;write_ba    <=  2'b11;write_addr  <=  13'h1fff;endendcase//wr_sdram_en:数据总线输出使能
always@(posedge sys_clk or negedge sys_rst_n)if(sys_rst_n == 1'b0)wr_sdram_en <=  1'b0;elsewr_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/1nsmodule  sdram_read
(input   wire            sys_clk         ,   //系统时钟,频率166.66MHzinput   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;elserd_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;elsecnt_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;elsecase(read_state)RD_IDLE:if((rd_en ==1'b1) && (init_end == 1'b1))read_state <=  RD_ACTIVE;elseread_state <=  RD_IDLE;RD_ACTIVE:read_state <=  RD_TRCD;RD_TRCD:if(trcd_end == 1'b1)read_state <=  RD_READ;elseread_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@(*)begincase(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;endcaseend//SDRAM操作指令控制
always@(posedge sys_clk or negedge sys_rst_n)if(sys_rst_n == 1'b0)beginread_cmd    <=  NOP;read_ba     <=  2'b11;read_addr   <=  13'h1fff;endelsecase(read_state)RD_IDLE,RD_TRCD,RD_TRP:beginread_cmd    <=  NOP;read_ba     <=  2'b11;read_addr   <=  13'h1fff;endRD_ACTIVE:  //激活指令beginread_cmd    <=  ACTIVE;read_ba     <=  rd_addr[23:22];read_addr   <=  rd_addr[21:9];endRD_READ:    //读操作指令beginread_cmd    <=  READ;read_ba     <=  rd_addr[23:22];read_addr   <=  {4'b0000,rd_addr[8:0]};endRD_DATA:    //突发传输终止指令beginif(rdburst_end == 1'b1)read_cmd <=  B_STOP;elsebeginread_cmd    <=  NOP;read_ba     <=  2'b11;read_addr   <=  13'h1fff;endendRD_PRE:     //预充电指令beginread_cmd    <= P_CHARGE;read_ba     <= rd_addr[23:22];read_addr   <= 13'h0400;endRD_END:beginread_cmd    <=  NOP;read_ba     <=  2'b11;read_addr   <=  13'h1fff;enddefault:beginread_cmd    <=  NOP;read_ba     <=  2'b11;read_addr   <=  13'h1fff;endendcase//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代码:


// -----------------------------------------------------------------------------
// 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 signalsinput 					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_csif(~sys_rst_n) beginstate_cs 	<= 	IDLE 		;end else begin	state_cs 	<= 	state_ns	;end
end//组合逻辑,判断跳转
always@(*) 	begincase(state_cs)IDLE 	:	beginif(init_end == 1'b1)state_ns = 	ARBIT 	;elsestate_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 	;elsestate_ns = ARBIT 	;end AREF 	:	beginif(aref_end == 1'b1)state_ns  	=  	ARBIT 	;elsestate_ns  	=	AREF 	;end WRITE	:	beginif(wr_end == 1'b1)state_ns 	=	ARBIT 	;elsestate_ns 	=	WRITE	;end READ 	:	beginif(rd_end == 1'b1)state_ns 	=	ARBIT 	;elsestate_ns	=	READ 	;end default	:		state_ns 	=	IDLE	;endcase end //时序逻辑 输出错误,组合逻辑输出,可组合可时序
//sdram_ba sdram_addr sdram_cmd
always @(* ) begin case(state_cs)IDLE 	:	beginsdram_cmd  	=  init_cmd	;sdram_ba    =  init_ba		;sdram_addr  =  init_addr	;end ARBIT 	:	beginsdram_cmd   =  NOP			;sdram_ba    =  2'b11		;sdram_addr  =  13'h1fff	;endAREF 	:	beginsdram_cmd   =  aref_cmd	;sdram_ba    =  aref_ba		;sdram_addr  =  aref_addr	;end WRITE 	:	beginsdram_cmd   =  wr_cmd		;sdram_ba    =  wr_ba		;sdram_addr  =  wr_addr		;end READ 	:	beginsdram_cmd   =  rd_cmd		;sdram_ba    =  rd_ba		;sdram_addr  =  rd_addr		;end default : 	beginsdram_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) beginaref_en 	<= 	1'b0		;end else if ((state_cs == ARBIT) && (aref_req == 1'b1) )beginaref_en 	<= 	1'b1 		;endelse 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) beginwr_en  <= 1'b0		;end else if((state_cs == ARBIT) && (aref_req == 1'b0) && (wr_req == 1'b1)) beginwr_en  <= 1'b1		;endelse 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) beginrd_en  		<= 1'b0 		;endelse  if((state_cs == ARBIT) && (aref_req == 1'b0) && (rd_req == 1'b1) )beginrd_en 		 <= 1'b1 		;endelse 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) beginwr_data_reg <= 0;end else beginwr_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_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代码:

// -----------------------------------------------------------------------------
// 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/1psmodule fifo_ctrl (//signal define //system signalsinput 					sys_clk 			,		//系统时钟,167MHZinput 					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) beginwr_ack_dly 	<= 1'b0				;end else beginwr_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) beginrd_ack_dly  <= 		1'b0  	;end else beginrd_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) beginsdram_wr_addr  <= 24'd0		;end else if(wr_rst == 1'b1) beginsdram_wr_addr  <= sdram_wr_b_addr ;endelse if(wr_ack_fall == 1'b1 )	//一次突发写结束,更改写地址beginif(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) //一次突发读结束,更改读地址beginif(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) beginif(~sys_rst_n) beginsdram_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 	beginsdram_rd_req 	 	<= 	1'b0		;sdram_wr_req 		<= 	1'b0 		;endendelse 	beginsdram_rd_req 	 	<= 	1'b0		;sdram_wr_req 		<= 	1'b0 		;endend//读写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/1psmodule sdram_top (//system signalsinput	wire 				sys_clk 			,	//系统时钟,167MHZinput 	wire 				clk_out 			,	//相位偏移时钟,传给SDRAMinput 	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综合的影响

注意以下问题:

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

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


http://chatgpt.dhexx.cn/article/RHwPUZnh.shtml

相关文章

SDRAM控制器——仲裁模块的实现

前面一文中&#xff0c;我们已经对SDRAM的上电初始化、自动刷新以及突发读写进行了学习。 本文跟着大佬学习SDRAM中的仲裁模块。 仲裁机制 仲裁&#xff08;arbit&#xff09;&#xff1a;在FPGA中&#xff0c;当多个source源同时发出请求&#xff0c;容易导致操作冲突&#x…

SDRAM读写控制器

第1节 –作者&#xff1a;小黑同学 本文为明德扬原创及录用文章&#xff0c;转载请注明出处&#xff01; 1.1 总体设计 1.1.1 概述 同步动态随机存取内存&#xff08;synchronous dynamic randon-access menory&#xff0c;简称SDRAM&#xff09;是有一个同步接口的动态随…

FPGA综合项目——SDRAM控制器

FPGA综合项目——SDRAM控制器 目录 1.整体框架2.串口接收模块3.接收模块测试仿真4.串口发送模块5.发送模块测试仿真6.SDRAM基础学习7.SDRAM顶层模块8.SDRAM初始化模块设计与仿真测试9.自动刷新模块设计与测试10.写模块设计与测试11.读模块设计与仿真测试12.通信处理模块13.顶层…

细说SDRAM控制器

SDRAM的基本概念 SDRAM凭借其极高的性价比&#xff0c;广泛应用于高速数据存储、实时图像处理等设计当中&#xff0c;但是相对于SRAM、FIFO等其他存储器件&#xff0c;SDRAM的控制相对复杂。虽说是复杂&#xff0c;但也不代表没办法实现&#xff0c;仔细梳理一下&#xff0c;发…

SDRAM控制器

1.SDRAM简介 简介&#xff1a;SDRAM为同步动态随机存储内存&#xff0c;同步指的是时钟与外部输入的时钟保持一致&#xff0c;也就是与外部共用一个时钟&#xff1b;动态指的是每个时间段内&#xff0c;都要进行一次刷新操作&#xff0c;否则里面的数据会丢失&#xff0c;这也…

FPGA学习历程(四):SDRAM 控制器(初始化与刷新)

目录 一、数据手册相关信息1.1 命令真值表1.2 时间参数1.3 模式寄存器配置 二、初始化模块2.1 模块时序图2.2 模块源码2.2.1 sdram_init.v2.2.2 sdram_top.v2.2.3 tb_sdram_top.v 2.3 Modelsim仿真 三、刷新模块3.1 模块时序图3.2 模块源码3.2.1 sdram_aref.v3.2.2 sdram_top.v…

手把手带你实现SDRAM控制器(带Verilog代码)

上篇博客&#xff0c;我们了解了SDRAM的控制命令以及寻址方式&#xff0c;SDRAM芯片需要配合专门的控制电路使用才能发挥功能&#xff0c;这一节我们将一步步分析&#xff0c;使用Verilog搭建一个SDRAM驱动控制器。 目录 学习目标 问题分析 初始化模块 信息收集 模块接口确…

SDRAM 控制器(一)

1、基础知识 SDRAM&#xff08;synchronous Dynamic Random &#xff09;&#xff0c;同步动态随机存储器&#xff0c;同步指内存工作需要同步时钟&#xff0c;内存的命令的发送和数据的接收都以它为标准。动态是指需要不断地刷新来保证数据不丢失&#xff08;电容存储&#xf…

操作系统面试题(二十一):什么是DMA

DMA DMA&#xff08;Direct Memory Access 直接内存访问&#xff09; DMA意味着CPU授予I/O模块权限不涉及在不涉及CPU的情况下依然可以读取/写入内存&#xff0c;即DMA不需要CPUde支持 DMAC&#xff08;DMA 控制器&#xff09; 控制直接内存访问的过程 DMA的优点&#xff1a…

操作系统面试题:虚拟内存是什么,解决了什么问题,如何映射?

虚拟内存是什么&#xff1f; 虚拟内存别称虚拟存储器&#xff08;Virtual Memory&#xff09;。电脑中所运行的程序均需经由内存执行&#xff0c;若执行的程序占用内存很大或很多&#xff0c;则会导致内存消耗殆尽。为解决该问题&#xff0c;Windows中运用了虚拟内存技术&…

Linux面试题(34道)

1、Linux中各个目录的作用 1&#xff09;/ 根目录 2&#xff09;/boot 引导程序&#xff0c;内核等存放的目录 3&#xff09;/sbin 超级用户可以使用的命令的存放目录 4&#xff09;/bin 普通用户可以使用的命令的存放目录 5&#xff09;/lib 根目录下的所程序的共享库目录 6&…

【游戏客户端面试题干货】-- 2021年度最新游戏客户端面试干货(操作系统篇)

【游戏客户端面试题干货】-- 2021年度最新游戏客户端面试干货(操作系统篇&#xff09; 大家好&#xff0c;我是Lampard~~ 经过一番艰苦奋战之后&#xff0c;我终于是进入了心仪的公司。 今天给大家分享一下我在之前精心准备的一套面试知识。 今天和大家分享的是操作系统相关的面…

操作系统和网络(一):计算机网络常见面试题

计算机网络常见面试题总结 1. OSI &#xff0c; TCP/IP &#xff0c;五层协议的体系结构 OSI分层&#xff08;7层&#xff09; &#xff1a;物理层、数据链路层、网络层、传输层、会话层、表示层、应用层。 TCP/IP分层&#xff08;4层&#xff09; &#xff1a;网络接…

【Java实习生】每日面试题打卡——操作系统篇

临近秋招&#xff0c;备战暑期实习&#xff0c;祝大家每天进步亿点点&#xff01;Day15本篇总结的是 操作系统 相关的面试题&#xff0c;后续会每日更新~ 1、请分别简单说一说进程和线程以及它们的区别? 根本区别&#xff1a;进程是操作系统资源分配的基本单位&#xff0c;而…

操作系统面试题(一)

请你说一下进程与线程的概念&#xff0c;以及为什么要有进程线程&#xff0c;其中有什么区别&#xff0c;他们各自又是怎么同步的 参考回答&#xff1a; 基本概念&#xff1a; 进程是对运行时程序的封装&#xff0c;是系统进行资源调度和分配的的基本单位&#xff0c;实现了…

操作系统面试常问——for考研复试面试

关于操作系统的一些面试常问问题 前言&#xff1a; 本人22考研党已上岸&#xff0c;发一些复试准备整理的资料作为对考研准备的一个收尾。由于近几年基本都是线上复试&#xff0c;线上的话会更加注重概念的考察&#xff0c;本人在复试准备期间搜集了面试题&#xff0c;整理了…

操作系统面试题(三)

请你来说一说协程 参考回答&#xff1a; 1、概念&#xff1a; 协程&#xff0c;又称微线程&#xff0c;纤程&#xff0c;英文名Coroutine。协程看上去也是子程序&#xff0c;但执行过程中&#xff0c;在子程序内部可中断&#xff0c;然后转而执行别的子程序&#xff0c;在适…

操作系统面试题(十四):什么是虚拟内存?

虚拟内存&#xff08;virtual Memory&#xff09; 日常生活中&#xff0c;当我们使用电脑的时候&#xff0c;尤其是windows电脑&#xff0c;经常会打开许多软件&#xff0c;这些软件占用的内存已经远远大于计算机的物理内存。之所以会这样&#xff0c;就是因为虚拟内存的存在 …

操作系统面试题:设备管理

管理什么设备&#xff1f; 除cpu,内存外的所有设备 怎么管理设备&#xff1f; 通过将物理设备映射成逻辑设备 为什么要把物理设备映射成逻辑设备&#xff1f; 物理设备是I/O系统中实际安装的设备&#xff0c;物理名通常是字符串或者ID 而逻辑设备的逻辑名比较好记 如何理解…

操作系统面试问题汇总(超详细)

操作系统的组成 1、驱动程序是最底层的、直接控制和监视各类硬件的部分&#xff0c;它们的职责是隐藏硬件的具体细节&#xff0c;并向其他部分提供一个抽象的、通用的接口。 2、内核是操作系统之最内核部分&#xff0c;通常运行在最高特权级&#xff0c;负责提供基础性、结构性…