Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Changelog

## Unreleased

### Breaking Changes

- CRC `match_o` is now a held level instead of a one-cycle idle-cleared pulse.
It clears on reset, `init_i`, or the next checked data update. Designs that
require a pulse should qualify or edge-detect it at the protocol boundary that
marks the end of the CRC field.

### Changed

- CRC checkers compare against the computed residue for transmitted CRC fields
that include `xorout`.
- The parallel CRC reflected-input mode now reflects bits within each byte
rather than reversing the whole input word.
- CRC engines support 4- to 64-bit polynomials.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ library.
- [User guide](docs/user_guide.md): module functionality, instantiation
template, generic configuration notes, simulation flow, synthesis compile
examples, and local vendor synthesis report generation.
- [Changelog](CHANGELOG.md): compatibility notes and unreleased changes.
- [Vendor synthesis smoke tests](tools/synth/README.md): local Vivado, Quartus,
and Diamond campaign runner.
- [VHDL coding standard](docs/LM_VHDL_coding_standard.md): style rules used by
Expand Down
14 changes: 14 additions & 0 deletions docs/user_guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,20 @@ When synthesis completes, Vivado reports include:
| `lm_util_rr_arbiter` | Round-robin request arbiter. | `g_units` |
| `lm_util_tick_gen` | Periodic one-cycle tick generator. | `g_clock_div` |

CRC engines support polynomial widths from 4 to 64 bits. `init_i` reloads
`g_init_value` synchronously between messages and clears `match_o`; reset keeps
the same behavior. `init_i` has priority over `dv_i`, so callers must not assert
it on the same cycle as a data word they intend to keep. For the parallel
engine, `g_flip_data_in` reflects bits within each input byte, so reflected wide
buses keep byte order intact and require `g_data_w` to be a multiple of 8.
`match_o` compares against the CRC residue derived from `g_xor_out`/`g_flip_out`,
which allows checking streams that include the transmitted CRC field rather than
the internal un-xored CRC register value. `match_o` is a level that is held
until reset, `init_i`, or the next checked data update; qualify it with the
protocol boundary that marks the end of the CRC field. In the serial engine,
`flush_i` shifts the CRC register for output serialization and does not update
`match_o`.

`lm_util_pkg` contains shared constants, types, and utility functions. Common
configuration constants include:

Expand Down
20 changes: 13 additions & 7 deletions sim/scripts/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,16 +135,22 @@ def generate_tests(test, **param_lists):
generate_tests(test, g_polynomial = ["1021"], g_init = ["c6c6"], g_refin = [True], g_refout = [True], g_xor_out = ["0000"], g_crc_check = ["bf05"]) #CRC-16/ISO-IEC-14443-3-A
generate_tests(test, g_polynomial = ["0589"], g_init = ["0000"], g_refin = [False], g_refout = [False], g_xor_out = ["0001"], g_crc_check = ["007e"]) #CRC-16/DECT-R
generate_tests(test, g_polynomial = ["8005"], g_init = ["800d"], g_refin = [False], g_refout = [False], g_xor_out = ["0000"], g_crc_check = ["9ecf"]) #CRC-16/DDS-110
generate_tests(test, g_polynomial = ["04C11DB7"], g_init = ["FFFFFFFF"], g_refin = [True], g_refout = [True], g_xor_out = ["FFFFFFFF"], g_crc_check = ["cbf43926"]) #CRC-32/ISO-HDLC
generate_tests(test, g_polynomial = ["42F0E1EBA9EA3693"], g_init = ["FFFFFFFFFFFFFFFF"], g_refin = [False], g_refout = [False], g_xor_out = ["FFFFFFFFFFFFFFFF"], g_crc_check = ["62ec59e3f1a4f00a"]) #CRC-64/WE

# lm_util_crc_parallel
test = lib.test_bench("tb_vu_lm_util_crc_parallel").test("parallel")
generate_tests(test, g_polynomial = ["1021"], g_init = ["FFFF"], g_refin = [False], g_refout = [False], g_xor_out = ["FFFF"], g_crc_check = ["d64e"]) #CRC-16/GENIBUS
generate_tests(test, g_polynomial = ["1021"], g_init = ["0000"], g_refin = [False], g_refout = [False], g_xor_out = ["FFFF"], g_crc_check = ["ce3c"]) #CRC-16/GSM
generate_tests(test, g_polynomial = ["1021"], g_init = ["FFFF"], g_refin = [False], g_refout = [False], g_xor_out = ["0000"], g_crc_check = ["29b1"]) #CRC-16/IBM-3740
generate_tests(test, g_polynomial = ["1021"], g_init = ["FFFF"], g_refin = [True], g_refout = [True], g_xor_out = ["FFFF"], g_crc_check = ["906e"]) #CRC-16/IBM-SDLC
generate_tests(test, g_polynomial = ["1021"], g_init = ["c6c6"], g_refin = [True], g_refout = [True], g_xor_out = ["0000"], g_crc_check = ["bf05"]) #CRC-16/ISO-IEC-14443-3-A
generate_tests(test, g_polynomial = ["0589"], g_init = ["0000"], g_refin = [False], g_refout = [False], g_xor_out = ["0001"], g_crc_check = ["007e"]) #CRC-16/DECT-R
generate_tests(test, g_polynomial = ["8005"], g_init = ["800d"], g_refin = [False], g_refout = [False], g_xor_out = ["0000"], g_crc_check = ["9ecf"]) #CRC-16/DDS-110
generate_tests(test, g_polynomial = ["1021"], g_init = ["FFFF"], g_refin = [False], g_refout = [False], g_flip_in = [0], g_xor_out = ["FFFF"], g_crc_check = ["d64e"]) #CRC-16/GENIBUS
generate_tests(test, g_polynomial = ["1021"], g_init = ["0000"], g_refin = [False], g_refout = [False], g_flip_in = [0], g_xor_out = ["FFFF"], g_crc_check = ["ce3c"]) #CRC-16/GSM
generate_tests(test, g_polynomial = ["1021"], g_init = ["FFFF"], g_refin = [False], g_refout = [False], g_flip_in = [0], g_xor_out = ["0000"], g_crc_check = ["29b1"]) #CRC-16/IBM-3740
generate_tests(test, g_polynomial = ["1021"], g_init = ["FFFF"], g_refin = [True], g_refout = [True], g_flip_in = [1], g_xor_out = ["FFFF"], g_crc_check = ["906e"]) #CRC-16/IBM-SDLC
generate_tests(test, g_polynomial = ["1021"], g_init = ["c6c6"], g_refin = [True], g_refout = [True], g_flip_in = [1], g_xor_out = ["0000"], g_crc_check = ["bf05"]) #CRC-16/ISO-IEC-14443-3-A
generate_tests(test, g_polynomial = ["0589"], g_init = ["0000"], g_refin = [False], g_refout = [False], g_flip_in = [0], g_xor_out = ["0001"], g_crc_check = ["007e"]) #CRC-16/DECT-R
generate_tests(test, g_polynomial = ["8005"], g_init = ["800d"], g_refin = [False], g_refout = [False], g_flip_in = [0], g_xor_out = ["0000"], g_crc_check = ["9ecf"]) #CRC-16/DDS-110
generate_tests(test, g_polynomial = ["04C11DB7"], g_init = ["FFFFFFFF"], g_refin = [True], g_refout = [True], g_flip_in = [1], g_xor_out = ["FFFFFFFF"], g_crc_check = ["cbf43926"]) #CRC-32/ISO-HDLC
generate_tests(test, g_polynomial = ["42F0E1EBA9EA3693"], g_init = ["FFFFFFFFFFFFFFFF"], g_refin = [False], g_refout = [False], g_flip_in = [0], g_xor_out = ["FFFFFFFFFFFFFFFF"], g_crc_check = ["62ec59e3f1a4f00a"]) #CRC-64/WE
generate_tests(test, g_polynomial = ["1021"], g_init = ["FFFF"], g_refin = [True], g_refout = [True], g_flip_in = [1], g_data_w = [16], g_test_str = ["12345678"], g_xor_out = ["FFFF"], g_crc_check = ["086a"]) #CRC-16/IBM-SDLC, wide bus
generate_tests(test, g_polynomial = ["04C11DB7"], g_init = ["FFFFFFFF"], g_refin = [True], g_refout = [True], g_flip_in = [1], g_data_w = [32], g_test_str = ["12345678"], g_xor_out = ["FFFFFFFF"], g_crc_check = ["9ae0daaf"]) #CRC-32/ISO-HDLC, wide bus

# lm_util_debouncer
test = lib.test_bench("tb_vu_lm_util_debouncer").test("length")
Expand Down
79 changes: 58 additions & 21 deletions sim/tb/tb_vu_lm_util_crc_parallel.vhd
Original file line number Diff line number Diff line change
Expand Up @@ -62,26 +62,56 @@ architecture a_tb of tb_vu_lm_util_crc_parallel is
constant C_CRC_CHECK : std_logic_vector := f_hex2slv(g_crc_check);
constant C_FLIP_OUT : integer := f_bool2int(g_refout);
constant C_POLY_LEN : integer := C_POLYNOMIAL'length;
constant C_CRC_ZERO : std_logic_vector(C_POLYNOMIAL'range) := (others => '0');

-- Stimulus signals - signals mapped to the input and inout ports of tested entity

signal clk_i : std_logic := '1'; -- input clock
signal rst_n_i : std_logic := '0'; -- synchronous rst, active low
signal init_i : std_logic := '0'; -- synchronous crc initialization
signal dv_i : std_logic := '0'; -- data valid

signal data_i : std_logic_vector (g_data_w - 1 downto 0); -- data input parallel
signal flush_i : std_logic := '0'; -- flush crc, when '1' crc is flushed out on crc_o
-- Observed signals - signals mapped to the output ports of tested entity
signal match_o : std_logic; -- CRC match flag
signal crc_o : std_logic_vector(C_POLY_LEN - 1 downto 0); -- paralle CRC output

-- auxilary signals
signal s_crc : std_logic_vector(C_POLY_LEN - 1 downto 0); -- calculated CRC
function f_crc_word(
p_crc : std_logic_vector;
p_word_idx : natural;
p_word_w : positive;
p_flip_in : integer;
p_refout : boolean
) return std_logic_vector is
constant C_CRC_W : integer := p_crc'length;
variable v_crc : std_logic_vector(C_CRC_W - 1 downto 0) := p_crc;
variable v_word : std_logic_vector(p_word_w - 1 downto 0);
variable v_seq : integer;
variable v_src : integer;
variable v_dst : integer;
begin
for bit_idx in 0 to p_word_w - 1 loop
v_seq := p_word_idx * p_word_w + bit_idx;
if p_refout then
v_src := v_seq;
else
v_src := C_CRC_W - 1 - v_seq;
end if;

if p_flip_in = 1 then
v_dst := p_word_w - 1 - (bit_idx / 8) * 8 - 7 + (bit_idx mod 8);
else
v_dst := p_word_w - 1 - bit_idx;
end if;

v_word(v_dst) := v_crc(v_src);
end loop;
return v_word;
end function f_crc_word;
begin
assert g_test_str'length > 0 report "g_test_string must be provided" severity error;
assert C_TOTAL_BITS mod g_data_w = 0 report "C_TOTAL_BITS must be multiple of g_data_w" severity error;
assert C_POLY_LEN mod g_data_w = 0 report "C_POLY_LEN must be multiple of g_data_w" severity error;
assert (g_flip_in = 0) or (g_data_w mod 8 = 0) report "g_flip_in requires g_data_w to be a multiple of 8" severity failure;

-- Unit Under Test port map
inst_dut : entity lm_util_lib.lm_util_crc_par
Expand All @@ -97,6 +127,7 @@ begin
(
clk_i => clk_i,
rst_n_i => rst_n_i,
init_i => init_i,
dv_i => dv_i,
data_i => data_i,
match_o => match_o,
Expand All @@ -108,11 +139,12 @@ begin

proc_main : process
variable v_msg : std_logic_vector(C_TOTAL_BITS - 1 downto 0);
variable v_crc : std_logic_vector(C_POLY_LEN - 1 downto 0);
begin
test_runner_setup(runner, runner_cfg);

--Convert g_test_str to bit vector
if (g_refin) then
if (g_refin and (g_flip_in = 0)) then
v_msg := f_string2slv_lsb(g_test_str); -- reflected input bits in bytes
else
v_msg := f_string2slv(g_test_str);
Expand All @@ -125,7 +157,7 @@ begin

-- reset
dv_i <= '0';
flush_i <= '0';
init_i <= '0';
p_wait_clk(clk_i);
rst_n_i <= '0';
-- deassert reset
Expand All @@ -140,41 +172,46 @@ begin
end loop;
dv_i <= '0'; -- Deassert data valid
wait for 1 ps;
if (g_refin) then
s_crc <= f_flip(crc_o xor C_XOR_OUT); -- store crc in unxored form and reversed
else
s_crc <= crc_o xor C_XOR_OUT; -- store crc in unxored form
end if;
dv_i <= '0';
v_crc := crc_o; -- transmitted CRC, including refout/xorout

-- check crc output against check "reference" value
check_equal(crc_o, C_CRC_CHECK, "Parallel CRC output (" & f_slv2hex(crc_o) & ") does not match check value (" & f_slv2hex(C_CRC_CHECK) & ")");

--Check crc
-- reset
rst_n_i <= '0';
-- reload initial value without pulsing reset
init_i <= '1';
p_wait_clk(clk_i);
-- deassert reset
rst_n_i <= '1';
init_i <= '0';
p_wait_clk(clk_i);
wait for 1 ps;
check_equal(match_o, '0', "Parallel init_i did not clear match_o");

-- clock-in data
dv_i <= '1';
for i in C_TOTAL_WORDS downto 1 loop
data_i <= v_msg(i * g_data_w - 1 downto (i - 1) * g_data_w);
p_wait_clk(clk_i);
end loop;
-- clock-in crc (unxored)
for i in C_POLY_LEN / g_data_w downto 1 loop
data_i <= s_crc(i * g_data_w - 1 downto (i - 1) * g_data_w);

-- clock-in transmitted crc
for word_idx in 0 to C_POLY_LEN / g_data_w - 1 loop
data_i <= f_crc_word(v_crc, word_idx, g_data_w, g_flip_in, g_refout);
p_wait_clk(clk_i);
end loop;
wait for 1 ps;

-- check crc output and match_o
check_equal(crc_o, C_CRC_ZERO xor C_XOR_OUT, "Parallel CRC output (" & f_slv2hex(crc_o) & ") is not zero after crc check");
-- check match_o, hold while dv_i is low, and clear through init_i
check_equal(match_o, '1', "Parallel match_o was not set");
dv_i <= '0'; -- Deassert data valid
p_wait_clk(clk_i);
wait for 1 ps;
check_equal(match_o, '1', "Parallel match_o was not held when dv_i was low");

init_i <= '1';
p_wait_clk(clk_i);
init_i <= '0';
wait for 1 ps;
check_equal(match_o, '0', "Parallel init_i did not clear match_o after a match");

end if;

Expand Down
90 changes: 64 additions & 26 deletions sim/tb/tb_vu_lm_util_crc_serial.vhd
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,12 @@ architecture a_tb of tb_vu_lm_util_crc_serial is
constant C_CRC_CHECK : std_logic_vector := f_hex2slv(g_crc_check);
constant C_POLY_LEN : integer := C_POLYNOMIAL'length;
constant C_FLIP_OUT : integer := f_bool2int(g_refout);
constant C_CRC_ZERO : std_logic_vector(C_POLYNOMIAL'range) := (others => '0');

-- Stimulus signals - signals mapped to the input and inout ports of tested entity

signal clk_i : std_logic := '1'; -- input clock
signal rst_n_i : std_logic := '0'; -- synchronous rst, active low
signal init_i : std_logic := '0'; -- synchronous crc initialization
signal dv_i : std_logic := '0'; -- data valid

signal data_i : std_logic; -- data input serial
Expand All @@ -77,8 +77,32 @@ architecture a_tb of tb_vu_lm_util_crc_serial is
signal match_o : std_logic; -- CRC match flag
signal crc_o : std_logic_vector(C_POLYNOMIAL'length - 1 downto 0); -- serail CRC output

-- auxilary signals
signal s_crc : std_logic_vector(C_POLYNOMIAL'length - 1 downto 0); -- calculated CRC
function f_expected_flush_crc(
p_crc_o : std_logic_vector;
p_xor : std_logic_vector;
p_refout : boolean
) return std_logic_vector is
variable v_internal : std_logic_vector(p_crc_o'length - 1 downto 0);
variable v_shifted : std_logic_vector(p_crc_o'length - 1 downto 0);
variable v_output : std_logic_vector(p_crc_o'length - 1 downto 0);
begin
if p_refout then
v_internal := f_flip(p_crc_o xor p_xor);
else
v_internal := p_crc_o xor p_xor;
end if;

v_shifted(0) := '0';
v_shifted(v_shifted'left downto 1) := v_internal(v_internal'left - 1 downto 0);

if p_refout then
v_output := f_flip(v_shifted);
else
v_output := v_shifted;
end if;

return v_output xor p_xor;
end function f_expected_flush_crc;
begin
assert g_test_str'length > 0 report "g_test_string must be provided" severity error;

Expand All @@ -94,6 +118,7 @@ begin
(
clk_i => clk_i,
rst_n_i => rst_n_i,
init_i => init_i,
dv_i => dv_i,
data_i => data_i,
match_o => match_o,
Expand All @@ -106,6 +131,8 @@ begin

proc_main : process
variable v_msg : std_logic_vector(C_TOTAL_BITS - 1 downto 0);
variable v_crc : std_logic_vector(C_POLY_LEN - 1 downto 0);
variable v_flush : std_logic_vector(C_POLY_LEN - 1 downto 0);
begin
test_runner_setup(runner, runner_cfg);

Expand All @@ -127,6 +154,7 @@ begin
-- reset
dv_i <= '0';
flush_i <= '0';
init_i <= '0';
p_wait_clk(clk_i);
rst_n_i <= '0';
-- deassert reset
Expand All @@ -140,12 +168,7 @@ begin
p_wait_clk(clk_i);
end loop;
wait for 1 ps;

if (g_refin) then
s_crc <= f_flip(crc_o xor C_XOR_OUT); -- store crc in unxored form and reversed
else
s_crc <= crc_o xor C_XOR_OUT; -- store crc in unxored form
end if;
v_crc := crc_o; -- transmitted CRC, including refout/xorout

-- check crc output against check "reference" value
check_equal(crc_o, C_CRC_CHECK, "Serial CRC output (" & f_slv2hex(crc_o) & ") does not match check value (" & f_slv2hex(C_CRC_CHECK) & ")");
Expand All @@ -160,12 +183,13 @@ begin
dv_i <= '0'; -- Deassert data valid

--Check crc
-- reset
rst_n_i <= '0';
-- reload initial value without pulsing reset
init_i <= '1';
p_wait_clk(clk_i);
-- deassert reset
rst_n_i <= '1';
init_i <= '0';
p_wait_clk(clk_i);
wait for 1 ps;
check_equal(match_o, '0', "Serial init_i did not clear match_o");

-- clock-in data
dv_i <= '1';
Expand All @@ -174,26 +198,40 @@ begin
p_wait_clk(clk_i);
end loop;

-- clock-in crc (unxored)
-- clock-in transmitted crc
dv_i <= '1';
for i in C_POLY_LEN - 1 downto 0 loop
data_i <= s_crc(i);
p_wait_clk(clk_i);
end loop;
if (g_refout) then
for i in 0 to C_POLY_LEN - 1 loop
data_i <= v_crc(i);
p_wait_clk(clk_i);
end loop;
else
for i in C_POLY_LEN - 1 downto 0 loop
data_i <= v_crc(i);
p_wait_clk(clk_i);
end loop;
end if;
wait for 1 ps;

-- check crc output and match_o
check_equal(crc_o, C_CRC_ZERO xor C_XOR_OUT, "Serial CRC output (" & f_slv2hex(crc_o) & ") is not zero after crc check");
-- check match_o, hold while dv_i is low, and clear through init_i
check_equal(match_o, '1', "Serial match_o was not set");

--flush crc
-- Flush behavior is intentionally preserved by the current reference vector.
v_flush := f_expected_flush_crc(crc_o, C_XOR_OUT, g_refout);
flush_i <= '1';
for i in 0 to g_polynomial'length - 1 loop
p_wait_clk(clk_i);
end loop;
p_wait_clk(clk_i);
flush_i <= '0';
dv_i <= '0'; -- Deassert data valid
wait for 1 ps;
check_equal(crc_o, v_flush, "Serial flush_i did not shift crc_o as expected");
check_equal(match_o, '1', "Serial match_o changed during flush_i");
p_wait_clk(clk_i);
wait for 1 ps;
check_equal(match_o, '1', "Serial match_o was not held when dv_i was low");

init_i <= '1';
p_wait_clk(clk_i);
init_i <= '0';
wait for 1 ps;
check_equal(match_o, '0', "Serial init_i did not clear match_o after a match");
end if;

test_runner_cleanup(runner);
Expand Down
Loading