test(popcount_pipeline): add cocotb test for popcount pipeline module

- Add Makefile with cocotb configuration for popcount_pipeline RTL simulation
- Add Python test module with reset, drive/expect helpers
- Test row metadata preservation and bit-count correctness across 7 vectors
This commit is contained in:
2026-05-18 13:36:51 +08:00
parent 583b2156ea
commit b9b5684718
5 changed files with 96 additions and 1 deletions

View File

@@ -1,3 +1,4 @@
plugins/
node_modules
package.json
package-lock.json

View File

@@ -1,5 +1,5 @@
PYTHON ?= python
MODULE_TESTS := cam_core_banked match_engine_pipeline cam_write_noise cam_read_noise
MODULE_TESTS := cam_core_banked match_engine_pipeline cam_write_noise cam_read_noise popcount_pipeline
.PHONY: help test-all test-top test-modules test-module test-model test-perf clean $(MODULE_TESTS:%=test-module-%)

View File

@@ -0,0 +1,9 @@
SIM_ROOT := $(abspath ../../..)
RTL_ROOT := $(abspath $(SIM_ROOT)/../rtl)
include $(SIM_ROOT)/mk/rtl-sources.mk
TOPLEVEL := popcount_pipeline
COCOTB_TEST_MODULES := tests.modules.popcount_pipeline.test_popcount_pipeline
VERILOG_SOURCES := $(RTL_MATCH_ENGINE)
include $(SIM_ROOT)/mk/cocotb-common.mk

View File

@@ -0,0 +1,85 @@
from __future__ import annotations
import cocotb
from cocotb.clock import Clock
from cocotb.triggers import RisingEdge
# ── Helpers ──────────────────────────────────────────────────────────────────
async def reset_popcount(dut):
"""Assert reset for 5 cycles, then release and wait 2 cycles."""
dut.rst_n.value = 0
dut.valid_i.value = 0
dut.row_i.value = 0
dut.bits_i.value = 0
for _ in range(5):
await RisingEdge(dut.clk)
dut.rst_n.value = 1
for _ in range(2):
await RisingEdge(dut.clk)
async def drive_and_expect(dut, row: int, bits: int, expected_count: int):
"""Drive valid_i for one cycle, wait for valid_o, assert row_o / count_o."""
dut.row_i.value = row
dut.bits_i.value = bits
dut.valid_i.value = 1
await RisingEdge(dut.clk)
dut.valid_i.value = 0
# Pipeline depth = 3 (valid_s1_q -> valid_s2_q -> valid_o); wait with timeout
for _ in range(20):
await RisingEdge(dut.clk)
if int(dut.valid_o.value):
break
assert int(dut.valid_o.value) == 1, "valid_o never asserted"
assert int(dut.row_o.value) == row, (
f"row_o mismatch: got {int(dut.row_o.value)}, expected {row}"
)
assert int(dut.count_o.value) == expected_count, (
f"count_o mismatch: got {int(dut.count_o.value)}, expected {expected_count}"
)
def _width(dut) -> int:
"""Return the WIDTH of bits_i in the DUT."""
return len(dut.bits_i)
# ── Test ─────────────────────────────────────────────────────────────────────
@cocotb.test()
async def popcount_pipeline_counts_bits_and_preserves_row_metadata(dut):
"""
Popcount pipeline: for each deterministic vector, assert row_o matches row_i
and count_o equals the bit-count of bits_i.
"""
cocotb.start_soon(Clock(dut.clk, 10, unit="ns").start())
await reset_popcount(dut)
w = _width(dut)
all_ones = (1 << w) - 1
# Fixed literal: "0123456789ABCDEF" repeated enough for width, masked.
hex_repeat = (w + 63) // 64
fixed_literal = int("0123456789ABCDEF" * hex_repeat, 16) & all_ones
# Alternating 0xAA pattern
alternating_aa = int.from_bytes(b"\xAA" * (w // 8), "little")
vectors = [
(0, 0, 0),
(1, all_ones, w),
(2, 1 << 0, 1),
(3, 1 << min(127, w - 1), 1),
(4, 1 << min(511, w - 1), 1),
(5, alternating_aa, alternating_aa.bit_count()),
(6, fixed_literal, fixed_literal.bit_count()),
]
for row, bits, expected in vectors:
await drive_and_expect(dut, row, bits, expected)