写在前面的话
这个实践项目来源于研究生电子设计竞赛,在涉及到视频图像处理时需要用到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;
    end 
    else if(cnt_200us == cnt_pow) begin
         cnt_200us    <=     0     ;
         pow_end    <=    1     ;
    end
    else    begin
        cnt_200us    <=    cnt_200us + 1'b1    ;
        pow_end        <=    0                     ;
    end
end

//cnt_clk:时钟周期计数,记录初始化各状态的等待时间
always @(posedge sys_clk or negedge sys_rst_n) begin 
    if(~sys_rst_n) begin
        cnt_clk <= 0     ;
    end 
    else if(pow_end == 1 || pre_end == 1 || aref_end == 1 ) begin
         cnt_clk <=    0      ;
    end
    else
        cnt_clk     <=    cnt_clk + 1;
end


//cnt_init_aref:初始化阶段的刷新次数
always @(posedge sys_clk or negedge sys_rst_n) begin 
    if(~sys_rst_n) begin
         cnt_init_aref         <=     0     ;
    end 
    else if(init_state_cs == INIT_IDLE) begin        //这里为什么设置两次清零
         cnt_init_aref         <=     0     ;
    end
    else if (init_state_cs == INIT_AREF) begin
        cnt_init_aref        <=    cnt_init_aref    + 1'b1    ;
    end
    else
        cnt_init_aref         <=    cnt_init_aref            ;
end

//预充电结束标志
//pre_end
always@(*)    begin
    if(init_state_cs == INIT_TRP && cnt_clk == cnt_rp)
                pre_end         =             1     ;
    else
                pre_end            =            0     ;
end

//刷新结束标志
//aref_end
always@(*)    begin
    if(init_state_cs == INIT_TRFC && cnt_clk == cnt_rfc)
                aref_end         =             1     ;
    else
                aref_end            =            0     ;
end


//模式寄存器结束标志
//mrd_end
always@(*)    begin
    if(init_state_cs == INIT_TMRD && cnt_clk == cnt_mrd)
                mrd_end                  =             1     ;
    else
                mrd_end                =            0     ;
end

//初始化状态机 三段式
//同步时序描述状态转移
always @(posedge sys_clk or negedge sys_rst_n) begin 
    if(~sys_rst_n) begin
         init_state_cs <= INIT_IDLE;
    end 
    else begin
         init_state_cs <= init_state_ns ;
    end
end


//组合逻辑描述状态转移条件
always@(*) begin
    case(init_state_cs)
        INIT_IDLE    :
                        if(pow_end == 1)
                            init_state_ns    =    INIT_PRE    ;
                        else
                            init_state_ns    =    INIT_IDLE    ;

        INIT_PRE    :
                            init_state_ns    =    INIT_TRP    ;

        INIT_TRP    :
                        if(pre_end == 1)
                            init_state_ns    =    INIT_AREF    ;
                        else
                            init_state_ns    =    INIT_TRP    ;

        INIT_AREF:           init_state_ns     =     INIT_TRFC    ; 

        INIT_TRFC    ://自动刷新等待状态,等待结束,自动跳转到模式寄存器,记录刷新次数
                        if(aref_end == 1)    //    刷新结束,需要判断刷新次数
                            if(cnt_init_aref == aref_num)
                                     init_state_ns    =    INIT_LMR    ;
                            else
                                    init_state_ns   =     INIT_AREF     ;
                        else
                            init_state_ns    =    INIT_TRFC    ;

        INIT_LMR     :         init_state_ns    =    INIT_TMRD     ;

        INIT_TMRD    :
                        if(mrd_end == 1)
                            init_state_ns    =    INIT_END    ;
                        else
                            init_state_ns    =    INIT_TMRD    ;
        INIT_END    :
                            init_state_ns   =   INIT_IDLE    ;
        default:
                            init_state_ns     =     INIT_IDLE    ;
    endcase // init_state_cs

end


//时序逻辑描述状态输出
always @(posedge sys_clk or negedge sys_rst_n) begin 
    if(~sys_rst_n) begin
                init_cmd     <= NOP                ;
                init_ba         <= 2'b11            ;
                init_addr      <= 13'h1fff        ;
                 init_end     <= 1'b0     ;    
    end 
    else begin
         case (init_state_cs)
             INIT_IDLE,INIT_TRP,INIT_TRFC,INIT_TMRD: begin
                              init_cmd     <= NOP                ;
                             init_ba         <= 2'b11            ;
                             init_addr      <= 13'h1fff        ;
             end

            INIT_PRE    : begin
                              init_cmd     <= PRE                ;
                             init_ba         <= 2'b11            ;
                             init_addr      <= 13'h1fff        ;
            end

            INIT_AREF     :    begin
                            init_cmd     <= AREF            ;
                             init_ba         <= 2'b11            ;
                             init_addr      <= 13'h1fff        ;
            end            

            INIT_LMR    :     begin
                            init_cmd     <= LMR                ;
                             init_ba         <= 2'b00            ;    //这里11和00有什么区别吗
                             init_addr      <= init_lmrset        ;
            end


            INIT_END    : begin
                              init_cmd     <= NOP                ;
                             init_ba         <= 2'b11            ;
                             init_addr      <= 13'h1fff        ;
                             init_end     <=    1'b1             ;
             end
             default : /* default */begin
                             init_cmd     <= NOP                ;
                             init_ba         <= 2'b11            ;
                             init_addr      <= 13'h1fff        ;
             end
         endcase
    end
end




endmodule

SDRAM自动刷新模块
模块图:



端口描述:
| 名称 |备注 |
| —————— | —————— |
|sys_clk |系统时钟,166MHz |
| sys_rst_n | 复位信号,低电平有效 |
| init_end | 初始化模块结束标志 |
|aref_en |自动刷新使能 |
| aref_cmd | 自动刷新阶段指令 |
|aref_req | 自动刷新阶段请求 |
| aref_ba | 自动刷新阶段Bank地址 |
| aref_addr |自动刷新地址总线 |
| aref_end |自动刷新结束标志 |

工作时序:



备注:
SDRAM中的电容是存在电荷泄露的,因此需要定期刷新,这是DRAM最重要的操作,也是保存数据留存的关键步骤。公认的标准是64ms完成一行的数据刷新,64ms/行数量,以8192行为例,需要7.8125us,这里选择7.5us是为了给仲裁机留出仲裁裕量。


需要注意的地方:
(1)3个时间,SDRAM自动刷新模块涉及3个关键时间:

名称 时间 备注
T_RP 18ns 预充电等待时间
T_RFC 66ns 自动刷新周期
刷新时间 64ms/8192 = 7.8125us,取7.5us

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

名称 编码 备注
AREF_IDLE 3’b000 初始状态,等待自动刷新使能
AREF_PRE 3’b001 预充电状态
AREF_TRP 3’b011 预充电等待状态
AREF_AREF 3’b010 自动刷新状态
AREF_TRFC 3’b110 自动刷新等待状态
AREF_END 3’b111 自动刷新结束状态

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

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

刷新流程:
72717…17

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


仿真波形:




Verilog代码:

// -----------------------------------------------------------------------------
// Copyright (c) 2014-2022 All rights reserved

// File   : sdram_aref.v
// Create : 2022-07-04 09:20:46
// -----------------------------------------------------------------------------

