blob: d15ac7a6b2038ebfe34b4d610a69ebf2f49adb42 [file] [log] [blame]
/******************************************************************************
The MIT License (MIT)
Copyright (c) 2019 Alchitry
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*****************************************************************************
This is an asynchronous fifo. That means it has two independently clocked
interfaces that allow you to write data from one clock domain and read
it from another.
This is a first-word-fall-through fifo meaning that dout is valid whenever
empty is 0. If you want to perform a read, simply check if empty is 0 and
if it is read the value of dout and set rget high to advance to the next
value.
*/
module AsyncFIFO
#(
parameter SIZE = 4, // Size of the data
parameter DEPTH = 8 // DEPTH must be a power of 2
)(
input wire wclk, // write clock
input wire wrst, // write reset
input wire [SIZE-1:0] din, // write data
input wire wput, // write flag (1 = write)
output reg full, // full flag (1 = full)
input wire rclk, // read clock
input wire rrst, // read reset
output wire [SIZE-1:0] dout, // read data
input wire rget, // data read flag (1 = get next entry)
output reg empty // empty flag (1 = empty)
);
localparam ADDR_SIZE = $clog2(DEPTH); // size of RAM address
// write clock domain
reg [ADDR_SIZE-1:0] waddr; // write address
reg [ADDR_SIZE-1:0] wsync [2:0]; // write sync dffs
reg [ADDR_SIZE-1:0] gwsync;
// read clock domain
reg [ADDR_SIZE-1:0] raddr; // read address
reg [ADDR_SIZE-1:0] rsync [2:0]; // read sync dffs
reg [ADDR_SIZE-1:0] grsync;
// dual port RAM
reg write_en;
reg [ADDR_SIZE-1:0] ram_raddr;
AsyncRAM #(.SIZE(SIZE), .DEPTH(DEPTH)) ram(
.wclk(wclk),
.waddr(waddr),
.write_data(din),
.write_en(write_en),
.rclk(rclk),
.raddr(ram_raddr),
.read_data(dout));
reg [ADDR_SIZE-1:0] waddr_gray; // gray-encoded version of waddr
reg [ADDR_SIZE-1:0] wnext_gray; // gray-encoded version of waddr + 1
reg [ADDR_SIZE-1:0] raddr_gray; // gray-encoded version of raddr
reg wrdy, rrdy; // write and read ready flags
reg [ADDR_SIZE-1:0] wnext;
reg [ADDR_SIZE-1:0] read_sync;
always @(*) begin
write_en = 0; // default to not writing
// convert the various values to their gray-encoded versions
wnext = waddr + 1;
// This doesn't seem to compile
// waddr_gray = waddr[ADDR_SIZE-1:1] ^ waddr;
// wnext_gray = wnext[ADDR_SIZE-1:1] ^ wnext;
// raddr_gray = raddr[ADDR_SIZE-1:1] ^ raddr;
// From https://en.wikipedia.org/wiki/Gray_code
waddr_gray = waddr ^ (waddr >> 1);
wnext_gray = wnext ^ (wnext >> 1);
raddr_gray = raddr ^ (raddr >> 1);
// if next write address isn't the read address we can write
wrdy = wnext_gray != wsync[2];
read_sync = wsync[2];
// if the current read address isn't the write address we can read
rrdy = raddr_gray != rsync[2];
// invert ready signals for full and empty
full = !wrdy;
empty = !rrdy;
// connect RAM ports
ram_raddr = raddr;
// if we have space and should write
if (wput && wrdy) begin
write_en = 1; // set write flag
end
// if we have data and a read request
if (rget && rrdy) begin
ram_raddr = raddr + 1; // set to next address to keep up with reads
end
end
always @(posedge wclk) begin
if (wrst) begin
waddr <= ADDR_SIZE'b0;
wsync[0] <= ADDR_SIZE'b0;
wsync[1] <= ADDR_SIZE'b0;
wsync[2] <= ADDR_SIZE'b0;
gwsync <= ADDR_SIZE'b0;
end else begin
gwsync <= waddr_gray;
// cross clock domains with synchronizers
wsync[0] <= grsync;
wsync[1] <= wsync[0];
wsync[2] <= wsync[1];
// if we have space and should write increment write address
if (wput && wrdy) waddr <= waddr + 1;
end
end
always @(posedge rclk) begin
if (rrst) begin
raddr <= ADDR_SIZE'b0;
rsync[0] <= ADDR_SIZE'b0;
rsync[1] <= ADDR_SIZE'b0;
rsync[2] <= ADDR_SIZE'b0;
grsync <= ADDR_SIZE'b0;
end else begin
grsync <= raddr_gray;
// cross clock domains with synchronizers
rsync[0] <= gwsync;
rsync[1] <= rsync[0];
rsync[2] <= rsync[1];
// if we have data and a read request increment read address
if (rget && rrdy) raddr <= raddr + 1;
end
end
endmodule