Reorganize repository

Signed-off-by: Alex Forencich <alex@alexforencich.com>
This commit is contained in:
Alex Forencich
2025-05-18 12:25:59 -07:00
parent 8cdae180a1
commit 66b53d98a2
690 changed files with 2314 additions and 1581 deletions

313
src/lfsr/rtl/taxi_lfsr.sv Normal file
View File

@@ -0,0 +1,313 @@
// 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
/*
* Parametrizable combinatorial parallel LFSR/CRC
*/
module taxi_lfsr #
(
// width of LFSR
parameter LFSR_W = 31,
// LFSR polynomial
parameter logic [LFSR_W-1:0] LFSR_POLY = 31'h10000001,
// LFSR configuration: 0 for Fibonacci (PRBS), 1 for Galois (CRC)
parameter logic LFSR_GALOIS = 1'b0,
// LFSR feed forward enable
parameter logic LFSR_FEED_FORWARD = 1'b0,
// bit-reverse input and output
parameter logic REVERSE = 1'b0,
// width of data input
parameter DATA_W = 8
)
(
input wire logic [DATA_W-1:0] data_in,
input wire logic [LFSR_W-1:0] state_in,
output wire logic [DATA_W-1:0] data_out,
output wire logic [LFSR_W-1:0] state_out
);
/*
Fully parametrizable combinatorial parallel LFSR/CRC module. Implements an unrolled LFSR
next state computation, shifting DATA_W bits per pass through the module. Input data
is XORed with LFSR feedback path, tie data_in to zero if this is not required.
Works in two parts: statically computes a set of bit masks, then uses these bit masks to
select bits for XORing to compute the next state.
Ports:
data_in
Data bits to be shifted through the LFSR (DATA_W bits)
state_in
LFSR/CRC current state input (LFSR_W bits)
data_out
Data bits shifted out of LFSR (DATA_W bits)
state_out
LFSR/CRC next state output (LFSR_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_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
'----' '----' '----' '----' '----'
LFSR_FEED_FORWARD
Generate feed forward instead of feed back LFSR. Enable this for PRBS checking and self-
synchronous descrambling.
Fibonacci feed-forward style (example for 64b66b descrambler, 0x8000000001)
DIN (LSB first)
|
| .----. .----. .----. .----. .----. .----.
+->| 0 |->| 1 |->...->| 38 |-+->| 39 |->...->| 56 |->| 57 |--.
| '----' '----' '----' | '----' '----' '----' |
| V |
(+)<---------------------------(+)------------------------------'
|
V
DOUT
Galois feed-forward style
,-------------------+-------------------------+------------+--- DIN (MSB first)
| | | |
| .----. .----. V .----. .----. V .----. V
`->| 0 |->| 1 |->(+)->| 2 |->...->| 14 |->(+)->| 15 |->(+)-> DOUT
'----' '----' '----' '----' '----'
REVERSE
Bit-reverse LFSR input and output. Shifts MSB first by default, set REVERSE for LSB first.
DATA_W
Specify width of input and output 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.
Settings for common LFSR/CRC implementations:
Name Configuration Length Polynomial Initial value Notes
CRC16-IBM Galois, bit-reverse 16 16'h8005 16'hffff
CRC16-CCITT Galois 16 16'h1021 16'h1d0f
CRC32 Galois, bit-reverse 32 32'h04c11db7 32'hffffffff Ethernet FCS; invert final output
CRC32C Galois, bit-reverse 32 32'h1edc6f41 32'hffffffff iSCSI, Intel CRC32 instruction; 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
*/
function [LFSR_W+DATA_W-1:0][LFSR_W+DATA_W-1:0] lfsr_mask();
logic [LFSR_W-1:0] lfsr_mask_state[LFSR_W-1:0];
logic [DATA_W-1:0] lfsr_mask_data[LFSR_W-1:0];
logic [LFSR_W-1:0] output_mask_state[DATA_W-1:0];
logic [DATA_W-1:0] output_mask_data[DATA_W-1:0];
logic [LFSR_W-1:0] state_val;
logic [DATA_W-1:0] data_val;
logic [DATA_W-1:0] data_mask;
// init bit masks
for (integer i = 0; i < LFSR_W; i = i + 1) begin
lfsr_mask_state[i] = '0;
lfsr_mask_state[i][i] = 1'b1;
lfsr_mask_data[i] = '0;
end
for (integer i = 0; i < DATA_W; i = i + 1) begin
output_mask_state[i] = '0;
if (i < LFSR_W) begin
output_mask_state[i][i] = 1'b1;
end
output_mask_data[i] = '0;
end
// 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;
// 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 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;
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
end
if (REVERSE) begin
// output reversed
for (integer i = 0; i < LFSR_W; i = i + 1) begin
for (integer j = 0; j < LFSR_W; j = j + 1) begin
lfsr_mask[i][j] = lfsr_mask_state[LFSR_W-i-1][LFSR_W-j-1];
end
for (integer j = 0; j < DATA_W; j = j + 1) begin
lfsr_mask[i][j+LFSR_W] = lfsr_mask_data[LFSR_W-i-1][DATA_W-j-1];
end
end
for (integer i = 0; i < DATA_W; i = i + 1) begin
for (integer j = 0; j < LFSR_W; j = j + 1) begin
lfsr_mask[i+LFSR_W][j] = output_mask_state[DATA_W-i-1][LFSR_W-j-1];
end
for (integer j = 0; j < DATA_W; j = j + 1) begin
lfsr_mask[i+LFSR_W][j+LFSR_W] = output_mask_data[DATA_W-i-1][DATA_W-j-1];
end
end
end else begin
// output normal
for (integer i = 0; i < LFSR_W; i = i + 1) begin
lfsr_mask[i] = {lfsr_mask_data[i], lfsr_mask_state[i]};
end
for (integer i = 0; i < DATA_W; i = i + 1) begin
lfsr_mask[i+LFSR_W] = {output_mask_data[i], output_mask_state[i]};
end
end
endfunction
wire [LFSR_W+DATA_W-1:0][LFSR_W+DATA_W-1:0] mask = lfsr_mask();
for (genvar n = 0; n < LFSR_W; n = n + 1) begin : lfsr_state
assign state_out[n] = ^({data_in, state_in} & mask[n]);
end
for (genvar n = 0; n < DATA_W; n = n + 1) begin : lfsr_data
assign data_out[n] = ^({data_in, state_in} & mask[n+LFSR_W]);
end
endmodule
`resetall

View File

@@ -0,0 +1,179 @@
// 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 CRC generator
*/
module taxi_lfsr_crc #
(
// width of LFSR
parameter LFSR_W = 32,
// LFSR polynomial
parameter logic [LFSR_W-1:0] LFSR_POLY = 32'h04c11db7,
// 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'b1,
// bit-reverse input and output
parameter logic REVERSE = 1'b1,
// invert output
parameter logic INVERT = 1'b1,
// width of data input and output
parameter DATA_W = 8
)
(
input wire logic clk,
input wire logic rst,
input wire logic [DATA_W-1:0] data_in,
input wire logic data_in_valid,
output wire logic [LFSR_W-1:0] crc_out
);
/*
Fully parametrizable combinatorial parallel LFSR CRC module. Implements an unrolled LFSR
next state computation.
Ports:
clk
Clock input
rst
Reset module, set state to LFSR_INIT
data_in
CRC data input
data_in_valid
Shift input data through CRC when asserted
data_out
LFSR output (OUTPUT_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 input and output. Shifts MSB first by default, set REVERSE for LSB first.
INVERT
Bitwise invert CRC output.
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.
Settings for common LFSR/CRC implementations:
Name Configuration Length Polynomial Initial value Notes
CRC16-IBM Galois, bit-reverse 16 16'h8005 16'hffff
CRC16-CCITT Galois 16 16'h1021 16'h1d0f
CRC32 Galois, bit-reverse 32 32'h04c11db7 32'hffffffff Ethernet FCS; invert final output
CRC32C Galois, bit-reverse 32 32'h1edc6f41 32'hffffffff iSCSI, Intel CRC32 instruction; invert final output
*/
logic [LFSR_W-1:0] state_reg = LFSR_INIT;
wire [LFSR_W-1:0] lfsr_state;
assign crc_out = INVERT ? ~state_reg : state_reg;
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(data_in),
.state_in(state_reg),
.data_out(),
.state_out(lfsr_state)
);
always_ff @(posedge clk) begin
if (data_in_valid) begin
state_reg <= lfsr_state;
end
if (rst) begin
state_reg <= LFSR_INIT;
end
end
endmodule
`resetall

