| /* |
| * 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 assembly program generator |
| // |
| // This is the main class to generate a complete RISC-V program, including the init routine, |
| // instruction section, data section, stack section, page table, interrupt and exception |
| // handling etc. Check gen_program() function to see how the program is generated. |
| //----------------------------------------------------------------------------------------- |
| |
| class riscv_asm_program_gen extends uvm_object; |
| |
| riscv_instr_gen_config cfg; |
| riscv_data_page_gen data_page_gen; |
| // User mode programs |
| riscv_instr_sequence main_program; |
| riscv_instr_sequence sub_program[]; |
| riscv_instr_sequence debug_program; |
| riscv_instr_sequence debug_sub_program[]; |
| // Kernel programs |
| // These programs are called in the interrupt/exception handling routine based on the privileged |
| // mode settings. For example, when the interrupt/exception is delegated to S-mode, if both SUM |
| // and MPRV equal 1, kernel program can fetch/load/store from U-mode pages, |
| // umode_program is designed for this purpose. There can be other cases that |
| // instruction can only be fetched from S-mode pages but load/store can access U-mode pages, or |
| // everything needs to be in S-mode pages. |
| riscv_instr_sequence umode_program; |
| riscv_instr_sequence smode_program; |
| riscv_instr_sequence smode_lsu_program; |
| riscv_instr_stream directed_instr[]; |
| string instr_stream[$]; |
| riscv_callstack_gen callstack_gen; |
| riscv_privileged_common_seq privil_seq; |
| // Directed instruction ratio, occurance per 1000 instructions |
| int unsigned directed_instr_stream_ratio[string]; |
| riscv_page_table_list#(SATP_MODE) page_table_list; |
| |
| `uvm_object_utils(riscv_asm_program_gen) |
| |
| function new (string name = ""); |
| super.new(name); |
| endfunction |
| |
| //--------------------------------------------------------------------------------------- |
| // Main function to generate the whole program |
| //--------------------------------------------------------------------------------------- |
| |
| // This is the main function to generate all sections of the program. |
| virtual function void gen_program(); |
| string sub_program_name[$]; |
| instr_stream.delete(); |
| // Generate program header |
| gen_program_header(); |
| // Initialize general purpose registers |
| init_gpr(); |
| if (!cfg.bare_program_mode) begin |
| setup_misa(); |
| // Create all page tables |
| create_page_table(); |
| // Setup privileged mode registers and enter target privileged mode |
| pre_enter_privileged_mode(); |
| end |
| // Init section |
| gen_init_section(); |
| // Generate sub program |
| gen_sub_program(sub_program, sub_program_name, cfg.num_of_sub_program); |
| // Generate main program |
| main_program = riscv_instr_sequence::type_id::create("main_program"); |
| main_program.instr_cnt = cfg.main_program_instr_cnt; |
| main_program.is_debug_program = 0; |
| main_program.label_name = "_main"; |
| generate_directed_instr_stream(.label("main"), |
| .original_instr_cnt(main_program.instr_cnt), |
| .min_insert_cnt(1), |
| .instr_stream(main_program.directed_instr)); |
| main_program.cfg = cfg; |
| `DV_CHECK_RANDOMIZE_FATAL(main_program) |
| main_program.gen_instr(.is_main_program(1), .no_branch(cfg.no_branch_jump)); |
| // Setup jump instruction among main program and sub programs |
| gen_callstack(main_program, sub_program, sub_program_name, cfg.num_of_sub_program); |
| `uvm_info(`gfn, "Generating callstack...done", UVM_LOW) |
| main_program.post_process_instr(); |
| `uvm_info(`gfn, "Post-processing main program...done", UVM_LOW) |
| main_program.generate_instr_stream(); |
| `uvm_info(`gfn, "Generating main program instruction stream...done", UVM_LOW) |
| instr_stream = {instr_stream, main_program.instr_string_list}; |
| // Test done section |
| gen_test_done(); |
| // Shuffle the sub programs and insert to the instruction stream |
| insert_sub_program(sub_program, instr_stream); |
| `uvm_info(`gfn, "Inserting sub-programs...done", UVM_LOW) |
| `uvm_info(`gfn, "Main/sub program generation...done", UVM_LOW) |
| // Program end |
| gen_program_end(); |
| if (!cfg.bare_program_mode) begin |
| // Privileged mode switch routine |
| gen_privileged_mode_switch_routine(); |
| // Generate debug rom section |
| gen_debug_rom(); |
| // Generate debug mode exception handler |
| gen_debug_exception_handler(); |
| end |
| // Starting point of data section |
| gen_data_page_begin(); |
| // Page table |
| if (!cfg.bare_program_mode) begin |
| gen_page_table_section(); |
| end |
| if(!cfg.no_data_page) begin |
| // Kernel data section |
| gen_data_page(); |
| end |
| gen_data_page_end(); |
| // Stack section |
| gen_stack_section(); |
| if (!cfg.bare_program_mode) begin |
| // Generate kernel program/data/stack section |
| gen_kernel_sections(); |
| end |
| endfunction |
| |
| //--------------------------------------------------------------------------------------- |
| // Generate kernel program/data/stack sections |
| //--------------------------------------------------------------------------------------- |
| virtual function void gen_kernel_sections(); |
| instr_stream.push_back("_kernel_instr_start: .align 12"); |
| instr_stream.push_back(".text"); |
| // Kernel programs |
| if (cfg.virtual_addr_translation_on) begin |
| umode_program = riscv_instr_sequence::type_id::create("umode_program"); |
| gen_kernel_program(umode_program); |
| smode_program = riscv_instr_sequence::type_id::create("smode_program"); |
| gen_kernel_program(smode_program); |
| smode_lsu_program = riscv_instr_sequence::type_id::create("smode_lsu_program"); |
| gen_kernel_program(smode_lsu_program); |
| end |
| // All trap/interrupt handling is in the kernel region |
| // Trap/interrupt delegation to user mode is not supported now |
| // Trap handler |
| gen_all_trap_handler(); |
| // Interrupt handling subroutine |
| foreach(riscv_instr_pkg::supported_privileged_mode[i]) begin |
| gen_interrupt_handler_section(riscv_instr_pkg::supported_privileged_mode[i]); |
| end |
| instr_stream.push_back("_kernel_instr_end: nop"); |
| // User stack and data pages may not be accessible when executing trap handling programs in |
| // machine/supervisor mode. Generate separate kernel data/stack sections to solve it. |
| if (cfg.virtual_addr_translation_on) begin |
| // Kernel data pages |
| instr_stream.push_back("_kernel_data_start: .align 12"); |
| if(!cfg.no_data_page) begin |
| // Data section |
| gen_data_page(1'b1); |
| end |
| gen_data_page_end(); |
| end |
| // Kernel stack section |
| gen_kernel_stack_section(); |
| endfunction |
| |
| virtual function void gen_kernel_program(riscv_instr_sequence seq); |
| seq.instr_cnt = cfg.kernel_program_instr_cnt; |
| generate_directed_instr_stream(.label(seq.get_name()), |
| .original_instr_cnt(seq.instr_cnt), |
| .min_insert_cnt(0), |
| .instr_stream(seq.directed_instr), |
| .kernel_mode(1'b1)); |
| seq.label_name = seq.get_name(); |
| seq.is_debug_program = 0; |
| seq.cfg = cfg; |
| `DV_CHECK_RANDOMIZE_FATAL(seq) |
| seq.gen_instr(.is_main_program(0), .no_branch(cfg.no_branch_jump)); |
| seq.post_process_instr(); |
| seq.generate_instr_stream(); |
| instr_stream = {instr_stream, seq.instr_string_list}; |
| endfunction |
| |
| //--------------------------------------------------------------------------------------- |
| // Generate any subprograms and set up the callstack |
| //--------------------------------------------------------------------------------------- |
| |
| virtual function void gen_sub_program(ref riscv_instr_sequence sub_program[], |
| ref string sub_program_name[$], |
| input int num_sub_program, |
| bit is_debug = 1'b0, |
| string prefix = "sub"); |
| if(num_sub_program > 0) begin |
| sub_program = new[num_sub_program]; |
| foreach(sub_program[i]) begin |
| sub_program[i] = riscv_instr_sequence::type_id::create($sformatf("%s_%0d",prefix,i+1)); |
| `uvm_info(`gfn, $sformatf("sub program name: %s", prefix), UVM_LOW) |
| sub_program[i].is_debug_program = is_debug; |
| if (is_debug) begin |
| sub_program[i].instr_cnt = cfg.debug_sub_program_instr_cnt[i]; |
| end else begin |
| sub_program[i].instr_cnt = cfg.sub_program_instr_cnt[i]; |
| end |
| generate_directed_instr_stream(.label(sub_program[i].get_name()), |
| .original_instr_cnt(sub_program[i].instr_cnt), |
| .min_insert_cnt(0), |
| .instr_stream(sub_program[i].directed_instr)); |
| sub_program[i].label_name = sub_program[i].get_name(); |
| sub_program[i].cfg = cfg; |
| `DV_CHECK_RANDOMIZE_FATAL(sub_program[i]) |
| sub_program[i].gen_instr(.is_main_program(0), .no_branch(cfg.no_branch_jump)); |
| sub_program_name.push_back(sub_program[i].label_name); |
| end |
| end |
| endfunction |
| |
| virtual function void gen_callstack(riscv_instr_sequence main_program, |
| ref riscv_instr_sequence sub_program[], |
| ref string sub_program_name[$], |
| input int num_sub_program); |
| if(num_sub_program != 0) begin |
| callstack_gen = riscv_callstack_gen::type_id::create("callstack_gen"); |
| callstack_gen.init(num_sub_program+1); |
| `uvm_info(get_full_name(), "Randomizing call stack", UVM_LOW) |
| if(callstack_gen.randomize()) begin |
| program_id_t pid; |
| int idx; |
| // Insert the jump instruction based on the call stack |
| foreach(callstack_gen.program_h[i]) begin |
| foreach(callstack_gen.program_h[i].sub_program_id[j]) begin |
| idx++; |
| pid = callstack_gen.program_h[i].sub_program_id[j] - 1; |
| `uvm_info(get_full_name(), $sformatf( |
| "Gen jump instr %0d -> sub[%0d] %0d", i, j, pid+1), UVM_LOW) |
| if(i == 0) |
| main_program.insert_jump_instr(sub_program_name[pid], idx); |
| else |
| sub_program[i-1].insert_jump_instr(sub_program_name[pid], idx); |
| end |
| end |
| end else begin |
| `uvm_fatal(get_full_name(), "Failed to generate callstack") |
| end |
| end |
| `uvm_info(get_full_name(), "Randomizing call stack..done", UVM_LOW) |
| endfunction |
| |
| virtual function void insert_sub_program(ref riscv_instr_sequence sub_program[], |
| ref string instr_list[$]); |
| sub_program.shuffle(); |
| foreach(sub_program[i]) begin |
| sub_program[i].post_process_instr(); |
| sub_program[i].generate_instr_stream(); |
| instr_list = {instr_list, sub_program[i].instr_string_list}; |
| end |
| endfunction |
| |
| //--------------------------------------------------------------------------------------- |
| // Major sections - init, stack, data, test_done etc. |
| //--------------------------------------------------------------------------------------- |
| |
| virtual function void gen_program_header(); |
| instr_stream.push_back(".include \"user_define.h\""); |
| instr_stream.push_back(".globl _start"); |
| instr_stream.push_back(".section .text"); |
| if (cfg.disable_compressed_instr) begin |
| instr_stream.push_back(".option norvc;"); |
| end |
| instr_stream.push_back("_start:"); |
| endfunction |
| |
| virtual function void gen_program_end(); |
| // Use write_tohost to terminate spike simulation |
| gen_section("write_tohost", {"sw gp, tohost, t5"}); |
| gen_section("_exit", {"j write_tohost"}); |
| endfunction |
| |
| virtual function void gen_data_page_begin(); |
| instr_stream.push_back(".data"); |
| instr_stream.push_back(".pushsection .tohost,\"aw\",@progbits;"); |
| instr_stream.push_back(".align 6; .global tohost; tohost: .dword 0;"); |
| instr_stream.push_back(".align 6; .global fromhost; fromhost: .dword 0;"); |
| instr_stream.push_back(".popsection;"); |
| endfunction |
| |
| virtual function void gen_data_page(bit is_kernel = 1'b0); |
| string data_page; |
| data_page_gen = riscv_data_page_gen::type_id::create("data_page_gen"); |
| data_page_gen.cfg = cfg; |
| data_page_gen.gen_data_page(cfg.data_page_pattern, is_kernel); |
| instr_stream = {instr_stream, data_page_gen.data_page_str}; |
| endfunction |
| |
| virtual function void gen_data_page_end(); |
| instr_stream.push_back(".align 4;"); |
| endfunction |
| |
| // Generate the user stack section |
| virtual function void gen_stack_section(); |
| instr_stream.push_back(".pushsection .user_stack,\"aw\",@progbits;"); |
| instr_stream.push_back(".align 12"); |
| instr_stream.push_back("_user_stack_start:"); |
| instr_stream.push_back($sformatf(".rept %0d", cfg.stack_len - 1)); |
| instr_stream.push_back($sformatf(".%0dbyte 0x0", XLEN/8)); |
| instr_stream.push_back(".endr"); |
| instr_stream.push_back("_user_stack_end:"); |
| instr_stream.push_back($sformatf(".%0dbyte 0x0", XLEN/8)); |
| instr_stream.push_back(".popsection;"); |
| endfunction |
| |
| // The kernal stack is used to save user program context before executing exception handling |
| virtual function void gen_kernel_stack_section(); |
| instr_stream.push_back(".pushsection .kernel_stack,\"aw\",@progbits;"); |
| instr_stream.push_back(".align 12"); |
| instr_stream.push_back("_kernel_stack_start:"); |
| instr_stream.push_back($sformatf(".rept %0d", cfg.kernel_stack_len - 1)); |
| instr_stream.push_back($sformatf(".%0dbyte 0x0", XLEN/8)); |
| instr_stream.push_back(".endr"); |
| instr_stream.push_back("_kernel_stack_end:"); |
| instr_stream.push_back($sformatf(".%0dbyte 0x0", XLEN/8)); |
| instr_stream.push_back(".popsection;"); |
| endfunction |
| |
| virtual function void gen_init_section(); |
| string str; |
| str = format_string("_init:", LABEL_STR_LEN); |
| instr_stream.push_back(str); |
| // Init stack pointer to point to the end of the user stack |
| str = {indent, $sformatf("la x%0d, _user_stack_end", cfg.sp)}; |
| instr_stream.push_back(str); |
| if (cfg.enable_floating_point) begin |
| init_floating_point_gpr(); |
| end |
| core_is_initialized(); |
| gen_dummy_csr_write(); |
| endfunction |
| |
| // Setup MISA based on supported extensions |
| virtual function void setup_misa(); |
| bit [XLEN-1:0] misa; |
| misa[XLEN-1:XLEN-2] = (XLEN == 32) ? 2'b01 : |
| (XLEN == 64) ? 2'b10 : 2'b11; |
| if (cfg.check_misa_init_val) begin |
| instr_stream.push_back({indent, "csrr x15, misa"}); |
| end |
| foreach (supported_isa[i]) begin |
| case (supported_isa[i]) inside |
| RV32C, RV64C, RV128C : misa[MISA_EXT_C] = 1'b1; |
| RV32I, RV64I, RV128I : misa[MISA_EXT_I] = 1'b1; |
| RV32M, RV64M : misa[MISA_EXT_M] = 1'b1; |
| RV32A, RV64A : misa[MISA_EXT_A] = 1'b1; |
| RV32F, RV64F, RV32FC : misa[MISA_EXT_F] = 1'b1; |
| RV32D, RV64D, RV32DC : misa[MISA_EXT_D] = 1'b1; |
| default : `uvm_fatal(`gfn, $sformatf("%0s is not yet supported", |
| supported_isa[i].name())) |
| endcase |
| end |
| if (SUPERVISOR_MODE inside {supported_privileged_mode}) begin |
| misa[MISA_EXT_S] = 1'b1; |
| end |
| instr_stream.push_back({indent, $sformatf("li x%0d, 0x%0x",cfg.gpr[0], misa)}); |
| instr_stream.push_back({indent, $sformatf("csrw misa, x%0d",cfg.gpr[0])}); |
| endfunction |
| |
| // Write to the signature_addr with values to indicate to the core testbench |
| // that is safe to start sending interrupt and debug stimulus |
| virtual function void core_is_initialized(); |
| string instr[$]; |
| if (cfg.require_signature_addr) begin |
| if (cfg.signature_addr != 32'hdead_beef) begin |
| gen_signature_handshake(instr, CORE_STATUS, INITIALIZED); |
| format_section(instr); |
| instr_stream = {instr_stream, instr}; |
| end else begin |
| `uvm_fatal(`gfn, "The signature_addr is not properly configured!") |
| end |
| end |
| endfunction |
| |
| // Generate some dummy writes to xSTATUS/xIE at the beginning of the test to check |
| // repeated writes to these CSRs. |
| virtual function void gen_dummy_csr_write(); |
| string instr[$]; |
| case (cfg.init_privileged_mode) |
| MACHINE_MODE: begin |
| instr.push_back($sformatf("csrr x%0d, 0x%0x", cfg.gpr[0], MSTATUS)); |
| instr.push_back($sformatf("csrr x%0d, 0x%0x", cfg.gpr[1], MIE)); |
| instr.push_back($sformatf("csrw 0x%0x, x%0d", MSTATUS, cfg.gpr[0])); |
| instr.push_back($sformatf("csrw 0x%0x, x%0d", MIE, cfg.gpr[1])); |
| end |
| SUPERVISOR_MODE: begin |
| instr.push_back($sformatf("csrr x%0d, 0x%0x", cfg.gpr[0], SSTATUS)); |
| instr.push_back($sformatf("csrr x%0d, 0x%0x", cfg.gpr[1], SIE)); |
| instr.push_back($sformatf("csrw 0x%0x, x%0d", SSTATUS, cfg.gpr[0])); |
| instr.push_back($sformatf("csrw 0x%0x, x%0d", SIE, cfg.gpr[1])); |
| end |
| USER_MODE: begin |
| instr.push_back($sformatf("csrr x%0d, 0x%0x", cfg.gpr[0], USTATUS)); |
| instr.push_back($sformatf("csrr x%0d, 0x%0x", cfg.gpr[1], UIE)); |
| instr.push_back($sformatf("csrw 0x%0x, x%0d", USTATUS, cfg.gpr[0])); |
| instr.push_back($sformatf("csrw 0x%0x, x%0d", UIE, cfg.gpr[1])); |
| end |
| default: begin |
| `uvm_fatal(`gfn, "Unsupported boot mode") |
| end |
| endcase |
| format_section(instr); |
| instr_stream = {instr_stream, instr}; |
| endfunction |
| |
| // Initialize general purpose registers with random value |
| virtual function void init_gpr(); |
| string str; |
| bit [DATA_WIDTH-1:0] reg_val; |
| // Init general purpose registers with random values |
| for(int i = 0; i < 32; i++) begin |
| `DV_CHECK_STD_RANDOMIZE_WITH_FATAL(reg_val, |
| reg_val dist { |
| 'h0 :/ 1, |
| 'h8000_0000 :/ 1, |
| ['h1 : 'hF] :/ 1, |
| ['h10 : 'hEFFF_FFFF] :/ 1, |
| ['hF000_0000 : 'hFFFF_FFFF] :/ 1 |
| };) |
| str = $sformatf("%0sli x%0d, 0x%0x", indent, i, reg_val); |
| instr_stream.push_back(str); |
| end |
| endfunction |
| |
| // Initialize floating point general purpose registers |
| virtual function void init_floating_point_gpr(); |
| int int_gpr; |
| string str; |
| // TODO: Initialize floating point GPR with more interesting numbers |
| for(int i = 0; i < 32; i++) begin |
| int_gpr = $urandom_range(0, 31); |
| // Use a random integer GPR to initialize floating point GPR |
| if (RV64F inside {supported_isa}) begin |
| str = $sformatf("%0sfcvt.d.l f%0d, x%0d", indent, i, int_gpr); |
| end else begin |
| str = $sformatf("%0sfcvt.s.w f%0d, x%0d", indent, i, int_gpr); |
| end |
| instr_stream.push_back(str); |
| end |
| // Initialize rounding mode of FCSR |
| str = $sformatf("%0sfsrmi %0d", indent, cfg.fcsr_rm); |
| instr_stream.push_back(str); |
| endfunction |
| |
| // Generate "test_done" section, test is finished by an ECALL instruction |
| // The ECALL trap handler will handle the clean up procedure before finishing the test. |
| virtual function void gen_test_done(); |
| string str = format_string("test_done:", LABEL_STR_LEN); |
| instr_stream.push_back(str); |
| instr_stream.push_back({indent, "li gp, 1"}); |
| if (cfg.bare_program_mode) begin |
| instr_stream.push_back({indent, "j write_tohost"}); |
| end else begin |
| instr_stream.push_back({indent, "ecall"}); |
| end |
| endfunction |
| |
| // Dump all GPR to the starting point of the program |
| // TB can check the GPR value for this memory location to compare with expected value generated |
| // by the ISA simulator. If the processor doesn't have a good tracer unit, it might not be |
| // possible to compare the GPR value after each instruction execution. |
| virtual function void gen_register_dump(); |
| string str; |
| // Load base address |
| str = {indent, $sformatf("la x%0d, _start", cfg.gpr[0])}; |
| instr_stream.push_back(str); |
| // Generate sw/sd instructions |
| for(int i = 0; i < 32; i++) begin |
| if(XLEN == 64) begin |
| str = {indent, $sformatf("sd x%0d, %0d(x%0d)", i, i*(XLEN/8), cfg.gpr[0])}; |
| end else begin |
| str = {indent, $sformatf("sw x%0d, %0d(x%0d)", i, i*(XLEN/8), cfg.gpr[0])}; |
| end |
| instr_stream.push_back(str); |
| end |
| endfunction |
| |
| //--------------------------------------------------------------------------------------- |
| // Privileged mode entering routine |
| //--------------------------------------------------------------------------------------- |
| |
| virtual function void pre_enter_privileged_mode(); |
| string instr[]; |
| // Setup kerenal stack pointer |
| gen_section("kernel_sp", {$sformatf("la x%0d, _kernel_stack_end", cfg.tp)}); |
| // Setup interrupt and exception delegation |
| if(!cfg.no_delegation && (cfg.init_privileged_mode != MACHINE_MODE)) begin |
| gen_delegation(); |
| end |
| // Setup trap vector register |
| trap_vector_init(); |
| // Initialize PTE (link page table based on their real physical address) |
| if(cfg.virtual_addr_translation_on) begin |
| page_table_list.process_page_table(instr); |
| gen_section("process_pt", instr); |
| end |
| // Setup mepc register, jump to init entry |
| setup_epc(); |
| endfunction |
| |
| virtual function void gen_privileged_mode_switch_routine(); |
| privil_seq = riscv_privileged_common_seq::type_id::create("privil_seq"); |
| foreach(riscv_instr_pkg::supported_privileged_mode[i]) begin |
| string instr[$]; |
| string csr_handshake[$]; |
| string ret_instr; |
| if(riscv_instr_pkg::supported_privileged_mode[i] < cfg.init_privileged_mode) continue; |
| `uvm_info(`gfn, $sformatf("Generating privileged mode routing for %0s", |
| riscv_instr_pkg::supported_privileged_mode[i].name()), UVM_LOW) |
| // Enter privileged mode |
| privil_seq.cfg = cfg; |
| `DV_CHECK_RANDOMIZE_FATAL(privil_seq) |
| privil_seq.enter_privileged_mode(riscv_instr_pkg::supported_privileged_mode[i], instr); |
| if (cfg.require_signature_addr) begin |
| ret_instr = instr.pop_back(); |
| // Want to write the main system CSRs to the testbench before indicating that initialization |
| // is complete, for any initial state analysis |
| case(riscv_instr_pkg::supported_privileged_mode[i]) |
| MACHINE_MODE: begin |
| gen_signature_handshake(.instr(csr_handshake), .signature_type(WRITE_CSR), .csr(MSTATUS)); |
| gen_signature_handshake(.instr(csr_handshake), .signature_type(WRITE_CSR), .csr(MIE)); |
| end |
| SUPERVISOR_MODE: begin |
| gen_signature_handshake(.instr(csr_handshake), .signature_type(WRITE_CSR), .csr(SSTATUS)); |
| gen_signature_handshake(.instr(csr_handshake), .signature_type(WRITE_CSR), .csr(SIE)); |
| end |
| USER_MODE: begin |
| gen_signature_handshake(.instr(csr_handshake), .signature_type(WRITE_CSR), .csr(USTATUS)); |
| gen_signature_handshake(.instr(csr_handshake), .signature_type(WRITE_CSR), .csr(UIE)); |
| end |
| endcase |
| format_section(csr_handshake); |
| instr = {instr, csr_handshake, ret_instr}; |
| end |
| instr_stream = {instr_stream, instr}; |
| end |
| endfunction |
| |
| // Setup EPC before entering target privileged mode |
| virtual function void setup_epc(); |
| string instr[]; |
| string mode_name; |
| instr = {$sformatf("la x%0d, _init", cfg.gpr[0])}; |
| if(cfg.virtual_addr_translation_on) begin |
| // For supervisor and user mode, use virtual address instead of physical address. |
| // Virtual address starts from address 0x0, here only the lower 12 bits are kept |
| // as virtual address offset. |
| instr = {instr, |
| $sformatf("slli x%0d, x%0d, %0d", cfg.gpr[0], cfg.gpr[0], XLEN - 12), |
| $sformatf("srli x%0d, x%0d, %0d", cfg.gpr[0], cfg.gpr[0], XLEN - 12)}; |
| end |
| mode_name = cfg.init_privileged_mode.name(); |
| instr = {instr, |
| $sformatf("csrw mepc, x%0d", cfg.gpr[0]), |
| $sformatf("j init_%0s", mode_name.tolower()) |
| }; |
| gen_section("mepc_setup", instr); |
| endfunction |
| |
| //--------------------------------------------------------------------------------------- |
| // Privileged CSR setup for interrupt and exception handling and delegation |
| //--------------------------------------------------------------------------------------- |
| |
| // Interrupt and exception delegation setting. |
| // The lower level exception and interrupt can be delegated to higher level handler. |
| virtual function void gen_delegation(); |
| gen_delegation_instr(MEDELEG, MIDELEG, |
| cfg.m_mode_exception_delegation, |
| cfg.m_mode_interrupt_delegation); |
| if(riscv_instr_pkg::support_umode_trap) begin |
| gen_delegation_instr(SEDELEG, SIDELEG, |
| cfg.s_mode_exception_delegation, |
| cfg.s_mode_interrupt_delegation); |
| end |
| endfunction |
| |
| virtual function void gen_delegation_instr(privileged_reg_t edeleg, |
| privileged_reg_t ideleg, |
| bit edeleg_enable[exception_cause_t], |
| bit ideleg_enable[interrupt_cause_t]); |
| bit [XLEN-1:0] deleg_val; |
| string section_name; |
| string instr[]; |
| // Exception delegation setup |
| foreach(edeleg_enable[cause]) begin |
| if(edeleg_enable[cause]) begin |
| deleg_val = deleg_val | (1 << int'(cause)); |
| end |
| end |
| instr = {$sformatf("li x%0d, 0x%0x", cfg.gpr[0], deleg_val), |
| $sformatf("csrw 0x%0x, x%0d # %0s", edeleg, cfg.gpr[0], edeleg.name())}; |
| // Interrupt delegation setup |
| deleg_val = '0; |
| foreach(ideleg_enable[cause]) begin |
| if(ideleg_enable[cause]) begin |
| deleg_val = deleg_val | (1 << int'(cause)); |
| end |
| end |
| instr = {instr, |
| $sformatf("li x%0d, 0x%0x", cfg.gpr[0], deleg_val), |
| $sformatf("csrw 0x%0x, x%0d # %0s", ideleg, cfg.gpr[0], ideleg.name())}; |
| section_name = edeleg.name(); |
| section_name = section_name.tolower(); |
| gen_section($sformatf("%0s_setup", section_name), instr); |
| endfunction |
| |
| // Setup trap vector - MTVEC, STVEC, UTVEC |
| virtual function void trap_vector_init(); |
| string instr[]; |
| privileged_reg_t trap_vec_reg; |
| string tvec_name; |
| foreach(riscv_instr_pkg::supported_privileged_mode[i]) begin |
| case(riscv_instr_pkg::supported_privileged_mode[i]) |
| MACHINE_MODE: trap_vec_reg = MTVEC; |
| SUPERVISOR_MODE: trap_vec_reg = STVEC; |
| USER_MODE: trap_vec_reg = UTVEC; |
| endcase |
| // Skip utvec init if trap delegation to u_mode is not supported |
| if ((riscv_instr_pkg::supported_privileged_mode[i] == USER_MODE) && |
| !riscv_instr_pkg::support_umode_trap) continue; |
| if (riscv_instr_pkg::supported_privileged_mode[i] < cfg.init_privileged_mode) continue; |
| tvec_name = trap_vec_reg.name(); |
| instr = {instr, $sformatf("la x%0d, %0s_handler", cfg.gpr[0], tvec_name.tolower())}; |
| if (SATP_MODE != BARE && riscv_instr_pkg::supported_privileged_mode[i] != MACHINE_MODE) begin |
| // For supervisor and user mode, use virtual address instead of physical address. |
| // Virtual address starts from address 0x0, here only the lower 20 bits are kept |
| // as virtual address offset. |
| instr = {instr, |
| $sformatf("slli x%0d, x%0d, %0d", cfg.gpr[0], cfg.gpr[0], XLEN - 20), |
| $sformatf("srli x%0d, x%0d, %0d", cfg.gpr[0], cfg.gpr[0], XLEN - 20)}; |
| end |
| instr = {instr, $sformatf("ori x%0d, x%0d, %0d", cfg.gpr[0], cfg.gpr[0], cfg.mtvec_mode)}; |
| instr = {instr, $sformatf("csrw 0x%0x, x%0d # %0s", |
| trap_vec_reg, cfg.gpr[0], trap_vec_reg.name())}; |
| end |
| gen_section("trap_vec_init", instr); |
| endfunction |
| |
| //--------------------------------------------------------------------------------------- |
| // Exception handling routine |
| //--------------------------------------------------------------------------------------- |
| |
| // Trap handling routine |
| virtual function void gen_all_trap_handler(); |
| string instr[$]; |
| foreach(riscv_instr_pkg::supported_privileged_mode[i]) begin |
| if(riscv_instr_pkg::supported_privileged_mode[i] < cfg.init_privileged_mode) continue; |
| case(riscv_instr_pkg::supported_privileged_mode[i]) |
| MACHINE_MODE: |
| gen_trap_handler_section("m", MCAUSE, MTVEC, MTVAL, MEPC, MSCRATCH, MSTATUS, MIE, MIP); |
| SUPERVISOR_MODE: |
| gen_trap_handler_section("s", SCAUSE, STVEC, STVAL, SEPC, SSCRATCH, SSTATUS, SIE, SIP); |
| USER_MODE: |
| if(riscv_instr_pkg::support_umode_trap) |
| gen_trap_handler_section("u", UCAUSE, UTVEC, UTVAL, UEPC, USCRATCH, USTATUS, UIE, UIP); |
| endcase |
| end |
| // Ebreak handler |
| gen_ebreak_handler(); |
| // Ecall handler |
| gen_ecall_handler(); |
| // Illegal instruction handler |
| gen_illegal_instr_handler(); |
| // Instruction fault handler |
| gen_instr_fault_handler(); |
| // Load fault handler |
| gen_load_fault_handler(); |
| // Store fault handler |
| gen_store_fault_handler(); |
| // Generate page table fault handling routine |
| // Page table fault is always handled in machine mode, as virtual address translation may be |
| // broken when page fault happens. |
| gen_signature_handshake(instr, CORE_STATUS, HANDLING_EXCEPTION); |
| if(page_table_list != null) begin |
| page_table_list.gen_page_fault_handling_routine(instr); |
| end else begin |
| instr.push_back("nop"); |
| end |
| gen_section("pt_fault_handler", instr); |
| endfunction |
| |
| // Generate the interrupt and trap handler for different privileged mode. |
| // The trap handler checks the xCAUSE to determine the type of the exception and jumps to |
| // corresponding exeception handling routine. |
| virtual function void gen_trap_handler_section(string mode, |
| privileged_reg_t cause, privileged_reg_t tvec, |
| privileged_reg_t tval, privileged_reg_t epc, |
| privileged_reg_t scratch, privileged_reg_t status, |
| privileged_reg_t ie, privileged_reg_t ip); |
| bit is_interrupt = 'b1; |
| string tvec_name; |
| string instr[$]; |
| if (cfg.mtvec_mode == VECTORED) begin |
| gen_interrupt_vector_table(mode, status, cause, ie, ip, scratch, instr); |
| end else begin |
| // Push user mode GPR to kernel stack before executing exception handling, this is to avoid |
| // exception handling routine modify user program state unexpectedly |
| push_gpr_to_kernel_stack(status, scratch, cfg.mstatus_mprv, cfg.sp, cfg.tp, instr); |
| // Checking xStatus can be optional if ISS (like spike) has different implementation of certain |
| // fields compared with the RTL processor. |
| if (cfg.check_xstatus) begin |
| instr = {instr, $sformatf("csrr x%0d, 0x%0x # %0s", cfg.gpr[0], status, status.name())}; |
| end |
| instr = {instr, |
| // Use scratch CSR to save a GPR value |
| // Check if the exception is caused by an interrupt, if yes, jump to interrupt handler |
| // Interrupt is indicated by xCause[XLEN-1] |
| $sformatf("csrr x%0d, 0x%0x # %0s", cfg.gpr[0], cause, cause.name()), |
| $sformatf("srli x%0d, x%0d, %0d", cfg.gpr[0], cfg.gpr[0], XLEN-1), |
| $sformatf("bne x%0d, x0, %0smode_intr_handler", cfg.gpr[0], mode)}; |
| end |
| // The trap handler will occupy one 4KB page, it will be allocated one entry in the page table |
| // with a specific privileged mode. |
| instr_stream.push_back(".align 12"); |
| tvec_name = tvec.name(); |
| gen_section($sformatf("%0s_handler", tvec_name.tolower()), instr); |
| // Exception handler |
| instr = {}; |
| if (cfg.mtvec_mode == VECTORED) begin |
| push_gpr_to_kernel_stack(status, scratch, cfg.mstatus_mprv, cfg.sp, cfg.tp, instr); |
| end |
| gen_signature_handshake(instr, CORE_STATUS, HANDLING_EXCEPTION); |
| instr = {instr, |
| // The trap is caused by an exception, read back xCAUSE, xEPC to see if these |
| // CSR values are set properly. The checking is done by comparing against the log |
| // generated by ISA simulator (spike). |
| $sformatf("csrr x%0d, 0x%0x # %0s", cfg.gpr[0], epc, epc.name()), |
| $sformatf("csrr x%0d, 0x%0x # %0s", cfg.gpr[0], cause, cause.name()), |
| // Breakpoint |
| $sformatf("li x%0d, 0x%0x # BREAKPOINT", cfg.gpr[1], BREAKPOINT), |
| $sformatf("beq x%0d, x%0d, ebreak_handler", cfg.gpr[0], cfg.gpr[1]), |
| // Check if it's an ECALL exception. Jump to ECALL exception handler |
| $sformatf("li x%0d, 0x%0x # ECALL_UMODE", cfg.gpr[1], ECALL_UMODE), |
| $sformatf("beq x%0d, x%0d, ecall_handler", cfg.gpr[0], cfg.gpr[1]), |
| $sformatf("li x%0d, 0x%0x # ECALL_SMODE", cfg.gpr[1], ECALL_SMODE), |
| $sformatf("beq x%0d, x%0d, ecall_handler", cfg.gpr[0], cfg.gpr[1]), |
| $sformatf("li x%0d, 0x%0x # ECALL_MMODE", cfg.gpr[1], ECALL_MMODE), |
| $sformatf("beq x%0d, x%0d, ecall_handler", cfg.gpr[0], cfg.gpr[1]), |
| // Page table fault or access fault conditions |
| $sformatf("li x%0d, 0x%0x", cfg.gpr[1], INSTRUCTION_ACCESS_FAULT), |
| $sformatf("beq x%0d, x%0d, instr_fault_handler", cfg.gpr[0], cfg.gpr[1]), |
| $sformatf("li x%0d, 0x%0x", cfg.gpr[1], LOAD_ACCESS_FAULT), |
| $sformatf("beq x%0d, x%0d, load_fault_handler", cfg.gpr[0], cfg.gpr[1]), |
| $sformatf("li x%0d, 0x%0x", cfg.gpr[1], STORE_AMO_ACCESS_FAULT), |
| $sformatf("beq x%0d, x%0d, store_fault_handler", cfg.gpr[0], cfg.gpr[1]), |
| $sformatf("li x%0d, 0x%0x", cfg.gpr[1], INSTRUCTION_PAGE_FAULT), |
| $sformatf("beq x%0d, x%0d, pt_fault_handler", cfg.gpr[0], cfg.gpr[1]), |
| $sformatf("li x%0d, 0x%0x", cfg.gpr[1], LOAD_PAGE_FAULT), |
| $sformatf("beq x%0d, x%0d, pt_fault_handler", cfg.gpr[0], cfg.gpr[1]), |
| $sformatf("li x%0d, 0x%0x", cfg.gpr[1], STORE_AMO_PAGE_FAULT), |
| $sformatf("beq x%0d, x%0d, pt_fault_handler", cfg.gpr[0], cfg.gpr[1]), |
| // Illegal instruction exception |
| $sformatf("li x%0d, 0x%0x # ILLEGAL_INSTRUCTION", cfg.gpr[1], ILLEGAL_INSTRUCTION), |
| $sformatf("beq x%0d, x%0d, illegal_instr_handler", cfg.gpr[0], cfg.gpr[1]), |
| // Skip checking tval for illegal instruction as it's implementation specific |
| $sformatf("csrr x%0d, 0x%0x # %0s", cfg.gpr[1], tval, tval.name()), |
| "1: jal x1, test_done " |
| }; |
| gen_section($sformatf("%0smode_exception_handler", mode), instr); |
| endfunction |
| |
| // Generate for interrupt vector table |
| virtual function void gen_interrupt_vector_table(string mode, |
| privileged_reg_t status, |
| privileged_reg_t cause, |
| privileged_reg_t ie, |
| privileged_reg_t ip, |
| privileged_reg_t scratch, |
| ref string instr[$]); |
| |
| // In vector mode, the BASE address is shared between interrupt 0 and exception handling. |
| // When vectored interrupts are enabled, interrupt cause 0, which corresponds to user-mode |
| // software interrupts, are vectored to the same location as synchronous exceptions. This |
| // ambiguity does not arise in practice, since user-mode software interrupts are either |
| // disabled or delegated |
| instr = {instr, ".option norvc;", |
| $sformatf("j %0smode_exception_handler", mode)}; |
| // Redirect the interrupt to the corresponding interrupt handler |
| for (int i = 1; i < max_interrupt_vector_num; i++) begin |
| instr.push_back($sformatf("j %0smode_intr_vector_%0d", mode, i)); |
| end |
| if (!cfg.disable_compressed_instr) begin |
| instr = {instr, ".option rvc;"}; |
| end |
| for (int i = 1; i < max_interrupt_vector_num; i++) begin |
| string intr_handler[$]; |
| push_gpr_to_kernel_stack(status, scratch, cfg.mstatus_mprv, cfg.sp, cfg.tp, intr_handler); |
| gen_signature_handshake(.instr(intr_handler), .signature_type(CORE_STATUS), .core_status(HANDLING_IRQ)); |
| intr_handler = {intr_handler, |
| $sformatf("csrr x%0d, 0x%0x # %0s", cfg.gpr[0], cause, cause.name()), |
| // Terminate the test if xCause[31] != 0 (indicating exception) |
| $sformatf("srli x%0d, x%0d, 0x%0x", cfg.gpr[0], cfg.gpr[0], XLEN-1), |
| $sformatf("beqz x%0d, 1f", cfg.gpr[0])}; |
| gen_signature_handshake(.instr(intr_handler), .signature_type(WRITE_CSR), .csr(status)); |
| gen_signature_handshake(.instr(intr_handler), .signature_type(WRITE_CSR), .csr(cause)); |
| gen_signature_handshake(.instr(intr_handler), .signature_type(WRITE_CSR), .csr(ie)); |
| gen_signature_handshake(.instr(intr_handler), .signature_type(WRITE_CSR), .csr(ip)); |
| // Jump to commmon interrupt handling routine |
| intr_handler = {intr_handler, |
| $sformatf("j %0smode_intr_handler", mode), |
| "1: j test_done"}; |
| gen_section($sformatf("%0smode_intr_vector_%0d", mode, i), intr_handler); |
| end |
| endfunction |
| |
| // ECALL trap handler |
| // It does some clean up like dump GPRs before communicating with host to terminate the test. |
| // User can extend this function if some custom clean up routine is needed. |
| virtual function void gen_ecall_handler(); |
| string str; |
| str = format_string("ecall_handler:", LABEL_STR_LEN); |
| instr_stream.push_back(str); |
| dump_perf_stats(); |
| gen_register_dump(); |
| str = format_string(" ", LABEL_STR_LEN); |
| str = {str, "j write_tohost"}; |
| instr_stream.push_back(str); |
| endfunction |
| |
| // Ebreak trap handler |
| // When breakpoint exception happens, epc will be written with ebreak instruction |
| // itself. Add epc by 4 and resume execution. |
| // Note the breakpoint could be triggered by a C.EBREAK instruction, the generated program |
| // guarantees that epc + 4 is a valid instruction boundary |
| // TODO: Support random operations in debug mode |
| // TODO: Support ebreak exception delegation |
| // TODO: handshake the correct Xcause CSR based on delegation privil. mode |
| virtual function void gen_ebreak_handler(); |
| string instr[$]; |
| gen_signature_handshake(instr, CORE_STATUS, EBREAK_EXCEPTION); |
| gen_signature_handshake(.instr(instr), .signature_type(WRITE_CSR), .csr(MCAUSE)); |
| instr = {instr, |
| $sformatf("csrr x%0d, mepc", cfg.gpr[0]), |
| $sformatf("addi x%0d, x%0d, 4", cfg.gpr[0], cfg.gpr[0]), |
| $sformatf("csrw mepc, x%0d", cfg.gpr[0]) |
| }; |
| pop_gpr_from_kernel_stack(MSTATUS, MSCRATCH, cfg.mstatus_mprv, cfg.sp, cfg.tp, instr); |
| instr.push_back("mret"); |
| gen_section("ebreak_handler", instr); |
| endfunction |
| |
| // Illegal instruction handler |
| // Note: Save the illegal instruction to MTVAL is optional in the spec, and mepc could be |
| // a virtual address that cannot be used in machine mode handler. As a result, there's no way to |
| // know the illegal instruction is compressed or not. This hanlder just simply adds the PC by |
| // 4 and resumes execution. The way that the illegal instruction is injected guarantees that |
| // PC + 4 is a valid instruction boundary. |
| // TODO: handshake the corret Xcause CSR based on delegation setup |
| virtual function void gen_illegal_instr_handler(); |
| string instr[$]; |
| gen_signature_handshake(instr, CORE_STATUS, ILLEGAL_INSTR_EXCEPTION); |
| gen_signature_handshake(.instr(instr), .signature_type(WRITE_CSR), .csr(MCAUSE)); |
| instr = {instr, |
| $sformatf("csrr x%0d, mepc", cfg.gpr[0]), |
| $sformatf("addi x%0d, x%0d, 4", cfg.gpr[0], cfg.gpr[0]), |
| $sformatf("csrw mepc, x%0d", cfg.gpr[0]) |
| }; |
| pop_gpr_from_kernel_stack(MSTATUS, MSCRATCH, cfg.mstatus_mprv, cfg.sp, cfg.tp, instr); |
| instr.push_back("mret"); |
| gen_section("illegal_instr_handler", instr); |
| endfunction |
| |
| // TODO: handshake correct csr based on delegation |
| virtual function void gen_instr_fault_handler(); |
| string instr[$]; |
| gen_signature_handshake(instr, CORE_STATUS, INSTR_FAULT_EXCEPTION); |
| gen_signature_handshake(.instr(instr), .signature_type(WRITE_CSR), .csr(MCAUSE)); |
| pop_gpr_from_kernel_stack(MSTATUS, MSCRATCH, cfg.mstatus_mprv, cfg.sp, cfg.tp, instr); |
| instr.push_back("mret"); |
| gen_section("instr_fault_handler", instr); |
| endfunction |
| |
| // TODO: handshake correct csr based on delegation |
| virtual function void gen_load_fault_handler(); |
| string instr[$]; |
| gen_signature_handshake(instr, CORE_STATUS, LOAD_FAULT_EXCEPTION); |
| gen_signature_handshake(.instr(instr), .signature_type(WRITE_CSR), .csr(MCAUSE)); |
| pop_gpr_from_kernel_stack(MSTATUS, MSCRATCH, cfg.mstatus_mprv, cfg.sp, cfg.tp, instr); |
| instr.push_back("mret"); |
| gen_section("load_fault_handler", instr); |
| endfunction |
| |
| // TODO: handshake correct csr based on delegation |
| virtual function void gen_store_fault_handler(); |
| string instr[$]; |
| gen_signature_handshake(instr, CORE_STATUS, STORE_FAULT_EXCEPTION); |
| gen_signature_handshake(.instr(instr), .signature_type(WRITE_CSR), .csr(MCAUSE)); |
| pop_gpr_from_kernel_stack(MSTATUS, MSCRATCH, cfg.mstatus_mprv, cfg.sp, cfg.tp, instr); |
| instr.push_back("mret"); |
| gen_section("store_fault_handler", instr); |
| endfunction |
| |
| //--------------------------------------------------------------------------------------- |
| // Page table setup |
| //--------------------------------------------------------------------------------------- |
| |
| // Create page table if virtual address translation is supported. |
| // The page is created based on the address translation mode - SV32, SV39, SV48 |
| // Right now only the lowest level 4KB page table is configured as leaf page table entry (PTE), |
| // all the other super pages are link PTE. |
| virtual function void create_page_table(); |
| string instr[]; |
| if(cfg.virtual_addr_translation_on) begin |
| page_table_list = riscv_page_table_list#(SATP_MODE):: |
| type_id::create("page_table_list"); |
| page_table_list.cfg = cfg; |
| page_table_list.create_page_table_list(); |
| page_table_list.enable_exception = cfg.enable_page_table_exception; |
| `uvm_info(`gfn, $sformatf("Randomizing page tables, totally %0d page tables, mode = %0s", |
| page_table_list.page_table.size(), cfg.init_privileged_mode.name()), UVM_LOW) |
| page_table_list.privileged_mode = cfg.init_privileged_mode; |
| `DV_CHECK_RANDOMIZE_FATAL(page_table_list); |
| page_table_list.randomize_page_table(); |
| `uvm_info(`gfn, "Finished creating page tables", UVM_LOW) |
| end |
| endfunction |
| |
| // Generate the page table section of the program |
| // The page table is generated as a group of continuous 4KB data sections. |
| virtual function void gen_page_table_section(); |
| string page_table_section[$]; |
| if(page_table_list != null) begin |
| instr_stream.push_back(".pushsection .page_table,\"aw\",@progbits;"); |
| foreach(page_table_list.page_table[i]) begin |
| page_table_list.page_table[i].gen_page_table_section(page_table_section); |
| instr_stream = {instr_stream, page_table_section}; |
| end |
| instr_stream.push_back(".popsection;"); |
| end |
| endfunction |
| |
| // Only extend this function if the core utilizes a PLIC for handling interrupts |
| // In this case, the core will write to a specific location as the response to the interrupt, and |
| // external PLIC unit can detect this response and process the interrupt clean up accordingly. |
| virtual function void gen_plic_section(ref string interrupt_handler_instr[$]); |
| // Utilize the memory mapped handshake scheme to signal the testbench that the interrupt |
| // handling has been completed and we are about to xRET out of the handler |
| gen_signature_handshake(.instr(interrupt_handler_instr), .signature_type(CORE_STATUS), |
| .core_status(FINISHED_IRQ)); |
| endfunction |
| |
| // Interrupt handler routine |
| virtual function void gen_interrupt_handler_section(privileged_mode_t mode); |
| string mode_prefix; |
| string ls_unit; |
| privileged_reg_t status, ip, ie, scratch; |
| string interrupt_handler_instr[$]; |
| ls_unit = (XLEN == 32) ? "w" : "d"; |
| if(mode < cfg.init_privileged_mode) return; |
| case(mode) |
| MACHINE_MODE: begin |
| mode_prefix = "m"; |
| status = MSTATUS; |
| ip = MIP; |
| ie = MIE; |
| scratch = MSCRATCH; |
| end |
| SUPERVISOR_MODE: begin |
| mode_prefix = "s"; |
| status = SSTATUS; |
| ip = SIP; |
| ie = SIE; |
| scratch = SSCRATCH; |
| end |
| USER_MODE: begin |
| mode_prefix = "u"; |
| status = USTATUS; |
| ip = UIP; |
| ie = UIE; |
| scratch = USCRATCH; |
| end |
| default: `uvm_fatal(get_full_name(), $sformatf("Unsupported mode: %0s", mode.name())) |
| endcase |
| // Read back interrupt related privileged CSR |
| // The value of these CSR are checked by comparing with spike simulation result. |
| interrupt_handler_instr = { |
| interrupt_handler_instr, |
| $sformatf("csrr x%0d, 0x%0x # %0s;", cfg.gpr[0], status, status.name()), |
| $sformatf("csrr x%0d, 0x%0x # %0s;", cfg.gpr[0], ie, ie.name()), |
| $sformatf("csrr x%0d, 0x%0x # %0s;", cfg.gpr[0], ip, ip.name()), |
| // Clean all the pending interrupt |
| $sformatf("csrrc x%0d, 0x%0x, x%0d # %0s;", |
| cfg.gpr[0], ip, cfg.gpr[0], ip.name()) |
| }; |
| gen_plic_section(interrupt_handler_instr); |
| // Restore user mode GPR value from kernel stack before return |
| pop_gpr_from_kernel_stack(status, scratch, cfg.mstatus_mprv, |
| cfg.sp, cfg.tp, interrupt_handler_instr); |
| interrupt_handler_instr = {interrupt_handler_instr, |
| $sformatf("%0sret;", mode_prefix) |
| }; |
| // The interrupt handler will use one 4KB page |
| instr_stream.push_back(".align 12"); |
| gen_section($sformatf("%0smode_intr_handler", mode_prefix), interrupt_handler_instr); |
| endfunction |
| |
| //--------------------------------------------------------------------------------------- |
| // Helper functions |
| //--------------------------------------------------------------------------------------- |
| |
| // Format a code section, without generating it |
| virtual function void format_section(ref string instr[$]); |
| string prefix = format_string(" ", LABEL_STR_LEN); |
| foreach(instr[i]) begin |
| instr[i] = {prefix, instr[i]}; |
| end |
| endfunction |
| |
| // Generate a code section |
| virtual function void gen_section(string label, string instr[$]); |
| string str; |
| if(label != "") begin |
| str = format_string($sformatf("%0s:", label), LABEL_STR_LEN); |
| instr_stream.push_back(str); |
| end |
| foreach(instr[i]) begin |
| str = {indent, instr[i]}; |
| instr_stream.push_back(str); |
| end |
| instr_stream.push_back(""); |
| endfunction |
| |
| // Dump performance CSRs if applicable |
| virtual function void dump_perf_stats(); |
| foreach(implemented_csr[i]) begin |
| if (implemented_csr[i] inside {[MCYCLE:MHPMCOUNTER31H]}) begin |
| gen_signature_handshake(.instr(instr_stream), .signature_type(WRITE_CSR), .csr(implemented_csr[i])); |
| end |
| end |
| endfunction |
| |
| // Write the generated program to a file |
| function void gen_test_file(string test_name); |
| int fd; |
| fd = $fopen(test_name,"w"); |
| foreach(instr_stream[i]) begin |
| $fwrite(fd, {instr_stream[i],"\n"}); |
| end |
| $fclose(fd); |
| `uvm_info(get_full_name(), $sformatf("%0s is generated", test_name), UVM_LOW) |
| endfunction |
| |
| // Helper function to generate the proper sequence of handshake instructions |
| // to signal the testbench (see riscv_signature_pkg.sv) |
| function void gen_signature_handshake(ref string instr[$], |
| input signature_type_t signature_type, |
| core_status_t core_status = INITIALIZED, |
| test_result_t test_result = TEST_FAIL, |
| privileged_reg_t csr = MSCRATCH, |
| string addr_label = ""); |
| if (cfg.require_signature_addr) begin |
| string str[$]; |
| str = {$sformatf("li x%0d, 0x%0h", cfg.gpr[1], cfg.signature_addr)}; |
| instr = {instr, str}; |
| case (signature_type) |
| // A single data word is written to the signature address. |
| // Bits [7:0] contain the signature_type of CORE_STATUS, and the upper |
| // XLEN-8 bits contain the core_status_t data. |
| CORE_STATUS: begin |
| str = {$sformatf("li x%0d, 0x%0h", cfg.gpr[0], core_status), |
| $sformatf("slli x%0d, x%0d, 8", cfg.gpr[0], cfg.gpr[0]), |
| $sformatf("addi x%0d, x%0d, 0x%0h", cfg.gpr[0], |
| cfg.gpr[0], signature_type), |
| $sformatf("sw x%0d, 0(x%0d)", cfg.gpr[0], cfg.gpr[1])}; |
| instr = {instr, str}; |
| end |
| // A single data word is written to the signature address. |
| // Bits [7:0] contain the signature_type of TEST_RESULT, and the upper |
| // XLEN-8 bits contain the test_result_t data. |
| TEST_RESULT: begin |
| str = {$sformatf("li x%0d, 0x%0h", cfg.gpr[0], test_result), |
| $sformatf("slli x%0d, x%0d, 8", cfg.gpr[0], cfg.gpr[0]), |
| $sformatf("addi x%0d, x%0d, 0x%0h", cfg.gpr[0], |
| cfg.gpr[0], signature_type), |
| $sformatf("sw x%0d, 0(x%0d)", cfg.gpr[0], cfg.gpr[1])}; |
| instr = {instr, str}; |
| end |
| // The first write to the signature address contains just the |
| // signature_type of WRITE_GPR. |
| // It is followed by 32 consecutive writes to the signature address, |
| // each writing the data contained in one GPR, starting from x0 as the |
| // first write, and ending with x31 as the 32nd write. |
| WRITE_GPR: begin |
| str = {$sformatf("li x%0d, 0x%0h", cfg.gpr[0], signature_type), |
| $sformatf("sw x%0d, 0(x%0d)", cfg.gpr[0], cfg.gpr[1])}; |
| instr = {instr, str}; |
| for(int i = 0; i < 32; i++) begin |
| str = {$sformatf("sw x%0x, 0(x%0d)", i, cfg.gpr[1])}; |
| instr = {instr, str}; |
| end |
| end |
| // The first write to the signature address contains the |
| // signature_type of WRITE_CSR in bits [7:0], and the CSR address in |
| // the upper XLEN-8 bits. |
| // It is followed by a second write to the signature address, |
| // containing the data stored in the specified CSR. |
| WRITE_CSR: begin |
| str = {$sformatf("li x%0d, 0x%0h", cfg.gpr[0], csr), |
| $sformatf("slli x%0d, x%0d, 8", cfg.gpr[0], cfg.gpr[0]), |
| $sformatf("addi x%0d, x%0d, 0x%0h", cfg.gpr[0], |
| cfg.gpr[0], signature_type), |
| $sformatf("sw x%0d, 0(x%0d)", cfg.gpr[0], cfg.gpr[1]), |
| $sformatf("csrr x%0d, 0x%0h", cfg.gpr[0], csr), |
| $sformatf("sw x%0d, 0(x%0d)", cfg.gpr[0], cfg.gpr[1])}; |
| instr = {instr, str}; |
| end |
| default: begin |
| `uvm_fatal(`gfn, "signature_type is not defined") |
| end |
| endcase |
| end |
| endfunction |
| |
| //--------------------------------------------------------------------------------------- |
| // Inject directed instruction stream |
| //--------------------------------------------------------------------------------------- |
| |
| virtual function void add_directed_instr_stream(string name, int unsigned ratio); |
| directed_instr_stream_ratio[name] = ratio; |
| `uvm_info(`gfn, $sformatf("Adding directed instruction stream:%0s ratio:%0d/1000", name, ratio), UVM_LOW) |
| endfunction |
| |
| virtual function void get_directed_instr_stream(); |
| string args, val; |
| string stream_name_opts, stream_freq_opts; |
| string stream_name; |
| int stream_freq; |
| string opts[$]; |
| for (int i=0; i<cfg.max_directed_instr_stream_seq; i++) begin |
| args = $sformatf("directed_instr_%0d=", i); |
| stream_name_opts = $sformatf("stream_name_%0d=", i); |
| stream_freq_opts = $sformatf("stream_freq_%0d=", i); |
| if ($value$plusargs({args,"%0s"}, val)) begin |
| uvm_split_string(val, ",", opts); |
| if (opts.size() != 2) begin |
| `uvm_fatal(`gfn, $sformatf( |
| "Incorrect directed instruction format : %0s, expect: name,ratio", val)) |
| end else begin |
| add_directed_instr_stream(opts[0], opts[1].atoi()); |
| end |
| end else if ($value$plusargs({stream_name_opts,"%0s"}, stream_name) && |
| $value$plusargs({stream_freq_opts,"%0d"}, stream_freq)) begin |
| add_directed_instr_stream(stream_name, stream_freq); |
| end |
| end |
| endfunction |
| |
| // Generate directed instruction stream based on the ratio setting |
| virtual function void generate_directed_instr_stream(input string label, |
| input int unsigned original_instr_cnt, |
| input int unsigned min_insert_cnt = 0, |
| input bit kernel_mode = 0, |
| output riscv_instr_stream instr_stream[]); |
| uvm_object object_h; |
| riscv_rand_instr_stream new_instr_stream; |
| int unsigned instr_insert_cnt; |
| int unsigned idx; |
| uvm_coreservice_t coreservice = uvm_coreservice_t::get(); |
| uvm_factory factory = coreservice.get_factory(); |
| if(cfg.no_directed_instr) return; |
| foreach(directed_instr_stream_ratio[instr_stream_name]) begin |
| instr_insert_cnt = original_instr_cnt * directed_instr_stream_ratio[instr_stream_name] / 1000; |
| if(instr_insert_cnt <= min_insert_cnt) begin |
| instr_insert_cnt = min_insert_cnt; |
| end |
| `ifdef DSIM |
| // Temporarily skip loop instruction for dsim as it cannot support dynamic array |
| // randomization |
| if (uvm_is_match("*loop*", instr_stream_name)) begin |
| `uvm_info(`gfn, $sformatf("%0s is skipped", instr_stream_name), UVM_LOW) |
| continue; |
| end |
| `endif |
| `uvm_info(get_full_name(), $sformatf("Insert directed instr stream %0s %0d/%0d times", |
| instr_stream_name, instr_insert_cnt, original_instr_cnt), UVM_LOW) |
| for(int i = 0; i < instr_insert_cnt; i++) begin |
| string name = $sformatf("%0s_%0d", instr_stream_name, i); |
| object_h = factory.create_object_by_name(instr_stream_name, get_full_name(), name); |
| if(object_h == null) begin |
| `uvm_fatal(get_full_name(), $sformatf("Cannot create instr stream %0s", name)) |
| end |
| if($cast(new_instr_stream, object_h)) begin |
| new_instr_stream.cfg = cfg; |
| new_instr_stream.label = $sformatf("%0s_%0d", label, idx); |
| new_instr_stream.kernel_mode = kernel_mode; |
| `DV_CHECK_RANDOMIZE_FATAL(new_instr_stream) |
| instr_stream = {instr_stream, new_instr_stream}; |
| end else begin |
| `uvm_fatal(get_full_name(), $sformatf("Cannot cast instr stream %0s", name)) |
| end |
| idx++; |
| end |
| end |
| instr_stream.shuffle(); |
| endfunction |
| |
| //--------------------------------------------------------------------------------------- |
| // Generate the debug rom, and any related programs |
| // TODO - refactor such that debug_rom is generated by a separate class |
| //--------------------------------------------------------------------------------------- |
| |
| // Generate the program in the debug ROM |
| // Processor will fetch instruction from here upon receiving debug request from debug module |
| virtual function void gen_debug_rom(); |
| string instr[$]; |
| string debug_end[$]; |
| string dret; |
| string debug_sub_program_name[$] = {}; |
| string str[$]; |
| if (riscv_instr_pkg::support_debug_mode) begin |
| dret = {format_string(" ", LABEL_STR_LEN), "dret"}; |
| // The main debug rom |
| if (!cfg.gen_debug_section) begin |
| // If the debug section should not be generated, we just populate it |
| // with a dret instruction. |
| instr = {dret}; |
| gen_section("debug_rom", instr); |
| end else begin |
| if (cfg.enable_ebreak_in_debug_rom) begin |
| // As execution of ebreak in D mode causes core to |
| // re-enter D mode, this directed sequence will be a loop that ensures the |
| // ebreak instruction will only be executed once to prevent infinitely |
| // looping back to the beginning of the debug rom. |
| // Write dscratch to random GPR and branch to debug_end if greater |
| // than 0, for ebreak loops. |
| // Use dscratch1 to store original GPR value. |
| str = {$sformatf("csrw 0x%0x, x%0d", DSCRATCH1, cfg.scratch_reg), |
| $sformatf("csrr x%0d, 0x%0x", cfg.scratch_reg, DSCRATCH0), |
| $sformatf("beq x%0d, x0, 1f", cfg.scratch_reg), |
| $sformatf("j debug_end"), |
| $sformatf("1: csrr x%0d, 0x%0x", cfg.scratch_reg, DSCRATCH1)}; |
| instr = {instr, str}; |
| end |
| // Need to save off GPRs to avoid modifying program flow |
| push_gpr_to_kernel_stack(MSTATUS, MSCRATCH, cfg.mstatus_mprv, cfg.sp, cfg.tp, instr); |
| // Signal that the core entered debug rom only if the rom is actually |
| // being filled with random instructions to prevent stress tests from |
| // having to execute unnecessary push/pop of GPRs on the stack ever |
| // time a debug request is sent |
| gen_signature_handshake(instr, CORE_STATUS, IN_DEBUG_MODE); |
| if (cfg.enable_ebreak_in_debug_rom) begin |
| // send dpc and dcsr to testbench, as this handshake will be |
| // executed twice due to the ebreak loop, there should be no change |
| // in their values as by the Debug Mode Spec Ch. 4.1.8 |
| gen_signature_handshake(.instr(instr), .signature_type(WRITE_CSR), .csr(DCSR)); |
| gen_signature_handshake(.instr(instr), .signature_type(WRITE_CSR), .csr(DPC)); |
| end |
| if (cfg.set_dcsr_ebreak) begin |
| // We want to set dcsr.ebreak(m/s/u) to 1'b1, depending on what modes |
| // are available. |
| // TODO(udinator) - randomize the dcsr.ebreak setup |
| gen_dcsr_ebreak(instr); |
| end |
| if (cfg.enable_debug_single_step) begin |
| // To enable debug single stepping, we must set dcsr.step to 1. |
| // We will repeat the debug single stepping process a random number |
| // of times, using a dscratch CSR as the counter, and decrement |
| // this counter by 1 every time we enter debug mode, until this |
| // counter reaches 0, at which point we set dcsr.step to 0 until |
| // the next debug stimulus is asserted. |
| |
| // Store our designated scratch_reg to dscratch1 |
| str = {$sformatf("csrw 0x%0x, x%0d", DSCRATCH1, cfg.scratch_reg), |
| // Only un-set dcsr.step if it is 1 and the iterations counter |
| // is at 0 (has finished iterating) |
| $sformatf("csrr x%0d, 0x%0x", cfg.scratch_reg, DCSR), |
| $sformatf("andi x%0d, x%0d, 4", cfg.scratch_reg, cfg.scratch_reg), |
| // If dcsr.step is 0, set to 1 and set the counter |
| $sformatf("beqz x%0d, 1f", cfg.scratch_reg), |
| $sformatf("csrr x%0d, 0x%0x", cfg.scratch_reg, DSCRATCH0), |
| // if the counter is greater than 0, decrement and continue single stepping |
| $sformatf("bgtz x%0d, 2f", cfg.scratch_reg), |
| $sformatf("csrc 0x%0x, 0x4", DCSR), |
| $sformatf("beqz x0, 3f"), |
| // Set dcsr.step and the num_iterations counter |
| $sformatf("1: csrs 0x%0x, 0x4", DCSR), |
| $sformatf("li x%0d, %0d", cfg.scratch_reg, cfg.single_step_iterations), |
| $sformatf("csrw 0x%0x, x%0d", DSCRATCH0, cfg.scratch_reg), |
| $sformatf("beqz x0, 3f"), |
| // Decrement dscratch counter |
| $sformatf("2: csrr x%0d, 0x%0x", cfg.scratch_reg, DSCRATCH0), |
| $sformatf("addi x%0d, x%0d, -1", cfg.scratch_reg, cfg.scratch_reg), |
| $sformatf("csrw 0x%0x, x%0d", DSCRATCH0, cfg.scratch_reg), |
| // Restore scratch_reg value from dscratch1 |
| $sformatf("3: csrr x%0d, 0x%0x", cfg.scratch_reg, DSCRATCH1) |
| }; |
| instr = {instr, str}; |
| // write dpc to testbench |
| gen_signature_handshake(.instr(instr), .signature_type(WRITE_CSR), .csr(DPC)); |
| // write out the counter to the testbench |
| gen_signature_handshake(.instr(instr), .signature_type(WRITE_CSR), .csr(DSCRATCH0)); |
| end |
| // Check dcsr.cause, and update dpc by 0x4 if the cause is ebreak, as |
| // ebreak will set set dpc to its own address, which will cause an |
| // infinite loop. |
| str = {$sformatf("csrr x%0d, 0x%0x", cfg.scratch_reg, DCSR), |
| $sformatf("slli x%0d, x%0d, 0x17", cfg.scratch_reg, cfg.scratch_reg), |
| $sformatf("srli x%0d, x%0d, 0x1d", cfg.scratch_reg, cfg.scratch_reg), |
| $sformatf("li x%0d, 0x1", cfg.gpr[0]), |
| $sformatf("bne x%0d, x%0d, 4f", cfg.scratch_reg, cfg.gpr[0])}; |
| instr = {instr, str}; |
| increment_csr(DPC, 4, instr); |
| str = {"4: nop"}; |
| instr = {instr, str}; |
| // write DCSR to the testbench for any analysis |
| gen_signature_handshake(.instr(instr), .signature_type(WRITE_CSR), .csr(DCSR)); |
| // Increment dscratch0 by 1 to update the loop counter for all ebreak |
| // tests |
| if (cfg.enable_ebreak_in_debug_rom || cfg.set_dcsr_ebreak) begin |
| // Add 1 to dscratch0 |
| increment_csr(DSCRATCH0, 1, instr); |
| str = {$sformatf("csrr x%0d, 0x%0x", cfg.scratch_reg, DSCRATCH1)}; |
| instr = {instr, str}; |
| end |
| format_section(instr); |
| gen_sub_program(debug_sub_program, debug_sub_program_name, |
| cfg.num_debug_sub_program, 1'b1, "debug_sub"); |
| debug_program = riscv_instr_sequence::type_id::create("debug_program"); |
| debug_program.instr_cnt = cfg.debug_program_instr_cnt; |
| debug_program.is_debug_program = 1; |
| debug_program.cfg = cfg; |
| `DV_CHECK_RANDOMIZE_FATAL(debug_program) |
| debug_program.gen_instr(.is_main_program(1'b1), .no_branch(cfg.no_branch_jump)); |
| gen_callstack(debug_program, debug_sub_program, debug_sub_program_name, |
| cfg.num_debug_sub_program); |
| debug_program.post_process_instr(); |
| debug_program.generate_instr_stream(.no_label(1'b1)); |
| insert_sub_program(debug_sub_program, instr_stream); |
| instr = {instr, debug_program.instr_string_list}; |
| gen_section("debug_rom", instr); |
| // Set dscratch0 back to 0x0 to prepare for the next entry into debug |
| // mode, and write dscratch0 and dcsr to the testbench for any |
| // analysis |
| if (cfg.enable_ebreak_in_debug_rom) begin |
| // send dpc and dcsr to testbench, as this handshake will be |
| // executed twice due to the ebreak loop, there should be no change |
| // in their values as by the Debug Mode Spec Ch. 4.1.8 |
| gen_signature_handshake(.instr(debug_end), .signature_type(WRITE_CSR), .csr(DCSR)); |
| gen_signature_handshake(.instr(debug_end), .signature_type(WRITE_CSR), .csr(DPC)); |
| str = {$sformatf("csrwi 0x%0x, 0x0", DSCRATCH0)}; |
| debug_end = {debug_end, str}; |
| end |
| pop_gpr_from_kernel_stack(MSTATUS, MSCRATCH, cfg.mstatus_mprv, |
| cfg.sp, cfg.tp, debug_end); |
| // We have been using dscratch1 to store the |
| // value of our given scratch register for use in ebreak loop, so we |
| // need to restore its value before returning from D mode |
| if (cfg.enable_ebreak_in_debug_rom) begin |
| str = {$sformatf("csrr x%0d, 0x%0x", cfg.scratch_reg, DSCRATCH1)}; |
| debug_end = {debug_end, str}; |
| end |
| format_section(debug_end); |
| debug_end = {debug_end, dret}; |
| gen_section("debug_end", debug_end); |
| end |
| end |
| endfunction |
| |
| // Generate exception handling routine for debug ROM |
| virtual function void gen_debug_exception_handler(); |
| if (riscv_instr_pkg::support_debug_mode) begin |
| string instr[]; |
| instr = {"dret"}; |
| gen_section("debug_exception", instr); |
| end |
| endfunction |
| |
| // Set dcsr.ebreak(m/s/u) |
| // TODO(udinator) - randomize the setup for these fields |
| virtual function void gen_dcsr_ebreak(ref string instr[$]); |
| string str; |
| if (MACHINE_MODE inside {riscv_instr_pkg::supported_privileged_mode}) begin |
| str = $sformatf("li x%0d, 0x8000", cfg.scratch_reg); |
| instr.push_back(str); |
| str = $sformatf("csrs dcsr, x%0d", cfg.scratch_reg); |
| instr.push_back(str); |
| end |
| if (SUPERVISOR_MODE inside {riscv_instr_pkg::supported_privileged_mode}) begin |
| str = $sformatf("li x%0d, 0x2000", cfg.scratch_reg); |
| instr.push_back(str); |
| str = $sformatf("csrs dcsr, x%0d", cfg.scratch_reg); |
| instr.push_back(str); |
| end |
| if (USER_MODE inside {riscv_instr_pkg::supported_privileged_mode}) begin |
| str = $sformatf("li x%0d, 0x1000", cfg.scratch_reg); |
| instr.push_back(str); |
| str = $sformatf("csrs dcsr, x%0d", cfg.scratch_reg); |
| instr.push_back(str); |
| end |
| endfunction |
| |
| virtual function void increment_csr(privileged_reg_t csr, int val, ref string instr[$]); |
| string str; |
| str = $sformatf("csrr x%0d, 0x%0x", cfg.scratch_reg, csr); |
| instr.push_back(str); |
| str = $sformatf("addi x%0d, x%0d, 0x%0x", cfg.scratch_reg, cfg.scratch_reg, val); |
| instr.push_back(str); |
| str = $sformatf("csrw 0x%0x, x%0d", csr, cfg.scratch_reg); |
| instr.push_back(str); |
| endfunction |
| |
| endclass |