Research & Resources

Insights, methodologies, and technical papers from our experts.

Research Paper

Performing End-to-End Traffic Traceability Using Functional Coverage

SV

Shailesh Vasekar

Director, Neurotech Circuits Private Limited

April 01, 2020

Story

For each of the functional features of a DUT to verify, all possible stimulus generation is developed through test cases and with the help of scoreboard, models, checkers, and assertions.

Most of the time, defining functional coverage goals is done during the process of test plan preparation. For each of the functional features of a device under test (DUT) to verify, all possible stimulus generation is developed through test cases and with the help of scoreboard, models, checkers, and assertions. We confirm the correctness of DUT by checking responses for that stimulus. To make sure we have created enough combinations of input scenarios and DUT responses, coverage goals are defined for every feature in terms of covergroups, coverpoints, and assertions coverage which gives us confidence on how thoroughly particular feature has been verified.

DUTs RTL blocks into variables defined out of a score of coverage class and using those variables in cross covergroups with different value hits will confirm that whether the input transactions have followed designated designed path to output or not. This is very important for a couple reasons. First, functional verification scoreboard does end to end transactions checks, but it does not confirm the DUT traffic path stimulus has followed. Secondly, individual covergroups or UVM RAL Regmodel functional coverage or assertion coverage will help at individual stimulus coverage but using in combination will make sure correctness of functional path. Let’s see in detail how we can use functional coverage to confirm traffic from input to output port traverse through desired data and control path.

Performing Traffic Traceability

Many times, covering just individual features and cross features is not enough. There is an essential requirement to check if the traffic of input interfaces have reached to output interfaces of other ends by travelling through all possible combinations of different data blocks, various control logic and different DUT configurations.

Having individual covergroups for DUT input, output interfaces, on control block interface, regmodel auto-generated functional coverage may not guarantee that based on register configurations. The input transactions, packet, or command has travelled through desired data blocks and control path or has bypassed a typical control block.

Using cross-coverage, on the variables defined out of coverage class and used to store transactions from various write methods one can make sure those variables different values are hit and trace the designed DUT path. The covergroup will be also defined out of the coverage class.

Scenario

Let’s consider a scenario, shown in Figure 1. In a DUT, which is highly configured and an input…a packet can be routed to output port through opted datapath and control path blocks and through selected packet Router port based on register configuration. Register CTRNL_RUT with field RT=2’b01 decides packet path from input port 1 through DataPath_blk1 using Cntrl_plan1, switch 1 (not shown in the diagram) and to Router port 1 and finally to output port 1. However, the register CTRNL_RUT.RT=2’b10 will change route for new incoming packet through DataPath_blk2 using Cntrl_plan2 and through Router port 2.

In this case, the covergroups defined for datapath_block1, control path i.e. Cntrl_plane1 and register field RT will individually cover stimulus values. However, if variables are defined outside scope of coverage class, holding the values of signals from these interfaces and if used in cross coverages with interesting value then it can be confirmed that the packet has traversed correct path and achieve packet traceability.

Traffic trace getting information at each point
Figure 1 : Traffic trace getting information at each point

Coverage Implementation

In a testbench, environment class typically has all interfaces agents, functional coverage and scoreboard instantiated in it. IO and control interfaces monitor’s analysis ports are connected to functional coverage class’s exports to get interface packets and transactions. Required resources like regmodel, different configuration objects will be set using uvm_condig_db construct to coverage class. Coverage implementation wise, define variables, packet storage elements outside of coverage class and use those to copy values of important interface signals, control signals and packet’s fields. These variables defined outside of coverage class will be used in cross coverages.

As shown in above Figure 1, variables defined outside of coverage class will be used to save information like packet ids, valid for DataPath_blk1. And for control plane, save information like ready, status, destination id, routing channel numbers. Register values of fields such as CTRNL_RUT.RT values can be crossed with these variables to make sure that if field value of RT is 01 then Datapath_blk1 fields and control plan signals has interested values. Implementing the functional coverage with the approach mentioned will helpful especially in case of block and cluster level verification. Independent implementation of coverage class explores reusability options across projects with similar DUT interfaces.

The register configuration information can also receive through the interface but again need to decode address and fields, so it will be meaningful to use regmodel. Regmodel comes with rich sets of API and default sequences to exercise all registers and fields with their attributes. The Regmodel based auto generated functional coverage can be treated separately.

There is a known limitation with embedded covergroup. We cannot use the multiple instances of the same covergroup as the datatype, hence defining the covergroups outside of the functional coverage class will help to define an array of covergroups, especially it will help if most of the interfaces are of the same type, a usual case of networking domain DUT. The single covergroup with multiple instances can be sampled with different indexes in each of the write_<> methods of coverage class.

Code Example

