EEPROM layout
This commit is contained in:
@@ -4,6 +4,7 @@ import argparse
|
||||
from pathlib import Path
|
||||
|
||||
from ..formatting import h16, parse_int
|
||||
from .eeprom_image import write_eeprom_snapshot
|
||||
from .memory import describe_regions
|
||||
from .runner import H8536Emulator
|
||||
|
||||
@@ -47,6 +48,11 @@ def build_arg_parser() -> argparse.ArgumentParser:
|
||||
parser.add_argument("--p9-fast-optimistic-wrapper", action="store_true", help="legacy fallback for older wrapper experiments; known BFE0/BFFE wrappers use the X24164 model")
|
||||
parser.add_argument("--p7-input", type=parse_int, default=0xFF, help="external P7 pin state for input bits; DIP-off board default is 0xFF")
|
||||
parser.add_argument("--eeprom-seed", choices=("blank", "factory"), default="blank", help="initial X24164/shadow state before reset")
|
||||
parser.add_argument("--eeprom-load", type=Path, help="load a 0x1000-byte logical EEPROM image before running")
|
||||
parser.add_argument("--eeprom-save", type=Path, help="save the final 0x1000-byte logical EEPROM image after running")
|
||||
parser.add_argument("--eeprom-report", type=Path, help="write a readable EEPROM snapshot report after running")
|
||||
parser.add_argument("--eeprom-report-json", type=Path, help="write a structured EEPROM snapshot report after running")
|
||||
parser.add_argument("--eeprom-report-include-image", action="store_true", help="include the full EEPROM image as hex in JSON reports")
|
||||
return parser
|
||||
|
||||
|
||||
@@ -70,6 +76,9 @@ def main(argv: list[str] | None = None) -> int:
|
||||
p7_input=args.p7_input,
|
||||
eeprom_seed=args.eeprom_seed,
|
||||
)
|
||||
if args.eeprom_load:
|
||||
emulator.memory.load_eeprom_image(args.eeprom_load.read_bytes())
|
||||
print(f"eeprom_loaded={args.eeprom_load}")
|
||||
print(f"rom={rom_path}")
|
||||
print(f"reset_vector={h16(emulator.reset_vector())}")
|
||||
if args.memory_map:
|
||||
@@ -82,4 +91,20 @@ def main(argv: list[str] | None = None) -> int:
|
||||
print(line)
|
||||
if not report.heartbeat_seen:
|
||||
print("heartbeat_status=not reached; no heartbeat is reported unless bytes are emitted via SCI1_TDR")
|
||||
if args.eeprom_save:
|
||||
args.eeprom_save.parent.mkdir(parents=True, exist_ok=True)
|
||||
args.eeprom_save.write_bytes(emulator.memory.dump_eeprom_image())
|
||||
print(f"eeprom_saved={args.eeprom_save}")
|
||||
if args.eeprom_report:
|
||||
write_eeprom_snapshot(emulator.memory, args.eeprom_report, rom_bytes=rom_bytes)
|
||||
print(f"eeprom_report={args.eeprom_report}")
|
||||
if args.eeprom_report_json:
|
||||
write_eeprom_snapshot(
|
||||
emulator.memory,
|
||||
args.eeprom_report_json,
|
||||
rom_bytes=rom_bytes,
|
||||
as_json=True,
|
||||
include_image_hex=args.eeprom_report_include_image,
|
||||
)
|
||||
print(f"eeprom_report_json={args.eeprom_report_json}")
|
||||
return 0
|
||||
|
||||
351
h8536/emulator/eeprom_image.py
Normal file
351
h8536/emulator/eeprom_image.py
Normal file
@@ -0,0 +1,351 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
from collections import defaultdict
|
||||
from collections.abc import Mapping
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from ..formatting import h16
|
||||
from .memory import MemoryMap
|
||||
from .peripherals.x24164 import (
|
||||
X24164_FACTORY_DEFAULT_BYTES,
|
||||
X24164_LOGICAL_PAGE_COUNT,
|
||||
X24164_LOGICAL_PAGE_SIZE,
|
||||
X24164_LOGICAL_SIZE,
|
||||
factory_default_words_from_rom,
|
||||
)
|
||||
|
||||
|
||||
JsonObject = dict[str, Any]
|
||||
|
||||
SELECTOR_MAP_BASE = 0xC564
|
||||
SELECTOR_MAP_COUNT = 0x0200
|
||||
RECORD_BYTES = 8
|
||||
SHADOW_BASE = 0xF400
|
||||
|
||||
|
||||
def build_eeprom_snapshot(
|
||||
memory: MemoryMap,
|
||||
*,
|
||||
rom_bytes: bytes | None = None,
|
||||
include_image_hex: bool = False,
|
||||
) -> JsonObject:
|
||||
image = memory.dump_eeprom_image()
|
||||
selectors_by_offset = _selectors_by_offset(rom_bytes)
|
||||
factory_image = _factory_image(rom_bytes)
|
||||
write_events = _write_events(memory, selectors_by_offset)
|
||||
write_words = _coalesced_write_words(memory, selectors_by_offset)
|
||||
factory_diffs = _factory_diffs(image, factory_image, selectors_by_offset)
|
||||
|
||||
report: JsonObject = {
|
||||
"kind": "emulator_eeprom_snapshot",
|
||||
"summary": {
|
||||
"logical_size": len(image),
|
||||
"logical_size_hex": f"0x{len(image):04X}",
|
||||
"sha256": hashlib.sha256(image).hexdigest(),
|
||||
"write_byte_count": len(write_events),
|
||||
"write_word_count": len(write_words),
|
||||
"factory_diff_word_count": len(factory_diffs),
|
||||
"record_count": X24164_LOGICAL_PAGE_COUNT,
|
||||
},
|
||||
"records": _records(image),
|
||||
"write_events": write_events,
|
||||
"write_word_events": write_words,
|
||||
"factory_diffs": factory_diffs,
|
||||
"shadow_f400": _shadow_summary(memory, rom_bytes, selectors_by_offset),
|
||||
}
|
||||
if include_image_hex:
|
||||
report["image_hex"] = image.hex()
|
||||
return report
|
||||
|
||||
|
||||
def format_eeprom_snapshot(report: Mapping[str, Any], *, limit: int = 80) -> str:
|
||||
summary = report["summary"]
|
||||
lines = [
|
||||
"Emulator EEPROM Snapshot",
|
||||
"",
|
||||
f"size={summary['logical_size_hex']} sha256={summary['sha256']}",
|
||||
(
|
||||
f"writes: bytes={summary['write_byte_count']} words={summary['write_word_count']} "
|
||||
f"factory_diff_words={summary['factory_diff_word_count']}"
|
||||
),
|
||||
"",
|
||||
"Persistent Records:",
|
||||
]
|
||||
for record in report.get("records", []):
|
||||
lines.append(
|
||||
f"- page {record['page_hex']} EEPROM {record['range_hex']} "
|
||||
f"bytes={record['bytes_hex']} text={record['ascii']!r}"
|
||||
)
|
||||
|
||||
lines.extend(["", "EEPROM Word Writes:"])
|
||||
word_events = list(report.get("write_word_events", []))
|
||||
if not word_events:
|
||||
lines.append("- none since EEPROM setup/load")
|
||||
for event in word_events[:limit]:
|
||||
suffix = _event_suffix(event)
|
||||
lines.append(
|
||||
f"- {event['address_hex']} page={event['page_hex']} offset={event['offset_hex']} "
|
||||
f"{event['old_word_hex']}->{event['new_word_hex']} source={event['source']}{suffix}"
|
||||
)
|
||||
if len(word_events) > limit:
|
||||
lines.append(f"- ... {len(word_events) - limit} more word writes omitted")
|
||||
|
||||
lines.extend(["", "Factory Diffs:"])
|
||||
diffs = list(report.get("factory_diffs", []))
|
||||
if not diffs:
|
||||
lines.append("- current EEPROM image matches ROM factory/default image")
|
||||
for diff in diffs[:limit]:
|
||||
suffix = _event_suffix(diff)
|
||||
lines.append(
|
||||
f"- {diff['address_hex']} page={diff['page_hex']} offset={diff['offset_hex']} "
|
||||
f"expected={diff['expected_word_hex']} actual={diff['actual_word_hex']}{suffix}"
|
||||
)
|
||||
if len(diffs) > limit:
|
||||
lines.append(f"- ... {len(diffs) - limit} more factory diffs omitted")
|
||||
|
||||
shadow = report.get("shadow_f400", {})
|
||||
lines.extend(["", "F400 Shadow Diffs:"])
|
||||
shadow_diffs = list(shadow.get("diffs", [])) if isinstance(shadow, Mapping) else []
|
||||
if not shadow_diffs:
|
||||
lines.append("- F400-F4FF shadow matches ROM factory words or no ROM factory baseline was supplied")
|
||||
for diff in shadow_diffs[:limit]:
|
||||
suffix = _event_suffix(diff)
|
||||
lines.append(
|
||||
f"- {diff['address_hex']} offset={diff['offset_hex']} "
|
||||
f"expected={diff['expected_word_hex']} actual={diff['actual_word_hex']}{suffix}"
|
||||
)
|
||||
if len(shadow_diffs) > limit:
|
||||
lines.append(f"- ... {len(shadow_diffs) - limit} more shadow diffs omitted")
|
||||
return "\n".join(lines).rstrip() + "\n"
|
||||
|
||||
|
||||
def write_eeprom_snapshot(
|
||||
memory: MemoryMap,
|
||||
output_path: Path,
|
||||
*,
|
||||
rom_bytes: bytes | None = None,
|
||||
as_json: bool = False,
|
||||
include_image_hex: bool = False,
|
||||
) -> JsonObject:
|
||||
report = build_eeprom_snapshot(memory, rom_bytes=rom_bytes, include_image_hex=include_image_hex)
|
||||
output_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
if as_json:
|
||||
output_path.write_text(json.dumps(report, indent=2, sort_keys=True) + "\n", encoding="utf-8")
|
||||
else:
|
||||
output_path.write_text(format_eeprom_snapshot(report), encoding="utf-8")
|
||||
return report
|
||||
|
||||
|
||||
def _write_events(memory: MemoryMap, selectors_by_offset: Mapping[int, list[int]]) -> list[JsonObject]:
|
||||
events = []
|
||||
for index, event in enumerate(memory.p9_bus.x24164_bus.write_events):
|
||||
item = _address_info(event.logical_address, selectors_by_offset)
|
||||
item.update(
|
||||
{
|
||||
"index": index,
|
||||
"device": event.device,
|
||||
"device_offset": event.device_offset,
|
||||
"device_offset_hex": f"0x{event.device_offset:03X}",
|
||||
"old_value": event.old_value,
|
||||
"old_value_hex": f"0x{event.old_value & 0xFF:02X}",
|
||||
"new_value": event.new_value,
|
||||
"new_value_hex": f"0x{event.new_value & 0xFF:02X}",
|
||||
"source": event.source,
|
||||
}
|
||||
)
|
||||
events.append(item)
|
||||
return events
|
||||
|
||||
|
||||
def _coalesced_write_words(memory: MemoryMap, selectors_by_offset: Mapping[int, list[int]]) -> list[JsonObject]:
|
||||
events = memory.p9_bus.x24164_bus.write_events
|
||||
words: list[JsonObject] = []
|
||||
index = 0
|
||||
while index < len(events):
|
||||
event = events[index]
|
||||
next_event = events[index + 1] if index + 1 < len(events) else None
|
||||
if (
|
||||
next_event is not None
|
||||
and (event.logical_address & 1) == 0
|
||||
and next_event.logical_address == ((event.logical_address + 1) & 0x0FFF)
|
||||
and next_event.device == event.device
|
||||
and next_event.source == event.source
|
||||
):
|
||||
old_word = ((event.old_value & 0xFF) << 8) | (next_event.old_value & 0xFF)
|
||||
new_word = ((event.new_value & 0xFF) << 8) | (next_event.new_value & 0xFF)
|
||||
item = _address_info(event.logical_address, selectors_by_offset)
|
||||
item.update(
|
||||
{
|
||||
"index": index,
|
||||
"device": event.device,
|
||||
"old_word": old_word,
|
||||
"old_word_hex": f"0x{old_word:04X}",
|
||||
"new_word": new_word,
|
||||
"new_word_hex": f"0x{new_word:04X}",
|
||||
"source": event.source,
|
||||
}
|
||||
)
|
||||
words.append(item)
|
||||
index += 2
|
||||
else:
|
||||
index += 1
|
||||
return words
|
||||
|
||||
|
||||
def _factory_diffs(
|
||||
image: bytes,
|
||||
factory_image: bytes | None,
|
||||
selectors_by_offset: Mapping[int, list[int]],
|
||||
) -> list[JsonObject]:
|
||||
if factory_image is None:
|
||||
return []
|
||||
diffs = []
|
||||
for address in range(0, min(len(image), len(factory_image)), 2):
|
||||
expected = (factory_image[address] << 8) | factory_image[address + 1]
|
||||
actual = (image[address] << 8) | image[address + 1]
|
||||
if expected == actual:
|
||||
continue
|
||||
item = _address_info(address, selectors_by_offset)
|
||||
item.update(
|
||||
{
|
||||
"expected_word": expected,
|
||||
"expected_word_hex": f"0x{expected:04X}",
|
||||
"actual_word": actual,
|
||||
"actual_word_hex": f"0x{actual:04X}",
|
||||
}
|
||||
)
|
||||
diffs.append(item)
|
||||
return diffs
|
||||
|
||||
|
||||
def _shadow_summary(
|
||||
memory: MemoryMap,
|
||||
rom_bytes: bytes | None,
|
||||
selectors_by_offset: Mapping[int, list[int]],
|
||||
) -> JsonObject:
|
||||
if rom_bytes is None:
|
||||
return {"diffs": [], "note": "no ROM factory baseline supplied"}
|
||||
factory_words = dict(factory_default_words_from_rom(rom_bytes))
|
||||
diffs = []
|
||||
for offset in range(0, X24164_FACTORY_DEFAULT_BYTES, 2):
|
||||
expected = factory_words[offset]
|
||||
address = SHADOW_BASE + offset
|
||||
high = memory.external.get(address & 0xFFFF, 0xFF)
|
||||
low = memory.external.get((address + 1) & 0xFFFF, 0xFF)
|
||||
actual = ((high & 0xFF) << 8) | (low & 0xFF)
|
||||
if expected == actual:
|
||||
continue
|
||||
item = _address_info(offset, selectors_by_offset)
|
||||
item.update(
|
||||
{
|
||||
"address": address,
|
||||
"address_hex": h16(address),
|
||||
"expected_word": expected,
|
||||
"expected_word_hex": f"0x{expected:04X}",
|
||||
"actual_word": actual,
|
||||
"actual_word_hex": f"0x{actual:04X}",
|
||||
}
|
||||
)
|
||||
diffs.append(item)
|
||||
return {"diffs": diffs, "diff_count": len(diffs)}
|
||||
|
||||
|
||||
def _records(image: bytes) -> list[JsonObject]:
|
||||
records = []
|
||||
for page in range(X24164_LOGICAL_PAGE_COUNT):
|
||||
base = page * X24164_LOGICAL_PAGE_SIZE
|
||||
data = image[base : base + RECORD_BYTES]
|
||||
records.append(
|
||||
{
|
||||
"page": page,
|
||||
"page_hex": f"0x{page:X}",
|
||||
"address": base,
|
||||
"address_hex": f"0x{base:03X}",
|
||||
"range_hex": f"0x{base:03X}-0x{base + RECORD_BYTES - 1:03X}",
|
||||
"bytes_hex": data.hex(" ").upper(),
|
||||
"words_hex": [
|
||||
f"0x{((data[index] << 8) | data[index + 1]):04X}"
|
||||
for index in range(0, len(data), 2)
|
||||
],
|
||||
"ascii": _ascii(data),
|
||||
"is_blank_spaces": data == (b" " * RECORD_BYTES),
|
||||
}
|
||||
)
|
||||
return records
|
||||
|
||||
|
||||
def _factory_image(rom_bytes: bytes | None) -> bytes | None:
|
||||
if rom_bytes is None:
|
||||
return None
|
||||
image = bytearray([0xFF] * X24164_LOGICAL_SIZE)
|
||||
for offset, word in factory_default_words_from_rom(rom_bytes):
|
||||
for page in range(X24164_LOGICAL_PAGE_COUNT):
|
||||
address = (page * X24164_LOGICAL_PAGE_SIZE) + offset
|
||||
image[address] = (word >> 8) & 0xFF
|
||||
image[address + 1] = word & 0xFF
|
||||
for page in range(1, X24164_LOGICAL_PAGE_COUNT):
|
||||
base = page * X24164_LOGICAL_PAGE_SIZE
|
||||
for offset in range(0, RECORD_BYTES, 2):
|
||||
image[base + offset] = 0x20
|
||||
image[base + offset + 1] = 0x20
|
||||
return bytes(image)
|
||||
|
||||
|
||||
def _selectors_by_offset(rom_bytes: bytes | None) -> dict[int, list[int]]:
|
||||
if rom_bytes is None:
|
||||
return {}
|
||||
result: dict[int, list[int]] = defaultdict(list)
|
||||
for selector in range(SELECTOR_MAP_COUNT):
|
||||
address = SELECTOR_MAP_BASE + selector * 2
|
||||
if address + 1 >= len(rom_bytes):
|
||||
break
|
||||
low = rom_bytes[address + 1]
|
||||
if low:
|
||||
result[low & 0xFE].append(selector)
|
||||
return dict(result)
|
||||
|
||||
|
||||
def _address_info(address: int, selectors_by_offset: Mapping[int, list[int]]) -> JsonObject:
|
||||
address &= 0x0FFF
|
||||
page = (address // X24164_LOGICAL_PAGE_SIZE) & 0x0F
|
||||
offset = address & 0xFF
|
||||
aligned_offset = offset & 0xFE
|
||||
selectors = selectors_by_offset.get(aligned_offset, [])
|
||||
return {
|
||||
"address": address,
|
||||
"address_hex": f"0x{address:03X}",
|
||||
"page": page,
|
||||
"page_hex": f"0x{page:X}",
|
||||
"offset": offset,
|
||||
"offset_hex": f"0x{offset:02X}",
|
||||
"aligned_offset": aligned_offset,
|
||||
"aligned_offset_hex": f"0x{aligned_offset:02X}",
|
||||
"record_byte": offset if offset < RECORD_BYTES else None,
|
||||
"role": "record_header_or_label" if offset < RECORD_BYTES else "factory_shadow_offset",
|
||||
"mapped_selectors": selectors[:24],
|
||||
"mapped_selectors_hex": [f"0x{selector:03X}" for selector in selectors[:24]],
|
||||
}
|
||||
|
||||
|
||||
def _event_suffix(event: Mapping[str, Any]) -> str:
|
||||
parts = []
|
||||
if event.get("role"):
|
||||
parts.append(str(event["role"]))
|
||||
selectors = event.get("mapped_selectors_hex")
|
||||
if isinstance(selectors, list) and selectors:
|
||||
parts.append("selectors=" + ",".join(str(item) for item in selectors[:6]))
|
||||
return f" ({'; '.join(parts)})" if parts else ""
|
||||
|
||||
|
||||
def _ascii(data: bytes) -> str:
|
||||
return "".join(chr(value) if 0x20 <= value <= 0x7E else "." for value in data)
|
||||
|
||||
|
||||
__all__ = [
|
||||
"build_eeprom_snapshot",
|
||||
"format_eeprom_snapshot",
|
||||
"write_eeprom_snapshot",
|
||||
]
|
||||
@@ -161,6 +161,20 @@ class MemoryMap:
|
||||
self.p9_bus.x24164_bus.seed_factory_defaults_from_rom(self.rom.data)
|
||||
self.p9_bus.clear_x24164_trace()
|
||||
|
||||
def load_eeprom_image(self, data: bytes | bytearray, *, mirror_shadow: bool = True) -> None:
|
||||
self.p9_bus.x24164_bus.load_linear(data)
|
||||
if mirror_shadow:
|
||||
image = self.p9_bus.x24164_bus.dump_linear()
|
||||
for offset in range(min(0x0100, len(image))):
|
||||
self.external[(0xF400 + offset) & 0xFFFF] = image[offset]
|
||||
self.p9_bus.clear_x24164_trace()
|
||||
|
||||
def dump_eeprom_image(self) -> bytes:
|
||||
return self.p9_bus.x24164_bus.dump_linear()
|
||||
|
||||
def clear_eeprom_write_log(self) -> None:
|
||||
self.p9_bus.x24164_bus.clear_write_log()
|
||||
|
||||
def _set_register(self, address: int, value: int) -> None:
|
||||
self.registers[address - REGISTER_FIELD_START] = value & 0xFF
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ from __future__ import annotations
|
||||
|
||||
from .lcd import LCD, LCD_E_CLOCK_DATA, LCD_E_CLOCK_STATUS, LCD_LINE_WIDTH
|
||||
from .p9_bus import P9_ACK_BIT, P9_STROBE_BIT, P9Bus, P9StrobeEvent, P9TraceEvent
|
||||
from .x24164 import X24164Bus, X24164Device, X24164TraceEvent, factory_default_words_from_rom
|
||||
from .x24164 import X24164Bus, X24164Device, X24164TraceEvent, X24164WriteEvent, factory_default_words_from_rom
|
||||
|
||||
__all__ = [
|
||||
"LCD_E_CLOCK_DATA",
|
||||
@@ -17,5 +17,6 @@ __all__ = [
|
||||
"X24164Bus",
|
||||
"X24164Device",
|
||||
"X24164TraceEvent",
|
||||
"X24164WriteEvent",
|
||||
"factory_default_words_from_rom",
|
||||
]
|
||||
|
||||
@@ -4,6 +4,7 @@ from dataclasses import dataclass, field
|
||||
|
||||
|
||||
X24164_SIZE = 2048
|
||||
X24164_LOGICAL_SIZE = 4096
|
||||
X24164_FACTORY_DEFAULT_BASE = 0xC964
|
||||
X24164_FACTORY_DEFAULT_BYTES = 0x0100
|
||||
X24164_LOGICAL_PAGE_SIZE = 0x0100
|
||||
@@ -72,12 +73,30 @@ class X24164TraceEvent:
|
||||
return " ".join(parts)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class X24164WriteEvent:
|
||||
logical_address: int
|
||||
device: str
|
||||
device_offset: int
|
||||
old_value: int
|
||||
new_value: int
|
||||
source: str
|
||||
|
||||
def line(self) -> str:
|
||||
return (
|
||||
f"addr={self.logical_address & 0x0FFF:03X} device={self.device} "
|
||||
f"offset={self.device_offset & (X24164_SIZE - 1):03X} "
|
||||
f"{self.old_value & 0xFF:02X}->{self.new_value & 0xFF:02X} source={self.source}"
|
||||
)
|
||||
|
||||
|
||||
class X24164Bus:
|
||||
"""Bit-level two-wire bus model for X24164 EEPROMs."""
|
||||
|
||||
def __init__(self, devices: list[X24164Device] | None = None) -> None:
|
||||
self.devices = devices if devices is not None else default_x24164_devices()
|
||||
self.trace_events: list[X24164TraceEvent] = []
|
||||
self.write_events: list[X24164WriteEvent] = []
|
||||
self.active = False
|
||||
self.phase = "idle"
|
||||
self.selected: X24164Device | None = None
|
||||
@@ -197,6 +216,18 @@ class X24164Bus:
|
||||
)
|
||||
return True, value
|
||||
|
||||
def read_linear_byte(self, address: int) -> tuple[bool, int]:
|
||||
device = self._device_for_linear_address(address)
|
||||
if device is None:
|
||||
self.trace_events.append(
|
||||
X24164TraceEvent("x24164_linear_read_miss", address=address & 0x0FFF, message="no_mapped_device")
|
||||
)
|
||||
return False, 0xFF
|
||||
offset = address & (X24164_SIZE - 1)
|
||||
value = device.read(offset)
|
||||
self.trace_events.append(X24164TraceEvent("x24164_linear_read_byte", device.name, value=value, address=offset))
|
||||
return True, value
|
||||
|
||||
def write_linear_word(self, address: int, value: int) -> bool:
|
||||
device = self._device_for_linear_address(address)
|
||||
if device is None:
|
||||
@@ -205,8 +236,8 @@ class X24164Bus:
|
||||
)
|
||||
return False
|
||||
offset = address & (X24164_SIZE - 1)
|
||||
device.write(offset, (value >> 8) & 0xFF)
|
||||
device.write((offset + 1) & (X24164_SIZE - 1), value & 0xFF)
|
||||
self._write_device_byte(device, offset, (value >> 8) & 0xFF, source="linear_word")
|
||||
self._write_device_byte(device, (offset + 1) & (X24164_SIZE - 1), value & 0xFF, source="linear_word")
|
||||
self.trace_events.append(
|
||||
X24164TraceEvent(
|
||||
"x24164_linear_write_word",
|
||||
@@ -218,20 +249,61 @@ class X24164Bus:
|
||||
)
|
||||
return True
|
||||
|
||||
def write_linear_byte(self, address: int, value: int, *, source: str = "linear_byte") -> bool:
|
||||
device = self._device_for_linear_address(address)
|
||||
if device is None:
|
||||
self.trace_events.append(
|
||||
X24164TraceEvent("x24164_linear_write_miss", address=address & 0x0FFF, message="no_mapped_device")
|
||||
)
|
||||
return False
|
||||
offset = address & (X24164_SIZE - 1)
|
||||
self._write_device_byte(device, offset, value, source=source)
|
||||
self.trace_events.append(X24164TraceEvent("x24164_linear_write_byte", device.name, value=value & 0xFF, address=offset))
|
||||
return True
|
||||
|
||||
def dump_linear(self) -> bytes:
|
||||
data = bytearray()
|
||||
for address in range(X24164_LOGICAL_SIZE):
|
||||
device = self._device_for_linear_address(address)
|
||||
if device is None:
|
||||
data.append(0xFF)
|
||||
else:
|
||||
data.append(device.read(address & (X24164_SIZE - 1)))
|
||||
return bytes(data)
|
||||
|
||||
def load_linear(self, data: bytes | bytearray, *, fill: int = 0xFF) -> None:
|
||||
if len(data) > X24164_LOGICAL_SIZE:
|
||||
raise ValueError(f"EEPROM image is too large: {len(data)} > {X24164_LOGICAL_SIZE}")
|
||||
padded = bytearray([fill & 0xFF] * X24164_LOGICAL_SIZE)
|
||||
padded[: len(data)] = data
|
||||
for address, value in enumerate(padded):
|
||||
device = self._device_for_linear_address(address)
|
||||
if device is not None:
|
||||
device.write(address & (X24164_SIZE - 1), value)
|
||||
self.clear_write_log()
|
||||
|
||||
def clear_write_log(self) -> None:
|
||||
self.write_events.clear()
|
||||
|
||||
def seed_factory_defaults_from_rom(self, rom_bytes: bytes) -> None:
|
||||
for offset, word in factory_default_words_from_rom(rom_bytes):
|
||||
for page in range(X24164_LOGICAL_PAGE_COUNT):
|
||||
self.write_linear_word((page * X24164_LOGICAL_PAGE_SIZE) + offset, word)
|
||||
|
||||
for page in range(X24164_LOGICAL_PAGE_COUNT):
|
||||
for page in range(1, X24164_LOGICAL_PAGE_COUNT):
|
||||
base = page * X24164_LOGICAL_PAGE_SIZE
|
||||
for offset in range(0, 8, 2):
|
||||
self.write_linear_word(base + offset, 0x2020)
|
||||
self.clear_write_log()
|
||||
|
||||
def trace_lines(self, limit: int | None = None) -> list[str]:
|
||||
events = self.trace_events if limit is None else self.trace_events[-limit:]
|
||||
return [event.line() for event in events]
|
||||
|
||||
def write_log_lines(self, limit: int | None = None) -> list[str]:
|
||||
events = self.write_events if limit is None else self.write_events[-limit:]
|
||||
return [event.line() for event in events]
|
||||
|
||||
def _scl_rising(self, master_sda: bool, master_sda_output: bool) -> None:
|
||||
if not self.active:
|
||||
return
|
||||
@@ -327,7 +399,7 @@ class X24164Bus:
|
||||
self._ack_armed_on_current_clock = True
|
||||
self.phase = "ignore"
|
||||
return
|
||||
self.selected.write(self.address, value)
|
||||
self._write_device_byte(self.selected, self.address, value, source="bit_banged")
|
||||
self.trace_events.append(
|
||||
X24164TraceEvent("x24164_write_data", self.selected.name, value=value, address=self.address, ack=True)
|
||||
)
|
||||
@@ -365,6 +437,24 @@ class X24164Bus:
|
||||
return device
|
||||
return None
|
||||
|
||||
def _linear_base_for_device(self, device: X24164Device) -> int:
|
||||
return 0x0800 if device.control_base == 0xE0 else 0x0000
|
||||
|
||||
def _write_device_byte(self, device: X24164Device, offset: int, value: int, *, source: str) -> None:
|
||||
offset &= X24164_SIZE - 1
|
||||
old_value = device.read(offset)
|
||||
device.write(offset, value)
|
||||
self.write_events.append(
|
||||
X24164WriteEvent(
|
||||
logical_address=(self._linear_base_for_device(device) + offset) & 0x0FFF,
|
||||
device=device.name,
|
||||
device_offset=offset,
|
||||
old_value=old_value,
|
||||
new_value=value & 0xFF,
|
||||
source=source,
|
||||
)
|
||||
)
|
||||
|
||||
def _selected_name(self) -> str | None:
|
||||
return self.selected.name if self.selected is not None else None
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ from .constants import (
|
||||
SCI_SSR_RDRF,
|
||||
VECTOR_SCI1_RXI,
|
||||
)
|
||||
from .eeprom_image import write_eeprom_snapshot
|
||||
from .errors import UnsupportedInstruction
|
||||
from .memory import MemoryAccess
|
||||
from .runner import H8536Emulator
|
||||
@@ -216,6 +217,7 @@ def run_rx_probe(
|
||||
p9_fast_optimistic_wrapper: bool = False,
|
||||
p7_input: int = 0xFF,
|
||||
eeprom_seed: str = "blank",
|
||||
eeprom_image: bytes | None = None,
|
||||
stop_after_tx_frame: bool = True,
|
||||
) -> tuple[Path, H8536Emulator, str, list[FrameResult]]:
|
||||
rom_bytes, discovered_rom_path = load_rom(rom_path)
|
||||
@@ -231,6 +233,8 @@ def run_rx_probe(
|
||||
p7_input=p7_input,
|
||||
eeprom_seed=eeprom_seed,
|
||||
)
|
||||
if eeprom_image is not None:
|
||||
emulator.memory.load_eeprom_image(eeprom_image)
|
||||
|
||||
boot_context = RunContext()
|
||||
boot_steps_used, boot_reason = _run_until(emulator, boot_steps, _rx_ready, boot_context)
|
||||
@@ -277,6 +281,11 @@ def build_arg_parser() -> argparse.ArgumentParser:
|
||||
parser.add_argument("--p9-fast-optimistic-wrapper", action="store_true", help="legacy fallback for older wrapper experiments; known BFE0/BFFE wrappers use the X24164 model")
|
||||
parser.add_argument("--p7-input", type=parse_int, default=0xFF, help="external P7 pin state for input bits; DIP-off board default is 0xFF")
|
||||
parser.add_argument("--eeprom-seed", choices=("blank", "factory"), default="blank", help="initial X24164/shadow state before reset")
|
||||
parser.add_argument("--eeprom-load", type=Path, help="load a 0x1000-byte logical EEPROM image before booting the ROM")
|
||||
parser.add_argument("--eeprom-save", type=Path, help="save the final 0x1000-byte logical EEPROM image after probing")
|
||||
parser.add_argument("--eeprom-report", type=Path, help="write a readable EEPROM snapshot report after probing")
|
||||
parser.add_argument("--eeprom-report-json", type=Path, help="write a structured EEPROM snapshot report after probing")
|
||||
parser.add_argument("--eeprom-report-include-image", action="store_true", help="include the full EEPROM image as hex in JSON reports")
|
||||
return parser
|
||||
|
||||
|
||||
@@ -305,16 +314,35 @@ def main(argv: list[str] | None = None) -> int:
|
||||
p9_fast_optimistic_wrapper=args.p9_fast_optimistic_wrapper,
|
||||
p7_input=args.p7_input,
|
||||
eeprom_seed=args.eeprom_seed,
|
||||
eeprom_image=args.eeprom_load.read_bytes() if args.eeprom_load else None,
|
||||
stop_after_tx_frame=not args.keep_listening,
|
||||
)
|
||||
|
||||
print(f"rom={rom_path}")
|
||||
if args.eeprom_load:
|
||||
print(f"eeprom_loaded={args.eeprom_load}")
|
||||
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))
|
||||
if args.eeprom_save:
|
||||
args.eeprom_save.parent.mkdir(parents=True, exist_ok=True)
|
||||
args.eeprom_save.write_bytes(emulator.memory.dump_eeprom_image())
|
||||
print(f"eeprom_saved={args.eeprom_save}")
|
||||
if args.eeprom_report:
|
||||
write_eeprom_snapshot(emulator.memory, args.eeprom_report, rom_bytes=emulator.memory.rom.data)
|
||||
print(f"eeprom_report={args.eeprom_report}")
|
||||
if args.eeprom_report_json:
|
||||
write_eeprom_snapshot(
|
||||
emulator.memory,
|
||||
args.eeprom_report_json,
|
||||
rom_bytes=emulator.memory.rom.data,
|
||||
as_json=True,
|
||||
include_image_hex=args.eeprom_report_include_image,
|
||||
)
|
||||
print(f"eeprom_report_json={args.eeprom_report_json}")
|
||||
return 0
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user