Update paths for vendored DV code

This commit amends some paths in the vendoring hjson file (and updates
config files to use things at the new paths). Finally it re-runs the
vendoring tool:

  Update code from upstream repository
  https://github.com/lowRISC/opentitan to revision
  92e9242424c72c59008e267dd3779e2af5ec8e83

which just ends up with a load of file renames.

Signed-off-by: Rupert Swarbrick <rswarbrick@lowrisc.org>
This commit is contained in:
Rupert Swarbrick 2020-11-26 17:41:38 +00:00 committed by Rupert Swarbrick
parent 77d8010015
commit 690f8af65e
316 changed files with 35 additions and 35 deletions

View file

@ -0,0 +1,29 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
//----------------------------- DESCRIPTION ------------------------------------
//
// Generic clock interface for clock events in various utilities
//
//------------------------------------------------------------------------------
interface clk_if(input logic clk);
clocking cb @(posedge clk);
endclocking
clocking cbn @(negedge clk);
endclocking
// Wait for 'n' clocks based of postive clock edge
task automatic wait_clks(int num_clks);
repeat (num_clks) @cb;
endtask
// Wait for 'n' clocks based of negative clock edge
task automatic wait_n_clks(int num_clks);
repeat (num_clks) @cbn;
endtask
endinterface

View file

