FIFO在数据处理过程中是十分重要的。
同步FIFO比较简单,面试过程中手撕代码可能会用到。
module sFIFO #(
parameter DATA_WIDTH = 8,
ADDR_WIDTH = 4
)
(
input clk,
input reset_p,
input wr_en,
input [DATA_WIDTH-1:0]wr_data,
input rd_en,
output reg[DATA_WIDTH-1:0]rd_data,
output full,
output empty
);
reg [ADDR_WIDTH-1:0] wr_addr;
reg [ADDR_WIDTH-1:0] rd_addr;
reg [DATA_WIDTH-1:0] ram[2**ADDR_WIDTH-1:0];//定义16个8位宽的寄存器
reg [ADDR_WIDTH:0] cnt;
//默认先写后读
/读写地址
always@(posedge clk or negedge reset_p)begin
if(!reset_p)
wr_addr <= 'd0;
else if(wr_en &&(!full))
wr_addr <= wr_addr + 'd1;
else wr_addr <= wr_addr;
end
always@(posedge clk or negedge reset_p)begin
if(!reset_p)
rd_addr <= 'd0;
else if(rd_en &&(!empty))
rd_addr <= rd_addr + 'd1;
else rd_addr <= rd_addr;
end
//读写数据
integer i;
always@(posedge clk or negedge reset_p)begin
if(!reset_p)begin //FIFO内所有数都为0
for(i=0; i<16; i=i+1)begin
ram[i] <= 0;
end
end
else if(wr_en)begin
ram[wr_addr] <= wr_data;
end
end
always@(posedge clk or negedge reset_p)begin
if(!reset_p)begin//FIFO内所有数都为0
rd_data <= 'd0;
end
else if(wr_en)begin
rd_data <= ram[rd_addr];
end
end
空满
always@(posedge clk or negedge reset_p)begin
if(!reset_p) cnt <= 'd0;
else if(wr_en && (!rd_en) && (cnt!=16))
cnt <= cnt + 'd1;
else if(rd_en && (!wr_en) && (cnt!=0))
cnt <= cnt - 'd1;
else;
end
assign full = (cnt == 16);
assign empty = (cnt == 0);
endmodule
对于异步FIFO来说,需要关注的点为跨时钟域以及空满判断的问题;
解决思路:
跨时钟域:1、对地址打两拍,避免亚稳态的产生;2、需要对读写地址进行格雷码转换,避免地址同步过程中产生意外。
空满判断:由于是异步电路,两时钟的频率可能不相同,若默认先写后读,若写时钟频率大于读时钟频率,则写满判断和读空判断条件一致(读写地址相同)。为解决此问题,提前给出读写指针,位宽比地址位宽大1,由此,可以得到写满标志为最高位不同,其他位相同。转换为格雷码的写满标志为最高2位不同,其他位相同。
module asFIFO#(
parameter DATA_WIDTH = 8,
ADDR_WIDTH = 4
)
(
input reset_p,
input wr_clk,
input wr_en,
input [DATA_WIDTH-1:0]wr_data,
input rd_clk,
input rd_en,
output reg[DATA_WIDTH-1:0]rd_data,
output wr_full,
output rd_empty
);
reg [ADDR_WIDTH:0]wr_addr_ptr;
reg [ADDR_WIDTH:0]rd_addr_ptr;
wire [ADDR_WIDTH-1:0]wr_addr;
wire [ADDR_WIDTH-1:0]rd_addr;
//跨时钟域,打两拍
wire [ADDR_WIDTH:0]wr_addr_gray;
reg [ADDR_WIDTH:0]wr_addr_gray_r;
reg [ADDR_WIDTH:0]wr_addr_gray_r1;
wire [ADDR_WIDTH:0]rd_addr_gray;
reg [ADDR_WIDTH:0]rd_addr_gray_r;
reg [ADDR_WIDTH:0]rd_addr_gray_r1;
reg [DATA_WIDTH-1:0] ram[2**ADDR_WIDTH-1:0];
//地址指针
always@(posedge wr_clk or negedge reset_p)
if(!reset_p)
wr_addr_ptr <= 'd0;
else if(wr_en && (!wr_full))
wr_addr_ptr <= wr_addr_ptr +1;
else;
always@(posedge rd_clk or negedge reset_p)
if(!reset_p)
rd_addr_ptr <= 'd0;
else if(rd_en && (!rd_empty))
rd_addr_ptr <= rd_addr_ptr +1;
else;
//格雷码转换
assign wr_addr_gray = (wr_addr_ptr>>1 )^ wr_addr_ptr;
assign rd_addr_gray = (rd_addr_ptr>>1 )^ rd_addr_ptr;
//打拍
always@(posedge wr_clk )begin
wr_addr_gray_r <= wr_addr_gray;
wr_addr_gray_r1 <= wr_addr_gray_r;
end
always@(posedge rd_clk )begin
rd_addr_gray_r <= rd_addr_gray;
rd_addr_gray_r1 <= rd_addr_gray_r;
end
//空满标志,重要
assign wr_full =(wr_addr_gray == {~rd_addr_gray_r1[ADDR_WIDTH:ADDR_WIDTH-1],rd_addr_gray_r1[ADDR_WIDTH-2:0]}) ;
assign rd_empty = (rd_addr_gray == wr_addr_gray_r1) ;
assign wr_addr = wr_addr_ptr[ADDR_WIDTH-1:0];
assign rd_addr = rd_addr_ptr[ADDR_WIDTH-1:0];
///读写数据
integer i;
always@(posedge wr_clk or negedge reset_p)
if(!reset_p)
for(i=0;i<2**ADDR_WIDTH;i=i+1) begin
ram[i] <= 'd0;
end
else if(wr_en &&(!wr_full))
ram[wr_addr] <= wr_data;
else ;
always@(posedge rd_clk or negedge reset_p)
if(!reset_p)
rd_data <= 'd0;
else if(rd_en && (!rd_empty))
rd_data <= ram[rd_addr];
else ;
endmodule
以上为面试过程中可能问到的问题,在FPGA的实际开发过程中,只需要我们调用IP核即可进行开发。
本项目使用的是ACX720开发板,配有DDR3 SDRAM。
为了实现FIFO模块与DDR控制器接口的对接,需要设计fifo2axi模块,将普通的FIFO接口转换为AXI接口,实现将FIFO里的数据读出然后储存在DDR储存器中以及将DDR储存器里的数据读出到FIFO中。
AXI4 总线的一大特征是它有 5 个独立的传输通道,这些通道都只支持单向传输。
作为类比,SPI 总线有 2 条单向传输通道:MISO, MOSI。SPI 输入和输出的数据,大路朝天,各走一条。
而作为对比, IIC 协议则只有 SDA 一条双向通道,输入输出数据只能在这一条通道上分时双向传输。
单向传输的通道意味着两端的终端节点是有身份差距的,好比水只能从上游流到下流。在 AXI 总线传输中,通道两端分为 Master 主机与 Slave 从机,主机总是发起读写请求的一方。常见的主机有CPU、DMA,而存储介质控制器(比如 DDR 控制器)则是典型的从机。主机可能通过从机读取或者写入存储介质。而显然从机不可能主动向 CPU 写入数据。
通道的读/写定义都是根据主机来定义的,那么五个通道都有谁呢:
读地址 (AR) read address
读数据 (R) read data
写地址 (AW) write address
写数据 (W) write data
写回复 (R) write response
以上内容摘自空白MAX的微博,对AXI协议有更加详细的描述!
以下为fifo2axi模块的设计思路:
localparam S_IDLE = 7'b0000001,
S_ARB = 7'b0000010,
S_WR_ADDR = 7'b0000100,
S_WR_DATA = 7'b0001000,
S_WR_RESP = 7'b0010000,
S_RD_ADDR = 7'b0100000,
S_RD_RESP = 7'b1000000;
reg [6:0]c_state;
reg [6:0]n_state;
wire wr_ddr3_req;
wire rd_ddr3_req;
reg wr_rd_poll;
reg [7:0]wr_data_cnt ;
wire[7:0]wr_req_cnt_thresh;
wire[7:0]rd_req_cnt_thresh;
assign wr_ddr3_req = (wr_fifo_rst_busy == 1'b0) && (wr_fifo_rd_cnt >= wr_req_cnt_thresh) ? 1'b1:1'b0;
assign rd_ddr3_req = (rd_fifo_rst_busy == 1'b0) && (rd_fifo_wr_cnt <= rd_req_cnt_thresh) ? 1'b1:1'b0;
//wr_rd_poll读写仲裁,默认先写后读
always@(posedge ui_clk or posedge ui_clk_sync_rst)
begin
if(ui_clk_sync_rst)
wr_rd_poll <= 1'b0;
else if(c_state == S_ARB)
wr_rd_poll <= ~wr_rd_poll;
else
wr_rd_poll <= wr_rd_poll;
end
always@(posedge ui_clk or posedge ui_clk_sync_rst)
begin
if(ui_clk_sync_rst)
c_state <= S_IDLE;
else
c_state <= n_state;
end
//状态机
always@(*)
begin
case(c_state)
S_IDLE:
begin
if(init_calib_complete && mmcm_locked)
n_state = S_ARB;
else
n_state = S_IDLE;
end
S_ARB:
begin
if((wr_ddr3_req == 1'b1) && (wr_rd_poll == 1'b0))
n_state = S_WR_ADDR;
else if((rd_ddr3_req == 1'b1) && (wr_rd_poll == 1'b1))
n_state = S_RD_ADDR;
else
n_state = S_ARB;
end
//写地址
S_WR_ADDR:
begin
if(m_axi_awvalid && m_axi_awready)
n_state = S_WR_DATA;
else
n_state = S_WR_ADDR;
end
//写数据
S_WR_DATA:
begin
if(m_axi_wready && m_axi_wvalid && m_axi_wlast)
n_state = S_WR_RESP;
else
n_state = S_WR_DATA;
end
//写反馈
S_WR_RESP:
begin
if(m_axi_bvalid && m_axi_bready && (m_axi_bresp == 2'b00) && (m_axi_bid == AXI_ID))
n_state = S_ARB;
else if(m_axi_bvalid && m_axi_bready)
n_state = S_IDLE;
else
n_state = S_WR_RESP;
end
//读地址
S_RD_ADDR:
begin
if(m_axi_arvalid && m_axi_arready)
n_state = S_RD_RESP;
else
n_state = S_RD_ADDR;
end
//读数据+读反馈
S_RD_RESP:
begin
if(m_axi_rready && m_axi_rvalid && m_axi_rlast && (m_axi_rresp == 2'b00) && (m_axi_rid == AXI_ID))
n_state = S_ARB;
else if(m_axi_rvalid && m_axi_rready && m_axi_rlast)
n_state = S_IDLE;
else
n_state = S_RD_RESP;
end
default: n_state = S_IDLE ;
endcase
end
在5条通道的状态机完成之后,就是产生状态机中各判断信号。
包括写地址/写地址有效,写数据有效/写最后,读地址/读地址有效。
//awaaddr 的产生
always@(posedge ui_clk or posedge ui_clk_sync_rst) begin
if(ui_clk_sync_rst)
m_axi_awaddr <= WR_DDR_ADDR_BEGIN;
else if(wr_addr_clr)
m_axi_awaddr <= WR_DDR_ADDR_BEGIN;
else if(m_axi_awaddr >= WR_DDR_ADDR_END)
m_axi_awaddr <= WR_DDR_ADDR_BEGIN;
else if((c_state == S_WR_RESP) && m_axi_bvalid && m_axi_bready && (m_axi_bresp == 2'b00) && (m_axi_bid == AXI_ID))
m_axi_awaddr <= m_axi_awaddr + ((m_axi_awlen + 1'b1) << 4);
else m_axi_awaddr <= m_axi_awaddr;
end
//awvalid
always@(posedge ui_clk or posedge ui_clk_sync_rst) begin
if(ui_clk_sync_rst)
m_axi_awvalid <= 1'b0;
else if((c_state == S_WR_ADDR) && m_axi_awready && m_axi_awvalid)
m_axi_awvalid <= 1'b0;
else if(c_state == S_WR_ADDR)
m_axi_awvalid <= 1'b1;
else m_axi_awvalid <= m_axi_awvalid;
end
//wvalid
always@(posedge ui_clk or posedge ui_clk_sync_rst) begin
if(ui_clk_sync_rst)
m_axi_wvalid <= 1'b0;
else if(c_state == S_WR_DATA)
m_axi_wvalid <= 1'b1;
else if((c_state == S_WR_DATA) && m_axi_wvalid && m_axi_wready && m_axi_wlast)
m_axi_wvalid <= 1'b0;
else m_axi_wvalid <= m_axi_wvalid;
end
//wlast
always@(posedge ui_clk or posedge ui_clk_sync_rst) begin
if(ui_clk_sync_rst)
wr_data_cnt <= 1'b0;
else if(c_state == S_ARB)
wr_data_cnt <= 1'b0;
else if((c_state == S_WR_DATA) && m_axi_wvalid && m_axi_wready )
wr_data_cnt <= wr_data_cnt + 1'b1;
else wr_data_cnt <= wr_data_cnt;
end
always@(posedge ui_clk or posedge ui_clk_sync_rst) begin
if(ui_clk_sync_rst)
m_axi_wlast <= 1'b0;
else if((c_state == S_WR_DATA) && m_axi_wvalid && m_axi_wready && m_axi_wlast)
m_axi_wlast <= 1'b0;
else if((c_state == S_WR_DATA) && m_axi_awlen == 8'd0 )
m_axi_wlast <= 1'b1;
else if((c_state == S_WR_DATA) && m_axi_wvalid && m_axi_wready && (wr_data_cnt == m_axi_awlen - 1'b1))
m_axi_wlast <= 1'b1;
else m_axi_wlast <= m_axi_wlast;
end
//araddr
always@(posedge ui_clk or posedge ui_clk_sync_rst) begin
if(ui_clk_sync_rst)
m_axi_araddr <= RD_DDR_ADDR_BEGIN;
else if(rd_addr_clr)
m_axi_araddr <= RD_DDR_ADDR_BEGIN;
else if(m_axi_araddr >= RD_DDR_ADDR_END)
m_axi_araddr <= RD_DDR_ADDR_BEGIN;
else if((c_state == S_RD_RESP) && m_axi_rvalid && m_axi_rready && (m_axi_rresp == 2'b00) && m_axi_rlast && (m_axi_rid == AXI_ID))
m_axi_araddr <= m_axi_araddr + ((m_axi_awlen + 1'b1) << 4);
else m_axi_araddr <= m_axi_araddr;
end
//arvalid
always@(posedge ui_clk or posedge ui_clk_sync_rst) begin
if(ui_clk_sync_rst)
m_axi_arvalid <= 1'b0;
else if(c_state == S_RD_ADDR && m_axi_arready && m_axi_arvalid)
m_axi_arvalid <= 1'b0;
else if(c_state == S_RD_ADDR)
m_axi_arvalid <= 1'b1;
else m_axi_arvalid <= m_axi_arvalid;
end
DDR3的读写请求是根据读写FIFO数据量确定的,当写FIFO中数据大于一定量(设置为突发读写数据长度)时,产生写DDR请求;当读FIFO中的数据小于一定量时,产生读DDR请求。
assign wr_ddr3_req = (wr_fifo_rst_busy == 1'b0) && (wr_fifo_rd_cnt >= wr_req_cnt_thresh) ? 1'b1:1'b0;
assign rd_ddr3_req = (rd_fifo_rst_busy == 1'b0) && (rd_fifo_wr_cnt <= rd_req_cnt_thresh) ? 1'b1:1'b0;