From 328a00e30ff2b42763d28d310d2c33cc4cea31e8 Mon Sep 17 00:00:00 2001 From: Alex Forencich Date: Wed, 5 Feb 2025 15:28:08 -0800 Subject: [PATCH] lfsr: Add LFSR PRBS generator module and testbench Signed-off-by: Alex Forencich --- rtl/lfsr/taxi_lfsr_prbs_gen.sv | 183 ++++++++++++++++++ tb/lfsr/taxi_lfsr_prbs_gen/Makefile | 52 +++++ .../test_taxi_lfsr_prbs_gen.py | 171 ++++++++++++++++ 3 files changed, 406 insertions(+) create mode 100644 rtl/lfsr/taxi_lfsr_prbs_gen.sv create mode 100644 tb/lfsr/taxi_lfsr_prbs_gen/Makefile create mode 100644 tb/lfsr/taxi_lfsr_prbs_gen/test_taxi_lfsr_prbs_gen.py diff --git a/rtl/lfsr/taxi_lfsr_prbs_gen.sv b/rtl/lfsr/taxi_lfsr_prbs_gen.sv new file mode 100644 index 0000000..5a85f67 --- /dev/null +++ b/rtl/lfsr/taxi_lfsr_prbs_gen.sv @@ -0,0 +1,183 @@ +// 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 + +/* + * LFSR PRBS generator + */ +module taxi_lfsr_prbs_gen # +( + // width of LFSR + parameter LFSR_W = 31, + // LFSR polynomial + parameter logic [LFSR_W-1:0] LFSR_POLY = 31'h10000001, + // Initial state + parameter logic [LFSR_W-1:0] LFSR_INIT = '1, + // LFSR configuration: 0 for Fibonacci (PRBS), 1 for Galois (CRC) + parameter logic LFSR_GALOIS = 1'b0, + // bit-reverse input and output + parameter logic REVERSE = 1'b0, + // invert output + parameter logic INVERT = 1'b1, + // width of data output + parameter DATA_W = 8 +) +( + input wire logic clk, + input wire logic rst, + + input wire logic enable, + output wire logic [DATA_W-1:0] data_out +); + +/* + +Fully parametrizable combinatorial parallel LFSR PRBS module. Implements an unrolled LFSR +next state computation. + +Ports: + +clk + +Clock input + +rst + +Reset input, set state to LFSR_INIT + +enable + +Generate new output data + +data_out + +LFSR output (DATA_W bits) + +Parameters: + +LFSR_W + +Specify width of LFSR/CRC register + +LFSR_POLY + +Specify the LFSR/CRC polynomial in hex format. For example, the polynomial + +x^32 + x^26 + x^23 + x^22 + x^16 + x^12 + x^11 + x^10 + x^8 + x^7 + x^5 + x^4 + x^2 + x + 1 + +would be represented as + +32'h04c11db7 + +Note that the largest term (x^32) is suppressed. This term is generated automatically based +on LFSR_W. + +LFSR_INIT + +Initial state of LFSR. Defaults to all 1s. + +LFSR_GALOIS + +Specify the LFSR configuration, either Fibonacci (0) or Galois (1). Fibonacci is generally used +for linear-feedback shift registers (LFSR) for pseudorandom binary sequence (PRBS) generators, +scramblers, and descrambers, while Galois is generally used for cyclic redundancy check +generators and checkers. + +Fibonacci style (example for 64b66b scrambler, 0x8000000001) + + DIN (LSB first) + | + V + (+)<---------------------------(+)<-----------------------------. + | ^ | + | .----. .----. .----. | .----. .----. .----. | + +->| 0 |->| 1 |->...->| 38 |-+->| 39 |->...->| 56 |->| 57 |--' + | '----' '----' '----' '----' '----' '----' + V + DOUT + +Galois style (example for CRC16, 0x8005) + + ,-------------------+-------------------------+----------(+)<-- DIN (MSB first) + | | | ^ + | .----. .----. V .----. .----. V .----. | + `->| 0 |->| 1 |->(+)->| 2 |->...->| 14 |->(+)->| 15 |--+---> DOUT + '----' '----' '----' '----' '----' + +REVERSE + +Bit-reverse LFSR output. Shifts MSB first by default, set REVERSE for LSB first. + +INVERT + +Bitwise invert PRBS output. + +DATA_W + +Specify width of output data bus. + +Settings for common LFSR/CRC implementations: + +Name Configuration Length Polynomial Initial value Notes +CRC32 Galois, bit-reverse 32 32'h04c11db7 32'hffffffff Ethernet FCS; invert final output +PRBS6 Fibonacci 6 6'h21 any +PRBS7 Fibonacci 7 7'h41 any +PRBS9 Fibonacci 9 9'h021 any ITU V.52 +PRBS10 Fibonacci 10 10'h081 any ITU +PRBS11 Fibonacci 11 11'h201 any ITU O.152 +PRBS15 Fibonacci, inverted 15 15'h4001 any ITU O.152 +PRBS17 Fibonacci 17 17'h04001 any +PRBS20 Fibonacci 20 20'h00009 any ITU V.57 +PRBS23 Fibonacci, inverted 23 23'h040001 any ITU O.151 +PRBS29 Fibonacci, inverted 29 29'h08000001 any +PRBS31 Fibonacci, inverted 31 31'h10000001 any +64b66b Fibonacci, bit-reverse 58 58'h8000000001 any 10G Ethernet +128b130b Galois, bit-reverse 23 23'h210125 any PCIe gen 3 + +*/ + +logic [LFSR_W-1:0] state_reg = LFSR_INIT; + +wire [DATA_W-1:0] lfsr_data; +wire [LFSR_W-1:0] lfsr_state; + +assign data_out = INVERT ? ~lfsr_data : lfsr_data; + +taxi_lfsr #( + .LFSR_W(LFSR_W), + .LFSR_POLY(LFSR_POLY), + .LFSR_GALOIS(LFSR_GALOIS), + .LFSR_FEED_FORWARD('0), + .REVERSE(REVERSE), + .DATA_W(DATA_W) +) +lfsr_inst ( + .data_in('0), + .state_in(state_reg), + .data_out(lfsr_data), + .state_out(lfsr_state) +); + +always @(posedge clk) begin + if (enable) begin + state_reg <= lfsr_state; + end + + if (rst) begin + state_reg <= LFSR_INIT; + end +end + +endmodule + +`resetall diff --git a/tb/lfsr/taxi_lfsr_prbs_gen/Makefile b/tb/lfsr/taxi_lfsr_prbs_gen/Makefile new file mode 100644 index 0000000..9ea951f --- /dev/null +++ b/tb/lfsr/taxi_lfsr_prbs_gen/Makefile @@ -0,0 +1,52 @@ +# SPDX-License-Identifier: CERN-OHL-S-2.0 +# +# Copyright (c) 2023-2025 FPGA Ninja, LLC +# +# Authors: +# - Alex Forencich + +TOPLEVEL_LANG = verilog + +SIM ?= verilator +WAVES ?= 0 + +COCOTB_HDL_TIMEUNIT = 1ns +COCOTB_HDL_TIMEPRECISION = 1ps + +DUT = taxi_lfsr_prbs_gen +COCOTB_TEST_MODULES = test_$(DUT) +COCOTB_TOPLEVEL = $(DUT) +MODULE = $(COCOTB_TEST_MODULES) +TOPLEVEL = $(COCOTB_TOPLEVEL) +VERILOG_SOURCES += ../../../rtl/lfsr/$(DUT).sv +VERILOG_SOURCES += ../../../rtl/lfsr/taxi_lfsr.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_LFSR_W ?= 31 +export PARAM_LFSR_POLY ?= "31'h10000001" +export PARAM_LFSR_INIT ?= "'1" +export PARAM_LFSR_GALOIS ?= "1'b0" +export PARAM_REVERSE ?= "1'b0" +export PARAM_INVERT ?= "1'b1" +export PARAM_DATA_W ?= 8 + +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/lfsr/taxi_lfsr_prbs_gen/test_taxi_lfsr_prbs_gen.py b/tb/lfsr/taxi_lfsr_prbs_gen/test_taxi_lfsr_prbs_gen.py new file mode 100644 index 0000000..8a51938 --- /dev/null +++ b/tb/lfsr/taxi_lfsr_prbs_gen/test_taxi_lfsr_prbs_gen.py @@ -0,0 +1,171 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: CERN-OHL-S-2.0 +""" + +Copyright (c) 2023-2025 FPGA Ninja, LLC + +Authors: +- Alex Forencich + +""" + +import itertools +import logging +import os + +import pytest +import cocotb_test.simulator + +import cocotb +from cocotb.clock import Clock +from cocotb.triggers import RisingEdge +from cocotb.regression import TestFactory + + +class TB: + 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()) + + dut.enable.setimmediatevalue(0) + + 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) + + +def chunks(lst, n, padvalue=None): + return itertools.zip_longest(*[iter(lst)]*n, fillvalue=padvalue) + + +def prbs9(state=0x1ff): + while True: + for i in range(8): + if bool(state & 0x10) ^ bool(state & 0x100): + state = ((state & 0xff) << 1) | 1 + else: + state = (state & 0xff) << 1 + yield ~state & 0xff + + +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 + + +async def run_test_prbs(dut, ref_prbs): + + data_width = len(dut.data_out) + byte_lanes = data_width // 8 + + tb = TB(dut) + + await tb.reset() + + gen = chunks(ref_prbs(), byte_lanes) + + dut.enable.value = 1 + await RisingEdge(dut.clk) + + for i in range(512): + ref = int.from_bytes(bytes(next(gen)), 'big') + val = dut.data_out.value.integer + + tb.log.info("PRBS: 0x%x (ref: 0x%x)", val, ref) + + assert ref == val + + await RisingEdge(dut.clk) + + +if cocotb.SIM_NAME: + + if int(cocotb.top.LFSR_POLY.value) == 0x021: + factory = TestFactory(run_test_prbs) + factory.add_option("ref_prbs", [prbs9]) + factory.generate_tests() + + if int(cocotb.top.LFSR_POLY.value) == 0x10000001: + factory = TestFactory(run_test_prbs) + factory.add_option("ref_prbs", [prbs31]) + factory.generate_tests() + + +# 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(("lfsr_w", "lfsr_poly", "lfsr_init", "lfsr_galois", "reverse", "invert", "data_w"), [ + (9, "9'h021", "'1", 0, 0, 1, 8), + (9, "9'h021", "'1", 0, 0, 1, 64), + (31, "31'h10000001", "'1", 0, 0, 1, 8), + (31, "31'h10000001", "'1", 0, 0, 1, 64), + ]) +def test_taxi_lfsr_prbs_gen(request, lfsr_w, lfsr_poly, lfsr_init, lfsr_galois, reverse, invert, data_w): + dut = "taxi_lfsr_prbs_gen" + module = os.path.splitext(os.path.basename(__file__))[0] + toplevel = dut + + verilog_sources = [ + os.path.join(rtl_dir, "lfsr", f"{dut}.sv"), + os.path.join(rtl_dir, "lfsr", "taxi_lfsr.sv"), + ] + + verilog_sources = process_f_files(verilog_sources) + + parameters = {} + + parameters['LFSR_W'] = lfsr_w + parameters['LFSR_POLY'] = lfsr_poly + parameters['LFSR_INIT'] = lfsr_init + parameters['LFSR_GALOIS'] = f"1'b{lfsr_galois}" + parameters['REVERSE'] = f"1'b{reverse}" + parameters['INVERT'] = f"1'b{invert}" + parameters['DATA_W'] = data_w + + 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, + )