前面一文中,我们已经对SDRAM的上电初始化、自动刷新以及突发读写进行了学习。
本文跟着大佬学习SDRAM中的仲裁模块。
仲裁机制
仲裁(arbit):在FPGA中,当多个source源同时发出请求,容易导致操作冲突,因此我我们需要根据相应的优先级来响应哪一个source,这个过程就叫仲裁。
对于SDRAM控制器来说,其中包含上电初始化、自动刷新以及突发读写模块,由于SDRAM在一个clk中只执行一个操作,因此当4个模块中的两个及以上模块同时发出操作命令的时候,就会出现操作冲突,从而导致SDRAM工作出错,因此这里就需要引入仲裁机制,仲裁机制根据优先级对4个模块发出来的操作请求统一管理。
四个模块的仲裁总框图如下:

仲裁模块的输入信号
仲裁模块的输入信号主要就是4个模块的输出信号。
其中四个小模块的使能信号是由仲裁模块发出的,初始化模块由于上电后自动工作,因此仲裁模块实质是控制3个模块。
可看到仲裁模块发出刷新使能、读写使能信号,根据这三个使能信号,来控制小模块的独立工作,避免操作冲突。

除了四个模块的输出信号作为仲裁模块的输入信号,以及三个使能信号作为仲裁模块的输出之外,仲裁模块的端口信号中还包含时钟复位信号以及SDRAM芯片物理接口:
module sdram_arbit
(input wire arbit_clk , //系统时钟input wire arbit_rst_n , //复位信号
//sdram_init input wire [3:0] init_cmd , //初始化阶段命令input wire init_end , //初始化结束标志input wire [1:0] init_bank , //初始化阶段Bank地址input wire [12:0] init_addr , //初始化阶段数据地址
//sdram_auto_ref input wire atref_req , //自刷新请求input wire atref_end , //自刷新结束input wire [3:0] atref_cmd , //自刷新阶段命令input wire [1:0] atref_bank , //自动刷新阶段Bank地址input wire [12:0] atref_addr , //自刷新阶段数据地址
//sdram_writeinput wire wr_req , //写数据请求input wire [1:0] wr_bank , //写阶段Bank地址input wire wr_end , //一次写结束信号input wire [3:0] wr_cmd , //写阶段命令input wire [12:0] wr_addr , //写阶段数据地址input wire wr_sdram_en , //写数据有效input wire [15:0] wr_sdram_data , //要写入sdram的数据
//sdram_readinput wire rd_req , //读数据请求input wire rd_end , //一次读结束input wire [3:0] rd_cmd , //读阶段命令input wire [12:0] rd_addr , //读阶段数据地址input wire [1:0] rd_bank , //读阶段Bank地址
//输出控制逻辑 output reg atref_en , //自刷新使能output reg wr_en , //写数据使能output reg rd_en , //读数据使能
//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 reg [1:0] sdram_bank , //SDRAM Bank地址output reg [12:0] sdram_addr , //SDRAM地址总线inout wire [15:0] sdram_dq //SDRAM数据总线
);
最终给出仲裁模块的输入输出端口:

分析仲裁模块的工作状态
首先我们确定优先级:
上电初始化——自动刷新——写操作——读操作(先写在读,防止数据被覆盖)
根据上面的分析,得到如下的状态图:
仲裁模块:负责发出自动刷新、读写模块的使能信号 (注意优先级)
四个模块:负责发出相应操作的请求信号
这里的状态不复杂且大佬博文中也有讲解,很清晰,不再赘述

