UART simulation
This commit is contained in:
@@ -88,9 +88,9 @@ The real-device bench helper uses `pyserial`; install repo dependencies with `.\
|
|||||||
- Generates a separate C-like pseudocode view from the JSON, preserving labels, calls, branches, register names, inferred symbols, metadata comments, optional cycle notes, and simple structured `if`/`do while` patterns.
|
- Generates a separate C-like pseudocode view from the JSON, preserving labels, calls, branches, register names, inferred symbols, metadata comments, optional cycle notes, and simple structured `if`/`do while` patterns.
|
||||||
- Provides an early H8/536 emulator harness with ROM/RAM/register memory mapping, reset-vector boot, SCI1 transmit capture, MOV condition-code updates, `SCB/F`, stack/call/return support, indirect `JMP/JSR @Rn` dispatch, scaffolded SCI1 RXI/ERI/TXI and interval timer scheduling, manual-derived FRT1/FRT2 OCIA cycle scheduling, a P9 bit-banged bus model, a 16x4 LCD bus/DDRAM model for `H'F200`/`H'F201`, and an opt-in P9 transfer fast path.
|
- Provides an early H8/536 emulator harness with ROM/RAM/register memory mapping, reset-vector boot, SCI1 transmit capture, MOV condition-code updates, `SCB/F`, stack/call/return support, indirect `JMP/JSR @Rn` dispatch, scaffolded SCI1 RXI/ERI/TXI and interval timer scheduling, manual-derived FRT1/FRT2 OCIA cycle scheduling, a P9 bit-banged bus model, a 16x4 LCD bus/DDRAM model for `H'F200`/`H'F201`, and an opt-in P9 transfer fast path.
|
||||||
- Includes an emulator probe that reports hot PCs, recent P9/SCI accesses, serial report queue/gate traces, RAM lifecycle watches, final SCI1/TXI state, and captured P9 byte candidates while running the real ROM.
|
- Includes an emulator probe that reports hot PCs, recent P9/SCI accesses, serial report queue/gate traces, RAM lifecycle watches, final SCI1/TXI state, and captured P9 byte candidates while running the real ROM.
|
||||||
- Includes an RX command probe that boots until SCI1 RXI is serviceable, injects host six-byte frames through RDR/RDRF, listens for device TX frames, and reports serial latch/table/LCD-buffer and emulated-LCD effects.
|
- Includes an RX command probe that boots until SCI1 RXI is serviceable, injects host six-byte frames through RDR/RDRF, can optionally schedule 38400 8N1 byte arrivals at real UART spacing, listens for device TX frames, and reports serial latch/table/LCD-buffer and emulated-LCD effects.
|
||||||
- Includes a bench helper for replaying the emulator-derived CONNECT LCD frame sequence against the real device through COM5, with optional COM6 relay power cycling and timestamped capture logs.
|
- Includes a bench helper for replaying the emulator-derived CONNECT LCD frame sequence against the real device through COM5, with optional COM6 relay power cycling and timestamped capture logs.
|
||||||
- Includes a bench-log replay harness that feeds recorded host TX frames back into the ROM emulator and asserts parity against the real device's observed response/LCD state.
|
- Includes a bench-log replay harness that feeds recorded host TX frames back into the ROM emulator with bench-style UART byte timing by default and asserts parity against the real device's observed response/LCD state.
|
||||||
|
|
||||||
Current serial observations:
|
Current serial observations:
|
||||||
|
|
||||||
@@ -216,9 +216,10 @@ python h8536_emulator_rx_probe.py --help
|
|||||||
- `--trace-report-gates`, `--trace-report-queue`, and `--trace-ram-lifecycle`: inspect the serial report queue, `loc_4046`/`F9C4` gate, and watched RAM byte history.
|
- `--trace-report-gates`, `--trace-report-queue`, and `--trace-ram-lifecycle`: inspect the serial report queue, `loc_4046`/`F9C4` gate, and watched RAM byte history.
|
||||||
- `--target-frame "00 00 00 00 80 DA"`: compare staged/emitted TX bytes against an expected six-byte frame.
|
- `--target-frame "00 00 00 00 80 DA"`: compare staged/emitted TX bytes against an expected six-byte frame.
|
||||||
- `h8536_emulator_rx_probe.py "04 00 00 80 00"`: append the checksum, inject the host frame through SCI1 RX, and summarize responses.
|
- `h8536_emulator_rx_probe.py "04 00 00 80 00"`: append the checksum, inject the host frame through SCI1 RX, and summarize responses.
|
||||||
|
- `h8536_emulator_rx_probe.py --uart-timing --uart-baud 38400 "04 00 00 80 00"`: inject all six host bytes with 8N1 wire spacing of about 260 us per byte, letting RXI/TXI/timers interleave; if the ROM has not cleared `RDRF` before the next byte, the SCI model raises `ORER`.
|
||||||
- `h8536_emulator_rx_probe.py --preset connect-lcd`: replay the current CONNECT LCD activation candidates.
|
- `h8536_emulator_rx_probe.py --preset connect-lcd`: replay the current CONNECT LCD activation candidates.
|
||||||
- `scripts\bench_connect_lcd_sequence.py --port COM5 --relay-port COM6 --prompt-screen`: power-cycle the bench device, wait for heartbeat readiness, send `04 00 00 40 00 1E`, `04 00 00 80 00 DE`, `04 00 00 C0 00 9E`, log RX/TX, and prompt for observed LCD text.
|
- `scripts\bench_connect_lcd_sequence.py --port COM5 --relay-port COM6 --prompt-screen`: power-cycle the bench device, wait for heartbeat readiness, send `04 00 00 40 00 1E`, `04 00 00 80 00 DE`, `04 00 00 C0 00 9E`, log RX/TX, and prompt for observed LCD text.
|
||||||
- `h8536_emulator_bench_replay.py captures\bench-connect-lcd-sequence-20260525-214411.txt --assert-bench-parity`: replay a real bench log into the emulator and intentionally fail while any response/LCD state still diverges from the bench-observed `CONNECT NOT ACT` plus `07 80 C0 60 20 5D` path.
|
- `h8536_emulator_bench_replay.py captures\bench-connect-lcd-sequence-20260525-214411.txt --assert-bench-parity`: replay a real bench log into the emulator using timed UART RX by default and intentionally fail while any response/LCD state still diverges from the bench-observed `CONNECT NOT ACT` plus `07 80 C0 60 20 5D` path. Pass `--polite-rx` for the old wait-until-consumed injection mode.
|
||||||
- Current status: boots from `H'1000`, initializes SCI1, models the first P9 bit-banged handshakes, captures P9 byte candidates, can optionally fast-path known P9 routines, schedules FRT1/FRT2 OCIA from timer registers and `--clock-hz`, captures the ROM-driven LCD line ` CONNECT:NOT ACT`, and emits the observed heartbeat frame `00 00 00 00 80 DA`.
|
- Current status: boots from `H'1000`, initializes SCI1, models the first P9 bit-banged handshakes, captures P9 byte candidates, can optionally fast-path known P9 routines, schedules FRT1/FRT2 OCIA from timer registers and `--clock-hz`, captures the ROM-driven LCD line ` CONNECT:NOT ACT`, and emits the observed heartbeat frame `00 00 00 00 80 DA`.
|
||||||
|
|
||||||
## Code Layout
|
## Code Layout
|
||||||
@@ -250,7 +251,7 @@ python h8536_emulator_rx_probe.py --help
|
|||||||
- `h8536/report_source_trace.py`: direct `loc_3E54` report enqueue source tracer.
|
- `h8536/report_source_trace.py`: direct `loc_3E54` report enqueue source tracer.
|
||||||
- `h8536/table_xrefs.py`: table/index xrefs and LCD correlation report generation.
|
- `h8536/table_xrefs.py`: table/index xrefs and LCD correlation report generation.
|
||||||
- `h8536/consistency.py`: decompiler/pseudocode semantic consistency checks.
|
- `h8536/consistency.py`: decompiler/pseudocode semantic consistency checks.
|
||||||
- `h8536/emulator/`: early H8/536 emulator package split into CPU state, memory map, SCI1 TX capture, P9 bus model, LCD model, manual-derived FRT timer scheduling, runner, probe, CLI, and peripheral scaffolding.
|
- `h8536/emulator/`: early H8/536 emulator package split into CPU state, memory map, SCI1 TX capture, 38400 8N1 UART injection timing, P9 bus model, LCD model, manual-derived FRT timer scheduling, runner, probe, CLI, and peripheral scaffolding.
|
||||||
- `h8536/emulator/rx_probe.py`: host-frame injection and response/listener probe for SCI1 RX experiments.
|
- `h8536/emulator/rx_probe.py`: host-frame injection and response/listener probe for SCI1 RX experiments.
|
||||||
- `h8536/board_profile.py`: Sony RCP-TX7 board-trace annotations, including the MAX202 RS232 path.
|
- `h8536/board_profile.py`: Sony RCP-TX7 board-trace annotations, including the MAX202 RS232 path.
|
||||||
- `h8536/peripheral_access.py`: FRT/A-D TEMP-register access analysis.
|
- `h8536/peripheral_access.py`: FRT/A-D TEMP-register access analysis.
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ from .memory import MemoryAccess, MemoryMap, describe_regions
|
|||||||
from .peripherals import LCD
|
from .peripherals import LCD
|
||||||
from .runner import H8536Emulator, RunReport
|
from .runner import H8536Emulator, RunReport
|
||||||
from .sci import SCI1, SciTxEvent
|
from .sci import SCI1, SciTxEvent
|
||||||
|
from .uart import UartTiming
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"CPUState",
|
"CPUState",
|
||||||
@@ -114,6 +115,7 @@ __all__ = [
|
|||||||
"SCI_SSR_TDRE",
|
"SCI_SSR_TDRE",
|
||||||
"SciTxEvent",
|
"SciTxEvent",
|
||||||
"UnsupportedInstruction",
|
"UnsupportedInstruction",
|
||||||
|
"UartTiming",
|
||||||
"VECTOR_FRT1_OCIA",
|
"VECTOR_FRT1_OCIA",
|
||||||
"VECTOR_INTERVAL_TIMER",
|
"VECTOR_INTERVAL_TIMER",
|
||||||
"VECTOR_FRT2_OCIA",
|
"VECTOR_FRT2_OCIA",
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ from .errors import UnsupportedInstruction
|
|||||||
from .runner import H8536Emulator
|
from .runner import H8536Emulator
|
||||||
from .rx_probe import (
|
from .rx_probe import (
|
||||||
RunContext,
|
RunContext,
|
||||||
|
UartTiming,
|
||||||
|
_inject_frame_uart_timed,
|
||||||
_interrupt_mask,
|
_interrupt_mask,
|
||||||
_rx_byte_consumed,
|
_rx_byte_consumed,
|
||||||
_rx_ready,
|
_rx_ready,
|
||||||
@@ -88,6 +90,7 @@ class ReplayFrameResult:
|
|||||||
host_frame: bytes
|
host_frame: bytes
|
||||||
host_timestamp: str
|
host_timestamp: str
|
||||||
host_delta_ms: int
|
host_delta_ms: int
|
||||||
|
rx_injection: str
|
||||||
steps_before: int
|
steps_before: int
|
||||||
steps_during_rx: int
|
steps_during_rx: int
|
||||||
emulator_gap_frames_before: tuple[bytes, ...]
|
emulator_gap_frames_before: tuple[bytes, ...]
|
||||||
@@ -120,6 +123,7 @@ class BenchReplayResult:
|
|||||||
"host_timestamp": item.host_timestamp,
|
"host_timestamp": item.host_timestamp,
|
||||||
"host_delta_ms": item.host_delta_ms,
|
"host_delta_ms": item.host_delta_ms,
|
||||||
"host_frame": format_frame(item.host_frame),
|
"host_frame": format_frame(item.host_frame),
|
||||||
|
"rx_injection": item.rx_injection,
|
||||||
"steps_before": item.steps_before,
|
"steps_before": item.steps_before,
|
||||||
"steps_during_rx": item.steps_during_rx,
|
"steps_during_rx": item.steps_during_rx,
|
||||||
"emulator_gap_frames_before": [format_frame(frame) for frame in item.emulator_gap_frames_before],
|
"emulator_gap_frames_before": [format_frame(frame) for frame in item.emulator_gap_frames_before],
|
||||||
@@ -150,6 +154,7 @@ class BenchReplayResult:
|
|||||||
lines.append(
|
lines.append(
|
||||||
(
|
(
|
||||||
f" [{index}] {item.host_timestamp} delta={item.host_delta_ms}ms "
|
f" [{index}] {item.host_timestamp} delta={item.host_delta_ms}ms "
|
||||||
|
f"rx_injection={item.rx_injection} "
|
||||||
f"steps_before={item.steps_before} steps_rx={item.steps_during_rx} "
|
f"steps_before={item.steps_before} steps_rx={item.steps_during_rx} "
|
||||||
f"host={format_frame(item.host_frame)} "
|
f"host={format_frame(item.host_frame)} "
|
||||||
f"gap_emu={_format_frame_list(item.emulator_gap_frames_before)} "
|
f"gap_emu={_format_frame_list(item.emulator_gap_frames_before)} "
|
||||||
@@ -171,6 +176,8 @@ class ReplayConfig:
|
|||||||
frt1_ocia_steps: int | None = None
|
frt1_ocia_steps: int | None = None
|
||||||
frt2_ocia_steps: int | None = None
|
frt2_ocia_steps: int | None = None
|
||||||
clock_hz: int = 10_000_000
|
clock_hz: int = 10_000_000
|
||||||
|
uart_timing: bool = True
|
||||||
|
uart_baud: int = 38_400
|
||||||
p9_fast_path: bool = True
|
p9_fast_path: bool = True
|
||||||
p9_fast_input: int = 0xFF
|
p9_fast_input: int = 0xFF
|
||||||
|
|
||||||
@@ -256,12 +263,25 @@ def run_bench_replay(log_path: Path, *, rom_path: Path | None = None, config: Re
|
|||||||
steps_before = _run_cycles_for_ms(emulator, delta_ms, config.clock_hz, context)
|
steps_before = _run_cycles_for_ms(emulator, delta_ms, config.clock_hz, context)
|
||||||
gap_frames = tuple(emulator.sci1.tx_frames[tx_frame_start_before_delay:])
|
gap_frames = tuple(emulator.sci1.tx_frames[tx_frame_start_before_delay:])
|
||||||
tx_frame_start = len(emulator.sci1.tx_frames)
|
tx_frame_start = len(emulator.sci1.tx_frames)
|
||||||
|
if config.uart_timing:
|
||||||
|
timing = UartTiming(baud=config.uart_baud)
|
||||||
|
steps_during_rx, inject_reason = _inject_frame_uart_timed(
|
||||||
|
emulator,
|
||||||
|
host.frame,
|
||||||
|
timing=timing,
|
||||||
|
max_steps_per_gap=config.per_byte_steps,
|
||||||
|
context=context,
|
||||||
|
)
|
||||||
|
rx_injection = f"{timing.summary(emulator.clock_hz)} reason={inject_reason}"
|
||||||
|
else:
|
||||||
steps_during_rx = _inject_host_frame(emulator, host.frame, config.per_byte_steps, context)
|
steps_during_rx = _inject_host_frame(emulator, host.frame, config.per_byte_steps, context)
|
||||||
|
rx_injection = "polite_wait_for_rdrf_clear"
|
||||||
replay_results.append(
|
replay_results.append(
|
||||||
ReplayFrameResult(
|
ReplayFrameResult(
|
||||||
host_frame=host.frame,
|
host_frame=host.frame,
|
||||||
host_timestamp=host.timestamp,
|
host_timestamp=host.timestamp,
|
||||||
host_delta_ms=delta_ms,
|
host_delta_ms=delta_ms,
|
||||||
|
rx_injection=rx_injection,
|
||||||
steps_before=steps_before,
|
steps_before=steps_before,
|
||||||
steps_during_rx=steps_during_rx,
|
steps_during_rx=steps_during_rx,
|
||||||
emulator_gap_frames_before=gap_frames,
|
emulator_gap_frames_before=gap_frames,
|
||||||
@@ -336,10 +356,12 @@ def build_arg_parser() -> argparse.ArgumentParser:
|
|||||||
parser.add_argument("log", type=Path, help="bench log produced by scripts/bench_connect_lcd_sequence.py")
|
parser.add_argument("log", type=Path, help="bench log produced by scripts/bench_connect_lcd_sequence.py")
|
||||||
parser.add_argument("--rom", type=Path, help="ROM image path; defaults to ROM/M27C512@DIP28_1.BIN")
|
parser.add_argument("--rom", type=Path, help="ROM image path; defaults to ROM/M27C512@DIP28_1.BIN")
|
||||||
parser.add_argument("--boot-steps", type=int, default=ReplayConfig.boot_steps)
|
parser.add_argument("--boot-steps", type=int, default=ReplayConfig.boot_steps)
|
||||||
parser.add_argument("--per-byte-steps", type=int, default=ReplayConfig.per_byte_steps)
|
parser.add_argument("--per-byte-steps", type=int, default=ReplayConfig.per_byte_steps, help="UART mode step limit between byte arrivals, or polite mode byte-consume limit")
|
||||||
parser.add_argument("--post-log-steps", type=int, default=ReplayConfig.post_log_steps)
|
parser.add_argument("--post-log-steps", type=int, default=ReplayConfig.post_log_steps)
|
||||||
parser.add_argument("--interval-steps", type=int, default=ReplayConfig.interval_steps)
|
parser.add_argument("--interval-steps", type=int, default=ReplayConfig.interval_steps)
|
||||||
parser.add_argument("--clock-hz", type=lambda text: int(text, 0), default=ReplayConfig.clock_hz)
|
parser.add_argument("--clock-hz", type=lambda text: int(text, 0), default=ReplayConfig.clock_hz)
|
||||||
|
parser.add_argument("--uart-baud", type=lambda text: int(text, 0), default=ReplayConfig.uart_baud, help="baud rate for bench-style UART injection")
|
||||||
|
parser.add_argument("--polite-rx", action="store_true", help="wait for each RX byte to be consumed before injecting the next byte")
|
||||||
parser.add_argument("--frt1-ocia-steps", type=int, default=ReplayConfig.frt1_ocia_steps)
|
parser.add_argument("--frt1-ocia-steps", type=int, default=ReplayConfig.frt1_ocia_steps)
|
||||||
parser.add_argument("--frt2-ocia-steps", type=int, default=ReplayConfig.frt2_ocia_steps)
|
parser.add_argument("--frt2-ocia-steps", type=int, default=ReplayConfig.frt2_ocia_steps)
|
||||||
parser.add_argument("--no-p9-fast-path", action="store_true", help="disable shortcut handling for known P9 routines")
|
parser.add_argument("--no-p9-fast-path", action="store_true", help="disable shortcut handling for known P9 routines")
|
||||||
@@ -362,6 +384,8 @@ def main(argv: list[str] | None = None) -> int:
|
|||||||
frt1_ocia_steps=args.frt1_ocia_steps,
|
frt1_ocia_steps=args.frt1_ocia_steps,
|
||||||
frt2_ocia_steps=args.frt2_ocia_steps,
|
frt2_ocia_steps=args.frt2_ocia_steps,
|
||||||
clock_hz=args.clock_hz,
|
clock_hz=args.clock_hz,
|
||||||
|
uart_timing=not args.polite_rx,
|
||||||
|
uart_baud=args.uart_baud,
|
||||||
p9_fast_path=not args.no_p9_fast_path,
|
p9_fast_path=not args.no_p9_fast_path,
|
||||||
p9_fast_input=args.p9_fast_input,
|
p9_fast_input=args.p9_fast_input,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ from .constants import (
|
|||||||
from .errors import UnsupportedInstruction
|
from .errors import UnsupportedInstruction
|
||||||
from .memory import MemoryAccess
|
from .memory import MemoryAccess
|
||||||
from .runner import H8536Emulator
|
from .runner import H8536Emulator
|
||||||
|
from .uart import UartTiming
|
||||||
|
|
||||||
|
|
||||||
CHECKSUM_SEED = 0x5A
|
CHECKSUM_SEED = 0x5A
|
||||||
@@ -120,6 +121,8 @@ class RunContext:
|
|||||||
class FrameResult:
|
class FrameResult:
|
||||||
input_frame: bytes
|
input_frame: bytes
|
||||||
checksum_ok: bool
|
checksum_ok: bool
|
||||||
|
rx_injection: str
|
||||||
|
uart_byte_cycles: int | None
|
||||||
steps: int
|
steps: int
|
||||||
stopped_reason: str
|
stopped_reason: str
|
||||||
new_tx_bytes: bytes
|
new_tx_bytes: bytes
|
||||||
@@ -132,6 +135,7 @@ class FrameResult:
|
|||||||
def lines(self, index: int) -> list[str]:
|
def lines(self, index: int) -> list[str]:
|
||||||
lines = [
|
lines = [
|
||||||
f"host_frame[{index}]={format_frame(self.input_frame)} checksum_ok={int(self.checksum_ok)}",
|
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" stopped={self.stopped_reason} steps={self.steps}",
|
||||||
f" new_tx_bytes={format_frame(self.new_tx_bytes) if self.new_tx_bytes else 'none'}",
|
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,
|
boot_steps: int = 250_000,
|
||||||
per_byte_steps: int = 5_000,
|
per_byte_steps: int = 5_000,
|
||||||
post_frame_steps: int = 80_000,
|
post_frame_steps: int = 80_000,
|
||||||
|
uart_timing: bool = False,
|
||||||
|
uart_baud: int = 38_400,
|
||||||
interval_steps: int = 512,
|
interval_steps: int = 512,
|
||||||
frt1_ocia_steps: int | None = None,
|
frt1_ocia_steps: int | None = None,
|
||||||
frt2_ocia_steps: int | None = None,
|
frt2_ocia_steps: int | None = None,
|
||||||
@@ -236,6 +242,8 @@ def run_rx_probe(
|
|||||||
frame,
|
frame,
|
||||||
per_byte_steps=per_byte_steps,
|
per_byte_steps=per_byte_steps,
|
||||||
post_frame_steps=post_frame_steps,
|
post_frame_steps=post_frame_steps,
|
||||||
|
uart_timing=uart_timing,
|
||||||
|
uart_baud=uart_baud,
|
||||||
stop_after_tx_frame=stop_after_tx_frame,
|
stop_after_tx_frame=stop_after_tx_frame,
|
||||||
)
|
)
|
||||||
for frame in frames
|
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("--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("--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("--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("--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("--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("--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")
|
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,
|
boot_steps=args.boot_steps,
|
||||||
per_byte_steps=args.per_byte_steps,
|
per_byte_steps=args.per_byte_steps,
|
||||||
post_frame_steps=args.post_frame_steps,
|
post_frame_steps=args.post_frame_steps,
|
||||||
|
uart_timing=args.uart_timing,
|
||||||
|
uart_baud=args.uart_baud,
|
||||||
interval_steps=args.interval_steps,
|
interval_steps=args.interval_steps,
|
||||||
frt1_ocia_steps=args.frt1_ocia_steps,
|
frt1_ocia_steps=args.frt1_ocia_steps,
|
||||||
frt2_ocia_steps=args.frt2_ocia_steps,
|
frt2_ocia_steps=args.frt2_ocia_steps,
|
||||||
@@ -300,6 +312,8 @@ def _run_frame(
|
|||||||
*,
|
*,
|
||||||
per_byte_steps: int,
|
per_byte_steps: int,
|
||||||
post_frame_steps: int,
|
post_frame_steps: int,
|
||||||
|
uart_timing: bool,
|
||||||
|
uart_baud: int,
|
||||||
stop_after_tx_frame: bool,
|
stop_after_tx_frame: bool,
|
||||||
) -> FrameResult:
|
) -> FrameResult:
|
||||||
state_before = _state_snapshot(emulator)
|
state_before = _state_snapshot(emulator)
|
||||||
@@ -309,15 +323,29 @@ def _run_frame(
|
|||||||
context = RunContext()
|
context = RunContext()
|
||||||
stopped_reason = "post_frame_steps"
|
stopped_reason = "post_frame_steps"
|
||||||
steps_total = 0
|
steps_total = 0
|
||||||
|
timing = UartTiming(baud=uart_baud)
|
||||||
|
|
||||||
|
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):
|
for offset, value in enumerate(frame):
|
||||||
emulator.inject_sci1_rx_byte(value)
|
emulator.inject_sci1_rx_byte(value)
|
||||||
steps, reason = _run_until(emulator, per_byte_steps, _rx_byte_consumed, context)
|
steps, reason = _run_until(emulator, per_byte_steps, _rx_byte_consumed, context)
|
||||||
steps_total += steps
|
steps_total += steps
|
||||||
if reason != "predicate":
|
if reason != "predicate":
|
||||||
stopped_reason = f"rx_byte_{offset}_{reason}"
|
stopped_reason = f"rx_byte_{offset}_{reason}"
|
||||||
|
injected_all_bytes = False
|
||||||
break
|
break
|
||||||
else:
|
|
||||||
|
if injected_all_bytes:
|
||||||
target_frame_count = tx_frame_start + 1
|
target_frame_count = tx_frame_start + 1
|
||||||
|
|
||||||
def post_predicate(inner: H8536Emulator) -> bool:
|
def post_predicate(inner: H8536Emulator) -> bool:
|
||||||
@@ -332,6 +360,8 @@ def _run_frame(
|
|||||||
return FrameResult(
|
return FrameResult(
|
||||||
input_frame=frame,
|
input_frame=frame,
|
||||||
checksum_ok=frame_checksum_ok(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,
|
steps=steps_total,
|
||||||
stopped_reason=stopped_reason,
|
stopped_reason=stopped_reason,
|
||||||
new_tx_bytes=bytes(emulator.sci1.tx_bytes[tx_byte_start:]),
|
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(
|
def _run_until(
|
||||||
emulator: H8536Emulator,
|
emulator: H8536Emulator,
|
||||||
max_steps: int,
|
max_steps: int,
|
||||||
@@ -362,6 +414,27 @@ def _run_until(
|
|||||||
return max_steps, "max_steps"
|
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:
|
def _rx_ready(emulator: H8536Emulator) -> bool:
|
||||||
if not (emulator.sci1.scr & SCI_SCR_RIE and emulator.sci1.scr & SCI_SCR_RE):
|
if not (emulator.sci1.scr & SCI_SCR_RIE and emulator.sci1.scr & SCI_SCR_RE):
|
||||||
return False
|
return False
|
||||||
@@ -477,4 +550,5 @@ __all__ = [
|
|||||||
"main",
|
"main",
|
||||||
"parse_frame",
|
"parse_frame",
|
||||||
"run_rx_probe",
|
"run_rx_probe",
|
||||||
|
"UartTiming",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -105,6 +105,11 @@ class SCI1:
|
|||||||
self.ssr = (self.ssr & (writable_zero_flags & value)) | (value & ~writable_zero_flags)
|
self.ssr = (self.ssr & (writable_zero_flags & value)) | (value & ~writable_zero_flags)
|
||||||
|
|
||||||
def inject_rx(self, value: int) -> None:
|
def inject_rx(self, value: int) -> None:
|
||||||
|
if self.ssr & SCI_SSR_ORER:
|
||||||
|
return
|
||||||
|
if self.ssr & SCI_SSR_RDRF:
|
||||||
|
self.ssr |= SCI_SSR_ORER
|
||||||
|
return
|
||||||
self.rdr = value & 0xFF
|
self.rdr = value & 0xFF
|
||||||
self.ssr |= SCI_SSR_RDRF
|
self.ssr |= SCI_SSR_RDRF
|
||||||
|
|
||||||
|
|||||||
35
h8536/emulator/uart.py
Normal file
35
h8536/emulator/uart.py
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class UartTiming:
|
||||||
|
baud: int = 38_400
|
||||||
|
data_bits: int = 8
|
||||||
|
parity_bits: int = 0
|
||||||
|
stop_bits: int = 1
|
||||||
|
start_bits: int = 1
|
||||||
|
|
||||||
|
@property
|
||||||
|
def bits_per_character(self) -> int:
|
||||||
|
return self.start_bits + self.data_bits + self.parity_bits + self.stop_bits
|
||||||
|
|
||||||
|
def seconds_per_character(self) -> float:
|
||||||
|
return self.bits_per_character / max(1, self.baud)
|
||||||
|
|
||||||
|
def micros_per_character(self) -> float:
|
||||||
|
return 1_000_000.0 * self.seconds_per_character()
|
||||||
|
|
||||||
|
def cycles_per_character(self, clock_hz: int) -> int:
|
||||||
|
return max(1, round(max(1, clock_hz) * self.seconds_per_character()))
|
||||||
|
|
||||||
|
def summary(self, clock_hz: int) -> str:
|
||||||
|
return (
|
||||||
|
f"uart_{self.data_bits}{'N' if self.parity_bits == 0 else 'P'}{self.stop_bits} "
|
||||||
|
f"baud={self.baud} byte_us={self.micros_per_character():.3f} "
|
||||||
|
f"byte_cycles={self.cycles_per_character(clock_hz)}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ["UartTiming"]
|
||||||
@@ -1,7 +1,15 @@
|
|||||||
import argparse
|
import argparse
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from h8536.emulator.rx_probe import frame_checksum, frame_checksum_ok, parse_frame
|
from h8536.emulator import H8536Emulator, SCI1_RDR, SCI1_SSR, SCI_SSR_ORER, SCI_SSR_RDRF
|
||||||
|
from h8536.emulator.rx_probe import RunContext, UartTiming, _inject_frame_uart_timed, frame_checksum, frame_checksum_ok, parse_frame
|
||||||
|
|
||||||
|
|
||||||
|
def rom_with_reset(*, reset: int = 0x1000, size: int = 0x1020) -> bytearray:
|
||||||
|
rom = bytearray([0xFF] * size)
|
||||||
|
rom[0:2] = reset.to_bytes(2, "big")
|
||||||
|
rom[reset : reset + 2] = b"\x20\xFE" # BRA self
|
||||||
|
return rom
|
||||||
|
|
||||||
|
|
||||||
class EmulatorRxProbeTest(unittest.TestCase):
|
class EmulatorRxProbeTest(unittest.TestCase):
|
||||||
@@ -22,6 +30,23 @@ class EmulatorRxProbeTest(unittest.TestCase):
|
|||||||
with self.assertRaises(argparse.ArgumentTypeError):
|
with self.assertRaises(argparse.ArgumentTypeError):
|
||||||
parse_frame("04 00 00 40")
|
parse_frame("04 00 00 40")
|
||||||
|
|
||||||
|
def test_uart_timed_injection_does_not_wait_for_rdrf_consumption(self):
|
||||||
|
emulator = H8536Emulator(bytes(rom_with_reset()), clock_hz=10_000_000)
|
||||||
|
context = RunContext()
|
||||||
|
|
||||||
|
steps, reason = _inject_frame_uart_timed(
|
||||||
|
emulator,
|
||||||
|
b"\x11\x22",
|
||||||
|
timing=UartTiming(baud=38_400),
|
||||||
|
max_steps_per_gap=1000,
|
||||||
|
context=context,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(reason, "frame_injected_uart_timing")
|
||||||
|
self.assertGreater(steps, 0)
|
||||||
|
self.assertEqual(emulator.memory.read8(SCI1_RDR), 0x11)
|
||||||
|
self.assertEqual(emulator.memory.read8(SCI1_SSR) & (SCI_SSR_RDRF | SCI_SSR_ORER), SCI_SSR_RDRF | SCI_SSR_ORER)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ from h8536.emulator import (
|
|||||||
SCI_SSR_RDRF,
|
SCI_SSR_RDRF,
|
||||||
SCI_SSR_TDRE,
|
SCI_SSR_TDRE,
|
||||||
SCI1,
|
SCI1,
|
||||||
|
UartTiming,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -66,6 +67,22 @@ class SciTimingTest(unittest.TestCase):
|
|||||||
self.assertEqual(sci.tx_frames, [HEARTBEAT_FRAME])
|
self.assertEqual(sci.tx_frames, [HEARTBEAT_FRAME])
|
||||||
self.assertTrue(sci.saw_heartbeat())
|
self.assertTrue(sci.saw_heartbeat())
|
||||||
|
|
||||||
|
def test_inject_rx_sets_overrun_if_rdrf_is_still_full(self):
|
||||||
|
sci = SCI1()
|
||||||
|
|
||||||
|
sci.inject_rx(0x11)
|
||||||
|
sci.inject_rx(0x22)
|
||||||
|
|
||||||
|
self.assertEqual(sci.read(SCI1_SSR) & (SCI_SSR_RDRF | SCI_SSR_ORER), SCI_SSR_RDRF | SCI_SSR_ORER)
|
||||||
|
self.assertEqual(sci.rdr, 0x11)
|
||||||
|
|
||||||
|
def test_uart_8n1_38400_byte_timing(self):
|
||||||
|
timing = UartTiming(baud=38_400)
|
||||||
|
|
||||||
|
self.assertEqual(timing.bits_per_character, 10)
|
||||||
|
self.assertAlmostEqual(timing.micros_per_character(), 260.416666, places=3)
|
||||||
|
self.assertEqual(timing.cycles_per_character(10_000_000), 2604)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
Reference in New Issue
Block a user