UART simulation
This commit is contained in:
@@ -18,6 +18,7 @@ from .constants import (
|
||||
from .errors import UnsupportedInstruction
|
||||
from .memory import MemoryAccess
|
||||
from .runner import H8536Emulator
|
||||
from .uart import UartTiming
|
||||
|
||||
|
||||
CHECKSUM_SEED = 0x5A
|
||||
@@ -120,6 +121,8 @@ class RunContext:
|
||||
class FrameResult:
|
||||
input_frame: bytes
|
||||
checksum_ok: bool
|
||||
rx_injection: str
|
||||
uart_byte_cycles: int | None
|
||||
steps: int
|
||||
stopped_reason: str
|
||||
new_tx_bytes: bytes
|
||||
@@ -132,6 +135,7 @@ class FrameResult:
|
||||
def lines(self, index: int) -> list[str]:
|
||||
lines = [
|
||||
f"host_frame[{index}]={format_frame(self.input_frame)} checksum_ok={int(self.checksum_ok)}",
|
||||
f" rx_injection={self.rx_injection}",
|
||||
f" stopped={self.stopped_reason} steps={self.steps}",
|
||||
f" new_tx_bytes={format_frame(self.new_tx_bytes) if self.new_tx_bytes else 'none'}",
|
||||
]
|
||||
@@ -201,6 +205,8 @@ def run_rx_probe(
|
||||
boot_steps: int = 250_000,
|
||||
per_byte_steps: int = 5_000,
|
||||
post_frame_steps: int = 80_000,
|
||||
uart_timing: bool = False,
|
||||
uart_baud: int = 38_400,
|
||||
interval_steps: int = 512,
|
||||
frt1_ocia_steps: int | None = None,
|
||||
frt2_ocia_steps: int | None = None,
|
||||
@@ -236,6 +242,8 @@ def run_rx_probe(
|
||||
frame,
|
||||
per_byte_steps=per_byte_steps,
|
||||
post_frame_steps=post_frame_steps,
|
||||
uart_timing=uart_timing,
|
||||
uart_baud=uart_baud,
|
||||
stop_after_tx_frame=stop_after_tx_frame,
|
||||
)
|
||||
for frame in frames
|
||||
@@ -249,8 +257,10 @@ def build_arg_parser() -> argparse.ArgumentParser:
|
||||
parser.add_argument("--rom", type=Path, help="ROM image path; defaults to ROM/M27C512@DIP28_1.BIN when present")
|
||||
parser.add_argument("--preset", choices=("connect-lcd",), help="append a built-in host-frame set")
|
||||
parser.add_argument("--boot-steps", type=int, default=250_000, help="maximum steps to boot until SCI1 RXI is serviceable")
|
||||
parser.add_argument("--per-byte-steps", type=int, default=5_000, help="maximum steps after each injected RX byte")
|
||||
parser.add_argument("--per-byte-steps", type=int, default=5_000, help="polite mode byte-consume limit, or UART mode step limit between byte arrivals")
|
||||
parser.add_argument("--post-frame-steps", type=int, default=80_000, help="maximum steps after a full injected frame")
|
||||
parser.add_argument("--uart-timing", action="store_true", help="inject frame bytes at real 8N1 UART inter-byte timing instead of waiting for RDRF consumption")
|
||||
parser.add_argument("--uart-baud", type=parse_int, default=38_400, help="baud rate for --uart-timing; 38400 gives about 260 us per 8N1 byte")
|
||||
parser.add_argument("--keep-listening", action="store_true", help="use all post-frame steps instead of stopping at the first new TX frame")
|
||||
parser.add_argument("--interval-steps", type=int, default=512, help="rough step period for the scaffolded interval timer interrupt")
|
||||
parser.add_argument("--clock-hz", type=parse_int, default=10_000_000, help="CPU/phi clock in Hz for calibrated FRT timing")
|
||||
@@ -275,6 +285,8 @@ def main(argv: list[str] | None = None) -> int:
|
||||
boot_steps=args.boot_steps,
|
||||
per_byte_steps=args.per_byte_steps,
|
||||
post_frame_steps=args.post_frame_steps,
|
||||
uart_timing=args.uart_timing,
|
||||
uart_baud=args.uart_baud,
|
||||
interval_steps=args.interval_steps,
|
||||
frt1_ocia_steps=args.frt1_ocia_steps,
|
||||
frt2_ocia_steps=args.frt2_ocia_steps,
|
||||
@@ -300,6 +312,8 @@ def _run_frame(
|
||||
*,
|
||||
per_byte_steps: int,
|
||||
post_frame_steps: int,
|
||||
uart_timing: bool,
|
||||
uart_baud: int,
|
||||
stop_after_tx_frame: bool,
|
||||
) -> FrameResult:
|
||||
state_before = _state_snapshot(emulator)
|
||||
@@ -309,15 +323,29 @@ def _run_frame(
|
||||
context = RunContext()
|
||||
stopped_reason = "post_frame_steps"
|
||||
steps_total = 0
|
||||
timing = UartTiming(baud=uart_baud)
|
||||
|
||||
for offset, value in enumerate(frame):
|
||||
emulator.inject_sci1_rx_byte(value)
|
||||
steps, reason = _run_until(emulator, per_byte_steps, _rx_byte_consumed, context)
|
||||
steps_total += steps
|
||||
if reason != "predicate":
|
||||
stopped_reason = f"rx_byte_{offset}_{reason}"
|
||||
break
|
||||
if uart_timing:
|
||||
steps_total, stopped_reason = _inject_frame_uart_timed(
|
||||
emulator,
|
||||
frame,
|
||||
timing=timing,
|
||||
max_steps_per_gap=per_byte_steps,
|
||||
context=context,
|
||||
)
|
||||
injected_all_bytes = stopped_reason == "frame_injected_uart_timing"
|
||||
else:
|
||||
injected_all_bytes = True
|
||||
for offset, value in enumerate(frame):
|
||||
emulator.inject_sci1_rx_byte(value)
|
||||
steps, reason = _run_until(emulator, per_byte_steps, _rx_byte_consumed, context)
|
||||
steps_total += steps
|
||||
if reason != "predicate":
|
||||
stopped_reason = f"rx_byte_{offset}_{reason}"
|
||||
injected_all_bytes = False
|
||||
break
|
||||
|
||||
if injected_all_bytes:
|
||||
target_frame_count = tx_frame_start + 1
|
||||
|
||||
def post_predicate(inner: H8536Emulator) -> bool:
|
||||
@@ -332,6 +360,8 @@ def _run_frame(
|
||||
return FrameResult(
|
||||
input_frame=frame,
|
||||
checksum_ok=frame_checksum_ok(frame),
|
||||
rx_injection=timing.summary(emulator.clock_hz) if uart_timing else "polite_wait_for_rdrf_clear",
|
||||
uart_byte_cycles=timing.cycles_per_character(emulator.clock_hz) if uart_timing else None,
|
||||
steps=steps_total,
|
||||
stopped_reason=stopped_reason,
|
||||
new_tx_bytes=bytes(emulator.sci1.tx_bytes[tx_byte_start:]),
|
||||
@@ -343,6 +373,28 @@ def _run_frame(
|
||||
)
|
||||
|
||||
|
||||
def _inject_frame_uart_timed(
|
||||
emulator: H8536Emulator,
|
||||
frame: bytes,
|
||||
*,
|
||||
timing: UartTiming,
|
||||
max_steps_per_gap: int,
|
||||
context: RunContext,
|
||||
) -> tuple[int, str]:
|
||||
steps_total = 0
|
||||
start_cycles = emulator.cpu.cycles
|
||||
byte_cycles = timing.cycles_per_character(emulator.clock_hz)
|
||||
for offset, value in enumerate(frame):
|
||||
if offset:
|
||||
target_cycles = start_cycles + (offset * byte_cycles)
|
||||
steps, reason = _run_until_cycle(emulator, target_cycles, max_steps_per_gap, context)
|
||||
steps_total += steps
|
||||
if reason != "target_cycle":
|
||||
return steps_total, f"rx_byte_{offset}_{reason}"
|
||||
emulator.inject_sci1_rx_byte(value)
|
||||
return steps_total, "frame_injected_uart_timing"
|
||||
|
||||
|
||||
def _run_until(
|
||||
emulator: H8536Emulator,
|
||||
max_steps: int,
|
||||
@@ -362,6 +414,27 @@ def _run_until(
|
||||
return max_steps, "max_steps"
|
||||
|
||||
|
||||
def _run_until_cycle(
|
||||
emulator: H8536Emulator,
|
||||
target_cycles: int,
|
||||
max_steps: int,
|
||||
context: RunContext,
|
||||
) -> tuple[int, str]:
|
||||
for index in range(max(0, max_steps)):
|
||||
if emulator.cpu.cycles >= target_cycles:
|
||||
return index, "target_cycle"
|
||||
pc = emulator.cpu.pc
|
||||
context.record_pc(pc)
|
||||
try:
|
||||
emulator.step()
|
||||
except UnsupportedInstruction as exc:
|
||||
context.unsupported = str(exc)
|
||||
return index, "unsupported_instruction"
|
||||
if emulator.cpu.cycles >= target_cycles:
|
||||
return max(0, max_steps), "target_cycle"
|
||||
return max(0, max_steps), "max_steps"
|
||||
|
||||
|
||||
def _rx_ready(emulator: H8536Emulator) -> bool:
|
||||
if not (emulator.sci1.scr & SCI_SCR_RIE and emulator.sci1.scr & SCI_SCR_RE):
|
||||
return False
|
||||
@@ -477,4 +550,5 @@ __all__ = [
|
||||
"main",
|
||||
"parse_frame",
|
||||
"run_rx_probe",
|
||||
"UartTiming",
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user