`timescale  1ns/1ns


module  sdram_aref
(
    input   wire            sys_clk     ,   //系统时钟,频率166MHz
    input   wire            sys_rst_n   ,   //复位信号,低电平有效
    input   wire            init_end    ,   //初始化结束信号
    input   wire            aref_en     ,   //自动刷新使能

    output  reg             aref_req    ,   //自动刷新请求
    output  reg     [3:0]   aref_cmd    ,   //自动刷新阶段写入sdram的指令,{cs_n,ras_n,cas_n,we_n}
    output  reg     [1:0]   aref_ba     ,   //自动刷新阶段Bank地址
    output  reg     [12:0]  aref_addr   ,   //地址数据,辅助预充电操作,A12-A0,13位地址
    output  wire            aref_end        //自动刷新结束标志
);

//********************************************************************//
//****************** Parameter and Internal Signal *******************//
//********************************************************************//

//parameter     define
parameter   CNT_REF_MAX =   11'd1248    ;   //自动刷新等待时钟数(7.5us)

parameter   TRP_CLK     =   3'd2        ,   //预充电等待周期
            TRC_CLK     =   4'd6       ;   //自动刷新等待周期


parameter   P_CHARGE    =   4'b0010     ,   //预充电指令
            A_REF       =   4'b0001     ,   //自动刷新指令
            NOP         =   4'b0111     ;   //空操作指令
parameter   AREF_IDLE   =   3'b000      ,   //初始状态,等待自动刷新使能
            AREF_PCHA   =   3'b001      ,   //预充电状态
            AREF_TRP    =   3'b011      ,   //预充电等待          tRP
            AUTO_REF    =   3'b010      ,   //自动刷新状态
            AREF_TRF    =   3'b100      ,   //自动刷新等待        tRC
            AREF_END    =   3'b101      ;   //自动刷新结束

//wire  define
wire            trp_end     ;   //预充电等待结束标志
wire            trc_end     ;   //自动刷新等待结束标志
wire            aref_ack    ;   //自动刷新应答信号

//reg   define
reg     [9:0]   cnt_aref        ;   //自动刷新计数器
reg     [2:0]   aref_state      ;   //SDRAM自动刷新状态
reg     [2:0]   cnt_clk         ;   //时钟周期计数,记录自刷新阶段各状态等待时间
reg             cnt_clk_rst     ;   //时钟周期计数复位标志
reg     [1:0]   cnt_aref_aref   ;   //自动刷新次数计数器

//********************************************************************//
//***************************** Main Code ****************************//
//********************************************************************//

//cnt_ref:刷新计数器
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_aref    <=  10'd0;
    else    if(cnt_aref >= CNT_REF_MAX)
        cnt_aref    <=  10'd0;
    else    if(init_end == 1'b1)
        cnt_aref    <=  cnt_aref + 1'b1;

//aref_req:自动刷新请求
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        aref_req    <=  1'b0;
    else    if(cnt_aref == (CNT_REF_MAX - 1'b1))
        aref_req    <=  1'b1;
    else    if(aref_ack == 1'b1)
        aref_req    <=  1'b0;

//aref_ack:自动刷新应答信号
assign  aref_ack = (aref_state == AREF_PCHA ) ? 1'b1 : 1'b0;

//aref_end:自动刷新结束标志
assign  aref_end = (aref_state == AREF_END  ) ? 1'b1 : 1'b0;

//cnt_clk:时钟周期计数,记录初始化各状态等待时间
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_clk <=  3'd0;
    else    if(cnt_clk_rst == 1'b1)
        cnt_clk <=  3'd0;
    else
        cnt_clk <=  cnt_clk + 1'b1;

//trp_end,trc_end,tmrd_end:等待结束标志
assign  trp_end = ((aref_state == AREF_TRP)
                    && (cnt_clk == TRP_CLK )) ? 1'b1 : 1'b0;
assign  trc_end = ((aref_state == AREF_TRF)
                    && (cnt_clk == TRC_CLK )) ? 1'b1 : 1'b0;

//cnt_aref_aref:初始化过程自动刷新次数计数器
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_aref_aref   <=  2'd0;
    else    if(aref_state == AREF_IDLE)
        cnt_aref_aref   <=  2'd0;
    else    if(aref_state == AUTO_REF)
        cnt_aref_aref   <=  cnt_aref_aref + 1'b1;
    else
        cnt_aref_aref   <=  cnt_aref_aref;

//SDRAM自动刷新状态机
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        aref_state  <=  AREF_IDLE;
    else
        case(aref_state)
            AREF_IDLE:
                if((aref_en == 1'b1) && (init_end == 1'b1))
                    aref_state  <=  AREF_PCHA;
                else
                    aref_state  <=  aref_state;
            AREF_PCHA:
                aref_state  <=  AREF_TRP;
            AREF_TRP:
                if(trp_end == 1'b1)
                    aref_state  <=  AUTO_REF;
                else
                    aref_state  <=  aref_state;
            AUTO_REF:
                aref_state  <=  AREF_TRF;
            AREF_TRF:
                if(trc_end == 1'b1)
                    if(cnt_aref_aref == 2'd2)
                        aref_state  <=  AREF_END;
                    else
                        aref_state  <=  AUTO_REF;
                else
                    aref_state  <=  aref_state;
            AREF_END:
                aref_state  <=  AREF_IDLE;
            default:
                aref_state  <=  AREF_IDLE;
        endcase

//cnt_clk_rst:时钟周期计数复位标志
always@(*)
    begin
        case (aref_state)
            AREF_IDLE:  cnt_clk_rst <=  1'b1;   //时钟周期计数器清零
            AREF_TRP:   cnt_clk_rst <=  (trp_end == 1'b1) ? 1'b1 : 1'b0;
                                                //等待结束标志有效,计数器清零
            AREF_TRF:   cnt_clk_rst <=  (trc_end == 1'b1) ? 1'b1 : 1'b0;
                                                //等待结束标志有效,计数器清零
            AREF_END:   cnt_clk_rst <=  1'b1;
            default:    cnt_clk_rst <=  1'b0;
        endcase
    end

//SDRAM操作指令控制
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        begin
            aref_cmd    <=  NOP;
            aref_ba     <=  2'b11;
            aref_addr   <=  13'h1fff;
        end
    else
        case(aref_state)
            AREF_IDLE,AREF_TRP,AREF_TRF:    //执行空操作指令
                begin
                    aref_cmd    <=  NOP;
                    aref_ba     <=  2'b11;
                    aref_addr   <=  13'h1fff;
                end
            AREF_PCHA:  //预充电指令
                begin
                    aref_cmd    <=  P_CHARGE;
                    aref_ba     <=  2'b11;
                    aref_addr   <=  13'h1fff;
                end 
            AUTO_REF:   //自动刷新指令
                begin
                    aref_cmd    <=  A_REF;
                    aref_ba     <=  2'b11;
                    aref_addr   <=  13'h1fff;
                end
            AREF_END:   //一次自动刷新完成
                begin
                    aref_cmd    <=  NOP;
                    aref_ba     <=  2'b11;
                    aref_addr   <=  13'h1fff;
                end    
            default:
                begin
                    aref_cmd    <=  NOP;
                    aref_ba     <=  2'b11;
                    aref_addr   <=  13'h1fff;
                end    
        endcase

endmodule

SDRAM写模块
模块图:



端口描述:

名称 备注
sys_clk 系统时钟,166MHz
sys_rst_n 复位信号,低电平有效
init_end 初始化模块结束标志
wr_en 写使能
wr_addr 写地址
wr_data 写数据
wr_burst_len 写突发长度
wr_ack 写响应
wr_end 单次写结束
write_cmd 写指令
write_ba 写Bank地址
write_addr 写地址
wr_sdram_en 写SDRAM使能
wr_sdram_data 写SDRAM数据

工作时序:



备注:
SDRAM写模块要和初始化模块模式寄存器设置的工作模式相匹配,这里采用的是页突发写模式。
需要注意的地方:
(1)3个时间,SDRAM写模块涉及3个关键时间:

名称 时间 备注
T_RP 18ns 预充电等待时间
T_RCD 18ns 写入激活命令到数据开始读写,中间需要等待的时间
T_data (n-1)*clk_period 数据突发时间,按照突发长度设定

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

名称 编码 备注
WR_IDLE 3’b000 写初始状态
WR_ACT 3’b001 激活状态
WR_TRCD 3’b011 激活等待状态
WR_WR 3’b010 写操作状态
WR_DATA 3’b110 写数据状态
WR_PRE 3’b111 预充电状态
WR_TRD 3’b101 预充电等待状态
WR_END 3’b100 写结束状态

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

名称 编码 备注 快速记忆码(10进制)
ACT 4’b0011 激活 3
NOP 4’b0111 空操作 7
B_STOP 4’b0110 突发停止 6
WR 4’b0100 SDRAM写 4
PRE 4’b0010 预充电 2

写流程:
73747…762

其中,7至少要一次,对应突发写长度的周期。


仿真波形:




Verilog代码:

// -----------------------------------------------------------------------------
// File   : sdram_write.v
// Create : 2022-07-04 09:15:24
// -----------------------------------------------------------------------------

`timescale  1ns/1ns
module  sdram_write
(
    input   wire            sys_clk         ,   //系统时钟,频率166MHz
    input   wire            sys_rst_n       ,   //复位信号,低电平有效
    input   wire            init_end        ,   //初始化结束信号
    input   wire            wr_en           ,   //写使能
    input   wire    [23:0]  wr_addr         ,   //写SDRAM地址
    input   wire    [15:0]  wr_data         ,   //待写入SDRAM的数据(写FIFO传入)
    input   wire    [9:0]   wr_burst_len    ,   //写突发SDRAM字节数

    output  wire            wr_ack          ,   //写SDRAM响应信号
    output  wire            wr_end          ,   //一次突发写结束
    output  reg     [3:0]   write_cmd       ,   //写数据阶段写入sdram的指令,{cs_n,ras_n,cas_n,we_n}
    output  reg     [1:0]   write_ba        ,   //写数据阶段Bank地址
    output  reg     [12:0]  write_addr      ,   //地址数据,辅助预充电操作,行、列地址,A12-A0,13位地址
    output  reg             wr_sdram_en     ,   //数据总线输出使能
    output  wire    [15:0]  wr_sdram_data       //写入SDRAM的数据
);

//********************************************************************//
//****************** Parameter and Internal Signal *******************//
//********************************************************************//

//parameter     define
parameter   TRCD_CLK    =   'd2   ,   //激活周期
            TRP_CLK     =   'd2   ;   //预充电周期


parameter   WR_IDLE     =   4'b0000 ,   //初始状态
            WR_ACTIVE   =   4'b0001 ,   //激活
            WR_TRCD     =   4'b0011 ,   //激活等待
            WR_WRITE    =   4'b0010 ,   //写操作
            WR_DATA     =   4'b0100 ,   //写数据
            WR_PRE      =   4'b0101 ,   //预充电
            WR_TRP      =   4'b0111 ,   //预充电等待
            WR_END      =   4'b0110 ;   //一次突发写结束
parameter   NOP         =   4'b0111 ,   //空操作指令
            ACTIVE      =   4'b0011 ,   //激活指令
            WRITE       =   4'b0100 ,   //数据写指令
            B_STOP      =   4'b0110 ,   //突发停止指令
            P_CHARGE    =   4'b0010 ;   //预充电指令

//wire  define
wire            trcd_end    ;   //激活等待周期结束
wire            twrite_end  ;   //突发写结束
wire            trp_end     ;   //预充电有效周期结束

