lfsr: Update LFSR module to support shifting the state before/after shifting the data, including backwards shifts

Signed-off-by: Alex Forencich <alex@alexforencich.com>
This commit is contained in:
Alex Forencich
2026-05-11 15:02:30 -07:00
parent ffb49f1e9c
commit 2063e47a4e
8 changed files with 443 additions and 81 deletions

View File

@@ -1,7 +1,7 @@
// SPDX-License-Identifier: CERN-OHL-S-2.0
/*
Copyright (c) 2016-2025 FPGA Ninja, LLC
Copyright (c) 2016-2026 FPGA Ninja, LLC
Authors:
- Alex Forencich
@@ -31,7 +31,10 @@ module taxi_lfsr #
parameter DATA_W = 8,
// enable data input and output
parameter logic DATA_IN_EN = 1'b1,
parameter logic DATA_OUT_EN = 1'b1
parameter logic DATA_OUT_EN = 1'b1,
// shift control
parameter STATE_SHIFT_PRE = 0,
parameter STATE_SHIFT_POST = 0
)
(
input wire logic [DATA_W-1:0] data_in,
@@ -150,6 +153,21 @@ Specify width of input and output data bus. The module will perform one shift p
data bit, so if the input data bus is not required tie data_in to zero and set DATA_W
to the required number of shifts per clock cycle.
DATA_IN_EN, DATA_OUT_EN
Enable data input and/or output ports. Useful for CRC computation where the
data shifted out of the register is not used, or PRBS generation where no data
is shifted in to the register. Disabling unused inputs and outputs will
increase simulation speed.
STATE_SHIFT_PRE, STATE_SHIFT_POST
Shift the state before/after shifting the data. Useful for either shifting the
state only, or for performing a split CRC computation on a wide/segmented data
bus. Positive shift values are equivalent to extending the data input port with
zeros. Negative shift amounts shift the state backwards, useful for removing
zero padding and similar.
Settings for common LFSR/CRC implementations:
Name Configuration Length Polynomial Initial value Notes
@@ -210,75 +228,318 @@ function [OUT_W-1:0][IN_W-1:0] lfsr_mask();
// simulate shift register
if (LFSR_GALOIS) begin
// Galois configuration
for (data_mask = {1'b1, {DATA_W-1{1'b0}}}; data_mask != 0; data_mask = data_mask >> 1) begin
// determine shift in value
// current value in last FF, XOR with input data bit (MSB first)
state_val = lfsr_mask_state[LFSR_W-1];
data_val = lfsr_mask_data[LFSR_W-1];
data_val = data_val ^ data_mask;
// shift
for (integer j = LFSR_W-1; j > 0; j = j - 1) begin
lfsr_mask_state[j] = lfsr_mask_state[j-1];
lfsr_mask_data[j] = lfsr_mask_data[j-1];
end
for (integer j = DATA_W-1; j > 0; j = j - 1) begin
output_mask_state[j] = output_mask_state[j-1];
output_mask_data[j] = output_mask_data[j-1];
end
output_mask_state[0] = state_val;
output_mask_data[0] = data_val;
if (LFSR_FEED_FORWARD) begin
// only shift in new input data
state_val = '0;
data_val = data_mask;
end
lfsr_mask_state[0] = state_val;
lfsr_mask_data[0] = data_val;
// Shift state alone before shifting data
if (STATE_SHIFT_PRE > 0) begin
// forward shift
for (integer i = 0; i < STATE_SHIFT_PRE; i = i + 1) begin
// determine shift in value
// current value in last FF, XOR with input data bit (MSB first)
state_val = lfsr_mask_state[LFSR_W-1];
data_val = lfsr_mask_data[LFSR_W-1];
// add XOR inputs at correct indicies
for (integer j = 1; j < LFSR_W; j = j + 1) begin
if (LFSR_POLY[j]) begin
lfsr_mask_state[j] = lfsr_mask_state[j] ^ state_val;
lfsr_mask_data[j] = lfsr_mask_data[j] ^ data_val;
// shift
for (integer j = LFSR_W-1; j > 0; j = j - 1) begin
lfsr_mask_state[j] = lfsr_mask_state[j-1];
lfsr_mask_data[j] = lfsr_mask_data[j-1];
end
if (LFSR_FEED_FORWARD) begin
// only shift in new input data
state_val = '0;
data_val = '0;
end
lfsr_mask_state[0] = state_val;
lfsr_mask_data[0] = data_val;
// add XOR inputs at correct indicies
for (integer j = 1; j < LFSR_W; j = j + 1) begin
if (LFSR_POLY[j]) begin
lfsr_mask_state[j] = lfsr_mask_state[j] ^ state_val;
lfsr_mask_data[j] = lfsr_mask_data[j] ^ data_val;
end
end
end
end else if (STATE_SHIFT_PRE < 0) begin
// reverse shift
for (integer i = 0; i < -STATE_SHIFT_PRE; i = i + 1) begin
state_val = lfsr_mask_state[0];
data_val = lfsr_mask_data[0];
// add XOR inputs at correct indicies
for (integer j = 1; j < LFSR_W; j = j + 1) begin
if (LFSR_POLY[j]) begin
lfsr_mask_state[j] = lfsr_mask_state[j] ^ state_val;
lfsr_mask_data[j] = lfsr_mask_data[j] ^ data_val;
end
end
// shift
for (integer j = 0; j < LFSR_W-1; j = j + 1) begin
lfsr_mask_state[j] = lfsr_mask_state[j+1];
lfsr_mask_data[j] = lfsr_mask_data[j+1];
end
if (LFSR_FEED_FORWARD) begin
// only shift in new input data
state_val = '0;
data_val = '0;
end
lfsr_mask_state[LFSR_W-1] = state_val;
lfsr_mask_data[LFSR_W-1] = data_val;
end
end
// Shift data
if (DATA_IN_EN || DATA_OUT_EN) begin
for (data_mask = {1'b1, {DATA_W-1{1'b0}}}; data_mask != 0; data_mask = data_mask >> 1) begin
// determine shift in value
// current value in last FF, XOR with input data bit (MSB first)
state_val = lfsr_mask_state[LFSR_W-1];
data_val = lfsr_mask_data[LFSR_W-1];
data_val = data_val ^ data_mask;
// shift
for (integer j = LFSR_W-1; j > 0; j = j - 1) begin
lfsr_mask_state[j] = lfsr_mask_state[j-1];
lfsr_mask_data[j] = lfsr_mask_data[j-1];
end
for (integer j = DATA_W-1; j > 0; j = j - 1) begin
output_mask_state[j] = output_mask_state[j-1];
output_mask_data[j] = output_mask_data[j-1];
end
output_mask_state[0] = state_val;
output_mask_data[0] = data_val;
if (LFSR_FEED_FORWARD) begin
// only shift in new input data
state_val = '0;
data_val = data_mask;
end
lfsr_mask_state[0] = state_val;
lfsr_mask_data[0] = data_val;
// add XOR inputs at correct indicies
for (integer j = 1; j < LFSR_W; j = j + 1) begin
if (LFSR_POLY[j]) begin
lfsr_mask_state[j] = lfsr_mask_state[j] ^ state_val;
lfsr_mask_data[j] = lfsr_mask_data[j] ^ data_val;
end
end
end
end
// Shift state alone after shifting data
if (STATE_SHIFT_POST > 0) begin
// forward shift
for (integer i = 0; i < STATE_SHIFT_POST; i = i + 1) begin
// determine shift in value
// current value in last FF, XOR with input data bit (MSB first)
state_val = lfsr_mask_state[LFSR_W-1];
data_val = lfsr_mask_data[LFSR_W-1];
// shift
for (integer j = LFSR_W-1; j > 0; j = j - 1) begin
lfsr_mask_state[j] = lfsr_mask_state[j-1];
lfsr_mask_data[j] = lfsr_mask_data[j-1];
end
if (LFSR_FEED_FORWARD) begin
// only shift in new input data
state_val = '0;
data_val = '0;
end
lfsr_mask_state[0] = state_val;
lfsr_mask_data[0] = data_val;
// add XOR inputs at correct indicies
for (integer j = 1; j < LFSR_W; j = j + 1) begin
if (LFSR_POLY[j]) begin
lfsr_mask_state[j] = lfsr_mask_state[j] ^ state_val;
lfsr_mask_data[j] = lfsr_mask_data[j] ^ data_val;
end
end
end
end else if (STATE_SHIFT_POST < 0) begin
// reverse shift
for (integer i = 0; i < -STATE_SHIFT_POST; i = i + 1) begin
state_val = lfsr_mask_state[0];
data_val = lfsr_mask_data[0];
// add XOR inputs at correct indicies
for (integer j = 1; j < LFSR_W; j = j + 1) begin
if (LFSR_POLY[j]) begin
lfsr_mask_state[j] = lfsr_mask_state[j] ^ state_val;
lfsr_mask_data[j] = lfsr_mask_data[j] ^ data_val;
end
end
// shift
for (integer j = 0; j < LFSR_W-1; j = j + 1) begin
lfsr_mask_state[j] = lfsr_mask_state[j+1];
lfsr_mask_data[j] = lfsr_mask_data[j+1];
end
if (LFSR_FEED_FORWARD) begin
// only shift in new input data
state_val = '0;
data_val = '0;
end
lfsr_mask_state[LFSR_W-1] = state_val;
lfsr_mask_data[LFSR_W-1] = data_val;
end
end
end else begin
// Fibonacci configuration
for (data_mask = {1'b1, {DATA_W-1{1'b0}}}; data_mask != 0; data_mask = data_mask >> 1) begin
// determine shift in value
// current value in last FF, XOR with input data bit (MSB first)
state_val = lfsr_mask_state[LFSR_W-1];
data_val = lfsr_mask_data[LFSR_W-1];
data_val = data_val ^ data_mask;
// add XOR inputs from correct indicies
for (integer j = 1; j < LFSR_W; j = j + 1) begin
if (LFSR_POLY[j]) begin
state_val = lfsr_mask_state[j-1] ^ state_val;
data_val = lfsr_mask_data[j-1] ^ data_val;
// Shift state alone before shifting data
if (STATE_SHIFT_PRE > 0) begin
// forward shift
for (integer i = 0; i < STATE_SHIFT_PRE; i = i + 1) begin
// determine shift in value
// current value in last FF, XOR with input data bit (MSB first)
state_val = lfsr_mask_state[LFSR_W-1];
data_val = lfsr_mask_data[LFSR_W-1];
// add XOR inputs from correct indicies
for (integer j = 1; j < LFSR_W; j = j + 1) begin
if (LFSR_POLY[j]) begin
state_val = lfsr_mask_state[j-1] ^ state_val;
data_val = lfsr_mask_data[j-1] ^ data_val;
end
end
// shift
for (integer j = LFSR_W-1; j > 0; j = j - 1) begin
lfsr_mask_state[j] = lfsr_mask_state[j-1];
lfsr_mask_data[j] = lfsr_mask_data[j-1];
end
if (LFSR_FEED_FORWARD) begin
// only shift in new input data
state_val = '0;
data_val = '0;
end
lfsr_mask_state[0] = state_val;
lfsr_mask_data[0] = data_val;
end
end else if (STATE_SHIFT_PRE < 0) begin
// reverse shift
for (integer i = 0; i < -STATE_SHIFT_PRE; i = i + 1) begin
state_val = lfsr_mask_state[0];
data_val = lfsr_mask_data[0];
// shift
for (integer j = 0; j < LFSR_W-1; j = j + 1) begin
lfsr_mask_state[j] = lfsr_mask_state[j+1];
lfsr_mask_data[j] = lfsr_mask_data[j+1];
end
if (LFSR_FEED_FORWARD) begin
// only shift in new input data
state_val = '0;
data_val = '0;
end
lfsr_mask_state[LFSR_W-1] = state_val;
lfsr_mask_data[LFSR_W-1] = data_val;
// add XOR inputs from correct indicies
for (integer j = 1; j < LFSR_W; j = j + 1) begin
if (LFSR_POLY[j]) begin
state_val = lfsr_mask_state[j-1] ^ state_val;
data_val = lfsr_mask_data[j-1] ^ data_val;
end
end
end
end
// shift
for (integer j = LFSR_W-1; j > 0; j = j - 1) begin
lfsr_mask_state[j] = lfsr_mask_state[j-1];
lfsr_mask_data[j] = lfsr_mask_data[j-1];
// Shift data
if (DATA_IN_EN || DATA_OUT_EN) begin
for (data_mask = {1'b1, {DATA_W-1{1'b0}}}; data_mask != 0; data_mask = data_mask >> 1) begin
// determine shift in value
// current value in last FF, XOR with input data bit (MSB first)
state_val = lfsr_mask_state[LFSR_W-1];
data_val = lfsr_mask_data[LFSR_W-1];
data_val = data_val ^ data_mask;
// add XOR inputs from correct indicies
for (integer j = 1; j < LFSR_W; j = j + 1) begin
if (LFSR_POLY[j]) begin
state_val = lfsr_mask_state[j-1] ^ state_val;
data_val = lfsr_mask_data[j-1] ^ data_val;
end
end
// shift
for (integer j = LFSR_W-1; j > 0; j = j - 1) begin
lfsr_mask_state[j] = lfsr_mask_state[j-1];
lfsr_mask_data[j] = lfsr_mask_data[j-1];
end
for (integer j = DATA_W-1; j > 0; j = j - 1) begin
output_mask_state[j] = output_mask_state[j-1];
output_mask_data[j] = output_mask_data[j-1];
end
output_mask_state[0] = state_val;
output_mask_data[0] = data_val;
if (LFSR_FEED_FORWARD) begin
// only shift in new input data
state_val = '0;
data_val = data_mask;
end
lfsr_mask_state[0] = state_val;
lfsr_mask_data[0] = data_val;
end
for (integer j = DATA_W-1; j > 0; j = j - 1) begin
output_mask_state[j] = output_mask_state[j-1];
output_mask_data[j] = output_mask_data[j-1];
end
// Shift state alone after shifting data
if (STATE_SHIFT_POST > 0) begin
for (integer i = 0; i < STATE_SHIFT_POST; i = i + 1) begin
// determine shift in value
// current value in last FF, XOR with input data bit (MSB first)
state_val = lfsr_mask_state[LFSR_W-1];
data_val = lfsr_mask_data[LFSR_W-1];
// add XOR inputs from correct indicies
for (integer j = 1; j < LFSR_W; j = j + 1) begin
if (LFSR_POLY[j]) begin
state_val = lfsr_mask_state[j-1] ^ state_val;
data_val = lfsr_mask_data[j-1] ^ data_val;
end
end
// shift
for (integer j = LFSR_W-1; j > 0; j = j - 1) begin
lfsr_mask_state[j] = lfsr_mask_state[j-1];
lfsr_mask_data[j] = lfsr_mask_data[j-1];
end
if (LFSR_FEED_FORWARD) begin
// only shift in new input data
state_val = '0;
data_val = '0;
end
lfsr_mask_state[0] = state_val;
lfsr_mask_data[0] = data_val;
end
output_mask_state[0] = state_val;
output_mask_data[0] = data_val;
if (LFSR_FEED_FORWARD) begin
// only shift in new input data
state_val = '0;
data_val = data_mask;
end else if (STATE_SHIFT_POST < 0) begin
// reverse shift
for (integer i = 0; i < -STATE_SHIFT_POST; i = i + 1) begin
state_val = lfsr_mask_state[0];
data_val = lfsr_mask_data[0];
// shift
for (integer j = 0; j < LFSR_W-1; j = j + 1) begin
lfsr_mask_state[j] = lfsr_mask_state[j+1];
lfsr_mask_data[j] = lfsr_mask_data[j+1];
end
if (LFSR_FEED_FORWARD) begin
// only shift in new input data
state_val = '0;
data_val = '0;
end
lfsr_mask_state[LFSR_W-1] = state_val;
lfsr_mask_data[LFSR_W-1] = data_val;
// add XOR inputs from correct indicies
for (integer j = 1; j < LFSR_W; j = j + 1) begin
if (LFSR_POLY[j]) begin
state_val = lfsr_mask_state[j-1] ^ state_val;
data_val = lfsr_mask_data[j-1] ^ data_val;
end
end
end
lfsr_mask_state[0] = state_val;
lfsr_mask_data[0] = data_val;
end
end

View File

@@ -131,7 +131,7 @@ DATA_W
Specify width of input data bus. The module will perform one shift per input data bit,
so if the input data bus is not required tie data_in to zero and set DATA_W to the
required number of shifts per clock cycle.
required number of shifts per clock cycle.
Settings for common LFSR/CRC implementations:
@@ -157,7 +157,9 @@ taxi_lfsr #(
.REVERSE(REVERSE),
.DATA_W(DATA_W),
.DATA_IN_EN(1'b1),
.DATA_OUT_EN(1'b0)
.DATA_OUT_EN(1'b0),
.STATE_SHIFT_PRE(0),
.STATE_SHIFT_POST(0)
)
lfsr_inst (
.data_in(data_in),

View File

@@ -164,7 +164,9 @@ taxi_lfsr #(
.REVERSE(REVERSE),
.DATA_W(DATA_W),
.DATA_IN_EN(SELF_SYNC),
.DATA_OUT_EN(1'b1)
.DATA_OUT_EN(1'b1),
.STATE_SHIFT_PRE(0),
.STATE_SHIFT_POST(0)
)
lfsr_inst (
.data_in(SELF_SYNC ? data_in : '0),

View File

@@ -167,7 +167,9 @@ taxi_lfsr #(
.REVERSE(REVERSE),
.DATA_W(DATA_W),
.DATA_IN_EN(1'b1),
.DATA_OUT_EN(1'b1)
.DATA_OUT_EN(1'b1),
.STATE_SHIFT_PRE(0),
.STATE_SHIFT_POST(0)
)
lfsr_inst (
.data_in(INVERT ? ~data_in : data_in),

View File

@@ -161,7 +161,9 @@ taxi_lfsr #(
.REVERSE(REVERSE),
.DATA_W(DATA_W),
.DATA_IN_EN(1'b0),
.DATA_OUT_EN(1'b1)
.DATA_OUT_EN(1'b1),
.STATE_SHIFT_PRE(0),
.STATE_SHIFT_POST(0)
)
lfsr_inst (
.data_in('0),

View File

@@ -164,7 +164,9 @@ taxi_lfsr #(
.REVERSE(REVERSE),
.DATA_W(DATA_W),
.DATA_IN_EN(SELF_SYNC),
.DATA_OUT_EN(1'b1)
.DATA_OUT_EN(1'b1),
.STATE_SHIFT_PRE(0),
.STATE_SHIFT_POST(0)
)
lfsr_inst (
.data_in(SELF_SYNC ? data_in : '0),

View File

@@ -37,6 +37,8 @@ export PARAM_REVERSE ?= "1'b1"
export PARAM_DATA_W ?= 8
export PARAM_DATA_IN_EN ?= "1'b1"
export PARAM_DATA_OUT_EN ?= "1'b1"
export PARAM_STATE_SHIFT_PRE ?= 0
export PARAM_STATE_SHIFT_POST ?= 0
ifeq ($(SIM), icarus)
PLUSARGS += -fst

View File

@@ -2,7 +2,7 @@
# SPDX-License-Identifier: CERN-OHL-S-2.0
"""
Copyright (c) 2023-2025 FPGA Ninja, LLC
Copyright (c) 2023-2026 FPGA Ninja, LLC
Authors:
- Alex Forencich
@@ -37,8 +37,40 @@ def chunks(lst, n, padvalue=None):
return itertools.zip_longest(*[iter(lst)]*n, fillvalue=padvalue)
def crc32(data):
return zlib.crc32(data) & 0xffffffff
def crc32(data, crc=0xffffffff, poly=0xedb88320):
# return zlib.crc32(data) & 0xffffffff
for d in data:
crc = crc ^ d
for bit in range(0, 8):
if crc & 1:
crc = (crc >> 1) ^ poly
else:
crc = crc >> 1
return ~crc & 0xffffffff
def crc32_shift(crc, count, poly=0xedb88320):
crc = ~crc & 0xffffffff
if count > 0:
# shift forwards
for i in range(count):
if crc & 1:
crc = (crc >> 1) ^ poly
else:
crc = crc >> 1
elif count < 0:
# shift backwards
for i in range(-count):
if crc & 0x80000000:
crc ^= poly
crc = (crc << 1) | 1
else:
crc = crc << 1
crc = crc & 0xffffffff
return ~crc & 0xffffffff
def crc32c(data, crc=0xffffffff, poly=0x82f63b78):
@@ -149,12 +181,65 @@ async def run_test_prbs(dut, ref_prbs):
await Timer(10, 'ns')
async def run_test_shift_crc(dut):
data_width = len(dut.data_in)
byte_lanes = data_width // 8
state_width = len(dut.state_in)
state_mask = 2**state_width-1
shift = int(dut.STATE_SHIFT_PRE.value)
if shift > 0 and shift & 0x80000000:
shift -= 0x100000000
tb = TB(dut)
await Timer(10, 'ns')
val = 0x12345678
dut.state_in.value = ~val & state_mask
dut.data_in.value = 0
await Timer(10, 'ns')
crc = ~int(dut.state_out.value) & state_mask
ref = crc32_shift(val, shift)
tb.log.info("Shifted CRC: 0x%x (ref: 0x%x)", crc, ref)
assert crc == ref
await Timer(10, 'ns')
for k in range(10):
val = crc32(bytearray(k))
dut.state_in.value = ~val & state_mask
dut.data_in.value = 0
await Timer(10, 'ns')
crc = ~int(dut.state_out.value) & state_mask
ref = crc32_shift(val, shift)
tb.log.info("Shifted CRC: 0x%x (ref: 0x%x)", crc, ref)
assert crc == ref
await Timer(10, 'ns')
if getattr(cocotb, 'top', None) is not None:
if cocotb.top.LFSR_POLY.value == 0x4c11db7:
factory = TestFactory(run_test_crc)
factory.add_option("ref_crc", [crc32])
factory.generate_tests()
if cocotb.top.STATE_SHIFT_PRE.value == 0:
factory = TestFactory(run_test_crc)
factory.add_option("ref_crc", [crc32])
factory.generate_tests()
else:
factory = TestFactory(run_test_shift_crc)
factory.generate_tests()
if cocotb.top.LFSR_POLY.value == 0x1edc6f41:
factory = TestFactory(run_test_crc)
@@ -191,17 +276,19 @@ def process_f_files(files):
return list(lst.values())
@pytest.mark.parametrize(("lfsr_w", "lfsr_poly", "lfsr_galois", "reverse", "data_w"), [
(32, "32'h4c11db7", 1, 1, 8),
(32, "32'h4c11db7", 1, 1, 64),
(32, "32'h1edc6f41", 1, 1, 8),
(32, "32'h1edc6f41", 1, 1, 64),
(9, "9'h021", 0, 0, 8),
(9, "9'h021", 0, 0, 64),
(31, "31'h10000001", 0, 0, 8),
(31, "31'h10000001", 0, 0, 64),
@pytest.mark.parametrize(("lfsr_w", "lfsr_poly", "lfsr_galois", "reverse", "data_w", "data_shift", "pre_shift"), [
(32, "32'h4c11db7", 1, 1, 8, 1, 0),
(32, "32'h4c11db7", 1, 1, 64, 1, 0),
(32, "32'h4c11db7", 1, 1, 64, 0, 8),
(32, "32'h4c11db7", 1, 1, 64, 0, -8),
(32, "32'h1edc6f41", 1, 1, 8, 1, 0),
(32, "32'h1edc6f41", 1, 1, 64, 1, 0),
(9, "9'h021", 0, 0, 8, 1, 0),
(9, "9'h021", 0, 0, 64, 1, 0),
(31, "31'h10000001", 0, 0, 8, 1, 0),
(31, "31'h10000001", 0, 0, 64, 1, 0),
])
def test_taxi_lfsr(request, lfsr_w, lfsr_poly, lfsr_galois, reverse, data_w):
def test_taxi_lfsr(request, lfsr_w, lfsr_poly, lfsr_galois, reverse, data_w, data_shift, pre_shift):
dut = "taxi_lfsr"
module = os.path.splitext(os.path.basename(__file__))[0]
toplevel = dut
@@ -220,8 +307,10 @@ def test_taxi_lfsr(request, lfsr_w, lfsr_poly, lfsr_galois, reverse, data_w):
parameters['LFSR_FEED_FORWARD'] = "1'b0"
parameters['REVERSE'] = f"1'b{reverse}"
parameters['DATA_W'] = data_w
parameters['DATA_IN_EN'] = "1'b1"
parameters['DATA_OUT_EN'] = "1'b1"
parameters['DATA_IN_EN'] = f"1'b{data_shift}"
parameters['DATA_OUT_EN'] = f"1'b{data_shift}"
parameters['STATE_SHIFT_PRE'] = pre_shift
parameters['STATE_SHIFT_POST'] = 0
extra_env = {f'PARAM_{k}': str(v) for k, v in parameters.items()}