blob: 55da65f5a6339cb81ae39a7dc6392469ea72ad91 [file] [log] [blame]
/*
* Copyright 2018 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
//-----------------------------------------------------------------------------------------
// RISC-V instruction sequence
//
// This class is used to generate a single instruction sequence for a RISC-V assembly program.
// It's used by riscv_asm_program_gen to generate the main program and all sub-programs. The
// flow is explained below:
// For main program:
// - Generate instruction sequence body.
// - Post-process the load/store/branch instructions.
// - Insert the jump instructions to its sub-programs (done by riscv_asm_program_gen).
// For sub program:
// - Generate the stack push instructions which are executed when entering this program.
// - Generate instruction sequence body.
// - Generate the stack pop instructions which are executed before exiting this program.
// - Post-process the load/store/branch instructions.
// - Insert the jump instructions to its sub-programs (done by riscv_asm_program_gen).
// - Generate a return instruction at the end of the program.
//-----------------------------------------------------------------------------------------
class riscv_instr_sequence extends uvm_sequence;
int unsigned instr_cnt; // Instruction count of this sequence
riscv_push_stack_instr instr_stack_enter; // Stack push instructions for sub-programs
riscv_pop_stack_instr instr_stack_exit; // Stack pop instructions for sub-programs
riscv_rand_instr_stream instr_stream; // Main instruction streams
bit is_main_program; // Type of this sequence (main or sub program)
bit is_debug_program; // Indicates whether sequence is debug program
string label_name; // Label of the sequence (program name)
riscv_instr_gen_config cfg; // Configuration class handle
string instr_string_list[$]; // Save the instruction list in string format
int program_stack_len; // Stack space allocated for this program
riscv_instr_stream directed_instr[]; // List of all directed instruction stream
riscv_illegal_instr illegal_instr; // Illegal instruction generator
int illegal_instr_pct; // Percentage of illegal instruction
int hint_instr_pct; // Percentage of HINT instruction
`uvm_object_utils(riscv_instr_sequence)
function new (string name = "");
super.new(name);
if(!uvm_config_db#(riscv_instr_gen_config)::get(null, "*", "instr_cfg", cfg))
`uvm_fatal(get_full_name(), "Cannot get instr_gen_cfg")
instr_stream = riscv_rand_instr_stream::type_id::create("instr_stream");
instr_stack_enter = riscv_push_stack_instr::type_id::create("instr_stack_enter");
instr_stack_exit = riscv_pop_stack_instr::type_id::create("instr_stack_exit");
illegal_instr = riscv_illegal_instr::type_id::create("illegal_instr");
endfunction
// Main function to generate the instruction stream
// The main random instruction stream is generated by instr_stream.gen_instr(), which generates
// each instruction one by one with a separate randomization call. It's not done by a single
// randomization call for the entire instruction stream because this solution won't scale if
// we have hundreds of thousands of instructions to generate. The constraint solver slows down
// considerably as the instruction stream becomes longer. The downside is we cannot specify
// constraints between instructions. The way to solve it is to have a dedicated directed
// instruction stream for such scenarios, like hazard sequence.
virtual function void gen_instr(bit is_main_program, bit no_branch = 1'b0);
this.is_main_program = is_main_program;
instr_stream.cfg = cfg;
instr_stream.initialize_instr_list(instr_cnt);
`uvm_info(get_full_name(), $sformatf("Start generating %0d instruction",
instr_stream.instr_list.size()), UVM_LOW)
// Do not generate load/store instruction here
// The load/store instruction will be inserted as directed instruction stream
instr_stream.gen_instr(.no_branch(no_branch), .no_load_store(1'b1),
.is_debug_program(is_debug_program));
if(!is_main_program) begin
gen_stack_enter_instr();
gen_stack_exit_instr();
end
`uvm_info(get_full_name(), "Finishing instruction generation", UVM_LOW)
endfunction
// Generate the stack push operations for this program
// It pushes the necessary context to the stack like RA, T0,loop registers etc. The stack
// pointer(SP) is reduced by the amount the stack space allocated to this program.
function void gen_stack_enter_instr();
bit allow_branch = ((illegal_instr_pct > 0) || (hint_instr_pct > 0)) ? 1'b0 : 1'b1;
allow_branch &= !cfg.no_branch_jump;
`DV_CHECK_STD_RANDOMIZE_WITH_FATAL(program_stack_len,
program_stack_len inside {[cfg.min_stack_len_per_program : cfg.max_stack_len_per_program]};
// Keep stack len word aligned to avoid unaligned load/store
program_stack_len % (XLEN/8) == 0;,
"Cannot randomize program_stack_len")
instr_stack_enter.cfg = cfg;
instr_stack_enter.push_start_label = {label_name, "_stack_p"};
instr_stack_enter.gen_push_stack_instr(program_stack_len, .allow_branch(allow_branch));
instr_stream.instr_list = {instr_stack_enter.instr_list, instr_stream.instr_list};
endfunction
// Recover the saved GPR from the stack
// Advance the stack pointer(SP) to release the allocated stack space.
function void gen_stack_exit_instr();
instr_stack_exit.cfg = cfg;
instr_stack_exit.gen_pop_stack_instr(
program_stack_len, instr_stack_enter.saved_regs);
instr_stream.instr_list = {instr_stream.instr_list, instr_stack_exit.instr_list};
endfunction
//----------------------------------------------------------------------------------------------
// Instruction post-process
//
// Post-process is required for branch instructions:
//
// - Need to assign a valid branch target. This is done by picking a random instruction label in
// this sequence and assigning to the branch instruction. All the non-atomic instructions
// will have a unique numeric label as the local branch target identifier.
// - The atomic instruction streams don't have labels except for the first instruction. This is
// to avoid branching into an atomic instruction stream which breaks its atomicy. The
// definition of an atomic instruction stream here is a sequence of instructions which must be
// executed in-order.
// - In this sequence, only forward branch is handled. The backward branch target is implemented
// in a dedicated loop instruction sequence. Randomly choosing a backward branch target could
// lead to dead loops in the absence of proper loop exiting conditions.
//
//----------------------------------------------------------------------------------------------
virtual function void post_process_instr();
int i;
int label_idx;
int branch_cnt;
int unsigned branch_idx[];
int branch_target[int] = '{default: 0};
// Insert directed instructions, it's randomly mixed with the random instruction stream.
foreach (directed_instr[i]) begin
instr_stream.insert_instr_stream(directed_instr[i].instr_list);
end
// Assign an index for all instructions, these indexes won't change even a new instruction
// is injected in the post process.
foreach (instr_stream.instr_list[i]) begin
instr_stream.instr_list[i].idx = label_idx;
if (instr_stream.instr_list[i].has_label && !instr_stream.instr_list[i].atomic) begin
if ((illegal_instr_pct > 0) && (instr_stream.instr_list[i].is_illegal_instr == 0)) begin
// The illegal instruction generator always increase PC by 4 when resume execution, need
// to make sure PC + 4 is at the correct instruction boundary.
if (instr_stream.instr_list[i].is_compressed) begin
if (i < instr_stream.instr_list.size()-1) begin
if (instr_stream.instr_list[i+1].is_compressed) begin
instr_stream.instr_list[i].is_illegal_instr =
($urandom_range(0, 100) < illegal_instr_pct);
end
end
end else begin
instr_stream.instr_list[i].is_illegal_instr =
($urandom_range(0, 100) < illegal_instr_pct);
end
end
if ((hint_instr_pct > 0) && (instr_stream.instr_list[i].is_illegal_instr == 0)) begin
if (instr_stream.instr_list[i].is_compressed) begin
instr_stream.instr_list[i].is_hint_instr =
($urandom_range(0, 100) < hint_instr_pct);
end
end
instr_stream.instr_list[i].label = $sformatf("%0d", label_idx);
instr_stream.instr_list[i].is_local_numeric_label = 1'b1;
label_idx++;
end
end
// Generate branch target
branch_idx = new[30];
`DV_CHECK_STD_RANDOMIZE_WITH_FATAL(branch_idx,
foreach(branch_idx[i]) {
branch_idx[i] inside {[1:cfg.max_branch_step]};
})
while(i < instr_stream.instr_list.size()) begin
if((instr_stream.instr_list[i].category == BRANCH) &&
(!instr_stream.instr_list[i].branch_assigned) &&
(!instr_stream.instr_list[i].is_illegal_instr)) begin
// Post process the branch instructions to give a valid local label
// Here we only allow forward branch to avoid unexpected infinite loop
// The loop structure will be inserted with a separate routine using
// reserved loop registers
int branch_target_label;
int branch_byte_offset;
branch_target_label = instr_stream.instr_list[i].idx + branch_idx[branch_cnt];
if (branch_target_label >= label_idx) begin
branch_target_label = label_idx-1;
end
branch_cnt++;
if (branch_cnt == branch_idx.size()) begin
branch_cnt = 0;
branch_idx.shuffle();
end
`uvm_info(get_full_name(),
$sformatf("Processing branch instruction[%0d]:%0s # %0d -> %0d",
i, instr_stream.instr_list[i].convert2asm(),
instr_stream.instr_list[i].idx, branch_target_label), UVM_HIGH)
instr_stream.instr_list[i].imm_str = $sformatf("%0df", branch_target_label);
// Below calculation is only needed for generating the instruction stream in binary format
for (int j = i + 1; j < instr_stream.instr_list.size(); j++) begin
branch_byte_offset = (instr_stream.instr_list[j-1].is_compressed) ?
branch_byte_offset + 2 : branch_byte_offset + 4;
if (instr_stream.instr_list[j].label == $sformatf("%0d", branch_target_label)) begin
instr_stream.instr_list[i].imm = branch_byte_offset;
break;
end else if (j == instr_stream.instr_list.size() - 1) begin
`uvm_fatal(`gfn, $sformatf("Cannot find target label : %0d", branch_target_label))
end
end
instr_stream.instr_list[i].branch_assigned = 1'b1;
branch_target[branch_target_label] = 1;
end
// Remove the local label which is not used as branch target
if(instr_stream.instr_list[i].has_label &&
instr_stream.instr_list[i].is_local_numeric_label) begin
int idx = instr_stream.instr_list[i].label.atoi();
if(!branch_target[idx]) begin
instr_stream.instr_list[i].has_label = 1'b0;
end
end
i++;
end
`uvm_info(get_full_name(), "Finished post-processing instructions", UVM_HIGH)
endfunction
// Inject a jump instruction stream
// This function is called by riscv_asm_program_gen with the target program label
// The jump routine is implmented with an atomic instruction stream(riscv_jump_instr). Similar
// to load/store instructions, JALR/JAL instructions also need a proper base address and offset
// as the jump target.
function void insert_jump_instr(string target_label, int idx);
riscv_jump_instr jump_instr;
jump_instr = riscv_jump_instr::type_id::create("jump_instr");
jump_instr.target_program_label = target_label;
if(!is_main_program)
jump_instr.stack_exit_instr = instr_stack_exit.pop_stack_instr;
jump_instr.cfg = cfg;
jump_instr.label = label_name;
jump_instr.idx = idx;
jump_instr.use_jalr = is_main_program;
`DV_CHECK_RANDOMIZE_FATAL(jump_instr)
instr_stream.insert_instr_stream(jump_instr.instr_list);
`uvm_info(get_full_name(), $sformatf("%0s -> %0s...done",
jump_instr.jump.instr_name.name(), target_label), UVM_LOW)
endfunction
// Convert the instruction stream to the string format.
// Label is attached to the instruction if available, otherwise attach proper space to make
// the code indent consistent.
function void generate_instr_stream(bit no_label = 1'b0);
string prefix, str;
int i;
instr_string_list = {};
for(i = 0; i < instr_stream.instr_list.size(); i++) begin
if(i == 0) begin
if (no_label) begin
prefix = format_string(" ", LABEL_STR_LEN);
end else begin
prefix = format_string($sformatf("%0s:", label_name), LABEL_STR_LEN);
end
instr_stream.instr_list[i].has_label = 1'b1;
end else begin
if(instr_stream.instr_list[i].has_label) begin
prefix = format_string($sformatf("%0s:", instr_stream.instr_list[i].label),
LABEL_STR_LEN);
end else begin
prefix = format_string(" ", LABEL_STR_LEN);
end
end
str = {prefix, instr_stream.instr_list[i].convert2asm()};
instr_string_list.push_back(str);
end
insert_illegal_hint_instr();
prefix = format_string($sformatf("%0d:", i), LABEL_STR_LEN);
if(!is_main_program) begin
if (!cfg.disable_compressed_instr) begin
str = {prefix, $sformatf("c.jr x%0d", cfg.ra)};
end else begin
str = {prefix, $sformatf("jalr x0, x%0d, 0", cfg.ra)};
end
instr_string_list.push_back(str);
end
endfunction
function void insert_illegal_hint_instr();
int bin_instr_cnt;
int idx;
string str;
illegal_instr.init(cfg);
bin_instr_cnt = instr_cnt * cfg.illegal_instr_ratio / 1000;
if (bin_instr_cnt >= 0) begin
`uvm_info(`gfn, $sformatf("Injecting %0d illegal instructions, ratio %0d/100",
bin_instr_cnt, cfg.illegal_instr_ratio), UVM_LOW)
repeat (bin_instr_cnt) begin
`DV_CHECK_RANDOMIZE_WITH_FATAL(illegal_instr,
exception != kHintInstr;)
str = {indent, $sformatf(".4byte 0x%s # %0s",
illegal_instr.get_bin_str(), illegal_instr.exception.name())};
idx = $urandom_range(0, instr_string_list.size());
instr_string_list.insert(idx, str);
end
end
bin_instr_cnt = instr_cnt * cfg.hint_instr_ratio / 1000;
if (bin_instr_cnt >= 0) begin
`uvm_info(`gfn, $sformatf("Injecting %0d HINT instructions, ratio %0d/100",
bin_instr_cnt, cfg.illegal_instr_ratio), UVM_LOW)
repeat (bin_instr_cnt) begin
`DV_CHECK_RANDOMIZE_WITH_FATAL(illegal_instr,
exception == kHintInstr;)
str = {indent, $sformatf(".2byte 0x%s # %0s",
illegal_instr.get_bin_str(), illegal_instr.exception.name())};
idx = $urandom_range(0, instr_string_list.size());
instr_string_list.insert(idx, str);
end
end
endfunction
endclass