Skip to content

Commit 17b3e1e

Browse files
committed
[hw, otbn] Add a pre_dv testbench for the mask_accelerator
This commit adds a testbench for all 4 modes of the mask accelerator. It also tests the secure wipe. This commit also consolidates some of the testbench constants and helper functions into a single header file serving the sec_add and mask_accelerator testbenches. Furthermore, this commit changes the README to explain the added functionality. Signed-off-by: Hakim Filali <hfilali@lowrisc.org>
1 parent 1e6310f commit 17b3e1e

4 files changed

Lines changed: 406 additions & 98 deletions

File tree

hw/ip/otbn/pre_dv/README.md

Lines changed: 85 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,85 @@
1-
# `otbn_sec_add` Verilator Testbench
1+
# `otbn_mask_accelerator` Verilator Testbench
22

3-
This directory contains a C++ Verilator testbench for `otbn_sec_add`, the first-order masked parallel prefix adder used in the OTBN mask accelerator.
4-
The testbench drives random two-share Boolean inputs and fresh randomness for 1 000 000 stimulus/check pairs, unmasking both input shares and the output shares to verify that the adder computes the correct (Width+1)-bit sum.
3+
This directory contains C++ Verilator testbenches for modules in the OTBN mask accelerator pipeline.
4+
5+
- `otbn_mask_accelerator_tb.cpp` — tests `otbn_mask_accelerator`, the top-level masked operation unit.
6+
- `otbn_sec_add_tb.cpp` — tests `otbn_sec_add`, the first-order masked parallel prefix adder.
7+
- `otbn_tb_utils.h` — shared utilities (clock helpers, share accessors, derived constants).
58

69

710
## Prerequisites
811

912
Follow the [Verilator setup guide](../../../../doc/getting_started/setup_verilator.md).
1013

1114

12-
## Building and running
15+
## Building and running `otbn_mask_accelerator`
16+
17+
All commands below are run from the OpenTitan repository root.
18+
19+
1. Elaborate the RTL and compile the Verilated model.
20+
```sh
21+
verilator --cc --exe --build --trace --assert -Wno-WIDTH -Wno-UNOPTFLAT \
22+
--top-module otbn_mask_accelerator \
23+
-Ihw/ip/prim/rtl \
24+
-Ihw/ip/prim_generic/rtl \
25+
hw/ip/prim/rtl/prim_secded_pkg.sv \
26+
hw/ip/prim/rtl/prim_trivium_pkg.sv \
27+
hw/ip/prim/rtl/prim_util_pkg.sv \
28+
hw/ip/prim/rtl/prim_mubi_pkg.sv \
29+
hw/ip/prim/rtl/prim_count_pkg.sv \
30+
hw/ip/lc_ctrl/rtl/lc_ctrl_reg_pkg.sv \
31+
hw/ip/lc_ctrl/rtl/lc_ctrl_state_pkg.sv \
32+
hw/ip/lc_ctrl/rtl/lc_ctrl_pkg.sv \
33+
hw/ip/otp_ctrl/rtl/otp_ctrl_pkg.sv \
34+
hw/ip/otbn/rtl/otbn_pkg.sv \
35+
hw/ip/otbn/rtl/otbn_sec_add.sv \
36+
hw/ip/otbn/rtl/otbn_sec_add_mod.sv \
37+
hw/ip/otbn/rtl/otbn_mask_accelerator.sv \
38+
hw/ip/prim/rtl/prim_blanker.sv \
39+
hw/ip/prim/rtl/prim_count.sv \
40+
hw/ip/prim/rtl/prim_fifo_sync.sv \
41+
hw/ip/prim_generic/rtl/prim_flop.sv \
42+
hw/ip/prim_generic/rtl/prim_flop_en.sv \
43+
hw/ip/otbn/pre_dv/otbn_mask_accelerator_tb.cpp \
44+
-o otbn_mask_accelerator_tb
45+
```
46+
47+
`--Wno-UNOPTFLAT` suppresses a false-positive combinational-loop warning on `pre_p` inside `otbn_sec_add`.
48+
Verilator 4.x traces the whole `buffer_data` array as one node and misses the register break provided by the `prim_flop_en` chain, making the pass-2 feedback path look combinational when it is not.
49+
50+
2. Run the testbench.
51+
```sh
52+
obj_dir/otbn_mask_accelerator_tb
53+
```
54+
55+
A passing run produces:
56+
```
57+
Mode 0 (SecAdd): PASS - 0 errors / 8000 checks
58+
Mode 1 (SecAddMod): PASS - 0 errors / 8000 checks
59+
Mode 2 (ArithToBool): PASS - 0 errors / 8000 checks
60+
Mode 3 (BoolToArith): PASS - 0 errors / 8000 checks
61+
Test ***PASSED*** all 4 modes
62+
```
63+
64+
The simulation also writes a VCD waveform to `dump.vcd` in the working directory.
65+
66+
67+
## Operation modes
68+
69+
The testbench exercises all four modes in a single run, in order:
70+
71+
| Mode | Name | Description | Output sharing |
72+
|------|---------------|------------------------------------------------|----------------|
73+
| 0 | `SecAdd` | Masked addition, no modular reduction | Boolean |
74+
| 1 | `SecAddMod` | Masked modular addition | Boolean |
75+
| 2 | `ArithToBool` | Arithmetic-to-Boolean share conversion | Boolean |
76+
| 3 | `BoolToArith` | Boolean-to-Arithmetic share conversion | Arithmetic |
77+
78+
Each mode runs a number of accepted vectors.
79+
After each mode the testbench waits for `wready_o` to go high (adder idle), then issues a one-cycle pulse of `sec_wipe_running_i` to flush the pipeline before the next mode begins.
80+
81+
82+
## Building and running `otbn_sec_add`
1383

