`default_nettype none
module serv_decode
  (
   input wire 	     clk,
   //Input
   input wire 	     i_cnt_en,
   input wire 	     i_cnt_done,
   input wire [31:2] i_wb_rdt,
   input wire 	     i_wb_en,
   input wire 	     i_alu_cmp,
   //To state
   output wire 	     o_take_branch,
   output wire 	     o_e_op,
   output wire 	     o_ebreak,
   output wire 	     o_branch_op,
   output wire 	     o_mem_op,
   output wire 	     o_shift_op,
   output wire 	     o_slt_op,
   output wire 	     o_rd_op,
   //To bufreg
   output wire 	     o_bufreg_loop,
   output wire 	     o_bufreg_rs1_en,
   output wire 	     o_bufreg_imm_en,
   output wire 	     o_bufreg_clr_lsb,
   //To ctrl
   output wire 	     o_ctrl_jal_or_jalr,
   output wire 	     o_ctrl_utype,
   output wire 	     o_ctrl_pc_rel,
   output wire 	     o_ctrl_mret,
   //To alu
   output wire 	     o_alu_sub,
   output wire [1:0] o_alu_bool_op,
   output wire 	     o_alu_cmp_eq,
   output wire 	     o_alu_cmp_uns,
   output wire 	     o_alu_sh_signed,
   output wire 	     o_alu_sh_right,
   output wire [3:0]  o_alu_rd_sel,
   //To RF
   output reg [4:0]  o_rf_rd_addr,
   output reg [4:0]  o_rf_rs1_addr,
   output reg [4:0]  o_rf_rs2_addr,
   //To mem IF
   output wire 	     o_mem_signed,
   output wire 	     o_mem_word,
   output wire 	     o_mem_half,
   output wire 	     o_mem_cmd,
   //To CSR
   output wire 	     o_csr_en,
   output wire [1:0] o_csr_addr,
   output wire 	     o_csr_mstatus_en,
   output wire 	     o_csr_mie_en,
   output wire 	     o_csr_mcause_en,
   output wire [1:0] o_csr_source,
   output wire 	     o_csr_d_sel,
   output wire 	     o_csr_imm,
   //To top
   output wire 	     o_imm,
   output wire 	     o_op_b_source,
   output wire 	     o_rd_csr_en,
   output wire 	     o_rd_alu_en);

`include "serv_params.vh"

   reg [4:0] opcode;
   reg [2:0] funct3;
   reg 	      op20;
   reg 	      op21;
   reg 	      op22;
   reg 	      op26;

   reg       imm30;

   wire      op_or_opimm = (!opcode[4] & opcode[2] & !opcode[0]);

   assign o_mem_op   = !opcode[4] & !opcode[2] & !opcode[0];
   assign o_shift_op = op_or_opimm & (funct3[1:0] == 2'b01);
   assign o_slt_op   = op_or_opimm & (funct3[2:1] == 2'b01);
   assign o_branch_op = opcode[4] & !opcode[2];

   //Matches system opcodes except CSR accesses (funct3 == 0)
   //No idea anymore why the !op21 condition is needed here
   assign o_e_op = opcode[4] & opcode[2] & !op21 & !(|funct3);

   assign o_ebreak = op20;

   //jal,branch =     imm
   //jalr       = rs1+imm
   //mem        = rs1+imm
   //shift      = rs1
   assign o_bufreg_rs1_en = !opcode[4] | (!opcode[1] & opcode[0]);
   assign o_bufreg_imm_en = !opcode[2];

   //Loop bufreg contents for shift operations
   assign o_bufreg_loop   = op_or_opimm;

   //Clear LSB of immediate for BRANCH and JAL ops
   //True for BRANCH and JAL
   //False for JALR/LOAD/STORE/OP/OPIMM?
   assign o_bufreg_clr_lsb = opcode[4] & ((opcode[1:0] == 2'b00) | (opcode[1:0] == 2'b11));

   //Take branch for jump or branch instructions (opcode == 1x0xx) if
   //a) It's an unconditional branch (opcode[0] == 1)
   //b) It's a conditional branch (opcode[0] == 0) of type beq,blt,bltu (funct3[0] == 0) and ALU compare is true
   //c) It's a conditional branch (opcode[0] == 0) of type bne,bge,bgeu (funct3[0] == 1) and ALU compare is false
   //Only valid during the last cycle of INIT, when the branch condition has
   //been calculated.
   assign o_take_branch = opcode[4] & !opcode[2] & (opcode[0] | (i_alu_cmp^funct3[0]));

   assign o_ctrl_utype       = !opcode[4] & opcode[2] & opcode[0];
   assign o_ctrl_jal_or_jalr = opcode[4] & opcode[0];

   //True for jal, b* auipc
   //False for jalr, lui
   assign o_ctrl_pc_rel = (opcode[2:0] == 3'b000) |
			  (opcode[1:0] == 2'b11) |
			  (opcode[4:3] == 2'b00);

   assign o_ctrl_mret = (opcode[4] & opcode[2] & op21 & !(|funct3));

   //Write to RD
   //True for OP-IMM, AUIPC, OP, LUI, SYSTEM, JALR, JAL, LOAD
   //False for STORE, BRANCH, MISC-MEM
   assign o_rd_op = (opcode[2] |
		     (!opcode[2] & opcode[4] & opcode[0]) |
		     (!opcode[2] & !opcode[3] & !opcode[0])) & (|o_rf_rd_addr);

   assign o_alu_sub = opcode[3] & imm30/*alu_sub_r*/;

   /*
    300 0_000 mstatus RWSC
    304 0_100 mie SCWi
    305 0_101 mtvec RW
    340 1_000 mscratch
    341 1_001 mepc  RW
    342 1_010 mcause R
    343 1_011 mtval
    344 1_100 mip CWi
    */

   //true  for mtvec,mscratch,mepc and mtval
   //false for mstatus, mie, mcause, mip
   wire csr_valid = op20 | (op26 & !op22 & !op21);

   //Matches system ops except eceall/ebreak
   wire csr_op = opcode[4] & opcode[2] & (|funct3);
   assign o_rd_csr_en = csr_op;

   assign o_csr_en         = csr_op & csr_valid;
   assign o_csr_mstatus_en = csr_op & !op26 & !op22;
   assign o_csr_mie_en     = csr_op & !op26 &  op22 & !op20;
   assign o_csr_mcause_en  = csr_op         &  op21 & !op20;

   assign o_csr_source = funct3[1:0];
   assign o_csr_d_sel = funct3[2];
   assign o_csr_imm = o_rf_rs1_addr[0];

   assign o_csr_addr = (op26 & !op20) ? CSR_MSCRATCH :
		       (op26 & !op21) ? CSR_MEPC :
		       (op26)         ? CSR_MTVAL :
		       CSR_MTVEC;

   assign o_alu_cmp_eq = funct3[2:1] == 2'b00;

   assign o_alu_cmp_uns = (funct3[0] & funct3[1]) | (funct3[1] & funct3[2]);
   assign o_alu_sh_signed = imm30;
   assign o_alu_sh_right = funct3[2];

   assign o_mem_cmd  = opcode[3];
   assign o_mem_signed = ~funct3[2];
   assign o_mem_word   = funct3[1];
   assign o_mem_half   = funct3[0];

   assign o_alu_bool_op = funct3[1:0];

   reg 	      signbit;

   reg [8:0]  imm19_12_20;
   reg 	      imm7;
   reg [5:0]  imm30_25;
   reg [4:0]  imm24_20;
   reg [4:0]  imm11_7;

   wire [1:0] m2;
   //True for OP-IMM, LOAD, STORE, JALR
   //False for LUI, AUIPC, JAL
   assign m2[0] = (opcode[1:0] == 2'b00) | (opcode[2:1] == 2'b00);
   assign m2[1] = opcode[4] & !opcode[0];
   wire m3 = opcode[4];

   assign o_alu_rd_sel[0] = (funct3 == 3'b000); // Add/sub
   assign o_alu_rd_sel[1] = (funct3[1:0] == 2'b01); //Shift
   assign o_alu_rd_sel[2] = (funct3[2:1] == 2'b01); //SLT*
   assign o_alu_rd_sel[3] = (funct3[2] & !(funct3[1:0] == 2'b01)); //Bool
   always @(posedge clk) begin
      if (i_wb_en) begin
         o_rf_rd_addr  <= i_wb_rdt[11:7];
         o_rf_rs1_addr <= i_wb_rdt[19:15];
         o_rf_rs2_addr <= i_wb_rdt[24:20];
         funct3        <= i_wb_rdt[14:12];
         imm30         <= i_wb_rdt[30];
         opcode        <= i_wb_rdt[6:2];
	 op20 <= i_wb_rdt[20];
	 op21 <= i_wb_rdt[21];
	 op22 <= i_wb_rdt[22];
	 op26 <= i_wb_rdt[26];

	 //Immediate decoder
	 signbit     <= i_wb_rdt[31];
	 imm19_12_20 <= {i_wb_rdt[19:12],i_wb_rdt[20]};
	 imm7        <= i_wb_rdt[7];
	 imm30_25    <= i_wb_rdt[30:25];
	 imm24_20    <= i_wb_rdt[24:20];
	 imm11_7     <= i_wb_rdt[11:7];
      end
      if (i_cnt_en) begin
	 imm19_12_20 <= {m3 ? signbit : imm24_20[0], imm19_12_20[8:1]};
	 imm7        <= signbit;
	 imm30_25    <= {m2[1] ? imm7 : m2[0] ? signbit : imm19_12_20[0], imm30_25[5:1]};
	 imm24_20    <= {imm30_25[0], imm24_20[4:1]};
	 imm11_7     <= {imm30_25[0], imm11_7[4:1]};
	 if (csr_op & o_csr_d_sel)
	   o_rf_rs1_addr <= {1'b0,o_rf_rs1_addr[4:1]};
      end
   end

   //True for S (STORE) or B (BRANCH) type instructions
   //False for J type instructions
   wire m1 = opcode[3:0] == 4'b1000;

   assign o_imm = i_cnt_done ? signbit : m1 ? imm11_7[0] : imm24_20[0];

   //0 (OP_B_SOURCE_IMM) when OPIMM
   //1 (OP_B_SOURCE_RS2) when BRANCH or OP
   assign o_op_b_source = opcode[3];

   assign o_rd_alu_en  = !opcode[0] & opcode[2] & !opcode[4];


endmodule