View File

@@ -0,0 +1,185 @@
// 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 descrambler
*/
module taxi_lfsr_descramble #
(
// width of LFSR
parameter LFSR_W = 58,
// LFSR polynomial
parameter logic [LFSR_W-1:0] LFSR_POLY = 58'h8000000001,
// 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'b1,
// width of data bus
parameter DATA_W = 64
)
(
input wire logic clk,
input wire logic rst,
input wire logic [DATA_W-1:0] data_in,
input wire logic data_in_valid,
output wire logic [DATA_W-1:0] data_out
);
/*
Fully parametrizable combinatorial parallel LFSR CRC module. Implements an unrolled LFSR
next state computation.
Ports:
clk
Clock input
rst
Reset module, set state to LFSR_INIT
data_in
Scrambled data input (DATA_W bits)
data_in_valid
Shift input data through CRC when asserted
data_out
Descrambled data 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 input and output.
DATA_W
Specify width of the data bus. The module will perform one shift per input data bit.
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;
logic [DATA_W-1:0] output_reg = '0;
wire [DATA_W-1:0] lfsr_data;
wire [LFSR_W-1:0] lfsr_state;
assign data_out = output_reg;
taxi_lfsr #(
.LFSR_W(LFSR_W),
.LFSR_POLY(LFSR_POLY),
.LFSR_GALOIS(LFSR_GALOIS),
.LFSR_FEED_FORWARD('1),
.REVERSE(REVERSE),
.DATA_W(DATA_W)
)
lfsr_inst (
.data_in(data_in),
.state_in(state_reg),
.data_out(lfsr_data),
.state_out(lfsr_state)
);
always_ff @(posedge clk) begin
if (data_in_valid) begin
state_reg <= lfsr_state;
output_reg <= lfsr_data;
end
if (rst) begin
state_reg <= LFSR_INIT;
output_reg <= '0;
end
end
endmodule
`resetall

View File

@@ -0,0 +1,191 @@
// 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 checker
*/
module taxi_lfsr_prbs_check #
(
// 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 input
parameter logic INVERT = 1'b1,
// width of data input and output
parameter DATA_W = 8
)
(
input wire logic clk,
input wire logic rst,
input wire logic [DATA_W-1:0] data_in,
input wire logic data_in_valid,
output wire logic [DATA_W-1:0] data_out
);
/*
Fully parametrizable combinatorial parallel LFSR PRBS checker. Implements an unrolled LFSR
PRBS checker.
Ports:
clk
Clock input
rst
Reset input, set state to LFSR_INIT
data_in
PRBS data input (DATA_W bits)
data_in_valid
Shift input data through LFSR when asserted
data_out
Error 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 input.
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;
logic [DATA_W-1:0] output_reg = '0;
wire [DATA_W-1:0] lfsr_data;
wire [LFSR_W-1:0] lfsr_state;
assign data_out = output_reg;
taxi_lfsr #(
.LFSR_W(LFSR_W),
.LFSR_POLY(LFSR_POLY),
.LFSR_GALOIS(LFSR_GALOIS),
.LFSR_FEED_FORWARD('1),
.REVERSE(REVERSE),
.DATA_W(DATA_W)
)
lfsr_inst (
.data_in(INVERT ? ~data_in : data_in),
.state_in(state_reg),
.data_out(lfsr_data),
.state_out(lfsr_state)
);
always_ff @(posedge clk) begin
if (data_in_valid) begin
state_reg <= lfsr_state;
output_reg <= lfsr_data;
end
if (rst) begin
state_reg <= LFSR_INIT;
output_reg <= '0;
end
end
endmodule
`resetall

View File

@@ -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

View File

@@ -0,0 +1,185 @@
// 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 scrambler
*/
module taxi_lfsr_scramble #
(
// width of LFSR
parameter LFSR_W = 58,
// LFSR polynomial
parameter logic [LFSR_W-1:0] LFSR_POLY = 58'h8000000001,
// 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'b1,
// width of data bus
parameter DATA_W = 64
)
(
input wire logic clk,
input wire logic rst,
input wire logic [DATA_W-1:0] data_in,
input wire logic data_in_valid,
output wire logic [DATA_W-1:0] data_out
);
/*
Fully parametrizable combinatorial parallel LFSR CRC module. Implements an unrolled LFSR
next state computation.
Ports:
clk
Clock input
rst
Reset module, set state to LFSR_INIT
data_in
Unscrambled data input (DATA_W bits)
data_in_valid
Shift input data through CRC when asserted
data_out
Scrambled data 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 input and output.
DATA_W
Specify width of the data bus. The module will perform one shift per input data bit.
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
*/
reg [LFSR_W-1:0] state_reg = LFSR_INIT;
reg [DATA_W-1:0] output_reg = '0;
wire [DATA_W-1:0] lfsr_data;
wire [LFSR_W-1:0] lfsr_state;
assign data_out = output_reg;
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(data_in),
.state_in(state_reg),
.data_out(lfsr_data),
.state_out(lfsr_state)
);
always_ff @(posedge clk) begin
if (data_in_valid) begin
state_reg <= lfsr_state;
output_reg <= lfsr_data;
end
if (rst) begin
state_reg <= LFSR_INIT;
output_reg <= '0;
end
end
endmodule
`resetall

View File

@@ -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
RTL_DIR = ../../rtl
DUT = taxi_lfsr
COCOTB_TEST_MODULES = test_$(DUT)
COCOTB_TOPLEVEL = $(DUT)
MODULE = $(COCOTB_TEST_MODULES)
TOPLEVEL = $(COCOTB_TOPLEVEL)
VERILOG_SOURCES += $(RTL_DIR)/$(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_LFSR_W ?= 32
export PARAM_LFSR_POLY ?= "32'h4c11db7"
export PARAM_LFSR_GALOIS ?= "1'b1"
export PARAM_LFSR_FEED_FORWARD ?= "1'b0"
export PARAM_REVERSE ?= "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

View File

@@ -0,0 +1,238 @@
#!/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 zlib
import pytest
import cocotb_test.simulator
import cocotb
from cocotb.triggers import Timer
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)
dut.data_in.setimmediatevalue(0)
dut.state_in.setimmediatevalue(0)
def chunks(lst, n, padvalue=None):
return itertools.zip_longest(*[iter(lst)]*n, fillvalue=padvalue)
def crc32(data):
return zlib.crc32(data) & 0xffffffff
def crc32c(data, crc=0xffffffff, poly=0x82f63b78):
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
async def run_test_crc(dut, ref_crc):
data_width = len(dut.data_in)
byte_lanes = data_width // 8
state_width = len(dut.state_in)
state_mask = 2**state_width-1
tb = TB(dut)
await Timer(10, 'ns')
block = bytes([(x+1)*0x11 for x in range(byte_lanes)])
dut.state_in.value = state_mask
dut.data_in.value = int.from_bytes(block, 'little')
await Timer(10, 'ns')
val = ~dut.state_out.value.integer & state_mask
ref = ref_crc(block)
tb.log.info("CRC: 0x%x (ref: 0x%x)", val, ref)
assert val == ref
await Timer(10, 'ns')
block = bytearray(itertools.islice(itertools.cycle(range(256)), 1024))
dut.state_in.value = state_mask
for b in chunks(block, byte_lanes):
dut.data_in.value = int.from_bytes(b, 'little')
await Timer(10, 'ns')
dut.state_in.value = dut.state_out.value
val = ~int(dut.state_out.value) & state_mask
ref = ref_crc(block)
tb.log.info("CRC: 0x%x (ref: 0x%x)", val, ref)
assert val == ref
await Timer(10, 'ns')
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_in)
byte_lanes = data_width // 8
data_mask = 2**data_width-1
state_width = len(dut.state_in)
state_mask = 2**state_width-1
tb = TB(dut)
await Timer(10, 'ns')
dut.state_in.value = state_mask
dut.data_in.value = 0
gen = chunks(ref_prbs(), byte_lanes)
await Timer(10, 'ns')
for i in range(512):
ref = int.from_bytes(bytes(next(gen)), 'big')
val = ~int(dut.data_out.value) & data_mask
tb.log.info("PRBS: 0x%x (ref: 0x%x)", val, ref)
assert ref == val
dut.state_in.value = dut.state_out.value
await Timer(10, 'ns')
if cocotb.SIM_NAME:
if cocotb.top.LFSR_POLY.value == 0x4c11db7:
factory = TestFactory(run_test_crc)
factory.add_option("ref_crc", [crc32])
factory.generate_tests()
if cocotb.top.LFSR_POLY.value == 0x1edc6f41:
factory = TestFactory(run_test_crc)
factory.add_option("ref_crc", [crc32c])
factory.generate_tests()
if cocotb.top.LFSR_POLY.value == 0x021:
factory = TestFactory(run_test_prbs)
factory.add_option("ref_prbs", [prbs9])
factory.generate_tests()
if 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_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),
])
def test_taxi_lfsr(request, lfsr_w, lfsr_poly, lfsr_galois, reverse, data_w):
dut = "taxi_lfsr"
module = os.path.splitext(os.path.basename(__file__))[0]
toplevel = dut
verilog_sources = [
os.path.join(rtl_dir, f"{dut}.sv"),
]
verilog_sources = process_f_files(verilog_sources)
parameters = {}
parameters['LFSR_W'] = lfsr_w
parameters['LFSR_POLY'] = lfsr_poly
parameters['LFSR_GALOIS'] = f"1'b{lfsr_galois}"
parameters['LFSR_FEED_FORWARD'] = "1'b0"
parameters['REVERSE'] = f"1'b{reverse}"
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,
)

View File

@@ -0,0 +1,54 @@
# 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
RTL_DIR = ../../rtl
DUT = taxi_lfsr_crc
COCOTB_TEST_MODULES = test_$(DUT)
COCOTB_TOPLEVEL = $(DUT)
MODULE = $(COCOTB_TEST_MODULES)
TOPLEVEL = $(COCOTB_TOPLEVEL)
VERILOG_SOURCES += $(RTL_DIR)/$(DUT).sv
VERILOG_SOURCES += $(RTL_DIR)/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 ?= 32
export PARAM_LFSR_POLY ?= "32'h4c11db7"
export PARAM_LFSR_INIT ?= "'1"
export PARAM_LFSR_GALOIS ?= "1'b1"
export PARAM_REVERSE ?= "1'b1"
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

View File

@@ -0,0 +1,189 @@
#!/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 zlib
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.data_in.setimmediatevalue(0)
dut.data_in_valid.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 crc32(data):
return zlib.crc32(data) & 0xffffffff
def crc32c(data, crc=0xffffffff, poly=0x82f63b78):
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
async def run_test_crc(dut, ref_crc):
data_width = len(dut.data_in)
byte_lanes = data_width // 8
tb = TB(dut)
await tb.reset()
block = bytes([(x+1)*0x11 for x in range(byte_lanes)])
dut.data_in.value = int.from_bytes(block, 'little')
dut.data_in_valid.value = 1
await RisingEdge(dut.clk)
dut.data_in_valid.value = 0
await RisingEdge(dut.clk)
val = dut.crc_out.value.integer
ref = ref_crc(block)
tb.log.info("CRC: 0x%x (ref: 0x%x)", val, ref)
assert val == ref
await tb.reset()
block = bytearray(itertools.islice(itertools.cycle(range(256)), 1024))
for b in chunks(block, byte_lanes):
dut.data_in.value = int.from_bytes(b, 'little')
dut.data_in_valid.value = 1
await RisingEdge(dut.clk)
dut.data_in_valid.value = 0
await RisingEdge(dut.clk)
val = dut.crc_out.value.integer
ref = ref_crc(block)
tb.log.info("CRC: 0x%x (ref: 0x%x)", val, ref)
assert val == ref
await RisingEdge(dut.clk)
await RisingEdge(dut.clk)
if cocotb.SIM_NAME:
if int(cocotb.top.LFSR_POLY.value) == 0x4c11db7:
factory = TestFactory(run_test_crc)
factory.add_option("ref_crc", [crc32])
factory.generate_tests()
if int(cocotb.top.LFSR_POLY.value) == 0x1edc6f41:
factory = TestFactory(run_test_crc)
factory.add_option("ref_crc", [crc32c])
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"), [
(32, "32'h4c11db7", "'1", 1, 1, 1, 8),
(32, "32'h4c11db7", "'1", 1, 1, 1, 64),
(32, "32'h1edc6f41", "'1", 1, 1, 1, 8),
(32, "32'h1edc6f41", "'1", 1, 1, 1, 64),
])
def test_taxi_lfsr_crc(request, lfsr_w, lfsr_poly, lfsr_init, lfsr_galois, reverse, invert, data_w):
dut = "taxi_lfsr_crc"
module = os.path.splitext(os.path.basename(__file__))[0]
toplevel = dut
verilog_sources = [
os.path.join(rtl_dir, f"{dut}.sv"),
os.path.join(rtl_dir, "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,
)

View File

@@ -0,0 +1,53 @@
# 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
RTL_DIR = ../../rtl
DUT = taxi_lfsr_descramble
COCOTB_TEST_MODULES = test_$(DUT)
COCOTB_TOPLEVEL = $(DUT)
MODULE = $(COCOTB_TEST_MODULES)
TOPLEVEL = $(COCOTB_TOPLEVEL)
VERILOG_SOURCES += $(RTL_DIR)/$(DUT).sv
VERILOG_SOURCES += $(RTL_DIR)/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 ?= 58
export PARAM_LFSR_POLY ?= "58'h8000000001"
export PARAM_LFSR_INIT ?= "'1"
export PARAM_LFSR_GALOIS ?= "1'b0"
export PARAM_REVERSE ?= "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

View File

@@ -0,0 +1,187 @@
#!/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.data_in.setimmediatevalue(0)
dut.data_in_valid.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 scramble_64b66b(data, state=0x3ffffffffffffff):
data_out = bytearray()
for d in data:
b = 0
for i in range(8):
if bool(state & (1 << 38)) ^ bool(state & (1 << 57)) ^ bool(d & (1 << i)):
state = ((state & 0x1ffffffffffffff) << 1) | 1
b = b | (1 << i)
else:
state = (state & 0x1ffffffffffffff) << 1
data_out.append(b)
return data_out
def descramble_64b66b(data, state=0x3ffffffffffffff):
data_out = bytearray()
for d in data:
b = 0
for i in range(8):
if bool(state & (1 << 38)) ^ bool(state & (1 << 57)) ^ bool(d & (1 << i)):
b = b | (1 << i)
state = (state & 0x1ffffffffffffff) << 1 | bool(d & (1 << i))
data_out += bytearray([b])
return data_out
async def run_test_descramble(dut, ref_scramble):
data_width = len(dut.data_in)
byte_lanes = data_width // 8
tb = TB(dut)
await tb.reset()
block = bytearray(itertools.islice(itertools.cycle(range(256)), 1024))
scr = scramble_64b66b(block)
dscr = descramble_64b66b(scr)
assert dscr == block
ref_iter = iter(chunks(block, byte_lanes))
first = True
for b in chunks(scr, byte_lanes):
dut.data_in.value = int.from_bytes(b, 'little')
dut.data_in_valid.value = 1
await RisingEdge(dut.clk)
val = dut.data_out.value.integer
if not first:
ref = int.from_bytes(bytes(next(ref_iter)), 'little')
tb.log.info("Descrambled: 0x%x (ref: 0x%x)", val, ref)
assert ref == val
first = False
dut.data_in_valid.value = 0
await RisingEdge(dut.clk)
if cocotb.SIM_NAME:
# if cocotb.top.LFSR_POLY.value == 0x8000000001:
if int(cocotb.top.LFSR_W.value) == 58:
factory = TestFactory(run_test_descramble)
factory.add_option("ref_scramble", [scramble_64b66b])
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", "data_w"), [
(58, "58'h8000000001", "'1", 0, 1, 8),
(58, "58'h8000000001", "'1", 0, 1, 64),
])
def test_taxi_lfsr_descramble(request, lfsr_w, lfsr_poly, lfsr_init, lfsr_galois, reverse, data_w):
dut = "taxi_lfsr_descramble"
module = os.path.splitext(os.path.basename(__file__))[0]
toplevel = dut
verilog_sources = [
os.path.join(rtl_dir, f"{dut}.sv"),
os.path.join(rtl_dir, "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['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,
)

View File

@@ -0,0 +1,54 @@
# 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
RTL_DIR = ../../rtl
DUT = taxi_lfsr_prbs_check
COCOTB_TEST_MODULES = test_$(DUT)
COCOTB_TOPLEVEL = $(DUT)
MODULE = $(COCOTB_TEST_MODULES)
TOPLEVEL = $(COCOTB_TOPLEVEL)
VERILOG_SOURCES += $(RTL_DIR)/$(DUT).sv
VERILOG_SOURCES += $(RTL_DIR)/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

View File

@@ -0,0 +1,223 @@
#!/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.data_in.setimmediatevalue(0)
dut.data_in_valid.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
def count_set_bits(n):
cnt = 0
while n:
n &= n - 1
cnt += 1
return cnt
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)
err_cnt = 0
for i in range(512):
dut.data_in.value = int.from_bytes(bytes(next(gen)), 'big')
dut.data_in_valid.value = 1
val = dut.data_out.value.integer
tb.log.info("Error value: 0x%x", val)
err_cnt += count_set_bits(val)
assert val == 0
await RisingEdge(dut.clk)
dut.data_in_valid.value = 0
tb.log.info("Error count: %d", err_cnt)
assert err_cnt == 0
await tb.reset()
tb.log.info("Single error test")
gen = chunks(ref_prbs(), byte_lanes)
err_cnt = 0
for i in range(64):
val = int.from_bytes(bytes(next(gen)), 'big')
if i == 32:
val = val ^ (1 << (data_width // 2))
dut.data_in.value = val
dut.data_in_valid.value = 1
val = dut.data_out.value.integer
tb.log.info("Error value: 0x%x", val)
err_cnt += count_set_bits(val)
await RisingEdge(dut.clk)
dut.data_in_valid.value = 0
tb.log.info("Error count: %d", err_cnt)
# one bit set per tap
assert err_cnt == 3
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_check(request, lfsr_w, lfsr_poly, lfsr_init, lfsr_galois, reverse, invert, data_w):
dut = "taxi_lfsr_prbs_check"
module = os.path.splitext(os.path.basename(__file__))[0]
toplevel = dut
verilog_sources = [
os.path.join(rtl_dir, f"{dut}.sv"),
os.path.join(rtl_dir, "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,
)

View File

@@ -0,0 +1,54 @@
# 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
RTL_DIR = ../../rtl
DUT = taxi_lfsr_prbs_gen
COCOTB_TEST_MODULES = test_$(DUT)
COCOTB_TOPLEVEL = $(DUT)
MODULE = $(COCOTB_TEST_MODULES)
TOPLEVEL = $(COCOTB_TOPLEVEL)
VERILOG_SOURCES += $(RTL_DIR)/$(DUT).sv
VERILOG_SOURCES += $(RTL_DIR)/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

View File

@@ -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, f"{dut}.sv"),
os.path.join(rtl_dir, "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,
)

View File

@@ -0,0 +1,53 @@
# 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
RTL_DIR = ../../rtl
DUT = taxi_lfsr_scramble
COCOTB_TEST_MODULES = test_$(DUT)
COCOTB_TOPLEVEL = $(DUT)
MODULE = $(COCOTB_TEST_MODULES)
TOPLEVEL = $(COCOTB_TOPLEVEL)
VERILOG_SOURCES += $(RTL_DIR)/$(DUT).sv
VERILOG_SOURCES += $(RTL_DIR)/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 ?= 58
export PARAM_LFSR_POLY ?= "58'h8000000001"
export PARAM_LFSR_INIT ?= "'1"
export PARAM_LFSR_GALOIS ?= "1'b0"
export PARAM_REVERSE ?= "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

View File

@@ -0,0 +1,170 @@
#!/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.data_in.setimmediatevalue(0)
dut.data_in_valid.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 scramble_64b66b(data, state=0x3ffffffffffffff):
data_out = bytearray()
for d in data:
b = 0
for i in range(8):
if bool(state & (1 << 38)) ^ bool(state & (1 << 57)) ^ bool(d & (1 << i)):
state = ((state & 0x1ffffffffffffff) << 1) | 1
b = b | (1 << i)
else:
state = (state & 0x1ffffffffffffff) << 1
data_out.append(b)
return data_out
async def run_test_scramble(dut, ref_scramble):
data_width = len(dut.data_in)
byte_lanes = data_width // 8
tb = TB(dut)
await tb.reset()
block = bytearray(itertools.islice(itertools.cycle(range(256)), 1024))
scr = scramble_64b66b(block)
scr_iter = iter(chunks(scr, byte_lanes))
first = True
for b in chunks(block, byte_lanes):
dut.data_in.value = int.from_bytes(b, 'little')
dut.data_in_valid.value = 1
await RisingEdge(dut.clk)
val = dut.data_out.value.integer
if not first:
ref = int.from_bytes(bytes(next(scr_iter)), 'little')
tb.log.info("Scrambled: 0x%x (ref: 0x%x)", val, ref)
assert ref == val
first = False
dut.data_in_valid.value = 0
await RisingEdge(dut.clk)
if cocotb.SIM_NAME:
# if cocotb.top.LFSR_POLY.value == 0x8000000001:
if int(cocotb.top.LFSR_W.value) == 58:
factory = TestFactory(run_test_scramble)
factory.add_option("ref_scramble", [scramble_64b66b])
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", "data_w"), [
(58, "58'h8000000001", "'1", 0, 1, 8),
(58, "58'h8000000001", "'1", 0, 1, 64),
])
def test_taxi_lfsr_scramble(request, lfsr_w, lfsr_poly, lfsr_init, lfsr_galois, reverse, data_w):
dut = "taxi_lfsr_scramble"
module = os.path.splitext(os.path.basename(__file__))[0]
toplevel = dut
verilog_sources = [
os.path.join(rtl_dir, f"{dut}.sv"),
os.path.join(rtl_dir, "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['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,
)