blob: 7e0d8f2f112baeac05456f8f53d9447dc81db4c7 [file] [log] [blame]
import re
from typing import Optional, Sequence, Tuple
# Precompile some regular expressions
RE_RVV_STORE = re.compile(r"(vse|vsuxei|vsse|vsoxei)\d+")
RE_ADDR_OFF1 = re.compile(r"^\d*\((\w+)\)$")
RE_ADDR_OFF2 = re.compile(r"^(\w+)\s*[-+]\s*(\d+|0x[0-9a-fA-F]+)$")
RE_IMM = re.compile(r"^(-?\d+|0x[0-9a-fA-F]+)$")
def asm_registers(
mnemonic: str,
operands: Sequence[str]) -> Tuple[Sequence[str], Sequence[str]]:
"""Generate list of inputs and output registers from operands.
Args:
mnemonic: assembly instruction mnemonic
operands: operands
Returns:
inputs: input registers
outputs: output registers
TODO(sflur): The implementation is very incomplete. Instead of parsing the
disassembled instruction, which is too much work, the functional simulator
should provide the set of registers it read from (input) and wrote to
(output), for every instruction. Spike only provides the register writes!
Alterantively, use an external library to parse the instruction (machine
code or disassembled).
"""
if (mnemonic in ["sb", "sh", "sw", "sbu", "shu", "fsw", "fsd"] or
RE_RVV_STORE.match(mnemonic)):
# store
input_ops = operands
output_ops = []
elif (mnemonic in ["j", "jr", "c.j"] or mnemonic.startswith("b")):
# jump/branch
input_ops = operands
output_ops = []
elif (mnemonic in ["jal", "jalr"] and len(operands) == 1):
# pseudo-instructions
input_ops = operands
output_ops = ["x1"]
else:
# default behaviour: first operand is destination, remainder are outputs
input_ops = operands[1:]
output_ops = operands[:1]
inputs = [r for r in [input_reg(o) for o in input_ops] if r]
outputs = [o for o in output_ops if not o[0].isdigit()]
# Add implicit inputs and outputs of instructions
if mnemonic.startswith("vset"):
outputs.extend(["vtype", "vl"])
elif mnemonic.startswith("v"):
inputs.extend(["vtype", "vl", "vstart"])
return (normalize(inputs), normalize(outputs))
def input_reg(operand: str) -> Optional[str]:
"""Extract a register from an input operand.
Discards immediate operands, branch address, etc.
Args:
operand: input operand
Returns:
register or None
"""
# address offset
m = RE_ADDR_OFF1.match(operand)
if m:
return m.group(1)
# address offset
m = RE_ADDR_OFF2.match(operand)
if m:
return m.group(1)
# discard immediate operand
if RE_IMM.match(operand):
return None
# default: it's probably a register
return operand
# table of RISC-V ABI names
ABI_NAMES = {
"zero": "x0",
"ra": "x1",
"sp": "x2",
"gp": "x3",
"tp": "x4",
"t0": "x5",
"t1": "x6",
"t2": "x7",
"s0": "x8",
"s1": "x9",
"a0": "x10",
"a1": "x11",
"a2": "x12",
"a3": "x13",
"a4": "x14",
"a5": "x15",
"a6": "x16",
"a7": "x17",
"s2": "x18",
"s3": "x19",
"s4": "x20",
"s5": "x21",
"s6": "x22",
"s7": "x23",
"s8": "x24",
"s9": "x25",
"s10": "x26",
"s11": "x27",
"t3": "x28",
"t4": "x29",
"t5": "x30",
"t6": "x31",
# This is the RVV mask register (not exactly abi).
"v0.t": "v0",
}
# list of non-register names
BOGUS_REGISTERS = {
"x0",
"e8",
"e16",
"e32",
"e64",
"e128",
"m1",
"m2",
"m4",
"m8",
"m16",
"ta",
"tu",
"ma",
"mu",
}
def normalize(rs: Sequence[str]) -> Sequence[str]:
"""Replace ABI register names with their architectural names (removing x0).
Also, removes duplicates.
Args:
rs: list of registers.
Returns:
list of registers
"""
return list({ABI_NAMES.get(r, r) for r in rs} - BOGUS_REGISTERS)
NOPS = {
"nop",
"c.nop",
"fence",
"fence.i",
"sfence.vma",
"wfi",
}
def is_nop(mnemonic):
"""Test whether an instruction mnemonic is a NOP in TBM sense.
A NOP instruction is any instruction that TBM will retire without placing
in a dispatch queue.
"""
return mnemonic in NOPS
# List of all known branch instructions
BRANCHES = {
"beq",
"bne",
"blt",
"bge",
"bltu",
"bgeu",
"jal",
"jalr",
"bnez",
"beqz",
"blez",
"bgez",
"bltz",
"bgtz",
"bleu",
"bgtu",
"j",
"c.j",
"jr",
"ret",
"sret",
"mret",
"ecall",
"ebreak",
}
def is_branch(mnemonic: str) -> bool:
"""Test whether an instruction mnemonic is a branch."""
return mnemonic in BRANCHES
FLUSHES = {
"csrr",
"csrw",
"csrs",
"csrwi",
"csrrw",
"csrrs",
"csrrc",
"csrrwi",
"csrrsi",
"csrrci",
"fence",
"fence.i",
"sfence.vma",
}
def is_flush(mnemonic: str) -> bool:
"""Test whether an instruction mnemonic is a flush in TBM sense.
A flush instruction is any instruction that should be placed in a dispatch
queue (or retired, see is_nop) only when the pipeline is empty.
"""
return mnemonic in FLUSHES
VCTRL = {
"vsetivli",
"vsetvli",
"vsetvl",
}
def is_vctrl(mnemonic: str) -> bool:
"""Test whether an instruction mnemonic is a vctrl."""
return mnemonic in VCTRL
# List of control/status registers (incomplete)
CSRS = {
"cycle",
"cycleh",
"dcsr",
"dpc",
"dscratch0",
"dscratch1",
"fcsr",
"fflags",
"frm",
"hcounteren",
"hedeleg",
"hgatp",
"hgeie",
"hgeip",
"hideleg",
"hie",
"hip",
"hstatus",
"htimedelta",
"htimedeltah",
"htinst",
"htval",
"hvip",
"instret",
"instreth",
"marchid",
"mcause",
"mcontext",
"mcounteren",
"mcountinhibit",
"mcycle",
"medeleg",
"mepc",
"mhartid",
"mideleg",
"mie",
"mimpid",
"minstret",
"mintstatus",
"mip",
"misa",
"mnxti",
"mscratch",
"mscratchcsw",
"mscratchcswl",
"mstatus",
"mtinst",
"mtval",
"mtval2",
"mtvec",
"mtvt",
"mvendorid",
"pmpaddr0",
"pmpaddr1",
"pmpaddr10",
"pmpaddr11",
"pmpaddr12",
"pmpaddr13",
"pmpaddr14",
"pmpaddr15",
"pmpaddr2",
"pmpaddr3",
"pmpaddr4",
"pmpaddr5",
"pmpaddr6",
"pmpaddr7",
"pmpaddr8",
"pmpaddr9",
"pmpcfg0",
"pmpcfg1",
"pmpcfg2",
"pmpcfg3",
"satp",
"scause",
"scontext",
"scounteren",
"sedeleg",
"sentropy",
"sepc",
"sideleg",
"sie",
"sintstatus",
"sip",
"snxti",
"sscratch",
"sscratchcsw",
"sscratchcswl",
"sstatus",
"stval",
"stvec",
"stvt",
"tcontrol",
"tdata1",
"tdata2",
"tdata3",
"time",
"timeh",
"tinfo",
"tselect",
"ucause",
"uepc",
"uie",
"uintstatus",
"uip",
"unxti",
"uscratch",
"uscratchcsw",
"uscratchcswl",
"ustatus",
"utval",
"utvec",
"utvt",
"vcsr",
"vl",
"vlenb",
"vsatp",
"vscause",
"vsepc",
"vsie",
"vsip",
"vsscratch",
"vsstatus",
"vstart",
"vstval",
"vstvec",
"vtype",
"vxrm",
"vxsat",
}