I wrote an asynchronous FIFO in SystemVerilog for clock domain crossing and the read/empty is working. When I try to write until it is full, the full flag goes high, but the write will push on data one more time than it is suppose to (overwrites one spot that has not been read).
This is strange because when I look at the simulation results the state that the write logic is in is full and write is low, but it still writes to the ram. Here is the code and a simple testbench.
`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
dual port ram module:
`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
Simple testbench:
`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
Below are the results. As you can see at the yellow marker, write is low for both the ram and async fifo (wr_en is high from the tb), but mem (signal at the very bottom) is still writen to