-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: Anderson Ignacio da Silva <anderson@aignacio.com>
- Loading branch information
Showing
5 changed files
with
352 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
#!/bin/bash | ||
docker run -it --rm -v $(pwd):/temp/ -w /temp/ aignacio/rtl bash |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,173 @@ | ||
/** | ||
* File: async_gp_fifo.sv | ||
* Description: General purpose Asynchronous FIFO, | ||
* based on the following articles: | ||
* -> http://www.sunburst-design.com/papers/CummingsSNUG2002SJ_FIFO1.pdf | ||
* -> https://zipcpu.com/blog/2018/07/06/afifo.html* File: async_fifo.sv | ||
* Author: Anderson Ignacio da Silva <aignacio@aignacio.com> | ||
* | ||
* MIT License | ||
* 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. | ||
*/ | ||
module cdc_async_fifo_w_ocup # ( | ||
parameter int SLOTS = 2, | ||
parameter int WIDTH = 8 | ||
) ( | ||
// Clock domain 1 | ||
input clk_wr, | ||
input arst_wr, | ||
input wr_en_i, | ||
input [WIDTH-1:0] wr_data_i, | ||
output logic wr_full_o, | ||
output logic [$clog2(SLOTS):0] ocup_o, | ||
|
||
// Clock domain 2 | ||
input clk_rd, | ||
input arst_rd, | ||
input rd_en_i, | ||
output logic [WIDTH-1:0] rd_data_o, | ||
output logic rd_empty_o | ||
); | ||
`define IDX_PTR w_wr_bin_ptr_ff[$clog2(SLOTS)-1:0] // Valid index pointer | ||
|
||
// Naming convention | ||
// ptr - pointer | ||
// wr - write | ||
// rd - read | ||
// bin - binary | ||
// | ||
// Pointers convention used follows the below pattern: | ||
// -> CLKDOMAIN(w/r)_TYPE(wr/rd)_ENCODING(bin/gray)_ptr | ||
// Example: | ||
// w_wr_bin_ptr - Write binary pointer in write clock domain | ||
// r_wr_gray_ptr - Write gray pointer in read clock domain | ||
// META_... - Metastable transition FF | ||
typedef logic [$clog2(SLOTS):0] ptr_t; | ||
logic [SLOTS-1:0] [WIDTH-1:0] array_fifo_ff; | ||
|
||
ptr_t w_wr_bin_ptr_ff, next_w_wr_bin_ptr; | ||
ptr_t w_rd_gry_ptr_ff, w_rd_bin_ptr; // We only bring to wr domain | ||
// the rd gray encoded ptr once | ||
// we only use it to gen the full | ||
// flag, and crossing gray encoding | ||
// it's more stable than bin encoding | ||
|
||
ptr_t r_rd_bin_ptr_ff, next_r_rd_bin_ptr; | ||
ptr_t r_wr_gry_ptr_ff; // We only bring to rd domain | ||
// the wr gray encoded ptr once | ||
// we only use it to gen the empty | ||
// flag, and crossing gray encoding | ||
// it's more stable than bin encoding | ||
(* async_reg = "true" *) ptr_t META_w_rd_gry_ff; | ||
(* async_reg = "true" *) ptr_t META_r_wr_gry_ff; | ||
|
||
//************************ | ||
// Functions | ||
//************************ | ||
function automatic ptr_t bin_to_gray (ptr_t input_bin); | ||
ptr_t value; | ||
value = (input_bin >> 1) ^ input_bin; | ||
return value; | ||
endfunction | ||
|
||
function automatic ptr_t gray_to_bin (ptr_t input_gray); | ||
ptr_t value; | ||
value = input_gray; | ||
for (int i=$clog2(SLOTS);i>0;i--) | ||
value[i-1] = value[i]^value[i-1]; | ||
return value; | ||
endfunction | ||
|
||
//************************ | ||
// Write logic | ||
//************************ | ||
always_comb begin : wr_pointer | ||
next_w_wr_bin_ptr = w_wr_bin_ptr_ff; | ||
w_rd_bin_ptr = gray_to_bin(w_rd_gry_ptr_ff); | ||
|
||
ocup_o = w_wr_bin_ptr_ff - w_rd_bin_ptr; | ||
|
||
wr_full_o = (w_wr_bin_ptr_ff[$clog2(SLOTS)] == ~w_rd_bin_ptr[$clog2(SLOTS)]) && | ||
(w_wr_bin_ptr_ff[$clog2(SLOTS)-1:0] == w_rd_bin_ptr[$clog2(SLOTS)-1:0]); | ||
|
||
if (wr_en_i && ~wr_full_o) begin | ||
next_w_wr_bin_ptr = w_wr_bin_ptr_ff + 'd1; | ||
end | ||
end | ||
|
||
always_ff @ (posedge clk_wr or posedge arst_wr) begin | ||
if (arst_wr) begin | ||
w_wr_bin_ptr_ff <= ptr_t'(0); | ||
META_w_rd_gry_ff <= ptr_t'(0); | ||
w_rd_gry_ptr_ff <= ptr_t'(0); | ||
//array_fifo_ff <= '0; // --> Let's make it "low power" | ||
end | ||
else begin | ||
w_wr_bin_ptr_ff <= next_w_wr_bin_ptr; | ||
// 2FF Synchronizer: | ||
// Bring RD ptr to WR domain | ||
META_w_rd_gry_ff <= bin_to_gray(r_rd_bin_ptr_ff); | ||
w_rd_gry_ptr_ff <= META_w_rd_gry_ff; | ||
|
||
if (wr_en_i && ~wr_full_o) begin | ||
array_fifo_ff[`IDX_PTR] <= wr_data_i; | ||
end | ||
end | ||
end | ||
|
||
//************************ | ||
// Read logic | ||
//************************ | ||
always_comb begin : rd_pointer | ||
next_r_rd_bin_ptr = r_rd_bin_ptr_ff; | ||
|
||
rd_empty_o = (bin_to_gray(r_rd_bin_ptr_ff) == r_wr_gry_ptr_ff); | ||
|
||
if (rd_en_i && ~rd_empty_o) begin | ||
next_r_rd_bin_ptr = r_rd_bin_ptr_ff + 'd1; | ||
end | ||
|
||
rd_data_o = array_fifo_ff[r_rd_bin_ptr_ff[$clog2(SLOTS)-1:0]]; | ||
end | ||
|
||
always_ff @ (posedge clk_rd or posedge arst_rd) begin | ||
if (arst_rd) begin | ||
r_rd_bin_ptr_ff <= ptr_t'(0); | ||
META_r_wr_gry_ff <= ptr_t'(0); | ||
r_wr_gry_ptr_ff <= ptr_t'(0); | ||
end | ||
else begin | ||
r_rd_bin_ptr_ff <= next_r_rd_bin_ptr; | ||
// 2FF Synchronizer: | ||
// Bring RD ptr to WR domain | ||
META_r_wr_gry_ff <= bin_to_gray(w_wr_bin_ptr_ff); | ||
r_wr_gry_ptr_ff <= META_r_wr_gry_ff; | ||
end | ||
end | ||
|
||
`ifndef NO_ASSERTIONS | ||
initial begin | ||
illegal_fifo_slot : assert (2**$clog2(SLOTS) == SLOTS) | ||
else $error("ASYNC FIFO Slots must be power of 2"); | ||
|
||
min_fifo_size_2 : assert (SLOTS >= 2) | ||
else $error("ASYNC FIFO size of SLOTS defined is illegal!"); | ||
end | ||
`endif | ||
endmodule |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,169 @@ | ||
#!/usr/bin/env python3 | ||
# -*- coding: utf-8 -*- | ||
# File : test_afifo_w_ocup.py | ||
# License : MIT license <Check LICENSE> | ||
# Author : Anderson Ignacio da Silva (aignacio) <anderson@aignacio.com> | ||
# Date : 12.05.2021 | ||
# Last Modified Date: 10.09.2024 | ||
# Last Modified By : Anderson Ignacio da Silva (aignacio) <anderson@aignacio.com> | ||
import random | ||
import cocotb | ||
import os | ||
import logging | ||
import pytest | ||
|
||
from logging.handlers import RotatingFileHandler | ||
from cocotb.log import SimColourLogFormatter, SimLog, SimTimeContextFilter | ||
from cocotb.regression import TestFactory | ||
from cocotb_test.simulator import run | ||
from cocotb.regression import TestFactory | ||
from cocotb.clock import Clock | ||
from cocotb_bus.drivers import Driver | ||
from cocotb.triggers import ClockCycles, FallingEdge, RisingEdge, Timer | ||
from collections import namedtuple | ||
|
||
CLK_100MHz = (10, "ns") | ||
CLK_50MHz = (20, "ns") | ||
RST_CYCLES = 2 | ||
WAIT_CYCLES = 2 | ||
|
||
class AFIFODriver(): | ||
def __init__(self, signals, debug=False, slots=0, width=0): | ||
level = logging.DEBUG if debug else logging.WARNING | ||
self.log = SimLog("afifo.log") | ||
file_handler = RotatingFileHandler("sim.log", maxBytes=(5 * 1024 * 1024), backupCount=2, mode='w') | ||
file_handler.setFormatter(SimColourLogFormatter()) | ||
self.log.addHandler(file_handler) | ||
self.log.addFilter(SimTimeContextFilter()) | ||
self.log.setLevel(level) | ||
self.log.info("SEED ======> %s",str(cocotb.RANDOM_SEED)) | ||
|
||
self.clk_wr = signals.clk_wr | ||
self.valid_wr = signals.wr_en_i | ||
self.data_wr = signals.wr_data_i | ||
self.ready_wr = signals.wr_full_o | ||
self.clk_rd = signals.clk_rd | ||
self.valid_rd = signals.rd_empty_o | ||
self.data_rd = signals.rd_data_o | ||
self.ready_rd = signals.rd_en_i | ||
self.valid_wr <= 0 | ||
self.ready_rd <= 0 | ||
self.log.setLevel(level) | ||
|
||
async def write(self, data, sync=True, **kwargs): | ||
self.log.info("[AFIFO driver] write => %x"%data) | ||
while True: | ||
await FallingEdge(self.clk_wr) | ||
self.valid_wr <= 1 | ||
self.data_wr <= data | ||
await RisingEdge(self.clk_wr) | ||
if self.ready_wr == 0: | ||
break | ||
elif kwargs["exit_full"] == True: | ||
return "FULL" | ||
self.valid_wr <= 0 | ||
return 0 | ||
|
||
async def read(self, sync=True, **kwargs): | ||
while True: | ||
await FallingEdge(self.clk_rd) | ||
if self.valid_rd == 0: | ||
data = self.data_rd.value # We capture before we incr. rd ptr | ||
self.ready_rd <= 1 | ||
await RisingEdge(self.clk_rd) | ||
break | ||
elif kwargs["exit_empty"] == True: | ||
return "EMPTY" | ||
self.log.info("[AFIFO-driver] read => %x"%data) | ||
self.ready_rd <= 0 | ||
return data | ||
|
||
async def setup_dut(dut, clk_mode): | ||
dut._log.info("Configuring clocks... -- %d", clk_mode) | ||
if clk_mode == 0: | ||
dut._log.info("50MHz - wr clk / 100MHz - rd clk") | ||
cocotb.fork(Clock(dut.clk_wr, *CLK_50MHz).start()) | ||
cocotb.fork(Clock(dut.clk_rd, *CLK_100MHz).start()) | ||
else: | ||
dut._log.info("50MHz - rd clk / 100MHz - wr clk") | ||
cocotb.fork(Clock(dut.clk_rd, *CLK_50MHz).start()) | ||
cocotb.fork(Clock(dut.clk_wr, *CLK_100MHz).start()) | ||
|
||
async def reset_dut(dut): | ||
dut.arst_wr.setimmediatevalue(0) | ||
dut.arst_rd.setimmediatevalue(0) | ||
dut._log.info("Reseting DUT") | ||
dut.arst_wr <= 1 | ||
dut.arst_rd <= 1 | ||
await ClockCycles(dut.clk_wr, RST_CYCLES) | ||
dut.arst_wr <= 1 | ||
dut.arst_rd <= 1 | ||
await ClockCycles(dut.clk_rd, RST_CYCLES) | ||
dut.arst_rd <= 0 | ||
dut.arst_wr <= 0 | ||
|
||
def randomly_switch_config(): | ||
return random.randint(0, 1) | ||
|
||
async def run_test(dut, config_clock): | ||
logging.basicConfig(filename='sim.log', encoding='utf-8', level=logging.DEBUG) | ||
MAX_SLOTS_FIFO = int(os.environ['PARAM_SLOTS']) | ||
MAX_WIDTH_FIFO = int(os.environ['PARAM_WIDTH']) | ||
TEST_RUNS = int(os.environ['TEST_RUNS']) | ||
await setup_dut(dut, config_clock) | ||
await reset_dut(dut) | ||
ff_driver = AFIFODriver(signals=dut,debug=True,slots=MAX_SLOTS_FIFO,width=MAX_WIDTH_FIFO) | ||
for i in range(TEST_RUNS): | ||
samples = [random.randint(0,(2**MAX_WIDTH_FIFO)-1) for i in range(random.randint(0,MAX_SLOTS_FIFO))] | ||
for i in samples: | ||
await ff_driver.write(i,exit_full=False) | ||
for i in samples: | ||
assert (read_value := await ff_driver.read(exit_empty=False)) == i, "%d != %d" % (read_value, i) | ||
# Testing FIFO full flag | ||
await reset_dut(dut) | ||
samples = [random.randint(0,(2**MAX_WIDTH_FIFO)-1) for i in range(MAX_SLOTS_FIFO)] | ||
for i in samples: | ||
await ff_driver.write(i,exit_full=False) | ||
assert (value := await ff_driver.write(samples[0],exit_full=True)) == "FULL", "AFIFO not signaling full correctly" | ||
# Testing FIFO empty flag | ||
await reset_dut(dut) | ||
assert (value := await ff_driver.read(exit_empty=True)) == "EMPTY", "AFIFO not signaling empty correctly" | ||
|
||
if cocotb.SIM_NAME: | ||
factory = TestFactory(run_test) | ||
factory.add_option('config_clock', [0, 1]) | ||
factory.generate_tests() | ||
|
||
@pytest.mark.parametrize("slots",[2,4,8,16,32,64,128]) | ||
def test_fifo_async_w_ocup(slots): | ||
tests_dir = os.path.dirname(os.path.abspath(__file__)) | ||
rtl_dir = tests_dir | ||
dut = "cdc_async_fifo_w_ocup" | ||
module = os.path.splitext(os.path.basename(__file__))[0] | ||
toplevel = dut | ||
verilog_sources = [ | ||
os.path.join(rtl_dir, f"../src/{dut}.sv"), | ||
] | ||
parameters = {} | ||
parameters['SLOTS'] = slots | ||
parameters['WIDTH'] = 2**random.randint(2,8) | ||
|
||
extra_env = {f'PARAM_{k}': str(v) for k, v in parameters.items()} | ||
extra_env['TEST_RUNS'] = str(random.randint(2,10)) | ||
extra_env['COCOTB_HDL_TIMEUNIT'] = "1ns" | ||
extra_env['COCOTB_HDL_TIMEPRECISION'] = "1ns" | ||
|
||
sim_build = os.path.join(tests_dir, "../run_dir/sim_build_afifo_w_ocup_"+"_".join(("{}={}".format(*i) for i in parameters.items()))) | ||
|
||
run( | ||
python_search=[tests_dir], | ||
verilog_sources=verilog_sources, | ||
toplevel=toplevel, | ||
module=module, | ||
parameters=parameters, | ||
sim_build=sim_build, | ||
extra_env=extra_env, | ||
extra_args=["--trace-fst","--trace-structs"], | ||
plus_args=["--trace"], | ||
waves=True | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters