blob: 7b9dfd1b8789e1d29e2155463aef40f460e5dde6 [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.
*/
// Base class for RISC-V instruction stream
// A instruction stream here is a queue of RISC-V basic instructions.
// This class also provides some functions to manipulate the instruction stream, like insert a new
// instruction, mix two instruction streams etc.
class riscv_instr_stream extends uvm_object;
riscv_instr_base instr_list[$];
int unsigned instr_cnt;
string label = "";
// User can specify a small group of available registers to generate various hazard condition
rand riscv_reg_t avail_regs[];
// Some additional reserved registers that should not be used as rd register
// by this instruction stream
riscv_reg_t reserved_rd[];
`uvm_object_utils(riscv_instr_stream)
`uvm_object_new
// Initialize the instruction stream, create each instruction instance
function void initialize_instr_list(int unsigned instr_cnt);
instr_list = {};
this.instr_cnt = instr_cnt;
create_instr_instance();
endfunction
virtual function void create_instr_instance();
riscv_instr_base instr;
for(int i = 0; i < instr_cnt; i++) begin
instr = riscv_instr_base::type_id::create($sformatf("instr_%0d", i));
instr_list.push_back(instr);
end
endfunction
// Insert an instruction to the existing instruction stream at the given index
// When index is -1, the instruction is injected at a random location
function void insert_instr(riscv_instr_base instr, int idx = -1);
int current_instr_cnt = instr_list.size();
if(idx == -1) begin
idx = $urandom_range(0, current_instr_cnt-1);
while(instr_list[idx].atomic) begin
idx = $urandom_range(0, current_instr_cnt-1);
end
end else if((idx > current_instr_cnt) || (idx < 0)) begin
`uvm_error(`gfn, $sformatf("Cannot insert instr:%0s at idx %0d",
instr.convert2asm(), idx))
end
instr_list.insert(idx, instr);
endfunction
// Insert an instruction to the existing instruction stream at the given index
// When index is -1, the instruction is injected at a random location
// When replace is 1, the original instruction at the inserted position will be replaced
function void insert_instr_stream(riscv_instr_base new_instr[], int idx = -1, bit replace = 1'b0);
int current_instr_cnt = instr_list.size();
int new_instr_cnt = new_instr.size();
if(current_instr_cnt == 0) begin
instr_list = new_instr;
return;
end
if(idx == -1) begin
idx = $urandom_range(0, current_instr_cnt-1);
repeat(10) begin
if (instr_list[idx].atomic) break;
idx = $urandom_range(0, current_instr_cnt-1);
end
if (instr_list[idx].atomic) begin
foreach (instr_list[i]) begin
if (!instr_list[i].atomic) begin
idx = i;
break;
end
end
if (instr_list[idx].atomic) begin
`uvm_fatal(`gfn, $sformatf("Cannot inject the instruction"))
end
end
end else if((idx > current_instr_cnt) || (idx < 0)) begin
`uvm_error(`gfn, $sformatf("Cannot insert instr stream at idx %0d", idx))
end
// When replace is 1, the original instruction at this index will be removed. The label of the
// original instruction will be copied to the head of inserted instruction stream.
if(replace) begin
new_instr[0].label = instr_list[idx].label;
new_instr[0].has_label = instr_list[idx].has_label;
if (idx == 0) begin
instr_list = {new_instr, instr_list[idx+1:current_instr_cnt-1]};
end else begin
instr_list = {instr_list[0:idx-1], new_instr, instr_list[idx+1:current_instr_cnt-1]};
end
end else begin
if (idx == 0) begin
instr_list = {new_instr, instr_list[idx:current_instr_cnt-1]};
end else begin
instr_list = {instr_list[0:idx-1], new_instr, instr_list[idx:current_instr_cnt-1]};
end
end
endfunction
// Mix the input instruction stream with the original instruction, the instruction order is
// preserved. When 'contained' is set, the original instruction stream will be inside the
// new instruction stream with the first and last instruction from the input instruction stream.
function void mix_instr_stream(riscv_instr_base new_instr[], bit contained = 1'b0);
int current_instr_cnt = instr_list.size();
int insert_instr_position[];
int new_instr_cnt = new_instr.size();
insert_instr_position = new[new_instr_cnt];
`DV_CHECK_STD_RANDOMIZE_WITH_FATAL(insert_instr_position,
foreach(insert_instr_position[i]) {
insert_instr_position[i] inside {[0:current_instr_cnt-1]};
})
if (insert_instr_position.size() > 0) begin
insert_instr_position.sort();
end
if(contained) begin
insert_instr_position[0] = 0;
if(new_instr_cnt > 1)
insert_instr_position[new_instr_cnt-1] = current_instr_cnt-1;
end
foreach(new_instr[i]) begin
insert_instr(new_instr[i], insert_instr_position[i] + i);
end
endfunction
function string convert2string();
string str;
foreach(instr_list[i])
str = {str, instr_list[i].convert2asm(), "\n"};
return str;
endfunction
endclass
// Generate a random instruction stream based on the configuration
// There are two ways to use this class to generate instruction stream
// 1. For short instruction stream, you can call randomize() directly.
// 2. For long instruction stream (>1K), randomize() all instructions together might take a long
// time for the constraint solver. In this case, you can call gen_instr to generate instructions
// one by one. The time only grows linearly with the instruction count
class riscv_rand_instr_stream extends riscv_instr_stream;
riscv_instr_gen_config cfg;
bit kernel_mode;
riscv_instr_name_t allowed_instr[$];
int unsigned category_dist[riscv_instr_category_t];
`uvm_object_utils(riscv_rand_instr_stream)
`uvm_object_new
virtual function void create_instr_instance();
riscv_instr_base instr;
for(int i = 0; i < instr_cnt; i++) begin
instr = riscv_instr_base::type_id::create($sformatf("instr_%0d", i));
instr_list.push_back(instr);
end
endfunction
virtual function void setup_allowed_instr(bit no_branch = 1'b0, bit no_load_store = 1'b1);
allowed_instr = cfg.basic_instr;
if (no_branch == 0) begin
allowed_instr = {allowed_instr, cfg.instr_category[BRANCH]};
end
if (no_load_store == 0) begin
allowed_instr = {allowed_instr, cfg.instr_category[LOAD], cfg.instr_category[STORE]};
end
setup_instruction_dist(no_branch, no_load_store);
endfunction
function setup_instruction_dist(bit no_branch = 1'b0, bit no_load_store = 1'b1);
if (cfg.dist_control_mode) begin
category_dist = cfg.category_dist;
if (no_branch) begin
category_dist[BRANCH] = 0;
end
if (no_load_store) begin
category_dist[LOAD] = 0;
category_dist[STORE] = 0;
end
`uvm_info(`gfn, $sformatf("setup_instruction_dist: %0d", category_dist.size()), UVM_LOW)
end
endfunction
virtual function void gen_instr(bit no_branch = 1'b0, bit no_load_store = 1'b1,
bit is_debug_program = 1'b0);
setup_allowed_instr(no_branch, no_load_store);
foreach(instr_list[i]) begin
randomize_instr(instr_list[i], is_debug_program);
end
// Do not allow branch instruction as the last instruction because there's no
// forward branch target
while (instr_list[$].category == BRANCH) begin
void'(instr_list.pop_back());
if (instr_list.size() == 0) break;
end
endfunction
function void randomize_instr(riscv_instr_base instr,
bit is_in_debug = 1'b0,
bit skip_rs1 = 1'b0,
bit skip_rs2 = 1'b0,
bit skip_rd = 1'b0,
bit skip_imm = 1'b0,
bit skip_csr = 1'b0,
bit disable_dist = 1'b0);
riscv_instr_name_t instr_name;
if ((cfg.dist_control_mode == 1) && !disable_dist) begin
riscv_instr_category_t category;
int unsigned idx;
`DV_CHECK_STD_RANDOMIZE_WITH_FATAL(category,
category dist {LOAD := category_dist[LOAD],
STORE := category_dist[STORE],
SHIFT := category_dist[SHIFT],
ARITHMETIC := category_dist[ARITHMETIC],
LOGICAL := category_dist[LOGICAL],
COMPARE := category_dist[COMPARE],
BRANCH := category_dist[BRANCH],
SYNCH := category_dist[SYNCH],
CSR := category_dist[CSR]};)
idx = $urandom_range(0, cfg.instr_category[category].size() - 1);
instr_name = cfg.instr_category[category][idx];
// if set_dcsr_ebreak is set, we do not want to generate any ebreak
// instructions inside the debug_rom
end else if ((cfg.no_ebreak && !is_in_debug) ||
(!cfg.enable_ebreak_in_debug_rom && is_in_debug)) begin
`DV_CHECK_STD_RANDOMIZE_WITH_FATAL(instr_name,
instr_name inside {allowed_instr};
!(instr_name inside {EBREAK, C_EBREAK});)
end else begin
`DV_CHECK_STD_RANDOMIZE_WITH_FATAL(instr_name,
instr_name inside {allowed_instr};)
end
instr.copy_base_instr(cfg.instr_template[instr_name]);
`uvm_info(`gfn, $sformatf("%s: rs1:%0d, rs2:%0d, rd:%0d, imm:%0d",
instr.instr_name.name(),
instr.has_rs1,
instr.has_rs2,
instr.has_rd,
instr.has_imm), UVM_FULL)
if (instr.has_imm && !skip_imm) begin
instr.gen_rand_imm();
end
if (instr.has_rs1 && !skip_rs1) begin
if (instr.is_compressed) begin
// Compressed instruction could use the same register for rs1 and rd
instr.rs1 = instr.gen_rand_gpr(
.included_reg(avail_regs),
.excluded_reg({reserved_rd, cfg.reserved_regs}));
end else begin
instr.rs1 = instr.gen_rand_gpr(.included_reg(avail_regs));
end
end
if (instr.has_rs2 && !skip_rs2) begin
instr.rs2 = instr.gen_rand_gpr(.included_reg(avail_regs));
end
if (instr.has_rd && !skip_rd) begin
if (instr_name == C_LUI) begin
instr.rd = instr.gen_rand_gpr(
.included_reg(avail_regs),
.excluded_reg({reserved_rd, cfg.reserved_regs, SP}));
end else begin
instr.rd = instr.gen_rand_gpr(
.included_reg(avail_regs),
.excluded_reg({reserved_rd, cfg.reserved_regs}));
end
end
if ((instr.category == CSR) && !skip_csr) begin
instr.gen_rand_csr(.privileged_mode(cfg.init_privileged_mode),
.enable_floating_point(cfg.enable_floating_point),
.illegal_csr_instr(cfg.enable_illegal_csr_instruction));
end
if (instr.has_fs1) begin
instr.fs1 = instr.gen_rand_fpr();
end
if (instr.has_fs2) begin
instr.fs2 = instr.gen_rand_fpr();
end
if (instr.has_fs3) begin
instr.fs3 = instr.gen_rand_fpr();
end
if (instr.has_fd) begin
instr.fd = instr.gen_rand_fpr();
end
endfunction
endclass