仲裁模块的verilog实现
module sdram_arbit
(input wire arbit_clk , //系统时钟input wire arbit_rst_n , //复位信号
//sdram_init input wire [3:0] init_cmd , //初始化阶段命令input wire init_end , //初始化结束标志input wire [1:0] init_bank , //初始化阶段Bank地址input wire [12:0] init_addr , //初始化阶段数据地址
//sdram_auto_ref input wire atref_req , //自刷新请求input wire atref_end , //自刷新结束input wire [3:0] atref_cmd , //自刷新阶段命令input wire [1:0] atref_bank , //自动刷新阶段Bank地址input wire [12:0] atref_addr , //自刷新阶段数据地址
//sdram_writeinput wire wr_req , //写数据请求input wire [1:0] wr_bank , //写阶段Bank地址input wire wr_end , //一次写结束信号input wire [3:0] wr_cmd , //写阶段命令input wire [12:0] wr_addr , //写阶段数据地址input wire wr_sdram_en , //写数据有效input wire [15:0] wr_sdram_data , //要写入sdram的数据
//sdram_readinput wire rd_req , //读数据请求input wire rd_end , //一次读结束input wire [3:0] rd_cmd , //读阶段命令input wire [12:0] rd_addr , //读阶段数据地址input wire [1:0] rd_bank , //读阶段Bank地址
//输出控制逻辑 output reg atref_en , //自刷新使能output reg wr_en , //写数据使能output reg rd_en , //读数据使能
//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 reg [1:0] sdram_bank , //SDRAM Bank地址output reg [12:0] sdram_addr , //SDRAM地址总线inout wire [15:0] sdram_dq //SDRAM数据总线
);//对状态进行格雷码编码
localparam INIT = 5'b0_0001 , //初始状态ARBIT = 5'b0_0010 , //仲裁状态ATREF = 5'b0_0100 , //自动刷新状态WRITE = 5'b0_1000 , //写状态READ = 5'b1_0000 ; //读状态//命令定义
localparam NOP = 4'b0111 ; //空操作指令reg [3:0] sdram_cmd ; //写入SDRAM命令
reg [4:0] state ;
reg [4:0] state_next ; //SDRAM时钟使能
assign sdram_cke = 1'b1;//wr_sdram_data:写入 SDRAM 的数据
assign sdram_dq = (wr_sdram_en == 1'b1) ? wr_sdram_data : 16'bz;//片选信号,行地址选通信号,列地址选通信号,写使能信号组成sdram命令
assign {sdram_cs_n, sdram_ras_n, sdram_cas_n, sdram_we_n} = sdram_cmd;//第一段状态机,状态寄存器
always@(posedge arbit_clk or negedge arbit_rst_n)beginif(!arbit_rst_n)state <= INIT; elsestate <= state_next;
end//第二段状态机,组合逻辑描述状态转移
always@(*)beginstate_next= INIT;case(state)INIT: state_next = (init_end) ? ARBIT : INIT ;ARBIT: //注意优先级if(atref_req) //自动刷新state_next = ATREF;else if(wr_req) //写state_next = WRITE;else if(rd_req) //读state_next = READ;elsestate_next = ARBIT;ATREF: state_next = (atref_end) ? ARBIT : ATREF ; WRITE: state_next = (wr_end) ? ARBIT : WRITE ; READ: state_next = (rd_end) ? ARBIT : READ ; default:state_next = INIT;endcase
end//第三段状态机,时序逻辑描述输出
always@(*)begincase(state) INIT: beginsdram_cmd <= init_cmd;sdram_bank <= init_bank;sdram_addr <= init_addr;endATREF: beginsdram_cmd <= atref_cmd;sdram_bank <= atref_bank;sdram_addr <= atref_addr;endWRITE: beginsdram_cmd <= wr_cmd;sdram_bank <= wr_bank;sdram_addr <= wr_addr;endREAD: beginsdram_cmd <= rd_cmd;sdram_bank <= rd_bank;sdram_addr <= rd_addr;enddefault: beginsdram_cmd <= NOP;sdram_bank <= 2'b11; //全部拉高sdram_addr <= 13'h1fff;endendcase
end//atref_en:自动刷新使能
always@(posedge arbit_clk or negedge arbit_rst_n)beginif(!arbit_rst_n)atref_en <= 1'b0;else if((state == ARBIT) && (atref_req)) //仲裁状态且有自动刷新请求atref_en <= 1'b1;else if(atref_end)atref_en <= 1'b0;
end//wr_en:写数据使能
always@(posedge arbit_clk or negedge arbit_rst_n)beginif(!arbit_rst_n)wr_en <= 1'b0;else if((state == ARBIT) && (!atref_req) && (wr_req)) //仲裁状态且自动刷新请求无效,写请求有效wr_en <= 1'b1;else if(wr_end)wr_en <= 1'b0;
end//rd_en:读数据使能
always@(posedge arbit_clk or negedge arbit_rst_n)beginif(!arbit_rst_n)rd_en <= 1'b0; //仲裁状态且自动刷新请求无效,写请求无效,读请求有效else if((state == ARBIT) && (!atref_req) && (!wr_req) && (rd_req))rd_en <= 1'b1;else if(rd_end)rd_en <= 1'b0;
endendmodule
sdram控制器
最终我们即可将四个模块以及仲裁模块例化到顶层模块中,该顶层模块为sdram_ctrl。
//顶层模块——sdram控制器,包含仲裁模块、初始化、自动刷新、读写模块
module sdram_ctrl
(input wire sdram_clk , //sdram时钟input wire sdram_rst_n , //sdram复位信号 output wire init_end , //SDRAM 初始化完成标志
//SDRAM写端口input wire sdram_wr_req , //写SDRAM请求信号input wire [23:0] sdram_wr_addr , //SDRAM写操作的地址input wire [9:0] wr_burst_len , //写sdram时数据突发长度input wire [15:0] sdram_data_in , //写入SDRAM的数据output wire sdram_wr_ack , //写SDRAM响应信号
//SDRAM读端口input wire sdram_rd_req , //读SDRAM请求信号input wire [23:0] sdram_rd_addr , //SDRAM读操作的地址input wire [9:0] rd_burst_len , //读sdram时数据突发长度output wire [15:0] sdram_data_out , //从SDRAM读出的数据output wire sdram_rd_ack , //读SDRAM响应信号
//FPGA与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_bank , // SDRAM Bank地址output wire [12:0] sdram_addr , // SDRAM 地址总线inout wire [15:0] sdram_dq // SDRAM 数据总线
);//sdram_init
wire [3:0] init_cmd ; //初始化阶段写入sdram的指令
wire [1:0] init_bank ; //初始化阶段Bank地址
wire [12:0] init_addr ; //初始化阶段地址数据,辅助预充电操作
//sdram_a_ref
wire atref_req ; //自动刷新请求
wire atref_end ; //自动刷新结束标志
wire [3:0] atref_cmd ; //自动刷新阶段写入sdram的指令
wire [1:0] atref_bank ; //自动刷新阶段Bank地址
wire [12:0] atref_addr ; //地址数据,辅助预充电操作
wire atref_en ; //自动刷新使能
//sdram_write
wire wr_en ; //写使能
wire wr_end ; //一次写结束信号
wire [3:0] wr_sdram_cmd ; //写阶段命令
wire [1:0] wr_sdram_bank ; //写数据阶段Bank地址
wire [12:0] wr_sdram_addr ; //写阶段数据地址
wire wr_sdram_en ; //SDRAM写使能
wire [15:0] wr_sdram_data ; //写入SDRAM的数据
//sdram_read
wire rd_en ; //读使能
wire rd_end ; //一次突发读结束
wire [3:0] rd_sdram_cmd ; //读数据阶段写入sdram的指令
wire [1:0] rd_sdram_bank ; //读阶段Bank地址
wire [12:0] rd_sdram_addr ; //读阶段数据地址//实例化sdram初始化模块
sdram_init sdram_init_inst(.init_clk (sdram_clk ), //系统时钟,频率100MHz.init_rst_n (sdram_rst_n ), //复位信号,低电平有效.init_cmd (init_cmd ), //初始化阶段写入sdram的指令.init_bank (init_bank ), //初始化阶段Bank地址.init_addr (init_addr ), //初始化阶段地址数据,辅助预充电操作.init_end (init_end ) //初始化结束信号
);//实例化仲裁模块
sdram_arbit sdram_arbit_inst
(.arbit_clk (sdram_clk ), .arbit_rst_n (sdram_rst_n ),
//初始化 .init_cmd (init_cmd ), //初始化阶段命令.init_end (init_end ), //初始化结束标志.init_bank (init_bank ), //初始化阶段Bank地址.init_addr (init_addr ), //初始化阶段数据地址
//自动刷新 .atref_req (atref_req ), //自刷新请求.atref_end (atref_end ), //自刷新结束.atref_cmd (atref_cmd ), //自刷新阶段命令.atref_bank (atref_bank ), //自动刷新阶段Bank地址.atref_addr (atref_addr ), //自刷新阶段数据地址.atref_en (atref_en ), //自刷新使能
//写.wr_req (sdram_wr_req ), //写数据请求.wr_end (wr_end ), //一次写结束信号.wr_en (wr_en ), //写数据使能 .wr_cmd (wr_sdram_cmd ), //写阶段命令.wr_bank (wr_sdram_bank ), //写阶段Bank地址.wr_addr (wr_sdram_addr ), //写阶段数据地址.wr_sdram_en (wr_sdram_en ), //写数据有效 .wr_sdram_data (wr_sdram_data ), //要写入sdram的数据
//读 .rd_req (sdram_rd_req ), //读数据请求.rd_end (rd_end ), //一次读结束.rd_en (rd_en ), //读数据使能.rd_cmd (rd_sdram_cmd ), //读阶段命令.rd_addr (rd_sdram_addr ), //读阶段数据地址.rd_bank (rd_sdram_bank ), //读阶段Bank地址
//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_bank (sdram_bank ), //SDRAM Bank地址.sdram_addr (sdram_addr ), //SDRAM地址总线.sdram_dq (sdram_dq ) //SDRAM数据总线
);//实例化自动刷新模块
sdram_atref sdram_atref_inst
(.atref_clk (sdram_clk ), .atref_rst_n (sdram_rst_n ),.init_end (init_end ), //初始化结束信号.atref_en (atref_en ), //自动刷新使能.atref_req (atref_req ), //自动刷新请求.atref_cmd (atref_cmd ), //自动刷新阶段写入sdram的指令.atref_bank (atref_bank ), //自动刷新阶段Bank地址.atref_addr (atref_addr ), //地址数据,辅助预充电操作.atref_end (atref_end ) //自动刷新结束标志
);//实例化写模块
sdram_write sdram_write_inst
(.wr_clk (sdram_clk ), .wr_rst_n (sdram_rst_n ), .init_end (init_end ), //初始化结束信号.wr_en (wr_en ), //写使能.wr_addr (sdram_wr_addr ), //写SDRAM地址.wr_data (sdram_data_in ), //待写入SDRAM的数据(写FIFO传入).wr_burst_len (wr_burst_len ), //写突发SDRAM字节数.wr_ack (sdram_wr_ack ), //写SDRAM响应信号.wr_end (wr_end ), //一次突发写结束.wr_sdram_cmd (wr_sdram_cmd ), //写数据阶段写入sdram的指令.wr_sdram_bank (wr_sdram_bank ), //写数据阶段Bank地址.wr_sdram_addr (wr_sdram_addr ), //地址数据,辅助预充电操作.wr_sdram_en (wr_sdram_en ), //数据总线输出使能.wr_sdram_data (wr_sdram_data ) //写入SDRAM的数据
);//实例化读模块
sdram_read sdram_read_inst
(.rd_clk (sdram_clk ), .rd_rst_n (sdram_rst_n ), .init_end (init_end ), //初始化结束信号.rd_en (rd_en ), //读使能.rd_addr (sdram_rd_addr ), //读SDRAM地址.rd_data (sdram_dq ), //自SDRAM中读出的数据.rd_burst_len (rd_burst_len ), //读突发SDRAM字节数.rd_ack (sdram_rd_ack ), //读SDRAM响应信号.rd_end (rd_end ), //一次突发读结束.rd_sdram_cmd (rd_sdram_cmd ), //读数据阶段写入sdram的指令.rd_sdram_bank (rd_sdram_bank ), //读数据阶段Bank地址.rd_sdram_addr (rd_sdram_addr ), //地址数据,辅助预充电操作.rd_sdram_data (sdram_data_out ) //SDRAM读出的数据
);endmodule
生成的RTL图如下:

















