From 7f684185068b04c08baa3c05584f9d136b93054c Mon Sep 17 00:00:00 2001 From: Samuel Riedel Date: Thu, 18 Sep 2025 12:23:46 +0000 Subject: [PATCH 1/4] Implement the Zcmp extension --- src/isa/riscv_instr.sv | 46 ++++++- src/isa/riscv_instr_cov.svh | 16 +++ src/isa/riscv_zcmp_instr.sv | 225 ++++++++++++++++++++++++++++++++ src/isa/rv32zcmp_instr.sv | 24 ++++ src/riscv_asm_program_gen.sv | 2 +- src/riscv_defines.svh | 5 + src/riscv_directed_instr_lib.sv | 3 +- src/riscv_illegal_instr.sv | 16 +++ src/riscv_instr_cover_group.sv | 48 ++++++- src/riscv_instr_gen_config.sv | 8 ++ src/riscv_instr_pkg.sv | 16 +++ src/riscv_instr_stream.sv | 40 ++++-- test/riscv_instr_cov_test.sv | 4 +- 13 files changed, 438 insertions(+), 15 deletions(-) create mode 100644 src/isa/riscv_zcmp_instr.sv create mode 100644 src/isa/rv32zcmp_instr.sv diff --git a/src/isa/riscv_instr.sv b/src/isa/riscv_instr.sv index 8ef885e5..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, RV32ZCB, RV64ZCB})) && + (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 3fd3b223..54773de3 100644 --- a/src/isa/riscv_instr_cov.svh +++ b/src/isa/riscv_instr_cov.svh @@ -424,6 +424,22 @@ 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_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/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/riscv_asm_program_gen.sv b/src/riscv_asm_program_gen.sv index ed3be75d..fbcc97c3 100644 --- a/src/riscv_asm_program_gen.sv +++ b/src/riscv_asm_program_gen.sv @@ -459,7 +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 : ; // No Misa bit for Zc* 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 b6cb34ef..39cfd10e 100644 --- a/src/riscv_defines.svh +++ b/src/riscv_defines.svh @@ -134,3 +134,8 @@ `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 bafb1bca..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, RV32ZCB, RV64ZCB, 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 48233358..d9977b81 100644 --- a/src/riscv_illegal_instr.sv +++ b/src/riscv_illegal_instr.sv @@ -244,6 +244,22 @@ class riscv_illegal_instr extends uvm_object; } } + 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 1b652121..d704456e 100644 --- a/src/riscv_instr_cover_group.sv +++ b/src/riscv_instr_cover_group.sv @@ -39,6 +39,7 @@ `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); @@ -441,6 +442,13 @@ } \ `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; \ @@ -1692,6 +1700,26 @@ class riscv_instr_cover_group; // 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, @@ -2131,6 +2159,15 @@ class riscv_instr_cover_group; 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(); @@ -2472,6 +2509,13 @@ class riscv_instr_cover_group; 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) @@ -2622,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, RV32ZCB, - RV64ZBA, RV64ZBB, RV64ZBC, RV64ZBS, RV64ZCB})) 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 b5b8e73b..4b7cab53 100644 --- a/src/riscv_instr_gen_config.sv +++ b/src/riscv_instr_gen_config.sv @@ -265,6 +265,7 @@ class riscv_instr_gen_config extends uvm_object; 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}; @@ -543,6 +544,7 @@ class riscv_instr_gen_config extends uvm_object; `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 @@ -614,6 +616,7 @@ class riscv_instr_gen_config extends uvm_object; 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 @@ -665,6 +668,11 @@ class riscv_instr_gen_config extends uvm_object; (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 df29cfeb..f3df5bc5 100644 --- a/src/riscv_instr_pkg.sv +++ b/src/riscv_instr_pkg.sv @@ -110,6 +110,8 @@ package riscv_instr_pkg; RV64ZBS, RV32ZCB, RV64ZCB, + RV32ZCMP, + RV64ZCMP, RV32ZMMUL, RV64ZMMUL, RV32X, @@ -289,6 +291,13 @@ package riscv_instr_pkg; C_MUL, // RV64ZCB instructions C_ZEXT_W, + // RV32ZCMP + CM_PUSH, + CM_POP, + CM_POPRETZ, + CM_POPRET, + CM_MVA01S, + CM_MVSA01, // RV32M instructions MUL, MULH, @@ -683,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 @@ -717,6 +728,8 @@ package riscv_instr_pkg; CLH_FORMAT, CSH_FORMAT, CU_FORMAT, + CMMV_FORMAT, + CMPP_FORMAT, // Vector instruction format VSET_FORMAT, VA_FORMAT, @@ -1557,6 +1570,7 @@ package riscv_instr_pkg; 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" @@ -1585,6 +1599,8 @@ package riscv_instr_pkg; `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 b1703077..252b4b2e 100644 --- a/src/riscv_instr_stream.sv +++ b/src/riscv_instr_stream.sv @@ -262,25 +262,49 @@ 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 (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 (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/test/riscv_instr_cov_test.sv b/test/riscv_instr_cov_test.sv index b8f0370c..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, RV32ZCB, - RV64ZBA, RV64ZBB, RV64ZBC, RV64ZBS, RV64ZCB}) && + 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(); From f82f22eaaf36bb0b0ea2eab1af97e5a462777f4b Mon Sep 17 00:00:00 2001 From: Samuel Riedel Date: Fri, 19 Sep 2025 20:34:44 +0100 Subject: [PATCH 2/4] Fix register usage of CMMV instructions in loops Those registers overwrite registers other than the `rd` register. This can lead to infinite loops. This ensures that we don't use any loop registers for all registers modified by those instructions. --- src/riscv_loop_instr.sv | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/riscv_loop_instr.sv b/src/riscv_loop_instr.sv index 9cdc8216..507a5e75 100644 --- a/src/riscv_loop_instr.sv +++ b/src/riscv_loop_instr.sv @@ -149,6 +149,10 @@ class riscv_loop_instr extends riscv_rand_instr_stream; .include_category({ARITHMETIC, LOGICAL, COMPARE}), .exclude_instr(exclude_instr)); `DV_CHECK_RANDOMIZE_WITH_FATAL(loop_branch_target_instr[i], + 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}); }, "Cannot randomize branch target instruction") From 85e646b1fd3476d319f3dc8a6937c42116a7e3ec Mon Sep 17 00:00:00 2001 From: Samuel Riedel Date: Mon, 15 Dec 2025 12:40:29 +0000 Subject: [PATCH 3/4] Add dedicated CMMP test class Since the push/pop instructions have so many side effects, test them in a dedicated instruction stream. This class builds blocks of push and pop instructions, which operate withing a given stack size. Multiple such blocks are generated and linked in a random order. To jump between these blocks, we use the popret/popretz instructions. --- src/riscv_load_store_instr_lib.sv | 266 ++++++++++++++++++++++++++++++ 1 file changed, 266 insertions(+) diff --git a/src/riscv_load_store_instr_lib.sv b/src/riscv_load_store_instr_lib.sv index d822fd01..6dcbbfdd 100644 --- a/src/riscv_load_store_instr_lib.sv +++ b/src/riscv_load_store_instr_lib.sv @@ -696,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 From 42026d4e3b633da964b69f657a17353bd1481dab Mon Sep 17 00:00:00 2001 From: Samuel Riedel Date: Mon, 15 Dec 2025 09:16:27 +0000 Subject: [PATCH 4/4] Exclude cm.mva01s instruction if a0 or a1 are reserved --- src/riscv_instr_stream.sv | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/riscv_instr_stream.sv b/src/riscv_instr_stream.sv index 252b4b2e..0a7f15fc 100644 --- a/src/riscv_instr_stream.sv +++ b/src/riscv_instr_stream.sv @@ -234,6 +234,12 @@ class riscv_rand_instr_stream extends riscv_instr_stream; ((avail_regs.size() > 0) && !(SP inside {avail_regs}))) begin exclude_instr = {exclude_instr, C_ADDI4SPN, C_ADDI16SP, C_LWSP, C_LDSP}; end + 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