基于FPGA的目标颜色识别追踪三——FIFO(同/异步FIFO)、DDR3

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;

你可能感兴趣的:(FPGA,fpga,verilog)