我在SystemVerilog中编写了一个异步FIFO,用于时钟域穿越,并且读/空操作正常。当我尝试写入直到其写满时,full标志变高,但是写操作将推入数据的时间比预期的要多(覆盖一个尚未读取的点)。
这很奇怪,因为当我查看仿真结果时,写入逻辑处于已满状态,而写入则处于低状态,但仍会写入RAM。这是代码和一个简单的测试台。
`timescale 1ns / 1ps
module fifo_async
#(parameter DATA_WIDTH = 8,
parameter ADDR_WIDTH = 4,
parameter MEM_SIZE = 2**ADDR_WIDTH)
(input logic clka,
input logic clkb,
input logic reset,
input logic rd_en,
input logic wr_en,
input logic [DATA_WIDTH-1:0] wrdata,
output logic [DATA_WIDTH-1:0] rdata,
output logic empty,
output logic full
);
// ram inputs //
logic write;
logic read;
logic rd;
logic [ADDR_WIDTH-1:0] waddr;
logic [ADDR_WIDTH-1:0] raddr;
logic [DATA_WIDTH-1:0] wdata;
// top pointer in clock domain A and B //
logic [ADDR_WIDTH-1:0] top_ptrA;
logic [ADDR_WIDTH-1:0] top_grayA;
logic [ADDR_WIDTH-1:0] top_grayA_tmp;
logic [ADDR_WIDTH-1:0] top_grayB;
// bottom pointer in clock domain B and A //
logic [ADDR_WIDTH-1:0] bot_ptrB;
logic [ADDR_WIDTH-1:0] bot_grayB;
logic [ADDR_WIDTH-1:0] bot_grayB_tmp;
logic [ADDR_WIDTH-1:0] bot_grayA;
// used for full flag logic
logic [ADDR_WIDTH-1:0] top_ptr_minus_one;
logic [ADDR_WIDTH-1:0] top_ptr_minus_one_grayA ;
// dual port ram //
dual_port_ram_async #(.DATA_WIDTH(DATA_WIDTH),.ADDR_WIDTH(ADDR_WIDTH))
async_ram (.clk(clka),.write, .read(rd), .raddr, .waddr, .wdata,.rdata);
// converts binary to gray code //
function automatic [ADDR_WIDTH-1:0] gray(input [ADDR_WIDTH-1:0] ptr);
gray = (ptr >> 1) ^ ptr;
endfunction
//====================================================================//
//===================== CLOCK A DOMAIN (WRITE) =======================//
//====================================================================//
typedef enum logic [1:0] {RESETA=2'b00,IDLEA = 2'b01, WRITEA=2'b11,FULL = 2'b10} states_a;
states_a state_a, next_a;
// Current State Logic -- sequential logic //
always_ff @(posedge clka) begin
if (reset) begin
state_a <= RESETA;
end
else
state_a <= next_a;
end
// next state logic //
always_comb begin
unique case(state_a)
RESETA:
next_a = IDLEA;
IDLEA:
if (wr_en) next_a = WRITEA;
else next_a = IDLEA;
WRITEA:
if (top_grayA == bot_grayA) next_a = FULL;
else if (!write) next_a = IDLEA;
else next_a = WRITEA;
FULL:
if (top_ptr_minus_one_grayA != bot_grayA) next_a = IDLEA;
else next_a = FULL;
endcase
end
// moore outputs logic //
always_comb begin
unique case(state_a)
RESETA:
begin
write = 0;
full = 0;
wdata = '0;
end
IDLEA:
begin
write = 1'b0;
full = 1'b0;
end
WRITEA:
begin
write = wr_en;
wdata = wrdata;
end
FULL:
begin
write = 1'b0;
full = 1'b1;
end
endcase
end
// datapath //
always @(*) begin
if (reset) begin
top_ptr_minus_one = 0;
top_ptrA = 0;
waddr = 0;
end
else if (write && !full) begin
top_ptr_minus_one = top_ptrA;
top_ptrA = top_ptrA + 1;
waddr = top_ptrA;
end
end
// convert top_ptrA to graycode //
assign top_grayA = gray(top_ptrA);
assign top_ptr_minus_one_grayA = gray(top_ptr_minus_one);
// Double flop top_grayA to clock domain B //
always_ff @(posedge clkb) begin
top_grayA_tmp <= top_grayA;
top_grayB <= top_grayA_tmp;
end
//====================================================================//
//======================== CLOCK B DOMAIN (READ) ======================//
//====================================================================//
typedef enum logic [1:0] {RESETB=2'b00,IDLEB = 2'b01, READB=2'b11,EMPTY = 2'b10} states_b;
states_b state_b, next_b;
// current state logic //
always_ff @(posedge clkb) begin
if (reset)
state_b <= RESETB;
else
state_b <= next_b;
end
//next state logic //
always_comb begin
unique case(state_b)
RESETB:
next_b = EMPTY;
EMPTY:
if (top_grayB != bot_grayB) next_b = IDLEB;
READB:
if (top_grayB == bot_grayB) next_b = EMPTY;
else next_b = IDLEB;
IDLEB:
if (rd_en) next_b = READB;
endcase;
end
// moore outpur logic //
always_comb begin
unique case(state_b)
RESETB:
begin
read = 1'b0;
empty = 1'b0;
end
EMPTY:
begin
read = 1'b0;
empty = 1'b1;
end
READB:
begin
read = 1'b1;
empty = 1'b0;
end
IDLEB:
begin
read = 1'b0;
empty = 1'b0;
end
endcase
end
// Datapath //
always_comb begin
if (reset) begin
bot_ptrB = 0;
raddr = 0;
end
else if (read && !empty) begin
bot_ptrB = bot_ptrB + 1;
raddr = bot_ptrB;
end
end
// convert bot_ptrB to gray code //
assign bot_grayB = gray(bot_ptrB);
// double flop to cross bot_grayB to clock domain A
always_ff @(posedge clka) begin
bot_grayB_tmp <= bot_grayB;
bot_grayA <= bot_grayB_tmp;
rd <= read;
end
endmodule
双端口内存模块:
`timescale 1ns / 1ps
module dual_port_ram_async
#(parameter DATA_WIDTH = 8,
parameter ADDR_WIDTH = 3,
parameter MEM_SIZE = 2**ADDR_WIDTH
)(
input logic clk,
input logic write,
input logic read,
input logic [ADDR_WIDTH-1:0] raddr,
input logic [ADDR_WIDTH-1:0] waddr,
input logic [DATA_WIDTH-1:0] wdata,
output logic [DATA_WIDTH-1:0] rdata
);
bit [DATA_WIDTH-1:0] mem [0:MEM_SIZE-1];
always @(write) begin
if (write) begin mem[waddr] = wdata; end
else begin mem[waddr] = mem[waddr]; end
end
assign rdata = (read)?mem[raddr]:'z;
endmodule
简单的测试台:
`timescale 1ns / 1ps
module fifo_async_tb;
localparam DATA_WIDTH = 8;
localparam ADDR_WIDTH = 8;
localparam Ta = 10;
localparam Tb = 20;
//inputs
logic clka = 0;
logic clkb = 0;
logic reset;
logic rd_en;
logic wr_en;
logic [DATA_WIDTH-1:0] wrdata;
// outputs
logic [DATA_WIDTH-1:0] rdata;
logic empty;
logic full;
fifo_async fifo(.*);
always #(Ta/2) clka = ~clka;
always #(Tb/2) clkb = ~clkb;
initial begin
rd_en = 0;
wr_en = 0;
wrdata = '0;
reset = 1;
#40
reset = 0;
#15
#10
rd_en = 1;
#50;
rd_en = 0;
// start test
repeat(18) begin
wr_en = 1;
wrdata++;
#(Ta/2);
wr_en = 0;
#(Ta/2);
end
$stop;
end
endmodule
下面是结果。正如您在黄色标记处看到的那样,ram和异步fifo的write
都较低(wr_en在tb处较高),但mem(最底部的信号)仍被写入
无法保证这些过程的顺序:
always @(*) begin
if (reset) begin
top_ptr_minus_one = 0;
top_ptrA = 0;
waddr = 0;
end
else if (write && !full) begin
top_ptr_minus_one = top_ptrA;
top_ptrA = top_ptrA + 1;
waddr = top_ptrA;
end
end
assign top_grayA = gray(top_ptrA);
assign
可以在always
之前或之后发生,因此top_grayA
可能以递增之前或之后的值结束。
[除此之外,您在这里使用always @*
而不是always_comb
,所以也许您打算创建锁存器。但这通常不建议用于FPGA。