From 5966d057409d902a4168b9745137a8fd5b38d904 Mon Sep 17 00:00:00 2001 From: Alex Forencich Date: Fri, 28 Feb 2025 21:04:32 -0800 Subject: [PATCH] prim: Add priority encoder and testbench Signed-off-by: Alex Forencich --- rtl/prim/taxi_penc.sv | 76 +++++++++++++++++ tb/prim/taxi_penc/Makefile | 46 +++++++++++ tb/prim/taxi_penc/test_taxi_penc.py | 123 ++++++++++++++++++++++++++++ 3 files changed, 245 insertions(+) create mode 100644 rtl/prim/taxi_penc.sv create mode 100644 tb/prim/taxi_penc/Makefile create mode 100644 tb/prim/taxi_penc/test_taxi_penc.py diff --git a/rtl/prim/taxi_penc.sv b/rtl/prim/taxi_penc.sv new file mode 100644 index 0000000..cdc5d95 --- /dev/null +++ b/rtl/prim/taxi_penc.sv @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: CERN-OHL-S-2.0 +/* + +Copyright (c) 2014-2025 FPGA Ninja, LLC + +Authors: +- Alex Forencich + +*/ + +`resetall +`timescale 1ns / 1ps +`default_nettype none + +/* + * Priority encoder module + */ +module taxi_penc # +( + parameter WIDTH = 4, + // LSB priority selection + parameter logic LSB_HIGH_PRIO = 1'b0 +) +( + input wire logic [WIDTH-1:0] input_mask, + output wire logic output_valid, + output wire logic [$clog2(WIDTH)-1:0] output_index, + output wire logic [WIDTH-1:0] output_mask +); + +// hopefully a temporary workaround +// verilator lint_off UNOPTFLAT + +localparam CL_WIDTH = $clog2(WIDTH); +localparam LEVELS = WIDTH > 2 ? CL_WIDTH : 1; +localparam W = 2**LEVELS; + +// pad input to even power of two +wire [W-1:0] mask = {{W-WIDTH{1'b0}}, input_mask}; + +wire [W/2-1:0] stage_valid[LEVELS]; +wire [W/2-1:0] stage_enc[LEVELS]; + +// process input bits; generate valid bit and encoded bit for each pair +for (genvar n = 0; n < W/2; n = n + 1) begin : loop_in + assign stage_valid[0][n] = |mask[n*2+1:n*2]; + if (LSB_HIGH_PRIO) begin + // bit 0 is highest priority + assign stage_enc[0][n] = !mask[n*2+0]; + end else begin + // bit 0 is lowest priority + assign stage_enc[0][n] = mask[n*2+1]; + end +end + +// compress down to single valid bit and encoded bus +for (genvar l = 1; l < LEVELS; l = l + 1) begin : loop_levels + for (genvar n = 0; n < W/(2*2**l); n = n + 1) begin : loop_compress + assign stage_valid[l][n] = |stage_valid[l-1][n*2+1:n*2]; + if (LSB_HIGH_PRIO) begin + // bit 0 is highest priority + assign stage_enc[l][(n+1)*(l+1)-1:n*(l+1)] = stage_valid[l-1][n*2+0] ? {1'b0, stage_enc[l-1][(n*2+1)*l-1:(n*2+0)*l]} : {1'b1, stage_enc[l-1][(n*2+2)*l-1:(n*2+1)*l]}; + end else begin + // bit 0 is lowest priority + assign stage_enc[l][(n+1)*(l+1)-1:n*(l+1)] = stage_valid[l-1][n*2+1] ? {1'b1, stage_enc[l-1][(n*2+2)*l-1:(n*2+1)*l]} : {1'b0, stage_enc[l-1][(n*2+1)*l-1:(n*2+0)*l]}; + end + end +end + +assign output_valid = stage_valid[LEVELS-1][0]; +assign output_index = CL_WIDTH'(stage_enc[LEVELS-1]); +assign output_mask = WIDTH'(output_valid) << output_index; + +endmodule + +`resetall diff --git a/tb/prim/taxi_penc/Makefile b/tb/prim/taxi_penc/Makefile new file mode 100644 index 0000000..a5235f9 --- /dev/null +++ b/tb/prim/taxi_penc/Makefile @@ -0,0 +1,46 @@ +# SPDX-License-Identifier: CERN-OHL-S-2.0 +# +# Copyright (c) 2025 FPGA Ninja, LLC +# +# Authors: +# - Alex Forencich + +TOPLEVEL_LANG = verilog + +SIM ?= verilator +WAVES ?= 0 + +COCOTB_HDL_TIMEUNIT = 1ns +COCOTB_HDL_TIMEPRECISION = 1ps + +DUT = taxi_penc +COCOTB_TEST_MODULES = test_$(DUT) +COCOTB_TOPLEVEL = $(DUT) +MODULE = $(COCOTB_TEST_MODULES) +TOPLEVEL = $(COCOTB_TOPLEVEL) +VERILOG_SOURCES += ../../../rtl/prim/$(DUT).sv + +# 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_WIDTH := 32 +export PARAM_LSB_HIGH_PRIO := "1'b0" + +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/prim/taxi_penc/test_taxi_penc.py b/tb/prim/taxi_penc/test_taxi_penc.py new file mode 100644 index 0000000..babec7b --- /dev/null +++ b/tb/prim/taxi_penc/test_taxi_penc.py @@ -0,0 +1,123 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: CERN-OHL-S-2.0 +""" + +Copyright (c) 2020-2025 FPGA Ninja, LLC + +Authors: +- Alex Forencich + +""" + +import logging +import os + +import cocotb_test.simulator +import pytest + +import cocotb +from cocotb.triggers import Timer + + +@cocotb.test() +async def run_single_bit(dut): + + log = logging.getLogger("cocotb.tb") + log.setLevel(logging.DEBUG) + + for i in range(32): + in_val = 1 << i + + log.info("In: 0x%08x", in_val) + dut.input_mask.value = in_val + + await Timer(1, "ns") + + log.info("Out (index): %d", int(dut.output_index.value)) + log.info("Out (mask): 0x%08x", int(dut.output_mask.value)) + + assert int(dut.output_valid.value) + assert int(dut.output_index.value) == i + assert int(dut.output_mask.value) == 1 << i + + +@cocotb.test() +async def run_two_bits(dut): + + lsb_high_prio = bool(int(dut.LSB_HIGH_PRIO.value)) + + log = logging.getLogger("cocotb.tb") + log.setLevel(logging.DEBUG) + + for i in range(32): + for j in range(32): + in_val = (1 << i) | (1 << j) + + log.info("In: 0x%08x", in_val) + dut.input_mask.value = in_val + + await Timer(1, "ns") + + log.info("Out (index): %d", int(dut.output_index.value)) + log.info("Out (mask): 0x%08x", int(dut.output_mask.value)) + + assert int(dut.output_valid.value) + if lsb_high_prio: + assert int(dut.output_index.value) == min(i, j) + assert int(dut.output_mask.value) == 1 << min(i, j) + else: + assert int(dut.output_index.value) == max(i, j) + assert int(dut.output_mask.value) == 1 << max(i, j) + + +# cocotb-test + +tests_dir = os.path.abspath(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("lsb_high_prio", [0, 1]) +def test_taxi_penc(request, lsb_high_prio): + dut = "taxi_penc" + module = os.path.splitext(os.path.basename(__file__))[0] + toplevel = dut + + verilog_sources = [ + os.path.join(rtl_dir, "prim", f"{dut}.sv"), + ] + + verilog_sources = process_f_files(verilog_sources) + + parameters = {} + + parameters['WIDTH'] = 32 + parameters['LSB_HIGH_PRIO'] = f"1'b{lsb_high_prio}" + + 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, + )