gigatron/rom/Contrib/hsnaves/GtForth/vcpu.py
2025-01-28 19:17:01 +03:00

781 lines
23 KiB
Python

# SYNOPSIS: from vcpu import *
#
# This is the a small vCPU emulator in Python.
# It can be very useful for debugging and testing GT1 programs
# in Python, specially the ones written using vasm.
#
# VirtualCpu to emulate the vCPU inside the Gigatron ROM.
class VirtualCpu(object):
vPC_ADDR = 0x16
vAC_ADDR = 0x18
vLR_ADDR = 0x1A
vSP_ADDR = 0x1C
def __init__(self):
"""Constructor of the VirtualCpu."""
self._memory = None
self._mem_size = None
self._read_watchpoints = None
self._write_watchpoints = None
self._lup_callback = None
self._sys_callback = None
self._enable_experimental = None
self._halt = None
# Instruction table
insn_table = [
( 0x5E, "ST", 2, self.execute_st ),
( 0x2B, "STW", 2, self.execute_stw ),
( 0xEC, "STLW", 2, self.execute_stlw ),
( 0x1A, "LD", 2, self.execute_ld ),
( 0x59, "LDI", 2, self.execute_ldi ),
( 0x11, "LDWI", 3, self.execute_ldwi ),
( 0x21, "LDW", 2, self.execute_ldw ),
( 0xEE, "LDLW", 2, self.execute_ldlw ),
( 0x99, "ADDW", 2, self.execute_addw ),
( 0xB8, "SUBW", 2, self.execute_subw ),
( 0xE3, "ADDI", 2, self.execute_addi ),
( 0xE6, "SUBI", 2, self.execute_subi ),
( 0xE9, "LSLW", 1, self.execute_lslw ),
( 0x93, "INC", 2, self.execute_inc ),
( 0x82, "ANDI", 2, self.execute_andi ),
( 0xF8, "ANDW", 2, self.execute_andw ),
( 0x88, "ORI", 2, self.execute_ori ),
( 0xFA, "ORW", 2, self.execute_orw ),
( 0x8C, "XORI", 2, self.execute_xori ),
( 0xFC, "XORW", 2, self.execute_xorw ),
( 0xAD, "PEEK", 1, self.execute_peek ),
( 0xF6, "DEEK", 1, self.execute_deek ),
( 0xF0, "POKE", 2, self.execute_poke ),
( 0xF3, "DOKE", 2, self.execute_doke ),
( 0x7F, "LUP", 2, self.execute_lup ),
( 0x90, "BRA", 2, self.execute_bra ),
( 0x35, "BCC", 3, self.execute_bcc ),
( 0xCF, "CALL", 2, self.execute_call ),
( 0xFF, "RET", 1, self.execute_ret ),
( 0x75, "PUSH", 1, self.execute_push ),
( 0x63, "POP", 1, self.execute_pop ),
( 0xDF, "ALLOC", 2, self.execute_alloc ),
( 0xB4, "SYS", 2, self.execute_sys ),
( 0xCD, "DEF", 2, self.execute_def ),
# Experimental instructions
( 0x85, "CALLI", 3, self.execute_calli ),
( 0x1F, "CMPHS", 2, self.execute_cmphs ),
( 0x97, "CMPHU", 2, self.execute_cmphu ),
]
# Construct a dispatch table for the instructions
self._insn_table = {
entry[0] : entry for entry in insn_table
}
# Branch table
branch_table = [
( 0x3F, "BEQ" ),
( 0x72, "BNE" ),
( 0x50, "BLT" ),
( 0x4D, "BGT" ),
( 0x56, "BLE" ),
( 0x53, "BGE" ),
]
self._branch_table = {
entry[0] : entry for entry in branch_table
}
def read_byte(self, addr):
"""Reads one byte on a given address.
Parameters
----------
addr: int
The address to read from.
Returns
-------
The byte at the given address.
"""
cb = self._read_watchpoints.get(addr, None)
if cb is not None:
cb(self, addr)
addr = addr % self._mem_size
return self._memory[addr]
def write_byte(self, addr, b):
"""Writes one byte to a given address.
Parameters
----------
addr: int
The address to write to.
b: int
The byte to write.
"""
cb = self._write_watchpoints.get(addr, None)
if cb is not None:
cb(self, addr)
addr = addr % self._mem_size
self._memory[addr] = (b & 0xFF)
def read_word(self, addr):
"""Reads one word (2 bytes) on a given address.
Parameters
----------
addr: int
The address to read from.
Returns
-------
The word at the given address.
"""
lo = self.read_byte(addr)
hi = self.read_byte(self.relative(addr, 1))
return (hi << 8) | lo
def write_word(self, addr, w):
"""Writes one word to a given address.
Parameters
----------
addr: int
The address to write to.
w: int
The word to write.
"""
self.write_byte(addr, w & 0xFF)
self.write_byte(self.relative(addr, 1), (w >> 8))
def absolute(self, base, page_offset):
"""Computes the address at same page as in the base address.
Parameters
----------
base: int
The base address.
page_offset: int
The offset relative to the page of the base address.
Returns
-------
The address.
"""
return (base & ~0xFF) | (page_offset & 0xFF)
def relative(self, base, offset):
"""Computes the address at given offset of a base address.
Because addresses stay on the same page (they wrap around, and
when adding, the carry to the high byte is not included), this
function is necessary.
Parameters
----------
base: int
The base address.
offset: int
The offset relative to the base address.
Returns
-------
The address.
"""
return self.absolute(base, base + offset)
def get_vPC(self):
"""Gets the value of the vPC register.
Returns
-------
The vPC register.
"""
return self.read_word(VirtualCpu.vPC_ADDR)
def set_vPC(self, w):
"""Sets the value of the vPC register.
Parameters
----------
w: int
The new value of the vPC register.
"""
self.write_word(VirtualCpu.vPC_ADDR, w)
def get_vAC(self):
"""Gets the value of the vAC register.
Returns
-------
The vAC register.
"""
return self.read_word(VirtualCpu.vAC_ADDR)
def set_vAC(self, w):
"""Sets the value of the vAC register.
Parameters
----------
w: int
The new value of the vAC register.
"""
self.write_word(VirtualCpu.vAC_ADDR, w)
def get_vLR(self):
"""Gets the value of the vLR register.
Returns
-------
The vLR register.
"""
return self.read_word(VirtualCpu.vLR_ADDR)
def set_vLR(self, w):
"""Sets the value of the vLR register.
Parameters
----------
w: int
The new value of the vLR register.
"""
self.write_word(VirtualCpu.vLR_ADDR, w)
def get_vSP(self):
"""Gets the value of the vSP register.
Returns
-------
The SP register.
"""
return self.read_byte(VirtualCpu.vSP_ADDR)
def set_vSP(self, b):
"""Sets the value of the vSP register.
Parameters
----------
b: int
The new value of the vSP register.
"""
self.write_byte(VirtualCpu.vSP_ADDR, b)
def halt(self, v=True):
"""Halts the emulation of the vCPU.
Parameters
----------
v: bool
Set to True to halt.
"""
self._halt = v
def load_gt1(self, gt1_bytes, mem_size=32768,
lup_callback=None, sys_callback=None,
enable_experimental=False):
"""Loads the GT1 file to memory and resets the vCPU.
Parameters
----------
gt1_bytes: bytes
A byte array / buffer that holds the contents of the GT1
file to load.
mem_size: int
The memory size of this VirtualCpu instance.
lup_callback: callable
The callback used to implement LUP instructions.
sys_callback: callable
The callback used to implement SYS instructions.
enable_experimental: bool
To enable experimental instructions (such as CALLI).
"""
self._mem_size = mem_size
self._memory = bytearray(mem_size)
self._read_watchpoints = dict()
self._write_watchpoints = dict()
self._lup_callback = lup_callback
self._sys_callback = sys_callback
self._enable_experimental = enable_experimental
idx = 0
hi_addr = gt1_bytes[idx]
idx += 1
while hi_addr != 0:
lo_addr = gt1_bytes[idx]
idx += 1
num_bytes = gt1_bytes[idx]
idx += 1
addr = (hi_addr << 8) | lo_addr
for j in range(num_bytes):
self.write_byte(self.relative(addr, j),
gt1_bytes[idx + j])
idx += num_bytes
hi_addr = gt1_bytes[idx]
idx += 1
hi_addr = gt1_bytes[idx]
idx += 1
lo_addr = gt1_bytes[idx]
idx += 1
vPC = (hi_addr << 8) | lo_addr
self.set_vPC(vPC)
self.set_vSP(0xFE)
def disassemble(self):
vPC = self.get_vPC()
insn = self.read_byte(vPC)
if insn in self._insn_table:
name, nb = self._insn_table[insn][1:3]
t = "%-5s" % name
if name == "BCC":
b1 = self.read_byte(self.relative(vPC, 1))
b2 = self.read_byte(self.relative(vPC, 2))
if b1 in self._branch_table:
t = self._branch_table[b1][-1]
t = "%-5s $%02X" % (t, b2)
else:
t = "<err>"
else:
if nb == 2:
b = self.read_byte(self.relative(vPC, 1))
t = "%s $%02X" % (t, b)
elif nb == 3:
w = self.read_word(self.relative(vPC, 1))
t = "%s $%04X" % (t, w)
else:
t = "<err>"
t = "%-15s" % t
vAC = self.get_vAC()
vLR = self.get_vLR()
vSP = self.get_vSP()
print("PC: $%04X, AC: $%04X, LR: $%04X, SP: $%02X %s" %\
(vPC, vAC, vLR, vSP, t))
def run(self,
max_instructions=None,
breakpoints=None,
read_watchpoints=None,
write_watchpoints=None):
"""Runs the vCPU for a specified amount.
Parameters
----------
max_instructions: int
The maximum number of instructions to run.
If not specified, it runs indefinitely.
breakpoints: dict
The dictionary of breakpoints. The keys are the addresses
of the breakpoints and the associated values are callbacks
to be called when the vCPU hits the corresponding breakpoint.
The callbacks should accept two parameters: a reference to
`VirtualCpu` itself, and the address of the breakpoint.
read_watchpoints: dict
The dictionary of memory addresses to watch for reads.
It works similarly as the `breakpoints` parameter.
write_watchpoints: dict
The dictionary of memory addresses to watch for writes.
It works similarly as the `breakpoints` parameter.
"""
if breakpoints is None:
breakpoints = dict()
else:
breakpoints = dict(breakpoints)
if read_watchpoints is None:
read_watchpoints = dict()
else:
read_watchpoints = dict(read_watchpoints)
if write_watchpoints is None:
write_watchpoints = dict()
else:
write_watchpoints = ditc(write_watchpoints)
self._read_watchpoints = read_watchpoints
self._write_watchpoints = write_watchpoints
self._halt = False
num_instructions = 0
while not self._halt:
vPC = self.get_vPC()
cb = breakpoints.get(vPC, None)
if cb is not None:
cb(self, vPC)
if self._halt:
break
self.execute_instruction()
num_instructions += 1
if max_instructions is not None and\
num_instructions >= max_instructions:
break
def execute_instruction(self):
"""Executes one vCPU instruction."""
vPC = self.get_vPC()
insn = self.read_byte(vPC)
if insn in self._insn_table:
self._insn_table[insn][-1]()
else:
raise RuntimeError("invalid instruction 0x%02X" % insn)
def execute_st(self):
"""Executes the ST instruction."""
vPC = self.get_vPC()
dd = self.read_byte(self.relative(vPC, 1))
vAC = self.get_vAC()
self.write_byte(dd, vAC & 0xFF)
self.set_vPC(self.relative(vPC, 2))
def execute_stw(self):
"""Executes the STW instruction."""
vPC = self.get_vPC()
dd = self.read_byte(self.relative(vPC, 1))
vAC = self.get_vAC()
self.write_word(dd, vAC)
self.set_vPC(self.relative(vPC, 2))
def execute_stlw(self):
"""Executes the STLW instruction."""
vPC = self.get_vPC()
dd = self.read_byte(self.relative(vPC, 1))
dd += self.get_vSP()
vAC = self.get_vAC()
self.write_word(dd & 0xFF, vAC)
self.set_vPC(self.relative(vPC, 2))
def execute_ld(self):
"""Executes the LD instruction."""
vPC = self.get_vPC()
dd = self.read_byte(self.relative(vPC, 1))
vAC = self.read_byte(dd)
self.set_vAC(vAC)
self.set_vPC(self.relative(vPC, 2))
def execute_ldi(self):
"""Executes the LDI instruction."""
vPC = self.get_vPC()
vAC = self.read_byte(self.relative(vPC, 1))
self.set_vAC(vAC)
self.set_vPC(self.relative(vPC, 2))
def execute_ldwi(self):
"""Executes the LDWI instruction."""
vPC = self.get_vPC()
vAC = self.read_word(self.relative(vPC, 1))
self.set_vAC(vAC)
self.set_vPC(self.relative(vPC, 3))
def execute_ldw(self):
"""Executes the LDW instruction."""
vPC = self.get_vPC()
dd = self.read_byte(self.relative(vPC, 1))
vAC = self.read_word(dd)
self.set_vAC(vAC)
self.set_vPC(self.relative(vPC, 2))
def execute_ldlw(self):
"""Executes the LDLW instruction."""
vPC = self.get_vPC()
dd = self.read_byte(self.relative(vPC, 1))
dd += self.get_vSP()
vAC = self.read_word(dd & 0xFF)
self.set_vAC(vAC)
self.set_vPC(self.relative(vPC, 2))
def execute_addw(self):
"""Executes the ADDW instruction."""
vPC = self.get_vPC()
dd = self.read_byte(self.relative(vPC, 1))
vAC = self.get_vAC()
vAC += self.read_word(dd)
self.set_vAC(vAC)
self.set_vPC(self.relative(vPC, 2))
def execute_subw(self):
"""Executes the SUBW instruction."""
vPC = self.get_vPC()
dd = self.read_byte(self.relative(vPC, 1))
vAC = self.get_vAC()
vAC -= self.read_word(dd)
self.set_vAC(vAC)
self.set_vPC(self.relative(vPC, 2))
def execute_addi(self):
"""Executes the ADDI instruction."""
vPC = self.get_vPC()
dd = self.read_byte(self.relative(vPC, 1))
vAC = self.get_vAC()
vAC += dd
self.set_vAC(vAC)
self.set_vPC(self.relative(vPC, 2))
def execute_subi(self):
"""Executes the SUBI instruction."""
vPC = self.get_vPC()
dd = self.read_byte(self.relative(vPC, 1))
vAC = self.get_vAC()
vAC -= dd
self.set_vAC(vAC)
self.set_vPC(self.relative(vPC, 2))
def execute_lslw(self):
"""Executes the LSLW instruction."""
vPC = self.get_vPC()
vAC = self.get_vAC()
vAC = vAC << 1
self.set_vAC(vAC)
self.set_vPC(self.relative(vPC, 1))
def execute_inc(self):
"""Executes the INC instruction."""
vPC = self.get_vPC()
dd = self.read_byte(self.relative(vPC, 1))
v = self.read_byte(dd)
self.write_byte(dd, v + 1)
self.set_vPC(self.relative(vPC, 2))
def execute_andi(self):
"""Executes the ANDI instruction."""
vPC = self.get_vPC()
dd = self.read_byte(self.relative(vPC, 1))
vAC = self.get_vAC()
vAC = vAC & dd
self.set_vAC(vAC)
self.set_vPC(self.relative(vPC, 2))
def execute_andw(self):
"""Executes the ANDW instruction."""
vPC = self.get_vPC()
dd = self.read_byte(self.relative(vPC, 1))
vAC = self.get_vAC()
vAC = vAC & self.read_word(dd)
self.set_vAC(vAC)
self.set_vPC(self.relative(vPC, 2))
def execute_ori(self):
"""Executes the ORI instruction."""
vPC = self.get_vPC()
dd = self.read_byte(self.relative(vPC, 1))
vAC = self.get_vAC()
vAC = vAC | dd
self.set_vAC(vAC)
self.set_vPC(self.relative(vPC, 2))
def execute_orw(self):
"""Executes the ORW instruction."""
vPC = self.get_vPC()
dd = self.read_byte(self.relative(vPC, 1))
vAC = self.get_vAC()
vAC = vAC | self.read_word(dd)
self.set_vAC(vAC)
self.set_vPC(self.relative(vPC, 2))
def execute_xori(self):
"""Executes the XORI instruction."""
vPC = self.get_vPC()
dd = self.read_byte(self.relative(vPC, 1))
vAC = self.get_vAC()
vAC = vAC ^ dd
self.set_vAC(vAC)
self.set_vPC(self.relative(vPC, 2))
def execute_xorw(self):
"""Executes the XORW instruction."""
vPC = self.get_vPC()
dd = self.read_byte(self.relative(vPC, 1))
vAC = self.get_vAC()
vAC = vAC ^ self.read_word(dd)
self.set_vAC(vAC)
self.set_vPC(self.relative(vPC, 2))
def execute_peek(self):
"""Executes the PEEK instruction."""
vPC = self.get_vPC()
vAC = self.get_vAC()
vAC = self.read_byte(vAC)
self.set_vAC(vAC)
self.set_vPC(self.relative(vPC, 1))
def execute_deek(self):
"""Executes the DEEK instruction."""
vPC = self.get_vPC()
vAC = self.get_vAC()
vAC = self.read_word(vAC)
self.set_vAC(vAC)
self.set_vPC(self.relative(vPC, 1))
def execute_poke(self):
"""Executes the POKE instruction."""
vPC = self.get_vPC()
dd = self.read_byte(self.relative(vPC, 1))
addr = self.read_word(dd)
vAC = self.get_vAC()
self.write_byte(addr, vAC)
self.set_vPC(self.relative(vPC, 2))
def execute_doke(self):
"""Executes the DOKE instruction."""
vPC = self.get_vPC()
dd = self.read_byte(self.relative(vPC, 1))
addr = self.read_word(dd)
vAC = self.get_vAC()
self.write_word(addr, vAC)
self.set_vPC(self.relative(vPC, 2))
def execute_lup(self):
"""Executes the LUP instruction."""
vPC = self.get_vPC()
dd = self.read_byte(self.relative(vPC, 1))
vAC = self.get_vAC()
if self._lup_callback is not None:
vAC = self._lup_callback(self, self.relative(vAC, dd))
self.set_vAC(vAC)
self.set_vPC(self.relative(vPC, 2))
def execute_bra(self):
"""Executes the BRA instruction."""
vPC = self.get_vPC()
dd = self.read_byte(self.relative(vPC, 1))
vPC = self.absolute(vPC, dd + 2)
self.set_vPC(vPC)
def execute_bcc(self):
"""Executes the several BCC instructions (conditional branches)."""
vPC = self.get_vPC()
cc = self.read_byte(self.relative(vPC, 1))
dd = self.read_byte(self.relative(vPC, 2))
vAC = self.get_vAC()
jump = ((cc == 0x3F) and (vAC == 0)) or\
((cc == 0x72) and (vAC != 0)) or\
((cc == 0x50) and (vAC >= 0x8000)) or\
((cc == 0x4D) and (vAC > 0) and (vAC < 0x8000)) or\
((cc == 0x56) and ((vAC >= 0x8000) or (vAC == 0))) or\
((cc == 0x53) and (vAC < 0x8000))
if jump:
vPC = self.absolute(vPC, dd + 2)
else:
vPC = self.relative(vPC, 3)
self.set_vPC(vPC)
def execute_call(self):
"""Executes the CALL instruction."""
vPC = self.get_vPC()
dd = self.read_byte(self.relative(vPC, 1))
addr = self.read_word(dd)
self.set_vLR(self.relative(vPC, 2))
self.set_vPC(addr)
def execute_ret(self):
"""Executes the RET instruction."""
vLR = self.get_vLR()
self.set_vPC(vLR)
def execute_push(self):
"""Executes the PUSH instruction."""
vPC = self.get_vPC()
vSP = self.get_vSP()
vSP = (vSP - 2) & 0xFF
vLR = self.get_vLR()
self.write_word(vSP, vLR)
self.set_vSP(vSP)
self.set_vPC(self.relative(vPC, 1))
def execute_pop(self):
"""Executes the POP instruction."""
vPC = self.get_vPC()
vSP = self.get_vSP()
vLR = self.read_word(vSP)
vSP = (vSP + 2) & 0xFF
self.set_vSP(vSP)
self.set_vLR(vLR)
self.set_vPC(self.relative(vPC, 1))
def execute_alloc(self):
"""Executes the ALLOC instruction."""
vPC = self.get_vPC()
dd = self.read_byte(self.relative(vPC, 1))
vSP = self.get_vSP()
vSP += dd
self.set_vSP(vSP)
self.set_vPC(self.relative(vPC, 2))
def execute_sys(self):
"""Executes the SYS instruction."""
if self._sys_callback is not None:
self._sys_callback(self)
vPC = self.get_vPC()
self.set_vPC(self.relative(vPC, 2))
def execute_def(self):
"""Executes the DEF instruction."""
vPC = self.get_vPC()
dd = self.read_byte(self.relative(vPC, 1))
vAC = self.relative(vPC, 2)
self.set_vAC(vAC)
vPC = self.absolute(vPC, dd)
self.set_vPC(vPC)
def execute_calli(self):
"""Executes the CALLI instruction."""
if not self._enable_experimental:
raise RuntimeError("experimental instruction!")
vPC = self.get_vPC()
self.set_vLR(self.relative(vPC, 2))
self.set_vPC(self.read_word(self.relative(vPC, 1)))
def execute_cmphs(self):
"""Executes the CMPHS instruction."""
if not self._enable_experimental:
raise RuntimeError("experimental instruction!")
vPC = self.get_vPC()
vAC = self.get_vAC()
vACH = vAC >> 8
addr = self.read_byte(self.relative(vPC, 1))
b = self.read_byte(addr)
if (vACH ^ b) & 0x80 != 0:
if b & 0x80 != 0:
vAC = ((b + 1) & 0xFF) << 8 | (vAC & 0xFF)
else:
vAC = ((b - 1) & 0xFF) << 8 | (vAC & 0xFF)
self.set_vPC(self.relative(vPC, 2))
def execute_cmphu(self):
"""Executes the CMPHU instruction."""
if not self._enable_experimental:
raise RuntimeError("experimental instruction!")
vPC = self.get_vPC()
vAC = self.get_vAC()
vACH = vAC >> 8
addr = self.read_byte(self.relative(vPC, 1))
b = self.read_byte(addr)
if (vACH ^ b) & 0x80 != 0:
if b & 0x80 != 0:
vAC = ((b - 1) & 0xFF) << 8 | (vAC & 0xFF)
else:
vAC = ((b + 1) & 0xFF) << 8 | (vAC & 0xFF)
self.set_vPC(self.relative(vPC, 2))