1
0

LCD emulation

This commit is contained in:
Aiden
2026-05-25 21:33:19 +10:00
parent e141f3b30d
commit 191b72d418
7 changed files with 113 additions and 11 deletions

View File

@@ -46,6 +46,7 @@ from .cpu import CPUState
from .errors import EmulatorError, UnsupportedInstruction
from .fast_paths import P9FastPath, P9FastPathConfig, P9FastPathEvent
from .memory import MemoryAccess, MemoryMap, describe_regions
from .peripherals import LCD
from .runner import H8536Emulator, RunReport
from .sci import SCI1, SciTxEvent
@@ -63,6 +64,7 @@ __all__ = [
"IPRA",
"IPRC",
"IPRE",
"LCD",
"MemoryAccess",
"MemoryMap",
"ON_CHIP_RAM_END",

View File

@@ -20,7 +20,7 @@ from .constants import (
SCI1_SSR,
SCI1_TDR,
)
from .peripherals.lcd import LCD_E_CLOCK_DATA, LCD_E_CLOCK_STATUS
from .peripherals.lcd import LCD, LCD_E_CLOCK_DATA, LCD_E_CLOCK_STATUS
from .peripherals.p9_bus import P9Bus
from .sci import SCI1
@@ -38,6 +38,7 @@ class MemoryMap:
def __init__(self, rom_bytes: bytes, sci1: SCI1 | None = None) -> None:
self.rom = Rom(rom_bytes, base=0)
self.sci1 = sci1 if sci1 is not None else SCI1()
self.lcd = LCD()
self.p9_bus = P9Bus()
self.ram = bytearray(ON_CHIP_RAM_END - ON_CHIP_RAM_START + 1)
self.registers = bytearray(REGISTER_FIELD_END - REGISTER_FIELD_START + 1)
@@ -60,11 +61,9 @@ class MemoryMap:
value = self.sci1.read(address)
self._set_register(address, value)
elif address == LCD_E_CLOCK_STATUS:
# LCD E-clock/status space. Default to ready/zero so boot can pass
# busy-flag polling until a fuller external bus model exists.
value = 0x00
value = self.lcd.read_status()
elif address == LCD_E_CLOCK_DATA:
value = self.external.get(address, 0x00)
value = self.lcd.read_data()
elif address in self.external:
value = self.external[address]
elif ON_CHIP_RAM_START <= address <= ON_CHIP_RAM_END:
@@ -94,6 +93,12 @@ class MemoryMap:
if address in (SCI1_SMR, SCI1_BRR, SCI1_SCR, SCI1_TDR, SCI1_SSR, SCI1_RDR):
self.sci1.write(address, value)
self._set_register(address, self.sci1.read(address))
elif address == LCD_E_CLOCK_STATUS:
self.lcd.write_command(value)
self.external[address] = value
elif address == LCD_E_CLOCK_DATA:
self.lcd.write_data(value)
self.external[address] = value
elif ON_CHIP_RAM_START <= address <= ON_CHIP_RAM_END:
self.ram[address - ON_CHIP_RAM_START] = value
elif address == P9DDR:

View File

@@ -1,11 +1,13 @@
from __future__ import annotations
from .lcd import LCD_E_CLOCK_DATA, LCD_E_CLOCK_STATUS
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
__all__ = [
"LCD_E_CLOCK_DATA",
"LCD_E_CLOCK_STATUS",
"LCD",
"LCD_LINE_WIDTH",
"P9_ACK_BIT",
"P9_STROBE_BIT",
"P9Bus",

View File

@@ -1,5 +1,69 @@
from __future__ import annotations
from dataclasses import dataclass, field
LCD_E_CLOCK_STATUS = 0xF200
LCD_E_CLOCK_DATA = 0xF201
LCD_LINE_WIDTH = 16
LCD_LINE_STARTS = (0x00, 0x40, 0x10, 0x50)
LCD_DDRAM_SIZE = 0x80
@dataclass
class LCD:
ddram: bytearray = field(default_factory=lambda: bytearray([0x20] * LCD_DDRAM_SIZE))
cursor: int = 0
data_latch: int = 0
command_latch: int = 0
def read_status(self) -> int:
return self.cursor & 0x7F
def read_data(self) -> int:
return self.data_latch & 0xFF
def write_command(self, value: int) -> None:
value &= 0xFF
self.command_latch = value
if value & 0x80:
self.cursor = value & 0x7F
elif value == 0x01:
self.ddram[:] = bytes([0x20]) * LCD_DDRAM_SIZE
self.cursor = 0
elif value == 0x02:
self.cursor = 0
def write_data(self, value: int) -> None:
value &= 0xFF
self.data_latch = value
if 0 <= self.cursor < LCD_DDRAM_SIZE:
self.ddram[self.cursor] = value
self.cursor = (self.cursor + 1) & 0x7F
def line_text(self, line: int, width: int = LCD_LINE_WIDTH) -> str:
if not 0 <= line < len(LCD_LINE_STARTS):
raise ValueError(f"LCD line out of range: {line}")
start = LCD_LINE_STARTS[line]
return "".join(_display_char(self.ddram[(start + offset) & 0x7F]) for offset in range(width))
def display_lines(self, lines: int = 4, width: int = LCD_LINE_WIDTH) -> list[str]:
return [self.line_text(line, width) for line in range(lines)]
def display_text(self, lines: int = 4, width: int = LCD_LINE_WIDTH) -> str:
return " | ".join(self.display_lines(lines, width))
def _display_char(value: int) -> str:
return chr(value) if 0x20 <= value <= 0x7E else "."
__all__ = [
"LCD",
"LCD_DDRAM_SIZE",
"LCD_E_CLOCK_DATA",
"LCD_E_CLOCK_STATUS",
"LCD_LINE_STARTS",
"LCD_LINE_WIDTH",
]

View File

@@ -139,6 +139,9 @@ class FrameResult:
lines.append(" new_tx_frames=" + " | ".join(format_frame(frame) for frame in self.new_tx_frames))
else:
lines.append(" new_tx_frames=none")
lcd_display = self.state_after.get("lcd_display_ascii")
if isinstance(lcd_display, str):
lines.append(f" lcd_display={lcd_display!r}")
state_changes = _state_change_lines(self.state_before, self.state_after)
if state_changes:
lines.append(" state_changes:")
@@ -220,7 +223,8 @@ def run_rx_probe(
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))}"
f"rx_serviceable={int(_rx_ready(emulator))} "
f"lcd_display={emulator.memory.lcd.display_text(lines=4)!r}"
)
results = [
@@ -380,6 +384,7 @@ def _state_snapshot(emulator: H8536Emulator) -> dict[str, int | str]:
for address, name in STATE_WORDS.items():
snapshot[name] = emulator.memory.read16(address)
snapshot["lcd_line_buffer_ascii"] = _ascii_window(emulator, 0xFAF0, 16)
snapshot["lcd_display_ascii"] = emulator.memory.lcd.display_text(lines=4, width=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