题解 | #异步FIFO#
异步FIFO
https://www.nowcoder.com/practice/40246577a1a04c08b3b7f529f9a268cf
`timescale 1ns/1ns
/***************************************RAM*****************************************/
module dual_port_RAM #(parameter DEPTH = 16,
parameter WIDTH = 8)(
input wclk
,input wenc
,input [$clog2(DEPTH)-1:0] waddr //深度对2取对数,得到地址的位宽。
,input [WIDTH-1:0] wdata //数据写入
,input rclk
,input renc
,input [$clog2(DEPTH)-1:0] raddr //深度对2取对数,得到地址的位宽。
,output reg [WIDTH-1:0] rdata //数据输出
);
reg [WIDTH-1:0] RAM_MEM [0:DEPTH-1];
always @(posedge wclk) begin
if(wenc)
RAM_MEM[waddr] <= wdata;
end
always @(posedge rclk) begin
if(renc)
rdata <= RAM_MEM[raddr];
end
endmodule
/***************************************AFIFO*****************************************/
module asyn_fifo#(
parameter WIDTH = 8,
parameter DEPTH = 16
)(
input wclk ,
input rclk ,
input wrstn ,
input rrstn ,
input winc ,
input rinc ,
input [WIDTH-1:0] wdata ,
output wire wfull ,
output wire rempty ,
output wire [WIDTH-1:0] rdata
);
//写时钟域信号定义
reg [$clog2(DEPTH):0] w_addr_bin; //这里的信号定义的位宽都是5位,也就是说比正常的信号要多一位,这个主要是用来进行空满的判别以及循环
reg [$clog2(DEPTH):0] w_addr_gray; //在0到15之间,最高位都是0,而后面的在变,在16到31之间,最高位都是1,后面的在变,但是变的和之前的前十五个一样
reg [$clog2(DEPTH):0] wtr_addr_gray_ff1; //在二进制码中,当后四位相同,最高位不同时,两者之间相差刚好就是一个循环也就是存储深度,
reg [$clog2(DEPTH):0] wtr_addr_gray_ff2; //在格雷码中,当后三位相同最高位和次高位皆不同时,两者在二进制码中差一个循环,但是换到格雷码就是DEPTH + (DEPTH >> 1)个位置,整个运算就是二进制的10000(差值)需要换算到格雷码
wire wenc; //写工作使能,在输入的写指令下和写时钟下的未满情况下拉高
//读时钟域信号定义
reg [$clog2(DEPTH):0] r_addr_bin;
reg [$clog2(DEPTH):0] r_addr_gray;
reg [$clog2(DEPTH):0] rtw_addr_gray_ff1;
reg [$clog2(DEPTH):0] rtw_addr_gray_ff2;
wire renc; //读工作使能,在输入的读指令和读时钟下的未空情况下拉高
//双端口RAM读写使能信号
assign wenc = winc & !wfull;
assign renc = rinc & !rempty;
//*************************写时钟域*************************//
//二进制写地址递增
always@(posedge wclk or negedge wrstn)
begin
if(!wrstn)
w_addr_bin <= 'b0;
else if(wenc)
w_addr_bin <= w_addr_bin + 1'b1;
else
w_addr_bin <= w_addr_bin;
end
//二进制地址转格雷码地址
always@(posedge wclk or negedge wrstn)
begin
if(!wrstn)
w_addr_gray <= 'b0;
else //这里若是用组合逻辑,下面的同步就要打三拍,但是这里是时序逻辑就可以下面打两拍,因为这里已经是一拍了
//w_addr_gray <= {w_addr_bin[$clog2(DEPTH)] , w_addr_bin[$clog2(DEPTH):1] ^ w_addr_bin[$clog2(DEPTH) - 1:1]};
w_addr_gray <= (w_addr_bin >> 1) ^ w_addr_bin; //两种方法都可以
end
//读格雷码地址同步到写时钟域(这里只需打两拍操作,原因是上面的格雷码的生成时已经打了一拍了,另外打三拍的原因是两拍要同步到写时钟域,一拍是为了消除竞争冒险,主要是怕组合逻辑的中间态被采集到)
always@(posedge wclk or negedge wrstn)
begin
if(!wrstn)
begin
rtw_addr_gray_ff1 <= 'b0;
rtw_addr_gray_ff2 <= 'b0;
end
else
begin
rtw_addr_gray_ff1 <= r_addr_gray;
rtw_addr_gray_ff2 <= rtw_addr_gray_ff1;
end
end
//*************************读时钟域*************************//
//二进制读地址递增
always@(posedge rclk or negedge rrstn)
begin
if(!rrstn)
r_addr_bin <= 'b0;
else if(renc)
r_addr_bin <= r_addr_bin + 1'b1;
else
r_addr_bin <= r_addr_bin;
end
//二进制地址转格雷码地址
always@(posedge rclk or negedge rrstn)
begin
if(!rrstn)
r_addr_gray <= 'b0;
else
r_addr_gray <= (r_addr_bin >> 1) ^ r_addr_bin;
end
//写格雷码地址同步到读时钟域
always@(posedge rclk or negedge rrstn)
begin
if(!rrstn)
begin
wtr_addr_gray_ff1 <= 'b0;
wtr_addr_gray_ff2 <= 'b0;
end
else
begin
wtr_addr_gray_ff1 <= w_addr_gray;
wtr_addr_gray_ff2 <= wtr_addr_gray_ff1;
end
end
//****************************空满标志产生*****************************//
assign wfull = w_addr_gray == (rtw_addr_gray_ff2 + DEPTH + (DEPTH >> 1)) ? 1'b1 : 1'b0; //这里所用的判断是格雷码判断,也可以用二进制码判断,但是需要把格雷码转换为二进制码
assign rempty = (r_addr_gray == wtr_addr_gray_ff2) ? 1'b1 : 1'b0;
//此处的满标志的判断就是在写时钟域下,写的地址要比读的地址整整大一圈,就是刚好一个循环,若是我们之前设定的地址都是4位的,那么此处就无法判断
//但是我们是5位,最高位可以作为循环位,所以在判断条件可以为,在二进制码中,最高位写地址和读打两拍到写的地址不一致,但是低四位都一致,这样的就是刚好套了一圈,两者的差值为DEPTH,这就是写满
//但是在格雷码中,两者就是二进制的差值为DEPTH,换算过来格雷码就是DEPTH + (DEPTH >> 1),这样也就是格雷码的套圈
//同样此处的空标志就是在读时钟域下,读的地址要和写打两拍到读的地址相同,是完全相同,之前所想的套圈的问题在最高位完美解决,最高位判断套圈
dual_port_RAM
#( .DEPTH (DEPTH),
.WIDTH (WIDTH)
)
u_dual_port_RAM
(
.wclk (wclk),
.wenc (wenc),
.waddr (w_addr_bin[$clog2(DEPTH) - 1 : 0]), //深度对2取对数,得到地址的位宽。
.wdata (wdata), //数据写入
.rclk (rclk),
.renc (renc),
.raddr (r_addr_bin[$clog2(DEPTH) - 1 : 0]), //深度对2取对数,得到地址的位宽。
.rdata (rdata) //数据输出
);
endmodule
查看11道真题和解析