1484
All commands below are run from the OpenTitan repository root.
1585

@@ -36,8 +106,10 @@ All commands below are run from the OpenTitan repository root.
36106
-o otbn_sec_add_tb
37107
```
38108

109+
`-UVERILATOR +define+INC_ASSERT` activates the full standard assertion macros and enables the `INC_ASSERT`-gated checks in `otbn_sec_add`.
110+
This works because `otbn_sec_add` uses only `ASSERT_INIT` and `ASSERT_FINAL` (immediate/final blocks), which Verilator 4.x supports.
39111

40-
1. Run the testbench.
112+
2. Run the testbench.
41113
```sh
42114
obj_dir/otbn_sec_add_tb
43115
```
@@ -47,53 +119,19 @@ All commands below are run from the OpenTitan repository root.
47119
Test ***PASSED*** 1000000 checks
48120
```
49121

50-
A failing run prints one line per mismatch and then:
51-
```
52-
Test ***FAILED*** <n> / 1000000
53-
```
54-
55-
The simulation also writes a VCD waveform to `dump.vcd` in the working directory.
56-
57-
58-
## Configuring the adder width
122+
### Configuring the adder width
59123

60-
The testbench supports Width values of 4, 8, 16, and 32 bits.
124+
The `otbn_sec_add` testbench supports Width values of 4, 8, 16, and 32 bits.
61125
Two flags must always be kept in sync:
62126

63-
| Flag | Purpose |
64-
|-----------------------|------------------------------------------------------|
65-
| `-GWidth=N` | Sets the `Width` parameter of the elaborated RTL |
66-
| `-CFLAGS "-DWIDTH=N"` | Sets the matching `WIDTH` macro in the C++ testbench |
127+
| Flag | Purpose |
128+
|---------------------------|------------------------------------------------------|
129+
| `-GWidth=N` | Sets the `Width` parameter of the elaborated RTL |
130+
| `-CFLAGS "-DWIDTH=N ..."` | Sets the matching `WIDTH` macro in the C++ testbench |
67131

