Testing Guide
Test Structure
Important: The testbench package uses integration tests (not unit tests). Each test file in testbench/tests/ is compiled as a separate binary. This is the recommended pattern for Verilator-based tests because:
- Integration tests run in separate processes, preventing C++ memory conflicts
- Each test binary gets its own Verilator runtime, avoiding destructor race conditions
- Follows marlin crate's recommended usage patterns
- No manual locking or serialization required
Test Suite Overview
The project has 264 comprehensive tests across all packages:
testbench package (integration tests):
- ALU tests: Validate arithmetic/logic operations + M extension (MUL, MULH, MULHSU, MULHU, DIV, DIVU, REM, REMU)
- Register file tests: Validate register behavior (including x0 immutability)
- FP register file tests: Validate floating-point register file with 3 read ports
- FPU tests: Validate all 26 FP operations (arithmetic, comparisons, conversions, etc.)
- Decompressor tests: Validate all 27 RV32C compressed instructions
- Peripheral tests: SRAM, LED controller, clock peripheral, system controller, UART
- Bus tests: bus arbiter, FIFO, async FIFO, ff_sync, host bus interface, host RX buffer
Other packages:
- cpu-sim: integration tests including:
- ELF loading and execution
- FIFO communication and packet protocol
- VCD waveform dumping validation
- Instruction trace callbacks with comprehensive validation
- Programmatic instruction sequence testing with trace verification
- FP integration tests: Validate all 26 FP instructions in CPU context
- Combined trace + VCD testing
- Variable memory latency testing
- RV32C compressed instruction tests: Basic instructions and critical transition scenarios (C→C, C→U, U→C, U→U, mixed)
- riscv_core: 33 utility and tracing tests
- device-runtime: integration tests covering ELF execution, reset, RTL peripherals, DMA, packet protocol, video/audio, RV32C/F
- host-bus-handler and bus-shared: bus protocol and system bus tests
New Validation Tests
test_comprehensive_trace_validation: Validates instruction trace accuracy for 12+ instructions with full operand checking (PC, register values, immediates)
test_trace_with_branches: Ensures branch instructions skip correct sequences in trace output
test_trace_and_vcd_together: Demonstrates VCD + instruction trace working simultaneously
Running Tests
Run all tests
cargo test
Run specific test suite
cargo test --package testbench -- alu_test
cargo test --package testbench -- regfile_test
cargo test --package testbench -- cpu_test
Run with verbose output
cargo test --verbose
Run single test
cargo test --package testbench -- test_cpu_branch_beq_bne --nocapture
Build only (without running tests)
cargo build
Clean build artifacts
cargo clean # Important after RTL changes to clear Verilator cache
Testing Best Practices
When Adding New Tests
- Location: Add to appropriate test file (
alu_test.rs, regfile_test.rs, or cpu_test.rs)
- Register in lib.rs: Add module declaration if creating new test file
- Use helper macros:
clock_cycle!(dut) for clock edge transitions
create_runtime() for consistent test setup
- Memory management:
- Use
HashMap<u32, u32> for instruction/data memory
- Read
dmem_addr AFTER eval() for stores
- Set
dmem_rdata BEFORE eval() for loads
Test Naming Convention
- Prefix with
test_
- Use descriptive names:
test_cpu_branch_beq_bne, test_alu_shift_ops
- Group related tests logically
Verilator Build Process
The marlin crate automatically:
- Compiles SystemVerilog files with Verilator
- Creates shared libraries in
target/verilator/
- Links them to Rust test code
Build artifacts are cached between runs for performance.
Debugging Tests
Enable Verbose Output
cargo test -- --nocapture # See println! output from tests
cargo test -- --show-output # Show output even for passing tests
Check Verilator Compilation
Verilator creates intermediate C++ files in target/verilator/. Check these if you suspect compilation issues.
Debugging Strategy
When debugging test failures:
- Enable debug output: Use
--nocapture flag
- Run single test: Isolate the failing test
- Add print statements: Use
println!() in Rust or $display() in SystemVerilog
- Check RTL signals: Add
$display() statements to observe hardware state
- Verify assumptions: Don't rely on abstract reasoning - observe actual signal values
- Clean and rebuild: If behavior is inconsistent, run
cargo clean first
Non-Trivial Debugging Tasks
IMPORTANT: For complex debugging sessions that don't directly relate to the main task:
- Delegate to specialized agents: Use the
task tool to spawn debugging-focused agents
- Preserve main context: Keep your primary context clean by offloading debug work
- Exception: If the user's primary request IS debugging, handle it directly
When to delegate debugging:
- Unexpected hardware behavior requiring extensive signal analysis
- Complex timing issues across multiple cycles
- Memory interface problems requiring trace analysis
- Multi-module interaction bugs
Example delegation:
Use the task tool with agent_type="fpga-architect" for RTL debugging
Use the task tool with agent_type="rust-verification-architect" for test harness debugging
After Modifying RTL
- Lint the RTL:
find rtl/common -name '*.sv' -exec verilator --lint-only --Wno-MULTITOP {} +
- Clean build:
cargo clean (Verilator cache may be stale)
- Run tests:
cargo test
- Verify all tests pass: Look for
test result: ok with all tests passed
Converted and distributed by TomeVault — claim your Tome and manage your conversions.
1---2name: testing-393description: Guide for writing, running, and debugging tests in the RISC-V verification project. Use when asked about test structure, test best practices, running tests, or debugging test failures. Use when this capability is needed.4---56# Testing Guide78## Test Structure910**Important:** The `testbench` package uses **integration tests** (not unit tests). Each test file in `testbench/tests/` is compiled as a separate binary. This is the recommended pattern for Verilator-based tests because:11- Integration tests run in separate processes, preventing C++ memory conflicts12- Each test binary gets its own Verilator runtime, avoiding destructor race conditions13- Follows marlin crate's recommended usage patterns14- No manual locking or serialization required1516## Test Suite Overview1718The project has 264 comprehensive tests across all packages:1920### testbench package (integration tests):21- **ALU tests:** Validate arithmetic/logic operations + M extension (MUL, MULH, MULHSU, MULHU, DIV, DIVU, REM, REMU)22- **Register file tests:** Validate register behavior (including x0 immutability)23- **FP register file tests:** Validate floating-point register file with 3 read ports24- **FPU tests:** Validate all 26 FP operations (arithmetic, comparisons, conversions, etc.)25- **Decompressor tests:** Validate all 27 RV32C compressed instructions26- **Peripheral tests:** SRAM, LED controller, clock peripheral, system controller, UART27- **Bus tests:** bus arbiter, FIFO, async FIFO, ff_sync, host bus interface, host RX buffer2829### Other packages:30- **cpu-sim: integration tests including:**31 - ELF loading and execution32 - FIFO communication and packet protocol33 - VCD waveform dumping validation34 - Instruction trace callbacks with comprehensive validation35 - Programmatic instruction sequence testing with trace verification36 - FP integration tests: Validate all 26 FP instructions in CPU context37 - Combined trace + VCD testing38 - Variable memory latency testing39 - RV32C compressed instruction tests: Basic instructions and critical transition scenarios (C→C, C→U, U→C, U→U, mixed)40- **riscv_core: 33 utility and tracing tests**41- **device-runtime: integration tests covering ELF execution, reset, RTL peripherals, DMA, packet protocol, video/audio, RV32C/F**42- **host-bus-handler and bus-shared: bus protocol and system bus tests**4344## New Validation Tests4546- `test_comprehensive_trace_validation`: Validates instruction trace accuracy for 12+ instructions with full operand checking (PC, register values, immediates)47- `test_trace_with_branches`: Ensures branch instructions skip correct sequences in trace output48- `test_trace_and_vcd_together`: Demonstrates VCD + instruction trace working simultaneously4950## Running Tests5152### Run all tests53```bash54cargo test55```5657### Run specific test suite58```bash59cargo test --package testbench -- alu_test60cargo test --package testbench -- regfile_test61cargo test --package testbench -- cpu_test62```6364### Run with verbose output65```bash66cargo test --verbose67```6869### Run single test70```bash71cargo test --package testbench -- test_cpu_branch_beq_bne --nocapture72```7374### Build only (without running tests)75```bash76cargo build77```7879### Clean build artifacts80```bash81cargo clean # Important after RTL changes to clear Verilator cache82```8384## Testing Best Practices8586### When Adding New Tests87881. **Location:** Add to appropriate test file (`alu_test.rs`, `regfile_test.rs`, or `cpu_test.rs`)892. **Register in lib.rs:** Add module declaration if creating new test file903. **Use helper macros:** 91 - `clock_cycle!(dut)` for clock edge transitions92 - `create_runtime()` for consistent test setup934. **Memory management:** 94 - Use `HashMap<u32, u32>` for instruction/data memory95 - Read `dmem_addr` AFTER `eval()` for stores96 - Set `dmem_rdata` BEFORE `eval()` for loads9798### Test Naming Convention99100- Prefix with `test_`101- Use descriptive names: `test_cpu_branch_beq_bne`, `test_alu_shift_ops`102- Group related tests logically103104## Verilator Build Process105106The marlin crate automatically:1071. Compiles SystemVerilog files with Verilator1082. Creates shared libraries in `target/verilator/`1093. Links them to Rust test code110111Build artifacts are cached between runs for performance.112113## Debugging Tests114115### Enable Verbose Output116117```bash118cargo test -- --nocapture # See println! output from tests119cargo test -- --show-output # Show output even for passing tests120```121122### Check Verilator Compilation123124Verilator creates intermediate C++ files in `target/verilator/`. Check these if you suspect compilation issues.125126### Debugging Strategy127128When debugging test failures:1291301. **Enable debug output:** Use `--nocapture` flag1312. **Run single test:** Isolate the failing test1323. **Add print statements:** Use `println!()` in Rust or `$display()` in SystemVerilog1334. **Check RTL signals:** Add `$display()` statements to observe hardware state1345. **Verify assumptions:** Don't rely on abstract reasoning - observe actual signal values1356. **Clean and rebuild:** If behavior is inconsistent, run `cargo clean` first136137### Non-Trivial Debugging Tasks138139**IMPORTANT:** For complex debugging sessions that don't directly relate to the main task:140141- **Delegate to specialized agents:** Use the `task` tool to spawn debugging-focused agents142- **Preserve main context:** Keep your primary context clean by offloading debug work143- **Exception:** If the user's primary request IS debugging, handle it directly144145**When to delegate debugging:**146- Unexpected hardware behavior requiring extensive signal analysis147- Complex timing issues across multiple cycles148- Memory interface problems requiring trace analysis149- Multi-module interaction bugs150151**Example delegation:**152```153Use the task tool with agent_type="fpga-architect" for RTL debugging154Use the task tool with agent_type="rust-verification-architect" for test harness debugging155```156157## After Modifying RTL1581591. **Lint the RTL:** `find rtl/common -name '*.sv' -exec verilator --lint-only --Wno-MULTITOP {} +`1602. **Clean build:** `cargo clean` (Verilator cache may be stale)1613. **Run tests:** `cargo test`1624. **Verify all tests pass:** Look for `test result: ok` with all tests passed163164---165> Converted and distributed by [TomeVault](https://tomevault.io/claim/impakt73) — claim your Tome and manage your conversions.166<!-- tomevault:4.0:skill_md:2026-04-16 -->