@ -0,0 +1,269 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
//
// Interface: clk_rst_if
// Generic clock and reset interface for clock events in various utilities
// It also generates o_clk and o_rst_n signals for driving clk and rst_n in the tb. The advantage is
// clk and rst_n can be completely controlled in course of the simulation.
// This interface provides methods to set freq/period, wait for clk/rst_n, apply rst_n among other
// things. See individual method descriptions below.
// inout clk
// inout rst_n
interface clk_rst_if #(
parameter string IfName = "main"
) (
inout clk,
inout rst_n
);
`ifndef VERILATOR
// include macros and import pkgs
`include "dv_macros.svh"
`include "uvm_macros.svh"
import uvm_pkg::*;
`endif
bit drive_clk; // enable clk generation
logic o_clk; // output clk
bit drive_rst_n; // enable rst_n generation
logic o_rst_n; // output rst_n
// clk params
bit clk_gate = 1'b0; // clk gate signal
int clk_period_ps = 20_000; // 50MHz default
real clk_freq_mhz = 50; // 50MHz default
int duty_cycle = 50; // 50% default
int max_jitter_ps = 1000; // 1ns default
bit recompute = 1'b1; // compute half periods when period/freq/duty are changed
int clk_hi_ps; // half period hi in ps
int clk_lo_ps; // half period lo in ps
int jitter_chance_pc = 0; // jitter chance in percentage on clock edge - disabled by default
bit sole_clock = 1'b0; // if true, this is the only clock in the system
// use IfName as a part of msgs to indicate which clk_rst_vif instance
string msg_id = {"clk_rst_if::", IfName};
clocking cb @(posedge clk);
endclocking
clocking cbn @(negedge clk);
endclocking
// Wait for 'n' clocks based of postive clock edge
task automatic wait_clks(int num_clks);
repeat (num_clks) @cb;
endtask
// Wait for 'n' clocks based of negative clock edge
task automatic wait_n_clks(int num_clks);
repeat (num_clks) @cbn;
endtask
// wait for rst_n to assert and then deassert
task automatic wait_for_reset(bit wait_negedge = 1'b1, bit wait_posedge = 1'b1);
if (wait_negedge && ($isunknown(rst_n) || rst_n === 1'b1)) @(negedge rst_n);
if (wait_posedge && (rst_n === 1'b0)) @(posedge rst_n);
endtask
// set the clk frequency in khz
function automatic void set_freq_khz(int freq_khz);
clk_freq_mhz = $itor(freq_khz) / 1000;
clk_period_ps = 1000_000 / clk_freq_mhz;
recompute = 1'b1;
endfunction
// set the clk frequency in mhz
function automatic void set_freq_mhz(int freq_mhz);
set_freq_khz(freq_mhz * 1000);
endfunction
// call this function at t=0 (from tb top) to enable clk and rst_n to be driven
function automatic void set_active(bit drive_clk_val = 1'b1, bit drive_rst_n_val = 1'b1);
time t = $time;
if (t == 0) begin
drive_clk = drive_clk_val;
drive_rst_n = drive_rst_n_val;
end
else begin
`ifdef VERILATOR
$error({msg_id, "this function can only be called at t=0"});
`else
`uvm_fatal(msg_id, "this function can only be called at t=0")
`endif
end
endfunction
// set the clk period in ns
function automatic void set_period_ns(int period_ps);
clk_period_ps = period_ps;
clk_freq_mhz = 1000_000 / clk_period_ps;
recompute = 1'b1;
endfunction
// set the duty cycle (1-99)
function automatic void set_duty_cycle(int duty);
if (!(duty inside {[1:99]})) begin
`ifdef VERILATOR
$error({msg_id, $sformatf("duty cycle %0d is not inside [1:99]", duty)});
`else
`uvm_fatal(msg_id, $sformatf("duty cycle %0d is not inside [1:99]", duty))
`endif
end
duty_cycle = duty;
recompute = 1'b1;
endfunction
// set maximum jitter in ps
function automatic void set_max_jitter_ps(int jitter_ps);
max_jitter_ps = jitter_ps;
endfunction
// set jitter chance in percentage (0 - 100)
// 0 - dont add any jitter; 100 - add jitter on every clock edge
function automatic void set_jitter_chance_pc(int jitter_chance);
if (!(jitter_chance inside {[0:100]})) begin
`ifdef VERILATOR
$error({msg_id, $sformatf("jitter_chance %0d is not inside [0:100]", jitter_chance)});
`else
`uvm_fatal(msg_id, $sformatf("jitter_chance %0d is not inside [0:100]", jitter_chance))
`endif
end
jitter_chance_pc = jitter_chance;
endfunction
// Set whether this is the only clock in the system. If true, various bits of timing randomisation
// are disabled. If there's no other clock to (de)synchronise with, this should not weaken the
// test at all.
function automatic void set_sole_clock(bit is_sole = 1'b1);
sole_clock = is_sole;
endfunction
// start / ungate the clk
task automatic start_clk(bit wait_for_posedge = 1'b0);
clk_gate = 1'b0;
if (wait_for_posedge) wait_clks(1);
endtask
// stop / gate the clk
function automatic void stop_clk();
clk_gate = 1'b1;
endfunction
// add jitter to clk_hi and clk_lo half periods based on jitter_chance_pc
function automatic void add_jitter();
int jitter_ps;
if ($urandom_range(1, 100) <= jitter_chance_pc) begin
`ifndef VERILATOR
`DV_CHECK_STD_RANDOMIZE_WITH_FATAL(jitter_ps,
jitter_ps inside {[-1*max_jitter_ps:max_jitter_ps]};, "", msg_id)
`endif
clk_hi_ps += jitter_ps;
end
if ($urandom_range(1, 100) <= jitter_chance_pc) begin
`ifndef VERILATOR
`DV_CHECK_STD_RANDOMIZE_WITH_FATAL(jitter_ps,
jitter_ps inside {[-1*max_jitter_ps:max_jitter_ps]};, "", msg_id)
`endif
clk_lo_ps += jitter_ps;
end
endfunction
// can be used to override clk/rst pins, e.g. at the beginning of the simulation
task automatic drive_rst_pin(logic val = 1'b0);
o_rst_n = val;
endtask
// apply reset with specified scheme
// TODO make this enum?
// rst_n_scheme
// 0 - fullly synchronous reset - it is asserted and deasserted on clock edges
// 1 - async assert, sync dessert (default)
// 2 - async assert, async dessert
// 3 - clk gated when reset asserted
// Note: for power on reset, please ensure pre_reset_dly_clks is set to 0
// TODO #2338 issue workaround - $urandom call moved from default argument value to function body
task automatic apply_reset(int pre_reset_dly_clks = 0,
integer reset_width_clks = 'x,
int post_reset_dly_clks = 0,
int rst_n_scheme = 1);
int dly_ps;
if ($isunknown(reset_width_clks)) reset_width_clks = $urandom_range(4, 20);
dly_ps = $urandom_range(0, clk_period_ps);
wait_clks(pre_reset_dly_clks);
case (rst_n_scheme)
0: begin : sync_assert_deassert
o_rst_n <= 1'b0;
wait_clks(reset_width_clks);
o_rst_n <= 1'b1;
end
1: begin : async_assert_sync_deassert
#(dly_ps * 1ps);
o_rst_n <= 1'b0;
wait_clks(reset_width_clks);
o_rst_n <= 1'b1;
end
2: begin : async_assert_async_deassert
#(dly_ps * 1ps);
o_rst_n <= 1'b0;
wait_clks(reset_width_clks);
dly_ps = $urandom_range(0, clk_period_ps);
#(dly_ps * 1ps);
o_rst_n <= 1'b1;
end
default: begin
`ifdef VERILATOR
$error({msg_id, $sformatf("rst_n_scheme %0d not supported", rst_n_scheme)});
`else
`uvm_fatal(msg_id, $sformatf("rst_n_scheme %0d not supported", rst_n_scheme))
`endif
end
endcase
wait_clks(post_reset_dly_clks);
endtask
// clk gen
initial begin
// start driving clk only after the first por reset assertion. The fork/join means that we'll
// wait a whole number of clock periods, which means it's possible for the clock to synchronise
// with the "expected" timestamps.
bit done;
fork
begin
wait_for_reset(.wait_posedge(1'b0));
// Wait a short time after reset before starting to drive the clock.
#1ps;
o_clk = 1'b0;
done = 1'b1;
end
while (!done) #(clk_period_ps * 1ps);
join
// If there might be multiple clocks in the system, wait another (randomised) short time to
// desynchronise.
if (!sole_clock) #($urandom_range(0, clk_period_ps) * 1ps);
forever begin
if (recompute) begin
clk_hi_ps = clk_period_ps * duty_cycle / 100;
clk_lo_ps = clk_period_ps - clk_hi_ps;
recompute = 1'b0;
end
if (jitter_chance_pc != 0) add_jitter();
#(clk_lo_ps * 1ps);
// wiggle output clk if not gated
if (!clk_gate) o_clk = 1'b1;
#(clk_hi_ps * 1ps);
o_clk = 1'b0;
end
end
assign clk = drive_clk ? o_clk : 1'bz;
assign rst_n = drive_rst_n ? o_rst_n : 1'bz;
endinterface

View file

@ -0,0 +1,20 @@
CAPI=2:
# Copyright lowRISC contributors.
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
# SPDX-License-Identifier: Apache-2.0
name: "lowrisc:dv:common_ifs"
description: "Common interfaces used in DV"
filesets:
files_dv:
depend:
- lowrisc:dv:pins_if
files:
- clk_if.sv
- clk_rst_if.sv
file_type: systemVerilogSource
targets:
default:
filesets:
- files_dv

View file

@ -0,0 +1,81 @@
# Common interfaces
## Overview
In this directory, we provide commonly used interfaces used to construct
testbenches for DV. These interfaces are instantiated inside `tb` module for
connecting dut signals. They are described in detail below.
### `clk_if`
This is a passive clock interface that is used to wait for clock events in
testbenches.
This interface has two clocking blocks: `cb` and `cbn` for synchronizing to
positive and negative clock edges. This interface consists of following tasks:
* `wait_clks`: waits for specified number of positive clock edges
* `wait_n_clks`: waits for specified number of negative clock edges
### `clk_rst_if`
This interface provides the ability to drive / sample clock and reset signal.
It provides various methods related to clock and reset generation. These
methods can be categorized into `setup methods` and `drive / sample` methods.
Following are `setup methods` of `pins_if`:
* `set_freq_mhz`: set the clk frequency in mhz and calclate period in ns
* `set_duty_cycle`: set the duty cycle (1-99)
* `set_active`: enables `clk` and `rst_n` generation
typically, called at t=0 (from tb top)
* `set_period_ns`: set the clk period in ns and calculate frequency in mhz
* `set_jitter_chance_pc`: set jitter chance in percentage (0 - 100)
* 0: do not add any jitter
* 100: add jitter on every clock edge
* `set_max_jitter_ps`: set maximum jitter in ps
Following are `drive / sample` methods of `pins_if`:
* `wait_for_reset`: wait for rst_n to assert and then deassert
* `apply_reset`: apply reset with specified scheme out of following:
* fullly synchronous reset
* async assert, sync dessert
* async assert, async dessert
* clk gated when reset asserted
* `add_jitter`: add jitter to `clk_hi` and `clk_lo` half periods based on
`jitter_chance_pc`
* `start_clk`: start / ungate clock
* `stop_clk`: stop / gate the clk
* `wait_clks`: waits for specified number of positive clock edges
* `wait_n_clks`: waits for specified number of negative clock edges
### `pins_if`
This paramterized interface provides the ability to drive / sample any signal
in the DUT.
```systemverilog
interface pins_if #(
parameter int Width = 1
) (
inout [Width-1:0] pins
);
```
The member `pins` is inout type and it can be connected to any of input or
output port within of dut to drive or sample them. `pins` can be driven either
internally using `pins_o` and `pins_oe` signals, that constitute a tri-state
buffer implementation. This provide an ability to disconnects `pins` by driving
them to high impedance state. `pins` may also be driven through an external
driver that it gets connected to. This interface also provides capability
to drive weak pull-up or pull-down on `pins` in case of no internal or external
drivers. The members `pins_pu` and `pins_pd` control weak pull-up or pull-down
functionality. Following diagram explains working of `pins_if`:
## `pins_if` block diagram
![Block diagram](pins_if.svg)
Some of the commonly used methods of `pins_if` are as follows:
* `drive_en_pin`: Drive specified value `val` on specified index `idx` of
`pins_oe` signal
* `drive_en`: Drive `pins_oe` signal to specified value `val`
* `drive_pin`: Drive specified index `idx` of pins_oe signal to 1, and the same
index of `pins_o` to specified value `val`
value
* `drive`: Drive `pins_oe` to all 1's and specified value `val` on `pins_o`
* `sample_pin`: Sample and return value of `pins[idx]` for specified index `idx`
* `sample`: Sample and return value of `pins`
* `set_pullup_en`: Implement pull-up on specific bits of `pins` based on
specified value `val`
* `set_pulldown_en`: Implement pull-down on specifc bits of `pins` based on
specified value `val`

View file

@ -0,0 +1,96 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
// Interface: pins_if
// Description: Pin interface for driving and sampling individual pins such as interrupts, alerts
// and gpios.
`ifndef SYNTHESIS
interface pins_if #(
parameter int Width = 1
) (
inout [Width-1:0] pins
);
logic [Width-1:0] pins_o; // value to be driven out
wire [Width-1:0] pins_int; // value of pin using internal pull-up / pull-down
bit [Width-1:0] pins_oe = '0; // output enable
bit [Width-1:0] pins_pd = '0; // pull down enable
bit [Width-1:0] pins_pu = '0; // pull up enable
// function to set pin output enable for specific pin (useful for single pin interface)
function automatic void drive_en_pin(int idx = 0, bit val);
pins_oe[idx] = val;
endfunction
// function to set pin output enable for all pins
function automatic void drive_en(bit [Width-1:0] val);
pins_oe = val;
endfunction
// function to drive a specific pin with a value (useful for single pin interface)
function automatic void drive_pin(int idx = 0, logic val);
pins_oe[idx] = 1'b1;
pins_o[idx] = val;
endfunction // drive_pin
// function to drive all pins
function automatic void drive(logic [Width-1:0] val);
pins_oe = {Width{1'b1}};
pins_o = val;
endfunction // drive
// function to drive all pull down values
function automatic void set_pulldown_en(bit [Width-1:0] val);
pins_pd = val;
endfunction // set_pulldown_en
// function to drive all pull up values
function automatic void set_pullup_en(bit [Width-1:0] val);
pins_pu = val;
endfunction // set_pullup_en
// function to drive the pull down value on a specific pin
function automatic void set_pulldown_en_pin(int idx = 0, bit val);
pins_pd[idx] = val;
endfunction // set_pulldown_en_pin
// function to drive the pull up value on a specific pin
function automatic void set_pullup_en_pin(int idx = 0, bit val);
pins_pu[idx] = val;
endfunction // set_pullup_en_pin
// function to sample a specific pin (useful for single pin interface)
function automatic logic sample_pin(int idx = 0);
return pins[idx];
endfunction
// function to sample all pins
function automatic logic [Width-1:0] sample();
return pins;
endfunction
// make connections
generate
for (genvar i = 0; i < Width; i++) begin : each_pin
assign pins_int[i] = pins_pd[i] ? 1'b0 :
pins_pu[i] ? 1'b1 : 1'bz;
// If output enable is 1, strong driver assigns pin to 'value to be driven out';
// the external strong driver can still affect pin, if exists.
// Else if output enable is 0, weak pullup or pulldown is applied to pin.
// By doing this, we make sure that weak pullup or pulldown does not override
// any 'x' value on pin, that may result due to conflicting values
// between 'value to be driven out' and the external driver's value.
assign pins[i] = pins_oe[i] ? pins_o[i] : 1'bz;
`ifdef VERILATOR
assign pins[i] = ~pins_oe[i] ? pins_int[i] : 1'bz;
`else
assign (pull0, pull1) pins[i] = ~pins_oe[i] ? pins_int[i] : 1'bz;
`endif
end
endgenerate
endinterface
`endif

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 56 KiB

View file

@ -0,0 +1,17 @@
CAPI=2:
# Copyright lowRISC contributors.
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
# SPDX-License-Identifier: Apache-2.0
name: "lowrisc:dv:pins_if"
description: "Common interfaces used in DV"
filesets:
files_dv:
files:
- pins_if.sv
file_type: systemVerilogSource
targets:
default:
filesets:
- files_dv

View file

@ -0,0 +1,159 @@
# CSR utilities
This csr_utils folder intends to implement CSR related methods and test sequences for DV
to share across all testbenches.
### CSR utility package
`csr_utils_pkg` provides common methods and properties to support and manage CSR accesses
and CSR related test sequences.
#### Global types and variables
All common types and variables are defined at this package level. Examples are:
```systemverilog
uint outstanding_accesses = 0;
uint default_timeout_ns = 1_000_000;
```
##### Outstanding_accesses
`csr_utils_pkg` used an internal variable to store the number of accesses
(read or write) that have not yet completed. This variable is shared among all methods of
register reading and writing. Directly accessing this variable is discouraged. Instead,
the following methods are used to control this variable to keep track of non-blocking
accesses made in the testbench:
```systemverilog
function automatic void increment_outstanding_access();
outstanding_accesses++;
endfunction
function automatic void decrement_outstanding_access();
outstanding_accesses--;
endfunction
task automatic wait_no_outstanding_access();
wait(outstanding_accesses == 0);
endtask
function automatic void clear_outstanding_access();
outstanding_accesses = 0;
endfunction
```
##### CSR spinwait
One of the commonly used tasks in `csr_utils_pkg` is `csr_spinwait`. This task
can poll a CSR or CSR field continuously or periodically until it reads out the
expected value. This task also has a timeout check in case due to DUT or testbench
issue, the CSR or CSR field never returns the expected value.
Example below uses the `csr_spinwait` to wait until the CSR `fifo_status` field
`fifo_full` reaches value bit 1:
```systemverilog
csr_spinwait(.ptr(ral.status.fifo_full), .exp_data(1'b0));
```
##### Read and check all CSRs
The purpose of the `read_and_check_all_csrs` task is to read all valid CSRs from
the given `uvm_reg_block` and check against their expected values from RAL. This
task is primarily implemented to use after reset, to make sure all the CSRs are
being reset to the default value.
##### Under_reset
Due to `csr_utils_pkg` is not connected to any interface, methods inside
this package are not able to get reset information. Current the `under_reset`
bit is declared with two functions:
```systemverilog
function automatic void reset_asserted();
under_reset = 1;
endfunction
function automatic void reset_deasserted();
under_reset = 0;
endfunction
```
This reset information is updated in `dv_lib/dv_base_vseq.sv`. When the
`apply_reset` task is triggered, it will set and reset the `under_reset` bit
via the functions above.
#### Global CSR util methods
##### Global methods for CSR and MEM attributes
This package provides methods to access CSR or Memory attributes, such as address,
value, etc. Examples are:
* `get_csr_addrs`
* `get_mem_addr_ranges`
* `decode_csr_or_field`
##### Global methods for CSR access
The CSR access methods are based on `uvm_reg` methods, such as `uvm_reg::read()`,
`uvm_reg::write()`, `uvm_reg::update()`. For all CSR methods, user can
pass either a register or a field handle. Examples are:
* `csr_rd_check`: Given the uvm_reg or uvm_reg_field object, this method will
compare the CSR value with the expected value (given as an input) or with
the RAL mirrored value
* `csr_update`: Given the uvm_reg object, this method will update the value of the
register in DUT to match the desired value
To enhance the usability, these methods support CSR blocking, non-blocking
read/write, and a timeout checking.
* A blocking thread will not execute the next sequence until the current CSR
access is finished
* A non-blocking thread allows multiple CSR accesses to be issued back-to-back
without waiting for the response
* A timeout check will discard the ongoing CSR access by disabling the forked
thread and will throw a UVM_ERROR once the process exceeds the max timeout setting
### CSR sequence library
`csr_seq_lib.sv` provides common CSR related test sequences to share across all testbenches.
These test sequences are based off the standard sequences provided in UVM1.2 RAL.
The parent class (DUT-specific test or sequence class) that creates them needs to provide them
with the DUT RAL model. The list of CSRs are then extracted from the RAL model to performs the checks.
In addition, the test sequences provide an ability to exclude a CSR from writes or reads (or both)
depending on the behavior of the CSR in the design. This is explained more in the
[CSR exclusion methodology](#csr-exclusion-methodology) section below.
All CSR accesses in these sequences are made non-blocking to ensure back-to-back scenarios
are exercised.
Supported CSR test sequences are:
* `csr_hw_reset`: Write all CSRs with random values and then reset the DUT.
After reset, read all CSRs and compare with expected values
* `csr_rw`: Write a randomly selected CSRs, then read out the updated
CSR or CSR field and compare with expected value
* `csr_bit_bash`: Randomly select a CSR and write 1's and 0's to
every bit, then read the CSR to compare with expected value
* `csr_aliasing`: Randomly write a CSR, then read all CSRs to
verify that only the CSR that was written was updated
* `mem_walk`: Write and read all valid addresses in the memory. Compare
the read results with the expected values
### CSR exclusion methodology
The CSR test sequences listed above intend to perform a sanity check to CSR
read/write accesses, but do not intend to check specific DUT functionalities. Thus the
sequences might need to exclude reading or writing certain CSRs depending on the
specific testbench.
`csr_excl_item` is a class that supports adding exclusions to CSR test sequences.
Examples of useful functions in this class are:
* `add_excl`: Add exclusions to the CSR test sequences. This function has two inputs:
- Exclusion scope: A hierarchical path name at all levels including block,
CSR, and field. This input supports * and ? wildcards for glob style matching
- CSR_exclude type: An enumeration defined as below:
```systemverilog
typedef enum bit[2:0] {
CsrNoExcl = 3'b000, // no exclusions
CsrExclInitCheck = 3'b001, // exclude csr from init val check
CsrExclWriteCheck = 3'b010, // exclude csr from write-read check
CsrExclCheck = 3'b011, // exclude csr from init or write-read check
CsrExclWrite = 3'b100, // exclude csr from write
CsrExclAll = 3'b111 // exclude csr from init or write or write-read check
} csr_excl_type_e;
```
One example to use this function in HMAC to exclude all CSRs or fields with
names starting with "key":
```systemverilog
csr_excl.add_excl({scope, ".", "key?"}, CsrExclWrite);
```
* `has_excl`: Check if the CSR has a match in the existing exclusions loopup,
and is not intended to use externally
### CSR sequence framework
The [cip_lib]({{< relref "hw/dv/sv/cip_lib/doc" >}}) includes a virtual sequence named `cip_base_vseq`,
that provides a common framework for all testbenchs to run these CSR test sequences and
add exclusions.

View file

@ -0,0 +1,500 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
// CSR suite of sequences that do writes and reads to csrs
// includes hw_reset, rw, bit_bash and aliasing tests for csrs, and mem_walk for uvm_mems
// TODO: when mem backdoor is implemented, add uvm_mem_access_seq for backdoor rd
// The sequences perform csr writes and reads and follow the standard csr test suite. If external
// checker is enabled, then the external entity is required to update the mirrored value on
// writes. If not enabled, the sequences themselves call predict function to update the mirrored
// value. Consequently, the read values are checked against the mirrored value and not the
// previously written value. This approach is better since it takes care of special
// register and field access policies. Also, we use csr_rd_check task instead of csr_mirror to take
// field exclusions into account.
//
// Csrs to be tested is accumulated and shuffled from the supplied reg models.
// What / how many csrs to test can be further controlled in 3 ways -
// 1. Externally add specific csrs to test_csrs queue (highest prio)
// 2. Set num_test_csrs test a randomly picked set of csrs from the supplied models
// 3. Set / pass via plusarg, num_csr_chunks / test_csr_chunk
//
// Exclusions are to be provided using the csr_excl_item item (see class for more details).
class csr_base_seq extends uvm_reg_sequence #(uvm_sequence #(uvm_reg_item));
`uvm_object_utils(csr_base_seq)
uvm_reg_block models[$];
uvm_reg all_csrs[$];
uvm_reg test_csrs[$];
csr_excl_item m_csr_excl_item;
// By default, assume external checker (example, scoreboard) is turned off. If that is the case,
// then writes are followed by call to predict function to update the mirrored value. Reads are
// then checked against the mirrored value using csr_rd_check task. If external checker is
// enabled, then we let the external checker do the predict and compare.
// In either case, we should be able to do completely non-blocking writes and reads.
bit external_checker = 1'b0;
// either use num_test_csrs or {test_csr_chunk, num_csr_chunks} to test slice of all csrs
int num_test_csrs = 0;
int test_csr_chunk = 1;
int num_csr_chunks = 1;
`uvm_object_new
// pre_start
virtual task pre_start();
super.pre_start();
// create test_csrs list only if its empty
if (test_csrs.size() == 0) set_csr_test_range();
// create dummy m_csr_excl_item if not supplied
if (m_csr_excl_item == null) begin
`uvm_info(`gtn, "m_csr_excl_item is null, creating a dummy one locally", UVM_LOW)
m_csr_excl_item = csr_excl_item::type_id::create("m_csr_excl_item");
end
endtask
// post_start
virtual task post_start();
super.post_start();
wait_no_outstanding_access();
test_csrs.delete();
endtask
function void set_csr_excl_item(csr_excl_item item);
this.m_csr_excl_item = item;
endfunction
// extract csrs and split and prune to a specified test_csr_chunk
virtual function void set_csr_test_range();
int start_idx;
int end_idx;
int chunk_size;
// extract all csrs from the model
// TODO: add and use function here instead that allows pre filtering csrs
all_csrs.delete();
foreach (models[i]) begin
models[i].get_registers(all_csrs);
end
if (num_test_csrs != 0) begin
num_csr_chunks = all_csrs.size / num_test_csrs + 1;
`DV_CHECK_STD_RANDOMIZE_WITH_FATAL(test_csr_chunk,
test_csr_chunk inside {[1:num_csr_chunks]};)
end
else begin
// extract test_csr_chunk, num_csr_chunks from plusargs
void'($value$plusargs("test_csr_chunk=%0d", test_csr_chunk));
void'($value$plusargs("num_csr_chunks=%0d", num_csr_chunks));
end
if (!(test_csr_chunk inside {[1:num_csr_chunks]})) begin
`uvm_fatal(`gtn, $sformatf({{"invalid opt +test_csr_chunk=%0d, +num_csr_chunks=%0d "},
{"(1 <= test_csr_chunk <= num_csr_chunks)"}},
test_csr_chunk, num_csr_chunks))
end
chunk_size = (num_test_csrs != 0) ? num_test_csrs : (all_csrs.size / num_csr_chunks + 1);
start_idx = (test_csr_chunk - 1) * chunk_size;
end_idx = test_csr_chunk * chunk_size;
if (end_idx >= all_csrs.size())
end_idx = all_csrs.size() - 1;
test_csrs = all_csrs[start_idx:end_idx];
`uvm_info(`gtn, $sformatf("testing %0d csrs [%0d - %0d] in all supplied models",
test_csrs.size(), start_idx, end_idx), UVM_MEDIUM)
foreach (test_csrs[i]) begin
`uvm_info(`gtn, $sformatf("test_csrs list: %0s, reset: 0x%0x", test_csrs[i].get_full_name(),
test_csrs[i].get_mirrored_value()), UVM_HIGH)
end
test_csrs.shuffle();
endfunction
endclass
//--------------------------------------------------------------------------------------------------
// Class: csr_hw_reset_seq
// Brief Description: This sequence reads all CSRs and checks it against the reset value provided
// in the RAL specification. Note that this does not sufficiently qualify as the CSR HW reset test.
// The 'full' CSR HW reset test is constructed externally by running the csr_write_seq below first,
// issuing reset and only then running this sequence.
//--------------------------------------------------------------------------------------------------
class csr_hw_reset_seq extends csr_base_seq;
`uvm_object_utils(csr_hw_reset_seq)
`uvm_object_new
virtual task body();
foreach (test_csrs[i]) begin
uvm_reg_data_t compare_mask;
// check if parent block or register is excluded from init check
if (m_csr_excl_item.is_excl(test_csrs[i], CsrExclInitCheck, CsrHwResetTest)) begin
`uvm_info(`gtn, $sformatf("Skipping register %0s due to CsrExclInitCheck exclusion",
test_csrs[i].get_full_name()), UVM_MEDIUM)
continue;
end
`uvm_info(`gtn, $sformatf("Verifying reset value of register %0s",
test_csrs[i].get_full_name()), UVM_MEDIUM)
compare_mask = get_mask_excl_fields(test_csrs[i], CsrExclInitCheck, CsrHwResetTest,
m_csr_excl_item);
csr_rd_check(.ptr (test_csrs[i]),
.blocking (0),
.compare (!external_checker),
.compare_vs_ral(1'b1),
.compare_mask (compare_mask));
end
endtask
endclass
//--------------------------------------------------------------------------------------------------
// Class: csr_write_seq
// Brief Description: This sequence writes a random value to all CSRs. It does not perform any
// checks. It is run as the first step of the CSR HW reset test.
//--------------------------------------------------------------------------------------------------
class csr_write_seq extends csr_base_seq;
static bit test_backdoor_path_done; // only run once
bit en_rand_backdoor_write;
`uvm_object_utils(csr_write_seq)
`uvm_object_new
virtual task body();
uvm_reg_data_t wdata;
// check all hdl paths are valid
if (!test_backdoor_path_done) begin
uvm_reg_mem_hdl_paths_seq hdl_check_seq;
hdl_check_seq = uvm_reg_mem_hdl_paths_seq::type_id::create("hdl_check_seq");
foreach (models[i]) begin
hdl_check_seq.model = models[i];
hdl_check_seq.start(null);
end
test_backdoor_path_done = 1;
end
foreach (test_csrs[i]) begin
dv_base_reg dv_csr;
bit backdoor;
// check if parent block or register is excluded from write
if (m_csr_excl_item.is_excl(test_csrs[i], CsrExclWrite, CsrHwResetTest)) begin
`uvm_info(`gtn, $sformatf("Skipping register %0s due to CsrExclWrite exclusion",
test_csrs[i].get_full_name()), UVM_MEDIUM)
continue;
end
`uvm_info(`gtn, $sformatf("Writing random data to register %0s",
test_csrs[i].get_full_name()), UVM_MEDIUM)
`DV_CHECK_STD_RANDOMIZE_FATAL(wdata)
wdata &= get_mask_excl_fields(test_csrs[i], CsrExclWrite, CsrHwResetTest, m_csr_excl_item);
`downcast(dv_csr, test_csrs[i])
if (en_rand_backdoor_write && !dv_csr.get_is_ext_reg()) begin
`DV_CHECK_STD_RANDOMIZE_WITH_FATAL(backdoor,
backdoor dist {0 :/ 7, 1 :/ 3};)
end
csr_wr(.csr(test_csrs[i]), .value(wdata), .blocking(0), .backdoor(backdoor));
end
endtask
endclass
//--------------------------------------------------------------------------------------------------
// Class: csr_rw_seq
// Brief Description: This seq writes a random value to a CSR and reads it back. The read value
// is checked for correctness while adhering to its access policies. A random choice is made between
// reading back the CSR as a whole or reading fields individually, so that partial accesses are made
// into the DUT as well.
//--------------------------------------------------------------------------------------------------
class csr_rw_seq extends csr_base_seq;
`uvm_object_utils(csr_rw_seq)
`uvm_object_new
rand bit do_csr_rd_check;
rand bit do_csr_field_rd_check;
constraint csr_or_field_rd_check_c {
// at least one of them should be set
do_csr_rd_check || do_csr_field_rd_check;
}
virtual task body();
foreach (test_csrs[i]) begin
uvm_reg_data_t wdata;
uvm_reg_data_t compare_mask;
uvm_reg_field test_fields[$];
// check if parent block or register is excluded from write
if (m_csr_excl_item.is_excl(test_csrs[i], CsrExclWrite, CsrRwTest)) begin
`uvm_info(`gtn, $sformatf("Skipping register %0s due to CsrExclWrite exclusion",
test_csrs[i].get_full_name()), UVM_MEDIUM)
continue;
end
`uvm_info(`gtn, $sformatf("Verifying register read/write for %0s",
test_csrs[i].get_full_name()), UVM_MEDIUM)
`DV_CHECK_FATAL(randomize(do_csr_rd_check, do_csr_field_rd_check))
`DV_CHECK_STD_RANDOMIZE_FATAL(wdata)
wdata &= get_mask_excl_fields(test_csrs[i], CsrExclWrite, CsrRwTest, m_csr_excl_item);
// if external checker is not enabled and writes are made non-blocking, then we need to
// pre-predict so that the mirrored value will be updated. if we dont, then csr_rd_check task
// might pick up stale mirrored value
// the pre-predict also needs to happen after the register is being written, to make sure the
// register is getting the updated access information.
csr_wr(.csr(test_csrs[i]), .value(wdata), .blocking(0), .predict(!external_checker));
// check if parent block or register is excluded from read-check
if (m_csr_excl_item.is_excl(test_csrs[i], CsrExclWriteCheck, CsrRwTest)) begin
`uvm_info(`gtn, $sformatf("Skipping register %0s due to CsrExclWriteCheck exclusion",
test_csrs[i].get_full_name()), UVM_MEDIUM)
continue;
end
compare_mask = get_mask_excl_fields(test_csrs[i], CsrExclWriteCheck, CsrRwTest,
m_csr_excl_item);
if (do_csr_rd_check) begin
csr_rd_check(.ptr (test_csrs[i]),
.blocking (0),
.compare (!external_checker),
.compare_vs_ral(1'b1),
.compare_mask (compare_mask));
end
if (do_csr_field_rd_check) begin
test_csrs[i].get_fields(test_fields);
test_fields.shuffle();
foreach (test_fields[j]) begin
bit compare = !m_csr_excl_item.is_excl(test_fields[j], CsrExclWriteCheck, CsrRwTest);
csr_rd_check(.ptr (test_fields[j]),
.blocking (0),
.compare (!external_checker && compare),
.compare_vs_ral(1'b1));
end
end
wait_if_max_outstanding_accesses_reached();
end
endtask
endclass
//--------------------------------------------------------------------------------------------------
// Class: csr_bit_bash_seq
// Brief Description: This sequence walks a 1 through each CSR by writing one bit at a time and
// reading the CSR back. The read value is checked for correctness while adhering to its access
// policies. This verifies that there is no aliasing within the fields / bits of a CSR.
//--------------------------------------------------------------------------------------------------
class csr_bit_bash_seq extends csr_base_seq;
`uvm_object_utils(csr_bit_bash_seq)
`uvm_object_new
virtual task body();
foreach (test_csrs[i]) begin
// check if parent block or register is excluded from write
if (m_csr_excl_item.is_excl(test_csrs[i], CsrExclWrite, CsrBitBashTest) ||
m_csr_excl_item.is_excl(test_csrs[i], CsrExclWriteCheck, CsrBitBashTest)) begin
`uvm_info(`gtn, $sformatf("Skipping register %0s due to CsrExclWrite/WriteCheck exclusion",
test_csrs[i].get_full_name()), UVM_MEDIUM)
continue;
end
`uvm_info(`gtn, $sformatf("Verifying register bit bash for %0s",
test_csrs[i].get_full_name()), UVM_MEDIUM)
begin
uvm_reg_field fields[$];
string mode[`UVM_REG_DATA_WIDTH];
uvm_reg_data_t dc_mask; // dont write or read
uvm_reg_data_t cmp_mask; // read but dont compare
int n_bits;
string field_access;
int next_lsb;
n_bits = test_csrs[i].get_n_bytes() * 8;
// Let's see what kind of bits we have...
test_csrs[i].get_fields(fields);
next_lsb = 0;
dc_mask = 0;
cmp_mask = 0;
foreach (fields[j]) begin
int lsb, w, dc, cmp;
field_access = fields[j].get_access(test_csrs[i].get_default_map());
cmp = (fields[j].get_compare() == UVM_NO_CHECK);
lsb = fields[j].get_lsb_pos();
w = fields[j].get_n_bits();
// Exclude write-only fields from compare because you are not supposed to read them
case (field_access)
"WO", "WOC", "WOS", "WO1", "NOACCESS", "": cmp = 1;
endcase
// skip fields that are wr-excluded
if (m_csr_excl_item.is_excl(fields[j], CsrExclWrite, CsrBitBashTest)) begin
`uvm_info(`gtn, $sformatf("Skipping field %0s due to CsrExclWrite exclusion",
fields[j].get_full_name()), UVM_MEDIUM)
dc = 1;
end
// ignore fields that are init or rd-excluded
cmp = m_csr_excl_item.is_excl(fields[j], CsrExclInitCheck, CsrBitBashTest) ||
m_csr_excl_item.is_excl(fields[j], CsrExclWriteCheck, CsrBitBashTest) ;
// Any unused bits on the right side of the LSB?
while (next_lsb < lsb) mode[next_lsb++] = "RO";
repeat (w) begin
mode[next_lsb] = field_access;
dc_mask[next_lsb] = dc;
cmp_mask[next_lsb] = cmp;
next_lsb++;
end
end
// Any unused bits on the left side of the MSB?
while (next_lsb < `UVM_REG_DATA_WIDTH)
mode[next_lsb++] = "RO";
// Bash the kth bit
for (int k = 0; k < n_bits; k++) begin
// Cannot test unpredictable bit behavior
if (dc_mask[k]) continue;
bash_kth_bit(test_csrs[i], k, mode[k], cmp_mask);
end
end
end
endtask
task bash_kth_bit(uvm_reg rg,
int k,
string mode,
uvm_reg_data_t mask);
uvm_reg_data_t val;
string err_msg;
`uvm_info(`gtn, $sformatf("bashing %0s bit #%0d", mode, k), UVM_HIGH)
repeat (2) begin
val = rg.get();
val[k] = ~val[k];
err_msg = $sformatf("Wrote %0s[%0d]: %0b", rg.get_full_name(), k, val[k]);
csr_wr(.csr(rg), .value(val), .blocking(1));
// if external checker is not enabled and writes are made non-blocking, then we need to
// pre-predict so that the mirrored value will be updated. if we dont, then csr_rd_check task
// might pick up stale mirrored value
if (!external_checker) begin
void'(rg.predict(.value(val), .kind(UVM_PREDICT_WRITE)));
end
// TODO, outstanding access to same reg isn't supported in uvm_reg. Need to add another seq
// uvm_reg waits until transaction is completed, before start another read/write in same reg
csr_rd_check(.ptr (rg),
.blocking (0),
.compare (!external_checker),
.compare_vs_ral(1'b1),
.compare_mask (~mask),
.err_msg (err_msg));
end
endtask: bash_kth_bit
endclass
//--------------------------------------------------------------------------------------------------
// Class: csr_aliasing_seq
// Brief Description: For each CSR, this sequence writes a random value to it and reads ALL CSRs
// back. The read value of the CSR that was written is checked for correctness while adhering to its
// access policies. The read value of all other CSRs are compared against their previous values.
// This verifies that there is no aliasing across the address bits within the valid CSR space.
//--------------------------------------------------------------------------------------------------
class csr_aliasing_seq extends csr_base_seq;
`uvm_object_utils(csr_aliasing_seq)
`uvm_object_new
virtual task body();
foreach(test_csrs[i]) begin
uvm_reg_data_t wdata;
// check if parent block or register is excluded
if (m_csr_excl_item.is_excl(test_csrs[i], CsrExclWrite, CsrAliasingTest)) begin
`uvm_info(`gtn, $sformatf("Skipping register %0s due to CsrExclWrite exclusion",
test_csrs[i].get_full_name()), UVM_MEDIUM)
continue;
end
`uvm_info(`gtn, $sformatf("Verifying register aliasing for %0s",
test_csrs[i].get_full_name()), UVM_MEDIUM)
`DV_CHECK_STD_RANDOMIZE_FATAL(wdata)
wdata &= get_mask_excl_fields(test_csrs[i], CsrExclWrite, CsrAliasingTest, m_csr_excl_item);
csr_wr(.csr(test_csrs[i]), .value(wdata), .blocking(0));
// if external checker is not enabled and writes are made non-blocking, then we need to
// pre-predict so that the mirrored value will be updated. if we dont, then csr_rd_check task
// might pick up stale mirrored value
if (!external_checker) begin
void'(test_csrs[i].predict(.value(wdata), .kind(UVM_PREDICT_WRITE)));
end
all_csrs.shuffle();
foreach (all_csrs[j]) begin
uvm_reg_data_t compare_mask;
// check if parent block or register is excluded
if (m_csr_excl_item.is_excl(all_csrs[j], CsrExclInitCheck, CsrAliasingTest) ||
m_csr_excl_item.is_excl(all_csrs[j], CsrExclWriteCheck, CsrAliasingTest)) begin
`uvm_info(`gtn, $sformatf("Skipping register %0s due to CsrExclInit/WriteCheck exclusion",
all_csrs[j].get_full_name()), UVM_MEDIUM)
continue;
end
compare_mask = get_mask_excl_fields(all_csrs[j], CsrExclWriteCheck, CsrAliasingTest,
m_csr_excl_item);
csr_rd_check(.ptr (all_csrs[j]),
.blocking (0),
.compare (!external_checker),
.compare_vs_ral(1'b1),
.compare_mask (compare_mask));
end
wait_no_outstanding_access();
end
endtask
endclass
//--------------------------------------------------------------------------------------------------
// Class: csr_mem_walk_seq
// Brief Description: This seq walks through each address of the memory by running the default
// UVM mem walk sequence.
//--------------------------------------------------------------------------------------------------
class csr_mem_walk_seq extends csr_base_seq;
uvm_mem_walk_seq mem_walk_seq;
`uvm_object_utils(csr_mem_walk_seq)
`uvm_object_new
virtual task body();
mem_walk_seq = uvm_mem_walk_seq::type_id::create("mem_walk_seq");
foreach (models[i]) begin
mem_walk_seq.model = models[i];
mem_walk_seq.start(null);
end
endtask : body
endclass

View file

@ -0,0 +1,21 @@
CAPI=2:
# Copyright lowRISC contributors.
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
# SPDX-License-Identifier: Apache-2.0
name: "lowrisc:dv:csr_utils"
description: "CSR utilities"
filesets:
files_dv:
depend:
- lowrisc:dv:dv_utils
- lowrisc:dv:dv_base_reg
files:
- csr_utils_pkg.sv
- csr_seq_lib.sv: {is_include_file: true}
file_type: systemVerilogSource
targets:
default:
filesets:
- files_dv

View file

@ -0,0 +1,657 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
package csr_utils_pkg;
// dep packages
import uvm_pkg::*;
import dv_utils_pkg::*;
import dv_base_reg_pkg::*;
// macro includes
`include "uvm_macros.svh"
`include "dv_macros.svh"
// local types and variables
uint outstanding_accesses = 0;
uint default_timeout_ns = 1_000_000; // 1ms
uint default_spinwait_timeout_ns = 10_000_000; // 10ms
string msg_id = "csr_utils";
bit default_csr_blocking = 1;
bit under_reset = 0;
int max_outstanding_accesses = 100;
// csr field struct - hold field specific params
typedef struct {
uvm_reg csr;
uvm_reg_field field;
uvm_reg_data_t mask;
uint shift;
} csr_field_t;
function automatic void increment_outstanding_access();
outstanding_accesses++;
endfunction
function automatic void decrement_outstanding_access();
outstanding_accesses--;
endfunction
task automatic wait_no_outstanding_access();
wait(outstanding_accesses == 0);
endtask
function automatic void clear_outstanding_access();
outstanding_accesses = 0;
endfunction
// timeout may happen if we issue too many non-blocking accesses at once
// limit the nonblocking items to be up to max outstanding
task automatic wait_if_max_outstanding_accesses_reached(int max = max_outstanding_accesses);
wait(outstanding_accesses <= max);
endtask
function automatic void reset_asserted();
under_reset = 1;
endfunction
function automatic void reset_deasserted();
under_reset = 0;
endfunction
// Get all valid csr addrs - useful to check if incoming addr falls in the csr range.
function automatic void get_csr_addrs(input uvm_reg_block ral, ref uvm_reg_addr_t csr_addrs[$]);
uvm_reg csrs[$];
ral.get_registers(csrs);
foreach (csrs[i]) begin
csr_addrs.push_back(csrs[i].get_address());
end
endfunction
// Get all valid mem addr ranges - useful to check if incoming addr falls in the mem range.
function automatic void get_mem_addr_ranges(uvm_reg_block ral, ref addr_range_t mem_ranges[$]);
uvm_mem mems[$];
ral.get_memories(mems);
foreach (mems[i]) begin
addr_range_t mem_range;
mem_range.start_addr = mems[i].get_address();
mem_range.end_addr = mem_range.start_addr +
mems[i].get_size() * mems[i].get_n_bytes() - 1;
mem_ranges.push_back(mem_range);
end
endfunction
// get mem object from address
function automatic uvm_mem get_mem_by_addr(uvm_reg_block ral, uvm_reg_addr_t addr);
uvm_mem mem;
addr[1:0] = 0;
mem = ral.default_map.get_mem_by_offset(addr);
`DV_CHECK_NE_FATAL(mem, null, $sformatf("Can't find any mem with addr 0x%0h", addr), msg_id)
return mem;
endfunction
// get mem access like RW, RO
function automatic string get_mem_access_by_addr(uvm_reg_block ral, uvm_reg_addr_t addr);
uvm_mem mem = get_mem_by_addr(ral, addr);
return mem.get_access();
endfunction
// This fucntion return mirrored value of reg/field of given RAL
function automatic uvm_reg_data_t get_reg_fld_mirror_value(uvm_reg_block ral,
string reg_name,
string field_name = "");
uvm_reg csr;
uvm_reg_field fld;
uvm_reg_data_t result;
string msg_id = {csr_utils_pkg::msg_id, "::get_reg_fld_mirror_value"};
csr = ral.get_reg_by_name(reg_name);
`DV_CHECK_NE_FATAL(csr, null, "", msg_id)
// return field mirror value if field_name is passed, else return reg mirror value
if (field_name != "") begin
fld = csr.get_field_by_name(field_name);
`DV_CHECK_NE_FATAL(fld, null, "", msg_id)
result = fld.get_mirrored_value();
end
else begin
result = csr.get_mirrored_value();
end
return result;
endfunction : get_reg_fld_mirror_value
// This function attempts to cast a given uvm_object ptr into uvm_reg or uvm_reg_field. If cast
// is successful on either, then set the appropriate csr_field_t return values.
function automatic csr_field_t decode_csr_or_field(input uvm_object ptr);
uvm_reg csr;
uvm_reg_field fld;
csr_field_t result;
string msg_id = {csr_utils_pkg::msg_id, "::decode_csr_or_field"};
if ($cast(csr, ptr)) begin
// return csr object with null field; set the mask to all 1s and shift to 0
result.csr = csr;
result.mask = '1;
result.shift = 0;
end
else if ($cast(fld, ptr)) begin
// return csr field object; return the appropriate mask and shift values
result.csr = fld.get_parent();
result.field = fld;
result.mask = (1 << fld.get_n_bits()) - 1;
result.shift = fld.get_lsb_pos();
end
else begin
`uvm_fatal(msg_id, $sformatf("ptr %0s is not of type uvm_reg or uvm_reg_field",
ptr.get_full_name()))
end
return result;
endfunction : decode_csr_or_field
// mask and shift data to extract the value specific to that supplied field
function automatic uvm_reg_data_t get_field_val(uvm_reg_field field,
uvm_reg_data_t value);
uvm_reg_data_t mask = (1 << field.get_n_bits()) - 1;
uint shift = field.get_lsb_pos();
get_field_val = (value >> shift) & mask;
endfunction
// get updated reg value by using new specific field value
function automatic uvm_reg_data_t get_csr_val_with_updated_field(uvm_reg_field field,
uvm_reg_data_t csr_value,
uvm_reg_data_t field_value);
uvm_reg_data_t mask = (1 << field.get_n_bits()) - 1;
uint shift = field.get_lsb_pos();
csr_value = csr_value & ~(mask << shift) | ((mask & field_value) << shift);
return csr_value;
endfunction
// wait until current csr op is complete
task automatic csr_wait(input uvm_reg csr);
`uvm_info(msg_id, $sformatf("%0s: wait_busy: %0b",
csr.get_full_name(), csr.m_is_busy), UVM_HIGH)
wait(csr.m_is_busy == 1'b0);
`uvm_info(msg_id, $sformatf("%0s: done wait_busy: %0b",
csr.get_full_name(), csr.m_is_busy), UVM_HIGH)
endtask
task automatic csr_update(input uvm_reg csr,
input uvm_check_e check = UVM_CHECK,
input uvm_path_e path = UVM_DEFAULT_PATH,
input bit blocking = default_csr_blocking,
input uint timeout_ns = default_timeout_ns,
input uvm_reg_map map = null,
input bit en_shadow_wr = 1);
if (blocking) begin
csr_update_sub(csr, check, path, timeout_ns, map, en_shadow_wr);
end else begin
fork
csr_update_sub(csr, check, path, timeout_ns, map, en_shadow_wr);
join_none
// Add #0 to ensure that this thread starts executing before any subsequent call
#0;
end
endtask
// subroutine of csr_update, don't use it directly
task automatic csr_update_sub(input uvm_reg csr,
input uvm_check_e check = UVM_CHECK,
input uvm_path_e path = UVM_DEFAULT_PATH,
input uint timeout_ns = default_timeout_ns,
input uvm_reg_map map = null,
input bit en_shadow_wr = 1);
fork
begin : isolation_fork
uvm_status_e status;
string msg_id = {csr_utils_pkg::msg_id, "::csr_update"};
fork
begin
increment_outstanding_access();
csr_pre_write_sub(csr, en_shadow_wr);
csr.update(.status(status), .path(path), .map(map), .prior(100));
csr_post_write_sub(csr, en_shadow_wr);
if (check == UVM_CHECK) begin
`DV_CHECK_EQ(status, UVM_IS_OK, "", error, msg_id)
end
decrement_outstanding_access();
end
begin
wait_timeout(timeout_ns, msg_id,
$sformatf("Timeout waiting to csr_update %0s (addr=0x%0h)",
csr.get_full_name(), csr.get_address()));
end
join_any
disable fork;
end : isolation_fork
join
endtask
task automatic csr_wr(input uvm_reg csr,
input uvm_reg_data_t value,
input uvm_check_e check = UVM_CHECK,
input uvm_path_e path = UVM_DEFAULT_PATH,
input bit blocking = default_csr_blocking,
input bit backdoor = 0,
input uint timeout_ns = default_timeout_ns,
input bit predict = 0,
input uvm_reg_map map = null,
input bit en_shadow_wr = 1);
if (backdoor) begin
csr_poke(csr, value, check, predict);
end else if (blocking) begin
csr_wr_sub(csr, value, check, path, timeout_ns, map, en_shadow_wr);
if (predict) void'(csr.predict(.value(value), .kind(UVM_PREDICT_WRITE)));
end else begin
fork
begin
csr_wr_sub(csr, value, check, path, timeout_ns, map, en_shadow_wr);
// predict after csr_wr_sub, to ensure predict after enable register overwrite the locked
// registers' access information
if (predict) void'(csr.predict(.value(value), .kind(UVM_PREDICT_WRITE)));
end
join_none
// Add #0 to ensure that this thread starts executing before any subsequent call
#0;
end
endtask
// subroutine of csr_wr, don't use it directly
task automatic csr_wr_sub(input uvm_reg csr,
input uvm_reg_data_t value,
input uvm_check_e check = UVM_CHECK,
input uvm_path_e path = UVM_DEFAULT_PATH,
input uint timeout_ns = default_timeout_ns,
input uvm_reg_map map = null,
input bit en_shadow_wr = 1);
fork
begin : isolation_fork
uvm_status_e status;
string msg_id = {csr_utils_pkg::msg_id, "::csr_wr"};
fork
begin
increment_outstanding_access();
csr_pre_write_sub(csr, en_shadow_wr);
csr.write(.status(status), .value(value), .path(path), .map(map), .prior(100));
csr_post_write_sub(csr, en_shadow_wr);
if (check == UVM_CHECK) begin
`DV_CHECK_EQ(status, UVM_IS_OK, "", error, msg_id)
end
decrement_outstanding_access();
end
begin
wait_timeout(timeout_ns, msg_id,
$sformatf("Timeout waiting to csr_wr %0s (addr=0x%0h)",
csr.get_full_name(), csr.get_address()));
end
join_any
disable fork;
end : isolation_fork
join
endtask
task automatic csr_pre_write_sub(ref uvm_reg csr, bit en_shadow_wr);
dv_base_reg dv_reg;
`downcast(dv_reg, csr, "", fatal, msg_id)
if (dv_reg.get_is_shadowed()) begin
if (en_shadow_wr) increment_outstanding_access();
dv_reg.atomic_en_shadow_wr.get(1);
dv_reg.set_en_shadow_wr(en_shadow_wr);
end
endtask
task automatic csr_post_write_sub(ref uvm_reg csr, bit en_shadow_wr);
dv_base_reg dv_reg;
`downcast(dv_reg, csr, "", fatal, msg_id)
if (dv_reg.get_is_shadowed()) begin
// try setting en_shadow_wr back to default value 1, this function will only work if the
// shadow reg finished both writes
dv_reg.set_en_shadow_wr(1);
dv_reg.atomic_en_shadow_wr.put(1);
if (en_shadow_wr) begin
decrement_outstanding_access();
end
end
endtask
// backdoor write csr
task automatic csr_poke(input uvm_reg csr,
input uvm_reg_data_t value,
input uvm_check_e check = UVM_CHECK,
input bit predict = 0);
uvm_status_e status;
string msg_id = {csr_utils_pkg::msg_id, "::csr_poke"};
uvm_reg_data_t old_mirrored_val = csr.get_mirrored_value();
csr.poke(.status(status), .value(value));
if (check == UVM_CHECK && status != UVM_IS_OK) begin
string str;
uvm_hdl_path_concat paths[$];
csr.get_full_hdl_path(paths);
foreach (paths[0].slices[i]) str = $sformatf("%0s\n%0s", str, paths[0].slices[i].path);
`uvm_fatal(msg_id, $sformatf("poke failed for %0s, check below paths %0s",
csr.get_full_name(), str))
end
// poke always updates predict value, if predict == 0, revert back to old mirrored value
if (!predict) begin
void'(csr.predict(.value(old_mirrored_val), .kind(UVM_PREDICT_DIRECT)));
end
endtask
task automatic csr_rd(input uvm_object ptr, // accept reg or field
output uvm_reg_data_t value,
input uvm_check_e check = UVM_CHECK,
input uvm_path_e path = UVM_DEFAULT_PATH,
input bit blocking = default_csr_blocking,
input bit backdoor = 0,
input uint timeout_ns = default_timeout_ns,
input uvm_reg_map map = null);
if (backdoor) begin
csr_peek(ptr, value, check);
end else if (blocking) begin
csr_rd_sub(ptr, value, check, path, timeout_ns, map);
end else begin
fork
csr_rd_sub(ptr, value, check, path, timeout_ns, map);
join_none
// Add #0 to ensure that this thread starts executing before any subsequent call
#0;
end
endtask
// subroutine of csr_rd, don't use it directly
task automatic csr_rd_sub(input uvm_object ptr, // accept reg or field
output uvm_reg_data_t value,
input uvm_check_e check = UVM_CHECK,
input uvm_path_e path = UVM_DEFAULT_PATH,
input uint timeout_ns = default_timeout_ns,
input uvm_reg_map map = null);
fork
begin : isolation_fork
csr_field_t csr_or_fld;
uvm_status_e status;
string msg_id = {csr_utils_pkg::msg_id, "::csr_rd"};
fork
begin
increment_outstanding_access();
csr_or_fld = decode_csr_or_field(ptr);
if (csr_or_fld.field != null) begin
csr_or_fld.field.read(.status(status), .value(value), .path(path), .map(map),
.prior(100));
end else begin
csr_or_fld.csr.read(.status(status), .value(value), .path(path), .map(map),
.prior(100));
end
if (check == UVM_CHECK) begin
`DV_CHECK_EQ(status, UVM_IS_OK, "", error, msg_id)
end
decrement_outstanding_access();
end
begin
wait_timeout(timeout_ns, msg_id,
$sformatf("Timeout waiting to csr_rd %0s (addr=0x%0h)",
ptr.get_full_name(), csr_or_fld.csr.get_address()));
end
join_any
disable fork;
end : isolation_fork
join
endtask
// backdoor read csr
// uvm_reg::peek() returns a 2-state value, directly get data from hdl path
task automatic csr_peek(input uvm_object ptr,
output uvm_reg_data_t value,
input uvm_check_e check = UVM_CHECK);
string msg_id = {csr_utils_pkg::msg_id, "::csr_peek"};
csr_field_t csr_or_fld = decode_csr_or_field(ptr);
uvm_reg csr = csr_or_fld.csr;
if (csr.has_hdl_path()) begin
uvm_hdl_path_concat paths[$];
csr.get_full_hdl_path(paths);
foreach (paths[0].slices[i]) begin
uvm_reg_data_t field_val;
if (uvm_hdl_read(paths[0].slices[i].path, field_val)) begin
if (check == UVM_CHECK) `DV_CHECK_EQ($isunknown(value), 0, "", error, msg_id)
value |= field_val << paths[0].slices[i].offset;
end else begin
`uvm_fatal(msg_id, $sformatf("uvm_hdl_read failed for %0s", csr.get_full_name()))
end
end
end else begin
`uvm_fatal(msg_id, $sformatf("No backdoor defined for %0s", csr.get_full_name()))
end
// if it's field, only return field value
if (csr_or_fld.field != null) value = get_field_val(csr_or_fld.field, value);
endtask
task automatic csr_rd_check(input uvm_object ptr,
input uvm_check_e check = UVM_CHECK,
input uvm_path_e path = UVM_DEFAULT_PATH,
input bit blocking = default_csr_blocking,
input bit backdoor = 0,
input uint timeout_ns = default_timeout_ns,
input bit compare = 1'b1,
input bit compare_vs_ral = 1'b0,
input uvm_reg_data_t compare_mask = '1,
input uvm_reg_data_t compare_value = 0,
input string err_msg = "",
input uvm_reg_map map = null);
fork
begin : isolation_fork
fork
begin
csr_field_t csr_or_fld;
uvm_status_e status;
uvm_reg_data_t obs;
uvm_reg_data_t exp;
string msg_id = {csr_utils_pkg::msg_id, "::csr_rd_check"};
csr_or_fld = decode_csr_or_field(ptr);
csr_rd(.ptr(ptr), .value(obs), .check(check), .path(path), .backdoor(backdoor),
.blocking(1), .timeout_ns(timeout_ns), .map(map));
// get mirrored value after read to make sure the read reg access is updated
if (csr_or_fld.field != null) begin
exp = csr_or_fld.field.get_mirrored_value();
end else begin
exp = csr_or_fld.csr.get_mirrored_value();
end
if (compare && !under_reset) begin
obs = obs & compare_mask;
exp = (compare_vs_ral ? exp : compare_value) & compare_mask;
`DV_CHECK_EQ(obs, exp, {"Regname: ", ptr.get_full_name(), " ", err_msg},
error, msg_id)
end
end
join_none
if (blocking) wait fork;
// Add #0 to ensure that this thread starts executing before any subsequent call
else #0;
end : isolation_fork
join
endtask
// task to read all csrs and check against ral expected value. Mainly used after reset
task automatic read_and_check_all_csrs(input uvm_reg_block ral);
uvm_reg ral_csrs[$];
ral.get_registers(ral_csrs);
ral_csrs.shuffle();
foreach (ral_csrs[i]) csr_rd_check(.ptr(ral_csrs[i]), .compare_vs_ral(1));
endtask
// poll a csr or csr field continuously until it reads the expected value.
task automatic csr_spinwait(input uvm_object ptr,
input uvm_reg_data_t exp_data,
input uvm_check_e check = UVM_CHECK,
input uvm_path_e path = UVM_DEFAULT_PATH,
input uvm_reg_map map = null,
input uint spinwait_delay_ns = 0,
input uint timeout_ns = default_spinwait_timeout_ns,
input compare_op_e compare_op = CompareOpEq,
input bit backdoor = 0,
input uvm_verbosity verbosity = UVM_HIGH);
fork
begin : isolation_fork
csr_field_t csr_or_fld;
uvm_reg_data_t read_data;
string msg_id = {csr_utils_pkg::msg_id, "::csr_spinwait"};
csr_or_fld = decode_csr_or_field(ptr);
if (backdoor && spinwait_delay_ns == 0) spinwait_delay_ns = 1;
fork
while (!under_reset) begin
if (spinwait_delay_ns) #(spinwait_delay_ns * 1ns);
csr_rd(.ptr(ptr), .value(read_data), .check(check), .path(path),
.blocking(1), .map(map), .backdoor(backdoor));
`uvm_info(msg_id, $sformatf("ptr %0s == 0x%0h",
ptr.get_full_name(), read_data), verbosity)
case (compare_op)
CompareOpEq: if (read_data == exp_data) break;
CompareOpCaseEq: if (read_data === exp_data) break;
CompareOpNe: if (read_data != exp_data) break;
CompareOpCaseNe: if (read_data !== exp_data) break;
CompareOpGt: if (read_data > exp_data) break;
CompareOpGe: if (read_data >= exp_data) break;
CompareOpLt: if (read_data < exp_data) break;
CompareOpLe: if (read_data <= exp_data) break;
default: begin
`uvm_fatal(ptr.get_full_name(), $sformatf("invalid operator:%0s", compare_op))
end
endcase
end
begin
wait_timeout(timeout_ns, msg_id, $sformatf("timeout %0s (addr=0x%0h) == 0x%0h",
ptr.get_full_name(), csr_or_fld.csr.get_address(), exp_data));
end
join_any
disable fork;
end : isolation_fork
join
endtask
task automatic mem_rd(input uvm_mem ptr,
input int offset,
output bit[31:0] data,
input uvm_check_e check = UVM_CHECK,
input bit blocking = default_csr_blocking,
input uint timeout_ns = default_timeout_ns,
input uvm_reg_map map = null);
if (blocking) begin
mem_rd_sub(ptr, offset, data, check, timeout_ns, map);
end else begin
fork
mem_rd_sub(ptr, offset, data, check, timeout_ns, map);
join_none
// Add #0 to ensure that this thread starts executing before any subsequent call
#0;
end
endtask : mem_rd
task automatic mem_rd_sub(input uvm_mem ptr,
input int offset,
output bit[31:0] data,
input uvm_check_e check = UVM_CHECK,
input uint timeout_ns = default_timeout_ns,
input uvm_reg_map map = null);
fork
begin : isolating_fork
uvm_status_e status;
string msg_id = {csr_utils_pkg::msg_id, "::mem_rd"};
fork
begin
increment_outstanding_access();
ptr.read(.status(status), .offset(offset), .value(data), .map(map), .prior(100));
if (check == UVM_CHECK) begin
`DV_CHECK_EQ(status, UVM_IS_OK, "", error, msg_id)
end
decrement_outstanding_access();
end
begin : mem_rd_timeout
wait_timeout(timeout_ns, msg_id,
$sformatf("Timeout waiting to csr_rd %0s (addr=0x%0h)",
ptr.get_full_name(), offset));
end
join_any
disable fork;
end : isolating_fork
join
endtask : mem_rd_sub
task automatic mem_wr(input uvm_mem ptr,
input int offset,
input bit[31:0] data,
input bit blocking = default_csr_blocking,
input uint timeout_ns = default_timeout_ns,
input uvm_check_e check = UVM_CHECK,
input uvm_reg_map map = null);
if (blocking) begin
mem_wr_sub(ptr, offset, data, timeout_ns, check, map);
end else begin
fork
mem_wr_sub(ptr, offset, data, timeout_ns, check, map);
join_none
// Add #0 to ensure that this thread starts executing before any subsequent call
#0;
end
endtask : mem_wr
task automatic mem_wr_sub(input uvm_mem ptr,
input int offset,
input bit[31:0] data,
input uint timeout_ns = default_timeout_ns,
input uvm_check_e check = UVM_CHECK,
input uvm_reg_map map = null);
fork
begin : isolation_fork
uvm_status_e status;
string msg_id = {csr_utils_pkg::msg_id, "::mem_wr"};
fork
begin
increment_outstanding_access();
ptr.write(.status(status), .offset(offset), .value(data), .map(map), .prior(100));
if (check == UVM_CHECK) begin
`DV_CHECK_EQ(status, UVM_IS_OK, "", error, msg_id)
end
decrement_outstanding_access();
end
begin
wait_timeout(timeout_ns, msg_id,
$sformatf("Timeout waiting to csr_wr %0s (addr=0x%0h)",
ptr.get_full_name(), offset));
end
join_any
disable fork;
end : isolation_fork
join
endtask : mem_wr_sub
// Fields could be excluded from writes & reads - This function zeros out the excluded fields
function automatic uvm_reg_data_t get_mask_excl_fields(uvm_reg csr,
csr_excl_type_e csr_excl_type,
csr_test_type_e csr_test_type,
csr_excl_item m_csr_excl_item);
uvm_reg_field flds[$];
csr.get_fields(flds);
get_mask_excl_fields = '1;
foreach (flds[i]) begin
if (m_csr_excl_item.is_excl(flds[i], csr_excl_type, csr_test_type)) begin
csr_field_t fld_params = decode_csr_or_field(flds[i]);
`uvm_info(msg_id, $sformatf("Skipping field %0s due to %0s exclusion",
flds[i].get_full_name(), csr_excl_type.name()), UVM_MEDIUM)
get_mask_excl_fields &= ~(fld_params.mask << fld_params.shift);
end
end
endfunction
// sources
`include "csr_seq_lib.sv"
endpackage

View file

@ -0,0 +1,111 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
// Class: csr_excl_item
// Description: CSR exclusion item that holds exclusions applied for a given set of blocks /
// registers / fields provided and maintained as strings.
class csr_excl_item extends uvm_object;
`uvm_object_utils(csr_excl_item)
typedef struct {
int csr_test_type;
csr_excl_type_e csr_excl_type;
} csr_excl_s;
local csr_excl_s exclusions[string];
`uvm_object_new
// add exclusion for an individual block, csr or field
// arg obj: this is the hierarchical path name to the block, csr or field - passing * and ?
// wildcards for glob style matching is allowed. User needs to take care that wildcards does not
// end up inadvertently matching more that what was desired. Examples:
// To exclude ral.ctrl.tx field from writes, obj can be "ral.ctrl.tx" or "*.ctrl.tx"; passing
// "*.tx" might be too generic
virtual function void add_excl(string obj,
csr_excl_type_e csr_excl_type,
csr_test_type_e csr_test_type = CsrAllTests);
bit [2:0] val = CsrNoExcl;
bit [NUM_CSR_TESTS-1:0] test = CsrInvalidTest;
csr_excl_s csr_excl_item;
if (csr_test_type == CsrInvalidTest) begin
`uvm_fatal(`gfn, $sformatf("add %s exclusion without a test", obj))
end
val = csr_excl_type | exclusions[obj].csr_excl_type;
test = csr_test_type | exclusions[obj].csr_test_type;
exclusions[obj].csr_excl_type = csr_excl_type_e'(val);
exclusions[obj].csr_test_type = test;
endfunction
// function to check if given blk / csr or field AND its parent has been excluded with the
// supplied exclusion type
// arg uvm_object obj: given blk, csr or field
// arg csr_excl_type_e csr_excl_type: exclusion type
function bit is_excl(uvm_object obj,
csr_excl_type_e csr_excl_type,
csr_test_type_e csr_test_type);
uvm_reg_block blk;
uvm_reg csr;
// if supplied obj is a uvm_reg_block or uvm_reg, then its parent is a uvm_reg_block
// check if obj's parent is excluded
if ($cast(blk, obj)) begin
if (blk.get_parent() != null) begin
blk = blk.get_parent();
if (has_excl(blk.`gfn, csr_excl_type, csr_test_type)) return 1'b1;
end
end
if ($cast(csr, obj)) begin
blk = csr.get_parent();
if (has_excl(blk.`gfn, csr_excl_type, csr_test_type)) return 1'b1;
end
// TODO: check if any parent in the hierarchy above is excluded
// check if obj is excluded
return (has_excl(obj.`gfn, csr_excl_type, csr_test_type));
endfunction
// check if applied string obj has a match in existing exclusions lookup in defined csr_test_type
// function is to not be called externally
local function bit has_excl(string obj,
csr_excl_type_e csr_excl_type,
csr_test_type_e csr_test_type);
// check if obj exists verbatim
if (exclusions.exists(obj)) begin
`uvm_info(`gfn, $sformatf("has_excl: found exact excl match for %0s: %0s",
obj, exclusions[obj].csr_excl_type.name()), UVM_DEBUG)
// check if bit(s) corresponding to csr_excl_type are set in defined csr_test_type
if ((exclusions[obj].csr_test_type & csr_test_type) != CsrInvalidTest) begin
if ((exclusions[obj].csr_excl_type & csr_excl_type) != CsrNoExcl) return 1'b1;
end
end
else begin
// attempt glob style matching
foreach (exclusions[str]) begin
if (!uvm_re_match(str, obj)) begin
`uvm_info(`gfn, $sformatf("has_excl: found glob excl match for %0s(%0s): %0s",
obj, str, exclusions[str].csr_excl_type.name()), UVM_DEBUG)
// check if bit(s) corresponding to csr_excl_type are set in defined csr_test_type
if ((exclusions[str].csr_test_type & csr_test_type) != CsrInvalidTest) begin
if ((exclusions[str].csr_excl_type & csr_excl_type) != CsrNoExcl) return 1'b1;
end
end
end
end
return 1'b0;
endfunction
// print all exclusions for ease of debug (call this ideally after adding all exclusions)
virtual function void print_exclusions(uvm_verbosity verbosity = UVM_HIGH);
string test_names;
for (int i = NUM_CSR_TESTS - 1; i >= 0; i--) begin
csr_test_type_e csr_test = csr_test_type_e'(1 << i);
test_names = {test_names, csr_test.name(), (i > 0) ? " " : ""};
end
foreach (exclusions[item]) begin
`uvm_info(`gfn, $sformatf("CSR/field [%0s] excluded with %0s in csr_tests: {%s} = {%0b}",
item, exclusions[item].csr_excl_type.name(), test_names,
exclusions[item].csr_test_type), verbosity)
end
endfunction
endclass

View file

@ -0,0 +1,61 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
//
// base register reg class which will be used to generate the reg mem
class dv_base_mem extends uvm_mem;
// uvm_mem::m_access is local variable. Create it again in order to use "access" in current class
local string m_access;
// if mem doesn't support partial write, doing that will result d_error = 1
local bit mem_partial_write_support;
function new(string name,
longint unsigned size,
int unsigned n_bits,
string access = "RW",
int has_coverage = UVM_NO_COVERAGE);
super.new(name, size, n_bits, access, has_coverage);
m_access = access;
endfunction : new
function void set_mem_partial_write_support(bit enable);
mem_partial_write_support = enable;
endfunction : set_mem_partial_write_support
function bit get_mem_partial_write_support();
return mem_partial_write_support;
endfunction : get_mem_partial_write_support
// rewrite this function to support "WO" access type for mem
function void configure(uvm_reg_block parent,
string hdl_path="");
if (parent == null)
`uvm_fatal("REG/NULL_PARENT","configure: parent argument is null")
set_parent(parent);
if (!(m_access inside {"RW", "RO", "WO"})) begin
`uvm_error("RegModel", {"Memory '",get_full_name(),"' can only be RW, RO or WO"})
end
begin
uvm_mem_mam_cfg cfg = new;
cfg.n_bytes = ((get_n_bits() - 1) / 8) + 1;
cfg.start_offset = 0;
cfg.end_offset = get_size() - 1;
cfg.mode = uvm_mem_mam::GREEDY;
cfg.locality = uvm_mem_mam::BROAD;
mam = new(get_full_name(), cfg, this);
end
parent.add_mem(this);
if (hdl_path != "") add_hdl_path_slice(hdl_path, -1, -1);
endfunction: configure
endclass

View file

@ -0,0 +1,25 @@
CAPI=2:
# Copyright lowRISC contributors.
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
# SPDX-License-Identifier: Apache-2.0
name: "lowrisc:dv:dv_base_reg"
description: "DV base reg/mem library"
filesets:
files_dv:
depend:
- lowrisc:dv:dv_utils
files:
- dv_base_reg_pkg.sv
- csr_excl_item.sv: {is_include_file: true}
- dv_base_reg_field.sv: {is_include_file: true}
- dv_base_reg.sv: {is_include_file: true}
- dv_base_mem.sv: {is_include_file: true}
- dv_base_reg_block.sv: {is_include_file: true}
- dv_base_reg_map.sv: {is_include_file: true}
file_type: systemVerilogSource
targets:
default:
filesets:
- files_dv

View file

@ -0,0 +1,212 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
//
// base register class which will be used to generate the reg
class dv_base_reg extends uvm_reg;
// external reg doesn't have storage in reg module, which may connect to some combinational logic
// hence, backdoor write isn't available
local bit is_ext_reg;
local dv_base_reg locked_regs[$];
local uvm_reg_data_t staged_shadow_val;
local bit is_shadowed;
local bit shadow_wr_staged; // stage the first shadow reg write
local bit shadow_update_err;
local bit en_shadow_wr = 1;
// atomic_shadow_wr: semaphore to guarantee atomicity of the two writes for shadowed registers.
// In case a parallel thread writing a different value to the same reg causing an update_err
semaphore atomic_shadow_wr;
// atomic_en_shadow_wr: semaphore to guarantee setting or resetting en_shadow_wr is unchanged
// through the 1st/2nd (or both) writes
semaphore atomic_en_shadow_wr;
function new(string name = "",
int unsigned n_bits,
int has_coverage);
super.new(name, n_bits, has_coverage);
atomic_en_shadow_wr = new(1);
atomic_shadow_wr = new(1);
endfunction : new
function void get_dv_base_reg_fields(ref dv_base_reg_field dv_fields[$]);
uvm_reg_field ral_fields[$];
get_fields(ral_fields);
foreach (ral_fields[i]) `downcast(dv_fields[i], ral_fields[i])
endfunction
// get_n_bits will return number of all the bits in the csr
// while this function will return actual number of bits used in reg field
function uint get_n_used_bits();
uvm_reg_field fields[$];
get_fields(fields);
foreach (fields[i]) get_n_used_bits += fields[i].get_n_bits();
endfunction
// loop all the fields to find the msb position of this reg
function uint get_msb_pos();
uvm_reg_field fields[$];
get_fields(fields);
foreach (fields[i]) begin
uint field_msb_pos = fields[i].get_lsb_pos() + fields[i].get_n_bits() - 1;
if (field_msb_pos > get_msb_pos) get_msb_pos = field_msb_pos;
end
endfunction
// if the register is an enable reg, it will add controlled registers in the queue
function void add_locked_reg(dv_base_reg locked_reg);
locked_regs.push_back(locked_reg);
endfunction
function bit is_enable_reg();
return (locked_regs.size() > 0);
endfunction
function bit is_staged();
return shadow_wr_staged;
endfunction
// if enable register is set to 1, the locked registers will be set to RO access
// once enable register is reset to 0, the locked registers will be set back to original access
function void set_locked_regs_access(string access = "original_access");
foreach (locked_regs[i]) begin
dv_base_reg_field locked_fields[$];
locked_regs[i].get_dv_base_reg_fields(locked_fields);
foreach (locked_fields[i]) locked_fields[i].set_locked_fields_access(access);
end
endfunction
function void get_locked_regs(ref dv_base_reg locked_regs_q[$]);
locked_regs_q = locked_regs;
endfunction
// is_shadowed bit is only one-time programmable
// once this function is called in RAL auto-generated class, it cannot be changed
function void set_is_shadowed();
is_shadowed = 1;
endfunction
function uvm_reg_data_t get_staged_shadow_val();
return staged_shadow_val;
endfunction
function void set_en_shadow_wr(bit val);
// do not update en_shadow_wr if shadow register write is in process
if ((en_shadow_wr ^ val) && shadow_wr_staged) begin
`uvm_info(`gfn,
$sformatf("unable to %0s en_shadow_wr because register already completed first write",
val ? "set" : "clear"), UVM_HIGH)
return;
end
en_shadow_wr = val;
endfunction
function bit get_is_shadowed();
return is_shadowed;
endfunction
function bit get_shadow_update_err();
return shadow_update_err;
endfunction
virtual function void clear_shadow_update_err();
shadow_update_err = 0;
endfunction
// post_write callback to handle special regs:
// - shadow register, enable reg won't be updated until the second write has no error
// - enable register, if enable_reg is disabled, change access policy to all the locked_regs
// TODO: create an `enable_field_access_policy` variable and set the template code during
// automation.
virtual task post_write(uvm_reg_item rw);
dv_base_reg_field fields[$];
string field_access;
if (is_shadowed) begin
// first write
if (!shadow_wr_staged) begin
shadow_wr_staged = 1;
staged_shadow_val = rw.value[0];
return;
end begin
// second write
shadow_wr_staged = 0;
if (staged_shadow_val != rw.value[0]) begin
shadow_update_err = 1;
return;
end
end
end
if (is_enable_reg()) begin
get_dv_base_reg_fields(fields);
field_access = fields[0].get_access();
case (field_access)
// rw.value is a dynamic array
"W1C": if (rw.value[0][0] == 1'b1) set_locked_regs_access("RO");
"W0C": if (rw.value[0][0] == 1'b0) set_locked_regs_access("RO");
"RO": ; // if RO, it's updated by design, need to predict in scb
default:`uvm_fatal(`gfn, $sformatf("enable register invalid access %s", field_access))
endcase
end
endtask
// shadow register read will clear its phase tracker
virtual task post_read(uvm_reg_item rw);
if (is_shadowed) shadow_wr_staged = 0;
endtask
virtual function void set_is_ext_reg(bit is_ext);
is_ext_reg = is_ext;
endfunction
virtual function bit get_is_ext_reg();
return is_ext_reg;
endfunction
// if it is a shadowed register, and is enabled to write it twice, this task will write the
// register twice with the same value and address.
virtual task write(output uvm_status_e status,
input uvm_reg_data_t value,
input uvm_path_e path = UVM_DEFAULT_PATH,
input uvm_reg_map map = null,
input uvm_sequence_base parent=null,
input int prior = -1,
input uvm_object extension = null,
input string fname = "",
input int lineno = 0);
if (is_shadowed) atomic_shadow_wr.get(1);
super.write(status, value, path, map, parent, prior, extension, fname, lineno);
if (is_shadowed && en_shadow_wr) begin
super.write(status, value, path, map, parent, prior, extension, fname, lineno);
end
if (is_shadowed) atomic_shadow_wr.put(1);
endtask
// override do_predict function to support shadow_reg:
// skip predict if it is shadow_reg's first write, or second write with an update_err
virtual function void do_predict (uvm_reg_item rw,
uvm_predict_e kind = UVM_PREDICT_DIRECT,
uvm_reg_byte_en_t be = -1);
if (is_shadowed && (shadow_wr_staged || shadow_update_err) && kind != UVM_PREDICT_READ) begin
`uvm_info(`gfn,
$sformatf("skip predict csr %s: due to shadow_reg_first_wr=%0b or update_err=%0b",
get_name(), shadow_wr_staged, shadow_update_err), UVM_HIGH)
return;
end
super.do_predict(rw, kind, be);
endfunction
virtual function void reset(string kind = "HARD");
super.reset(kind);
if (is_shadowed) begin
shadow_update_err = 0;
shadow_wr_staged = 0;
// in case reset is issued during shadowed writes
void'(atomic_shadow_wr.try_get(1));
void'(atomic_en_shadow_wr.try_get(1));
atomic_shadow_wr.put(1);
atomic_en_shadow_wr.put(1);
end
endfunction
endclass

View file

@ -0,0 +1,57 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
//
// base register block class which will be used to generate the reg blocks
class dv_base_reg_block extends uvm_reg_block;
`uvm_object_utils(dv_base_reg_block)
csr_excl_item csr_excl;
function new (string name = "", int has_coverage = UVM_NO_COVERAGE);
super.new(name, has_coverage);
endfunction
// provide build function to supply base addr
virtual function void build(uvm_reg_addr_t base_addr,
csr_excl_item csr_excl = null);
`uvm_fatal(`gfn, "this method is not supposed to be called directly!")
endfunction
function void get_dv_base_reg_blocks(ref dv_base_reg_block blks[$]);
uvm_reg_block uvm_blks[$];
this.get_blocks(uvm_blks);
foreach (uvm_blks[i]) `downcast(blks[i], uvm_blks[i])
endfunction
function void get_dv_base_regs(ref dv_base_reg dv_regs[$]);
uvm_reg ral_regs[$];
this.get_registers(ral_regs);
foreach (ral_regs[i]) `downcast(dv_regs[i], ral_regs[i])
endfunction
function void get_enable_regs(ref dv_base_reg enable_regs[$]);
dv_base_reg_block blks[$];
this.get_dv_base_reg_blocks(blks);
if (blks.size() == 0) begin
dv_base_reg all_regs[$];
this.get_dv_base_regs(all_regs);
foreach (all_regs[i]) begin
if (all_regs[i].is_enable_reg()) enable_regs.push_back(all_regs[i]);
end
return;
end else begin
foreach (blks[i]) blks[i].get_enable_regs(enable_regs);
end
endfunction
// override RAL's reset function to support enable registers
// when reset issued - the locked registers' access will be reset to original access
virtual function void reset(string kind = "HARD");
dv_base_reg enable_regs[$];
super.reset(kind);
get_enable_regs(enable_regs);
foreach (enable_regs[i]) enable_regs[i].set_locked_regs_access();
endfunction
endclass

View file

@ -0,0 +1,42 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
//
// base register reg class which will be used to generate the reg field
class dv_base_reg_field extends uvm_reg_field;
local string m_original_access;
`uvm_object_utils(dv_base_reg_field)
`uvm_object_new
// when use UVM_PREDICT_WRITE and the CSR access is WO, this function will return the default
// val of the register, rather than the written value
virtual function uvm_reg_data_t XpredictX(uvm_reg_data_t cur_val,
uvm_reg_data_t wr_val,
uvm_reg_map map);
if (get_access(map) == "WO") return get_reset();
else return super.XpredictX(cur_val, wr_val, map);
endfunction
virtual function string get_original_access();
return m_original_access;
endfunction
virtual function void set_original_access(string access);
if (m_original_access == "") begin
m_original_access = access;
end else begin
`uvm_fatal(`gfn, "register original access can only be written once")
end
endfunction
virtual function void set_locked_fields_access(string access = "original_access");
case (access)
"RO": void'(this.set_access(access));
"original_access": void'(this.set_access(m_original_access));
default: `uvm_fatal(`gfn, $sformatf("attempt to set access to %s", access))
endcase
endfunction
endclass

View file

@ -0,0 +1,9 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
//
// base register reg class which will be used to generate the reg map
class dv_base_reg_map extends uvm_reg_map;
`uvm_object_utils(dv_base_reg_map)
`uvm_object_new
endclass

View file

@ -0,0 +1,49 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
package dv_base_reg_pkg;
// dep packages
import uvm_pkg::*;
import dv_utils_pkg::*;
// macro includes
`include "uvm_macros.svh"
`include "dv_macros.svh"
// global paramters for number of csr tests (including memory test)
parameter uint NUM_CSR_TESTS = 4;
// csr exclusion indications
typedef enum bit [2:0] {
CsrNoExcl = 3'b000, // no exclusions
CsrExclInitCheck = 3'b001, // exclude csr from init val check
CsrExclWriteCheck = 3'b010, // exclude csr from write-read check
CsrExclCheck = 3'b011, // exclude csr from init or write-read check
CsrExclWrite = 3'b100, // exclude csr from write
CsrExclAll = 3'b111 // exclude csr from init or write or write-read check
} csr_excl_type_e;
// csr test types
typedef enum bit [NUM_CSR_TESTS-1:0] {
CsrInvalidTest = 4'h0,
// elementary test types
CsrHwResetTest = 4'h1,
CsrRwTest = 4'h2,
CsrBitBashTest = 4'h4,
CsrAliasingTest = 4'h8,
// combinational test types (combinations of the above), used for exclusion tagging
CsrNonInitTests = 4'he, // all but HwReset test
CsrAllTests = 4'hf // all tests
} csr_test_type_e;
// package sources
// base ral
`include "csr_excl_item.sv"
`include "dv_base_reg_field.sv"
`include "dv_base_reg.sv"
`include "dv_base_mem.sv"
`include "dv_base_reg_block.sv"
`include "dv_base_reg_map.sv"
endpackage

View file

@ -0,0 +1,56 @@
---
title: "DV Library Classes"
---
# DV library classes
## Overview
The DV library classes form the base layer / framework for constructing UVM
testbenches. These classes provide features (settings, methods, hooks and other
constructs used in verification) that are generic enough to be reused across
all testbenches.
In this doc, we will capture some of the most salient / frequently used features
in extended classes. These classes are being updated frequently. So, for a more
detailed understanding, please read the class definitions directly.
The DV library classes fall into 3 categories - UVM RAL (register abstraction
layer), UVM agent, and UVM environment extensions.
### UVM RAL extensions
The RAL model generated using the [reggen]({{< relref "util/reggen/README.md" >}}) tool
extend from these classes. These themselves extend from the corresponding RAL
classes provided in UVM.
#### `dv_base_reg_field`
Currently, this class does not provide any additional features. One of the
features planned for future is setting exclusion tags at the field level for the
CSR suite of tests that will be extracted automatically from the Hjson-based
IP CSR specification.
#### `dv_base_reg`
This class provides the following functions to support verification:
* `gen_n_used_bits()`: This function returns the actual number of bits used in
the CSR (sum of all available field widths).
* `get_msb_pos()`: This function returns the MSB bit position of all available
fields. CSR either ends at this bit (`BUS_DW` - 1) or has reserved / invalid
bits beyond this bit.
#### `dv_base_reg_block`
* ` build(uvm_reg_addr_t base_addr)`: This function is implemented as a pseudo
pure virtual function (returns a fatal error if called directly). It is used
for building the complete RAL model. For a polymorphic approach, the DV user
can use this class handle to create the extended (IP specific) class instance
and call this function to build the actual RAL model. This is exactly how it
is done in [dv_base_env_cfg](#dv_base_env_cfg).
#### `dv_base_reg_map`
Currently, this class does not provide any additional features. Having this
extension provides an opportunity to add common features in future.
### UVM Agent extensions
TODO
### UVM Environment extensions
TODO

View file

@ -0,0 +1,60 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
class dv_base_agent #(type CFG_T = dv_base_agent_cfg,
type DRIVER_T = dv_base_driver,
type HOST_DRIVER_T = DRIVER_T,
type DEVICE_DRIVER_T = DRIVER_T,
type SEQUENCER_T = dv_base_sequencer,
type MONITOR_T = dv_base_monitor,
type COV_T = dv_base_agent_cov) extends uvm_agent;
`uvm_component_param_utils(dv_base_agent #(CFG_T, DRIVER_T, HOST_DRIVER_T, DEVICE_DRIVER_T,
SEQUENCER_T, MONITOR_T, COV_T))
CFG_T cfg;
COV_T cov;
DRIVER_T driver;
SEQUENCER_T sequencer;
MONITOR_T monitor;
`uvm_component_new
function void build_phase(uvm_phase phase);
super.build_phase(phase);
// get CFG_T object from uvm_config_db
if (!uvm_config_db#(CFG_T)::get(this, "", "cfg", cfg)) begin
`uvm_fatal(`gfn, $sformatf("failed to get %s from uvm_config_db", cfg.get_type_name()))
end
`uvm_info(`gfn, $sformatf("\n%0s", cfg.sprint()), UVM_HIGH)
// create components
if (cfg.en_cov) begin
cov = COV_T ::type_id::create("cov", this);
cov.cfg = cfg;
end
monitor = MONITOR_T::type_id::create("monitor", this);
monitor.cfg = cfg;
monitor.cov = cov;
if (cfg.is_active) begin
sequencer = SEQUENCER_T::type_id::create("sequencer", this);
sequencer.cfg = cfg;
if (cfg.if_mode == Host) driver = HOST_DRIVER_T::type_id::create("driver", this);
else driver = DEVICE_DRIVER_T::type_id::create("driver", this);
driver.cfg = cfg;
end
endfunction
function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
if (cfg.is_active) begin
driver.seq_item_port.connect(sequencer.seq_item_export);
end
endfunction
endclass

View file

@ -0,0 +1,23 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
class dv_base_agent_cfg extends uvm_object;
// agent cfg knobs
bit is_active = 1'b1; // active driver or passive monitor
bit en_cov = 1'b1; // enable coverage
if_mode_e if_mode; // interface mode - Host or Device
// use for phase_ready_to_end to add additional delay after ok_to_end is set
int ok_to_end_delay_ns = 1000;
`uvm_object_utils_begin(dv_base_agent_cfg)
`uvm_field_int (is_active, UVM_DEFAULT)
`uvm_field_int (en_cov, UVM_DEFAULT)
`uvm_field_enum(if_mode_e, if_mode, UVM_DEFAULT)
`uvm_object_utils_end
`uvm_object_new
endclass

View file

@ -0,0 +1,12 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
class dv_base_agent_cov #(type CFG_T = dv_base_agent_cfg) extends uvm_component;
`uvm_component_param_utils(dv_base_agent_cov #(CFG_T))
CFG_T cfg;
`uvm_component_new
endclass

View file

@ -0,0 +1,37 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
class dv_base_driver #(type ITEM_T = uvm_sequence_item,
type CFG_T = dv_base_agent_cfg,
type RSP_ITEM_T = ITEM_T)
extends uvm_driver #(.REQ(ITEM_T), .RSP(RSP_ITEM_T));
`uvm_component_param_utils(dv_base_driver #(.ITEM_T (ITEM_T),
.CFG_T (CFG_T),
.RSP_ITEM_T (RSP_ITEM_T)))
bit under_reset;
CFG_T cfg;
`uvm_component_new
virtual task run_phase(uvm_phase phase);
fork
reset_signals();
get_and_drive();
join
endtask
// reset signals
virtual task reset_signals();
`uvm_fatal(`gfn, "this is implemented as pure virtual task - please extend")
endtask
// drive trans received from sequencer
virtual task get_and_drive();
`uvm_fatal(`gfn, "this is implemented as pure virtual task - please extend")
endtask
endclass

View file

@ -0,0 +1,60 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
class dv_base_env #(type CFG_T = dv_base_env_cfg,
type VIRTUAL_SEQUENCER_T = dv_base_virtual_sequencer,
type SCOREBOARD_T = dv_base_scoreboard,
type COV_T = dv_base_env_cov) extends uvm_env;
`uvm_component_param_utils(dv_base_env #(CFG_T, VIRTUAL_SEQUENCER_T, SCOREBOARD_T, COV_T))
CFG_T cfg;
VIRTUAL_SEQUENCER_T virtual_sequencer;
SCOREBOARD_T scoreboard;
COV_T cov;
`uvm_component_new
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
// get dv_base_env_cfg object from uvm_config_db
if (!uvm_config_db#(CFG_T)::get(this, "", "cfg", cfg)) begin
`uvm_fatal(`gfn, $sformatf("failed to get %s from uvm_config_db", cfg.get_type_name()))
end
// get vifs
if (!uvm_config_db#(virtual clk_rst_if)::get(this, "", "clk_rst_vif", cfg.clk_rst_vif)) begin
`uvm_fatal(get_full_name(), "failed to get clk_rst_if from uvm_config_db")
end
cfg.clk_rst_vif.set_freq_mhz(cfg.clk_freq_mhz);
// create components
if (cfg.en_cov) begin
cov = COV_T::type_id::create("cov", this);
cov.cfg = cfg;
end
if (cfg.is_active) begin
virtual_sequencer = VIRTUAL_SEQUENCER_T::type_id::create("virtual_sequencer", this);
virtual_sequencer.cfg = cfg;
virtual_sequencer.cov = cov;
end
// scb also monitors the reset and call cfg.reset_asserted/reset_deasserted for reset
scoreboard = SCOREBOARD_T::type_id::create("scoreboard", this);
scoreboard.cfg = cfg;
scoreboard.cov = cov;
endfunction
virtual function void end_of_elaboration_phase(uvm_phase phase);
super.end_of_elaboration_phase(phase);
if (cfg.has_ral) begin
// Lock the ral model
cfg.ral.lock_model();
// Get list of valid csr addresses (useful in seq to randomize addr as well as in scb checks)
get_csr_addrs(cfg.ral, cfg.csr_addrs);
get_mem_addr_ranges(cfg.ral, cfg.mem_ranges);
end
endfunction : end_of_elaboration_phase
endclass

View file

@ -0,0 +1,96 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
class dv_base_env_cfg #(type RAL_T = dv_base_reg_block) extends uvm_object;
bit is_active = 1;
bit en_scb = 1; // can be changed at run-time
bit en_scb_tl_err_chk = 1;
bit en_scb_mem_chk = 1;
bit en_cov = 1;
bit has_ral = 1;
bit under_reset = 0;
// bit to configure all uvcs with zero delays to create high bw test
rand bit zero_delays;
// set zero_delays 40% of the time
constraint zero_delays_c {
zero_delays dist {1'b0 := 6, 1'b1 := 4};
}
// reg model & q of valid csr addresses
RAL_T ral;
dv_base_reg_block ral_models[$];
bit [bus_params_pkg::BUS_AW-1:0] csr_addrs[$];
addr_range_t mem_ranges[$];
// ral base address and size
bit [bus_params_pkg::BUS_AW-1:0] csr_base_addr; // base address where csr map begins
bit [bus_params_pkg::BUS_AW:0] csr_addr_map_size; // csr addr region allocated to the ip,
// max: 1 << bus_params_pkg::BUS_AW
// clk_rst_if & freq
virtual clk_rst_if clk_rst_vif;
rand clk_freq_mhz_e clk_freq_mhz;
`uvm_object_param_utils_begin(dv_base_env_cfg #(RAL_T))
`uvm_field_int (is_active, UVM_DEFAULT)
`uvm_field_int (en_scb, UVM_DEFAULT)
`uvm_field_int (en_cov, UVM_DEFAULT)
`uvm_field_int (zero_delays, UVM_DEFAULT)
`uvm_field_int (csr_base_addr, UVM_DEFAULT)
`uvm_field_int (csr_addr_map_size, UVM_DEFAULT)
`uvm_field_enum (clk_freq_mhz_e, clk_freq_mhz, UVM_DEFAULT)
`uvm_object_utils_end
`uvm_object_new
virtual function void initialize(bit [bus_params_pkg::BUS_AW-1:0] csr_base_addr = '1);
initialize_csr_addr_map_size();
`DV_CHECK_NE_FATAL(csr_addr_map_size, 0, "csr_addr_map_size can't be 0")
// use locally randomized csr base address, unless provided as arg to this function
if (csr_base_addr != '1) begin
bit is_aligned;
this.csr_base_addr = csr_base_addr;
// check alignment
is_aligned = ~|(this.csr_base_addr & (this.csr_addr_map_size - 1));
`DV_CHECK_EQ_FATAL(is_aligned, 1'b1)
end else begin
// base address needs to be aligned to csr_addr_map_size
`DV_CHECK_STD_RANDOMIZE_WITH_FATAL(csr_base_addr,
~|(csr_base_addr & (csr_addr_map_size - 1));)
this.csr_base_addr = csr_base_addr;
end
// build the ral model
if (has_ral) begin
ral = RAL_T::type_id::create("ral");
ral.build(this.csr_base_addr, null);
apply_ral_fixes();
ral_models.push_back(ral);
end
endfunction
// This function must be implemented in extended class to
// initialize value of csr_addr_map_size member
virtual function void initialize_csr_addr_map_size();
`uvm_fatal(`gfn, "This task must be implemented in the extended class!")
endfunction : initialize_csr_addr_map_size
// ral flow is limited in terms of setting correct field access policies and reset values
// We apply those fixes here - please note these fixes need to be reflected in the scoreboard
protected virtual function void apply_ral_fixes();
// fix access policies & reset values
endfunction
virtual function void reset_asserted();
this.under_reset = 1;
csr_utils_pkg::reset_asserted();
endfunction
virtual function void reset_deasserted();
this.under_reset = 0;
csr_utils_pkg::reset_deasserted();
endfunction
endclass

View file

@ -0,0 +1,42 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
// TODO - We are enclosing generic covergroups inside class so that we can
// take avoid tool limitation of not allowing arrays of covergroup
// Refer to Issue#375 for more details
class dv_base_generic_cov_obj;
// Covergroup: bit_toggle_cg
// Generic covergroup definition
covergroup bit_toggle_cg(string name, bit toggle_cov_en = 1) with function sample(bit value);
option.per_instance = 1;
option.name = name;
cp_value: coverpoint value;
cp_transitions: coverpoint value {
option.weight = toggle_cov_en;
bins rising = (0 => 1);
bins falling = (1 => 0);
}
endgroup : bit_toggle_cg
// Function: new
function new(string name = "dv_base_generic_cov_obj", bit toggle_cov_en = 1);
bit_toggle_cg = new(name, toggle_cov_en);
endfunction : new
// Function: sample
function void sample(bit value);
bit_toggle_cg.sample(value);
endfunction : sample
endclass : dv_base_generic_cov_obj
class dv_base_env_cov #(type CFG_T = dv_base_env_cfg) extends uvm_component;
`uvm_component_param_utils(dv_base_env_cov #(CFG_T))
CFG_T cfg;
`uvm_component_new
endclass

View file

@ -0,0 +1,113 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
class dv_base_monitor #(type ITEM_T = uvm_sequence_item,
type CFG_T = dv_base_agent_cfg,
type COV_T = dv_base_agent_cov) extends uvm_monitor;
`uvm_component_param_utils(dv_base_monitor #(ITEM_T, CFG_T, COV_T))
CFG_T cfg;
COV_T cov;
// Indicates activity on the interface, driven only within the `monitor_ready_to_end()` task.
protected bit ok_to_end = 1;
// Used to ensure we run the watchdog exactly once at the end of the run phase. Cleared at start
// of run phase. Set once watchdog_ok_to_end has been started at the end of the run_phase.
protected bit phase_ready_to_end_invoked = 0;
// Analysis port for the collected transfer.
uvm_analysis_port #(ITEM_T) analysis_port;
`uvm_component_new
function void build_phase(uvm_phase phase);
super.build_phase(phase);
analysis_port = new("analysis_port", this);
endfunction
virtual task run_phase(uvm_phase phase);
fork
collect_trans(phase);
join
endtask
// collect transactions forever
virtual protected task collect_trans(uvm_phase phase);
`uvm_fatal(`gfn, "this method is not supposed to be called directly!")
endtask
// UVM callback which is invoked during phase sequencing.
virtual function void phase_ready_to_end(uvm_phase phase);
if (!phase.is(uvm_run_phase::get())) return;
if (phase_ready_to_end_invoked) return;
phase_ready_to_end_invoked = 1;
fork
monitor_ready_to_end();
watchdog_ok_to_end(phase);
join_none
endfunction
// Ensures that ok_to_end when asserted, stays asserted for 1 ok_to_end_delay_ns period.
//
// If ok_to_end de-asserts before the watchdog expires, it waits for it to assert again
// and restarts the timer. This ensures that there is sufficient drain time to allow the
// simulation to end gracefully. It raises and drops the objection at the appropriate times.
virtual task watchdog_ok_to_end(uvm_phase run_phase);
bit objection_raised;
bit watchdog_done;
uint watchdog_restart_count = 1;
forever begin
if (!objection_raised) begin
`uvm_info(`gfn, "watchdog_ok_to_end: raising objection", UVM_MEDIUM)
run_phase.raise_objection(this, {`gfn, " objection raised"});
objection_raised = 1'b1;
end
// Start the timer only when ok_to_end is asserted.
wait(ok_to_end);
`uvm_info(`gfn, $sformatf("watchdog_ok_to_end: starting the timer (count: %0d)",
watchdog_restart_count++), UVM_MEDIUM)
fork
begin: isolation_fork
fork
begin
watchdog_done = 1'b0;
#(cfg.ok_to_end_delay_ns * 1ns);
watchdog_done = 1'b1;
end
@(ok_to_end);
join_any
disable fork;
end: isolation_fork
join
// The #0 delay ensures that we sample the stabilized value of ok_to_end in the condition
// below in case it toggles more than once in the same simulation time-step.
#0;
// If ok_to_end stayed high throughout the watchdog timer expiry, then drop the objection.
if (ok_to_end && watchdog_done) begin
`uvm_info(`gfn, "watchdog_ok_to_end: dropping objection", UVM_MEDIUM)
run_phase.drop_objection(this, {`gfn, " objection dropped"});
objection_raised = 1'b0;
// Wait for ok_to_end to de-assert again in future.
wait(!ok_to_end);
end
end
endtask
// Asserts/de-asserts ok_to_end to indicate bus activity.
//
// This task is invoked in a forked thread within `phase_ready_to_end()`, which is callback
// invoked by UVM at the end of the phase. The forked thread does not join. Hence, the extended
// monitor needs to override this function and assert ok_to_end based on the activity on the bus
// (assert it when idle, de-assert when its not) in a forever loop.
virtual task monitor_ready_to_end();
endtask
endclass

View file

@ -0,0 +1,68 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
class dv_base_scoreboard #(type RAL_T = dv_base_reg_block,
type CFG_T = dv_base_env_cfg,
type COV_T = dv_base_env_cov) extends uvm_component;
`uvm_component_param_utils(dv_base_scoreboard #(RAL_T, CFG_T, COV_T))
CFG_T cfg;
RAL_T ral;
COV_T cov;
bit obj_raised = 1'b0;
bit under_pre_abort = 1'b0;
`uvm_component_new
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
ral = cfg.ral;
endfunction
virtual task run_phase(uvm_phase phase);
super.run_phase(phase);
fork
monitor_reset();
join_none
endtask
virtual task monitor_reset();
forever begin
if (!cfg.clk_rst_vif.rst_n) begin
`uvm_info(`gfn, "reset occurred", UVM_HIGH)
cfg.reset_asserted();
@(posedge cfg.clk_rst_vif.rst_n);
reset();
cfg.reset_deasserted();
`uvm_info(`gfn, "out of reset", UVM_HIGH)
end
else begin
// wait for a change to rst_n
@(cfg.clk_rst_vif.rst_n);
end
end
endtask
virtual function void reset(string kind = "HARD");
// reset the ral model
if (cfg.has_ral) ral.reset(kind);
endfunction
virtual function void pre_abort();
super.pre_abort();
// use under_pre_abort flag to prevent deadloop described below:
// when fatal_err occurred, it will skip check_phase. We add the additional check_phase call
// here to help debugging. But if inside the check_phase there are UVM_ERRORs, and the err cnt
// is larger than max_err_cnt, then check_phase will call pre_abort again. This will end up
// creating a deadloop.
if (has_uvm_fatal_occurred() && !under_pre_abort) begin
under_pre_abort = 1;
check_phase(m_current_phase);
under_pre_abort = 0;
end
endfunction : pre_abort
endclass

View file

@ -0,0 +1,25 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
class dv_base_seq #(type REQ = uvm_sequence_item,
type RSP = REQ,
type CFG_T = dv_base_agent_cfg,
type SEQUENCER_T = dv_base_sequencer) extends uvm_sequence#(REQ, RSP);
`uvm_object_param_utils(dv_base_seq #(REQ, RSP, CFG_T, SEQUENCER_T))
`uvm_declare_p_sequencer(SEQUENCER_T)
CFG_T cfg;
`uvm_object_new
task pre_start();
super.pre_start();
cfg = p_sequencer.cfg;
endtask
task body();
`uvm_fatal(`gtn, "Need to override this when you extend from this class!")
endtask : body
endclass

View file

@ -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
class dv_base_sequencer #(type ITEM_T = uvm_sequence_item,
type CFG_T = dv_base_agent_cfg,
type RSP_ITEM_T = ITEM_T)
extends uvm_sequencer #(.REQ(ITEM_T), .RSP(RSP_ITEM_T));
`uvm_component_param_utils(dv_base_sequencer #(.ITEM_T (ITEM_T),
.CFG_T (CFG_T),
.RSP_ITEM_T (RSP_ITEM_T)))
CFG_T cfg;
`uvm_component_new
endclass

View file

@ -0,0 +1,77 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
class dv_base_test #(type CFG_T = dv_base_env_cfg,
type ENV_T = dv_base_env) extends uvm_test;
`uvm_component_param_utils(dv_base_test #(CFG_T, ENV_T))
ENV_T env;
CFG_T cfg;
bit run_test_seq = 1'b1;
string test_seq_s;
uint max_quit_count = 1;
uint64 test_timeout_ns = 200_000_000; // 200ms
uint drain_time_ns = 2_000; // 2us
`uvm_component_new
virtual function void build_phase(uvm_phase phase);
dv_report_server m_dv_report_server = new();
uvm_report_server::set_server(m_dv_report_server);
super.build_phase(phase);
env = ENV_T::type_id::create("env", this);
cfg = CFG_T::type_id::create("cfg", this);
// don't add args for initialize. Use default value instead
cfg.initialize();
`DV_CHECK_RANDOMIZE_FATAL(cfg)
uvm_config_db#(CFG_T)::set(this, "env", "cfg", cfg);
// knob to en/dis scb (enabled by default)
void'($value$plusargs("en_scb=%0b", cfg.en_scb));
void'($value$plusargs("en_scb_tl_err_chk=%0b", cfg.en_scb_tl_err_chk));
void'($value$plusargs("en_scb_mem_chk=%0b", cfg.en_scb_mem_chk));
// knob to cfg all agents with zero delays
void'($value$plusargs("zero_delays=%0b", cfg.zero_delays));
endfunction : build_phase
virtual function void end_of_elaboration_phase(uvm_phase phase);
super.end_of_elaboration_phase(phase);
void'($value$plusargs("max_quit_count=%0d", max_quit_count));
set_max_quit_count(max_quit_count);
void'($value$plusargs("test_timeout_ns=%0d", test_timeout_ns));
uvm_top.set_timeout((test_timeout_ns * 1ns));
endfunction : end_of_elaboration_phase
virtual task run_phase(uvm_phase phase);
void'($value$plusargs("drain_time_ns=%0d", drain_time_ns));
phase.phase_done.set_drain_time(this, (drain_time_ns * 1ns));
void'($value$plusargs("UVM_TEST_SEQ=%0s", test_seq_s));
if (run_test_seq) begin
run_seq(test_seq_s, phase);
end
// TODO: add hook for end of test checking
endtask : run_phase
virtual task run_seq(string test_seq_s, uvm_phase phase);
uvm_sequence test_seq = create_seq_by_name(test_seq_s);
// provide virtual_sequencer earlier, so we may use the p_sequencer in constraint
test_seq.set_sequencer(env.virtual_sequencer);
`DV_CHECK_RANDOMIZE_FATAL(test_seq)
`uvm_info(`gfn, {"Starting test sequence ", test_seq_s}, UVM_MEDIUM)
phase.raise_objection(this, $sformatf("%s objection raised", `gn));
test_seq.start(env.virtual_sequencer);
phase.drop_objection(this, $sformatf("%s objection dropped", `gn));
`uvm_info(`gfn, {"Finished test sequence ", test_seq_s}, UVM_MEDIUM)
endtask
// TODO: add default report_phase implementation
endclass : dv_base_test

View file

@ -0,0 +1,14 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
class dv_base_virtual_sequencer #(type CFG_T = dv_base_env_cfg,
type COV_T = dv_base_env_cov) extends uvm_sequencer;
`uvm_component_param_utils(dv_base_virtual_sequencer #(CFG_T, COV_T))
CFG_T cfg;
COV_T cov;
`uvm_component_new
endclass

View file

@ -0,0 +1,215 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
class dv_base_vseq #(type RAL_T = dv_base_reg_block,
type CFG_T = dv_base_env_cfg,
type COV_T = dv_base_env_cov,
type VIRTUAL_SEQUENCER_T = dv_base_virtual_sequencer) extends uvm_sequence;
`uvm_object_param_utils(dv_base_vseq #(RAL_T, CFG_T, COV_T, VIRTUAL_SEQUENCER_T))
`uvm_declare_p_sequencer(VIRTUAL_SEQUENCER_T)
// number of iterations to run the test seq - please override constraint in extended vseq
// randomization for this is disabled in pre_start since we don't want to re-randomize it again
rand uint num_trans;
constraint num_trans_c {
num_trans inside {[1:20]};
}
// handles for ease of op
CFG_T cfg;
RAL_T ral;
COV_T cov;
// knobs to enable pre_start routines
bit do_dut_init = 1'b1;
bit do_apply_reset = 1'b1;
bit do_wait_for_reset = 1'b1;
// knobs to enable post_start routines
bit do_dut_shutdown = 1'b1;
// various knobs to enable certain routines
// this knob allows user to disable assertions in csr_hw_reset before random write sequence,
// the assertions will turn back on after the hw reset deasserted
bit enable_asserts_in_hw_reset_rand_wr = 1'b1;
`uvm_object_new
virtual function void set_handles();
`DV_CHECK_NE_FATAL(p_sequencer, null, "Did you forget to call `set_sequencer()`?")
cfg = p_sequencer.cfg;
cov = p_sequencer.cov;
ral = cfg.ral;
endfunction
// This function is invoked in pre_randomize(). Override it in the extended classes to configure
// / control the randomization of this sequence.
virtual function void configure_vseq();
endfunction
function void pre_randomize();
// Set the handles in pre_randomize(), so that the knobs in cfg are available during sequence
// randomization. This forces `p_sequencer` handle to be set before the randomization - users
// are required to call `set_sequencer()` right after creating the sequence and before
// randomizing it.
if (cfg == null) set_handles();
configure_vseq();
endfunction
task pre_start();
super.pre_start();
if (cfg == null) set_handles();
if (do_dut_init) dut_init("HARD");
num_trans.rand_mode(0);
endtask
task body();
`uvm_fatal(`gtn, "Need to override this when you extend from this class!")
endtask : body
task post_start();
super.post_start();
if (do_dut_shutdown) dut_shutdown();
endtask
/*
* startup, reset and shutdown related tasks
*/
virtual task dut_init(string reset_kind = "HARD");
if (do_apply_reset) apply_reset(reset_kind);
else if (do_wait_for_reset) wait_for_reset(reset_kind);
// delay after reset for tl agent check seq_item_port empty
#1ps;
endtask
virtual task apply_reset(string kind = "HARD");
if (kind == "HARD") begin
csr_utils_pkg::reset_asserted();
cfg.clk_rst_vif.apply_reset();
csr_utils_pkg::reset_deasserted();
end
if (cfg.has_ral) begin
foreach (cfg.ral_models[i]) cfg.ral_models[i].reset(kind);
end
endtask
virtual task wait_for_reset(string reset_kind = "HARD",
bit wait_for_assert = 1,
bit wait_for_deassert = 1);
if (wait_for_assert) begin
`uvm_info(`gfn, "waiting for rst_n assertion...", UVM_MEDIUM)
@(negedge cfg.clk_rst_vif.rst_n);
end
if (wait_for_deassert) begin
`uvm_info(`gfn, "waiting for rst_n de-assertion...", UVM_MEDIUM)
@(posedge cfg.clk_rst_vif.rst_n);
end
`uvm_info(`gfn, "wait_for_reset done", UVM_HIGH)
endtask
// dut shutdown - this is called in post_start if do_dut_shutdown bit is set
virtual task dut_shutdown();
csr_utils_pkg::wait_no_outstanding_access();
endtask
// function to add csr exclusions of the given type using the csr_excl_item item
// arg csr_test_type: this the the type of csr test run - we may want additional exclusions
// depending on what test seq we are running
// arg csr_excl: this is the csr exclusion object that maintains the list of exclusions
// the same object handle is to be passed to csr sequences in csr_seq_lib so that they can query
// those exclusions
virtual function void add_csr_exclusions(string csr_test_type,
csr_excl_item csr_excl,
string scope = "ral");
`uvm_info(`gfn, "no exclusion item added from this function", UVM_DEBUG)
endfunction
// TODO: temp support, can delete this once all IPs update their exclusion in hjson
virtual function csr_excl_item add_and_return_csr_excl(string csr_test_type);
add_csr_exclusions(csr_test_type, ral.csr_excl);
ral.csr_excl.print_exclusions();
return ral.csr_excl;
endfunction
// wrapper task around run_csr_vseq - the purpose is to be able to call this directly for actual
// csr tests (as opposed to higher level stress test that could also run csr seq as a fork by
// calling run_csr_vseq(..) task)
virtual task run_csr_vseq_wrapper(int num_times = 1);
string csr_test_type;
csr_excl_item csr_excl;
// env needs to have a ral instance
`DV_CHECK_EQ_FATAL(cfg.has_ral, 1'b1)
// get csr_test_type from plusarg
void'($value$plusargs("csr_%0s", csr_test_type));
// create csr exclusions before running the csr seq
csr_excl = add_and_return_csr_excl(csr_test_type);
// run the csr seq
for (int i = 1; i <= num_times; i++) begin
`uvm_info(`gfn, $sformatf("running csr %0s vseq iteration %0d/%0d",
csr_test_type, i, num_times), UVM_LOW)
run_csr_vseq(.csr_test_type(csr_test_type), .csr_excl(csr_excl));
end
endtask
// capture the entire csr seq as a task that can be overridden if desired
// arg csr_test_type: what csr test to run {hw_reset, rw, bit_bash, aliasing}
// arg csr_excl: csr exclusion object - needs to be created and exclusions set before call
// arg num_test_csrs:instead of testing the entire ral model or passing test chunk info via
// plusarg, provide ability to set a random number of csrs to test from higher level sequence
virtual task run_csr_vseq(string csr_test_type = "",
csr_excl_item csr_excl = null,
int num_test_csrs = 0,
bit do_rand_wr_and_reset = 1);
csr_base_seq m_csr_seq;
// env needs to have a ral instance
`DV_CHECK_EQ_FATAL(cfg.has_ral, 1'b1)
// check which csr test type
case (csr_test_type)
"hw_reset": csr_base_seq::type_id::set_type_override(csr_hw_reset_seq::get_type());
"rw" : csr_base_seq::type_id::set_type_override(csr_rw_seq::get_type());
"bit_bash": csr_base_seq::type_id::set_type_override(csr_bit_bash_seq::get_type());
"aliasing": csr_base_seq::type_id::set_type_override(csr_aliasing_seq::get_type());
"mem_walk": csr_base_seq::type_id::set_type_override(csr_mem_walk_seq::get_type());
default : `uvm_fatal(`gfn, $sformatf("specified opt is invalid: +csr_%0s", csr_test_type))
endcase
// if hw_reset test, then write all CSRs first and reset the whole dut
if (csr_test_type == "hw_reset" && do_rand_wr_and_reset) begin
string reset_type = "HARD";
csr_write_seq m_csr_write_seq;
// run write-only sequence to randomize the csr values
m_csr_write_seq = csr_write_seq::type_id::create("m_csr_write_seq");
m_csr_write_seq.models = cfg.ral_models;
m_csr_write_seq.set_csr_excl_item(csr_excl);
m_csr_write_seq.external_checker = cfg.en_scb;
m_csr_write_seq.en_rand_backdoor_write = 1;
if (!enable_asserts_in_hw_reset_rand_wr) $assertoff;
m_csr_write_seq.start(null);
// run dut_shutdown before asserting reset
dut_shutdown();
// issue reset
void'($value$plusargs("do_reset=%0s", reset_type));
dut_init(reset_type);
if (!enable_asserts_in_hw_reset_rand_wr) $asserton;
end
// create base csr seq and pass our ral
m_csr_seq = csr_base_seq::type_id::create("m_csr_seq");
m_csr_seq.num_test_csrs = num_test_csrs;
m_csr_seq.models = cfg.ral_models;
m_csr_seq.set_csr_excl_item(csr_excl);
m_csr_seq.external_checker = cfg.en_scb;
m_csr_seq.start(null);
endtask
endclass

View file

@ -0,0 +1,38 @@
CAPI=2:
# Copyright lowRISC contributors.
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
# SPDX-License-Identifier: Apache-2.0
name: "lowrisc:dv:dv_lib"
description: "DV base class UVM library"
filesets:
files_dv:
depend:
- lowrisc:dv:dv_utils
- lowrisc:dv:csr_utils
- lowrisc:dv:dv_base_reg
- lowrisc:ibex:bus_params_pkg
files:
- dv_lib_pkg.sv
- dv_base_agent_cfg.sv: {is_include_file: true}
- dv_base_agent_cov.sv: {is_include_file: true}
- dv_base_monitor.sv: {is_include_file: true}
- dv_base_sequencer.sv: {is_include_file: true}
- dv_base_driver.sv: {is_include_file: true}
- dv_base_agent.sv: {is_include_file: true}
- dv_base_seq.sv: {is_include_file: true}
- dv_base_env_cfg.sv: {is_include_file: true}
- dv_base_env_cov.sv: {is_include_file: true}
- dv_base_virtual_sequencer.sv: {is_include_file: true}
- dv_base_scoreboard.sv: {is_include_file: true}
- dv_base_env.sv: {is_include_file: true}
- dv_base_vseq.sv: {is_include_file: true}
- dv_base_test.sv: {is_include_file: true}
file_type: systemVerilogSource
targets:
default:
filesets:
- files_dv

View file

@ -0,0 +1,45 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
package dv_lib_pkg;
// dep packages
import uvm_pkg::*;
import bus_params_pkg::*;
import dv_utils_pkg::*;
import csr_utils_pkg::*;
import dv_base_reg_pkg::*;
// macro includes
`include "uvm_macros.svh"
`include "dv_macros.svh"
// package variables
string msg_id = "dv_lib_pkg";
// package sources
// base agent
`include "dv_base_agent_cfg.sv"
`include "dv_base_agent_cov.sv"
`include "dv_base_monitor.sv"
`include "dv_base_sequencer.sv"
`include "dv_base_driver.sv"
`include "dv_base_agent.sv"
// base seq
`include "dv_base_seq.sv"
// base env
`include "dv_base_env_cfg.sv"
`include "dv_base_env_cov.sv"
`include "dv_base_virtual_sequencer.sv"
`include "dv_base_scoreboard.sv"
`include "dv_base_env.sv"
// base test vseq
`include "dv_base_vseq.sv"
// base test
`include "dv_base_test.sv"
endpackage

View file

View file

@ -0,0 +1,322 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
// UVM speficic macros
`ifndef gfn
`define gfn get_full_name()
`endif
`ifndef gtn
`define gtn get_type_name()
`endif
`ifndef gn
`define gn get_name()
`endif
`ifndef gmv
`define gmv(csr) csr.get_mirrored_value()
`endif
// cast base class obj holding extended class handle to extended class handle;
// throw error if cast fails
`ifndef downcast
`define downcast(EXT_, BASE_, MSG_="", SEV_=fatal, ID_=`gfn) \
begin \
if (!$cast(EXT_, BASE_)) begin \
`uvm_``SEV_(ID_, $sformatf({"Cast failed: base class variable %0s ", \
"does not hold extended class %0s handle %s"}, \
`"BASE_`", `"EXT_`", MSG_)) \
end \
end
`endif
// Note, UVM provides a macro `uvm_new_func -- which only applies to uvm_components
`ifndef uvm_object_new
`define uvm_object_new \
function new (string name=""); \
super.new(name); \
endfunction : new
`endif
`ifndef uvm_create_obj
`define uvm_create_obj(_type_name_, _inst_name_) \
_inst_name_ = _type_name_::type_id::create(`"_inst_name_`");
`endif
`ifndef uvm_component_new
`define uvm_component_new \
function new (string name="", uvm_component parent=null); \
super.new(name, parent); \
endfunction : new
`endif
`ifndef uvm_create_comp
`define uvm_create_comp(_type_name_, _inst_name_) \
_inst_name_ = _type_name_::type_id::create(`"_inst_name_`", this);
`endif
// Common check macros used by DV_CHECK error and fatal macros.
// Note: Should not be called by user code
`ifndef DV_CHECK
`define DV_CHECK(T_, MSG_="", SEV_=error, ID_=`gfn) \
begin \
if (!(T_)) begin \
`uvm_``SEV_(ID_, $sformatf("Check failed (%s) %s ", `"T_`", MSG_)) \
end \
end
`endif
`ifndef DV_CHECK_EQ
`define DV_CHECK_EQ(ACT_, EXP_, MSG_="", SEV_=error, ID_=`gfn) \
begin \
if (!(ACT_ == EXP_)) begin \
`uvm_``SEV_(ID_, $sformatf("Check failed %s == %s (%0d [0x%0h] vs %0d [0x%0h]) %s", \
`"ACT_`", `"EXP_`", ACT_, ACT_, EXP_, EXP_, MSG_)) \
end \
end
`endif
`ifndef DV_CHECK_NE
`define DV_CHECK_NE(ACT_, EXP_, MSG_="", SEV_=error, ID_=`gfn) \
begin \
if (!(ACT_ != EXP_)) begin \
`uvm_``SEV_(ID_, $sformatf("Check failed %s != %s (%0d [0x%0h] vs %0d [0x%0h]) %s", \
`"ACT_`", `"EXP_`", ACT_, ACT_, EXP_, EXP_, MSG_)) \
end \
end
`endif
`ifndef DV_CHECK_CASE_EQ
`define DV_CHECK_CASE_EQ(ACT_, EXP_, MSG_="", SEV_=error, ID_=`gfn) \
begin \
if (!(ACT_ === EXP_)) begin \
`uvm_``SEV_(ID_, $sformatf("Check failed %s === %s (0x%0h [%0b] vs 0x%0h [%0b]) %s", \
`"ACT_`", `"EXP_`", ACT_, ACT_, EXP_, EXP_, MSG_)) \
end \
end
`endif
`ifndef DV_CHECK_CASE_NE
`define DV_CHECK_CASE_NE(ACT_, EXP_, MSG_="", SEV_=error, ID_=`gfn) \
begin \
if (!(ACT_ !== EXP_)) begin \
`uvm_``SEV_(ID_, $sformatf("Check failed %s !== %s (%0d [0x%0h] vs %0d [0x%0h]) %s", \
`"ACT_`", `"EXP_`", ACT_, ACT_, EXP_, EXP_, MSG_)) \
end \
end
`endif
`ifndef DV_CHECK_LT
`define DV_CHECK_LT(ACT_, EXP_, MSG_="", SEV_=error, ID_=`gfn) \
begin \
if (!(ACT_ < EXP_)) begin \
`uvm_``SEV_(ID_, $sformatf("Check failed %s < %s (%0d [0x%0h] vs %0d [0x%0h]) %s", \
`"ACT_`", `"EXP_`", ACT_, ACT_, EXP_, EXP_, MSG_)) \
end \
end
`endif
`ifndef DV_CHECK_GT
`define DV_CHECK_GT(ACT_, EXP_, MSG_="", SEV_=error, ID_=`gfn) \
begin \
if (!(ACT_ > EXP_)) begin \
`uvm_``SEV_(ID_, $sformatf("Check failed %s > %s (%0d [0x%0h] vs %0d [0x%0h]) %s", \
`"ACT_`", `"EXP_`", ACT_, ACT_, EXP_, EXP_, MSG_)) \
end \
end
`endif
`ifndef DV_CHECK_LE
`define DV_CHECK_LE(ACT_, EXP_, MSG_="", SEV_=error, ID_=`gfn) \
begin \
if (!(ACT_ <= EXP_)) begin \
`uvm_``SEV_(ID_, $sformatf("Check failed %s <= %s (%0d [0x%0h] vs %0d [0x%0h]) %s", \
`"ACT_`", `"EXP_`", ACT_, ACT_, EXP_, EXP_, MSG_)) \
end \
end
`endif
`ifndef DV_CHECK_GE
`define DV_CHECK_GE(ACT_, EXP_, MSG_="", SEV_=error, ID_=`gfn) \
begin \
if (!(ACT_ >= EXP_)) begin \
`uvm_``SEV_(ID_, $sformatf("Check failed %s >= %s (%0d [0x%0h] vs %0d [0x%0h]) %s", \
`"ACT_`", `"EXP_`", ACT_, ACT_, EXP_, EXP_, MSG_)) \
end \
end
`endif
// Fatal version of the checks
`ifndef DV_CHECK_FATAL
`define DV_CHECK_FATAL(T_, MSG_="", ID_=`gfn) \
`DV_CHECK(T_, MSG_, fatal, ID_)
`endif
`ifndef DV_CHECK_EQ_FATAL
`define DV_CHECK_EQ_FATAL(ACT_, EXP_, MSG_="", ID_=`gfn) \
`DV_CHECK_EQ(ACT_, EXP_, MSG_, fatal, ID_)
`endif
`ifndef DV_CHECK_NE_FATAL
`define DV_CHECK_NE_FATAL(ACT_, EXP_, MSG_="", ID_=`gfn) \
`DV_CHECK_NE(ACT_, EXP_, MSG_, fatal, ID_)
`endif
`ifndef DV_CHECK_LT_FATAL
`define DV_CHECK_LT_FATAL(ACT_, EXP_, MSG_="", ID_=`gfn) \
`DV_CHECK_LT(ACT_, EXP_, MSG_, fatal, ID_)
`endif
`ifndef DV_CHECK_GT_FATAL
`define DV_CHECK_GT_FATAL(ACT_, EXP_, MSG_="", ID_=`gfn) \
`DV_CHECK_GT(ACT_, EXP_, MSG_, fatal, ID_)
`endif
`ifndef DV_CHECK_LE_FATAL
`define DV_CHECK_LE_FATAL(ACT_, EXP_, MSG_="", ID_=`gfn) \
`DV_CHECK_LE(ACT_, EXP_, MSG_, fatal, ID_)
`endif
`ifndef DV_CHECK_GE_FATAL
`define DV_CHECK_GE_FATAL(ACT_, EXP_, MSG_="", ID_=`gfn) \
`DV_CHECK_GE(ACT_, EXP_, MSG_, fatal, ID_)
`endif
// Shorthand for common foo.randomize() + fatal check
`ifndef DV_CHECK_RANDOMIZE_FATAL
`define DV_CHECK_RANDOMIZE_FATAL(VAR_, MSG_="Randomization failed!", ID_=`gfn) \
`DV_CHECK_FATAL(VAR_.randomize(), MSG_, ID_)
`endif
// Shorthand for common foo.randomize() with { } + fatal check
`ifndef DV_CHECK_RANDOMIZE_WITH_FATAL
`define DV_CHECK_RANDOMIZE_WITH_FATAL(VAR_, WITH_C_, MSG_="Randomization failed!", ID_=`gfn) \
`DV_CHECK_FATAL(VAR_.randomize() with {WITH_C_}, MSG_, ID_)
`endif
// Shorthand for common std::randomize(foo) + fatal check
`ifndef DV_CHECK_STD_RANDOMIZE_FATAL
`define DV_CHECK_STD_RANDOMIZE_FATAL(VAR_, MSG_="Randomization failed!", ID_=`gfn) \
`DV_CHECK_FATAL(std::randomize(VAR_), MSG_, ID_)
`endif
// Shorthand for common std::randomize(foo) with { } + fatal check
`ifndef DV_CHECK_STD_RANDOMIZE_WITH_FATAL
`define DV_CHECK_STD_RANDOMIZE_WITH_FATAL(VAR_, WITH_C_, MSG_="Randomization failed!",ID_=`gfn) \
`DV_CHECK_FATAL(std::randomize(VAR_) with {WITH_C_}, MSG_, ID_)
`endif
// Shorthand for common this.randomize(foo) + fatal check
`ifndef DV_CHECK_MEMBER_RANDOMIZE_FATAL
`define DV_CHECK_MEMBER_RANDOMIZE_FATAL(VAR_, MSG_="Randomization failed!", ID_=`gfn) \
`DV_CHECK_FATAL(this.randomize(VAR_), MSG_, ID_)
`endif
// Shorthand for common this.randomize(foo) with { } + fatal check
`ifndef DV_CHECK_MEMBER_RANDOMIZE_WITH_FATAL
`define DV_CHECK_MEMBER_RANDOMIZE_WITH_FATAL(VAR_, C_, MSG_="Randomization failed!", ID_=`gfn) \
`DV_CHECK_FATAL(this.randomize(VAR_) with {C_}, MSG_, ID_)
`endif
// print static/dynamic 1d array or queue
`ifndef DV_PRINT_ARR_CONTENTS
`define DV_PRINT_ARR_CONTENTS(ARR_, V_=UVM_MEDIUM, ID_=`gfn) \
begin \
foreach (ARR_[i]) begin \
`uvm_info(ID_, $sformatf("%s[%0d] = 0x%0d[0x%0h]", `"ARR_`", i, ARR_[i], ARR_[i]), V_) \
end \
end
`endif
// print non-empty tlm fifos that were uncompared at end of test
`ifndef DV_EOT_PRINT_TLM_FIFO_CONTENTS
`define DV_EOT_PRINT_TLM_FIFO_CONTENTS(TYP_, FIFO_, SEV_=error, ID_=`gfn) \
begin \
while (!FIFO_.is_empty()) begin \
TYP_ item; \
void'(FIFO_.try_get(item)); \
`uvm_``SEV_(ID_, $sformatf("%s item uncompared:\n%s", `"FIFO_`", item.sprint())) \
end \
end
`endif
// print non-empty tlm fifos that were uncompared at end of test
`ifndef DV_EOT_PRINT_TLM_FIFO_ARR_CONTENTS
`define DV_EOT_PRINT_TLM_FIFO_ARR_CONTENTS(TYP_, FIFO_, SEV_=error, ID_=`gfn) \
begin \
foreach (FIFO_[i]) begin \
while (!FIFO_[i].is_empty()) begin \
TYP_ item; \
void'(FIFO_[i].try_get(item)); \
`uvm_``SEV_(ID_, $sformatf("%s[%0d] item uncompared:\n%s", `"FIFO_`", i, item.sprint())) \
end \
end \
end
`endif
// print non-empty tlm fifos that were uncompared at end of test
`ifndef DV_EOT_PRINT_Q_CONTENTS
`define DV_EOT_PRINT_Q_CONTENTS(TYP_, Q_, SEV_=error, ID_=`gfn) \
begin \
while (Q_.size() != 0) begin \
TYP_ item = Q_.pop_front(); \
`uvm_``SEV_(ID_, $sformatf("%s item uncompared:\n%s", `"Q_`", item.sprint())) \
end \
end
`endif
// print non-empty tlm fifos that were uncompared at end of test
`ifndef DV_EOT_PRINT_Q_ARR_CONTENTS
`define DV_EOT_PRINT_Q_ARR_CONTENTS(TYP_, Q_, SEV_=error, ID_=`gfn) \
begin \
foreach (Q_[i]) begin \
while (Q_[i].size() != 0) begin \
TYP_ item = Q_[i].pop_front(); \
`uvm_``SEV_(ID_, $sformatf("%s[%0d] item uncompared:\n%s", `"Q_`", i, item.sprint())) \
end \
end \
end
`endif
// check for non-empty mailbox and print items that were uncompared at end of test
`ifndef DV_EOT_PRINT_MAILBOX_CONTENTS
`define DV_EOT_PRINT_MAILBOX_CONTENTS(TYP_, MAILBOX_, SEV_=error, ID_=`gfn) \
begin \
while (MAILBOX_.num() != 0) begin \
TYP_ item; \
void'(MAILBOX_.try_get(item)); \
`uvm_``SEV_(ID_, $sformatf("%s item uncompared:\n%s", `"MAILBOX_`", item.sprint())) \
end \
end
`endif
// get parity - implemented as a macro so that it can be invoked in constraints as well
`ifndef GET_PARITY
`define GET_PARITY(val, odd=0) (^val ^ odd)
`endif
// wait a task or statement with timer watchdog
// input WAIT_ need to be a statement. Here are some examples
// `DV_SPINWAIT(wait(...);, "Wait for ...")
// `DV_SPINWAIT(
// while (1) begin
// ...
// end)
`ifndef DV_SPINWAIT
`define DV_SPINWAIT(WAIT_, MSG_ = "timeout occurred!", TIMEOUT_NS_ = default_spinwait_timeout_ns, ID_ =`gfn) \
begin \
fork begin \
fork \
begin \
WAIT_ \
end \
begin \
wait_timeout(TIMEOUT_NS_, ID_, MSG_); \
end \
join_any \
disable fork; \
end join \
end
`endif

View file

@ -0,0 +1,87 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
// Standardize look & feel of report phase and uvm logging messages.
class dv_report_server extends uvm_default_report_server;
bit show_file_line = 1'b1;
bit use_default_uvm_report_message_format = 1'b0;
function new (string name = "");
super.new(name);
// provide ability to override these knobs over cli
void'($value$plusargs("show_file_line=%0b", show_file_line));
void'($value$plusargs("use_default_uvm_report_message_format=%0b",
use_default_uvm_report_message_format));
endfunction
function void report_summarize(UVM_FILE file = 0);
int num_uvm_warning;
int num_uvm_error;
int num_uvm_fatal;
num_uvm_warning = get_severity_count(UVM_WARNING);
num_uvm_error = get_severity_count(UVM_ERROR);
num_uvm_fatal = get_severity_count(UVM_FATAL);
// Print default summary report
super.report_summarize(file);
// Print final test pass-fail - external tool can use this signature for test status
// Treat UVM_WARNINGs as a sign of test failure since it could silently result in false pass
if ((num_uvm_warning + num_uvm_error + num_uvm_fatal) == 0) begin
$display("\nTEST PASSED CHECKS");
$display(" _____ _ _ _ ");
$display("|_ _|__ ___| |_ _ __ __ _ ___ ___ ___ __| | |");
$display(" | |/ _ \\/ __| __| | '_ \\ / _` / __/ __|/ _ \\/ _` | |");
$display(" | | __/\\__ \\ |_ | |_) | (_| \\__ \\__ \\ __/ (_| |_|");
$display(" |_|\\___||___/\\__| | .__/ \\__,_|___/___/\\___|\\__,_(_)");
$display(" |_| \n");
end
else begin
$display("\nTEST FAILED CHECKS");
$display(" _____ _ __ _ _ _ _ ");
$display("|_ _|__ ___| |_ / _| __ _(_) | ___ __| | |");
$display(" | |/ _ \\/ __| __| | |_ / _` | | |/ _ \\/ _` | |");
$display(" | | __/\\__ \\ |_ | _| (_| | | | __/ (_| |_|");
$display(" |_|\\___||___/\\__| |_| \\__,_|_|_|\\___|\\__,_(_)\n");
end
endfunction
// Override default messaging format to standard "pretty" format for all testbenches
virtual function string compose_report_message(uvm_report_message report_message,
string report_object_name = "");
if (use_default_uvm_report_message_format) begin
return (super.compose_report_message(report_message, report_object_name));
end else begin
uvm_severity severity = report_message.get_severity();
string filename = report_message.get_filename();
int line = report_message.get_line();
string obj_name = report_message.get_report_object().get_full_name();
string id = report_message.get_id();
string message = report_message.get_message();
string file_line;
if (show_file_line && filename != "") begin
filename = get_no_hier_filename(filename);
file_line = $sformatf("(%0s:%0d) ", filename, line);
end
obj_name = {obj_name, ((obj_name != "") ? " " : "")};
compose_report_message = $sformatf({"%0s @ %t: ", file_line, obj_name, "[%0s] %0s"},
severity.name(), $realtime, id, message);
return compose_report_message;
end
endfunction
// get we don't really want the full path to the filename
// this should be reasonably lightweight
local function string get_no_hier_filename(string filename);
int idx;
for (idx = filename.len() - 1; idx >= 0; idx--) if (filename[idx] == "/") break;
return (filename.substr(idx + 1, filename.len() - 1));
endfunction
endclass

View file

@ -0,0 +1,23 @@
CAPI=2:
# Copyright lowRISC contributors.
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
# SPDX-License-Identifier: Apache-2.0
name: "lowrisc:dv:dv_utils"
description: "DV utilities"
filesets:
files_dv:
depend:
- lowrisc:dv:common_ifs
- lowrisc:prim:assert:0.1
- lowrisc:ibex:bus_params_pkg
files:
- dv_utils_pkg.sv
- dv_macros.svh: {is_include_file: true}
- dv_report_server.sv: {is_include_file: true}
file_type: systemVerilogSource
targets:
default:
filesets:
- files_dv

View file

@ -0,0 +1,156 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
package dv_utils_pkg;
// dep packages
import uvm_pkg::*;
import bus_params_pkg::*;
// macro includes
`include "dv_macros.svh"
`include "uvm_macros.svh"
// common parameters used across all benches
parameter int NUM_MAX_INTERRUPTS = 32;
parameter int NUM_MAX_ALERTS = 32;
// types & variables
typedef bit [31:0] uint;
typedef bit [7:0] uint8;
typedef bit [15:0] uint16;
typedef bit [31:0] uint32;
typedef bit [63:0] uint64;
// typedef parameterized pins_if for ease of implementation for interrupts and alerts
typedef virtual pins_if #(NUM_MAX_INTERRUPTS) intr_vif;
typedef virtual pins_if #(1) devmode_vif;
// interface direction / mode - Host or Device
typedef enum bit {
Host,
Device
} if_mode_e;
// speed for the clock
typedef enum int {
ClkFreq24Mhz = 24,
ClkFreq25Mhz = 25,
ClkFreq48Mhz = 48,
ClkFreq50Mhz = 50,
ClkFreq100Mhz = 100
} clk_freq_mhz_e;
// compare operator types
typedef enum {
CompareOpEq,
CompareOpCaseEq,
CompareOpNe,
CompareOpCaseNe,
CompareOpGt,
CompareOpGe,
CompareOpLt,
CompareOpLe
} compare_op_e;
// mem address struct
typedef struct {
uvm_reg_addr_t start_addr;
uvm_reg_addr_t end_addr;
} addr_range_t;
// Enum representing a bus operation type - read or write.
typedef enum bit {
BusOpWrite = 1'b0,
BusOpRead = 1'b1
} bus_op_e;
// Enum representing a type of host requests - read only, write only or random read & write
typedef enum int {
HostReqNone = 0,
HostReqReadOnly = 1,
HostReqWriteOnly = 2,
HostReqReadWrite = 3
} host_req_type_e;
string msg_id = "dv_utils_pkg";
// return the smaller value of 2 inputs
function automatic int min2(int a, int b);
return (a < b) ? a : b;
endfunction
// return the bigger value of 2 inputs
function automatic int max2(int a, int b);
return (a > b) ? a : b;
endfunction
// Simple function to set max errors before quitting sim
function automatic void set_max_quit_count(int n);
uvm_report_server report_server = uvm_report_server::get_server();
report_server.set_max_quit_count(n);
endfunction
// return if uvm_fatal occurred
function automatic bit has_uvm_fatal_occurred();
uvm_report_server report_server = uvm_report_server::get_server();
return report_server.get_severity_count(UVM_FATAL) > 0;
endfunction
// task that waits for the specfied timeout
task automatic wait_timeout(input uint timeout_ns,
input string error_msg_id = msg_id,
input string error_msg = "timeout occurred!",
input bit report_fatal = 1);
#(timeout_ns * 1ns);
if (report_fatal) `uvm_fatal(error_msg_id, error_msg)
else `uvm_error(error_msg_id, error_msg)
endtask : wait_timeout
// get masked data based on provided byte mask; if csr reg handle is provided (optional) then
// masked bytes from csr's mirrored value are returned, else masked bytes are 0's
function automatic bit [bus_params_pkg::BUS_DW-1:0]
get_masked_data(bit [bus_params_pkg::BUS_DW-1:0] data,
bit [bus_params_pkg::BUS_DBW-1:0] mask,
uvm_reg csr = null);
bit [bus_params_pkg::BUS_DW-1:0] csr_data;
csr_data = (csr != null) ? csr.get_mirrored_value() : '0;
get_masked_data = data;
foreach (mask[i]) begin
if (~mask[i]) get_masked_data[i * 8 +: 8] = csr_data[i * 8 +: 8];
end
endfunction
// get absolute value of the input. Usage: absolute(val) or absolute(a - b)
function automatic uint absolute(int val);
return val >= 0 ? val : -val;
endfunction
// endian swap
function automatic logic [31:0] endian_swap(logic [31:0] data);
return {<<8{data}};
endfunction
// create a sequence by name and return the handle of uvm_sequence
function automatic uvm_sequence create_seq_by_name(string seq_name);
uvm_object obj;
uvm_factory factory;
uvm_sequence seq;
factory = uvm_factory::get();
obj = factory.create_object_by_name(seq_name, "", seq_name);
if (obj == null) begin
// print factory overrides to help debug
factory.print(1);
`uvm_fatal(msg_id, $sformatf("could not create %0s seq", seq_name))
end
if (!$cast(seq, obj)) begin
`uvm_fatal(msg_id, $sformatf("cast failed - %0s is not a uvm_sequence", seq_name))
end
return seq;
endfunction
// sources
`include "dv_report_server.sv"
endpackage

View file

@ -0,0 +1,26 @@
## Memory Model
The memory model UVC models a memory device which any host interface can read
from or write to. It is implemented as a `uvm_object`, and instantiates an
associative array of bytes `system_memory`. This class is paramterized by both
the address width and the data width, and creates two `typedefs` to represent
both, `mem_addr_t` and `mem_data_t`.
The `mem_model` class has four main functions, which are detailed below.
### `read_byte(mem_addr_t addr)`
This function looks up the byte of data corresponding to the memory address
passed in, and returns it. If the address does not exist in `system_memory`, it
will randomize the returned data and throw a `UVM_ERROR`.
### `write_byte(mem_addr_t addr, bit [7:0] data)`
This function simply assigns the given data to the specified memory address
location in `system_memory`.
### `write(input mem_addr_t addr, mem_data_t data)`
This function writes a full memory word of width `mem_data_t` to the specified
address, breaking it down into a series of back-to-back calls to `write_byte()`
to correctly byte-address the memory.
### `read(mem_addr_t addr)`
This function reads a full memory word of width `mem_data_t` from the specified
address, breaking it down into a series of back-to-back calls to `read_byte()`
to correctly byte-address the memory.

View file

@ -0,0 +1,20 @@
CAPI=2:
# Copyright lowRISC contributors.
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
# SPDX-License-Identifier: Apache-2.0
name: "lowrisc:dv:mem_model"
description: "DV Memory Model"
filesets:
files_dv:
depend:
- lowrisc:opentitan:bus_params_pkg
files:
- mem_model_pkg.sv
- mem_model.sv: {is_include_file: true}
file_type: systemVerilogSource
targets:
default:
filesets:
- files_dv

View file

@ -0,0 +1,85 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
class mem_model #(int AddrWidth = bus_params_pkg::BUS_AW,
int DataWidth = bus_params_pkg::BUS_DW,
int MaskWidth = bus_params_pkg::BUS_DBW) extends uvm_object;
typedef bit [AddrWidth-1:0] mem_addr_t;
typedef bit [DataWidth-1:0] mem_data_t;
typedef bit [MaskWidth-1:0] mem_mask_t;
bit [7:0] system_memory[mem_addr_t];
`uvm_object_param_utils(mem_model#(AddrWidth, DataWidth))
`uvm_object_new
function int get_written_bytes();
return system_memory.size();
endfunction
function bit [7:0] read_byte(mem_addr_t addr);
bit [7:0] data;
if (system_memory.exists(addr)) begin
data = system_memory[addr];
`uvm_info(`gfn, $sformatf("Read Mem : Addr[0x%0h], Data[0x%0h]", addr, data), UVM_HIGH)
end else begin
`DV_CHECK_STD_RANDOMIZE_FATAL(data)
`uvm_error(`gfn, $sformatf("read to uninitialzed addr 0x%0h", addr))
end
return data;
endfunction
function void write_byte(mem_addr_t addr, bit [7:0] data);
`uvm_info(`gfn, $sformatf("Write Mem : Addr[0x%0h], Data[0x%0h]", addr, data), UVM_HIGH)
system_memory[addr] = data;
endfunction
function void compare_byte(mem_addr_t addr, bit [7:0] act_data);
`uvm_info(`gfn, $sformatf("Compare Mem : Addr[0x%0h], Act Data[0x%0h], Exp Data[0x%0h]",
addr, act_data, system_memory[addr]), UVM_HIGH)
system_memory[addr] = act_data;
`DV_CHECK_EQ(act_data, system_memory[addr], $sformatf("addr 0x%0h read out mismatch", addr))
endfunction
function void write(input mem_addr_t addr, mem_data_t data, mem_mask_t mask = '1);
bit [7:0] byte_data;
for (int i = 0; i < DataWidth / 8; i++) begin
if (mask[0]) begin
byte_data = data[7:0];
write_byte(addr + i, byte_data);
end
data = data >> 8;
mask = mask >> 1;
end
endfunction
function mem_data_t read(mem_addr_t addr, mem_mask_t mask = '1);
mem_data_t data;
for (int i = DataWidth / 8 - 1; i >= 0; i--) begin
data = data << 8;
if (mask[MaskWidth - 1]) data[7:0] = read_byte(addr + i);
else data[7:0] = 0;
mask = mask << 1;
end
return data;
endfunction
function void compare(mem_addr_t addr, mem_data_t act_data, mem_mask_t mask = '1);
bit [7:0] byte_data;
for (int i = 0; i < DataWidth / 8; i++) begin
byte_data = act_data[7:0];
if (mask[0]) begin
compare_byte(addr + i, byte_data);
end else begin
`DV_CHECK_EQ(byte_data, 0,
$sformatf("addr 0x%0h masked data aren't 0, mask 0x%0h", addr, mask))
end
act_data = act_data>> 8;
mask = mask >> 1;
end
endfunction
endclass

View file

@ -0,0 +1,12 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
package mem_model_pkg;
import uvm_pkg::*;
`include "uvm_macros.svh"
`include "mem_model.sv"
endpackage

View file

@ -0,0 +1,13 @@
# Verilator memory loading support
## ELF support
### BSS sections
When loading ELF files into the memory, only the data stored in the file is loaded into the memory.
Data specified by the memory size is not set.
This is the case for BSS sections for which only the size information is stored in the ELF file.
The zero-ing of this sections is the responsibility of the executed code.
This is typically achieved by setting symbols for the start and end of the BSS section in the linker script and zero-ing the intermediate addresses by the startup routine.
**Requirement: BSS zero-ing must be implemented by the executed software.**

View file

@ -0,0 +1,472 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
#include "verilator_memutil.h"
#include <fcntl.h>
#include <gelf.h>
#include <getopt.h>
#include <libelf.h>
#include <sys/stat.h>
#include <unistd.h>
#include <cassert>
#include <cstring>
#include <iostream>
#include <list>
// DPI Exports
extern "C" {
/**
* Write |file| to a memory
*
* @param file path to a SystemVerilog $readmemh()-compatible file (VMEM file)
*/
extern void simutil_verilator_memload(const char *file);
/**
* Write a 32 bit word |val| to memory at index |index|
*
* @return 1 if successful, 0 otherwise
*/
extern int simutil_verilator_set_mem(int index, const svBitVecVal *val);
}
bool VerilatorMemUtil::RegisterMemoryArea(const std::string name,
const std::string location) {
// Default to 32bit width
return RegisterMemoryArea(name, location, 32);
}
bool VerilatorMemUtil::RegisterMemoryArea(const std::string name,
const std::string location,
size_t width_bit) {
MemArea mem = {.name = name, .location = location, .width_bit = width_bit};
assert((width_bit <= 256) &&
"TODO: Memory loading only supported up to 256 bits.");
auto ret = mem_register_.emplace(name, mem);
if (ret.second == false) {
std::cerr << "ERROR: Can not register \"" << name << "\" at: \"" << location
<< "\" (Previously defined at: \"" << ret.first->second.location
<< "\")" << std::endl;
return false;
}
return true;
}
bool VerilatorMemUtil::ParseCLIArguments(int argc, char **argv,
bool &exit_app) {
const struct option long_options[] = {
{"rominit", required_argument, nullptr, 'r'},
{"raminit", required_argument, nullptr, 'm'},
{"flashinit", required_argument, nullptr, 'f'},
{"meminit", required_argument, nullptr, 'l'},
{"help", no_argument, nullptr, 'h'},
{nullptr, no_argument, nullptr, 0}};
// Reset the command parsing index in-case other utils have already parsed
// some arguments
optind = 1;
while (1) {
int c = getopt_long(argc, argv, ":r:m:f:l:h", long_options, nullptr);
if (c == -1) {
break;
}
// Disable error reporting by getopt
opterr = 0;
switch (c) {
case 0:
break;
case 'r':
if (!MemWrite("rom", optarg)) {
std::cerr << "ERROR: Unable to initialize memory." << std::endl;
return false;
}
break;
case 'm':
if (!MemWrite("ram", optarg)) {
std::cerr << "ERROR: Unable to initialize memory." << std::endl;
return false;
}
break;
case 'f':
if (!MemWrite("flash", optarg)) {
std::cerr << "ERROR: Unable to initialize memory." << std::endl;
return false;
}
break;
case 'l': {
if (strcasecmp(optarg, "list") == 0) {
PrintMemRegions();
exit_app = true;
return true;
}
std::string name;
std::string filepath;
MemImageType type;
if (!ParseMemArg(optarg, name, filepath, type)) {
std::cerr << "ERROR: Unable to parse meminit arguments." << std::endl;
return false;
}
if (!MemWrite(name, filepath, type)) {
std::cerr << "ERROR: Unable to initialize memory." << std::endl;
return false;
}
} break;
case 'h':
PrintHelp();
return true;
case ':': // missing argument
std::cerr << "ERROR: Missing argument." << std::endl << std::endl;
return false;
case '?':
default:;
// Ignore unrecognized options since they might be consumed by
// other utils
}
}
return true;
}
void VerilatorMemUtil::PrintMemRegions() const {
std::cout << "Registered memory regions:" << std::endl;
for (const auto &m : mem_register_) {
std::cout << "\t'" << m.second.name << "' (" << m.second.width_bit
<< "bits) at location: '" << m.second.location << "'"
<< std::endl;
}
}
void VerilatorMemUtil::PrintHelp() const {
std::cout << "Simulation memory utilities:\n\n"
"-r|--rominit=FILE\n"
" Initialize the ROM with FILE (elf/vmem)\n\n"
"-m|--raminit=FILE\n"
" Initialize the RAM with FILE (elf/vmem)\n\n"
"-f|--flashinit=FILE\n"
" Initialize the FLASH with FILE (elf/vmem)\n\n"
"-l|--meminit=NAME,FILE[,TYPE]\n"
" Initialize memory region NAME with FILE [of TYPE]\n"
" TYPE is either 'elf' or 'vmem'\n\n"
"-l list|--meminit=list\n"
" Print registered memory regions\n\n"
"-h|--help\n"
" Show help\n\n";
}
bool VerilatorMemUtil::ParseMemArg(std::string mem_argument, std::string &name,
std::string &filepath, MemImageType &type) {
std::array<std::string, 3> args;
size_t pos = 0;
size_t end_pos = 0;
size_t i;
for (i = 0; i < 3; ++i) {
end_pos = mem_argument.find(",", pos);
// Check for possible exit conditions
if (pos == end_pos) {
std::cerr << "ERROR: empty field in: " << mem_argument << std::endl;
return false;
}
if (end_pos == std::string::npos) {
args[i] = mem_argument.substr(pos);
break;
}
args[i] = mem_argument.substr(pos, end_pos - pos);
pos = end_pos + 1;
}
// mem_argument is not empty as getopt requires an argument,
// but not a valid argument for memory initialization
if (i == 0) {
std::cerr << "ERROR: meminit must be in \"name,file[,type]\""
<< " got: " << mem_argument << std::endl;
return false;
}
name = args[0];
filepath = args[1];
if (i == 1) {
// Type not set explicitly
type = DetectMemImageType(filepath);
} else {
type = GetMemImageTypeByName(args[2]);
}
return true;
}
MemImageType VerilatorMemUtil::DetectMemImageType(const std::string filepath) {
size_t ext_pos = filepath.find_last_of(".");
std::string ext = filepath.substr(ext_pos + 1);
if (ext_pos == std::string::npos) {
// Assume ELF files if no file extension is given.
// TODO: Make this more robust by actually checking the file contents.
return kMemImageElf;
}
return GetMemImageTypeByName(ext);
}
MemImageType VerilatorMemUtil::GetMemImageTypeByName(const std::string name) {
if (name.compare("elf") == 0) {
return kMemImageElf;
}
if (name.compare("vmem") == 0) {
return kMemImageVmem;
}
return kMemImageUnknown;
}
bool VerilatorMemUtil::IsFileReadable(std::string filepath) const {
struct stat statbuf;
return stat(filepath.data(), &statbuf) == 0;
}
bool VerilatorMemUtil::ElfFileToBinary(const std::string &filepath,
uint8_t **data,
size_t &len_bytes) const {
uint8_t *buf;
bool retval, any = false;
GElf_Phdr phdr;
GElf_Addr high = 0;
GElf_Addr low = (GElf_Addr)-1;
Elf_Data *elf_data;
size_t i;
(void)elf_errno();
len_bytes = 0;
if (elf_version(EV_CURRENT) == EV_NONE) {
std::cerr << elf_errmsg(-1) << std::endl;
return false;
}
int fd = open(filepath.c_str(), O_RDONLY, 0);
if (fd < 0) {
std::cerr << "Could not open file: " << filepath << std::endl;
return false;
}
Elf *elf_desc;
elf_desc = elf_begin(fd, ELF_C_READ, NULL);
if (elf_desc == NULL) {
std::cerr << elf_errmsg(-1) << " in: " << filepath << std::endl;
retval = false;
goto return_fd_end;
}
if (elf_kind(elf_desc) != ELF_K_ELF) {
std::cerr << "Not a ELF file: " << filepath << std::endl;
retval = false;
goto return_elf_end;
}
// TODO: add support for ELFCLASS64
if (gelf_getclass(elf_desc) != ELFCLASS32) {
std::cerr << "Not a 32-bit ELF file: " << filepath << std::endl;
retval = false;
goto return_elf_end;
}
size_t phnum;
if (elf_getphdrnum(elf_desc, &phnum) != 0) {
std::cerr << elf_errmsg(-1) << " in: " << filepath << std::endl;
retval = false;
goto return_elf_end;
}
//
// To mimic what objcopy does (that is, the binary target of BFD), we need to
// iterate over all loadable program headers, find the lowest address, and
// then copy in our loadable sections based on their offset with respect to
// the found base address.
//
for (i = 0; i < phnum; i++) {
if (gelf_getphdr(elf_desc, i, &phdr) == NULL) {
std::cerr << elf_errmsg(-1) << " segment number: " << i
<< " in: " << filepath << std::endl;
retval = false;
goto return_elf_end;
}
if (phdr.p_type != PT_LOAD) {
std::cout << "Program header number " << i << " is not of type PT_LOAD; "
<< "ignoring." << std::endl;
continue;
}
if (phdr.p_filesz == 0) {
continue;
}
if (!any || phdr.p_paddr < low) {
low = phdr.p_paddr;
}
if (!any || phdr.p_paddr + phdr.p_filesz > high) {
high = phdr.p_paddr + phdr.p_filesz;
}
any = true;
}
len_bytes = high - low;
buf = (uint8_t *)malloc(len_bytes);
assert(buf != NULL);
for (i = 0; i < phnum; i++) {
(void)gelf_getphdr(elf_desc, i, &phdr);
if (phdr.p_type != PT_LOAD || phdr.p_filesz == 0) {
continue;
}
elf_data = elf_getdata_rawchunk(elf_desc, phdr.p_offset, phdr.p_filesz,
ELF_T_BYTE);
if (elf_data == NULL) {
retval = false;
free(buf);
goto return_elf_end;
}
memcpy(&buf[phdr.p_paddr - low], (uint8_t *)elf_data->d_buf,
elf_data->d_size);
}
*data = buf;
retval = true;
return_elf_end:
elf_end(elf_desc);
return_fd_end:
close(fd);
return retval;
}
bool VerilatorMemUtil::MemWrite(const std::string &name,
const std::string &filepath) {
MemImageType type = DetectMemImageType(filepath);
if (type == kMemImageUnknown) {
std::cerr << "ERROR: Unable to detect file type for: " << filepath
<< std::endl;
// Continuing for more error messages
}
return MemWrite(name, filepath, type);
}
bool VerilatorMemUtil::MemWrite(const std::string &name,
const std::string &filepath,
MemImageType type) {
// Search for corresponding registered memory based on the name
auto it = mem_register_.find(name);
if (it == mem_register_.end()) {
std::cerr << "ERROR: Memory location not set for: '" << name << "'"
<< std::endl;
PrintMemRegions();
return false;
}
if (!MemWrite(it->second, filepath, type)) {
std::cerr << "ERROR: Setting memory '" << name << "' failed." << std::endl;
return false;
}
return true;
}
bool VerilatorMemUtil::MemWrite(const MemArea &m, const std::string &filepath,
MemImageType type) {
if (!IsFileReadable(filepath)) {
std::cerr << "ERROR: Memory initialization file "
<< "'" << filepath << "'"
<< " is not readable." << std::endl;
return false;
}
svScope scope = svGetScopeFromName(m.location.data());
if (!scope) {
std::cerr << "ERROR: No memory found at " << m.location << std::endl;
return false;
}
if ((m.width_bit % 8) != 0) {
std::cerr << "ERROR: width for: " << m.name
<< "must be a multiple of 8 (was : " << m.width_bit << ")"
<< std::endl;
return false;
}
size_t size_byte = m.width_bit / 8;
switch (type) {
case kMemImageElf:
if (!WriteElfToMem(scope, filepath, size_byte)) {
std::cerr << "ERROR: Writing ELF file to memory \"" << m.name << "\" ("
<< m.location << ") failed." << std::endl;
return false;
}
break;
case kMemImageVmem:
if (!WriteVmemToMem(scope, filepath)) {
std::cerr << "ERROR: Writing VMEM file to memory \"" << m.name << "\" ("
<< m.location << ") failed." << std::endl;
return false;
}
break;
case kMemImageUnknown:
default:
std::cerr << "ERROR: Unknown file type for " << m.name << std::endl;
return false;
}
return true;
}
bool VerilatorMemUtil::WriteElfToMem(const svScope &scope,
const std::string &filepath,
size_t size_byte) {
bool retcode;
svScope prev_scope = svSetScope(scope);
uint8_t *buf = nullptr;
size_t len_bytes;
if (!ElfFileToBinary(filepath, &buf, len_bytes)) {
std::cerr << "ERROR: Could not load: " << filepath << std::endl;
retcode = false;
goto ret;
}
for (int i = 0; i < (len_bytes + size_byte - 1) / size_byte; ++i) {
if (!simutil_verilator_set_mem(i, (svBitVecVal *)&buf[size_byte * i])) {
std::cerr << "ERROR: Could not set memory byte: " << i * size_byte << "/"
<< len_bytes << "" << std::endl;
retcode = false;
goto ret;
}
}
retcode = true;
ret:
svSetScope(prev_scope);
free(buf);
return retcode;
}
bool VerilatorMemUtil::WriteVmemToMem(const svScope &scope,
const std::string &filepath) {
svScope prev_scope = svSetScope(scope);
// TODO: Add error handling.
simutil_verilator_memload(filepath.data());
svSetScope(prev_scope);
return true;
}

View file

@ -0,0 +1,115 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
#ifndef OPENTITAN_HW_DV_VERILATOR_CPP_VERILATOR_MEMUTIL_H_
#define OPENTITAN_HW_DV_VERILATOR_CPP_VERILATOR_MEMUTIL_H_
#include "sim_ctrl_extension.h"
#include <vltstd/svdpi.h>
#include <map>
#include <string>
enum MemImageType {
kMemImageUnknown = 0,
kMemImageElf,
kMemImageVmem,
};
struct MemArea {
std::string name; // Unique identifier
std::string location; // Design scope location
size_t width_bit; // Memory width
};
/**
* Provide various memory loading utilities for Verilator simulations
*
* These utilities require the corresponding DPI functions:
* simutil_verilator_memload()
* simutil_verilator_set_mem()
* to be defined somewhere as SystemVerilog functions.
*/
class VerilatorMemUtil : public SimCtrlExtension {
public:
/**
* Register a memory as instantiated by generic ram
*
* The |name| must be a unique identifier. The function will return false
* if |name| is already used. |location| is the path to the scope of the
* instantiated memory, which needs to support the DPI-C interfaces
* 'simutil_verilator_memload' and 'simutil_verilator_set_mem' used for
* 'vmem' and 'elf' files, respectively.
* The |width_bit| argument specifies the with in bits of the target memory
* instance (used for packing data).
*
* Memories must be registered before command arguments are parsed by
* ParseCommandArgs() in order for them to be known.
*/
bool RegisterMemoryArea(const std::string name, const std::string location,
size_t width_bit);
/**
* Register a memory with default width (32bits)
*/
bool RegisterMemoryArea(const std::string name, const std::string location);
/**
* Parse command line arguments
*
* Process all recognized command-line arguments from argc/argv.
*
* @param argc, argv Standard C command line arguments
* @param exit_app Indicate that program should terminate
* @return Return code, true == success
*/
virtual bool ParseCLIArguments(int argc, char **argv, bool &exit_app);
private:
std::map<std::string, MemArea> mem_register_;
/**
* Print a list of all registered memory regions
*
* @see RegisterMemoryArea()
*/
void PrintMemRegions() const;
/**
* Print help how to use this tool
*/
void PrintHelp() const;
/**
* Parse argument section specific to memory initialization.
*
* Must be in the form of: name,file[,type].
*/
bool ParseMemArg(std::string mem_argument, std::string &name,
std::string &filepath, MemImageType &type);
MemImageType DetectMemImageType(const std::string filepath);
MemImageType GetMemImageTypeByName(const std::string name);
bool IsFileReadable(std::string filepath) const;
/**
* Dump an ELF file into a raw binary
*/
bool ElfFileToBinary(const std::string &filepath, uint8_t **data,
size_t &len_bytes) const;
bool MemWrite(const std::string &name, const std::string &filepath);
bool MemWrite(const std::string &name, const std::string &filepath,
MemImageType type);
bool MemWrite(const MemArea &m, const std::string &filepath,
MemImageType type);
bool WriteElfToMem(const svScope &scope, const std::string &filepath,
size_t size_byte);
bool WriteVmemToMem(const svScope &scope, const std::string &filepath);
};
#endif // OPENTITAN_HW_DV_VERILATOR_CPP_VERILATOR_MEMUTIL_H_

View file

@ -0,0 +1,20 @@
CAPI=2:
# Copyright lowRISC contributors.
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
# SPDX-License-Identifier: Apache-2.0
name: "lowrisc:dv_verilator:memutil_verilator"
description: "Verilator memory utilities"
filesets:
files_cpp:
depend:
- lowrisc:dv_verilator:simutil_verilator
files:
- cpp/verilator_memutil.cc
- cpp/verilator_memutil.h: { is_include_file: true }
file_type: cppSource
targets:
default:
filesets:
- files_cpp

View file

@ -0,0 +1,41 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
#ifndef OPENTITAN_HW_DV_VERILATOR_SIMUTIL_VERILATOR_CPP_SIM_CTRL_EXTENSION_H_
#define OPENTITAN_HW_DV_VERILATOR_SIMUTIL_VERILATOR_CPP_SIM_CTRL_EXTENSION_H_
class SimCtrlExtension {
public:
virtual ~SimCtrlExtension() = default;
/**
* Parse command line arguments
*
* Process all recognized command-line arguments from argc/argv.
*
* @param argc, argv Standard C command line arguments
* @param exit_app Indicate that program should terminate
* @return Return code, true == success
*/
virtual bool ParseCLIArguments(int argc, char **argv, bool &exit_app) {
return true;
}
/**
* Function to be called prior to executing the simulation
*/
virtual void PreExec() {}
/**
* Function to be called every clock cycle
*/
virtual void OnClock(unsigned long sim_time) {}
/**
* Function to be called after executing the simulation
*/
virtual void PostExec() {}
};
#endif // OPENTITAN_HW_DV_VERILATOR_SIMUTIL_VERILATOR_CPP_SIM_CTRL_EXTENSION_H_

View file

@ -0,0 +1,12 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
#include "verilated_toplevel.h"
TOPLEVEL_NAME &VerilatedToplevel::dut() {
// The static_cast below is generally unsafe, but we know the types involved.
// It's safe for these.
TOPLEVEL_NAME &dut = static_cast<TOPLEVEL_NAME &>(*this);
return dut;
}

View file

@ -0,0 +1,155 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
#ifndef OPENTITAN_HW_DV_VERILATOR_SIMUTIL_VERILATOR_CPP_VERILATED_TOPLEVEL_H_
#define OPENTITAN_HW_DV_VERILATOR_SIMUTIL_VERILATOR_CPP_VERILATED_TOPLEVEL_H_
#ifndef TOPLEVEL_NAME
#error "TOPLEVEL_NAME must be set to the name of the toplevel."
#endif
#include <verilated.h>
#define STR(s) #s
#define STR_AND_EXPAND(s) STR(s)
// Include Verilator-generated toplevel
#define VERILATED_TOPLEVEL_HEADER_STR2(s) STR(V##s)
#define VERILATED_TOPLEVEL_HEADER_STR(s) VERILATED_TOPLEVEL_HEADER_STR2(s)
#include VERILATED_TOPLEVEL_HEADER_STR(TOPLEVEL_NAME.h)
// Name of the Verilated class
#define VERILATED_TOPLEVEL_NAME3(s) V##s
#define VERILATED_TOPLEVEL_NAME2(s) VERILATED_TOPLEVEL_NAME3(s)
#define VERILATED_TOPLEVEL_NAME VERILATED_TOPLEVEL_NAME2(TOPLEVEL_NAME)
// VM_TRACE is defined by Verilator and passed through the command line as 1 or
// 0. The code below serves as safety net only.
#ifndef VM_TRACE
#define VM_TRACE 0
#endif
// VM_TRACE_FMT_FST must be set by the user when calling Verilator with
// --trace-fst. VM_TRACE is set by Verilator itself.
#if VM_TRACE == 1
#ifdef VM_TRACE_FMT_FST
#include "verilated_fst_c.h"
#define VM_TRACE_CLASS_NAME VerilatedFstC
#else
#include "verilated_vcd_c.h"
#define VM_TRACE_CLASS_NAME VerilatedVcdC
#endif
#endif
#if VM_TRACE == 1
/**
* "Base" for all tracers in Verilator with common functionality
*
* This class is (like the VerilatedToplevel class) a workaround for the
* insufficient class hierarchy in Verilator-generated C++ code.
*
* Once Verilator is improved to support this functionality natively this class
* should go away.
*/
class VerilatedTracer {
public:
VerilatedTracer() : impl_(nullptr) { impl_ = new VM_TRACE_CLASS_NAME(); };
~VerilatedTracer() { delete impl_; }
bool isOpen() const { return impl_->isOpen(); };
void open(const char *filename) { impl_->open(filename); };
void close() { impl_->close(); };
void dump(vluint64_t timeui) { impl_->dump(timeui); }
operator VM_TRACE_CLASS_NAME *() const {
assert(impl_);
return impl_;
}
private:
VM_TRACE_CLASS_NAME *impl_;
};
#else
/**
* No-op tracer interface
*/
class VerilatedTracer {
public:
VerilatedTracer(){};
~VerilatedTracer() {}
bool isOpen() const { return false; };
void open(const char *filename){};
void close(){};
void dump(vluint64_t timeui) {}
};
#endif // VM_TRACE == 1
// Forward-declare for use in VerilatedToplevel
class TOPLEVEL_NAME;
/**
* Abstract class for verilated toplevel modules
*
* Verilator-produced toplevel modules do not have a common base class defining
* the methods such as eval(); instead, they are only inheriting from the
* generic VerilatedModule class, which doesn't have toplevel-specific
* functionality. This makes it impossible to write code which accepts any
* toplevel module as input by specifying the common "toplevel base class".
*
* This class, VerilatedToplevel, fills this gap by defining an abstract base
* class for verilated toplevel modules. This class should be used together with
* the VERILATED_TOPLEVEL macro.
*
* Note that this function is a workaround until Verilator gains this
* functionality natively.
*
* To support the different tracing implementations (VCD, FST or no tracing),
* the trace() function is modified to take a VerilatedTracer argument instead
* of the tracer-specific class.
*/
class VerilatedToplevel {
public:
VerilatedToplevel(){};
virtual ~VerilatedToplevel(){};
virtual void eval() = 0;
virtual void final() = 0;
virtual const char *name() const = 0;
virtual void trace(VerilatedTracer &tfp, int levels, int options) = 0;
/**
* Get the Verilator-generated device under test
*
* Use this method to access all public signals of the DUT:
*
* VerilatedToplevel &top;
* int clk_value = top->dut().IO_CLK;
* top->dut().IO_CLK = 1;
*/
TOPLEVEL_NAME &dut();
};
class TOPLEVEL_NAME : public VERILATED_TOPLEVEL_NAME, public VerilatedToplevel {
public:
TOPLEVEL_NAME(const char *name = "TOP")
: VERILATED_TOPLEVEL_NAME(name), VerilatedToplevel() {}
const char *name() const { return STR_AND_EXPAND(TOPLEVEL_NAME); }
void eval() { VERILATED_TOPLEVEL_NAME::eval(); }
void final() { VERILATED_TOPLEVEL_NAME::final(); }
void trace(VerilatedTracer &tfp, int levels, int options = 0) {
#if VM_TRACE == 1
VERILATED_TOPLEVEL_NAME::trace(static_cast<VM_TRACE_CLASS_NAME *>(tfp),
levels, options);
#else
assert(0 && "Tracing not enabled.");
#endif
}
};
#endif // OPENTITAN_HW_DV_VERILATOR_SIMUTIL_VERILATOR_CPP_VERILATED_TOPLEVEL_H_

View file

@ -0,0 +1,394 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
#include "verilator_sim_ctrl.h"
#include <getopt.h>
#include <iostream>
#include <signal.h>
#include <sys/stat.h>
#include <verilated.h>
// This is defined by Verilator and passed through the command line
#ifndef VM_TRACE
#define VM_TRACE 0
#endif
/**
* Get the current simulation time
*
* Called by $time in Verilog, converts to double, to match what SystemC does
*/
double sc_time_stamp() { return VerilatorSimCtrl::GetInstance().GetTime(); }
VerilatorSimCtrl &VerilatorSimCtrl::GetInstance() {
static VerilatorSimCtrl instance;
return instance;
}
void VerilatorSimCtrl::SetTop(VerilatedToplevel *top, CData *sig_clk,
CData *sig_rst, VerilatorSimCtrlFlags flags) {
top_ = top;
sig_clk_ = sig_clk;
sig_rst_ = sig_rst;
flags_ = flags;
}
int VerilatorSimCtrl::Exec(int argc, char **argv) {
bool exit_app = false;
if (!ParseCommandArgs(argc, argv, exit_app)) {
return 1;
}
if (exit_app) {
// Successful exit requested by command argument parsing
return 0;
}
RunSimulation();
if (!WasSimulationSuccessful()) {
return 1;
}
return 0;
}
bool VerilatorSimCtrl::ParseCommandArgs(int argc, char **argv, bool &exit_app) {
const struct option long_options[] = {
{"term-after-cycles", required_argument, nullptr, 'c'},
{"trace", no_argument, nullptr, 't'},
{"help", no_argument, nullptr, 'h'},
{nullptr, no_argument, nullptr, 0}};
while (1) {
int c = getopt_long(argc, argv, ":c:th", long_options, nullptr);
if (c == -1) {
break;
}
// Disable error reporting by getopt
opterr = 0;
switch (c) {
case 0:
break;
case 't':
if (!tracing_possible_) {
std::cerr << "ERROR: Tracing has not been enabled at compile time."
<< std::endl;
return false;
}
TraceOn();
break;
case 'c':
term_after_cycles_ = atoi(optarg);
break;
case 'h':
PrintHelp();
exit_app = true;
break;
case ':': // missing argument
std::cerr << "ERROR: Missing argument." << std::endl << std::endl;
return false;
case '?':
default:;
// Ignore unrecognized options since they might be consumed by
// Verilator's built-in parsing below.
}
}
// Pass args to verilator
Verilated::commandArgs(argc, argv);
// Parse arguments for all registered extensions
for (auto it = extension_array_.begin(); it != extension_array_.end(); ++it) {
if (!(*it)->ParseCLIArguments(argc, argv, exit_app)) {
return false;
if (exit_app) {
return true;
}
}
}
return true;
}
void VerilatorSimCtrl::RunSimulation() {
RegisterSignalHandler();
// Print helper message for tracing
if (TracingPossible()) {
std::cout << "Tracing can be toggled by sending SIGUSR1 to this process:"
<< std::endl
<< "$ kill -USR1 " << getpid() << std::endl;
}
// Call all extension pre-exec methods
for (auto it = extension_array_.begin(); it != extension_array_.end(); ++it) {
(*it)->PreExec();
}
// Run the simulation
Run();
// Call all extension post-exec methods
for (auto it = extension_array_.begin(); it != extension_array_.end(); ++it) {
(*it)->PostExec();
}
// Print simulation speed info
PrintStatistics();
// Print helper message for tracing
if (TracingEverEnabled()) {
std::cout << std::endl
<< "You can view the simulation traces by calling" << std::endl
<< "$ gtkwave " << GetTraceFileName() << std::endl;
}
}
void VerilatorSimCtrl::SetInitialResetDelay(unsigned int cycles) {
initial_reset_delay_cycles_ = cycles;
}
void VerilatorSimCtrl::SetResetDuration(unsigned int cycles) {
reset_duration_cycles_ = cycles;
}
void VerilatorSimCtrl::RequestStop(bool simulation_success) {
request_stop_ = true;
simulation_success_ &= simulation_success;
}
void VerilatorSimCtrl::RegisterExtension(SimCtrlExtension *ext) {
extension_array_.push_back(ext);
}
VerilatorSimCtrl::VerilatorSimCtrl()
: top_(nullptr),
time_(0),
tracing_enabled_(false),
tracing_enabled_changed_(false),
tracing_ever_enabled_(false),
tracing_possible_(VM_TRACE),
initial_reset_delay_cycles_(2),
reset_duration_cycles_(2),
request_stop_(false),
simulation_success_(true),
tracer_(VerilatedTracer()),
term_after_cycles_(0) {}
void VerilatorSimCtrl::RegisterSignalHandler() {
struct sigaction sigIntHandler;
sigIntHandler.sa_handler = SignalHandler;
sigemptyset(&sigIntHandler.sa_mask);
sigIntHandler.sa_flags = 0;
sigaction(SIGINT, &sigIntHandler, NULL);
sigaction(SIGUSR1, &sigIntHandler, NULL);
}
void VerilatorSimCtrl::SignalHandler(int sig) {
VerilatorSimCtrl &simctrl = VerilatorSimCtrl::GetInstance();
switch (sig) {
case SIGINT:
simctrl.RequestStop(true);
break;
case SIGUSR1:
if (simctrl.TracingEnabled()) {
simctrl.TraceOff();
} else {
simctrl.TraceOn();
}
break;
}
}
void VerilatorSimCtrl::PrintHelp() const {
std::cout << "Execute a simulation model for " << GetName() << "\n\n";
if (tracing_possible_) {
std::cout << "-t|--trace\n"
" Write a trace file from the start\n\n";
}
std::cout << "-c|--term-after-cycles=N\n"
" Terminate simulation after N cycles\n\n"
"-h|--help\n"
" Show help\n\n"
"All arguments are passed to the design and can be used "
"in the design, e.g. by DPI modules.\n\n";
}
bool VerilatorSimCtrl::TraceOn() {
bool old_tracing_enabled = tracing_enabled_;
tracing_enabled_ = tracing_possible_;
tracing_ever_enabled_ = tracing_enabled_;
if (old_tracing_enabled != tracing_enabled_) {
tracing_enabled_changed_ = true;
}
return tracing_enabled_;
}
bool VerilatorSimCtrl::TraceOff() {
if (tracing_enabled_) {
tracing_enabled_changed_ = true;
}
tracing_enabled_ = false;
return tracing_enabled_;
}
void VerilatorSimCtrl::PrintStatistics() const {
double speed_hz = time_ / 2 / (GetExecutionTimeMs() / 1000.0);
double speed_khz = speed_hz / 1000.0;
std::cout << std::endl
<< "Simulation statistics" << std::endl
<< "=====================" << std::endl
<< "Executed cycles: " << time_ / 2 << std::endl
<< "Wallclock time: " << GetExecutionTimeMs() / 1000.0 << " s"
<< std::endl
<< "Simulation speed: " << speed_hz << " cycles/s "
<< "(" << speed_khz << " kHz)" << std::endl;
int trace_size_byte;
if (tracing_enabled_ && FileSize(GetTraceFileName(), trace_size_byte)) {
std::cout << "Trace file size: " << trace_size_byte << " B" << std::endl;
}
}
const char *VerilatorSimCtrl::GetTraceFileName() const {
#ifdef VM_TRACE_FMT_FST
return "sim.fst";
#else
return "sim.vcd";
#endif
}
void VerilatorSimCtrl::Run() {
assert(top_ && "Use SetTop() first.");
// We always need to enable this as tracing can be enabled at runtime
if (tracing_possible_) {
Verilated::traceEverOn(true);
top_->trace(tracer_, 99, 0);
}
// Evaluate all initial blocks, including the DPI setup routines
top_->eval();
std::cout << std::endl
<< "Simulation running, end by pressing CTRL-c." << std::endl;
time_begin_ = std::chrono::steady_clock::now();
UnsetReset();
Trace();
while (1) {
if (time_ / 2 >= initial_reset_delay_cycles_) {
SetReset();
}
if (time_ / 2 >= reset_duration_cycles_ + initial_reset_delay_cycles_) {
UnsetReset();
}
*sig_clk_ = !*sig_clk_;
// Call all extension on-clock methods
if (*sig_clk_) {
for (auto it = extension_array_.begin(); it != extension_array_.end();
++it) {
(*it)->OnClock(time_);
}
}
top_->eval();
time_++;
Trace();
if (request_stop_) {
std::cout << "Received stop request, shutting down simulation."
<< std::endl;
break;
}
if (Verilated::gotFinish()) {
std::cout << "Received $finish() from Verilog, shutting down simulation."
<< std::endl;
break;
}
if (term_after_cycles_ && (time_ / 2 >= term_after_cycles_)) {
std::cout << "Simulation timeout of " << term_after_cycles_
<< " cycles reached, shutting down simulation." << std::endl;
break;
}
}
top_->final();
time_end_ = std::chrono::steady_clock::now();
if (TracingEverEnabled()) {
tracer_.close();
}
}
std::string VerilatorSimCtrl::GetName() const {
if (top_) {
return top_->name();
}
return "unknown";
}
unsigned int VerilatorSimCtrl::GetExecutionTimeMs() const {
return std::chrono::duration_cast<std::chrono::milliseconds>(time_end_ -
time_begin_)
.count();
}
void VerilatorSimCtrl::SetReset() {
if (flags_ & ResetPolarityNegative) {
*sig_rst_ = 0;
} else {
*sig_rst_ = 1;
}
}
void VerilatorSimCtrl::UnsetReset() {
if (flags_ & ResetPolarityNegative) {
*sig_rst_ = 1;
} else {
*sig_rst_ = 0;
}
}
bool VerilatorSimCtrl::FileSize(std::string filepath, int &size_byte) const {
struct stat statbuf;
if (stat(filepath.data(), &statbuf) != 0) {
size_byte = 0;
return false;
}
size_byte = statbuf.st_size;
return true;
}
void VerilatorSimCtrl::Trace() {
// We cannot output a message when calling TraceOn()/TraceOff() as these
// functions can be called from a signal handler. Instead we print the message
// here from the main loop.
if (tracing_enabled_changed_) {
if (TracingEnabled()) {
std::cout << "Tracing enabled." << std::endl;
} else {
std::cout << "Tracing disabled." << std::endl;
}
tracing_enabled_changed_ = false;
}
if (!TracingEnabled()) {
return;
}
if (!tracer_.isOpen()) {
tracer_.open(GetTraceFileName());
std::cout << "Writing simulation traces to " << GetTraceFileName()
<< std::endl;
}
tracer_.dump(GetTime());
}

View file

@ -0,0 +1,240 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
#ifndef OPENTITAN_HW_DV_VERILATOR_SIMUTIL_VERILATOR_CPP_VERILATOR_SIM_CTRL_H_
#define OPENTITAN_HW_DV_VERILATOR_SIMUTIL_VERILATOR_CPP_VERILATOR_SIM_CTRL_H_
#include <chrono>
#include <string>
#include <vector>
#include "sim_ctrl_extension.h"
#include "verilated_toplevel.h"
enum VerilatorSimCtrlFlags {
Defaults = 0,
ResetPolarityNegative = 1,
};
/**
* Simulation controller for verilated simulations
*/
class VerilatorSimCtrl {
public:
/**
* Get the simulation controller instance
*
* @see SetTop()
*/
static VerilatorSimCtrl &GetInstance();
VerilatorSimCtrl(VerilatorSimCtrl const &) = delete;
void operator=(VerilatorSimCtrl const &) = delete;
/**
* Set the top-level design
*/
void SetTop(VerilatedToplevel *top, CData *sig_clk, CData *sig_rst,
VerilatorSimCtrlFlags flags = Defaults);
/**
* Setup and run the simulation (all in one)
*
* Use this function as high-level entry point, suitable for most use cases.
*
* SetTop() must be called before this function.
*
* This function performs the following tasks:
* 1. Parses a C-style set of command line arguments (see ParseCommandArgs())
* 2. Runs the simulation (see RunSimulation())
*
* @return a main()-compatible process exit code: 0 for success, 1 in case
* of an error.
*/
int Exec(int argc, char **argv);
/**
* Parse command line arguments
*
* Process all recognized command-line arguments from argc/argv.
*
* @param argc, argv Standard C command line arguments
* @param exit_app Indicate that program should terminate
* @return Return code, true == success
*/
bool ParseCommandArgs(int argc, char **argv, bool &exit_app);
/**
* A helper function to execute a standard set of run commands.
*
* This function performs the following tasks:
* 1. Sets up a signal handler to enable tracing to be turned on/off during
* a run by sending SIGUSR1 to the process
* 2. Prints some tracer-related helper messages
* 3. Runs the simulation
* 4. Prints some further helper messages and statistics once the simulation
* has run to completion
*/
void RunSimulation();
/**
* Get the simulation result
*/
bool WasSimulationSuccessful() const { return simulation_success_; }
/**
* Set the number of clock cycles (periods) before the reset signal is
* activated
*/
void SetInitialResetDelay(unsigned int cycles);
/**
* Set the number of clock cycles (periods) the reset signal is activated
*/
void SetResetDuration(unsigned int cycles);
/**
* Request the simulation to stop
*/
void RequestStop(bool simulation_success);
/**
* Register an extension to be called automatically
*/
void RegisterExtension(SimCtrlExtension *ext);
/**
* Get the current time in ticks
*/
unsigned long GetTime() const { return time_; }
private:
VerilatedToplevel *top_;
CData *sig_clk_;
CData *sig_rst_;
VerilatorSimCtrlFlags flags_;
unsigned long time_;
bool tracing_enabled_;
bool tracing_enabled_changed_;
bool tracing_ever_enabled_;
bool tracing_possible_;
unsigned int initial_reset_delay_cycles_;
unsigned int reset_duration_cycles_;
volatile unsigned int request_stop_;
volatile bool simulation_success_;
std::chrono::steady_clock::time_point time_begin_;
std::chrono::steady_clock::time_point time_end_;
VerilatedTracer tracer_;
int term_after_cycles_;
std::vector<SimCtrlExtension *> extension_array_;
/**
* Default constructor
*
* Use GetInstance() instead.
*/
VerilatorSimCtrl();
/**
* Register the signal handler
*/
void RegisterSignalHandler();
/**
* Signal handler callback
*
* Use RegisterSignalHandler() to setup.
*/
static void SignalHandler(int sig);
/**
* Print help how to use this tool
*/
void PrintHelp() const;
/**
* Enable tracing (if possible)
*
* Enabling tracing can fail if no tracing support has been compiled into the
* simulation.
*
* @return Is tracing enabled?
*/
bool TraceOn();
/**
* Disable tracing
*
* @return Is tracing enabled?
*/
bool TraceOff();
/**
* Is tracing currently enabled?
*/
bool TracingEnabled() const { return tracing_enabled_; }
/**
* Has tracing been ever enabled during the run?
*
* Tracing can be enabled and disabled at runtime.
*/
bool TracingEverEnabled() const { return tracing_ever_enabled_; }
/**
* Is tracing support compiled into the simulation?
*/
bool TracingPossible() const { return tracing_possible_; }
/**
* Print statistics about the simulation run
*/
void PrintStatistics() const;
/**
* Get the file name of the trace file
*/
const char *GetTraceFileName() const;
/**
* Run the main loop of the simulation
*
* This function blocks until the simulation finishes.
*/
void Run();
/**
* Get a name for this simulation
*
* This name is typically the name of the top-level.
*/
std::string GetName() const;
/**
* Get the wallclock execution time in ms
*/
unsigned int GetExecutionTimeMs() const;
/**
* Assert the reset signal
*/
void SetReset();
/**
* Deassert the reset signal
*/
void UnsetReset();
/**
* Return the size of a file
*/
bool FileSize(std::string filepath, int &size_byte) const;
/**
* Perform tracing in Verilator if required
*/
void Trace();
};
#endif // OPENTITAN_HW_DV_VERILATOR_SIMUTIL_VERILATOR_CPP_VERILATOR_SIM_CTRL_H_

View file

@ -0,0 +1,21 @@
CAPI=2:
# Copyright lowRISC contributors.
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
# SPDX-License-Identifier: Apache-2.0
name: "lowrisc:dv_verilator:simutil_verilator"
description: "Verilator simulator support"
filesets:
files_cpp:
files:
- cpp/verilator_sim_ctrl.cc
- cpp/verilated_toplevel.cc
- cpp/verilator_sim_ctrl.h: { is_include_file: true }
- cpp/verilated_toplevel.h: { is_include_file: true }
- cpp/sim_ctrl_extension.h: { is_include_file: true }
file_type: cppSource
targets:
default:
filesets:
- files_cpp