diff --git a/SUMMARY.md b/SUMMARY.md index 6be4a62ca0ef7..b95efb61ed6e6 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -61,7 +61,8 @@ - [Formal Masking Verification Using Alma](./hw/ip/otbn/pre_sca/alma/README.md) - [Functional Coverage](./hw/ip/otbn/dv/doc/fcov.md) - [Programmer's Guide](./hw/ip/otbn/doc/programmers_guide.md) - - [Interface and Registers](./hw/ip/otbn/data/otbn.hjson) + - [Hardware Interfaces](./hw/ip/otbn/doc/interfaces.md) + - [Registers](./hw/ip/otbn/doc/registers.md) - [Checklist](./hw/ip/otbn/doc/checklist.md) - [Hardware IP Blocks](./hw/ip/README.md) diff --git a/hw/dv/sv/push_pull_agent/push_pull_agent_cfg.sv b/hw/dv/sv/push_pull_agent/push_pull_agent_cfg.sv index 52b7b965a7cab..f9d24e6fb3834 100644 --- a/hw/dv/sv/push_pull_agent/push_pull_agent_cfg.sv +++ b/hw/dv/sv/push_pull_agent/push_pull_agent_cfg.sv @@ -187,6 +187,33 @@ class push_pull_agent_cfg #(parameter int HostDataWidth = 32, return (d_user_data_q.size() > 0); endfunction + // Return true if the interface is completely silent + virtual function logic is_silent(); + return !((agent_type == PushAgent) ? + (vif.mon_cb.valid || vif.mon_cb.ready) : + (vif.mon_cb.req || vif.mon_cb.ack)); + endfunction + + // Return true if there's a stalled transaction + // + // If this is a pull agent, there is a stalled transaction when the req signal is high (so + // something is trying to read data), but the ack signal is low (there's no data available). If it + // is a push agent, there is a stalled transaction when the valid signal is high (so something is + // trying to provide data) but the ready signal is low (the data isn't being consumed). + virtual function logic is_stalled(); + return ((agent_type == PushAgent) ? + (vif.mon_cb.valid && !vif.mon_cb.ready) : + (vif.mon_cb.req && !vif.mon_cb.ack)); + endfunction + + // Wait for any current transaction to finish + // + virtual task wait_while_running(); + while (is_stalled()) @(vif.mon_cb); + // Add one last cycle to wait past the final cycle for the transaction that was stalled. + @(vif.mon_cb); + endtask + `uvm_object_param_utils_begin(push_pull_agent_cfg#(HostDataWidth, DeviceDataWidth)) `uvm_field_enum(push_pull_agent_e, agent_type, UVM_DEFAULT) `uvm_field_enum(pull_handshake_e, pull_handshake_type, UVM_DEFAULT) diff --git a/hw/ip/aes/pre_syn/syn_yosys.sh b/hw/ip/aes/pre_syn/syn_yosys.sh index 48e8e26f3ad4e..84578299cd97d 100755 --- a/hw/ip/aes/pre_syn/syn_yosys.sh +++ b/hw/ip/aes/pre_syn/syn_yosys.sh @@ -81,6 +81,7 @@ OT_DEP_SOURCES=( OT_DEP_PACKAGES=( "$LR_SYNTH_SRC_DIR"/../../top_earlgrey/rtl/*_pkg.sv "$LR_SYNTH_SRC_DIR"/../edn/rtl/*_pkg.sv + "$LR_SYNTH_SRC_DIR"/../csrng/rtl/*_pkg.sv "$LR_SYNTH_SRC_DIR"/../entropy_src/rtl/*_pkg.sv "$LR_SYNTH_SRC_DIR"/../lc_ctrl/rtl/*_pkg.sv "$LR_SYNTH_SRC_DIR"/../tlul/rtl/*_pkg.sv diff --git a/hw/ip/aes/pre_syn/tcl/yosys_run_synth.tcl b/hw/ip/aes/pre_syn/tcl/yosys_run_synth.tcl index 5a9057a7aab7c..2beeb2321161e 100644 --- a/hw/ip/aes/pre_syn/tcl/yosys_run_synth.tcl +++ b/hw/ip/aes/pre_syn/tcl/yosys_run_synth.tcl @@ -81,7 +81,7 @@ if { $lr_synth_flatten } { yosys "flatten" } -yosys "clean" +yosys "clean -purge" yosys "write_verilog $lr_synth_netlist_out" if { $lr_synth_timing_run } { @@ -96,4 +96,3 @@ yosys "check" yosys "log ======== Yosys Stat Report ========" yosys "tee -o $lr_synth_out_dir/reports/area.rpt stat -liberty $lr_synth_cell_library_path" yosys "log ====== End Yosys Stat Report ======" - diff --git a/hw/ip/kmac/pre_syn/syn_yosys.sh b/hw/ip/kmac/pre_syn/syn_yosys.sh index 2fb12936fb6c3..358c50755980d 100755 --- a/hw/ip/kmac/pre_syn/syn_yosys.sh +++ b/hw/ip/kmac/pre_syn/syn_yosys.sh @@ -98,6 +98,7 @@ OT_DEP_SOURCES=( OT_DEP_PACKAGES=( "$LR_SYNTH_SRC_DIR"/../../top_earlgrey/rtl/*_pkg.sv "$LR_SYNTH_SRC_DIR"/../edn/rtl/*_pkg.sv + "$LR_SYNTH_SRC_DIR"/../csrng/rtl/*_pkg.sv "$LR_SYNTH_SRC_DIR"/../entropy_src/rtl/*_pkg.sv "$LR_SYNTH_SRC_DIR"/../lc_ctrl/rtl/*_pkg.sv "$LR_SYNTH_SRC_DIR"/../tlul/rtl/*_pkg.sv diff --git a/hw/ip/kmac/pre_syn/tcl/yosys_run_synth.tcl b/hw/ip/kmac/pre_syn/tcl/yosys_run_synth.tcl index ad57cb4b84cd5..591f76d98385f 100644 --- a/hw/ip/kmac/pre_syn/tcl/yosys_run_synth.tcl +++ b/hw/ip/kmac/pre_syn/tcl/yosys_run_synth.tcl @@ -64,7 +64,7 @@ if { $lr_synth_flatten } { yosys "flatten" } -yosys "clean" +yosys "clean -purge" yosys "write_verilog $lr_synth_netlist_out" if { $lr_synth_timing_run } { @@ -79,4 +79,3 @@ yosys "check" yosys "log ======== Yosys Stat Report ========" yosys "tee -o $lr_synth_out_dir/reports/area.rpt stat -liberty $lr_synth_cell_library_path" yosys "log ====== End Yosys Stat Report ======" - diff --git a/hw/ip/otbn/README.md b/hw/ip/otbn/README.md index 32326190c7deb..027948cf85507 100644 --- a/hw/ip/otbn/README.md +++ b/hw/ip/otbn/README.md @@ -103,10 +103,11 @@ Control and Status Registers (CSRs) are 32b wide registers used for "special" pu they are not related to the GPRs. CSRs can be accessed through dedicated instructions, {{#otbn-insn-ref CSRRS}} and {{#otbn-insn-ref CSRRW}}. Writes to read-only (RO) registers are ignored; they do not signal an error. -All read-write (RW) CSRs are set to 0 when OTBN starts an operation (when 1 is written to [`CMD.start`](data/otbn.hjson#cmd)). +All read-write (RW) CSRs are set to 0 when OTBN starts an operation (when 1 is written to [`CMD.start`](doc/registers.md#cmd)). + @@ -124,16 +125,16 @@ All read-write (RW) CSRs are set to 0 when OTBN starts an operation (when 1 is w @@ -145,16 +146,16 @@ All read-write (RW) CSRs are set to 0 when OTBN starts an operation (when 1 is w @@ -165,21 +166,21 @@ All read-write (RW) CSRs are set to 0 when OTBN starts an operation (when 1 is w @@ -270,11 +271,11 @@ All read-write (RW) CSRs are set to 0 when OTBN starts an operation (when 1 is w @@ -282,17 +283,19 @@ Reads when the cache is empty will cause OTBN to be stalled until a new random n
Wide arithmetic flag group 0. This CSR provides access to flag group 0 used by wide integer arithmetic. - FLAGS, FG0 and FG1 provide different views on the same underlying bits. + *FLAGS*, *FG0* and *FG1* provide different views on the same underlying bits. - - - - + + + +
BitDescription
0Carry of Flag Group 0
1MSb of Flag Group 0
2LSb of Flag Group 0
3Zero of Flag Group 0
0Carry of flag group 0
1MSb of flag group 0
2LSb of flag group 0
3Zero of flag group 0
Wide arithmetic flag group 1. This CSR provides access to flag group 1 used by wide integer arithmetic. - FLAGS, FG0 and FG1 provide different views on the same underlying bits. + *FLAGS*, *FG0* and *FG1* provide different views on the same underlying bits. - - - - + + + +
BitDescription
0Carry of Flag Group 1
1MSb of Flag Group 1
2LSb of Flag Group 1
3Zero of Flag Group 1
0Carry of flag group 1
1MSb of flag group 1
2LSb of flag group 1
3Zero of flag group 1
FLAGS Wide arithmetic flag groups. - This CSR provides access to both flags groups used by wide integer arithmetic. - FLAGS, FG0 and FG1 provide different views on the same underlying bits. + This CSR provides access to both flag groups used by wide integer arithmetic. + *FLAGS*, *FG0* and *FG1* provide different views on the same underlying bits. - - - - - - - - + + + + + + + +
BitDescription
0Carry of Flag Group 0
1MSb of Flag Group 0
2LSb of Flag Group 0
3Zero of Flag Group 0
4Carry of Flag Group 1
5MSb of Flag Group 1
6LSb of Flag Group 1
7Zero of Flag Group 1
0Carry of flag group 0
1MSb of flag group 0
2LSb of flag group 0
3Zero of flag group 0
4Carry of flag group 1
5MSb of flag group 1
6LSb of flag group 1
7Zero of flag group 1
RO RND -An AIS31-compliant class PTG.3 random number with guaranteed entropy and forward and backward secrecy. -Primarily intended to be used for key generation. - -The number is sourced from the EDN via a single-entry cache. -Reads when the cache is empty will cause OTBN to be stalled until a new random number is fetched from the EDN. + An AIS31-compliant class PTG.3 random number with guaranteed entropy and forward and backward secrecy. + Primarily intended to be used for key generation. +
+ The number is sourced from the EDN via a single-entry cache. + Reads when the cache is empty will cause OTBN to be stalled until a new random number is fetched from the EDN.
RO URND -A random number without guaranteed secrecy properties or specific statistical properties. -Intended for use in masking and blinding schemes. -Use RND for high-quality randomness. - -The number is sourced from an local PRNG. -Reads never stall. + A random number without guaranteed secrecy properties or specific statistical properties. + Intended for use in masking and blinding schemes. + Use RND for high-quality randomness. +
+ The number is sourced from an local PRNG. + Reads never stall.
+ + ### Wide Data Registers (WDRs) In addition to the 32b wide GPRs, OTBN has a second "wide" register file, which is used by the big number instruction subset. @@ -316,10 +319,11 @@ OTBN has 256b Wide Special purpose Registers (WSRs). These are analogous to the 32b CSRs, but are used by big number instructions. They can be accessed with the {{#otbn-insn-ref BN.WSRR}} and {{#otbn-insn-ref BN.WSRW}} instructions. Writes to read-only (RO) registers are ignored; they do not signal an error. -All read-write (RW) WSRs are set to 0 when OTBN starts an operation (when 1 is written to [`CMD.start`](data/otbn.hjson#cmd)). +All read-write (RW) WSRs are set to 0 when OTBN starts an operation (when 1 is written to [`CMD.start`](doc/registers.md#cmd)). + @@ -333,43 +337,41 @@ All read-write (RW) WSRs are set to 0 when OTBN starts an operation (when 1 is w - - + + - + - + - + @@ -379,9 +381,9 @@ Reads never stall. @@ -389,10 +391,10 @@ A `KEY_INVALID` software error is raised on read if the Key Manager has not prov @@ -400,9 +402,9 @@ A `KEY_INVALID` software error is raised on read if the Key Manager has not prov @@ -410,15 +412,17 @@ A `KEY_INVALID` software error is raised on read if the Key Manager has not prov
0x0 RWMOD - -The modulus used by the {{#otbn-insn-ref BN.ADDM}} and {{#otbn-insn-ref BN.SUBM}} instructions. -This WSR is also visible as CSRs `MOD0` through to `MOD7`. - -MOD + The modulus used by the {{#otbn-insn-ref BN.ADDM}} and {{#otbn-insn-ref BN.SUBM}} instructions. + This WSR is also visible as CSRs `MOD0` through to `MOD7`. +
0x1 RORNDRND -An AIS31-compliant class PTG.3 random number with guaranteed entropy and forward and backward secrecy. -Primarily intended to be used for key generation. - -The number is sourced from the EDN via a single-entry cache. -Reads when the cache is empty will cause OTBN to be stalled until a new random number is fetched from the EDN. + An AIS31-compliant class PTG.3 random number with guaranteed entropy and forward and backward secrecy. + Primarily intended to be used for key generation. +
+ The number is sourced from the EDN via a single-entry cache. + Reads when the cache is empty will cause OTBN to be stalled until a new random number is fetched from the EDN.
0x2 ROURNDURND -A random number without guaranteed secrecy properties or specific statistical properties. -Intended for use in masking and blinding schemes. -Use RND for high-quality randomness. - -The number is sourced from a local PRNG. -Reads never stall. + A random number without guaranteed secrecy properties or specific statistical properties. + Intended for use in masking and blinding schemes. + Use RND for high-quality randomness. +
+ The number is sourced from an local PRNG. + Reads never stall.
0x3 RWACCACC The accumulator register used by the {{#otbn-insn-ref BN.MULQACC}} instruction. RO KEY_S0_L -Bits [255:0] of share 0 of the 384b OTBN sideload key provided by the [Key Manager](../keymgr/README.md). - -A `KEY_INVALID` software error is raised on read if the Key Manager has not provided a key. + Bits [255:0] of share 0 of the 384b OTBN sideload key provided by the [Key Manager](../keymgr/README.md). +
+ A `KEY_INVALID` software error is raised on read if the Key Manager has not provided a valid key.
RO KEY_S0_H -Bits [255:128] of this register are always zero. -Bits [127:0] contain bits [383:256] of share 0 of the 384b OTBN sideload key provided by the [Key Manager](../keymgr/README.md). - -A `KEY_INVALID` software error is raised on read if the Key Manager has not provided a valid key. + Bits [255:128] of this register are always zero. + Bits [127:0] contain bits [383:256] of share 0 of the 384b OTBN sideload key provided by the [Key Manager](../keymgr/README.md). +
+ A `KEY_INVALID` software error is raised on read if the Key Manager has not provided a valid key.
RO KEY_S1_L -Bits [255:0] of share 1 of the 384b OTBN sideload key provided by the [Key Manager](../keymgr/README.md). - -A `KEY_INVALID` software error is raised on read if the Key Manager has not provided a valid key. + Bits [255:0] of share 1 of the 384b OTBN sideload key provided by the [Key Manager](../keymgr/README.md). +
+ A `KEY_INVALID` software error is raised on read if the Key Manager has not provided a valid key.
RO KEY_S1_H -Bits [255:128] of this register are always zero. -Bits [127:0] contain bits [383:256] of share 1 of the 384b OTBN sideload key provided by the [Key Manager](../keymgr/README.md). - -A `KEY_INVALID` software error is raised on read if the Key Manager has not provided a valid key. + Bits [255:128] of this register are always zero. + Bits [127:0] contain bits [383:256] of share 1 of the 384b OTBN sideload key provided by the [Key Manager](../keymgr/README.md). +
+ A `KEY_INVALID` software error is raised on read if the Key Manager has not provided a valid key.
+ + ### Flags In addition to the wide register file, OTBN maintains global state in two groups of flags for the use by wide integer operations. @@ -485,9 +489,9 @@ Refer to the [Secure Wipe](./doc/theory_of_operation.md#secure-wipe) section for ## Instruction Counter -In order to detect and mitigate fault injection attacks on the OTBN, the host CPU can read the number of executed instructions from [`INSN_CNT`](data/otbn.hjson#insn_cnt) and verify whether it matches the expectation. +In order to detect and mitigate fault injection attacks on the OTBN, the host CPU can read the number of executed instructions from [`INSN_CNT`](doc/registers.md#insn_cnt) and verify whether it matches the expectation. The host CPU can clear the instruction counter when OTBN is not running. -Writing any value to [`INSN_CNT`](data/otbn.hjson#insn_cnt) clears this register to zero. +Writing any value to [`INSN_CNT`](doc/registers.md#insn_cnt) clears this register to zero. Write attempts while OTBN is running are ignored. ## Key Sideloading diff --git a/hw/ip/otbn/data/base-insns.yml b/hw/ip/otbn/data/base-insns.yml index a369733dd32d0..c3d40e2f11201 100644 --- a/hw/ip/otbn/data/base-insns.yml +++ b/hw/ip/otbn/data/base-insns.yml @@ -418,6 +418,7 @@ - mnemonic: csrrs rv32i: true + uses_isr: true synopsis: Atomic Read and Set bits in CSR operands: [grd, csr, grs1] doc: | @@ -481,6 +482,7 @@ - mnemonic: csrrw rv32i: true + uses_isr: true synopsis: Atomic Read/Write CSR operands: [grd, csr, grs1] doc: | diff --git a/hw/ip/otbn/data/csr.yml b/hw/ip/otbn/data/csr.yml new file mode 100644 index 0000000000000..b69ea323c2b7a --- /dev/null +++ b/hw/ip/otbn/data/csr.yml @@ -0,0 +1,118 @@ +# Copyright lowRISC contributors. +# Licensed under the Apache License, Version 2.0, see LICENSE for details. +# SPDX-License-Identifier: Apache-2.0 + +- name: fg0 + address: 0x7c0 + doc: | + Wide arithmetic flag group 0. + This CSR provides access to flag group 0 used by wide integer arithmetic. + *FLAGS*, *FG0* and *FG1* provide different views on the same underlying bits. + bits: + 0: Carry of flag group 0 + 1: MSb of flag group 0 + 2: LSb of flag group 0 + 3: Zero of flag group 0 + +- name: fg1 + address: 0x7c1 + doc: | + Wide arithmetic flag group 1. + This CSR provides access to flag group 1 used by wide integer arithmetic. + *FLAGS*, *FG0* and *FG1* provide different views on the same underlying bits. + bits: + 0: Carry of flag group 1 + 1: MSb of flag group 1 + 2: LSb of flag group 1 + 3: Zero of flag group 1 + +- name: flags + address: 0x7c8 + doc: | + Wide arithmetic flag groups. + This CSR provides access to both flag groups used by wide integer arithmetic. + *FLAGS*, *FG0* and *FG1* provide different views on the same underlying bits. + bits: + 0: Carry of flag group 0 + 1: MSb of flag group 0 + 2: LSb of flag group 0 + 3: Zero of flag group 0 + 4: Carry of flag group 1 + 5: MSb of flag group 1 + 6: LSb of flag group 1 + 7: Zero of flag group 1 + +- name: mod0 + address: 0x7d0 + doc: | + Bits [31:0] of the modulus operand, used in the {{#otbn-insn-ref BN.ADDM}}/{{#otbn-insn-ref BN.SUBM}} instructions. + This CSR is mapped to the MOD WSR. + +- name: mod1 + address: 0x7d1 + doc: | + Bits [63:32] of the modulus operand, used in the {{#otbn-insn-ref BN.ADDM}}/{{#otbn-insn-ref BN.SUBM}} instructions. + This CSR is mapped to the MOD WSR. + +- name: mod2 + address: 0x7d2 + doc: | + Bits [95:64] of the modulus operand, used in the {{#otbn-insn-ref BN.ADDM}}/{{#otbn-insn-ref BN.SUBM}} instructions. + This CSR is mapped to the MOD WSR. + +- name: mod3 + address: 0x7d3 + doc: | + Bits [127:96] of the modulus operand, used in the {{#otbn-insn-ref BN.ADDM}}/{{#otbn-insn-ref BN.SUBM}} instructions. + This CSR is mapped to the MOD WSR. + +- name: mod4 + address: 0x7d4 + doc: | + Bits [159:128] of the modulus operand, used in the {{#otbn-insn-ref BN.ADDM}}/{{#otbn-insn-ref BN.SUBM}} instructions. + This CSR is mapped to the MOD WSR. + +- name: mod5 + address: 0x7d5 + doc: | + Bits [191:160] of the modulus operand, used in the {{#otbn-insn-ref BN.ADDM}}/{{#otbn-insn-ref BN.SUBM}} instructions. + This CSR is mapped to the MOD WSR. + +- name: mod6 + address: 0x7d6 + doc: | + Bits [223:192] of the modulus operand, used in the {{#otbn-insn-ref BN.ADDM}}/{{#otbn-insn-ref BN.SUBM}} instructions. + This CSR is mapped to the MOD WSR. + +- name: mod7 + address: 0x7d7 + doc: | + Bits [255:224] of the modulus operand, used in the {{#otbn-insn-ref BN.ADDM}}/{{#otbn-insn-ref BN.SUBM}} instructions. + This CSR is mapped to the MOD WSR. + +- name: rnd_prefetch + address: 0x7d8 + doc: | + Write to this CSR to begin a request to fill the RND cache. + Always reads as 0. + +- name: rnd + address: 0xfc0 + read-only: true + doc: | + An AIS31-compliant class PTG.3 random number with guaranteed entropy and forward and backward secrecy. + Primarily intended to be used for key generation. + + The number is sourced from the EDN via a single-entry cache. + Reads when the cache is empty will cause OTBN to be stalled until a new random number is fetched from the EDN. + +- name: urnd + address: 0xfc1 + read-only: true + doc: | + A random number without guaranteed secrecy properties or specific statistical properties. + Intended for use in masking and blinding schemes. + Use RND for high-quality randomness. + + The number is sourced from an local PRNG. + Reads never stall. diff --git a/hw/ip/otbn/data/insns.yml b/hw/ip/otbn/data/insns.yml index 7e2d15f139cce..93ca7ca973725 100644 --- a/hw/ip/otbn/data/insns.yml +++ b/hw/ip/otbn/data/insns.yml @@ -34,6 +34,11 @@ encoding-schemes: enc-schemes.yml # rv32i: A boolean. If true, this instruction came from the RV32I ISA. # Optional, defaults to false. # +# uses_isr: A boolean. If this is true, the instruction uses an ISR, which +# might be specified by name. In this case, we need the tooling to +# understand the instruction, even if it actually came from the +# RV32I ISA. Optional, defaults to false. +# # synopsis: A longer name for this instruction. If set, used as a subtitle in # the generated documentation. (optional) # diff --git a/hw/ip/otbn/data/wsr.yml b/hw/ip/otbn/data/wsr.yml new file mode 100644 index 0000000000000..572acda6f23cb --- /dev/null +++ b/hw/ip/otbn/data/wsr.yml @@ -0,0 +1,69 @@ +# Copyright lowRISC contributors. +# Licensed under the Apache License, Version 2.0, see LICENSE for details. +# SPDX-License-Identifier: Apache-2.0 + +- name: mod + address: 0 + doc: | + The modulus used by the {{#otbn-insn-ref BN.ADDM}} and {{#otbn-insn-ref BN.SUBM}} instructions. + This WSR is also visible as CSRs `MOD0` through to `MOD7`. + +- name: rnd + address: 1 + read-only: true + doc: | + An AIS31-compliant class PTG.3 random number with guaranteed entropy and forward and backward secrecy. + Primarily intended to be used for key generation. + + The number is sourced from the EDN via a single-entry cache. + Reads when the cache is empty will cause OTBN to be stalled until a new random number is fetched from the EDN. + +- name: urnd + address: 2 + read-only: true + doc: | + A random number without guaranteed secrecy properties or specific statistical properties. + Intended for use in masking and blinding schemes. + Use RND for high-quality randomness. + + The number is sourced from an local PRNG. + Reads never stall. + +- name: acc + address: 3 + doc: | + The accumulator register used by the {{#otbn-insn-ref BN.MULQACC}} instruction. + +- name: key_s0_l + address: 4 + read-only: true + doc: | + Bits [255:0] of share 0 of the 384b OTBN sideload key provided by the [Key Manager](../keymgr/README.md). + + A `KEY_INVALID` software error is raised on read if the Key Manager has not provided a valid key. + +- name: key_s0_h + address: 5 + read-only: true + doc: | + Bits [255:128] of this register are always zero. + Bits [127:0] contain bits [383:256] of share 0 of the 384b OTBN sideload key provided by the [Key Manager](../keymgr/README.md). + + A `KEY_INVALID` software error is raised on read if the Key Manager has not provided a valid key. + +- name: key_s1_l + address: 6 + read-only: true + doc: | + Bits [255:0] of share 1 of the 384b OTBN sideload key provided by the [Key Manager](../keymgr/README.md). + + A `KEY_INVALID` software error is raised on read if the Key Manager has not provided a valid key. + +- name: key_s1_h + address: 7 + read-only: true + doc: | + Bits [255:128] of this register are always zero. + Bits [127:0] contain bits [383:256] of share 1 of the 384b OTBN sideload key provided by the [Key Manager](../keymgr/README.md). + + A `KEY_INVALID` software error is raised on read if the Key Manager has not provided a valid key. diff --git a/hw/ip/otbn/doc/developing_otbn.md b/hw/ip/otbn/doc/developing_otbn.md index dde30d38c85f6..be5df32c52b42 100644 --- a/hw/ip/otbn/doc/developing_otbn.md +++ b/hw/ip/otbn/doc/developing_otbn.md @@ -23,7 +23,7 @@ For more details about the toolchain, see the [user guide](../../../../doc/contributing/sw/otbn_sw.md)). `otbn_as.py` and `otbn_ld.py` can be used to build .elf files for use with -simulations. They work work similarly to binutils programs they wrap. +simulations. They work similarly to binutils programs they wrap. ``` hw/ip/otbn/util/otbn_as.py -o prog_bin/prog.o prog.s diff --git a/hw/ip/otbn/doc/interfaces.md b/hw/ip/otbn/doc/interfaces.md new file mode 100644 index 0000000000000..4198dad274587 --- /dev/null +++ b/hw/ip/otbn/doc/interfaces.md @@ -0,0 +1,97 @@ +# Hardware Interfaces + + +Referring to the [Comportable guideline for peripheral device functionality](https://opentitan.org/book/doc/contributing/hw/comportability), the module **`otbn`** has the following hardware interfaces defined +- Primary Clock: **`clk_i`** +- Other Clocks: **`clk_edn_i`**, **`clk_otp_i`** +- Bus Device Interfaces (TL-UL): **`tl`** +- Bus Host Interfaces (TL-UL): *none* +- Peripheral Pins for Chip IO: *none* + +## [Inter-Module Signals](https://opentitan.org/book/doc/contributing/hw/comportability/index.html#inter-signal-handling) + +| Port Name | Package::Struct | Type | Act | Width | Description | +|:---------------|:----------------------------|:--------|:------|--------:|:--------------| +| otbn_otp_key | otp_ctrl_pkg::otbn_otp_key | req_rsp | req | 1 | | +| edn_rnd | edn_pkg::edn | req_rsp | req | 1 | | +| edn_urnd | edn_pkg::edn | req_rsp | req | 1 | | +| idle | prim_mubi_pkg::mubi4 | uni | req | 1 | | +| ram_cfg | prim_ram_1p_pkg::ram_1p_cfg | uni | rcv | 1 | | +| lc_escalate_en | lc_ctrl_pkg::lc_tx | uni | rcv | 1 | | +| lc_rma_req | lc_ctrl_pkg::lc_tx | uni | rcv | 1 | | +| lc_rma_ack | lc_ctrl_pkg::lc_tx | uni | req | 1 | | +| keymgr_key | keymgr_pkg::otbn_key_req | uni | rcv | 1 | | +| tl | tlul_pkg::tl | req_rsp | rsp | 1 | | + +## Interrupts + +| Interrupt Name | Type | Description | +|:-----------------|:-------|:----------------------------------| +| done | Event | OTBN has completed the operation. | + +## Security Alerts + +| Alert Name | Description | +|:-------------|:-----------------------------------------------------------------------------------------| +| fatal | A fatal error. Fatal alerts are non-recoverable and will be asserted until a hard reset. | +| recov | A recoverable error. Just sent once (as the processor stops). | + +## Security Countermeasures + +| Countermeasure ID | Description | +|:-----------------------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| OTBN.MEM.SCRAMBLE | Both the imem and dmem are scrambled by using prim_ram_1p_scr. | +| OTBN.DATA.MEM.INTEGRITY | Dmem is protected with ECC integrity. This is carried through to OTBN's register file. | +| OTBN.INSTRUCTION.MEM.INTEGRITY | Imem is protected with ECC integrity. This is carried through into OTBN's execute stage. | +| OTBN.BUS.INTEGRITY | End-to-end bus integrity scheme. | +| OTBN.CONTROLLER.FSM.GLOBAL_ESC | The controller FSM moves to a terminal error state upon global escalation. | +| OTBN.CONTROLLER.FSM.LOCAL_ESC | The controller FSM moves to a terminal error state upon local escalation. Can be triggered by CONTROLLER.FSM.SPARSE, SCRAMBLE_CTRL.FSM.SPARSE, and START_STOP_CTRL.FSM.SPARSE. | +| OTBN.CONTROLLER.FSM.SPARSE | The controller FSM uses a sparse state encoding. | +| OTBN.SCRAMBLE.KEY.SIDELOAD | The scrambling key is sideloaded from OTP and thus unreadable by SW. | +| OTBN.SCRAMBLE_CTRL.FSM.LOCAL_ESC | The scramble control FSM moves to a terminal error state upon local escalation. Can be triggered by SCRAMBLE_CTRL.FSM.SPARSE. | +| OTBN.SCRAMBLE_CTRL.FSM.SPARSE | The scramble control FSM uses a sparse state encoding. | +| OTBN.START_STOP_CTRL.FSM.GLOBAL_ESC | The start-stop control FSM moves to a terminal error state upon global escalation. | +| OTBN.START_STOP_CTRL.FSM.LOCAL_ESC | The start-stop control FSM moves to a terminal error state upon local escalation. Can be triggered by START_STOP_CTRL.FSM.SPARSE. | +| OTBN.START_STOP_CTRL.FSM.SPARSE | The start-stop control FSM uses a sparse state encoding. | +| OTBN.DATA_REG_SW.SCA | Blanking of bignum data paths when unused by the executing instruction. | +| OTBN.CTRL.REDUN | Check pre-decoded control matches separately decoded control from main decoder. This includes control signals used for blanking, pushing/popping the call stack, controlling loop and branch/jump instructions, as well as the actual branch target. | +| OTBN.PC.CTRL_FLOW.REDUN | Check prefetch stage PC and execute stage PC match. The prefetch stage and execute stage store their PC's separately and have separate increment calculations. | +| OTBN.RND.BUS.CONSISTENCY | Comparison on successive bus values received over the EDN RND interface. | +| OTBN.RND.RNG.DIGEST | Checking that the random numbers received over the EDN RND interface have not been generated from entropy that failed the FIPS health checks in the entropy source. | +| OTBN.RF_BASE.DATA_REG_SW.INTEGRITY | Register file is protected with ECC integrity. | +| OTBN.RF_BASE.DATA_REG_SW.GLITCH_DETECT | This countermeasure checks for spurious write-enable signals on the register file by monitoring the one-hot0 property of the individual write-enable strobes. | +| OTBN.STACK_WR_PTR.CTR.REDUN | The write pointer of the stack (used for calls and loops) is redundant. If the two instances of the counter mismatch, an error is emitted. | +| OTBN.RF_BIGNUM.DATA_REG_SW.INTEGRITY | Register file is protected with ECC integrity. | +| OTBN.RF_BIGNUM.DATA_REG_SW.GLITCH_DETECT | This countermeasure checks for spurious write-enable signals on the register file by monitoring the one-hot0 property of the individual write-enable strobes. | +| OTBN.LOOP_STACK.CTR.REDUN | The iteration counter of each entry in the loop step uses cross counts via prim_count. | +| OTBN.LOOP_STACK.ADDR.INTEGRITY | Loop start and end address on the loop stack are protected with ECC integrity. | +| OTBN.CALL_STACK.ADDR.INTEGRITY | Call stack entries are protected with ECC integrity. | +| OTBN.START_STOP_CTRL.STATE.CONSISTENCY | The secure wipe handshake between otbn_controller and otbn_start_stop_control uses a level-based req/ack interface. At the otbn_controller end, there is a check for unexpected acks. In otbn_start_stop_control, there is a check for secure wipe requests when we aren't in a state that allows it, and also a check for if the request drops at an unexpected time. | +| OTBN.DATA.MEM.SEC_WIPE | Rotate the scrambling key, effectively wiping the dmem. Initiated on command, upon fatal errors and before RMA entry. | +| OTBN.INSTRUCTION.MEM.SEC_WIPE | Rotate the scrambling key, effectively wiping the imem. Initiated on command, upon fatal errors and before RMA entry. | +| OTBN.DATA_REG_SW.SEC_WIPE | Securely wipe programmer visible OTBN register (GPRs, WDRs, CSRs, WSRs) state with random data. Initiated after reset, at the end of any OTBN operation, upon recoverable and fatal errors, and before RMA entry. | +| OTBN.WRITE.MEM.INTEGRITY | A software visible checksum is calculated for all dmem and imem writes | +| OTBN.CTRL_FLOW.COUNT | A software visible count of instructions executed | +| OTBN.CTRL_FLOW.SCA | OTBN architecture does not have any data dependent timing behaviour | +| OTBN.DATA.MEM.SW_NOACCESS | A portion of DMEM is invisible to CPU software | +| OTBN.KEY.SIDELOAD | Keys can be sideloaded without exposing them to the CPU | +| OTBN.TLUL_FIFO.CTR.REDUN | The TL-UL response FIFO pointers are implemented with duplicate counters. | + + + + +## Hardware Interface Requirements + +OTBN connects to other components in an OpenTitan system. +This section lists requirements on those interfaces that go beyond the physical connectivity. + +### Entropy Distribution Network (EDN) + +OTBN has two EDN connections: `edn_urnd` and `edn_rnd`. +What kind of randomness is provided on the EDN connections is configurable at runtime, but unknown to OTBN. +To maintain its security properties, OTBN requires the following configuration for the two EDN connections: + +* OTBN has no specific requirements on the randomness drawn from `edn_urnd`. + For performance reasons, requests on this EDN connection should be answered quickly. +* `edn_rnd` must provide AIS31-compliant class PTG.3 random numbers. + The randomness from this interface is made available through the `RND` WSR and intended to be used for key generation. diff --git a/hw/ip/otbn/doc/programmers_guide.md b/hw/ip/otbn/doc/programmers_guide.md index ddd8db81e2434..2b250194c39ff 100644 --- a/hw/ip/otbn/doc/programmers_guide.md +++ b/hw/ip/otbn/doc/programmers_guide.md @@ -10,24 +10,24 @@ The section [Writing OTBN applications](#writing-otbn-applications) describes ho The high-level sequence by which the host processor should use OTBN is as follows. -1. Optional: Initialise [`LOAD_CHECKSUM`](../data/otbn.hjson#load_checksum). -1. Write the OTBN application binary to [`IMEM`](../data/otbn.hjson#imem), starting at address 0. -1. Optional: Write constants and input arguments, as mandated by the calling convention of the loaded application, to the half of DMEM accessible through the [`DMEM`](../data/otbn.hjson#dmem) window. -1. Optional: Read back [`LOAD_CHECKSUM`](../data/otbn.hjson#load_checksum) and perform an integrity check. +1. Optional: Initialise [`LOAD_CHECKSUM`](registers.md#load_checksum). +1. Write the OTBN application binary to [`IMEM`](registers.md#imem), starting at address 0. +1. Optional: Write constants and input arguments, as mandated by the calling convention of the loaded application, to the half of DMEM accessible through the [`DMEM`](registers.md#dmem) window. +1. Optional: Read back [`LOAD_CHECKSUM`](registers.md#load_checksum) and perform an integrity check. 1. Start the operation on OTBN by [issuing the `EXECUTE` command](./theory_of_operation.md#operations-and-commands). Now neither data nor instruction memory may be accessed from the host CPU. After it has been started the OTBN application runs to completion without further interaction with the host. 1. Wait for the operation to complete (see below). As soon as the OTBN operation has completed the data and instruction memories can be accessed again from the host CPU. -1. Check if the operation was successful by reading the [`ERR_BITS`](../data/otbn.hjson#err_bits) register. -1. Optional: Retrieve results by reading [`DMEM`](../data/otbn.hjson#dmem), as mandated by the calling convention of the loaded application. +1. Check if the operation was successful by reading the [`ERR_BITS`](registers.md#err_bits) register. +1. Optional: Retrieve results by reading [`DMEM`](registers.md#dmem), as mandated by the calling convention of the loaded application. OTBN applications are run to completion. -The host CPU can determine if an application has completed by either polling [`STATUS`](../data/otbn.hjson#status) or listening for an interrupt. +The host CPU can determine if an application has completed by either polling [`STATUS`](registers.md#status) or listening for an interrupt. -* To poll for a completed operation, software should repeatedly read the [`STATUS`](../data/otbn.hjson#status) register. - The operation is complete if [`STATUS`](../data/otbn.hjson#status) is `IDLE` or `LOCKED`, otherwise the operation is in progress. - When [`STATUS`](../data/otbn.hjson#status) has become `LOCKED` a fatal error has occurred and OTBN must be reset to perform further operations. +* To poll for a completed operation, software should repeatedly read the [`STATUS`](registers.md#status) register. + The operation is complete if [`STATUS`](registers.md#status) is `IDLE` or `LOCKED`, otherwise the operation is in progress. + When [`STATUS`](registers.md#status) has become `LOCKED` a fatal error has occurred and OTBN must be reset to perform further operations. * Alternatively, software can listen for the `done` interrupt to determine if the operation has completed. The standard sequence of working with interrupts has to be followed, i.e. the interrupt has to be enabled, an interrupt service routine has to be registered, etc. The [DIF](#device-interface-functions-difs) contains helpers to do so conveniently. @@ -62,21 +62,21 @@ All data passing must be done when OTBN [is idle](./theory_of_operation.md#opera ### Returning from an application -The software running on OTBN signals completion by executing the {{#otbn-insn-ref ECALL}} instruction. +The software running on OTBN signals completion by executing the [`ECALL`](isa.md#ecall) instruction. -Once OTBN has executed the {{#otbn-insn-ref ECALL}} instruction, the following things happen: +Once OTBN has executed the [`ECALL`](isa.md#ecall) instruction, the following things happen: - No more instructions are fetched or executed. - A [secure wipe of internal state](./theory_of_operation.md#internal-state-secure-wipe) is performed. -- The [`ERR_BITS`](../data/otbn.hjson#err_bits) register is set to 0, indicating a successful operation. -- The current operation is marked as complete by setting [`INTR_STATE.done`](../data/otbn.hjson#intr_state) and clearing [`STATUS`](../data/otbn.hjson#status). +- The [`ERR_BITS`](registers.md#err_bits) register is set to 0, indicating a successful operation. +- The current operation is marked as complete by setting [`INTR_STATE.done`](registers.md#intr_state) and clearing [`STATUS`](registers.md#status). The first 2kiB of DMEM can be used to pass data back to the host processor, e.g. a "return value" or an "exit code". Refer to the section [Passing of data between the host CPU and OTBN](#passing-of-data-between-the-host-cpu-and-otbn) for more information. ### Using hardware loops -OTBN provides two hardware loop instructions: {{#otbn-insn-ref LOOP}} and {{#otbn-insn-ref LOOPI}}. +OTBN provides two hardware loop instructions: [`LOOP`](isa.md#loop) and [`LOOPI`](isa.md#loopi) . #### Loop nesting @@ -147,8 +147,8 @@ outer_body: ### Algorithic Examples: Multiplication with BN.MULQACC The big number instruction subset of OTBN generally operates on WLEN bit numbers. -{{#otbn-insn-ref BN.MULQACC}} operates with WLEN/4 bit operands (with a full WLEN accumulator). -This section outlines two techniques to perform larger multiplies by composing multiple {{#otbn-insn-ref BN.MULQACC}} instructions. +[`BN.MULQACC`](isa.md#bnmulqacc) operates with WLEN/4 bit operands (with a full WLEN accumulator). +This section outlines two techniques to perform larger multiplies by composing multiple [`BN.MULQACC`](isa.md#bnmulqacc) instructions. #### Multiplying two WLEN/2 numbers with BN.MULQACC @@ -362,7 +362,3 @@ Code snippets giving examples of 256x256 and 384x384 multiplies can be found in A higher-level driver for the OTBN block is available at `sw/lib/sw/device/runtime/otbn.h`. Another driver for OTBN is part of the silicon creator code at `sw/device/silicon_creator/lib/drivers/otbn.h`. - -## Register Table - -* [Register Table](../data/otbn.hjson#registers) diff --git a/hw/ip/otbn/doc/registers.md b/hw/ip/otbn/doc/registers.md new file mode 100644 index 0000000000000..c244f34597aa0 --- /dev/null +++ b/hw/ip/otbn/doc/registers.md @@ -0,0 +1,331 @@ +# Registers + + +## Summary + +| Name | Offset | Length | Description | +|:-----------------------------------------------|:---------|---------:|:------------------------------------------------| +| otbn.[`INTR_STATE`](#intr_state) | 0x0 | 4 | Interrupt State Register | +| otbn.[`INTR_ENABLE`](#intr_enable) | 0x4 | 4 | Interrupt Enable Register | +| otbn.[`INTR_TEST`](#intr_test) | 0x8 | 4 | Interrupt Test Register | +| otbn.[`ALERT_TEST`](#alert_test) | 0xc | 4 | Alert Test Register | +| otbn.[`CMD`](#cmd) | 0x10 | 4 | Command Register | +| otbn.[`CTRL`](#ctrl) | 0x14 | 4 | Control Register | +| otbn.[`STATUS`](#status) | 0x18 | 4 | Status Register | +| otbn.[`ERR_BITS`](#err_bits) | 0x1c | 4 | Operation Result Register | +| otbn.[`FATAL_ALERT_CAUSE`](#fatal_alert_cause) | 0x20 | 4 | Fatal Alert Cause Register | +| otbn.[`INSN_CNT`](#insn_cnt) | 0x24 | 4 | Instruction Count Register | +| otbn.[`LOAD_CHECKSUM`](#load_checksum) | 0x28 | 4 | A 32-bit CRC checksum of data written to memory | +| otbn.[`IMEM`](#imem) | 0x4000 | 4096 | Instruction Memory Access | +| otbn.[`DMEM`](#dmem) | 0x8000 | 3072 | Data Memory Access | + +## INTR_STATE +Interrupt State Register +- Offset: `0x0` +- Reset default: `0x0` +- Reset mask: `0x1` + +### Fields + +```wavejson +{"reg": [{"name": "done", "bits": 1, "attr": ["rw1c"], "rotate": -90}, {"bits": 31}], "config": {"lanes": 1, "fontsize": 10, "vspace": 80}} +``` + +| Bits | Type | Reset | Name | Description | +|:------:|:------:|:-------:|:-------|:----------------------------------| +| 31:1 | | | | Reserved | +| 0 | rw1c | 0x0 | done | OTBN has completed the operation. | + +## INTR_ENABLE +Interrupt Enable Register +- Offset: `0x4` +- Reset default: `0x0` +- Reset mask: `0x1` + +### Fields + +```wavejson +{"reg": [{"name": "done", "bits": 1, "attr": ["rw"], "rotate": -90}, {"bits": 31}], "config": {"lanes": 1, "fontsize": 10, "vspace": 80}} +``` + +| Bits | Type | Reset | Name | Description | +|:------:|:------:|:-------:|:-------|:---------------------------------------------------------------| +| 31:1 | | | | Reserved | +| 0 | rw | 0x0 | done | Enable interrupt when [`INTR_STATE.done`](#intr_state) is set. | + +## INTR_TEST +Interrupt Test Register +- Offset: `0x8` +- Reset default: `0x0` +- Reset mask: `0x1` + +### Fields + +```wavejson +{"reg": [{"name": "done", "bits": 1, "attr": ["wo"], "rotate": -90}, {"bits": 31}], "config": {"lanes": 1, "fontsize": 10, "vspace": 80}} +``` + +| Bits | Type | Reset | Name | Description | +|:------:|:------:|:-------:|:-------|:--------------------------------------------------------| +| 31:1 | | | | Reserved | +| 0 | wo | 0x0 | done | Write 1 to force [`INTR_STATE.done`](#intr_state) to 1. | + +## ALERT_TEST +Alert Test Register +- Offset: `0xc` +- Reset default: `0x0` +- Reset mask: `0x3` + +### Fields + +```wavejson +{"reg": [{"name": "fatal", "bits": 1, "attr": ["wo"], "rotate": -90}, {"name": "recov", "bits": 1, "attr": ["wo"], "rotate": -90}, {"bits": 30}], "config": {"lanes": 1, "fontsize": 10, "vspace": 80}} +``` + +| Bits | Type | Reset | Name | Description | +|:------:|:------:|:-------:|:-------|:-------------------------------------------------| +| 31:2 | | | | Reserved | +| 1 | wo | 0x0 | recov | Write 1 to trigger one alert event of this kind. | +| 0 | wo | 0x0 | fatal | Write 1 to trigger one alert event of this kind. | + +## CMD +Command Register + +A command initiates an OTBN operation. While performing the operation, +OTBN is busy; the [`STATUS`](#status) register reflects that. + +All operations signal their completion by raising the done +interrupt; alternatively, software may poll the [`STATUS`](#status) register. + +Writes are ignored if OTBN is not idle. +Unrecognized commands are ignored. +- Offset: `0x10` +- Reset default: `0x0` +- Reset mask: `0xff` + +### Fields + +```wavejson +{"reg": [{"name": "cmd", "bits": 8, "attr": ["wo"], "rotate": 0}, {"bits": 24}], "config": {"lanes": 1, "fontsize": 10, "vspace": 80}} +``` + +| Bits | Type | Reset | Name | +|:------:|:------:|:-------:|:-----------------| +| 31:8 | | | Reserved | +| 7:0 | wo | 0x0 | [cmd](#cmd--cmd) | + +### CMD . cmd +The operation to perform. + +| Value | Name | Description | +|:------|:--------------|:------------| +| 0xd8 | EXECUTE | Starts the execution of the program stored in the instruction memory, starting at address zero. | +| 0xc3 | SEC_WIPE_DMEM | Securely removes all contents from the data memory. | +| 0x1e | SEC_WIPE_IMEM | Securely removes all contents from the instruction memory. | + +## CTRL +Control Register +- Offset: `0x14` +- Reset default: `0x0` +- Reset mask: `0x1` + +### Fields + +```wavejson +{"reg": [{"name": "software_errs_fatal", "bits": 1, "attr": ["rw"], "rotate": -90}, {"bits": 31}], "config": {"lanes": 1, "fontsize": 10, "vspace": 210}} +``` + +| Bits | Type | Reset | Name | Description | +|:------:|:------:|:-------:|:--------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| 31:1 | | | | Reserved | +| 0 | rw | 0x0 | software_errs_fatal | Controls the reaction to software errors. When set software errors produce fatal errors, rather than recoverable errors. Writes are ignored if OTBN is not idle. | + +## STATUS +Status Register +- Offset: `0x18` +- Reset default: `0x4` +- Reset mask: `0xff` + +### Fields + +```wavejson +{"reg": [{"name": "status", "bits": 8, "attr": ["ro"], "rotate": 0}, {"bits": 24}], "config": {"lanes": 1, "fontsize": 10, "vspace": 80}} +``` + +| Bits | Type | Reset | Name | +|:------:|:------:|:-------:|:--------------------------| +| 31:8 | | | Reserved | +| 7:0 | ro | 0x4 | [status](#status--status) | + +### STATUS . status +Indicates the current operational state OTBN is in. + +All BUSY values represent an operation started by a write to the +[`CMD`](#cmd) register. + +| Value | Name | Description | +|:------|:-------------------|:------------------------------------------------------| +| 0x00 | IDLE | OTBN is idle: it is not performing any action. | +| 0x01 | BUSY_EXECUTE | OTBN is busy executing software. | +| 0x02 | BUSY_SEC_WIPE_DMEM | OTBN is busy securely wiping the data memory. | +| 0x03 | BUSY_SEC_WIPE_IMEM | OTBN is busy securely wiping the instruction memory. | +| 0x04 | BUSY_SEC_WIPE_INT | OTBN is busy securely wiping the internal state. | +| 0xFF | LOCKED | OTBN is locked as reaction to a fatal error, and must be reset to unlock it again. See also the section "Reaction to Fatal Errors". | + + +## ERR_BITS +Operation Result Register + +Describes the errors detected during an operation. + +Refer to the "List of Errors" section for a detailed description of the +errors. + +The host CPU can clear this register when OTBN is not running, +by writing any value. Write attempts while OTBN is running are ignored. +- Offset: `0x1c` +- Reset default: `0x0` +- Reset mask: `0xff00ff` + +### Fields + +```wavejson +{"reg": [{"name": "bad_data_addr", "bits": 1, "attr": ["rw"], "rotate": -90}, {"name": "bad_insn_addr", "bits": 1, "attr": ["rw"], "rotate": -90}, {"name": "call_stack", "bits": 1, "attr": ["rw"], "rotate": -90}, {"name": "illegal_insn", "bits": 1, "attr": ["rw"], "rotate": -90}, {"name": "loop", "bits": 1, "attr": ["rw"], "rotate": -90}, {"name": "key_invalid", "bits": 1, "attr": ["rw"], "rotate": -90}, {"name": "rnd_rep_chk_fail", "bits": 1, "attr": ["rw"], "rotate": -90}, {"name": "rnd_fips_chk_fail", "bits": 1, "attr": ["rw"], "rotate": -90}, {"bits": 8}, {"name": "imem_intg_violation", "bits": 1, "attr": ["rw"], "rotate": -90}, {"name": "dmem_intg_violation", "bits": 1, "attr": ["rw"], "rotate": -90}, {"name": "reg_intg_violation", "bits": 1, "attr": ["rw"], "rotate": -90}, {"name": "bus_intg_violation", "bits": 1, "attr": ["rw"], "rotate": -90}, {"name": "bad_internal_state", "bits": 1, "attr": ["rw"], "rotate": -90}, {"name": "illegal_bus_access", "bits": 1, "attr": ["rw"], "rotate": -90}, {"name": "lifecycle_escalation", "bits": 1, "attr": ["rw"], "rotate": -90}, {"name": "fatal_software", "bits": 1, "attr": ["rw"], "rotate": -90}, {"bits": 8}], "config": {"lanes": 1, "fontsize": 10, "vspace": 220}} +``` + +| Bits | Type | Reset | Name | Description | +|:------:|:------:|:-------:|:---------------------|:---------------------------------------------| +| 31:24 | | | | Reserved | +| 23 | rw | 0x0 | fatal_software | A `FATAL_SOFTWARE` error was observed. | +| 22 | rw | 0x0 | lifecycle_escalation | A `LIFECYCLE_ESCALATION` error was observed. | +| 21 | rw | 0x0 | illegal_bus_access | An `ILLEGAL_BUS_ACCESS` error was observed. | +| 20 | rw | 0x0 | bad_internal_state | A `BAD_INTERNAL_STATE` error was observed. | +| 19 | rw | 0x0 | bus_intg_violation | A `BUS_INTG_VIOLATION` error was observed. | +| 18 | rw | 0x0 | reg_intg_violation | A `REG_INTG_VIOLATION` error was observed. | +| 17 | rw | 0x0 | dmem_intg_violation | A `DMEM_INTG_VIOLATION` error was observed. | +| 16 | rw | 0x0 | imem_intg_violation | A `IMEM_INTG_VIOLATION` error was observed. | +| 15:8 | | | | Reserved | +| 7 | rw | 0x0 | rnd_fips_chk_fail | An `RND_FIPS_CHK_FAIL` error was observed. | +| 6 | rw | 0x0 | rnd_rep_chk_fail | An `RND_REP_CHK_FAIL` error was observed. | +| 5 | rw | 0x0 | key_invalid | A `KEY_INVALID` error was observed. | +| 4 | rw | 0x0 | loop | A `LOOP` error was observed. | +| 3 | rw | 0x0 | illegal_insn | An `ILLEGAL_INSN` error was observed. | +| 2 | rw | 0x0 | call_stack | A `CALL_STACK` error was observed. | +| 1 | rw | 0x0 | bad_insn_addr | A `BAD_INSN_ADDR` error was observed. | +| 0 | rw | 0x0 | bad_data_addr | A `BAD_DATA_ADDR` error was observed. | + +## FATAL_ALERT_CAUSE +Fatal Alert Cause Register + +Describes any errors that led to a fatal alert. +A fatal error puts OTBN in locked state; the value of this register +does not change until OTBN is reset. + +Refer to the "List of Errors" section for a detailed description of the +errors. +- Offset: `0x20` +- Reset default: `0x0` +- Reset mask: `0xff` + +### Fields + +```wavejson +{"reg": [{"name": "imem_intg_violation", "bits": 1, "attr": ["ro"], "rotate": -90}, {"name": "dmem_intg_violation", "bits": 1, "attr": ["ro"], "rotate": -90}, {"name": "reg_intg_violation", "bits": 1, "attr": ["ro"], "rotate": -90}, {"name": "bus_intg_violation", "bits": 1, "attr": ["ro"], "rotate": -90}, {"name": "bad_internal_state", "bits": 1, "attr": ["ro"], "rotate": -90}, {"name": "illegal_bus_access", "bits": 1, "attr": ["ro"], "rotate": -90}, {"name": "lifecycle_escalation", "bits": 1, "attr": ["ro"], "rotate": -90}, {"name": "fatal_software", "bits": 1, "attr": ["ro"], "rotate": -90}, {"bits": 24}], "config": {"lanes": 1, "fontsize": 10, "vspace": 220}} +``` + +| Bits | Type | Reset | Name | Description | +|:------:|:------:|:-------:|:---------------------|:---------------------------------------------| +| 31:8 | | | | Reserved | +| 7 | ro | 0x0 | fatal_software | A `FATAL_SOFTWARE` error was observed. | +| 6 | ro | 0x0 | lifecycle_escalation | A `LIFECYCLE_ESCALATION` error was observed. | +| 5 | ro | 0x0 | illegal_bus_access | A `ILLEGAL_BUS_ACCESS` error was observed. | +| 4 | ro | 0x0 | bad_internal_state | A `BAD_INTERNAL_STATE` error was observed. | +| 3 | ro | 0x0 | bus_intg_violation | A `BUS_INTG_VIOLATION` error was observed. | +| 2 | ro | 0x0 | reg_intg_violation | A `REG_INTG_VIOLATION` error was observed. | +| 1 | ro | 0x0 | dmem_intg_violation | A `DMEM_INTG_VIOLATION` error was observed. | +| 0 | ro | 0x0 | imem_intg_violation | A `IMEM_INTG_VIOLATION` error was observed. | + +## INSN_CNT +Instruction Count Register + +Returns the number of instructions executed in the current or last +operation. The counter saturates at 2^32-1 and is reset to 0 at the +start of a new operation. + +Only the EXECUTE operation counts instructions; for all other operations +this register remains at 0. Instructions triggering an error do not +count towards the total. + +Always reads as 0 if OTBN is locked. + +The host CPU can clear this register when OTBN is not running, +by writing any value. Write attempts while OTBN is running are ignored. +- Offset: `0x24` +- Reset default: `0x0` +- Reset mask: `0xffffffff` + +### Fields + +```wavejson +{"reg": [{"name": "insn_cnt", "bits": 32, "attr": ["rw"], "rotate": 0}], "config": {"lanes": 1, "fontsize": 10, "vspace": 80}} +``` + +| Bits | Type | Reset | Name | Description | +|:------:|:------:|:-------:|:---------|:-------------------------------------| +| 31:0 | rw | 0x0 | insn_cnt | The number of executed instructions. | + +## LOAD_CHECKSUM +A 32-bit CRC checksum of data written to memory + +See the "Memory Load Integrity" section of the manual for full details. +- Offset: `0x28` +- Reset default: `0x0` +- Reset mask: `0xffffffff` + +### Fields + +```wavejson +{"reg": [{"name": "checksum", "bits": 32, "attr": ["rw"], "rotate": 0}], "config": {"lanes": 1, "fontsize": 10, "vspace": 80}} +``` + +| Bits | Type | Reset | Name | Description | +|:------:|:------:|:-------:|:---------|:---------------------| +| 31:0 | rw | 0x0 | checksum | Checksum accumulator | + +## IMEM +Instruction Memory Access + +The instruction memory may only be accessed through this window +while OTBN is idle. + +If OTBN is busy or locked, read accesses return 0 and write accesses +are ignored. +If OTBN is busy, any access additionally triggers an +ILLEGAL_BUS_ACCESS fatal error. + +- Word Aligned Offset Range: `0x4000`to`0x4ffc` +- Size (words): `1024` +- Access: `rw` +- Byte writes are *not* supported. + +## DMEM +Data Memory Access + +The data memory may only be accessed through this window while OTBN +is idle. + +If OTBN is busy or locked, read accesses return 0 and write accesses +are ignored. +If OTBN is busy, any access additionally triggers an +ILLEGAL_BUS_ACCESS fatal error. + +Note that DMEM is actually 4kiB in size, but only the first 3kiB of +the memory is visible through this register interface. + +- Word Aligned Offset Range: `0x8000`to`0x8bfc` +- Size (words): `768` +- Access: `rw` +- Byte writes are *not* supported. + + + diff --git a/hw/ip/otbn/doc/theory_of_operation.md b/hw/ip/otbn/doc/theory_of_operation.md index cd81c486b8ddb..f36a28bc49e28 100644 --- a/hw/ip/otbn/doc/theory_of_operation.md +++ b/hw/ip/otbn/doc/theory_of_operation.md @@ -4,36 +4,6 @@ ![OTBN architecture block diagram](./otbn_blockarch.svg) -## Hardware Interfaces - -* [Interface Tables](../data/otbn.hjson#interfaces) - -### Hardware Interface Requirements - -OTBN connects to other components in an OpenTitan system. -This section lists requirements on those interfaces that go beyond the physical connectivity. - -#### Entropy Distribution Network (EDN) - -OTBN has two EDN connections: `edn_urnd` and `edn_rnd`. -What kind of randomness is provided on the EDN connections is configurable at runtime, but unknown to OTBN. -To maintain its security properties, OTBN requires the following configuration for the two EDN connections: - -* OTBN has no specific requirements on the randomness drawn from `edn_urnd`. - For performance reasons, requests on this EDN connection should be answered quickly. -* `edn_rnd` must provide AIS31-compliant class PTG.3 random numbers. - The randomness from this interface is made available through the `RND` WSR and intended to be used for key generation. - -#### Life Cycle Controller (LC_CTRL) - -OTBN has three LC_CTRL connections: one for triggering life cycle escalation requests (`lc_escalate_en`) and two for handling RMA entry (`lc_rma_req/ack`). - -As LC_CTRL might sit in a different clock domain and since all these connections are using multi-bit signals, OTBN might observe staggered signal transitions due to the clock domain crossings. -To avoid spurious life cycle escalations and to enable reliable RMA entry, it should be ensured that: - -* The `lc_escalate_en` and `lc_rma_req` inputs are stably driven to `lc_ctrl_pkg::Off` before releasing the reset of OTBN. -* When triggering RMA entry, the `lc_rma_req` input switches from `lc_ctrl_pkg::Off` to `lc_ctrl_pkg::On` exactly once, and then remains `On` until OTBN signals completion of the secure wipe operation with the `lc_rma_ack` output switching to `lc_ctrl_pkg::On`. - ## Design Details ### Memories @@ -54,12 +24,12 @@ These access 32b-aligned 32b words. In the big number instruction subset, there are {{#otbn-insn-ref BN.LID}} (load indirect) and {{#otbn-insn-ref BN.SID}} (store indirect). These access 256b-aligned 256b words. -Both memories can be accessed through OTBN's register interface ([`DMEM`](../data/otbn.hjson#dmem) and [`IMEM`](../data/otbn.hjson#imem)). +Both memories can be accessed through OTBN's register interface ([`DMEM`](registers.md#dmem) and [`IMEM`](registers.md#imem)). All memory accesses through the register interface must be word-aligned 32b word accesses. When OTBN is in any state other than [idle](#operational-states), reads return zero and writes have no effect. Furthermore, a memory access when OTBN is neither idle nor locked will cause OTBN to generate a fatal error with code `ILLEGAL_BUS_ACCESS`. -A host processor can check whether OTBN is busy by reading the [`STATUS`](../data/otbn.hjson#status) register. +A host processor can check whether OTBN is busy by reading the [`STATUS`](registers.md#status) register. The underlying memories used to implement the IMEM and DMEM may not grant all access requests (see [Memory Scrambling](#memory-scrambling) for details). A request won't be granted if new scrambling keys have been requested for the memory that aren't yet available. @@ -129,24 +99,24 @@ After reset (*init*), OTBN performs a secure wipe of the internal state and then OTBN is *busy* for as long it is performing an operation. OTBN is *locked* if a fatal error was observed or after handling an RMA request. -The current operational state is reflected in the [`STATUS`](../data/otbn.hjson#status) register. -- After reset, OTBN is busy with the internal secure wipe and the [`STATUS`](../data/otbn.hjson#status) register is set to `BUSY_SEC_WIPE_INT`. -- If OTBN is idle, the [`STATUS`](../data/otbn.hjson#status) register is set to `IDLE`. -- If OTBN is busy, the [`STATUS`](../data/otbn.hjson#status) register is set to one of the values starting with `BUSY_`. -- If OTBN is locked, the [`STATUS`](../data/otbn.hjson#status) register is set to `LOCKED`. +The current operational state is reflected in the [`STATUS`](registers.md#status) register. +- After reset, OTBN is busy with the internal secure wipe and the [`STATUS`](registers.md#status) register is set to `BUSY_SEC_WIPE_INT`. +- If OTBN is idle, the [`STATUS`](registers.md#status) register is set to `IDLE`. +- If OTBN is busy, the [`STATUS`](registers.md#status) register is set to one of the values starting with `BUSY_`. +- If OTBN is locked, the [`STATUS`](registers.md#status) register is set to `LOCKED`. OTBN transitions into the busy state as result of host software [issuing a command](#operations-and-commands); OTBN is then said to perform an operation. OTBN transitions out of the busy state whenever the operation has completed. -In the [`STATUS`](../data/otbn.hjson#status) register the different `BUSY_*` values represent the operation that is currently being performed. +In the [`STATUS`](registers.md#status) register the different `BUSY_*` values represent the operation that is currently being performed. -A transition out of the busy state is signaled by the `done` interrupt ([`INTR_STATE.done`](../data/otbn.hjson#intr_state)). +A transition out of the busy state is signaled by the `done` interrupt ([`INTR_STATE.done`](registers.md#intr_state)). The locked state is a terminal state; transitioning out of it requires an OTBN reset. ### Operations and Commands OTBN understands a set of commands to perform certain operations. -Commands are issued by writing to the [`CMD`](../data/otbn.hjson#cmd) register. +Commands are issued by writing to the [`CMD`](registers.md#cmd) register. The `EXECUTE` command starts the [execution of the application](#software-execution) contained in OTBN's instruction memory. @@ -159,14 +129,14 @@ The `SEC_WIPE_IMEM` command [securely wipes the instruction memory](#secure-wipe Software execution on OTBN is triggered by host software by [issuing the `EXECUTE` command](#operations-and-commands). The software then runs to completion, without the ability for host software to interrupt or inspect the execution. -- OTBN transitions into the busy state, and reflects this by setting [`STATUS`](../data/otbn.hjson#status) to `BUSY_EXECUTE`. +- OTBN transitions into the busy state, and reflects this by setting [`STATUS`](registers.md#status) to `BUSY_EXECUTE`. - The internal randomness source, which provides random numbers to the `URND` CSR and WSR, is re-seeded from the EDN. - The instruction at address zero is fetched and executed. - From this point on, all subsequent instructions are executed according to their semantics until either an {{#otbn-insn-ref ECALL}} instruction is executed, or an error is detected. - A [secure wipe of internal state](#internal-state-secure-wipe) is performed. -- The [`ERR_BITS`](../data/otbn.hjson#err_bits) register is set to indicate either a successful execution (value `0`), or to indicate the error that was observed (a non-zero value). +- The [`ERR_BITS`](registers.md#err_bits) register is set to indicate either a successful execution (value `0`), or to indicate the error that was observed (a non-zero value). - OTBN transitions into the [idle state](#operational-states) (in case of a successful execution, or a recoverable error) or the locked state (in case of a fatal error). - This transition is signaled by raising the `done` interrupt ([`INTR_STATE.done`](../data/otbn.hjson#intr_state)), and reflected in the [`STATUS`](../data/otbn.hjson#status) register. + This transition is signaled by raising the `done` interrupt ([`INTR_STATE.done`](registers.md#intr_state)), and reflected in the [`STATUS`](registers.md#status) register. ### Errors @@ -180,8 +150,8 @@ Whenever an error is detected, OTBN reacts locally, and informs the OpenTitan sy OTBN generally does not try to recover from errors itself, and provides no error handling support to code that runs on it. OTBN gives host software the option to recover from some errors by restarting the operation. -All software errors are treated as recoverable, unless [`CTRL.software_errs_fatal`](../data/otbn.hjson#ctrl) is set, and are handled as described in the section [Reaction to Recoverable Errors](#reaction-to-recoverable-errors). -When [`CTRL.software_errs_fatal`](../data/otbn.hjson#ctrl) is set, software errors become fatal errors. +All software errors are treated as recoverable, unless [`CTRL.software_errs_fatal`](registers.md#ctrl) is set, and are handled as described in the section [Reaction to Recoverable Errors](#reaction-to-recoverable-errors). +When [`CTRL.software_errs_fatal`](registers.md#ctrl) is set, software errors become fatal errors. Fatal errors are treated as described in the section [Reaction to Fatal Errors](#reaction-to-fatal-errors). @@ -195,9 +165,9 @@ The following actions are taken when OTBN detects a recoverable error: 1. The currently running operation is terminated, similar to the way an {{#otbn-insn-ref ECALL}} instruction [is executed](#returning-from-an-application): - No more instructions are fetched or executed. - A [secure wipe of internal state](#internal-state-secure-wipe) is performed. - - The [`ERR_BITS`](../data/otbn.hjson#err_bits) register is set to a non-zero value that describes the error. - - The current operation is marked as complete by setting [`INTR_STATE.done`](../data/otbn.hjson#intr_state). - - The [`STATUS`](../data/otbn.hjson#status) register is set to `IDLE`. + - The [`ERR_BITS`](registers.md#err_bits) register is set to a non-zero value that describes the error. + - The current operation is marked as complete by setting [`INTR_STATE.done`](registers.md#intr_state). + - The [`STATUS`](registers.md#status) register is set to `IDLE`. 2. A [recoverable alert](#alerts) is raised. The host software can start another operation on OTBN after a recoverable error was detected. @@ -213,17 +183,17 @@ The following actions are taken when OTBN detects a fatal error: 2. If OTBN [is not idle](#operational-states), then the currently running operation is terminated, similarly to how an operation ends after an {{#otbn-insn-ref ECALL}} instruction [is executed](#returning-from-an-application): - No more instructions are fetched or executed. - A [secure wipe of internal state](#internal-state-secure-wipe) is performed. - - The [`ERR_BITS`](../data/otbn.hjson#err_bits) register is set to a non-zero value that describes the error. - - The current operation is marked as complete by setting [`INTR_STATE.done`](../data/otbn.hjson#intr_state). -3. The [`STATUS`](../data/otbn.hjson#status) register is set to `LOCKED`. + - The [`ERR_BITS`](registers.md#err_bits) register is set to a non-zero value that describes the error. + - The current operation is marked as complete by setting [`INTR_STATE.done`](registers.md#intr_state). +3. The [`STATUS`](registers.md#status) register is set to `LOCKED`. 4. A [fatal alert](#alerts) is raised. Note that OTBN can detect some errors even when it isn't running. One example of this is an error caused by an integrity error when reading or writing OTBN's memories over the bus. -In this case, the [`ERR_BITS`](../data/otbn.hjson#err_bits) register will not change. +In this case, the [`ERR_BITS`](registers.md#err_bits) register will not change. This avoids race conditions with the host processor's error handling software. However, every error that OTBN detects when it isn't running is fatal. -This means that the cause will be reflected in [`FATAL_ALERT_CAUSE`](../data/otbn.hjson#fatal_alert_cause), as described below in [Alerts](#alerts). +This means that the cause will be reflected in [`FATAL_ALERT_CAUSE`](registers.md#fatal_alert_cause), as described below in [Alerts](#alerts). This way, no alert is generated without setting an error code somewhere. ### List of Errors @@ -325,7 +295,7 @@ This way, no alert is generated without setting an error code somewhere. FATAL_SOFTWARE fatal - A software error was seen and [`CTRL.software_errs_fatal`](../data/otbn.hjson#ctrl) was set. + A software error was seen and [`CTRL.software_errs_fatal`](registers.md#ctrl) was set. @@ -336,15 +306,15 @@ An alert is a reaction to an error that OTBN detected. OTBN has two alerts, one recoverable and one fatal. A **recoverable alert** is a one-time triggered alert caused by [recoverable errors](#reaction-to-recoverable-errors). -The error that caused the alert can be determined by reading the [`ERR_BITS`](../data/otbn.hjson#err_bits) register. +The error that caused the alert can be determined by reading the [`ERR_BITS`](registers.md#err_bits) register. A **fatal alert** is a continuously triggered alert caused by [fatal errors](#reaction-to-fatal-errors). -The error that caused the alert can be determined by reading the [`FATAL_ALERT_CAUSE`](../data/otbn.hjson#fatal_alert_cause) register. -If OTBN was running, this value will also be reflected in the [`ERR_BITS`](../data/otbn.hjson#err_bits) register. +The error that caused the alert can be determined by reading the [`FATAL_ALERT_CAUSE`](registers.md#fatal_alert_cause) register. +If OTBN was running, this value will also be reflected in the [`ERR_BITS`](registers.md#err_bits) register. A fatal alert can only be cleared by resetting OTBN through the `rst_ni` line. -The host CPU can clear the [`ERR_BITS`](../data/otbn.hjson#err_bits) when OTBN is not running. -Writing any value to [`ERR_BITS`](../data/otbn.hjson#err_bits) clears this register to zero. +The host CPU can clear the [`ERR_BITS`](registers.md#err_bits) when OTBN is not running. +Writing any value to [`ERR_BITS`](registers.md#err_bits) clears this register to zero. Write attempts while OTBN is running are ignored. ### Reaction to Life Cycle Escalation Requests @@ -468,7 +438,7 @@ Detected integrity violations in the data memory raise a fatal `imem_error`. ### Memory Load Integrity As well as the integrity protection discussed above for the memories and bus interface, OTBN has a second layer of integrity checking to allow a host processor to ensure that a program has been loaded correctly. -This is visible through the [`LOAD_CHECKSUM`](../data/otbn.hjson#load_checksum) register. +This is visible through the [`LOAD_CHECKSUM`](registers.md#load_checksum) register. The register exposes a cumulative CRC checksum which is updated on every write to either memory. This is intended as a light-weight way to implement a more efficient "write and read back" check. @@ -477,7 +447,7 @@ However, in this case the attacker would be equally able to control responses fr The CRC used is the 32-bit CRC-32-IEEE checksum. This standard choice of generating polynomial makes it compatible with other tooling and libraries, such as the [crc32 function](https://docs.python.org/3/library/binascii.html#binascii.crc32) in the python 'binascii' module and the crc instructions in the RISC-V bitmanip specification [[SYMBIOTIC21]](#ref-symbiotic21). -The stream over which the checksum is computed is the stream of writes that have been seen since the last write to [`LOAD_CHECKSUM`](../data/otbn.hjson#load_checksum). +The stream over which the checksum is computed is the stream of writes that have been seen since the last write to [`LOAD_CHECKSUM`](registers.md#load_checksum). Each write is treated as a 48b value, `{imem, idx, wdata}`. Here, `imem` is a single bit flag which is one for writes to IMEM and zero for writes to DMEM. The `idx` value is the index of the word within the memory, zero extended from 10b to 15b. @@ -489,9 +459,9 @@ Typically, this will be to clear the value to `32'h00000000`, the traditional st Note the internal representation of the CRC is inverted from the register visible version. This is done to maintain compatibility with existing CRC-32-IEEE tooling and libraries. -To use this functionality, the host processor should set [`LOAD_CHECKSUM`](../data/otbn.hjson#load_checksum) to a known value (traditionally, `32'h00000000`). +To use this functionality, the host processor should set [`LOAD_CHECKSUM`](registers.md#load_checksum) to a known value (traditionally, `32'h00000000`). Next, it should write the program to be loaded to OTBN's IMEM and DMEM over the bus. -Finally, it should read back the value of [`LOAD_CHECKSUM`](../data/otbn.hjson#load_checksum) and compare it with an expected value. +Finally, it should read back the value of [`LOAD_CHECKSUM`](registers.md#load_checksum) and compare it with an expected value. ### Secure Wipe diff --git a/hw/ip/otbn/dv/otbnsim/sim/ext_regs.py b/hw/ip/otbn/dv/otbnsim/sim/ext_regs.py index 3ef13209bd404..7d869ae6ff74f 100644 --- a/hw/ip/otbn/dv/otbnsim/sim/ext_regs.py +++ b/hw/ip/otbn/dv/otbnsim/sim/ext_regs.py @@ -14,7 +14,7 @@ from .trace import Trace -class ExtRegChange(Trace): +class ExtRegChange: def __init__(self, op: str, written: int, from_hw: bool, new_value: int): self.op = op self.written = written @@ -129,8 +129,8 @@ class RGReg: def __init__(self, fields: List[RGField], double_flopped: bool): self.fields = fields self.double_flopped = double_flopped - self._trace = [] # type: List[ExtRegChange] - self._next_trace = [] # type: List[ExtRegChange] + self._changes = [] # type: List[ExtRegChange] + self._next_changes = [] # type: List[ExtRegChange] @staticmethod def from_register(reg: Register, double_flopped: bool) -> 'RGReg': @@ -155,14 +155,14 @@ def write(self, value: int, from_hw: bool) -> None: ''' assert value >= 0 now = self._apply_fields(lambda fld, fv: fld.write(fv, from_hw), value) - trace = self._next_trace if self.double_flopped else self._trace - trace.append(ExtRegChange('=', value, from_hw, now)) + changes = self._next_changes if self.double_flopped else self._changes + changes.append(ExtRegChange('=', value, from_hw, now)) def set_bits(self, value: int) -> None: assert value >= 0 now = self._apply_fields(lambda fld, fv: fld.set_bits(fv), value) - trace = self._next_trace if self.double_flopped else self._trace - trace.append(ExtRegChange('=', value, False, now)) + changes = self._next_changes if self.double_flopped else self._changes + changes.append(ExtRegChange('=', value, False, now)) def read(self, from_hw: bool) -> int: value = 0 @@ -173,17 +173,17 @@ def read(self, from_hw: bool) -> int: def commit(self) -> None: for field in self.fields: field.commit() - self._trace = self._next_trace - self._next_trace = [] + self._changes = self._next_changes + self._next_changes = [] def abort(self) -> None: for field in self.fields: field.abort() - self._trace = [] - self._next_trace = [] + self._changes = [] + self._next_changes = [] def changes(self) -> List[ExtRegChange]: - return self._trace + return self._changes class RndReq(RGReg): @@ -305,7 +305,6 @@ def increment_insn_cnt(self) -> None: assert len(reg.fields) == 1 fld = reg.fields[0] reg.write(min(fld.value + 1, (1 << 32) - 1), True) - self._dirty = 2 def read(self, reg_name: str, from_hw: bool) -> int: reg = self.regs.get(reg_name) @@ -317,8 +316,11 @@ def step(self) -> None: self._rnd_req.step() def changes(self) -> Sequence[Trace]: + # If the dirty flag is not set, we know the only possible change is to + # the INSN_CNT register. if self._dirty == 0: - return [] + return [TraceExtRegChange('INSN_CNT', erc) + for erc in self.regs['INSN_CNT'].changes()] trace = [] for name, reg in self.regs.items(): @@ -326,12 +328,19 @@ def changes(self) -> Sequence[Trace]: return trace def commit(self) -> None: - # We know that we'll only have any pending changes if self._dirty is - # positive, so needn't bother calling commit on each register if not. + # If self._dirty is positive, there might be some pending changes to an + # ext register. In that case, we need to iterate over all of them, + # calling commit() to make the changes land. + # + # If self._dirty is zero, we know there are no pending changes to ext + # registers except possibly INSN_CNT. Writes to *that* don't trigger + # the dirty flag because they happen most cycles. if self._dirty > 0: for reg in self.regs.values(): reg.commit() self._dirty = max(0, self._dirty - 1) + else: + self.regs['INSN_CNT'].commit() def abort(self) -> None: for reg in self.regs.values(): diff --git a/hw/ip/otbn/dv/otbnsim/sim/insn.py b/hw/ip/otbn/dv/otbnsim/sim/insn.py index 0cb097d1c8648..bf6488db0c65b 100644 --- a/hw/ip/otbn/dv/otbnsim/sim/insn.py +++ b/hw/ip/otbn/dv/otbnsim/sim/insn.py @@ -261,7 +261,7 @@ def execute(self, state: OTBNState) -> Optional[Iterator[None]]: result = state.dmem.load_u32(addr) # Stall for a single cycle for memory to respond - yield + yield None if result is None: state.stop_at_end_of_cycle(ErrBits.DMEM_INTG_VIOLATION) @@ -432,7 +432,7 @@ def execute(self, state: OTBNState) -> Optional[Iterator[None]]: # value is available, it returns True. while not state.wsrs.RND.request_value(): # There's a pending EDN request. Stall for a cycle. - yield + yield None # At this point, the CSR is ready. Read, update and write back to grs1. old_val = state.read_csr(self.csr) @@ -468,7 +468,7 @@ def execute(self, state: OTBNState) -> Optional[Iterator[None]]: # value is available, it returns True. while not state.wsrs.RND.request_value(): # There's a pending EDN request. Stall for a cycle. - yield + yield None # At this point, the CSR is either ready or unneeded. Read it if # necessary and write to grd, then overwrite with new_val. @@ -1083,7 +1083,7 @@ def execute(self, state: OTBNState) -> Optional[Iterator[None]]: state.gprs.get_reg(self.grs1).write_unsigned(new_grs1_val) # Stall for a single cycle for memory to respond - yield + yield None if value is None: state.stop_at_end_of_cycle(ErrBits.DMEM_INTG_VIOLATION) @@ -1140,7 +1140,7 @@ def execute(self, state: OTBNState) -> Optional[Iterator[None]]: new_grs2_val = grs2_val + 1 state.gprs.get_reg(self.grs2).write_unsigned(new_grs2_val) - yield + yield None wrs = grs2_val & 0x1f wrs_val = state.wdrs.get_reg(wrs).read_unsigned() @@ -1210,7 +1210,7 @@ def execute(self, state: OTBNState) -> Optional[Iterator[None]]: new_grs_val = grs_val + 1 state.gprs.get_reg(self.grs).write_unsigned(new_grs_val) - yield + yield None value = state.wdrs.get_reg(wrs).read_unsigned() state.wdrs.get_reg(wrd).write_unsigned(value) @@ -1237,7 +1237,7 @@ def execute(self, state: OTBNState) -> Optional[Iterator[None]]: # value is available, it returns True. while not state.wsrs.RND.request_value(): # There's a pending EDN request. Stall for a cycle. - yield + yield None # At this point, the WSR is ready. Does it have a valid value? (It # might not if this is a sideload key register and keymgr hasn't diff --git a/hw/ip/otbn/dv/otbnsim/sim/sim.py b/hw/ip/otbn/dv/otbnsim/sim/sim.py index d62a4d8261c66..8f842849c304c 100644 --- a/hw/ip/otbn/dv/otbnsim/sim/sim.py +++ b/hw/ip/otbn/dv/otbnsim/sim/sim.py @@ -176,8 +176,8 @@ def _step_idle(self, verbose: bool) -> StepRes: if self.state.init_sec_wipe_is_running(): # Wait for the URND seed. Once that appears, switch to WIPING_GOOD - # unless the FSM state is already LOCKED, in which case we change it - # to WIPING_BAD. + # unless the FSM state is already LOCKED, in which case we change + # it to WIPING_BAD. if self.state.wsrs.URND.running: if self.state.get_fsm_state() == FsmState.LOCKED: self.state.set_fsm_state(FsmState.WIPING_BAD) @@ -301,7 +301,8 @@ def _step_wiping(self, verbose: bool) -> StepRes: # Zero INSN_CNT once if we're in state WIPING_BAD. if not is_good and self.state.ext_regs.read('INSN_CNT', True) != 0: - if self.state.zero_insn_cnt_next or not self.state.lock_immediately: + if ((self.state.zero_insn_cnt_next or + not self.state.lock_immediately)): self.state.ext_regs.write('INSN_CNT', 0, True) self.state.zero_insn_cnt_next = False if self.state.lock_immediately: @@ -327,15 +328,16 @@ def _step_wiping(self, verbose: bool) -> StepRes: if self.state.wipe_cycles == 0: # This is the final clock cycle of a wipe round. if self.state.first_round_of_wipe and not self.state.rma_req: - # This is the first wipe round and since there's no RMA request, - # a second round must follow after URND refresh acknowledgment. + # This is the first wipe round and since there's no RMA + # request, a second round must follow after URND refresh + # acknowledgment. if self.state.wsrs.URND.running: self.state.first_round_of_wipe = False self.state.set_fsm_state(self.state.get_fsm_state()) else: - # This is the second wipe round or the only wipe round during an - # RMA request. If the wipe was good, set the next state to IDLE; - # otherwise set it to LOCKED. + # This is the second wipe round or the only wipe round during + # an RMA request. If the wipe was good, set the next state to + # IDLE; otherwise set it to LOCKED. if is_good: assert not self.state.rma_req next_state = FsmState.IDLE @@ -373,7 +375,8 @@ def on_otp_cdc_done(self) -> None: self.state.ext_regs.write('STATUS', Status.IDLE, True) self.state.set_fsm_state(FsmState.IDLE) - def send_err_escalation(self, err_val: int, lock_immediately: bool) -> None: + def send_err_escalation(self, + err_val: int, lock_immediately: bool) -> None: '''React to an error escalation''' assert err_val & ~ErrBits.MASK == 0 self.state.injected_err_bits |= err_val diff --git a/hw/ip/otbn/dv/otbnsim/sim/state.py b/hw/ip/otbn/dv/otbnsim/sim/state.py index f0e21e4850da5..956c81f0b0e58 100644 --- a/hw/ip/otbn/dv/otbnsim/sim/state.py +++ b/hw/ip/otbn/dv/otbnsim/sim/state.py @@ -139,9 +139,9 @@ def __init__(self) -> None: # current fsm_state. self.cycles_in_this_state = 0 - # RMA request changes state to LOCKED, basically an escalation from lifecycle - # controller. Initiates secure wiping through stop_at_end_of_cycle method and - # this flag. + # RMA request changes state to LOCKED, basically an escalation from + # lifecycle controller. Initiates secure wiping through + # stop_at_end_of_cycle method and this flag. self.rma_req = False def get_next_pc(self) -> int: @@ -351,7 +351,8 @@ def stop(self) -> None: # might have updated state (either registers, memory or # externally-visible registers). We want to roll back any of those # changes. - if (self._err_bits and self._fsm_state == FsmState.EXEC) or self.rma_req: + insn_failed = self._err_bits and self._fsm_state == FsmState.EXEC + if insn_failed or self.rma_req: self._abort() # INTR_STATE is the interrupt state register. Bit 0 (which is being @@ -389,7 +390,8 @@ def stop(self) -> None: # Switch to a 'wiping' state self.set_fsm_state(FsmState.WIPING_BAD if should_lock else FsmState.WIPING_GOOD) - elif self._fsm_state in [FsmState.WIPING_BAD, FsmState.WIPING_GOOD]: + elif self._fsm_state in [FsmState.WIPING_BAD, + FsmState.WIPING_GOOD]: assert should_lock self._next_fsm_state = FsmState.WIPING_BAD elif self._init_sec_wipe_state in [InitSecWipeState.IN_PROGRESS]: diff --git a/hw/ip/otbn/dv/otbnsim/sim/wsr.py b/hw/ip/otbn/dv/otbnsim/sim/wsr.py index d0966290e580d..86b6254da6ee1 100644 --- a/hw/ip/otbn/dv/otbnsim/sim/wsr.py +++ b/hw/ip/otbn/dv/otbnsim/sim/wsr.py @@ -63,12 +63,10 @@ def write_signed(self, value: int) -> None: def commit(self) -> None: '''Commit pending changes''' self._pending_write = False - return def abort(self) -> None: '''Abort pending changes''' self._pending_write = False - return def changes(self) -> Sequence[Trace]: '''Return list of pending architectural changes''' @@ -210,10 +208,9 @@ def __init__(self, name: str): super().__init__(name) seed = [0x84ddfadaf7e1134d, 0x70aa1c59de6197ff, 0x25a4fe335d095f1e, 0x2cba89acbe4a07e9] - self.state = [seed, 4 * [0], 4 * [0], 4 * [0], 4 * [0]] - self.out = 4 * [0] - self._next_value = None # type: Optional[int] - self._value = None # type: Optional[int] + self._state = [seed, 4 * [0], 4 * [0], 4 * [0], 4 * [0]] + self._next_value = 0 + self._value = 0 self.running = False def rol(self, n: int, d: int) -> int: @@ -232,7 +229,6 @@ def on_start(self) -> None: self.running = False def read_unsigned(self) -> int: - assert self._value is not None return self._value def state_update(self, data_in: List[int]) -> List[int]: @@ -244,7 +240,7 @@ def state_update(self, data_in: List[int]) -> List[int]: a_out = a_in ^ b_in ^ d_in b_out = a_in ^ b_in ^ c_in c_out = a_in ^ ((b_in << 17) & ((1 << 64) - 1)) ^ c_in - d_out = self.rol(d_in, 45) ^ self.rol(b_in, 45) + d_out = self.rol(d_in ^ b_in, 45) assert a_out < (1 << 64) assert b_out < (1 << 64) assert c_out < (1 << 64) @@ -252,9 +248,9 @@ def state_update(self, data_in: List[int]) -> List[int]: return [d_out, c_out, b_out, a_out] def set_seed(self, value: List[int]) -> None: - assert(len(value) == 4) + assert len(value) == 4 self.running = True - self.state[0] = value + self._state[0] = value # Step immediately to update the internal state with the new seed self.step() @@ -264,23 +260,19 @@ def step(self) -> None: mid = 4 * [0] nv = 0 for i in range(4): - st_i = self.state[i] - self.state[i + 1] = self.state_update(st_i) + st_i = self._state[i] + self._state[(i + 1) & 3] = self.state_update(st_i) mid[i] = (st_i[3] + st_i[0]) & mask64 - self.out[i] = (self.rol(mid[i], 23) + st_i[3]) & mask64 - nv |= self.out[i] << (64 * i) + nv |= ((self.rol(mid[i], 23) + st_i[3]) & mask64) << (64 * i) self._next_value = nv - self.state[0] = self.state[4] def commit(self) -> None: - if self._next_value is not None: - self._value = self._next_value - - def abort(self) -> None: - self._next_value = 0 + self._value = self._next_value def changes(self) -> List[TraceWSR]: - return ([]) + # Our URND model doesn't track (or report) changes to its internal + # state. + raise NotImplementedError class KeyTrace(Trace): @@ -435,7 +427,6 @@ def changes(self) -> List[Trace]: ret = [] # type: List[Trace] ret += self.MOD.changes() ret += self.RND.changes() - ret += self.URND.changes() ret += self.ACC.changes() ret += self.KeyS0.changes() ret += self.KeyS1.changes() diff --git a/hw/ip/otbn/dv/rig/rig/configs/safe.yml b/hw/ip/otbn/dv/rig/rig/configs/safe.yml new file mode 100644 index 0000000000000..ece0610618227 --- /dev/null +++ b/hw/ip/otbn/dv/rig/rig/configs/safe.yml @@ -0,0 +1,32 @@ +# Copyright lowRISC contributors. +# Licensed under the Apache License, Version 2.0, see LICENSE for details. +# SPDX-License-Identifier: Apache-2.0 + +# An example configuration that tries to make programs that don't +# "die". +# +# This boils down to avoiding calls of stop_at_end_of_cycle() with +# error bits in the simulator. For example, programs should not try to +# read sideload keys that don't have a value. + +inherit: base + +# Squash some snippet generators that we might expect to kill the +# program. +gen-weights: + BadAtEnd: 0 + BadBNMovr: 0 + BadBranch: 0 + BadCallStackRW: 0 + BadDeepLoop: 0 + BadLoadStore: 0 + BadInsn: 0 + BadGiantLoop: 0 + BadZeroLoop: 0 + MisalignedLoadStore: 0 + BadIspr: 0 + +insn-weights: + # Avoid using the WSRR instruction, which might try to read from a + # sideload WSR (that may not have a value). + bn.wsrr: 0 diff --git a/hw/ip/otbn/dv/smoke/BUILD b/hw/ip/otbn/dv/smoke/BUILD new file mode 100644 index 0000000000000..c1267a77b6250 --- /dev/null +++ b/hw/ip/otbn/dv/smoke/BUILD @@ -0,0 +1,18 @@ +# Copyright lowRISC contributors. +# Licensed under the Apache License, Version 2.0, see LICENSE for details. +# SPDX-License-Identifier: Apache-2.0 + +load("//rules:otbn.bzl", "otbn_binary") + +package(default_visibility = ["//visibility:public"]) + +otbn_binary( + name = "smoke_test", + srcs = [ + "smoke_test.s", + ], + args = [ + "--defsym", + "deterministic=1", + ], +) diff --git a/hw/ip/otbn/dv/smoke/smoke_test.s b/hw/ip/otbn/dv/smoke/smoke_test.s index 0fd354ecd5cad..c1e12a67d7bac 100644 --- a/hw/ip/otbn/dv/smoke/smoke_test.s +++ b/hw/ip/otbn/dv/smoke/smoke_test.s @@ -5,7 +5,7 @@ # OTBN Smoke test, runs various instructions which are expected to produce the # final register state see in smoke_expected.txt -.section .text +.section .text.start # x2 = 0xd0beb513 lui x2, 0xd0beb @@ -125,13 +125,18 @@ csrrs x23, 0x7d5, x0 # w1 = mod = 0x78fccc06_2228e9d6_89c9b54f_887cf14e_c79af825_69be586e_9866bb3b_53769ada bn.wsrr w1, 0x0 /* MOD */ -# Request an RND value with a write to CSR RND_PREFETCH -csrrw x0, 0x7d8, x0 - -# sim environment provides a fixed value for RND (in other environment RND isn't -# fixed so this test will have a different final state) # w2 = rnd = 0xAAAAAAAA_99999999_AAAAAAAA_99999999_AAAAAAAA_99999999_AAAAAAAA_99999999 -bn.wsrr w2, 0x1 /* RND */ +.ifdef deterministic + la x25, rnd_value + li x26, 2 + bn.lid x26, 0(x25) +.else + # Request an RND value with a write to CSR RND_PREFETCH + csrrw x0, rnd_prefetch, x0 + # sim environment provides a fixed value for RND (in other environment RND isn't + # fixed so this test will have a different final state) + bn.wsrr w2, 0x1 /* RND */ +.endif # w3 = w1 + w2 = 0x23a776b0_bbc28370_34745ffa_22168ae8_7245a2d0_0357f208_431165e5_ed103473 bn.add w3, w1, w2 @@ -320,30 +325,33 @@ jal x1, call_stack_3 call_stack_3: -# w1 = KEY_S0L = 0xdeadbeef_deadbeef_deadbeef_deadbeef_deadbeef_deadbeef_deadbeef_deadbeef -# w2 = w2 + w1 = w2 + KEY_S0L = 0x8958699a_78475889_8958699a_78475889_8958699a_78475889_8958699a_78475888 -bn.wsrr w1, 0x4 -bn.add w2, w2, w1 +.ifnotdef deterministic + # w1 = KEY_S0L = 0xdeadbeef_deadbeef_deadbeef_deadbeef_deadbeef_deadbeef_deadbeef_deadbeef + # w2 = w2 + w1 = w2 + KEY_S0L = 0x8958699a_78475889_8958699a_78475889_8958699a_78475889_8958699a_78475888 + bn.wsrr w1, 0x4 + bn.add w2, w2, w1 -# w1 = KEY_S0H = 0xdeadbeef_deadbeef_deadbeef_deadbeef -# w2 = w2 + w1 = w2 + KEY_S0H = 0x8958699a_78475889_8958699a_7847588a_6806288a_56f51779_6806288a_56f51777 -bn.wsrr w1, 0x5 -bn.add w2, w2, w1 + # w1 = KEY_S0H = 0xdeadbeef_deadbeef_deadbeef_deadbeef + # w2 = w2 + w1 = w2 + KEY_S0H = 0x8958699a_78475889_8958699a_7847588a_6806288a_56f51779_6806288a_56f51777 + bn.wsrr w1, 0x5 + bn.add w2, w2, w1 -# w1 = KEY_S1L = 0xbaadf00d_baadf00d_baadf00d_baadf00d_baadf00d_baadf00d_baadf00d_baadf00d -# w2 = w2 + w1 = w2 + KEY_S1L = 0x440659a8_32f54897_440659a8_32f54898_22b41898_11a30787_22b41898_11a30784 -bn.wsrr w1, 0x6 -bn.add w2, w2, w1 + # w1 = KEY_S1L = 0xbaadf00d_baadf00d_baadf00d_baadf00d_baadf00d_baadf00d_baadf00d_baadf00d + # w2 = w2 + w1 = w2 + KEY_S1L = 0x440659a8_32f54897_440659a8_32f54898_22b41898_11a30787_22b41898_11a30784 + bn.wsrr w1, 0x6 + bn.add w2, w2, w1 -# w1 = KEY_S1H = 0xbaadf00d_baadf00d_baadf00d_baadf00d -# w2 = w2 + w1 = w2 + KEY_S1H = 0x440659a8_32f54897_440659a8_32f54898_dd6208a5_cc50f794_dd6208a5_cc50f791 -bn.wsrr w1, 0x7 -bn.add w2, w2, w1 + # w1 = KEY_S1H = 0xbaadf00d_baadf00d_baadf00d_baadf00d + # w2 = w2 + w1 = w2 + KEY_S1H = 0x440659a8_32f54897_440659a8_32f54898_dd6208a5_cc50f794_dd6208a5_cc50f791 + bn.wsrr w1, 0x7 + bn.add w2, w2, w1 +.endif # Set unused registers to zero. Without this, they would keep the random value assigned during # secure wipe, which cannot be compared against an expected value. xor x30, x30, x30 +jal x1, reg_dump ecall test_fn_1: @@ -356,12 +364,87 @@ test_fn_2: addi x22, x22, 3 jalr x0, x1, 0 + +# This function dumps both the register files +# into gpr_state and wdr_state. +# The registers aren't clobbered by this function. +reg_dump: + # Dump all the GPRs into gpr_state + la x1, gpr_state # (using the x1 to hold a temporary value) + sw x2, 0(x1) + + la x2, gpr_state + sw x3, 4(x2) # 1 * 4 + sw x4, 8(x2) # 2 * 4 + sw x5, 12(x2) # 3 * 4 + sw x6, 16(x2) # 4 * 4 + sw x7, 20(x2) # 5 * 4 + sw x8, 24(x2) # 6 * 4 + sw x9, 28(x2) # 7 * 4 + sw x10, 32(x2) # 8 * 4 + sw x11, 36(x2) # 9 * 4 + sw x12, 40(x2) # 10 * 4 + sw x13, 44(x2) # 11 * 4 + sw x14, 48(x2) # 12 * 4 + sw x15, 52(x2) # 13 * 4 + sw x16, 56(x2) # 14 * 4 + sw x17, 60(x2) # 15 * 4 + sw x18, 64(x2) # 16 * 4 + sw x19, 68(x2) # 17 * 4 + sw x20, 72(x2) # 18 * 4 + sw x21, 76(x2) # 19 * 4 + sw x22, 80(x2) # 20 * 4 + sw x23, 84(x2) # 21 * 4 + sw x24, 88(x2) # 22 * 4 + sw x25, 92(x2) # 23 * 4 + sw x26, 96(x2) # 24 * 4 + sw x27, 100(x2) # 25 * 4 + sw x28, 104(x2) # 26 * 4 + sw x29, 108(x2) # 27 * 4 + sw x30, 112(x2) # 28 * 4 + sw x31, 116(x2) # 29 * 4 + + # Dump all the WDRs into wdr_state + li x2, 0 + la x3, wdr_state + li x1, 32 # (using the x1 to hold a temporary value) + loop x1, 2 + bn.sid x2++, 0(x3) + addi x3, x3, 32 + + # Restore the value of x2 and x3 to the value they had + # before this function. + la x2, gpr_state + lw x3, 4(x2) + lw x2, 0(x2) + + ret + .section .data -.word 0x1234abcd -.word 0xbaadf00d -.word 0xcafed00d -.word 0xdeadbeef -.word 0xfacefeed -.word 0xaaaaaaaa -.word 0xbbbbbbbb -.word 0xcccccccc + .word 0x1234abcd + .word 0xbaadf00d + .word 0xcafed00d + .word 0xdeadbeef + .word 0xfacefeed + .word 0xaaaaaaaa + .word 0xbbbbbbbb + .word 0xcccccccc + +.ifdef deterministic +.balign 32 +rnd_value: + .rept 4 + .word 0x99999999 + .word 0xaaaaaaaa + .endr +.endif + +.global gpr_state +.balign 32 +gpr_state: + .zero (32 / 4) * 30 # not including x0 and x1 + +.global wdr_state +.balign 32 +wdr_state: + .zero (256 / 4) * 32 diff --git a/hw/ip/otbn/dv/tracer/rtl/otbn_trace_if.sv b/hw/ip/otbn/dv/tracer/rtl/otbn_trace_if.sv index c2404f09f4c98..e9b8cc23b8d86 100644 --- a/hw/ip/otbn/dv/tracer/rtl/otbn_trace_if.sv +++ b/hw/ip/otbn/dv/tracer/rtl/otbn_trace_if.sv @@ -332,7 +332,7 @@ interface otbn_trace_if ((u_otbn_alu_bignum.alu_predec_bignum_i.flags_adder_update[i_fg] | u_otbn_alu_bignum.alu_predec_bignum_i.flags_logic_update[i_fg] | u_otbn_alu_bignum.alu_predec_bignum_i.flags_mac_update[i_fg] | - |u_otbn_alu_bignum.alu_predec_bignum_i.flags_ispr_wr) & + (|u_otbn_alu_bignum.alu_predec_bignum_i.flags_ispr_wr)) & u_otbn_alu_bignum.operation_commit_i)) & ~ispr_init; assign flags_write_data[i_fg] = u_otbn_alu_bignum.flags_d[i_fg]; diff --git a/hw/ip/otbn/dv/uvm/env/otbn_env.core b/hw/ip/otbn/dv/uvm/env/otbn_env.core index 5a78cf56ef66a..2d28a0a9515e2 100644 --- a/hw/ip/otbn/dv/uvm/env/otbn_env.core +++ b/hw/ip/otbn/dv/uvm/env/otbn_env.core @@ -11,6 +11,7 @@ filesets: - lowrisc:dv:ralgen - lowrisc:dv:cip_lib - lowrisc:dv:dv_lib + - lowrisc:dv:mem_bkdr_util - lowrisc:dv:tl_agent - lowrisc:dv:mem_bkdr_util - lowrisc:dv:alert_esc_agent diff --git a/hw/ip/otbn/dv/uvm/env/seq_lib/otbn_alu_bignum_mod_err_vseq.sv b/hw/ip/otbn/dv/uvm/env/seq_lib/otbn_alu_bignum_mod_err_vseq.sv index e329b9162e685..5198fef666098 100644 --- a/hw/ip/otbn/dv/uvm/env/seq_lib/otbn_alu_bignum_mod_err_vseq.sv +++ b/hw/ip/otbn/dv/uvm/env/seq_lib/otbn_alu_bignum_mod_err_vseq.sv @@ -10,14 +10,14 @@ class otbn_alu_bignum_mod_err_vseq extends otbn_intg_err_vseq; `uvm_object_new - task await_use(output bit [otbn_pkg::BaseWordsPerWLEN-1:0] used_words); + protected task await_use(output bit [otbn_pkg::BaseWordsPerWLEN-1:0] used_words); used_words = '0; `uvm_info(`gfn, "Waiting for `mod_intg_q` to be used", UVM_LOW) cfg.alu_bignum_vif.wait_for_mod_used(200, used_words); endtask - task inject_errors(input bit [otbn_pkg::BaseWordsPerWLEN-1:0] used_words, - output bit [otbn_pkg::BaseWordsPerWLEN-1:0] corrupted_words); + protected task inject_errors(input bit [otbn_pkg::BaseWordsPerWLEN-1:0] used_words, + output bit [otbn_pkg::BaseWordsPerWLEN-1:0] corrupted_words); bit [otbn_pkg::ExtWLEN-1:0] new_data = corrupt_data(cfg.alu_bignum_vif.mod_intg_q, '{default: 50}, corrupted_words); @@ -29,7 +29,7 @@ class otbn_alu_bignum_mod_err_vseq extends otbn_intg_err_vseq; end endtask - task release_force(); + protected task release_force(); cfg.alu_bignum_vif.release_mod_intg_q(); endtask diff --git a/hw/ip/otbn/dv/uvm/env/seq_lib/otbn_base_vseq.sv b/hw/ip/otbn/dv/uvm/env/seq_lib/otbn_base_vseq.sv index af978bf0ba47b..e88e2fc239d9e 100644 --- a/hw/ip/otbn/dv/uvm/env/seq_lib/otbn_base_vseq.sv +++ b/hw/ip/otbn/dv/uvm/env/seq_lib/otbn_base_vseq.sv @@ -868,4 +868,66 @@ class otbn_base_vseq extends cip_base_vseq #( dut_init("HARD"); end endtask + + // Wait for a single beat of data on an EDN interface + // + // This waits until it has seen the end of a beat. If we are in the middle of a beat, it will just + // wait until the end of this one. Otherwise, it will wait for the next one to start. + protected task wait_edn_beat(edn_idx_e iface_idx, int max_wait_before); + // Wait at least one cycle (EDN clock) to clear any finishing beat on the EDN interface. Then + // wait up to max_wait_before cycles (main clock) for OTBN to ask for some data. + @(cfg.m_edn_pull_agent_cfgs[iface_idx].vif.mon_cb); + for (int i = 0; i < max_wait_before; ++i) begin + if (!cfg.m_edn_pull_agent_cfgs[iface_idx].is_silent()) break; + @(cfg.clk_rst_vif.cbn); + end + + // Now we know that OTBN is asking for *something*. Wait until the beat has completed. + cfg.m_edn_pull_agent_cfgs[iface_idx].wait_while_running(); + endtask + + // Wait for a reseed on RND or URND + // + // This assumes that none of the 8 beats of data had been transferred so far, so it waits for each + // of them. The max_wait_before argument gives the number of cycles allowed before OTBN asks for + // each beat of data. + // + // If we are called when some beats have already been transferred then we'll end up waiting some + // multiple of max_wait_before cycles after the reseed has finished. + protected task wait_edn_reseed(edn_idx_e iface_idx, int max_wait_before); + repeat (8) wait_edn_beat(iface_idx, max_wait_before); + endtask + + // Wait for one phase of a secure wipe + // + // This might take a bit of time because it needs to reseed over URND and also walk over the + // registers. To give ourselves enough time, we wait for the URND reseed and then an extra 64 + // cycles. + protected task wait_secure_wipe_phase(); + // A secure wipe phase consists of wiping with whatever data we've currently got from the EDN + // (takes 64 cycles), then reseeding over the EDN (depends on EDN timing). + // + // As a special case, the RTL doesn't bother reseeding over the EDN on the second pass if it + // knows it's done because it's locking anyway. In that case, this task will wait too long + // because it will wait some extra cycles for an EDN transaction that doesn't happen. + // + // Wipe with whatever we've currently got from the EDN + repeat (64) @(cfg.clk_rst_vif.cbn); + + // Ask the EDN for more data + // + // We pass 10 as max_wait_before in the call to wait_edn_reseed: we expect the secure wipe to + // start immediately, but wish to allow a couple of cycles leeway, in case the thing that + // caused the wipe is *now*. + wait_edn_reseed(UrndEdnIdx, 10); + + endtask + + // Wait for a secure wipe + // + // This is just waiting for the two phases, one after the other. + task wait_secure_wipe(); + repeat (2) wait_secure_wipe_phase(); + endtask + endclass : otbn_base_vseq diff --git a/hw/ip/otbn/dv/uvm/env/seq_lib/otbn_controller_ispr_rdata_err_vseq.sv b/hw/ip/otbn/dv/uvm/env/seq_lib/otbn_controller_ispr_rdata_err_vseq.sv index 50d42de6ea609..63025b7f88edf 100644 --- a/hw/ip/otbn/dv/uvm/env/seq_lib/otbn_controller_ispr_rdata_err_vseq.sv +++ b/hw/ip/otbn/dv/uvm/env/seq_lib/otbn_controller_ispr_rdata_err_vseq.sv @@ -10,14 +10,14 @@ class otbn_controller_ispr_rdata_err_vseq extends otbn_intg_err_vseq; `uvm_object_new - task await_use(output bit [otbn_pkg::BaseWordsPerWLEN-1:0] used_words); + protected task await_use(output bit [otbn_pkg::BaseWordsPerWLEN-1:0] used_words); used_words = '0; `uvm_info(`gfn, "Waiting for `ispr_rdata_intg` to be used", UVM_LOW) cfg.controller_vif.wait_for_ispr_rdata_used(200, used_words); endtask - task inject_errors(input bit [otbn_pkg::BaseWordsPerWLEN-1:0] used_words, - output bit [otbn_pkg::BaseWordsPerWLEN-1:0] corrupted_words); + protected task inject_errors(input bit [otbn_pkg::BaseWordsPerWLEN-1:0] used_words, + output bit [otbn_pkg::BaseWordsPerWLEN-1:0] corrupted_words); int unsigned corrupt_word_pct[otbn_pkg::BaseWordsPerWLEN]; bit [otbn_pkg::ExtWLEN-1:0] new_data; @@ -43,7 +43,7 @@ class otbn_controller_ispr_rdata_err_vseq extends otbn_intg_err_vseq; end endtask - task release_force(); + protected task release_force(); cfg.controller_vif.release_ispr_rdata_intg_i(); endtask diff --git a/hw/ip/otbn/dv/uvm/env/seq_lib/otbn_ctrl_redun_vseq.sv b/hw/ip/otbn/dv/uvm/env/seq_lib/otbn_ctrl_redun_vseq.sv index e6ec4f389b5dd..b4fe9b6053ed4 100644 --- a/hw/ip/otbn/dv/uvm/env/seq_lib/otbn_ctrl_redun_vseq.sv +++ b/hw/ip/otbn/dv/uvm/env/seq_lib/otbn_ctrl_redun_vseq.sv @@ -8,8 +8,16 @@ class otbn_ctrl_redun_vseq extends otbn_single_vseq; `uvm_object_utils(otbn_ctrl_redun_vseq) `uvm_object_new + bit have_injected_error; + task body(); do_end_addr_check = 0; + + // Run the otbn_single_vseq body, which runs a single OTBN application to completion. At the + // same time, try to inject an error and check that the RTL (and model) both spot the error. If + // we didn't find a good moment to inject the error, fail the test: sadly, it hasn't actually + // tested anything. + have_injected_error = 1'b0; fork begin super.body(); @@ -18,8 +26,26 @@ class otbn_ctrl_redun_vseq extends otbn_single_vseq; inject_redun_err(); end join_any + + if (!have_injected_error) begin + `uvm_fatal(`gfn, "Never found a time to inject an error.") + end + endtask: body + // Wait until the value at path becomes nonzero + task wait_for_flag(string path); + // Initialise flag to zero. With some simulators (one version of Xcelium, at least), it seems + // that the call to uvm_hdl_read might leave its destination set to X. The HDL path exists and + // the signal is not X, so this is rather confusing. Initialising flag to zero before calling + // uvm_hdl_read seems to fix the behaviour. + uvm_hdl_data_t flag = 0; + `DV_SPINWAIT(do begin + @(cfg.clk_rst_vif.cb); + `DV_CHECK_FATAL(uvm_hdl_read(path, flag) == 1); + end while(!flag);) + endtask + task inject_redun_err(); bit [3:0] err_type; string err_path; @@ -27,19 +53,21 @@ class otbn_ctrl_redun_vseq extends otbn_single_vseq; bit [3:0] bad_addr; bit [3:0] mask; bit [31:0] err_val = 32'd1 << 20; + + // We're doing something evil and forcing a signal inside the design. We expect the design to + // notice that something went wrong and lock itself and we test for that. However, the design + // RTL has some assertions that are rendered false by the forcing that we do. To avoid killing + // the simulation with the failed assertion, turn them off here, and turn them on again at the + // end of this task. + $assertoff(0, "tb.dut.g_secure_wipe_assertions"); + `DV_CHECK_STD_RANDOMIZE_WITH_FATAL(err_type, err_type inside {[0:6]};) case(err_type) // Injecting error on ispr_addr during a write 0: begin - bit wr_en; err_path = "tb.dut.u_otbn_core.u_otbn_alu_bignum.ispr_addr_i"; - `DV_SPINWAIT( - do begin - @(cfg.clk_rst_vif.cb); - uvm_hdl_read("tb.dut.u_otbn_core.u_otbn_alu_bignum.ispr_wr_en", wr_en); - end while(!wr_en); - ) - uvm_hdl_read(err_path, good_addr); + wait_for_flag("tb.dut.u_otbn_core.u_otbn_alu_bignum.ispr_wr_en"); + `DV_CHECK_FATAL(uvm_hdl_read(err_path, good_addr)); // Mask to corrupt 1 to 2 bits of the ispr addr `DV_CHECK_STD_RANDOMIZE_WITH_FATAL(mask, $countones(mask) inside {[1:2]};) bad_addr = good_addr ^ mask; @@ -47,15 +75,9 @@ class otbn_ctrl_redun_vseq extends otbn_single_vseq; end // Injecting error on ispr_addr during a read 1: begin - bit rd_en; err_path = "tb.dut.u_otbn_core.u_otbn_alu_bignum.ispr_addr_i"; - `DV_SPINWAIT( - do begin - @(cfg.clk_rst_vif.cb); - uvm_hdl_read("tb.dut.u_otbn_core.u_otbn_alu_bignum.ispr_rd_en_i", rd_en); - end while(!rd_en); - ) - uvm_hdl_read(err_path, good_addr); + wait_for_flag("tb.dut.u_otbn_core.u_otbn_alu_bignum.ispr_rd_en_i"); + `DV_CHECK_FATAL(uvm_hdl_read(err_path, good_addr)); // Mask to corrupt 1 to 2 bits of the ispr addr `DV_CHECK_STD_RANDOMIZE_WITH_FATAL(mask, $countones(mask) inside {[1:2]};) bad_addr = good_addr ^ mask; @@ -64,15 +86,9 @@ class otbn_ctrl_redun_vseq extends otbn_single_vseq; // injecting error on opcode 2: begin logic [3:0] good_op, bad_op; - bit op_valid; err_path = "tb.dut.u_otbn_core.u_otbn_alu_bignum.operation_i.op"; - `DV_SPINWAIT( - do begin - @(cfg.clk_rst_vif.cb); - uvm_hdl_read("tb.dut.u_otbn_core.alu_bignum_operation_valid", op_valid); - end while(!op_valid); - ) - uvm_hdl_read(err_path, good_op); + wait_for_flag("tb.dut.u_otbn_core.alu_bignum_operation_valid"); + `DV_CHECK_FATAL(uvm_hdl_read(err_path, good_op)); `DV_CHECK_STD_RANDOMIZE_WITH_FATAL(bad_op, bad_op != good_op; bad_op != otbn_pkg::AluOpBignumNone;); @@ -80,17 +96,11 @@ class otbn_ctrl_redun_vseq extends otbn_single_vseq; end // injecting error on lsu_addr_en 3: begin - bit insn_valid; bit choose_err; otbn_pkg::insn_dec_shared_t insn_dec_shared_i; err_path = "tb.dut.u_otbn_core.u_otbn_controller.insn_dec_shared_i"; - `DV_SPINWAIT( - do begin - @(cfg.clk_rst_vif.cb); - uvm_hdl_read("tb.dut.u_otbn_core.u_otbn_controller.insn_valid_i", insn_valid); - end while(!insn_valid); - ) - uvm_hdl_read(err_path, insn_dec_shared_i); + wait_for_flag("tb.dut.u_otbn_core.u_otbn_controller.insn_valid_i"); + `DV_CHECK_FATAL(uvm_hdl_read(err_path, insn_dec_shared_i)); `DV_CHECK_STD_RANDOMIZE_FATAL(choose_err) case(choose_err) 0: begin @@ -107,22 +117,16 @@ class otbn_ctrl_redun_vseq extends otbn_single_vseq; end // injects error into otbn_core 4: begin - bit insn_valid; bit [1:0] choose_err; cfg.clk_rst_vif.wait_clks($urandom_range(10, 1000)); - `DV_SPINWAIT( - do begin - @(cfg.clk_rst_vif.cb); - uvm_hdl_read("tb.dut.u_otbn_core.insn_valid", insn_valid); - end while(!insn_valid); - ) + wait_for_flag("tb.dut.u_otbn_core.insn_valid"); `DV_CHECK_STD_RANDOMIZE_WITH_FATAL(choose_err, choose_err inside {[0:2]};) case(choose_err) 0: begin bit [31:0] bad_rf_ren_a; bit [31:0] good_rf_ren_a; err_path = "tb.dut.u_otbn_core.rf_predec_bignum.rf_ren_a"; - uvm_hdl_read(err_path, good_rf_ren_a); + `DV_CHECK_FATAL(uvm_hdl_read(err_path, good_rf_ren_a)); `DV_CHECK_STD_RANDOMIZE_WITH_FATAL(bad_rf_ren_a, $countones(bad_rf_ren_a) == 1; bad_rf_ren_a != good_rf_ren_a;) `DV_CHECK_FATAL(uvm_hdl_force(err_path, bad_rf_ren_a) == 1); @@ -131,7 +135,7 @@ class otbn_ctrl_redun_vseq extends otbn_single_vseq; bit [31:0] bad_rf_ren_b; bit [31:0] good_rf_ren_b; err_path = "tb.dut.u_otbn_core.rf_predec_bignum.rf_ren_b"; - uvm_hdl_read(err_path, good_rf_ren_b); + `DV_CHECK_FATAL(uvm_hdl_read(err_path, good_rf_ren_b)); `DV_CHECK_STD_RANDOMIZE_WITH_FATAL(bad_rf_ren_b, $countones(bad_rf_ren_b) == 1; bad_rf_ren_b != good_rf_ren_b;) `DV_CHECK_FATAL(uvm_hdl_force(err_path, bad_rf_ren_b) == 1); @@ -140,7 +144,7 @@ class otbn_ctrl_redun_vseq extends otbn_single_vseq; bit [8:0] bad_ispr_rd_en; bit [8:0] good_ispr_rd_en; err_path = "tb.dut.u_otbn_core.ispr_predec_bignum.ispr_rd_en"; - uvm_hdl_read(err_path, good_ispr_rd_en); + `DV_CHECK_FATAL(uvm_hdl_read(err_path, good_ispr_rd_en)); `DV_CHECK_STD_RANDOMIZE_WITH_FATAL(bad_ispr_rd_en, $countones(bad_ispr_rd_en) == 1; bad_ispr_rd_en != good_ispr_rd_en;) `DV_CHECK_FATAL(uvm_hdl_force(err_path, bad_ispr_rd_en) == 1); @@ -154,34 +158,23 @@ class otbn_ctrl_redun_vseq extends otbn_single_vseq; 5: begin bit mac_en; bit choose_err; - bit insn_valid; `DV_WAIT(cfg.model_agent_cfg.vif.status == otbn_pkg::StatusBusyExecute) cfg.clk_rst_vif.wait_clks($urandom_range(10, 100)); `DV_CHECK_STD_RANDOMIZE_FATAL(choose_err) // Wait for valid instruction, because `otbn_core` only propagates bignum MAC predec errors // for valid instructions. - `DV_SPINWAIT( - do begin - @(cfg.clk_rst_vif.cb); - uvm_hdl_read("tb.dut.u_otbn_core.insn_valid", insn_valid); - end while(!insn_valid); - ) + wait_for_flag("tb.dut.u_otbn_core.insn_valid"); case(choose_err) 0: begin err_path = "tb.dut.u_otbn_core.u_otbn_mac_bignum.mac_en_i"; - uvm_hdl_read(err_path, mac_en); + `DV_CHECK_FATAL(uvm_hdl_read(err_path, mac_en)); `DV_CHECK_FATAL(uvm_hdl_force(err_path, !mac_en) == 1); end 1: begin bit zero_acc; err_path = "tb.dut.u_otbn_core.u_otbn_mac_bignum.operation_i.zero_acc"; - `DV_SPINWAIT( - do begin - @(cfg.clk_rst_vif.cb); - uvm_hdl_read("tb.dut.u_otbn_core.u_otbn_mac_bignum.mac_en_i", mac_en); - end while(!mac_en); - ) - uvm_hdl_read(err_path, zero_acc); + wait_for_flag("tb.dut.u_otbn_core.u_otbn_mac_bignum.mac_en_i"); + `DV_CHECK_FATAL(uvm_hdl_read(err_path, zero_acc)); `DV_CHECK_FATAL(uvm_hdl_force(err_path, !zero_acc) == 1); end default: begin @@ -197,43 +190,25 @@ class otbn_ctrl_redun_vseq extends otbn_single_vseq; `DV_CHECK_STD_RANDOMIZE_WITH_FATAL(choose_err, choose_err inside {[0:2]};) case(choose_err) 0: begin - bit [1:0] en; err_path = "tb.dut.u_otbn_core.u_otbn_rf_bignum.wr_addr_i[4:0]"; - `DV_SPINWAIT( - do begin - @(cfg.clk_rst_vif.cb); - uvm_hdl_read("tb.dut.u_otbn_core.u_otbn_rf_bignum.wr_en_i[1:0]", en); - end while(!en); - ) - uvm_hdl_read(err_path, addr); + wait_for_flag("tb.dut.u_otbn_core.u_otbn_rf_bignum.wr_en_i[1:0]"); + `DV_CHECK_FATAL(uvm_hdl_read(err_path, addr)); `DV_CHECK_STD_RANDOMIZE_WITH_FATAL(mask, $countones(mask) inside {[1:2]};) addr = addr ^ mask; `DV_CHECK_FATAL(uvm_hdl_force(err_path, addr) == 1); end 1: begin - bit en; err_path = "tb.dut.u_otbn_core.u_otbn_rf_bignum.rd_addr_a_i"; - `DV_SPINWAIT( - do begin - @(cfg.clk_rst_vif.cb); - uvm_hdl_read("tb.dut.u_otbn_core.u_otbn_rf_bignum.rd_en_a_i", en); - end while(!en); - ) - uvm_hdl_read(err_path, addr); + wait_for_flag("tb.dut.u_otbn_core.u_otbn_rf_bignum.rd_en_a_i"); + `DV_CHECK_FATAL(uvm_hdl_read(err_path, addr)); `DV_CHECK_STD_RANDOMIZE_WITH_FATAL(mask, $countones(mask) inside {[1:2]};) addr = addr ^ mask; `DV_CHECK_FATAL(uvm_hdl_force(err_path, addr) == 1); end 2: begin - bit en; err_path = "tb.dut.u_otbn_core.u_otbn_rf_bignum.rd_addr_b_i"; - `DV_SPINWAIT( - do begin - @(cfg.clk_rst_vif.cb); - uvm_hdl_read("tb.dut.u_otbn_core.u_otbn_rf_bignum.rd_en_b_i", en); - end while(!en); - ) - uvm_hdl_read(err_path, addr); + wait_for_flag("tb.dut.u_otbn_core.u_otbn_rf_bignum.rd_en_b_i"); + `DV_CHECK_FATAL(uvm_hdl_read(err_path, addr)); `DV_CHECK_STD_RANDOMIZE_WITH_FATAL(mask, $countones(mask) inside {[1:2]};) addr = addr ^ mask; `DV_CHECK_FATAL(uvm_hdl_force(err_path, addr) == 1); @@ -248,10 +223,15 @@ class otbn_ctrl_redun_vseq extends otbn_single_vseq; end endcase `uvm_info(`gfn, "injecting bad internal state error into ISS", UVM_HIGH) + have_injected_error = 1'b1; cfg.model_agent_cfg.vif.send_err_escalation(err_val); `DV_WAIT(cfg.model_agent_cfg.vif.status == otbn_pkg::StatusLocked) `DV_CHECK_FATAL(uvm_hdl_release(err_path) == 1); reset_if_locked(); + + // Turn the secure wipe assertions on again (there's a note above the $assertoff call earlier + // which explains what we're doing). + $asserton(0, "tb.dut.g_secure_wipe_assertions"); endtask endclass : otbn_ctrl_redun_vseq diff --git a/hw/ip/otbn/dv/uvm/env/seq_lib/otbn_intg_err_vseq.sv b/hw/ip/otbn/dv/uvm/env/seq_lib/otbn_intg_err_vseq.sv index f0417515111d6..5a723f2d0fa7c 100644 --- a/hw/ip/otbn/dv/uvm/env/seq_lib/otbn_intg_err_vseq.sv +++ b/hw/ip/otbn/dv/uvm/env/seq_lib/otbn_intg_err_vseq.sv @@ -92,10 +92,10 @@ class otbn_intg_err_vseq extends otbn_base_vseq; release_force(); if (|(corrupted_words & used_words)) begin - // OTBN should now do a secure wipe. Give it up to 400 cycles to do so (because it needs to go - // twice over all registers and reseed URND in between, the time of which depends on the delay - // configured in the EDN model). - cfg.clk_rst_vif.wait_n_clks(400); + // OTBN should now do a secure wipe. Give it up to 4000 cycles to do so (because it needs to + // go twice over all registers and reseed URND in between, the time of which depends on the + // delay configured in the EDN model). + cfg.clk_rst_vif.wait_n_clks(4000); // We should now be in a locked state after the secure wipe. `DV_CHECK_FATAL(cfg.model_agent_cfg.vif.status == otbn_pkg::StatusLocked); diff --git a/hw/ip/otbn/dv/uvm/env/seq_lib/otbn_mac_bignum_acc_err_vseq.sv b/hw/ip/otbn/dv/uvm/env/seq_lib/otbn_mac_bignum_acc_err_vseq.sv index d2db550884977..b5e86bb834cfc 100644 --- a/hw/ip/otbn/dv/uvm/env/seq_lib/otbn_mac_bignum_acc_err_vseq.sv +++ b/hw/ip/otbn/dv/uvm/env/seq_lib/otbn_mac_bignum_acc_err_vseq.sv @@ -10,14 +10,14 @@ class otbn_mac_bignum_acc_err_vseq extends otbn_intg_err_vseq; `uvm_object_new - task await_use(output bit [otbn_pkg::BaseWordsPerWLEN-1:0] used_words); + protected task await_use(output bit [otbn_pkg::BaseWordsPerWLEN-1:0] used_words); used_words = '0; `uvm_info(`gfn, "Waiting for `acc_intg_q` to be used", UVM_LOW) cfg.mac_bignum_vif.wait_for_acc_used(200, used_words); endtask - task inject_errors(input bit [otbn_pkg::BaseWordsPerWLEN-1:0] used_words, - output bit [otbn_pkg::BaseWordsPerWLEN-1:0] corrupted_words); + protected task inject_errors(input bit [otbn_pkg::BaseWordsPerWLEN-1:0] used_words, + output bit [otbn_pkg::BaseWordsPerWLEN-1:0] corrupted_words); bit [otbn_pkg::ExtWLEN-1:0] new_data = corrupt_data(cfg.mac_bignum_vif.acc_intg_q, '{default: 50}, corrupted_words); @@ -29,7 +29,7 @@ class otbn_mac_bignum_acc_err_vseq extends otbn_intg_err_vseq; end endtask - task release_force(); + protected task release_force(); cfg.mac_bignum_vif.release_acc_intg_q(); endtask diff --git a/hw/ip/otbn/dv/uvm/env/seq_lib/otbn_mem_gnt_acc_err_vseq.sv b/hw/ip/otbn/dv/uvm/env/seq_lib/otbn_mem_gnt_acc_err_vseq.sv index a63df2ebae7bd..81a91cbb59848 100644 --- a/hw/ip/otbn/dv/uvm/env/seq_lib/otbn_mem_gnt_acc_err_vseq.sv +++ b/hw/ip/otbn/dv/uvm/env/seq_lib/otbn_mem_gnt_acc_err_vseq.sv @@ -35,7 +35,8 @@ class otbn_mem_gnt_acc_err_vseq extends otbn_single_vseq; `DV_SPINWAIT( do begin @(cfg.clk_rst_vif.cb); - uvm_hdl_read("tb.dut.u_dmem.req_i", req); + if (!uvm_hdl_read("tb.dut.u_dmem.req_i", req)) + `uvm_fatal(`gfn, "failed to read u_dmem.req_i"); end while(!req); ) `DV_CHECK_FATAL(uvm_hdl_force(gnt_path, 0) == 1) @@ -45,7 +46,8 @@ class otbn_mem_gnt_acc_err_vseq extends otbn_single_vseq; `DV_SPINWAIT( do begin @(cfg.clk_rst_vif.cb); - uvm_hdl_read("tb.dut.u_imem.req_i", req); + if (!uvm_hdl_read("tb.dut.u_imem.req_i", req)) + `uvm_fatal(`gfn, "failed to read u_imem.req_i"); end while(!req); ) `DV_CHECK_FATAL(uvm_hdl_force(gnt_path, 0) == 1) diff --git a/hw/ip/otbn/dv/uvm/env/seq_lib/otbn_pc_ctrl_flow_redun_vseq.sv b/hw/ip/otbn/dv/uvm/env/seq_lib/otbn_pc_ctrl_flow_redun_vseq.sv index f4454bccdc3ae..efaf6ac889909 100644 --- a/hw/ip/otbn/dv/uvm/env/seq_lib/otbn_pc_ctrl_flow_redun_vseq.sv +++ b/hw/ip/otbn/dv/uvm/env/seq_lib/otbn_pc_ctrl_flow_redun_vseq.sv @@ -32,13 +32,16 @@ class otbn_pc_ctrl_flow_redun_vseq extends otbn_single_vseq; do begin @(cfg.clk_rst_vif.cb); - uvm_hdl_read("tb.dut.u_otbn_core.u_otbn_instruction_fetch.imem_rvalid_i", imem_rvalid); - uvm_hdl_read("tb.dut.u_otbn_core.u_otbn_instruction_fetch.insn_fetch_req_valid_raw_i", - insn_fetch_req_valid); - uvm_hdl_read("tb.dut.u_otbn_core.u_otbn_instruction_fetch.prefetch_ignore_errs_i", - prefetch_ignore_err); + if (!uvm_hdl_read("tb.dut.u_otbn_core.u_otbn_instruction_fetch.imem_rvalid_i", imem_rvalid)) + `uvm_fatal(`gfn, "failed to read imem_rvalid_i"); + if (!uvm_hdl_read("tb.dut.u_otbn_core.u_otbn_instruction_fetch.insn_fetch_req_valid_raw_i", + insn_fetch_req_valid)) + `uvm_fatal(`gfn, "failed to read insn_fetch_req_valid_raw_i"); + if (!uvm_hdl_read("tb.dut.u_otbn_core.u_otbn_instruction_fetch.prefetch_ignore_errs_i", + prefetch_ignore_err)) + `uvm_fatal(`gfn, "failed to read prefetch_ignore_errs_i"); end while(!(imem_rvalid & insn_fetch_req_valid & !prefetch_ignore_err)); - uvm_hdl_read(addr_path, good_addr); + `DV_CHECK_FATAL(uvm_hdl_read(addr_path, good_addr)); // Mask to corrupt 1 to 2 bits of the prefetch addr `DV_CHECK_STD_RANDOMIZE_WITH_FATAL(mask, $countones(mask) inside {[1:2]};) bad_addr = good_addr ^ mask; diff --git a/hw/ip/otbn/dv/uvm/env/seq_lib/otbn_rf_base_intg_err_vseq.sv b/hw/ip/otbn/dv/uvm/env/seq_lib/otbn_rf_base_intg_err_vseq.sv index b4616a21f55ea..946410d8b1e12 100644 --- a/hw/ip/otbn/dv/uvm/env/seq_lib/otbn_rf_base_intg_err_vseq.sv +++ b/hw/ip/otbn/dv/uvm/env/seq_lib/otbn_rf_base_intg_err_vseq.sv @@ -11,6 +11,11 @@ class otbn_rf_base_intg_err_vseq extends otbn_base_vseq; `uvm_object_new rand bit insert_intg_err_to_a; + // Wait until the selected register file is being used + // + // This will normally just be a couple of cycles (because most instructions read from the register + // file), but it might be a bit longer if we happen to be stalled waiting for the EDN, which + // happens on a RND read. task await_use(); logic rd_en; `uvm_info(`gfn, "Waiting for selected RF to be used", UVM_LOW) @@ -20,7 +25,8 @@ class otbn_rf_base_intg_err_vseq extends otbn_base_vseq; rd_en = insert_intg_err_to_a ? cfg.trace_vif.rf_base_rd_en_a : cfg.trace_vif.rf_base_rd_en_b; end while(!rd_en);, - cfg.clk_rst_vif.wait_clks(20000);) + cfg.clk_rst_vif.wait_clks(20000); + ) if (!rd_en) begin `uvm_fatal(`gfn, $sformatf("Timeout while waiting for register file %s to be used", @@ -84,10 +90,9 @@ class otbn_rf_base_intg_err_vseq extends otbn_base_vseq; @(cfg.clk_rst_vif.cbn); release_force(); - // OTBN should now do a secure wipe. Give it up to 400 cycles to do so (because it needs to go - // twice over all registers and reseed URND in between, the time of which depends on the delay - // configured in the EDN model). - cfg.clk_rst_vif.wait_n_clks(400); + // OTBN should now do a secure wipe + wait_secure_wipe(); + // We should now be in a locked state after the secure wipe. `DV_CHECK_FATAL(cfg.model_agent_cfg.vif.status == otbn_pkg::StatusLocked); // The scoreboard will have seen the transition to locked state and inferred that it should diff --git a/hw/ip/otbn/dv/uvm/env/seq_lib/otbn_rf_bignum_intg_err_vseq.sv b/hw/ip/otbn/dv/uvm/env/seq_lib/otbn_rf_bignum_intg_err_vseq.sv index 5a44fdecdaa8f..41f26b32a6762 100644 --- a/hw/ip/otbn/dv/uvm/env/seq_lib/otbn_rf_bignum_intg_err_vseq.sv +++ b/hw/ip/otbn/dv/uvm/env/seq_lib/otbn_rf_bignum_intg_err_vseq.sv @@ -11,7 +11,7 @@ class otbn_rf_bignum_intg_err_vseq extends otbn_intg_err_vseq; `uvm_object_new rand bit insert_intg_err_to_a; - task await_use(output bit [otbn_pkg::BaseWordsPerWLEN-1:0] used_words); + protected task await_use(output bit [otbn_pkg::BaseWordsPerWLEN-1:0] used_words); logic rd_en; used_words = '0; `uvm_info(`gfn, "Waiting for selected RF to be used", UVM_LOW) @@ -26,8 +26,8 @@ class otbn_rf_bignum_intg_err_vseq extends otbn_intg_err_vseq; ) endtask - task inject_errors(input bit [otbn_pkg::BaseWordsPerWLEN-1:0] used_words, - output bit [otbn_pkg::BaseWordsPerWLEN-1:0] corrupted_words); + protected task inject_errors(input bit [otbn_pkg::BaseWordsPerWLEN-1:0] used_words, + output bit [otbn_pkg::BaseWordsPerWLEN-1:0] corrupted_words); logic [otbn_pkg::ExtWLEN-1:0] orig_data; bit [otbn_pkg::ExtWLEN-1:0] new_data; @@ -47,7 +47,7 @@ class otbn_rf_bignum_intg_err_vseq extends otbn_intg_err_vseq; end endtask - task release_force(); + protected task release_force(); if (insert_intg_err_to_a) begin cfg.trace_vif.release_rf_bignum_rd_data_a_intg(); end else begin diff --git a/hw/ip/otbn/dv/uvm/env/seq_lib/otbn_stack_addr_integ_chk_vseq.sv b/hw/ip/otbn/dv/uvm/env/seq_lib/otbn_stack_addr_integ_chk_vseq.sv index 43a1b1ff33652..77da85efd189c 100644 --- a/hw/ip/otbn/dv/uvm/env/seq_lib/otbn_stack_addr_integ_chk_vseq.sv +++ b/hw/ip/otbn/dv/uvm/env/seq_lib/otbn_stack_addr_integ_chk_vseq.sv @@ -53,11 +53,11 @@ class otbn_stack_addr_integ_chk_vseq extends otbn_single_vseq; `DV_SPINWAIT( do begin @(cfg.clk_rst_vif.cb); - uvm_hdl_read(top_valid_path, top_valid); + `DV_CHECK_FATAL(uvm_hdl_read(top_valid_path, top_valid)); end while (!top_valid); ) cfg.clk_rst_vif.wait_clks($urandom_range(10, 100)); - uvm_hdl_read(top_valid_path, top_valid); + `DV_CHECK_FATAL(uvm_hdl_read(top_valid_path, top_valid)); if (top_valid) begin fork begin: isolation_fork @@ -67,7 +67,7 @@ class otbn_stack_addr_integ_chk_vseq extends otbn_single_vseq; `DV_SPINWAIT( do begin @(cfg.clk_rst_vif.cb); - uvm_hdl_read(stack_read_path, stack_read); + `DV_CHECK_FATAL(uvm_hdl_read(stack_read_path, stack_read)); end while (!stack_read); ) cfg.model_agent_cfg.vif.send_err_escalation(err_val); @@ -84,7 +84,7 @@ class otbn_stack_addr_integ_chk_vseq extends otbn_single_vseq; `DV_SPINWAIT( do begin @(cfg.clk_rst_vif.cb); - uvm_hdl_read(stack_write_path, stack_write); + `DV_CHECK_FATAL(uvm_hdl_read(stack_write_path, stack_write)); end while (!stack_write); ) @(cfg.clk_rst_vif.cb); @@ -107,18 +107,18 @@ class otbn_stack_addr_integ_chk_vseq extends otbn_single_vseq; bit [38:0] bad_data; bit [31:0] mask; bit [31:0] err_val = 32'd1 << 20; - uvm_hdl_read(stack_wr_idx_path, stack_wr_idx); + `DV_CHECK_FATAL(uvm_hdl_read(stack_wr_idx_path, stack_wr_idx)); if (stack_wr_idx != 0) begin if (err_type) begin $sformat(err_path, "tb.dut.u_otbn_core.u_otbn_rf_base.u_call_stack.stack_storage[%0d]", (stack_wr_idx-1)); end else begin - uvm_hdl_read(stack_wr_idx_path, stack_wr_idx); + `DV_CHECK_FATAL(uvm_hdl_read(stack_wr_idx_path, stack_wr_idx)); $sformat(err_path, "tb.dut.u_otbn_core.u_otbn_controller.u_otbn_loop_controller.loop_info_stack.stack_storage[%0d]" , (stack_wr_idx - 1)); end - uvm_hdl_read(err_path, good_data); + `DV_CHECK_FATAL(uvm_hdl_read(err_path, good_data)); `DV_CHECK_STD_RANDOMIZE_WITH_FATAL(mask, $countones(mask) inside {[1:2]};) bad_data = good_data ^ mask; `DV_CHECK_FATAL(uvm_hdl_force(err_path, bad_data)) diff --git a/hw/ip/otbn/dv/uvm/env/seq_lib/otbn_zero_state_err_urnd_vseq.sv b/hw/ip/otbn/dv/uvm/env/seq_lib/otbn_zero_state_err_urnd_vseq.sv index 30d5d648561cd..f4f849bdf1d94 100644 --- a/hw/ip/otbn/dv/uvm/env/seq_lib/otbn_zero_state_err_urnd_vseq.sv +++ b/hw/ip/otbn/dv/uvm/env/seq_lib/otbn_zero_state_err_urnd_vseq.sv @@ -7,6 +7,14 @@ class otbn_zero_state_err_urnd_vseq extends otbn_single_vseq; `uvm_object_new task body(); + // Disable some assertions in the RTL which assert that the base registers don't get + // secure-wiped to zero. Of course, they do in this test, so we need to disable the assertions. + // + // Note that we can't disable them more specifically because there is at least one assertion for + // each register and you can't use a "for" loop because $assertoff() expects a hierarchical + // identifier for the assertion to control, rather than just an expression. + $assertoff(0, "tb.dut"); + do_end_addr_check = 0; fork begin diff --git a/hw/ip/otbn/dv/uvm/gen-binaries.py b/hw/ip/otbn/dv/uvm/gen-binaries.py index 47eb8ec65adb9..163e7faa8170d 100755 --- a/hw/ip/otbn/dv/uvm/gen-binaries.py +++ b/hw/ip/otbn/dv/uvm/gen-binaries.py @@ -106,6 +106,8 @@ def main() -> int: nargs='?', const='unlimited', help='Number of parallel jobs.') + parser.add_argument('--config', type=str, default='default', + help='Configuration to use for any RIG calls') parser.add_argument('--gen-only', action='store_true', help="Generate the ninja file but don't run it") @@ -151,7 +153,7 @@ def main() -> int: with open(os.path.join(args.destdir, ninja_fname), 'w') as ninja_handle: if args.src_dir is None: write_ninja_rnd(ninja_handle, toolchain, otbn_dir, args.count, - args.seed, args.size) + args.seed, args.size, args.config) else: write_ninja_fixed(ninja_handle, toolchain, otbn_dir, args.src_dir) @@ -176,7 +178,8 @@ def main() -> int: def write_ninja_rnd(handle: TextIO, toolchain: Toolchain, otbn_dir: str, - count: int, start_seed: int, size: int) -> None: + count: int, start_seed: int, + size: int, config: str) -> None: '''Write a build.ninja to build random binaries. The rules build everything in the same directory as the build.ninja file. @@ -191,8 +194,8 @@ def write_ninja_rnd(handle: TextIO, toolchain: Toolchain, otbn_dir: str, handle.write( 'rule rig-gen\n' - ' command = {rig} gen --size {size} --seed $seed -o $out\n\n'.format( - rig=otbn_rig, size=size)) + ' command = {rig} gen --size {size} --config {config} ' + '--seed $seed -o $out\n\n'.format(rig=otbn_rig, size=size, config=config)) handle.write('rule rig-asm\n' ' command = {rig} asm -o $seed $in\n\n'.format(rig=otbn_rig)) diff --git a/hw/ip/otbn/dv/uvm/otbn_model_agent/otbn_model_agent.sv b/hw/ip/otbn/dv/uvm/otbn_model_agent/otbn_model_agent.sv index 2c1c133d1647d..a6054ae5697ae 100644 --- a/hw/ip/otbn/dv/uvm/otbn_model_agent/otbn_model_agent.sv +++ b/hw/ip/otbn/dv/uvm/otbn_model_agent/otbn_model_agent.sv @@ -19,7 +19,9 @@ class otbn_model_agent extends dv_base_agent #( `DV_CHECK_FATAL(!cfg.is_active) // get otbn_model_if handle - if (!uvm_config_db#(virtual otbn_model_if)::get(this, "", "vif", cfg.vif)) begin + if (!uvm_config_db#( + virtual otbn_model_if#(.ImemSizeByte(otbn_reg_pkg::OTBN_IMEM_SIZE)) + )::get(this, "", "vif", cfg.vif)) begin `uvm_fatal(`gfn, "failed to get otbn_model_if handle from uvm_config_db") end endfunction diff --git a/hw/ip/otbn/dv/uvm/otbn_model_agent/otbn_model_agent_cfg.sv b/hw/ip/otbn/dv/uvm/otbn_model_agent/otbn_model_agent_cfg.sv index 683908f1faee5..43505bddbc920 100644 --- a/hw/ip/otbn/dv/uvm/otbn_model_agent/otbn_model_agent_cfg.sv +++ b/hw/ip/otbn/dv/uvm/otbn_model_agent/otbn_model_agent_cfg.sv @@ -5,7 +5,7 @@ class otbn_model_agent_cfg extends dv_base_agent_cfg; // interface handle used by driver, monitor & the sequencer, via cfg handle - virtual otbn_model_if vif; + virtual otbn_model_if#(.ImemSizeByte(otbn_reg_pkg::OTBN_IMEM_SIZE)) vif; `uvm_object_utils_begin(otbn_model_agent_cfg) `uvm_object_utils_end diff --git a/hw/ip/otbn/dv/uvm/otbn_sim_cfg.hjson b/hw/ip/otbn/dv/uvm/otbn_sim_cfg.hjson index da8702bed79d5..2902a4d643582 100644 --- a/hw/ip/otbn/dv/uvm/otbn_sim_cfg.hjson +++ b/hw/ip/otbn/dv/uvm/otbn_sim_cfg.hjson @@ -98,7 +98,7 @@ name: multi_err_dir: "{otbn_dir}/dv/otbnsim/test/simple/multi" run_modes: [ - // Run the random instruction generator and builds the one resulting binary + // Run the random instruction generator and build the one resulting binary // in {otbn_elf_dir}. If you override the otbn_elf_dir plusarg with // --run-opts, we'll still build the binary (but will ignore it). { @@ -108,6 +108,16 @@ name: ] } + // Run the random instruction generator in a "safe mode" and build + // resulting binary in {otbn_elf_dir}. Other than the choice of + // RIG config, this the same as build_otbn_rig_binary_mode. + { + name: build_otbn_rig_safe_binary_mode + pre_run_cmds: [ + "{gen_rnd} --count 1 {otbn_elf_dir} --config safe" + ] + } + // Run the random instruction generator several times and build the // resulting binaries in {otbn_elf_dir}. { @@ -293,6 +303,8 @@ name: { name: "otbn_pc_ctrl_flow_redun" uvm_test_seq: "otbn_pc_ctrl_flow_redun_vseq" + // Use a "safe" binary, in the hope that it will run for long + // enough that we can interrupt it with an error. en_run_modes: ["build_otbn_rig_binary_mode"] reseed: 5 } @@ -305,7 +317,7 @@ name: { name: "otbn_ctrl_redun" uvm_test_seq: "otbn_ctrl_redun_vseq" - en_run_modes: ["build_otbn_rig_binary_mode"] + en_run_modes: ["build_otbn_rig_safe_binary_mode"] reseed: 12 } { diff --git a/hw/ip/otbn/dv/uvm/tb.sv b/hw/ip/otbn/dv/uvm/tb.sv index 59218e170b546..40ed3dbb13d29 100644 --- a/hw/ip/otbn/dv/uvm/tb.sv +++ b/hw/ip/otbn/dv/uvm/tb.sv @@ -290,7 +290,8 @@ module tb; uvm_config_db#(virtual tl_if)::set(null, "*.env.m_tl_agent*", "vif", tl_if); uvm_config_db#(escalate_vif)::set(null, "*.env", "escalate_vif", escalate_if); uvm_config_db#(intr_vif)::set(null, "*.env", "intr_vif", intr_if); - uvm_config_db#(virtual otbn_model_if)::set(null, "*.env.model_agent", "vif", model_if); + uvm_config_db#(virtual otbn_model_if#(.ImemSizeByte(ImemSizeByte)))::set( + null, "*.env.model_agent", "vif", model_if); uvm_config_db#(virtual key_sideload_if#(keymgr_pkg::otbn_key_req_t))::set( null, "*.env.keymgr_sideload_agent", "vif", keymgr_if); diff --git a/hw/ip/otbn/dv/verilator/otbn_top_sim.cc b/hw/ip/otbn/dv/verilator/otbn_top_sim.cc index b768e09304d82..ecda32667ee3d 100644 --- a/hw/ip/otbn/dv/verilator/otbn_top_sim.cc +++ b/hw/ip/otbn/dv/verilator/otbn_top_sim.cc @@ -249,6 +249,15 @@ extern "C" void OtbnTopApplyLoopWarp() { loop_count_stack.push_back(loop_controller->loop_iterations_i); } + // We only have work to do if the RTL actually thinks that it's + // currently in a loop. This normally matches the model, but they + // disagree if we finish an execution while still running loop + // iterations. In that case, we don't need to do any fixing up: + // we've stopped anyway. + if (!loop_controller->current_loop_valid) { + return; + } + if (!loop_count_stack.empty()) { // There is a loop that's currently active. Its iteration count for the // next cycle is provided to the prefetcher via `prefetch_loop_iterations_o` diff --git a/hw/ip/otbn/dv/verilator/otbn_top_sim.sv b/hw/ip/otbn/dv/verilator/otbn_top_sim.sv index 92d59ac281da4..2d734bba743d3 100644 --- a/hw/ip/otbn/dv/verilator/otbn_top_sim.sv +++ b/hw/ip/otbn/dv/verilator/otbn_top_sim.sv @@ -25,7 +25,7 @@ module otbn_top_sim ( logic otbn_done, otbn_done_r, otbn_done_rr; core_err_bits_t core_err_bits; err_bits_t otbn_err_bits, otbn_err_bits_r, otbn_err_bits_rr; - logic otbn_start; + logic otbn_start, otbn_start_r; // Intialise otbn_start_done to 1 so that we only signal otbn_start after we have seen a reset. If // you don't do this, we start OTBN before the reset, which can generate confusing trace messages. @@ -75,7 +75,7 @@ module otbn_top_sim ( .clk_i ( IO_CLK ), .rst_ni ( IO_RST_N ), - .start_i ( otbn_start ), + .start_i ( otbn_start_r ), .done_o ( otbn_done ), .locking_o ( ), .secure_wipe_running_o ( secure_wipe_running ), @@ -218,6 +218,7 @@ module otbn_top_sim ( always @(posedge IO_CLK or negedge IO_RST_N) begin if (!IO_RST_N) begin otbn_start <= 1'b0; + otbn_start_r <= 1'b0; otbn_start_done <= 1'b0; otbn_done_r <= 1'b0; otbn_done_rr <= 1'b0; @@ -231,6 +232,7 @@ module otbn_top_sim ( otbn_start <= 1'b0; end + otbn_start_r <= otbn_start; otbn_done_r <= otbn_done; otbn_done_rr <= otbn_done_r; otbn_err_bits_r <= otbn_err_bits; @@ -426,7 +428,8 @@ module otbn_top_sim ( end bit err_latched; - assign err_latched = model_err_latched | done_mismatch_latched | err_bits_mismatch_latched; + assign err_latched = |{done_mismatch_latched, err_bits_mismatch_latched, cnt_mismatch_latched, + model_err_latched}; int bad_cycles; always_ff @(negedge IO_CLK or negedge IO_RST_N) begin diff --git a/hw/ip/otbn/pre_syn/syn_yosys.sh b/hw/ip/otbn/pre_syn/syn_yosys.sh index 58607840dc444..fa21e81ff595e 100755 --- a/hw/ip/otbn/pre_syn/syn_yosys.sh +++ b/hw/ip/otbn/pre_syn/syn_yosys.sh @@ -106,6 +106,7 @@ OT_DEP_SOURCES=( OT_DEP_PACKAGES=( "$LR_SYNTH_SRC_DIR"/../../top_earlgrey/rtl/*_pkg.sv "$LR_SYNTH_SRC_DIR"/../edn/rtl/*_pkg.sv + "$LR_SYNTH_SRC_DIR"/../csrng/rtl/*_pkg.sv "$LR_SYNTH_SRC_DIR"/../entropy_src/rtl/*_pkg.sv "$LR_SYNTH_SRC_DIR"/../lc_ctrl/rtl/*_pkg.sv "$LR_SYNTH_SRC_DIR"/../tlul/rtl/*_pkg.sv diff --git a/hw/ip/otbn/pre_syn/tcl/yosys_run_synth.tcl b/hw/ip/otbn/pre_syn/tcl/yosys_run_synth.tcl index 2422a9f6d98e2..f356ae8f41153 100644 --- a/hw/ip/otbn/pre_syn/tcl/yosys_run_synth.tcl +++ b/hw/ip/otbn/pre_syn/tcl/yosys_run_synth.tcl @@ -64,7 +64,7 @@ if { $lr_synth_flatten } { yosys "flatten" } -yosys "clean" +yosys "clean -purge" yosys "write_verilog $lr_synth_netlist_out" if { $lr_synth_timing_run } { diff --git a/hw/ip/otbn/rtl/otbn.sv b/hw/ip/otbn/rtl/otbn.sv index 11e81e2ea92df..03c0b01cc13fe 100644 --- a/hw/ip/otbn/rtl/otbn.sv +++ b/hw/ip/otbn/rtl/otbn.sv @@ -1230,33 +1230,45 @@ module otbn !rst_ni || u_otbn_core.urnd_reseed_err || u_otbn_core.u_otbn_start_stop_control.mubi_err_d) end - // WDR assertions for secure wipe - // 1. urnd_reseed_err disables the assertion because secure wipe finishes with failure and OTBN - // goes to LOCKED state immediately after this error which means that it's not guaranteed to have - // secure wiping complete. - // 2. mubi_err_d of start_stop_control disables the internal secure wipe related assertion - // because a fatal error affecting internal secure wiping could cause an immediate locking - // behaviour in which it's not guaranteed to see a succesful secure wipe. - for (genvar i = 0; i < NWdr; ++i) begin : gen_sec_wipe_wdr_asserts - // Initial secure wipe needs to initialise all registers to nonzero - `ASSERT(InitSecWipeNonZeroWideRegs_A, - $fell(busy_secure_wipe) |-> - u_otbn_core.u_otbn_rf_bignum.gen_rf_bignum_ff.u_otbn_rf_bignum_inner.rf[i] != - EccWideZeroWord, - clk_i, - !rst_ni || u_otbn_core.urnd_reseed_err || + // We have several assertions that check that secure wipe worked properly. However, we've also got + // some tests where we force nets, stopping it from working properly! That's fine, and the tests + // are checking that some other mechanism catches the problem. However, we don't want the + // simulation to die with a failed assertion, so we put everything in a named block which we can + // turn off with $assertoff. + // + // The silly-looking name is to avoid a lint warning. Verible (correctly) points out that + // SystemVerilog doesn't allow bare begin/end blocks at module level. So I cheated and put + // everything in an if(1) block. But this is treated as a generate block, and our lint rules + // therefore expect its name to start with a "g_". + if (1) begin : g_secure_wipe_assertions + // WDR assertions for secure wipe + // 1. urnd_reseed_err disables the assertion because secure wipe finishes with failure and OTBN + // goes to LOCKED state immediately after this error which means that it's not guaranteed to + // have secure wiping complete. + // 2. mubi_err_d of start_stop_control disables the internal secure wipe related assertion + // because a fatal error affecting internal secure wiping could cause an immediate locking + // behaviour in which it's not guaranteed to see a succesful secure wipe. + for (genvar i = 0; i < NWdr; ++i) begin : gen_sec_wipe_wdr_asserts + // Initial secure wipe needs to initialise all registers to nonzero + `ASSERT(InitSecWipeNonZeroWideRegs_A, + $fell(busy_secure_wipe) |-> + u_otbn_core.u_otbn_rf_bignum.gen_rf_bignum_ff.u_otbn_rf_bignum_inner.rf[i] != + EccWideZeroWord, + clk_i, + !rst_ni || u_otbn_core.urnd_reseed_err || + u_otbn_core.u_otbn_start_stop_control.mubi_err_d) + + // After execution, it's expected to see a change resulting with a nonzero register value + `ASSERT(SecWipeChangedWideRegs_A, + $rose(busy_secure_wipe) |-> ((##[0:$] + u_otbn_core.u_otbn_rf_bignum.gen_rf_bignum_ff.u_otbn_rf_bignum_inner.rf[i] != + EccWideZeroWord && + $changed( + u_otbn_core.u_otbn_rf_bignum.gen_rf_bignum_ff.u_otbn_rf_bignum_inner.rf[i])) + within ($rose(busy_secure_wipe) ##[0:$] $fell(busy_secure_wipe))), + clk_i, !rst_ni || u_otbn_core.urnd_reseed_err || u_otbn_core.u_otbn_start_stop_control.mubi_err_d) - - // After execution, it's expected to see a change resulting with a nonzero register value - `ASSERT(SecWipeChangedWideRegs_A, - $rose(busy_secure_wipe) |-> ((##[0:$] - u_otbn_core.u_otbn_rf_bignum.gen_rf_bignum_ff.u_otbn_rf_bignum_inner.rf[i] != - EccWideZeroWord && - $changed( - u_otbn_core.u_otbn_rf_bignum.gen_rf_bignum_ff.u_otbn_rf_bignum_inner.rf[i])) - within ($rose(busy_secure_wipe) ##[0:$] $fell(busy_secure_wipe))), - clk_i, !rst_ni || u_otbn_core.urnd_reseed_err || - u_otbn_core.u_otbn_start_stop_control.mubi_err_d) + end end // Secure wipe needs to invalidate call and loop stack, initialize MOD, ACC to nonzero and set diff --git a/hw/ip/otbn/rtl/otbn_alu_bignum.sv b/hw/ip/otbn/rtl/otbn_alu_bignum.sv index 9acc5d0816cf8..4db320808e1ed 100644 --- a/hw/ip/otbn/rtl/otbn_alu_bignum.sv +++ b/hw/ip/otbn/rtl/otbn_alu_bignum.sv @@ -100,7 +100,9 @@ module otbn_alu_bignum output logic reg_intg_violation_err_o, - input logic sec_wipe_mod_urnd_i, + input logic sec_wipe_mod_urnd_i, + input logic sec_wipe_running_i, + output logic sec_wipe_err_o, input flags_t mac_operation_flags_i, input flags_t mac_operation_flags_en_i, @@ -963,6 +965,9 @@ module otbn_alu_bignum assign reg_intg_violation_err_o = mod_used & |(mod_intg_err); `ASSERT_KNOWN(RegIntgErrKnown_A, reg_intg_violation_err_o) + // Detect and signal unexpected secure wipe signals. + assign sec_wipe_err_o = sec_wipe_mod_urnd_i & ~sec_wipe_running_i; + // Blanking Assertions // All blanking assertions are reset with predec_error or overall error in the whole system // -indicated by operation_commit_i port- as OTBN does not guarantee blanking in the case diff --git a/hw/ip/otbn/rtl/otbn_controller.sv b/hw/ip/otbn/rtl/otbn_controller.sv index 2c7ae6e859e32..600d6da17b6ba 100644 --- a/hw/ip/otbn/rtl/otbn_controller.sv +++ b/hw/ip/otbn/rtl/otbn_controller.sv @@ -147,6 +147,7 @@ module otbn_controller input logic secure_wipe_ack_i, input logic sec_wipe_zero_i, input logic secure_wipe_running_i, + input logic sec_wipe_err_i, input logic state_reset_i, output logic [31:0] insn_cnt_o, @@ -193,6 +194,7 @@ module otbn_controller logic executing; logic state_error, state_error_d, state_error_q; logic spurious_secure_wipe_ack_q, spurious_secure_wipe_ack_d; + logic sec_wipe_err_q, sec_wipe_err_d; logic mubi_err_q, mubi_err_d; logic insn_fetch_req_valid_raw; @@ -349,6 +351,18 @@ module otbn_controller ~secure_wipe_running_q & ~secure_wipe_running_i); + // Detect and latch unexpected secure wipe signals. + always_ff @(posedge clk_i or negedge rst_ni) begin + if (!rst_ni) begin + sec_wipe_err_q <= 1'b0; + end else begin + sec_wipe_err_q <= sec_wipe_err_d; + end + end + assign sec_wipe_err_d = sec_wipe_err_q | + sec_wipe_err_i | + (sec_wipe_zero_i & ~secure_wipe_running_i); + // Stall a cycle on loads to allow load data writeback to happen the following cycle. Stall not // required on stores as there is no response to deal with. assign mem_stall = lsu_load_req_raw; @@ -568,7 +582,7 @@ module otbn_controller assign fatal_software_err = software_err & software_errs_fatal_i; assign bad_internal_state_err = |{state_error_d, loop_hw_err, rf_base_call_stack_hw_err_i, rf_bignum_spurious_we_err, spurious_secure_wipe_ack_q, - mubi_err_q}; + sec_wipe_err_q, mubi_err_q}; assign reg_intg_violation_err = rf_bignum_intg_err | ispr_rdata_intg_err; assign key_invalid_err = ispr_rd_bignum_insn & insn_valid_i & key_invalid; assign illegal_insn_err = illegal_insn_static | rf_indirect_err; diff --git a/hw/ip/otbn/rtl/otbn_core.sv b/hw/ip/otbn/rtl/otbn_core.sv index 81159e153ff41..f736a5928570b 100644 --- a/hw/ip/otbn/rtl/otbn_core.sv +++ b/hw/ip/otbn/rtl/otbn_core.sv @@ -146,6 +146,7 @@ module otbn_core logic [31:0] rf_base_wr_data_no_intg_ctrl; logic [BaseIntgWidth-1:0] rf_base_wr_data_intg; logic rf_base_wr_data_intg_sel, rf_base_wr_data_intg_sel_ctrl; + logic rf_base_wr_sec_wipe_err; logic [4:0] rf_base_rd_addr_a; logic rf_base_rd_en_a; logic [BaseIntgWidth-1:0] rf_base_rd_data_a_intg; @@ -157,6 +158,7 @@ module otbn_core logic rf_base_call_stack_hw_err; logic rf_base_intg_err; logic rf_base_spurious_we_err; + logic rf_base_sec_wipe_err; alu_base_operation_t alu_base_operation; alu_base_comparison_t alu_base_comparison; @@ -185,6 +187,7 @@ module otbn_core logic [WLEN-1:0] rf_bignum_wr_data_no_intg_ctrl; logic [ExtWLEN-1:0] rf_bignum_wr_data_intg; logic rf_bignum_wr_data_intg_sel, rf_bignum_wr_data_intg_sel_ctrl; + logic rf_bignum_wr_sec_wipe_err; logic [WdrAw-1:0] rf_bignum_rd_addr_a; logic rf_bignum_rd_en_a; logic [ExtWLEN-1:0] rf_bignum_rd_data_a_intg; @@ -200,6 +203,7 @@ module otbn_core logic [WLEN-1:0] alu_bignum_operation_result; logic alu_bignum_selection_flag; logic alu_bignum_reg_intg_violation_err; + logic alu_bignum_sec_wipe_err; mac_bignum_operation_t mac_bignum_operation; logic [WLEN-1:0] mac_bignum_operation_result; @@ -208,6 +212,7 @@ module otbn_core logic mac_bignum_en; logic mac_bignum_commit; logic mac_bignum_reg_intg_violation_err; + logic mac_bignum_sec_wipe_err; ispr_e ispr_addr; logic [31:0] ispr_base_wdata; @@ -255,6 +260,7 @@ module otbn_core logic sec_wipe_acc_urnd; logic sec_wipe_mod_urnd; logic sec_wipe_zero; + logic sec_wipe_err; logic zero_flags; @@ -416,6 +422,12 @@ module otbn_core ispr_predec_error | rd_predec_error; + assign sec_wipe_err = |{rf_base_wr_sec_wipe_err, + rf_base_sec_wipe_err, + rf_bignum_wr_sec_wipe_err, + alu_bignum_sec_wipe_err, + mac_bignum_sec_wipe_err}; + // Controller: coordinate between functional units, prepare their inputs (e.g. by muxing between // operand sources), and post-process their outputs as needed. otbn_controller #( @@ -544,6 +556,7 @@ module otbn_core .secure_wipe_ack_i (secure_wipe_ack), .sec_wipe_zero_i (sec_wipe_zero), .secure_wipe_running_i (secure_wipe_running_o), + .sec_wipe_err_i (sec_wipe_err), .state_reset_i (state_reset), .insn_cnt_o (insn_cnt), @@ -684,6 +697,7 @@ module otbn_core .state_reset_i (state_reset), .sec_wipe_stack_reset_i(sec_wipe_zero), + .sec_wipe_running_i (secure_wipe_running_o), .wr_addr_i (rf_base_wr_addr), .wr_en_i (rf_base_wr_en), @@ -703,7 +717,8 @@ module otbn_core .call_stack_sw_err_o(rf_base_call_stack_sw_err), .call_stack_hw_err_o(rf_base_call_stack_hw_err), .intg_err_o (rf_base_intg_err), - .spurious_we_err_o (rf_base_spurious_we_err) + .spurious_we_err_o (rf_base_spurious_we_err), + .sec_wipe_err_o (rf_base_sec_wipe_err) ); assign rf_base_wr_addr = sec_wipe_base ? sec_wipe_addr : rf_base_wr_addr_ctrl; @@ -726,6 +741,8 @@ module otbn_core end end + assign rf_base_wr_sec_wipe_err = sec_wipe_base & ~secure_wipe_running_o; + otbn_alu_base u_otbn_alu_base ( .clk_i, .rst_ni, @@ -799,6 +816,8 @@ module otbn_core end end + assign rf_bignum_wr_sec_wipe_err = sec_wipe_wdr_q & ~secure_wipe_running_o; + otbn_alu_bignum u_otbn_alu_bignum ( .clk_i, .rst_ni, @@ -830,6 +849,8 @@ module otbn_core .reg_intg_violation_err_o(alu_bignum_reg_intg_violation_err), .sec_wipe_mod_urnd_i(sec_wipe_mod_urnd), + .sec_wipe_running_i (secure_wipe_running_o), + .sec_wipe_err_o (alu_bignum_sec_wipe_err), .mac_operation_flags_i (mac_bignum_operation_flags), .mac_operation_flags_en_i(mac_bignum_operation_flags_en), @@ -858,6 +879,8 @@ module otbn_core .urnd_data_i (urnd_data), .sec_wipe_acc_urnd_i(sec_wipe_acc_urnd), + .sec_wipe_running_i (secure_wipe_running_o), + .sec_wipe_err_o (mac_bignum_sec_wipe_err), .mac_en_i (mac_bignum_en), .mac_commit_i(mac_bignum_commit), @@ -959,17 +982,18 @@ module otbn_core mubi4_test_true_loose(start_stop_escalate_en) && mubi4_test_false_strict(escalate_en_i) |=> err_bits_q) - // The following assertions allow up to 400 cycles from escalation until the start/stop FSM locks. - // This is a long time, but it's necessary because following an escalation the start/stop FSM goes - // through two rounds of secure wiping with random data with an URND reseed in between. Depending - // on the delay configured in the EDN model, the reseed alone can take 200 cycles. + // The following assertions allow up to 4000 cycles from escalation until the start/stop FSM + // locks. This is to allow the core to do a secure wipe (which involves waiting for data from the + // EDN) before it changes status. The long wait here won't mask problems because the logic of "ask + // for URND data" and "do the secure wipe once it arrives" is duplicated in the Python model, + // against which the RTL is checked. `ASSERT(OtbnStartStopGlobalEscCntrMeasure_A, err_bits_q && mubi4_test_true_loose(escalate_en_i) - && mubi4_test_true_loose(start_stop_escalate_en)|=> ##[1:400] + && mubi4_test_true_loose(start_stop_escalate_en)|=> ##[1:4000] u_otbn_start_stop_control.state_q == otbn_pkg::OtbnStartStopStateLocked) `ASSERT(OtbnStartStopLocalEscCntrMeasure_A, err_bits_q && mubi4_test_false_strict(escalate_en_i) - && mubi4_test_true_loose(start_stop_escalate_en) |=> ##[1:400] + && mubi4_test_true_loose(start_stop_escalate_en) |=> ##[1:4000] u_otbn_start_stop_control.state_q == otbn_pkg::OtbnStartStopStateLocked) // In contrast to the start/stop FSM, the controller FSM should lock quickly after an escalation, diff --git a/hw/ip/otbn/rtl/otbn_mac_bignum.sv b/hw/ip/otbn/rtl/otbn_mac_bignum.sv index 072d6af784324..5ef8f3781d0d1 100644 --- a/hw/ip/otbn/rtl/otbn_mac_bignum.sv +++ b/hw/ip/otbn/rtl/otbn_mac_bignum.sv @@ -22,8 +22,10 @@ module otbn_mac_bignum input mac_predec_bignum_t mac_predec_bignum_i, output logic predec_error_o, - input logic [WLEN-1:0] urnd_data_i, - input logic sec_wipe_acc_urnd_i, + input logic [WLEN-1:0] urnd_data_i, + input logic sec_wipe_acc_urnd_i, + input logic sec_wipe_running_i, + output logic sec_wipe_err_o, output logic [ExtWLEN-1:0] ispr_acc_intg_o, input logic [ExtWLEN-1:0] ispr_acc_wr_data_intg_i, @@ -236,5 +238,7 @@ module otbn_mac_bignum assign predec_error_o = |{expected_op_en != mac_predec_bignum_i.op_en, expected_acc_rd_en != mac_predec_bignum_i.acc_rd_en}; + assign sec_wipe_err_o = sec_wipe_acc_urnd_i & ~sec_wipe_running_i; + `ASSERT(NoISPRAccWrAndMacEn, ~(ispr_acc_wr_en_i & mac_en_i)) endmodule diff --git a/hw/ip/otbn/rtl/otbn_rf_base.sv b/hw/ip/otbn/rtl/otbn_rf_base.sv index 89303d3c2c36b..0b9a8144771eb 100644 --- a/hw/ip/otbn/rtl/otbn_rf_base.sv +++ b/hw/ip/otbn/rtl/otbn_rf_base.sv @@ -41,6 +41,7 @@ module otbn_rf_base input logic state_reset_i, input logic sec_wipe_stack_reset_i, + input logic sec_wipe_running_i, input logic [4:0] wr_addr_i, input logic wr_en_i, @@ -62,7 +63,8 @@ module otbn_rf_base output logic call_stack_sw_err_o, output logic call_stack_hw_err_o, output logic intg_err_o, - output logic spurious_we_err_o + output logic spurious_we_err_o, + output logic sec_wipe_err_o ); localparam int unsigned CallStackRegIndex = 1; localparam int unsigned CallStackDepth = 8; @@ -236,4 +238,6 @@ module otbn_rf_base // secure wipe. `ASSERT(OtbnRfBaseRdAKnown, rd_en_a_i && !pop_stack_a |-> !$isunknown(rd_data_a_raw_intg)) `ASSERT(OtbnRfBaseRdBKnown, rd_en_b_i && !pop_stack_b |-> !$isunknown(rd_data_b_raw_intg)) + + assign sec_wipe_err_o = sec_wipe_stack_reset_i & ~sec_wipe_running_i; endmodule diff --git a/hw/ip/otbn/rtl/otbn_start_stop_control.sv b/hw/ip/otbn/rtl/otbn_start_stop_control.sv index 7c0b7e64d1efa..8b7a5ae39df1f 100644 --- a/hw/ip/otbn/rtl/otbn_start_stop_control.sv +++ b/hw/ip/otbn/rtl/otbn_start_stop_control.sv @@ -81,6 +81,7 @@ module otbn_start_stop_control logic mubi_err_q, mubi_err_d; logic urnd_reseed_err_q, urnd_reseed_err_d; logic secure_wipe_error_q, secure_wipe_error_d; + logic secure_wipe_running_q, secure_wipe_running_d; logic skip_reseed_q; logic addr_cnt_inc; @@ -167,7 +168,7 @@ module otbn_start_stop_control sec_wipe_zero_o = 1'b0; addr_cnt_inc = 1'b0; secure_wipe_ack_o = 1'b0; - secure_wipe_running_o = 1'b0; + secure_wipe_running_d = 1'b0; state_error_d = state_error_q; allow_secure_wipe = 1'b0; expect_secure_wipe = 1'b0; @@ -178,7 +179,7 @@ module otbn_start_stop_control unique case (state_q) OtbnStartStopStateInitial: begin - secure_wipe_running_o = 1'b1; + secure_wipe_running_d = 1'b1; urnd_reseed_req_o = 1'b1; if (rma_request) begin // If we get an RMA request before the URND got reseeded, proceed with the initial secure @@ -205,7 +206,8 @@ module otbn_start_stop_control if (rma_request) begin // Do not reseed URND before secure wipe for RMA, as the entropy complex may not be able // to provide entropy at this point. - state_d = OtbnStartStopSecureWipeWdrUrnd; + secure_wipe_running_d = 1'b1; + state_d = OtbnStartStopSecureWipeWdrUrnd; // As we don't reseed URND, there's no point in doing two rounds of wiping, so we // pretend that the first round is already the second round. wipe_after_urnd_refresh_d = MuBi4True; @@ -227,7 +229,7 @@ module otbn_start_stop_control // wait for the ACK and then do a secure wipe. allow_secure_wipe = 1'b1; expect_secure_wipe = 1'b1; - secure_wipe_running_o = 1'b1; + secure_wipe_running_d = 1'b1; if (urnd_reseed_ack_i) begin state_d = OtbnStartStopSecureWipeWdrUrnd; end @@ -244,7 +246,7 @@ module otbn_start_stop_control // wait for the ACK and then do a secure wipe. allow_secure_wipe = 1'b1; expect_secure_wipe = 1'b1; - secure_wipe_running_o = 1'b1; + secure_wipe_running_d = 1'b1; if (urnd_reseed_ack_i) begin state_d = OtbnStartStopSecureWipeWdrUrnd; end @@ -256,7 +258,8 @@ module otbn_start_stop_control allow_secure_wipe = 1'b1; if (stop) begin - state_d = OtbnStartStopSecureWipeWdrUrnd; + secure_wipe_running_d = 1'b1; + state_d = OtbnStartStopSecureWipeWdrUrnd; end end // SEC_CM: DATA_REG_SW.SEC_WIPE @@ -268,7 +271,7 @@ module otbn_start_stop_control sec_wipe_wdr_urnd_o = 1'b1; allow_secure_wipe = 1'b1; expect_secure_wipe = 1'b1; - secure_wipe_running_o = 1'b1; + secure_wipe_running_d = 1'b1; // Count one extra cycle when wiping the WDR, because the wipe signals to the WDR // (`sec_wipe_wdr_o` and `sec_wipe_wdr_urnd_o`) are flopped once but the wipe signals to the @@ -293,7 +296,7 @@ module otbn_start_stop_control addr_cnt_inc = 1'b1; allow_secure_wipe = 1'b1; expect_secure_wipe = 1'b1; - secure_wipe_running_o = 1'b1; + secure_wipe_running_d = 1'b1; // The first two clock cycles are used to write random data to accumulator and modulus. sec_wipe_acc_urnd_o = (addr_cnt_q == 6'b000000); sec_wipe_mod_urnd_o = (addr_cnt_q == 6'b000001); @@ -307,10 +310,9 @@ module otbn_start_stop_control // Writing zeros to the CSRs and reset the stack. The other registers are intentionally not // overwritten with zero. OtbnStartStopSecureWipeAllZero: begin - sec_wipe_zero_o = 1'b1; - allow_secure_wipe = 1'b1; - expect_secure_wipe = 1'b1; - secure_wipe_running_o = 1'b1; + sec_wipe_zero_o = 1'b1; + allow_secure_wipe = 1'b1; + expect_secure_wipe = 1'b1; // Leave this state after a single cycle, which is sufficient to reset the CSRs and the // stack. @@ -318,12 +320,14 @@ module otbn_start_stop_control // This is the first round of wiping with random numbers, refresh URND and do a second // round. state_d = OtbnStartStopStateUrndRefresh; + secure_wipe_running_d = 1'b1; wipe_after_urnd_refresh_d = MuBi4True; end else begin // This is the second round of wiping with random numbers, so the secure wipe is // complete. state_d = OtbnStartStopSecureWipeComplete; - secure_wipe_ack_o = 1'b1; + secure_wipe_running_d = 1'b0; + secure_wipe_ack_o = 1'b1; end end OtbnStartStopSecureWipeComplete: begin @@ -398,11 +402,13 @@ module otbn_start_stop_control always_ff @(posedge clk_i or negedge rst_ni) begin if (!rst_ni) begin - addr_cnt_q <= 6'd0; - init_sec_wipe_done_q <= 1'b0; + addr_cnt_q <= 6'd0; + init_sec_wipe_done_q <= 1'b0; + secure_wipe_running_q <= 1'b1; end else begin - addr_cnt_q <= addr_cnt_d; - init_sec_wipe_done_q <= init_sec_wipe_done_d; + addr_cnt_q <= addr_cnt_d; + init_sec_wipe_done_q <= init_sec_wipe_done_d; + secure_wipe_running_q <= secure_wipe_running_d; end end @@ -421,6 +427,7 @@ module otbn_start_stop_control assign sec_wipe_addr_o = addr_cnt_q[4:0]; `ASSERT(NoSecWipeAbove32Bit_A, addr_cnt_q[5] |-> (!sec_wipe_wdr_o && !sec_wipe_acc_urnd_o)) + // SEC_CM: START_STOP_CTRL.STATE.CONSISTENCY // A check for spurious or dropped secure wipe requests. // We only expect to start a secure wipe when running. assign spurious_secure_wipe_req = secure_wipe_req_i & ~allow_secure_wipe; @@ -455,6 +462,8 @@ module otbn_start_stop_control assign rma_ack_o = rma_ack_q; + assign secure_wipe_running_o = secure_wipe_running_q; + `ASSERT(StartStopStateValid_A, state_q inside {OtbnStartStopStateInitial, OtbnStartStopStateHalt, diff --git a/hw/ip/otbn/util/Makefile b/hw/ip/otbn/util/Makefile index 2cbbddca84f41..4eca5f036eacc 100644 --- a/hw/ip/otbn/util/Makefile +++ b/hw/ip/otbn/util/Makefile @@ -16,11 +16,12 @@ $(build-dir) $(lint-build-dir): mkdir -p $@ pylibs := $(wildcard shared/*.py docs/*.py) -pyscripts := yaml_to_doc.py otbn_as.py otbn_ld.py otbn_objdump.py +pyscripts := yaml_to_doc.py otbn_as.py otbn_ld.py otbn_objdump.py docs/md_isrs.py lint-stamps := $(foreach s,$(pyscripts),$(lint-build-dir)/$(s).stamp) $(lint-build-dir)/%.stamp: % $(pylibs) | $(lint-build-dir) mypy --strict --config-file=mypy.ini $< + mkdir -p $(@D) touch $@ .PHONY: lint diff --git a/hw/ip/otbn/util/docs/md_isrs.py b/hw/ip/otbn/util/docs/md_isrs.py new file mode 100755 index 0000000000000..930092c2723b7 --- /dev/null +++ b/hw/ip/otbn/util/docs/md_isrs.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python3 +# Copyright lowRISC contributors. +# Licensed under the Apache License, Version 2.0, see LICENSE for details. +# SPDX-License-Identifier: Apache-2.0 + +# A tool for generating the CSR and WSR documentation (for use in README.md) +# from the yaml files that contain it. + +import argparse +import os +import sys +from typing import List + +sys.path.append(os.path.normpath(os.path.join(os.path.dirname(__file__), + '../../util'))) + +from shared.isr import Isr, read_isrs # noqa: E402 + + +def print_thead(indent: str, columns: List[str]) -> None: + print(f'{indent}') + print(f'{indent} ') + for column in columns: + print(f'{indent} {column}') + print(f'{indent} ') + print(f'{indent}') + + +def print_isr(indent: str, isr: Isr, add_anchors: bool) -> None: + uc_name = isr.name.upper() + lc_key = isr.name.lower().replace('_', '-') + + print(f'{indent}') + print(f'{indent} 0x{isr.address:X}') + print(f'{indent} {isr.access_str()}') + if add_anchors: + print(f'{indent} {uc_name}') + else: + print(f'{indent} {uc_name}') + print(f'{indent} ') + + doc_lines = isr.doc.splitlines() + if isr.bits: + doc_lines += ['', + ' ', + ' ', + ' ', + ' '] + for k, v in isr.bits.items(): + doc_lines.append(f' ') + doc_lines += [' ', '
BitDescription
{k}{v}
'] + + for line in doc_lines: + if line == '': + line = '
' + print(indent + ' ' * 4 + line) + + print(f'{indent} ') + print(f'{indent}') + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument('--add-anchors', action='store_true') + parser.add_argument('yml') + + args = parser.parse_args() + + print('') + print_thead(' ' * 2, ['Number', 'Access', 'Name', 'Description']) + print(' ') + for isr in read_isrs(args.yml): + print_isr(' ' * 2, isr, args.add_anchors) + + print(' ') + print('
') + + +if __name__ == '__main__': + main() diff --git a/hw/ip/otbn/util/otbn_as.py b/hw/ip/otbn/util/otbn_as.py index 959fd2ca665dc..d95291a1aec04 100755 --- a/hw/ip/otbn/util/otbn_as.py +++ b/hw/ip/otbn/util/otbn_as.py @@ -796,8 +796,11 @@ def gen_line(self, insn: Insn, op_to_expr: Dict[str, return # If this instruction comes from the rv32i instruction set, we can just - # pass it straight through. - if insn.rv32i: + # pass it straight through. The extra check for "uses_isr" is needed to + # support ISR names in instructions like CSRRW. This instruction is + # part of the rv32i instruction set, but we want to do some work to + # resolve CSR names. + if insn.rv32i and not insn.uses_isr: self.out_handle.write('.line {}\n{}\n'.format( self.line_number - 1, reconstructed)) return diff --git a/hw/ip/otbn/util/shared/BUILD b/hw/ip/otbn/util/shared/BUILD index e770da5cea9e7..267937b5c2de4 100644 --- a/hw/ip/otbn/util/shared/BUILD +++ b/hw/ip/otbn/util/shared/BUILD @@ -105,6 +105,14 @@ py_library( ], ) +py_library( + name = "isr", + srcs = ["isr.py"], + deps = [ + "//util/serialize:parse_helpers", + ], +) + py_library( name = "insn_yaml", srcs = ["insn_yaml.py"], @@ -112,6 +120,7 @@ py_library( ":encoding", ":encoding_scheme", ":information_flow", + ":isr", ":lsu_desc", ":operand", ":syntax", @@ -151,6 +160,7 @@ py_library( deps = [ ":encoding", ":encoding_scheme", + ":isr", "//util/serialize:parse_helpers", ], ) diff --git a/hw/ip/otbn/util/shared/constants.py b/hw/ip/otbn/util/shared/constants.py index 122bef14904fb..4fb2d4dafccea 100644 --- a/hw/ip/otbn/util/shared/constants.py +++ b/hw/ip/otbn/util/shared/constants.py @@ -85,9 +85,9 @@ def update_insn(self, insn: Insn, op_vals: Dict[str, int]) -> None: Currently, this procedure supports only a limited set of instructions. Since constant values only need to be known in order to decode indirect - references to WDRs and loop counts, this set is chosen based on operations - likely to happen to those registers: `addi`, `lui`, and bignum instructions - containing `_inc` op_vals. + references to WDRs and loop counts, this set is chosen based on + operations likely to happen to those registers: `addi`, `lui`, and + bignum instructions containing `_inc` op_vals. ''' new_values = {} if insn.mnemonic == 'addi': @@ -110,10 +110,10 @@ def update_insn(self, insn: Insn, op_vals: Dict[str, int]) -> None: if inc_name in self.values: new_values[inc_name] = self.values[inc_name] + 1 - # If the instruction's information-flow graph indicates that we updated any - # constant register other than the ones handled above, the value of that - # register can no longer be determined; remove it from the constants - # dictionary. + # If the instruction's information-flow graph indicates that we updated + # any constant register other than the ones handled above, the value of + # that register can no longer be determined; remove it from the + # constants dictionary. iflow = insn.iflow.evaluate(op_vals, self.values) self.removemany(iflow.all_sinks()) diff --git a/hw/ip/otbn/util/shared/control_flow.py b/hw/ip/otbn/util/shared/control_flow.py index 09f765da824c6..785b89f270601 100644 --- a/hw/ip/otbn/util/shared/control_flow.py +++ b/hw/ip/otbn/util/shared/control_flow.py @@ -308,9 +308,9 @@ def _get_next_control_locations(insn: Insn, operands: Dict[str, int], elif insn.mnemonic == 'ecall': return [Ecall()] - raise RuntimeError( - 'Unrecognized control flow instruction (straight-line=false) at PC {:#x}: {}' - .format(pc, insn.disassemble(pc, operands))) + raise RuntimeError('Unrecognized control flow instruction ' + '(straight-line=false) at PC {:#x}: {}' + .format(pc, insn.disassemble(pc, operands))) def _populate_control_graph(graph: ControlGraph, program: OTBNProgram, diff --git a/hw/ip/otbn/util/shared/information_flow_analysis.py b/hw/ip/otbn/util/shared/information_flow_analysis.py index fb3027fb275eb..ebfa10297e979 100755 --- a/hw/ip/otbn/util/shared/information_flow_analysis.py +++ b/hw/ip/otbn/util/shared/information_flow_analysis.py @@ -8,7 +8,8 @@ from .cache import Cache, CacheEntry from .constants import ConstantContext, get_op_val_str -from .control_flow import ControlLoc, ControlGraph, Cycle, Ecall, ImemEnd, LoopStart, Ret +from .control_flow import (ControlLoc, ControlGraph, Cycle, Ecall, + ImemEnd, LoopStart, Ret) from .decode import OTBNProgram from .information_flow import InformationFlowGraph from .insn_yaml import Insn @@ -410,7 +411,7 @@ def _get_iflow(program: OTBNProgram, graph: ControlGraph, start_pc: int, constants.update_insn(last_insn, last_op_vals) # We're only returning constants that are the same in all RET branches - common_constants = None + common_consts = None for loc in edges: if isinstance(loc, Ecall) or isinstance(loc, ImemEnd): @@ -424,8 +425,8 @@ def _get_iflow(program: OTBNProgram, graph: ControlGraph, start_pc: int, '{:#x})'.format(section.end, loop_end_pc)) # Ret nodes are expected to be the only edge assert len(edges) == 1 - # Since this is the only edge, common_constants must be unset - common_constants = constants + # Since this is the only edge, common_consts must be unset + common_consts = constants return_iflow.update(iflow) elif isinstance(loc, Cycle): # Add the flow from start PC to this cyclic PC to the existing @@ -448,10 +449,10 @@ def _get_iflow(program: OTBNProgram, graph: ControlGraph, start_pc: int, # If there were any return paths, take values on which existing and # recursive constants agree. if rec_return_iflow.exists: - if common_constants is None: - common_constants = local_constants + if common_consts is None: + common_consts = local_constants else: - common_constants = common_constants.intersect(local_constants) + common_consts = common_consts.intersect(local_constants) # Update return_iflow with the current iflow composed with return # paths @@ -462,13 +463,13 @@ def _get_iflow(program: OTBNProgram, graph: ControlGraph, start_pc: int, section.end, type(loc))) # Update used_constants to include any constant dependencies of - # common_constants, since common_constants will be cached - if common_constants is not None: - # If there is no return branch, we would expect common_constants to be + # common_consts, since common_consts will be cached + if common_consts is not None: + # If there is no return branch, we would expect common_consts to be # None. assert return_iflow.exists used_constants.update( - return_iflow.sources_for_any(common_constants.values.keys())) + return_iflow.sources_for_any(common_consts.values.keys())) # If this PC is the start of one of the cycles we're currently processing, # see if it can be finalized. @@ -490,7 +491,8 @@ def _get_iflow(program: OTBNProgram, graph: ControlGraph, start_pc: int, # If all starting constants were stable, just loop() the information # flow graph for this cycle to get the combined flow for all paths that - # cycle back to this point (including no cycling, i.e. the empty graph). + # cycle back to this point (including no cycling, i.e. the empty + # graph). cycle_iflow = cycle_iflow.loop() # The final information flow for all paths is the graph of any valid @@ -520,15 +522,17 @@ def _get_iflow(program: OTBNProgram, graph: ControlGraph, start_pc: int, control_deps.pop('x0', None) # Update the cache and return - out = (used_constants, return_iflow, program_end_iflow, common_constants, + out = (used_constants, return_iflow, program_end_iflow, common_consts, cycles, control_deps) _get_iflow_cache_update(start_pc, start_constants, out, cache) return out -def get_subroutine_iflow(program: OTBNProgram, graph: ControlGraph, - subroutine_name: str, start_constants: Dict[str,int]) -> SubroutineIFlow: +def get_subroutine_iflow(program: OTBNProgram, + graph: ControlGraph, + subroutine_name: str, + start_constants: Dict[str, int]) -> SubroutineIFlow: '''Gets the information-flow graphs for the subroutine. Returns three items: @@ -542,7 +546,7 @@ def get_subroutine_iflow(program: OTBNProgram, graph: ControlGraph, ''' if 'x0' in start_constants and start_constants['x0'] != 0: raise ValueError('The x0 register is always 0; cannot require ' - f'x0={start_constants["x0"]}') + f'x0={start_constants["x0"]}') start_constants['x0'] = 0 constants = ConstantContext(start_constants) start_pc = program.get_pc_at_symbol(subroutine_name) diff --git a/hw/ip/otbn/util/shared/insn_yaml.py b/hw/ip/otbn/util/shared/insn_yaml.py index 0645887c09849..5e9f4a7b37937 100644 --- a/hw/ip/otbn/util/shared/insn_yaml.py +++ b/hw/ip/otbn/util/shared/insn_yaml.py @@ -16,6 +16,7 @@ from .encoding import Encoding from .encoding_scheme import EncSchemes from .information_flow import InsnInformationFlow +from .isr import Isr, read_isrs, IsrMap, IsrMaps from .lsu_desc import LSUDesc from .operand import Operand from .syntax import InsnSyntax @@ -24,10 +25,11 @@ class Insn: def __init__(self, yml: object, - encoding_schemes: Optional[EncSchemes]) -> None: + encoding_schemes: Optional[EncSchemes], + isrs: Optional[IsrMaps]) -> None: yd = check_keys(yml, 'instruction', ['mnemonic', 'operands'], - ['group', 'rv32i', 'synopsis', + ['group', 'rv32i', 'uses_isr', 'synopsis', 'syntax', 'doc', 'errs', 'note', 'encoding', 'glued-ops', 'literal-pseudo-op', 'python-pseudo-op', 'lsu', @@ -48,7 +50,7 @@ def __init__(self, self.encoding = Encoding(encoding_yml, encoding_schemes, self.mnemonic) - self.operands = [Operand(y, self.mnemonic, self.encoding) + self.operands = [Operand(y, self.mnemonic, self.encoding, isrs) for y in check_list(yd['operands'], 'operands for ' + what)] self.name_to_operand = index_list('operands for ' + what, @@ -82,6 +84,8 @@ def __init__(self, self.rv32i = check_bool(yd.get('rv32i', False), 'rv32i flag for ' + what) + self.uses_isr = check_bool(yd.get('uses_isr', False), + 'uses_isr flag for ' + what) self.glued_ops = check_bool(yd.get('glued-ops', False), 'glued-ops flag for ' + what) self.synopsis = get_optional_str(yd, 'synopsis', what) @@ -162,8 +166,9 @@ def __init__(self, self.straight_line = yd.get('straight-line', True) + iflow_what = 'iflow field for {}'.format(what) self.iflow = InsnInformationFlow.from_yaml(yd.get('iflow', None), - 'iflow field for {}'.format(what), self.operands) + iflow_what, self.operands) def enc_vals_to_op_vals(self, cur_pc: int, @@ -230,14 +235,15 @@ def __init__(self) -> None: 'mnemonic': 'dummy-insn', 'operands': [] } - super().__init__(fake_yml, None) + super().__init__(fake_yml, None, None) class InsnGroup: def __init__(self, path: str, encoding_schemes: Optional[EncSchemes], - yml: object) -> None: + yml: object, + isrs: Optional[IsrMaps]) -> None: yd = check_keys(yml, 'insn-group', ['key', 'title', 'doc', 'insns'], []) @@ -251,7 +257,7 @@ def __init__(self, insns_rel_path)) insns_yaml = load_yaml(insns_path, insns_what) try: - self.insns = [Insn(i, encoding_schemes) + self.insns = [Insn(i, encoding_schemes, isrs) for i in check_list(insns_yaml, insns_what)] except ValueError as err: raise RuntimeError('Invalid schema in YAML file at {!r}: {}' @@ -262,8 +268,9 @@ class InsnGroups: def __init__(self, path: str, encoding_schemes: Optional[EncSchemes], - yml: object) -> None: - self.groups = [InsnGroup(path, encoding_schemes, y) + yml: object, + isrs: Optional[IsrMaps]) -> None: + self.groups = [InsnGroup(path, encoding_schemes, y, isrs) for y in check_list(yml, 'insn-groups')] if not self.groups: raise ValueError('Empty list of instruction groups: ' @@ -273,7 +280,10 @@ def __init__(self, class InsnsFile: - def __init__(self, path: str, yml: object) -> None: + def __init__(self, + path: str, + yml: object, + isrs: Optional[IsrMaps]) -> None: yd = check_keys(yml, 'top-level', ['insn-groups'], ['encoding-schemes']) @@ -293,7 +303,8 @@ def __init__(self, path: str, yml: object) -> None: self.groups = InsnGroups(path, self.encoding_schemes, - yd['insn-groups']) + yd['insn-groups'], + isrs) # The instructions are grouped by instruction group and stored in # self.groups. Most of the time, however, we just want "an OTBN @@ -377,19 +388,35 @@ def mnem_for_word(self, word: int) -> Optional[str]: return ret -def load_file(path: str) -> InsnsFile: +def load_file(path: str, isrs: Optional[IsrMaps]) -> InsnsFile: '''Load the YAML file at path. Raises a RuntimeError on syntax or schema error. ''' try: - return InsnsFile(path, load_yaml(path, None)) + return InsnsFile(path, load_yaml(path, None), isrs) except ValueError as err: raise RuntimeError('Invalid schema in YAML file at {!r}: {}' .format(path, err)) from None +def make_isr_dict(path: str) -> IsrMap: + '''Load a YAML file at path and return a map from name to Isr.''' + try: + name_to_isr = {} # type: Dict[str, Isr] + for isr in read_isrs(path): + name = isr.name.lower() + if name in name_to_isr: + raise ValueError(f'Duplicate ISRs with name {name}.') + name_to_isr[name] = isr + return IsrMap(name_to_isr) + + except ValueError as err: + raise RuntimeError('Invalid schema in ISR YAML file at {!r}: {}' + .format(path, err)) from None + + _DEFAULT_INSNS_FILE = None # type: Optional[InsnsFile] @@ -400,10 +427,16 @@ def load_insns_yaml() -> InsnsFile: ''' global _DEFAULT_INSNS_FILE - if _DEFAULT_INSNS_FILE is None: - dirname = os.path.dirname(__file__) - rel_path = os.path.join('..', '..', 'data', 'insns.yml') - insns_yml = os.path.normpath(os.path.join(dirname, rel_path)) - _DEFAULT_INSNS_FILE = load_file(insns_yml) + if _DEFAULT_INSNS_FILE is not None: + return _DEFAULT_INSNS_FILE + + dirname = os.path.dirname(__file__) + data_path = os.path.normpath(os.path.join(dirname, '..', '..', 'data')) + + csrs = make_isr_dict(os.path.join(data_path, 'csr.yml')) + wsrs = make_isr_dict(os.path.join(data_path, 'wsr.yml')) + + _DEFAULT_INSNS_FILE = load_file(os.path.join(data_path, 'insns.yml'), + IsrMaps(csrs, wsrs)) return _DEFAULT_INSNS_FILE diff --git a/hw/ip/otbn/util/shared/isr.py b/hw/ip/otbn/util/shared/isr.py new file mode 100644 index 0000000000000..957635d3d5b7a --- /dev/null +++ b/hw/ip/otbn/util/shared/isr.py @@ -0,0 +1,70 @@ +# Copyright lowRISC contributors. +# Licensed under the Apache License, Version 2.0, see LICENSE for details. +# SPDX-License-Identifier: Apache-2.0 + +from typing import Dict, Iterable +from serialize.parse_helpers import (check_keys, check_str, check_list, + check_int, check_bool, load_yaml) + + +class Isr: + def __init__(self, name: str, address: int, + doc: str, read_only: bool, bits: Dict[int, str]) -> None: + self.name = name + self.address = address + self.doc = doc + self.read_only = read_only + self.bits = bits + + def access_str(self) -> str: + return 'RO' if self.read_only else 'RW' + + @staticmethod + def from_yml(yml: object) -> 'Isr': + yd = check_keys(yml, 'isr', + ['name', 'address', 'doc'], + ['read-only', 'bits']) + name = check_str(yd['name'], 'name') + address = check_int(yd['address'], 'index/address') + if address < 0: + raise ValueError(f'Address of ISR {name:!r} is negative.') + read_only = check_bool(yd.get('read-only', False), 'read-only flag') + doc = check_str(yd['doc'], 'documentation') + pre_bits = yd.get('bits', {}) + if not isinstance(pre_bits, dict): + raise ValueError(f'bits field of ISR {name:!r} is not a dict.') + bits = {} # type: Dict[int, str] + for k, v in pre_bits.items(): + k_int = check_int(k, 'address of ISR bit') + v_str = check_str(v, f'description of ISR bit {k}') + bits[k_int] = v_str + + return Isr(name, address, doc, read_only, bits) + + +def read_isrs(path: str) -> Iterable[Isr]: + isrs = {} # type: Dict[int, Isr] + yml = load_yaml(path, None) + for isr_yml in check_list(yml, 'contents of ISR file'): + isr = Isr.from_yml(isr_yml) + if isr.address in isrs: + raise ValueError(f'Multiple ISRs at address {isr.address}') + isrs[isr.address] = isr + + return isrs.values() + + +class IsrMap: + def __init__(self, isrs: Dict[str, Isr]): + self.name_to_isr = isrs + self.addr_to_isr = {} # type: Dict[int, Isr] + for isr in isrs.values(): + if isr.address in self.addr_to_isr: + raise ValueError(f'Duplicate ISR at address {isr.address:x}') + self.addr_to_isr[isr.address] = isr + + +class IsrMaps: + def __init__(self, csrs: IsrMap, wsrs: IsrMap): + self.csrs = csrs + self.wsrs = wsrs diff --git a/hw/ip/otbn/util/shared/operand.py b/hw/ip/otbn/util/shared/operand.py index b1719145390c9..711414ce10990 100644 --- a/hw/ip/otbn/util/shared/operand.py +++ b/hw/ip/otbn/util/shared/operand.py @@ -10,6 +10,7 @@ from .encoding import Encoding from .encoding_scheme import EncSchemeField +from .isr import IsrMap, IsrMaps class OperandType: @@ -620,8 +621,43 @@ def get_max_enc_val(self) -> int: return 1 +class IsrOperandType(ImmOperandType): + '''An operand type for a CSR or WSR.''' + def __init__(self, width: int, name: str, isrs: IsrMap): + super().__init__(width, 0, 0, False, False) + self.name = name + self.isrs = isrs + + def syntax_determines_value(self) -> bool: + return True + + def str_to_op_val(self, as_str: str) -> int: + # Check to see whether we recognise this as the name of an ISR (folding + # case to lower). + as_isr = self.isrs.name_to_isr.get(as_str.lower()) + if as_isr is not None: + return as_isr.address + + # If it's not a known ISR name, check whether this can be parsed as an + # integer. + as_int = super().str_to_op_val(as_str) + if as_int is not None: + return as_int + + raise ValueError(f'Failed to recognise {self.name} ' + f'name from {as_str}.') + + def op_val_to_str(self, op_val: int, cur_pc: Optional[int]) -> str: + isr_at_addr = self.isrs.addr_to_isr.get(op_val) + if isr_at_addr is not None: + return isr_at_addr.name + + return str(op_val) + + def parse_operand_type(fmt: str, pc_rel: bool, what: str, - scheme_field: Optional[EncSchemeField]) -> OperandType: + scheme_field: Optional[EncSchemeField], + isr_maps: Optional[IsrMaps]) -> OperandType: '''Make sense of the operand type syntax''' # Registers reg_fmts = { @@ -644,12 +680,16 @@ def parse_operand_type(fmt: str, pc_rel: bool, what: str, # CSR and WSR indices. These are treated like unsigned immediates, with # width 12 and 8, respectively. - xsr_fmts = {'csr': 12, 'wsr': 8} - xsr_match = xsr_fmts.get(fmt) - if xsr_match is not None: + isr_widths = {'csr': 12, 'wsr': 8} + isr_width = isr_widths.get(fmt) + if isr_width is not None: assert not pc_rel - return ImmOperandType.make(xsr_match, 0, 0, False, False, what, - scheme_field) + + isrs = IsrMap({}) + if isr_maps is not None: + isrs = isr_maps.csrs if fmt == 'csr' else isr_maps.wsrs + + return IsrOperandType(isr_width, fmt, isrs) # Immediates for base, signed in [('simm', True), ('uimm', False)]: @@ -693,7 +733,8 @@ def parse_operand_type(fmt: str, pc_rel: bool, what: str, def infer_operand_type(name: str, pc_rel: bool, what: str, - scheme_field: Optional[EncSchemeField]) -> OperandType: + scheme_field: Optional[EncSchemeField], + isrs: Optional[IsrMaps]) -> OperandType: '''Try to guess an operand's type from its name''' op_type_name = None @@ -711,12 +752,13 @@ def infer_operand_type(name: str, pc_rel: bool, what: str, "Operand name {!r} doesn't imply an operand type: " "you'll have to set the type explicitly.".format(name)) - return parse_operand_type(op_type_name, pc_rel, what, scheme_field) + return parse_operand_type(op_type_name, pc_rel, what, scheme_field, isrs) def make_operand_type(op_type_name: Optional[str], pc_rel: bool, operand_name: str, mnemonic: str, - scheme_field: Optional[EncSchemeField]) -> OperandType: + scheme_field: Optional[EncSchemeField], + isrs: Optional[IsrMaps]) -> OperandType: '''Construct a type for an operand This is either based on the type, if given, or inferred from the name @@ -726,15 +768,17 @@ def make_operand_type(op_type_name: Optional[str], pc_rel: bool, ''' what = ('the type for the {!r} operand of instruction {!r}'.format( operand_name, mnemonic)) - return (parse_operand_type(op_type_name, pc_rel, what, scheme_field) - if op_type_name is not None else infer_operand_type( - operand_name, pc_rel, what, scheme_field)) + return (parse_operand_type(op_type_name, pc_rel, what, + scheme_field, isrs) + if op_type_name is not None + else infer_operand_type(operand_name, pc_rel, what, + scheme_field, isrs)) class Operand: - def __init__(self, yml: object, mnemonic: str, - insn_encoding: Optional[Encoding]) -> None: + insn_encoding: Optional[Encoding], + isrs: Optional[IsrMaps]) -> None: # The YAML representation should be a string (a bare operand name) or a # dict. what = 'operand for {!r} instruction'.format(mnemonic) @@ -779,5 +823,5 @@ def __init__(self, yml: object, mnemonic: str, self.name = name self.abbrev = abbrev self.op_type = make_operand_type(op_type, pc_rel, name, mnemonic, - enc_scheme_field) + enc_scheme_field, isrs) self.doc = doc diff --git a/hw/ip/otbn/util/yaml_to_doc.py b/hw/ip/otbn/util/yaml_to_doc.py index 860706eb777cd..9c5515f484d14 100755 --- a/hw/ip/otbn/util/yaml_to_doc.py +++ b/hw/ip/otbn/util/yaml_to_doc.py @@ -391,7 +391,7 @@ def main() -> int: args = parser.parse_args() try: - insns = load_file(args.yaml_file) + insns = load_file(args.yaml_file, None) impls = read_implementation(args.py_file) except RuntimeError as err: print(err, file=sys.stderr) diff --git a/sw/device/tests/BUILD b/sw/device/tests/BUILD index d55bfae460e30..3b70b7d5eff8c 100644 --- a/sw/device/tests/BUILD +++ b/sw/device/tests/BUILD @@ -1540,6 +1540,19 @@ opentitan_functest( ], ) +opentitan_functest( + name = "otbn_isa_test", + srcs = ["otbn_isa_test.c"], + deps = [ + "//hw/ip/otbn/dv/smoke:smoke_test", + "//hw/top_earlgrey/sw/autogen:top_earlgrey", + "//sw/device/lib/dif:otbn", + "//sw/device/lib/testing:entropy_testutils", + "//sw/device/lib/testing:otbn_testutils", + "//sw/device/lib/testing/test_framework:ottf_main", + ], +) + opentitan_functest( name = "otp_ctrl_smoketest", srcs = ["otp_ctrl_smoketest.c"], diff --git a/sw/device/tests/otbn_isa_test.c b/sw/device/tests/otbn_isa_test.c new file mode 100644 index 0000000000000..e50dd4590e5bc --- /dev/null +++ b/sw/device/tests/otbn_isa_test.c @@ -0,0 +1,153 @@ +// Copyright lowRISC contributors. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +#include "sw/device/lib/dif/dif_otbn.h" +#include "sw/device/lib/testing/entropy_testutils.h" +#include "sw/device/lib/testing/otbn_testutils.h" +#include "sw/device/lib/testing/test_framework/check.h" +#include "sw/device/lib/testing/test_framework/ottf_main.h" + +#include "hw/top_earlgrey/sw/autogen/top_earlgrey.h" + +/** + * This test runs every instruction in OTBN's ISA and checks the result. + * + * It reuses the `smoke_test.s` script used in OTBN's Design Verification, + * but with accesses the `RND` and `KEY_*` WSRs removed. + * The result in `w1` and `w2` are downstream of the `KEY_*`, + * loads and so are not checked. + * + * The zero register and `x1` stack register are ignored. + */ +OTTF_DEFINE_TEST_CONFIG(); + +OTBN_DECLARE_APP_SYMBOLS(smoke_test); +OTBN_DECLARE_SYMBOL_ADDR(smoke_test, gpr_state); +OTBN_DECLARE_SYMBOL_ADDR(smoke_test, wdr_state); + +static const otbn_app_t kAppSmokeTest = OTBN_APP_T_INIT(smoke_test); +static const otbn_addr_t kGprState = OTBN_ADDR_T_INIT(smoke_test, gpr_state); +static const otbn_addr_t kWdrState = OTBN_ADDR_T_INIT(smoke_test, wdr_state); + +enum { + kNumExpectedGprs = 30, + kNumExpectedWdrs = 32, + kExpectedInstrCount = 284, +}; + +// The expected values of the GPRs and WDRs are taken from +// `hw/ip/otbn/dv/smoke/smoke_expected.txt`. +static const uint32_t kExpectedGprs[kNumExpectedGprs] = { + 0xd0beb513, 0xa0be911a, 0x717d462d, 0xcfffdc07, 0xf0beb51b, 0x80be9112, + 0x70002409, 0xd0beb533, 0x00000510, 0xd0beb169, 0xfad44c00, 0x000685f5, + 0xffa17d6a, 0x4c000000, 0x00000034, 0xfffffff4, 0xfacefeed, 0xd0beb533, + 0x00000123, 0x00000123, 0xcafef010, 0x89c9b54f, 0x00000052, 0x00000020, + 0x00000016, 0x0000001a, 0x00400000, 0x00018000, 0x00000000, 0x00000804}; + +static const uint32_t kExpectedWdrs[kNumExpectedWdrs][8] = { + [0] = {0x0f09b7c8, 0x25769434, 0x6978ad1b, 0x67a8c221, 0x5466a52c, + 0x73880075, 0xf9dbff5e, 0x37adadae}, + [1] = {0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000}, + [2] = {0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000}, + [3] = {0xed103473, 0x431165e5, 0x0357f208, 0x7245a2d0, 0x22168ae8, + 0x34745ffa, 0xbbc28370, 0x23a776b0}, + [4] = {0xb9dd0141, 0xedbc1090, 0xd024bed4, 0x1cf04d7a, 0xeee357b5, + 0xdf1f0aa4, 0x888f503c, 0xce52215b}, + [5] = {0xdbff9bdb, 0xbaeebbbb, 0xf9bfd9ff, 0xefbafaaf, 0x99fdf9df, + 0xabebbfef, 0xbbb9f9df, 0xfafeeeae}, + [6] = {0x11109898, 0x8822aa2a, 0x09981808, 0x828aa820, 0x88189108, + 0x8888a00a, 0x00088990, 0x28a88802}, + [7] = {0xcaef0343, 0x32cc1191, 0xf027c1f7, 0x6d30528f, 0x11e568d7, + 0x23631fe5, 0xbbb1704f, 0xd25666ac}, + [8] = {0xac896525, 0x679944c4, 0x9641a791, 0x386507da, 0x77830eb1, + 0x76364ab0, 0xddd71629, 0x870333f9}, + [9] = {0xcccccd55, 0x555554cc, 0xcccccd55, 0x555554cc, 0xb4d6d555, + 0x35d9da9b, 0xf2c374c3, 0xd7c12b4d}, + [10] = {0xf0cd30f0, 0x45114443, 0xd30cf2cd, 0x1045054f, 0x32ced2ed, + 0x54144010, 0x1112d2ed, 0x05011151}, + [11] = {0xbbbc3433, 0x77dd55d5, 0xc334b4c4, 0x7d7557df, 0x44b43bc4, + 0x77775ff5, 0xccc4433c, 0xd75777fd}, + [12] = {0x22229a9a, 0xcd32ab2b, 0x299b1b2a, 0xd2caad35, 0xab1aa22a, + 0xccccb54a, 0x332aa9a2, 0x2caccd53}, + [13] = {0x97ba66a7, 0x20896565, 0xa689a3aa, 0x4a25a045, 0x43c8b58a, + 0x1252555a, 0x5564a69a, 0xa1a55408}, + [14] = {0x7956327a, 0xbcee9991, 0x630e74e6, 0x8dba5ca7, 0x2c59e406, + 0xac10254c, 0xd09a8aec, 0x5ec45f47}, + [15] = {0xac896524, 0xbcee9a19, 0x9641a791, 0x8dba5d2f, 0x77830eb1, + 0xcb8ba005, 0xddd71629, 0xdc58894e}, + [16] = {0xb9dd0141, 0xedbc1090, 0xd024bed4, 0x1cf04d7a, 0xeee357b5, + 0xdf1f0aa4, 0x888f503c, 0xce52215b}, + [17] = {0x33333331, 0x55555555, 0x33333333, 0x55555555, 0x33333333, + 0x55555555, 0x33333333, 0x55555555}, + [18] = {0x53769ada, 0x9866bb3b, 0x69be586e, 0xc79af825, 0x22168a4e, + 0x34745fe9, 0xbbc28381, 0x23a7769f}, + [19] = {0x00000000, 0x00000000, 0x09981800, 0x828aa801, 0x8818910a, + 0x8888a009, 0x00088982, 0x28a88800}, + [20] = {0xb9dd0130, 0xedbc10a1, 0x69be57c3, 0xc79af825, 0x887cf14e, + 0x89c9b54f, 0x2228e9d6, 0x78fccc06}, + [21] = {0xdbff9bfa, 0xbaeebbbb, 0xf9bfd9ee, 0xefbafabd, 0x887cf1ee, + 0x89c9b54f, 0x2228e9d6, 0x78fccc06}, + [22] = {0xdbff9db7, 0xbaeebbbb, 0xf9bfd9ee, 0xefbafabd, 0x887cf1ee, + 0x89c9b54f, 0x2228e9d6, 0x78fccc06}, + [23] = {0xdbff99f3, 0xbaeebbbb, 0xf9bfd9ee, 0xefbafabd, 0x887cf1ee, + 0x89c9b54f, 0x2228e9d6, 0x78fccc06}, + [24] = {0x1234abcd, 0xd0beb533, 0xcafed00d, 0xdeadbeef, 0xfacefeed, + 0xaaaaaaaa, 0xbbbbbbbb, 0xcccccccc}, + [25] = {0x1234abcd, 0xd0beb533, 0xcafed00d, 0xdeadbeef, 0xfacefeed, + 0xaaaaaaaa, 0xbbbbbbbb, 0xcccccccc}, + [26] = {0xdbff9bfa, 0xbaeebbbb, 0xf9bfd9ee, 0xefbafabd, 0x887cf1ee, + 0x89c9b54f, 0x2228e9d6, 0x78fccc06}, + [27] = {0x11109898, 0x8822aa2a, 0x09981808, 0x828aa820, 0x88189108, + 0x8888a00a, 0x00088990, 0x28a88802}, + [28] = {0xcaef0343, 0x32cc1191, 0xf027c1f7, 0x6d30528f, 0x11e568d7, + 0x23631fe5, 0xbbb1704f, 0xd25666ac}, + [29] = {0xeb0953c2, 0xe0654fef, 0x63388709, 0x5763bcdf, 0x26628bdb, + 0x64341d3c, 0x9f24f0c1, 0x4f0d4b81}, + [30] = {0x68ba2fa1, 0xb55098e0, 0x4efa2ec9, 0xaee49292, 0xab123192, + 0xffa3d88b, 0xe9ee7ac7, 0x2167f87d}, + [31] = {0x0f09b7c8, 0x25769434, 0x6978ad1b, 0x67a8c221, 0x5466a52c, + 0x73880075, 0xf9dbff5e, 0x37adadae}, +}; + +bool test_main(void) { + // Initialise the entropy source and OTBN + dif_otbn_t otbn; + CHECK_STATUS_OK(entropy_testutils_auto_mode_init()); + CHECK_DIF_OK( + dif_otbn_init(mmio_region_from_addr(TOP_EARLGREY_OTBN_BASE_ADDR), &otbn)); + + // Load the Smoke Test App + CHECK_STATUS_OK(otbn_testutils_load_app(&otbn, kAppSmokeTest)); + CHECK_STATUS_OK(otbn_testutils_execute(&otbn)); + CHECK_STATUS_OK(otbn_testutils_wait_for_done(&otbn, kDifOtbnErrBitsNoError)); + + // Check the instruction count is what was expected. + uint32_t instruction_count; + CHECK_DIF_OK(dif_otbn_get_insn_cnt(&otbn, &instruction_count)); + CHECK(kExpectedInstrCount == instruction_count, + "Expected OTBN to execute %d instructions, but it exected %d", + kExpectedInstrCount, instruction_count); + + // Check the GPR registers of interest hold the expected values. + uint32_t gpr_state[kNumExpectedGprs]; + CHECK_STATUS_OK(otbn_testutils_read_data(&otbn, sizeof(kExpectedGprs), + kGprState, &gpr_state)); + CHECK_ARRAYS_EQ(gpr_state, kExpectedGprs, kNumExpectedGprs); + + // Check the WDR registers of interest hold the expected values. + uint32_t wdr_state[kNumExpectedWdrs][8]; + CHECK_STATUS_OK(otbn_testutils_read_data(&otbn, sizeof(kExpectedWdrs), + kWdrState, &wdr_state)); + + CHECK_ARRAYS_EQ(wdr_state[0], kExpectedWdrs[0], 8, + "w0 didn't match the expected value."); + // We ignore register w1 and w2. + for (size_t i = 3; i < kNumExpectedWdrs; ++i) { + CHECK_ARRAYS_EQ(wdr_state[i], kExpectedWdrs[i], 8, + "w%d didn't match the expected value.", i); + }; + return true; +}