In the above example, covergroup and variable are storing transactions & interface signals from write methods and are declared outside of class. This will allow these variables to be used across covergroups.

Conclusion

Based on important interface signals, which will validate the transactions or packets, the global variable outside of coverage class scope can be defined and used to copy these signals. Same can be done with every interface signals, control signals and those many global variables can be used in one covergroup which will define the cross coverage for such variables. This is how you can confirm traffic from input to output port traverse through desired data and control path.

SV

About the Author

Shailesh Vasekar is the Director of Neurotech Circuits Private Limited. He has worked in functional verification of ASICs and SOCs development in various domains like networking, consumer, communication. He has used advance verification methodologies like UVM, formal verification, functional coverage drive verification, assertions, SystemVerilog.

Research Paper

RISC-V source class riscv_asm_program_gen: The brain behind assembly instruction generator

SV

Shailesh Vasekar

Director, Neurotech Circuits Private Limited

June 3, 2025

Abstract

CHIPS Alliance has developed an open-source riscv-dv random instruction generator for RISC-V processor verification. This article focuses on the class riscv_asm_program_gen.sv and its various functions, which generate the complete RISC-V assembly program, which is then used to verify RISC-V IP. This class can also address any customization to RISC-V GPR or instruction.

Introduction

The open source https://github.com/chipsalliance/riscv-dv/tree/master defines the SV UVM-based class structure very helpful in the verification of RISC-V IP. The generated random test can be added directly to run with the design IP. The various sections of the ASM program, such as initialization routine, instruction section, data section, stack section, page table, interrupt and exception handling, etc., are generated with different functions in the riscv_asm_program_gen class itself.

First Thing First

First, the class riscv_instr_gen_config is randomized from the test riscv_instr_base_test.sv. This randomization decides the RISC-V extension we are running with, the privilege mode supported, the instruction count in the main and subprogram, whether the program must generate break instructions using variable no_ebreak, similarly no_dret, no_fence, no_wfi, and such configurations.

Configuration variables

Many other such variables can be set to true or false based on DUT features and testbench stimulus generation requirements. The above snapshot lists very few of them.

Kick Start Function gen_program()

The function gen_program() is the main function to generate all sections of the program. Once it’s called from upper layer, it calls other function from the riscv_asm_program_gen one by one.

Then it calls function call get_directed_instr_stream(), selects the ratio of instruction generation from another function called add_directed_instr_stream().

riscv_asm_program_gen.sv(1552) @ 0: reporter [asm_gen] Adding directed instruction stream:riscv_jal_instr ratio: 30/1000
Code snippet for stream insertion

Next function call is to gen_program_header() and uses string array instr_stream to fill it up with header.

str[0] =.include “user_init.s” //example output
gen_program_header snippet

The gen_program_header() function calls another function gen_section(“_start”, str); which inserts header instruction in the instr_stream.

gen_program() then it calls init_gpr() function whose functionality to initialize general purpose registers with random value.

init_gpr code snippet

Next generate_directed_instr_stream() functions get call which decides the ratio and insert directed instruction stream and randomizes the instruction as well. The function randomizes and selects which rs1, rs2 and rd to use based on instruction type. This will generate the asm program which has the instructions with various GPRs x0 to x31 used in all instructions the post_random() function of riscv_instr helps here.

Then sequence riscv_instr_sequence which has function generate_instr_stream which has now all the stream of instruction available and uses convert2asm() function.

There are also check for any illegal or HINT instructions ration is defined, if they are 0 then no illegal or HINT instruction is generated.

From riscv_asm_program_gen main_program[hart].generate_instr_stream() is called which convert the instruction stream to the string format.

There is any sub_program instruction to generate then function call to function insert_sub_program(sub_program[hart], instr_stream);

Once the main and sub_program generation is done then host interface related instruction are added by gen_section function

str=write_tohost:
str= sw gp, tohost, t1
instr[0]=sw gp, tohost, t1
str=_exit:

Then function push_gpr_to_kernel_stack() which pushes general purpose register to stack for trap handling. The riscv_asm_program uses gen_section() selects string for instruction str=mtvec_handler which has exception_hander and interrupt_handler defined.

Generated Program

The function gen_program() and group of functions defined in the same class together with riscv_instruction_sequence and base test and other helper class generates full assembly language RISC-V program with random instructions, random GPR for each instruction with different patterns of instructions.

Final generated assembly program snippet

Summary of Generated Output

The `gen_program()` function and associated helpers work in concert with the `riscv_instruction_sequence`, base test classes, and various configuration helpers to produce a complete RISC-V assembly program. These programs feature randomized instructions and register selections suitable for robust IP verification.

Conclusion

The `riscv_asm_program_gen` class serves as a comprehensive utility to automate the generation of RISC-V assembly programs. Its modular function calls and rich configuration support make it a crucial component in the `riscv-dv` verification ecosystem.