还是上次那个同步FIFO,传送门在这~
上次讲的是用SystemVerilog去设计这个FIFO,那么如果用可综合的RTL代码怎么设计呢?
// write pointer
always @(posedge clock or negedge reset_n) begin
if (!reset_n) begin
wptr <= 0;
end else begin
if (valid_in & ready_in) begin
// can write into the fifo when input is valid and fifo is ready
case (size)
2'b00: begin
fifo_mem[wptr[ADDR_WIDTH-1:0]] <= data_in[7:0];
wptr <= wptr + 1;
end
2'b01: begin
fifo_mem[wptr[ADDR_WIDTH-1:0]] <= data_in[7:0];
fifo_mem[wptr[ADDR_WIDTH-1:0]+1] <= data_in[15:8];
wptr <= wptr + 2;
end
2'b10: begin
fifo_mem[wptr[ADDR_WIDTH-1:0]] <= data_in[7:0];
fifo_mem[wptr[ADDR_WIDTH-1:0]+1] <= data_in[15:8];
fifo_mem[wptr[ADDR_WIDTH-1:0]+2] <= data_in[23:16];
fifo_mem[wptr[ADDR_WIDTH-1:0]+3] <= data_in[31:24];
wptr <= wptr + 4;
上述代码的主要问题是,会存在数据写飞的情况...
数据在哪会丢失呢?确实,我们把wptr写指针的数据宽度设置好了,为5位宽,因此wptr最多为31,没毛病,可是这时候wptr[ADDR_WIDTH-1:0]+3为多少呢?这个数据会写到地址为34的FIFO中,因为不存在这个地址,然后数据就丢了...丢了
解决方法有好几种,比如我们多设计几个中间变量,把变量的宽度进行限制;比如我们把上述写指针取余,这样就不可能宽度溢出...还有很多方法
设计FIFO的方法有很多,可以加计数器,判断FIFO内部还有多少数据,甚至可以写状态机...
我设计的时候就是不想加计数器,这样可以减少硬件资源,只通过读写地址之间的关系判断是否空满...
主要思路如下(code仅供参考,欢迎讨论):
`timescale 1ns / 100ps
//******************************************************************
// Author:SJTU_chen
// Date: 2019/10/26
// Version: v1.0
// Module Name: fifo-dut
// Project Name: SystemVerilog Lab1
//*******************************************************************
module fifo_dut(
clock,reset_n,valid_in,wstrb,data_in,valid_out,data_out,ready_in
);
input clock,reset_n,valid_in;
input [1:0] wstrb;
input [63:0] data_in;
output valid_out;
output[31:0] data_out;
output ready_in;
parameter FIFO_DATA_WIDTH = 8 ;
parameter FIFO_DP = 32 ;
parameter FIFO_ADDR_WIDTH = clogb2(FIFO_DP) ;
reg [31:0] data_out;
reg valid_out;
wire enough_data;
wire enough_space;
wire ready_in;
wire [4:0] fifo_wr_addr1,fifo_wr_addr2,fifo_wr_addr3,fifo_wr_addr4,fifo_wr_addr5,fifo_wr_addr6,fifo_wr_addr7;
wire [FIFO_ADDR_WIDTH-1:0] fifo_wr_addr ; //fifo write address
wire [FIFO_ADDR_WIDTH-1:0] fifo_rd_addr ; //fifo read address
reg [FIFO_ADDR_WIDTH:0] wr_addr_ptr; //fifo write pointer
reg [FIFO_ADDR_WIDTH:0] rd_addr_ptr; //fifo read pointer
reg [FIFO_DATA_WIDTH-1:0] fifo_mem [FIFO_DP-1:0] ; //fifo data width is 8 .depth is 32
assign fifo_wr_addr1=fifo_wr_addr+1;
assign fifo_wr_addr2=fifo_wr_addr+2;
assign fifo_wr_addr3=fifo_wr_addr+3;
assign fifo_wr_addr4=fifo_wr_addr+4;
assign fifo_wr_addr5=fifo_wr_addr+5;
assign fifo_wr_addr6=fifo_wr_addr+6;
assign fifo_wr_addr7=fifo_wr_addr+7;
上述参数和中间变量设置好以后,接下来就是读写数据啦
// write data to fifo
always @(posedge clock or negedge reset_n)
if (reset_n &&valid_in && enough_space) begin
if(wstrb==2'b00) begin
fifo_mem[fifo_wr_addr] <= data_in[7:0];
end
else if (wstrb==2'b01) begin
fifo_mem[fifo_wr_addr] <= data_in[7:0];
fifo_mem[fifo_wr_addr1] <= data_in[15:8];
end
else if (wstrb==2'b10) begin
fifo_mem[fifo_wr_addr] <= data_in[7:0];
fifo_mem[fifo_wr_addr1] <= data_in[15:8];
fifo_mem[fifo_wr_addr2] <= data_in[23:16];
fifo_mem[fifo_wr_addr3] <= data_in[31:24];
end
else if (wstrb==2'b11) begin
fifo_mem[fifo_wr_addr] <= data_in[7:0];
fifo_mem[fifo_wr_addr1] <= data_in[15:8];
fifo_mem[fifo_wr_addr2] <= data_in[23:16];
fifo_mem[fifo_wr_addr3] <= data_in[31:24];
fifo_mem[fifo_wr_addr4] <= data_in[39:32];
fifo_mem[fifo_wr_addr5] <= data_in[47:40];
fifo_mem[fifo_wr_addr6] <= data_in[55:48];
fifo_mem[fifo_wr_addr7] <= data_in[63:56];
end
end
else begin
fifo_mem[fifo_wr_addr] <= fifo_mem[fifo_wr_addr];
end
// read data
always @(posedge clock or negedge reset_n)
begin
if(reset_n == 1'b0) begin
data_out <= {32{1'b0}} ;
end
else if (enough_data) begin
data_out[7:0] <= fifo_mem[fifo_rd_addr] ;
data_out[15:8] <= fifo_mem[fifo_rd_addr+1] ;
data_out[23:16] <= fifo_mem[fifo_rd_addr+2] ;
data_out[31:24] <= fifo_mem[fifo_rd_addr+3] ;
end
else begin
data_out <=data_out;
end
end
指针变化
// write pointer
always @(posedge clock or negedge reset_n)
begin
if(reset_n == 1'b0)
wr_addr_ptr <= {(FIFO_ADDR_WIDTH+1){1'b0}} ;
else if (valid_in && enough_space)begin
if(wstrb==2'b00) begin
wr_addr_ptr <= wr_addr_ptr + 1'b1 ;
end
else if(wstrb==2'b01) begin
wr_addr_ptr <= wr_addr_ptr + 2'b10 ;
end
else if(wstrb==2'b10) begin
wr_addr_ptr <= wr_addr_ptr + 3'b100 ;
end
else if(wstrb==2'b11) begin
wr_addr_ptr <= wr_addr_ptr + 4'b1000 ;
end
end
else begin
wr_addr_ptr <= wr_addr_ptr;
end
end
//read pointer
always @(posedge clock or negedge reset_n)
begin
if(reset_n == 1'b0)
rd_addr_ptr <= {(FIFO_ADDR_WIDTH+1){1'b0}} ;
else if (enough_data)
rd_addr_ptr <= rd_addr_ptr + 3'b100 ;
else
rd_addr_ptr <= rd_addr_ptr;
end
状态变化
// valid_out state
always @(posedge clock or negedge reset_n)
begin
if(reset_n == 1'b0) begin
valid_out <='0;
end
else if (enough_data) begin
valid_out <='1;
end
else begin
valid_out <='0;
end
end
判断内部空间:
//ready_in
assign ready_in = enough_space;
assign fifo_wr_addr = wr_addr_ptr[FIFO_ADDR_WIDTH-1:0];
assign fifo_rd_addr = rd_addr_ptr[FIFO_ADDR_WIDTH-1:0];
assign enough_data = ((wr_addr_ptr >= rd_addr_ptr+4)||(rd_addr_ptr >= wr_addr_ptr+4))?1:0;
wire addr_select1,addr_select2,addr_select3;
assign addr_select1 =((fifo_rd_addr>fifo_wr_addr)&&( fifo_rd_addr-fifo_wr_addr<8))?1:0;
assign addr_select2 =((fifo_rd_addr<fifo_wr_addr)&&( fifo_wr_addr-fifo_rd_addr>=24))?1:0;
assign addr_select3 = (wr_addr_ptr[FIFO_ADDR_WIDTH]^rd_addr_ptr[FIFO_ADDR_WIDTH])?1:0;
assign addr_select4 = (wr_addr_ptr[FIFO_ADDR_WIDTH]~^rd_addr_ptr[FIFO_ADDR_WIDTH])?1:0;
assign enough_space =((addr_select1&addr_select3)|(addr_select2&addr_select4))?0:1;
上述代码中,addr_select判断读指针和写指针的位置,1.当读指针大于写指针,两个指针位置小于8,并且wr_addr_ptr的最高位不相同时则接近满。2.当写指针大于读指针,两个指针位置大于24,并且wr_addr_ptr的最高位相同时则接近满。这个是在深度为32时的情况,当深度为其他深度n时,则数值24应该改为n-8。
function integer clogb2 (input integer size);
begin
size = size - 1;
for (clogb2=1; size>1; clogb2=clogb2+1)
size = size >> 1;
end
endfunction // clogb2
上述代码实现的就是一个取对数函数,用来生成地址宽度与数据深度之间的关系。
上述代码经过questa sim10.6c验证,应该是没问题,但是不保证完全正确,如有bug,欢迎私信交流~