module fifo #( parameter DSIZE = 8, parameter ASIZE = 4 ) ( output [DSIZE-1:0] rdata, output wfull, output rempty, input [DSIZE-1:0] wdata, input winc, wclk, wrst_n, input rinc, rclk, rrst_n ); wire [ASIZE-1:0] waddr, raddr; wire [ASIZE:0] wptr, rptr, wq2_rptr, rq2_wptr; // synchronize the read pointer into the write-clock domain sync_r2w sync_r2w ( .wq2_rptr (wq2_rptr), .rptr (rptr ), .wclk (wclk ), .wrst_n (wrst_n ) ); // synchronize the write pointer into the read-clock domain sync_w2r sync_w2r ( .rq2_wptr(rq2_wptr), .wptr(wptr), .rclk(rclk), .rrst_n(rrst_n) ); //this is the FIFO memory buffer that is accessed by both the write and read clock domains. //This buffer is most likely an instantiated, synchronous dual-port RAM. //Other memory styles can be adapted to function as the FIFO buffer. fifomem #(DSIZE, ASIZE) fifomem ( .rdata(rdata), .wdata(wdata), .waddr(waddr), .raddr(raddr), .wclken(winc), .wfull(wfull), .wclk(wclk) ); //this module is completely synchronous to the read-clock domain and contains the FIFO read pointer and empty-flag logic. rptr_empty #(ASIZE) rptr_empty ( .rempty(rempty), .raddr(raddr), .rptr(rptr), .rq2_wptr(rq2_wptr), .rinc(rinc), .rclk(rclk), .rrst_n(rrst_n) ); //this module is completely synchronous to the write-clock domain and contains the FIFO write pointer and full-flag logic wptr_full #(ASIZE) wptr_full ( .wfull(wfull), .waddr(waddr), .wptr(wptr), .wq2_rptr(wq2_rptr), .winc(winc), .wclk(wclk), .wrst_n(wrst_n) ); endmodule
二、fifomem.v 生成存储实体,FIFO 的本质是RAM,所以在设计存储实体的时候有两种方法:用数组存储数据或者调用RAM的IP核 html
module fifomem #( parameter DATASIZE = 8, // Memory data word width parameter ADDRSIZE = 4 // 深度为8即地址为3位便可,这里多定义一位的缘由是用来判断是空仍是满,详细在后文讲到 ) // Number of mem address bits ( output [DATASIZE-1:0] rdata, input [DATASIZE-1:0] wdata, input [ADDRSIZE-1:0] waddr, raddr, input wclken, wfull, wclk ); `ifdef RAM //能够调用一个RAM IP核 // instantiation of a vendor's dual-port RAM my_ram mem ( .dout(rdata), .din(wdata), .waddr(waddr), .raddr(raddr), .wclken(wclken), .wclken_n(wfull), .clk(wclk) ); `else //用数组生成存储体 // RTL Verilog memory model localparam DEPTH = 1<<ADDRSIZE; // 左移至关于乘法,2^4 reg [DATASIZE-1:0] mem [0:DEPTH-1]; //生成2^4个位宽位8的数组 assign rdata = mem[raddr]; always @(posedge wclk) //当写使能有效且还未写满的时候将数据写入存储实体中,注意这里是与wclk同步的 if (wclken && !wfull) mem[waddr] <= wdata; `endif endmodule
三、sync_r2w.v 将 rclk 时钟域的格雷码形式的读指针同步到 wclk 时钟域,简单来说就是用两级寄存器同步,即打两拍 数组
module sync_r2w #( parameter ADDRSIZE = 4 ) ( output reg [ADDRSIZE:0] wq2_rptr, //读指针同步到写时钟域 input [ADDRSIZE:0] rptr, // 格雷码形式的读指针,格雷码的好处后面会细说 input wclk, wrst_n ); reg [ADDRSIZE:0] wq1_rptr; always @(posedge wclk or negedge wrst_n) if (!wrst_n) begin wq1_rptr <= 0; wq2_rptr <= 0; end else begin wq1_rptr<= rptr; wq2_rptr<=wq1_rptr; end endmodule
四、sync_w2r.v 将 wclk 时钟域的格雷码形式的写指针同步到 rclk 时钟域缓存
module sync_w2r #(parameter ADDRSIZE = 4) ( output reg [ADDRSIZE:0] rq2_wptr, //写指针同步到读时钟域 input [ADDRSIZE:0] wptr, //格雷码形式的写指针 input rclk, rrst_n ); reg [ADDRSIZE:0] rq1_wptr; always @(posedge rclk or negedge rrst_n) if (!rrst_n)begin rq1_wptr <= 0; rq2_wptr <= 0; end else begin rq1_wpt <= wptr; rq2_wptr <= rq1_wptr; end endmodule
五、rptr_empty.v 将 sync_w2r.v 同步后的写指针与 rclk 时钟域的读指针进行比较生成都空信号dom
module rptr_empty #( parameter ADDRSIZE = 4 ) ( output reg rempty, output [ADDRSIZE-1:0] raddr, //二进制形式的读指针 output reg [ADDRSIZE :0] rptr, //格雷码形式的读指针 input [ADDRSIZE :0] rq2_wptr, //同步后的写指针 input rinc, rclk, rrst_n ); reg [ADDRSIZE:0] rbin; wire [ADDRSIZE:0] rgraynext, rbinnext; // GRAYSTYLE2 pointer //将二进制的读指针与格雷码进制的读指针同步 always @(posedge rclk or negedge rrst_n) if (!rrst_n) begin rbin <= 0; rptr <= 0; end else begin rbin<=rbinnext; //直接做为存储实体的地址 rptr<=rgraynext;//输出到 sync_r2w.v模块,被同步到 wrclk 时钟域 end // Memory read-address pointer (okay to use binary to address memory) assign raddr = rbin[ADDRSIZE-1:0]; //直接做为存储实体的地址,好比链接到RAM存储实体的读地址端。 assign rbinnext = rbin + (rinc & ~rempty); //不空且有读请求的时候读指针加1 assign rgraynext = (rbinnext>>1) ^ rbinnext; //将二进制的读指针转为格雷码 // FIFO empty when the next rptr == synchronized wptr or on reset assign rempty_val = (rgraynext == rq2_wptr); //当读指针等于同步后的写指针,则为空。 always @(posedge rclk or negedge rrst_n) if (!rrst_n) rempty <= 1'b1; else rempty <= rempty_val; endmodule
六、wptr_full.v 将 sync_r2w.v 同步后的读指针与wclk 时钟域的写指针进行比较生成写满信号异步
module wptr_full #( parameter ADDRSIZE = 4 ) ( output reg wfull, output [ADDRSIZE-1:0] waddr, output reg [ADDRSIZE :0] wptr, input [ADDRSIZE :0] wq2_rptr, input winc, wclk, wrst_n ); reg [ADDRSIZE:0] wbin; wire [ADDRSIZE:0] wgraynext, wbinnext; // GRAYSTYLE2 pointer always @(posedge wclk or negedge wrst_n) if (!wrst_n) {wbin, wptr} <= 0; else {wbin, wptr} <= {wbinnext, wgraynext}; // Memory write-address pointer (okay to use binary to address memory) assign waddr = wbin[ADDRSIZE-1:0]; assign wbinnext = wbin + (winc & ~wfull); assign wgraynext = (wbinnext>>1) ^ wbinnext; //二进制转为格雷码 //----------------------------------------------------------------- assign wfull_val = (wgraynext=={~wq2_rptr[ADDRSIZE:ADDRSIZE-1],wq2_rptr[ADDRSIZE-2:0]}); //当最高位和次高位不一样其他位相同时则写指针超前于读指针一圈,即写满。后面会详细解释。 always @(posedge wclk or negedge wrst_n) if (!wrst_n) wfull <= 1'b0; else wfull <= wfull_val; endmodule
七、测试文件ide
`timescale 1ns /1ns module test(); reg [7:0] wdata; reg winc, wclk, wrst_n; reg rinc, rclk, rrst_n; wire [7:0] rdata; wire wfull; wire rempty; fifo u_fifo ( .rdata(rdata), .wfull(wfull), .rempty(rempty), .wdata (wdata), .winc (winc), .wclk (wclk), .wrst_n(wrst_n), .rinc(rinc), .rclk(rclk), .rrst_n(rrst_n) ); localparam CYCLE = 20; localparam CYCLE1 = 40; //时钟周期,单位为ns,可在此修改时钟周期。 //生成本地时钟50M initial begin wclk = 0; forever #(CYCLE/2) wclk=~wclk; end initial begin rclk = 0; forever #(CYCLE1/2) rclk=~rclk; end //产生复位信号 initial begin wrst_n = 1; #2; wrst_n = 0; #(CYCLE*3); wrst_n = 1; end initial begin rrst_n = 1; #2; rrst_n = 0; #(CYCLE*3); rrst_n = 1; end always @(posedge wclk or negedge wrst_n)begin if(wrst_n==1'b0)begin winc <= 0; rinc <= 0; end else begin winc <= $random; rinc <= $random; end end always @(posedge rclk or negedge rrst_n)begin if(rrst_n==1'b0)begin rinc <= 0; end else begin rinc <= $random; end end always@(*)begin if(winc == 1) wdata= $random ; else wdata = 0; end endmodule
八、仿真结果性能
因为截图篇幅的限制请本身验证仿真。学习