120 lines
3.5 KiB
Python
120 lines
3.5 KiB
Python
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_FER,
|
|
SCI_SSR_ORER,
|
|
SCI_SSR_PER,
|
|
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.
|
|
- SSR status flags are R/(W)*: writing 0 clears a latched flag, while
|
|
writing 1 leaves the hardware-owned flag unchanged.
|
|
"""
|
|
|
|
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
|
|
tx_ready_ticks: int = 2
|
|
_tx_ready_pending: bool = False
|
|
|
|
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:
|
|
self._write_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.tx_ready_delay = max(0, self.tx_ready_ticks)
|
|
self._tx_ready_pending = True
|
|
self.tx_events.append(SciTxEvent(SCI1_TDR, value, self.scr, self.ssr, emitted))
|
|
|
|
def _write_ssr(self, value: int) -> None:
|
|
writable_zero_flags = SCI_SSR_TDRE | SCI_SSR_RDRF | SCI_SSR_ORER | SCI_SSR_FER | SCI_SSR_PER
|
|
self.ssr = (self.ssr & (writable_zero_flags & value)) | (value & ~writable_zero_flags)
|
|
|
|
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_pending and self.tx_ready_delay:
|
|
self.tx_ready_delay -= 1
|
|
if self._tx_ready_pending and self.tx_ready_delay == 0 and not (self.ssr & SCI_SSR_TDRE):
|
|
self.ssr |= SCI_SSR_TDRE
|
|
self._tx_ready_pending = False
|