Skip to content

Commit cbaffd6

Browse files
committed
[otbn,sw] Add WFI top level test
This adds a simple top level test to test the behaviour of the WFI instruction.
1 parent a406760 commit cbaffd6

4 files changed

Lines changed: 252 additions & 0 deletions

File tree

sw/device/tests/BUILD

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3249,6 +3249,28 @@ opentitan_test(
32493249
],
32503250
)
32513251

3252+
opentitan_test(
3253+
name = "otbn_wfi_test",
3254+
srcs = ["otbn_wfi_test.c"],
3255+
exec_env = dicts.add(
3256+
EARLGREY_TEST_ENVS,
3257+
EARLGREY_SILICON_OWNER_ROM_EXT_ENVS,
3258+
DARJEELING_TEST_ENVS,
3259+
{
3260+
"//hw/top_earlgrey:fpga_cw340_sival": None,
3261+
"//hw/top_earlgrey:silicon_creator": None,
3262+
},
3263+
),
3264+
deps = [
3265+
"//hw/top/dt",
3266+
"//sw/device/lib/dif:otbn",
3267+
"//sw/device/lib/testing:entropy_testutils",
3268+
"//sw/device/lib/testing:otbn_testutils",
3269+
"//sw/device/lib/testing/test_framework:ottf_main",
3270+
"//sw/otbn/wfi:wfi_test",
3271+
],
3272+
)
3273+
32523274
opentitan_test(
32533275
name = "otp_ctrl_mem_access_test",
32543276
srcs = ["otp_ctrl_mem_access_test.c"],

sw/device/tests/otbn_wfi_test.c

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
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 "sw/device/lib/dif/dif_otbn.h"
6+
#include "sw/device/lib/testing/entropy_testutils.h"
7+
#include "sw/device/lib/testing/otbn_testutils.h"
8+
#include "sw/device/lib/testing/test_framework/check.h"
9+
#include "sw/device/lib/testing/test_framework/ottf_main.h"
10+
11+
/**
12+
* This test exercises OTBN's WFI (Wait For Interrupt) instruction.
13+
*
14+
* This test does:
15+
* - Enable WFI for OTBN.
16+
* - Write the 1st magic value into `input` before starting.
17+
* - Start an OTBN program which copies `input` to `output` and then calls WFI.
18+
* - Poll until OTBN reports STATUS == PAUSED.
19+
* - Check if the done interrupt is pending and acknowledge it.
20+
* - Check OTBN does not advance while paused.
21+
* - Check `output` holds the 1st magic value in DMEM.
22+
* - Write a 2nd magic value into `input`.
23+
* - Issue the RESUME command
24+
* - Wait on the done interrupt until OTBN pauses again
25+
* - Check that OTBN copied the 2nd magic value to `output`.
26+
* - Resume execution and wait for the program to finish without errors.
27+
*/
28+
OTTF_DEFINE_TEST_CONFIG();
29+
30+
OTBN_DECLARE_APP_SYMBOLS(wfi_test);
31+
OTBN_DECLARE_SYMBOL_ADDR(wfi_test, input);
32+
OTBN_DECLARE_SYMBOL_ADDR(wfi_test, output);
33+
34+
static const otbn_app_t kAppWfiTest = OTBN_APP_T_INIT(wfi_test);
35+
// DMEM word the host writes for OTBN to copy (before start and while paused).
36+
static const otbn_addr_t kInput = OTBN_ADDR_T_INIT(wfi_test, input);
37+
// DMEM word OTBN copies `input` into before each WFI.
38+
static const otbn_addr_t kOutput = OTBN_ADDR_T_INIT(wfi_test, output);
39+
40+
// The 1st magic value the host writes to DMEM before starting the program.
41+
static const uint32_t kMagic1Value = 0xf00dcafe;
42+
// The 2nd magic value the host writes to DMEM while OTBN is paused.
43+
static const uint32_t kMagic2Value = 0xdeadbeef;
44+
45+
/**
46+
* Waits until OTBN stops executing the current program.
47+
*
48+
* First waits for the EXECUTE command to take effect (OTBN leaves IDLE), then
49+
* waits until OTBN is no longer busy executing. It stops executing because it
50+
* either paused on a WFI, finished, or locked up.
51+
*/
52+
static status_t otbn_wait_while_executing(const dif_otbn_t *otbn,
53+
dif_otbn_status_t *status) {
54+
do {
55+
TRY(dif_otbn_get_status(otbn, status));
56+
} while (*status == kDifOtbnStatusIdle);
57+
while (*status == kDifOtbnStatusBusyExecute) {
58+
TRY(dif_otbn_get_status(otbn, status));
59+
}
60+
return OK_STATUS();
61+
}
62+
63+
bool test_main(void) {
64+
dif_otbn_t otbn;
65+
CHECK_STATUS_OK(entropy_testutils_auto_mode_init());
66+
CHECK_DIF_OK(dif_otbn_init_from_dt(kDtOtbn, &otbn));
67+
68+
// Clear any stale interrupt state so we can observe the WFI-driven pause.
69+
CHECK_DIF_OK(dif_otbn_irq_acknowledge_all(&otbn));
70+
71+
// Load the app and enable the WFI instruction.
72+
CHECK_STATUS_OK(otbn_testutils_load_app(&otbn, kAppWfiTest));
73+
CHECK_DIF_OK(dif_otbn_set_ctrl_wfi_enable(&otbn, true));
74+
75+
// Seed `input` with the 1st magic value before starting. The program copies
76+
// it to `output` before the WFI, so the host can read it back while paused.
77+
CHECK_STATUS_OK(otbn_testutils_write_data(&otbn, sizeof(kMagic1Value),
78+
&kMagic1Value, kInput));
79+
80+
// Start execution. The program copies `input` to `output` and then pauses
81+
// itself on the WFI instruction.
82+
CHECK_STATUS_OK(otbn_testutils_execute(&otbn));
83+
84+
// Wait for OTBN to stop executing and confirm it entered the PAUSED state.
85+
dif_otbn_status_t status;
86+
CHECK_STATUS_OK(otbn_wait_while_executing(&otbn, &status));
87+
CHECK(status == kDifOtbnStatusPaused,
88+
"OTBN did not pause on WFI, status = 0x%x", status);
89+
90+
// The WFI instruction raises the `done` interrupt when it pauses.
91+
bool irq_pending;
92+
CHECK_DIF_OK(dif_otbn_irq_is_pending(&otbn, kDifOtbnIrqDone, &irq_pending));
93+
CHECK(irq_pending, "OTBN did not raise the done interrupt when pausing.");
94+
CHECK_DIF_OK(dif_otbn_irq_acknowledge(&otbn, kDifOtbnIrqDone));
95+
96+
// OTBN stays paused until we resume it, so INSN_CNT must be constant.
97+
uint32_t insn_cnt_paused;
98+
CHECK_DIF_OK(dif_otbn_get_insn_cnt(&otbn, &insn_cnt_paused));
99+
CHECK(insn_cnt_paused > 0, "Expected OTBN to retire instructions before WFI.");
100+
uint32_t insn_cnt_still_paused;
101+
CHECK_DIF_OK(dif_otbn_get_insn_cnt(&otbn, &insn_cnt_still_paused));
102+
CHECK(insn_cnt_still_paused == insn_cnt_paused,
103+
"OTBN executed instructions while paused.");
104+
105+
// Read back `output`: the program copied the 1st magic value from `input`
106+
// before the WFI.
107+
uint32_t output;
108+
CHECK_STATUS_OK(
109+
otbn_testutils_read_data(&otbn, sizeof(output), kOutput, &output));
110+
CHECK(output == kMagic1Value,
111+
"DMEM output = 0x%08x, expected 0x%08x", output, kMagic1Value);
112+
113+
// Write the 2nd magic value into `input`. After the RESUME the program copies
114+
// it to `output`, proving execution continued and saw the host's write.
115+
CHECK_STATUS_OK(otbn_testutils_write_data(&otbn, sizeof(kMagic2Value),
116+
&kMagic2Value, kInput));
117+
118+
CHECK_DIF_OK(dif_otbn_write_cmd(&otbn, kDifOtbnCmdResume));
119+
120+
// Wait on the done interrupt until OTBN pauses again on the 2nd WFI. We
121+
// acknowledged the interrupt after the 1st pause, so a fresh pending done
122+
// signals the 2nd pause.
123+
do {
124+
CHECK_DIF_OK(dif_otbn_irq_is_pending(&otbn, kDifOtbnIrqDone, &irq_pending));
125+
} while (!irq_pending);
126+
CHECK_DIF_OK(dif_otbn_get_status(&otbn, &status));
127+
CHECK(status == kDifOtbnStatusPaused,
128+
"OTBN did not pause on the 2nd WFI, status = 0x%x", status);
129+
CHECK_DIF_OK(dif_otbn_irq_acknowledge(&otbn, kDifOtbnIrqDone));
130+
131+
// The program copied the 2nd magic value from `input` to `output`.
132+
CHECK_STATUS_OK(
133+
otbn_testutils_read_data(&otbn, sizeof(output), kOutput, &output));
134+
CHECK(output == kMagic2Value,
135+
"DMEM output = 0x%08x, expected 0x%08x", output, kMagic2Value);
136+
137+
// Resume execution and wait for the program to finish without errors.
138+
CHECK_DIF_OK(dif_otbn_write_cmd(&otbn, kDifOtbnCmdResume));
139+
CHECK_STATUS_OK(otbn_testutils_wait_for_done(&otbn, kDifOtbnErrBitsNoError));
140+
141+
// The `done` interrupt fires again on completion.
142+
CHECK_DIF_OK(dif_otbn_irq_is_pending(&otbn, kDifOtbnIrqDone, &irq_pending));
143+
CHECK(irq_pending, "OTBN did not raise the done interrupt on completion.");
144+
CHECK_DIF_OK(dif_otbn_irq_acknowledge(&otbn, kDifOtbnIrqDone));
145+
146+
LOG_INFO("OTBN WFI test passed: paused and resumed correctly.");
147+
return true;
148+
}

sw/otbn/wfi/BUILD

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
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+
load("//rules:otbn.bzl", "otbn_binary")
6+
7+
package(default_visibility = ["//visibility:public"])
8+
9+
otbn_binary(
10+
name = "wfi_test",
11+
srcs = [
12+
"wfi_test.s",
13+
],
14+
)

sw/otbn/wfi/wfi_test.s

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
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+
/*
6+
* OTBN application to test the WFI instruction.
7+
*
8+
* It copies a magic word from `input` to `output` and pauses with a WFI
9+
* instruction. Once the host issues the RESUME command it copies a second magic
10+
* word (which the host stored into `input` while OTBN was paused) to `output`
11+
* and pauses again with a second WFI instruction. After the host resumes it a
12+
* second time the program finishes.
13+
*
14+
* The host stores the 1st magic word into `input` before starting the program.
15+
*
16+
* This lets the host confirm that:
17+
* - the code before the 1st WFI ran (`output` holds a copy of the 1st magic
18+
* word),
19+
* - it can access DMEM while OTBN is paused (it reads `output` and writes
20+
* `input`),
21+
* - execution resumed at the instruction after the 1st WFI and can see the
22+
* host's write (`output` holds a copy of the 2nd magic word),
23+
* - OTBN can pause more than once (it pauses again on the 2nd WFI before
24+
* finishing).
25+
*
26+
* The WFI instruction is only legal while CTRL.wfi_enabled is set, so the host
27+
* must enable it before starting this program.
28+
*/
29+
30+
.section .text.start
31+
32+
/* Copy the 1st magic word the host stored in `input` (before start) to
33+
`output` so the host can read it back while OTBN is paused. */
34+
la x2, input
35+
lw x3, 0(x2)
36+
la x2, output
37+
sw x3, 0(x2)
38+
39+
/* Pause and hand control back to the host. While paused the host reads
40+
`output` and writes the 2nd magic word into `input`. Execution resumes at
41+
the next instruction once the host writes the RESUME command to CMD. */
42+
wfi
43+
44+
/* Copy the 2nd magic word the host stored in `input` over to `output`. This
45+
proves execution resumed after the 1st WFI and can see the host's write. */
46+
la x2, input
47+
lw x3, 0(x2)
48+
la x2, output
49+
sw x3, 0(x2)
50+
51+
/* Pause a second time so the host can confirm `output` holds the copy before
52+
letting the program finish. Execution resumes at the `ecall` once the host
53+
writes the RESUME command a second time. */
54+
wfi
55+
56+
ecall
57+
58+
.section .data
59+
60+
.balign 32
61+
.globl input
62+
input:
63+
.word 0x00000000 /* written by the host before start and while paused */
64+
65+
.balign 32
66+
.globl output
67+
output:
68+
.word 0x00000000 /* written by OTBN: a copy of `input` */

0 commit comments

Comments
 (0)