//reg   define
reg     [3:0]   write_state ;   //SDRAM写状态
reg     [9:0]   cnt_clk     ;   //时钟周期计数,记录写数据阶段各状态等待时间
reg             cnt_clk_rst ;   //时钟周期计数复位标志

//********************************************************************//
//***************************** Main Code ****************************//
//********************************************************************//

//wr_end:一次突发写结束
assign  wr_end = (write_state == WR_END) ? 1'b1 : 1'b0;

//wr_ack:写SDRAM响应信号
assign  wr_ack = ( write_state == WR_WRITE)
                || ((write_state == WR_DATA) 
                && (cnt_clk <= (wr_burst_len - 2'd2)));

//cnt_clk:时钟周期计数,记录初始化各状态等待时间
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_clk <=  10'd0;
    else    if(cnt_clk_rst == 1'b1)
        cnt_clk <=  10'd0;
    else
        cnt_clk <=  cnt_clk + 1'b1;

//trcd_end,twrite_end,trp_end:等待结束标志
assign  trcd_end    =   ((write_state == WR_TRCD)
                        &&(cnt_clk == TRCD_CLK        )) ? 1'b1 : 1'b0;    //激活周期结束
assign  twrite_end  =   ((write_state == WR_DATA)
                        &&(cnt_clk == wr_burst_len - 1)) ? 1'b1 : 1'b0;    //突发写结束
assign  trp_end     =   ((write_state == WR_TRP )
                        &&(cnt_clk == TRP_CLK         )) ? 1'b1 : 1'b0;    //预充电等待周期结束

//write_state:SDRAM的工作状态机
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
            write_state <=  WR_IDLE;
    else
        case(write_state)
            WR_IDLE:
                if((wr_en ==1'b1) && (init_end == 1'b1))
                        write_state <=  WR_ACTIVE;
                else
                        write_state <=  write_state;
            WR_ACTIVE:
                write_state <=  WR_TRCD;
            WR_TRCD:
                if(trcd_end == 1'b1)
                    write_state <=  WR_WRITE;
                else
                    write_state <=  write_state;
            WR_WRITE:
                write_state <=  WR_DATA;
            WR_DATA:
                if(twrite_end == 1'b1)
                    write_state <=  WR_PRE;
                else
                    write_state <=  write_state;
            WR_PRE:
                write_state <=  WR_TRP;
            WR_TRP:
                if(trp_end == 1'b1)
                    write_state <=  WR_END;
                else
                    write_state <=  write_state;

            WR_END:
                write_state <=  WR_IDLE;
            default:
                write_state <=  WR_IDLE;
        endcase

//计数器控制逻辑
always@(*)
    begin
        case(write_state)
            WR_IDLE:    cnt_clk_rst   <=  1'b1;
            WR_TRCD:    cnt_clk_rst   <=  (trcd_end == 1'b1) ? 1'b1 : 1'b0;
            WR_WRITE:   cnt_clk_rst   <=  1'b1;
            WR_DATA:    cnt_clk_rst   <=  (twrite_end == 1'b1) ? 1'b1 : 1'b0;
            WR_TRP:     cnt_clk_rst   <=  (trp_end == 1'b1) ? 1'b1 : 1'b0;
            WR_END:     cnt_clk_rst   <=  1'b1;
            default:    cnt_clk_rst   <=  1'b0;
        endcase
    end

//SDRAM操作指令控制
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        begin
            write_cmd   <=  NOP;
            write_ba    <=  2'b11;
            write_addr  <=  13'h1fff;
        end
    else
        case(write_state)
            WR_IDLE,WR_TRCD,WR_TRP:
                begin
                    write_cmd   <=  NOP;
                    write_ba    <=  2'b11;
                    write_addr  <=  13'h1fff;
                end
            WR_ACTIVE:  //激活指令
                begin
                    write_cmd   <=  ACTIVE;
                    write_ba    <=  wr_addr[23:22];
                    write_addr  <=  wr_addr[21:9];
                end
            WR_WRITE:   //写操作指令
                begin
                    write_cmd   <=  WRITE;
                    write_ba    <=  wr_addr[23:22];
                    write_addr  <=  {4'b0000,wr_addr[8:0]};
                end     
            WR_DATA:    //突发传输终止指令
                begin
                    if(twrite_end == 1'b1)
                        write_cmd <=  B_STOP;
                    else
                        begin
                            write_cmd   <=  NOP;
                            write_ba    <=  2'b11;
                            write_addr  <=  13'h1fff;
                        end
                end
            WR_PRE:     //预充电指令
                begin
                    write_cmd   <= P_CHARGE;
                    write_ba    <= wr_addr[23:22];
                    write_addr  <= 13'h0400;
                end
            WR_END:
                begin
                    write_cmd   <=  NOP;
                    write_ba    <=  2'b11;
                    write_addr  <=  13'h1fff;
                end
            default:
                begin
                    write_cmd   <=  NOP;
                    write_ba    <=  2'b11;
                    write_addr  <=  13'h1fff;
                end
        endcase

//wr_sdram_en:数据总线输出使能
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        wr_sdram_en <=  1'b0;
    else
        wr_sdram_en <=  wr_ack;

//wr_sdram_data:写入SDRAM的数据
assign  wr_sdram_data = (wr_sdram_en == 1'b1) ? wr_data : 16'd0;

endmodule

SDRAM读模块
模块图:


端口描述:

名称 备注
sys_clk 系统时钟,166MHz
sys_rst_n 复位信号,低电平有效
init_end 初始化模块结束标志
rd_en 读使能
rd_addr 读地址
rd_data 读数据
rd_burst_len 读突发长度
rd_ack 读响应
rd_end 单次读结束
read_cmd 读SDRAM指令
read_ba 读Bank地址
read_addr 读地址
rd_sdram_data 读SDRAM数据

工作时序:

备注:
SDRAM读模块模式也是同预设寄存器保持一致,这里使用的是不带自动预充电的页突发读模式。
需要注意的地方:
(1)3个时间,SDRAM读模块涉及3个关键时间:

名称 时间 备注
T_RP 18ns 预充电等待时间
T_RCD 18ns 写入激活命令到数据开始读写,中间需要等待的时间
T_CL 2clk 潜伏期,读数据才有

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

名称 编码 备注
RD_IDLE 4’b0000 空闲状态
RD_ACT 4’b0001 激活状态
RD_TRCD 4’b0011 激活等待状态
RD_RD 4’b0010 读操作状态
RD_CL 4’b0110 潜伏期状态
RD_DATA 4’b0111 读数据状态
RD_PRE 4’b0101 预充电状态
RD_TRP 4’b0100 预充电等待状态
RD_END 4’b1100 读结束状态

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

名称 编码 备注 快速记忆码(10进制)
ACT 4’b0011 激活 3
NOP 4’b0111 空操作 7
B_STOP 4’b0110 突发停止 6
READ 4’b0101 SDRAM读 5
PRE 4’b0010 预充电 2

读流程:
73757…76727

其中,5和6之间的7至少要一次,对应突发读长度的周期。

仿真波形:


Verilog代码:

// File   : sdram_read.v
// Create : 2022-07-04 20:51:55
// -----------------------------------------------------------------------------
`timescale  1ns/1ns


module  sdram_read
(
    input   wire            sys_clk         ,   //系统时钟,频率166.66MHz
    input   wire            sys_rst_n       ,   //复位信号,低电平有效
    input   wire            init_end        ,   //初始化结束信号
    input   wire            rd_en           ,   //读使能
    input   wire    [23:0]  rd_addr         ,   //读SDRAM地址
    input   wire    [15:0]  rd_data         ,   //自SDRAM中读出的数据
    input   wire    [9:0]   rd_burst_len    ,   //读突发SDRAM字节数

    output  wire            rd_ack          ,   //读SDRAM响应信号
    output  wire            rd_end          ,   //一次突发读结束
    output  reg     [3:0]   read_cmd        ,   //读数据阶段写入sdram的指令,{cs_n,ras_n,cas_n,we_n}
    output  reg     [1:0]   read_ba         ,   //读数据阶段Bank地址
    output  reg     [12:0]  read_addr       ,   //地址数据,辅助预充电操作,行、列地址,A12-A0,13位地址
    output  wire    [15:0]  rd_sdram_data       //SDRAM读出的数据
);

//********************************************************************//
//****************** Parameter and Internal Signal *******************//
//********************************************************************//

//parameter     define
parameter   TRCD_CLK    =   10'd2   ,   //激活等待周期
            TCL_CLK     =   10'd3   ,   //潜伏期
            TRP_CLK     =   10'd2   ;   //预充电等待周期

parameter   RD_IDLE     =   4'b0000 ,   //空闲
            RD_ACTIVE   =   4'b0001 ,   //激活
            RD_TRCD     =   4'b0011 ,   //激活等待
            RD_READ     =   4'b0010 ,   //读操作
            RD_CL       =   4'b0100 ,   //潜伏期
            RD_DATA     =   4'b0101 ,   //读数据
            RD_PRE      =   4'b0111 ,   //预充电
            RD_TRP      =   4'b0110 ,   //预充电等待
            RD_END      =   4'b1100 ;   //一次突发读结束
parameter   NOP         =   4'b0111 ,   //空操作指令
            ACTIVE      =   4'b0011 ,   //激活指令
            READ        =   4'b0101 ,   //数据读指令
            B_STOP      =   4'b0110 ,   //突发停止指令
            P_CHARGE    =   4'b0010 ;   //预充电指令

//wire  define
wire            trcd_end    ;   //激活等待周期结束
wire            trp_end     ;   //预充电等待周期结束
wire            tcl_end     ;   //潜伏期结束标志
wire            tread_end   ;   //突发读结束
wire            rdburst_end ;   //读突发终止

//reg   define
reg     [3:0]   read_state  ;   //SDRAM写状态
reg     [9:0]   cnt_clk     ;   //时钟周期计数,记录初始化各状态等待时间
reg             cnt_clk_rst ;   //时钟周期计数复位标志
reg     [15:0]  rd_data_reg ;

//********************************************************************//
//***************************** Main Code ****************************//
//********************************************************************//

//rd_data_reg
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        rd_data_reg <=  16'd0;
    else
        rd_data_reg <=  rd_data;

//rd_end:一次突发读结束
assign  rd_end = (read_state == RD_END) ? 1'b1 : 1'b0;

//rd_ack:读SDRAM响应信号  //166m时钟对齐问题
assign  rd_ack = (read_state == RD_DATA)
                && (cnt_clk >= 10'd2)
                && (cnt_clk < (rd_burst_len + 'd2));

//cnt_clk:时钟周期计数,记录初始化各状态等待时间
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_clk <=  10'd0;
    else    if(cnt_clk_rst == 1'b1)
        cnt_clk <=  10'd0;
    else
        cnt_clk <=  cnt_clk + 1'b1;

//trcd_end,trp_end,tcl_end,tread_end,rdburst_end:等待结束标志
assign  trcd_end    =   ((read_state == RD_TRCD)
                        && (cnt_clk == TRCD_CLK        )) ? 1'b1 : 1'b0;    //行选通周期结束
assign  trp_end     =   ((read_state == RD_TRP )
                        && (cnt_clk == TRP_CLK         )) ? 1'b1 : 1'b0;    //预充电有效周期结束
assign  tcl_end     =   ((read_state == RD_CL  )
                        && (cnt_clk == TCL_CLK - 1     )) ? 1'b1 : 1'b0;    //潜伏期结束
assign  tread_end   =   ((read_state == RD_DATA)
                        && (cnt_clk == rd_burst_len + 2)) ? 1'b1 : 1'b0;    //突发读结束
assign  rdburst_end =   ((read_state == RD_DATA)
                        && (cnt_clk == rd_burst_len - 4)) ? 1'b1 : 1'b0;    //读突发终止

//read_state:SDRAM的工作状态机
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
            read_state  <=  RD_IDLE;
    else
        case(read_state)
            RD_IDLE:
                if((rd_en ==1'b1) && (init_end == 1'b1))
                        read_state <=  RD_ACTIVE;
                else
                        read_state <=  RD_IDLE;
            RD_ACTIVE:
                read_state <=  RD_TRCD;
            RD_TRCD:
                if(trcd_end == 1'b1)
                    read_state <=  RD_READ;
                else
                    read_state <=  RD_TRCD;
            RD_READ:
                read_state <=  RD_CL;
            RD_CL:
                read_state <=  (tcl_end == 1'b1) ? RD_DATA : RD_CL;
            RD_DATA:
                read_state <=  (tread_end == 1'b1) ? RD_PRE : RD_DATA;
            RD_PRE:
                read_state  <=  RD_TRP;
            RD_TRP:
                read_state  <=  (trp_end == 1'b1) ? RD_END : RD_TRP;
            RD_END:
                read_state  <=  RD_IDLE;
            default:
                read_state  <=  RD_IDLE;
        endcase

//计数器控制逻辑
always@(*)
    begin
        case(read_state)
            RD_IDLE:    cnt_clk_rst   <=  1'b1;
            RD_TRCD:    cnt_clk_rst   <=  (trcd_end == 1'b1) ? 1'b1 : 1'b0;
            RD_READ:    cnt_clk_rst   <=  1'b1;
            RD_CL:      cnt_clk_rst   <=  (tcl_end == 1'b1) ? 1'b1 : 1'b0;
            RD_DATA:    cnt_clk_rst   <=  (tread_end == 1'b1) ? 1'b1 : 1'b0;
            RD_TRP:     cnt_clk_rst   <=  (trp_end == 1'b1) ? 1'b1 : 1'b0;
            RD_END:     cnt_clk_rst   <=  1'b1;
            default:    cnt_clk_rst   <=  1'b0;
        endcase
    end

//SDRAM操作指令控制
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        begin
            read_cmd    <=  NOP;
            read_ba     <=  2'b11;
            read_addr   <=  13'h1fff;
        end
    else
        case(read_state)
            RD_IDLE,RD_TRCD,RD_TRP:
                begin
                    read_cmd    <=  NOP;
                    read_ba     <=  2'b11;
                    read_addr   <=  13'h1fff;
                end
            RD_ACTIVE:  //激活指令
                begin
                    read_cmd    <=  ACTIVE;
                    read_ba     <=  rd_addr[23:22];
                    read_addr   <=  rd_addr[21:9];
                end
            RD_READ:    //读操作指令
                begin
                    read_cmd    <=  READ;
                    read_ba     <=  rd_addr[23:22];
                    read_addr   <=  {4'b0000,rd_addr[8:0]};
                end
            RD_DATA:    //突发传输终止指令
                begin
                    if(rdburst_end == 1'b1)
                        read_cmd <=  B_STOP;
                    else
                        begin
                            read_cmd    <=  NOP;
                            read_ba     <=  2'b11;
                            read_addr   <=  13'h1fff;
                        end
                end
            RD_PRE:     //预充电指令
                begin
                    read_cmd    <= P_CHARGE;
                    read_ba     <= rd_addr[23:22];
                    read_addr   <= 13'h0400;
                end
            RD_END:
                begin
                    read_cmd    <=  NOP;
                    read_ba     <=  2'b11;
                    read_addr   <=  13'h1fff;
                end
            default:
                begin
                    read_cmd    <=  NOP;
                    read_ba     <=  2'b11;
                    read_addr   <=  13'h1fff;
                end
        endcase

//rd_sdram_data:SDRAM读出的数据
assign  rd_sdram_data = (rd_ack == 1'b1) ? rd_data_reg : 16'b0;

endmodule

SDRAM仲裁机
模块图:



端口描述:

名称 备注
sys_clk 系统时钟,166MHz
sys_rst_n 复位信号,低电平有效
init_end 初始化模块结束标志
init_cmd 初始化模块指令
init_ba 初始化模块bank地址
init_addr 初始化模块地址总线
aref_req 自动刷新阶段请求
aref_end 自动刷新结束标志
aref_cmd 自动刷新阶段指令
aref_ba 自动刷新阶段Bank地址
aref_addr 自动刷新地址总线
wr_req 数据写请求
wr_req 单次写结束
wr_cmd 写指令
write_ba 写Bank地址
wr_addr 写地址
wr_data 写数据
wr_sdram_en 写SDRAM使能
wr_sdram_en 读请求
rd_end 单次读结束
rd_cmd 读SDRAM指令
rd_ba 读Bank地址
rd_addr 读Bank地址
aref_en 自动刷新使能
wr_en 数据写使能
rd_en 数据读使能
sdram_cke SDRAM时钟使能信号
sdram_cs_n SDRAM片选信号
sdram_cas_n SDRAM列选通信号
sdram_ras_n SDRAM行选通信号
sdram_we_n SDRAM写使能
sdram_ba SDRAM Bank地址
sdram_addr SDRAM地址总线
sdram_dq SDRAM数据总线

工作时序:



备注:
仲裁机包含五个状态,默认优先级为自动刷新操作>数据写操作>数据读操作;一切都是以保证数据存留为前提。


仿真波形:



Verilog代码:

// -----------------------------------------------------------------------------
// Copyright (c) 2014-2022 All rights reserved
// -----------------------------------------------------------------------------
// File   : sdram_arbit.v
// Create : 2022-06-07 15:32:59
// Revise : 2022-06-20 16:21:04
// Verdion:
// Description:
// -----------------------------------------------------------------------------
`timescale 1ps/1ps
module sdram_arbit (
    //system signals
            input                     sys_clk         ,        //系统时钟,167M    
            input                     sys_rst_n         ,        //系统复位信号,低电平有效
    //init signals                    
            input                     init_end         ,        //初始化结束标志        
            input      [3:0]            init_cmd         ,        //初始化阶段命令
            input     [1:0]            init_ba            ,        //初始化阶段bank地址
            input     [12:0]            init_addr         ,        //初始化阶段地址总线
    //aref signals                    
            input                     aref_req        ,        //刷新请求信号
            input                     aref_end         ,        //刷新结束信号
            input     [3:0]            aref_cmd         ,        //刷新阶段命令
            input     [1:0]            aref_ba         ,        //刷新阶段bank地址
            input     [12:0]            aref_addr        ,        //刷新阶段地址
    //write signals                    
            input                     wr_req             ,        //写数据请求
            input                     wr_end             ,        //一次写结束信号
            input     [3:0]            wr_cmd             ,        //写阶段命令
            input     [1:0]            wr_ba             ,        //写阶段BANK地址
            input     [12:0]            wr_addr         ,        //写阶段地址总线
            input     [15:0]            wr_data         ,        //写数据
            input                     wr_sdram_en     ,        //写sdram使能信号
    //read signals                
            input                     rd_req             ,        //读请求
            input                     rd_end             ,        //读数据结束
            input     [3:0]            rd_cmd             ,        //读阶段命令
            input     [1:0]             rd_ba             ,        //读阶段bank地址
            input     [12:0]            rd_addr         ,        //读地址总线
    //output signals                
            output      reg            aref_en         ,        //刷新请求
            output         reg            wr_en             ,        //写数据使能
            output         reg            rd_en             ,        //读数据使能
            output         wire        sdram_cke         ,        //sdram时钟有效信号
            output         wire        sdram_cs_n         ,        //sdram片选信号
            output         wire        sdram_cas_n     ,        //sdram行选通信号
            output         wire        sdram_ras_n        ,        //sdram列选通信号
            output         wire        sdram_we_n        ,        //sdram写使能信号
            output reg    [1:0]        sdram_ba         ,        //sdram的bank地址
            output reg    [12:0]        sdram_addr         ,        //sdram的地址总线
            inout wire [15:0]         sdram_dq                //sdram的数据总线

    );    

//localparam
localparam         IDLE     =    3'b000        ,        //初始状态
                ARBIT     =    3'b001        ,        //仲裁状态
                AREF     =    3'b011        ,        //自动刷新
                WRITE     =    3'b010        ,        //写状态
                READ     =    3'b110        ;        //读状态
//命令
localparam         NOP     =    4'b0111        ;    //空操作命令


//reg define
reg     [3:0]        sdram_cmd    ;    //写入SDRAM 命令
reg     [2:0]        state_cs     ;    //当前状态
reg     [2:0]        state_ns    ;    //下一状态
reg     [15:0]         wr_data_reg    ;    //数据寄存

//状态机
always @(posedge sys_clk or negedge sys_rst_n) begin : proc_state_cs
    if(~sys_rst_n) begin
        state_cs     <=     IDLE         ;
    end else begin    
        state_cs     <=     state_ns    ;
    end
end


//组合逻辑,判断跳转
always@(*)     begin
        case(state_cs)
            IDLE     :    begin
                            if(init_end == 1'b1)
                                state_ns =     ARBIT     ;
                            else
                                state_ns =    IDLE     ;

            end 

            ARBIT    :    begin                        //刷新请求>写请求>读请求
                            if(aref_req == 1'b1)
                                state_ns = AREF     ;
                            else if(wr_req == 1'b1 )
                                state_ns = WRITE     ;
                            else if(rd_req == 1'b1)
                                state_ns = READ     ;
                            else
                                state_ns = ARBIT     ;

            end 

            AREF     :    begin
                            if(aref_end == 1'b1)
                                state_ns      =      ARBIT     ;
                            else
                                state_ns      =    AREF     ;

            end 

            WRITE    :    begin
                            if(wr_end == 1'b1)
                                state_ns     =    ARBIT     ;
                            else
                                state_ns     =    WRITE    ;

            end 

            READ     :    begin
                            if(rd_end == 1'b1)
                                state_ns     =    ARBIT     ;
                            else
                                state_ns    =    READ     ;

            end 

            default    :        state_ns     =    IDLE    ;

        endcase 

end 


//时序逻辑 输出错误,组合逻辑输出,可组合可时序
//sdram_ba sdram_addr sdram_cmd
always @(* ) begin 
    case(state_cs)

        IDLE     :    begin
                        sdram_cmd      =  init_cmd    ;
                        sdram_ba    =  init_ba        ;
                        sdram_addr  =  init_addr    ;
        end 

        ARBIT     :    begin
                        sdram_cmd   =  NOP            ;
                        sdram_ba    =  2'b11        ;
                        sdram_addr  =  13'h1fff    ;
        end

        AREF     :    begin
                        sdram_cmd   =  aref_cmd    ;
                        sdram_ba    =  aref_ba        ;
                        sdram_addr  =  aref_addr    ;

        end 

        WRITE     :    begin
                        sdram_cmd   =  wr_cmd        ;
                        sdram_ba    =  wr_ba        ;
                        sdram_addr  =  wr_addr        ;

        end 

        READ     :    begin
                        sdram_cmd   =  rd_cmd        ;
                        sdram_ba    =  rd_ba        ;
                        sdram_addr  =  rd_addr        ;

        end 

        default :     begin
                        sdram_cmd   =  NOP            ;
                        sdram_ba    =  2'b11        ;
                        sdram_addr  =  13'h1fff    ;

        end 
    endcase // state_cs
end

//自动刷新使能
always @(posedge sys_clk or negedge sys_rst_n) begin : proc_
    if(~sys_rst_n) begin
        aref_en     <=     1'b0        ;
    end 
    else if ((state_cs == ARBIT) && (aref_req == 1'b1) )begin
         aref_en     <=     1'b1         ;
    end
    else if(aref_end == 1'b1 )
        aref_en     <=     1'b0         ;
end

//写数据使能
//wr_en
always @(posedge sys_clk or negedge sys_rst_n) begin : proc_wr_en 
    if(~sys_rst_n) begin
        wr_en  <= 1'b0        ;
    end 
    else if((state_cs == ARBIT) && (aref_req == 1'b0) && (wr_req == 1'b1)) begin
        wr_en  <= 1'b1        ;
    end
    else if(wr_end == 1'b1)
        wr_en     <=    1'b0    ;
end


//读数据使能
//rd_en
always @(posedge sys_clk or negedge sys_rst_n) begin : proc_rd_en 
    if(~sys_rst_n) begin
        rd_en          <= 1'b0         ;
    end

    else  if((state_cs == ARBIT) && (aref_req == 1'b0) && (rd_req == 1'b1) )begin
        rd_en          <= 1'b1         ;
    end

    else if(rd_end    ==    1'b1)
        rd_en         <=    1'b0         ;
end


//SDRAM 时钟使能
assign    sdram_cke = 1'b1     ;

//SDRAM 数据总线
assign    sdram_dq  = (wr_sdram_en == 1'b1 )?wr_data: 16'bz     ;    //作为输出端口,延迟一拍?

always @(posedge sys_clk or negedge sys_rst_n) begin 
    if(~sys_rst_n) begin
         wr_data_reg <= 0;
    end else begin
         wr_data_reg <=    wr_data ;
    end
end

//片选信号,行地址选通信号,列地址选通信号,写使能信号
assign     {sdram_cs_n, sdram_ras_n, sdram_cas_n, sdram_we_n}    =    sdram_cmd     ;

endmodule


SDRAM控制模块
模块图:

verlog代码:


// -----------------------------------------------------------------------------
// Copyright (c) 2014-2022 All rights reserved
// File   : sdram_top.v
// Create : 2022-07-04 20:51:45
// -----------------------------------------------------------------------------
`timescale  1ns/1ns


module  sdram_top
(
    input   wire            sys_clk         ,   //系统时钟
    input   wire            clk_out         ,   //相位偏移时钟
    input   wire            sys_rst_n       ,   //复位信号,低有效
//写FIFO信号
    input   wire            wr_fifo_wr_clk  ,   //写FIFO写时钟
    input   wire            wr_fifo_wr_req  ,   //写FIFO写请求
    input   wire    [15:0]  wr_fifo_wr_data ,   //写FIFO写数据
    input   wire    [23:0]  sdram_wr_b_addr ,   //写SDRAM首地址
    input   wire    [23:0]  sdram_wr_e_addr ,   //写SDRAM末地址
    input   wire    [9:0]   wr_burst_len    ,   //写SDRAM数据突发长度
    input   wire            wr_rst          ,   //写复位信号
//读FIFO信号
    input   wire            rd_fifo_rd_clk  ,   //读FIFO读时钟
    input   wire            rd_fifo_rd_req  ,   //读FIFO读请求
    input   wire    [23:0]  sdram_rd_b_addr ,   //读SDRAM首地址
    input   wire    [23:0]  sdram_rd_e_addr ,   //读SDRAM末地址
    input   wire    [9:0]   rd_burst_len    ,   //读SDRAM数据突发长度
    input   wire            rd_rst          ,   //读复位信号
    output  wire    [15:0]  rd_fifo_rd_data ,   //读FIFO读数据
    output  wire    [9:0]   rd_fifo_num     ,   //读fifo中的数据量

    input   wire            read_valid      ,   //SDRAM读使能
    output  wire            init_end        ,   //SDRAM初始化完成标志
//SDRAM接口信号
    output  wire            sdram_clk       ,   //SDRAM芯片时钟
    output  wire            sdram_cke       ,   //SDRAM时钟有效信号
    output  wire            sdram_cs_n      ,   //SDRAM片选信号
    output  wire            sdram_ras_n     ,   //SDRAM行地址选通脉冲
    output  wire            sdram_cas_n     ,   //SDRAM列地址选通脉冲
    output  wire            sdram_we_n      ,   //SDRAM写允许位
    output  wire    [1:0]   sdram_ba        ,   //SDRAM的L-Bank地址线
    output  wire    [12:0]  sdram_addr      ,   //SDRAM地址总线
    output  wire    [1:0]   sdram_dqm       ,   //SDRAM数据掩码
    inout   wire    [15:0]  sdram_dq            //SDRAM数据总线
);

//********************************************************************//
//****************** Parameter and Internal Signal *******************//
//********************************************************************//

//wire  define
wire            sdram_wr_req    ;   //sdram 写请求
wire            sdram_wr_ack    ;   //sdram 写响应
wire    [23:0]  sdram_wr_addr   ;   //sdram 写地址
wire    [15:0]  sdram_data_in   ;   //写入sdram中的数据

wire            sdram_rd_req    ;   //sdram 读请求
wire            sdram_rd_ack    ;   //sdram 读响应
wire    [23:0]  sdram_rd_addr   ;   //sdram 读地址
wire    [15:0]  sdram_data_out  ;   //从sdram中读出的数据

//sdram_clk:SDRAM芯片时钟
assign  sdram_clk = clk_out;
//sdram_dqm:SDRAM数据掩码
assign  sdram_dqm = 2'b00;

//********************************************************************//
//*************************** Instantiation **************************//
//********************************************************************//

//------------- fifo_ctrl_inst -------------
fifo_ctrl   fifo_ctrl_inst(

//system    signal
    .sys_clk        (sys_clk        ),  //SDRAM控制时钟
    .sys_rst_n      (sys_rst_n      ),  //复位信号
//write fifo signal
    .wr_fifo_wr_clk (wr_fifo_wr_clk ),  //写FIFO写时钟
    .wr_fifo_wr_req (wr_fifo_wr_req ),  //写FIFO写请求
    .wr_fifo_wr_data(wr_fifo_wr_data),  //写FIFO写数据
    .sdram_wr_b_addr(sdram_wr_b_addr),  //写SDRAM首地址
    .sdram_wr_e_addr(sdram_wr_e_addr),  //写SDRAM末地址
    .wr_burst_len   (wr_burst_len   ),  //写SDRAM数据突发长度
    .wr_rst         (wr_rst         ),  //写清零信号
//read fifo signal
    .rd_fifo_rd_clk (rd_fifo_rd_clk ),  //读FIFO读时钟
    .rd_fifo_rd_req (rd_fifo_rd_req ),  //读FIFO读请求
    .rd_fifo_rd_data(rd_fifo_rd_data),  //读FIFO读数据
    .rd_fifo_num    (rd_fifo_num    ),  //读FIFO中的数据量
    .sdram_rd_b_addr(sdram_rd_b_addr),  //读SDRAM首地址
    .sdram_rd_e_addr(sdram_rd_e_addr),  //读SDRAM末地址
    .rd_burst_len   (rd_burst_len   ),  //读SDRAM数据突发长度
    .rd_rst         (rd_rst         ),  //读清零信号
//USER ctrl signal
    .read_valid     (read_valid     ),  //SDRAM读使能
    .init_end       (init_end       ),  //SDRAM初始化完成标志
//SDRAM ctrl of write
    .sdram_wr_ack   (sdram_wr_ack   ),  //SDRAM写响应
    .sdram_wr_req   (sdram_wr_req   ),  //SDRAM写请求
    .sdram_wr_addr  (sdram_wr_addr  ),  //SDRAM写地址
    .sdram_data_in  (sdram_data_in  ),  //写入SDRAM的数据
//SDRAM ctrl of read
    .sdram_rd_ack   (sdram_rd_ack   ),  //SDRAM读请求
    .sdram_data_out (sdram_data_out ),  //SDRAM读响应
    .sdram_rd_req   (sdram_rd_req   ),  //SDRAM读地址
    .sdram_rd_addr  (sdram_rd_addr  )  //读出SDRAM数据

);

//------------- sdram_ctrl_inst -------------
sdram_ctrl  sdram_ctrl_inst(

    .sys_clk        (sys_clk        ),   //系统时钟
    .sys_rst_n      (sys_rst_n      ),   //复位信号,低电平有效
//SDRAM 控制器写端口
    .sdram_wr_req   (sdram_wr_req   ),   //写SDRAM请求信号
    .sdram_wr_addr  (sdram_wr_addr  ),   //SDRAM写操作的地址
    .wr_burst_len   (wr_burst_len   ),   //写sdram时数据突发长度
    .sdram_data_in  (sdram_data_in  ),   //写入SDRAM的数据
    .sdram_wr_ack   (sdram_wr_ack   ),   //写SDRAM响应信号
//SDRAM 控制器读端口
    .sdram_rd_req   (sdram_rd_req   ),  //读SDRAM请求信号
    .sdram_rd_addr  (sdram_rd_addr  ),  //SDRAM写操作的地址
    .rd_burst_len   (rd_burst_len   ),  //读sdram时数据突发长度
    .sdram_data_out (sdram_data_out ),  //从SDRAM读出的数据
    .init_end       (init_end       ),  //SDRAM 初始化完成标志
    .sdram_rd_ack   (sdram_rd_ack   ),  //读SDRAM响应信号
//FPGA与SDRAM硬件接口
    .sdram_cke      (sdram_cke      ),  // SDRAM 时钟有效信号
    .sdram_cs_n     (sdram_cs_n     ),  // SDRAM 片选信号
    .sdram_ras_n    (sdram_ras_n    ),  // SDRAM 行地址选通脉冲
    .sdram_cas_n    (sdram_cas_n    ),  // SDRAM 列地址选通脉冲
    .sdram_we_n     (sdram_we_n     ),  // SDRAM 写允许位
    .sdram_ba       (sdram_ba       ),  // SDRAM L-Bank地址线
    .sdram_addr     (sdram_addr     ),  // SDRAM 地址总线
    .sdram_dq       (sdram_dq       )   // SDRAM 数据总线

);

endmodule

FIFO控制模块
模块图:




端口描述:

名称 备注
sys_clk 系统时钟,166MHz
sys_rst_n 复位信号,低电平有效
wr_fifo_wr_clk 写fifo写时钟,测试按照50MHz
wr_fifo_wr_req 写fifo写请求
wr_fifo_wr_data 写fifo写数据
sdram_wr_b_addr 写SDRAM的首地址
sdram_wr_e_addr 写SDRAM的末地址
wr_burst_len 写SDRAM的突发长度
wr_rst 写复位信号,写fifo清零
rd_fifo_rd_clk 读fifo读时钟
rd_fifo_rd_req 读fifo读请求
sdram_rd_b_addr 读SDRAM的首地址
sdram_rd_e_addr 读SDRAM的末地址
rd_burst_len 读SDRAM的突发长度
rd_rst 读复位信号,读fifo清零
rd_fifo_rd_data 读fifo读数据
rd_fifo_num 读FIFO中的数据量 /读FIFO中写入的数据量
read_valid SDRAM读使能
init_end SDRAM初始化结束信号
sdram_wr_ack SDRAM写响应
sdram_wr_req SDRAM写请求
sdram_wr_addr SDRAM写地址
sdram_data_in 写入SDRAM的数据
sdram_data_out SDRAM读出的数据
sdram_rd_req SDRAM读请求
sdram_rd_addr SDRAM读请求

Verilog代码:

// -----------------------------------------------------------------------------
// Copyright (c) 2014-2022 All rights reserved
// -----------------------------------------------------------------------------
// File   : fifo_ctrl.v
// Create : 2022-06-09 09:19:13
// Revise : 2022-06-20 09:14:11
// -----------------------------------------------------------------------------
`timescale 1ps/1ps

module fifo_ctrl (
        //signal define 
        //system signals
        input                     sys_clk             ,        //系统时钟,167MHZ
        input                     sys_rst_n             ,        //系统复位信号,低电平有效
        //写fifo信号                //
        input                    wr_fifo_wr_clk        ,        //写fifo写时钟
        input                     wr_fifo_wr_req         ,        //写fifo写请求
        input     [15:0]            wr_fifo_wr_data     ,        //写fifo写数据
        input     [23:0]            sdram_wr_b_addr     ,        //写SDRAM的首地址
        input     [23:0]            sdram_wr_e_addr     ,        //写SDRAM的末地址
        input     [9:0]            wr_burst_len         ,        //写SDRAM的突发长度
        input                     wr_rst                 ,        //写复位信号,写fifo清零
        //读fifo信号                //
        input                    rd_fifo_rd_clk        ,        //读fifo读时钟
        input                     rd_fifo_rd_req         ,        //读fifo读请求
        input     [23:0]            sdram_rd_b_addr     ,        //读SDRAM的首地址
        input     [23:0]            sdram_rd_e_addr     ,        //读SDRAM的末地址
        input     [9:0]            rd_burst_len         ,        //读SDRAM的突发长度
        input                     rd_rst                 ,        //读复位信号,读fifo清零
        output     wire  [15:0]    rd_fifo_rd_data     ,        //读fifo读数据
        output     wire  [9:0]        rd_fifo_num            ,        //读FIFO中的数据量 /读FIFO中写入的数据量
        //
        input                     read_valid             ,        //SDRAM读使能
        input                     init_end             ,        //SDRAM初始化结束信号
        //
        //SDRAM写信号        //
        input                     sdram_wr_ack         ,        //SDRAM写响应
        output     reg             sdram_wr_req         ,        //SDRAM写请求
        output     reg     [23:0]    sdram_wr_addr         ,        //SDRAM写地址
        output     wire     [15:0]    sdram_data_in         ,        //写入SDRAM的数据
        //SDRAM读信号        //
        input                     sdram_rd_ack        ,        //SDRAM读响应
        input             [15:0]    sdram_data_out         ,        //SDRAM读出的数据
        output     reg             sdram_rd_req        ,        //SDRAM读请求
        output reg        [23:0]    sdram_rd_addr                 //SDRAM读地址


    );


//======================================
//param and internal signals
//======================================


//wire define 
wire            wr_ack_fall ;   //写响应信号下降沿
wire            rd_ack_fall ;   //读相应信号下降沿
wire    [9:0]   wr_fifo_num ;   //写fifo中的数据量


//reg define
reg        wr_ack_dly       ;   //写响应打拍
reg        rd_ack_dly       ;   //读响应打拍


//wr_ack_dly: 写响应信号打拍
always @(posedge sys_clk or negedge sys_rst_n) begin : proc_
    if(~sys_rst_n) begin
         wr_ack_dly     <= 1'b0                ;
    end 
    else begin
         wr_ack_dly     <=     sdram_wr_ack    ;
    end
end


//rd_ack_dly:读响应信号打拍
always @(posedge sys_clk or negedge sys_rst_n) begin : proc_rd_ack_dly 
    if(~sys_rst_n) begin
        rd_ack_dly  <=         1'b0      ;
    end 
    else begin
        rd_ack_dly  <=          sdram_rd_ack     ;
    end
end

//wr_ack_fall,rd_ack_fall:检测读写响应信号下降沿
assign  wr_ack_fall = (wr_ack_dly & ~sdram_wr_ack);
assign  rd_ack_fall = (rd_ack_dly & ~sdram_rd_ack);


//sdram_wr_addr :sdram写地址
always @(posedge sys_clk or negedge sys_rst_n) begin : proc_sdram_wr_addr 
    if(~sys_rst_n) begin
        sdram_wr_addr  <= 24'd0        ;
    end 
    else if(wr_rst == 1'b1) begin
        sdram_wr_addr  <= sdram_wr_b_addr ;
    end
    else if(wr_ack_fall == 1'b1 )    //一次突发写结束,更改写地址
        begin
                if(sdram_wr_addr < (sdram_wr_e_addr - wr_burst_len))            //不使用乒乓操作,一次突发写结束,更改写地址,未达到末地址,写地址累加
                    sdram_wr_addr     <=    sdram_wr_addr + wr_burst_len    ;
                else                                                            //不使用乒乓操作,到达末地址,回到写起始地址
                    sdram_wr_addr     <=  sdram_wr_b_addr             ;
        end 
end


//sdram_rd_addr:sdram读地址
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        sdram_rd_addr   <=  24'd0;
    else    if(rd_rst == 1'b1)
        sdram_rd_addr   <=  sdram_rd_b_addr;
    else    if(rd_ack_fall == 1'b1) //一次突发读结束,更改读地址
        begin
            if(sdram_rd_addr < (sdram_rd_e_addr - rd_burst_len))
                    //读地址未达到末地址,读地址累加
                sdram_rd_addr   <=  sdram_rd_addr + rd_burst_len;
            else    //到达末地址,回到首地址
                sdram_rd_addr   <=  sdram_rd_b_addr;
        end

//sdram_wr_req,sdram_rd_req:读写请求信号
always @(posedge sys_clk or negedge sys_rst_n) begin
    if(~sys_rst_n) begin
        sdram_rd_req          <=     1'b0        ;
        sdram_wr_req         <=     1'b0         ;
    end 
    else if (init_end == 1'b1 )begin        //初始化完成,响应读写请求
        //优先执行写操作,防止写入SDRAM中的数据丢失
            if(wr_fifo_num >= wr_burst_len)     begin    //写FIFO中的数据量达到写突发长度,数据送出
                 sdram_wr_req         <=         1'b1     ;    //写请求有效,输出到仲裁机,仲裁机判断后输出写使能到写模块,写模块输出
                 sdram_rd_req        <=         1'b0     ;
             end 

             else if((rd_fifo_num < rd_burst_len ) && (read_valid == 1'b1 ))        begin//读FIFO中的数据量小于读突发长度,且读使能信号有效
                 sdram_wr_req         <=         1'b0     ;    
                sdram_rd_req         <=         1'b1     ;
             end 

             else     begin
                 sdram_rd_req          <=     1'b0        ;
                sdram_wr_req         <=     1'b0         ;
             end
    end
    else     begin
                 sdram_rd_req          <=     1'b0        ;
                sdram_wr_req         <=     1'b0         ;
    end

end

//读写fifo例化
//------------- wr_fifo_data -------------
/*
fifo_data   wr_fifo_data(
    //用户接口
    .wrclk      (wr_fifo_wr_clk ),  //写时钟
    .wrreq      (wr_fifo_wr_req ),  //写请求
    .data       (wr_fifo_wr_data),  //写数据
    //SDRAM接口
    .rdclk      (sys_clk        ),  //读时钟
    .rdreq      (sdram_wr_ack   ),  //读请求
    .q          (sdram_data_in  ),  //读数据

    .rdusedw    (wr_fifo_num    ),  //FIFO中的数据量,读时钟域的指针
    .wrusedw    (               ),
    .aclr       (~sys_rst_n || wr_rst)  //清零信号
    );
*/
//------------- rd_fifo_data -------------
/*
fifo_data   rd_fifo_data(
    //sdram接口
    .wrclk      (sys_clk        ),  //写时钟
    .wrreq      (sdram_rd_ack   ),  //写请求
    .data       (sdram_data_out ),  //写数据
    //用户接口
    .rdclk      (rd_fifo_rd_clk ),  //读时钟
    .rdreq      (rd_fifo_rd_req ),  //读请求
    .q          (rd_fifo_rd_data),  //读数据

    .rdusedw    (               ),
    .wrusedw    (rd_fifo_num    ),  //FIFO中的数据量
    .aclr       (~sys_rst_n || rd_rst)  //清零信号
    );
*/

//写fifo例化
    FIFO_async #(
            .FIFO_data_size(16),
            .FIFO_addr_size(10)
        ) inst_FIFO_async_wr (
            .clk_w    (wr_fifo_wr_clk                ),        //写时钟    
            .rst_w    (~sys_rst_n || wr_rst         ),        //写复位
            .w_en     (wr_fifo_wr_req                 ),        //写使能 / 写请求

            .clk_r    (sys_clk                         ),        //读时钟
            .rst_r    (~sys_rst_n || wr_rst         ),        //读复位
            .r_en     (sdram_wr_ack                 ),        //读使能 / 读请求

            .data_in  (wr_fifo_wr_data                 ),        //写数据
            .data_out (sdram_data_in                 ),        //读数据
            .empty    (                                ),        //空信号
            .full     (                                ),        //满信号
            .wrusedw  (                                ),        //写指针
            .rdusedw  (    wr_fifo_num                    )        //读指针
        );

//读fifo例化
    FIFO_async #(
            .FIFO_data_size(16),
            .FIFO_addr_size(10)
        ) inst_FIFO_async_rd  (
            .clk_w    (sys_clk                        ),        //写时钟    
            .rst_w    (~sys_rst_n || rd_rst         ),        //写复位
            .w_en     (sdram_rd_ack                 ),        //写使能 / 写请求

            .clk_r    (rd_fifo_rd_clk                 ),        //读时钟
            .rst_r    (~sys_rst_n || wr_rst         ),        //读复位
            .r_en     (rd_fifo_rd_req                 ),        //读使能 / 读请求

            .data_in  (sdram_data_out                 ),        //写数据
            .data_out (rd_fifo_rd_data                 ),        //读数据
            .empty    (                                ),        //空信号
            .full     (                                ),        //满信号
            .wrusedw  (                                ),        //写指针
            .rdusedw  (    rd_fifo_num                    )        //读指针
        );
          endmodule 


查看另一篇博客链接:

SDRAM控制器顶层模块
模块图:



Verilog代码:

// -----------------------------------------------------------------------------
// Copyright (c) 2014-2022 All rights reserved
// -----------------------------------------------------------------------------
// File   : sdram_top.v
// Create : 2022-06-09 20:41:48
// Revise : 2022-06-10 15:24:19
// Verdion:
// Description:
// -----------------------------------------------------------------------------
`timescale 1ps/1ps

module sdram_top (
    //system signals
    input    wire                 sys_clk             ,    //系统时钟,167MHZ
    input     wire                 clk_out             ,    //相位偏移时钟,传给SDRAM
    input     wire                  sys_rst_n             ,    //系统复位信号,低电平有效
    //写FIFO信号            
    input     wire                 wr_fifo_wr_clk         ,    //写FIFO写时钟    
    input     wire                 wr_fifo_wr_req         ,    //写FIFO写请求
    input     wire     [15:0]         wr_fifo_wr_data     ,    //写FIFO写数据    /存入SDRAM的数据
    input     wire     [23:0]        sdram_wr_b_addr     ,    //写SDRAM的首地址
    input     wire     [23:0]        sdram_wr_e_addr     ,    //写SDRAM的末地址
    input     wire     [9:0]        wr_burst_len         ,    //写突发长度
    input     wire                 wr_rst                 ,    //写复位 /清零
    //读FIFO信号                //
    input     wire                 rd_fifo_rd_clk         ,    //读FIFO读时钟
    input     wire                 rd_fifo_rd_req         ,    //读FIFO读请求
    input     wire     [23:0]         sdram_rd_b_addr     ,    //读SDRAM的首地址
    input     wire     [23:0]         sdram_rd_e_addr     ,    //读SDRAM的末地址
    input     wire     [9:0]        rd_burst_len         ,    //读突发长度
    input     wire                 rd_rst                 ,    //读复位
    output     wire     [15:0]        rd_fifo_rd_data     ,    //读FIFO的读数据 / 读出SDRAM的数据
    output     wire     [9:0]        rd_fifo_num            ,    //读fifo中的数据量 /读FIFO中写入的数量
    //
    input     wire                 read_valid             ,    //SDRAM读使能
    output     wire                 init_end             ,    //SDRAM的初始化结束信号
    //SDRAM接口信号
    output    wire                 sdram_clk             ,    //SDRAM芯片时钟
    output     wire                 sdram_cke             ,    //SDRAM时钟有效信号
    output     wire                 sdram_cs_n             ,    //SDRAM片选信号
    output     wire                 sdram_ras_n         ,    //SDRAM行选通信号
    output     wire                 sdram_cas_n         ,    //SDRAM列选通信号
    output     wire                 sdram_we_n             ,    //SDRAM写使能  低电平写 高电平读
    output     wire     [1:0]        sdram_ba             ,    //SDRAM的bank地址
    output     wire     [12:0]        sdram_addr             ,    //SDRAM的地址总线
    output     wire     [1:0]        sdram_dqm             ,    //SDRAM的数据掩码
    inout     wire     [15:0]        sdram_dq                 //SDRAM的数据总线



);

//********************************************************************//
//****************** Parameter and Internal Signal *******************//
//********************************************************************//

//wire  define

//wire  define
wire            sdram_wr_req    ;   //sdram 写请求
wire            sdram_wr_ack    ;   //sdram 写响应
wire    [23:0]  sdram_wr_addr   ;   //sdram 写地址
wire    [15:0]  sdram_data_in   ;   //写入sdram中的数据

wire            sdram_rd_req    ;   //sdram 读请求
wire            sdram_rd_ack    ;   //sdram 读响应
wire    [23:0]  sdram_rd_addr   ;   //sdram 读地址
wire    [15:0]  sdram_data_out  ;   //从sdram中读出的数据

//sdram_clk:SDRAM芯片时钟
assign  sdram_clk = clk_out;
//sdram_dqm:SDRAM数据掩码
assign  sdram_dqm = 2'b00;






//模块例化
    sdram_ctrl sdram_ctrl_inst
        (
            .sys_clk        (sys_clk        ),        //系统时钟,167MHZ
            .sys_rst_n      (sys_rst_n        ),        //系统复位信号,低电平有效
            .init_end       (init_end        ),        //SDRAM初始化完成标志
            //写FIFO模块
            .sdram_wr_req   (sdram_wr_req    ),        //写SDRAM请求信号
            .sdram_wr_addr  (sdram_wr_addr     ),        //SDRAM写操作的地址
            .sdram_data_in  (sdram_data_in     ),        //写SDRAM的数据
            .wr_burst_len   (wr_burst_len     ),        //写入SDRAM的突发长度
            .sdram_wr_ack   (sdram_wr_ack     ),        //写SDRAM响应信号
            //读FIFO模块
            .sdram_rd_req   (sdram_rd_req     ),        //读SDRAM请求信号
            .sdram_rd_addr  (sdram_rd_addr     ),        //SDRAM读操作的地址
            .rd_burst_len   (rd_burst_len     ),        //读sdram时数据突发长度
            .sdram_data_out (sdram_data_out ),        //从SDRAM读出的数据
            .sdram_rd_ack   (sdram_rd_ack     ),        //读SDRAM响应信号
            //SDRAM接口
            .sdram_cke      (sdram_cke         ),        //SDRAM 时钟有效信号
            .sdram_cs_n     (sdram_cs_n     ),        //SDRAM 片选信号
            .sdram_ras_n    (sdram_ras_n     ),        //SDRAM 行地址选通
            .sdram_cas_n    (sdram_cas_n     ),        //SDRAM 列地址选通
            .sdram_we_n     (sdram_we_n     ),        //SDRAM 写使能
            .sdram_ba       (sdram_ba         ),        //SDRAM Bank地址
            .sdram_addr     (sdram_addr     ),        //SDRAM 地址总线
            .sdram_dq       (sdram_dq         )         //SDRAM 数据总线
        );


    fifo_ctrl fifo_ctrl_inst
        (
            .sys_clk         (sys_clk             ),        //系统时钟,167MHZ
            .sys_rst_n       (sys_rst_n         ),        //系统复位信号,低电平有效
            //写FIFO接口
            .wr_fifo_wr_clk  (wr_fifo_wr_clk     ),        //写fifo写时钟
            .wr_fifo_wr_req  (wr_fifo_wr_req     ),        //写fifo写请求
            .wr_fifo_wr_data (wr_fifo_wr_data     ),        //写fifo写数据
            .sdram_wr_b_addr (sdram_wr_b_addr     ),        //写SDRAM的首地址
            .sdram_wr_e_addr (sdram_wr_e_addr     ),        //写SDRAM的末地址
            .wr_burst_len    (wr_burst_len         ),        //写SDRAM的突发长度
            .wr_rst          (wr_rst             ),        //写复位信号,写fifo清零
            //读FIFO接口
            .rd_fifo_rd_clk  (rd_fifo_rd_clk     ),        //读fifo读时钟
            .rd_fifo_rd_req  (rd_fifo_rd_req     ),        //读fifo读请求
            .sdram_rd_b_addr (sdram_rd_b_addr     ),        //读SDRAM的首地址
            .sdram_rd_e_addr (sdram_rd_e_addr     ),        //读SDRAM的末地址
            .rd_burst_len    (rd_burst_len         ),        //读SDRAM的突发长度
            .rd_rst          (rd_rst             ),        //读复位信号,读fifo清零
            .rd_fifo_rd_data (rd_fifo_rd_data     ),        //读fifo读数据
            .rd_fifo_num     (rd_fifo_num         ),        //读FIFO中的数据量

            .read_valid      (read_valid         ),        //SDRAM读使能
            .init_end        (init_end             ),        //SDRAM初始化结束信号
            //SDRAM接口
            .sdram_wr_ack    (sdram_wr_ack         ),        //SDRAM写响应
            .sdram_wr_req    (sdram_wr_req         ),        //SDRAM写请求
            .sdram_wr_addr   (sdram_wr_addr     ),        //SDRAM写地址
            .sdram_data_in   (sdram_data_in     ),        //写入SDRAM的数据
            .sdram_rd_ack    (sdram_rd_ack         ),        //SDRAM读响应
            .sdram_data_out  (sdram_data_out     ),        //SDRAM读出的数据
            .sdram_rd_req    (sdram_rd_req         ),        //SDRAM读请求
            .sdram_rd_addr   (sdram_rd_addr     )        //SDRAM读地址
        );




endmodule

Modelsim前仿
对所有代码进行编译仿真



Quartus综合结果
使用Quartus (Quartus Prime 17.1) Standard Edition对RTL进行综合,对综合后的资源占用和电路图进行检查。


注意:在AC620综合时,FIFO采用IP核
RTL图
顶层模块


FIFO控制


SDRAM控制


Timing Slack
关键路径在于对FIFO读写端数据存量的判断

在FPGA综合时涉及到fifo内数据量的判断,从而实现突发读写,这里判断条件要采用相同时钟域下的信号,否则会出现timing error。在ac620下板验证时,速度100Mhz、133Mhz和166M均满足。



避免出现跨时钟域采样,以免造成时序不满足。
VCS仿真&DC综合结果
VCS仿真结果




DC综合结果
面积报告:



功耗报告:



layout window (standard cell):



Bug&Debug
在modelsim仿真&VCS仿真中,部分code存在风格问题,例如在两个always块中对同一信号进行赋值。在综合阶段会出现报错,需要进行修改。
**这里将错误的部分列举出来:

  1. sdram_aref 中的 cnt_clk
  2. sdram_read 中的cnt_clk
  3. sdram_write中的cnt_clk


此外,还需要额外注意SDRAM芯片在不同工作频率时对应的CL latency值
后续会上传bug版本和debug版本代码

总结


至此,整个入门级工程项目完成,从完成度和难度来讲,这个项目更偏向于工程实现。SDRAM控制器虽然看上去简单,但是通过这个小项目可以更好的培养工程文件的管理、项目实践习惯以及
verilog综合语句理解。希望大家都能培养一个良好的工程习惯和coding style。
SDRAM控制器是用于管理SDRAM存储器的硬件模块,其主要任务是协调CPU和SDRAM之间的数据传输。由于SDRAM存储器的特殊性质,SDRAM控制器需要具有复杂的时序控制和缓存管理功能,以确保数据的正确性和存取效率。
Verilog综合是将Verilog代码转换为门级电路网络的过程,它可以生成与目标FPGA或ASIC芯片兼容的二进制文件。在SDRAM控制器的设计中,Verilog综合对于实现快速、准确、高效的控制器至关重要。因此,在进行SDRAM控制器设计时,必须考虑到Verilog综合的影响
注意以下问题:
1.Verilog代码的规范性:Verilog代码必须符合语言规范和硬件设计规范,以确保能够被正确地综合成目标电路。
2.时序约束的设置:时序约束是指定义逻辑操作的最小和最大时间限制,以确保电路能够在所需的时间内完成操作。在SDRAM控制器设计中,时序约束的设置非常重要,因为SDRAM存储器需要严格控制时序以确保数据的完整性。STA是数字IC设计中相当重要的组成部分。
3.确定优化策略:Verilog综合工具通常会提供一系列优化选项,以便在综合过程中最大限度地减小资源使用和功耗消耗。在SDRAM控制器设计中,需要根据具体情况选择合适的优化策略。
SDRAM控制器是一个对新手而言相对复杂的硬件模块,Verilog综合对于实现高效、准确、可靠的控制器至关重要。在进行SDRAM控制器设计时,必须考虑到Verilog综合工具的影响,并严格遵守规范和约束条件,以确保电路能够正确地综合成目标电路。**