// Licensed under the Solderpad Hardware Licence, Version 2.1 (the "License");
// you may not use this file except in compliance with the License.
// SPDX-License-Identifier: Apache-2.0 WITH SHL-2.1
// You may obtain a copy of the License at https://solderpad.org/licenses/
//
// Author: Farhan Ali Shah, 10xEngineers
// Date: 15.11.2024
// Description: ZCMT extension in the CVA6 core targeting the 32-bit embedded-class platforms (CV32A60x).
// ZCMT is a code-size reduction feature that utilizes compressed table jump instructions (cm.jt and cm.jalt) to
//reduce code size for embedded systems
//
module zcmt_decoder #(
    parameter config_pkg::cva6_cfg_t CVA6Cfg = config_pkg::cva6_cfg_empty,
    parameter type dcache_req_i_t = logic,
    parameter type dcache_req_o_t = logic,
    parameter type jvt_t = logic,
    parameter type branchpredict_sbe_t = logic
) (
    // Subsystem Clock - SUBSYSTEM
    input  logic                             clk_i,
    // Asynchronous reset active low - SUBSYSTEM
    input  logic                             rst_ni,
    // Instruction input - compressed_decoder
    input  logic          [            31:0] instr_i,
    // current PC - FRONTEND
    input  logic          [CVA6Cfg.VLEN-1:0] pc_i,
    // Intruction is of ZCMT extension - compressed_decoder
    input  logic                             is_zcmt_instr_i,
    // Instruction is illegal - compressed_decoder
    input  logic                             illegal_instr_i,
    // Instruction is compressed - compressed_decoder
    input  logic                             is_compressed_i,
    // JVT struct input - CSR
    input  jvt_t                             jvt_i,
    // Data cache request output - CACHE
    input  dcache_req_o_t                    req_port_i,
    // Instruction out - cvxif_compressed_if_driver
    output logic          [            31:0] instr_o,
    // Instruction is illegal out - cvxif_compressed_if_driver     
    output logic                             illegal_instr_o,
    // Instruction is compressed out - cvxif_compressed_if_driver
    output logic                             is_compressed_o,
    // Fetch stall - cvxif_compressed_if_driver
    output logic                             fetch_stall_o,
    // Data cache request input - CACHE
    output dcache_req_i_t                    req_port_o,
    // jump_address
    output logic          [CVA6Cfg.XLEN-1:0] jump_address_o
);

  // FSM States
  enum logic {
    IDLE,  // if ZCMT instruction then request sent to fetch the entry from jump table
    TABLE_JUMP  // Check the valid data from jump table and Calculate the offset for jump and create jal instruction
  }
      state_d, state_q;
  // Temporary registers
  // Physical address: jvt + (index <<2)
  logic [CVA6Cfg.VLEN-1:0] table_address;

  always_comb begin
    state_d               = state_q;
    illegal_instr_o       = 1'b0;
    is_compressed_o       = is_zcmt_instr_i || is_compressed_i;
    fetch_stall_o         = '0;
    jump_address_o        = '0;

    // cache request port
    req_port_o.data_wdata = '0;
    req_port_o.data_wuser = '0;
    req_port_o.data_req   = 1'b0;
    req_port_o.data_we    = 1'b0;
    req_port_o.data_be    = '0;
    req_port_o.data_size  = 2'b10;
    req_port_o.data_id    = 1'b1;
    req_port_o.kill_req   = 1'b0;
    req_port_o.tag_valid  = 1'b1;

    unique case (state_q)
      IDLE: begin
        fetch_stall_o = 1'b0;
        if (is_zcmt_instr_i) begin
          if (CVA6Cfg.IS_XLEN32) begin  //It is only target for 32 bit targets in cva6 with No MMU
            table_address = {jvt_i.base, 6'b000000} + {22'h0, instr_i[9:2], 2'b00};
            req_port_o.address_index = table_address[9:0];
            req_port_o.address_tag = table_address[CVA6Cfg.VLEN-1:10];  // No MMU support
            state_d = TABLE_JUMP;
            req_port_o.data_req = 1'b1;
            fetch_stall_o = 1'b1;
          end else illegal_instr_o = 1'b1;
          // Condition may be extended for 64 bits embedded targets with No MMU
        end else begin
          illegal_instr_o = illegal_instr_i;
          instr_o         = instr_i;
          state_d         = IDLE;
        end
      end
      TABLE_JUMP: begin
        if (req_port_i.data_rvalid) begin
          // save the PC relative Xlen table jump address 
          jump_address_o = $unsigned($signed(req_port_i.data_rdata) - $signed(pc_i));
          if (instr_i[9:2] < 32) begin  // jal pc_offset, x0 for no return stack
            instr_o = {
              20'h0, 5'h0, riscv::OpcodeJal
            };  // immediate assigned here (0) will be overwrite in decode stage with jump_address_o
          end else if ((instr_i[9:2] >= 32) & (instr_i[9:2] <= 255))  begin  //- jal pc_offset, x1 for return stack
            instr_o = {
              20'h0, 5'h1, riscv::OpcodeJal
            };  // immediate assigned here (0) will be overwrite in decode stage with jump_address_o
          end else begin
            illegal_instr_o = 1'b1;
            instr_o         = instr_i;
          end
          state_d = IDLE;
        end else begin
          state_d = TABLE_JUMP;
        end
      end
      default: begin
        state_d = IDLE;
      end
    endcase
  end

  always_ff @(posedge clk_i or negedge rst_ni) begin
    if (~rst_ni) begin
      state_q <= IDLE;

    end else begin
      state_q <= state_d;
    end
  end
endmodule
