diff --git a/doc/01_overview/compliance.rst b/doc/01_overview/compliance.rst index bdb039cd8..f9436be88 100644 --- a/doc/01_overview/compliance.rst +++ b/doc/01_overview/compliance.rst @@ -47,6 +47,14 @@ In addition, the following instruction set extensions are available. - 2.0 - always enabled + * - **Zcb**: Simple Code-Size Saving Instructions + - 1.0.0 + - optional + + * - **Zcmp**: Push/Pop/Move Code-Size Saving Instructions + - 1.0.0 + - optional + * - **Smepmp** - PMP Enhancements for memory access and execution prevention on Machine mode - 1.0 - always enabled in configurations with PMP see :ref:`PMP Enhancements` diff --git a/doc/02_user/integration.rst b/doc/02_user/integration.rst index c002de571..5a16787a7 100644 --- a/doc/02_user/integration.rst +++ b/doc/02_user/integration.rst @@ -99,6 +99,7 @@ Instantiation Template .RV32E ( 0 ), .RV32M ( ibex_pkg::RV32MFast ), .RV32B ( ibex_pkg::RV32BNone ), + .RV32ZC ( ibex_pkg::RV32ZcbZcmp ), .RegFile ( ibex_pkg::RegFileFF ), .ICache ( 0 ), .ICacheECC ( 0 ), @@ -168,72 +169,78 @@ Instantiation Template Parameters ---------- -+------------------------------+---------------------+------------+-----------------------------------------------------------------------+ -| Name | Type/Range | Default | Description | -+==============================+=====================+============+=======================================================================+ -| ``PMPEnable`` | bit | 0 | Enable PMP support | -+------------------------------+---------------------+------------+-----------------------------------------------------------------------+ -| ``PMPGranularity`` | int (0..31) | 0 | Minimum granularity of PMP address matching | -+------------------------------+---------------------+------------+-----------------------------------------------------------------------+ -| ``PMPNumRegions`` | int (1..16) | 4 | Number implemented PMP regions (ignored if PMPEnable == 0) | -+------------------------------+---------------------+------------+-----------------------------------------------------------------------+ -| ``MHPMCounterNum`` | int (0..10) | 0 | Number of performance monitor event counters | -+------------------------------+---------------------+------------+-----------------------------------------------------------------------+ -| ``MHPMCounterWidth`` | int (64..1) | 40 | Bit width of performance monitor event counters | -+------------------------------+---------------------+------------+-----------------------------------------------------------------------+ -| ``RV32E`` | bit | 0 | RV32E mode enable (16 integer registers only) | -+------------------------------+---------------------+------------+-----------------------------------------------------------------------+ -| ``RV32M`` | ibex_pkg::rv32m_e | RV32MFast | M(ultiply) extension select: | -| | | | "ibex_pkg::RV32MNone": No M-extension | -| | | | "ibex_pkg::RV32MSlow": Slow multi-cycle multiplier, iterative divider | -| | | | "ibex_pkg::RV32MFast": 3-4 cycle multiplier, iterative divider | -| | | | "ibex_pkg::RV32MSingleCycle": 1-2 cycle multiplier, iterative divider | -+------------------------------+---------------------+------------+-----------------------------------------------------------------------+ -| ``RV32B`` | ibex_pkg::rv32b_e | RV32BNone | B(itmanipulation) extension select: | -| | | | "ibex_pkg::RV32BNone": No B-extension | -| | | | "ibex_pkg::RV32BBalanced": Sub-extensions Zba, Zbb, Zbs, Zbf and Zbt | -| | | | "ibex_pkg::RV32BOTEarlGrey": All sub-extensions except Zbe | -| | | | "ibex_pkg::RV32BFull": All sub-extensions | -+------------------------------+---------------------+------------+-----------------------------------------------------------------------+ -| ``RegFile`` | ibex_pkg::regfile_e | RegFileFF | Register file implementation select: | -| | | | "ibex_pkg::RegFileFF": Generic flip-flop-based register file | -| | | | "ibex_pkg::RegFileFPGA": Register file for FPGA targets | -| | | | "ibex_pkg::RegFileLatch": Latch-based register file for ASIC targets | -+------------------------------+---------------------+------------+-----------------------------------------------------------------------+ -| ``BranchTargetALU`` | bit | 0 | Enables branch target ALU removing a stall cycle from taken branches | -+------------------------------+---------------------+------------+-----------------------------------------------------------------------+ -| ``WritebackStage`` | bit | 0 | Enables third pipeline stage (writeback) improving performance of | -| | | | loads and stores | -+------------------------------+---------------------+------------+-----------------------------------------------------------------------+ -| ``ICache`` | bit | 0 | Enable instruction cache instead of prefetch buffer | -+------------------------------+---------------------+------------+-----------------------------------------------------------------------+ -| ``ICacheECC`` | bit | 0 | Enable SECDED ECC protection in ICache (if ICache == 1) | -+------------------------------+---------------------+------------+-----------------------------------------------------------------------+ -| ``ICacheScramble`` | bit | 0 | Enabling this parameter replaces tag and data RAMs of ICache with | -| | | | scrambling RAM primitives. | -+------------------------------+---------------------+------------+-----------------------------------------------------------------------+ -| ``BranchPrediction`` | bit | 0 | *EXPERIMENTAL* Enable Static branch prediction | -+------------------------------+---------------------+------------+-----------------------------------------------------------------------+ -| ``SecureIbex`` | bit | 0 | Enable various additional features targeting secure code execution. | -| | | | Note: SecureIbex == 1'b1 and RV32M == ibex_pkg::RV32MNone is an | -| | | | illegal combination. | -+------------------------------+---------------------+------------+-----------------------------------------------------------------------+ -| ``RndCnstLfsrSeed`` | lfsr_seed_t | see above | Set the starting seed of the LFSR used to generate dummy instructions | -| | | | (only relevant when SecureIbex == 1'b1) | -+------------------------------+---------------------+------------+-----------------------------------------------------------------------+ -| ``RndCnstLfsrPerm`` | lfsr_perm_t | see above | Set the permutation applied to the output of the LFSR used to | -| | | | generate dummy instructions (only relevant when SecureIbex == 1'b1) | -+------------------------------+---------------------+------------+-----------------------------------------------------------------------+ -| ``DbgTriggerEn`` | bit | 0 | Enable debug trigger support (one trigger only) | -+------------------------------+---------------------+------------+-----------------------------------------------------------------------+ -| ``DmBaseAddr`` | int | 0x1A110000 | Base address of the Debug Module | -+------------------------------+---------------------+------------+-----------------------------------------------------------------------+ -| ``DmAddrMask`` | int | 0x1A110000 | Address mask of the Debug Module | -+------------------------------+---------------------+------------+-----------------------------------------------------------------------+ -| ``DmHaltAddr`` | int | 0x1A110800 | Address to jump to when entering Debug Mode | -+------------------------------+---------------------+------------+-----------------------------------------------------------------------+ -| ``DmExceptionAddr`` | int | 0x1A110808 | Address to jump to when an exception occurs while in Debug Mode | -+------------------------------+---------------------+------------+-----------------------------------------------------------------------+ ++------------------------------+---------------------+-------------+-----------------------------------------------------------------------+ +| Name | Type/Range | Default | Description | ++==============================+=====================+=============+=======================================================================+ +| ``PMPEnable`` | bit | 0 | Enable PMP support | ++------------------------------+---------------------+-------------+-----------------------------------------------------------------------+ +| ``PMPGranularity`` | int (0..31) | 0 | Minimum granularity of PMP address matching | ++------------------------------+---------------------+-------------+-----------------------------------------------------------------------+ +| ``PMPNumRegions`` | int (1..16) | 4 | Number implemented PMP regions (ignored if PMPEnable == 0) | ++------------------------------+---------------------+-------------+-----------------------------------------------------------------------+ +| ``MHPMCounterNum`` | int (0..10) | 0 | Number of performance monitor event counters | ++------------------------------+---------------------+-------------+-----------------------------------------------------------------------+ +| ``MHPMCounterWidth`` | int (64..1) | 40 | Bit width of performance monitor event counters | ++------------------------------+---------------------+-------------+-----------------------------------------------------------------------+ +| ``RV32E`` | bit | 0 | RV32E mode enable (16 integer registers only) | ++------------------------------+---------------------+-------------+-----------------------------------------------------------------------+ +| ``RV32M`` | ibex_pkg::rv32m_e | RV32MFast | M(ultiply) extension select: | +| | | | "ibex_pkg::RV32MNone": No M-extension | +| | | | "ibex_pkg::RV32MSlow": Slow multi-cycle multiplier, iterative divider | +| | | | "ibex_pkg::RV32MFast": 3-4 cycle multiplier, iterative divider | +| | | | "ibex_pkg::RV32MSingleCycle": 1-2 cycle multiplier, iterative divider | ++------------------------------+---------------------+-------------+-----------------------------------------------------------------------+ +| ``RV32B`` | ibex_pkg::rv32b_e | RV32BNone | B(itmanipulation) extension select: | +| | | | "ibex_pkg::RV32BNone": No B-extension | +| | | | "ibex_pkg::RV32BBalanced": Sub-extensions Zba, Zbb, Zbs, Zbf and Zbt | +| | | | "ibex_pkg::RV32BOTEarlGrey": All sub-extensions except Zbe | +| | | | "ibex_pkg::RV32BFull": All sub-extensions | ++------------------------------+---------------------+-------------+-----------------------------------------------------------------------+ +| ``RV32ZC`` | ibex_pkg::rv32zc_e | RV32ZcbZcmp | Zc code-size saving extension select: | +| | | | "ibex_pkg::RV32ZcNone": No Zc-extensions | +| | | | "ibex_pkg::RV32Zcb": Zcb extension only | +| | | | "ibex_pkg::RV32Zcmp": Zcmp extension only | +| | | | "ibex_pkg::RV32ZcbZcmp": Both Zcb and Zcmp extensions | ++------------------------------+---------------------+-------------+-----------------------------------------------------------------------+ +| ``RegFile`` | ibex_pkg::regfile_e | RegFileFF | Register file implementation select: | +| | | | "ibex_pkg::RegFileFF": Generic flip-flop-based register file | +| | | | "ibex_pkg::RegFileFPGA": Register file for FPGA targets | +| | | | "ibex_pkg::RegFileLatch": Latch-based register file for ASIC targets | ++------------------------------+---------------------+-------------+-----------------------------------------------------------------------+ +| ``BranchTargetALU`` | bit | 0 | Enables branch target ALU removing a stall cycle from taken branches | ++------------------------------+---------------------+-------------+-----------------------------------------------------------------------+ +| ``WritebackStage`` | bit | 0 | Enables third pipeline stage (writeback) improving performance of | +| | | | loads and stores | ++------------------------------+---------------------+-------------+-----------------------------------------------------------------------+ +| ``ICache`` | bit | 0 | Enable instruction cache instead of prefetch buffer | ++------------------------------+---------------------+-------------+-----------------------------------------------------------------------+ +| ``ICacheECC`` | bit | 0 | Enable SECDED ECC protection in ICache (if ICache == 1) | ++------------------------------+---------------------+-------------+-----------------------------------------------------------------------+ +| ``ICacheScramble`` | bit | 0 | Enabling this parameter replaces tag and data RAMs of ICache with | +| | | | scrambling RAM primitives. | ++------------------------------+---------------------+-------------+-----------------------------------------------------------------------+ +| ``BranchPrediction`` | bit | 0 | *EXPERIMENTAL* Enable Static branch prediction | ++------------------------------+---------------------+-------------+-----------------------------------------------------------------------+ +| ``SecureIbex`` | bit | 0 | Enable various additional features targeting secure code execution. | +| | | | Note: SecureIbex == 1'b1 and RV32M == ibex_pkg::RV32MNone is an | +| | | | illegal combination. | ++------------------------------+---------------------+-------------+-----------------------------------------------------------------------+ +| ``RndCnstLfsrSeed`` | lfsr_seed_t | see above | Set the starting seed of the LFSR used to generate dummy instructions | +| | | | (only relevant when SecureIbex == 1'b1) | ++------------------------------+---------------------+-------------+-----------------------------------------------------------------------+ +| ``RndCnstLfsrPerm`` | lfsr_perm_t | see above | Set the permutation applied to the output of the LFSR used to | +| | | | generate dummy instructions (only relevant when SecureIbex == 1'b1) | ++------------------------------+---------------------+-------------+-----------------------------------------------------------------------+ +| ``DbgTriggerEn`` | bit | 0 | Enable debug trigger support (one trigger only) | ++------------------------------+---------------------+-------------+-----------------------------------------------------------------------+ +| ``DmBaseAddr`` | int | 0x1A110000 | Base address of the Debug Module | ++------------------------------+---------------------+-------------+-----------------------------------------------------------------------+ +| ``DmAddrMask`` | int | 0x1A110000 | Address mask of the Debug Module | ++------------------------------+---------------------+-------------+-----------------------------------------------------------------------+ +| ``DmHaltAddr`` | int | 0x1A110800 | Address to jump to when entering Debug Mode | ++------------------------------+---------------------+-------------+-----------------------------------------------------------------------+ +| ``DmExceptionAddr`` | int | 0x1A110808 | Address to jump to when an exception occurs while in Debug Mode | ++------------------------------+---------------------+-------------+-----------------------------------------------------------------------+ Any parameter marked *EXPERIMENTAL* when enabled is not verified to the same standard as the rest of the Ibex core. diff --git a/doc/03_reference/pipeline_details.rst b/doc/03_reference/pipeline_details.rst index e0d663430..91e57f74f 100644 --- a/doc/03_reference/pipeline_details.rst +++ b/doc/03_reference/pipeline_details.rst @@ -93,3 +93,13 @@ Read the description for more information. | | | jump (which does the required flushing) so it has the same | | | | stall characteristics (see above). | +-----------------------+--------------------------------------+-------------------------------------------------------------+ +| Zcmp Push/Pop | 2 - N | The cm.push/pop instructions as defined in 'Zcmp' of the | +| | | RISC-V specification. Internally they expand to multiple | +| | | register load/store operations combined with stack pointer | +| | | adjustments, so their latency corresponds to the total | +| | | number of instructions issued and the memory latency. | ++-----------------------+--------------------------------------+-------------------------------------------------------------+ +| Zcmp Move | 2 | The `cm.mvsa01` and `cm.mva01s` instruction as defined in | +| | | 'Zcmp' of the RISC-V specification. Internally they are | +| | | implemented as two `addi rd, rs1, 0` instructions. | ++-----------------------+--------------------------------------+-------------------------------------------------------------+ diff --git a/dv/cosim/cosim.h b/dv/cosim/cosim.h index 4a5c63c75..0b11f23b1 100644 --- a/dv/cosim/cosim.h +++ b/dv/cosim/cosim.h @@ -74,9 +74,12 @@ class Cosim { // In this case the instruction doesn't retire so no register write occurs (so // `write_reg` must be 0). // + // `expanded_insn` is the 32-bit instruction that is being expanded or zero. + // // Returns false if there are any errors; use `get_errors` to obtain details virtual bool step(uint32_t write_reg, uint32_t write_reg_data, uint32_t pc, - bool sync_trap, bool suppress_reg_write) = 0; + bool sync_trap, bool suppress_reg_write, + uint32_t expanded_insn) = 0; // When more than one of `set_mip`, `set_nmi` or `set_debug_req` is called // before `step` which one takes effect is chosen by the co-simulator. Which diff --git a/dv/cosim/cosim_dpi.cc b/dv/cosim/cosim_dpi.cc index 30a3da74d..983605fd3 100644 --- a/dv/cosim/cosim_dpi.cc +++ b/dv/cosim/cosim_dpi.cc @@ -12,11 +12,12 @@ int riscv_cosim_step(Cosim *cosim, const svBitVecVal *write_reg, const svBitVecVal *write_reg_data, const svBitVecVal *pc, - svBit sync_trap, svBit suppress_reg_write) { + svBit sync_trap, svBit suppress_reg_write, + const svBitVecVal *expanded_insn) { assert(cosim); return cosim->step(write_reg[0], write_reg_data[0], pc[0], sync_trap, - suppress_reg_write) + suppress_reg_write, expanded_insn[0]) ? 1 : 0; } diff --git a/dv/cosim/cosim_dpi.h b/dv/cosim/cosim_dpi.h index bbadbc5e3..2ba5f8834 100644 --- a/dv/cosim/cosim_dpi.h +++ b/dv/cosim/cosim_dpi.h @@ -16,7 +16,8 @@ extern "C" { int riscv_cosim_step(Cosim *cosim, const svBitVecVal *write_reg, const svBitVecVal *write_reg_data, const svBitVecVal *pc, - svBit sync_trap, svBit suppress_reg_write); + svBit sync_trap, svBit suppress_reg_write, + const svBitVecVal *expanded_insn); void riscv_cosim_set_mip(Cosim *cosim, const svBitVecVal *pre_mip, const svBitVecVal *post_mip); void riscv_cosim_set_nmi(Cosim *cosim, svBit nmi); diff --git a/dv/cosim/cosim_dpi.svh b/dv/cosim/cosim_dpi.svh index 35ecd3b8c..1464edbee 100644 --- a/dv/cosim/cosim_dpi.svh +++ b/dv/cosim/cosim_dpi.svh @@ -11,7 +11,8 @@ `define COSIM_DPI_SVH import "DPI-C" function int riscv_cosim_step(chandle cosim_handle, bit [4:0] write_reg, - bit [31:0] write_reg_data, bit [31:0] pc, bit sync_trap, bit suppress_reg_write); + bit [31:0] write_reg_data, bit [31:0] pc, bit sync_trap, bit suppress_reg_write, + bit [31:0] expanded_insn); import "DPI-C" function void riscv_cosim_set_mip(chandle cosim_handle, bit [31:0] pre_mip, bit [31:0] post_mip); import "DPI-C" function void riscv_cosim_set_nmi(chandle cosim_handle, bit nmi); diff --git a/dv/cosim/spike_cosim.cc b/dv/cosim/spike_cosim.cc index daa400709..1d36b3424 100644 --- a/dv/cosim/spike_cosim.cc +++ b/dv/cosim/spike_cosim.cc @@ -39,7 +39,8 @@ SpikeCosim::SpikeCosim(const std::string &isa_string, uint32_t start_pc, uint32_t pmp_num_regions, uint32_t pmp_granularity, uint32_t mhpm_counter_num, uint32_t dm_start_addr, uint32_t dm_end_addr) - : nmi_mode(false), pending_iside_error(false), insn_cnt(0) { + : nmi_mode(false), pending_iside_error(false), insn_cnt(0), + pending_expanded_insn(0) { FILE *log_file = nullptr; if (trace_log_path.length() != 0) { log = std::make_unique(trace_log_path.c_str()); @@ -170,7 +171,8 @@ bool SpikeCosim::backdoor_read_mem(uint32_t addr, size_t len, // processor, and when we call step() again we start executing in the new // context of the trap (trap handler, new MSTATUS, debug rom, etc. etc.) bool SpikeCosim::step(uint32_t write_reg, uint32_t write_reg_data, uint32_t pc, - bool sync_trap, bool suppress_reg_write) { + bool sync_trap, bool suppress_reg_write, + uint32_t expanded_insn) { assert(write_reg < 32); // The DUT has just produced an RVFI item @@ -214,7 +216,12 @@ bool SpikeCosim::step(uint32_t write_reg, uint32_t write_reg_data, uint32_t pc, // (If the current step causes a synchronous trap, it will be // recorded against the current pc) initial_spike_pc = (processor->get_state()->pc & 0xffffffff); - processor->step(1); + // Only step Spike if the current instruction is not an expanded one. Spike + // does not expand instructions, so we have to consume a single Spike step + // over multiple steps of Ibex. + if (pending_expanded_insn == 0) { + processor->step(1); + } // ISS // - If encountered an async trap, @@ -309,8 +316,16 @@ bool SpikeCosim::step(uint32_t write_reg, uint32_t write_reg_data, uint32_t pc, suppressed_write_reg_data); } - if (!check_retired_instr(write_reg, write_reg_data, pc, suppress_reg_write)) { - return false; + if (expanded_insn) { + if (!check_expanded_instr(write_reg, write_reg_data, pc, suppress_reg_write, + expanded_insn)) { + return false; + } + } else { + if (!check_retired_instr(write_reg, write_reg_data, pc, suppress_reg_write, + expanded_insn)) { + return false; + } } // Only increment insn_cnt and return true if there are no errors @@ -318,9 +333,85 @@ bool SpikeCosim::step(uint32_t write_reg, uint32_t write_reg_data, uint32_t pc, return true; } +bool SpikeCosim::check_expanded_instr(uint32_t write_reg, + uint32_t write_reg_data, uint32_t dut_pc, + bool suppress_reg_write, + uint32_t expanded_insn) { + // If this is the first step of an expanded instruction, set up our expectations. + if (!pending_expanded_insn) { + pending_expanded_insn = expanded_insn; + expanded_insn_pc = dut_pc; + expanded_reg_changes.clear(); + + // The ISS has just stepped once for the entire expanded instruction. + // We now collect all the GPR changes it produced and store them in a map + auto ®_changes = processor->get_state()->log_reg_write; + for (auto reg_change : reg_changes) { + // Check if it's a GPR write (type 0) + if ((reg_change.first & 0xf) == 0) { + uint32_t iss_write_reg = (reg_change.first >> 4) & 0x1f; + // Ignore writes to x0 + if (iss_write_reg == 0) { + continue; + } + uint32_t iss_write_data = reg_change.second.v[0]; + expanded_reg_changes[iss_write_reg] = iss_write_data; + } + } + } + + // TODO: We have to deal with suppressed writes here too + if (suppress_reg_write) { + return true; + } + + // If the DUT did not write a register, this is a valid micro-op (e.g., a + // memory access or intermediate calculation). There is nothing to check. + if (write_reg == 0) { + return true; + } + + // The DUT wrote a register. Check if it matches one of our expectations. + auto reg_change = expanded_reg_changes.find(write_reg); + + // DUT wrote, but ISS didn't + if (reg_change == expanded_reg_changes.end()) { + std::stringstream err_str; + err_str << "Expanded instruction at PC 0x" << std::hex << expanded_insn_pc + << ": DUT wrote to x" << std::dec << write_reg + << ", which was not an expected register write for this sequence."; + errors.emplace_back(err_str.str()); + return false; + } + + // The register index matches. Now check if the data matches. + uint32_t expected_data = reg_change->second; + if (write_reg_data != expected_data) { + std::stringstream err_str; + err_str << "Expanded instruction at PC 0x" << std::hex << expanded_insn_pc + << ": Data mismatch for register x" << std::dec << write_reg + << ". DUT wrote: 0x" << std::hex << write_reg_data + << ", but ISS expected: 0x" << expected_data; + errors.emplace_back(err_str.str()); + return false; + } + + // Match is perfect. Remove this from our set of expectations. + expanded_reg_changes.erase(reg_change); + + if(expanded_reg_changes.empty()) { + // All expected register writes have been matched. Clear the pending state. + pending_expanded_insn = 0; + expanded_insn_pc = 0; + } + + return true; +} + bool SpikeCosim::check_retired_instr(uint32_t write_reg, uint32_t write_reg_data, uint32_t dut_pc, - bool suppress_reg_write) { + bool suppress_reg_write, + uint32_t expanded_insn) { // Check the retired instruction and all of its side-effects match those from // the DUT @@ -333,6 +424,11 @@ bool SpikeCosim::check_retired_instr(uint32_t write_reg, << " , but the ISS retired: " << std::hex << (processor->get_state()->last_inst_pc & 0xffffffff); errors.emplace_back(err_str.str()); + if (pending_expanded_insn) { + err_str << " (while processing expanded instruction at PC 0x" + << std::hex << expanded_insn_pc << ")"; + errors.emplace_back(err_str.str()); + } return false; } @@ -355,7 +451,7 @@ bool SpikeCosim::check_retired_instr(uint32_t write_reg, assert(!gpr_write_seen); if (!suppress_reg_write && - !check_gpr_write(reg_change, write_reg, write_reg_data)) { + !check_gpr_write(reg_change, write_reg, write_reg_data, expanded_insn)) { return false; } @@ -433,7 +529,8 @@ bool SpikeCosim::check_sync_trap(uint32_t write_reg, uint32_t dut_pc, } bool SpikeCosim::check_gpr_write(const commit_log_reg_t::value_type ®_change, - uint32_t write_reg, uint32_t write_reg_data) { + uint32_t write_reg, uint32_t write_reg_data, + uint32_t expanded_insn) { uint32_t cosim_write_reg = (reg_change.first >> 4) & 0x1f; if (write_reg == 0) { diff --git a/dv/cosim/spike_cosim.h b/dv/cosim/spike_cosim.h index 68fd2204f..c32a8f54a 100644 --- a/dv/cosim/spike_cosim.h +++ b/dv/cosim/spike_cosim.h @@ -77,7 +77,8 @@ class SpikeCosim : public simif_t, public Cosim { bool check_debug_ebreak(uint32_t write_reg, uint32_t pc, bool sync_trap); bool check_gpr_write(const commit_log_reg_t::value_type ®_change, - uint32_t write_reg, uint32_t write_reg_data); + uint32_t write_reg, uint32_t write_reg_data, + uint32_t expanded_insn); bool check_suppress_reg_write(uint32_t write_reg, uint32_t pc, uint32_t &suppressed_write_reg); @@ -99,6 +100,11 @@ class SpikeCosim : public simif_t, public Cosim { unsigned int insn_cnt; + // Handle expanded insn + uint32_t pending_expanded_insn; + uint32_t expanded_insn_pc; + std::map expanded_reg_changes; + public: SpikeCosim(const std::string &isa_string, uint32_t start_pc, uint32_t start_mtvec, const std::string &trace_log_path, @@ -120,10 +126,15 @@ class SpikeCosim : public simif_t, public Cosim { const uint8_t *data_in) override; bool backdoor_read_mem(uint32_t addr, size_t len, uint8_t *data_out) override; bool step(uint32_t write_reg, uint32_t write_reg_data, uint32_t pc, - bool sync_trap, bool suppress_reg_write) override; + bool sync_trap, bool suppress_reg_write, + uint32_t expanded_insn) override; bool check_retired_instr(uint32_t write_reg, uint32_t write_reg_data, - uint32_t dut_pc, bool suppress_reg_write); + uint32_t dut_pc, bool suppress_reg_write, + uint32_t expanded_insn); + bool check_expanded_instr(uint32_t write_reg, uint32_t write_reg_data, + uint32_t dut_pc, bool suppress_reg_write, + uint32_t expanded_insn); bool check_sync_trap(uint32_t write_reg, uint32_t pc, uint32_t initial_spike_pc); void set_mip(uint32_t pre_mip, uint32_t post_mip) override; diff --git a/dv/uvm/core_ibex/common/ibex_cosim_agent/ibex_cosim_scoreboard.sv b/dv/uvm/core_ibex/common/ibex_cosim_agent/ibex_cosim_scoreboard.sv index 7998349d8..f92fed752 100644 --- a/dv/uvm/core_ibex/common/ibex_cosim_agent/ibex_cosim_scoreboard.sv +++ b/dv/uvm/core_ibex/common/ibex_cosim_agent/ibex_cosim_scoreboard.sv @@ -163,7 +163,7 @@ class ibex_cosim_scoreboard extends uvm_scoreboard; riscv_cosim_set_ic_scr_key_valid(cosim_handle, rvfi_instr.ic_scr_key_valid); if (!riscv_cosim_step(cosim_handle, rvfi_instr.rd_addr, rvfi_instr.rd_wdata, rvfi_instr.pc, - rvfi_instr.trap, rvfi_instr.rf_wr_suppress)) begin + rvfi_instr.trap, rvfi_instr.rf_wr_suppress, rvfi_instr.expanded_insn)) begin // cosim instruction step doesn't match rvfi captured instruction, report a fatal error // with the details if (cfg.relax_cosim_check) begin diff --git a/dv/uvm/core_ibex/common/ibex_cosim_agent/ibex_rvfi_monitor.sv b/dv/uvm/core_ibex/common/ibex_cosim_agent/ibex_rvfi_monitor.sv index 0bf50116e..a246adade 100644 --- a/dv/uvm/core_ibex/common/ibex_cosim_agent/ibex_rvfi_monitor.sv +++ b/dv/uvm/core_ibex/common/ibex_cosim_agent/ibex_rvfi_monitor.sv @@ -45,6 +45,7 @@ class ibex_rvfi_monitor extends uvm_monitor; trans_collected.rf_wr_suppress = vif.monitor_cb.ext_rf_wr_suppress; trans_collected.mcycle = vif.monitor_cb.ext_mcycle; trans_collected.ic_scr_key_valid = vif.monitor_cb.ext_ic_scr_key_valid; + trans_collected.expanded_insn = vif.monitor_cb.ext_expanded_insn; for (int i=0; i < 10; i++) begin trans_collected.mhpmcounters[i] = vif.monitor_cb.ext_mhpmcounters[i]; diff --git a/dv/uvm/core_ibex/common/ibex_cosim_agent/ibex_rvfi_seq_item.sv b/dv/uvm/core_ibex/common/ibex_cosim_agent/ibex_rvfi_seq_item.sv index dceba31c4..28e5ff6a6 100644 --- a/dv/uvm/core_ibex/common/ibex_cosim_agent/ibex_rvfi_seq_item.sv +++ b/dv/uvm/core_ibex/common/ibex_cosim_agent/ibex_rvfi_seq_item.sv @@ -21,6 +21,8 @@ class ibex_rvfi_seq_item extends uvm_sequence_item; bit [31:0] mhpmcountersh [10]; bit ic_scr_key_valid; + bit [31:0] expanded_insn; + `uvm_object_utils_begin(ibex_rvfi_seq_item) `uvm_field_int (trap, UVM_DEFAULT) `uvm_field_int (pc, UVM_DEFAULT) @@ -37,6 +39,7 @@ class ibex_rvfi_seq_item extends uvm_sequence_item; `uvm_field_sarray_int (mhpmcounters, UVM_DEFAULT) `uvm_field_sarray_int (mhpmcountersh, UVM_DEFAULT) `uvm_field_int (ic_scr_key_valid, UVM_DEFAULT) + `uvm_field_int (expanded_insn, UVM_DEFAULT) `uvm_object_utils_end `uvm_object_new diff --git a/dv/uvm/core_ibex/env/core_ibex_rvfi_if.sv b/dv/uvm/core_ibex/env/core_ibex_rvfi_if.sv index 0199b87f7..68c0dbd90 100644 --- a/dv/uvm/core_ibex/env/core_ibex_rvfi_if.sv +++ b/dv/uvm/core_ibex/env/core_ibex_rvfi_if.sv @@ -40,6 +40,8 @@ interface core_ibex_rvfi_if(input logic clk); logic ext_ic_scr_key_valid; + logic [31:0] ext_expanded_insn; + clocking monitor_cb @(posedge clk); input reset; input valid; @@ -74,6 +76,7 @@ interface core_ibex_rvfi_if(input logic clk); input ext_mhpmcountersh; input ext_ic_scr_key_valid; input ext_irq_valid; + input ext_expanded_insn; endclocking task automatic wait_clks(input int num); diff --git a/dv/uvm/core_ibex/riscv_dv_extension/riscv_core_setting.tpl.sv b/dv/uvm/core_ibex/riscv_dv_extension/riscv_core_setting.tpl.sv index fff0a1af1..c64ce3023 100644 --- a/dv/uvm/core_ibex/riscv_dv_extension/riscv_core_setting.tpl.sv +++ b/dv/uvm/core_ibex/riscv_dv_extension/riscv_core_setting.tpl.sv @@ -53,7 +53,7 @@ bit support_unaligned_load_store = 1'b1; // ISA supported by the processor // TODO: Determine how Ibex RV32B types map to RISCV-DV ISA names -riscv_instr_group_t supported_isa[$] = {RV32I, RV32M, RV32C +riscv_instr_group_t supported_isa[$] = {RV32I, RV32M, RV32C, RV32ZCB, RV32ZCMP % if ibex_config['RV32B'] == 'ibex_pkg::RV32BNone': }; % else: diff --git a/dv/uvm/core_ibex/riscv_dv_extension/testlist.yaml b/dv/uvm/core_ibex/riscv_dv_extension/testlist.yaml index 8c6516650..a7474d22c 100644 --- a/dv/uvm/core_ibex/riscv_dv_extension/testlist.yaml +++ b/dv/uvm/core_ibex/riscv_dv_extension/testlist.yaml @@ -1157,3 +1157,13 @@ rtl_test: core_ibex_base_test rtl_params: RV32B: ["ibex_pkg::RV32BFull", "ibex_pkg::RV32BOTEarlGrey", "ibex_pkg::RV32BBalanced"] + +- test: riscv_zcb_balanced_test + desc: > + Random instruction test with zcb instructions in balanced configuration + iterations: 10 + gen_test: riscv_rand_instr_test + gen_opts: > + +enable_zcb_extension=1 + rtl_test: core_ibex_base_test + diff --git a/dv/uvm/core_ibex/scripts/ibex_cmd.py b/dv/uvm/core_ibex/scripts/ibex_cmd.py index 0d8c5346e..21c84b7b4 100644 --- a/dv/uvm/core_ibex/scripts/ibex_cmd.py +++ b/dv/uvm/core_ibex/scripts/ibex_cmd.py @@ -114,8 +114,9 @@ def get_isas_for_config(cfg: Config) -> Tuple[str, str]: has_bitmanip = cfg.rv32b != 'ibex_pkg::RV32BNone' toolchain_isa = base_isa + ('b' if has_bitmanip else '') + toolchain_isa = toolchain_isa + ('_zicsr_zifencei_zcb_zcmp') - return (toolchain_isa, '_'.join([base_isa] + bitmanip_isa)) + return (toolchain_isa, '_'.join([base_isa] + ['Zicsr','Zifencei','Zcb','Zcmp'] + bitmanip_isa)) _TestEntry = Dict[str, object] diff --git a/dv/uvm/core_ibex/tb/core_ibex_tb_top.sv b/dv/uvm/core_ibex/tb/core_ibex_tb_top.sv index 6e1199e8f..0c692b203 100644 --- a/dv/uvm/core_ibex/tb/core_ibex_tb_top.sv +++ b/dv/uvm/core_ibex/tb/core_ibex_tb_top.sv @@ -239,6 +239,7 @@ module core_ibex_tb_top; assign rvfi_if.ext_mhpmcountersh = dut.rvfi_ext_mhpmcountersh; assign rvfi_if.ext_ic_scr_key_valid = dut.rvfi_ext_ic_scr_key_valid; assign rvfi_if.ext_irq_valid = dut.rvfi_ext_irq_valid; + assign rvfi_if.ext_expanded_insn = dut.rvfi_ext_expanded_insn; // Irq interface connections assign irq_vif.reset = ~rst_n; // Dut_if interface connections diff --git a/dv/uvm/core_ibex/tests/core_ibex_base_test.sv b/dv/uvm/core_ibex/tests/core_ibex_base_test.sv index 47847ad67..668843b0e 100644 --- a/dv/uvm/core_ibex/tests/core_ibex_base_test.sv +++ b/dv/uvm/core_ibex/tests/core_ibex_base_test.sv @@ -70,6 +70,10 @@ class core_ibex_base_test extends uvm_test; isa = {"rv32", RV32E ? "e" : "i"}; if (RV32M != RV32MNone) isa = {isa, "m"}; isa = {isa, "c"}; + isa = {isa, "_Zicsr"}; + isa = {isa, "_Zifencei"}; + isa = {isa, "_Zcb"}; + isa = {isa, "_Zcmp"}; case (RV32B) RV32BNone: ; diff --git a/dv/verilator/simple_system_cosim/ibex_simple_system_cosim_checker.sv b/dv/verilator/simple_system_cosim/ibex_simple_system_cosim_checker.sv index 3d9741797..0d2c27f8a 100644 --- a/dv/verilator/simple_system_cosim/ibex_simple_system_cosim_checker.sv +++ b/dv/verilator/simple_system_cosim/ibex_simple_system_cosim_checker.sv @@ -63,7 +63,7 @@ module ibex_simple_system_cosim_checker #( if (riscv_cosim_step(cosim_handle, u_top.rvfi_rd_addr, u_top.rvfi_rd_wdata, u_top.rvfi_pc_rdata, u_top.rvfi_trap, - u_top.rvfi_ext_rf_wr_suppress) == 0) + u_top.rvfi_ext_rf_wr_suppress, u_top.rvfi_ext_expanded_insn) == 0) begin $display("FAILURE: Co-simulation mismatch at time %t", $time()); for (int i = 0;i < riscv_cosim_get_num_errors(cosim_handle); ++i) begin diff --git a/rtl/ibex_compressed_decoder.sv b/rtl/ibex_compressed_decoder.sv index e3c6aa52c..5e2c90e5d 100644 --- a/rtl/ibex_compressed_decoder.sv +++ b/rtl/ibex_compressed_decoder.sv @@ -13,14 +13,18 @@ `include "prim_assert.sv" -module ibex_compressed_decoder ( - input logic clk_i, - input logic rst_ni, - input logic valid_i, - input logic [31:0] instr_i, - output logic [31:0] instr_o, - output logic is_compressed_o, - output logic illegal_instr_o +module ibex_compressed_decoder #( + parameter ibex_pkg::rv32zc_e RV32ZC = ibex_pkg::RV32ZcbZcmp +) ( + input logic clk_i, + input logic rst_ni, + input logic valid_i, + input logic id_in_ready_i, + input logic [31:0] instr_i, + output logic [31:0] instr_o, + output logic is_compressed_o, + output ibex_pkg::instr_exp_e gets_expanded_o, + output logic illegal_instr_o ); import ibex_pkg::*; @@ -29,14 +33,162 @@ module ibex_compressed_decoder ( logic unused_valid; assign unused_valid = valid_i; + function automatic logic [6:0] cm_stack_adj_base(input logic [3:0] rlist); + unique case (rlist) + // Deliberately not written as `case .. inside` because that is not supported by all tools. + 4'd4, 4'd5, 4'd6, 4'd7: return 7'd16; + 4'd8, 4'd9, 4'd10, 4'd11: return 7'd32; + 4'd12, 4'd13, 4'd14: return 7'd48; + 4'd15: return 7'd64; + default: return 7'd0; // illegal + endcase + endfunction + + function automatic logic [6:0] cm_stack_adj(input logic [3:0] rlist, input logic [1:0] spimm); + return cm_stack_adj_base(rlist) + spimm * 16; + endfunction + + function automatic logic [4:0] cm_stack_adj_word(input logic [3:0] rlist, + input logic [1:0] spimm); + logic [6:0] tmp; + logic [1:0] _unused; + tmp = cm_stack_adj(.rlist(rlist), .spimm(spimm)); + _unused = tmp[1:0]; + return tmp[6:2]; + endfunction + + function automatic logic [4:0] cm_rlist_top_reg(input logic [4:0] rlist); + unique case (rlist) + // Deliberately not written as `case .. inside` because that is not supported by all tools. + 5'd16, // `rlist` can be 16 after instruction decoding, to handle `x26`+`x27`. + 5'd15, 5'd14, 5'd13, + 5'd12, 5'd11, 5'd10, + 5'd9, 5'd8, 5'd7: return 5'd11 + rlist; + 5'd6, 5'd5: return 5'd3 + rlist; + 5'd4: return 5'd1; + default: return 5'd0; // illegal + endcase + endfunction + + function automatic logic [31:0] cm_push_store_reg(input logic [4:0] rlist, + input logic [4:0] sp_offset); + logic [11:0] neg_offset; + logic [31:0] instr; + neg_offset = ~{5'b00000, sp_offset, 2'b00} + 12'd1; + instr[ 6: 0] /* opcode */ = OPCODE_STORE; + instr[11: 7] /* offset[4:0] */ = neg_offset[4:0]; + instr[14:12] /* width */ = 3'b010; // 32 bit + instr[19:15] /* base reg */ = 5'd2; // x2 (sp / stack pointer) + instr[24:20] /* src reg */ = cm_rlist_top_reg(rlist); + instr[31:25] /* offset[11:5] */ = neg_offset[11:5]; + return instr; + endfunction + + function automatic logic [31:0] cm_pop_load_reg(input logic [4:0] rlist, + input logic [4:0] sp_offset); + logic [31:0] instr; + instr[ 6: 0] /* opcode */ = OPCODE_LOAD; + instr[11: 7] /* dest reg */ = cm_rlist_top_reg(rlist); + instr[14:12] /* width */ = 3'b010; // 32 bit + instr[19:15] /* base reg */ = 5'd2; // x2 (sp / stack pointer) + instr[31:20] /* offset[11:0] */ = {5'b00000, sp_offset, 2'b00}; + return instr; + endfunction + + function automatic logic [31:0] cm_sp_addi(input logic [3:0] rlist, + input logic [1:0] spimm, + input logic decr = 1'b0); + logic [11:0] imm; + logic [31:0] instr; + imm[11:7] = '0; + imm[ 6:0] = cm_stack_adj(.rlist(rlist), .spimm(spimm)); + if (decr) imm = ~imm + 12'd1; + instr[ 6: 0] /* opcode */ = OPCODE_OP_IMM; + instr[11: 7] /* dest reg */ = 5'd2; // x2 (sp / stack pointer) + instr[14:12] /* funct3 */ = 3'b000; // addi + instr[19:15] /* src reg */ = 5'd2; // x2 + instr[31:20] /* imm[11:0] */ = imm; + return instr; + endfunction + + function automatic logic [31:0] cm_mv_reg(input logic [4:0] src, input logic [4:0] dst); + logic [31:0] instr; + instr[ 6: 0] /* opcode */ = OPCODE_OP_IMM; + instr[11: 7] /* dest reg */ = dst; + instr[14:12] /* funct3 */ = 3'b000; // addi + instr[19:15] /* src reg */ = src; + instr[31:20] /* imm[11:0] */ = 12'd0; // 0 + return instr; + endfunction + + function automatic logic [31:0] cm_zero_a0(); + return cm_mv_reg(.src(5'd0 /* x0 */), .dst(5'd10 /* a0 */)); + endfunction + + function automatic logic [31:0] cm_ret_ra(); + logic [31:0] instr; + instr[ 6: 0] /* opcode */ = OPCODE_JALR; + instr[11: 7] /* dest reg */ = 5'd0; // x0 + instr[14:12] /* funct3 */ = 3'b000; // jalr + instr[19:15] /* base reg */ = 5'd1; // x1 (ra) + instr[31:20] /* offset[11:0] */ = 12'd0; // 0 + return instr; + endfunction + + function automatic logic [31:0] cm_mvsa01(input logic a01, input logic [2:0] rs); + logic [4:0] src, dst; + src = 5'd10 + {4'd0, a01}; + dst = {(rs[2:1] > 2'd0), (rs[2:1] == 2'd0), rs[2:0]}; + return cm_mv_reg(.src(src), .dst(dst)); + endfunction + + function automatic logic [31:0] cm_mva01s(input logic [2:0] rs, input logic a01); + logic [4:0] src, dst; + src = {(rs[2:1] > 2'd0), (rs[2:1] == 2'd0), rs[2:0]}; + dst = 5'd10 + {4'd0, a01}; + return cm_mv_reg(.src(src), .dst(dst)); + endfunction + + function automatic logic [4:0] cm_rlist_init(input logic [3:0] instr_rlist); + logic [4:0] rlist; + rlist = {1'b0, instr_rlist}; + if (rlist == 5'd15) begin + // An `rlist` value of 15 means that x26 and x27 have to be stored. + // Handle this by initializing `rlist` internally to 16. + rlist = 5'd16; + end + return rlist; + endfunction + + typedef enum logic [3:0] { + CmIdle, + CmPushStoreReg, + CmPushDecrSp, + CmPopLoadReg, + CmPopIncrSp, + CmPopZeroA0, + CmPopRetRa, + CmMvSA1, + CmMvA1S + } cm_state_e; + logic [4:0] cm_rlist_d, cm_rlist_q; + logic [4:0] cm_sp_offset_d, cm_sp_offset_q; + cm_state_e cm_state_d, cm_state_q; + //////////////////////// // Compressed decoder // //////////////////////// always_comb begin - // By default, forward incoming instruction, mark it as legal. + // By default, forward incoming instruction, mark it as legal, and don't expand. instr_o = instr_i; illegal_instr_o = 1'b0; + gets_expanded_o = INSTR_NOT_EXPANDED; + + // Maintain state of CM FSM. + cm_rlist_d = cm_rlist_q; + cm_sp_offset_d = cm_sp_offset_q; + cm_state_d = cm_state_q; // Check if incoming instruction is compressed. unique case (instr_i[1:0]) @@ -63,9 +215,69 @@ module ibex_compressed_decoder ( 2'b00, {OPCODE_STORE}}; end + 3'b100: begin // loads and stores + if (RV32ZC == RV32ZcbZcmp || RV32ZC == RV32Zcb) begin + unique case (instr_i[12:10]) + 3'b000: begin + // c.lbu -> lbu rd', imm(rs1') + instr_o = {10'b0, instr_i[5], instr_i[6], 2'b01, instr_i[9:7], + 3'b100, 2'b01, instr_i[4:2], {OPCODE_LOAD}}; + end + + 3'b001: begin + unique case (instr_i[6]) + 1'b0: begin + // c.lhu -> lhu rd', imm(rs1') + instr_o = {10'b0, instr_i[5], 1'b0, 2'b01, instr_i[9:7], + 3'b101, 2'b01, instr_i[4:2], {OPCODE_LOAD}}; + end + 1'b1: begin + // c.lh -> lh rd', imm(rs1') + instr_o = {10'b0, instr_i[5], 1'b0, 2'b01, instr_i[9:7], + 3'b001, 2'b01, instr_i[4:2], {OPCODE_LOAD}}; + end + + default: begin + illegal_instr_o = 1'b1; + end + endcase + end + + 3'b010: begin + // c.sb -> sb rs2', imm(rs1') + instr_o = {7'b0, 2'b01, instr_i[4:2], 2'b01, instr_i[9:7], + 3'b000, 3'b0, instr_i[5], instr_i[6], {OPCODE_STORE}}; + end + + 3'b011: begin + unique case (instr_i[6]) + 1'b0: begin + // c.sh -> sh rs2', imm(rs1') // The instr_i[6] should always be zero according to the reference + instr_o = {7'b0, 2'b01, instr_i[4:2], 2'b01, instr_i[9:7], + 3'b001, 3'b0, instr_i[5], 1'b0, {OPCODE_STORE}}; + end + 1'b1: begin + illegal_instr_o = 1'b1; + end + + default: begin + illegal_instr_o = 1'b1; + end + endcase + end + + default: begin + illegal_instr_o = 1'b1; + end + endcase // unique case (instr_i[12:10]) + end else begin + // The Zcb extension is not enabled + illegal_instr_o = 1'b1; + end + end + 3'b001, 3'b011, - 3'b100, 3'b101, 3'b111: begin illegal_instr_o = 1'b1; @@ -165,14 +377,71 @@ module ibex_compressed_decoder ( end 3'b100, - 3'b101, - 3'b110, - 3'b111: begin + 3'b101: begin // 100: c.subw // 101: c.addw illegal_instr_o = 1'b1; end + 3'b110: begin + if (RV32ZC == RV32ZcbZcmp || RV32ZC == RV32Zcb) begin + // c.mul -> m.mul rsd', rsd', rs2' + instr_o = {7'b0000001, 2'b01, instr_i[4:2], 2'b01, instr_i[9:7], + 3'b000, 2'b01, instr_i[9:7], {OPCODE_OP}}; + end else begin + // The Zcb extension is not enabled + illegal_instr_o = 1'b1; + end + end + + 3'b111: begin + if (RV32ZC == RV32ZcbZcmp || RV32ZC == RV32Zcb) begin + unique case ({instr_i[4:2]}) + 3'b000: begin + // c.zext.b -> andi rsd', rsd', 8'hff + instr_o = {4'b0, 8'hff, 2'b01, instr_i[9:7], 3'b111, + 2'b01, instr_i[9:7], {OPCODE_OP_IMM}}; + end + + 3'b001: begin + // c.sext.b -> sext.b rsd', rsd' + instr_o = {7'b0110000, 5'b00100, 2'b01, instr_i[9:7], + 3'b001, 2'b01, instr_i[9:7], {OPCODE_OP_IMM}}; + end + + 3'b010: begin + // c.zext.h -> zext.h rsd', rsd' + instr_o = {7'b0000100, 5'b0, 2'b01, instr_i[9:7], + 3'b100, 2'b01, instr_i[9:7], {OPCODE_OP}}; + end + + 3'b011: begin + // c.sext.h -> sext.h rsd', rsd' + instr_o = {7'b0110000, 5'b00101, 2'b01, instr_i[9:7], + 3'b001, 2'b01, instr_i[9:7], {OPCODE_OP_IMM}}; + end + + 3'b100: begin + // c.zext.w -> add.uw: only valid instruction for RV64 cores + illegal_instr_o = 1'b1; + end + + 3'b101: begin + // c.not -> xori rsd', rsd', -1 + instr_o = {12'hfff, 2'b01, instr_i[9:7], 3'b100, + 2'b01, instr_i[9:7], {OPCODE_OP_IMM}}; + end + + default: begin + illegal_instr_o = 1'b1; + end + endcase + end else begin + // The Zcb extension is not enabled + illegal_instr_o = 1'b1; + end + end + default: begin illegal_instr_o = 1'b1; end @@ -248,6 +517,223 @@ module ibex_compressed_decoder ( end end + 3'b101: begin + if (RV32ZC == RV32ZcbZcmp || RV32ZC == RV32Zcmp) begin + unique casez (instr_i[12:8]) + // cm.push + 5'b11000: begin + // This compressed instruction gets expanded into multiple instructions. + gets_expanded_o = INSTR_EXPANDED; + unique case (cm_state_q) + CmIdle: begin + // No cm.push instruction is active yet; start a new one. + // Initialize `rlist` to the value provided by the instruction. + cm_rlist_d = cm_rlist_init(instr_i[7:4]); + // Store the register at the top of `rlist`. + instr_o = cm_push_store_reg(.rlist(cm_rlist_d), .sp_offset(5'd1)); + if (cm_rlist_d <= 5'd3) begin + // Reserved --> illegal instruction. + illegal_instr_o = 1'b1; + end else if (cm_rlist_d == 5'd4) begin + // Only `ra` has to be stored, which is done in this cycle. Proceed by + // decrementing SP. + if (id_in_ready_i) begin + cm_state_d = CmPushDecrSp; + end + end else begin + // More registers have to be stored. + // Remove the current register from `rlist`. + cm_rlist_d -= 5'd1; + // Initialize SP offset to 2. + cm_sp_offset_d = 5'd2; + // Proceed with storing registers. + if (id_in_ready_i) begin + cm_state_d = CmPushStoreReg; + end + end + end + CmPushStoreReg: begin + // Store register at the top of current `rlist`. + instr_o = cm_push_store_reg(.rlist(cm_rlist_q), .sp_offset(cm_sp_offset_q)); + if (id_in_ready_i) begin + // Remove top register from `rlist`. + cm_rlist_d = cm_rlist_q - 5'd1; + // Increment the SP offset. + cm_sp_offset_d = cm_sp_offset_q + 5'd1; + if (cm_rlist_q == 5'd4) begin + // The last register gets stored in this cycle. Proceed by decrementing + // SP. + cm_state_d = CmPushDecrSp; + end + end + end + CmPushDecrSp: begin + // Decrement stack pointer. + instr_o = cm_sp_addi(.rlist(instr_i[7:4]), + .spimm(instr_i[3:2]), + .decr(1'b1)); + if (id_in_ready_i) begin + // This is the final operation, so stop expanding and return to idle. + gets_expanded_o = INSTR_EXPANDED_LAST; + cm_state_d = CmIdle; + end + end + default: cm_state_d = CmIdle; + endcase + end + + // cm.pop, cm.popretz, cm.popret + 5'b11010, + 5'b11100, + 5'b11110: begin + // This compressed instruction gets expanded into multiple instructions. + gets_expanded_o = INSTR_EXPANDED; + unique case (cm_state_q) + CmIdle: begin + // No cm.pop instruction is active yet; start a new one. + // Initialize `rlist` to the value provided by the instruction. + cm_rlist_d = cm_rlist_init(instr_i[7:4]); + // Initialize SP offset. + cm_sp_offset_d = cm_stack_adj_word(.rlist(instr_i[7:4]), + .spimm(instr_i[3:2])) - 5'd1; + // Load the register at the top of `rlist`. + instr_o = cm_pop_load_reg(.rlist(cm_rlist_d), .sp_offset(cm_sp_offset_d)); + if (cm_rlist_d <= 5'd3) begin + // Reserved --> illegal instruction. + illegal_instr_o = 1'b1; + end else if (cm_rlist_d == 5'd4) begin + // Only `ra` has to be loaded, which is done in this cycle. Proceed by + // incrementing SP. + if (id_in_ready_i) begin + cm_state_d = CmPopIncrSp; + end + end else begin + // More registers have to be loaded. + // Remove the current register from `rlist` and decrement the SP offset. + cm_rlist_d -= 5'd1; + cm_sp_offset_d -= 5'd1; + // Proceed with loading registers. + if (id_in_ready_i) begin + cm_state_d = CmPopLoadReg; + end + end + end + CmPopLoadReg: begin + // Load register at the top of current `rlist`. + instr_o = cm_pop_load_reg(.rlist(cm_rlist_q), .sp_offset(cm_sp_offset_q)); + if (id_in_ready_i) begin + // Remove top register from `rlist`. + cm_rlist_d = cm_rlist_q - 5'd1; + // Decrement the SP offset. + cm_sp_offset_d = cm_sp_offset_q - 5'd1; + if (cm_rlist_q == 5'd4) begin + // The last register gets stored in this cycle. Proceed by incrementing + // SP. + cm_state_d = CmPopIncrSp; + end + end + end + CmPopIncrSp: begin + // Increment stack pointer. + instr_o = cm_sp_addi(.rlist(instr_i[7:4]), + .spimm(instr_i[3:2]), + .decr(1'b0)); + if (id_in_ready_i) begin + unique case (instr_i[12:8]) + 5'b11100: cm_state_d = CmPopZeroA0; // cm.popretz + 5'b11110: cm_state_d = CmPopRetRa; // cm.popret + default: begin // cm.pop + // This is the final operation, so stop expanding and return to idle. + gets_expanded_o = INSTR_EXPANDED_LAST; + cm_state_d = CmIdle; + end + endcase + end + end + CmPopZeroA0: begin + instr_o = cm_zero_a0(); + if (id_in_ready_i) begin + cm_state_d = CmPopRetRa; + end + end + CmPopRetRa: begin + instr_o = cm_ret_ra(); + if (id_in_ready_i) begin + // This is the final operation, so stop expanding and return to idle. + gets_expanded_o = INSTR_EXPANDED_LAST; + cm_state_d = CmIdle; + end + end + default: cm_state_d = CmIdle; + endcase + end + + // cm.mvsa01, cm.mva01s + 5'b011??: begin + unique case (instr_i[6:5]) + // cm.mvsa01 + 2'b01: begin + // This compressed instruction gets expanded into multiple instructions. + gets_expanded_o = INSTR_EXPANDED; + unique case (cm_state_q) + CmIdle: begin + // No cm.mvsa01 instruction is active yet; start a new one. + // Move a0 to register indicated by r1s'. + instr_o = cm_mvsa01(.a01(1'b0), .rs(instr_i[9:7])); + if (id_in_ready_i) begin + cm_state_d = CmMvSA1; + end + end + CmMvSA1: begin + // Move a1 to register indicated by r2s'. + instr_o = cm_mvsa01(.a01(1'b1), .rs(instr_i[4:2])); + if (id_in_ready_i) begin + // This is the final operation, so stop expanding and return to idle. + gets_expanded_o = INSTR_EXPANDED_LAST; + cm_state_d = CmIdle; + end + end + default: cm_state_d = CmIdle; + endcase + end + + // cm.mva01s + 2'b11: begin + // This compressed instruction gets expanded into multiple instructions. + gets_expanded_o = INSTR_EXPANDED; + unique case (cm_state_q) + CmIdle: begin + // No cm.mva01s instruction is active yet; start a new one. + // Move register indicated by r1s' into a0. + instr_o = cm_mva01s(.rs(instr_i[9:7]), .a01(1'b0)); + if (id_in_ready_i) begin + cm_state_d = CmMvA1S; + end + end + CmMvA1S: begin + // Move register indicated by r2s' into a1. + instr_o = cm_mva01s(.rs(instr_i[4:2]), .a01(1'b1)); + if (id_in_ready_i) begin + // This is the final operation, so stop expanding and return to idle. + gets_expanded_o = INSTR_EXPANDED_LAST; + cm_state_d = CmIdle; + end + end + default: cm_state_d = CmIdle; + endcase + end + default: illegal_instr_o = 1'b1; + endcase + end + + default: illegal_instr_o = 1'b1; + endcase + end else begin + // The Zcmp extension is not enabled + illegal_instr_o = 1'b1; + end + end + 3'b110: begin // c.swsp -> sw rs2, imm(x2) instr_o = {4'b0, instr_i[8:7], instr_i[12], instr_i[6:2], 5'h02, 3'b010, @@ -256,7 +742,6 @@ module ibex_compressed_decoder ( 3'b001, 3'b011, - 3'b101, 3'b111: begin illegal_instr_o = 1'b1; end @@ -278,6 +763,18 @@ module ibex_compressed_decoder ( assign is_compressed_o = (instr_i[1:0] != 2'b11); + always_ff @(posedge clk_i, negedge rst_ni) begin + if (!rst_ni) begin + cm_state_q <= CmIdle; + // The following regs don't need to be reset as they get assigned before first usage: + // cm_rlist_q, cm_sp_offset_q + end else begin + cm_rlist_q <= cm_rlist_d; + cm_sp_offset_q <= cm_sp_offset_d; + cm_state_q <= cm_state_d; + end + end + //////////////// // Assertions // //////////////// diff --git a/rtl/ibex_core.sv b/rtl/ibex_core.sv index b5ff81ace..75c36fae3 100644 --- a/rtl/ibex_core.sv +++ b/rtl/ibex_core.sv @@ -25,6 +25,7 @@ module ibex_core import ibex_pkg::*; #( parameter bit RV32E = 1'b0, parameter rv32m_e RV32M = RV32MFast, parameter rv32b_e RV32B = RV32BNone, + parameter rv32zc_e RV32ZC = RV32ZcbZcmp, parameter bit BranchTargetALU = 1'b0, parameter bit WritebackStage = 1'b0, parameter bit ICache = 1'b0, @@ -158,6 +159,7 @@ module ibex_core import ibex_pkg::*; #( output logic [31:0] rvfi_ext_mhpmcountersh [10], output logic rvfi_ext_ic_scr_key_valid, output logic rvfi_ext_irq_valid, + output logic [31:0] rvfi_ext_expanded_insn, `endif // CPU Control Signals @@ -184,6 +186,8 @@ module ibex_core import ibex_pkg::*; #( // ease fan-out) logic [15:0] instr_rdata_c_id; // Compressed instruction sampled inside IF stage logic instr_is_compressed_id; + instr_exp_e instr_gets_expanded_id; + logic [31:0] instr_expanded_id; logic instr_perf_count_id; logic instr_bp_taken_id; logic instr_fetch_err; // Bus error on instr fetch @@ -470,6 +474,8 @@ module ibex_core import ibex_pkg::*; #( .instr_rdata_alu_id_o (instr_rdata_alu_id), .instr_rdata_c_id_o (instr_rdata_c_id), .instr_is_compressed_id_o(instr_is_compressed_id), + .instr_gets_expanded_id_o(instr_gets_expanded_id), + .instr_expanded_id_o (instr_expanded_id), .instr_bp_taken_o (instr_bp_taken_id), .instr_fetch_err_o (instr_fetch_err), .instr_fetch_err_plus2_o (instr_fetch_err_plus2), @@ -1314,6 +1320,9 @@ module ibex_core import ibex_pkg::*; #( logic [31:0] rvfi_ext_stage_mhpmcountersh [RVFI_STAGES][10]; logic rvfi_ext_stage_ic_scr_key_valid [RVFI_STAGES]; logic rvfi_ext_stage_irq_valid [RVFI_STAGES+1]; + logic [31:0] rvfi_ext_stage_expanded_insn [RVFI_STAGES]; + + logic [31:0] rvfi_expanded_insn; logic rvfi_stage_valid_d [RVFI_STAGES]; @@ -1375,6 +1384,7 @@ module ibex_core import ibex_pkg::*; #( assign rvfi_ext_mhpmcountersh = rvfi_ext_stage_mhpmcountersh [RVFI_STAGES-1]; assign rvfi_ext_ic_scr_key_valid = rvfi_ext_stage_ic_scr_key_valid [RVFI_STAGES-1]; assign rvfi_ext_irq_valid = rvfi_ext_stage_irq_valid [RVFI_STAGES]; + assign rvfi_ext_expanded_insn = rvfi_ext_stage_expanded_insn [RVFI_STAGES-1]; // When an instruction takes a trap the `rvfi_trap` signal will be set. Instructions that take // traps flush the pipeline so ordinarily wouldn't be seen to be retire. The RVFI tracking @@ -1592,6 +1602,7 @@ module ibex_core import ibex_pkg::*; #( rvfi_ext_stage_debug_mode[i] <= '0; rvfi_ext_stage_mcycle[i] <= '0; rvfi_ext_stage_ic_scr_key_valid[i] <= '0; + rvfi_ext_stage_expanded_insn[i] <= '0; // DSim does not properly support array assignment in for loop, so unroll rvfi_ext_stage_mhpmcounters[i][0] <= '0; rvfi_ext_stage_mhpmcountersh[i][0] <= '0; @@ -1643,6 +1654,7 @@ module ibex_core import ibex_pkg::*; #( rvfi_ext_stage_debug_mode[i] <= debug_mode; rvfi_ext_stage_mcycle[i] <= cs_registers_i.mcycle_counter_i.counter_val_o; rvfi_ext_stage_ic_scr_key_valid[i] <= cs_registers_i.cpuctrlsts_ic_scr_key_valid_q; + rvfi_ext_stage_expanded_insn[i] <= rvfi_expanded_insn; // DSim does not properly support array assignment in for loop, so unroll rvfi_ext_stage_mhpmcounters[i][0] <= cs_registers_i.mhpmcounter[3][31:0]; rvfi_ext_stage_mhpmcountersh[i][0] <= cs_registers_i.mhpmcounter[3][63:32]; @@ -1712,6 +1724,7 @@ module ibex_core import ibex_pkg::*; #( rvfi_ext_stage_ic_scr_key_valid[i] <= rvfi_ext_stage_ic_scr_key_valid[i-1]; rvfi_ext_stage_mhpmcounters[i] <= rvfi_ext_stage_mhpmcounters[i-1]; rvfi_ext_stage_mhpmcountersh[i] <= rvfi_ext_stage_mhpmcountersh[i-1]; + rvfi_ext_stage_expanded_insn[i] <= rvfi_ext_stage_expanded_insn[i-1]; end // Some of the rvfi_ext_* signals are used to provide an interrupt notification (signalled @@ -1773,13 +1786,21 @@ module ibex_core import ibex_pkg::*; #( end always_comb begin - if (instr_is_compressed_id) begin + if (instr_is_compressed_id && (instr_gets_expanded_id == INSTR_NOT_EXPANDED)) begin rvfi_insn_id = {16'b0, instr_rdata_c_id}; end else begin rvfi_insn_id = instr_rdata_id; end end + always_comb begin + if (instr_gets_expanded_id == INSTR_NOT_EXPANDED) begin + rvfi_expanded_insn = '0; + end else begin + rvfi_expanded_insn = instr_expanded_id; + end + end + // Source registers 1 and 2 are read in the first instruction cycle // Source register 3 is read in the second instruction cycle. always_comb begin diff --git a/rtl/ibex_if_stage.sv b/rtl/ibex_if_stage.sv index ebdd6e46c..ca5bb6524 100644 --- a/rtl/ibex_if_stage.sv +++ b/rtl/ibex_if_stage.sv @@ -18,6 +18,7 @@ module ibex_if_stage import ibex_pkg::*; #( parameter int unsigned DmExceptionAddr = 32'h1A110808, parameter bit DummyInstructions = 1'b0, parameter bit ICache = 1'b0, + parameter rv32zc_e RV32ZC = RV32ZcbZcmp, parameter bit ICacheECC = 1'b0, parameter int unsigned BusSizeECC = BUS_SIZE, parameter int unsigned TagSizeECC = IC_TAG_SIZE, @@ -70,6 +71,11 @@ module ibex_if_stage import ibex_pkg::*; #( // instr_is_compressed_id_o = 1'b1 output logic instr_is_compressed_id_o, // compressed decoder thinks this // is a compressed instr + output instr_exp_e instr_gets_expanded_id_o, // this instruction comes from one + // that gets expanded by the + // compressed decoder + output logic [31:0] instr_expanded_id_o, // the instruction that is currently + // getting expanded output logic instr_bp_taken_o, // instruction was predicted to be // a taken branch output logic instr_fetch_err_o, // bus error on fetch @@ -144,6 +150,7 @@ module ibex_if_stage import ibex_pkg::*; #( logic [31:0] instr_decompressed; logic illegal_c_insn; logic instr_is_compressed; + instr_exp_e instr_gets_expanded; logic if_instr_valid; logic [31:0] if_instr_rdata; @@ -161,6 +168,7 @@ module ibex_if_stage import ibex_pkg::*; #( logic stall_dummy_instr; logic [31:0] instr_out; logic instr_is_compressed_out; + instr_exp_e instr_gets_expanded_out; logic illegal_c_instr_out; logic instr_err_out; @@ -401,13 +409,17 @@ module ibex_if_stage import ibex_pkg::*; #( // // since it does not matter where we decompress instructions, we do it here // to ease timing closure - ibex_compressed_decoder compressed_decoder_i ( + ibex_compressed_decoder #( + .RV32ZC (RV32ZC) + ) compressed_decoder_i ( .clk_i (clk_i), .rst_ni (rst_ni), .valid_i (fetch_valid & ~fetch_err), + .id_in_ready_i (id_in_ready_i & ~pc_set_i), .instr_i (if_instr_rdata), .instr_o (instr_decompressed), .is_compressed_o(instr_is_compressed), + .gets_expanded_o(instr_gets_expanded), .illegal_instr_o(illegal_c_insn) ); @@ -436,6 +448,7 @@ module ibex_if_stage import ibex_pkg::*; #( // Mux between actual instructions and dummy instructions assign instr_out = insert_dummy_instr ? dummy_instr_data : instr_decompressed; assign instr_is_compressed_out = insert_dummy_instr ? 1'b0 : instr_is_compressed; + assign instr_gets_expanded_out = insert_dummy_instr ? INSTR_NOT_EXPANDED : instr_gets_expanded; assign illegal_c_instr_out = insert_dummy_instr ? 1'b0 : illegal_c_insn; assign instr_err_out = insert_dummy_instr ? 1'b0 : if_instr_err; @@ -465,6 +478,7 @@ module ibex_if_stage import ibex_pkg::*; #( assign unused_dummy_seed = dummy_instr_seed_i; assign instr_out = instr_decompressed; assign instr_is_compressed_out = instr_is_compressed; + assign instr_gets_expanded_out = instr_gets_expanded; assign illegal_c_instr_out = illegal_c_insn; assign instr_err_out = if_instr_err; assign stall_dummy_instr = 1'b0; @@ -476,7 +490,7 @@ module ibex_if_stage import ibex_pkg::*; #( // Valid is held until it is explicitly cleared (due to an instruction completing or an exception) assign instr_valid_id_d = (if_instr_valid & id_in_ready_i & ~pc_set_i) | (instr_valid_id_q & ~instr_valid_clear_i); - assign instr_new_id_d = if_instr_valid & id_in_ready_i; + assign instr_new_id_d = if_instr_valid & id_in_ready_i & ~pc_set_i; always_ff @(posedge clk_i or negedge rst_ni) begin if (!rst_ni) begin @@ -504,6 +518,8 @@ module ibex_if_stage import ibex_pkg::*; #( instr_fetch_err_plus2_o <= '0; instr_rdata_c_id_o <= '0; instr_is_compressed_id_o <= '0; + instr_gets_expanded_id_o <= INSTR_NOT_EXPANDED; + instr_expanded_id_o <= '0; illegal_c_insn_id_o <= '0; pc_id_o <= '0; end else if (if_id_pipe_reg_we) begin @@ -514,6 +530,8 @@ module ibex_if_stage import ibex_pkg::*; #( instr_fetch_err_plus2_o <= if_instr_err_plus2; instr_rdata_c_id_o <= if_instr_rdata[15:0]; instr_is_compressed_id_o <= instr_is_compressed_out; + instr_gets_expanded_id_o <= instr_gets_expanded_out; + instr_expanded_id_o <= if_instr_rdata[15:0]; illegal_c_insn_id_o <= illegal_c_instr_out; pc_id_o <= pc_if_o; end @@ -528,6 +546,8 @@ module ibex_if_stage import ibex_pkg::*; #( instr_fetch_err_plus2_o <= if_instr_err_plus2; instr_rdata_c_id_o <= if_instr_rdata[15:0]; instr_is_compressed_id_o <= instr_is_compressed_out; + instr_gets_expanded_id_o <= instr_gets_expanded_out; + instr_expanded_id_o <= if_instr_rdata[15:0]; illegal_c_insn_id_o <= illegal_c_instr_out; pc_id_o <= pc_if_o; end @@ -544,7 +564,7 @@ module ibex_if_stage import ibex_pkg::*; #( // request, all of which will set branch_req. Also do not check after reset or for dummy // instructions. assign prev_instr_seq_d = (prev_instr_seq_q | instr_new_id_d) & - ~branch_req & ~if_instr_err & ~stall_dummy_instr; + ~branch_req & ~if_instr_err & ~stall_dummy_instr & !(instr_gets_expanded == INSTR_EXPANDED); always_ff @(posedge clk_i or negedge rst_ni) begin if (!rst_ni) begin @@ -606,7 +626,7 @@ module ibex_if_stage import ibex_pkg::*; #( assign instr_skid_en = predict_branch_taken & ~pc_set_i & ~id_in_ready_i & ~instr_skid_valid_q; - assign instr_skid_valid_d = (instr_skid_valid_q & ~id_in_ready_i & ~stall_dummy_instr) | + assign instr_skid_valid_d = (instr_skid_valid_q & ~id_in_ready_i & ~stall_dummy_instr & !(instr_gets_expanded == INSTR_EXPANDED)) | instr_skid_en; always_ff @(posedge clk_i or negedge rst_ni) begin @@ -665,7 +685,7 @@ module ibex_if_stage import ibex_pkg::*; #( assign if_instr_bus_err = ~instr_skid_valid_q & fetch_err; assign instr_bp_taken_d = instr_skid_valid_q ? instr_skid_bp_taken_q : predict_branch_taken; - assign fetch_ready = id_in_ready_i & ~stall_dummy_instr & ~instr_skid_valid_q; + assign fetch_ready = id_in_ready_i & ~stall_dummy_instr & !(instr_gets_expanded == INSTR_EXPANDED) & ~instr_skid_valid_q; assign instr_bp_taken_o = instr_bp_taken_q; @@ -680,7 +700,7 @@ module ibex_if_stage import ibex_pkg::*; #( assign if_instr_rdata = fetch_rdata; assign if_instr_addr = fetch_addr; assign if_instr_bus_err = fetch_err; - assign fetch_ready = id_in_ready_i & ~stall_dummy_instr; + assign fetch_ready = id_in_ready_i & ~stall_dummy_instr & !(instr_gets_expanded == INSTR_EXPANDED); end ////////// diff --git a/rtl/ibex_pkg.sv b/rtl/ibex_pkg.sv index f5cb75a3e..ba1ac3c14 100644 --- a/rtl/ibex_pkg.sv +++ b/rtl/ibex_pkg.sv @@ -52,6 +52,13 @@ package ibex_pkg; RV32BFull = 3 } rv32b_e; + typedef enum integer { + RV32ZcNone = 0, + RV32Zcb = 1, + RV32Zcmp = 2, + RV32ZcbZcmp = 3 + } rv32zc_e; + ///////////// // Opcodes // ///////////// @@ -300,6 +307,13 @@ package ibex_pkg; PC_BP } pc_sel_e; + // Compressed instruction expansion + typedef enum logic [1:0] { + INSTR_NOT_EXPANDED, + INSTR_EXPANDED, + INSTR_EXPANDED_LAST + } instr_exp_e; + // Exception PC mux selection typedef enum logic [1:0] { EXC_PC_EXC, diff --git a/rtl/ibex_top.sv b/rtl/ibex_top.sv index acf1e2502..74bd2a5f5 100644 --- a/rtl/ibex_top.sv +++ b/rtl/ibex_top.sv @@ -24,6 +24,7 @@ module ibex_top import ibex_pkg::*; #( parameter bit RV32E = 1'b0, parameter rv32m_e RV32M = RV32MFast, parameter rv32b_e RV32B = RV32BNone, + parameter rv32zc_e RV32ZC = RV32ZcbZcmp, parameter regfile_e RegFile = RegFileFF, parameter bit BranchTargetALU = 1'b0, parameter bit WritebackStage = 1'b0, @@ -143,6 +144,7 @@ module ibex_top import ibex_pkg::*; #( output logic [31:0] rvfi_ext_mhpmcountersh [10], output logic rvfi_ext_ic_scr_key_valid, output logic rvfi_ext_irq_valid, + output logic [31:0] rvfi_ext_expanded_insn, `endif // CPU Control Signals @@ -305,6 +307,7 @@ module ibex_top import ibex_pkg::*; #( .RV32E (RV32E), .RV32M (RV32M), .RV32B (RV32B), + .RV32ZC (RV32ZC), .BranchTargetALU (BranchTargetALU), .ICache (ICache), .ICacheECC (ICacheECC), @@ -424,6 +427,7 @@ module ibex_top import ibex_pkg::*; #( .rvfi_ext_mhpmcountersh, .rvfi_ext_ic_scr_key_valid, .rvfi_ext_irq_valid, + .rvfi_ext_expanded_insn, `endif .fetch_enable_i (fetch_enable_buf), @@ -1013,6 +1017,7 @@ module ibex_top import ibex_pkg::*; #( .RV32E (RV32E), .RV32M (RV32M), .RV32B (RV32B), + .RV32ZC (RV32ZC), .BranchTargetALU (BranchTargetALU), .ICache (ICache), .ICacheECC (ICacheECC), diff --git a/rtl/ibex_top_tracing.sv b/rtl/ibex_top_tracing.sv index 4af5b5b8a..22741a52c 100644 --- a/rtl/ibex_top_tracing.sv +++ b/rtl/ibex_top_tracing.sv @@ -15,6 +15,7 @@ module ibex_top_tracing import ibex_pkg::*; #( parameter bit RV32E = 1'b0, parameter rv32m_e RV32M = RV32MFast, parameter rv32b_e RV32B = RV32BNone, + parameter rv32zc_e RV32ZC = RV32ZcbZcmp, parameter regfile_e RegFile = RegFileFF, parameter bit BranchTargetALU = 1'b0, parameter bit WritebackStage = 1'b0, @@ -134,6 +135,7 @@ module ibex_top_tracing import ibex_pkg::*; #( logic [31:0] rvfi_ext_mhpmcountersh [10]; logic rvfi_ext_ic_scr_key_valid; logic rvfi_ext_irq_valid; + logic [31:0] rvfi_ext_expanded_insn; logic [31:0] unused_perf_regs [10]; logic [31:0] unused_perf_regsh [10]; @@ -149,6 +151,7 @@ module ibex_top_tracing import ibex_pkg::*; #( logic [63:0] unused_rvfi_ext_mcycle; logic unused_rvfi_ext_ic_scr_key_valid; logic unused_rvfi_ext_irq_valid; + logic [31:0] unused_rvfi_ext_expanded_insn; // Tracer doesn't use these signals, though other modules may probe down into tracer to observe // them. @@ -164,6 +167,7 @@ module ibex_top_tracing import ibex_pkg::*; #( assign unused_perf_regsh = rvfi_ext_mhpmcountersh; assign unused_rvfi_ext_ic_scr_key_valid = rvfi_ext_ic_scr_key_valid; assign unused_rvfi_ext_irq_valid = rvfi_ext_irq_valid; + assign unused_rvfi_ext_expanded_insn = rvfi_ext_expanded_insn; ibex_top #( .PMPEnable ( PMPEnable ), @@ -271,6 +275,7 @@ module ibex_top_tracing import ibex_pkg::*; #( .rvfi_ext_mhpmcountersh, .rvfi_ext_ic_scr_key_valid, .rvfi_ext_irq_valid, + .rvfi_ext_expanded_insn, .fetch_enable_i, .alert_minor_o, @@ -308,7 +313,9 @@ module ibex_top_tracing import ibex_pkg::*; #( .rvfi_mem_rmask, .rvfi_mem_wmask, .rvfi_mem_rdata, - .rvfi_mem_wdata + .rvfi_mem_wdata, + + .rvfi_ext_expanded_insn ); endmodule diff --git a/rtl/ibex_tracer.sv b/rtl/ibex_tracer.sv index c086f52ba..e9bc53cbe 100644 --- a/rtl/ibex_tracer.sv +++ b/rtl/ibex_tracer.sv @@ -65,7 +65,8 @@ module ibex_tracer ( input logic [ 3:0] rvfi_mem_rmask, input logic [ 3:0] rvfi_mem_wmask, input logic [31:0] rvfi_mem_rdata, - input logic [31:0] rvfi_mem_wdata + input logic [31:0] rvfi_mem_wdata, + input logic [31:0] rvfi_ext_expanded_insn ); // These signals are part of RVFI, but not used in this module currently. @@ -143,6 +144,9 @@ module ibex_tracer ( $fwrite(fh, " load:0x%08x", rvfi_mem_rdata); end end + if (rvfi_ext_expanded_insn != 32'b0) begin + $fwrite(fh, " expand_insn: (0x%08x %s)", rvfi_ext_expanded_insn, decode_expanded_insn()); + end $fwrite(fh, "\n"); endfunction @@ -157,6 +161,31 @@ module ibex_tracer ( end endfunction + // Format register address with "x" prefix, left-aligned to a fixed width of 3 characters. + function automatic string reg_addr_to_abi_str(input logic [4:0] addr); + case (addr) + // Hard-wired Zero Register + 5'd0: return "zero"; + // Return Address and Pointers + 5'd1: return "ra"; // Return Address + 5'd2: return "sp"; // Stack Pointer + 5'd3: return "gp"; // Global Pointer + 5'd4: return "tp"; // Thread Pointer + // Temporary Registers + 5'd5, 5'd6, 5'd7: return $sformatf("t%0d", addr - 5); + // Saved Registers + 5'd8, 5'd9: return $sformatf("s%0d", addr - 8); + // Function Arguments / Return Values + 5'd10, 5'd11, 5'd12, 5'd13, + 5'd14, 5'd15, 5'd16, 5'd17: return $sformatf("a%0d", addr - 10); + // Saved Registers + 5'd18, 5'd19, 5'd20, 5'd21, 5'd22, + 5'd23, 5'd24, 5'd25, 5'd26, 5'd27: return $sformatf("s%0d", addr - 16); + // Temporary Registers + 5'd28, 5'd29, 5'd30, 5'd31: return $sformatf("t%0d", addr - 25); + endcase + endfunction + // Get a CSR name for a CSR address. function automatic string get_csr_name(input logic [11:0] csr_addr); unique case (csr_addr) @@ -603,6 +632,11 @@ module ibex_tracer ( decoded_str = $sformatf("%s\t%0x", mnemonic, rvfi_pc_wdata); endfunction + function automatic void decode_Zc_cu_insn(input string mnemonic); + data_accessed = RS1 | RD; // RS1 == RD + decoded_str = $sformatf("%s\tx%0d", mnemonic, rvfi_rd_addr); + endfunction + function automatic void decode_compressed_load_insn(input string mnemonic); logic [7:0] imm; @@ -617,6 +651,21 @@ module ibex_tracer ( decoded_str = $sformatf("%s\tx%0d,%0d(x%0d)", mnemonic, rvfi_rd_addr, imm, rvfi_rs1_addr); endfunction + function automatic void decode_Zc_load_insn(input string mnemonic); + logic [1:0] imm; + + if (rvfi_insn[10] == 1'b0) begin + // C.LBU + imm = {rvfi_insn[5], rvfi_insn[6]}; + end else begin + // C.LHU, C.LH + imm = {rvfi_insn[5], 1'b0}; + end + data_accessed = RS1 | RD | MEM; + decoded_str = $sformatf("%s\tx%0d,%0d(x%0d)", mnemonic, rvfi_rd_addr, imm, rvfi_rs1_addr); + endfunction + + function automatic void decode_compressed_store_insn(input string mnemonic); logic [7:0] imm; if (rvfi_insn[1:0] == OPCODE_C0) begin @@ -630,6 +679,69 @@ module ibex_tracer ( decoded_str = $sformatf("%s\tx%0d,%0d(x%0d)", mnemonic, rvfi_rs2_addr, imm, rvfi_rs1_addr); endfunction + function automatic void decode_Zc_store_insn(input string mnemonic); + logic [1:0] imm; + + if (rvfi_insn[10] == 1'b0) begin + // C.SB + imm = {rvfi_insn[5], rvfi_insn[6]}; + end else begin + // C.SH + imm = {rvfi_insn[5], 1'b0}; + end + data_accessed = RS1 | RS2 | MEM; + decoded_str = $sformatf("%s\tx%0d,%0d(x%0d)", mnemonic, rvfi_rd_addr, imm, rvfi_rs1_addr); + endfunction + + function automatic string cm_reg_to_str(input logic [2:0] addr); + logic [4:0] xreg = {addr[2:1] > 0, addr[2:1]==0, addr[2:0]}; + return reg_addr_to_abi_str(xreg); + endfunction + + function automatic string decode_Zcmp_cmmv_insn(input string mnemonic); + return $sformatf("%s\t%0s,%0s", mnemonic, cm_reg_to_str(rvfi_ext_expanded_insn[9:7]), + cm_reg_to_str(rvfi_ext_expanded_insn[4:2])); + endfunction + + function automatic string decode_Zcmp_cmpp_insn(input string mnemonic); + logic [3:0] rlist; + logic [1:0] spimm; + string rlist_str; + int base; + int spimm_val; + rlist = rvfi_ext_expanded_insn[7:4]; + spimm = rvfi_ext_expanded_insn[3:2]; + // Decode rlist to string + if (rlist < 4 || rlist > 15) begin + rlist_str = $sformatf("{INVALID (%0d)}", rlist); + end else begin + case(rlist) + 4: rlist_str = "{ra}"; + 5: rlist_str = "{ra, s0}"; + 15: rlist_str = "{ra, s0-s11}"; // The special case for s10/s11 + // The default case handles the general pattern for rlist 6 through 14 + default: rlist_str = $sformatf("{ra, s0-s%0d}", rlist - 5); + endcase + end + // Decode spimm + base = (rlist == 15) ? 64 : (rlist >> 2) * 16; + spimm_val = base + (spimm * 16); + spimm_val = mnemonic == "cm.push" ? -spimm_val : spimm_val; + return $sformatf("%s\t%s,%0d", mnemonic, rlist_str, spimm_val); + endfunction + + function automatic string decode_expanded_insn(); + unique casez (rvfi_ext_expanded_insn[15:0]) + INSN_CMPUSH: return decode_Zcmp_cmpp_insn("cm.push"); + INSN_CMPOP: return decode_Zcmp_cmpp_insn("cm.pop"); + INSN_CMPOPRETZ: return decode_Zcmp_cmpp_insn("cm.popretz"); + INSN_CMPOPRET: return decode_Zcmp_cmpp_insn("cm.popret"); + INSN_CMMVSA01: return decode_Zcmp_cmmv_insn("cm.mvsa01"); + INSN_CMMVA01S: return decode_Zcmp_cmmv_insn("cm.mva01s"); + default: return "Decoding error"; + endcase + endfunction + function automatic void decode_load_insn(); string mnemonic; @@ -803,6 +915,13 @@ module ibex_tracer ( end INSN_CLW: decode_compressed_load_insn("c.lw"); INSN_CSW: decode_compressed_store_insn("c.sw"); + // Zc extension C0 + INSN_CLBU: decode_Zc_load_insn("c.lbu"); + INSN_CLHU: decode_Zc_load_insn("c.lhu"); + INSN_CLH: decode_Zc_load_insn("c.lh"); + INSN_CSB: decode_Zc_store_insn("c.sb"); + INSN_CSH: decode_Zc_store_insn("c.sh"); + // C1 Opcodes INSN_CADDI: decode_ci_caddi_insn("c.addi"); INSN_CJAL: decode_cj_insn("c.jal"); @@ -825,10 +944,26 @@ module ibex_tracer ( INSN_CAND: decode_cs_insn("c.and"); INSN_CBEQZ: decode_cb_insn("c.beqz"); INSN_CBNEZ: decode_cb_insn("c.bnez"); + // Zc extension C1 + INSN_CZEXTB: decode_Zc_cu_insn("c.zext.b"); + INSN_CSEXTB: decode_Zc_cu_insn("c.sext.b"); + INSN_CZEXTH: decode_Zc_cu_insn("c.zext.h"); + INSN_CSEXTH: decode_Zc_cu_insn("c.sext.h"); + INSN_CNOT: decode_Zc_cu_insn("c.not"); + INSN_CMUL: decode_cs_insn("c.mul"); + + // C2 Opcodes INSN_CSLLI: decode_ci_cslli_insn("c.slli"); INSN_CLWSP: decode_compressed_load_insn("c.lwsp"); INSN_SWSP: decode_compressed_store_insn("c.swsp"); + // Zc extension C2 + INSN_CMPUSH: decode_Zcmp_cmpp_insn("cm.push"); + INSN_CMPOP: decode_Zcmp_cmpp_insn("cm.pop"); + INSN_CMPOPRETZ: decode_Zcmp_cmpp_insn("cm.popretz"); + INSN_CMPOPRET: decode_Zcmp_cmpp_insn("cm.popret"); + INSN_CMMVSA01: decode_Zcmp_cmmv_insn("cm.mvsa01"); + INSN_CMMVA01S: decode_Zcmp_cmmv_insn("cm.mva01s"); default: decode_mnemonic("INVALID"); endcase end diff --git a/rtl/ibex_tracer_pkg.sv b/rtl/ibex_tracer_pkg.sv index 6dbbfc905..d5dcb59f7 100644 --- a/rtl/ibex_tracer_pkg.sv +++ b/rtl/ibex_tracer_pkg.sv @@ -305,7 +305,13 @@ package ibex_tracer_pkg; parameter logic [15:0] INSN_CADDI4SPN = { 3'b000, 11'h?, {OPCODE_C0} }; parameter logic [15:0] INSN_CLW = { 3'b010, 11'h?, {OPCODE_C0} }; parameter logic [15:0] INSN_CSW = { 3'b110, 11'h?, {OPCODE_C0} }; - + // Zc extension C0 + parameter logic [15:0] INSN_CLBU = { 3'b100, 3'b000, 8'h?, {OPCODE_C0} }; + parameter logic [15:0] INSN_CLHU = { 3'b100, 3'b001, 3'h?, 1'b0, 4'h?, {OPCODE_C0} }; + parameter logic [15:0] INSN_CLH = { 3'b100, 3'b001, 3'h?, 1'b1, 4'h?, {OPCODE_C0} }; + parameter logic [15:0] INSN_CSB = { 3'b100, 3'b010, 8'h?, {OPCODE_C0} }; + parameter logic [15:0] INSN_CSH = { 3'b100, 3'b011, 3'h?, 1'b0, 4'h?, {OPCODE_C0} }; + // C1 parameter logic [15:0] INSN_CADDI = { 3'b000, 11'h?, {OPCODE_C1} }; parameter logic [15:0] INSN_CJAL = { 3'b001, 11'h?, {OPCODE_C1} }; @@ -321,7 +327,14 @@ package ibex_tracer_pkg; parameter logic [15:0] INSN_CXOR = { 3'b100, 1'b0, 2'b11, 3'h?, 2'b01, 3'h?, {OPCODE_C1} }; parameter logic [15:0] INSN_COR = { 3'b100, 1'b0, 2'b11, 3'h?, 2'b10, 3'h?, {OPCODE_C1} }; parameter logic [15:0] INSN_CAND = { 3'b100, 1'b0, 2'b11, 3'h?, 2'b11, 3'h?, {OPCODE_C1} }; - + // Zc extension C1 + parameter logic [15:0] INSN_CZEXTB = { 3'b100, 3'b111, 3'h?, 2'b11, 3'b000, {OPCODE_C1} }; + parameter logic [15:0] INSN_CSEXTB = { 3'b100, 3'b111, 3'h?, 2'b11, 3'b001, {OPCODE_C1} }; + parameter logic [15:0] INSN_CZEXTH = { 3'b100, 3'b111, 3'h?, 2'b11, 3'b010, {OPCODE_C1} }; + parameter logic [15:0] INSN_CSEXTH = { 3'b100, 3'b111, 3'h?, 2'b11, 3'b011, {OPCODE_C1} }; + parameter logic [15:0] INSN_CNOT = { 3'b100, 3'b111, 3'h?, 2'b11, 3'b101, {OPCODE_C1} }; + parameter logic [15:0] INSN_CMUL = { 3'b100, 3'b111, 3'h?, 2'b10, 3'h?, {OPCODE_C1} }; + // C2 parameter logic [15:0] INSN_CSLLI = { 3'b000, 11'h?, {OPCODE_C2} }; parameter logic [15:0] INSN_CLWSP = { 3'b010, 11'h?, {OPCODE_C2} }; @@ -331,5 +344,12 @@ package ibex_tracer_pkg; parameter logic [15:0] INSN_CEBREAK = { 3'b100, 1'b1, 5'h0, 5'h0, {OPCODE_C2} }; parameter logic [15:0] INSN_CJR = { 3'b100, 1'b0, 5'h0, 5'h0, {OPCODE_C2} }; parameter logic [15:0] INSN_CJALR = { 3'b100, 1'b1, 5'h?, 5'h0, {OPCODE_C2} }; + // Zc extension Cc + parameter logic [15:0] INSN_CMPUSH = { 3'b101, 3'b110, 2'b00, 4'h?, 2'b?, {OPCODE_C2} }; + parameter logic [15:0] INSN_CMPOP = { 3'b101, 3'b110, 2'b10, 4'h?, 2'b?, {OPCODE_C2} }; + parameter logic [15:0] INSN_CMPOPRETZ = { 3'b101, 3'b111, 2'b00, 4'h?, 2'b?, {OPCODE_C2} }; + parameter logic [15:0] INSN_CMPOPRET = { 3'b101, 3'b111, 2'b10, 4'h?, 2'b?, {OPCODE_C2} }; + parameter logic [15:0] INSN_CMMVSA01 = { 3'b101, 3'b011, 3'h?, 2'b01, 3'h?, {OPCODE_C2} }; + parameter logic [15:0] INSN_CMMVA01S = { 3'b101, 3'b011, 3'h?, 2'b11, 3'h?, {OPCODE_C2} }; endpackage