blob: 3b7bd7a379d00f5ce0ddf3d6b07abc08b2bd0b80 [file] [log] [blame] [view]
# Comportable IP Testbench Architecture
## Overview
Going along the lines of what it takes to design an IP that adheres to the
[Comportability Specifications](../../../../doc/contributing/hw/comportability/README.md),
we attempt to standardize the DV methodology for developing the IP level
testbench environment as well by following the same approach. This document describes
the Comportable IP (CIP) library, which is a complete UVM environment framework that
each IP level environment components can extend from to get started with DV. The goal
here is to maximize code reuse across all test benches so that we can improve the
efficiency and time to market. The features described here are not exhaustive,
so it is highly recommended to the reader that they examine the code directly. In
course of development, we also periodically identify pieces of verification logic that
might be developed for one IP but is actually a good candidate to be added to
these library classes instead. This doc is instead intended to provide the user
a foray into what these are and how are the meant to be used.
## CIP environment block diagram
![CIP environment block diagram](./doc/env.svg)
## CIP library classes
The CIP library includes the base ral model, env cfg object, coverage
object, virtual sequencer, scoreboard, env, base virtual sequence and finally
the test class. To achieve run-time polymorphism, these classes are type
parameterized to indicate what type of child objects are to be created. In the
IP environments, the extended classes indicate the correct type parameters.
### cip_base_env_cfg
This class is intended to contain all of the settings, knobs, features, interface
handles and downstream agent cfg handles. Features that are common to all IPs in
accordance with the comportability spec are made a part of this base class, while the
extended IP env cfg class will contain settings specific to that IP. An instance of
the env cfg class is created in `cip_base_test::build_phase` and the handle is
passed over uvm_config_db for the CIP env components to pick up. This allows
the handle to the env cfg object to be available in the env's build_phase. Settings
in the env cfg can then be used to configure the env based on the test needs.
A handle to this class instance is passed on to the scoreboard, virtual
sequencer and coverage objects so that all such common settings and features
are instantly accessible everywhere.
This class is type parameterized in the following way:
```systemverilog
class cip_base_env_cfg #(type RAL_T = dv_base_reg_block) extends uvm_object;
```
The IP env cfg class will then extend from this class with the RAL_T parameter set
to the actual IP RAL model class. This results in IP RAL model getting factory
overridden automatically in the base env cfg itself during creation, so there is no
need for manual factory override. We follow the same philosophy in all CIP library
classes.
The following is a list of common features and settings:
* **clk_rst_if**: A handle to the clk_rst_if that controls the main clk and reset
to the DUT.
* **intr_vif**: This is a handle to the `pins_if #(NUM_MAX_INTERRUPTS=64)` interface
instance created in the tb to hookup the DUT interrupts. The actual number of
interrupts might be much less than 64, but that is ok - we just connect as
many as the DUT provides. The reason for going with a fixed width pins_if is
to allow the intr_vif to be available in this base env cfg class (which does not
know how many interrupt each IP DUT provides).
* **devmode_vif**: This is a handle to the `pins_if #(1)` interface instance created
in the tb to hookup the DUT input `devmode`.
* **tl_agent_cfg**: The downstream TileLink host agent created in the cip_base_env
class requires the agent cfg handle to tell it how to configure the agent.
* **alert_agent_cfgs**: Similar to tl_agent_cfg, the downstream alert device agent
created in the cip_base_env class requires the agent cfg handles to tell it how to
configure the agent. In default, alert agent is configured in device mode,
asynchronous, active and the ping coverage is turned off.
* **ral**: This is the instance to the auto-generated RAL model that is
extended from `dv_base_reg_block`. In the base class, this is created using
the RAL_T class parameter which the extended IP env cfg class sets correctly.
* **tl_intg_alert_name**: Name of the alert that will be triggered on TLUL
integrity error detection. The default name used for this type of alert is
"fatal_fault". The block may use a different name too - in that case, please
update this member to reflect the correct name in the `initialize()` method.
* **tl_intg_alert_fields**: An associative array of CSR fields keyed with the
objection handle of the corresponding CSR field and valued with the expected
value. This is the list of CSR fields that are modified when an alert triggers
due to TL integrity violation event. The DV user is required to build this list
in the `initialize()` method after `super.initialize(csr_base_addr);`
```systemverilog
virtual function void initialize(bit [31:0] csr_base_addr = '1);
super.initialize(csr_base_addr); // ral model is created in `super.initialize`
tl_intg_alert_fields[ral.a_status_reg.a_field] = value;
```
Apart from these, there are several common settings such as `zero_delays`,
`clk_freq_mhz`, which are randomized as well as knobs such as `en_scb` and
`en_cov` to turn on/off scoreboard and coverage collection respectively.
The base class provides a virtual method called `initialize()` which is called
in `cip_base_test::build_phase` to create some of the objects listed above. If
the extended IP env cfg class has more such objects added, then the `initialize()`
method is required to be overridden to create those objects as well.
We make all downstream interface agent cfg handles as a part of IP extension of
cip_base_env_cfg so that all settings for the env and all downstream agents are
available within the env cfg handle. Since the env cfg handle is passed to all cip
components, all those settings are also accessible.
### cip_base_env_cov
This is the base coverage object that contain all functional coverpoints and
covergroups. The main goal is to have all functional coverage elements
implemented in a single place. This class is extended from `uvm_component`
so that it allows items to be set via `'uvm_config_db` using the component's
hierarchy. This is created in cip_base_env and a handle to it is passed to the
scoreboard and the virtual sequencer. This allows coverage to be sampled in
scoreboard as well as the test sequences.
This class is type parameterized with the env cfg class type `CFG_T` so that it
can derive coverage on some of the env cfg settings.
```systemverilog
class cip_base_env_cov #(type CFG_T = cip_base_env_cfg) extends uvm_component;
```
The following covergroups are defined outside of the class for use by all IP
testbenches:
* `intr_cg`: Covers individual and cross coverage on intr_enable and intr_state for all interrupts in IP
* `intr_test_cg`: Covers intr_test coverage and its cross with intr_enable and intr_state for all interrupts in IP
* `intr_pins_cg`: Covers values and transitions on all interrupt output pins of IP
* `sticky_intr_cov`: Covers sticky interrupt functionality of all applicable interrupts in IP
Covergroups `intr_cg`, `intr_test_cg` and `intr_pins_cg` are instantiated
and allocated in `cip_base_env_cov` by default in all IPs.
On the other hand, `sticky_intr_cov` is instantiated with string key.
The string key represents the interrupts names that are sticky. This is specific
to each IP and is required to be created and instantiated in extended `cov` class.
### cip_base_virtual_sequencer
This is the base virtual sequencer class that contains a handle to the
`tl_sequencer` to allow layered test sequences to be created. The extended IP
virtual sequencer class will include handles to the IP specific agent
sequencers.
This class is type-parameterized with the env cfg class type `CFG_T` and coverage
class type `COV_T` so that all test sequences can access the env cfg settings and
sample the coverage via the `p_sequencer` handle.
```systemverilog
class cip_base_virtual_sequencer #(type CFG_T = cip_base_env_cfg,
type COV_T = cip_base_env_cov) extends uvm_sequencer;
```
### cip_base_scoreboard
This is the base scoreboard component that already connects with the TileLink
agent monitor to grab tl packets via analysis port at the address and the data
phases. It provides a virtual task called `process_tl_access` that the extended
IP scoreboard needs to implement. Please see code for additional details. The
extended IP scoreboard class will connect with the IP-specific interface monitors
if applicable to grab items from those analysis ports.
This class is type-parameterized with the env cfg class type `CFG_T`, ral class
type `RAL_T` and the coverage class type `COV_T`.
```systemverilog
class cip_base_scoreboard #(type RAL_T = dv_base_reg_block,
type CFG_T = cip_base_env_cfg,
type COV_T = cip_base_env_cov) extends uvm_component;
```
There are several virtual tasks and functions that are to be overridden
in extended IP scoreboard class. Please take a look at the code for more
details.
### cip_base_env
This is the base UVM env that puts all of the above components together
and creates and makes connections across them. In the build phase, it retrieves
the env cfg class type handle from `uvm_config_db` as well as all the virtual
interfaces (which are actually part of the env cfg class). It then uses the env
cfg settings to modify the downstream agent cfg settings as required. All of
the above components are created based on env cfg settings, along with the TileLink
host agent and alert device agents if the module has alerts. In the connect phase,
the scoreboard connects with the monitor within the TileLink agent to be able to
grab packets from the TL interface during address and the data phases. The scoreboard
also connects the alert monitor within the alert_esc_agent to grab packets
regarding alert handshake status. In the end of elaboration phase, the ral
model within the env cfg handle is locked and the ral sequencer and adapters are
set to be used with the TileLink interface.
This class is type parameterized with env cfg class type CFG_T, coverage class type
`COV_T`, virtual sequencer class type `VIRTUAL_SEQUENCER_T` and scoreboard class
type `SCOREBOARD_T`.
```systemverilog
class cip_base_env #(type CFG_T = cip_base_env_cfg,
type VIRTUAL_SEQUENCER_T = cip_base_virtual_sequencer,
type SCOREBOARD_T = cip_base_scoreboard,
type COV_T = cip_base_env_cov) extends uvm_env;
```
### cip_base_vseq
This is the base virtual sequence class that will run on the cip virtual
sequencer. This base class provides 'sequencing' set of tasks such as
`dut_init()` and `dut_shutdown()` which are called within `pre_start` and
`post_start` respectively. This sequence also provides an array of
sub-sequences some of which are complete tests within themselves, but
implemented as tasks. The reason for doing so is SystemVerilog does not
support multi-inheritance so all sub-sequences that are identified as being
common to all IP benches implemented as tasks in this base virtual sequence class.
Some examples:
* **task run_csr_vseq_wrapper**: This is a complete CSR test suite in itself -
Extended IP CSR vseq can simply call this in the body. This is paired with a
helper function `add_csr_exclusions`.
* **function add_csr_exclusions**: This is extended in the IP CSR vseq to add
exclusions when running the CSR suite of tests.
* **task tl_access**: This is a common generic task to create a read or a write
access over the TileLink host interface.
* **task cfg_interrupts, check_interrupts**: All interrupt CSRs are standardized
according to the comportability spec, which allows us to create common tasks
to turn on / off interrupts as well as check them.
* **task run_tl_errors_vseq**: This task will test all kinds of TileLink error
cases, including unmapped address error, protocol error, memory access error
etc. All the items sent in this task will trigger d_error and won't change the
CSR/memory value.
* **task run_tl_intg_err_vseq**: This task will test TLUL integrity error. It contains
2 parallel threads. The first one invokes the `csr_rw` seq to drive random, legal
CSR accesses. The second drives a bad TLUL transaction that violates the payload
integrity. The bad packet is created by corrupting upto 3 bits either in the integrity
(ECC) fields (`a_user.cmd_intg`, `a_user.d_intg`), or in their corresponding command /
data payload itself. The sequence then verifies that the DUT not only returns an error
response (with `d_error` = 1), but also triggers a fatal alert and updates status CSRs
such as `ERR_CODE`. The list of CSRs that are impacted by this alert event, maintained
in `cfg.tl_intg_alert_fields`, are also checked for correctness.
* **task run_seq_with_rand_reset_vseq**: This task runs 3 parallel threads,
which are a sequence provided, run_tl_errors_vseq and reset sequence. After
reset occurs, the other threads will be killed and then all the CSRs will be read
for check. This task runs multiple iterations to ensure DUT won't be broken after
reset and TL errors.
To ensure the reset functionality works correctly, user will have to disable
any internal reset from the stress_all sequence. Below is an example of
disabling internal reset in `hmac_stress_all_vseq.sv`:
* **task run_same_csr_outstanding_vseq**: This task tests the same CSR with
non-blocking accesses as the regular CSR sequences don't cover that due to
limitation of uvm_reg.
* **task run_mem_partial_access_vseq**: This task tests the partial access to the
memories by randomizing mask, size, and the 2 LSB bits of the address. It also runs
with non-blocking access enabled.
```
// randomly trigger internal dut_init reset sequence
// disable any internal reset if used in stress_all_with_rand_reset vseq
if (do_dut_init) hmac_vseq.do_dut_init = $urandom_range(0, 1);
else hmac_vseq.do_dut_init = 0;
```
This class is type parameterized with the env cfg class type `CFG_T`, ral class type
`RAL_T` and the virtual sequencer class type `VIRTUAL_SEQUENCER_T` so that the
env cfg settings, the ral CSRs are accessible and the `p_sequencer` type can be
declared.
```systemverilog
class cip_base_vseq #(type RAL_T = dv_base_reg_block,
type CFG_T = cip_base_env_cfg,
type COV_T = cip_base_env_cov,
type VIRTUAL_SEQUENCER_T = cip_base_virtual_sequencer) extends uvm_sequence;
```
All virtual sequences in the extended IP will eventually extend from this class and
can hence, call these tasks and functions directly as needed.
### cip_base_test
This basically creates the IP UVM env and its env cfg class instance. Any env cfg
setting that may be required to be controlled externally via plusargs are looked
up here, before the env instance is created. This also sets a few variables that
pertain to how / when should the test exit on timeout or failure. In the run
phase, the test calls `run_seq` which basically uses factory to create the
virtual sequence instance using the `UVM_TEST_SEQ` string that is passed via
plusarg. As a style guide, it is preferred to encapsulate a complete test within
a virtual sequence and use the same `UVM_TEST` plusarg for all of the tests (which
points to the extended IP test class), and only change the `UVM_TEST_SEQ` plusarg.
This class is type parameterized with the env cfg class type `CFG_T` and the env
class type `ENV_T` so that when extended IP test class creates the env and env cfg
specific to that IP.
```systemverilog
class cip_base_test #(type CFG_T = cip_base_env_cfg,
type ENV_T = cip_base_env) extends uvm_test;
```
### cip_tl_seq_item
This is extended class of tl_seq_item to generate correct integrity values in
`a_user` and `d_user`.
## Extending from CIP library classes
Let's say we are verifying an actual comportable IP `uart` which has `uart_tx`
and `uart_rx` interface. User then develops the `uart_agent` to be able to talk
to that interface. User invokes the `ralgen` tool to generate the `uart_reg_block`,
and then starts developing UVM environment by extending from the CIP library
classes in the following way.
### uart_env_cfg
```systemverilog
class uart_env_cfg extends cip_base_env_cfg #(.RAL_T(uart_reg_block));
```
User adds the `uart_agent_cfg` object as a member so that it remains as a
part of the env cfg and can be accessed everywhere. In the base class's
`initialize()` function call, an instance of `uart_reg_block` is created, not
the `dv_base_reg_block`, since we override the `RAL_T` type.
### uart_env_cov
```systemverilog
class uart_env_cov extends cip_base_env_cov #(.CFG_T(uart_env_cfg));
```
User adds `uart` IP specific coverage items and uses the `cov` handle in
scoreboard and test sequences to sample the coverage.
### uart_virtual_sequencer
```systemverilog
class uart_virtual_sequencer extends cip_base_virtual_sequencer #(.CFG_T(uart_env_cfg),
.COV_T(uart_env_cov));
```
User adds the `uart_sequencer` handle to allow layered test sequences
to send traffic to / from TileLink as well as `uart` interfaces.
### uart_scoreboard
```systemverilog
class uart_scoreboard extends cip_base_scoreboard #(.CFG_T(uart_env_cfg),
.RAL_T(uart_reg_block),
.COV_T(uart_env_cov));
```
User adds analysis ports to grab packets from the `uart_monitor` to
perform end-to-end checking.
### uart_env
```systemverilog
class uart_env extends cip_base_env #(.CFG_T (uart_env_cfg),
.COV_T (uart_env_cov),
.VIRTUAL_SEQUENCER_T (uart_virtual_sequencer),
.SCOREBOARD_T (uart_scoreboard));
```
User creates `uart_agent` object in the env and use it to connect with the
virtual sequencer and the scoreboard. User also uses the env cfg settings to
manipulate the uart agent cfg settings if required.
### uart_base_vseq
```systemverilog
class uart_base_vseq extends cip_base_vseq #(.CFG_T (uart_env_cfg),
.RAL_T (uart_reg_block),
.COV_T (uart_env_cov),
.VIRTUAL_SEQUENCER_T (uart_virtual_sequencer));
```
User adds a base virtual sequence as a starting point and adds common tasks and
functions to perform `uart` specific operations. User then extends from
`uart_base_vseq` to add layered test sequences.
### uart_base_test
```systemverilog
class uart_base_test extends cip_base_test #(.ENV_T(uart_env), .CFG_T(uart_env_cfg));
```
User sets `UVM_TEST` plus arg to `uart_base_test` so that all tests create the UVM env
that is automatically tailored to UART IP. Each test then sets the
`UVM_TEST_SEQ` plusarg to run the specific test sequence, along with additional
plusargs as required.
## Configure Alert Device Agent from CIP library classes
To configure alert device agents in a block level testbench environment that is extended
from this CIP library class, please follow the steps below:
* **${ip_name}_env_pkg.sv**: Add parameter `LIST_OF_ALERTS[]` and `NUM_ALERTS`.
Please make sure the alert names and order are correct.
For example in `otp_ctrl_env_pkg.sv`:
```systemverilog
parameter string LIST_OF_ALERTS[] = {"fatal_macro_error", "fatal_check_error"};
parameter uint NUM_ALERTS = 2;
```
* **${ip_name}_env_cfg.sv**: In function `initialize()`, assign `LIST_OF_ALERTS`
parameter to `list_of_alerts` variable which is created in `cip_base_env_cfg.sv`.
Note that this assignment should to be written before calling `super.initialize()`,
so that creating alert host agents will take the updated `list_of_alerts` variable.
For example in `otp_ctrl_env_cfg.sv`:
```systemverilog
virtual function void initialize(bit [31:0] csr_base_addr = '1);
list_of_alerts = otp_ctrl_env_pkg::LIST_OF_ALERTS;
super.initialize(csr_base_addr);
```
* **tb.sv**: Add `DV_ALERT_IF_CONNECT` macro that declares alert interfaces,
connect alert interface wirings with DUT, and set alert_if to uvm_config_db.
Then connect alert_rx/tx to the DUT ports.
For example in otp_ctrl's `tb.sv`:
```systemverilog
`DV_ALERT_IF_CONNECT()
otp_ctrl dut (
.clk_i (clk ),
.rst_ni (rst_n ),
.alert_rx_i (alert_rx ),
.alert_tx_o (alert_tx ),
```
Note that if the testbench is generated from `uvmdvgen.py`, using the `-hr` switch
will automatically generate the skeleton code listed above for alert device agent.
Details on how to use `uvmdvgen.py` please refer to the
[uvmdvgen document](../../../../util/uvmdvgen/README.md).
## CIP Testbench
![CIP testbench diagram](./doc/tb.svg)
The block diagram above shows the CIP testbench architecture, that puts
together the static side `tb` which instantiates the `dut`, and the dynamic
side, which is the UVM environment extended from CIP library. The diagram
lists some common items that need to be instantiated in `tb`
and set into `uvm_config_db` for the testbench to work.
## Security Verification in cip_lib
CIP contains reusable security verification components, sequences and function coverage.
This section describes the details of them and the steps to enable them.
### Security Verification for bus integrity
The countermeasure of bus integrity can be fully verified via importing [tl_access_tests](https://github.com/lowRISC/opentitan/blob/master/hw/dv/tools/dvsim/tests/tl_access_tests.hjson) and [tl_device_access_types_testplan](https://github.com/lowRISC/opentitan/blob/master/hw/dv/tools/dvsim/testplans/tl_device_access_types_testplan.hjson).
The `tl_intg_err` test injects errors on control, data, or the ECC bits and verifies that the integrity error will trigger a fatal alert (provided via `cfg.tl_intg_alert_name`) and error status (provided via `cfg.tl_intg_alert_fields`) is set.
Refer to section [cip_base_env_cfg](#cip_base_env_cfg) for more information on these 2 variables.
The user may update these 2 variables as follows.
```systemverilog
class ip_env_cfg extends cip_base_env_cfg #(.RAL_T(ip_reg_block));
virtual function void initialize(bit [31:0] csr_base_addr = '1);
super.initialize(csr_base_addr);
tl_intg_alert_name = "fatal_fault_err";
// csr / field name may vary in different IPs
tl_intg_alert_fields[ral.fault_status.intg_err] = 1;
```
### Security Verification for memory integrity
The memory integrity countermeasure stores the data integrity in the memory rather than generating the integrity on-the-fly during a read.
The [passthru_mem_intg_tests](https://github.com/lowRISC/opentitan/blob/master/hw/dv/tools/dvsim/tests/passthru_mem_intg_tests.hjson) can fully verify this countermeasure.
The details of the test sequences are described in the [tl_device_access_types_testplan](https://github.com/lowRISC/opentitan/blob/master/hw/dv/tools/dvsim/testplans/passthru_mem_intg_testplan.hjson). Users need to override the task `inject_intg_fault_in_passthru_mem` to inject an integrity fault to the memory in the block common_vseq.
The following is an example from `sram_ctrl`, in which it flips up to `MAX_TL_ECC_ERRORS` bits of the data and generates a backdoor write to the memory.
```systemverilog
class sram_ctrl_common_vseq extends sram_ctrl_base_vseq;
...
virtual function void inject_intg_fault_in_passthru_mem(dv_base_mem mem,
bit [bus_params_pkg::BUS_AW-1:0] addr);
bit[bus_params_pkg::BUS_DW-1:0] rdata;
bit[tlul_pkg::DataIntgWidth+bus_params_pkg::BUS_DW-1:0] flip_bits;
rdata = cfg.mem_bkdr_util_h.sram_encrypt_read32_integ(addr, cfg.scb.key, cfg.scb.nonce);
`DV_CHECK_STD_RANDOMIZE_WITH_FATAL(flip_bits,
$countones(flip_bits) inside {[1:cip_base_pkg::MAX_TL_ECC_ERRORS]};)
`uvm_info(`gfn, $sformatf("Backdoor change mem (addr 0x%0h) value 0x%0h by flipping bits %0h",
addr, rdata, flip_bits), UVM_LOW)
cfg.mem_bkdr_util_h.sram_encrypt_write32_integ(addr, rdata, cfg.scb.key, cfg.scb.nonce,
flip_bits);
endfunction
endclass
```
### Security Verification for shadow CSRs
The countermeasure of shadow CSRs can be fully verified via importing [shadow_reg_errors_tests](https://github.com/lowRISC/opentitan/blob/master/hw/dv/tools/dvsim/tests/shadow_reg_errors_tests.hjson) and [shadow_reg_errors_testplan](https://github.com/lowRISC/opentitan/blob/master/hw/dv/tools/dvsim/testplans/shadow_reg_errors_testplan.hjson).
The details of the test sequences are described in the testplan. Users need to assign the status CSR fields to `cfg.shadow_update_err_status_fields` and `cfg.shadow_storage_err_status_fields` for update error and storage error respectively.
```systemverilog
class ip_env_cfg extends cip_base_env_cfg #(.RAL_T(ip_reg_block));
virtual function void initialize(bit [31:0] csr_base_addr = '1);
super.initialize(csr_base_addr);
// csr / field name may vary in different IPs
shadow_update_err_status_fields[ral.err_code.invalid_shadow_update] = 1;
shadow_storage_err_status_fields[ral.fault_status.shadow] = 1;
```
### Security Verification for REGWEN CSRs
If the REGWEN CSR meets the following criteria, it can be fully verified by the common [csr_tests](https://github.com/lowRISC/opentitan/blob/master/hw/dv/tools/dvsim/tests/csr_tests.hjson).
- The REGWEN CSR and its related lockable CSRs are HW read-only registers.
- The related lockable CSRs are not WO type, otherwise the read value is always 0 and CSR tests can't really verify if the write value is taken or not.
- No CSR exclusions have been added to the REGWEN CSR and its related lockable CSRs.
If not, users need to write a test to verify it separately since cip_lib and dv_base_reg can't predict its value.
For example, the [sram_ctrl_regwen_vseq](https://github.com/lowRISC/opentitan/blob/master/hw/ip/sram_ctrl/dv/env/seq_lib/sram_ctrl_regwen_vseq.sv) has been added to verify `ctrl_regwen` and the the lockable register `ctrl` since `ctrl` is a `WO` register and excluded in CSR tests.
Functional coverage for REGWEN CSRs and their related lockable CSRs is generated automatically in dv_base_reg.
The details of functional coverage is described in [csr_testplan](https://github.com/lowRISC/opentitan/blob/master/hw/dv/tools/dvsim/testplans/csr_testplan.hjson).
### Security Verification for MUBI type CSRs
A functional covergroup of MUBI type CSR is automatically created in the RAL model for each MUBI CSR, which ensures `True`, `False` and at least N of other values (N = width of the MUBI type) have been collected.
This covergroup won't be sampled in CSR tests, since CSR tests only test the correctness of the value of register read / write but it won't check the block behavior when a different value is supplied to the MUBI CSR.
Users should randomize the values of all the MUBI CSRs in non-CSR tests and check the design behaves correctly.
The helper functions `cip_base_pkg::get_rand_mubi4|8|12|16_val(t_weight, f_weight, other_weight)` can be used to get the random values.
### Security Verification for MUBI/LC_TX type ports
In OpenTitan [Design Verification Methodology](../../../../doc/contributing/dv/methodology/README.md), it's mandatory to have 100% toggle coverage on all the ports.
However, the MUBI defined values (`True` and `False`) are complement numbers.
If users only test with `True` and `False` without using other values, toggle coverage can be 100%.
Hence, user should add a functional covergroup for each MUBI type input port, via binding the interface `cip_mubi_cov_if` which contains a covergroup for MUBI.
The type `lc_ctrl_pkg::lc_tx_t` is different than the Mubi4 type, as its defined values are different.
So, it needs to be bound with the interface `cip_lc_tx_cov_if`.
The helper functions `cip_base_pkg::get_rand_mubi4|8|12|16_val(t_weight, f_weight, other_weight)` and `cip_base_pkg::get_rand_lc_tx_val` can be used to get the random values.
The following is an example from `sram_ctrl`, in which it binds the coverage interface to 2 MUBI input ports.
```systemverilog
module sram_ctrl_cov_bind;
bind sram_ctrl cip_mubi_cov_if #(.Width(4)) u_hw_debug_en_mubi_cov_if (
.rst_ni (rst_ni),
.mubi (lc_hw_debug_en_i)
);
bind sram_ctrl cip_lc_tx_cov_if u_lc_escalate_en_cov_if (
.rst_ni (rst_ni),
.val (lc_escalate_en_i)
);
endmodule
```
Note: The `sim_tops` in sim_cfg.hjson should be updated to include this bind file.
### Security Verification for common countermeasure primitives
A [security countermeasure verification framework](../../../../doc/contributing/dv/sec_cm_dv_framework/README.md) is implemented in cip_lib to verify common countermeasure primitives in a semi-automated way.
#### Design Verification
cip_lib imports [sec_cm_pkg](https://github.com/lowRISC/opentitan/tree/master/hw/dv/sv/sec_cm), which automatically locates all the common countermeasure primitives and binds an interface to each of them.
In the cib_base_vseq, it injects a fault to each of these primitives and verifies that the fault will lead to a fatal alert.
The details of the sequences can be found in testplans - [sec_cm_count_testplan](https://github.com/lowRISC/opentitan/blob/master/hw/dv/tools/dvsim/testplans/sec_cm_count_testplan.hjson), [sec_cm_fsm_testplan](https://github.com/lowRISC/opentitan/blob/master/hw/dv/tools/dvsim/testplans/sec_cm_fsm_testplan.hjson) and [sec_cm_double_lfsr_testplan](https://github.com/lowRISC/opentitan/blob/master/hw/dv/tools/dvsim/testplans/sec_cm_double_lfsr_testplan.hjson).
If the block uses common security countermeasure primitives (prim_count, prim_sparse_fsm_flop, prim_double_lfsr), users can enable this sequence to fully verify them via following steps.
1. Import the applicable sec_cm testplans.
If more checks or sequences are needed, add another testpoint in the block testplan.
For example, when the fault is detected by countermeasure, some subsequent operations won’t be executed.
Add a testpoint in the testplan to capture this sequence and the checks.
2. Import [sec_cm_tests](https://github.com/lowRISC/opentitan/blob/master/hw/dv/tools/dvsim/tests/sec_cm_tests.hjson) in sim_cfg.hjson file, as well as add applicable sec_cm bind files for `sim_tops`.
The `ip_sec_cm` test will be added and all common countermeasure primitives will be verified in this test.
```
sim_tops: ["ip_ctrl_bind", "ip_ctrl_cov_bind",
// only add the corresponding bind file if DUT has the primitive
"sec_cm_prim_sparse_fsm_flop_bind",
"sec_cm_prim_count_bind",
"sec_cm_prim_double_lfsr_bind"]
```
3. Import sec_cm_pkg in the env_pkg
```systemverilog
package ip_env_pkg;
import uvm_pkg::*;
import sec_cm_pkg::*;
```
4. Set alert name for countermeasure if the alert name is different from the default name - “fatal_fault”.
```systemverilog
class ip_env_cfg extends cip_base_env_cfg #(.RAL_T(ip_reg_block));
virtual function void initialize(bit [31:0] csr_base_addr = '1);
super.initialize(csr_base_addr);
sec_cm_alert_name = "fatal_check_error";
```
5. Override the `check_sec_cm_fi_resp` task in ip_common_vseq to add additional sequences and checks after fault injection.
This is an example from keymgr, in which CSR `fault_status` will be updated according to the location of the fault and the operation after fault inject will lead design to enter `StInvalid` state.
```systemverilog
class keymgr_common_vseq extends keymgr_base_vseq;
virtual task check_sec_cm_fi_resp(sec_cm_base_if_proxy if_proxy);
bit[TL_DW-1:0] exp;
super.check_sec_cm_fi_resp(if_proxy);
case (if_proxy.sec_cm_type)
SecCmPrimCount: begin
// more than one prim_count are used, distinguishing them through the path of the primitive.
if (!uvm_re_match("*.u_reseed_ctrl*", if_proxy.path)) begin
exp[keymgr_pkg::FaultReseedCnt] = 1;
end else begin
exp[keymgr_pkg::FaultCtrlCnt] = 1;
end
end
SecCmPrimSparseFsmFlop: begin
exp[keymgr_pkg::FaultCtrlFsm] = 1;
end
default: `uvm_fatal(`gfn, $sformatf("unexpected sec_cm_type %s", if_proxy.sec_cm_type.name))
endcase
csr_rd_check(.ptr(ral.fault_status), .compare_value(exp));
// after an advance, keymgr should enter StInvalid
keymgr_advance();
csr_rd_check(.ptr(ral.op_status), .compare_value(keymgr_pkg::OpDoneFail));
csr_rd_check(.ptr(ral.working_state), .compare_value(keymgr_pkg::StInvalid));
endtask : check_sec_cm_fi_resp
```
6. Fault injection may trigger unexpected SVA errors. Override the `sec_cm_fi_ctrl_svas` function to disable them. `sec_cm_fi_ctrl_svas(.enable(1))` will be invoked before injecting fault. After reset, `sec_cm_fi_ctrl_svas(.enable(0))` will be called to re-enable the SVA checks.
```systemverilog
class keymgr_common_vseq extends keymgr_base_vseq;
virtual function void sec_cm_fi_ctrl_svas(sec_cm_base_if_proxy if_proxy, bit enable);
case (if_proxy.sec_cm_type)
SecCmPrimCount: begin
if (enable) begin
$asserton(0, "tb.keymgr_kmac_intf");
$asserton(0, "tb.dut.tlul_assert_device.gen_device.dDataKnown_A");
$asserton(0, "tb.dut.u_ctrl.DataEn_A");
$asserton(0, "tb.dut.u_ctrl.DataEnDis_A");
$asserton(0, "tb.dut.u_ctrl.CntZero_A");
$asserton(0, "tb.dut.u_kmac_if.LastStrb_A");
$asserton(0, "tb.dut.KmacDataKnownO_A");
end else begin
$assertoff(0, "tb.keymgr_kmac_intf");
$assertoff(0, "tb.dut.tlul_assert_device.gen_device.dDataKnown_A");
$assertoff(0, "tb.dut.u_ctrl.DataEn_A");
$assertoff(0, "tb.dut.u_ctrl.DataEnDis_A");
$assertoff(0, "tb.dut.u_ctrl.CntZero_A");
$assertoff(0, "tb.dut.u_kmac_if.LastStrb_A");
$assertoff(0, "tb.dut.KmacDataKnownO_A");
end
end
SecCmPrimSparseFsmFlop: begin
// No need to disable any assertion
end
default: `uvm_fatal(`gfn, $sformatf("unexpected sec_cm_type %s", if_proxy.sec_cm_type.name))
endcase
endfunction: sec_cm_fi_ctrl_svas
```
#### Formal Verification
Please refer to [formal document](../../../formal/README.md) on how to create a FPV environment for common countermeasures.