272 lines
6.4 KiB
Go
272 lines
6.4 KiB
Go
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
package asm
|
|
|
|
// RISC-V register encoding: maps register names to their 5-bit numbers.
|
|
// The Go assembler uses the standard RISC-V ABI naming.
|
|
|
|
// riscvRegNum returns the 5-bit register number for a RISC-V register name.
|
|
// Returns -1 if the register is not recognized.
|
|
func riscvRegNum(name string) int {
|
|
switch name {
|
|
// Numbered integer registers.
|
|
case "X0", "ZERO":
|
|
return 0
|
|
case "X1", "RA":
|
|
return 1
|
|
case "X2", "SP":
|
|
return 2
|
|
case "X3", "GP":
|
|
return 3
|
|
case "X4", "TP":
|
|
return 4
|
|
case "X5", "T0", "LR":
|
|
return 5
|
|
case "X6", "T1", "TMP":
|
|
return 6
|
|
case "X7", "T2":
|
|
return 7
|
|
case "X8", "S0", "FP":
|
|
return 8
|
|
case "X9", "S1":
|
|
return 9
|
|
case "X10", "A0":
|
|
return 10
|
|
case "X11", "A1":
|
|
return 11
|
|
case "X12", "A2":
|
|
return 12
|
|
case "X13", "A3":
|
|
return 13
|
|
case "X14", "A4":
|
|
return 14
|
|
case "X15", "A5":
|
|
return 15
|
|
case "X16", "A6":
|
|
return 16
|
|
case "X17", "A7":
|
|
return 17
|
|
case "X18", "S2":
|
|
return 18
|
|
case "X19", "S3":
|
|
return 19
|
|
case "X20", "S4":
|
|
return 20
|
|
case "X21", "S5":
|
|
return 21
|
|
case "X22", "S6":
|
|
return 22
|
|
case "X23", "S7":
|
|
return 23
|
|
case "X24", "S8":
|
|
return 24
|
|
case "X25", "S9":
|
|
return 25
|
|
case "X26", "S10":
|
|
return 26
|
|
case "X27", "S11":
|
|
return 27
|
|
case "X28", "T3":
|
|
return 28
|
|
case "X29", "T4":
|
|
return 29
|
|
case "X30", "T5":
|
|
return 30
|
|
case "X31", "T6":
|
|
return 31
|
|
// Floating-point registers (F0-F31).
|
|
case "F0", "FT0":
|
|
return 0
|
|
case "F1", "FT1":
|
|
return 1
|
|
case "F2", "FT2":
|
|
return 2
|
|
case "F3", "FT3":
|
|
return 3
|
|
case "F4", "FT4":
|
|
return 4
|
|
case "F5", "FT5":
|
|
return 5
|
|
case "F6", "FT6":
|
|
return 6
|
|
case "F7", "FT7":
|
|
return 7
|
|
case "F8", "FS0":
|
|
return 8
|
|
case "F9", "FS1":
|
|
return 9
|
|
case "F10", "FA0":
|
|
return 10
|
|
case "F11", "FA1":
|
|
return 11
|
|
case "F12", "FA2":
|
|
return 12
|
|
case "F13", "FA3":
|
|
return 13
|
|
case "F14", "FA4":
|
|
return 14
|
|
case "F15", "FA5":
|
|
return 15
|
|
case "F16", "FA6":
|
|
return 16
|
|
case "F17", "FA7":
|
|
return 17
|
|
case "F18", "FS2":
|
|
return 18
|
|
case "F19", "FS3":
|
|
return 19
|
|
case "F20", "FS4":
|
|
return 20
|
|
case "F21", "FS5":
|
|
return 21
|
|
case "F22", "FS6":
|
|
return 22
|
|
case "F23", "FS7":
|
|
return 23
|
|
case "F24", "FS8":
|
|
return 24
|
|
case "F25", "FS9":
|
|
return 25
|
|
case "F26", "FS10":
|
|
return 26
|
|
case "F27", "FS11":
|
|
return 27
|
|
case "F28", "FT8":
|
|
return 28
|
|
case "F29", "FT9":
|
|
return 29
|
|
case "F30", "FT10":
|
|
return 30
|
|
case "F31", "FT11":
|
|
return 31
|
|
default:
|
|
return -1
|
|
}
|
|
}
|
|
|
|
// RISC-V instruction encoding parameters.
|
|
type riscvEnc struct {
|
|
opcode uint32 // bits [6:0]
|
|
funct3 uint32 // bits [14:12]
|
|
funct7 uint32 // bits [31:25]
|
|
}
|
|
|
|
// riscvInstrTable maps RISC-V mnemonics to their encoding.
|
|
var riscvInstrTable = map[string]riscvEnc{
|
|
// RV64I — R-type arithmetic/logic.
|
|
"ADD": {0x33, 0x0, 0x00},
|
|
"SUB": {0x33, 0x0, 0x20},
|
|
"SLL": {0x33, 0x1, 0x00},
|
|
"SLT": {0x33, 0x2, 0x00},
|
|
"SLTU": {0x33, 0x3, 0x00},
|
|
"XOR": {0x33, 0x4, 0x00},
|
|
"SRL": {0x33, 0x5, 0x00},
|
|
"SRA": {0x33, 0x5, 0x20},
|
|
"OR": {0x33, 0x6, 0x00},
|
|
"AND": {0x33, 0x7, 0x00},
|
|
// RV64I — 32-bit variants (W suffix).
|
|
"ADDW": {0x3B, 0x0, 0x00},
|
|
"SUBW": {0x3B, 0x0, 0x20},
|
|
"SLLW": {0x3B, 0x1, 0x00},
|
|
"SRLW": {0x3B, 0x5, 0x00},
|
|
"SRAW": {0x3B, 0x5, 0x20},
|
|
// RV64M — multiply/divide.
|
|
"MUL": {0x33, 0x0, 0x01},
|
|
"MULH": {0x33, 0x1, 0x01},
|
|
"MULHSU": {0x33, 0x2, 0x01},
|
|
"MULHU": {0x33, 0x3, 0x01},
|
|
"DIV": {0x33, 0x4, 0x01},
|
|
"DIVU": {0x33, 0x5, 0x01},
|
|
"REM": {0x33, 0x6, 0x01},
|
|
"REMU": {0x33, 0x7, 0x01},
|
|
// RV64M — 32-bit variants.
|
|
"MULW": {0x3B, 0x0, 0x01},
|
|
"DIVW": {0x3B, 0x4, 0x01},
|
|
"DIVUW": {0x3B, 0x5, 0x01},
|
|
"REMW": {0x3B, 0x6, 0x01},
|
|
"REMUW": {0x3B, 0x7, 0x01},
|
|
// RV64I — I-type arithmetic.
|
|
"ADDI": {0x13, 0x0, 0x00},
|
|
"ADDIW": {0x1B, 0x0, 0x00},
|
|
"SLTI": {0x13, 0x2, 0x00},
|
|
"SLTIU": {0x13, 0x3, 0x00},
|
|
"XORI": {0x13, 0x4, 0x00},
|
|
"ORI": {0x13, 0x6, 0x00},
|
|
"ANDI": {0x13, 0x7, 0x00},
|
|
// Loads (I-type).
|
|
"LB": {0x03, 0x0, 0x00},
|
|
"LH": {0x03, 0x1, 0x00},
|
|
"LW": {0x03, 0x2, 0x00},
|
|
"LD": {0x03, 0x3, 0x00},
|
|
"LBU": {0x03, 0x4, 0x00},
|
|
"LHU": {0x03, 0x5, 0x00},
|
|
"LWU": {0x03, 0x6, 0x00},
|
|
// Stores (S-type).
|
|
"SB": {0x23, 0x0, 0x00},
|
|
"SH": {0x23, 0x1, 0x00},
|
|
"SW": {0x23, 0x2, 0x00},
|
|
"SD": {0x23, 0x3, 0x00},
|
|
// Branches (B-type).
|
|
"BEQ": {0x63, 0x0, 0x00},
|
|
"BNE": {0x63, 0x1, 0x00},
|
|
"BLT": {0x63, 0x4, 0x00},
|
|
"BGE": {0x63, 0x5, 0x00},
|
|
"BLTU": {0x63, 0x6, 0x00},
|
|
"BGEU": {0x63, 0x7, 0x00},
|
|
// U-type.
|
|
"LUI": {0x37, 0x0, 0x00},
|
|
"AUIPC": {0x17, 0x0, 0x00},
|
|
// System.
|
|
"ECALL": {0x73, 0x0, 0x00},
|
|
"EBREAK": {0x73, 0x0, 0x00},
|
|
"FENCE": {0x0F, 0x0, 0x00},
|
|
}
|
|
|
|
// riscvRType encodes an R-type instruction: funct7 | rs2 | rs1 | funct3 | rd | opcode.
|
|
func riscvRType(enc riscvEnc, rd, rs1, rs2 int) uint32 {
|
|
return (enc.funct7 << 25) | (uint32(rs2) << 20) | (uint32(rs1) << 15) |
|
|
(enc.funct3 << 12) | (uint32(rd) << 7) | enc.opcode
|
|
}
|
|
|
|
// riscvIType encodes an I-type instruction: imm[11:0] | rs1 | funct3 | rd | opcode.
|
|
func riscvIType(enc riscvEnc, rd, rs1 int, imm int32) uint32 {
|
|
return (uint32(imm&0xFFF) << 20) | (uint32(rs1) << 15) |
|
|
(enc.funct3 << 12) | (uint32(rd) << 7) | enc.opcode
|
|
}
|
|
|
|
// riscvSType encodes an S-type instruction: imm[11:5] | rs2 | rs1 | funct3 | imm[4:0] | opcode.
|
|
func riscvSType(enc riscvEnc, rs1, rs2 int, imm int32) uint32 {
|
|
immU := uint32(imm) & 0xFFF
|
|
return ((immU >> 5) << 25) | (uint32(rs2) << 20) | (uint32(rs1) << 15) |
|
|
(enc.funct3 << 12) | ((immU & 0x1F) << 7) | enc.opcode
|
|
}
|
|
|
|
// riscvBType encodes a B-type instruction (branches).
|
|
func riscvBType(enc riscvEnc, rs1, rs2 int, offset int32) uint32 {
|
|
imm := uint32(offset) & 0x1FFE // bits [12:1], bit 0 is always 0
|
|
return (((imm >> 12) & 1) << 31) | // imm[12]
|
|
(((imm >> 5) & 0x3F) << 25) | // imm[10:5]
|
|
(uint32(rs2) << 20) | (uint32(rs1) << 15) |
|
|
(enc.funct3 << 12) |
|
|
(((imm >> 1) & 0xF) << 8) | // imm[4:1]
|
|
(((imm >> 11) & 1) << 7) | // imm[11]
|
|
enc.opcode
|
|
}
|
|
|
|
// riscvUType encodes a U-type instruction: imm[31:12] | rd | opcode.
|
|
func riscvUType(enc riscvEnc, rd int, imm int32) uint32 {
|
|
return (uint32(imm) & 0xFFFFF000) | (uint32(rd) << 7) | enc.opcode
|
|
}
|
|
|
|
// riscvJType encodes a J-type instruction (JAL).
|
|
func riscvJType(rd int, offset int32) uint32 {
|
|
imm := uint32(offset) & 0x1FFFFE // bits [20:1]
|
|
return (((imm >> 20) & 1) << 31) | // imm[20]
|
|
(((imm >> 1) & 0x3FF) << 21) | // imm[10:1]
|
|
(((imm >> 11) & 1) << 20) | // imm[11]
|
|
(((imm >> 12) & 0xFF) << 12) | // imm[19:12]
|
|
(uint32(rd) << 7) |
|
|
0x6F // JAL opcode
|
|
}
|