Emualtor RX side
This commit is contained in:
11
README.md
11
README.md
@@ -42,6 +42,7 @@ To start the current emulator harness:
|
|||||||
.\.venv\Scripts\python.exe h8536_emulator.py --max-steps 1000000 --stop-on-heartbeat --interval-steps 512
|
.\.venv\Scripts\python.exe h8536_emulator.py --max-steps 1000000 --stop-on-heartbeat --interval-steps 512
|
||||||
.\.venv\Scripts\python.exe h8536_emulator_probe.py --max-steps 4000000 --stop-on-tx
|
.\.venv\Scripts\python.exe h8536_emulator_probe.py --max-steps 4000000 --stop-on-tx
|
||||||
.\.venv\Scripts\python.exe h8536_emulator_probe.py --max-steps 1000000 --stop-on-tx --p9-fast-path
|
.\.venv\Scripts\python.exe h8536_emulator_probe.py --max-steps 1000000 --stop-on-tx --p9-fast-path
|
||||||
|
.\.venv\Scripts\python.exe h8536_emulator_rx_probe.py --preset connect-lcd
|
||||||
```
|
```
|
||||||
|
|
||||||
## What It Does
|
## What It Does
|
||||||
@@ -81,8 +82,9 @@ To start the current emulator harness:
|
|||||||
- Handles the E-clock transfer instructions `MOVFPE` and `MOVTPE`.
|
- Handles the E-clock transfer instructions `MOVFPE` and `MOVTPE`.
|
||||||
- Recognizes likely LCD E-clock access routines at `H'F200`/`H'F201`, including busy-flag polling and data/control writes.
|
- Recognizes likely LCD E-clock access routines at `H'F200`/`H'F201`, including busy-flag polling and data/control writes.
|
||||||
- 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, scaffolded SCI1 TXI/interval/FRT1-OCIA/FRT2-OCIA interrupt scheduling, a P9 bit-banged bus model, 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/FRT1-OCIA/FRT2-OCIA interrupt scheduling, a P9 bit-banged bus model, 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 effects.
|
||||||
|
|
||||||
Current serial observations:
|
Current serial observations:
|
||||||
|
|
||||||
@@ -92,6 +94,7 @@ Current serial observations:
|
|||||||
- Idle cadence from the reference file: 54 frames, average about 699.9 ms, min 601 ms, max 803 ms.
|
- Idle cadence from the reference file: 54 frames, average about 699.9 ms, min 601 ms, max 803 ms.
|
||||||
- Static/runtime finding: `F9C4` is a candidate idle heartbeat/report countdown. Init loads `H'14`, `loc_BA26` reloads `H'07` after a send, FRT2 OCIA decrements it, and `loc_4046` can enqueue report `H'0000` when it reaches zero and the queue is empty.
|
- Static/runtime finding: `F9C4` is a candidate idle heartbeat/report countdown. Init loads `H'14`, `loc_BA26` reloads `H'07` after a send, FRT2 OCIA decrements it, and `loc_4046` can enqueue report `H'0000` when it reaches zero and the queue is empty.
|
||||||
- Runtime-confirmed heartbeat path: `loc_4067` writes `H'0000` into the queue via a zero-extended word move, `loc_BAF2/loc_BB08` dequeue it, `loc_BB1C/loc_BB20/loc_BB2B` stage the TX bytes, and `loc_BA26` emits `00 00 00 00 80 DA`.
|
- Runtime-confirmed heartbeat path: `loc_4067` writes `H'0000` into the queue via a zero-extended word move, `loc_BAF2/loc_BB08` dequeue it, `loc_BB1C/loc_BB20/loc_BB2B` stage the TX bytes, and `loc_BA26` emits `00 00 00 00 80 DA`.
|
||||||
|
- RX probe finding: the `--preset connect-lcd` sequence reaches the command-`0x04` handler; `04 00 00 80 00 DE` writes table index zero, fills the LCD line buffer with `CONNECT: OK`, and emits `02 00 02 00 00 5A` in the current emulator model.
|
||||||
- Observed capture labels such as `cam_power_button_candidate` and `call_button_candidate` are deliberately treated as capture overlays, not protocol facts hard-coded in ROM.
|
- Observed capture labels such as `cam_power_button_candidate` and `call_button_candidate` are deliberately treated as capture overlays, not protocol facts hard-coded in ROM.
|
||||||
|
|
||||||
The generated listing is written to:
|
The generated listing is written to:
|
||||||
@@ -190,6 +193,7 @@ For the emulator harness:
|
|||||||
```powershell
|
```powershell
|
||||||
python h8536_emulator.py --help
|
python h8536_emulator.py --help
|
||||||
python h8536_emulator_probe.py --help
|
python h8536_emulator_probe.py --help
|
||||||
|
python h8536_emulator_rx_probe.py --help
|
||||||
```
|
```
|
||||||
|
|
||||||
- `--rom PATH`: use an explicit ROM path instead of auto-discovering `ROM\M27C512@DIP28_1.BIN`.
|
- `--rom PATH`: use an explicit ROM path instead of auto-discovering `ROM\M27C512@DIP28_1.BIN`.
|
||||||
@@ -201,6 +205,8 @@ python h8536_emulator_probe.py --help
|
|||||||
- `--p9-fast-path`: shortcut known P9 transfer routines for exploration.
|
- `--p9-fast-path`: shortcut known P9 transfer routines for exploration.
|
||||||
- `--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 --preset connect-lcd`: replay the current CONNECT LCD activation candidates.
|
||||||
- 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, and schedules FRT1/FRT2 OCIA. With the P9 fast path and current timer cadence, the emulator reaches the SCI1 transmit path 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, and schedules FRT1/FRT2 OCIA. With the P9 fast path and current timer cadence, the emulator reaches the SCI1 transmit path and emits the observed heartbeat frame `00 00 00 00 80 DA`.
|
||||||
|
|
||||||
## Code Layout
|
## Code Layout
|
||||||
@@ -233,6 +239,7 @@ python h8536_emulator_probe.py --help
|
|||||||
- `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, runner, probe, CLI, and peripheral scaffolding.
|
- `h8536/emulator/`: early H8/536 emulator package split into CPU state, memory map, SCI1 TX capture, P9 bus model, runner, probe, CLI, and peripheral scaffolding.
|
||||||
|
- `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.
|
||||||
- `h8536/pseudocode.py`: JSON-to-C-like pseudocode generation.
|
- `h8536/pseudocode.py`: JSON-to-C-like pseudocode generation.
|
||||||
@@ -242,4 +249,4 @@ python h8536_emulator_probe.py --help
|
|||||||
- `h8536_serial_pseudocode.py`: focused serial pseudocode CLI wrapper.
|
- `h8536_serial_pseudocode.py`: focused serial pseudocode CLI wrapper.
|
||||||
- `h8536_protocol_trace.py`, `h8536_protocol_capture.py`: protocol analysis CLI wrappers.
|
- `h8536_protocol_trace.py`, `h8536_protocol_capture.py`: protocol analysis CLI wrappers.
|
||||||
- `h8536_serial_gate.py`, `h8536_report_source_trace.py`, `h8536_table_xrefs.py`, `h8536_consistency.py`: sidecar analysis CLI wrappers.
|
- `h8536_serial_gate.py`, `h8536_report_source_trace.py`, `h8536_table_xrefs.py`, `h8536_consistency.py`: sidecar analysis CLI wrappers.
|
||||||
- `h8536_emulator.py`, `h8536_emulator_probe.py`: emulator CLI wrappers.
|
- `h8536_emulator.py`, `h8536_emulator_probe.py`, `h8536_emulator_rx_probe.py`: emulator CLI wrappers.
|
||||||
|
|||||||
@@ -37,6 +37,8 @@ from .constants import (
|
|||||||
VECTOR_FRT1_OCIA,
|
VECTOR_FRT1_OCIA,
|
||||||
VECTOR_INTERVAL_TIMER,
|
VECTOR_INTERVAL_TIMER,
|
||||||
VECTOR_FRT2_OCIA,
|
VECTOR_FRT2_OCIA,
|
||||||
|
VECTOR_SCI1_ERI,
|
||||||
|
VECTOR_SCI1_RXI,
|
||||||
VECTOR_SCI1_TXI,
|
VECTOR_SCI1_TXI,
|
||||||
WDT_TCSR_R,
|
WDT_TCSR_R,
|
||||||
)
|
)
|
||||||
@@ -95,6 +97,8 @@ __all__ = [
|
|||||||
"VECTOR_FRT1_OCIA",
|
"VECTOR_FRT1_OCIA",
|
||||||
"VECTOR_INTERVAL_TIMER",
|
"VECTOR_INTERVAL_TIMER",
|
||||||
"VECTOR_FRT2_OCIA",
|
"VECTOR_FRT2_OCIA",
|
||||||
|
"VECTOR_SCI1_ERI",
|
||||||
|
"VECTOR_SCI1_RXI",
|
||||||
"VECTOR_SCI1_TXI",
|
"VECTOR_SCI1_TXI",
|
||||||
"WDT_TCSR_R",
|
"WDT_TCSR_R",
|
||||||
"build_arg_parser",
|
"build_arg_parser",
|
||||||
|
|||||||
@@ -43,4 +43,6 @@ HEARTBEAT_FRAME = bytes([0x00, 0x00, 0x00, 0x00, 0x80, 0xDA])
|
|||||||
VECTOR_INTERVAL_TIMER = 0x0042
|
VECTOR_INTERVAL_TIMER = 0x0042
|
||||||
VECTOR_FRT1_OCIA = 0x0062
|
VECTOR_FRT1_OCIA = 0x0062
|
||||||
VECTOR_FRT2_OCIA = 0x006A
|
VECTOR_FRT2_OCIA = 0x006A
|
||||||
|
VECTOR_SCI1_ERI = 0x0080
|
||||||
|
VECTOR_SCI1_RXI = 0x0082
|
||||||
VECTOR_SCI1_TXI = 0x0084
|
VECTOR_SCI1_TXI = 0x0084
|
||||||
|
|||||||
@@ -115,6 +115,11 @@ class MemoryMap:
|
|||||||
self.write8(address, (value >> 8) & 0xFF)
|
self.write8(address, (value >> 8) & 0xFF)
|
||||||
self.write8((address + 1) & 0xFFFF, value & 0xFF)
|
self.write8((address + 1) & 0xFFFF, value & 0xFF)
|
||||||
|
|
||||||
|
def inject_sci1_rx_byte(self, value: int) -> None:
|
||||||
|
self.sci1.inject_rx(value)
|
||||||
|
self._set_register(SCI1_RDR, self.sci1.read(SCI1_RDR))
|
||||||
|
self._set_register(SCI1_SSR, self.sci1.read(SCI1_SSR))
|
||||||
|
|
||||||
def _set_register(self, address: int, value: int) -> None:
|
def _set_register(self, address: int, value: int) -> None:
|
||||||
self.registers[address - REGISTER_FIELD_START] = value & 0xFF
|
self.registers[address - REGISTER_FIELD_START] = value & 0xFF
|
||||||
|
|
||||||
|
|||||||
@@ -16,11 +16,19 @@ from .constants import (
|
|||||||
IPRA,
|
IPRA,
|
||||||
IPRC,
|
IPRC,
|
||||||
IPRE,
|
IPRE,
|
||||||
|
SCI_SCR_RE,
|
||||||
|
SCI_SCR_RIE,
|
||||||
SCI_SCR_TIE,
|
SCI_SCR_TIE,
|
||||||
|
SCI_SSR_FER,
|
||||||
|
SCI_SSR_ORER,
|
||||||
|
SCI_SSR_PER,
|
||||||
|
SCI_SSR_RDRF,
|
||||||
SCI_SSR_TDRE,
|
SCI_SSR_TDRE,
|
||||||
VECTOR_FRT1_OCIA,
|
VECTOR_FRT1_OCIA,
|
||||||
VECTOR_FRT2_OCIA,
|
VECTOR_FRT2_OCIA,
|
||||||
VECTOR_INTERVAL_TIMER,
|
VECTOR_INTERVAL_TIMER,
|
||||||
|
VECTOR_SCI1_ERI,
|
||||||
|
VECTOR_SCI1_RXI,
|
||||||
VECTOR_SCI1_TXI,
|
VECTOR_SCI1_TXI,
|
||||||
)
|
)
|
||||||
from .cpu import CPUState, mask, s8, s16, sign_bit
|
from .cpu import CPUState, mask, s8, s16, sign_bit
|
||||||
@@ -97,6 +105,9 @@ class H8536Emulator:
|
|||||||
return self.memory.rom.u16(0)
|
return self.memory.rom.u16(0)
|
||||||
raise DecodeError("ROM does not contain a reset vector at H'0000")
|
raise DecodeError("ROM does not contain a reset vector at H'0000")
|
||||||
|
|
||||||
|
def inject_sci1_rx_byte(self, value: int) -> None:
|
||||||
|
self.memory.inject_sci1_rx_byte(value)
|
||||||
|
|
||||||
def step(self) -> str:
|
def step(self) -> str:
|
||||||
pc = self.cpu.pc
|
pc = self.cpu.pc
|
||||||
if self.p9_fast_path.try_handle(self):
|
if self.p9_fast_path.try_handle(self):
|
||||||
@@ -116,6 +127,8 @@ class H8536Emulator:
|
|||||||
pass
|
pass
|
||||||
elif raw[0] == 0x02 and len(raw) == 2:
|
elif raw[0] == 0x02 and len(raw) == 2:
|
||||||
self._pop_register_mask(raw[1])
|
self._pop_register_mask(raw[1])
|
||||||
|
elif raw[0] == 0x11 and len(raw) >= 2:
|
||||||
|
next_pc = self._indirect_jump_call(raw, pc, next_pc)
|
||||||
elif raw[0] == 0x12 and len(raw) == 2:
|
elif raw[0] == 0x12 and len(raw) == 2:
|
||||||
self._push_register_mask(raw[1])
|
self._push_register_mask(raw[1])
|
||||||
elif raw[0] in (0x01, 0x06, 0x07) and len(raw) == 3 and 0xB8 <= raw[1] <= 0xBF:
|
elif raw[0] in (0x01, 0x06, 0x07) and len(raw) == 3 and 0xB8 <= raw[1] <= 0xBF:
|
||||||
@@ -366,6 +379,20 @@ class H8536Emulator:
|
|||||||
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")
|
return int.from_bytes(raw[1:3], "big")
|
||||||
|
|
||||||
|
def _indirect_jump_call(self, raw: bytes, pc: int, next_pc: int) -> int:
|
||||||
|
op = raw[1]
|
||||||
|
if 0xC0 <= op <= 0xDF:
|
||||||
|
target = self.cpu.regs[op & 0x07] & 0xFFFF
|
||||||
|
elif 0xE0 <= op <= 0xEF and len(raw) >= 3:
|
||||||
|
target = (self.cpu.regs[op & 0x07] + s8(raw[2])) & 0xFFFF
|
||||||
|
elif 0xF0 <= op <= 0xFF and len(raw) >= 4:
|
||||||
|
target = (self.cpu.regs[op & 0x07] + s16(int.from_bytes(raw[2:4], "big"))) & 0xFFFF
|
||||||
|
else:
|
||||||
|
raise UnsupportedInstruction(pc, raw, H8536Decoder(self.memory.rom, br=self.cpu.br).decode(pc).text)
|
||||||
|
if 0xC8 <= op <= 0xCF or 0xD8 <= op <= 0xDF or 0xE8 <= op <= 0xEF or 0xF8 <= op <= 0xFF:
|
||||||
|
self._push16(next_pc)
|
||||||
|
return target
|
||||||
|
|
||||||
def _scb(self, raw: bytes, pc: int, next_pc: int) -> int:
|
def _scb(self, raw: bytes, pc: int, next_pc: int) -> int:
|
||||||
reg = raw[1] & 0x07
|
reg = raw[1] & 0x07
|
||||||
condition = {0x01: False, 0x06: not self.cpu.z, 0x07: self.cpu.z}[raw[0]]
|
condition = {0x01: False, 0x06: not self.cpu.z, 0x07: self.cpu.z}[raw[0]]
|
||||||
@@ -389,6 +416,15 @@ class H8536Emulator:
|
|||||||
if self.cpu.interrupt_depth:
|
if self.cpu.interrupt_depth:
|
||||||
return
|
return
|
||||||
candidates: list[tuple[int, int, str]] = []
|
candidates: list[tuple[int, int, str]] = []
|
||||||
|
sci1_rx_interrupts_enabled = bool(self.sci1.scr & SCI_SCR_RIE and self.sci1.scr & SCI_SCR_RE)
|
||||||
|
if sci1_rx_interrupts_enabled and self.sci1.ssr & (SCI_SSR_ORER | SCI_SSR_FER | SCI_SSR_PER):
|
||||||
|
target = self._vector_target(VECTOR_SCI1_ERI)
|
||||||
|
if target is not None:
|
||||||
|
candidates.append((self._sci1_priority(), target, "sci1_eri"))
|
||||||
|
if sci1_rx_interrupts_enabled and self.sci1.ssr & SCI_SSR_RDRF:
|
||||||
|
target = self._vector_target(VECTOR_SCI1_RXI)
|
||||||
|
if target is not None:
|
||||||
|
candidates.append((self._sci1_priority(), target, "sci1_rxi"))
|
||||||
if self.sci1.scr & SCI_SCR_TIE and self.sci1.ssr & SCI_SSR_TDRE:
|
if self.sci1.scr & SCI_SCR_TIE and self.sci1.ssr & SCI_SSR_TDRE:
|
||||||
target = self._vector_target(VECTOR_SCI1_TXI)
|
target = self._vector_target(VECTOR_SCI1_TXI)
|
||||||
if target is not None:
|
if target is not None:
|
||||||
|
|||||||
470
h8536/emulator/rx_probe.py
Normal file
470
h8536/emulator/rx_probe.py
Normal file
@@ -0,0 +1,470 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
from collections import Counter
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Callable, Iterable
|
||||||
|
|
||||||
|
from ..formatting import h16, parse_int
|
||||||
|
from .cli import load_rom
|
||||||
|
from .constants import (
|
||||||
|
IPRE,
|
||||||
|
SCI_SCR_RE,
|
||||||
|
SCI_SCR_RIE,
|
||||||
|
SCI_SSR_RDRF,
|
||||||
|
VECTOR_SCI1_RXI,
|
||||||
|
)
|
||||||
|
from .errors import UnsupportedInstruction
|
||||||
|
from .memory import MemoryAccess
|
||||||
|
from .runner import H8536Emulator
|
||||||
|
|
||||||
|
|
||||||
|
CHECKSUM_SEED = 0x5A
|
||||||
|
FRAME_LENGTH = 6
|
||||||
|
|
||||||
|
CONNECT_LCD_FRAMES = (
|
||||||
|
bytes.fromhex("04000040001E"),
|
||||||
|
bytes.fromhex("0400008000DE"),
|
||||||
|
bytes.fromhex("040000C0009E"),
|
||||||
|
)
|
||||||
|
|
||||||
|
WATCH_PCS = {
|
||||||
|
0xBB57: "sci1_eri_entry",
|
||||||
|
0xBB67: "sci1_rxi_entry",
|
||||||
|
0xBBD6: "rx_checksum_seed",
|
||||||
|
0xBBF0: "rx_checksum_compare",
|
||||||
|
0xBC08: "command_dispatch",
|
||||||
|
0xBD0E: "command_04_handler",
|
||||||
|
0xBCCD: "command_04_send",
|
||||||
|
0xBE05: "command_07_handler",
|
||||||
|
0xBE29: "rx_error_or_retry",
|
||||||
|
0xBA26: "tx_builder",
|
||||||
|
0xBA72: "tx_first_byte",
|
||||||
|
0xBA84: "txi_entry",
|
||||||
|
0x3ECC: "lcd_line_buffer_entry",
|
||||||
|
0x3F28: "lcd_driver_stage",
|
||||||
|
0x3F40: "lcd_port_writer",
|
||||||
|
}
|
||||||
|
|
||||||
|
WATCH_RANGES = (
|
||||||
|
(0x2CA6, 0x2D20, "connect_display_window"),
|
||||||
|
(0xBB57, 0xBE6F, "sci1_rx_command_window"),
|
||||||
|
)
|
||||||
|
|
||||||
|
ACCESS_RANGES = (
|
||||||
|
(0xF850, 0xF85D, "tx_staging_or_frame"),
|
||||||
|
(0xF860, 0xF86D, "rx_validation_or_capture"),
|
||||||
|
(0xF870, 0xF96F, "report_queue"),
|
||||||
|
(0xF970, 0xF9AF, "secondary_dispatch_or_table"),
|
||||||
|
(0xF9B0, 0xF9C8, "serial_gate_state"),
|
||||||
|
(0xFAA2, 0xFAA6, "serial_latches"),
|
||||||
|
(0xFAF0, 0xFAFF, "lcd_line_buffer"),
|
||||||
|
(0xE000, 0xE001, "primary_table_index_0000"),
|
||||||
|
(0xE400, 0xE401, "secondary_table_index_0000"),
|
||||||
|
(0xE800, 0xE801, "current_table_index_0000"),
|
||||||
|
(0xEC00, 0xEC01, "flag_table_index_0000"),
|
||||||
|
(0xF200, 0xF201, "lcd_ports"),
|
||||||
|
)
|
||||||
|
|
||||||
|
STATE_BYTES = {
|
||||||
|
0xF9B0: "queue_head",
|
||||||
|
0xF9B5: "queue_tail",
|
||||||
|
0xF9C0: "tx_gate",
|
||||||
|
0xF9C1: "rx_interbyte_timeout",
|
||||||
|
0xF9C3: "rx_index",
|
||||||
|
0xF9C4: "idle_heartbeat_gate",
|
||||||
|
0xF9C5: "rx_session_timeout",
|
||||||
|
0xF9C6: "resend_period_hi",
|
||||||
|
0xF9C7: "resend_period_lo",
|
||||||
|
0xF9C8: "resend_countdown",
|
||||||
|
0xFAA2: "session_flags",
|
||||||
|
0xFAA3: "pending_mask",
|
||||||
|
0xFAA4: "rx_error_latch",
|
||||||
|
0xFAA5: "retry_or_gate_flags",
|
||||||
|
0xFAA6: "retry_counter",
|
||||||
|
}
|
||||||
|
|
||||||
|
STATE_WORDS = {
|
||||||
|
0xE000: "E000_index_0000_primary",
|
||||||
|
0xE400: "E400_index_0000_secondary",
|
||||||
|
0xE800: "E800_index_0000_current",
|
||||||
|
0xF860: "rx_frame_01",
|
||||||
|
0xF862: "rx_frame_23",
|
||||||
|
0xF864: "rx_frame_45",
|
||||||
|
0xF970: "F970_selector_zero_dispatch",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class RunContext:
|
||||||
|
pc_hits: Counter[str] = field(default_factory=Counter)
|
||||||
|
first_pcs: list[tuple[int, str]] = field(default_factory=list)
|
||||||
|
unsupported: str | None = None
|
||||||
|
|
||||||
|
def record_pc(self, pc: int) -> None:
|
||||||
|
label = WATCH_PCS.get(pc)
|
||||||
|
if label is None:
|
||||||
|
for start, end, range_label in WATCH_RANGES:
|
||||||
|
if start <= pc <= end:
|
||||||
|
label = range_label
|
||||||
|
break
|
||||||
|
if label is None:
|
||||||
|
return
|
||||||
|
self.pc_hits[label] += 1
|
||||||
|
if len(self.first_pcs) < 32:
|
||||||
|
self.first_pcs.append((pc, label))
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class FrameResult:
|
||||||
|
input_frame: bytes
|
||||||
|
checksum_ok: bool
|
||||||
|
steps: int
|
||||||
|
stopped_reason: str
|
||||||
|
new_tx_bytes: bytes
|
||||||
|
new_tx_frames: list[bytes]
|
||||||
|
state_before: dict[str, int | str]
|
||||||
|
state_after: dict[str, int | str]
|
||||||
|
accesses: list[MemoryAccess]
|
||||||
|
context: RunContext
|
||||||
|
|
||||||
|
def lines(self, index: int) -> list[str]:
|
||||||
|
lines = [
|
||||||
|
f"host_frame[{index}]={format_frame(self.input_frame)} checksum_ok={int(self.checksum_ok)}",
|
||||||
|
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'}",
|
||||||
|
]
|
||||||
|
if self.new_tx_frames:
|
||||||
|
lines.append(" new_tx_frames=" + " | ".join(format_frame(frame) for frame in self.new_tx_frames))
|
||||||
|
else:
|
||||||
|
lines.append(" new_tx_frames=none")
|
||||||
|
state_changes = _state_change_lines(self.state_before, self.state_after)
|
||||||
|
if state_changes:
|
||||||
|
lines.append(" state_changes:")
|
||||||
|
lines.extend(f" {line}" for line in state_changes)
|
||||||
|
pc_lines = _pc_hit_lines(self.context)
|
||||||
|
if pc_lines:
|
||||||
|
lines.append(" pc_hits:")
|
||||||
|
lines.extend(f" {line}" for line in pc_lines)
|
||||||
|
access_lines = _access_lines(self.accesses)
|
||||||
|
if access_lines:
|
||||||
|
lines.append(" interesting_accesses:")
|
||||||
|
lines.extend(f" {line}" for line in access_lines)
|
||||||
|
if self.context.unsupported:
|
||||||
|
lines.append(f" unsupported={self.context.unsupported}")
|
||||||
|
return lines
|
||||||
|
|
||||||
|
|
||||||
|
def parse_frame(text: str) -> bytes:
|
||||||
|
normalized = text.strip().replace(",", " ").replace(":", " ").replace("-", " ").replace("_", " ")
|
||||||
|
parts = normalized.split()
|
||||||
|
if len(parts) == 1:
|
||||||
|
compact = parts[0]
|
||||||
|
if compact.lower().startswith("0x"):
|
||||||
|
compact = compact[2:]
|
||||||
|
if compact.upper().startswith("H'"):
|
||||||
|
compact = compact[2:]
|
||||||
|
if len(compact) % 2:
|
||||||
|
raise argparse.ArgumentTypeError("frame compact hex must have an even number of digits")
|
||||||
|
parts = [compact[index : index + 2] for index in range(0, len(compact), 2)]
|
||||||
|
values = [_parse_byte(part) for part in parts]
|
||||||
|
if len(values) == FRAME_LENGTH - 1:
|
||||||
|
values.append(frame_checksum(bytes(values)))
|
||||||
|
if len(values) != FRAME_LENGTH:
|
||||||
|
raise argparse.ArgumentTypeError("frame must contain 5 bytes plus computed checksum or exactly 6 bytes")
|
||||||
|
return bytes(values)
|
||||||
|
|
||||||
|
|
||||||
|
def frame_checksum(data: bytes) -> int:
|
||||||
|
checksum = CHECKSUM_SEED
|
||||||
|
for value in data[: FRAME_LENGTH - 1]:
|
||||||
|
checksum ^= value
|
||||||
|
return checksum & 0xFF
|
||||||
|
|
||||||
|
|
||||||
|
def frame_checksum_ok(frame: bytes) -> bool:
|
||||||
|
return len(frame) == FRAME_LENGTH and frame_checksum(frame) == frame[-1]
|
||||||
|
|
||||||
|
|
||||||
|
def format_frame(data: bytes) -> str:
|
||||||
|
return data.hex(" ").upper()
|
||||||
|
|
||||||
|
|
||||||
|
def run_rx_probe(
|
||||||
|
frames: Iterable[bytes],
|
||||||
|
*,
|
||||||
|
rom_path: Path | None = None,
|
||||||
|
boot_steps: int = 250_000,
|
||||||
|
per_byte_steps: int = 5_000,
|
||||||
|
post_frame_steps: int = 80_000,
|
||||||
|
interval_steps: int = 512,
|
||||||
|
frt1_ocia_steps: int = 512,
|
||||||
|
frt2_ocia_steps: int = 512,
|
||||||
|
p9_fast_path: bool = True,
|
||||||
|
p9_fast_input: int = 0xFF,
|
||||||
|
stop_after_tx_frame: bool = True,
|
||||||
|
) -> tuple[Path, H8536Emulator, str, list[FrameResult]]:
|
||||||
|
rom_bytes, discovered_rom_path = load_rom(rom_path)
|
||||||
|
emulator = H8536Emulator(
|
||||||
|
rom_bytes,
|
||||||
|
interval_steps=interval_steps,
|
||||||
|
frt1_ocia_steps=frt1_ocia_steps,
|
||||||
|
frt2_ocia_steps=frt2_ocia_steps,
|
||||||
|
p9_fast_path_enabled=p9_fast_path,
|
||||||
|
p9_fast_default_input_byte=p9_fast_input,
|
||||||
|
)
|
||||||
|
|
||||||
|
boot_context = RunContext()
|
||||||
|
boot_steps_used, boot_reason = _run_until(emulator, boot_steps, _rx_ready, boot_context)
|
||||||
|
boot_summary = (
|
||||||
|
f"boot={boot_reason} steps={boot_steps_used} pc={h16(emulator.cpu.pc)} "
|
||||||
|
f"SCR={emulator.sci1.scr:02X} SSR={emulator.sci1.ssr:02X} "
|
||||||
|
f"rx_serviceable={int(_rx_ready(emulator))}"
|
||||||
|
)
|
||||||
|
|
||||||
|
results = [
|
||||||
|
_run_frame(
|
||||||
|
emulator,
|
||||||
|
frame,
|
||||||
|
per_byte_steps=per_byte_steps,
|
||||||
|
post_frame_steps=post_frame_steps,
|
||||||
|
stop_after_tx_frame=stop_after_tx_frame,
|
||||||
|
)
|
||||||
|
for frame in frames
|
||||||
|
]
|
||||||
|
return discovered_rom_path, emulator, boot_summary, results
|
||||||
|
|
||||||
|
|
||||||
|
def build_arg_parser() -> argparse.ArgumentParser:
|
||||||
|
parser = argparse.ArgumentParser(description="Inject host SCI1 frames into the H8/536 emulator and listen for ROM TX responses.")
|
||||||
|
parser.add_argument("frames", nargs="*", type=parse_frame, help="host frame hex; 5-byte inputs get a 0x5A-XOR checksum appended")
|
||||||
|
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("--post-frame-steps", type=int, default=80_000, help="maximum steps after a full injected 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("--frt1-ocia-steps", type=int, default=512, help="rough step period for the scaffolded FRT1 OCIA interrupt")
|
||||||
|
parser.add_argument("--frt2-ocia-steps", type=int, default=512, help="rough step period for the scaffolded FRT2 OCIA interrupt")
|
||||||
|
parser.add_argument("--no-p9-fast-path", action="store_true", help="disable shortcut handling for known P9 routines")
|
||||||
|
parser.add_argument("--p9-fast-input", type=parse_int, default=0xFF, help="default byte returned by the P9 fast-path read routine")
|
||||||
|
return parser
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv: list[str] | None = None) -> int:
|
||||||
|
args = build_arg_parser().parse_args(argv)
|
||||||
|
frames = list(args.frames)
|
||||||
|
if args.preset == "connect-lcd":
|
||||||
|
frames.extend(CONNECT_LCD_FRAMES)
|
||||||
|
if not frames:
|
||||||
|
raise SystemExit("pass at least one frame or use --preset connect-lcd")
|
||||||
|
|
||||||
|
rom_path, emulator, boot_summary, results = run_rx_probe(
|
||||||
|
frames,
|
||||||
|
rom_path=args.rom,
|
||||||
|
boot_steps=args.boot_steps,
|
||||||
|
per_byte_steps=args.per_byte_steps,
|
||||||
|
post_frame_steps=args.post_frame_steps,
|
||||||
|
interval_steps=args.interval_steps,
|
||||||
|
frt1_ocia_steps=args.frt1_ocia_steps,
|
||||||
|
frt2_ocia_steps=args.frt2_ocia_steps,
|
||||||
|
p9_fast_path=not args.no_p9_fast_path,
|
||||||
|
p9_fast_input=args.p9_fast_input,
|
||||||
|
stop_after_tx_frame=not args.keep_listening,
|
||||||
|
)
|
||||||
|
|
||||||
|
print(f"rom={rom_path}")
|
||||||
|
print(f"reset_vector={h16(emulator.reset_vector())}")
|
||||||
|
print(boot_summary)
|
||||||
|
for index, result in enumerate(results):
|
||||||
|
for line in result.lines(index):
|
||||||
|
print(line)
|
||||||
|
print("total_tx_frames=" + " | ".join(format_frame(frame) for frame in emulator.sci1.tx_frames))
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def _run_frame(
|
||||||
|
emulator: H8536Emulator,
|
||||||
|
frame: bytes,
|
||||||
|
*,
|
||||||
|
per_byte_steps: int,
|
||||||
|
post_frame_steps: int,
|
||||||
|
stop_after_tx_frame: bool,
|
||||||
|
) -> FrameResult:
|
||||||
|
state_before = _state_snapshot(emulator)
|
||||||
|
log_start = len(emulator.memory.access_log)
|
||||||
|
tx_byte_start = len(emulator.sci1.tx_bytes)
|
||||||
|
tx_frame_start = len(emulator.sci1.tx_frames)
|
||||||
|
context = RunContext()
|
||||||
|
stopped_reason = "post_frame_steps"
|
||||||
|
steps_total = 0
|
||||||
|
|
||||||
|
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
|
||||||
|
else:
|
||||||
|
target_frame_count = tx_frame_start + 1
|
||||||
|
|
||||||
|
def post_predicate(inner: H8536Emulator) -> bool:
|
||||||
|
return stop_after_tx_frame and len(inner.sci1.tx_frames) >= target_frame_count
|
||||||
|
|
||||||
|
steps, reason = _run_until(emulator, post_frame_steps, post_predicate, context)
|
||||||
|
steps_total += steps
|
||||||
|
stopped_reason = "tx_frame" if reason == "predicate" and stop_after_tx_frame else reason
|
||||||
|
|
||||||
|
log_end = len(emulator.memory.access_log)
|
||||||
|
state_after = _state_snapshot(emulator)
|
||||||
|
return FrameResult(
|
||||||
|
input_frame=frame,
|
||||||
|
checksum_ok=frame_checksum_ok(frame),
|
||||||
|
steps=steps_total,
|
||||||
|
stopped_reason=stopped_reason,
|
||||||
|
new_tx_bytes=bytes(emulator.sci1.tx_bytes[tx_byte_start:]),
|
||||||
|
new_tx_frames=list(emulator.sci1.tx_frames[tx_frame_start:]),
|
||||||
|
state_before=state_before,
|
||||||
|
state_after=state_after,
|
||||||
|
accesses=emulator.memory.access_log[log_start:log_end],
|
||||||
|
context=context,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _run_until(
|
||||||
|
emulator: H8536Emulator,
|
||||||
|
max_steps: int,
|
||||||
|
predicate: Callable[[H8536Emulator], bool],
|
||||||
|
context: RunContext,
|
||||||
|
) -> tuple[int, str]:
|
||||||
|
for index in range(max_steps):
|
||||||
|
if predicate(emulator):
|
||||||
|
return index, "predicate"
|
||||||
|
pc = emulator.cpu.pc
|
||||||
|
context.record_pc(pc)
|
||||||
|
try:
|
||||||
|
emulator.step()
|
||||||
|
except UnsupportedInstruction as exc:
|
||||||
|
context.unsupported = str(exc)
|
||||||
|
return index, "unsupported_instruction"
|
||||||
|
return 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
|
||||||
|
if emulator.vectors.get(VECTOR_SCI1_RXI) is None:
|
||||||
|
return False
|
||||||
|
return _sci1_priority(emulator) > _interrupt_mask(emulator)
|
||||||
|
|
||||||
|
|
||||||
|
def _rx_byte_consumed(emulator: H8536Emulator) -> bool:
|
||||||
|
return not (emulator.sci1.ssr & SCI_SSR_RDRF) and emulator.cpu.interrupt_depth == 0
|
||||||
|
|
||||||
|
|
||||||
|
def _sci1_priority(emulator: H8536Emulator) -> int:
|
||||||
|
return (emulator.memory.read8(IPRE) >> 4) & 0x07
|
||||||
|
|
||||||
|
|
||||||
|
def _interrupt_mask(emulator: H8536Emulator) -> int:
|
||||||
|
return (emulator.cpu.sr >> 8) & 0x07
|
||||||
|
|
||||||
|
|
||||||
|
def _state_snapshot(emulator: H8536Emulator) -> dict[str, int | str]:
|
||||||
|
snapshot: dict[str, int | str] = {}
|
||||||
|
for address, name in STATE_BYTES.items():
|
||||||
|
snapshot[name] = emulator.memory.read8(address)
|
||||||
|
for address, name in STATE_WORDS.items():
|
||||||
|
snapshot[name] = emulator.memory.read16(address)
|
||||||
|
snapshot["lcd_line_buffer_ascii"] = _ascii_window(emulator, 0xFAF0, 16)
|
||||||
|
snapshot["tx_frame_staging"] = format_frame(bytes(emulator.memory.read8(0xF850 + offset) for offset in range(6)))
|
||||||
|
snapshot["rx_frame_validation"] = format_frame(bytes(emulator.memory.read8(0xF860 + offset) for offset in range(6)))
|
||||||
|
return snapshot
|
||||||
|
|
||||||
|
|
||||||
|
def _ascii_window(emulator: H8536Emulator, start: int, length: int) -> str:
|
||||||
|
chars = []
|
||||||
|
for offset in range(length):
|
||||||
|
value = emulator.memory.read8(start + offset)
|
||||||
|
chars.append(chr(value) if 0x20 <= value <= 0x7E else ".")
|
||||||
|
return "".join(chars)
|
||||||
|
|
||||||
|
|
||||||
|
def _state_change_lines(before: dict[str, int | str], after: dict[str, int | str]) -> list[str]:
|
||||||
|
lines = []
|
||||||
|
for key in sorted(after):
|
||||||
|
if before.get(key) == after[key]:
|
||||||
|
continue
|
||||||
|
old = _state_value(before.get(key))
|
||||||
|
new = _state_value(after[key])
|
||||||
|
lines.append(f"{key}: {old}->{new}")
|
||||||
|
return lines
|
||||||
|
|
||||||
|
|
||||||
|
def _state_value(value: int | str | None) -> str:
|
||||||
|
if isinstance(value, int):
|
||||||
|
return f"H'{value:04X}" if value > 0xFF else f"H'{value:02X}"
|
||||||
|
return repr(value)
|
||||||
|
|
||||||
|
|
||||||
|
def _pc_hit_lines(context: RunContext) -> list[str]:
|
||||||
|
lines = [f"{name}={count}" for name, count in sorted(context.pc_hits.items())]
|
||||||
|
if context.first_pcs:
|
||||||
|
first = ", ".join(f"{h16(pc)}:{label}" for pc, label in context.first_pcs[:16])
|
||||||
|
lines.append(f"first={first}")
|
||||||
|
return lines
|
||||||
|
|
||||||
|
|
||||||
|
def _access_lines(accesses: list[MemoryAccess]) -> list[str]:
|
||||||
|
interesting = [access for access in accesses if _interesting_access(access)]
|
||||||
|
lines = []
|
||||||
|
for access in interesting[:80]:
|
||||||
|
label = _access_label(access.address)
|
||||||
|
lines.append(f"{access.kind:<5} {h16(access.address)} {access.value:02X} {label}")
|
||||||
|
if len(interesting) > 80:
|
||||||
|
lines.append(f"... {len(interesting) - 80} more interesting accesses")
|
||||||
|
return lines
|
||||||
|
|
||||||
|
|
||||||
|
def _interesting_access(access: MemoryAccess) -> bool:
|
||||||
|
if access.kind == "write":
|
||||||
|
return _access_label(access.address) != ""
|
||||||
|
return _access_label(access.address) in {"secondary_dispatch_or_table", "lcd_ports"}
|
||||||
|
|
||||||
|
|
||||||
|
def _access_label(address: int) -> str:
|
||||||
|
for start, end, label in ACCESS_RANGES:
|
||||||
|
if start <= address <= end:
|
||||||
|
return label
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_byte(text: str) -> int:
|
||||||
|
token = text.strip()
|
||||||
|
if token.lower().startswith("0x"):
|
||||||
|
token = token[2:]
|
||||||
|
if token.upper().startswith("H'"):
|
||||||
|
token = token[2:]
|
||||||
|
if not token or len(token) > 2:
|
||||||
|
raise argparse.ArgumentTypeError(f"invalid byte token {text!r}")
|
||||||
|
try:
|
||||||
|
value = int(token, 16)
|
||||||
|
except ValueError as exc:
|
||||||
|
raise argparse.ArgumentTypeError(f"invalid byte token {text!r}") from exc
|
||||||
|
if not 0 <= value <= 0xFF:
|
||||||
|
raise argparse.ArgumentTypeError(f"byte out of range {text!r}")
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"CONNECT_LCD_FRAMES",
|
||||||
|
"format_frame",
|
||||||
|
"frame_checksum",
|
||||||
|
"frame_checksum_ok",
|
||||||
|
"main",
|
||||||
|
"parse_frame",
|
||||||
|
"run_rx_probe",
|
||||||
|
]
|
||||||
5
h8536_emulator_rx_probe.py
Normal file
5
h8536_emulator_rx_probe.py
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
from h8536.emulator.rx_probe import main
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
@@ -7,11 +7,16 @@ from h8536.emulator import (
|
|||||||
IPRE,
|
IPRE,
|
||||||
ON_CHIP_RAM_START,
|
ON_CHIP_RAM_START,
|
||||||
REGISTER_FIELD_START,
|
REGISTER_FIELD_START,
|
||||||
|
SCI1_RDR,
|
||||||
SCI1_SCR,
|
SCI1_SCR,
|
||||||
SCI1_SSR,
|
SCI1_SSR,
|
||||||
SCI1_TDR,
|
SCI1_TDR,
|
||||||
|
SCI_SCR_RE,
|
||||||
|
SCI_SCR_RIE,
|
||||||
SCI_SCR_TE,
|
SCI_SCR_TE,
|
||||||
|
SCI_SSR_RDRF,
|
||||||
SCI_SSR_TDRE,
|
SCI_SSR_TDRE,
|
||||||
|
VECTOR_SCI1_RXI,
|
||||||
H8536Emulator,
|
H8536Emulator,
|
||||||
MemoryMap,
|
MemoryMap,
|
||||||
SCI1,
|
SCI1,
|
||||||
@@ -106,6 +111,18 @@ class EmulatorHarnessTest(unittest.TestCase):
|
|||||||
self.assertEqual(emulator.cpu.regs[1], 0x1234)
|
self.assertEqual(emulator.cpu.regs[1], 0x1234)
|
||||||
self.assertEqual(emulator.cpu.regs[7], 0xFE80)
|
self.assertEqual(emulator.cpu.regs[7], 0xFE80)
|
||||||
|
|
||||||
|
def test_jmp_register_uses_register_target(self):
|
||||||
|
rom = rom_with_reset(size=0x1020)
|
||||||
|
rom[0x1000:0x1003] = b"\x59\x10\x10" # MOV:I.W #H'1010, R1
|
||||||
|
rom[0x1003:0x1005] = b"\x11\xD1" # JMP @R1
|
||||||
|
rom[0x1010:0x1013] = b"\x58\x12\x34" # MOV:I.W #H'1234, R0
|
||||||
|
|
||||||
|
emulator = H8536Emulator(bytes(rom))
|
||||||
|
emulator.run(max_steps=3)
|
||||||
|
|
||||||
|
self.assertEqual(emulator.cpu.regs[0], 0x1234)
|
||||||
|
self.assertEqual(emulator.cpu.pc, 0x1013)
|
||||||
|
|
||||||
def test_sci1_txi_interrupt_can_emit_through_tdr(self):
|
def test_sci1_txi_interrupt_can_emit_through_tdr(self):
|
||||||
rom = rom_with_reset(size=0x1040)
|
rom = rom_with_reset(size=0x1040)
|
||||||
rom[0x0084:0x0086] = (0x1010).to_bytes(2, "big")
|
rom[0x0084:0x0086] = (0x1010).to_bytes(2, "big")
|
||||||
@@ -123,6 +140,28 @@ class EmulatorHarnessTest(unittest.TestCase):
|
|||||||
self.assertEqual(bytes(emulator.sci1.tx_bytes), b"\x42")
|
self.assertEqual(bytes(emulator.sci1.tx_bytes), b"\x42")
|
||||||
self.assertFalse(report.heartbeat_seen)
|
self.assertFalse(report.heartbeat_seen)
|
||||||
|
|
||||||
|
def test_sci1_rxi_interrupt_consumes_injected_rdr_byte(self):
|
||||||
|
rom = rom_with_reset(size=0x1040)
|
||||||
|
rom[VECTOR_SCI1_RXI : VECTOR_SCI1_RXI + 2] = (0x1010).to_bytes(2, "big")
|
||||||
|
rom[0x1000:0x1003] = b"\x5F\xFE\x80" # MOV:I.W #H'FE80, R7
|
||||||
|
rom[0x1003:0x1008] = bytes([0x15, (IPRE >> 8) & 0xFF, IPRE & 0xFF, 0x06, 0x70])
|
||||||
|
rom[0x1008:0x100D] = bytes(
|
||||||
|
[0x15, (SCI1_SCR >> 8) & 0xFF, SCI1_SCR & 0xFF, 0x06, SCI_SCR_RIE | SCI_SCR_RE],
|
||||||
|
)
|
||||||
|
rom[0x100D] = 0x00
|
||||||
|
rom[0x1010:0x1014] = bytes([0x15, (SCI1_SSR >> 8) & 0xFF, SCI1_SSR & 0xFF, 0xD6])
|
||||||
|
rom[0x1014:0x1018] = bytes([0x15, (SCI1_RDR >> 8) & 0xFF, SCI1_RDR & 0xFF, 0x80])
|
||||||
|
rom[0x1018:0x101C] = bytes([0x15, (ON_CHIP_RAM_START >> 8) & 0xFF, ON_CHIP_RAM_START & 0xFF, 0x90])
|
||||||
|
rom[0x101C] = 0x0A
|
||||||
|
|
||||||
|
emulator = H8536Emulator(bytes(rom))
|
||||||
|
emulator.run(max_steps=3)
|
||||||
|
emulator.inject_sci1_rx_byte(0xA5)
|
||||||
|
emulator.run(max_steps=5)
|
||||||
|
|
||||||
|
self.assertEqual(emulator.memory.read8(ON_CHIP_RAM_START), 0xA5)
|
||||||
|
self.assertFalse(emulator.sci1.read(SCI1_SSR) & SCI_SSR_RDRF)
|
||||||
|
|
||||||
def test_interval_interrupt_vector_can_fire(self):
|
def test_interval_interrupt_vector_can_fire(self):
|
||||||
rom = rom_with_reset(size=0x1040)
|
rom = rom_with_reset(size=0x1040)
|
||||||
rom[0x0042:0x0044] = (0x1020).to_bytes(2, "big")
|
rom[0x0042:0x0044] = (0x1020).to_bytes(2, "big")
|
||||||
|
|||||||
27
tests/test_emulator_rx_probe.py
Normal file
27
tests/test_emulator_rx_probe.py
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import argparse
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from h8536.emulator.rx_probe import frame_checksum, frame_checksum_ok, parse_frame
|
||||||
|
|
||||||
|
|
||||||
|
class EmulatorRxProbeTest(unittest.TestCase):
|
||||||
|
def test_parse_frame_accepts_five_bytes_and_appends_checksum(self):
|
||||||
|
frame = parse_frame("04 00 00 40 00")
|
||||||
|
|
||||||
|
self.assertEqual(frame, bytes.fromhex("04000040001E"))
|
||||||
|
self.assertTrue(frame_checksum_ok(frame))
|
||||||
|
|
||||||
|
def test_parse_frame_accepts_compact_checked_frame(self):
|
||||||
|
frame = parse_frame("0780684030C5")
|
||||||
|
|
||||||
|
self.assertEqual(frame, bytes.fromhex("0780684030C5"))
|
||||||
|
self.assertEqual(frame_checksum(frame), 0xC5)
|
||||||
|
self.assertTrue(frame_checksum_ok(frame))
|
||||||
|
|
||||||
|
def test_parse_frame_rejects_wrong_length(self):
|
||||||
|
with self.assertRaises(argparse.ArgumentTypeError):
|
||||||
|
parse_frame("04 00 00 40")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
Reference in New Issue
Block a user