Skip to content

Commit

Permalink
Added small afifo variant
Browse files Browse the repository at this point in the history
Signed-off-by: Anderson Ignacio da Silva <anderson@aignacio.com>
  • Loading branch information
aignacio committed Sep 10, 2024
1 parent d5ad655 commit 5d65044
Show file tree
Hide file tree
Showing 5 changed files with 352 additions and 8 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/regression.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ jobs:

strategy:
matrix:
python-version: [3.9]
python-version: [3.12]
group: [1, 2, 3, 4, 5]

steps:
Expand All @@ -24,7 +24,7 @@ jobs:
sudo apt install -y --no-install-recommends make g++ perl python3 autoconf flex bison libfl2 libfl-dev zlibc zlib1g zlib1g-dev
git clone https://github.com/verilator/verilator.git
cd verilator
git checkout v4.106
git checkout v5.026
autoconf
./configure
make -j $(nproc)
Expand All @@ -40,7 +40,7 @@ jobs:
SIM: verilator
CXX: 'g++'
CC: 'gcc'
PYTHON_VERSION: 3.9
PYTHON_VERSION: 3.12
FULL_REGRESSION: 1
TOX_TESTENV_PASSENV: GITHUB_ACTIONS
run: tox -- --splits 5 --group ${{ matrix.group }}
2 changes: 2 additions & 0 deletions run_docker.sh
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
173 changes: 173 additions & 0 deletions src/cdc_async_fifo_w_ocup.sv
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
169 changes: 169 additions & 0 deletions tb/test_afifo_w_ocup.py
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
)
10 changes: 5 additions & 5 deletions tox.ini
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
[tox]
envlist = py39
envlist = py312
skipsdist = True

[gh-actions]
python =
3.9: py39
3.12: py312

[testenv]
setenv =
Expand All @@ -18,9 +18,9 @@ deps =
pytest-sugar
pytest-xdist
pytest-split
cocotb-bus == 0.1.1
cocotb-test == 0.2.0
cocotb == 1.5.1
cocotb-bus >= 0.1.1
cocotb-test >=0.2.0
cocotb >= 1.8.1

commands =
pytest -n auto {posargs}
Expand Down

0 comments on commit 5d65044

Please sign in to comment.