Overview
RISC-V assembly program generation, as described for RISCV-DV, is the process of producing a complete bare-metal RISC-V assembly language test program for processor verification. The generated program can be compiled and executed on a RISC-V processor core or model. RISCV-DV can also generate an executable dump that may be loaded directly into memory for simulation or emulation; in the SystemVerilog version, this dump is represented as ASCII text because SystemVerilog does not provide native binary-data processing support. [1]
In the RISCV-DV implementation, the riscv_asm_program_gen.sv class is the central SystemVerilog/UVM component responsible for generating the full assembly program. The generated ASM program includes sections such as initialization, instruction, data, stack, page table, interrupt handling, and exception handling, with those sections produced by functions in riscv_asm_program_gen. [2]
Configuration and customization
Generation is parameterized before the assembly program is emitted. The riscv_instr_gen_config class is randomized from riscv_instr_base_test.sv; this configuration determines items such as the RISC-V extension being used, supported privilege mode, instruction counts in the main program and sub-programs, and whether specific instruction classes are suppressed through controls such as no_ebreak, no_dret, no_fence, and no_wfi. [2]
RISCV-DV is also customizable through command-line options. Examples include the requested number of sub-programs and ratios of instruction-stream categories. In the simplest mode, the generator can emit millions of disparate random instructions into a single main function. When a sub-program count is provided, it randomly divides the requested total instruction count across sub-programs and stitches the resulting call stack using suitable function-call control flow. [1]
gen_program() flow
The gen_program() function is the main entry point for generating all sections of the program. After being called from an upper layer, it invokes other riscv_asm_program_gen functions in sequence. The documented flow includes:
- Selecting directed instruction streams by calling
get_directed_instr_stream()and applying ratios throughadd_directed_instr_stream(). - Calling
gen_program_header()to populate theinstr_streamstring array with program-header content, such as an include ofuser_init.s. - Using
gen_section("_start", str)to insert header/start instructions into the instruction stream. - Calling
init_gpr()to initialize general-purpose registers with random values. - Calling
generate_directed_instr_stream()to choose stream ratios, insert directed streams, and randomize instruction operands such asrs1,rs2, andrdaccording to instruction type. - Using
riscv_instr_sequence.generate_instr_stream()andconvert2asm()to convert available instruction streams into assembly form. - Checking illegal-instruction and HINT-instruction ratios; if those ratios are zero, those instruction categories are not generated.
- Calling
main_program[hart].generate_instr_stream()to convert the main instruction stream into string format. - Calling
insert_sub_program(sub_program[hart], instr_stream)when sub-program instructions are generated. - Adding host-interface related instructions through
gen_section, including awrite_tohostsection withsw gp, tohost, t1and an_exitlabel. - Calling
push_gpr_to_kernel_stack()to push general-purpose registers to the stack for trap handling, and using anmtvec_handlersection containing exception-handler and interrupt-handler definitions. [2] [3]
Together with riscv_instruction_sequence, base-test classes, and helper configuration classes, gen_program() produces a complete RISC-V assembly program with randomized instructions, randomized GPR usage, and different instruction patterns for verification stimulus. [3]
Directed streams and sequencing
Not all instructions are useful or safe when generated in isolation. For example, an unconstrained jalr can jump near program exit and skip most executable test code, enter an infinite loop, or jump outside the generated program scope. To handle such cases, RISCV-DV groups execution use cases into directed streams, including loop sequences, load/store hazards, and numeric computation exceptions. Instructions in a directed stream are constrained together so that they form a meaningful execution scenario. [1]
The directed streams are generated, randomized, and inserted randomly into an initial dump of non-directed randomized instructions. During insertion, RISCV-DV avoids inserting one directed stream inside another already-injected directed stream. Near the end of generation, RISCV-DV revisits the merged instruction stream, examines embedded jump instructions, and annotates target locations with appropriate labels. [1]
Generated output
The generated output is intended as verification stimulus for RISC-V IP. The output program contains randomized instructions and register selections, can be integrated into a design-IP verification run, and includes support sections such as host-interface code and trap-handling support. [2] [3]
Profiling relevance
Because RISCV-DV is highly parameterized, the runtime of its components varies significantly depending on the parameters selected by the user. The cited DVCon paper describes profiling support using a uvm_trace method that records wall-clock timing around code sections, allowing generator performance to be analyzed before optimization. [1]