// Copyright 2018 ETH Zurich and University of Bologna.
// Copyright and related rights are licensed under the Solderpad Hardware
// License, Version 0.51 (the "License"); you may not use this file except in
// compliance with the License.  You may obtain a copy of the License at
// http://solderpad.org/licenses/SHL-0.51. Unless required by applicable law
// or agreed to in writing, software, hardware and materials distributed under
// this 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.
//
// Author: Florian Zaruba, ETH Zurich - Andreas Traber, ACP
// Date: 30.05.2017
// Description: Instruction tracer single instruction item

`ifndef VERILATOR
function string printPCexpr(input logic [63:0] imm);
  // check if the sign bit is set
  if ($signed(imm) > 0) begin
      return $sformatf("pc + %0d", $signed(imm));
  end else begin
      return $sformatf("pc - %0d", $signed(-imm));
  end
endfunction

class instr_trace_item #(
    parameter config_pkg::cva6_cfg_t CVA6Cfg = config_pkg::cva6_cfg_empty,
    parameter type bp_resolve_t = logic,
    parameter type scoreboard_entry_t = logic
);
    // keep a couple of general purpose information inside this instruction item
    time               simtime;
    longint unsigned   cycle;
    scoreboard_entry_t sbe;
    logic [31:0]       pc;
    logic [31:0]       instr;
    logic [63:0]       gp_reg_file [32];
    logic [63:0]       fp_reg_file [32];
    logic [4:0]        read_regs [$];
    logic              read_fpr [$];
    logic [4:0]        result_regs [$];
    logic              result_fpr [$];
    logic [63:0]       imm;
    logic [63:0]       result;
    logic [CVA6Cfg.PLEN-1:0]       paddr;
    string             priv_lvl;
    bp_resolve_t       bp;

    logic [4:0] rs1, rs2, rs3, rd;

    // constructor creating a new instruction trace item, e.g.: a single instruction with all relevant information
    function new (time simtime, longint unsigned cycle, scoreboard_entry_t sbe, logic [31:0] instr, logic [63:0] gp_reg_file [32],
                logic [63:0] fp_reg_file [32], logic [63:0] result, logic [CVA6Cfg.PLEN-1:0] paddr, riscv::priv_lvl_t priv_lvl, logic debug_mode, bp_resolve_t bp);
        this.simtime  = simtime;
        this.cycle    = cycle;
        this.pc       = sbe.pc;
        this.sbe      = sbe;
        this.gp_reg_file = gp_reg_file;
        this.fp_reg_file = fp_reg_file;
        this.result   = result;
        this.paddr    = paddr;
        this.bp       = bp;
        this.priv_lvl = (debug_mode) ? "D" : getPrivLevel(priv_lvl);
        this.rs1      = sbe.rs1[4:0];
        this.rs2      = sbe.rs2[4:0];
        this.rs3      = instr[31:27];
        this.rd       = sbe.rd[4:0];
        if (instr[1:0] != 2'b11) begin
            this.instr    = {16'b0, instr[15:0]};
        end else begin
            this.instr    = instr;
        end
    endfunction

    // convert gp register address to ABI compatible form
    function string regAddrToStr(logic [5:0] addr);
        case (addr[4:0])
            0: return "x0";
            1: return "ra";
            2: return "sp";
            3: return "gp";
            4: return "tp";
            5, 6, 7: return $sformatf("t%0d", (addr[4:0] - 5));
            8, 9: return $sformatf("s%0d", (addr[4:0] - 8));
            10, 11, 12, 13, 14, 15, 16, 17: return $sformatf("a%0d", (addr[4:0] - 10));
            28, 29, 30, 31: return $sformatf("t%0d", (addr[4:0] - 25));
            default: return $sformatf("s%0d", (addr[4:0] - 16));
        endcase
    endfunction
    // convert fp register address to ABI compatible form
    function string fpRegAddrToStr(logic [5:0] addr);
        case (addr) inside
            [0:7]   : return $sformatf("ft%0d", addr);
            [8:9]   : return $sformatf("fs%0d", (addr - 8));
            [10:17] : return $sformatf("fa%0d", (addr - 10));
            [18:27] : return $sformatf("fs%0d", (addr - 16));
            [28:31] : return $sformatf("ft%0d", (addr - 20));
        endcase
    endfunction

    function string fpFmtToStr(logic [1:0] fmt);
        case (fmt)
            2'b00 : return "s";
            2'b01 : return "d";
            2'b10 : return "h";
            2'b11 : return "b";
            default : return "XX";
        endcase
    endfunction

    function string fmvFpFmtToStr(logic [1:0] fmt);
        case (fmt)
            2'b00 : return "w";
            2'b01 : return "d";
            2'b10 : return "h";
            2'b11 : return "b";
            default : return "XX";
        endcase
    endfunction

    function string intFmtToStr(logic [1:0] ifmt);
        case (ifmt)
            2'b00 : return "w";
            2'b01 : return "wu";
            2'b10 : return "l";
            2'b11 : return "lu";
            default : return "XX";
        endcase
    endfunction

    function string fpRmToStr(logic [2:0] rm);
        case (rm)
            3'b000 : return "rne";
            3'b001 : return "rtz";
            3'b010 : return "rdn";
            3'b011 : return "rup";
            3'b100 : return "rmm";
            3'b111 : return "dyn"; // what is this called in rv binutils?
            default: return "INVALID";
        endcase
    endfunction

    function string csrAddrToStr(logic [11:0] addr);
        case (addr)
            riscv::CSR_FFLAGS:     return "fflags";
            riscv::CSR_FRM:        return "frm";
            riscv::CSR_FCSR:       return "fcsr";
            riscv::CSR_SSTATUS:    return "sstatus";
            riscv::CSR_SIE:        return "sie";
            riscv::CSR_STVEC:      return "stvec";
            riscv::CSR_SCOUNTEREN: return "scounteren";
            riscv::CSR_SSCRATCH:   return "sscratch";
            riscv::CSR_SEPC:       return "sepc";
            riscv::CSR_SCAUSE:     return "scause";
            riscv::CSR_STVAL:      return "stval";
            riscv::CSR_SIP:        return "sip";
            riscv::CSR_SATP:       return "satp";

            riscv::CSR_MSTATUS:    return "mstatus";
            riscv::CSR_MISA:       return "misa";
            riscv::CSR_MEDELEG:    return "medeleg";
            riscv::CSR_MIDELEG:    return "mideleg";
            riscv::CSR_MIE:        return "mie";
            riscv::CSR_MTVEC:      return "mtvec";
            riscv::CSR_MCOUNTEREN: return "mcounteren";
            riscv::CSR_MSCRATCH:   return "mscratch";
            riscv::CSR_MEPC:       return "mepc";
            riscv::CSR_MCAUSE:     return "mcause";
            riscv::CSR_MTVAL:      return "mtval";
            riscv::CSR_MIP:        return "mip";
            riscv::CSR_PMPCFG0:    return "pmpcfg0";
            riscv::CSR_PMPADDR0:   return "pmpaddr0";
            riscv::CSR_MVENDORID:  return "mvendorid";
            riscv::CSR_MARCHID:    return "marchid";
            riscv::CSR_MIMPID:     return "mimpid";
            riscv::CSR_MHARTID:    return "mhartid";
            riscv::CSR_MCYCLE:     return "mcycle";
            riscv::CSR_MINSTRET:   return "minstret";

            riscv::CSR_TSELECT:    return "tselect";
            riscv::CSR_TDATA1:     return "tdata1";
            riscv::CSR_TDATA2:     return "tdata2";
            riscv::CSR_TDATA3:     return "tdata3";
            riscv::CSR_TINFO:      return "tinfo";

            riscv::CSR_DCSR:       return "dcsr";
            riscv::CSR_DPC:        return "dpc";
            riscv::CSR_DSCRATCH0:  return "dscratch0";
            riscv::CSR_DSCRATCH1:  return "dscratch1";

            riscv::CSR_CYCLE:      return "cycle";
            riscv::CSR_TIME:       return "time";
            riscv::CSR_INSTRET:    return "instret";

            default:        return $sformatf("%0h", addr);
        endcase
    endfunction

    function string printInstr();
        string s;

        case (instr) inside
             // Aliases
            instr_tracer_pkg::INSTR_NOP:      s = this.printMnemonic("nop");
            // Regular opcodes
            instr_tracer_pkg::INSTR_LUI:      s = this.printUInstr("lui");
            instr_tracer_pkg::INSTR_AUIPC:    s = this.printUInstr("auipc");
            instr_tracer_pkg::INSTR_JAL:      s = this.printJump("jal");
            instr_tracer_pkg::INSTR_JALR:     s = this.printJump("jalr");
            // BRANCH
            instr_tracer_pkg::INSTR_BEQZ:     s = this.printSBInstr("beqz");
            instr_tracer_pkg::INSTR_BEQ:      s = this.printSBInstr("beq");
            instr_tracer_pkg::INSTR_BNEZ:     s = this.printSBInstr("bnez");
            instr_tracer_pkg::INSTR_BNE:      s = this.printSBInstr("bne");
            instr_tracer_pkg::INSTR_BLTZ:     s = this.printSBInstr("bltz");
            instr_tracer_pkg::INSTR_BLT:      s = this.printSBInstr("blt");
            instr_tracer_pkg::INSTR_BGEZ:     s = this.printSBInstr("bgez");
            instr_tracer_pkg::INSTR_BGE:      s = this.printSBInstr("bge");
            instr_tracer_pkg::INSTR_BLTU:     s = this.printSBInstr("bltu");
            instr_tracer_pkg::INSTR_BGEU:     s = this.printSBInstr("bgeu");
            // OPIMM
            instr_tracer_pkg::INSTR_LI:       s = this.printIInstr("li");
            instr_tracer_pkg::INSTR_ADDI:     s = this.printIInstr("addi");
            instr_tracer_pkg::INSTR_SLTI:     s = this.printIInstr("slti");
            instr_tracer_pkg::INSTR_SLTIU:    s = this.printIInstr("sltiu");
            instr_tracer_pkg::INSTR_XORI:     s = this.printIInstr("xori");
            instr_tracer_pkg::INSTR_ORI:      s = this.printIInstr("ori");
            instr_tracer_pkg::INSTR_ANDI:     s = this.printIInstr("andi");
            instr_tracer_pkg::INSTR_SLLI:     s = this.printIuInstr("slli");
            instr_tracer_pkg::INSTR_SRLI:     s = this.printIuInstr("srli");
            instr_tracer_pkg::INSTR_SRAI:     s = this.printIuInstr("srai");
            // OPIMM32
            instr_tracer_pkg::INSTR_ADDIW:    s = this.printIInstr("addiw");
            instr_tracer_pkg::INSTR_SLLIW:    s = this.printIuInstr("slliw");
            instr_tracer_pkg::INSTR_SRLIW:    s = this.printIuInstr("srliw");
            instr_tracer_pkg::INSTR_SRAIW:    s = this.printIuInstr("sraiw");
            // OP
            instr_tracer_pkg::INSTR_ADD:      s = this.printRInstr("add");
            instr_tracer_pkg::INSTR_SUB:      s = this.printRInstr("sub");
            instr_tracer_pkg::INSTR_SLL:      s = this.printRInstr("sll");
            instr_tracer_pkg::INSTR_SLT:      s = this.printRInstr("slt");
            instr_tracer_pkg::INSTR_SLTU:     s = this.printRInstr("sltu");
            instr_tracer_pkg::INSTR_XOR:      s = this.printRInstr("xor");
            instr_tracer_pkg::INSTR_SRL:      s = this.printRInstr("srl");
            instr_tracer_pkg::INSTR_SRA:      s = this.printRInstr("sra");
            instr_tracer_pkg::INSTR_OR:       s = this.printRInstr("or");
            instr_tracer_pkg::INSTR_AND:      s = this.printRInstr("and");
            instr_tracer_pkg::INSTR_MUL:      s = this.printMulInstr(1'b0);
            // OP32
            instr_tracer_pkg::INSTR_ADDW:     s = this.printRInstr("addw");
            instr_tracer_pkg::INSTR_SUBW:     s = this.printRInstr("subw");
            instr_tracer_pkg::INSTR_SLLW:     s = this.printRInstr("sllw");
            instr_tracer_pkg::INSTR_SRLW:     s = this.printRInstr("srlw");
            instr_tracer_pkg::INSTR_SRAW:     s = this.printRInstr("sraw");
            instr_tracer_pkg::INSTR_MULW:     s = this.printMulInstr(1'b1);
            // FP
            instr_tracer_pkg::INSTR_FMADD:    s = this.printR4Instr("fmadd");
            instr_tracer_pkg::INSTR_FMSUB:    s = this.printR4Instr("fmsub");
            instr_tracer_pkg::INSTR_FNSMSUB:  s = this.printR4Instr("fnmsub");
            instr_tracer_pkg::INSTR_FNMADD:   s = this.printR4Instr("fnmadd");

            instr_tracer_pkg::INSTR_FADD:     s = this.printRFBCInstr("fadd", 1'b1);
            instr_tracer_pkg::INSTR_FSUB:     s = this.printRFBCInstr("fsub", 1'b1);
            instr_tracer_pkg::INSTR_FMUL:     s = this.printRFInstr("fmul", 1'b1);
            instr_tracer_pkg::INSTR_FDIV:     s = this.printRFInstr("fdiv", 1'b1);
            instr_tracer_pkg::INSTR_FSQRT:    s = this.printRFInstr1Op("fsqrt", 1'b1);
            instr_tracer_pkg::INSTR_FSGNJ:    s = this.printRFInstr("fsgnj", 1'b0);
            instr_tracer_pkg::INSTR_FSGNJN:   s = this.printRFInstr("fsgnjn", 1'b0);
            instr_tracer_pkg::INSTR_FSGNJX:   s = this.printRFInstr("fsgnjx", 1'b0);
            instr_tracer_pkg::INSTR_FMIN:     s = this.printRFInstr("fmin", 1'b0);
            instr_tracer_pkg::INSTR_FMAX:     s = this.printRFInstr("fmax", 1'b0);
            instr_tracer_pkg::INSTR_FLE:      s = this.printRFInstr("fle", 1'b0);
            instr_tracer_pkg::INSTR_FLT:      s = this.printRFInstr("flt", 1'b0);
            instr_tracer_pkg::INSTR_FEQ:      s = this.printRFInstr("feq", 1'b0);

            instr_tracer_pkg::INSTR_FCLASS:   s = this.printRFInstr1Op("fclass", 1'b0);

            instr_tracer_pkg::INSTR_FCVT_F2F,
            instr_tracer_pkg::INSTR_FMV_F2X,
            instr_tracer_pkg::INSTR_FMV_X2F,
            instr_tracer_pkg::INSTR_FCVT_F2I,
            instr_tracer_pkg::INSTR_FCVT_I2F: s = this.printFpSpecialInstr(); // these are a mess to do nicely
            // FENCE
            instr_tracer_pkg::INSTR_FENCE:    s = this.printMnemonic("fence");
            instr_tracer_pkg::INSTR_FENCEI:   s = this.printMnemonic("fence.i");
            // SYSTEM (CSR manipulation)
            instr_tracer_pkg::INSTR_CSRW:     s = this.printCSRInstr("csrw");
            instr_tracer_pkg::INSTR_CSRRW:    s = this.printCSRInstr("csrrw");
            instr_tracer_pkg::INSTR_CSRR:     s = this.printCSRInstr("csrr");
            instr_tracer_pkg::INSTR_CSRRS:    s = this.printCSRInstr("csrrs");
            instr_tracer_pkg::INSTR_CSRS:     s = this.printCSRInstr("csrs");
            instr_tracer_pkg::INSTR_CSRRC:    s = this.printCSRInstr("csrrc");
            instr_tracer_pkg::INSTR_CSRC:     s = this.printCSRInstr("csrc");

            instr_tracer_pkg::INSTR_CSRWI:    s = this.printCSRInstr("csrwi");
            instr_tracer_pkg::INSTR_CSRRWI:   s = this.printCSRInstr("csrrwi");
            instr_tracer_pkg::INSTR_CSRSI:    s = this.printCSRInstr("csrsi");
            instr_tracer_pkg::INSTR_CSRRSI:   s = this.printCSRInstr("csrrsi");
            instr_tracer_pkg::INSTR_CSRCI:    s = this.printCSRInstr("csrci");
            instr_tracer_pkg::INSTR_CSRRCI:   s = this.printCSRInstr("csrrci");
            // SYSTEM (others)
            instr_tracer_pkg::INSTR_ECALL:    s = this.printMnemonic("ecall");
            instr_tracer_pkg::INSTR_EBREAK:   s = this.printMnemonic("ebreak");
            instr_tracer_pkg::INSTR_MRET:     s = this.printMnemonic("mret");
            instr_tracer_pkg::INSTR_SRET:     s = this.printMnemonic("sret");
            instr_tracer_pkg::INSTR_DRET:     s = this.printMnemonic("dret");
            instr_tracer_pkg::INSTR_WFI:      s = this.printMnemonic("wfi");
            instr_tracer_pkg::INSTR_SFENCE:   s = this.printMnemonic("sfence.vma");
            // loads and stores
            instr_tracer_pkg::LB:             s = this.printLoadInstr("lb");
            instr_tracer_pkg::LH:             s = this.printLoadInstr("lh");
            instr_tracer_pkg::LW:             s = this.printLoadInstr("lw");
            instr_tracer_pkg::LD:             s = this.printLoadInstr("ld");
            instr_tracer_pkg::LBU:            s = this.printLoadInstr("lbu");
            instr_tracer_pkg::LHU:            s = this.printLoadInstr("lhu");
            instr_tracer_pkg::LWU:            s = this.printLoadInstr("lwu");
            instr_tracer_pkg::FLW:            s = this.printLoadInstr("flw");
            instr_tracer_pkg::FLD:            s = this.printLoadInstr("fld");
            instr_tracer_pkg::FLQ:            s = this.printLoadInstr("flq");
            instr_tracer_pkg::SB:             s = this.printStoreInstr("sb");
            instr_tracer_pkg::SH:             s = this.printStoreInstr("sh");
            instr_tracer_pkg::SW:             s = this.printStoreInstr("sw");
            instr_tracer_pkg::SD:             s = this.printStoreInstr("sd");
            instr_tracer_pkg::FSW:            s = this.printStoreInstr("fsw");
            instr_tracer_pkg::FSD:            s = this.printStoreInstr("fsd");
            instr_tracer_pkg::FSQ:            s = this.printStoreInstr("fsq");
            instr_tracer_pkg::INSTR_AMO:      s = this.printAMOInstr();
            // Compressed Instructions
            instr_tracer_pkg::C_FLD:          s = this.printLoadInstr("c.fld");
            instr_tracer_pkg::C_LW:           s = this.printLoadInstr("c.lw");
            instr_tracer_pkg::C_LD:           s = this.printLoadInstr("c.ld");
            instr_tracer_pkg::C_LWSP:         s = this.printLoadInstr("c.lwsp");
            instr_tracer_pkg::C_LDSP:         s = this.printLoadInstr("c.ldsp");
            instr_tracer_pkg::C_FLDSP:        s = this.printLoadInstr("c.fldsp");
            instr_tracer_pkg::C_SDSP:         s = this.printStoreInstr("c.sdsp");
            instr_tracer_pkg::C_SWSP:         s = this.printStoreInstr("c.swsp");
            instr_tracer_pkg::C_FSDSP:        s = this.printStoreInstr("c.fsdsp");
            instr_tracer_pkg::C_SW:           s = this.printStoreInstr("c.sw");
            instr_tracer_pkg::C_SD:           s = this.printStoreInstr("c.sd");
            instr_tracer_pkg::C_FSD:          s = this.printStoreInstr("c.fsd");
            instr_tracer_pkg::C_J:            s = this.printJump("c.j");
            instr_tracer_pkg::C_JR:           s = this.printJump("c.jr");
            instr_tracer_pkg::C_JALR:         s = this.printJump("c.jalr");
            instr_tracer_pkg::C_MV:           s = this.printRInstr("c.mv");
            instr_tracer_pkg::C_ADD:          s = this.printRInstr("c.add");
            instr_tracer_pkg::C_BEQZ:         s = this.printSBInstr("c.beqz");
            instr_tracer_pkg::C_BNEZ:         s = this.printSBInstr("c.bnez");
            instr_tracer_pkg::C_LUI:          s = this.printUInstr("c.lui");
            instr_tracer_pkg::C_LI:           s = this.printIInstr("c.li");
            instr_tracer_pkg::C_ADDI:         s = this.printIInstr("c.addi");
            instr_tracer_pkg::C_ADDI16SP:     s = this.printIInstr("c.addi16sp");
            instr_tracer_pkg::C_ADDIW:        s = this.printIInstr("c.addiw");
            instr_tracer_pkg::C_SLLI:         s = this.printIInstr("c.slli");
            instr_tracer_pkg::C_SRLI:         s = this.printIInstr("c.srli");
            instr_tracer_pkg::C_SRAI:         s = this.printIInstr("c.srai");
            instr_tracer_pkg::C_ANDI:         s = this.printIInstr("c.andi");
            instr_tracer_pkg::C_ADDI4SPN:     s = this.printIInstr("c.addi4spn");
            instr_tracer_pkg::C_SUB:          s = this.printRInstr("c.sub");
            instr_tracer_pkg::C_XOR:          s = this.printRInstr("c.xor");
            instr_tracer_pkg::C_OR:           s = this.printRInstr("c.or");
            instr_tracer_pkg::C_AND:          s = this.printRInstr("c.and");
            instr_tracer_pkg::C_SUBW:         s = this.printRInstr("c.subw");
            instr_tracer_pkg::C_ADDW:         s = this.printRInstr("c.addw");
            instr_tracer_pkg::C_NOP:          s = this.printMnemonic("c.nop");
            instr_tracer_pkg::C_EBREAK:       s = this.printMnemonic("c.ebreak");
            default:                          s = this.printMnemonic("INVALID");
        endcase

        s = $sformatf("%8dns %8d %s %h %h %h %-36s", simtime,
                                             cycle,
                                             priv_lvl,
                                             sbe.pc,
                                             bp.is_mispredict & bp.valid,
                                             instr,
                                             s);

        // s = $sformatf("%s %h %h %-36s",
        //                                      priv_lvl,
        //                                      sbe.pc,
        //                                      instr,
        //                                      s);

        foreach (result_regs[i]) begin
            if (result_fpr[i])
                s = $sformatf("%s %-4s:%16x", s, fpRegAddrToStr(result_regs[i]), this.result);
            else if (result_regs[i] != 0)
                s = $sformatf("%s %-4s:%16x", s, regAddrToStr(result_regs[i]), this.result);
        end

        foreach (read_regs[i]) begin
            if (read_fpr[i])
                s = $sformatf("%s %-4s:%16x", s, fpRegAddrToStr(read_regs[i]), fp_reg_file[read_regs[i]]);
            else if (read_regs[i] != 0)
                s = $sformatf("%s %-4s:%16x", s, regAddrToStr(read_regs[i]), gp_reg_file[read_regs[i]]);
        end

        case (instr) inside
            // check of the instruction was a load or store
            instr_tracer_pkg::C_SDSP,
            instr_tracer_pkg::C_SWSP,
            instr_tracer_pkg::C_FSWSP,
            instr_tracer_pkg::C_FSDSP,
            instr_tracer_pkg::C_SW,
            instr_tracer_pkg::C_SD,
            instr_tracer_pkg::C_FSW,
            instr_tracer_pkg::C_FSD,
            instr_tracer_pkg::SB,
            instr_tracer_pkg::SH,
            instr_tracer_pkg::SW,
            instr_tracer_pkg::SD,
            instr_tracer_pkg::FSW,
            instr_tracer_pkg::FSD,
            instr_tracer_pkg::FSQ: begin
                logic [CVA6Cfg.VLEN-1:0] vaddress = gp_reg_file[read_regs[1]] + this.imm;
                s = $sformatf("%s VA: %x PA: %x", s, vaddress, this.paddr);
            end

            instr_tracer_pkg::C_FLD,
            instr_tracer_pkg::C_FLW,
            instr_tracer_pkg::C_LW,
            instr_tracer_pkg::C_LD,
            instr_tracer_pkg::C_LWSP,
            instr_tracer_pkg::C_LDSP,
            instr_tracer_pkg::C_FLWSP,
            instr_tracer_pkg::C_FLDSP,
            instr_tracer_pkg::LB,
            instr_tracer_pkg::LH,
            instr_tracer_pkg::LW,
            instr_tracer_pkg::LD,
            instr_tracer_pkg::LBU,
            instr_tracer_pkg::LHU,
            instr_tracer_pkg::LWU,
            instr_tracer_pkg::FLW,
            instr_tracer_pkg::FLD,
            instr_tracer_pkg::FLQ: begin
                logic [CVA6Cfg.VLEN-1:0] vaddress = gp_reg_file[read_regs[0]] + this.imm;
                s = $sformatf("%s VA: %x PA: %x", s, vaddress, this.paddr);
            end
        endcase
        return s;
    endfunction : printInstr

    // Return the current privilege level as a string
    function string getPrivLevel(input riscv::priv_lvl_t priv_lvl);
        case (priv_lvl)
            riscv::PRIV_LVL_M: return "M";
            riscv::PRIV_LVL_S: return "S";
            riscv::PRIV_LVL_U: return "U";
        endcase
    endfunction : getPrivLevel

    function string printMnemonic(input string mnemonic);
        return mnemonic;
    endfunction // printMnemonic

    function string printRInstr(input string mnemonic);

        result_regs.push_back(rd);
        result_fpr.push_back(1'b0);
        read_regs.push_back(rs1);
        read_fpr.push_back(1'b0);
        read_regs.push_back(rs2);
        read_fpr.push_back(1'b0);

        return $sformatf("%-12s %4s, %s, %s", mnemonic, regAddrToStr(rd), regAddrToStr(rs1), regAddrToStr(rs2));
    endfunction // printRInstr

    function string printRFBCInstr(input string mnemonic, input bit use_rnd);

        result_regs.push_back(rd);
        result_fpr.push_back(ariane_pkg::is_rd_fpr(sbe.op));
        read_regs.push_back(rs2);
        read_fpr.push_back(ariane_pkg::is_rs2_fpr(sbe.op));
        read_regs.push_back(sbe.result[4:0]);
        read_fpr.push_back(ariane_pkg::is_imm_fpr(sbe.op));

        if (use_rnd && instr[14:12]!=3'b111)
            return $sformatf("%-12s %4s, %s, %s, %s", $sformatf("%s.%s",mnemonic, fpFmtToStr(instr[26:25])), ariane_pkg::is_rd_fpr(sbe.op)?fpRegAddrToStr(rd):regAddrToStr(rd), ariane_pkg::is_rs2_fpr(sbe.op)?fpRegAddrToStr(rs2):regAddrToStr(rs2), ariane_pkg::is_imm_fpr(sbe.op)?fpRegAddrToStr(sbe.result[4:0]):regAddrToStr(sbe.result[4:0]), fpRmToStr(instr[14:12]));
        else
            return $sformatf("%-12s %4s, %s, %s", $sformatf("%s.%s",mnemonic, fpFmtToStr(instr[26:25])), ariane_pkg::is_rd_fpr(sbe.op)?fpRegAddrToStr(rd):regAddrToStr(rd), ariane_pkg::is_rs2_fpr(sbe.op)?fpRegAddrToStr(rs2):regAddrToStr(rs2), ariane_pkg::is_imm_fpr(sbe.op)?fpRegAddrToStr(sbe.result[4:0]):regAddrToStr(sbe.result[4:0]));
    endfunction // printRFInstr

    function string printRFInstr(input string mnemonic, input bit use_rnd);

        result_regs.push_back(rd);
        result_fpr.push_back(ariane_pkg::is_rd_fpr(sbe.op));
        read_regs.push_back(rs1);
        read_fpr.push_back(ariane_pkg::is_rs1_fpr(sbe.op));
        read_regs.push_back(rs2);
        read_fpr.push_back(ariane_pkg::is_rs2_fpr(sbe.op));

        if (use_rnd && instr[14:12]!=3'b111)
            return $sformatf("%-12s %4s, %s, %s, %s", $sformatf("%s.%s",mnemonic, fpFmtToStr(instr[26:25])), ariane_pkg::is_rd_fpr(sbe.op)?fpRegAddrToStr(rd):regAddrToStr(rd), ariane_pkg::is_rs1_fpr(sbe.op)?fpRegAddrToStr(rs1):regAddrToStr(rs1), ariane_pkg::is_rs2_fpr(sbe.op)?fpRegAddrToStr(rs2):regAddrToStr(rs2), fpRmToStr(instr[14:12]));
        else
            return $sformatf("%-12s %4s, %s, %s", $sformatf("%s.%s",mnemonic, fpFmtToStr(instr[26:25])), ariane_pkg::is_rd_fpr(sbe.op)?fpRegAddrToStr(rd):regAddrToStr(rd), ariane_pkg::is_rs1_fpr(sbe.op)?fpRegAddrToStr(rs1):regAddrToStr(rs1), ariane_pkg::is_rs2_fpr(sbe.op)?fpRegAddrToStr(rs2):regAddrToStr(rs2));
    endfunction // printRFInstr

    function string printRFInstr1Op(input string mnemonic, input bit use_rnd);

        result_regs.push_back(rd);
        result_fpr.push_back(ariane_pkg::is_rd_fpr(sbe.op));
        read_regs.push_back(rs1);
        read_fpr.push_back(ariane_pkg::is_rs1_fpr(sbe.op));

        if (use_rnd && instr[14:12]!=3'b111)
            return $sformatf("%-12s %4s, %s, %s", $sformatf("%s.%s",mnemonic, fpFmtToStr(instr[26:25])), ariane_pkg::is_rd_fpr(sbe.op)?fpRegAddrToStr(rd):regAddrToStr(rd), ariane_pkg::is_rs1_fpr(sbe.op)?fpRegAddrToStr(rs1):regAddrToStr(rs1), fpRmToStr(instr[14:12]));
        else
            return $sformatf("%-12s %4s, %s", $sformatf("%s.%s",mnemonic, fpFmtToStr(instr[26:25])), ariane_pkg::is_rd_fpr(sbe.op)?fpRegAddrToStr(rd):regAddrToStr(rd), ariane_pkg::is_rs1_fpr(sbe.op)?fpRegAddrToStr(rs1):regAddrToStr(rs1));
    endfunction // printRFInstr1Op

    function string printR4Instr(input string mnemonic);

        result_regs.push_back(rd);
        result_fpr.push_back(1'b1);
        read_regs.push_back(rs1);
        read_fpr.push_back(1'b1);
        read_regs.push_back(rs2);
        read_fpr.push_back(1'b1);
        read_regs.push_back(rs3);
        read_fpr.push_back(1'b1);

        return $sformatf("%-12s %4s, %s, %s, %s, %s", $sformatf("%s.%s",mnemonic, fpFmtToStr(instr[26:25])), fpRegAddrToStr(rd), fpRegAddrToStr(rs1), fpRegAddrToStr(rs2), fpRegAddrToStr(instr[31:27]), fpRmToStr(instr[14:12]));
    endfunction // printR4Instr

    function string printFpSpecialInstr();

        result_regs.push_back(rd);
        result_fpr.push_back(ariane_pkg::is_rd_fpr(sbe.op));
        read_regs.push_back(rs1);
        read_fpr.push_back(ariane_pkg::is_rs1_fpr(sbe.op));

        case (sbe.op)
            instr_tracer_pkg::INSTR_FCVT_F2F : return $sformatf("%-12s %4s, %s, %s", $sformatf("fcvt.%s.%s", fpFmtToStr(instr[26:25]), fpFmtToStr(instr[21:20])), fpRegAddrToStr(rd), fpRegAddrToStr(rs1), fpRmToStr(instr[14:12]));
            instr_tracer_pkg::INSTR_FCVT_F2I : return $sformatf("%-12s %4s, %s, %s", $sformatf("fcvt.%s.%s", intFmtToStr(instr[21:20]), fpFmtToStr(instr[26:25])), regAddrToStr(rd), fpRegAddrToStr(rs1), fpRmToStr(instr[14:12]));
            instr_tracer_pkg::INSTR_FCVT_I2F : return $sformatf("%-12s %4s, %s, %s", $sformatf("fcvt.%s.%s", fpFmtToStr(instr[26:25]), intFmtToStr(instr[21:20])), fpRegAddrToStr(rd), regAddrToStr(rs1), fpRmToStr(instr[14:12]));
            instr_tracer_pkg::INSTR_FMV_F2X  : return $sformatf("%-12s %4s, %s", $sformatf("fmv.x.%s", fmvFpFmtToStr(instr[26:25])), regAddrToStr(rd), fpRegAddrToStr(rs1));
            instr_tracer_pkg::INSTR_FMV_X2F  : return $sformatf("%-12s %4s, %s", $sformatf("fmv.%s.x", fmvFpFmtToStr(instr[26:25])), fpRegAddrToStr(rd), regAddrToStr(rs1));
        endcase
    endfunction

    function string printIInstr(input string mnemonic);

        result_regs.push_back(rd);
        result_fpr.push_back(1'b0);
        read_regs.push_back(rs1);
        read_fpr.push_back(1'b0);

        if (rs1 == 0)
            return $sformatf("%-12s %4s, %0d", mnemonic, regAddrToStr(rd), $signed(sbe.result));

        return $sformatf("%-12s %4s, %s, %0d", mnemonic, regAddrToStr(rd), regAddrToStr(rs1), $signed(sbe.result));
    endfunction // printIInstr

    function string printIuInstr(input string mnemonic);

        result_regs.push_back(rd);
        result_fpr.push_back(1'b0);
        read_regs.push_back(rs1);
        read_fpr.push_back(1'b0);

        return $sformatf("%-12s %4s, %s, 0x%0x", mnemonic, regAddrToStr(rd), regAddrToStr(rs1), sbe.result);
    endfunction // printIuInstr

    function string printSBInstr(input string mnemonic);

        read_regs.push_back(rs1);
        read_fpr.push_back(1'b0);
        read_regs.push_back(rs2);
        read_fpr.push_back(1'b0);

        if (rs2 == 0) begin
            return $sformatf("%-12s %4s, %s", mnemonic, regAddrToStr(rs1), printPCexpr(sbe.result));
        end else begin
            return $sformatf("%-12s %4s, %s, %s", mnemonic, regAddrToStr(rs1), regAddrToStr(rs2), printPCexpr(sbe.result));
        end
    endfunction // printIuInstr

    function string printUInstr(input string mnemonic);

        result_regs.push_back(rd);
        result_fpr.push_back(1'b0);

        return $sformatf("%-12s %4s, 0x%0h", mnemonic, regAddrToStr(rd), sbe.result[31:12]);
    endfunction // printUInstr

    function string printJump(input string mnemonic);
        case (instr[6:0])
            riscv::OpcodeJalr: begin
                // is this a return?
                if (rd == 'b0 && (rs1 == 'h1 || rs1 == 'h5)) return this.printMnemonic("ret");
            end
            riscv::OpcodeJal: begin
                if (rd == 'b0) return this.printUJInstr("j");
            end
        endcase
        return this.printIInstr(mnemonic);
    endfunction

    function string printUJInstr(input string mnemonic);

        result_regs.push_back(rd);
        result_fpr.push_back(1'b0);
        // jump instruction
        if (rd == 0)
            return $sformatf("%-12s   %s", mnemonic, printPCexpr(sbe.result));
        else
            return $sformatf("%-12s %4s, %s", mnemonic, regAddrToStr(rd), printPCexpr(sbe.result));
    endfunction // printUJInstr

    function string printCSRInstr(input string mnemonic);

        result_regs.push_back(rd);
        result_fpr.push_back(1'b0);
        if (instr[14] == 0) begin
        read_regs.push_back(rs1);
        read_fpr.push_back(1'b0);
            if (rd != 0 && rs1 != 0) begin
                  return $sformatf("%-12s %4s, %s, %s", mnemonic, regAddrToStr(rd), regAddrToStr(rs1), csrAddrToStr(sbe.result[11:0]));
            // don't display instructions which write to zero
            end else if (rd == 0) begin
                  return $sformatf("%-12s %4s, %s", mnemonic, regAddrToStr(rs1), csrAddrToStr(sbe.result[11:0]));
            end else if (rs1 == 0) begin
                return $sformatf("%-12s %4s, %s", mnemonic, regAddrToStr(rd), csrAddrToStr(sbe.result[11:0]));
            end
        end else begin
            if (rd != 0 && rs1 != 0) begin
                  return $sformatf("%-12s %4s, %d, %s", mnemonic, regAddrToStr(rd), $unsigned(rs1), csrAddrToStr(sbe.result[11:0]));
            // don't display instructions which write to zero
            end else if (rd == 0) begin
                  return $sformatf("%-14s %2d, %s", mnemonic, $unsigned(rs1), csrAddrToStr(sbe.result[11:0]));
            end else if (rs1 == 0) begin
                return $sformatf("%-12s %4s, %s", mnemonic, regAddrToStr(rd), csrAddrToStr(sbe.result[11:0]));
            end
        end
    endfunction // printCSRInstr

    function string printLoadInstr(input string mnemonic);
        result_regs.push_back(rd);
        result_fpr.push_back(ariane_pkg::is_rd_fpr(sbe.op));
        read_regs.push_back(rs1);
        read_fpr.push_back(1'b0);
        // save the immediate for calculating the virtual address
        this.imm = sbe.result;

        if (instr[6:0] == riscv::OpcodeLoadFp)
            return $sformatf("%-12s %4s, %0d(%s)", mnemonic, fpRegAddrToStr(rd), $signed(sbe.result), regAddrToStr(rs1));
        else
            return $sformatf("%-12s %4s, %0d(%s)", mnemonic, regAddrToStr(rd), $signed(sbe.result), regAddrToStr(rs1));
    endfunction

    function string printStoreInstr(input string mnemonic);
        read_regs.push_back(rs2);
        read_fpr.push_back(ariane_pkg::is_rs2_fpr(sbe.op));
        read_regs.push_back(rs1);
        read_fpr.push_back(1'b0);
        // save the immediate for calculating the virtual address
        this.imm = sbe.result;

        if (instr[6:0] == riscv::OpcodeStoreFp)
            return $sformatf("%-12s %4s, %0d(%s)", mnemonic, fpRegAddrToStr(rs2), $signed(sbe.result), regAddrToStr(rs1));
        else
            return $sformatf("%-12s %4s, %0d(%s)", mnemonic, regAddrToStr(rs2), $signed(sbe.result), regAddrToStr(rs1));
    endfunction // printSInstr

    function string printAMOInstr();
        string mnemonic;
        // words
        if (instr[14:12] == 3'h2) begin
            case (instr[31:27])
                5'h0:  mnemonic = "amoadd.w";
                5'h1:  mnemonic = "amoswap.w";
                5'h2:  mnemonic = "lr.w";
                5'h3:  mnemonic = "sc.w";
                5'h4:  mnemonic = "amoxor.w";
                5'h8:  mnemonic = "amoor.w";
                5'hC:  mnemonic = "amoand.w";
                5'h10: mnemonic = "amomin.w";
                5'h14: mnemonic = "amomax.w";
                5'h18: mnemonic = "amominu.w";
                5'h1C: mnemonic = "amomax.w";
                default: return printMnemonic("INVALID");
            endcase
        // doubles
        end else if (instr[14:12] == 3'h3) begin
            case (instr[31:27])
                5'h0:  mnemonic = "amoadd.d";
                5'h1:  mnemonic = "amoswap.d";
                5'h2:  mnemonic = "lr.d";
                5'h3:  mnemonic = "sc.d";
                5'h4:  mnemonic = "amoxor.d";
                5'h8:  mnemonic = "amoor.d";
                5'hC:  mnemonic = "amoand.d";
                5'h10: mnemonic = "amomin.d";
                5'h14: mnemonic = "amomax.d";
                5'h18: mnemonic = "amominu.d";
                5'h1C: mnemonic = "amomax.d";
                default: return printMnemonic("INVALID");
            endcase
        end else return printMnemonic("INVALID");

        result_regs.push_back(sbe.rd);
        read_regs.push_back(sbe.rs2);
        read_regs.push_back(sbe.rs1);
        // save the immediate for calculating the virtual address
        this.imm = 0;

        return $sformatf("%-16s %s, %s,(%s)", mnemonic, regAddrToStr(sbe.rd), regAddrToStr(sbe.rs2), regAddrToStr(sbe.rs1));
    endfunction

    function string printMulInstr(logic is_op32);
        string s = "";
        case (this.instr[14:12])
            3'b000: s = "mul";
            3'b001: s = "mulh";
            3'b010: s = "mulhsu";
            3'b011: s = "mulhu";
            3'b100: s = "div";
            3'b101: s = "divu";
            3'b110: s = "rem";
            3'b111: s = "remu";
        endcase
        // if it is a 32 bit instruction concatenate a w on it
        if (is_op32) s = {s, "w"};
        return this.printRInstr(s);
    endfunction
  endclass
`endif
