// SPDX-License-Identifier: Apache-2.0
// Copyright 2019 Western Digital Corporation or its affiliates.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

// Run time disassembler functions
// supports  RISCV 32 bit extentions I, C, M, A, B
`ifndef RV_NUM_THREADS
`define RV_NUM_THREADS 1
`endif

bit[31:0] [31:0] gpr[`RV_NUM_THREADS];

// main DASM function
function string dasm(input[31:0] opcode, input[31:0] pc, input[4:0] regn, input[31:0] regv, input tid=0);
    dasm = (opcode[1:0] == 2'b11) ? dasm32(opcode, pc, tid) : dasm16(opcode, pc, tid);
    if(regn) gpr[tid][regn] = regv;
endfunction


///////////////// 16 bits instructions ///////////////////////

function string dasm16( input[31:0] opcode, input[31:0] pc, input tid=0);
    case(opcode[1:0])
    0: return dasm16_0(opcode, tid);
    1: return dasm16_1(opcode, pc);
    2: return dasm16_2(opcode);
    endcase
    return $sformatf(".short 0x%h", opcode[15:0]);
endfunction

function string dasm16_0( input[31:0] opcode, tid);
    case(opcode[15:13])
    3'b000: return dasm16_ciw(opcode);
    3'b001: return {"c.fld  ", dasm16_cl(opcode, tid)};
    3'b010: return {"c.lw   ", dasm16_cl(opcode, tid)};
    3'b011: return {"c.flw  ", dasm16_cl(opcode, tid)};
    3'b101: return {"c.fsd  ", dasm16_cl(opcode, tid)};
    3'b110: return {"c.sw   ", dasm16_cl(opcode, tid)};
    3'b111: return {"c.fsw  ", dasm16_cl(opcode, tid)};
    endcase
    return $sformatf(".short  0x%h", opcode[15:0]);
endfunction

function string dasm16_ciw( input[31:0] opcode);
int imm;
    imm=0;
    if(opcode[15:0] == 0) return ".short  0";
    {imm[5:4],imm[9:6],imm[2],imm[3]} = opcode[12:5];
    return $sformatf("c.addi4spn %s,0x%0h", abi_reg[opcode[4:2]+8], imm);
endfunction

function string dasm16_cl( input[31:0] opcode, input tid=0);
int imm;
    imm=0;
    imm[5:3] = opcode[12:10];
    imm[7:6] = opcode[6:5];

    return $sformatf(" %s,%0d(%s) [%h]", abi_reg[opcode[4:2]+8], imm, abi_reg[opcode[9:7]+8], gpr[tid][opcode[9:7]+8]+imm);
endfunction

function string dasm16_1( input[31:0] opcode, input[31:0] pc);
    case(opcode[15:13])
    3'b000: return opcode[11:7]==0 ? "c.nop" : {"c.addi  ",dasm16_ci(opcode)};
    3'b001: return {"c.jal   ", dasm16_cj(opcode, pc)};
    3'b010: return {"c.li    ", dasm16_ci(opcode)};
    3'b011: return dasm16_1_3(opcode);
    3'b100: return dasm16_cr(opcode);
    3'b101: return {"c.j     ", dasm16_cj(opcode, pc)};
    3'b110: return {"c.beqz  ", dasm16_cb(opcode, pc)};
    3'b111: return {"c.bnez  ", dasm16_cb(opcode, pc)};
    endcase
endfunction

function string dasm16_ci( input[31:0] opcode);
int imm;
    imm=0;
    imm[4:0] = opcode[6:2];
    if(opcode[12]) imm [31:5] = '1;
    return $sformatf("%s,%0d", abi_reg[opcode[11:7]], imm);
endfunction

function string dasm16_cj( input[31:0] opcode, input[31:0] pc);
bit[31:0] imm;
    imm=0;
    {imm[11],imm[4],imm[9:8],imm[10],imm[6], imm[7],imm[3:1], imm[5]} = opcode[12:2];
    if(opcode[12]) imm [31:12] = '1;
    return $sformatf("0x%0h", imm+pc);
endfunction

function string dasm16_cb( input[31:0] opcode, input[31:0] pc);
bit[31:0] imm;
    imm=0;
    {imm[8],imm[4:3]} = opcode[12:10];
    {imm[7:6],imm[2:1], imm[5]} = opcode[6:2];
    if(opcode[12]) imm [31:9] = '1;
    return $sformatf("%s,0x%0h",abi_reg[opcode[9:7]+8], imm+pc);
endfunction

function string dasm16_cr( input[31:0] opcode);
bit[31:0] imm;

    imm = 0;
    imm[4:0] = opcode[6:2];
    if(opcode[5]) imm [31:5] = '1;
    case(opcode[11:10])
    0: return $sformatf("c.srli  %s,%0d",  abi_reg[opcode[9:7]+8], imm[5:0]);
    1: return $sformatf("c.srai  %s,%0d",  abi_reg[opcode[9:7]+8], imm[5:0]);
    2: return $sformatf("c.andi  %s,0x%0h", abi_reg[opcode[9:7]+8], imm);
    endcase

    case(opcode[6:5])
    0: return $sformatf("c.sub   %s,%s", abi_reg[opcode[9:7]+8], abi_reg[opcode[4:2]+8]);
    1: return $sformatf("c.xor   %s,%s", abi_reg[opcode[9:7]+8], abi_reg[opcode[4:2]+8]);
    2: return $sformatf("c.or    %s,%s", abi_reg[opcode[9:7]+8], abi_reg[opcode[4:2]+8]);
    3: return $sformatf("c.and   %s,%s", abi_reg[opcode[9:7]+8], abi_reg[opcode[4:2]+8]);
    endcase
endfunction

function string dasm16_1_3( input[31:0] opcode);
int imm;

    imm=0;
    if(opcode[11:7] == 2) begin
        {imm[4], imm[6],imm[8:7], imm[5]} = opcode[6:2];
        if(opcode[12]) imm [31:9] = '1;
        return $sformatf("c.addi16sp %0d", imm);
    end
    else begin
        imm[16:12] = opcode[6:2];
        if(opcode[12]) imm [31:17] = '1;
        return $sformatf("c.lui   %s,0x%0h", abi_reg[opcode[11:7]], imm);

    end
endfunction

function string dasm16_2( input[31:0] opcode, input tid=0);
    case(opcode[15:13])
    3'b000: return {"c.slli  ", dasm16_ci(opcode)};
    3'b001: return {"c.fldsp ", dasm16_cls(opcode,1,tid)};
    3'b010: return {"c.lwsp  ", dasm16_cls(opcode,0,tid)};
    3'b011: return {"c.flwsp ", dasm16_cls(opcode,0,tid)};
    3'b101: return {"c.fsdsp ", dasm16_css(opcode,1,tid)};
    3'b110: return {"c.swsp  ", dasm16_css(opcode,0,tid)};
    3'b111: return {"c.fswsp ", dasm16_css(opcode,0,tid)};
    endcase
    if(opcode[12]) begin
        if(opcode[12:2] == 0) return "c.ebreak";
        else if(opcode[6:2] == 0) return $sformatf("c.jalr  %s", abi_reg[opcode[11:7]]);
        else return $sformatf("c.add   %s,%s", abi_reg[opcode[11:7]], abi_reg[opcode[6:2]]);
    end
    else begin
        if(opcode[6:2] == 0) return $sformatf("c.jr    %s", abi_reg[opcode[11:7]]);
        else return $sformatf("c.mv    %s,%s", abi_reg[opcode[11:7]], abi_reg[opcode[6:2]]);
    end
endfunction


function string dasm16_cls( input[31:0] opcode, input sh1=0, tid=0);
bit[31:0] imm;
    imm=0;
    if(sh1) {imm[4:3],imm[8:6]} = opcode[6:2];
    else    {imm[4:2],imm[7:6]} = opcode[6:2];
    imm[5] = opcode[12];
    return $sformatf("%s,0x%0h [%h]", abi_reg[opcode[11:7]], imm, gpr[tid][2]+imm);
endfunction

function string dasm16_css( input[31:0] opcode, input sh1=0, tid=0);
bit[31:0] imm;
    imm=0;
    if(sh1) {imm[5:3],imm[8:6]} = opcode[12:7];
    else {imm[5:2],imm[7:6]} = opcode[12:7];
    return $sformatf("%s,0x%0h [%h]", abi_reg[opcode[6:2]], imm, gpr[tid][2]+imm);
endfunction

///////////////// 32 bit instructions ///////////////////////

function string dasm32( input[31:0] opcode, input[31:0] pc, input tid=0);
    case(opcode[6:0])
    7'b0110111: return {"lui     ", dasm32_u(opcode)};
    7'b0010111: return {"auipc   ", dasm32_u(opcode)};
    7'b1101111: return {"jal     ", dasm32_j(opcode,pc)};
    7'b1100111: return {"jalr    ", dasm32_jr(opcode,pc)};
    7'b1100011: return dasm32_b(opcode,pc);
    7'b0000011: return dasm32_l(opcode,tid);
    7'b0100011: return dasm32_s(opcode,tid);
    7'b0010011: return dasm32_ai(opcode);
    7'b0110011: return dasm32_ar(opcode);
    7'b0001111: return {"fence", dasm32_fence(opcode)};
    7'b1110011: return dasm32_e(opcode);
    7'b0101111: return dasm32_a(opcode,tid);

    endcase
    return $sformatf(".long   0x%h", opcode);
endfunction



function string dasm32_u( input[31:0] opcode);
bit[31:0] imm;
    imm=0;
    imm[31:12] = opcode[31:12];
    return $sformatf("%s,0x%0h", abi_reg[opcode[11:7]], imm);
endfunction

function string dasm32_j( input[31:0] opcode, input[31:0] pc);
int imm;
    imm=0;
    {imm[20], imm[10:1], imm[11], imm[19:12]} = opcode[31:12];
    if(opcode[31]) imm[31:20] = '1;
    return $sformatf("%s,0x%0h",abi_reg[opcode[11:7]], imm+pc);
endfunction

function string dasm32_jr( input[31:0] opcode, input[31:0] pc);
int imm;
    imm=0;
    imm[11:1] = opcode[31:19];
    if(opcode[31]) imm[31:12] = '1;
    return $sformatf("%s,%s,0x%0h",abi_reg[opcode[11:7]], abi_reg[opcode[19:15]], imm+pc);
endfunction

function string dasm32_b( input[31:0] opcode, input[31:0] pc);
int imm;
string mn;
    imm=0;
    {imm[12],imm[10:5]} = opcode[31:25];
    {imm[4:1],imm[11]} = opcode[11:7];
    if(opcode[31]) imm[31:12] = '1;
    case(opcode[14:12])
    0: mn = "beq     ";
    1: mn = "bne     ";
    2,3 : return $sformatf(".long    0x%h", opcode);
    4: mn = "blt     ";
    5: mn = "bge     ";
    6: mn = "bltu    ";
    7: mn = "bgeu    ";
    endcase
    return $sformatf("%s%s,%s,0x%0h", mn, abi_reg[opcode[19:15]], abi_reg[opcode[24:20]], imm+pc);
endfunction

function string dasm32_l( input[31:0] opcode, input tid=0, pri=0);
int imm;
string mn;
    imm=0;
    imm[11:0] = opcode[31:20];
    if(opcode[31]) imm[31:12] = '1;
    case({pri,opcode[14:12]})
    0: mn = "lb      ";
    1: mn = "lh      ";
    2: mn = "lw      ";
    4: mn = "lbu     ";
    5: mn = "lhu     ";
    default : return $sformatf(".long   0x%h", opcode);
    endcase
    return $sformatf("%s%s,%0d(%s) [%h]", mn, abi_reg[opcode[11:7]], imm, abi_reg[opcode[19:15]], imm+gpr[tid][opcode[19:15]]);
endfunction

function string dasm32_s( input[31:0] opcode, input tid=0, pri=0);
int imm;
string mn;
    imm=0;
    imm[11:5] = opcode[31:25];
    imm[4:0] = opcode[11:7];
    if(opcode[31]) imm[31:12] = '1;
    case({pri,opcode[14:12]})
    0: mn = "sb      ";
    1: mn = "sh      ";
    2: mn = "sw      ";
    default : return $sformatf(".long   0x%h", opcode);
    endcase
    return $sformatf("%s%s,%0d(%s) [%h]", mn, abi_reg[opcode[24:20]], imm, abi_reg[opcode[19:15]], imm+gpr[tid][opcode[19:15]]);
endfunction

function string dasm32_ai( input[31:0] opcode);
int imm;
string mn;
    imm=0;
    imm[11:0] = opcode[31:20];
    if(opcode[31]) imm[31:12] = '1;
    case(opcode[14:12])
    0: mn = "addi    ";
    2: mn = "slti    ";
    3: mn = "sltiu   ";
    4: mn = "xori    ";
    6: mn = "ori     ";
    7: mn = "andi    ";
    default: return dasm32_si(opcode);
endcase

return $sformatf("%s%s,%s,%0d", mn, abi_reg[opcode[11:7]], abi_reg[opcode[19:15]], imm);
endfunction

function string dasm32_si( input[31:0] opcode);
int imm;
string mn;
    imm = opcode[26:20];
    case({opcode[31:27],opcode[14:12]})
    8'b00000_001: mn = "slli   ";
    8'b01000_101: mn = "srai   ";
    8'b00000_101: mn = "srli   ";
    8'b00100_001: mn = "sloi   ";
    8'b00100_101: mn = "sroi   ";
    8'b01100_101: mn = "rori   ";
    8'b01001_001: mn = "bclri  ";
    8'b00101_001: mn = "bseti  ";
    8'b01101_001: mn = "binvi  ";
    8'b01001_101: mn = "bexti  ";
    8'b00101_101: mn = "gorci  ";
    8'b01101_101: mn = "grevi  ";
    8'b00001_101: mn = "unshfli";
    8'b00001_001: mn = "shfli  ";
    8'b01100_001: return dasm32_crc(opcode);
    default: return $sformatf(".long   0x%h", opcode);
    endcase

    return $sformatf("%s %s,%s,%0d", mn, abi_reg[opcode[11:7]], abi_reg[opcode[19:15]], imm);
endfunction

function string dasm32_crc( input[31:0] opcode);
string mn;
    case(opcode[24:20])
    5'b00000: mn = "clz    ";
    5'b00001: mn = "ctz    ";
    5'b00010: mn = "cpop   ";
    5'b00011: mn = "bmatflip";
    5'b00100: mn = "sext.b ";
    5'b00101: mn = "sext.h ";
    5'b10000: mn = "crc32.b";
    5'b10001: mn = "crc32.h";
    5'b10010: mn = "crc32.w";
    5'b10011: mn = "crc32.d";
    5'b11000: mn = "crc32c.b";
    5'b11001: mn = "crc32c.h";
    5'b11010: mn = "crc32c.w";
    5'b11011: mn = "crc32c.d";
    default: return $sformatf(".long   0x%h", opcode);
    endcase
    return $sformatf("%s %s,%s", mn, abi_reg[opcode[11:7]], abi_reg[opcode[19:15]]);
endfunction


function string dasm32_ar( input[31:0] opcode);
string mn;
//         func7         func3
    case({opcode[31:25],opcode[14:12]})
    10'b0000001_000: mn = "mul     ";
    10'b0000001_001: mn = "mulh    ";
    10'b0000001_010: mn = "mulhsu  ";
    10'b0000001_011: mn = "mulhu   ";
    10'b0000001_100: mn = "div     ";
    10'b0000001_101: mn = "divu    ";
    10'b0000001_110: mn = "rem     ";
    10'b0000001_111: mn = "remu    ";
    10'b0100000_000: mn = "sub     ";
    10'b0000000_000: mn = "add     ";
    10'b0000000_001: mn = "sll     ";
    10'b0000000_010: mn = "slt     ";
    10'b0000000_011: mn = "sltu    ";
    10'b0000000_100: mn = "xor     ";
    10'b0100000_101: mn = "sra     ";
    10'b0000000_101: mn = "srl     ";
    10'b0000000_110: mn = "or      ";
    10'b0000000_111: mn = "and     ";
// bit manip:
    10'b0100000_111: mn = "andn    ";
    10'b0100000_110: mn = "orn     ";
    10'b0100000_100: mn = "xnor    ";
    10'b0010000_001: mn = "slo     ";
    10'b0010000_101: mn = "sro     ";
    10'b0110000_001: mn = "rol     ";
    10'b0110000_101: mn = "ror     ";
    10'b0010000_010: mn = "sh1add  ";
    10'b0010000_100: mn = "sh2add  ";
    10'b0010000_110: mn = "sh3add  ";
    10'b0100100_001: mn = "bclr    ";
    10'b0010100_001: mn = "bset    ";
    10'b0110100_001: mn = "binv    ";
    10'b0100100_101: mn = "bext    ";
    10'b0010100_101: mn = "gorc    ";
    10'b0110100_101: mn = "grev    ";
    10'b0000101_001: mn = "slmul   ";
    10'b0000101_010: mn = "slmulr  ";
    10'b0000101_011: mn = "slmulh  ";
    10'b0000101_100: mn = "min     ";
    10'b0000101_101: mn = "minu    ";
    10'b0000101_110: mn = "max     ";
    10'b0000101_111: mn = "maxu    ";
    10'b0000100_001: mn = "shfl    ";
    10'b0000100_101: mn = "unshfl  ";
    10'b0100100_110: mn = "bdecompress";
    10'b0000100_110: mn = "bcompress";
    10'b0000100_100: mn = "pack    ";
    10'b0100100_100: mn = "packu   ";
    10'b0000100_011: mn = "bmator  ";
    10'b0100100_011: mn = "bmatxor ";
    10'b0000100_111: mn = "packh   ";
    10'b0100100_111: mn = "bfp     ";
    10'b0010100_010: mn = "xperm.n ";
    10'b0010100_100: mn = "xperm.b ";
    10'b0010100_110: mn = "xperm.h ";
    10'b0010100_000: mn = "xperm.w ";
    default: return $sformatf(".long  0x%h", opcode);
    endcase
    return $sformatf("%s%s,%s,%s", mn, abi_reg[opcode[11:7]], abi_reg[opcode[19:15]], abi_reg[opcode[24:20]]);
endfunction

function string dasm32_fence( input[31:0] opcode);
    return  opcode[12] ? ".i" : "";
endfunction

function string dasm32_e(input[31:0] opcode);
    if(opcode[31:7] == 0) return "ecall";
    else if({opcode[31:21],opcode [19:7]} == 0 && opcode[20]) return "ebreak";
    else if( opcode[31:20] == 'b0011000_00010 && opcode[19:7] == 0) return "mret";
    else
        case(opcode[14:12])
        0: return "wfi";
        1: return {"csrrw   ", dasm32_csr(opcode)};
        2: return {"csrrs   ", dasm32_csr(opcode)};
        3: return {"csrrc   ", dasm32_csr(opcode)};
        5: return {"csrrwi  ", dasm32_csr(opcode, 1)};
        6: return {"csrrsi  ", dasm32_csr(opcode, 1)};
        7: return {"csrrci  ", dasm32_csr(opcode, 1)};
        endcase
    return $sformatf (".long   0x%h", opcode);

endfunction


function string dasm32_csr(input[31:0] opcode, input im=0);
bit[11:0] csr;
    csr = opcode[31:20];
    if(im) begin
        return $sformatf("%s,csr_%0h,0x%h",  abi_reg[opcode[11:7]], csr, opcode[19:15]);
    end
    else begin
        return $sformatf("%s,csr_%0h,%s",  abi_reg[opcode[11:7]], csr, abi_reg[opcode[19:15]]);
    end

endfunction

//atomics
function string dasm32_a(input[31:0] opcode, input tid=0);
    case(opcode[31:27])
    'b00010: return $sformatf("lr.w    %s,(%s) [%h]",    abi_reg[opcode[11:7]],                         abi_reg[opcode[19:15]], gpr[tid][opcode[19:15]]);
    'b00011: return $sformatf("sc.w    %s,%s,(%s) [%h]", abi_reg[opcode[11:7]], abi_reg[opcode[24:20]], abi_reg[opcode[19:15]], gpr[tid][opcode[19:15]]);
    'b00001: return {"amoswap.w", dasm32_amo(opcode, tid)};
    'b00000: return {"amoadd.w",  dasm32_amo(opcode, tid)};
    'b00100: return {"amoxor.w",  dasm32_amo(opcode, tid)};
    'b01100: return {"amoand.w",  dasm32_amo(opcode, tid)};
    'b01000: return {"amoor.w",   dasm32_amo(opcode, tid)};
    'b10000: return {"amomin.w",  dasm32_amo(opcode, tid)};
    'b10100: return {"amomax.w",  dasm32_amo(opcode, tid)};
    'b11000: return {"amominu.w", dasm32_amo(opcode, tid)};
    'b11100: return {"amomaxu.w", dasm32_amo(opcode, tid)};
    endcase
    return $sformatf(".long   0x%h", opcode);
endfunction

function string dasm32_amo( input[31:0] opcode, input tid=0);
    return $sformatf(" %s,%s,(%s) [%h]", abi_reg[opcode[11:7]], abi_reg[opcode[24:20]], abi_reg[opcode[19:15]], gpr[tid][opcode[19:15]]);
endfunction
