-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
--
-- Copyright 2025 VLSI Solution Oy
-- SPDX-License-Identifier: Apache-2.0 WITH SHL-2.1
--
-- Licensed under the Solderpad Hardware License v 2.1 (the "License");
-- you may not use this file except in compliance with the License, or,
-- at your option, the Apache License version 2.0.
-- You may obtain a copy of the License at
--
--    https://solderpad.org/licenses/SHL-2.1/
--
-- Unless required by applicable law or agreed to in writing, any work
-- 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.
--
-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
--                               RISCV core
--                                 V1.1
--
--
-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

-- - - - - - - -
--  libraries
-- - - - - - - -
library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.numeric_std.all;
use work.rv_pkg.all;

entity rv_decoder is
  generic ( RISCV_M       : sl_t := '1';    -- mul / div extension
            RISCV_A       : sl_t := '1';    -- amo extension
            RISCV_Zba     : sl_t := '0';    -- address generation (shXadd)
            RISCV_ZB      : sl_t := '0' );  -- ZB extension
  port ( valid_i        : in sl_t;
         fetch_fault_i  : in sl_t;
         iword_i        : in slv_31_00;
         --
         alu_op_o       : out alu_op_t;
         mul_op_o       : out mul_op_t;     -- mul: mul, mulh, mulhsu, mulhu
         div_op_o       : out div_op_t;     -- div: div, divu, rem, remu
         lsu_op_o       : out lsu_op_t;
         csr_op_o       : out csr_op_t;
         branch_op_o    : out branch_op_t;
         amo_op_o       : out amo_op_t;
         --
         idec_group_o   : out i_group_t
         );
end entity rv_decoder;


architecture rtl of rv_decoder is

  signal invalid_w        : std_logic;
  signal alu_w            : std_logic;
  signal mul_w            : std_logic;
  signal div_w            : std_logic;
  signal lsu_w            : std_logic;
  signal lsu_s_w          : std_logic;
  signal branch_w         : std_logic;
  signal csr_reg_w        : std_logic;
  signal csr_w            : std_logic;

  signal alu_op_w         : alu_op_t;

  -- ALU immediate instructions
  signal instr_addi_w     : std_logic;
  signal instr_andi_w     : std_logic;
  signal instr_ori_w      : std_logic;
  signal instr_xori_w     : std_logic;
  signal instr_slli_w     : std_logic;
  signal instr_srli_w     : std_logic;
  signal instr_srai_w     : std_logic;
  signal instr_slti_w     : std_logic;
  signal instr_sltiu_w    : std_logic;
  signal alu_2operand_w   : std_logic;
  signal alu_immediate_W  : std_logic;
  signal func3_w          : slv_02_00;
  signal nop_w            : std_logic;  -- 0x00000013 == ADDI
  -- lsu op
  signal instr_lb_w       : std_logic;
  signal instr_lh_w       : std_logic;
  signal instr_lw_w       : std_logic;
  signal instr_lbu_w      : std_logic;  -- load byte unsigned
  signal instr_lhu_w      : std_logic;  -- load half word unsiged
  signal instr_lwu_w      : std_logic;  -- load word unsiged
  --
  signal instr_sb_w       : std_logic;
  signal instr_sh_w       : std_logic;
  signal instr_sw_w       : std_logic;

  -- branch op
  signal instr_jal_w      : std_logic;
  signal instr_jalr_w     : std_logic;
  signal instr_beq_w      : std_logic;
  signal instr_bne_w      : std_logic;
  signal instr_blt_w      : std_logic;
  signal instr_bge_w      : std_logic;
  signal instr_bltu_w     : std_logic;
  signal instr_bgeu_w     : std_logic;

  -- multiplier op
  signal instr_mul_w      : std_logic;
  signal instr_mulh_w     : std_logic;
  signal instr_mulhsu_w   : std_logic;
  signal instr_mulhu_w    : std_logic;

  -- divider op
  signal instr_div_w      : std_logic;
  signal instr_divu_w     : std_logic;
  signal instr_rem_w      : std_logic;
  signal instr_remu_w     : std_logic;

  -- csr op
  signal instr_ecall_w    : std_logic;
  signal instr_ebreak_w   : std_logic;
  signal instr_eret_w     : std_logic;
  signal instr_csrrw_w    : std_logic;
  signal instr_csrrs_w    : std_logic;
  signal instr_csrrc_w    : std_logic;
  signal instr_csrrwi_w   : std_logic;
  signal instr_csrrsi_w   : std_logic;
  signal instr_csrrci_w   : std_logic;
  signal instr_wfi_w      : std_logic;
  signal instr_fence_w    : std_logic;
  signal instr_sfence_w   : std_logic;
  signal instr_ifence_w   : std_logic;

  -- amp op
  signal amo_op_w         : amo_op_t;
  signal instr_amo_w      : std_logic;