68-
Mismatching the two values produces silent wrong results because the testbench and the DUT would disagree on the randomness bus width and the result layout.
69-
70-
Before switching to a different width, remove the stale build artefacts to avoid linker errors from a previous elaboration.
132+
Mismatching the two values produces wrong results because the testbench and the DUT will disagree on the randomness bus width and the result layout.
71133

134+
Before switching to a different width, remove the stale build artefacts to avoid linker errors from a previous elaboration:
72135
```sh
73136
rm -rf obj_dir
74137
```
75-
76-
Example for Width=16:
77-
78-
```sh
79-
verilator --cc --exe --build --trace --assert -Wno-WIDTH \
80-
--top-module otbn_sec_add \
81-
-GWidth=16 \
82-
-CFLAGS "-DWIDTH=16" \
83-
+define+INC_ASSERT -UVERILATOR \
84-
-Ihw/ip/prim/rtl \
85-
-Ihw/ip/prim_generic/rtl \
86-
hw/ip/prim/rtl/prim_secded_pkg.sv \
87-
hw/ip/prim/rtl/prim_trivium_pkg.sv \
88-
hw/ip/prim/rtl/prim_util_pkg.sv \
89-
hw/ip/prim/rtl/prim_mubi_pkg.sv \
90-
hw/ip/lc_ctrl/rtl/lc_ctrl_reg_pkg.sv \
91-
hw/ip/lc_ctrl/rtl/lc_ctrl_state_pkg.sv \
92-
hw/ip/lc_ctrl/rtl/lc_ctrl_pkg.sv \
93-
hw/ip/otp_ctrl/rtl/otp_ctrl_pkg.sv \
94-
hw/ip/otbn/rtl/otbn_pkg.sv \
95-
hw/ip/otbn/rtl/otbn_sec_add.sv \
96-
hw/ip/otbn/pre_dv/otbn_sec_add_tb.cpp \
97-
-o otbn_sec_add_tb
98-
obj_dir/otbn_sec_add_tb
99-
```
Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
// Copyright lowRISC contributors (OpenTitan project).
2+
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
3+
// SPDX-License-Identifier: Apache-2.0
4+
5+
#include <cstdint>
6+
#include <cstdio>
7+
#include <queue>
8+
#include <random>
9+
10+
#include "Votbn_mask_accelerator.h"
11+
#include "verilated.h"
12+
#include "verilated_vcd_c.h"
13+
14+
static constexpr int kWidth = 32;
15+
static constexpr uint32_t kShareMask = ~0u;
16+
17+
#include "otbn_tb_utils.h"
18+
19+
// mask_op_e encodings from otbn_pkg.sv, paired with display names.
20+
// arith_out: true when result_o carries an arithmetic sharing (s0+s1 mod q),
21+
// false when it carries a Boolean sharing (s0^s1).
22+
struct Mode {
23+
uint8_t enc;
24+
const char *name;
25+
bool arith_out;
26+
};
27+
static constexpr Mode kModes[] = {
28+
{0x17, "SecAdd", false},
29+
{0x0c, "SecAddMod", false},
30+
{0x0b, "ArithToBool", false},
31+
{0x10, "BoolToArith", true},
32+
};
33+
34+
static constexpr int kNumModes = sizeof(kModes) / sizeof(kModes[0]);
35+
static constexpr int kVecSize = 8;
36+
static constexpr int kElemPerMode = 1000 * kVecSize;
37+
static constexpr int kRemaskWords = 2;
38+
39+
int main(int argc, char **argv) {
40+
VerilatedContext *const contextp = new VerilatedContext;
41+
contextp->commandArgs(argc, argv);
42+
contextp->traceEverOn(true);
43+
44+
Votbn_mask_accelerator *const dut =
45+
new Votbn_mask_accelerator(contextp, "TOP");
46+
47+
// Access Verilator packed [N:0][W-1:0] ports as flat uint32_t[]
48+
// (little-endian).
49+
uint32_t *const rand_raw = reinterpret_cast<uint32_t *>(&dut->rand_i);
50+
uint32_t *const remask_raw =
51+
reinterpret_cast<uint32_t *>(&dut->remask_rand_i);
52+
uint32_t *const result_raw = reinterpret_cast<uint32_t *>(&dut->result_o);
53+
uint32_t *const in0_raw = reinterpret_cast<uint32_t *>(&dut->in0_i);
54+
uint32_t *const in1_raw = reinterpret_cast<uint32_t *>(&dut->in1_i);
55+
56+
// VCD waveform writer receives signal snapshots on each vcd->dump() call.
57+
VerilatedVcdC *const vcd = new VerilatedVcdC;
58+
dut->trace(vcd, 99);
59+
vcd->open("dump.vcd");
60+
61+
// Mersenne Twister RNG with a fixed seed for deterministic, reproducible
62+
// timulus.
63+
std::mt19937 rng(42);
64+
// Produces uniformly distributed random uint32_t values (full 32-bit range).
65+
std::uniform_int_distribution<uint32_t> dist;
66+
67+
// Initialise all inputs to a known state before the first eval().
68+
dut->clk_i = 0;
69+
dut->rst_ni = 0;
70+
dut->sec_wipe_running_i = 0;
71+
dut->wvalid_i = 0;
72+
dut->rready_i = 1;
73+
dut->mask_op_i = kModes[0].enc;
74+
dut->mod_i = dist(rng) & kShareMask;
75+
// mod_i = 0 would break rejection sampling
76+
if (dut->mod_i == 0)
77+
dut->mod_i = 1;
78+
for (int i = 0; i < kRandWords; i++)
79+
rand_raw[i] = 0;
80+
for (int i = 0; i < kRemaskWords; i++)
81+
remask_raw[i] = 0;
82+
set_share(in0_raw, 0, 0);
83+
set_share(in0_raw, 1, 0);
84+
set_share(in1_raw, 0, 0);
85+
set_share(in1_raw, 1, 0);
86+
dut->eval();
87+
88+
// Reset the DUT.
89+
run_reset(dut, vcd, contextp);
90+
91+
int total_errs = 0;
92+
const uint32_t mod = dut->mod_i;
93+
94+
for (int mode = 0; mode < kNumModes; mode++) {
95+
// Set the operation mode and advance by one cycle.
96+
dut->mask_op_i = kModes[mode].enc;
97+
tick(dut, vcd, contextp);
98+
99+
int n_stims = 0, n_checks = 0, n_errs = 0;
100+
101+
// FIFO of expected kWidth-bit results. Each entry is pushed when a stimulus
102+
// is accepted (wvalid_i && wready_o) and popped when rvalid_o fires.
103+
std::queue<uint32_t> exp_queue;
104+
105+
// Stimulus and result-check loop.
106+
while (n_checks < kElemPerMode) {
107+
// Drive fresh randomness before the rising edge.
108+
// For BoolToArith, wready_o is combinational on remask_rand_i[0].
109+
for (int i = 0; i < kRandWords; i++)
110+
rand_raw[i] = dist(rng);
111+
for (int i = 0; i < kRemaskWords; i++)
112+
remask_raw[i] = dist(rng) & kShareMask;
113+
114+
uint32_t golden = 0;
115+
if (n_stims < kElemPerMode) {
116+
uint32_t a0, a1, b0 = 0, b1 = 0;
117+
if (mode == 1) { // SecAddMod
118+
const uint32_t inp1 = dist(rng) % mod;
119+
const uint32_t inp2 = dist(rng) % mod;
120+
const uint32_t ma = dist(rng) & kShareMask;
121+
const uint32_t mb = dist(rng) & kShareMask;
122+
a0 = (inp1 - mod) ^ ma;
123+
a1 = ma;
124+
b0 = inp2 ^ mb;
125+
b1 = mb;
126+
golden = (uint32_t)(((uint64_t)inp1 + inp2) % mod);
127+
} else if (mode == 2) { // ArithToBool
128+
const uint32_t inp1 = dist(rng) % mod;
129+
const uint32_t inp2 = dist(rng) % mod;
130+
a0 = inp1;
131+
a1 = inp2;
132+
golden = (uint32_t)(((uint64_t)inp1 + inp2) % mod);
133+
} else if (mode == 3) { // BoolToArith
134+
const uint32_t secret = dist(rng) % mod;
135+
const uint32_t ma = dist(rng) & kShareMask;
136+
a0 = secret ^ ma;
137+
a1 = ma;
138+
golden = secret;
139+
} else { // SecAdd
140+
a0 = dist(rng) & kShareMask;
141+
a1 = dist(rng) & kShareMask;
142+
b0 = dist(rng) & kShareMask;
143+
b1 = dist(rng) & kShareMask;
144+
golden = ((a0 ^ a1) + (b0 ^ b1)) & kShareMask;
145+
}
146+
set_share(in0_raw, 0, a0);
147+
set_share(in0_raw, 1, a1);
148+
set_share(in1_raw, 0, b0);
149+
set_share(in1_raw, 1, b1);
150+
dut->wvalid_i = 1;
151+
} else {
152+
dut->wvalid_i = 0;
153+
}
154+
155+
// Settle combinational outputs after driving fresh randomness so that
156+
// wready_o reflects the current remask_rand_i before we sample it.
157+
// For BoolToArith, wready_o is gated by (mask_mod < mod_i) which is
158+
// purely combinational on remask_rand_i[0], without this eval() the
159+
// acceptance decision would be one cycle stale.
160+
dut->eval();
161+
const bool accepted = dut->wvalid_i && dut->wready_o;
162+
163+
// Rising edge
164+
dut->clk_i = 1;
165+
dut->eval();
166+
167+
if (accepted) {
168+
exp_queue.push(golden);
169+
n_stims++;
170+
}
171+
172+
if (dut->rvalid_o && !exp_queue.empty()) {
173+
const uint32_t s0 = get_share(result_raw, 0);
174+
const uint32_t s1 = get_share(result_raw, 1);
175+
const uint32_t got = kModes[mode].arith_out
176+
? (uint32_t)(((uint64_t)s0 + s1) % mod)
177+
: (s0 ^ s1) & kShareMask;
178+
const uint32_t exp = exp_queue.front();
179+
exp_queue.pop();
180+
n_checks++;
181+
if (got != exp) {
182+
n_errs++;
183+
printf(
184+
"[FAIL] mode=%d t=%-8llu | exp=%08x got=%08x (s0=%08x s1=%08x)\n",
185+
mode, (unsigned long long)contextp->time(), exp, got, s0, s1);
186+
}
187+
}
188+
189+
vcd->dump(contextp->time());
190+
contextp->timeInc(1);
191+
192+
// Falling edge
193+
dut->clk_i = 0;
194+
dut->eval();
195+
vcd->dump(contextp->time());
196+
contextp->timeInc(1);
197+
}
198+
199+
printf("Mode %d (%s): %s - %d errors / %d checks\n", mode,
200+
kModes[mode].name, n_errs ? "FAIL" : "PASS", n_errs, n_checks);
201+
total_errs += n_errs;
202+
}
203+
204+
if (total_errs)
205+
printf("Test ***FAILED*** %d total errors\n", total_errs);
206+
else
207+
printf("Test ***PASSED*** all 4 modes\n");
208+
209+
dut->final();
210+
vcd->close();
211+
delete vcd;
212+
delete dut;
213+
delete contextp;
214+
return total_errs ? 1 : 0;
215+
}

0 commit comments

Comments
 (0)