1 /****************************************************** 2 A fifo controller verilog description. 3 ******************************************************/ 4 module fifo(datain, rd, wr, rst, clk, dataout, full, empty); 5 input [7:0] datain; 6 input rd, wr, rst, clk; 7 output [7:0] dataout; 8 output full, empty; 9 wire [7:0] dataout; 10 reg full_in, empty_in; 11 reg [7:0] mem [15:0]; 12 reg [3:0] rp, wp;//其实是一个循环读写的过程,4位二进制数刚好16个状态,也即指示16个深度 13 assign full = full_in; 14 assign empty = empty_in; 15 // memory read out 16 assign dataout = mem[rp]; 17 // memory write in 18 always@(posedge clk) begin 19 if(wr && ~full_in) mem[wp]<=datain; 20 end 21 // memory write pointer increment 22 always@(posedge clk or negedge rst) begin 23 if(!rst) wp<=0; 24 else begin 25 if(wr && ~full_in) wp<= wp+1'b1; 26 end 27 end 28 // memory read pointer increment 29 always@(posedge clk or negedge rst)begin 30 if(!rst) rp <= 0; 31 else begin 32 if(rd && ~empty_in) rp <= rp + 1'b1; 33 end 34 end 35 // Full signal generate 36 always@(posedge clk or negedge rst) begin 37 if(!rst) full_in <= 1'b0; 38 else begin 39 if( (~rd && wr)&&((wp==rp-1)||(rp==4'h0&&wp==4'hf))) 40 full_in <= 1'b1; 41 else if(full_in && rd) full_in <= 1'b0; 42 end 43 end 44 // Empty signal generate 45 always@(posedge clk or negedge rst) begin 46 if(!rst) empty_in <= 1'b1; 47 else begin 48 if((rd&&~wr)&&(rp==wp-1 || (rp==4'hf&&wp==4'h0))) 49 empty_in<=1'b1; 50 else if(empty_in && wr) empty_in<=1'b0; 51 end 52 end 53 endmodule
同步FIFO相对简单,但稍微复杂点儿的就是full和empty信号的产生,有两种方法上述代码是常用的,但不很容易理解,解释下,FIFO先入先出,可知读写地址都是从零开始递增,这样才能满足先写进的将来会被先读出来。对于同步FIFO来说,时钟是同一个,如果同时读写,那么FIFO永远都不会满。因为当写指针到FIFO尽头时,会继续从零地址开始写(假设零地址的数据已经被读出,当然就可以覆盖了),如此循环往复。那么到底空满标志如何产生:
最直观的一种情况,对于full来说,假如一直写,都还没读,此时当wrp=FIFO深度时,应该产生满标志,如果继续写,就会覆盖还未读出的数据,从而使数据失效。
对于empty来说,假如wrp=0,而rdp=FIFO深度时,应该产生空标志,如果继续读。就会从零地址开始读,而零地址要么是以前的数据要么是空的,所以…
第二种情况:wrp与rdp之间差值为1,rdp-wrp=1时如果没有读,继续写的话会发生数据覆盖;wrp-rdp=1时如果没有写继续读,会读出错误数据。
这就是程序中标志位表达形式的原因。
还有一种简单的方法产生空满标志:
并不用读写地址判定FIFO是否空满。设计一个计数器,该计数器(pt_cnt)用于指示当前周期中FIFO中数据的个数。由于FIFO中最多只有16个数据,因此采用5位计数器来指示FIFO中数据个数。具体计算如下:
l 复位的时候,pt_cnt=0;
l 如果wr_en和rd_en同时有效的时候,pt_cnt不加也不减;表示同时对FIFO进行读写操作的时候,FIFO中的数据个数不变。
l 如果wr_en有效且full=0,则pt_cont+1;表示写操作且FIFO未满时候,FIFO中的数据个数增加了1;
l 如果rd_en有效且empty=0,则pt_cont-1; 表示读操作且FIFO未满时候,FIFO中的数据个数减少了1;
l 如果pt_cnt=0的时候,表示FIFO空,需要设置empty=1;如果pt_cnt=16的时候,表示FIFO现在已经满,需要设置full=1。
该模块的程序:
1 module flag_gen(clk,rst,full,emptyp,wr_en,rd_en); 2 input clk,rst; 3 input rd_en; 4 input wr_en; 5 output full,emptyp; 6 reg full,emptyp; 7 reg[4:0]count; 8 parameter max_count=5'b01111; 9 always @ (posedge clk or negedge rst) 10 begin 11 if(!rst) 12 count<=0; 13 else 14 begin 15 case({wr_en,rd_en}) 16 2'b00:count<=count; 17 2'b01: 18 if(count!==5'b00000) 19 count<=count-1; 20 2'b10: 21 if(count!== max_count) 22 count<=count+1; 23 2'b11:count<=count; 24 endcase 25 end 26 end 27 always @(count) 28 begin 29 if(count==5'b00000) 30 emptyp<=1; 31 else 32 emptyp<=0; 33 end 34 always @(count) 35 begin 36 if(count== max_count) 37 full<=1; 38 else 39 full<=0; 40 end 41 endmodule
我们又一次见证了位拼接运算符的强大。