begin

  -- invalid instruction
  invalid_w <= valid_i and
               not (alu_w    or  -- ANDI, ADDI, SLTI, SLTIU, ORI, XORI, SLLI,
                                 -- SRLI, SRAI,
                                 -- LUI, AUIPC,
                                 -- ADD, SUB, SLT, SLTU, XOR, OR,AND,SLL,SRL,SRA
                    nop_w    or
                    branch_w or  -- JAL, JALR,   BEQ,BNE,BLT,BGE,BLTU,BGEU
                    lsu_w    or  -- LB, LH, LW, LBU, LHU, LWUm
                    lsu_s_w  or  -- SB, SH, SW
                    csr_w    or  -- ECALL, EBREAK, ERET,
                                 -- CSRRW, CSRRS, CSRRC, CSRRWI, CSRRSI, CSRRCI,
                                 -- WFI,
                                 -- FENCE, IFENCE, SFENCE
                    mul_w    or  -- MUL, MULH, MULHSU, MULHU
                    div_w );     -- DIV, DIVU, REM, REMU

  idec_group_o.invalid <= invalid_w;


  -- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  idec_group_o.rd_valid <= instr_jalr_w  or
                           instr_jal_w   or
                           (alu_w and (not nop_w)) or    -- ! NOP
                                        -- ANDI, ADDI, SLTI, SLTIU, ORI, XORI,
                                        --   SLLI, SRLI, SRAI,
                                        --   LUI, AUIPC,
                                        --   ADD, SUB, SLT, SLTU, XOR,
                                        --   OR, AND, SLL, SRL, SRA
                                        --   ( SH1ADD, SH2ADD, SH3ADD )  - Zba
                           lsu_w or     -- LB, LH, LW, LBU, LHU,LWU
                           mul_w or     -- MUL, MULH, MULHSU, MULHU
                           div_w or     -- DIV, DIVU, REM, REMU
                           csr_reg_w ;  -- CSRRW, CSRRS, CSRRC,
                                        --   CSRRWI, CSRRSI, CSRRCI


  -- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  instr_jal_w         <= f_opcode_eq(iword_i, INST_JAL);  -- jump & link
  instr_jalr_w        <= f_opcode_eq(iword_i, INST_JALR); -- jump & link reg
  instr_beq_w         <= f_opcode_eq(iword_i, INST_BEQ);  -- branch equal
  instr_bne_w         <= f_opcode_eq(iword_i, INST_BNE);  -- branch not equal
  instr_blt_w         <= f_opcode_eq(iword_i, INST_BLT);  -- <=
  instr_bge_w         <= f_opcode_eq(iword_i, INST_BGE);  -- >=
  instr_bltu_w        <= f_opcode_eq(iword_i, INST_BLTU); -- <= unsigned
  instr_bgeu_w        <= f_opcode_eq(iword_i, INST_BGEU); -- >= unsigned

  branch_w            <= instr_jal_w or instr_jalr_w or
                         instr_beq_w or instr_bne_w  or instr_blt_w or
                         instr_bge_w or instr_bltu_w or instr_bgeu_w;

  branch_op_o.i_jal   <= instr_jal_w;
  branch_op_o.i_jalr  <= instr_jalr_w;
  branch_op_o.i_beq   <= instr_beq_w;
  branch_op_o.i_bne   <= instr_bne_w;
  branch_op_o.i_blt   <= instr_blt_w;
  branch_op_o.i_bge   <= instr_bge_w;
  branch_op_o.i_bltu  <= instr_bltu_w;
  branch_op_o.i_bgeu  <= instr_bgeu_w;

  idec_group_o.branch <= branch_w;


  -- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  -- alu ops
  --
  -- ! ! NOTE: NOP == 0x00000013 == INST_ADDI
  --

  nop_w                <= eq(iword_i, INST_NOP);

  --  immediate ops
  instr_addi_w         <= f_opcode_eq(iword_i, INST_ADDI) and (not nop_w);
  instr_andi_w         <= f_opcode_eq(iword_i, INST_ANDI);
  instr_ori_w          <= f_opcode_eq(iword_i, INST_ORI);
  instr_xori_w         <= f_opcode_eq(iword_i, INST_XORI);
  --  immediate shift
  instr_slli_w         <= f_opcode_eq(iword_i, INST_SLLI);
  instr_srli_w         <= f_opcode_eq(iword_i, INST_SRLI);
  instr_srai_w         <= f_opcode_eq(iword_i, INST_SRAI);
  instr_slti_w         <= f_opcode_eq(iword_i, INST_SLTI);
  instr_sltiu_w        <= f_opcode_eq(iword_i, INST_SLTIU);
  --
  alu_immediate_w      <= instr_addi_w  or
                          instr_andi_w  or
                          instr_ori_w   or
                          instr_xori_w  or
                          instr_slli_w  or
                          instr_srli_w  or
                          instr_srai_w  or
                          instr_slti_w  or
                          instr_sltiu_w;
  alu_op_w.i_immediate <= alu_immediate_w;
  --
  alu_op_w.i_add       <= f_opcode_eq(iword_i, INST_ADD);   -- alu ops
  -- Zba - address generation
  alu_op_w.i_sh1add    <= ( f_opcode_eq(iword_i, INST_SH1ADD) and
                            (RISCV_Zba or RISCV_ZB) );
  alu_op_w.i_sh2add    <= ( f_opcode_eq(iword_i, INST_SH2ADD) and
                            (RISCV_Zba or RISCV_ZB) );
  alu_op_w.i_sh3add    <= ( f_opcode_eq(iword_i, INST_SH3ADD) and
                            (RISCV_Zba or RISCV_ZB) );
  --
  alu_op_w.i_sub       <= f_opcode_eq(iword_i, INST_SUB);
  alu_op_w.i_and       <= f_opcode_eq(iword_i, INST_AND);
  alu_op_w.i_or        <= f_opcode_eq(iword_i, INST_OR);
  alu_op_w.i_xor       <= f_opcode_eq(iword_i, INST_XOR);
  alu_op_w.i_sll       <= f_opcode_eq(iword_i, INST_SLL);   -- alu shift ops
  alu_op_w.i_srl       <= f_opcode_eq(iword_i, INST_SRL);
  alu_op_w.i_sra       <= f_opcode_eq(iword_i, INST_SRA);
  alu_op_w.i_slt       <= f_opcode_eq(iword_i, INST_SLT);
  alu_op_w.i_sltu      <= f_opcode_eq(iword_i, INST_SLTU);
  --
  alu_2operand_w       <= not ( (not alu_op_w.i_add)   and
                                (not alu_op_w.i_and)   and
                                (not alu_op_w.i_or)    and
                                (not alu_op_w.i_xor)   and
                                (not alu_op_w.i_sll)   and
                                (not alu_op_w.i_srl)   and
                                (not alu_op_w.i_sra)   and
                                (not alu_op_w.i_slt)   and
                                (not alu_op_w.i_sltu)  and
                                (not alu_op_w.i_sub) ); -- and
                                -- Zba - addres generation
                                -- (not alu_op_w.i_sh1add) and
                                -- (not alu_op_w.i_sh2add) and
                                -- (not alu_op_w.i_sh3add) );
  alu_op_w.i_2operand  <= alu_2operand_w;
  --
  alu_op_w.i_lui       <= f_opcode_eq(iword_i, INST_LUI);   -- load upper imm20
  alu_op_w.i_auipc     <= f_opcode_eq(iword_i, INST_AUIPC); -- add  upper imm20
  --                                                        -- to pc
  --
  alu_op_w.i_jal       <= instr_jal_w;                      -- jump and link
  alu_op_w.i_jalr      <= instr_jalr_w;                     -- jump and link reg
  --
  --
  func3_w              <= iword_i(FUNC3_R'range);           -- 14:12
  alu_op_w.alu_func4   <= '1'  & func3_w when (alu_op_w.i_sra  = '1'  or
                                               instr_srai_w    = '1'  or
                                               alu_op_w.i_sub  = '1') else
                          '0'  & func3_w when (alu_2operand_w  = '1'  or
                                               alu_immediate_w = '1'  or
                                               alu_op_w.i_jalr = '1') else
                          ALU_ADD when (alu_op_w.i_auipc  = '1'       or
                                        alu_op_w.i_jal    = '1'       or
                                        alu_op_w.i_sh1add = '1'       or
                                        alu_op_w.i_sh2add = '1'       or
                                        alu_op_w.i_sh3add = '1')      else
                          ALU_NONE;
  --
  --
  --
  alu_op_o             <= alu_op_w;
  --
  alu_w                <= alu_immediate_w       -- alu immediate ops
                          --
                          or alu_2operand_w
                          --
                          or alu_op_w.i_lui     --  load upper imm20
                          or alu_op_w.i_auipc   --  add upper imm20 to pc
                          --
                          -- ! NOTE: EXCLUDE HERE jal /jalr already in branch_op
                          -- or alu_op_w.i_jal  -- jump and link
                          -- or alu_op_w.i_jalr -- jump and link register
                          or alu_op_w.zb_valid
                          --
                          or alu_op_w.i_sh1add
                          or alu_op_w.i_sh2add
                          or alu_op_w.i_sh3add
                          ;



  -- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  --         enable             legal
  p_zb_dec( RISCV_ZB, iword_i, alu_op_w.zb_valid, alu_op_w.zb_func5 );

  -- ! ! ZEXTH and PACK match partially ! !

  -- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  instr_lb_w         <= f_opcode_eq(iword_i, INST_LB);
  instr_lh_w         <= f_opcode_eq(iword_i, INST_LH);
  instr_lw_w         <= f_opcode_eq(iword_i, INST_LW) or
                        amo_op_w.i_lr  or  not_zero(amo_op_w.amofunc);
  --                                   amo executed as single load for the cpu
  --
  instr_lbu_w        <= f_opcode_eq(iword_i, INST_LBU); -- load byte unsiged
  instr_lhu_w        <= f_opcode_eq(iword_i, INST_LHU); -- ld half word unsiged
  instr_lwu_w        <= f_opcode_eq(iword_i, INST_LWU); -- load word unsiged
  --
  instr_sb_w         <= f_opcode_eq(iword_i, INST_SB);
  instr_sh_w         <= f_opcode_eq(iword_i, INST_SH);
  instr_sw_w         <= f_opcode_eq(iword_i, INST_SW)  or  amo_op_w.i_sc ;

  lsu_w              <= instr_lb_w  or instr_lh_w  or instr_lw_w  or
                        instr_lbu_w or instr_lhu_w or instr_lwu_w or
                        amo_op_w.i_sc ;  -- atomic sc also stores sc status


  lsu_s_w            <= instr_sb_w or instr_sh_w  or  instr_sw_w ; -- b / h / w

  lsu_op_o.i_lb      <= instr_lb_w;
  lsu_op_o.i_lh      <= instr_lh_w;
  lsu_op_o.i_lw      <= instr_lw_w;
  lsu_op_o.i_lbu     <= instr_lbu_w;                    -- ld byte unsiged
  lsu_op_o.i_lhu     <= instr_lhu_w;                    -- ld half word unsiged
  lsu_op_o.i_lwu     <= instr_lwu_w;                    -- ld word unsiged
  --
  lsu_op_o.i_sb      <= instr_sb_w;
  lsu_op_o.i_sh      <= instr_sh_w;
  lsu_op_o.i_sw      <= instr_sw_w;
  --
  lsu_op_o.i_csrrw   <= instr_csrrw_w;
  lsu_op_o.i_atomic  <= amo_op_w.i_lr or amo_op_w.i_sc;
  lsu_op_o.i_amo     <= not_zero(amo_op_w.amofunc);


  idec_group_o.lsu <= lsu_w or lsu_s_w; -- LB,LH,LW,  LBU,LHU,LWU,  SB,SH,SW


  -- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  instr_mul_w        <= f_opcode_eq(iword_i, INST_MUL);
  instr_mulh_w       <= f_opcode_eq(iword_i, INST_MULH);
  instr_mulhsu_w     <= f_opcode_eq(iword_i, INST_MULHSU);
  instr_mulhu_w      <= f_opcode_eq(iword_i, INST_MULHU);

  mul_w              <= RISCV_M and
                        ( instr_mul_w or instr_mulh_w or
                          instr_mulhsu_w or instr_mulhu_w ) ;

  -- function select to rv multiplier
  mul_op_o.i_mul     <= instr_mul_w;
  mul_op_o.i_mulh    <= instr_mulh_w;
  mul_op_o.i_mulhsu  <= instr_mulhsu_w;
  mul_op_o.i_mulhu   <= instr_mulhu_w;

  idec_group_o.mul   <= mul_w;


  -- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  instr_div_w        <= f_opcode_eq(iword_i, INST_DIV);
  instr_divu_w       <= f_opcode_eq(iword_i, INST_DIVU);
  instr_rem_w        <= f_opcode_eq(iword_i, INST_REM);
  instr_remu_w       <= f_opcode_eq(iword_i, INST_REMU);

  div_w              <= RISCV_M and
                        ( instr_div_w or instr_divu_w or
                          instr_rem_w or instr_remu_w );

  -- function select to riscv divider
  div_op_o.i_remu    <= instr_remu_w;
  div_op_o.i_rem     <= instr_rem_w;    -- NOTE: "rem" is reserved word
  div_op_o.i_divu    <= instr_divu_w;
  div_op_o.i_div     <= instr_div_w;

  idec_group_o.div <= div_w;


  -- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  instr_ecall_w        <= f_opcode_eq(iword_i, INST_ECALL);
  instr_ebreak_w       <= f_opcode_eq(iword_i, INST_EBREAK);
  instr_eret_w         <= f_opcode_eq(iword_i, INST_ERET);

  instr_csrrw_w        <= f_opcode_eq(iword_i, INST_CSRRW);
  instr_csrrs_w        <= f_opcode_eq(iword_i, INST_CSRRS);
  instr_csrrc_w        <= f_opcode_eq(iword_i, INST_CSRRC);
  instr_csrrwi_w       <= f_opcode_eq(iword_i, INST_CSRRWI);
  instr_csrrsi_w       <= f_opcode_eq(iword_i, INST_CSRRSI);
  instr_csrrci_w       <= f_opcode_eq(iword_i, INST_CSRRCI);

  instr_wfi_w          <= f_opcode_eq(iword_i, INST_WFI);      -- wait 4 int
  instr_fence_w        <= f_opcode_eq(iword_i, INST_FENCE);    -- ! ! NOP
  instr_sfence_w       <= f_opcode_eq(iword_i, INST_SFENCE);
  instr_ifence_w       <= f_opcode_eq(iword_i, INST_IFENCE);

  csr_reg_w            <= instr_csrrw_w or instr_csrrs_w or instr_csrrc_w or
                          instr_csrrwi_w or instr_csrrsi_w or instr_csrrci_w;

  csr_w                <= instr_ecall_w or instr_ebreak_w or instr_eret_w  or
                          csr_reg_w     or
                          instr_wfi_w   or
                          instr_fence_w or instr_ifence_w or instr_sfence_w ;

  -- function select to rv_csr
  csr_op_o.i_ecall     <= instr_ecall_w;
  csr_op_o.i_ebreak    <= instr_ebreak_w;
  csr_op_o.i_eret      <= instr_eret_w;
  csr_op_o.i_csrrw     <= instr_csrrw_w;
  csr_op_o.i_csrrs     <= instr_csrrs_w;
  csr_op_o.i_csrrc     <= instr_csrrc_w;
  csr_op_o.i_csrrwi    <= instr_csrrwi_w;
  csr_op_o.i_csrrsi    <= instr_csrrsi_w;
  csr_op_o.i_csrrci    <= instr_csrrci_w;
  csr_op_o.i_wfi       <= instr_wfi_w;
  csr_op_o.i_fence     <= instr_fence_w;    -- ! ! NOP
  csr_op_o.i_sfence    <= instr_sfence_w;
  csr_op_o.i_ifence    <= instr_ifence_w;

  idec_group_o.csr     <= invalid_w or fetch_fault_i  or csr_w;
  idec_group_o.csr_reg <= csr_reg_w;        -- single cycle csr instruction


  -- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  -- amo ops (atomic memory operation)
  -- lr (load-locked) / sc (store-cond) / amoswap
  -- amoadd / amoxor / amoand / amoor / amomin / amomax / amominu / amomaxu

  --          enable            legal
  p_amo_dec ( RISCV_A, iword_i, instr_amo_w, amo_op_w );
  amo_op_o             <= amo_op_w;
  idec_group_o.amo     <= instr_amo_w;

end rtl;
