本文仍跟着大佬继续学习,来完成SDRAM控制器中添加读写FIFO。
在此之前之前我们已经学习了sdram控制器的编写方式以及如何手写FIFO和FIFO ip核的调用。
读写FIFO模块设计
FIFO不仅能解决多bit数据跨时钟域的问题,还能缓存数据,防止SDRAM操作冲突时,数据被覆盖或者丢失的情况。
虽然我们采用仲裁模块免去操作冲突的问题,但是仍可能出现某些时刻读写操作被忽略的情况,例如刷新请求和写请求同时发出的时候,根据优先级会选择自动刷新,写操作被忽略,从而造成写数据的丢失。因此我们采用FIFO来对数据缓存,防止数据读取遗漏问题出现。
通过下图可看到,我们设计了两个FIFO——写FIFO模块,读FIFO模块
写FIFO模块:将要写入的数据存放在写FIFO中,待SDRAM响应了写FIFO发出的写请求后,即可从写FIFO中读取数据作为数据源写入SDRAM
在该模块,当写FIFO存放的数据量大于一次突发写长度数据量时就将写FIFO的写请求sdram_wr_req 拉高
读FIFO模块:用户侧发起读请求,待SDRAM响应了读FIFO发出读请求后,即可从SDRAM中读取的数据,被读取的数据则写入到读FIFO中,用户侧即可从读FIFO中读取数据。在该模块,当读FIFO存放的数据量小于突发长度,且读使能信号有效,此时就让读FIFO的读请求信号
sdram_rd_req 拉高
。
异步FIFO的调用
读写FIFO采用双端口异步FIFO,直接IP核调用即可。
FIFO控制器
调用异步FIFO后,我们将其例化到FIFO控制器模块中,例化两次,分别是写FIFO和读FIFO。
写FIFO:数据写入到FIFO,此时的输入信号为写FIFO的输入信号(用户端连接);数据从FIFO读出后写进sdram,因此此时FIFO的输出信号对应sdram的写有关信号。
读FIFO:SDRAM中的数据读出,读出的数据写入到读FIFO中,因此读FIFO的输入信号对应sdram的读有关信号;然后将写入到读FIFO的数据读出,此时的输出信号为读FIFO的输出信号(用户端连接)。
//写FIFO
fifo_data wr_fifo_data(
//用户接口
.wrclk (wr_fifo_wr_clk ), //写时钟
.wrreq (wr_fifo_wr_req ), //写请求
.data (wr_fifo_wr_data), //写数据
//SDRAM接口
.rdclk (sdram_clk ), //读时钟
.rdreq (sdram_wr_ack ), //读请求
.q (sdram_data_in ), //读数据
.rdusedw (wr_fifo_num ), //FIFO中的数据量
.wrusedw ( ),
.aclr (~sdram_rst_n || wr_fifo_rst) //清零信号
);
//读FIFO
fifo_data rd_fifo_data(
//sdram接口
.wrclk (sdram_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 (~sdram_rst_n || rd_fifo_rst) //清零信号
);
又由于读写响应信号为异步信号,因此为降低亚稳态,我们打拍同步。同步后取下降沿,因为读写响应信号是高电平有效,采集到下降沿,说明一次读写响应结束。
最终读写FIFO控制器的代码如下:
//读写FIFO控制器
module fifo_ctrl
(
input wire sdram_clk , //sdram时钟
input wire sdram_rst_n , //sdram复位信号
//写fifo信号
input wire wr_fifo_wr_clk , //写FIFO写时钟
input wire wr_fifo_rst , //写复位信号
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数据突发长度
output wire [9:0] wr_fifo_num , //写fifo中的数据量
//读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_fifo_rst , //读复位信号
output wire [15:0] rd_fifo_rd_data , //读FIFO读数据
output wire [9:0] rd_fifo_num , //读fifo中的数据量
input wire read_valid , //SDRAM读使能
input wire init_end , //SDRAM初始化完成标志
//SDRAM写信号
input wire 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 wire sdram_rd_ack , //SDRAM响应
input wire [15:0] sdram_data_out , //读出SDRAM数据
output reg sdram_rd_req , //SDRAM读请求
output reg [23:0] sdram_rd_addr //SDRAM读地址
);
wire sdram_wr_ack_fall ; //写响应信号下降沿
wire sdram_rd_ack_fall ; //读响应信号下降沿
reg sdram_wr_ack_d1 ; //写响应打1拍
reg sdram_wr_ack_d2 ; //写响应打2拍
reg sdram_rd_ack_d1 ; //读响应打1拍
reg sdram_rd_ack_d2 ; //读响应打2拍
//写响应信号打拍同步,降低亚稳态
always@(posedge sdram_clk or negedge sdram_rst_n)begin
if(!sdram_rst_n)begin
sdram_wr_ack_d1 <= 1'b0;
sdram_wr_ack_d2 <= 1'b0;
end
else begin
sdram_wr_ack_d1 <= sdram_wr_ack;
sdram_wr_ack_d2 <= sdram_wr_ack_d1;
end
end
//读响应信号打拍同步,降低亚稳态
always@(posedge sdram_clk or negedge sdram_rst_n)begin
if(!sdram_rst_n)begin
sdram_rd_ack_d1 <= 1'b0;
sdram_rd_ack_d2 <= 1'b0;
end
else begin
sdram_rd_ack_d1 <= sdram_rd_ack;
sdram_rd_ack_d2 <= sdram_rd_ack_d1;
end
end
//检测读写响应信号下降沿,下降沿位置表明读写响应结束
assign sdram_wr_ack_fall = (sdram_wr_ack_d2 & ~sdram_wr_ack_d1);
assign sdram_rd_ack_fall = (sdram_rd_ack_d2 & ~sdram_rd_ack_d1);
//sdram_wr_addr:sdram写地址
always@(posedge sdram_clk or negedge sdram_rst_n)begin
if(!sdram_rst_n)
sdram_wr_addr <= 24'd0;
else if(wr_fifo_rst)
sdram_wr_addr <= sdram_wr_b_addr; //复位fifo则地址为sdram首地址
else if(sdram_wr_ack_fall) //一次突发写结束,更改sdram写地址
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 sdram_clk or negedge sdram_rst_n)begin
if(!sdram_rst_n)
sdram_rd_addr <= 24'd0;
else if(rd_fifo_rst)
sdram_rd_addr <= sdram_rd_b_addr;
else if(sdram_rd_ack_fall) //一次突发读结束,更改读地址
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
end
//sdram_wr_req,sdram_rd_req:读写请求信号
always@(posedge sdram_clk or negedge sdram_rst_n)begin
if(!sdram_rst_n)
begin
sdram_wr_req <= 1'b0;
sdram_rd_req <= 1'b0;
end
else if(init_end) //初始化完成后响应读写请求
begin //优先执行写操作,防止写入SDRAM中的数据丢失
if(wr_fifo_num >= wr_burst_len)begin //写FIFO中的数据量达到写突发长度,写请求拉高
sdram_rd_req <= 1'b0;
sdram_wr_req <= 1'b1;
end //读FIFO中的数据量小于读突发长度,且读使能信号有效,则读请求拉高
else if((rd_fifo_num < rd_burst_len) && (read_valid))begin
sdram_wr_req <= 1'b0;
sdram_rd_req <= 1'b1;
end
else begin
sdram_wr_req <= 1'b0;
sdram_rd_req <= 1'b0;
end
end
else begin
sdram_wr_req <= 1'b0;
sdram_rd_req <= 1'b0;
end
end
//实例化读写FIFO
//写FIFO
fifo_data wr_fifo_data(
//用户接口
.wrclk (wr_fifo_wr_clk ), //写时钟
.wrreq (wr_fifo_wr_req ), //写请求
.data (wr_fifo_wr_data), //写数据
//SDRAM接口
.rdclk (sdram_clk ), //读时钟
.rdreq (sdram_wr_ack ), //读请求
.q (sdram_data_in ), //读数据
.rdusedw (wr_fifo_num ), //FIFO中的数据量
.wrusedw ( ),
.aclr (~sdram_rst_n || wr_fifo_rst) //清零信号
);
//读FIFO
fifo_data rd_fifo_data(
//sdram接口
.wrclk (sdram_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 (~sdram_rst_n || rd_fifo_rst) //清零信号
);
endmodule
SDRAM控制器添加读写FIFO
该部分就是将sdram控制器和FIFO控制器都例化到一个模块中为sdram_top,也就是带有读写FIFO的sdram控制器。
module sdram_top
(
input wire sdram_clk , //sdram时钟
input wire sdram_rst_n , //sdram复位信号
input wire clk_out , //sdram相位偏移时钟(直接给SDRAM芯片)
//写FIFO信号
input wire wr_fifo_wr_clk , //写FIFO写时钟
input wire wr_fifo_rst , //写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数据突发长度
output wire [9:0] wr_fifo_num , //写fifo中的数据量
//读FIFO信号
input wire rd_fifo_rd_clk , //读FIFO读时钟
input wire rd_fifo_rst , //读复位信号
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数据突发长度
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_out , //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的L-Bank地址线
output wire [12:0] sdram_addr , //SDRAM地址总线
output wire [1:0] sdram_dqm , //SDRAM数据掩码
inout wire [15:0] sdram_dq //SDRAM数据总线
);
//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_out:SDRAM芯片时钟
assign sdram_clk_out = clk_out;
//sdram_dqm:SDRAM数据掩码
assign sdram_dqm = 2'b00;
//FIFO控制器的例化
fifo_ctrl fifo_ctrl_inst(
//system signal
.sdram_clk (sdram_clk ), //SDRAM控制时钟
.sdram_rst_n (sdram_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_fifo_rst (wr_fifo_rst ), //写清零信号
.wr_fifo_num (wr_fifo_num ), //写fifo中的数据量
//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_fifo_rst (rd_fifo_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的例化
sdram_ctrl sdram_ctrl_inst(
.sdram_clk (sdram_clk ), //系统时钟
.sdram_rst_n (sdram_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_bank (sdram_bank ), // SDRAM L-Bank地址线
.sdram_addr (sdram_addr ), // SDRAM 地址总线
.sdram_dq (sdram_dq ) // SDRAM 数据总线
);
endmodule
生成的RTL: