16-bit RISC Processor
Overview
The 16-bit RISC Processor is one of two designs under test (DUTs) validated by the ConfigDrivenV Python-Integrated UVM verification framework. It is implemented as a custom RISC-style processor in SystemVerilog (file: simple_cpu.v) and serves as DUT 1 in the ConfigDrivenV testbench pipeline. ConfigDrivenV achieves 100% verification accuracy on the correct RTL (PASS=100, FAIL=0) across all 5 opcodes, all 4 destination registers, and the full signed immediate range, and 100% bug detection effectiveness on deliberately corrupted RTL [b6b66b496-5c1e-4ae0-a78a-2658594b58b2].
Architecture
The processor features the following datapath characteristics [1]:
- Register File: 4 × 16-bit registers (R0, R1, R2, R3)
- Program Counter: 8-bit
- Execution Model: Single-cycle execution FSM
The 16-bit word size governs both the register width and the instruction word width, with modular overflow handled via bitwise AND with 0xFFFF in the Python golden reference model, faithfully replicating the synthesized hardware datapath behavior [2].
Instruction Set
The processor supports a minimal five-opcode ISA [1]:
| Opcode | Mnemonic | Operation |
|---|---|---|
| 0x0 | NOP | PC ← PC+1 |
| 0x1 | ADD | rd ← rd + imm |
| 0x2 | SUB | rd ← rd − imm |
| 0x3 | MOV | rd ← imm |
| 0x4 | XOR | rd ← rd ⊕ imm |
| 0xF | HALT | halt ← 1 |
Instruction Encoding
Instructions follow the 16-bit format [3]:
- Bits [15:12]: opcode (4 bits)
- Bits [9:8]: destination register
rd(2 bits) - Bits [7:0]: immediate value
imm(8 bits, signed range −128 to +127)
Verification Approach
ConfigDrivenV verifies the 16-bit RISC Processor using a two-layer pipeline [3]:
- Python Layer: Generates random instruction sequences and computes mathematically correct expected register states. Register state tracking is maintained through a four-element Python list; each arithmetic operation applies bitwise AND with 0xFFFF to enforce 16-bit width constraints [2].
- UVM Layer: Reads Python-generated files, drives the DUT, and performs automated comparison via a self-checking scoreboard.
File I/O Bridge
Communication between the Python layer and UVM testbench occurs through three text files [1]:
- instr.hex: One instruction per line as a 4-digit hex value (16-bit instructions).
- checkpoints.mem: One line per instruction with five space-separated hex fields representing PC, R0, R1, R2, R3 after each instruction completes.
- golden.mem: Contains the four final register values.
The UVM test sequence reads these files using SystemVerilog $fscanf [2]:
fi = $fopen(instr_file, "r");
fc = $fopen(chkpt_file, "r");
$fscanf(fi, "%h", instr);
$fscanf(fc, "%h %h %h %h %h", pc, r0, r1, r2, r3);
Representative Checkpoint Format
Each line in checkpoints.mem represents the post-instruction state [2]:
0000 0000 0000 ffa2 0000 ; after instr 0
0001 000f 0000 ffa2 0000 ; after instr 1
0002 000f 0000 ffa2 0005 ; after instr 2
UVM Testbench Components
The CPU verification testbench comprises the following files [b02693db-011a-4664-a274-e2d5d421c19e, b5a349cf-7625-4030-b725-6cf21b6aa04a]:
| File | Component | Role |
|---|---|---|
| cpu_if.sv | Interface | Bundles clk, rst, instr, pc, halt, r0–r3 |
| cpu_seq_item.sv | Sequence Item | Holds instr, exp_r0–r3, act_r0–r3 |
| cpu_driver.sv | Driver | Drives instr, reads actual registers |
| cpu_monitor.sv | Monitor | Passive DUT output observer |
| cpu_scoreboard.sv | Scoreboard | PASS/FAIL register comparator |
| cpu_agent.sv | Agent | Bundles driver+monitor+sequencer |
| cpu_env.sv | Environment | Connects via TLM analysis ports |
| cpu_test.sv | Test | Reads Python files, drives sequences |
| cpu_tb_pkg.sv | Package | Compilation order management |
| tb_top_cpu.sv | Top Module | DUT instantiation + UVM start |
The scoreboard receives complete transaction objects from the driver containing Python-predicted values (exp_r0 through exp_r3) and actual DUT values (act_r0 through act_r3). Comparison uses the SystemVerilog === operator, which correctly handles 4-value logic (0, 1, X, Z) and avoids false passes when uninitialized X values propagate [2].
Verification Results
CPU verification results across 100 random instructions [b6b66b496-5c1e-4ae0-a78a-2658594b58b2]:
| Experiment | Instructions | PASS | FAIL | Pass Rate |
|---|---|---|---|---|
| Correct RTL | 100 | 100 | 0 | 100% |
| Buggy RTL (ADD→SUB) | 100 | 0 | 100 | 0% |
The correct RTL passed 100% of instructions spanning all 5 opcodes, all 4 destination registers, and the full signed immediate range (−128 to +127). The buggy RTL triggered FAIL for every instruction where ADD was selected, with each FAIL logging the expected vs. actual discrepancy precisely, enabling immediate bug localization.
Sample Scoreboard Output
PASS transaction [b6b66b496-5c1e-4ae0-a78a-2658594b58b2]:
UVM_INFO [SB] PASS | instr=26
r0=0002 r1=009b r2=0018 r3=006e
FAIL transaction (ADD→SUB bug injection) [b6b66b496-5c1e-4ae0-a78a-2658594b58b2]:
UVM_ERROR [SB] FAIL | instr=1
exp r2=ffa2 | got r2=005e
Comparison with Related Works
Among related UVM-based verification methodologies surveyed in the ConfigDrivenV paper, the 16-bit RISC Processor verification by ConfigDrivenV is distinguished by its use of a Python-constrained golden reference model with free EDA tools, achieving 100% pass rate [b6b66b496-5c1e-4ae0-a78a-2658594b58b2]:
| Approach | Stimulus | Predictor | Tools | Result |
|---|---|---|---|---|
| Existing CPU ISA work | Constrained random (SV) | SV predictor | Commercial | Partial |
| ConfigDrivenV CPU+UART | Python (Free) | Python | Free Tools | 100% |
Scalability
Test depth is controlled by a single Python variable NUM_INSTR, scaling verification from 30 to 1000 instructions without modifying any UVM code. Scalability results [b6b66b496-5c1e-4ae0-a78a-2658594b58b2]:
| NUM_INSTR | PASS (correct) | FAIL (buggy) | Est. Sim Time |
|---|---|---|---|
| 30 | 30 | 30 | ~1 s |
| 50 | 50 | 50 | ~2 s |
| 100 | 100 | 100 | ~3 s |
| 200 | 200 | 200 | ~5 s |