| /* |
| * 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 |