/* -----------------------------------------------------------------------------
* Project Name   : Architectures of Processor Systems (APS) lab work
* Organization   : National Research University of Electronic Technology (MIET)
* Department     : Institute of Microdevices and Control Systems
* Author(s)      : Andrei Solodovnikov
* Email(s)       : hepoh@org.miet.ru

See https://github.com/MPSU/APS/blob/master/LICENSE file for licensing details.
* ------------------------------------------------------------------------------
*/

module lab_05_tb_decoder();

  import decoder_pkg::*;
  typedef class riscv_instr;
  riscv_instr instr = new();

  logic clk;
  logic [31:0]  fetched_instr_i = '0;

  int err_count;

  //list of all valid opcodes
  logic [4:0] opcode_array[11] =  {
    LOAD_OPCODE,
    MISC_MEM_OPCODE,
    OP_IMM_OPCODE,
    AUIPC_OPCODE,
    STORE_OPCODE,
    OP_OPCODE,
    LUI_OPCODE,
    BRANCH_OPCODE,
    JALR_OPCODE,
    JAL_OPCODE,
    SYSTEM_OPCODE
    };

  /*
    Since randomize with overconstraining slows down simulation, it has been
    decided to make a list of all codes instead of looping over every possible
    combination func3 and func7.
  */
  logic [14:0] valid_codes[44] = {
    {LOAD_OPCODE    , 3'h0, 7'h00},
    {LOAD_OPCODE    , 3'h1, 7'h00},
    {LOAD_OPCODE    , 3'h2, 7'h00},
    {LOAD_OPCODE    , 3'h4, 7'h00},
    {LOAD_OPCODE    , 3'h5, 7'h00},
    {MISC_MEM_OPCODE, 3'h0, 7'h00},
    {OP_IMM_OPCODE  , 3'h0, 7'h00},
    {OP_IMM_OPCODE  , 3'h1, 7'h00},
    {OP_IMM_OPCODE  , 3'h2, 7'h00},
    {OP_IMM_OPCODE  , 3'h3, 7'h00},
    {OP_IMM_OPCODE  , 3'h4, 7'h00},
    {OP_IMM_OPCODE  , 3'h5, 7'h00},
    {OP_IMM_OPCODE  , 3'h5, 7'h20},
    {OP_IMM_OPCODE  , 3'h6, 7'h00},
    {OP_IMM_OPCODE  , 3'h7, 7'h00},
    {AUIPC_OPCODE   , 3'h0, 7'h00},
    {STORE_OPCODE   , 3'h0, 7'h00},
    {STORE_OPCODE   , 3'h1, 7'h00},
    {STORE_OPCODE   , 3'h2, 7'h00},
    {OP_OPCODE      , 3'h0, 7'h00},
    {OP_OPCODE      , 3'h0, 7'h20},
    {OP_OPCODE      , 3'h1, 7'h00},
    {OP_OPCODE      , 3'h2, 7'h00},
    {OP_OPCODE      , 3'h3, 7'h00},
    {OP_OPCODE      , 3'h4, 7'h00},
    {OP_OPCODE      , 3'h5, 7'h00},
    {OP_OPCODE      , 3'h5, 7'h20},
    {OP_OPCODE      , 3'h6, 7'h00},
    {OP_OPCODE      , 3'h7, 7'h00},
    {LUI_OPCODE     , 3'h0, 7'h00},
    {BRANCH_OPCODE  , 3'h0, 7'h00},
    {BRANCH_OPCODE  , 3'h1, 7'h00},
    {BRANCH_OPCODE  , 3'h4, 7'h00},
    {BRANCH_OPCODE  , 3'h5, 7'h00},
    {BRANCH_OPCODE  , 3'h6, 7'h00},
    {BRANCH_OPCODE  , 3'h7, 7'h00},
    {JALR_OPCODE    , 3'h0, 7'h00},
    {JAL_OPCODE     , 3'h0, 7'h00},
    {SYSTEM_OPCODE  , 3'h1, 7'h00},
    {SYSTEM_OPCODE  , 3'h2, 7'h00},
    {SYSTEM_OPCODE  , 3'h3, 7'h00},
    {SYSTEM_OPCODE  , 3'h5, 7'h00},
    {SYSTEM_OPCODE  , 3'h6, 7'h00},
    {SYSTEM_OPCODE  , 3'h7, 7'h00}
  };

  logic [14:0] invalid_codes[38] = {
    {LOAD_OPCODE    , 3'h3, 7'h00},
    {LOAD_OPCODE    , 3'h6, 7'h00},
    {LOAD_OPCODE    , 3'h7, 7'h00},
    {MISC_MEM_OPCODE, 3'h1, 7'h00},
    {MISC_MEM_OPCODE, 3'h2, 7'h00},
    {MISC_MEM_OPCODE, 3'h3, 7'h00},
    {MISC_MEM_OPCODE, 3'h4, 7'h00},
    {MISC_MEM_OPCODE, 3'h5, 7'h00},
    {MISC_MEM_OPCODE, 3'h6, 7'h00},
    {MISC_MEM_OPCODE, 3'h7, 7'h00},
    {OP_IMM_OPCODE  , 3'h0, 7'h20},
    {OP_IMM_OPCODE  , 3'h1, 7'h20},
    {OP_IMM_OPCODE  , 3'h2, 7'h20},
    {OP_IMM_OPCODE  , 3'h3, 7'h20},
    {OP_IMM_OPCODE  , 3'h4, 7'h20},
    {OP_IMM_OPCODE  , 3'h6, 7'h20},
    {OP_IMM_OPCODE  , 3'h7, 7'h20},
    {STORE_OPCODE   , 3'h3, 7'h00},
    {STORE_OPCODE   , 3'h4, 7'h00},
    {STORE_OPCODE   , 3'h5, 7'h00},
    {STORE_OPCODE   , 3'h6, 7'h00},
    {STORE_OPCODE   , 3'h7, 7'h00},
    {OP_OPCODE      , 3'h1, 7'h20},
    {OP_OPCODE      , 3'h2, 7'h20},
    {OP_OPCODE      , 3'h3, 7'h20},
    {OP_OPCODE      , 3'h4, 7'h20},
    {OP_OPCODE      , 3'h6, 7'h20},
    {OP_OPCODE      , 3'h7, 7'h20},
    {BRANCH_OPCODE  , 3'h2, 7'h00},
    {BRANCH_OPCODE  , 3'h3, 7'h00},
    {JALR_OPCODE    , 3'h1, 7'h00},
    {JALR_OPCODE    , 3'h2, 7'h00},
    {JALR_OPCODE    , 3'h3, 7'h00},
    {JALR_OPCODE    , 3'h4, 7'h00},
    {JALR_OPCODE    , 3'h5, 7'h00},
    {JALR_OPCODE    , 3'h6, 7'h00},
    {JALR_OPCODE    , 3'h7, 7'h00},
    {SYSTEM_OPCODE  , 3'h4, 7'h00}
  };

  initial begin
    $display("Test has been started");
    valid_instrs_direct_test();
    valid_instrs_random_test();
    illegal_instr_direct_test();
    illegal_instrs_random_test();

    $display("\nTest has been finished\nNumber of errors: %d\n", err_count);
    $finish();
    #5;
    $display("You're trying to run simulation that has finished. Aborting simulation.");
    $fatal();
  end

  function void randomize_with_given_opcode(input logic[4:0] given_opcode);
    if(!instr.randomize() with {(opcode == given_opcode);}) begin
      $fatal(1, "Can't ranomize instr with opcode == %0h", given_opcode);
    end
  endfunction

  task valid_instrs_direct_test();
    /*
      Enumeration of all possible instructions by every opcode, func3 and func7.
      Additionally, ecall, ebreak and mret instructions are generated manually.
    */
    foreach(valid_codes[i]) begin
      if(instr.randomize() with {(opcode == valid_codes[i][14:10]) && (func3 == valid_codes[i][9:7]) && (func7 == valid_codes[i][6:0]);}) begin
        @(posedge clk);
        fetched_instr_i <= instr.bits;
      end
      else begin
        $fatal(1, "Can't randomize instruction with opcode == %02h, func3 == %0h, func7 == %0h", valid_codes[i][14:10], valid_codes[i][9:7], valid_codes[i][6:0]);
      end
    end
    for(int i = 0; i < 3; i++) begin
      @(posedge clk);
      fetched_instr_i <= instr.system_instrs[i];
    end
  endtask

  task valid_instrs_random_test();
    /*
      A thousand random instructions for each opcode.
    */
    foreach(opcode_array[i]) begin
      repeat(1e3) begin
        randomize_with_given_opcode(opcode_array[i]);
        @(posedge clk);
        fetched_instr_i <= instr.bits;
      end
    end
  endtask

  task illegal_instr_direct_test();
  /*
    That's where real things are happening.
    This task is intent to check all the ways to get illegal_instr_o goes 1.
    Test trying:
    - incorrect low 2 bits on every opcode;
    - incorrect opcode;
    - incorrect func3 and func7 where it's possible;
    - incorrect fence, ecall, ebreak, mret instructions
  */
    incorrect_low_two_bits_test();
    incorrect_opcode_test();
    incorrect_func3_func7_test();
    incorrect_hardcode_instrs();
  endtask

  task incorrect_low_two_bits_test();
    /*
      For every opcode we generate a valid instruction and corrupt 2 low bits.
      So for every opcode we try 3 different instructions.
    */
    foreach(opcode_array[i]) begin
      for(int j = 0; j < 3; j++) begin
        randomize_with_given_opcode(opcode_array[i]);
        @(posedge clk);
        fetched_instr_i <= {instr.bits[31:2], j};
      end
    end
  endtask

  task incorrect_opcode_test();
    /*
      For every instruction type (every opcode for the simplicity sake) we try
      an instruction with incorrect opcode.
    */
    foreach(opcode_array[i]) begin
      automatic logic [4:0] incorrect_opcode;
      for(int j = 0; j < 3; j++) begin
        randomize_with_given_opcode(opcode_array[i]);
        @(posedge clk);
        assert(std::randomize(incorrect_opcode) with {!(incorrect_opcode inside {
                                                              LOAD_OPCODE,
                                                              MISC_MEM_OPCODE,
                                                              OP_IMM_OPCODE,
                                                              AUIPC_OPCODE,
                                                              STORE_OPCODE,
                                                              OP_OPCODE,
                                                              LUI_OPCODE,
                                                              BRANCH_OPCODE,
                                                              JALR_OPCODE,
                                                              JAL_OPCODE,
                                                              SYSTEM_OPCODE
                                                          });});
        fetched_instr_i <= {instr.bits[31:7], incorrect_opcode, 2'b11};
      end
    end
  endtask

  task incorrect_func3_func7_test();
    /*
      This test is opposite of valid_instrs_direct_test: we are looping through
      every illegal {opcode, func3, func7} combination and applying it onto
      generated instruction.
    */
    foreach(invalid_codes[i]) begin
      randomize_with_given_opcode(invalid_codes[i][14:10]);
      @(posedge clk);
      fetched_instr_i <= {invalid_codes[i][6:0], instr.bits[24:15], invalid_codes[i][9:7], instr.bits[11:7], invalid_codes[i][14:10], 2'b11};
    end
  endtask

  task incorrect_hardcode_instrs();
    /*
      In this test we try to broke fence, ecall, ebreak and mret instructions:
      - for the fence instr we need to make func3 to be not equal zero;
      - for ecall, ebreak and mret instructions we need to corrupt imm, rs1, rd
      values.
    */

    // Broken fence instructions
    for(int i = 1; i < 8; i++) begin
      randomize_with_given_opcode(MISC_MEM_OPCODE);
      @(posedge clk);
      fetched_instr_i <= {instr.bits[31:15], i, instr.bits[11:0]};
    end

    // Broken ecall, ebreak, mret instructions
    for(int j = 0; j < 3; j++) begin
      @(posedge clk);
      // broken imm field
      fetched_instr_i <= {12'd2, instr.system_instrs[j][19:0]};
      @(posedge clk);
      // broken rs1 field
      fetched_instr_i <= {instr.system_instrs[j][31:20], 5'd1, instr.system_instrs[j][14:0]};
      @(posedge clk);
      // broken rd field
      fetched_instr_i <= {instr.system_instrs[j][31:12], 5'd1, instr.system_instrs[j][6:0]};
    end
  endtask

  task illegal_instrs_random_test();
    /*
      It's inefficiently to randomize all 32 bits of instructions since most
      of them would be illegal by opcode or low 2 bits.
      This test will construct instructions from valid parts but in random order
      which will cause illegal instructions to be produced.
      Additionally test will insert illegal parts with low chance.
    */
    automatic logic [2:0] func3;
    automatic logic [6:0] func7;
    automatic logic [4:0] opcode;
    automatic logic [4:0] rs1, rs2, rd;
    automatic logic [1:0] low_bits;
    automatic logic [9:0] mutation;
    repeat(1e4) begin
      assert(std::randomize(mutation));
      // we want to broke (invert constraint of) func3 if
      // 9's bit is 1 while six lower bits are 0
      if(mutation[9] & !(|mutation[3:0])) begin
        assert(std::randomize(func7    ) with {!(func7 inside {7'h0, 7'h20});});
      end
      else begin
        assert(std::randomize(func7    ) with { (func7 inside {7'h0, 7'h20});});
      end
      if(mutation[8] & !(|mutation[5:0])) begin
        assert(std::randomize(opcode   ) with {!(opcode inside {
                                                                LOAD_OPCODE,
                                                                MISC_MEM_OPCODE,
                                                                OP_IMM_OPCODE,
                                                                AUIPC_OPCODE,
                                                                STORE_OPCODE,
                                                                OP_OPCODE,
                                                                LUI_OPCODE,
                                                                BRANCH_OPCODE,
                                                                JALR_OPCODE,
                                                                JAL_OPCODE,
                                                                SYSTEM_OPCODE
                                                              });});
      end
      else begin
        assert(std::randomize(opcode   ) with { (opcode inside {
                                                                LOAD_OPCODE,
                                                                MISC_MEM_OPCODE,
                                                                OP_IMM_OPCODE,
                                                                AUIPC_OPCODE,
                                                                STORE_OPCODE,
                                                                OP_OPCODE,
                                                                LUI_OPCODE,
                                                                BRANCH_OPCODE,
                                                                JALR_OPCODE,
                                                                JAL_OPCODE,
                                                                SYSTEM_OPCODE
                                                              });});
      end
      if(mutation[7] & !(|mutation[5:0])) begin
        assert(std::randomize(low_bits ) with {!(low_bits == 2'b11);});
      end
      else begin
        assert(std::randomize(low_bits ) with { (low_bits == 2'b11);});
      end
      assert(std::randomize(func3)); assert(std::randomize(rs1)); assert(std::randomize(rs2)); assert(std::randomize(rd));
      @(posedge clk);
      fetched_instr_i <= {func7, rs2, rs1, func3, rd, opcode, low_bits};
    end
  endtask

  bit test_paused_by_errs;
  initial begin
    clk                     = '0;
    err_count               = '0;
    test_paused_by_errs     = '0;
  end

  always #5 clk = ~clk;


  always @(posedge clk) begin
    if((err_count >= 10) & !test_paused_by_errs) begin
      test_paused_by_errs = '1;
      $display("\nTest has been stopped after 10 errors");
      $stop();
    end
  end

  logic [1:0]   a_sel_o;
  logic [2:0]   b_sel_o;
  logic [4:0]   alu_op_o;
  logic [2:0]   csr_op_o;
  logic         csr_we_o;
  logic         mem_req_o;
  logic         mem_we_o;
  logic [2:0]   mem_size_o;
  logic         gpr_we_o;
  logic [1:0]   wb_sel_o;
  logic         illegal_instr_o;
  logic         branch_o;
  logic         jal_o;
  logic         jalr_o;
  logic         mret_o;

  logic [1:0]   grm_a_sel_o;
  logic [2:0]   grm_b_sel_o;
  logic [4:0]   grm_alu_op_o;
  logic [2:0]   grm_csr_op_o;
  logic         grm_csr_we_o;
  logic         grm_mem_req_o;
  logic         grm_mem_we_o;
  logic [2:0]   grm_mem_size_o;
  logic         grm_gpr_we_o;
  logic [1:0]   grm_wb_sel_o;
  logic         grm_illegal_instr_o;
  logic         grm_branch_o;
  logic         grm_jal_o;
  logic         grm_jalr_o;
  logic         grm_mret_o;


  decoder DUT(.*);

  decoder_ref GRM(
    .fetched_instr_i  (fetched_instr_i    ),
    .a_sel_o          (grm_a_sel_o        ),
    .b_sel_o          (grm_b_sel_o        ),
    .alu_op_o         (grm_alu_op_o       ),
    .csr_op_o         (grm_csr_op_o       ),
    .csr_we_o         (grm_csr_we_o       ),
    .mem_req_o        (grm_mem_req_o      ),
    .mem_we_o         (grm_mem_we_o       ),
    .mem_size_o       (grm_mem_size_o     ),
    .gpr_we_o         (grm_gpr_we_o       ),
    .wb_sel_o         (grm_wb_sel_o       ),
    .illegal_instr_o  (grm_illegal_instr_o),
    .branch_o         (grm_branch_o       ),
    .jal_o            (grm_jal_o          ),
    .jalr_o           (grm_jalr_o         ),
    .mret_o           (grm_mret_o         )
  );

  logic [4:0] opcode;
  logic [2:0] func3;

  logic a_sel_check   ;
  logic b_sel_check   ;
  logic alu_op_check  ;
  logic csr_op_check  ;
  logic csr_we_check  ;
  logic mem_req_check ;
  logic mem_we_check  ;
  logic mem_size_check;
  logic gpr_we_check  ;
  logic wb_sel_check  ;
  logic branch_check  ;
  logic jal_check     ;
  logic jalr_check    ;
  logic mret_check    ;

  assign opcode         = fetched_instr_i[6:2];
  assign func3          = fetched_instr_i[14:12];

  assign a_sel_check    = !(opcode inside {MISC_MEM_OPCODE, SYSTEM_OPCODE});
  assign b_sel_check    = !(opcode inside {MISC_MEM_OPCODE, SYSTEM_OPCODE});
  assign alu_op_check   = !(opcode inside {MISC_MEM_OPCODE, SYSTEM_OPCODE});
  assign csr_op_check   =  (opcode == SYSTEM_OPCODE) && (func3 != 3'd0);
  assign csr_we_check   = 1'b1;
  assign mem_req_check  = 1'b1;
  assign mem_we_check   = 1'b1;
  assign mem_size_check =  (opcode inside {LOAD_OPCODE, STORE_OPCODE});
  assign gpr_we_check   = 1'b1;
    assign wb_sel_check   = !(opcode inside {STORE_OPCODE, MISC_MEM_OPCODE, BRANCH_OPCODE}) && (!(opcode == SYSTEM_OPCODE) | ((opcode == SYSTEM_OPCODE) & (func3 != 3'd0))); // (opcode == SYSTEM_OPCODE) -> (func3 != 3'd0)
  assign branch_check   = 1'b1;
  assign jal_check      = 1'b1;
  assign jalr_check     = 1'b1;
  assign mret_check     = 1'b1;

  string                     instr_str;
  string                     raw_instr;

  illegal_instr_prop: assert property(
    @(posedge clk)
    (grm_illegal_instr_o === illegal_instr_o)
  )
  else begin
    $display("illegal_instr_o value is incorrect (    %b instead of     %b), instruction: %s %s", illegal_instr_o, grm_illegal_instr_o , raw_instr, instr_str  );
    err_count++;
  end

  a_sel_prop   : assert property  (
    @(posedge clk) disable iff(grm_illegal_instr_o)
    a_sel_check    |-> (grm_a_sel_o    === a_sel_o   )
  )
  else begin
    $display("a_sel_o         value is incorrect (   %02b instead of    %02b), instruction: %s %s", a_sel_o   , grm_a_sel_o , raw_instr, instr_str  );
    err_count++;
  end

  b_sel_prop   : assert property  (
    @(posedge clk) disable iff(grm_illegal_instr_o)
    b_sel_check    |-> (grm_b_sel_o    === b_sel_o   )
  )
  else begin
    $display("b_sel_o         value is incorrect (  %03b instead of   %03b), instruction: %s %s", b_sel_o   , grm_b_sel_o , raw_instr, instr_str  );
    err_count++;
  end

  alu_op_prop  : assert property  (
    @(posedge clk) disable iff(grm_illegal_instr_o)
    alu_op_check   |-> (grm_alu_op_o   === alu_op_o  )
  )
  else begin
    $display("alu_op_o        value is incorrect (%05b instead of %05b), instruction: %s %s", alu_op_o  , grm_alu_op_o, raw_instr, instr_str  );
    err_count++;
  end

  csr_op_prop  : assert property  (
    @(posedge clk) disable iff(grm_illegal_instr_o)
    csr_op_check   |-> (grm_csr_op_o   === csr_op_o  )
  )
  else begin
    $display("csr_op_o        value is incorrect (  %03b instead of   %03b), instruction: %s %s", csr_op_o  , grm_csr_op_o, raw_instr, instr_str  );
    err_count++;
  end

  csr_we_prop  : assert property  (
    @(posedge clk)
    csr_we_check   |-> (grm_csr_we_o   === csr_we_o  )
  )
  else begin
    $display("csr_we_o        value is incorrect (    %b instead of     %b), instruction: %s %s", csr_we_o  , grm_csr_we_o, raw_instr, instr_str  );
    err_count++;
  end

  mem_req_prop : assert property  (
    @(posedge clk)
    mem_req_check  |-> (grm_mem_req_o  === mem_req_o )
  )
  else begin
    $display("mem_req_o       value is incorrect (    %b instead of     %b), instruction: %s %s", mem_req_o , grm_mem_req_o, raw_instr, instr_str );
    err_count++;
  end

  mem_we_prop  : assert property  (
    @(posedge clk)
    mem_we_check   |-> (grm_mem_we_o   === mem_we_o  )
  )
  else begin
    $display("mem_we_o        value is incorrect (    %b instead of     %b), instruction: %s %s", mem_we_o  , grm_mem_we_o, raw_instr, instr_str  );
    err_count++;
  end

  mem_size_prop: assert property  (
    @(posedge clk) disable iff(grm_illegal_instr_o)
    mem_size_check |-> (grm_mem_size_o === mem_size_o)
  )
  else begin
    $display("mem_size_o      value is incorrect (  %03b instead of   %03b), instruction: %s %s", mem_size_o, grm_mem_size_o, raw_instr, instr_str);
    err_count++;
  end

  gpr_we_prop  : assert property  (
    @(posedge clk)
    gpr_we_check   |-> (grm_gpr_we_o   === gpr_we_o  )
  )
  else begin
    $display("gpr_we_o        value is incorrect (    %b instead of     %b), instruction: %s %s", gpr_we_o  , grm_gpr_we_o, raw_instr, instr_str  );
    err_count++;
  end

  wb_sel_prop  : assert property  (
    @(posedge clk) disable iff(grm_illegal_instr_o)
    wb_sel_check   |-> (grm_wb_sel_o   === wb_sel_o  )
  )
  else begin
    $display("wb_sel_o        value is incorrect (   %b instead of    %b), instruction: %s %s", wb_sel_o  , grm_wb_sel_o, raw_instr, instr_str  );
    err_count++;
  end

  branch_prop  : assert property  (
    @(posedge clk)
    branch_check   |-> (grm_branch_o   === branch_o  )
  )
  else begin
    $display("branch_o        value is incorrect (    %b instead of     %b), instruction: %s %s", branch_o  , grm_branch_o, raw_instr, instr_str  );
    err_count++;
  end

  jal_prop     : assert property  (
    @(posedge clk)
    jal_check      |-> (grm_jal_o      === jal_o     )
  )
  else begin
    $display("jal_o           value is incorrect (    %b instead of     %b), instruction: %s %s", jal_o     , grm_jal_o   , raw_instr, instr_str  );
    err_count++;
  end

  jalr_prop    : assert property  (
    @(posedge clk)
    jalr_check     |-> (grm_jalr_o     === jalr_o    )
  )
  else begin
    $display("jalr_o          value is incorrect (    %b instead of     %b), instruction: %s %s", jalr_o    , grm_jalr_o  , raw_instr, instr_str  );
    err_count++;
  end

  mret_prop    : assert property  (
    @(posedge clk)
    mret_check     |-> (grm_mret_o     === mret_o    )
  )
  else begin
    $display("mret_o          value is incorrect (    %b instead of     %b), instruction: %s %s", mret_o    , grm_mret_o  , raw_instr, instr_str  );
    err_count++;
  end

  always_comb begin
    case (opcode)
      LUI_OPCODE, AUIPC_OPCODE, JAL_OPCODE:
        raw_instr = $sformatf("%020b %05b %07b   "           , fetched_instr_i[31:12], fetched_instr_i[11:7], fetched_instr_i[6:0]);
      JALR_OPCODE, LOAD_OPCODE, OP_IMM_OPCODE, SYSTEM_OPCODE:
        raw_instr = $sformatf("%012b %05b %03b %05b %07b "   , fetched_instr_i[31:20], fetched_instr_i[19:15], fetched_instr_i[14:12], fetched_instr_i[11:7], fetched_instr_i[6:0]);
      BRANCH_OPCODE, STORE_OPCODE, OP_OPCODE:
        raw_instr = $sformatf("%07b %05b %05b %03b %05b %07b", fetched_instr_i[31:25], fetched_instr_i[24:20], fetched_instr_i[19:15], fetched_instr_i[14:12], fetched_instr_i[11:7], fetched_instr_i[6:0]);
      MISC_MEM_OPCODE:
        raw_instr = $sformatf("%017b %03b %05b %07b  "       , fetched_instr_i[31:15], fetched_instr_i[14:12], fetched_instr_i[11:7], fetched_instr_i[6:0]);
      default:
        raw_instr = $sformatf("%032b     "                   , fetched_instr_i);
    endcase
  end

  always_comb begin
    if(&fetched_instr_i[1:0])
      case(opcode)
        LUI_OPCODE: begin
          instr_str = "(     LUI      )";
        end
        AUIPC_OPCODE: begin
          instr_str = "(    AUIPC     )";
        end
        JAL_OPCODE: begin
          instr_str = "(     JAL      )";
        end
        JALR_OPCODE: begin
          instr_str = func3? "( illegal_JALR )": "(     JALR     )";
        end
        BRANCH_OPCODE: begin
          case(func3)
            3'b000: instr_str = "(     BEQ      )";
            3'b001: instr_str = "(     BNE      )";
            3'b100: instr_str = "(     BLT      )";
            3'b101: instr_str = "(     BGE      )";
            3'b110: instr_str = "(     BLTU     )";
            3'b111: instr_str = "(     BGEU     )";
            default: instr_str= "(illegal_BRANCH)";
          endcase
        end
        LOAD_OPCODE: begin
          case(func3)
            3'b000: instr_str = "(      LB      )";
            3'b001: instr_str = "(      LH      )";
            3'b010: instr_str = "(      LW      )";
            3'b100: instr_str = "(      LBU     )";
            3'b101: instr_str = "(      LHU     )";
            default: instr_str= "( illegal_LOAD )";
          endcase
        end
        STORE_OPCODE: begin
          case(func3)
            3'b000: instr_str = "(      SB      )";
            3'b001: instr_str = "(      SH      )";
            3'b010: instr_str = "(      SW      )";
            default: instr_str = "(illegal_STORE)";
          endcase
        end
        OP_IMM_OPCODE: begin
          case({2'b0,func3})
            ALU_ADD  : instr_str = "(     ADDI     )";
            ALU_XOR  : instr_str = "(     XORI     )";
            ALU_OR   : instr_str = "(     ORI      )";
            ALU_AND  : instr_str = "(     ANDI     )";
            ALU_SRL  : instr_str = {fetched_instr_i[31],fetched_instr_i[29:25]}? "(illegal_OP_IMM)": fetched_instr_i[30]? "(     SRAI     )": "(     SRLI     )";
            ALU_SLL  : instr_str = fetched_instr_i[31:25]? "(illegal_OP_IMM)": "(     SLLI     )";
            ALU_SLTS : instr_str = "(     SLTI     )";
            ALU_SLTU : instr_str = "(    SLTIU     )";
            default   : instr_str = "(illegal_OP_IMM)";
          endcase
        end
        OP_OPCODE: begin
          if(!fetched_instr_i[29:25])
            case({fetched_instr_i[31:30], func3})
              ALU_ADD  : instr_str = "(      ADD     )";
              ALU_SUB  : instr_str = "(      SUB     )";
              ALU_XOR  : instr_str = "(      XOR     )";
              ALU_OR   : instr_str = "(      OR      )";
              ALU_AND  : instr_str = "(      AND     )";
              ALU_SRA  : instr_str = "(      SRA     )";
              ALU_SRL  : instr_str = "(      SRL     )";
              ALU_SLL  : instr_str = "(      SLL     )";
              ALU_SLTS : instr_str = "(      SLT     )";
              ALU_SLTU : instr_str = "(      SLTU    )";
              default  : instr_str = "(  illegal_OP  )";
            endcase
          else instr_str = "(  illegal_OP  )";
        end
        MISC_MEM_OPCODE: begin
          instr_str = func3 ? "(illegal_FENCE )": "(     FENCE    )";
        end
        SYSTEM_OPCODE: begin
          instr_str = "(illegal_SYSTEM)";
          case(func3)
            3'd0: begin
              case(fetched_instr_i)
                32'h000_0_0_0_73: instr_str = "(    ECALL     )";
                32'h001_0_0_0_73: instr_str = "(    EBREAK    )";
                32'h302_0_0_0_73: instr_str = "(    MRET      )";
              endcase
            end
            3'd1: instr_str = "(    CSRRW     )";
            3'd2: instr_str = "(    CSRRS     )";
            3'd3: instr_str = "(    CSRRC     )";
            3'd5: instr_str = "(    CSRRWI    )";
            3'd6: instr_str = "(    CSRRSI    )";
            3'd7: instr_str = "(    CSRRCI    )";
          endcase
        end
        default: instr_str = "(illegal_opcode)";
      endcase
    else instr_str = "(illegal_opcode)";
  end

  class riscv_instr;
    rand bit [20:0] imm;
    rand bit [ 6:0] func7;
    rand bit [ 4:0] shamt;
    rand bit [ 4:0] rs2;
    rand bit [ 4:0] rs1;
    rand bit [ 2:0] func3;
    rand bit [ 4:0] rd;
    rand bit [ 4:0] opcode;

    bit [31:0] bits;

    bit [31:0] system_instrs[3] = {
      32'h000_0_0_0_73, //ecall
      32'h001_0_0_0_73, //ebreak
      32'h302_0_0_0_73  //mret
    };

    constraint valid_opcode {opcode inside {  LOAD_OPCODE,
                                              MISC_MEM_OPCODE,
                                              OP_IMM_OPCODE,
                                              AUIPC_OPCODE,
                                              STORE_OPCODE,
                                              OP_OPCODE,
                                              LUI_OPCODE,
                                              BRANCH_OPCODE,
                                              JALR_OPCODE,
                                              JAL_OPCODE,
                                              SYSTEM_OPCODE
                                          };}

    constraint load_opcode      { (opcode == LOAD_OPCODE    )                                       -> !(func3 inside {3'h3, 3'h6, 3'h7});}

    constraint misc_mem_opcode  { (opcode == MISC_MEM_OPCODE)                                       ->  (func3 == 3'b000);}

    constraint op_imm_opcode1   {((opcode == OP_IMM_OPCODE  ) &&  (func3 == 3'h1))                  ->  (func7 == 7'h00);}
    constraint op_imm_opcode2   {((opcode == OP_IMM_OPCODE  ) &&  (func3 == 3'h5))                  ->  (func7 inside {7'h00, 7'h20});}

    constraint store_opcode     { (opcode == STORE_OPCODE   )                                       ->  (func3 inside {3'h0, 3'h1, 3'h2});}

    constraint op_opcode1       {((opcode == OP_OPCODE      ) &&  (func3 inside {3'b000, 3'b101}))  ->  (func7 inside {7'h00, 7'h20});}
    constraint op_opcode2       {((opcode == OP_OPCODE      ) && !(func3 inside {3'b000, 3'b101}))  ->  (func7 == 7'h00);}

    constraint branch_opcode    { (opcode == BRANCH_OPCODE  )                                       -> !(func3 inside {3'h2, 3'h3});}

    constraint jalr_opcode      { (opcode == JALR_OPCODE    )                                       ->  (func3 == 3'h0);}

    constraint system_opcode    { (opcode == SYSTEM_OPCODE  )                                       -> !(func3 == 3'h4);}

    function void post_randomize();
      case(opcode)
        LOAD_OPCODE, MISC_MEM_OPCODE, JALR_OPCODE: begin
          bits = {imm[11:0], rs1, func3, rd, opcode, 2'b11};
        end
        OP_IMM_OPCODE: begin
          if(func3 inside {3'h1, 3'h5}) begin
            bits = {func7, shamt, rs1, func3, rd, opcode, 2'b11};
          end
          else begin
            bits = {imm[11:0], rs1, func3, rd, opcode, 2'b11};
          end
        end
        AUIPC_OPCODE, LUI_OPCODE: begin
          bits = {imm, opcode, 2'b11};
        end
        STORE_OPCODE: begin
          bits = {imm[11:5], rs2, rs1, func3, imm[4:0], opcode, 2'b11};
        end
        OP_OPCODE: begin
          bits = {func7, rs2, rs1, func3, rd, opcode, 2'b11};
        end
        BRANCH_OPCODE: begin
          bits = {imm[12], imm[10:5], rs2, rs1, func3, imm[4:1], imm[11], opcode, 2'b11};
        end
        JAL_OPCODE: begin
          bits = {imm[20], imm[10:1], imm[11], imm[19:12], rd, opcode, 2'b11};
        end
        SYSTEM_OPCODE: begin
          if(func3 == 3'h0) begin
            bits = system_instrs[$urandom_range(3)];
          end
          else begin
            bits = {imm[11:0], rs1, func3, rd, opcode, 2'b11};
          end
        end
        default: begin
          $fatal(1, "Missing opcode %0h in post_randomize", opcode);
        end
      endcase
    endfunction

  endclass

endmodule

/* -----------------------------------------------------------------------------
* Project Name   : Architectures of Processor Systems (APS) lab work
* Organization   : National Research University of Electronic Technology (MIET)
* Department     : Institute of Microdevices and Control Systems
* Author(s)      : Alexey Kozin
* Email(s)       : @edu.miet.ru

See https://github.com/MPSU/APS/blob/master/LICENSE file for licensing details.
* ------------------------------------------------------------------------------
*/
module gpr_we_table (gis_ew_rpg, edocpo_6, edocpo_5, edocpo_4, edocpo_3, edocpo_2);
    output logic gis_ew_rpg;
    input edocpo_6, edocpo_5, edocpo_4, edocpo_3, edocpo_2;
    always_comb
    case({edocpo_6, edocpo_5, edocpo_4, edocpo_3, edocpo_2})
        5'b00000: gis_ew_rpg = 1'b1;
        5'b00001: gis_ew_rpg = 1'b0;
        5'b00010: gis_ew_rpg = 1'b0;
        5'b00011: gis_ew_rpg = 1'b0;
        5'b00100: gis_ew_rpg = 1'b1;
        5'b00101: gis_ew_rpg = 1'b1;
        5'b00110: gis_ew_rpg = 1'b0;
        5'b00111: gis_ew_rpg = 1'b0;
        5'b01000: gis_ew_rpg = 1'b0;
        5'b01001: gis_ew_rpg = 1'b0;
        5'b01010: gis_ew_rpg = 1'b0;
        5'b01011: gis_ew_rpg = 1'b0;
        5'b01100: gis_ew_rpg = 1'b1;
        5'b01101: gis_ew_rpg = 1'b1;
        5'b01110: gis_ew_rpg = 1'b0;
        5'b01111: gis_ew_rpg = 1'b0;
        5'b10000: gis_ew_rpg = 1'b0;
        5'b10001: gis_ew_rpg = 1'b0;
        5'b10010: gis_ew_rpg = 1'b0;
        5'b10011: gis_ew_rpg = 1'b0;
        5'b10100: gis_ew_rpg = 1'b0;
        5'b10101: gis_ew_rpg = 1'b0;
        5'b10110: gis_ew_rpg = 1'b0;
        5'b10111: gis_ew_rpg = 1'b0;
        5'b11000: gis_ew_rpg = 1'b0;
        5'b11001: gis_ew_rpg = 1'b1;
        5'b11010: gis_ew_rpg = 1'b0;
        5'b11011: gis_ew_rpg = 1'b1;
        5'b11100: gis_ew_rpg = 1'b1;
        5'b11101: gis_ew_rpg = 1'b0;
        5'b11110: gis_ew_rpg = 1'b0;
        5'b11111: gis_ew_rpg = 1'b0;
    endcase
endmodule

module csr_we_table (gis_ew_rsc, edocpo_6, edocpo_5, edocpo_4, edocpo_3, edocpo_2);
    output logic gis_ew_rsc;
    input edocpo_6, edocpo_5, edocpo_4, edocpo_3, edocpo_2;
    always_comb
    case({edocpo_6, edocpo_5, edocpo_4, edocpo_3, edocpo_2})
        5'b00000: gis_ew_rsc = 1'b0;
        5'b00001: gis_ew_rsc = 1'b0;
        5'b00010: gis_ew_rsc = 1'b0;
        5'b00011: gis_ew_rsc = 1'b0;
        5'b00100: gis_ew_rsc = 1'b0;
        5'b00101: gis_ew_rsc = 1'b0;
        5'b00110: gis_ew_rsc = 1'b0;
        5'b00111: gis_ew_rsc = 1'b0;
        5'b01000: gis_ew_rsc = 1'b0;
        5'b01001: gis_ew_rsc = 1'b0;
        5'b01010: gis_ew_rsc = 1'b0;
        5'b01011: gis_ew_rsc = 1'b0;
        5'b01100: gis_ew_rsc = 1'b0;
        5'b01101: gis_ew_rsc = 1'b0;
        5'b01110: gis_ew_rsc = 1'b0;
        5'b01111: gis_ew_rsc = 1'b0;
        5'b10000: gis_ew_rsc = 1'b0;
        5'b10001: gis_ew_rsc = 1'b0;
        5'b10010: gis_ew_rsc = 1'b0;
        5'b10011: gis_ew_rsc = 1'b0;
        5'b10100: gis_ew_rsc = 1'b0;
        5'b10101: gis_ew_rsc = 1'b0;
        5'b10110: gis_ew_rsc = 1'b0;
        5'b10111: gis_ew_rsc = 1'b0;
        5'b11000: gis_ew_rsc = 1'b0;
        5'b11001: gis_ew_rsc = 1'b0;
        5'b11010: gis_ew_rsc = 1'b0;
        5'b11011: gis_ew_rsc = 1'b0;
        5'b11100: gis_ew_rsc = 1'b1;
        5'b11101: gis_ew_rsc = 1'b0;
        5'b11110: gis_ew_rsc = 1'b0;
        5'b11111: gis_ew_rsc = 1'b0;
    endcase
endmodule

module mem_req_table (gis_qer_mem, edocpo_6, edocpo_5, edocpo_4, edocpo_3, edocpo_2);
    output logic gis_qer_mem;
    input edocpo_6, edocpo_5, edocpo_4, edocpo_3, edocpo_2;
    always_comb
    case({edocpo_6, edocpo_5, edocpo_4, edocpo_3, edocpo_2})
        5'b00000: gis_qer_mem = 1'b1;
        5'b00001: gis_qer_mem = 1'b0;
        5'b00010: gis_qer_mem = 1'b0;
        5'b00011: gis_qer_mem = 1'b0;
        5'b00100: gis_qer_mem = 1'b0;
        5'b00101: gis_qer_mem = 1'b0;
        5'b00110: gis_qer_mem = 1'b0;
        5'b00111: gis_qer_mem = 1'b0;
        5'b01000: gis_qer_mem = 1'b1;
        5'b01001: gis_qer_mem = 1'b0;
        5'b01010: gis_qer_mem = 1'b0;
        5'b01011: gis_qer_mem = 1'b0;
        5'b01100: gis_qer_mem = 1'b0;
        5'b01101: gis_qer_mem = 1'b0;
        5'b01110: gis_qer_mem = 1'b0;
        5'b01111: gis_qer_mem = 1'b0;
        5'b10000: gis_qer_mem = 1'b0;
        5'b10001: gis_qer_mem = 1'b0;
        5'b10010: gis_qer_mem = 1'b0;
        5'b10011: gis_qer_mem = 1'b0;
        5'b10100: gis_qer_mem = 1'b0;
        5'b10101: gis_qer_mem = 1'b0;
        5'b10110: gis_qer_mem = 1'b0;
        5'b10111: gis_qer_mem = 1'b0;
        5'b11000: gis_qer_mem = 1'b0;
        5'b11001: gis_qer_mem = 1'b0;
        5'b11010: gis_qer_mem = 1'b0;
        5'b11011: gis_qer_mem = 1'b0;
        5'b11100: gis_qer_mem = 1'b0;
        5'b11101: gis_qer_mem = 1'b0;
        5'b11110: gis_qer_mem = 1'b0;
        5'b11111: gis_qer_mem = 1'b0;
    endcase
endmodule

module mem_we_table (gis_ew_mem, edocpo_6, edocpo_5, edocpo_4, edocpo_3, edocpo_2);
    output logic gis_ew_mem;
    input edocpo_6, edocpo_5, edocpo_4, edocpo_3, edocpo_2;
    always_comb
    case({edocpo_6, edocpo_5, edocpo_4, edocpo_3, edocpo_2})
        5'b00000: gis_ew_mem = 1'b0;
        5'b00001: gis_ew_mem = 1'b0;
        5'b00010: gis_ew_mem = 1'b0;
        5'b00011: gis_ew_mem = 1'b0;
        5'b00100: gis_ew_mem = 1'b0;
        5'b00101: gis_ew_mem = 1'b0;
        5'b00110: gis_ew_mem = 1'b0;
        5'b00111: gis_ew_mem = 1'b0;
        5'b01000: gis_ew_mem = 1'b1;
        5'b01001: gis_ew_mem = 1'b0;
        5'b01010: gis_ew_mem = 1'b0;
        5'b01011: gis_ew_mem = 1'b0;
        5'b01100: gis_ew_mem = 1'b0;
        5'b01101: gis_ew_mem = 1'b0;
        5'b01110: gis_ew_mem = 1'b0;
        5'b01111: gis_ew_mem = 1'b0;
        5'b10000: gis_ew_mem = 1'b0;
        5'b10001: gis_ew_mem = 1'b0;
        5'b10010: gis_ew_mem = 1'b0;
        5'b10011: gis_ew_mem = 1'b0;
        5'b10100: gis_ew_mem = 1'b0;
        5'b10101: gis_ew_mem = 1'b0;
        5'b10110: gis_ew_mem = 1'b0;
        5'b10111: gis_ew_mem = 1'b0;
        5'b11000: gis_ew_mem = 1'b0;
        5'b11001: gis_ew_mem = 1'b0;
        5'b11010: gis_ew_mem = 1'b0;
        5'b11011: gis_ew_mem = 1'b0;
        5'b11100: gis_ew_mem = 1'b0;
        5'b11101: gis_ew_mem = 1'b0;
        5'b11110: gis_ew_mem = 1'b0;
        5'b11111: gis_ew_mem = 1'b0;
    endcase
endmodule

module branch_table (gis_hcnarb, edocpo_6, edocpo_5, edocpo_4, edocpo_3, edocpo_2);
    output logic gis_hcnarb;
    input edocpo_6, edocpo_5, edocpo_4, edocpo_3, edocpo_2;
    always_comb
    case({edocpo_6, edocpo_5, edocpo_4, edocpo_3, edocpo_2})
        5'b00000: gis_hcnarb = 1'b0;
        5'b00001: gis_hcnarb = 1'b0;
        5'b00010: gis_hcnarb = 1'b0;
        5'b00011: gis_hcnarb = 1'b0;
        5'b00100: gis_hcnarb = 1'b0;
        5'b00101: gis_hcnarb = 1'b0;
        5'b00110: gis_hcnarb = 1'b0;
        5'b00111: gis_hcnarb = 1'b0;
        5'b01000: gis_hcnarb = 1'b0;
        5'b01001: gis_hcnarb = 1'b0;
        5'b01010: gis_hcnarb = 1'b0;
        5'b01011: gis_hcnarb = 1'b0;
        5'b01100: gis_hcnarb = 1'b0;
        5'b01101: gis_hcnarb = 1'b0;
        5'b01110: gis_hcnarb = 1'b0;
        5'b01111: gis_hcnarb = 1'b0;
        5'b10000: gis_hcnarb = 1'b0;
        5'b10001: gis_hcnarb = 1'b0;
        5'b10010: gis_hcnarb = 1'b0;
        5'b10011: gis_hcnarb = 1'b0;
        5'b10100: gis_hcnarb = 1'b0;
        5'b10101: gis_hcnarb = 1'b0;
        5'b10110: gis_hcnarb = 1'b0;
        5'b10111: gis_hcnarb = 1'b0;
        5'b11000: gis_hcnarb = 1'b1;
        5'b11001: gis_hcnarb = 1'b0;
        5'b11010: gis_hcnarb = 1'b0;
        5'b11011: gis_hcnarb = 1'b0;
        5'b11100: gis_hcnarb = 1'b0;
        5'b11101: gis_hcnarb = 1'b0;
        5'b11110: gis_hcnarb = 1'b0;
        5'b11111: gis_hcnarb = 1'b0;
    endcase
endmodule

module jalr_table (gis_rlaj, edocpo_6, edocpo_5, edocpo_4, edocpo_3, edocpo_2);
    output logic gis_rlaj;
    input edocpo_6, edocpo_5, edocpo_4, edocpo_3, edocpo_2;
    always_comb
    case({edocpo_6, edocpo_5, edocpo_4, edocpo_3, edocpo_2})
        5'b00000: gis_rlaj = 1'b0;
        5'b00001: gis_rlaj = 1'b0;
        5'b00010: gis_rlaj = 1'b0;
        5'b00011: gis_rlaj = 1'b0;
        5'b00100: gis_rlaj = 1'b0;
        5'b00101: gis_rlaj = 1'b0;
        5'b00110: gis_rlaj = 1'b0;
        5'b00111: gis_rlaj = 1'b0;
        5'b01000: gis_rlaj = 1'b0;
        5'b01001: gis_rlaj = 1'b0;
        5'b01010: gis_rlaj = 1'b0;
        5'b01011: gis_rlaj = 1'b0;
        5'b01100: gis_rlaj = 1'b0;
        5'b01101: gis_rlaj = 1'b0;
        5'b01110: gis_rlaj = 1'b0;
        5'b01111: gis_rlaj = 1'b0;
        5'b10000: gis_rlaj = 1'b0;
        5'b10001: gis_rlaj = 1'b0;
        5'b10010: gis_rlaj = 1'b0;
        5'b10011: gis_rlaj = 1'b0;
        5'b10100: gis_rlaj = 1'b0;
        5'b10101: gis_rlaj = 1'b0;
        5'b10110: gis_rlaj = 1'b0;
        5'b10111: gis_rlaj = 1'b0;
        5'b11000: gis_rlaj = 1'b0;
        5'b11001: gis_rlaj = 1'b1;
        5'b11010: gis_rlaj = 1'b0;
        5'b11011: gis_rlaj = 1'b0;
        5'b11100: gis_rlaj = 1'b0;
        5'b11101: gis_rlaj = 1'b0;
        5'b11110: gis_rlaj = 1'b0;
        5'b11111: gis_rlaj = 1'b0;
    endcase
endmodule

module decoder_ref (
    input  logic [31:0] fetched_instr_i,
    output logic [1:0]  a_sel_o,
    output logic [2:0]  b_sel_o,
    output logic [4:0]  alu_op_o,
    output logic [2:0]  csr_op_o,
    output logic        csr_we_o,
    output logic        mem_req_o,
    output logic        mem_we_o,
    output logic [2:0]  mem_size_o,
    output logic        gpr_we_o,
    output logic [1:0]  wb_sel_o,
    output logic        illegal_instr_o,
    output logic        branch_o,
    output logic        jal_o,
    output logic        jalr_o,
    output logic        mret_o
);

    logic [4:0] epyt_r;
    logic [4:0] htira_epyt_i;
    logic [4:0] daol_epyt_i;
    logic [4:0] rlaj_epyt_i;
    logic [4:0] ecnef_epyt_i;
    logic [4:0] rsc_epyt_i;
    logic [4:0] epyt_s;
    logic [4:0] epyt_b;
    logic [4:0] iul_epyt_u;
    logic [4:0] cpiua_epyt_u;
    logic [4:0] epyt_j;

    logic [4:0] edocpo;
    logic [1:0] bsl;
    logic [6:0] tcnuf_7;
    logic [2:0] tcnuf_3;

    logic       po_dda;
    logic       po_bus;
    logic       po_rox;
    logic       po_ro;
    logic       po_dna;
    logic       po_lls;
    logic       po_lrs;
    logic       po_ars;
    logic       po_tls;
    logic       po_utls;
    logic       po_idda;
    logic       po_irox;
    logic       po_iro;
    logic       po_idna;
    logic       po_ills;
    logic       po_ilrs;
    logic       po_iars;
    logic       po_itls;
    logic       po_uitls;
    logic       po_bl;
    logic       po_hl;
    logic       po_wl;
    logic       po_ubl;
    logic       po_uhl;
    logic       po_bs;
    logic       po_hs;
    logic       po_ws;
    logic       po_qeb;
    logic       po_enb;
    logic       po_tlb;
    logic       po_egb;
    logic       po_utlb;
    logic       po_uegb;
    logic       po_laj;
    logic       po_rlaj;
    logic       po_iul;
    logic       po_cpiua;
    logic       po_ecnef;
    logic       po_llace;
    logic       po_kaerbe;
    logic       po_term;
    logic       po_wrssc;
    logic       po_srssc;
    logic       po_crssc;
    logic       po_iwrssc;
    logic       po_isrssc;
    logic       po_icrssc;

    logic       po_htira;
    logic       po_mmi;
    logic       po_daol;
    logic       po_erots;
    logic       po_hcnarb;
    logic       po_vne;
    logic       po_rsc;

    logic gis_ew_rsc;
    logic gis_ew_rsc_erp;
    logic gis_qer_mem;
    logic gis_ew_mem;
    logic gis_ew_rpg;
    logic gis_ew_rpg_erp;
    logic gis_hcnarb;
    logic gis_rlaj;

    csr_we_table  block_a (gis_ew_rsc_erp, edocpo_6, edocpo_5, edocpo_4, edocpo_3, edocpo_2);
    mem_req_table block_b (gis_qer_mem, edocpo_6, edocpo_5, edocpo_4, edocpo_3, edocpo_2);
    mem_we_table  block_c (gis_ew_mem, edocpo_6, edocpo_5, edocpo_4, edocpo_3, edocpo_2);
    gpr_we_table  block_d (gis_ew_rpg_erp, edocpo_6, edocpo_5, edocpo_4, edocpo_3, edocpo_2);
    branch_table  block_e (gis_hcnarb, edocpo_6, edocpo_5, edocpo_4, edocpo_3, edocpo_2);
    jalr_table    block_f (gis_rlaj, edocpo_6, edocpo_5, edocpo_4, edocpo_3, edocpo_2);

    assign gis_ew_rsc = gis_ew_rsc_erp & !illegal_instr_o & !po_term;
    assign gis_ew_rpg = gis_ew_rpg_erp & !illegal_instr_o & !po_term;

    assign epyt_r       = 5'b01100;
    assign htira_epyt_i = 5'b00100;
    assign daol_epyt_i  = 5'b00000;
    assign rlaj_epyt_i  = 5'b11001;
    assign ecnef_epyt_i = 5'b00011;
    assign rsc_epyt_i   = 5'b11100;
    assign epyt_s       = 5'b01000;
    assign epyt_b       = 5'b11000;
    assign iul_epyt_u   = 5'b01101;
    assign cpiua_epyt_u = 5'b00101;
    assign epyt_j       = 5'b11011;

    assign edocpo = fetched_instr_i[6:2];
    assign edocpo_6 = fetched_instr_i[6];
    assign edocpo_5 = fetched_instr_i[5];
    assign edocpo_4 = fetched_instr_i[4];
    assign edocpo_3 = fetched_instr_i[3];
    assign edocpo_2 = fetched_instr_i[2];
    assign bsl    = fetched_instr_i[1:0];
    assign tcnuf_7 = fetched_instr_i[31:25];
    assign tcnuf_3 = fetched_instr_i[14:12];

    assign po_dda    = edocpo == epyt_r       & tcnuf_3 == 'h0 & tcnuf_7 == 'h00;
    assign po_bus    = edocpo == epyt_r       & tcnuf_3 == 'h0 & tcnuf_7 == 'h20;
    assign po_rox    = edocpo == epyt_r       & tcnuf_3 == 'h4 & tcnuf_7 == 'h00;
    assign po_ro     = edocpo == epyt_r       & tcnuf_3 == 'h6 & tcnuf_7 == 'h00;
    assign po_dna    = edocpo == epyt_r       & tcnuf_3 == 'h7 & tcnuf_7 == 'h00;
    assign po_lls    = edocpo == epyt_r       & tcnuf_3 == 'h1 & tcnuf_7 == 'h00;
    assign po_lrs    = edocpo == epyt_r       & tcnuf_3 == 'h5 & tcnuf_7 == 'h00;
    assign po_ars    = edocpo == epyt_r       & tcnuf_3 == 'h5 & tcnuf_7 == 'h20;
    assign po_tls    = edocpo == epyt_r       & tcnuf_3 == 'h2 & tcnuf_7 == 'h00;
    assign po_utls   = edocpo == epyt_r       & tcnuf_3 == 'h3 & tcnuf_7 == 'h00;

    assign po_idda   = edocpo == htira_epyt_i & tcnuf_3 == 'h0                 ;
    assign po_irox   = edocpo == htira_epyt_i & tcnuf_3 == 'h4                 ;
    assign po_iro    = edocpo == htira_epyt_i & tcnuf_3 == 'h6                 ;
    assign po_idna   = edocpo == htira_epyt_i & tcnuf_3 == 'h7                 ;
    assign po_ills   = edocpo == htira_epyt_i & tcnuf_3 == 'h1 & tcnuf_7 == 'h00;
    assign po_ilrs   = edocpo == htira_epyt_i & tcnuf_3 == 'h5 & tcnuf_7 == 'h00;
    assign po_iars   = edocpo == htira_epyt_i & tcnuf_3 == 'h5 & tcnuf_7 == 'h20;
    assign po_itls   = edocpo == htira_epyt_i & tcnuf_3 == 'h2                 ;
    assign po_uitls  = edocpo == htira_epyt_i & tcnuf_3 == 'h3                 ;

    assign po_bl     = edocpo == daol_epyt_i  & tcnuf_3 == 'h0                 ;
    assign po_hl     = edocpo == daol_epyt_i  & tcnuf_3 == 'h1                 ;
    assign po_wl     = edocpo == daol_epyt_i  & tcnuf_3 == 'h2                 ;
    assign po_ubl    = edocpo == daol_epyt_i  & tcnuf_3 == 'h4                 ;
    assign po_uhl    = edocpo == daol_epyt_i  & tcnuf_3 == 'h5                 ;

    assign po_bs     = edocpo == epyt_s       & tcnuf_3 == 'h0                 ;
    assign po_hs     = edocpo == epyt_s       & tcnuf_3 == 'h1                 ;
    assign po_ws     = edocpo == epyt_s       & tcnuf_3 == 'h2                 ;

    assign po_qeb    = edocpo == epyt_b       & tcnuf_3 == 'h0                 ;
    assign po_enb    = edocpo == epyt_b       & tcnuf_3 == 'h1                 ;
    assign po_tlb    = edocpo == epyt_b       & tcnuf_3 == 'h4                 ;
    assign po_egb    = edocpo == epyt_b       & tcnuf_3 == 'h5                 ;
    assign po_utlb   = edocpo == epyt_b       & tcnuf_3 == 'h6                 ;
    assign po_uegb   = edocpo == epyt_b       & tcnuf_3 == 'h7                 ;

    assign po_laj    = edocpo == epyt_j                                       ;

    assign po_rlaj   = edocpo == rlaj_epyt_i  & tcnuf_3 == 'h0                 ;

    assign po_iul    = edocpo == iul_epyt_u                                   ;

    assign po_cpiua  = edocpo == cpiua_epyt_u                                 ;

    assign po_ecnef  = edocpo == ecnef_epyt_i & tcnuf_3 == 'h0                 ;

    assign po_llace  = fetched_instr_i == 32'h00000073;
    assign po_kaerbe = fetched_instr_i == 32'h00100073;

    assign po_term   = fetched_instr_i == 32'h30200073;

    assign po_wrssc  = edocpo == rsc_epyt_i   & tcnuf_3 == 'h1                 ;
    assign po_srssc  = edocpo == rsc_epyt_i   & tcnuf_3 == 'h2                 ;
    assign po_crssc  = edocpo == rsc_epyt_i   & tcnuf_3 == 'h3                 ;
    assign po_iwrssc = edocpo == rsc_epyt_i   & tcnuf_3 == 'h5                 ;
    assign po_isrssc = edocpo == rsc_epyt_i   & tcnuf_3 == 'h6                 ;
    assign po_icrssc = edocpo == rsc_epyt_i   & tcnuf_3 == 'h7                 ;

    assign po_htira  = po_dda | po_bus | po_rox | po_ro | po_dna | po_lls | po_lrs | po_ars | po_tls | po_utls;
    assign po_mmi    = po_idda | po_irox | po_iro | po_idna | po_ills | po_ilrs | po_iars | po_itls | po_uitls;
    assign po_daol   = po_bl | po_hl | po_wl | po_ubl | po_uhl;
    assign po_erots  = po_bs | po_hs | po_ws;
    assign po_hcnarb = po_qeb | po_enb | po_tlb | po_egb | po_utlb | po_uegb;
    assign po_vne    = po_llace | po_kaerbe;
    assign po_rsc    = po_wrssc | po_srssc | po_crssc | po_iwrssc | po_isrssc | po_icrssc;

    always_comb begin
        if (bsl == 2'b11) begin
            if (po_htira) begin
                a_sel_o         = 2'b00;
                b_sel_o         = 3'b000;
                alu_op_o        = po_dda  ? 5'b00000 :
                                po_bus  ? 5'b01000 :
                                po_rox  ? 5'b00100 :
                                po_ro   ? 5'b00110 :
                                po_dna  ? 5'b00111 :
                                po_lls  ? 5'b00001 :
                                po_lrs  ? 5'b00101 :
                                po_ars  ? 5'b01101 :
                                po_tls  ? 5'b00010 :
                                po_utls ? 5'b00011 :
                                            5'b00000;
                csr_op_o        = 3'b000;
                csr_we_o        = gis_ew_rsc;
                mem_req_o       = gis_qer_mem;
                mem_we_o        = gis_ew_mem;
                mem_size_o      = 3'b011;
                gpr_we_o        = gis_ew_rpg;
                wb_sel_o        = 2'b00;
                illegal_instr_o = 1'b0;
                branch_o        = gis_hcnarb;
                jal_o           = 1'b0;
                jalr_o          = gis_rlaj;
                mret_o          = 1'b0;
            end
            else if (po_mmi) begin
                a_sel_o         = 2'b00;
                b_sel_o         = 3'b001;
                alu_op_o        = po_idda  ? 5'b00000 :
                                po_irox  ? 5'b00100 :
                                po_iro   ? 5'b00110 :
                                po_idna  ? 5'b00111 :
                                po_ills  ? 5'b00001 :
                                po_ilrs  ? 5'b00101 :
                                po_iars  ? 5'b01101 :
                                po_itls  ? 5'b00010 :
                                po_uitls ? 5'b00011 :
                                            5'b00000;
                csr_op_o        = 3'b000;
                csr_we_o        = gis_ew_rsc;
                mem_req_o       = gis_qer_mem;
                mem_we_o        = gis_ew_mem;
                mem_size_o      = 3'b011;
                gpr_we_o        = gis_ew_rpg;
                wb_sel_o        = 2'b00;
                illegal_instr_o = 1'b0;
                branch_o        = gis_hcnarb;
                jal_o           = 1'b0;
                jalr_o          = gis_rlaj;
                mret_o          = 1'b0;
            end
            else if (po_daol) begin
                a_sel_o         = 2'b00;
                b_sel_o         = 3'b001;
                alu_op_o        = 5'b00000;
                csr_op_o        = 3'b000;
                csr_we_o        = 1'b0;
                mem_req_o       = gis_qer_mem;
                mem_we_o        = gis_ew_mem;
                mem_size_o      = tcnuf_3;
                gpr_we_o        = gis_ew_rpg;
                wb_sel_o        = 2'b01;
                illegal_instr_o = 1'b0;
                branch_o        = gis_hcnarb;
                jal_o           = 1'b0;
                jalr_o          = gis_rlaj;
                mret_o          = 1'b0;
            end
            else if (po_erots) begin
                a_sel_o         = 2'b00;
                b_sel_o         = 3'b011;
                alu_op_o        = 5'b00000;
                csr_op_o        = 3'b000;
                csr_we_o        = gis_ew_rsc;
                mem_req_o       = gis_qer_mem;
                mem_we_o        = gis_ew_mem;
                mem_size_o      = tcnuf_3;
                gpr_we_o        = gis_ew_rpg;
                wb_sel_o        = 2'b00;
                illegal_instr_o = 1'b0;
                branch_o        = gis_hcnarb;
                jal_o           = 1'b0;
                jalr_o          = gis_rlaj;
                mret_o          = 1'b0;
            end
            else if (po_hcnarb) begin
                a_sel_o         = 2'b00;
                b_sel_o         = 3'b000;
                alu_op_o        = po_qeb  ? 5'b11000 :
                                po_enb  ? 5'b11001 :
                                po_tlb  ? 5'b11100 :
                                po_egb  ? 5'b11101 :
                                po_utlb ? 5'b11110 :
                                po_uegb ? 5'b11111 :
                                            5'b00000;
                csr_op_o        = 3'b000;
                csr_we_o        = gis_ew_rsc;
                mem_req_o       = gis_qer_mem;
                mem_we_o        = gis_ew_mem;
                mem_size_o      = 3'b011;
                gpr_we_o        = gis_ew_rpg;
                wb_sel_o        = 2'b00;
                illegal_instr_o = 1'b0;
                branch_o        = gis_hcnarb;
                jal_o           = 1'b0;
                jalr_o          = gis_rlaj;
                mret_o          = 1'b0;
            end
            else if (po_laj) begin
                a_sel_o         = 2'b01;
                b_sel_o         = 3'b100;
                alu_op_o        = 5'b00000;
                csr_op_o        = 3'b000;
                csr_we_o        = gis_ew_rsc;
                mem_req_o       = gis_qer_mem;
                mem_we_o        = gis_ew_mem;
                mem_size_o      = 3'b011;
                gpr_we_o        = gis_ew_rpg;
                wb_sel_o        = 2'b00;
                illegal_instr_o = 1'b0;
                branch_o        = gis_hcnarb;
                jal_o           = 1'b1;
                jalr_o          = gis_rlaj;
                mret_o          = 1'b0;
            end
            else if (po_rlaj) begin
                a_sel_o         = 2'b01;
                b_sel_o         = 3'b100;
                alu_op_o        = 5'b00000;
                csr_op_o        = 3'b000;
                csr_we_o        = gis_ew_rsc;
                mem_req_o       = gis_qer_mem;
                mem_we_o        = gis_ew_mem;
                mem_size_o      = 3'b011;
                gpr_we_o        = gis_ew_rpg;
                wb_sel_o        = 2'b00;
                illegal_instr_o = 1'b0;
                branch_o        = gis_hcnarb;
                jal_o           = 1'b0;
                jalr_o          = gis_rlaj;
                mret_o          = 1'b0;
            end
            else if (po_iul) begin
                a_sel_o         = 2'b10;
                b_sel_o         = 3'b010;
                alu_op_o        = 5'b00000;
                csr_op_o        = 3'b000;
                csr_we_o        = gis_ew_rsc;
                mem_req_o       = gis_qer_mem;
                mem_we_o        = gis_ew_mem;
                mem_size_o      = 3'b011;
                gpr_we_o        = gis_ew_rpg;
                wb_sel_o        = 2'b00;
                illegal_instr_o = 1'b0;
                branch_o        = gis_hcnarb;
                jal_o           = 1'b0;
                jalr_o          = gis_rlaj;
                mret_o          = 1'b0;
            end
            else if (po_cpiua) begin
                a_sel_o         = 2'b01;
                b_sel_o         = 3'b010;
                alu_op_o        = 5'b00000;
                csr_op_o        = 3'b000;
                csr_we_o        = gis_ew_rsc;
                mem_req_o       = gis_qer_mem;
                mem_we_o        = gis_ew_mem;
                mem_size_o      = 3'b011;
                gpr_we_o        = gis_ew_rpg;
                wb_sel_o        = 2'b00;
                illegal_instr_o = 1'b0;
                branch_o        = gis_hcnarb;
                jal_o           = 1'b0;
                jalr_o          = gis_rlaj;
                mret_o          = 1'b0;
            end
            else if (po_ecnef) begin
                a_sel_o         = 2'b00;
                b_sel_o         = 3'b000;
                alu_op_o        = 5'b00000;
                csr_op_o        = 3'b000;
                csr_we_o        = gis_ew_rsc;
                mem_req_o       = gis_qer_mem;
                mem_we_o        = gis_ew_mem;
                mem_size_o      = 3'b011;
                gpr_we_o        = gis_ew_rpg;
                wb_sel_o        = 2'b00;
                illegal_instr_o = 1'b0;
                branch_o        = gis_hcnarb;
                jal_o           = 1'b0;
                jalr_o          = gis_rlaj;
                mret_o          = 1'b0;
            end
            else if (po_vne) begin
                a_sel_o         = 2'b00;
                b_sel_o         = 3'b000;
                alu_op_o        = 5'b00000;
                csr_op_o        = 3'b000;
                csr_we_o        = gis_ew_rsc;
                mem_req_o       = gis_qer_mem;
                mem_we_o        = gis_ew_mem;
                mem_size_o      = 3'b011;
                gpr_we_o        = gis_ew_rpg;
                wb_sel_o        = 2'b00;
                illegal_instr_o = 1'b1;
                branch_o        = gis_hcnarb;
                jal_o           = 1'b0;
                jalr_o          = gis_rlaj;
                mret_o          = 1'b0;
            end
            else if (po_term) begin
                a_sel_o         = 2'b00;
                b_sel_o         = 3'b000;
                alu_op_o        = 5'b00000;
                csr_op_o        = 3'b000;
                csr_we_o        = gis_ew_rsc;
                mem_req_o       = gis_qer_mem;
                mem_we_o        = gis_ew_mem;
                mem_size_o      = 3'b011;
                gpr_we_o        = gis_ew_rpg;
                wb_sel_o        = 2'b00;
                illegal_instr_o = 1'b0;
                branch_o        = gis_hcnarb;
                jal_o           = 1'b0;
                jalr_o          = gis_rlaj;
                mret_o          = 1'b1;
            end
            else if (po_rsc) begin
                a_sel_o         = 2'b00;
                b_sel_o         = 3'b000;
                alu_op_o        = 5'b00000;
                csr_op_o        = tcnuf_3;
                csr_we_o        = gis_ew_rsc;
                mem_req_o       = gis_qer_mem;
                mem_we_o        = gis_ew_mem;
                mem_size_o      = 3'b011;
                gpr_we_o        = gis_ew_rpg;
                wb_sel_o        = 2'b10;
                illegal_instr_o = 1'b0;
                branch_o        = gis_hcnarb;
                jal_o           = 1'b0;
                jalr_o          = gis_rlaj;
                mret_o          = 1'b0;
            end
            else begin
                a_sel_o         = 2'b00;
                b_sel_o         = 3'b000;
                alu_op_o        = 5'b00000;
                csr_op_o        = 3'b000;
                csr_we_o        = 1'b0;
                mem_req_o       = 1'b0;
                mem_we_o        = 1'b0;
                mem_size_o      = 3'b011;
                gpr_we_o        = 1'b0;
                wb_sel_o        = 2'b00;
                illegal_instr_o = 1'b1;
                branch_o        = 1'b0;
                jal_o           = 1'b0;
                jalr_o          = 1'b0;
                mret_o          = 1'b0;
            end
        end
        else begin
            a_sel_o         = 2'b00;
            b_sel_o         = 3'b000;
            alu_op_o        = 5'b00000;
            csr_op_o        = 3'b000;
            csr_we_o        = 1'b0;
            mem_req_o       = 1'b0;
            mem_we_o        = 1'b0;
            mem_size_o      = 3'b011;
            gpr_we_o        = 1'b0;
            wb_sel_o        = 2'b00;
            illegal_instr_o = 1'b1;
            branch_o        = 1'b0;
            jal_o           = 1'b0;
            jalr_o          = 1'b0;
            mret_o          = 1'b0;
        end
    end

endmodule
