From f0c9f699872ff1441cbe21e629f96cd4e7816dad Mon Sep 17 00:00:00 2001 From: Alex Forencich Date: Tue, 4 Feb 2025 11:49:50 -0800 Subject: [PATCH] axis: Add COBS encoder module and testbench Signed-off-by: Alex Forencich --- rtl/axis/taxi_axis_cobs_encode.f | 3 + rtl/axis/taxi_axis_cobs_encode.sv | 467 ++++++++++++++++++ tb/axis/taxi_axis_cobs_encode/Makefile | 46 ++ .../test_taxi_axis_cobs_encode.py | 250 ++++++++++ .../test_taxi_axis_cobs_encode.sv | 56 +++ 5 files changed, 822 insertions(+) create mode 100644 rtl/axis/taxi_axis_cobs_encode.f create mode 100644 rtl/axis/taxi_axis_cobs_encode.sv create mode 100644 tb/axis/taxi_axis_cobs_encode/Makefile create mode 100644 tb/axis/taxi_axis_cobs_encode/test_taxi_axis_cobs_encode.py create mode 100644 tb/axis/taxi_axis_cobs_encode/test_taxi_axis_cobs_encode.sv diff --git a/rtl/axis/taxi_axis_cobs_encode.f b/rtl/axis/taxi_axis_cobs_encode.f new file mode 100644 index 0000000..dcbc7d8 --- /dev/null +++ b/rtl/axis/taxi_axis_cobs_encode.f @@ -0,0 +1,3 @@ +taxi_axis_cobs_encode.sv +taxi_axis_fifo.sv +taxi_axis_if.sv diff --git a/rtl/axis/taxi_axis_cobs_encode.sv b/rtl/axis/taxi_axis_cobs_encode.sv new file mode 100644 index 0000000..9042a27 --- /dev/null +++ b/rtl/axis/taxi_axis_cobs_encode.sv @@ -0,0 +1,467 @@ +// SPDX-License-Identifier: CERN-OHL-S-2.0 +/* + +Copyright (c) 2016-2025 FPGA Ninja, LLC + +Authors: +- Alex Forencich + +*/ + +`resetall +`timescale 1ns / 1ps +`default_nettype none + +/* + * AXI4-Stream consistent overhead byte stuffing (COBS) encoder + */ +module taxi_axis_cobs_encode # +( + // append zero for in band framing + parameter logic APPEND_ZERO = 1'b1 +) +( + input wire logic clk, + input wire logic rst, + + /* + * AXI4-Stream input (sink) + */ + taxi_axis_if.snk s_axis, + + /* + * AXI4-Stream output (source) + */ + taxi_axis_if.src m_axis +); + +// check configuration +if (m_axis.DATA_W != 8 || s_axis.DATA_W != 8) + $fatal(0, "Error: Interface DATA_W parameter mismatch (instance %m)"); + +// state register +localparam [1:0] + INPUT_STATE_IDLE = 2'd0, + INPUT_STATE_SEGMENT = 2'd1, + INPUT_STATE_FINAL_ZERO = 2'd2, + INPUT_STATE_APPEND_ZERO = 2'd3; + +logic [1:0] input_state_reg = INPUT_STATE_IDLE, input_state_next; + +localparam [0:0] + OUTPUT_STATE_IDLE = 1'd0, + OUTPUT_STATE_SEGMENT = 1'd1; + +logic [0:0] output_state_reg = OUTPUT_STATE_IDLE, output_state_next; + +logic [7:0] input_count_reg = 8'd0, input_count_next; +logic [7:0] output_count_reg = 8'd0, output_count_next; +logic fail_frame_reg = 1'b0, fail_frame_next; + +// internal datapath +logic [7:0] m_axis_tdata_int; +logic m_axis_tvalid_int; +logic m_axis_tready_int_reg = 1'b0; +logic m_axis_tlast_int; +logic m_axis_tuser_int; +wire m_axis_tready_int_early; + +logic s_axis_tready_mask; + +taxi_axis_if #(.DATA_W(8), .USER_EN(1), .USER_W(1)) code_fifo_in(), code_fifo_out(); +taxi_axis_if #(.DATA_W(8)) data_fifo_in(), data_fifo_out(); + +assign s_axis.tready = code_fifo_in.tready && data_fifo_in.tready && s_axis_tready_mask; + +taxi_axis_fifo #( + .DEPTH(256), + .FRAME_FIFO(0) +) +code_fifo_inst ( + .clk(clk), + .rst(rst), + + /* + * AXI4-Stream input (sink) + */ + .s_axis(code_fifo_in), + + /* + * AXI4-Stream output (source) + */ + .m_axis(code_fifo_out), + + /* + * Pause + */ + .pause_req(), + .pause_ack(), + + /* + * Status + */ + .status_depth(), + .status_depth_commit(), + .status_overflow(), + .status_bad_frame(), + .status_good_frame() +); + +taxi_axis_fifo #( + .DEPTH(256), + .FRAME_FIFO(0) +) +data_fifo_inst ( + .clk(clk), + .rst(rst), + + /* + * AXI4-Stream input (sink) + */ + .s_axis(data_fifo_in), + + /* + * AXI4-Stream output (source) + */ + .m_axis(data_fifo_out), + + /* + * Pause + */ + .pause_req(), + .pause_ack(), + + /* + * Status + */ + .status_depth(), + .status_depth_commit(), + .status_overflow(), + .status_bad_frame(), + .status_good_frame() +); + +always @* begin + input_state_next = INPUT_STATE_IDLE; + + input_count_next = input_count_reg; + + fail_frame_next = fail_frame_reg; + + s_axis_tready_mask = 1'b0; + + code_fifo_in.tdata = 8'd0; + code_fifo_in.tvalid = 1'b0; + code_fifo_in.tlast = 1'b0; + code_fifo_in.tuser = 1'b0; + + data_fifo_in.tdata = s_axis.tdata; + data_fifo_in.tvalid = 1'b0; + data_fifo_in.tlast = 1'b0; + data_fifo_in.tuser = 1'b0; + + case (input_state_reg) + INPUT_STATE_IDLE: begin + // idle state + s_axis_tready_mask = 1'b1; + fail_frame_next = 1'b0; + + if (s_axis.tready && s_axis.tvalid) begin + // valid input data + + if (s_axis.tdata == 8'd0 || (s_axis.tlast && s_axis.tuser)) begin + // got a zero or propagated error, so store a zero code + code_fifo_in.tdata = 8'd1; + code_fifo_in.tvalid = 1'b1; + if (s_axis.tlast) begin + // last byte, so close out the frame + fail_frame_next = s_axis.tuser; + input_state_next = INPUT_STATE_FINAL_ZERO; + end else begin + // return to idle to await next segment + input_state_next = INPUT_STATE_IDLE; + end + end else begin + // got something other than a zero, so store it and init the segment counter + input_count_next = 8'd2; + data_fifo_in.tdata = s_axis.tdata; + data_fifo_in.tvalid = 1'b1; + if (s_axis.tlast) begin + // last byte, so store the code and close out the frame + code_fifo_in.tdata = 8'd2; + code_fifo_in.tvalid = 1'b1; + if (APPEND_ZERO) begin + // zero frame mode, need to add a zero code to end the frame + input_state_next = INPUT_STATE_APPEND_ZERO; + end else begin + // normal frame mode, close out the frame + data_fifo_in.tlast = 1'b1; + input_state_next = INPUT_STATE_IDLE; + end + end else begin + // await more segment data + input_state_next = INPUT_STATE_SEGMENT; + end + end + end else begin + input_state_next = INPUT_STATE_IDLE; + end + end + INPUT_STATE_SEGMENT: begin + // encode segment + s_axis_tready_mask = 1'b1; + fail_frame_next = 1'b0; + + if (s_axis.tready && s_axis.tvalid) begin + // valid input data + + if (s_axis.tdata == 8'd0 || (s_axis.tlast && s_axis.tuser)) begin + // got a zero or propagated error, so store the code + code_fifo_in.tdata = input_count_reg; + code_fifo_in.tvalid = 1'b1; + if (s_axis.tlast) begin + // last byte, so close out the frame + fail_frame_next = s_axis.tuser; + input_state_next = INPUT_STATE_FINAL_ZERO; + end else begin + // return to idle to await next segment + input_state_next = INPUT_STATE_IDLE; + end + end else begin + // got something other than a zero, so store it and increment the segment counter + input_count_next = input_count_reg+1; + data_fifo_in.tdata = s_axis.tdata; + data_fifo_in.tvalid = 1'b1; + if (input_count_reg == 8'd254) begin + // 254 bytes in frame, so dump and reset counter + code_fifo_in.tdata = input_count_reg+1; + code_fifo_in.tvalid = 1'b1; + input_count_next = 8'd1; + end + if (s_axis.tlast) begin + // last byte, so store the code and close out the frame + code_fifo_in.tdata = input_count_reg+1; + code_fifo_in.tvalid = 1'b1; + if (APPEND_ZERO) begin + // zero frame mode, need to add a zero code to end the frame + input_state_next = INPUT_STATE_APPEND_ZERO; + end else begin + // normal frame mode, close out the frame + data_fifo_in.tlast = 1'b1; + input_state_next = INPUT_STATE_IDLE; + end + end else begin + // await more segment data + input_state_next = INPUT_STATE_SEGMENT; + end + end + end else begin + input_state_next = INPUT_STATE_SEGMENT; + end + end + INPUT_STATE_FINAL_ZERO: begin + // final zero code required + s_axis_tready_mask = 1'b0; + + if (code_fifo_in.tready) begin + // push a zero code and close out frame + if (fail_frame_reg) begin + code_fifo_in.tdata = 8'd2; + code_fifo_in.tuser = 1'b1; + end else begin + code_fifo_in.tdata = 8'd1; + end + code_fifo_in.tvalid = 1'b1; + if (APPEND_ZERO) begin + // zero frame mode, need to add a zero code to end the frame + input_state_next = INPUT_STATE_APPEND_ZERO; + end else begin + // normal frame mode, close out the frame + code_fifo_in.tlast = 1'b1; + fail_frame_next = 1'b0; + input_state_next = INPUT_STATE_IDLE; + end + end else begin + input_state_next = INPUT_STATE_FINAL_ZERO; + end + end + INPUT_STATE_APPEND_ZERO: begin + // append zero for zero framing + s_axis_tready_mask = 1'b0; + + if (code_fifo_in.tready) begin + // push frame termination code and close out frame + code_fifo_in.tdata = 8'd0; + code_fifo_in.tlast = 1'b1; + code_fifo_in.tuser = fail_frame_reg; + code_fifo_in.tvalid = 1'b1; + fail_frame_next = 1'b0; + input_state_next = INPUT_STATE_IDLE; + end else begin + input_state_next = INPUT_STATE_APPEND_ZERO; + end + end + endcase +end + +always @* begin + output_state_next = OUTPUT_STATE_IDLE; + + output_count_next = output_count_reg; + + m_axis_tdata_int = 8'd0; + m_axis_tvalid_int = 1'b0; + m_axis_tlast_int = 1'b0; + m_axis_tuser_int = 1'b0; + + code_fifo_out.tready = 1'b0; + + data_fifo_out.tready = 1'b0; + + case (output_state_reg) + OUTPUT_STATE_IDLE: begin + // idle state + + if (m_axis_tready_int_reg && code_fifo_out.tvalid) begin + // transfer out code byte and load counter + m_axis_tdata_int = code_fifo_out.tdata; + m_axis_tlast_int = code_fifo_out.tlast; + m_axis_tuser_int = code_fifo_out.tuser && code_fifo_out.tlast; + output_count_next = code_fifo_out.tdata-1; + m_axis_tvalid_int = 1'b1; + code_fifo_out.tready = 1'b1; + if (code_fifo_out.tdata == 8'd0 || code_fifo_out.tdata == 8'd1 || code_fifo_out.tuser) begin + // frame termination and zero codes will be followed by codes + output_state_next = OUTPUT_STATE_IDLE; + end else begin + // transfer out data + output_state_next = OUTPUT_STATE_SEGMENT; + end + end else begin + output_state_next = OUTPUT_STATE_IDLE; + end + end + OUTPUT_STATE_SEGMENT: begin + // segment output + + if (m_axis_tready_int_reg && data_fifo_out.tvalid) begin + // transfer out data byte and decrement counter + m_axis_tdata_int = data_fifo_out.tdata; + m_axis_tlast_int = data_fifo_out.tlast; + output_count_next = output_count_reg - 1; + m_axis_tvalid_int = 1'b1; + data_fifo_out.tready = 1'b1; + if (output_count_reg == 1) begin + // done with segment, get a code byte next + output_state_next = OUTPUT_STATE_IDLE; + end else begin + // more data to transfer + output_state_next = OUTPUT_STATE_SEGMENT; + end + end else begin + output_state_next = OUTPUT_STATE_SEGMENT; + end + end + endcase +end + +always @(posedge clk) begin + input_state_reg <= input_state_next; + output_state_reg <= output_state_next; + + input_count_reg <= input_count_next; + output_count_reg <= output_count_next; + fail_frame_reg <= fail_frame_next; + + if (rst) begin + input_state_reg <= INPUT_STATE_IDLE; + output_state_reg <= OUTPUT_STATE_IDLE; + end +end + +// output datapath logic +reg [7:0] m_axis_tdata_reg = 8'd0; +reg m_axis_tvalid_reg = 1'b0, m_axis_tvalid_next; +reg m_axis_tlast_reg = 1'b0; +reg m_axis_tuser_reg = 1'b0; + +reg [7:0] temp_m_axis_tdata_reg = 8'd0; +reg temp_m_axis_tvalid_reg = 1'b0, temp_m_axis_tvalid_next; +reg temp_m_axis_tlast_reg = 1'b0; +reg temp_m_axis_tuser_reg = 1'b0; + +// datapath control +reg store_axis_int_to_output; +reg store_axis_int_to_temp; +reg store_axis_temp_to_output; + +assign m_axis.tdata = m_axis_tdata_reg; +assign m_axis.tkeep = 1'b1; +assign m_axis.tstrb = m_axis.tkeep; +assign m_axis.tvalid = m_axis_tvalid_reg; +assign m_axis.tlast = m_axis_tlast_reg; +assign m_axis.tuser = m_axis_tuser_reg; + +// enable ready input next cycle if output is ready or the temp reg will not be filled on the next cycle (output reg empty or no input) +assign m_axis_tready_int_early = m_axis.tready || (!temp_m_axis_tvalid_reg && (!m_axis_tvalid_reg || !m_axis_tvalid_int)); + +always @* begin + // transfer sink ready state to source + m_axis_tvalid_next = m_axis_tvalid_reg; + temp_m_axis_tvalid_next = temp_m_axis_tvalid_reg; + + store_axis_int_to_output = 1'b0; + store_axis_int_to_temp = 1'b0; + store_axis_temp_to_output = 1'b0; + + if (m_axis_tready_int_reg) begin + // input is ready + if (m_axis.tready || !m_axis_tvalid_reg) begin + // output is ready or currently not valid, transfer data to output + m_axis_tvalid_next = m_axis_tvalid_int; + store_axis_int_to_output = 1'b1; + end else begin + // output is not ready, store input in temp + temp_m_axis_tvalid_next = m_axis_tvalid_int; + store_axis_int_to_temp = 1'b1; + end + end else if (m_axis.tready) begin + // input is not ready, but output is ready + m_axis_tvalid_next = temp_m_axis_tvalid_reg; + temp_m_axis_tvalid_next = 1'b0; + store_axis_temp_to_output = 1'b1; + end +end + +always @(posedge clk) begin + m_axis_tvalid_reg <= m_axis_tvalid_next; + m_axis_tready_int_reg <= m_axis_tready_int_early; + temp_m_axis_tvalid_reg <= temp_m_axis_tvalid_next; + + // datapath + if (store_axis_int_to_output) begin + m_axis_tdata_reg <= m_axis_tdata_int; + m_axis_tlast_reg <= m_axis_tlast_int; + m_axis_tuser_reg <= m_axis_tuser_int; + end else if (store_axis_temp_to_output) begin + m_axis_tdata_reg <= temp_m_axis_tdata_reg; + m_axis_tlast_reg <= temp_m_axis_tlast_reg; + m_axis_tuser_reg <= temp_m_axis_tuser_reg; + end + + if (store_axis_int_to_temp) begin + temp_m_axis_tdata_reg <= m_axis_tdata_int; + temp_m_axis_tlast_reg <= m_axis_tlast_int; + temp_m_axis_tuser_reg <= m_axis_tuser_int; + end + + if (rst) begin + m_axis_tvalid_reg <= 1'b0; + m_axis_tready_int_reg <= 1'b0; + temp_m_axis_tvalid_reg <= 1'b0; + end +end + +endmodule + +`resetall diff --git a/tb/axis/taxi_axis_cobs_encode/Makefile b/tb/axis/taxi_axis_cobs_encode/Makefile new file mode 100644 index 0000000..2d8de24 --- /dev/null +++ b/tb/axis/taxi_axis_cobs_encode/Makefile @@ -0,0 +1,46 @@ +# SPDX-License-Identifier: CERN-OHL-S-2.0 +# +# Copyright (c) 2021-2025 FPGA Ninja, LLC +# +# Authors: +# - Alex Forencich + +TOPLEVEL_LANG = verilog + +SIM ?= verilator +WAVES ?= 0 + +COCOTB_HDL_TIMEUNIT = 1ns +COCOTB_HDL_TIMEPRECISION = 1ps + +DUT = taxi_axis_cobs_encode +COCOTB_TEST_MODULES = test_$(DUT) +COCOTB_TOPLEVEL = test_$(DUT) +MODULE = $(COCOTB_TEST_MODULES) +TOPLEVEL = $(COCOTB_TOPLEVEL) +VERILOG_SOURCES += $(COCOTB_TOPLEVEL).sv +VERILOG_SOURCES += ../../../rtl/axis/$(DUT).f + +# handle file list files +process_f_file = $(call process_f_files,$(addprefix $(dir $1),$(shell cat $1))) +process_f_files = $(foreach f,$1,$(if $(filter %.f,$f),$(call process_f_file,$f),$f)) +uniq_base = $(if $1,$(call uniq_base,$(foreach f,$1,$(if $(filter-out $(notdir $(lastword $1)),$(notdir $f)),$f,))) $(lastword $1)) +VERILOG_SOURCES := $(call uniq_base,$(call process_f_files,$(VERILOG_SOURCES))) + +# module parameters +export PARAM_APPEND_ZERO := 0 + +ifeq ($(SIM), icarus) + PLUSARGS += -fst + + COMPILE_ARGS += $(foreach v,$(filter PARAM_%,$(.VARIABLES)),-P $(COCOTB_TOPLEVEL).$(subst PARAM_,,$(v))=$($(v))) +else ifeq ($(SIM), verilator) + COMPILE_ARGS += $(foreach v,$(filter PARAM_%,$(.VARIABLES)),-G$(subst PARAM_,,$(v))=$($(v))) + + ifeq ($(WAVES), 1) + COMPILE_ARGS += --trace-fst + VERILATOR_TRACE = 1 + endif +endif + +include $(shell cocotb-config --makefiles)/Makefile.sim diff --git a/tb/axis/taxi_axis_cobs_encode/test_taxi_axis_cobs_encode.py b/tb/axis/taxi_axis_cobs_encode/test_taxi_axis_cobs_encode.py new file mode 100644 index 0000000..6a2bd5f --- /dev/null +++ b/tb/axis/taxi_axis_cobs_encode/test_taxi_axis_cobs_encode.py @@ -0,0 +1,250 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: CERN-OHL-S-2.0 +""" + +Copyright (c) 2021-2025 FPGA Ninja, LLC + +Authors: +- Alex Forencich + +""" + +import itertools +import logging +import os + +import cocotb_test.simulator +import pytest + +import cocotb +from cocotb.clock import Clock +from cocotb.triggers import RisingEdge +from cocotb.regression import TestFactory + +from cocotbext.axi import AxiStreamBus, AxiStreamFrame, AxiStreamSource, AxiStreamSink + + +def cobs_encode(block): + block = bytearray(block) + enc = bytearray() + + seg = bytearray() + code = 1 + + new_data = True + + for b in block: + if b == 0: + enc.append(code) + enc.extend(seg) + code = 1 + seg = bytearray() + new_data = True + else: + code += 1 + seg.append(b) + new_data = True + if code == 255: + enc.append(code) + enc.extend(seg) + code = 1 + seg = bytearray() + new_data = False + + if new_data: + enc.append(code) + enc.extend(seg) + + return bytes(enc) + + +def cobs_decode(block): + block = bytearray(block) + dec = bytearray() + + code = 0 + + i = 0 + + if 0 in block: + return None + + while i < len(block): + code = block[i] + i += 1 + if i+code-1 > len(block): + return None + dec.extend(block[i:i+code-1]) + i += code-1 + if code < 255 and i < len(block): + dec.append(0) + + return bytes(dec) + + +def prbs31(state=0x7fffffff): + while True: + for i in range(8): + if bool(state & 0x08000000) ^ bool(state & 0x40000000): + state = ((state & 0x3fffffff) << 1) | 1 + else: + state = (state & 0x3fffffff) << 1 + yield state & 0xff + + +class TB(object): + def __init__(self, dut): + self.dut = dut + + self.log = logging.getLogger("cocotb.tb") + self.log.setLevel(logging.DEBUG) + + cocotb.start_soon(Clock(dut.clk, 10, units="ns").start()) + + self.source = AxiStreamSource(AxiStreamBus.from_entity(dut.s_axis), dut.clk, dut.rst) + self.sink = AxiStreamSink(AxiStreamBus.from_entity(dut.m_axis), dut.clk, dut.rst) + + def set_idle_generator(self, generator=None): + if generator: + self.source.set_pause_generator(generator()) + + def set_backpressure_generator(self, generator=None): + if generator: + self.sink.set_pause_generator(generator()) + + async def reset(self): + self.dut.rst.setimmediatevalue(0) + await RisingEdge(self.dut.clk) + await RisingEdge(self.dut.clk) + self.dut.rst.value = 1 + await RisingEdge(self.dut.clk) + await RisingEdge(self.dut.clk) + self.dut.rst.value = 0 + await RisingEdge(self.dut.clk) + await RisingEdge(self.dut.clk) + + +async def run_test(dut, payload_lengths=None, payload_data=None, idle_inserter=None, backpressure_inserter=None): + + tb = TB(dut) + + append_zero = int(os.getenv("PARAM_APPEND_ZERO")) + + await tb.reset() + + tb.set_idle_generator(idle_inserter) + tb.set_backpressure_generator(backpressure_inserter) + + test_frames = [payload_data(x) for x in payload_lengths()] + + for test_data in test_frames: + test_frame = AxiStreamFrame(test_data) + await tb.source.send(test_frame) + + for test_data in test_frames: + rx_frame = await tb.sink.recv() + + if append_zero: + assert rx_frame.tdata == cobs_encode(test_data)+b'\x00' + assert cobs_decode(rx_frame.tdata[:-1]) == test_data + else: + assert rx_frame.tdata == cobs_encode(test_data) + assert cobs_decode(rx_frame.tdata) == test_data + assert not rx_frame.tuser + + assert tb.sink.empty() + + await RisingEdge(dut.clk) + await RisingEdge(dut.clk) + + +def cycle_pause(): + return itertools.cycle([1, 1, 1, 0]) + + +def size_list(): + return list(range(1, 33))+list(range(253, 259))+[512]+[1]*64 + + +def zero_payload(length): + return bytearray(length) + + +def incrementing_payload(length): + return bytearray(itertools.islice(itertools.cycle(range(256)), length)) + + +def nonzero_incrementing_payload(length): + return bytearray(itertools.islice(itertools.cycle(range(1, 256)), length)) + + +def nonzero_incrementing_payload_zero_framed(length): + return bytearray([0]+list(itertools.islice(itertools.cycle(range(1, 256)), length))+[0]) + + +def prbs_payload(length): + gen = prbs31() + return bytearray([next(gen) for x in range(length)]) + + +if cocotb.SIM_NAME: + + factory = TestFactory(run_test) + factory.add_option("payload_lengths", [size_list]) + factory.add_option("payload_data", [zero_payload, nonzero_incrementing_payload, nonzero_incrementing_payload_zero_framed, prbs_payload]) + factory.add_option("idle_inserter", [None, cycle_pause]) + factory.add_option("backpressure_inserter", [None, cycle_pause]) + factory.generate_tests() + + +# cocotb-test + +tests_dir = os.path.dirname(__file__) +rtl_dir = os.path.abspath(os.path.join(tests_dir, '..', '..', '..', 'rtl')) + + +def process_f_files(files): + lst = {} + for f in files: + if f[-2:].lower() == '.f': + with open(f, 'r') as fp: + l = fp.read().split() + for f in process_f_files([os.path.join(os.path.dirname(f), x) for x in l]): + lst[os.path.basename(f)] = f + else: + lst[os.path.basename(f)] = f + return list(lst.values()) + + +@pytest.mark.parametrize("append_zero", [0, 1]) +def test_taxi_axis_cobs_encode(request, append_zero): + dut = "taxi_axis_cobs_encode" + module = os.path.splitext(os.path.basename(__file__))[0] + toplevel = module + + verilog_sources = [ + os.path.join(tests_dir, f"{toplevel}.sv"), + os.path.join(rtl_dir, "axis", f"{dut}.f"), + ] + + verilog_sources = process_f_files(verilog_sources) + + parameters = {} + + parameters['APPEND_ZERO'] = append_zero + + extra_env = {f'PARAM_{k}': str(v) for k, v in parameters.items()} + + sim_build = os.path.join(tests_dir, "sim_build", + request.node.name.replace('[', '-').replace(']', '')) + + cocotb_test.simulator.run( + simulator="verilator", + python_search=[tests_dir], + verilog_sources=verilog_sources, + toplevel=toplevel, + module=module, + parameters=parameters, + sim_build=sim_build, + extra_env=extra_env, + ) diff --git a/tb/axis/taxi_axis_cobs_encode/test_taxi_axis_cobs_encode.sv b/tb/axis/taxi_axis_cobs_encode/test_taxi_axis_cobs_encode.sv new file mode 100644 index 0000000..9e6c1d1 --- /dev/null +++ b/tb/axis/taxi_axis_cobs_encode/test_taxi_axis_cobs_encode.sv @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: CERN-OHL-S-2.0 +/* + +Copyright (c) 2025 FPGA Ninja, LLC + +Authors: +- Alex Forencich + +*/ + +`resetall +`timescale 1ns / 1ps +`default_nettype none + +/* + * AXI4-Stream COBS encoder testbench + */ +module test_taxi_axis_cobs_encode # +( + /* verilator lint_off WIDTHTRUNC */ + parameter logic APPEND_ZERO = 1'b1 + /* verilator lint_on WIDTHTRUNC */ +) +(); + +logic clk; +logic rst; + +taxi_axis_if #( + .DATA_W(8), + .LAST_EN(1), + .USER_EN(1), + .USER_W(1) +) s_axis(), m_axis(); + +taxi_axis_cobs_encode #( + .APPEND_ZERO(APPEND_ZERO) +) +uut ( + .clk(clk), + .rst(rst), + + /* + * AXI4-Stream input (sink) + */ + .s_axis(s_axis), + + /* + * AXI4-Stream output (source) + */ + .m_axis(m_axis) +); + +endmodule + +`resetall