diff --git a/src/isa/riscv_compressed_instr.sv b/src/isa/riscv_compressed_instr.sv index 02fdf338..a603262f 100644 --- a/src/isa/riscv_compressed_instr.sv +++ b/src/isa/riscv_compressed_instr.sv @@ -33,12 +33,19 @@ class riscv_compressed_instr extends riscv_instr; } // C_ADDI16SP is only valid when rd == SP if (instr_name == C_ADDI16SP) { + rs1 == SP; rd == SP; } if (instr_name inside {C_JR, C_JALR}) { rs2 == ZERO; rs1 != ZERO; } + // These formats potentially have rd == rs1 + if (format inside {CA_FORMAT, CB_FORMAT, CI_FORMAT, CR_FORMAT}) { + if (has_rd && has_rs1) { + rd == rs1; + } + } } constraint imm_val_c { @@ -156,7 +163,13 @@ class riscv_compressed_instr extends riscv_instr; has_rs1 = 1'b0; has_imm = 1'b0; end - CI_FORMAT, CIW_FORMAT: begin + CI_FORMAT: begin + if (instr_name inside {C_LI, C_LUI, C_LWSP, C_NOP, C_EBREAK}) begin + has_rs1 = 1'b0; + end + has_rs2 = 1'b0; + end + CIW_FORMAT: begin has_rs1 = 1'b0; has_rs2 = 1'b0; end @@ -166,7 +179,7 @@ class riscv_compressed_instr extends riscv_instr; has_rd = 1'b0; end CB_FORMAT: begin - if (instr_name != C_ANDI) has_rd = 1'b0; + if (instr_name inside {C_BEQZ, C_BNEZ}) has_rd = 1'b0; has_rs2 = 1'b0; end endcase diff --git a/src/isa/riscv_instr.sv b/src/isa/riscv_instr.sv index f44e972a..229afc92 100644 --- a/src/isa/riscv_instr.sv +++ b/src/isa/riscv_instr.sv @@ -45,6 +45,8 @@ class riscv_instr extends uvm_object; rand riscv_reg_t rs1; rand riscv_reg_t rd; rand bit [31:0] imm; + rand bit [3:0] rlist; + rand int stack_adj; // Helper fields bit [31:0] imm_mask = 32'hFFFF_FFFF; @@ -66,6 +68,8 @@ class riscv_instr extends uvm_object; bit has_rs2 = 1'b1; bit has_rd = 1'b1; bit has_imm = 1'b1; + bit has_rlist = 1'b0; + bit has_stack_adj = 1'b0; constraint imm_c { if (instr_name inside {SLLIW, SRLIW, SRAIW}) { @@ -111,7 +115,8 @@ class riscv_instr extends uvm_object; if (cfg.no_fence && (instr_name inside {FENCE, FENCE_I, SFENCE_VMA})) continue; if ((instr_inst.group inside {supported_isa}) && !(cfg.disable_compressed_instr && - (instr_inst.group inside {RV32C, RV64C, RV32DC, RV32FC, RV128C})) && + (instr_inst.group inside {RV32C, RV64C, RV32DC, RV32FC, RV128C, + RV32ZCB, RV64ZCB, RV32ZCMP, RV64ZCMP})) && !(!cfg.enable_floating_point && (instr_inst.group inside {RV32F, RV64F, RV32D, RV64D})) && !(!cfg.enable_vector_extension && @@ -296,6 +301,7 @@ class riscv_instr extends uvm_object; rs2.rand_mode(has_rs2); rd.rand_mode(has_rd); imm.rand_mode(has_imm); + stack_adj.rand_mode(has_stack_adj); if (category != CSR) begin csr.rand_mode(0); end @@ -586,6 +592,40 @@ class riscv_instr extends uvm_object; return imm_str; endfunction + virtual function string get_rlist(); + // rlist 0-3 are reserved for future extensions + if (rlist < 4 || rlist > 15) begin + `uvm_fatal(`gfn, $sformatf("Unsupported rlist: %0d", rlist)) + end + // Handle the specific cases and use a default for the general pattern. + case(rlist) + 4: return "{ra}"; + 5: return "{ra, s0}"; + 15: return "{ra, s0-s11}"; // The special case for s10/s11 + // The default case handles the general pattern for rlist 6 through 14 + default: return $sformatf("{ra, s0-s%0d}", rlist - 5); + endcase + endfunction + + virtual function riscv_reglist_t get_rlist_as_list(); + case(rlist) + 4: return '{RA}; + 5: return '{RA, S0}; + 6: return '{RA, S0, S1}; + 7: return '{RA, S0, S1, S2}; + 8: return '{RA, S0, S1, S2, S3}; + 9: return '{RA, S0, S1, S2, S3, S4}; + 10: return '{RA, S0, S1, S2, S3, S4, S5}; + 11: return '{RA, S0, S1, S2, S3, S4, S5, S6}; + 12: return '{RA, S0, S1, S2, S3, S4, S5, S6, S7}; + 13: return '{RA, S0, S1, S2, S3, S4, S5, S6, S7, S8}; + 14: return '{RA, S0, S1, S2, S3, S4, S5, S6, S7, S8, S9}; + 15: return '{RA, S0, S1, S2, S3, S4, S5, S6, S7, S8, S9, S10, S11}; + default: `uvm_fatal(`gfn, $sformatf("Unsupported rlist: %0d", rlist)) + endcase + return '{}; + endfunction + virtual function void clear_unused_label(); if(has_label && !is_branch_target && is_local_numeric_label) begin has_label = 1'b0; @@ -620,6 +660,10 @@ class riscv_instr extends uvm_object; imm_str = $sformatf("%0d", $signed(imm)); endfunction + virtual function int get_imm_val(); + return $signed(imm); + endfunction + `include "isa/riscv_instr_cov.svh" endclass diff --git a/src/isa/riscv_instr_cov.svh b/src/isa/riscv_instr_cov.svh index 6819f383..54773de3 100644 --- a/src/isa/riscv_instr_cov.svh +++ b/src/isa/riscv_instr_cov.svh @@ -408,6 +408,38 @@ // c.j imm get_val(operands[0], imm); end + CU_FORMAT: begin + rs1 = get_gpr(operands[0]); + rs1_value = get_gpr_state(operands[0]); + end + CLB_FORMAT, CLH_FORMAT: begin + get_val(operands[2], imm); + rs1 = get_gpr(operands[1]); + rs1_value = get_gpr_state(operands[1]); + end + CSB_FORMAT, CSH_FORMAT: begin + rs2 = get_gpr(operands[0]); + rs2_value = get_gpr_state(operands[0]); + get_val(operands[2], imm); + rs1 = get_gpr(operands[1]); + rs1_value = get_gpr_state(operands[1]); + end + CMMV_FORMAT: begin + // cm.mva01s rs1, rs2 + // cm.mvsa01 r1s, r2s + rs1 = get_gpr(operands[0]); + rs1_value = get_gpr_state(operands[0]); + rs2 = get_gpr(operands[1]); + rs2_value = get_gpr_state(operands[1]); + end + CMPP_FORMAT: begin + // cm.push {reg_list}, -stack_adj + // cm.pop {reg_list}, stack_adj + // cm.popret {reg_list}, stack_adj + // cm.popretz {reg_list}, stack_adj + get_val(operands[0], rlist); + get_val(operands[1], stack_adj); + end default: `uvm_fatal(`gfn, $sformatf("Unsupported format %0s", format)) endcase endfunction : update_src_regs diff --git a/src/isa/riscv_zcb_instr.sv b/src/isa/riscv_zcb_instr.sv new file mode 100644 index 00000000..7eb7ce4b --- /dev/null +++ b/src/isa/riscv_zcb_instr.sv @@ -0,0 +1,225 @@ +/* + * Copyright 2020 Google LLC + * Copyright 2023 Frontgrade Gaisler + * + * 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. + */ + +class riscv_zcb_instr extends riscv_instr; + + // This code is based on the frozen v.1.0.1 code reduction specification. + // Most of the code is copied from the riscv_compressed_instr class. + + constraint rvc_csr_c { + // Registers specified by the three-bit rs1’, rs2’, and rd/rs1’ + if (format inside {CLB_FORMAT, CSB_FORMAT, CLH_FORMAT, CSH_FORMAT, CU_FORMAT, CA_FORMAT}) { + if (has_rs1) { + rs1 inside {[S0:A5]}; + } + if (has_rs2) { + rs2 inside {[S0:A5]}; + } + if (has_rd) { + rd inside {[S0:A5]}; + } + } + // CU_FORMAT and CA_FORMAT has rd == rs1 + if (format inside {CU_FORMAT, CA_FORMAT}) { + if (has_rd && has_rs1) { + rd == rs1; + } + } + } + + `uvm_object_utils(riscv_zcb_instr) + + function new(string name = ""); + super.new(name); + rs1 = S0; + rs2 = S0; + rd = S0; + is_compressed = 1'b1; + endfunction : new + + virtual function void set_imm_len(); + if (format inside {CLB_FORMAT, CSB_FORMAT}) begin + imm_len = 2; + end else if (format inside {CLH_FORMAT, CSH_FORMAT}) begin + imm_len = 1; + end + endfunction : set_imm_len + + virtual function void set_rand_mode(); + case (format) inside + CLB_FORMAT: begin + has_rs2 = 1'b0; + end + CSB_FORMAT: begin + has_rd = 1'b0; + end + CLH_FORMAT: begin + has_rs2 = 1'b0; + end + CSH_FORMAT: begin + has_rd = 1'b0; + end + CU_FORMAT: begin + has_rs2 = 1'b0; + has_imm = 1'b0; + end + CA_FORMAT: begin + has_imm = 1'b0; + end + default: `uvm_info(`gfn, $sformatf("Unsupported format %0s", format.name()), UVM_LOW) + endcase + endfunction + + // Convert the instruction to assembly code + virtual function string convert2asm(string prefix = ""); + string asm_str; + asm_str = format_string(get_instr_name(), MAX_INSTR_STR_LEN); + case(format) + CLB_FORMAT, CLH_FORMAT: + asm_str = $sformatf("%0s%0s, %0s(%0s)", asm_str, rd.name(), get_imm(), rs1.name()); + CSB_FORMAT, CSH_FORMAT: + asm_str = $sformatf("%0s%0s, %0s(%0s)", asm_str, rs2.name(), get_imm(), rs1.name()); + CU_FORMAT: + asm_str = $sformatf("%0s%0s", asm_str, rs1.name); + CA_FORMAT: + asm_str = $sformatf("%0s%0s, %0s", asm_str, rd.name(), rs2.name()); + default: `uvm_info(`gfn, $sformatf("Unsupported format %0s", format.name()), UVM_LOW) + endcase + + if (comment != "") + asm_str = {asm_str, " #",comment}; + return asm_str.tolower(); + endfunction : convert2asm + + // Convert the instruction to assembly code + virtual function string convert2bin(string prefix = ""); + string binary; + case (instr_name) inside + C_LBU: + binary = $sformatf("0x%4h", {get_func6(), get_c_gpr(rs1), imm[0], imm[1], + get_c_gpr(rd), get_c_opcode()}); + C_LHU: + binary = $sformatf("0x%4h", {get_func6(), get_c_gpr(rs1), 1'b0, imm[1], + get_c_gpr(rd), get_c_opcode()}); + C_LH: + binary = $sformatf("0x%4h", {get_func6(), get_c_gpr(rs1), 1'b1, imm[1], + get_c_gpr(rd), get_c_opcode()}); + C_SB: + binary = $sformatf("0x%4h", {get_func6(), get_c_gpr(rs1), imm[0], imm[1], + get_c_gpr(rs2), get_c_opcode()}); + C_SH: + binary = $sformatf("0x%4h", {get_func6(), get_c_gpr(rs1), 1'b0, imm[1], + get_c_gpr(rs2), get_c_opcode()}); + C_ZEXT_B: + binary = $sformatf("0x%4h", {get_func6(), get_c_gpr(rd), 5'b11000, + get_c_opcode()}); + C_SEXT_B: + binary = $sformatf("0x%4h", {get_func6(), get_c_gpr(rd), 5'b11001, + get_c_opcode()}); + C_ZEXT_H: + binary = $sformatf("0x%4h", {get_func6(), get_c_gpr(rd), 5'b11010, + get_c_opcode()}); + C_SEXT_H: + binary = $sformatf("0x%4h", {get_func6(), get_c_gpr(rd), 5'b11011, + get_c_opcode()}); + C_ZEXT_W: + binary = $sformatf("0x%4h", {get_func6(), get_c_gpr(rd), 5'b11100, + get_c_opcode()}); + C_NOT: + binary = $sformatf("0x%4h", {get_func6(), get_c_gpr(rd), 5'b11101, + get_c_opcode()}); + C_MUL: + binary = $sformatf("0x%4h", {get_func6(), get_c_gpr(rd), 2'b10, + get_c_gpr(rs2), get_c_opcode()}); + default: `uvm_fatal(`gfn, $sformatf("Unsupported instruction %0s", instr_name.name())) + endcase + return {prefix, binary}; + endfunction : convert2bin + + // Get opcode for zcb instruction + virtual function bit [1:0] get_c_opcode(); + case (instr_name) inside + C_ZEXT_B, C_SEXT_B, C_ZEXT_H, C_SEXT_H, + C_ZEXT_W, C_NOT, C_MUL : get_c_opcode = 2'b01; + C_LBU, C_LHU, C_LH, C_SB, C_SH : get_c_opcode = 2'b00; + default: `uvm_fatal(`gfn, $sformatf("Unsupported instruction %0s", instr_name.name())) + endcase + endfunction : get_c_opcode + + virtual function bit [5:0] get_func6(); + case (instr_name) + C_LBU: get_func6 = 6'b100000; + C_LHU: get_func6 = 6'b100001; + C_LH: get_func6 = 6'b100001; + C_SB: get_func6 = 6'b100010; + C_SH: get_func6 = 6'b100011; + C_ZEXT_B: get_func6 = 6'b100011; + C_SEXT_B: get_func6 = 6'b100111; + C_ZEXT_H: get_func6 = 6'b100111; + C_SEXT_H: get_func6 = 6'b100111; + C_ZEXT_W: get_func6 = 6'b100111; + C_NOT: get_func6 = 6'b100111; + C_MUL: get_func6 = 6'b100111; + default: `uvm_fatal(`gfn, $sformatf("Unsupported instruction %0s", instr_name.name())) + endcase + endfunction : get_func6 + + virtual function bit is_supported(riscv_instr_gen_config cfg); + return (cfg.enable_zcb_extension && + // RV32C, RV32Zbb, RV32Zba, M/Zmmul is prerequisites for this extension + (RV32ZBB inside {supported_isa}) && + (RV32C inside {supported_isa}) && + (RV32ZBA inside {supported_isa}) && + ((RV32M inside {supported_isa}) || (RV32ZMMUL inside {supported_isa})) && + ((RV32ZCB inside {supported_isa} || RV64ZCB inside {supported_isa})) && + instr_name inside { + C_ZEXT_B, C_SEXT_B, C_ZEXT_H, C_SEXT_H, C_ZEXT_W, + C_NOT, C_MUL, C_LBU, C_LHU, C_LH, C_SB, C_SH + }); + endfunction : is_supported + + // For coverage + virtual function void update_src_regs(string operands[$]); + case(format) + CU_FORMAT: begin + rs1 = get_gpr(operands[0]); + rs1_value = get_gpr_state(operands[0]); + end + CLB_FORMAT, CLH_FORMAT: begin + get_val(operands[2], imm); + rs1 = get_gpr(operands[1]); + rs1_value = get_gpr_state(operands[1]); + end + CSB_FORMAT, CSH_FORMAT: begin + rs2 = get_gpr(operands[0]); + rs2_value = get_gpr_state(operands[0]); + get_val(operands[2], imm); + rs1 = get_gpr(operands[1]); + rs1_value = get_gpr_state(operands[1]); + end + CA_FORMAT: begin + rs2 = get_gpr(operands[1]); + rs2_value = get_gpr_state(operands[1]); + rs1 = get_gpr(operands[0]); + rs1_value = get_gpr_state(operands[0]); + end + default: ; + endcase + super.update_src_regs(operands); + endfunction : update_src_regs + +endclass : riscv_zcb_instr; diff --git a/src/isa/riscv_zcmp_instr.sv b/src/isa/riscv_zcmp_instr.sv new file mode 100644 index 00000000..81c0c6dc --- /dev/null +++ b/src/isa/riscv_zcmp_instr.sv @@ -0,0 +1,225 @@ +/* + * Copyright 2025 Google LLC + * Copyright 2025 lowRISC CIC + * + * 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. + */ + +class riscv_zcmp_instr extends riscv_instr; + + constraint rvc_csr_c { + // Solve the stack adjustment first, so we can control the stack size + solve stack_adj before rlist; + if (format == CMPP_FORMAT) { + if (instr_name == CM_PUSH) { + // For PUSH, stack adjustment is negative + stack_adj inside {-16, -32, -48, -64, -80, -96, -112}; + (stack_adj == -16) -> rlist inside {[4:7]}; + (stack_adj == -32) -> rlist inside {[4:11]}; + (stack_adj == -48) -> rlist inside {[4:14]}; + (stack_adj == -64) -> rlist inside {[4:15]}; + (stack_adj == -80) -> rlist inside {[8:15]}; + (stack_adj == -96) -> rlist inside {[12:15]}; + (stack_adj == -112) -> rlist inside {15}; + } else { + // For POP and POPRET, stack adjustment is positive + stack_adj inside {16, 32, 48, 64, 80, 96, 112}; + (rlist inside {[4:7]}) -> stack_adj inside {16, 32, 48, 64}; + (rlist inside {[8:11]}) -> stack_adj inside {32, 48, 64, 80}; + (rlist inside {[12:14]}) -> stack_adj inside {48, 64, 80, 96}; + (rlist == 15) -> stack_adj inside {64, 80, 96, 112}; + } + // rlist can be anything between 4 and 15. 0-3 are reserved for future use. + rlist inside {[4:15]}; + } + if (format == CMMV_FORMAT) { + // Always has rs1 and rs2 and they must be different + rs1 != rs2; + // Those instructions use a special encoding, only S0, S1, S2-S7 are allowed + // which correspond to x8, x9, x18-x23, so the actual registers used are + // {r1sc[2:1]>0,r1sc[2:1]==0,r1sc[2:0]}; + // So for registers beyond x16 we prepend 0x10 to the three LSB + // For the registers x8 and x9 we prepend 0x01 to the three LSB + rs1 inside {S0, S1, [S2:S7]}; + rs2 inside {S0, S1, [S2:S7]}; + } + } + + `uvm_object_utils(riscv_zcmp_instr) + + function new(string name = ""); + super.new(name); + rs1 = S0; + rs2 = S0; + rlist = 4; + is_compressed = 1'b1; + stack_adj = instr_name == CM_PUSH ? -16 : 16; + endfunction : new + + virtual function void set_rand_mode(); + case (format) inside + CMMV_FORMAT : begin + has_imm = 1'b0; + end + CMPP_FORMAT : begin + has_rs1 = 1'b0; + has_rs2 = 1'b0; + has_rd = 1'b0; + has_rlist = 1'b1; + has_imm = 1'b0; + has_stack_adj = 1'b1; + end + default: `uvm_info(`gfn, $sformatf("Unsupported format %0s", format.name()), UVM_LOW) + endcase + endfunction + + // Convert the instruction to assembly code + virtual function string convert2asm(string prefix = ""); + string asm_str; + asm_str = format_string(get_instr_name(), MAX_INSTR_STR_LEN); + case(format) + CMMV_FORMAT: + asm_str = $sformatf("%0s %0s, %0s", asm_str, rs1.name(), rs2.name()); + CMPP_FORMAT: begin + asm_str = $sformatf("%0s %0s, %0d", asm_str, get_rlist(), get_stack_adj()); + end + default: `uvm_info(`gfn, $sformatf("Unsupported format %0s", format.name()), UVM_LOW) + endcase + + if (comment != "") + asm_str = {asm_str, " #",comment}; + return asm_str.tolower(); + endfunction : convert2asm + + // Convert the instruction to binary code + virtual function string convert2bin(string prefix = ""); + string binary; + case (instr_name) inside + CM_PUSH: + binary = $sformatf( + "0x%4h", {get_func6(), get_func2(), rlist, get_stack_adj_encoding(), get_c_opcode()}); + CM_POP: + binary = $sformatf( + "0x%4h", {get_func6(), get_func2(), rlist, get_stack_adj_encoding(), get_c_opcode()}); + CM_POPRETZ: + binary = $sformatf( + "0x%4h", {get_func6(), get_func2(), rlist, get_stack_adj_encoding(), get_c_opcode()}); + CM_POPRET: + binary = $sformatf( + "0x%4h", {get_func6(), get_func2(), rlist, get_stack_adj_encoding(), get_c_opcode()}); + CM_MVA01S: + binary = $sformatf( + "0x%4h", {get_func6(), get_c_gpr(rs1), get_func2(), get_c_gpr(rs2), get_c_opcode()}); + CM_MVSA01: + binary = $sformatf( + "0x%4h", {get_func6(), get_c_gpr(rs1), get_func2(), get_c_gpr(rs2), get_c_opcode()}); + default: `uvm_fatal(`gfn, $sformatf("Unsupported instruction %0s", instr_name.name())) + endcase + return {prefix, binary}; + endfunction : convert2bin + + // Get opcode for zcmp instruction + virtual function bit [1:0] get_c_opcode(); + case (instr_name) inside + CM_PUSH, CM_POP, CM_POPRETZ, CM_POPRET, CM_MVA01S, CM_MVSA01 : get_c_opcode = 2'b10; + default: `uvm_fatal(`gfn, $sformatf("Unsupported instruction %0s", instr_name.name())) + endcase + endfunction : get_c_opcode + + virtual function bit [5:0] get_func6(); + case (instr_name) inside + CM_PUSH: get_func6 = 6'b101110; + CM_POP: get_func6 = 6'b101110; + CM_POPRETZ: get_func6 = 6'b101111; + CM_POPRET: get_func6 = 6'b101111; + CM_MVSA01: get_func6 = 6'b101011; + CM_MVA01S: get_func6 = 6'b101011; + default: `uvm_fatal(`gfn, $sformatf("Unsupported instruction %0s", instr_name.name())) + endcase + endfunction : get_func6 + + virtual function bit [1:0] get_func2(); + case (instr_name) inside + CM_PUSH: get_func2 = 2'b00; + CM_POP: get_func2 = 2'b10; + CM_POPRETZ: get_func2 = 2'b00; + CM_POPRET: get_func2 = 2'b10; + CM_MVSA01: get_func2 = 2'b01; + CM_MVA01S: get_func2 = 2'b11; + default: `uvm_fatal(`gfn, $sformatf("Unsupported instruction %0s", instr_name.name())) + endcase + endfunction : get_func2 + + virtual function bit is_supported(riscv_instr_gen_config cfg); + `uvm_info(`gfn, "ZCMP Check supported", UVM_LOW) + return (cfg.enable_zcmp_extension && + // RV32C, RV32Zbb, RV32Zba, M/Zmmul is prerequisites for this extension + (RV32C inside {supported_isa} || RV64C inside {supported_isa}) && + (RV32ZCMP inside {supported_isa} || RV64ZCMP inside {supported_isa}) && + instr_name inside { + CM_PUSH, CM_POP, CM_POPRET, CM_POPRETZ, CM_MVA01S, CM_MVSA01 + }); + endfunction : is_supported + + // For coverage + virtual function void update_src_regs(string operands[$]); + case(format) + CMMV_FORMAT: begin + rs1 = get_gpr(operands[0]); + rs1_value = get_gpr_state(operands[0]); + rs2 = get_gpr(operands[1]); + rs2_value = get_gpr_state(operands[1]); + end + CMPP_FORMAT: begin + get_val(operands[0], rlist); + get_val(operands[1], stack_adj); + end + default: ; + endcase + super.update_src_regs(operands); + endfunction : update_src_regs + + virtual function bit [1:0] get_stack_adj_encoding(); + int unsigned stack_adj_base; + int unsigned stack_adj_abs; + bit [1:0] stack_adj_encoding; + if (XLEN == 32) begin + case (rlist) + 4, 5, 6, 7: stack_adj_base = 16; + 8, 9, 10, 11: stack_adj_base = 32; + 12, 13, 14: stack_adj_base = 48; + 15: stack_adj_base = 64; + default: stack_adj_base = 0; + endcase + end else begin + case (rlist) + 4, 5: stack_adj_base = 16; + 6, 7: stack_adj_base = 32; + 8, 9: stack_adj_base = 48; + 10, 11: stack_adj_base = 64; + 12, 13: stack_adj_base = 80; + 14: stack_adj_base = 96; + 15: stack_adj_base = 122; + default: stack_adj_base = 0; + endcase + end + stack_adj_abs = instr_name == CM_PUSH ? -stack_adj : stack_adj; + stack_adj_encoding = (stack_adj_abs - stack_adj_base) / 16; + return stack_adj_encoding; + endfunction + + virtual function int get_stack_adj(); + return stack_adj; + endfunction + +endclass : riscv_zcmp_instr; diff --git a/src/isa/rv32zcb_instr.sv b/src/isa/rv32zcb_instr.sv new file mode 100644 index 00000000..d83fbd80 --- /dev/null +++ b/src/isa/rv32zcb_instr.sv @@ -0,0 +1,29 @@ +/* + * Copyright 2020 Google LLC + * Copyright 2023 Frontgrade Gaisler + * + * 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. + */ + +`DEFINE_ZCB_INSTR(C_LBU, CLB_FORMAT, LOAD, RV32ZCB, UIMM) +`DEFINE_ZCB_INSTR(C_LHU, CLH_FORMAT, LOAD, RV32ZCB, UIMM) +`DEFINE_ZCB_INSTR(C_LH, CLH_FORMAT, LOAD, RV32ZCB, UIMM) +`DEFINE_ZCB_INSTR(C_SB, CSB_FORMAT, STORE, RV32ZCB, UIMM) +`DEFINE_ZCB_INSTR(C_SH, CSH_FORMAT, STORE, RV32ZCB, UIMM) +`DEFINE_ZCB_INSTR(C_ZEXT_B, CU_FORMAT, ARITHMETIC, RV32ZCB) +`DEFINE_ZCB_INSTR(C_SEXT_B, CU_FORMAT, ARITHMETIC, RV32ZCB) +`DEFINE_ZCB_INSTR(C_ZEXT_H, CU_FORMAT, ARITHMETIC, RV32ZCB) +`DEFINE_ZCB_INSTR(C_SEXT_H, CU_FORMAT, ARITHMETIC, RV32ZCB) +`DEFINE_ZCB_INSTR(C_NOT, CU_FORMAT, LOGICAL, RV32ZCB) +`DEFINE_ZCB_INSTR(C_MUL, CA_FORMAT, ARITHMETIC, RV32ZCB) + diff --git a/src/isa/rv32zcmp_instr.sv b/src/isa/rv32zcmp_instr.sv new file mode 100644 index 00000000..9faf5503 --- /dev/null +++ b/src/isa/rv32zcmp_instr.sv @@ -0,0 +1,24 @@ +/* + * Copyright 2020 Google LLC + * Copyright 2023 Frontgrade Gaisler + * + * 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. + */ + +`DEFINE_ZCMP_INSTR(CM_PUSH, CMPP_FORMAT, STORE, RV32ZCMP) +`DEFINE_ZCMP_INSTR(CM_POP, CMPP_FORMAT, LOAD, RV32ZCMP) +`DEFINE_ZCMP_INSTR(CM_POPRETZ, CMPP_FORMAT, LOAD, RV32ZCMP) +`DEFINE_ZCMP_INSTR(CM_POPRET, CMPP_FORMAT, LOAD, RV32ZCMP) +`DEFINE_ZCMP_INSTR(CM_MVA01S, CMMV_FORMAT, ARITHMETIC, RV32ZCMP) +`DEFINE_ZCMP_INSTR(CM_MVSA01, CMMV_FORMAT, ARITHMETIC, RV32ZCMP) + diff --git a/src/isa/rv64zcb_instr.sv b/src/isa/rv64zcb_instr.sv new file mode 100644 index 00000000..8225b823 --- /dev/null +++ b/src/isa/rv64zcb_instr.sv @@ -0,0 +1,18 @@ +/* + * Copyright 2020 Google LLC + * Copyright 2023 Frontgrade Gaisler + * + * 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. + */ + +`DEFINE_ZCB_INSTR(C_ZEXT_W, CU_FORMAT, ARITHMETIC, RV64ZCB) diff --git a/src/riscv_asm_program_gen.sv b/src/riscv_asm_program_gen.sv index 4c969241..fbcc97c3 100644 --- a/src/riscv_asm_program_gen.sv +++ b/src/riscv_asm_program_gen.sv @@ -459,6 +459,7 @@ class riscv_asm_program_gen extends uvm_object; RV32X, RV64X : misa[MISA_EXT_X] = 1'b1; RV32ZBA, RV32ZBB, RV32ZBC, RV32ZBS, RV64ZBA, RV64ZBB, RV64ZBC, RV64ZBS : ; // No Misa bit for Zb* extensions + RV32ZCB, RV64ZCB, RV32ZCMP, RV64ZCMP : ; // No Misa bit for Zc* extensions default : `uvm_fatal(`gfn, $sformatf("%0s is not yet supported", supported_isa[i].name())) endcase diff --git a/src/riscv_defines.svh b/src/riscv_defines.svh index 4d078e16..39cfd10e 100644 --- a/src/riscv_defines.svh +++ b/src/riscv_defines.svh @@ -129,3 +129,13 @@ `define DEFINE_ZBS_INSTR(instr_n, instr_format, instr_category, instr_group, imm_tp = IMM) \ class riscv_``instr_n``_instr extends riscv_zbs_instr; \ `INSTR_BODY(instr_n, instr_format, instr_category, instr_group, imm_tp) + +//Zcb-extension instruction +`define DEFINE_ZCB_INSTR(instr_n, instr_format, instr_category, instr_group, imm_tp = IMM) \ + class riscv_``instr_n``_instr extends riscv_zcb_instr; \ + `INSTR_BODY(instr_n, instr_format, instr_category, instr_group, imm_tp) + +//Zcb-extension instruction +`define DEFINE_ZCMP_INSTR(instr_n, instr_format, instr_category, instr_group, imm_tp = IMM) \ + class riscv_``instr_n``_instr extends riscv_zcmp_instr; \ + `INSTR_BODY(instr_n, instr_format, instr_category, instr_group, imm_tp) diff --git a/src/riscv_directed_instr_lib.sv b/src/riscv_directed_instr_lib.sv index f3bc7080..7c3a54f7 100644 --- a/src/riscv_directed_instr_lib.sv +++ b/src/riscv_directed_instr_lib.sv @@ -474,7 +474,8 @@ class riscv_int_numeric_corner_stream extends riscv_directed_instr_stream; for (int i = 0; i < num_of_instr; i++) begin riscv_instr instr = riscv_instr::get_rand_instr( .include_category({ARITHMETIC}), - .exclude_group({RV32C, RV64C, RV32F, RV64F, RV32D, RV64D})); + .exclude_group({RV32C, RV64C, RV32ZCB, RV64ZCB, RV32ZCMP, RV64ZCMP, + RV32F, RV64F, RV32D, RV64D})); randomize_gpr(instr); instr_list.push_back(instr); end diff --git a/src/riscv_illegal_instr.sv b/src/riscv_illegal_instr.sv index 60ff4b54..d9977b81 100644 --- a/src/riscv_illegal_instr.sv +++ b/src/riscv_illegal_instr.sv @@ -213,6 +213,53 @@ class riscv_illegal_instr extends uvm_object; } } + constraint zcb_extension_c { + // zcb adds instructions to the reserved funct6 space 6'b100111 + // this constraint defines the subset of that op-code space which is still + // reserved. Only way to trigger this constraint should be with + // kReservedC0/C1 or by kIllegalCompressedOpcode. Since the reserved + // instructions at c_msb = 3'b100 && c_op = 2'b00 are not added to + // legal_c00_opcode this combination could occur. + if (RV32ZCB inside {supported_isa}) { + if (exception inside {kIllegalCompressedOpcode, kReservedCompressedInstr} + && c_msb == 3'b100) { + if (c_op == 2'b00) { + !(instr_bin[12:10] inside {3'b000, 3'b001, 3'b010, 3'b011}); //load/store + // bits 9:2 don't have any inavlid combinations, hence only bit 12:10 + // can yield an op-code which is reserved. + } else if (c_op == 2'b01 && instr_bin[12:10] == 3'b111) { + // if funct2 = 2'b10 there are no reserved op-codes + !(instr_bin[6:5] inside {2'b10}); //funct2 + if (instr_bin[6:5] == 2'b11) { + if (XLEN == 32){ + // c.zext.w not allowed in 32-bit mode + (instr_bin[4:2] inside {3'b100, 3'b110, 3'b111}); + } else { + // reserved opcodes + (instr_bin[4:2] inside {3'b110, 3'b111}); + } + } + } + } + } + } + + constraint zcmp_extension_c { + // zcmp adds instructions where funct3/c_msb = 3'b101 and c2/c_op = 2'b10 + // Those codes are legal if they match a valid Zcmp instruction + if (RV32ZCMP inside {supported_isa}) { + if (exception inside {kIllegalCompressedOpcode, kReservedCompressedInstr}) { + if (c_op == 2'b10 && c_msb == 3'b101) { + !(instr_bin[12:8] inside {5'b11000, 5'b11010, 5'b11100, 5'b11110}); // push/pop + if (instr_bin[12:10] == 3'b011) { + // double move instructions + !(instr_bin[6:5] inside {2'b01, 2'b11}); + } + } + } + } + } + constraint illegal_compressed_op_c { if (exception == kIllegalCompressedOpcode) { c_op != 2'b01; diff --git a/src/riscv_instr_cover_group.sv b/src/riscv_instr_cover_group.sv index badc423d..d704456e 100644 --- a/src/riscv_instr_cover_group.sv +++ b/src/riscv_instr_cover_group.sv @@ -38,6 +38,8 @@ `define SAMPLE_ZBB(cg, val) `SAMPLE_W_TYPE(cg, val, riscv_zbb_instr) `define SAMPLE_ZBC(cg, val) `SAMPLE_W_TYPE(cg, val, riscv_zbc_instr) `define SAMPLE_ZBS(cg, val) `SAMPLE_W_TYPE(cg, val, riscv_zbs_instr) +`define SAMPLE_ZCB(cg, val) `SAMPLE_W_TYPE(cg, val, riscv_zcb_instr) +`define SAMPLE_ZCMP(cg, val) `SAMPLE_W_TYPE(cg, val, riscv_zcmp_instr) `define INSTR_CG_BEGIN(INSTR_NAME, INSTR_CLASS = riscv_instr) \ covergroup ``INSTR_NAME``_cg with function sample(INSTR_CLASS instr); @@ -433,6 +435,20 @@ cp_rd : coverpoint instr.rd; \ `DV(cp_gpr_hazard : coverpoint instr.gpr_hazard;) \ +`define ZCB_I_INSTR_CG_BEGIN(INSTR_NAME) \ + `INSTR_CG_BEGIN(INSTR_NAME) \ + cp_rs1 : coverpoint instr.rs1 { \ + bins gpr[] = {S0, S1, A0, A1, A2, A3, A4, A5}; \ + } \ + `DV(cp_gpr_hazard : coverpoint instr.gpr_hazard;) + +`define ZCMP_I_INSTR_CG_BEGIN(INSTR_NAME) \ + `INSTR_CG_BEGIN(INSTR_NAME) \ + cp_rs1 : coverpoint instr.rs1 { \ + bins gpr[] = {S0, S1, A0, A1, A2, A3, A4, A5}; \ + } \ + `DV(cp_gpr_hazard : coverpoint instr.gpr_hazard;) + `define B_I_INSTR_CG_BEGIN(INSTR_NAME) \ `INSTR_CG_BEGIN(INSTR_NAME, riscv_b_instr) \ cp_rs1 : coverpoint instr.rs1; \ @@ -1646,6 +1662,64 @@ class riscv_instr_cover_group; `CA_INSTR_CG_BEGIN(c_addw) `CG_END + // RV32ZCB + `CL_INSTR_CG_BEGIN(c_lbu) + `CG_END + + `CL_INSTR_CG_BEGIN(c_lhu) + `CG_END + + `CL_INSTR_CG_BEGIN(c_lh) + `CG_END + + `CS_INSTR_CG_BEGIN(c_sb) + `CG_END + + `CS_INSTR_CG_BEGIN(c_sh) + `CG_END + + // Similar to zbb with the exception of which registers can be used + `ZCB_I_INSTR_CG_BEGIN(c_zext_b) + `CG_END + + `ZCB_I_INSTR_CG_BEGIN(c_sext_b) + `CG_END + + `ZCB_I_INSTR_CG_BEGIN(c_zext_h) + `CG_END + + `ZCB_I_INSTR_CG_BEGIN(c_sext_h) + `CG_END + + `ZCB_I_INSTR_CG_BEGIN(c_not) + `CG_END + + `CA_INSTR_CG_BEGIN(c_mul) + `CG_END + + // RV64ZCB + `ZCB_I_INSTR_CG_BEGIN(c_zext_w) + `CG_END + + // TODO This needs more thought and probably different defines per type + `ZCMP_I_INSTR_CG_BEGIN(cm_push) + `CG_END + + `ZCMP_I_INSTR_CG_BEGIN(cm_pop) + `CG_END + + `ZCMP_I_INSTR_CG_BEGIN(cm_popretz) + `CG_END + + `ZCMP_I_INSTR_CG_BEGIN(cm_popret) + `CG_END + + `ZCMP_I_INSTR_CG_BEGIN(cm_mva01s) + `CG_END + + `ZCMP_I_INSTR_CG_BEGIN(cm_mvsa01) + `CG_END + `INSTR_CG_BEGIN(hint) cp_hint : coverpoint instr.binary[15:0] { wildcard bins addi = {16'b0000_1xxx_x000_0001, @@ -2071,6 +2145,29 @@ class riscv_instr_cover_group; bseti_cg = new(); `CG_SELECTOR_END + `CG_SELECTOR_BEGIN(RV32ZCB) + c_lbu_cg = new(); + c_lhu_cg = new(); + c_lh_cg = new(); + c_sb_cg = new(); + c_sh_cg = new(); + c_zext_b_cg = new(); + c_sext_b_cg = new(); + c_zext_h_cg = new(); + c_sext_h_cg = new(); + c_not_cg = new(); + c_mul_cg = new(); + `CG_SELECTOR_END + + `CG_SELECTOR_BEGIN(RV32ZCMP) + cm_push_cg = new(); + cm_pop_cg = new(); + cm_popretz_cg = new(); + cm_popret_cg = new(); + cm_mva01s_cg = new(); + cm_mvsa01_cg = new(); + `CG_SELECTOR_END + `CG_SELECTOR_BEGIN(RV32B) pack_cg = new(); packh_cg = new(); @@ -2118,6 +2215,10 @@ class riscv_instr_cover_group; roriw_cg = new(); `CG_SELECTOR_END + `CG_SELECTOR_BEGIN(RV64ZCB) + c_zext_w_cg = new(); + `CG_SELECTOR_END + `CG_SELECTOR_BEGIN(RV64B) packw_cg = new(); packuw_cg = new(); @@ -2396,6 +2497,25 @@ class riscv_instr_cover_group; BINVI : `SAMPLE_ZBS(binvi_cg, instr) BSET : `SAMPLE_ZBS(bset_cg, instr) BSETI : `SAMPLE_ZBS(bseti_cg, instr) + // RV32ZCB + C_LBU : `SAMPLE_ZCB(c_lbu_cg, instr) + C_LHU : `SAMPLE_ZCB(c_lhu_cg, instr) + C_LH : `SAMPLE_ZCB(c_lh_cg, instr) + C_SB : `SAMPLE_ZCB(c_sb_cg, instr) + C_SH : `SAMPLE_ZCB(c_sh_cg, instr) + C_ZEXT_B : `SAMPLE_ZCB(c_zext_b_cg, instr) + C_SEXT_B : `SAMPLE_ZCB(c_sext_b_cg, instr) + C_ZEXT_H : `SAMPLE_ZCB(c_zext_h_cg, instr) + C_SEXT_H : `SAMPLE_ZCB(c_sext_h_cg, instr) + C_NOT : `SAMPLE_ZCB(c_not_cg, instr) + C_MUL : `SAMPLE_ZCB(c_mul_cg, instr) + // RV32ZCMP + CM_PUSH : `SAMPLE_ZCMP(cm_push_cg, instr) + CM_POP : `SAMPLE_ZCMP(cm_pop_cg, instr) + CM_POPRETZ : `SAMPLE_ZCMP(cm_popretz_cg, instr) + CM_POPRET : `SAMPLE_ZCMP(cm_popret_cg, instr) + CM_MVA01S : `SAMPLE_ZCMP(cm_mva01s_cg, instr) + CM_MVSA01 : `SAMPLE_ZCMP(cm_mvsa01_cg, instr) // RV32B PACK : `SAMPLE_B(pack_cg, instr) PACKH : `SAMPLE_B(packh_cg, instr) @@ -2438,6 +2558,8 @@ class riscv_instr_cover_group; ROLW : `SAMPLE_ZBB(rolw_cg, instr) RORW : `SAMPLE_ZBB(rorw_cg, instr) RORIW : `SAMPLE_ZBB(roriw_cg, instr) + // RV64ZCB + C_ZEXT_W : `SAMPLE_ZCB(c_zext_w_cg, instr) // RV64B PACKW : `SAMPLE_B(packw_cg, instr) PACKUW : `SAMPLE_B(packuw_cg, instr) @@ -2544,8 +2666,8 @@ class riscv_instr_cover_group; if ((instr.group inside {supported_isa}) && (instr.group inside {RV32I, RV32M, RV64M, RV64I, RV32C, RV64C, RVV, RV64B, RV32B, - RV32ZBA, RV32ZBB, RV32ZBC, RV32ZBS, - RV64ZBA, RV64ZBB, RV64ZBC, RV64ZBS})) begin + RV32ZBA, RV32ZBB, RV32ZBC, RV32ZBS, RV32ZCB, RV32ZCMP, + RV64ZBA, RV64ZBB, RV64ZBC, RV64ZBS, RV64ZCB, RV64ZCMP})) begin if (((instr_name inside {URET}) && !support_umode_trap) || ((instr_name inside {SRET, SFENCE_VMA}) && !(SUPERVISOR_MODE inside {supported_privileged_mode})) || diff --git a/src/riscv_instr_gen_config.sv b/src/riscv_instr_gen_config.sv index 0712821e..4b7cab53 100644 --- a/src/riscv_instr_gen_config.sv +++ b/src/riscv_instr_gen_config.sv @@ -264,6 +264,8 @@ class riscv_instr_gen_config extends uvm_object; bit enable_zbb_extension; bit enable_zbc_extension; bit enable_zbs_extension; + bit enable_zcb_extension; + bit enable_zcmp_extension; b_ext_group_t enable_bitmanip_groups[] = {ZBB, ZBS, ZBP, ZBE, ZBF, ZBC, ZBR, ZBM, ZBT, ZB_TMP}; @@ -541,6 +543,8 @@ class riscv_instr_gen_config extends uvm_object; `uvm_field_int(enable_zbb_extension, UVM_DEFAULT) `uvm_field_int(enable_zbc_extension, UVM_DEFAULT) `uvm_field_int(enable_zbs_extension, UVM_DEFAULT) + `uvm_field_int(enable_zcb_extension, UVM_DEFAULT) + `uvm_field_int(enable_zcmp_extension, UVM_DEFAULT) `uvm_field_int(use_push_data_section, UVM_DEFAULT) `uvm_object_utils_end @@ -611,6 +615,8 @@ class riscv_instr_gen_config extends uvm_object; get_bool_arg_value("+enable_zbb_extension=", enable_zbb_extension); get_bool_arg_value("+enable_zbc_extension=", enable_zbc_extension); get_bool_arg_value("+enable_zbs_extension=", enable_zbs_extension); + get_bool_arg_value("+enable_zcb_extension=", enable_zcb_extension); + get_bool_arg_value("+enable_zcmp_extension=", enable_zcmp_extension); cmdline_enum_processor #(b_ext_group_t)::get_array_values("+enable_bitmanip_groups=", 1'b0, enable_bitmanip_groups); if(inst.get_arg_value("+boot_mode=", boot_mode_opts)) begin @@ -657,6 +663,16 @@ class riscv_instr_gen_config extends uvm_object; enable_zbs_extension = 0; end + + if (!((RV32ZCB inside {supported_isa}) || + (RV64ZCB inside {supported_isa}))) begin + enable_zcb_extension = 0; + end + + if (!((RV32ZCMP inside {supported_isa}) || + (RV64ZCMP inside {supported_isa}))) begin + enable_zcmp_extension = 0; + end vector_cfg = riscv_vector_cfg::type_id::create("vector_cfg"); pmp_cfg = riscv_pmp_cfg::type_id::create("pmp_cfg"); pmp_cfg.rand_mode(pmp_cfg.pmp_randomize); diff --git a/src/riscv_instr_pkg.sv b/src/riscv_instr_pkg.sv index 96207667..f3df5bc5 100644 --- a/src/riscv_instr_pkg.sv +++ b/src/riscv_instr_pkg.sv @@ -80,7 +80,7 @@ package riscv_instr_pkg; MACHINE_MODE = 2'b11 } privileged_mode_t; - typedef enum bit [4:0] { + typedef enum bit [5:0] { RV32I, RV64I, RV32M, @@ -108,6 +108,12 @@ package riscv_instr_pkg; RV64ZBB, RV64ZBC, RV64ZBS, + RV32ZCB, + RV64ZCB, + RV32ZCMP, + RV64ZCMP, + RV32ZMMUL, + RV64ZMMUL, RV32X, RV64X } riscv_instr_group_t; @@ -271,6 +277,27 @@ package riscv_instr_pkg; PACKW, PACKUW, XPERM_W, + // RV32ZCB + C_LBU, + C_LHU, + C_LH, + C_SB, + C_SH, + C_ZEXT_B, + C_SEXT_B, + C_ZEXT_H, + C_SEXT_H, + C_NOT, + C_MUL, + // RV64ZCB instructions + C_ZEXT_W, + // RV32ZCMP + CM_PUSH, + CM_POP, + CM_POPRETZ, + CM_POPRET, + CM_MVA01S, + CM_MVSA01, // RV32M instructions MUL, MULH, @@ -665,6 +692,8 @@ package riscv_instr_pkg; S2, S3, S4, S5, S6, S7, S8, S9, S10, S11, T3, T4, T5, T6 } riscv_reg_t; + typedef riscv_reg_t riscv_reglist_t[$]; + typedef enum bit [4:0] { FT0, FT1, FT2, FT3, FT4, FT5, FT6, FT7, FS0, FS1, FA0, FA1, FA2, FA3, FA4, FA5, FA6, FA7, FS2, FS3, FS4, FS5, FS6, FS7, FS8, FS9, FS10, FS11, FT8, FT9, FT10, FT11 @@ -693,6 +722,14 @@ package riscv_instr_pkg; CS_FORMAT, CSS_FORMAT, CIW_FORMAT, + // Zc compressed instruction format + CLB_FORMAT, + CSB_FORMAT, + CLH_FORMAT, + CSH_FORMAT, + CU_FORMAT, + CMMV_FORMAT, + CMPP_FORMAT, // Vector instruction format VSET_FORMAT, VA_FORMAT, @@ -1532,6 +1569,8 @@ package riscv_instr_pkg; typedef class riscv_zbb_instr; typedef class riscv_zbc_instr; typedef class riscv_zbs_instr; + typedef class riscv_zcb_instr; + typedef class riscv_zcmp_instr; typedef class riscv_b_instr; `include "riscv_instr_gen_config.sv" `include "isa/riscv_instr.sv" @@ -1557,6 +1596,11 @@ package riscv_instr_pkg; `include "isa/rv32zbb_instr.sv" `include "isa/rv32zbc_instr.sv" `include "isa/rv32zbs_instr.sv" + `include "isa/riscv_zcb_instr.sv" + `include "isa/rv32zcb_instr.sv" + `include "isa/rv64zcb_instr.sv" + `include "isa/riscv_zcmp_instr.sv" + `include "isa/rv32zcmp_instr.sv" `include "isa/rv32m_instr.sv" `include "isa/rv64a_instr.sv" `include "isa/rv64b_instr.sv" diff --git a/src/riscv_instr_stream.sv b/src/riscv_instr_stream.sv index 9e980da8..0a7f15fc 100644 --- a/src/riscv_instr_stream.sv +++ b/src/riscv_instr_stream.sv @@ -228,24 +228,36 @@ class riscv_rand_instr_stream extends riscv_instr_stream; end endfunction - function void randomize_instr(output riscv_instr instr, - input bit is_in_debug = 1'b0, - input bit disable_dist = 1'b0, - input riscv_instr_group_t include_group[$] = {}); - riscv_instr_name_t exclude_instr[]; + function void update_excluded_instr(ref riscv_instr_name_t exclude_instr[], + input bit is_in_debug = 1'b0); if ((SP inside {reserved_rd, cfg.reserved_regs}) || ((avail_regs.size() > 0) && !(SP inside {avail_regs}))) begin - exclude_instr = {C_ADDI4SPN, C_ADDI16SP, C_LWSP, C_LDSP}; + exclude_instr = {exclude_instr, C_ADDI4SPN, C_ADDI16SP, C_LWSP, C_LDSP}; end - // Post-process the allowed_instr and exclude_instr lists to handle - // adding ebreak instructions to the debug rom. + if ((A0 inside {reserved_rd, cfg.reserved_regs}) || + (A1 inside {reserved_rd, cfg.reserved_regs}) || + ((avail_regs.size() > 0) && (!(A0 inside {avail_regs}) || !(A1 inside {avail_regs})))) begin + // MVA01S instruction needs both A0 and A1 to be writable + exclude_instr = {exclude_instr, CM_MVA01S}; + end + // Post-process the exclude_instr lists to handle adding ebreak instructions to the debug rom. if (is_in_debug) begin - if (cfg.no_ebreak && cfg.enable_ebreak_in_debug_rom) begin - allowed_instr = {allowed_instr, EBREAK, C_EBREAK}; - end else if (!cfg.no_ebreak && !cfg.enable_ebreak_in_debug_rom) begin + if (!cfg.no_ebreak && !cfg.enable_ebreak_in_debug_rom) begin exclude_instr = {exclude_instr, EBREAK, C_EBREAK}; end end + endfunction + + function void randomize_instr(output riscv_instr instr, + input bit is_in_debug = 1'b0, + input bit disable_dist = 1'b0, + input riscv_instr_group_t include_group[$] = {}); + riscv_instr_name_t exclude_instr[]; + update_excluded_instr(exclude_instr, is_in_debug); + // Post-process the allowed_instr lists to handle adding ebreak instructions to the debug rom. + if (is_in_debug && cfg.no_ebreak && cfg.enable_ebreak_in_debug_rom) begin + allowed_instr = {allowed_instr, EBREAK, C_EBREAK}; + end instr = riscv_instr::get_rand_instr(.include_instr(allowed_instr), .exclude_instr(exclude_instr), .include_group(include_group)); @@ -256,30 +268,48 @@ class riscv_rand_instr_stream extends riscv_instr_stream; function void randomize_gpr(riscv_instr instr); `DV_CHECK_RANDOMIZE_WITH_FATAL(instr, if (avail_regs.size() > 0) { - if (has_rs1) { - rs1 inside {avail_regs}; - } - if (has_rs2) { - rs2 inside {avail_regs}; - } - if (has_rd) { - rd inside {avail_regs}; + if (format == CMMV_FORMAT) { + if (has_rs1 && has_rs2) { + rs2 != rs1; + } + rs1 inside {S0, S1, [S2:S7]}; + rs2 inside {S0, S1, [S2:S7]}; + } else { + if (has_rs1) { + rs1 inside {avail_regs}; + } + if (has_rs2) { + rs2 inside {avail_regs}; + } + if (has_rd) { + rd inside {avail_regs}; + } } } foreach (reserved_rd[i]) { if (has_rd) { rd != reserved_rd[i]; } - if (format == CB_FORMAT) { + if (instr_name == CM_MVSA01) { rs1 != reserved_rd[i]; + rs2 != reserved_rd[i]; + } + if (instr_name == CM_MVA01S) { + A0 != reserved_rd[i]; + A1 != reserved_rd[i]; } } foreach (cfg.reserved_regs[i]) { if (has_rd) { rd != cfg.reserved_regs[i]; } - if (format == CB_FORMAT) { + if (instr_name == CM_MVSA01) { rs1 != cfg.reserved_regs[i]; + rs2 != cfg.reserved_regs[i]; + } + if (instr_name == CM_MVA01S) { + A0 != cfg.reserved_regs[i]; + A1 != cfg.reserved_regs[i]; } } // TODO: Add constraint for CSR, floating point register diff --git a/src/riscv_load_store_instr_lib.sv b/src/riscv_load_store_instr_lib.sv index cae08731..6dcbbfdd 100644 --- a/src/riscv_load_store_instr_lib.sv +++ b/src/riscv_load_store_instr_lib.sv @@ -119,19 +119,33 @@ class riscv_load_store_base_instr_stream extends riscv_mem_access_stream; // Generate each load/store instruction virtual function void gen_load_store_instr(); - bit enable_compressed_load_store; + bit enable_compressed_load_store, enable_zcb; riscv_instr instr; randomize_avail_regs(); if ((rs1_reg inside {[S0 : A5], SP}) && !cfg.disable_compressed_instr) begin enable_compressed_load_store = 1; end + if ((RV32C inside {riscv_instr_pkg::supported_isa}) && + (RV32ZCB inside {riscv_instr_pkg::supported_isa} && cfg.enable_zcb_extension)) begin + enable_zcb = 1; + end foreach (addr[i]) begin // Assign the allowed load/store instructions based on address alignment // This is done separately rather than a constraint to improve the randomization performance allowed_instr = {LB, LBU, SB}; + if((offset[i] inside {[0:2]}) && enable_compressed_load_store && + enable_zcb && rs1_reg != SP) begin + `uvm_info(`gfn, "Add ZCB byte load/store to allowed instr", UVM_LOW) + allowed_instr = {C_LBU, C_SB}; + end if (!cfg.enable_unaligned_load_store) begin if (addr[i][0] == 1'b0) begin allowed_instr = {LH, LHU, SH, allowed_instr}; + if(((offset[i] == 0) || (offset[i] == 2)) && enable_compressed_load_store && + enable_zcb && rs1_reg != SP) begin + `uvm_info(`gfn, "Add ZCB half-word load/store to allowed instr", UVM_LOW) + allowed_instr = {C_LHU, C_LH, C_SH}; + end end if (addr[i] % 4 == 0) begin allowed_instr = {LW, SW, allowed_instr}; @@ -682,3 +696,269 @@ class riscv_vector_load_store_instr_stream extends riscv_mem_access_stream; endfunction endclass + +// Class to test Zcmp Push/Popret control flow chains +class riscv_zcmp_chain_instr_stream extends riscv_mem_access_stream; + // Number of push/pop blocks in the chain + rand int unsigned num_blocks; + // Execution order of these blocks + int unsigned block_order[]; + + // Track number of chains + static int unsigned chain_cnt = 0; + int unsigned chain_id; + + // Data page to use for stack operations + rand int base; + rand int unsigned data_page_id; + rand int unsigned max_load_store_offset; + + constraint addr_c { + solve data_page_id before max_load_store_offset; + solve max_load_store_offset before base; + data_page_id < max_data_page_id; + foreach (data_page[i]) { + if (i == data_page_id) {max_load_store_offset == data_page[i].size_in_bytes;} + } + base inside {[0 : max_load_store_offset - 1]}; + } + + constraint block_c {num_blocks inside {[1 : 50]};} + + function new(string name = ""); + super.new(name); + // Assign a unique ID to this specific stream instance + chain_id = chain_cnt++; + endfunction + + `uvm_object_utils(riscv_zcmp_chain_instr_stream) + + function void post_randomize(); + // SP cannot be modified by other instructions + if (!(SP inside {reserved_rd})) begin + reserved_rd = {reserved_rd, SP}; + end + setup_chain_order(); + + // Generate the chain of blocks + gen_zcmp_chain(); + // Ensure labels are preserved + foreach (instr_list[i]) begin + if (instr_list[i].label != "") instr_list[i].has_label = 1'b1; + else instr_list[i].has_label = 1'b0; + instr_list[i].atomic = 1'b1; + end + // Initizialize SP to point to a data page + add_rs1_init_la_instr(SP, data_page_id, 0); + // Don't call super here, because it will delete all labels + endfunction + + // Randomize the order in which blocks are executed + virtual function void setup_chain_order(); + block_order = new[num_blocks]; + foreach (block_order[i]) begin + block_order[i] = i; + end + block_order.shuffle(); + endfunction + + // Main generator function + virtual function void gen_zcmp_chain(); + riscv_instr instr; + riscv_instr popret_instr; + string prefix = $sformatf("zcmp_%0d", chain_id); + riscv_instr_name_t exclude_instr[]; + update_excluded_instr(exclude_instr); + + // Save all registers that will be overwritten by our upcoming push/pop chain + // A0 is overwritten by CM.POPRETZ, but cannot be saved by CM.PUSH/POP. + // Save A0 outside the main stack frame (e.g., at SP-120) + instr = riscv_instr::get_instr(ADDI); + instr.rd = SP; + instr.rs1 = SP; + instr.imm_str = "-16"; + instr_list.push_back(instr); + instr = riscv_instr::get_instr(XLEN == 32 ? SW : SD); + instr.rs1 = SP; + instr.rs2 = A0; + instr.imm_str = "0"; + instr_list.push_back(instr); + // Save all the remaining registers overwritten by pop + instr = riscv_instr::get_instr(CM_PUSH); + instr.stack_adj = -112; // Max stack frame + instr.rlist = 15; // All registers + instr_list.push_back(instr); + // Jump to the first block + instr = riscv_instr::get_instr(JAL); + instr.rd = ZERO; // Don't link, just jump + instr.imm_str = $sformatf("%s_%0d", prefix, block_order[0]); + instr.comment = "Bootstrap: Jump to first random block"; + instr_list.push_back(instr); + + // Generate the push/pop blocks + for (int i = 0; i < num_blocks; i++) begin + // Number of random instructions to insert between push/pop + int random_instructions; + // Labels to link blocks + string current_label = $sformatf("%s_%0d", prefix, i); + string next_label; + int next_block_idx = -1; + // Control the stack growth/shrinkage + // Total number of instructions (Push + Pop) + int num_steps; + // Stack size at each step + int stack_size[]; + int max_stack = max_load_store_offset; + int delta; + riscv_instr_name_t final_opcode; + + // Find which block comes after current block 'i' in the execution order + foreach (block_order[k]) begin + if (block_order[k] == i) begin + if (k < num_blocks - 1) begin + next_block_idx = block_order[k+1]; + next_label = $sformatf("%s_%0d", prefix, next_block_idx); + end else begin + // End of chain + next_label = $sformatf("%s_end", prefix); + end + end + end + + // Randomize the stack pointer's path and the number of steps + std::randomize(stack_size, num_steps) with { + // Number of push/pop steps per block + num_steps inside {[2:12]}; + // Include the initial zero depth + stack_size.size() == num_steps + 1; + // Start and stop at zero depth + stack_size[0] == 0; + stack_size[num_steps] == 0; + foreach (stack_size[i]) { + // Stay within stack region + stack_size[i] inside {[-max_stack:0]}; + // Transition Constraints + if (i > 0) { + // Must be 16-byte aligned steps + (stack_size[i] - stack_size[i-1]) % 16 == 0; + // Restrict adjustment size of single push/pop and avoid no-ops (diff==0) + (stack_size[i] - stack_size[i-1]) inside {[-112:-16], [16:112]}; + } + } + }; + + // Loop through all the steps and insert a push or pop instruction. Also sprinkle in some + // random instructions inbetween. Skip the very last pop, since that will be a popret we + // handle specially. + for (int i = 0; i < num_steps - 1; i++) begin + delta = stack_size[i+1] - stack_size[i]; + if (delta < 0) begin + // CM.PUSH (Stack Grows) + instr = riscv_instr::get_instr(CM_PUSH); + `DV_CHECK_RANDOMIZE_WITH_FATAL(instr, stack_adj == delta;) + end else begin + // CM.POP (Stack Shrinks) + instr = riscv_instr::get_instr(CM_POP); + `DV_CHECK_RANDOMIZE_WITH_FATAL(instr, stack_adj == delta;) + end + + // First instruction gets the label to jump to + if (i == 0) begin + instr.label = current_label; + end + + instr_list.push_back(instr); + + // Insert random instructions in between push and pops + random_instructions = $urandom_range(0, 5); + repeat (random_instructions) begin + instr = riscv_instr::get_rand_instr(.include_category({ARITHMETIC, LOGICAL, SHIFT, COMPARE + }), .exclude_instr(exclude_instr)); + `DV_CHECK_RANDOMIZE_WITH_FATAL(instr, + // CRITICAL: Do not touch SP! + if (cfg.reserved_regs.size() > 0 || reserved_rd.size() > 0) { + !(rd inside {cfg.reserved_regs, reserved_rd}); + }, + $sformatf("Cannot randomize instruction %s with constrained registers\n", + instr.convert2asm())) + instr_list.push_back(instr); + end + end + + // Last step is a POPRET/POPRETZ. Randomly choose which: + randcase + 1: final_opcode = CM_POPRET; + 1: final_opcode = CM_POPRETZ; + endcase + delta = stack_size[num_steps] - stack_size[num_steps-1]; + // Do not insert this popret into the instruction stream yet. First, we have to create a + // proper RA address to jump to. However, since we need to know where the POPRET will load the + // RA from (i.e., which rlist and stack_adj it uses), we have to randomize it first. + popret_instr = riscv_instr::get_instr(final_opcode); + `DV_CHECK_RANDOMIZE_WITH_FATAL(popret_instr, stack_adj == delta;) + + // Push the RA with the address of 'next_label' onto the stack at the correct location. The + // following instructiosn are "atomic" because we don't want other instructions to interleave + // between them. + if (next_label != "") begin + riscv_pseudo_instr la_instr_pseudo; + riscv_reg_t temp_reg; + int ra_offset_in_stack; + + // Calculate where RA will be read from by the POPRET instruction. + // For cm.pop {ra, s0-sN}, stack_adj: + // RA is usually at `[SP + stack_adj - XLEN/8*(rlist - 3)]` because we pop rlist-3 registers + // There is one exception, if rlist == 15 (all registers), we pop rlist-2 registers because + // it includes s10 and s11. So adjust accordingly. + ra_offset_in_stack = delta - XLEN / 8 * (popret_instr.rlist - 3); + if (popret_instr.rlist == 15) begin + ra_offset_in_stack = ra_offset_in_stack - XLEN / 8; + end + + // Get a temp reg that isn't reserved and not X0 + `DV_CHECK_STD_RANDOMIZE_WITH_FATAL( + temp_reg, !(temp_reg inside {cfg.reserved_regs, reserved_rd, ZERO});) + + // Load Address of the NEXT block + la_instr_pseudo = riscv_pseudo_instr::type_id::create("la_instr_pseudo"); + la_instr_pseudo.pseudo_instr_name = LA; + la_instr_pseudo.rd = temp_reg; + la_instr_pseudo.imm_str = next_label; + la_instr_pseudo.atomic = 1'b1; + instr_list.push_back(la_instr_pseudo); + + // Overwrite the RA slot on the stack + instr = riscv_instr::get_instr(XLEN == 32 ? SW : SD); + instr.rs1 = SP; + instr.rs2 = temp_reg; + instr.imm_str = $sformatf("%0d", ra_offset_in_stack); + instr.atomic = 1'b1; + instr.comment = "Overwrite saved RA with next block address"; + instr_list.push_back(instr); + end + + // Insert popret at the very end. We already randomized it earlier to place the RA at the correct address. + instr_list.push_back(popret_instr); + end + + // Endpoint of chain: Restore all registers overwritten by push/pop + instr = riscv_instr::get_instr(CM_POP); + instr.stack_adj = 112; // Max stack frame + instr.rlist = 15; // All registers + instr.label = $sformatf("%s_end", prefix); + instr.comment = "End of Zcmp Chain"; + instr_list.push_back(instr); + // Restore A0 from the stack + instr = riscv_instr::get_instr(XLEN == 32 ? LW : LD); + instr.rd = A0; + instr.rs1 = SP; + instr.imm_str = "0"; + instr_list.push_back(instr); + instr = riscv_instr::get_instr(ADDI); + instr.rd = SP; + instr.rs1 = SP; + instr.imm_str = "16"; + instr_list.push_back(instr); + endfunction + +endclass diff --git a/src/riscv_loop_instr.sv b/src/riscv_loop_instr.sv index c2fcecc6..507a5e75 100644 --- a/src/riscv_loop_instr.sv +++ b/src/riscv_loop_instr.sv @@ -113,7 +113,10 @@ class riscv_loop_instr extends riscv_rand_instr_stream; `uvm_object_new function void post_randomize(); + riscv_instr_name_t exclude_instr[]; reserved_rd = {loop_cnt_reg, loop_limit_reg}; + // Figure out which instructions we can no longer use after the loop regs are decided + update_excluded_instr(exclude_instr); // Generate instructions that mixed with the loop instructions initialize_instr_list(num_of_instr_in_loop); gen_instr(1'b1); @@ -144,10 +147,11 @@ class riscv_loop_instr extends riscv_rand_instr_stream; // Branch target instruction, can be anything loop_branch_target_instr[i] = riscv_instr::get_rand_instr( .include_category({ARITHMETIC, LOGICAL, COMPARE}), - .exclude_instr({C_ADDI16SP})); + .exclude_instr(exclude_instr)); `DV_CHECK_RANDOMIZE_WITH_FATAL(loop_branch_target_instr[i], - if (format == CB_FORMAT) { + if (instr_name == CM_MVSA01) { !(rs1 inside {reserved_rd, cfg.reserved_regs}); + !(rs2 inside {reserved_rd, cfg.reserved_regs}); } if (has_rd) { !(rd inside {reserved_rd, cfg.reserved_regs}); diff --git a/test/riscv_instr_cov_test.sv b/test/riscv_instr_cov_test.sv index 8d3ceff8..be4c5dff 100644 --- a/test/riscv_instr_cov_test.sv +++ b/test/riscv_instr_cov_test.sv @@ -133,8 +133,8 @@ class riscv_instr_cov_test extends uvm_test; instr = riscv_instr::get_instr(instr_name); if ((instr.group inside {RV32I, RV32M, RV32C, RV64I, RV64M, RV64C, RV32F, RV64F, RV32D, RV64D, RV32B, RV64B, - RV32ZBA, RV32ZBB, RV32ZBC, RV32ZBS, - RV64ZBA, RV64ZBB, RV64ZBC, RV64ZBS}) && + RV32ZBA, RV32ZBB, RV32ZBC, RV32ZBS, RV32ZCB, RV32ZCMP, + RV64ZBA, RV64ZBB, RV64ZBC, RV64ZBS, RV64ZCB, RV64ZCMP}) && (instr.group inside {supported_isa})) begin assign_trace_info_to_instr(instr); instr.pre_sample();