emulator improvements
This commit is contained in:
83
h8536/emulator/__init__.py
Normal file
83
h8536/emulator/__init__.py
Normal file
@@ -0,0 +1,83 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from .cli import build_arg_parser, discover_rom_path, load_rom, main
|
||||
from .constants import (
|
||||
HEARTBEAT_FRAME,
|
||||
IPRA,
|
||||
IPRE,
|
||||
ON_CHIP_RAM_END,
|
||||
ON_CHIP_RAM_START,
|
||||
P9DDR,
|
||||
P9DR,
|
||||
RAMCR,
|
||||
REGISTER_FIELD_END,
|
||||
REGISTER_FIELD_START,
|
||||
SCI_SCR_RE,
|
||||
SCI_SCR_RIE,
|
||||
SCI_SCR_TE,
|
||||
SCI_SCR_TIE,
|
||||
SCI_SSR_FER,
|
||||
SCI_SSR_ORER,
|
||||
SCI_SSR_PER,
|
||||
SCI_SSR_RDRF,
|
||||
SCI_SSR_TDRE,
|
||||
SCI1_BRR,
|
||||
SCI1_RDR,
|
||||
SCI1_SCR,
|
||||
SCI1_SMR,
|
||||
SCI1_SSR,
|
||||
SCI1_TDR,
|
||||
VECTOR_INTERVAL_TIMER,
|
||||
VECTOR_SCI1_TXI,
|
||||
WDT_TCSR_R,
|
||||
)
|
||||
from .cpu import CPUState
|
||||
from .errors import EmulatorError, UnsupportedInstruction
|
||||
from .memory import MemoryAccess, MemoryMap, describe_regions
|
||||
from .runner import H8536Emulator, RunReport
|
||||
from .sci import SCI1, SciTxEvent
|
||||
|
||||
__all__ = [
|
||||
"CPUState",
|
||||
"EmulatorError",
|
||||
"HEARTBEAT_FRAME",
|
||||
"H8536Emulator",
|
||||
"IPRA",
|
||||
"IPRE",
|
||||
"MemoryAccess",
|
||||
"MemoryMap",
|
||||
"ON_CHIP_RAM_END",
|
||||
"ON_CHIP_RAM_START",
|
||||
"P9DDR",
|
||||
"P9DR",
|
||||
"RAMCR",
|
||||
"REGISTER_FIELD_END",
|
||||
"REGISTER_FIELD_START",
|
||||
"RunReport",
|
||||
"SCI1",
|
||||
"SCI1_BRR",
|
||||
"SCI1_RDR",
|
||||
"SCI1_SCR",
|
||||
"SCI1_SMR",
|
||||
"SCI1_SSR",
|
||||
"SCI1_TDR",
|
||||
"SCI_SCR_RE",
|
||||
"SCI_SCR_RIE",
|
||||
"SCI_SCR_TE",
|
||||
"SCI_SCR_TIE",
|
||||
"SCI_SSR_FER",
|
||||
"SCI_SSR_ORER",
|
||||
"SCI_SSR_PER",
|
||||
"SCI_SSR_RDRF",
|
||||
"SCI_SSR_TDRE",
|
||||
"SciTxEvent",
|
||||
"UnsupportedInstruction",
|
||||
"VECTOR_INTERVAL_TIMER",
|
||||
"VECTOR_SCI1_TXI",
|
||||
"WDT_TCSR_R",
|
||||
"build_arg_parser",
|
||||
"describe_regions",
|
||||
"discover_rom_path",
|
||||
"load_rom",
|
||||
"main",
|
||||
]
|
||||
7
h8536/emulator/__main__.py
Normal file
7
h8536/emulator/__main__.py
Normal file
@@ -0,0 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from .cli import main
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
66
h8536/emulator/cli.py
Normal file
66
h8536/emulator/cli.py
Normal file
@@ -0,0 +1,66 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
|
||||
from ..formatting import h16
|
||||
from .memory import describe_regions
|
||||
from .runner import H8536Emulator
|
||||
|
||||
|
||||
def discover_rom_path(root: Path) -> Path | None:
|
||||
candidates = [
|
||||
root / "ROM" / "M27C512@DIP28_1.BIN",
|
||||
root / "rom.bin",
|
||||
]
|
||||
candidates.extend(sorted((root / "ROM").glob("*.BIN")) if (root / "ROM").exists() else [])
|
||||
candidates.extend(sorted((root / "ROM").glob("*.bin")) if (root / "ROM").exists() else [])
|
||||
for candidate in candidates:
|
||||
if candidate.is_file():
|
||||
return candidate
|
||||
return None
|
||||
|
||||
|
||||
def load_rom(path: Path | None = None, root: Path | None = None) -> tuple[bytes, Path]:
|
||||
root = root if root is not None else Path.cwd()
|
||||
rom_path = path if path is not None else discover_rom_path(root)
|
||||
if rom_path is None:
|
||||
raise FileNotFoundError(
|
||||
"could not discover ROM bytes; pass --rom PATH, expected ROM/M27C512@DIP28_1.BIN or another ROM/*.BIN"
|
||||
)
|
||||
return rom_path.read_bytes(), rom_path
|
||||
|
||||
|
||||
def build_arg_parser() -> argparse.ArgumentParser:
|
||||
parser = argparse.ArgumentParser(description="Minimal H8/536 emulation harness scaffold")
|
||||
parser.add_argument("--rom", type=Path, help="ROM image path; defaults to ROM/M27C512@DIP28_1.BIN when present")
|
||||
parser.add_argument("--max-steps", type=int, default=64, help="maximum CPU steps to execute")
|
||||
parser.add_argument("--trace", action="store_true", help="print decoded/executed instruction trace")
|
||||
parser.add_argument("--stop-on-heartbeat", action="store_true", help="stop only when 00 00 00 00 80 DA is emitted through SCI1 TDR")
|
||||
parser.add_argument("--memory-map", action="store_true", help="print the scaffolded memory map before running")
|
||||
parser.add_argument("--interval-steps", type=int, default=2048, help="rough step period for the scaffolded timer interrupt")
|
||||
return parser
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
args = build_arg_parser().parse_args(argv)
|
||||
try:
|
||||
rom_bytes, rom_path = load_rom(args.rom)
|
||||
except FileNotFoundError as exc:
|
||||
print(str(exc))
|
||||
return 2
|
||||
|
||||
emulator = H8536Emulator(rom_bytes, interval_steps=args.interval_steps)
|
||||
print(f"rom={rom_path}")
|
||||
print(f"reset_vector={h16(emulator.reset_vector())}")
|
||||
if args.memory_map:
|
||||
print(describe_regions())
|
||||
report = emulator.run(args.max_steps, trace=args.trace, stop_on_heartbeat=args.stop_on_heartbeat)
|
||||
if args.trace:
|
||||
for line in report.trace:
|
||||
print(line)
|
||||
for line in report.summary_lines():
|
||||
print(line)
|
||||
if not report.heartbeat_seen:
|
||||
print("heartbeat_status=not reached; no heartbeat is reported unless bytes are emitted via SCI1_TDR")
|
||||
return 0
|
||||
36
h8536/emulator/constants.py
Normal file
36
h8536/emulator/constants.py
Normal file
@@ -0,0 +1,36 @@
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
SCI1_SMR = 0xFED8
|
||||
SCI1_BRR = 0xFED9
|
||||
SCI1_SCR = 0xFEDA
|
||||
SCI1_TDR = 0xFEDB
|
||||
SCI1_SSR = 0xFEDC
|
||||
SCI1_RDR = 0xFEDD
|
||||
|
||||
P9DDR = 0xFEFE
|
||||
P9DR = 0xFEFF
|
||||
|
||||
IPRA = 0xFF00
|
||||
IPRE = 0xFF04
|
||||
WDT_TCSR_R = 0xFEEC
|
||||
|
||||
SCI_SCR_TIE = 0x80
|
||||
SCI_SCR_RIE = 0x40
|
||||
SCI_SCR_TE = 0x20
|
||||
SCI_SCR_RE = 0x10
|
||||
SCI_SSR_TDRE = 0x80
|
||||
SCI_SSR_RDRF = 0x40
|
||||
SCI_SSR_ORER = 0x20
|
||||
SCI_SSR_FER = 0x10
|
||||
SCI_SSR_PER = 0x08
|
||||
|
||||
ON_CHIP_RAM_START = 0xF680
|
||||
ON_CHIP_RAM_END = 0xFE7F
|
||||
REGISTER_FIELD_START = 0xFE80
|
||||
REGISTER_FIELD_END = 0xFFFF
|
||||
RAMCR = 0xFF11
|
||||
|
||||
HEARTBEAT_FRAME = bytes([0x00, 0x00, 0x00, 0x00, 0x80, 0xDA])
|
||||
VECTOR_INTERVAL_TIMER = 0x0042
|
||||
VECTOR_SCI1_TXI = 0x0084
|
||||
34
h8536/emulator/cpu.py
Normal file
34
h8536/emulator/cpu.py
Normal file
@@ -0,0 +1,34 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
|
||||
@dataclass
|
||||
class CPUState:
|
||||
pc: int = 0
|
||||
sr: int = 0
|
||||
br: int = 0
|
||||
regs: list[int] = field(default_factory=lambda: [0] * 8)
|
||||
cycles: int = 0
|
||||
steps: int = 0
|
||||
z: bool = False
|
||||
c: bool = False
|
||||
n: bool = False
|
||||
v: bool = False
|
||||
interrupt_depth: int = 0
|
||||
|
||||
|
||||
def s8(value: int) -> int:
|
||||
return value - 0x100 if value & 0x80 else value
|
||||
|
||||
|
||||
def s16(value: int) -> int:
|
||||
return value - 0x10000 if value & 0x8000 else value
|
||||
|
||||
|
||||
def mask(size: int) -> int:
|
||||
return 0xFFFF if size == 2 else 0xFF
|
||||
|
||||
|
||||
def sign_bit(size: int) -> int:
|
||||
return 0x8000 if size == 2 else 0x80
|
||||
16
h8536/emulator/errors.py
Normal file
16
h8536/emulator/errors.py
Normal file
@@ -0,0 +1,16 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from ..formatting import h16
|
||||
|
||||
|
||||
class EmulatorError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class UnsupportedInstruction(EmulatorError):
|
||||
def __init__(self, pc: int, raw: bytes, text: str) -> None:
|
||||
raw_text = " ".join(f"{byte:02X}" for byte in raw)
|
||||
super().__init__(f"unsupported instruction at {h16(pc)}: {raw_text} {text}".rstrip())
|
||||
self.pc = pc
|
||||
self.raw = raw
|
||||
self.text = text
|
||||
120
h8536/emulator/memory.py
Normal file
120
h8536/emulator/memory.py
Normal file
@@ -0,0 +1,120 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Iterable
|
||||
|
||||
from ..formatting import h16
|
||||
from ..memory import MEMORY_REGIONS, MemoryRegion, region_for
|
||||
from ..rom import Rom
|
||||
from .constants import (
|
||||
ON_CHIP_RAM_END,
|
||||
ON_CHIP_RAM_START,
|
||||
P9DDR,
|
||||
P9DR,
|
||||
REGISTER_FIELD_END,
|
||||
REGISTER_FIELD_START,
|
||||
SCI1_BRR,
|
||||
SCI1_RDR,
|
||||
SCI1_SCR,
|
||||
SCI1_SMR,
|
||||
SCI1_SSR,
|
||||
SCI1_TDR,
|
||||
)
|
||||
from .peripherals.lcd import LCD_E_CLOCK_DATA, LCD_E_CLOCK_STATUS
|
||||
from .peripherals.p9_bus import P9_ACK_BIT
|
||||
from .sci import SCI1
|
||||
|
||||
|
||||
@dataclass
|
||||
class MemoryAccess:
|
||||
address: int
|
||||
size: int
|
||||
value: int
|
||||
kind: str
|
||||
region: str
|
||||
|
||||
|
||||
class MemoryMap:
|
||||
def __init__(self, rom_bytes: bytes, sci1: SCI1 | None = None) -> None:
|
||||
self.rom = Rom(rom_bytes, base=0)
|
||||
self.sci1 = sci1 if sci1 is not None else SCI1()
|
||||
self.ram = bytearray(ON_CHIP_RAM_END - ON_CHIP_RAM_START + 1)
|
||||
self.registers = bytearray(REGISTER_FIELD_END - REGISTER_FIELD_START + 1)
|
||||
self.external: dict[int, int] = {}
|
||||
self.access_log: list[MemoryAccess] = []
|
||||
|
||||
self._set_register(SCI1_SMR, self.sci1.smr)
|
||||
self._set_register(SCI1_BRR, self.sci1.brr)
|
||||
self._set_register(SCI1_SCR, self.sci1.scr)
|
||||
self._set_register(SCI1_TDR, self.sci1.tdr)
|
||||
self._set_register(SCI1_SSR, self.sci1.ssr)
|
||||
self._set_register(SCI1_RDR, self.sci1.rdr)
|
||||
|
||||
def region(self, address: int) -> MemoryRegion:
|
||||
return region_for(address & 0xFFFF)
|
||||
|
||||
def read8(self, address: int) -> int:
|
||||
address &= 0xFFFF
|
||||
if address in (SCI1_SMR, SCI1_BRR, SCI1_SCR, SCI1_TDR, SCI1_SSR, SCI1_RDR):
|
||||
value = self.sci1.read(address)
|
||||
self._set_register(address, value)
|
||||
elif address in self.external:
|
||||
value = self.external[address]
|
||||
elif address in (LCD_E_CLOCK_DATA, LCD_E_CLOCK_STATUS):
|
||||
# LCD E-clock/status space. Default to ready/zero so boot can pass
|
||||
# busy-flag polling until a fuller external bus model exists.
|
||||
value = 0x00
|
||||
elif ON_CHIP_RAM_START <= address <= ON_CHIP_RAM_END:
|
||||
value = self.ram[address - ON_CHIP_RAM_START]
|
||||
elif REGISTER_FIELD_START <= address <= REGISTER_FIELD_END:
|
||||
value = self.registers[address - REGISTER_FIELD_START]
|
||||
if address == P9DR and not (self.registers[P9DDR - REGISTER_FIELD_START] & 0x80):
|
||||
# P97 is used as an input during the serial panel/camera-side
|
||||
# bit-bang handshake. With no external device modeled, hold the
|
||||
# input low so the firmware sees an idle/acknowledged bus rather
|
||||
# than reading back its previous output latch forever.
|
||||
value &= ~P9_ACK_BIT
|
||||
elif self.rom.contains(address):
|
||||
value = self.rom.u8(address)
|
||||
else:
|
||||
value = self.external.get(address, 0xFF)
|
||||
self._log("read", address, 1, value)
|
||||
return value
|
||||
|
||||
def read16(self, address: int) -> int:
|
||||
high = self.read8(address)
|
||||
low = self.read8((address + 1) & 0xFFFF)
|
||||
return (high << 8) | low
|
||||
|
||||
def write8(self, address: int, value: int) -> None:
|
||||
address &= 0xFFFF
|
||||
value &= 0xFF
|
||||
if address in (SCI1_SMR, SCI1_BRR, SCI1_SCR, SCI1_TDR, SCI1_SSR, SCI1_RDR):
|
||||
self.sci1.write(address, value)
|
||||
self._set_register(address, self.sci1.read(address))
|
||||
elif ON_CHIP_RAM_START <= address <= ON_CHIP_RAM_END:
|
||||
self.ram[address - ON_CHIP_RAM_START] = value
|
||||
elif REGISTER_FIELD_START <= address <= REGISTER_FIELD_END:
|
||||
self._set_register(address, value)
|
||||
elif self.rom.contains(address):
|
||||
# The ROM image spans the whole address space, but the H8/536 map
|
||||
# can place external RAM/peripherals in these ranges. Keep writes
|
||||
# as external overrides while leaving instruction fetch immutable.
|
||||
self.external[address] = value
|
||||
else:
|
||||
self.external[address] = value
|
||||
self._log("write", address, 1, value)
|
||||
|
||||
def write16(self, address: int, value: int) -> None:
|
||||
self.write8(address, (value >> 8) & 0xFF)
|
||||
self.write8((address + 1) & 0xFFFF, value & 0xFF)
|
||||
|
||||
def _set_register(self, address: int, value: int) -> None:
|
||||
self.registers[address - REGISTER_FIELD_START] = value & 0xFF
|
||||
|
||||
def _log(self, kind: str, address: int, size: int, value: int) -> None:
|
||||
self.access_log.append(MemoryAccess(address, size, value, kind, self.region(address).name))
|
||||
|
||||
|
||||
def describe_regions(regions: Iterable[MemoryRegion] = MEMORY_REGIONS) -> str:
|
||||
return "\n".join(f"{h16(region.start)}-{h16(region.end)} {region.name} {region.kind}" for region in regions)
|
||||
10
h8536/emulator/peripherals/__init__.py
Normal file
10
h8536/emulator/peripherals/__init__.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from .lcd import LCD_E_CLOCK_DATA, LCD_E_CLOCK_STATUS
|
||||
from .p9_bus import P9_ACK_BIT
|
||||
|
||||
__all__ = [
|
||||
"LCD_E_CLOCK_DATA",
|
||||
"LCD_E_CLOCK_STATUS",
|
||||
"P9_ACK_BIT",
|
||||
]
|
||||
5
h8536/emulator/peripherals/lcd.py
Normal file
5
h8536/emulator/peripherals/lcd.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
LCD_E_CLOCK_DATA = 0xF200
|
||||
LCD_E_CLOCK_STATUS = 0xF201
|
||||
4
h8536/emulator/peripherals/p9_bus.py
Normal file
4
h8536/emulator/peripherals/p9_bus.py
Normal file
@@ -0,0 +1,4 @@
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
P9_ACK_BIT = 0x80
|
||||
@@ -1,260 +1,23 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
from typing import Iterable
|
||||
|
||||
from .decoder import H8536Decoder
|
||||
from .formatting import h8, h16
|
||||
from .memory import MEMORY_REGIONS, MemoryRegion, region_for
|
||||
from .rom import DecodeError, Rom
|
||||
from .vectors import read_vectors_min
|
||||
|
||||
|
||||
SCI1_SMR = 0xFED8
|
||||
SCI1_BRR = 0xFED9
|
||||
SCI1_SCR = 0xFEDA
|
||||
SCI1_TDR = 0xFEDB
|
||||
SCI1_SSR = 0xFEDC
|
||||
SCI1_RDR = 0xFEDD
|
||||
P9DDR = 0xFEFE
|
||||
P9DR = 0xFEFF
|
||||
IPRA = 0xFF00
|
||||
IPRE = 0xFF04
|
||||
WDT_TCSR_R = 0xFEEC
|
||||
|
||||
SCI_SCR_TIE = 0x80
|
||||
SCI_SCR_RIE = 0x40
|
||||
SCI_SCR_TE = 0x20
|
||||
SCI_SCR_RE = 0x10
|
||||
SCI_SSR_TDRE = 0x80
|
||||
SCI_SSR_RDRF = 0x40
|
||||
SCI_SSR_ORER = 0x20
|
||||
SCI_SSR_FER = 0x10
|
||||
SCI_SSR_PER = 0x08
|
||||
|
||||
ON_CHIP_RAM_START = 0xF680
|
||||
ON_CHIP_RAM_END = 0xFE7F
|
||||
REGISTER_FIELD_START = 0xFE80
|
||||
REGISTER_FIELD_END = 0xFFFF
|
||||
RAMCR = 0xFF11
|
||||
HEARTBEAT_FRAME = bytes([0x00, 0x00, 0x00, 0x00, 0x80, 0xDA])
|
||||
VECTOR_INTERVAL_TIMER = 0x0042
|
||||
VECTOR_SCI1_TXI = 0x0084
|
||||
|
||||
|
||||
class EmulatorError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class UnsupportedInstruction(EmulatorError):
|
||||
def __init__(self, pc: int, raw: bytes, text: str) -> None:
|
||||
raw_text = " ".join(f"{byte:02X}" for byte in raw)
|
||||
super().__init__(f"unsupported instruction at {h16(pc)}: {raw_text} {text}".rstrip())
|
||||
self.pc = pc
|
||||
self.raw = raw
|
||||
self.text = text
|
||||
|
||||
|
||||
@dataclass
|
||||
class SciTxEvent:
|
||||
address: int
|
||||
value: int
|
||||
scr: int
|
||||
ssr: int
|
||||
emitted: bool
|
||||
|
||||
|
||||
@dataclass
|
||||
class SCI1:
|
||||
"""Small SCI1 model for the H8/536 serial path.
|
||||
|
||||
Manual anchors:
|
||||
- RDR/TDR/SCR/SSR live at H'FEDD/H'FEDB/H'FEDA/H'FEDC for SCI1.
|
||||
- SCR bit 7 TIE, bit 6 RIE, bit 5 TE, bit 4 RE.
|
||||
- SSR bit 7 TDRE, bit 6 RDRF, bits 5..3 ORER/FER/PER.
|
||||
- Software normally writes TDR after TDRE=1, then clears SSR.TDRE.
|
||||
"""
|
||||
|
||||
smr: int = 0x00
|
||||
brr: int = 0xFF
|
||||
scr: int = 0x0C
|
||||
tdr: int = 0xFF
|
||||
ssr: int = 0x87
|
||||
rdr: int = 0x00
|
||||
tx_bytes: list[int] = field(default_factory=list)
|
||||
tx_events: list[SciTxEvent] = field(default_factory=list)
|
||||
tx_frames: list[bytes] = field(default_factory=list)
|
||||
_frame_buffer: bytearray = field(default_factory=bytearray)
|
||||
tx_ready_delay: int = 0
|
||||
|
||||
def read(self, address: int) -> int:
|
||||
if address == SCI1_SMR:
|
||||
return self.smr
|
||||
if address == SCI1_BRR:
|
||||
return self.brr
|
||||
if address == SCI1_SCR:
|
||||
return self.scr
|
||||
if address == SCI1_TDR:
|
||||
return self.tdr
|
||||
if address == SCI1_SSR:
|
||||
return self.ssr
|
||||
if address == SCI1_RDR:
|
||||
return self.rdr
|
||||
raise KeyError(address)
|
||||
|
||||
def write(self, address: int, value: int) -> None:
|
||||
value &= 0xFF
|
||||
if address == SCI1_SMR:
|
||||
self.smr = value
|
||||
elif address == SCI1_BRR:
|
||||
self.brr = value
|
||||
elif address == SCI1_SCR:
|
||||
self.scr = value
|
||||
elif address == SCI1_TDR:
|
||||
self.tdr = value
|
||||
self._write_tdr(value)
|
||||
elif address == SCI1_SSR:
|
||||
# The real SSR is R/(W)*: writable zeroes clear latched flags after
|
||||
# the required read sequence. This scaffold applies zero bits
|
||||
# directly so ROM BCLR/BSET style accesses can be modeled.
|
||||
self.ssr = value
|
||||
elif address == SCI1_RDR:
|
||||
self.rdr = value
|
||||
else:
|
||||
raise KeyError(address)
|
||||
|
||||
def _write_tdr(self, value: int) -> None:
|
||||
emitted = bool(self.scr & SCI_SCR_TE)
|
||||
if emitted:
|
||||
self.tx_bytes.append(value)
|
||||
self._frame_buffer.append(value)
|
||||
if len(self._frame_buffer) == len(HEARTBEAT_FRAME):
|
||||
self.tx_frames.append(bytes(self._frame_buffer))
|
||||
self._frame_buffer.clear()
|
||||
self.ssr |= SCI_SSR_TDRE
|
||||
self.tx_ready_delay = 2
|
||||
self.tx_events.append(SciTxEvent(SCI1_TDR, value, self.scr, self.ssr, emitted))
|
||||
|
||||
def inject_rx(self, value: int) -> None:
|
||||
self.rdr = value & 0xFF
|
||||
self.ssr |= SCI_SSR_RDRF
|
||||
|
||||
def saw_heartbeat(self) -> bool:
|
||||
return HEARTBEAT_FRAME in self.tx_frames
|
||||
|
||||
def tick(self) -> None:
|
||||
if self.tx_ready_delay:
|
||||
self.tx_ready_delay -= 1
|
||||
if self.tx_ready_delay == 0:
|
||||
self.ssr |= SCI_SSR_TDRE
|
||||
|
||||
|
||||
@dataclass
|
||||
class MemoryAccess:
|
||||
address: int
|
||||
size: int
|
||||
value: int
|
||||
kind: str
|
||||
region: str
|
||||
|
||||
|
||||
class MemoryMap:
|
||||
def __init__(self, rom_bytes: bytes, sci1: SCI1 | None = None) -> None:
|
||||
self.rom = Rom(rom_bytes, base=0)
|
||||
self.sci1 = sci1 if sci1 is not None else SCI1()
|
||||
self.ram = bytearray(ON_CHIP_RAM_END - ON_CHIP_RAM_START + 1)
|
||||
self.registers = bytearray(REGISTER_FIELD_END - REGISTER_FIELD_START + 1)
|
||||
self.external: dict[int, int] = {}
|
||||
self.access_log: list[MemoryAccess] = []
|
||||
|
||||
self._set_register(SCI1_SMR, self.sci1.smr)
|
||||
self._set_register(SCI1_BRR, self.sci1.brr)
|
||||
self._set_register(SCI1_SCR, self.sci1.scr)
|
||||
self._set_register(SCI1_TDR, self.sci1.tdr)
|
||||
self._set_register(SCI1_SSR, self.sci1.ssr)
|
||||
self._set_register(SCI1_RDR, self.sci1.rdr)
|
||||
|
||||
def region(self, address: int) -> MemoryRegion:
|
||||
return region_for(address & 0xFFFF)
|
||||
|
||||
def read8(self, address: int) -> int:
|
||||
address &= 0xFFFF
|
||||
if address in (SCI1_SMR, SCI1_BRR, SCI1_SCR, SCI1_TDR, SCI1_SSR, SCI1_RDR):
|
||||
value = self.sci1.read(address)
|
||||
self._set_register(address, value)
|
||||
elif address in self.external:
|
||||
value = self.external[address]
|
||||
elif address in (0xF200, 0xF201):
|
||||
# LCD E-clock/status space. Default to ready/zero so boot can pass
|
||||
# busy-flag polling until a fuller external bus model exists.
|
||||
value = 0x00
|
||||
elif ON_CHIP_RAM_START <= address <= ON_CHIP_RAM_END:
|
||||
value = self.ram[address - ON_CHIP_RAM_START]
|
||||
elif REGISTER_FIELD_START <= address <= REGISTER_FIELD_END:
|
||||
value = self.registers[address - REGISTER_FIELD_START]
|
||||
if address == P9DR and not (self.registers[P9DDR - REGISTER_FIELD_START] & 0x80):
|
||||
# P97 is used as an input during the serial panel/camera-side
|
||||
# bit-bang handshake. With no external device modeled, hold the
|
||||
# input low so the firmware sees an idle/acknowledged bus rather
|
||||
# than reading back its previous output latch forever.
|
||||
value &= 0x7F
|
||||
elif self.rom.contains(address):
|
||||
value = self.rom.u8(address)
|
||||
else:
|
||||
value = self.external.get(address, 0xFF)
|
||||
self._log("read", address, 1, value)
|
||||
return value
|
||||
|
||||
def read16(self, address: int) -> int:
|
||||
high = self.read8(address)
|
||||
low = self.read8((address + 1) & 0xFFFF)
|
||||
return (high << 8) | low
|
||||
|
||||
def write8(self, address: int, value: int) -> None:
|
||||
address &= 0xFFFF
|
||||
value &= 0xFF
|
||||
if address in (SCI1_SMR, SCI1_BRR, SCI1_SCR, SCI1_TDR, SCI1_SSR, SCI1_RDR):
|
||||
self.sci1.write(address, value)
|
||||
self._set_register(address, self.sci1.read(address))
|
||||
elif ON_CHIP_RAM_START <= address <= ON_CHIP_RAM_END:
|
||||
self.ram[address - ON_CHIP_RAM_START] = value
|
||||
elif REGISTER_FIELD_START <= address <= REGISTER_FIELD_END:
|
||||
self._set_register(address, value)
|
||||
elif self.rom.contains(address):
|
||||
# The ROM image spans the whole address space, but the H8/536 map
|
||||
# can place external RAM/peripherals in these ranges. Keep writes
|
||||
# as external overrides while leaving instruction fetch immutable.
|
||||
self.external[address] = value
|
||||
else:
|
||||
self.external[address] = value
|
||||
self._log("write", address, 1, value)
|
||||
|
||||
def write16(self, address: int, value: int) -> None:
|
||||
self.write8(address, (value >> 8) & 0xFF)
|
||||
self.write8((address + 1) & 0xFFFF, value & 0xFF)
|
||||
|
||||
def _set_register(self, address: int, value: int) -> None:
|
||||
self.registers[address - REGISTER_FIELD_START] = value & 0xFF
|
||||
|
||||
def _log(self, kind: str, address: int, size: int, value: int) -> None:
|
||||
self.access_log.append(MemoryAccess(address, size, value, kind, self.region(address).name))
|
||||
|
||||
|
||||
@dataclass
|
||||
class CPUState:
|
||||
pc: int = 0
|
||||
sr: int = 0
|
||||
br: int = 0
|
||||
regs: list[int] = field(default_factory=lambda: [0] * 8)
|
||||
cycles: int = 0
|
||||
steps: int = 0
|
||||
z: bool = False
|
||||
c: bool = False
|
||||
n: bool = False
|
||||
v: bool = False
|
||||
interrupt_depth: int = 0
|
||||
from ..decoder import H8536Decoder
|
||||
from ..formatting import h16
|
||||
from ..rom import DecodeError
|
||||
from ..vectors import read_vectors_min
|
||||
from .constants import (
|
||||
IPRA,
|
||||
IPRE,
|
||||
SCI_SCR_TIE,
|
||||
SCI_SSR_TDRE,
|
||||
VECTOR_INTERVAL_TIMER,
|
||||
VECTOR_SCI1_TXI,
|
||||
)
|
||||
from .cpu import CPUState, mask, s8, s16, sign_bit
|
||||
from .errors import EmulatorError, UnsupportedInstruction
|
||||
from .memory import MemoryMap
|
||||
from .sci import SCI1
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -281,7 +44,9 @@ class RunReport:
|
||||
]
|
||||
if self.unsupported:
|
||||
lines.append(f"unsupported={self.unsupported}")
|
||||
lines.append("next_todo=implement the stopped opcode, then add interrupt scheduling for SCI1 TXI and interval/watchdog timer overflow")
|
||||
lines.append(
|
||||
"next_todo=implement the stopped opcode, then add interrupt scheduling for SCI1 TXI and interval/watchdog timer overflow"
|
||||
)
|
||||
return lines
|
||||
|
||||
|
||||
@@ -390,7 +155,7 @@ class H8536Emulator:
|
||||
|
||||
def _execute_general(self, pc: int, next_pc: int) -> int:
|
||||
ea = self._decode_ea(pc)
|
||||
op = self.memory.rom.u8(pc + ea["length"])
|
||||
op = self.memory.rom.u8(pc + int(ea["length"]))
|
||||
size = int(ea["size"])
|
||||
raw = self.memory.rom.slice(pc, self._general_length(pc, ea, op))
|
||||
|
||||
@@ -481,8 +246,8 @@ class H8536Emulator:
|
||||
self._set_logic_flags(value, size)
|
||||
elif op == 0x1A:
|
||||
value = self._read_ea(ea, size)
|
||||
result = (value << 1) & _mask(size)
|
||||
self.cpu.c = bool(value & _sign_bit(size))
|
||||
result = (value << 1) & mask(size)
|
||||
self.cpu.c = bool(value & sign_bit(size))
|
||||
self._write_ea(ea, result, size)
|
||||
self._set_logic_flags(result, size)
|
||||
elif op == 0x1B:
|
||||
@@ -522,7 +287,7 @@ class H8536Emulator:
|
||||
|
||||
def _branch8(self, raw: bytes, pc: int, next_pc: int) -> int:
|
||||
cond = raw[0] & 0x0F
|
||||
disp = _s8(raw[1])
|
||||
disp = s8(raw[1])
|
||||
target = (pc + 2 + disp) & 0xFFFF
|
||||
if self._branch_condition(cond):
|
||||
return target
|
||||
@@ -530,7 +295,7 @@ class H8536Emulator:
|
||||
|
||||
def _branch16(self, raw: bytes, pc: int, next_pc: int) -> int:
|
||||
cond = raw[0] & 0x0F
|
||||
disp = _s16(int.from_bytes(raw[1:3], "big"))
|
||||
disp = s16(int.from_bytes(raw[1:3], "big"))
|
||||
target = (pc + 3 + disp) & 0xFFFF
|
||||
if self._branch_condition(cond):
|
||||
return target
|
||||
@@ -550,9 +315,9 @@ class H8536Emulator:
|
||||
def _direct_call(self, raw: bytes, next_pc: int) -> int:
|
||||
self._push16(next_pc)
|
||||
if raw[0] == 0x0E:
|
||||
return (next_pc + _s8(raw[1])) & 0xFFFF
|
||||
return (next_pc + s8(raw[1])) & 0xFFFF
|
||||
if raw[0] == 0x1E:
|
||||
return (next_pc + _s16(int.from_bytes(raw[1:3], "big"))) & 0xFFFF
|
||||
return (next_pc + s16(int.from_bytes(raw[1:3], "big"))) & 0xFFFF
|
||||
return int.from_bytes(raw[1:3], "big")
|
||||
|
||||
def _scb(self, raw: bytes, pc: int, next_pc: int) -> int:
|
||||
@@ -564,7 +329,7 @@ class H8536Emulator:
|
||||
self.cpu.regs[reg] = value
|
||||
self.cpu.z = value == 0
|
||||
if value != 0:
|
||||
return (pc + 3 + _s8(raw[2])) & 0xFFFF
|
||||
return (pc + 3 + s8(raw[2])) & 0xFFFF
|
||||
return next_pc
|
||||
|
||||
def _tick_peripherals(self) -> None:
|
||||
@@ -631,14 +396,14 @@ class H8536Emulator:
|
||||
self.cpu.regs[7] = (sp + 2) & 0xFFFF
|
||||
return value
|
||||
|
||||
def _push_register_mask(self, mask: int) -> None:
|
||||
def _push_register_mask(self, mask_value: int) -> None:
|
||||
for reg in range(8):
|
||||
if mask & (1 << reg):
|
||||
if mask_value & (1 << reg):
|
||||
self._push16(self.cpu.regs[reg])
|
||||
|
||||
def _pop_register_mask(self, mask: int) -> None:
|
||||
def _pop_register_mask(self, mask_value: int) -> None:
|
||||
for reg in reversed(range(8)):
|
||||
if mask & (1 << reg):
|
||||
if mask_value & (1 << reg):
|
||||
self.cpu.regs[reg] = self._pop16()
|
||||
|
||||
def _decode_ea(self, pc: int) -> dict[str, int | str | None]:
|
||||
@@ -652,9 +417,9 @@ class H8536Emulator:
|
||||
if 0xD0 <= first <= 0xDF:
|
||||
return {"mode": "indirect", "reg": first & 0x07, "size": 2 if first & 0x08 else 1, "length": 1, "value": None}
|
||||
if 0xE0 <= first <= 0xEF:
|
||||
return {"mode": "disp", "reg": first & 0x07, "size": 2 if first & 0x08 else 1, "length": 2, "value": _s8(self.memory.rom.u8(pc + 1))}
|
||||
return {"mode": "disp", "reg": first & 0x07, "size": 2 if first & 0x08 else 1, "length": 2, "value": s8(self.memory.rom.u8(pc + 1))}
|
||||
if 0xF0 <= first <= 0xFF:
|
||||
return {"mode": "disp", "reg": first & 0x07, "size": 2 if first & 0x08 else 1, "length": 3, "value": _s16(self.memory.rom.u16(pc + 1))}
|
||||
return {"mode": "disp", "reg": first & 0x07, "size": 2 if first & 0x08 else 1, "length": 3, "value": s16(self.memory.rom.u16(pc + 1))}
|
||||
if first in (0x04, 0x0C):
|
||||
size = 2 if first == 0x0C else 1
|
||||
value = self.memory.rom.u16(pc + 1) if size == 2 else self.memory.rom.u8(pc + 1)
|
||||
@@ -697,7 +462,7 @@ class H8536Emulator:
|
||||
def _read_ea(self, ea: dict[str, int | str | None], size: int) -> int:
|
||||
mode = ea["mode"]
|
||||
if mode == "imm":
|
||||
return int(ea["value"]) & _mask(size)
|
||||
return int(ea["value"]) & mask(size)
|
||||
if mode == "reg":
|
||||
return self._reg_read(int(ea["reg"]), size)
|
||||
address = self._ea_address(ea, size)
|
||||
@@ -727,28 +492,28 @@ class H8536Emulator:
|
||||
self._set_sub_flags(lhs, rhs, lhs - rhs, size)
|
||||
|
||||
def _set_logic_flags(self, value: int, size: int) -> None:
|
||||
value &= _mask(size)
|
||||
value &= mask(size)
|
||||
self.cpu.z = value == 0
|
||||
self.cpu.n = bool(value & _sign_bit(size))
|
||||
self.cpu.n = bool(value & sign_bit(size))
|
||||
self.cpu.v = False
|
||||
|
||||
def _set_add_flags(self, lhs: int, rhs: int, result: int, size: int) -> None:
|
||||
mask = _mask(size)
|
||||
sign = _sign_bit(size)
|
||||
result &= mask
|
||||
lhs &= mask
|
||||
rhs &= mask
|
||||
max_value = mask(size)
|
||||
sign = sign_bit(size)
|
||||
result &= max_value
|
||||
lhs &= max_value
|
||||
rhs &= max_value
|
||||
self.cpu.z = result == 0
|
||||
self.cpu.n = bool(result & sign)
|
||||
self.cpu.c = lhs + rhs > mask
|
||||
self.cpu.c = lhs + rhs > max_value
|
||||
self.cpu.v = bool((~(lhs ^ rhs) & (lhs ^ result) & sign) != 0)
|
||||
|
||||
def _set_sub_flags(self, lhs: int, rhs: int, result: int, size: int) -> None:
|
||||
mask = _mask(size)
|
||||
sign = _sign_bit(size)
|
||||
result &= mask
|
||||
lhs &= mask
|
||||
rhs &= mask
|
||||
max_value = mask(size)
|
||||
sign = sign_bit(size)
|
||||
result &= max_value
|
||||
lhs &= max_value
|
||||
rhs &= max_value
|
||||
self.cpu.z = result == 0
|
||||
self.cpu.n = bool(result & sign)
|
||||
self.cpu.c = lhs < rhs
|
||||
@@ -756,15 +521,15 @@ class H8536Emulator:
|
||||
|
||||
def _bit_operation(self, ea: dict[str, int | str | None], size: int, op_base: int, bit: int) -> None:
|
||||
value = self._read_ea(ea, size)
|
||||
bit &= (15 if size == 2 else 7)
|
||||
mask = 1 << bit
|
||||
self.cpu.z = not bool(value & mask)
|
||||
bit &= 15 if size == 2 else 7
|
||||
bit_mask = 1 << bit
|
||||
self.cpu.z = not bool(value & bit_mask)
|
||||
if op_base == 0xC0:
|
||||
self._write_ea(ea, value | mask, size)
|
||||
self._write_ea(ea, value | bit_mask, size)
|
||||
elif op_base == 0xD0:
|
||||
self._write_ea(ea, value & ~mask, size)
|
||||
self._write_ea(ea, value & ~bit_mask, size)
|
||||
elif op_base == 0xE0:
|
||||
self._write_ea(ea, value ^ mask, size)
|
||||
self._write_ea(ea, value ^ bit_mask, size)
|
||||
|
||||
def _branch_condition(self, cond: int) -> bool:
|
||||
if cond == 0x0:
|
||||
@@ -798,81 +563,3 @@ class H8536Emulator:
|
||||
if cond == 0xE:
|
||||
return not self.cpu.z and self.cpu.n == self.cpu.v
|
||||
return self.cpu.z or self.cpu.n != self.cpu.v
|
||||
|
||||
|
||||
def _s8(value: int) -> int:
|
||||
return value - 0x100 if value & 0x80 else value
|
||||
|
||||
|
||||
def _s16(value: int) -> int:
|
||||
return value - 0x10000 if value & 0x8000 else value
|
||||
|
||||
|
||||
def _mask(size: int) -> int:
|
||||
return 0xFFFF if size == 2 else 0xFF
|
||||
|
||||
|
||||
def _sign_bit(size: int) -> int:
|
||||
return 0x8000 if size == 2 else 0x80
|
||||
|
||||
|
||||
def discover_rom_path(root: Path) -> Path | None:
|
||||
candidates = [
|
||||
root / "ROM" / "M27C512@DIP28_1.BIN",
|
||||
root / "rom.bin",
|
||||
]
|
||||
candidates.extend(sorted((root / "ROM").glob("*.BIN")) if (root / "ROM").exists() else [])
|
||||
candidates.extend(sorted((root / "ROM").glob("*.bin")) if (root / "ROM").exists() else [])
|
||||
for candidate in candidates:
|
||||
if candidate.is_file():
|
||||
return candidate
|
||||
return None
|
||||
|
||||
|
||||
def load_rom(path: Path | None = None, root: Path | None = None) -> tuple[bytes, Path]:
|
||||
root = root if root is not None else Path.cwd()
|
||||
rom_path = path if path is not None else discover_rom_path(root)
|
||||
if rom_path is None:
|
||||
raise FileNotFoundError(
|
||||
"could not discover ROM bytes; pass --rom PATH, expected ROM/M27C512@DIP28_1.BIN or another ROM/*.BIN"
|
||||
)
|
||||
return rom_path.read_bytes(), rom_path
|
||||
|
||||
|
||||
def describe_regions(regions: Iterable[MemoryRegion] = MEMORY_REGIONS) -> str:
|
||||
return "\n".join(f"{h16(region.start)}-{h16(region.end)} {region.name} {region.kind}" for region in regions)
|
||||
|
||||
|
||||
def build_arg_parser() -> argparse.ArgumentParser:
|
||||
parser = argparse.ArgumentParser(description="Minimal H8/536 emulation harness scaffold")
|
||||
parser.add_argument("--rom", type=Path, help="ROM image path; defaults to ROM/M27C512@DIP28_1.BIN when present")
|
||||
parser.add_argument("--max-steps", type=int, default=64, help="maximum CPU steps to execute")
|
||||
parser.add_argument("--trace", action="store_true", help="print decoded/executed instruction trace")
|
||||
parser.add_argument("--stop-on-heartbeat", action="store_true", help="stop only when 00 00 00 00 80 DA is emitted through SCI1 TDR")
|
||||
parser.add_argument("--memory-map", action="store_true", help="print the scaffolded memory map before running")
|
||||
parser.add_argument("--interval-steps", type=int, default=2048, help="rough step period for the scaffolded timer interrupt")
|
||||
return parser
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
args = build_arg_parser().parse_args(argv)
|
||||
try:
|
||||
rom_bytes, rom_path = load_rom(args.rom)
|
||||
except FileNotFoundError as exc:
|
||||
print(str(exc))
|
||||
return 2
|
||||
|
||||
emulator = H8536Emulator(rom_bytes, interval_steps=args.interval_steps)
|
||||
print(f"rom={rom_path}")
|
||||
print(f"reset_vector={h16(emulator.reset_vector())}")
|
||||
if args.memory_map:
|
||||
print(describe_regions())
|
||||
report = emulator.run(args.max_steps, trace=args.trace, stop_on_heartbeat=args.stop_on_heartbeat)
|
||||
if args.trace:
|
||||
for line in report.trace:
|
||||
print(line)
|
||||
for line in report.summary_lines():
|
||||
print(line)
|
||||
if not report.heartbeat_seen:
|
||||
print("heartbeat_status=not reached; no heartbeat is reported unless bytes are emitted via SCI1_TDR")
|
||||
return 0
|
||||
110
h8536/emulator/sci.py
Normal file
110
h8536/emulator/sci.py
Normal file
@@ -0,0 +1,110 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
from .constants import (
|
||||
HEARTBEAT_FRAME,
|
||||
SCI1_BRR,
|
||||
SCI1_RDR,
|
||||
SCI1_SCR,
|
||||
SCI1_SMR,
|
||||
SCI1_SSR,
|
||||
SCI1_TDR,
|
||||
SCI_SCR_TE,
|
||||
SCI_SSR_RDRF,
|
||||
SCI_SSR_TDRE,
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class SciTxEvent:
|
||||
address: int
|
||||
value: int
|
||||
scr: int
|
||||
ssr: int
|
||||
emitted: bool
|
||||
|
||||
|
||||
@dataclass
|
||||
class SCI1:
|
||||
"""Small SCI1 model for the H8/536 serial path.
|
||||
|
||||
Manual anchors:
|
||||
- RDR/TDR/SCR/SSR live at H'FEDD/H'FEDB/H'FEDA/H'FEDC for SCI1.
|
||||
- SCR bit 7 TIE, bit 6 RIE, bit 5 TE, bit 4 RE.
|
||||
- SSR bit 7 TDRE, bit 6 RDRF, bits 5..3 ORER/FER/PER.
|
||||
- Software normally writes TDR after TDRE=1, then clears SSR.TDRE.
|
||||
"""
|
||||
|
||||
smr: int = 0x00
|
||||
brr: int = 0xFF
|
||||
scr: int = 0x0C
|
||||
tdr: int = 0xFF
|
||||
ssr: int = 0x87
|
||||
rdr: int = 0x00
|
||||
tx_bytes: list[int] = field(default_factory=list)
|
||||
tx_events: list[SciTxEvent] = field(default_factory=list)
|
||||
tx_frames: list[bytes] = field(default_factory=list)
|
||||
_frame_buffer: bytearray = field(default_factory=bytearray)
|
||||
tx_ready_delay: int = 0
|
||||
|
||||
def read(self, address: int) -> int:
|
||||
if address == SCI1_SMR:
|
||||
return self.smr
|
||||
if address == SCI1_BRR:
|
||||
return self.brr
|
||||
if address == SCI1_SCR:
|
||||
return self.scr
|
||||
if address == SCI1_TDR:
|
||||
return self.tdr
|
||||
if address == SCI1_SSR:
|
||||
return self.ssr
|
||||
if address == SCI1_RDR:
|
||||
return self.rdr
|
||||
raise KeyError(address)
|
||||
|
||||
def write(self, address: int, value: int) -> None:
|
||||
value &= 0xFF
|
||||
if address == SCI1_SMR:
|
||||
self.smr = value
|
||||
elif address == SCI1_BRR:
|
||||
self.brr = value
|
||||
elif address == SCI1_SCR:
|
||||
self.scr = value
|
||||
elif address == SCI1_TDR:
|
||||
self.tdr = value
|
||||
self._write_tdr(value)
|
||||
elif address == SCI1_SSR:
|
||||
# The real SSR is R/(W)*: writable zeroes clear latched flags after
|
||||
# the required read sequence. This scaffold applies zero bits
|
||||
# directly so ROM BCLR/BSET style accesses can be modeled.
|
||||
self.ssr = value
|
||||
elif address == SCI1_RDR:
|
||||
self.rdr = value
|
||||
else:
|
||||
raise KeyError(address)
|
||||
|
||||
def _write_tdr(self, value: int) -> None:
|
||||
emitted = bool(self.scr & SCI_SCR_TE)
|
||||
if emitted:
|
||||
self.tx_bytes.append(value)
|
||||
self._frame_buffer.append(value)
|
||||
if len(self._frame_buffer) == len(HEARTBEAT_FRAME):
|
||||
self.tx_frames.append(bytes(self._frame_buffer))
|
||||
self._frame_buffer.clear()
|
||||
self.ssr |= SCI_SSR_TDRE
|
||||
self.tx_ready_delay = 2
|
||||
self.tx_events.append(SciTxEvent(SCI1_TDR, value, self.scr, self.ssr, emitted))
|
||||
|
||||
def inject_rx(self, value: int) -> None:
|
||||
self.rdr = value & 0xFF
|
||||
self.ssr |= SCI_SSR_RDRF
|
||||
|
||||
def saw_heartbeat(self) -> bool:
|
||||
return HEARTBEAT_FRAME in self.tx_frames
|
||||
|
||||
def tick(self) -> None:
|
||||
if self.tx_ready_delay:
|
||||
self.tx_ready_delay -= 1
|
||||
if self.tx_ready_delay == 0:
|
||||
self.ssr |= SCI_SSR_TDRE
|
||||
Reference in New Issue
Block a user