/*
 * A simple disassembler for RiscV written in VERILOG.
 * See table page 104 of RiscV instruction manual.
 * Bruno Levy, August 2022
 * usage:
 * module XXX( ... );
 *   ...
 *   ...
 *   `include "riscv_disassembly.v" // yes, *inside* module definition.
 *   always @(posedge clk) begin
 *      riscv_disasm(instr, PC);
 *      $write("\n");
 *   end
 * endmodule
 */


/* Functions to decode immediates */

function signed [31:0] riscv_disasm_Iimm;
  input [31:0] instr;
  riscv_disasm_Iimm = {{21{instr[31]}}, instr[30:20]};
endfunction

function signed [31:0] riscv_disasm_Simm;
  input [31:0] instr;
  riscv_disasm_Simm = {{21{instr[31]}}, instr[30:25],instr[11:7]};
endfunction

function [19:0] riscv_disasm_Uimm_raw;
  input [31:0] instr;
  riscv_disasm_Uimm_raw = {instr[31:12]};
endfunction

function [31:0] riscv_disasm_Uimm;
  input [31:0] instr;
  riscv_disasm_Uimm = {instr[31],instr[30:12],{12{1'b0}}};
endfunction

function [31:0] riscv_disasm_Bimm;
  input [31:0] instr;
  riscv_disasm_Bimm = {
	  {20{instr[31]}},instr[7],instr[30:25],instr[11:8],1'b0
  };
endfunction

function [31:0] riscv_disasm_Jimm;
  input [31:0] instr;
  riscv_disasm_Jimm = {
          {12{instr[31]}},instr[19:12],instr[20],instr[30:21],1'b0
  };
endfunction


/* 
 * disassembler (see comment at the beginning of this file)
 */ 
task riscv_disasm;
   input [31:0] instr;
   input [31:0] PC;   
   begin
      case(instr[6:0])
	7'b0110011: begin
	   if(instr[31:7] == 0) begin
	     $write("nop");
	   end else begin
	      if(instr[25]) begin  // RV32M instructions
		 case(instr[14:12])
		   3'b000: $write("mul");
		   3'b001: $write("mulh");
		   3'b010: $write("mulhsu");
		   3'b011: $write("mulhu");
		   3'b100: $write("div");
		   3'b101: $write("divu");
		   3'b110: $write("rem");
		   3'b111: $write("remu");
		 endcase 
	      end else begin
		 case(instr[14:12])
		   3'b000: $write("%s", instr[30] ? "sub" : "add");
		   3'b001: $write("sll");
		   3'b010: $write("slt");
		   3'b011: $write("sltu");
		   3'b100: $write("xor");
		   3'b101: $write("%s", instr[30] ? "sra" : "srl");
		   3'b110: $write("or");
		   3'b111: $write("and");
		 endcase // case (instr[14:12])
	      end
	      $write(" x%0d,x%0d,x%0d",instr[11:7],instr[19:15],instr[24:20]);
	   end
	end
	7'b0010011: begin
	   case(instr[14:12])
	     3'b000: $write("addi");
	     3'b010: $write("slti");
	     3'b011: $write("sltiu");
	     3'b100: $write("xori");
	     3'b110: $write("ori");
	     3'b111: $write("andi");
	     3'b001: $write("slli");
	     3'b101: $write("%s", instr[30] ? "srai" : "srli");
	   endcase
	   if(instr[14:12] == 3'b001 || instr[14:12] == 3'b101) begin
	      $write(" x%0d,x%0d,%0d",
		     instr[11:7],instr[19:15],instr[24:20]
		     );
	   end else begin
	      $write(" x%0d,x%0d,%0d",
		     instr[11:7],instr[19:15],riscv_disasm_Iimm(instr)
		     );
	   end
	end
	7'b1100011: begin
	   case(instr[14:12])
	     3'b000: $write("beq");
	     3'b001: $write("bne");
	     3'b100: $write("blt");
	     3'b101: $write("bge");
	     3'b110: $write("bltu");
	     3'b111: $write("bgeu");
	     default: $write("B???");
	   endcase 
  	   $write(" x%0d,x%0d,0x%0h",
		  instr[19:15],instr[24:20],PC+riscv_disasm_Bimm(instr)
	   );
	end
	7'b1100111:
	  $write("jalr x%0d,x%0d,%0d",
		 instr[11:7],instr[19:15],riscv_disasm_Iimm(instr)
	  );
	7'b1101111:
	  $write("jal x%0d,0x%0h",instr[11:7],PC+riscv_disasm_Jimm(instr));
	7'b0010111:
	  $write("auipc x%0d,0x%0h <0x%0h>",
		 instr[11:7],
		 riscv_disasm_Uimm_raw(instr),PC+riscv_disasm_Uimm(instr)
	  );	  	  	  	  
	7'b0110111:
	  $write("lui x%0d,0x%0h <0x%0h>",
		 instr[11:7],
		 riscv_disasm_Uimm_raw(instr),riscv_disasm_Uimm(instr)
          );	  
	7'b0000011: begin
	   case(instr[14:12])
	     3'b000: $write("lb");
	     3'b001: $write("lh");
	     3'b010: $write("lw");
	     3'b100: $write("lbu");
	     3'b101: $write("lhu");
	     default: $write("l??");
	   endcase 
	   $write(" x%0d,%0d(x%0d)",
		  instr[11:7],riscv_disasm_Iimm(instr),instr[19:15]
           );
	end
	7'b0100011: begin
	   case(instr[14:12])
	     3'b000: $write("sb");
	     3'b001: $write("sh");
	     3'b010: $write("sw");
	     default: $write("s??");
	   endcase 
	   $write(" x%0d,%0d(x%0d)",
		  instr[24:20],riscv_disasm_Simm(instr),instr[19:15]
	   );
	end
	7'b1110011: begin
	   case(instr[14:12])
	     3'b000: $write("ebreak");
	     3'b010: begin
		case({instr[27],instr[21]})
		  2'b00: $write("rdcycle x%0d",   instr[11:7]);
		  2'b10: $write("rdcycleh x%0d",  instr[11:7]);
		  2'b01: $write("rdinstret x%0d", instr[11:7]);
		  2'b11: $write("rdinstreth x%0d",instr[11:7]);
		endcase
	     end
	     default: $write("SYSTEM");
	   endcase
	end
	default:
	  $write("?????");
      endcase
   end
endtask


/* Instruction recognizers for the 10 RV32I instructions */
function riscv_disasm_isALUreg; input [31:0] I; riscv_disasm_isALUreg=(I[6:0]==7'b0110011); endfunction
function riscv_disasm_isALUimm; input [31:0] I; riscv_disasm_isALUimm=(I[6:0]==7'b0010011); endfunction
function riscv_disasm_isBranch; input [31:0] I; riscv_disasm_isBranch=(I[6:0]==7'b1100011); endfunction
function riscv_disasm_isJALR;   input [31:0] I; riscv_disasm_isJALR  =(I[6:0]==7'b1100111); endfunction
function riscv_disasm_isJAL;    input [31:0] I; riscv_disasm_isJAL   =(I[6:0]==7'b1101111); endfunction
function riscv_disasm_isAUIPC;  input [31:0] I; riscv_disasm_isAUIPC =(I[6:0]==7'b0010111); endfunction
function riscv_disasm_isLUI;    input [31:0] I; riscv_disasm_isLUI   =(I[6:0]==7'b0110111); endfunction
function riscv_disasm_isLoad;   input [31:0] I; riscv_disasm_isLoad  =(I[6:0]==7'b0000011); endfunction
function riscv_disasm_isStore;  input [31:0] I; riscv_disasm_isStore =(I[6:0]==7'b0100011); endfunction
function riscv_disasm_isSYSTEM; input [31:0] I; riscv_disasm_isSYSTEM=(I[6:0]==7'b1110011); endfunction
function riscv_disasm_isRV32M;  input [31:0] I; riscv_disasm_isRV32M=riscv_disasm_isALUreg(I) && I[25]; endfunction

/* Utility functions: register indices */
function [4:0] riscv_disasm_rs1Id; input [31:0] I; riscv_disasm_rs1Id = I[19:15];      endfunction
function [4:0] riscv_disasm_rs2Id; input [31:0] I; riscv_disasm_rs2Id = I[24:20];      endfunction
function [4:0] riscv_disasm_shamt; input [31:0] I; riscv_disasm_shamt = I[24:20];      endfunction   
function [4:0] riscv_disasm_rdId;  input [31:0] I; riscv_disasm_rdId  = I[11:7];       endfunction
function [1:0] riscv_disasm_csrId; input [31:0] I; riscv_disasm_csrId = {I[27],I[21]}; endfunction

/* Utility functions: funct3 and funct7 */
function [2:0] riscv_disasm_funct3; input [31:0] I; riscv_disasm_funct3 = I[14:12]; endfunction
function [6:0] riscv_disasm_funct7; input [31:0] I; riscv_disasm_funct7 = I[31:25]; endfunction      

function riscv_disasm_readsRs1;
   input [31:0] I;
   riscv_disasm_readsRs1 = !(riscv_disasm_isJAL(I) || riscv_disasm_isAUIPC(I) || riscv_disasm_isLUI(I));
endfunction

function riscv_disasm_readsRs2;
   input [31:0] I;
   riscv_disasm_readsRs2 = riscv_disasm_isALUreg(I) || riscv_disasm_isBranch(I) || riscv_disasm_isStore(I);
endfunction

   
