前面一文中,我们已经对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_write
input 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_read
input 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数据总线
);
最终给出仲裁模块的输入输出端口:
首先我们确定优先级:
上电初始化——自动刷新——写操作——读操作(先写在读,防止数据被覆盖)
根据上面的分析,得到如下的状态图:
仲裁模块:负责发出自动刷新、读写模块的使能信号 (注意优先级)
四个模块:负责发出相应操作的请求信号
这里的状态不复杂且大佬博文中也有讲解,很清晰,不再赘述
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_write
input 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_read
input 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)begin
if(!arbit_rst_n)
state <= INIT;
else
state <= state_next;
end
//第二段状态机,组合逻辑描述状态转移
always@(*)begin
state_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;
else
state_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@(*)begin
case(state)
INIT: begin
sdram_cmd <= init_cmd;
sdram_bank <= init_bank;
sdram_addr <= init_addr;
end
ATREF: begin
sdram_cmd <= atref_cmd;
sdram_bank <= atref_bank;
sdram_addr <= atref_addr;
end
WRITE: begin
sdram_cmd <= wr_cmd;
sdram_bank <= wr_bank;
sdram_addr <= wr_addr;
end
READ: begin
sdram_cmd <= rd_cmd;
sdram_bank <= rd_bank;
sdram_addr <= rd_addr;
end
default: begin
sdram_cmd <= NOP;
sdram_bank <= 2'b11; //全部拉高
sdram_addr <= 13'h1fff;
end
endcase
end
//atref_en:自动刷新使能
always@(posedge arbit_clk or negedge arbit_rst_n)begin
if(!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)begin
if(!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)begin
if(!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;
end
endmodule
最终我们即可将四个模块以及仲裁模块例化到顶层模块中,该顶层模块为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图如下: