diff --git a/verilog/dv/cocotb/cocotb_tests.py b/verilog/dv/cocotb/cocotb_tests.py index 4eb45b7..afcf5d6 100644 --- a/verilog/dv/cocotb/cocotb_tests.py +++ b/verilog/dv/cocotb/cocotb_tests.py @@ -6,3 +6,6 @@ from user_proj_tests.counter_la_reset.counter_la_reset import counter_la_reset from user_proj_tests.counter_la_clk.counter_la_clk import counter_la_clk from gpio_test.gpio_test import gpio_test from ldpc_tests.ldpc_basic.ldpc_basic import ldpc_basic +from ldpc_tests.ldpc_noisy.ldpc_noisy import ldpc_noisy +from ldpc_tests.ldpc_max_iter.ldpc_max_iter import ldpc_max_iter +from ldpc_tests.ldpc_back_to_back.ldpc_back_to_back import ldpc_back_to_back diff --git a/verilog/dv/cocotb/ldpc_tests/ldpc_back_to_back/ldpc_back_to_back.c b/verilog/dv/cocotb/ldpc_tests/ldpc_back_to_back/ldpc_back_to_back.c new file mode 100644 index 0000000..775095c --- /dev/null +++ b/verilog/dv/cocotb/ldpc_tests/ldpc_back_to_back/ldpc_back_to_back.c @@ -0,0 +1,180 @@ +// SPDX-FileCopyrightText: 2024 LDPC Optical Project + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// SPDX-License-Identifier: Apache-2.0 + +/* + * LDPC Back-to-Back Decode Test Firmware + * + * Runs on PicoRV32 inside Caravel. Exercises the LDPC decoder with two + * sequential decodes to verify no state leakage between runs: + * 1. First decode: all-zero codeword (LLRs=+31), check decoded=0 + * 2. Second decode: noisy correctable codeword (test vector 0), + * check decoded matches expected + * 3. Signals pass (0xAB) only if BOTH decodes are correct + */ + +#include + +// LDPC register word offsets (byte_addr / 4) +#define LDPC_CTRL 0 // 0x00 +#define LDPC_STATUS 1 // 0x04 +#define LDPC_LLR_BASE 4 // 0x10 +#define LDPC_DECODED 20 // 0x50 +#define LDPC_VERSION 21 // 0x54 + +#define LLR_WORD_COUNT 52 // 260 LLRs / 5 per word, rounded up + +// CTRL register fields +#define CTRL_START (1 << 0) +#define CTRL_EARLY_TERM (1 << 1) +#define CTRL_MAX_ITER(n) (((n) & 0x1F) << 8) + +// STATUS register fields +#define STATUS_BUSY (1 << 0) +#define STATUS_CONVERGED (1 << 1) +#define STATUS_ITER_MASK (0x1F << 8) +#define STATUS_SYN_MASK (0xFF << 16) + +// Expected values +#define EXPECTED_VERSION 0x1D010001 +#define PASS_SIGNATURE 0xAB +#define FAIL_SIGNATURE 0xFF + +// All-zero codeword: every LLR = +31 (6'b011111 = 0x1F) +// Pack 5x 0x1F per word: +// (0x1F << 0) | (0x1F << 6) | (0x1F << 12) | (0x1F << 18) | (0x1F << 24) +// = 0x1F7DF7DF +#define ALL_ZERO_LLR_WORD 0x1F7DF7DF + +// Test vector 0: noisy but correctable codeword +// Expected decoded word: 0x3FD74222 +#define EXPECTED_DECODED_VEC0 0x3FD74222 + +static const unsigned int noisy_llr_words[LLR_WORD_COUNT] = { + 0x1F7DF81F, 0x20C9F7E0, 0x207CC7DF, 0x1F82081F, 0x328207E0, 0x20820820, 0x208207CC, 0x1F81F7DF, + 0x1F7F27DF, 0x1F7CC81F, 0x0C81F81F, 0x207E07F2, 0x1F820820, 0x207DF7CC, 0x1F81F7E0, 0x2082081F, + 0x0C31F81F, 0x2081F7DF, 0x1FCA081F, 0x20820820, 0x1F7DF7DF, 0x207E07E0, 0x208207CC, 0x1F8207DF, + 0x0C7DF7DF, 0x2030C820, 0x207DF7E0, 0x1F82081F, 0x203207DF, 0x20832820, 0x2081F820, 0x20820832, + 0x1F82081F, 0x207E081F, 0x207DF820, 0x1F7E0320, 0x1F7E07E0, 0x1F81F820, 0x20CA07CC, 0x0C81F7E0, + 0x1F820820, 0x1FCA07DF, 0x1F7E080C, 0x208207F2, 0x207E081F, 0x20820820, 0x207E07DF, 0x2082081F, + 0x1F7E07DF, 0x1F7DF7E0, 0x207DF820, 0x00000020 +}; + +void main(){ + unsigned int status; + unsigned int decoded; + unsigned int version; + int pass = 1; + + // --- Setup management GPIO for synchronization --- + ManagmentGpio_outputEnable(); + ManagmentGpio_write(0); + enableHkSpi(0); + + // Configure all GPIOs as management standard output (driven by firmware) + GPIOs_configureAll(GPIO_MODE_MGMT_STD_OUTPUT); + GPIOs_loadConfigs(); + + // Clear GPIO output + GPIOs_writeLow(0x00000000); + + // Enable Wishbone interface to user project + User_enableIF(); + + // Signal firmware ready + ManagmentGpio_write(1); + + // --- Step 1: Read and verify VERSION register --- + version = USER_readWord(LDPC_VERSION); + if (version != EXPECTED_VERSION) { + pass = 0; + } + + // ===== DECODE 1: All-zero codeword ===== + + // Write all-zero LLRs (+31 confidence in bit=0) + for (int i = 0; i < LLR_WORD_COUNT - 1; i++) { + USER_writeWord(ALL_ZERO_LLR_WORD, LDPC_LLR_BASE + i); + } + // Last word: only LLR[255] = +31, remaining slots zero + USER_writeWord(0x0000001F, LDPC_LLR_BASE + LLR_WORD_COUNT - 1); + + // Start decode: start=1, early_term_en=1, max_iter=30 + USER_writeWord(CTRL_START | CTRL_EARLY_TERM | CTRL_MAX_ITER(30), LDPC_CTRL); + + // Poll until done + do { + status = USER_readWord(LDPC_STATUS); + } while (status & STATUS_BUSY); + + // Check converged + if (!(status & STATUS_CONVERGED)) { + pass = 0; + } + + // Check syndrome weight = 0 + if ((status & STATUS_SYN_MASK) != 0) { + pass = 0; + } + + // Check decoded = 0 (all-zero codeword) + decoded = USER_readWord(LDPC_DECODED); + if (decoded != 0x00000000) { + pass = 0; + } + + // ===== DECODE 2: Noisy correctable codeword ===== + + // Write noisy LLRs + for (int i = 0; i < LLR_WORD_COUNT; i++) { + USER_writeWord(noisy_llr_words[i], LDPC_LLR_BASE + i); + } + + // Start decode: start=1, early_term_en=1, max_iter=30 + USER_writeWord(CTRL_START | CTRL_EARLY_TERM | CTRL_MAX_ITER(30), LDPC_CTRL); + + // Poll until done + do { + status = USER_readWord(LDPC_STATUS); + } while (status & STATUS_BUSY); + + // Check converged + if (!(status & STATUS_CONVERGED)) { + pass = 0; + } + + // Check syndrome weight = 0 + if ((status & STATUS_SYN_MASK) != 0) { + pass = 0; + } + + // Check decoded matches expected + decoded = USER_readWord(LDPC_DECODED); + if (decoded != EXPECTED_DECODED_VEC0) { + pass = 0; + } + + // --- Signal result via GPIO[7:0] --- + if (pass) { + GPIOs_writeLow(PASS_SIGNATURE); // 0xAB = pass (both decodes correct) + } else { + GPIOs_writeLow(FAIL_SIGNATURE); // 0xFF = fail + } + + // Signal test complete via management GPIO + ManagmentGpio_write(0); + + return; +} diff --git a/verilog/dv/cocotb/ldpc_tests/ldpc_back_to_back/ldpc_back_to_back.py b/verilog/dv/cocotb/ldpc_tests/ldpc_back_to_back/ldpc_back_to_back.py new file mode 100644 index 0000000..9d029b0 --- /dev/null +++ b/verilog/dv/cocotb/ldpc_tests/ldpc_back_to_back/ldpc_back_to_back.py @@ -0,0 +1,84 @@ +# SPDX-FileCopyrightText: 2024 LDPC Optical Project + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# SPDX-License-Identifier: Apache-2.0 + +""" +LDPC Back-to-Back Decode Test - cocotb Monitor + +Tests the LDPC decoder with two sequential decodes to verify no state +leakage between runs. Firmware: + 1. First decode: all-zero codeword, check converged=1, decoded=0 + 2. Second decode: noisy correctable codeword, check converged=1, + decoded matches expected + 3. Signals pass (0xAB) only if BOTH decodes are correct + +The cocotb monitor waits for the management GPIO to signal firmware ready, +then waits for the management GPIO to drop (test complete), and reads +GPIO[7:0] for the pass/fail result. +""" + +from caravel_cocotb.caravel_interfaces import test_configure +from caravel_cocotb.caravel_interfaces import report_test +import cocotb + +PASS_SIGNATURE = 0xAB +FAIL_SIGNATURE = 0xFF + + +@cocotb.test() +@report_test +async def ldpc_back_to_back(dut): + caravelEnv = await test_configure(dut, timeout_cycles=500000) + + cocotb.log.info("[TEST] Starting LDPC back-to-back decode test") + + # Release CSB to allow GPIO to function + await caravelEnv.release_csb() + + # Wait for firmware to signal ready (mgmt_gpio = 1) + cocotb.log.info("[TEST] Waiting for firmware ready (mgmt_gpio=1)...") + await caravelEnv.wait_mgmt_gpio(1) + cocotb.log.info("[TEST] Firmware ready, back-to-back decodes in progress...") + + # Wait for firmware to signal test complete (mgmt_gpio = 0) + await caravelEnv.wait_mgmt_gpio(0) + cocotb.log.info("[TEST] Firmware signaled test complete") + + # Read GPIO[7:0] for pass/fail result + gpio_val = caravelEnv.monitor_gpio(7, 0) + + if not gpio_val.is_resolvable: + cocotb.log.error( + f"[TEST] FAIL - GPIO[7:0] is unresolvable: {gpio_val.binstr}" + ) + assert False, "GPIO[7:0] has X/Z values" + + result = gpio_val.integer + cocotb.log.info(f"[TEST] GPIO[7:0] = 0x{result:02X}") + + if result == PASS_SIGNATURE: + cocotb.log.info( + "[TEST] PASS - Both back-to-back decodes correct (0xAB)" + ) + elif result == FAIL_SIGNATURE: + cocotb.log.error( + "[TEST] FAIL - Back-to-back decode test failed (0xFF)" + ) + assert False, "Firmware reported LDPC back-to-back decode failure" + else: + cocotb.log.error( + f"[TEST] FAIL - Unexpected GPIO value: 0x{result:02X}" + ) + assert False, f"Unexpected GPIO result: 0x{result:02X}" diff --git a/verilog/dv/cocotb/ldpc_tests/ldpc_back_to_back/ldpc_back_to_back.yaml b/verilog/dv/cocotb/ldpc_tests/ldpc_back_to_back/ldpc_back_to_back.yaml new file mode 100644 index 0000000..9cf7a6a --- /dev/null +++ b/verilog/dv/cocotb/ldpc_tests/ldpc_back_to_back/ldpc_back_to_back.yaml @@ -0,0 +1,19 @@ +--- +# SPDX-FileCopyrightText: 2024 LDPC Optical Project + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# SPDX-License-Identifier: Apache-2.0 + +Tests: + - {name: ldpc_back_to_back, sim: RTL} diff --git a/verilog/dv/cocotb/ldpc_tests/ldpc_max_iter/ldpc_max_iter.c b/verilog/dv/cocotb/ldpc_tests/ldpc_max_iter/ldpc_max_iter.c new file mode 100644 index 0000000..2cd1df6 --- /dev/null +++ b/verilog/dv/cocotb/ldpc_tests/ldpc_max_iter/ldpc_max_iter.c @@ -0,0 +1,139 @@ +// SPDX-FileCopyrightText: 2024 LDPC Optical Project + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// SPDX-License-Identifier: Apache-2.0 + +/* + * LDPC Max Iteration Test Firmware + * + * Runs on PicoRV32 inside Caravel. Exercises the LDPC decoder with an + * uncorrectable codeword to verify it hits max iterations: + * 1. Writes uncorrectable noisy LLRs (test vector 11) + * 2. Starts decode with early termination, max_iter=30 + * 3. Checks converged=0 (not converged), iterations_used=30 (hit max) + * 4. Signals pass (0xAB) or fail (0xFF) on GPIO[7:0] + */ + +#include + +// LDPC register word offsets (byte_addr / 4) +#define LDPC_CTRL 0 // 0x00 +#define LDPC_STATUS 1 // 0x04 +#define LDPC_LLR_BASE 4 // 0x10 +#define LDPC_DECODED 20 // 0x50 +#define LDPC_VERSION 21 // 0x54 + +#define LLR_WORD_COUNT 52 // 260 LLRs / 5 per word, rounded up + +// CTRL register fields +#define CTRL_START (1 << 0) +#define CTRL_EARLY_TERM (1 << 1) +#define CTRL_MAX_ITER(n) (((n) & 0x1F) << 8) + +// STATUS register fields +#define STATUS_BUSY (1 << 0) +#define STATUS_CONVERGED (1 << 1) +#define STATUS_ITER_MASK (0x1F << 8) +#define STATUS_SYN_MASK (0xFF << 16) + +// Expected values +#define EXPECTED_VERSION 0x1D010001 +#define PASS_SIGNATURE 0xAB +#define FAIL_SIGNATURE 0xFF + +// Test vector 11: uncorrectable codeword (too many errors) +// Expected: converged=False, iterations=30, syndrome_weight=67 +static const unsigned int uncorrectable_llr_words[LLR_WORD_COUNT] = { + 0x0C30C80C, 0x0C30C30C, 0x2730C820, 0x0C80C30C, 0x0C32730C, 0x0C30C9E7, 0x0CE8C33A, 0x0C9CC320, + 0x2032030C, 0x0C32030C, 0x0C30C9E0, 0x209CC320, 0x0C9E730C, 0x0C33A9CC, 0x3A80C30C, 0x0C30C80C, + 0x279CC320, 0x0CEA080C, 0x0C30C9CC, 0x279E0320, 0x2730C30C, 0x0CE8C30C, 0x0C80C9CC, 0x0C9CC30C, + 0x3AE8CEA0, 0x20E8C320, 0x0C33A80C, 0x0CEBA33A, 0x0C30C9CC, 0x27EA0E8C, 0x0C30CEBA, 0x0CE8C30C, + 0x0CEA7EA7, 0x0C30C30C, 0x0C83A327, 0x0CEBA30C, 0x0C83AEA0, 0x2033A80C, 0x0C80C30C, 0x0C30C30C, + 0x0C82730C, 0x3A30C33A, 0x3A820E8C, 0x0C30C320, 0x0C30C9E7, 0x279CC320, 0x2080C30C, 0x27320327, + 0x3A32083A, 0x0C33A80C, 0x0C9CC30C, 0x0000000C +}; + +void main(){ + unsigned int status; + unsigned int version; + unsigned int iter_used; + int pass = 1; + + // --- Setup management GPIO for synchronization --- + ManagmentGpio_outputEnable(); + ManagmentGpio_write(0); + enableHkSpi(0); + + // Configure all GPIOs as management standard output (driven by firmware) + GPIOs_configureAll(GPIO_MODE_MGMT_STD_OUTPUT); + GPIOs_loadConfigs(); + + // Clear GPIO output + GPIOs_writeLow(0x00000000); + + // Enable Wishbone interface to user project + User_enableIF(); + + // Signal firmware ready + ManagmentGpio_write(1); + + // --- Step 1: Read and verify VERSION register --- + version = USER_readWord(LDPC_VERSION); + if (version != EXPECTED_VERSION) { + pass = 0; + } + + // --- Step 2: Write uncorrectable noisy LLRs --- + for (int i = 0; i < LLR_WORD_COUNT; i++) { + USER_writeWord(uncorrectable_llr_words[i], LDPC_LLR_BASE + i); + } + + // --- Step 3: Start decode --- + // start=1, early_term_en=1, max_iter=30 + USER_writeWord(CTRL_START | CTRL_EARLY_TERM | CTRL_MAX_ITER(30), LDPC_CTRL); + + // --- Step 4: Poll STATUS until not busy --- + do { + status = USER_readWord(LDPC_STATUS); + } while (status & STATUS_BUSY); + + // --- Step 5: Check results --- + // Should NOT have converged + if (status & STATUS_CONVERGED) { + pass = 0; // Unexpectedly converged + } + + // Should have used all 30 iterations + iter_used = (status & STATUS_ITER_MASK) >> 8; + if (iter_used != 30) { + pass = 0; // Did not hit max iterations + } + + // Syndrome weight should be non-zero (not converged) + if ((status & STATUS_SYN_MASK) == 0) { + pass = 0; // Syndrome weight unexpectedly zero + } + + // --- Step 6: Signal result via GPIO[7:0] --- + if (pass) { + GPIOs_writeLow(PASS_SIGNATURE); // 0xAB = pass + } else { + GPIOs_writeLow(FAIL_SIGNATURE); // 0xFF = fail + } + + // Signal test complete via management GPIO + ManagmentGpio_write(0); + + return; +} diff --git a/verilog/dv/cocotb/ldpc_tests/ldpc_max_iter/ldpc_max_iter.py b/verilog/dv/cocotb/ldpc_tests/ldpc_max_iter/ldpc_max_iter.py new file mode 100644 index 0000000..1586af6 --- /dev/null +++ b/verilog/dv/cocotb/ldpc_tests/ldpc_max_iter/ldpc_max_iter.py @@ -0,0 +1,84 @@ +# SPDX-FileCopyrightText: 2024 LDPC Optical Project + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# SPDX-License-Identifier: Apache-2.0 + +""" +LDPC Max Iteration Test - cocotb Monitor + +Tests the LDPC decoder with an uncorrectable codeword. Firmware: + 1. Verifies the VERSION register + 2. Loads uncorrectable noisy LLRs (test vector 11) + 3. Runs a decode with early termination, max_iter=30 + 4. Checks converged=0, iterations_used=30, syndrome_weight>0 + 5. Signals pass (0xAB) or fail (0xFF) on GPIO[7:0] + +The cocotb monitor waits for the management GPIO to signal firmware ready, +then waits for the management GPIO to drop (test complete), and reads +GPIO[7:0] for the pass/fail result. +""" + +from caravel_cocotb.caravel_interfaces import test_configure +from caravel_cocotb.caravel_interfaces import report_test +import cocotb + +PASS_SIGNATURE = 0xAB +FAIL_SIGNATURE = 0xFF + + +@cocotb.test() +@report_test +async def ldpc_max_iter(dut): + caravelEnv = await test_configure(dut, timeout_cycles=300000) + + cocotb.log.info("[TEST] Starting LDPC max iteration test") + + # Release CSB to allow GPIO to function + await caravelEnv.release_csb() + + # Wait for firmware to signal ready (mgmt_gpio = 1) + cocotb.log.info("[TEST] Waiting for firmware ready (mgmt_gpio=1)...") + await caravelEnv.wait_mgmt_gpio(1) + cocotb.log.info("[TEST] Firmware ready, decode in progress...") + + # Wait for firmware to signal test complete (mgmt_gpio = 0) + await caravelEnv.wait_mgmt_gpio(0) + cocotb.log.info("[TEST] Firmware signaled test complete") + + # Read GPIO[7:0] for pass/fail result + gpio_val = caravelEnv.monitor_gpio(7, 0) + + if not gpio_val.is_resolvable: + cocotb.log.error( + f"[TEST] FAIL - GPIO[7:0] is unresolvable: {gpio_val.binstr}" + ) + assert False, "GPIO[7:0] has X/Z values" + + result = gpio_val.integer + cocotb.log.info(f"[TEST] GPIO[7:0] = 0x{result:02X}") + + if result == PASS_SIGNATURE: + cocotb.log.info( + "[TEST] PASS - Firmware confirmed max iteration behavior (0xAB)" + ) + elif result == FAIL_SIGNATURE: + cocotb.log.error( + "[TEST] FAIL - Firmware reported max iteration test failure (0xFF)" + ) + assert False, "Firmware reported LDPC max iteration test failure" + else: + cocotb.log.error( + f"[TEST] FAIL - Unexpected GPIO value: 0x{result:02X}" + ) + assert False, f"Unexpected GPIO result: 0x{result:02X}" diff --git a/verilog/dv/cocotb/ldpc_tests/ldpc_max_iter/ldpc_max_iter.yaml b/verilog/dv/cocotb/ldpc_tests/ldpc_max_iter/ldpc_max_iter.yaml new file mode 100644 index 0000000..83d6a09 --- /dev/null +++ b/verilog/dv/cocotb/ldpc_tests/ldpc_max_iter/ldpc_max_iter.yaml @@ -0,0 +1,19 @@ +--- +# SPDX-FileCopyrightText: 2024 LDPC Optical Project + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# SPDX-License-Identifier: Apache-2.0 + +Tests: + - {name: ldpc_max_iter, sim: RTL} diff --git a/verilog/dv/cocotb/ldpc_tests/ldpc_noisy/ldpc_noisy.c b/verilog/dv/cocotb/ldpc_tests/ldpc_noisy/ldpc_noisy.c new file mode 100644 index 0000000..d141351 --- /dev/null +++ b/verilog/dv/cocotb/ldpc_tests/ldpc_noisy/ldpc_noisy.c @@ -0,0 +1,141 @@ +// SPDX-FileCopyrightText: 2024 LDPC Optical Project + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// SPDX-License-Identifier: Apache-2.0 + +/* + * LDPC Noisy Decode Test Firmware + * + * Runs on PicoRV32 inside Caravel. Exercises the LDPC decoder with a noisy + * but correctable codeword: + * 1. Writes noisy LLRs (test vector 0 from gen_firmware_vectors.py) + * 2. Starts decode with early termination, max_iter=30 + * 3. Checks converged=1, syndrome_weight=0, decoded matches expected + * 4. Signals pass (0xAB) or fail (0xFF) on GPIO[7:0] + */ + +#include + +// LDPC register word offsets (byte_addr / 4) +#define LDPC_CTRL 0 // 0x00 +#define LDPC_STATUS 1 // 0x04 +#define LDPC_LLR_BASE 4 // 0x10 +#define LDPC_DECODED 20 // 0x50 +#define LDPC_VERSION 21 // 0x54 + +#define LLR_WORD_COUNT 52 // 260 LLRs / 5 per word, rounded up + +// CTRL register fields +#define CTRL_START (1 << 0) +#define CTRL_EARLY_TERM (1 << 1) +#define CTRL_MAX_ITER(n) (((n) & 0x1F) << 8) + +// STATUS register fields +#define STATUS_BUSY (1 << 0) +#define STATUS_CONVERGED (1 << 1) +#define STATUS_ITER_MASK (0x1F << 8) +#define STATUS_SYN_MASK (0xFF << 16) + +// Expected values +#define EXPECTED_VERSION 0x1D010001 +#define PASS_SIGNATURE 0xAB +#define FAIL_SIGNATURE 0xFF + +// Test vector 0: noisy but correctable codeword +// Expected decoded word: 0x3FD74222 +#define EXPECTED_DECODED 0x3FD74222 + +static const unsigned int noisy_llr_words[LLR_WORD_COUNT] = { + 0x1F7DF81F, 0x20C9F7E0, 0x207CC7DF, 0x1F82081F, 0x328207E0, 0x20820820, 0x208207CC, 0x1F81F7DF, + 0x1F7F27DF, 0x1F7CC81F, 0x0C81F81F, 0x207E07F2, 0x1F820820, 0x207DF7CC, 0x1F81F7E0, 0x2082081F, + 0x0C31F81F, 0x2081F7DF, 0x1FCA081F, 0x20820820, 0x1F7DF7DF, 0x207E07E0, 0x208207CC, 0x1F8207DF, + 0x0C7DF7DF, 0x2030C820, 0x207DF7E0, 0x1F82081F, 0x203207DF, 0x20832820, 0x2081F820, 0x20820832, + 0x1F82081F, 0x207E081F, 0x207DF820, 0x1F7E0320, 0x1F7E07E0, 0x1F81F820, 0x20CA07CC, 0x0C81F7E0, + 0x1F820820, 0x1FCA07DF, 0x1F7E080C, 0x208207F2, 0x207E081F, 0x20820820, 0x207E07DF, 0x2082081F, + 0x1F7E07DF, 0x1F7DF7E0, 0x207DF820, 0x00000020 +}; + +void main(){ + unsigned int status; + unsigned int decoded; + unsigned int version; + int pass = 1; + + // --- Setup management GPIO for synchronization --- + ManagmentGpio_outputEnable(); + ManagmentGpio_write(0); + enableHkSpi(0); + + // Configure all GPIOs as management standard output (driven by firmware) + GPIOs_configureAll(GPIO_MODE_MGMT_STD_OUTPUT); + GPIOs_loadConfigs(); + + // Clear GPIO output + GPIOs_writeLow(0x00000000); + + // Enable Wishbone interface to user project + User_enableIF(); + + // Signal firmware ready + ManagmentGpio_write(1); + + // --- Step 1: Read and verify VERSION register --- + version = USER_readWord(LDPC_VERSION); + if (version != EXPECTED_VERSION) { + pass = 0; + } + + // --- Step 2: Write noisy LLRs --- + for (int i = 0; i < LLR_WORD_COUNT; i++) { + USER_writeWord(noisy_llr_words[i], LDPC_LLR_BASE + i); + } + + // --- Step 3: Start decode --- + // start=1, early_term_en=1, max_iter=30 + USER_writeWord(CTRL_START | CTRL_EARLY_TERM | CTRL_MAX_ITER(30), LDPC_CTRL); + + // --- Step 4: Poll STATUS until not busy --- + do { + status = USER_readWord(LDPC_STATUS); + } while (status & STATUS_BUSY); + + // --- Step 5: Check results --- + // Check converged (bit 1) + if (!(status & STATUS_CONVERGED)) { + pass = 0; + } + + // Check syndrome weight = 0 (bits [23:16]) + if ((status & STATUS_SYN_MASK) != 0) { + pass = 0; + } + + // Read decoded info bits + decoded = USER_readWord(LDPC_DECODED); + if (decoded != EXPECTED_DECODED) { + pass = 0; + } + + // --- Step 6: Signal result via GPIO[7:0] --- + if (pass) { + GPIOs_writeLow(PASS_SIGNATURE); // 0xAB = pass + } else { + GPIOs_writeLow(FAIL_SIGNATURE); // 0xFF = fail + } + + // Signal test complete via management GPIO + ManagmentGpio_write(0); + + return; +} diff --git a/verilog/dv/cocotb/ldpc_tests/ldpc_noisy/ldpc_noisy.py b/verilog/dv/cocotb/ldpc_tests/ldpc_noisy/ldpc_noisy.py new file mode 100644 index 0000000..45ede3f --- /dev/null +++ b/verilog/dv/cocotb/ldpc_tests/ldpc_noisy/ldpc_noisy.py @@ -0,0 +1,82 @@ +# SPDX-FileCopyrightText: 2024 LDPC Optical Project + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# SPDX-License-Identifier: Apache-2.0 + +""" +LDPC Noisy Decode Test - cocotb Monitor + +Tests the LDPC decoder with a noisy but correctable codeword. Firmware: + 1. Verifies the VERSION register + 2. Loads noisy LLRs (test vector 0) + 3. Runs a decode with early termination, max_iter=30 + 4. Checks converged=1, syndrome_weight=0, decoded matches expected + 5. Signals pass (0xAB) or fail (0xFF) on GPIO[7:0] + +The cocotb monitor waits for the management GPIO to signal firmware ready, +then waits for the management GPIO to drop (test complete), and reads +GPIO[7:0] for the pass/fail result. +""" + +from caravel_cocotb.caravel_interfaces import test_configure +from caravel_cocotb.caravel_interfaces import report_test +import cocotb + +PASS_SIGNATURE = 0xAB +FAIL_SIGNATURE = 0xFF + + +@cocotb.test() +@report_test +async def ldpc_noisy(dut): + caravelEnv = await test_configure(dut, timeout_cycles=300000) + + cocotb.log.info("[TEST] Starting LDPC noisy decode test") + + # Release CSB to allow GPIO to function + await caravelEnv.release_csb() + + # Wait for firmware to signal ready (mgmt_gpio = 1) + cocotb.log.info("[TEST] Waiting for firmware ready (mgmt_gpio=1)...") + await caravelEnv.wait_mgmt_gpio(1) + cocotb.log.info("[TEST] Firmware ready, decode in progress...") + + # Wait for firmware to signal test complete (mgmt_gpio = 0) + await caravelEnv.wait_mgmt_gpio(0) + cocotb.log.info("[TEST] Firmware signaled test complete") + + # Read GPIO[7:0] for pass/fail result + gpio_val = caravelEnv.monitor_gpio(7, 0) + + if not gpio_val.is_resolvable: + cocotb.log.error( + f"[TEST] FAIL - GPIO[7:0] is unresolvable: {gpio_val.binstr}" + ) + assert False, "GPIO[7:0] has X/Z values" + + result = gpio_val.integer + cocotb.log.info(f"[TEST] GPIO[7:0] = 0x{result:02X}") + + if result == PASS_SIGNATURE: + cocotb.log.info("[TEST] PASS - Firmware reported noisy decode success (0xAB)") + elif result == FAIL_SIGNATURE: + cocotb.log.error( + "[TEST] FAIL - Firmware reported noisy decode failure (0xFF)" + ) + assert False, "Firmware reported LDPC noisy decode failure" + else: + cocotb.log.error( + f"[TEST] FAIL - Unexpected GPIO value: 0x{result:02X}" + ) + assert False, f"Unexpected GPIO result: 0x{result:02X}" diff --git a/verilog/dv/cocotb/ldpc_tests/ldpc_noisy/ldpc_noisy.yaml b/verilog/dv/cocotb/ldpc_tests/ldpc_noisy/ldpc_noisy.yaml new file mode 100644 index 0000000..3335207 --- /dev/null +++ b/verilog/dv/cocotb/ldpc_tests/ldpc_noisy/ldpc_noisy.yaml @@ -0,0 +1,19 @@ +--- +# SPDX-FileCopyrightText: 2024 LDPC Optical Project + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# SPDX-License-Identifier: Apache-2.0 + +Tests: + - {name: ldpc_noisy, sim: RTL}