1
0

EMualtor adjustments

This commit is contained in:
Aiden
2026-05-25 20:42:45 +10:00
parent d2e7609bbf
commit 3ab79648ff
17 changed files with 3047 additions and 83 deletions

View File

@@ -0,0 +1,63 @@
import unittest
from h8536.emulator import H8536Emulator
def rom_with_reset(*, reset: int = 0x1000, size: int = 0x1100) -> bytearray:
rom = bytearray([0xFF] * size)
rom[0:2] = reset.to_bytes(2, "big")
return rom
class EmulatorAddressingTest(unittest.TestCase):
def test_txi_indexed_byte_load_uses_signed_word_displacement_and_full_index_register(self):
rom = rom_with_reset()
rom[0x1000:0x1004] = b"\xF0\xF8\x58\x80" # MOV:G.B @(-H'07A8,R0), R0
emulator = H8536Emulator(bytes(rom))
emulator.cpu.regs[0] = 5
emulator.memory.write8(0xF85D, 0xA6)
emulator.step()
self.assertEqual(emulator.cpu.regs[0], 0x00A6)
self.assertEqual(emulator.cpu.pc, 0x1004)
def test_txi_indexed_byte_load_preserves_destination_high_byte(self):
rom = rom_with_reset()
rom[0x1000:0x1004] = b"\xF0\xF8\x58\x80" # MOV:G.B @(-H'07A8,R0), R0
emulator = H8536Emulator(bytes(rom))
emulator.cpu.regs[0] = 0x0105
emulator.memory.write8(0xF95D, 0x4B)
emulator.step()
self.assertEqual(emulator.cpu.regs[0], 0x014B)
def test_extu_byte_clears_high_byte_before_txi_indexed_load(self):
rom = rom_with_reset()
rom[0x1000:0x1002] = b"\xA0\x12" # EXTU.B R0
rom[0x1002:0x1006] = b"\xF0\xF8\x58\x80" # MOV:G.B @(-H'07A8,R0), R0
emulator = H8536Emulator(bytes(rom))
emulator.cpu.regs[0] = 0x0105
emulator.memory.write8(0xF85D, 0xC7)
emulator.memory.write8(0xF95D, 0x99)
emulator.run(max_steps=2)
self.assertEqual(emulator.cpu.regs[0], 0x00C7)
self.assertEqual(emulator.cpu.pc, 0x1006)
def test_signed_byte_displacement_addresses_below_base(self):
rom = rom_with_reset()
rom[0x1000:0x1003] = b"\xE1\xFE\x81" # MOV:G.B @(H'-02,R1), R1
emulator = H8536Emulator(bytes(rom))
emulator.cpu.regs[1] = 0xFE90
emulator.memory.write8(0xFE8E, 0x37)
emulator.step()
self.assertEqual(emulator.cpu.regs[1], 0xFE37)
if __name__ == "__main__":
unittest.main()

View File

@@ -64,6 +64,8 @@ class P9FastPathTest(unittest.TestCase):
self.assertEqual(emulator.cpu.regs[7], 0xFE7E)
self.assertEqual(fast_path.events[-1].kind, "read_byte")
self.assertEqual(fast_path.events[-1].value, 0x3C)
self.assertEqual(fast_path.events[-1].source, "initial")
self.assertEqual(fast_path.events[-1].queue_depth, 0)
self.assertFalse(emulator.cpu.z)
self.assertFalse(emulator.cpu.n)
self.assertFalse(emulator.cpu.v)
@@ -79,9 +81,30 @@ class P9FastPathTest(unittest.TestCase):
self.assertTrue(fast_path.try_handle(emulator))
self.assertEqual(emulator.cpu.regs[5], 0x0081)
self.assertEqual(emulator.cpu.pc, 0x5678)
self.assertEqual(fast_path.events[-1].source, "default_input_byte")
self.assertEqual(fast_path.events[-1].queue_depth, 0)
self.assertIn("source=default_input_byte", fast_path.events[-1].line())
self.assertFalse(emulator.cpu.z)
self.assertTrue(emulator.cpu.n)
def test_named_input_script_records_read_source_and_remaining_depth(self):
emulator = H8536Emulator(bytes(rom_with_reset()))
emulator.cpu.pc = LOC_C0DB_P9_READ_BYTE
emulator.cpu.regs[7] = 0xFE82
emulator.memory.write16(0xFE82, 0x6789)
fast_path = P9FastPath(P9FastPathConfig(enabled=True))
fast_path.queue_input_script("idle-panel", [0x00, 0x80])
self.assertTrue(fast_path.try_handle(emulator))
event = fast_path.events[-1]
self.assertEqual(emulator.cpu.regs[5], 0x0000)
self.assertEqual(event.kind, "read_byte")
self.assertEqual(event.value, 0x00)
self.assertEqual(event.source, "script:idle-panel")
self.assertEqual(event.queue_depth, 1)
self.assertEqual(fast_path.trace_lines(), ["read_byte pc=C0DB value=00 source=script:idle-panel queued=1"])
if __name__ == "__main__":
unittest.main()

View File

@@ -1,6 +1,18 @@
import unittest
from collections import Counter
from h8536.emulator.probe import DEFAULT_WATCH_PCS, parse_watch_pc, run_probe
from h8536.emulator.probe import (
DEFAULT_WATCH_PCS,
ProbeReport,
RAMLifecycleTrace,
ReportGateTrace,
ReportQueueTrace,
TXFrameSnapshot,
TXFrameWriteTrace,
parse_watch_pc,
parse_tx_frame,
run_probe,
)
def rom_with_reset(*, reset: int = 0x1000, size: int = 0x1100) -> bytearray:
@@ -15,6 +27,13 @@ class EmulatorProbeTest(unittest.TestCase):
self.assertEqual(parse_watch_pc("0xC08B"), 0xC08B)
self.assertEqual(parse_watch_pc("H'C08B"), 0xC08B)
def test_parse_tx_frame_accepts_spaced_and_compact_hex(self):
expected = bytes.fromhex("00 00 00 00 80 DA")
self.assertEqual(parse_tx_frame("00 00 00 00 80 DA"), expected)
self.assertEqual(parse_tx_frame("0000000080DA"), expected)
self.assertEqual(parse_tx_frame("00,00,00,00,80,DA"), expected)
def test_default_watch_pcs_include_bit_bang_transfer_path(self):
self.assertIn(0xC08B, DEFAULT_WATCH_PCS)
self.assertIn(0xC0DB, DEFAULT_WATCH_PCS)
@@ -51,6 +70,291 @@ class EmulatorProbeTest(unittest.TestCase):
self.assertIn("H'1005<-H'1003", snapshot.line())
self.assertTrue(any("recent_watch_snapshots:" == line for line in report.lines()))
def test_report_lines_include_compact_tx_frame_snapshots(self):
staging_and_frame = bytes.fromhex("11 22 33 44 55 66 77 88 00 00 00 00 80 DA")
report = ProbeReport(
steps=99,
pc=0xBA72,
stopped_reason="tx",
hot_pcs=Counter({0xBA72: 1}),
tx_frame_snapshots=[
TXFrameSnapshot(
step=98,
pc=0xBA72,
label="first_tdr",
bytes_f850_f85d=staging_and_frame,
computed_checksum=0xDA,
stored_checksum=0xDA,
checksum_ok=True,
)
],
)
lines = report.lines()
self.assertIn("recent_tx_frame_snapshots:", lines)
self.assertIn(
" step=98 pc=H'BA72 first_tdr "
"F850-F85D=11 22 33 44 55 66 77 88 00 00 00 00 80 DA "
"TX=00 00 00 00 80 DA computed=DA stored=DA checksum_ok=1",
lines,
)
def test_report_lines_include_target_frame_diff_and_write_sources(self):
report = ProbeReport(
steps=10,
pc=0x1004,
stopped_reason="max_steps",
hot_pcs=Counter({0x1000: 1}),
final_tx_frame=bytes.fromhex("00 01 7F 00 00 24"),
target_frame=bytes.fromhex("00 00 00 00 80 DA"),
tx_frame_write_traces=[
TXFrameWriteTrace(
step=2,
pc=0x1003,
address=0xF859,
old_value=0x00,
new_value=0x01,
frame_after=bytes.fromhex("00 01 00 00 00 00"),
instruction="H'1003: 1D F8 58 90 MOV:G.W R0, @H'F858",
regs=(0x017F, 0, 0, 0, 0, 0, 0, 0),
target_value=0x00,
)
],
tx_target_divergences=[
TXFrameWriteTrace(
step=2,
pc=0x1003,
address=0xF859,
old_value=0x00,
new_value=0x01,
frame_after=bytes.fromhex("00 01 00 00 00 00"),
instruction="H'1003: 1D F8 58 90 MOV:G.W R0, @H'F858",
regs=(0x017F, 0, 0, 0, 0, 0, 0, 0),
target_value=0x00,
)
],
report_queue_traces=[
ReportQueueTrace(
step=3,
pc=0x1007,
kind="queue_write",
head=0,
tail=0,
queue_index=0,
address=0xF870,
old_word=0x0000,
new_word=0x00FF,
instruction="H'1003: 1D F8 70 93 MOV:G.W R3, @H'F870",
regs=(0, 0, 0, 0x00FF, 0, 0, 0, 0),
)
],
report_gate_traces=[
ReportGateTrace(
step=4,
pc=0x4050,
label="faa5_clear_enqueue_branch",
f9c4=0,
faa5=0,
f9c3=0,
head=0,
tail=0,
regs=(0, 0, 0, 0, 0, 0, 0, 0),
z=True,
c=False,
n=False,
decision="enqueue_candidate_faa5_clear",
f9c4_last_write_step=3,
f9c4_last_write_pc=0x40E0,
f9c4_last_write_value=0x00,
f9c4_last_write_age=1,
f9c4_last_nonzero_step=2,
f9c4_last_nonzero_pc=0x40D8,
f9c4_last_nonzero_value=0x14,
f9c4_last_nonzero_age=2,
)
],
ram_lifecycle_traces=[
RAMLifecycleTrace(
step=3,
pc=0x40E0,
address=0xF9C4,
name="F9C4_report_gate_timer",
old_value=0x14,
new_value=0x00,
instruction="H'40E0: 15 F9 C4 13 CLR.B @H'F9C4",
regs=(0, 0, 0, 0, 0, 0, 0, 0),
)
],
)
lines = report.lines()
self.assertIn(
"target_frame=target=00 00 00 00 80 DA current=00 01 7F 00 00 24 "
"diffs=1:01!=00 2:7F!=00 4:00!=80 5:24!=DA pre_checksum_diffs=1,2,4",
lines,
)
self.assertIn("recent_tx_frame_writes:", lines)
self.assertIn("first_target_divergences:", lines)
self.assertIn("recent_report_queue:", lines)
self.assertIn("recent_report_gates:", lines)
self.assertIn("recent_ram_lifecycle:", lines)
self.assertTrue(any("TX[1]" in line and "target=00 DIFF" in line for line in lines))
self.assertTrue(any("queue_write" in line and "word=H'0000->H'00FF" in line for line in lines))
self.assertTrue(any("decision=enqueue_candidate_faa5_clear" in line for line in lines))
self.assertTrue(any("F9C4_last=step=3@H'40E0 value=00 age=1" in line for line in lines))
self.assertTrue(any("F9C4_last_nonzero=step=2@H'40D8 value=14 age=2" in line for line in lines))
self.assertTrue(any("F9C4_report_gate_timer H'F9C4 14->00" in line for line in lines))
def test_run_probe_captures_tx_frame_watch_pc_and_bad_checksum(self):
rom = rom_with_reset(reset=0xBA26, size=0xBB00)
rom[0xBA26] = 0xFF
report = run_probe(
bytes(rom),
max_steps=1,
interval_steps=512,
stop_on_tx=False,
p9_log_limit=8,
watch_pcs=(),
tx_frame_snapshot_limit=4,
)
self.assertEqual(len(report.tx_frame_snapshots), 1)
snapshot = report.tx_frame_snapshots[0]
self.assertEqual(snapshot.pc, 0xBA26)
self.assertEqual(snapshot.label, "builder_entry")
self.assertEqual(snapshot.frame_bytes, b"\x00\x00\x00\x00\x00\x00")
self.assertEqual(snapshot.computed_checksum, 0x5A)
self.assertEqual(snapshot.stored_checksum, 0x00)
self.assertFalse(snapshot.checksum_ok)
def test_run_probe_traces_tx_frame_writes_against_target(self):
rom = rom_with_reset()
rom[0x1000:0x1003] = b"\x58\x01\x7F" # MOV:I.W #H'017F, R0
rom[0x1003:0x1007] = b"\x1D\xF8\x58\x90" # MOV:G.W R0, @H'F858
report = run_probe(
bytes(rom),
max_steps=2,
interval_steps=512,
stop_on_tx=False,
p9_log_limit=8,
watch_pcs=(),
trace_frame_sources=True,
target_frame=bytes.fromhex("00 00 00 00 80 DA"),
frame_write_trace_limit=4,
)
self.assertEqual(report.final_tx_frame[:3], b"\x01\x7F\x00")
self.assertEqual(len(report.tx_frame_write_traces), 2)
self.assertEqual(report.tx_frame_write_traces[0].address, 0xF858)
self.assertEqual(report.tx_frame_write_traces[0].target_value, 0x00)
self.assertIn("MOV:G.W R0, @H'F858", report.tx_frame_write_traces[0].instruction)
self.assertEqual(report.tx_target_divergences, [])
self.assertTrue(any("target_frame=" in line for line in report.lines()))
def test_run_probe_traces_report_queue_writes_and_cursors(self):
rom = rom_with_reset()
rom[0x1000:0x1003] = b"\x5B\x00\xFF" # MOV:I.W #H'00FF, R3
rom[0x1003:0x1007] = b"\x1D\xF8\x70\x93" # MOV:G.W R3, @H'F870
rom[0x1007:0x100B] = b"\x15\xF9\xB0\x08" # ADD:Q.B #1, @H'F9B0
report = run_probe(
bytes(rom),
max_steps=3,
interval_steps=512,
stop_on_tx=False,
p9_log_limit=8,
watch_pcs=(),
trace_report_queue=True,
report_queue_trace_limit=8,
watch_report_ids=(0x00FF,),
)
queue_writes = [trace for trace in report.report_queue_traces if trace.kind == "queue_write"]
cursor_writes = [trace for trace in report.report_queue_traces if trace.kind == "cursor_head_write"]
self.assertEqual(len(queue_writes), 1)
self.assertEqual(queue_writes[0].queue_index, 0)
self.assertEqual(queue_writes[0].old_word, 0x0000)
self.assertEqual(queue_writes[0].new_word, 0x00FF)
self.assertEqual(report.report_queue_first_writes, queue_writes)
self.assertEqual(report.report_queue_first_nonzero_writes, queue_writes)
self.assertEqual(report.report_queue_watch_hits, queue_writes)
self.assertEqual(len(cursor_writes), 1)
self.assertEqual(cursor_writes[0].old_value, 0x00)
self.assertEqual(cursor_writes[0].new_value, 0x01)
self.assertIn("recent_report_queue:", report.lines())
def test_run_probe_traces_ram_lifecycle_writes_and_last_nonzero_value(self):
rom = rom_with_reset()
rom[0x1000:0x1005] = b"\x15\xF9\xC4\x06\x14" # MOV:G.B #H'14, @H'F9C4
rom[0x1005:0x1009] = b"\x15\xF9\xC4\x13" # CLR.B @H'F9C4
report = run_probe(
bytes(rom),
max_steps=2,
interval_steps=512,
stop_on_tx=False,
p9_log_limit=8,
watch_pcs=(),
trace_ram_lifecycle=True,
ram_lifecycle_trace_limit=4,
)
self.assertEqual(len(report.ram_lifecycle_traces), 2)
set_trace, clear_trace = report.ram_lifecycle_traces
self.assertEqual(set_trace.address, 0xF9C4)
self.assertEqual(set_trace.old_value, 0x00)
self.assertEqual(set_trace.new_value, 0x14)
self.assertEqual(clear_trace.old_value, 0x14)
self.assertEqual(clear_trace.new_value, 0x00)
self.assertEqual(report.ram_lifecycle_last_writes, [clear_trace])
self.assertEqual(report.ram_lifecycle_last_nonzero_writes, [set_trace])
lines = report.lines()
self.assertIn("recent_ram_lifecycle:", lines)
self.assertIn("ram_lifecycle_last_writes:", lines)
self.assertIn("ram_lifecycle_last_nonzero_writes:", lines)
def test_run_probe_traces_loc_4046_report_gates(self):
rom = rom_with_reset(reset=0x4046, size=0x4080)
rom[0x4046:0x404A] = b"\x15\xF9\xC4\x16" # TST.B @H'F9C4
rom[0x404A:0x404C] = b"\x26\x0C" # BNE H'4058
rom[0x404C:0x4050] = b"\x15\xFA\xA5\xF7" # BTST.B #7, @H'FAA5
rom[0x4050:0x4052] = b"\x27\x07" # BEQ H'4059
rom[0x4052:0x4056] = b"\x15\xF9\xC3\x16" # TST.B @H'F9C3
rom[0x4056:0x4058] = b"\x27\x01" # BEQ H'4059
rom[0x4058] = 0x19 # RTS
rom[0x4059:0x405D] = b"\x15\xF9\xB0\x82" # MOV:G.B @H'F9B0, R2
rom[0x405D:0x405F] = b"\xA2\x12" # EXTU.B R2
rom[0x405F:0x4063] = b"\x15\xF9\xB5\x72" # CMP:G.B @H'F9B5, R2
rom[0x4063:0x4065] = b"\x26\x0F" # BNE H'4074
rom[0x4065:0x4067] = b"\xA2\x1A" # SHLL.B R2
rom[0x4067:0x406C] = b"\xFA\xF8\x70\x06\x00" # MOV:G.W #H'00, @(-H'0790,R2)
rom[0x406C:0x4070] = b"\x15\xF9\xB0\x08" # ADD:Q.B #1, @H'F9B0
rom[0x4070:0x4074] = b"\x15\xF9\xB0\xD7" # BCLR.B #7, @H'F9B0
report = run_probe(
bytes(rom),
max_steps=10,
interval_steps=512,
stop_on_tx=False,
p9_log_limit=8,
watch_pcs=(),
trace_report_gates=True,
report_gate_trace_limit=16,
)
decisions = {trace.pc: trace.decision for trace in report.report_gate_traces}
self.assertEqual(decisions[0x4046], "f9c4_zero_continue")
self.assertEqual(decisions[0x4050], "enqueue_candidate_faa5_clear")
self.assertEqual(decisions[0x4063], "enqueue_zero_report")
self.assertEqual(decisions[0x4067], "write_report_00ff_to_queue_slot")
self.assertTrue(any("recent_report_gates:" == line for line in report.lines()))
if __name__ == "__main__":
unittest.main()

View File

@@ -41,6 +41,20 @@ def fixture_payload() -> dict[str, object]:
ins(0x3FEF, "TST.B @H'F9C5", references=[0xF9C5]),
ins(0x3FF5, "CLR.B @H'F9B5", references=[0xF9B5]),
ins(0x3FF9, "CLR.B @H'F9B0", references=[0xF9B0]),
ins(0x4046, "TST.B @H'F9C4", references=[0xF9C4]),
ins(0x404A, "BNE loc_4058", targets=[0x4058]),
ins(0x404C, "BTST.B #7, @H'FAA5", references=[0xFAA5]),
ins(0x4050, "BEQ loc_4059", targets=[0x4059]),
ins(0x4052, "TST.B @H'F9C3", references=[0xF9C3]),
ins(0x4056, "BEQ loc_4059", targets=[0x4059]),
ins(0x4058, "RTS"),
ins(0x4059, "MOV:G.B @H'F9B0, R2", references=[0xF9B0]),
ins(0x405F, "CMP:G.B @H'F9B5, R2", references=[0xF9B5]),
ins(0x4063, "BNE loc_4074", targets=[0x4074]),
ins(0x4067, "MOV:G.W #H'00, @(-H'0790,R2)"),
ins(0x406C, "ADD:Q.B #1, @H'F9B0", references=[0xF9B0]),
ins(0x4070, "BCLR.B #7, @H'F9B0", references=[0xF9B0]),
ins(0x40E0, "MOV:G.B #H'14, @H'F9C4", references=[0xF9C4]),
ins(0xBAF2, "MOV:G.B @H'F9B5, R1", references=[0xF9B5]),
ins(0xBAF8, "CMP:G.B @H'F9B0, R1", references=[0xF9B0]),
ins(0xBAFC, "BNE loc_BB00", targets=[0xBB00]),
@@ -67,6 +81,7 @@ def fixture_payload() -> dict[str, object]:
ins(0xBEA5, "AND.B @H'FAA3, R0", references=[0xFAA3]),
ins(0xBEA9, "MOV:G.B R0, @H'FAA3", references=[0xFAA3]),
ins(0xBEAF, "CLR.B @H'FAA2", references=[0xFAA2]),
ins(0xBA31, "MOV:G.B #H'07, @H'F9C4", references=[0xF9C4]),
ins(0xBEB5, "TST.W @H'F9C6", references=[0xF9C6]),
ins(0xBEBB, "TST.B @H'F9C8", references=[0xF9C8]),
ins(0xBEC5, "MOV:G.W #H'01F4, @H'F9C6", references=[0xF9C6]),
@@ -83,9 +98,15 @@ def fixture_payload() -> dict[str, object]:
ins(0xBF02, "TST.W @H'F9C6", references=[0xF9C6]),
ins(0xBF06, "BEQ loc_BF0C", targets=[0xBF0C]),
ins(0xBF08, "ADD:Q.W #-1, @H'F9C6", references=[0xF9C6]),
ins(0xBF23, "BCLR.B #5, @FRT2_TCSR"),
ins(0xBF27, "TST.B @H'F9C4", references=[0xF9C4]),
ins(0xBF2D, "ADD:Q.B #-1, @H'F9C4", references=[0xF9C4]),
]
return {
"vectors": [{"address": 0x0062, "name": "frt1_ocia", "target": 0xBEEA, "target_label": "vec_frt1_ocia_BEEA"}],
"vectors": [
{"address": 0x0062, "name": "frt1_ocia", "target": 0xBEEA, "target_label": "vec_frt1_ocia_BEEA"},
{"address": 0x006A, "name": "frt2_ocia", "target": 0xBF23, "target_label": "vec_frt2_ocia_BF23"},
],
"call_graph": {"nodes": [{"start": 0x3FD3, "label": "loc_3FD3"}, {"start": 0xBAF2, "label": "loc_BAF2"}]},
"instructions": rows,
}
@@ -119,6 +140,17 @@ class SerialGateTest(unittest.TestCase):
self.assertIn("secondary delay", roles[0xF9C1]["role"])
self.assertIn("periodic report/heartbeat", roles[0xF9C6]["role"])
def test_json_analysis_includes_frt2_idle_heartbeat_gate(self):
analysis = analyze_serial_gate(fixture_payload())
gate = analysis["evidence"]["idle_heartbeat_gate_loc_4046"]
self.assertTrue(gate["present"])
self.assertEqual(gate["timer"]["handler_address_hex"], "H'BF23")
self.assertEqual(gate["post_tx_reload_value_hex"], "H'07")
self.assertIn("0.7s", gate["summary"])
roles = {role["address"]: role for role in gate["candidate_timer_roles"]}
self.assertIn("heartbeat", roles[0xF9C4]["role"])
def test_summarizes_key_state_readers_and_writers(self):
analysis = analyze_serial_gate(fixture_payload())
accesses = {entry["address"]: entry for entry in analysis["state_accesses"]}
@@ -126,6 +158,7 @@ class SerialGateTest(unittest.TestCase):
self.assertGreaterEqual(accesses[0xF9B5]["read_count"], 1)
self.assertGreaterEqual(accesses[0xF9B5]["read_write_count"], 1)
self.assertGreaterEqual(accesses[0xF9C1]["read_write_count"], 1)
self.assertGreaterEqual(accesses[0xF9C4]["read_write_count"], 1)
self.assertGreaterEqual(accesses[0xFAA3]["write_count"], 1)
self.assertIn("sample_accesses", accesses[0xFAA2])
@@ -145,6 +178,14 @@ class SerialGateTest(unittest.TestCase):
self.assertIn("H'F9C1: candidate secondary delay countdown", text)
self.assertIn("H'F9C6: candidate periodic report/heartbeat countdown", text)
def test_text_report_mentions_frt2_idle_heartbeat_gate(self):
text = format_text_report(analyze_serial_gate(fixture_payload()))
self.assertIn("loc_4046 idle heartbeat/report gate: present", text)
self.assertIn("FRT2 OCIA", text)
self.assertIn("H'F9C4: candidate idle heartbeat/report gate countdown", text)
self.assertIn("observed period ~= 700ms", text)
def test_cli_json_output_and_out_file(self):
with tempfile.TemporaryDirectory() as tmp:
input_path = Path(tmp) / "rom.json"

View File

@@ -403,6 +403,7 @@ class SerialPseudocodeTest(unittest.TestCase):
self.assertIn("host_ack_can_advance_queue: Commands 0x05/0x06 can ack or advance F9B5", text)
self.assertIn("static bool sci1_candidate_main_report_gate_open(void)", text)
self.assertIn("static bool sci1_candidate_report_queue_nonempty(void)", text)
self.assertIn("static bool sci1_candidate_idle_heartbeat_enqueue_gate_open(void)", text)
self.assertIn("static bool sci1_candidate_periodic_resend_gate_open(void)", text)
self.assertIn("TX/autonomous report model candidate:", text)
self.assertIn("loc_BB43 -> loc_BA26: bytes 0..2 encode candidate logical index/report id; bytes 3..4 come from current_value_table_candidate", text)
@@ -421,6 +422,41 @@ class SerialPseudocodeTest(unittest.TestCase):
self.assertIn("MEM8[0xF9C6u] = (u8)(MEM8[0xF9C6u] - 1u);", text)
self.assertIn("candidate effect: table_write_candidate; target primary_value_table_candidate", text)
def test_timer_source_models_emit_separate_tick_isrs(self):
analysis = {
"protocol_semantics": [
{
"confidence": "medium",
"confidence_score": 0.6,
"timer_interrupt_model": {
"sources": [
{
"source": "FRT2 OCIA",
"handler_address_hex": "H'BF23",
"summary": "Candidate periodic tick ISR for idle heartbeat/report counters.",
"counters": [
{
"address": 0xF9C4,
"address_hex": "H'F9C4",
"name_candidate": "idle_heartbeat_gate_countdown_candidate",
"role": "candidate idle/default report enqueue countdown.",
}
],
}
]
},
}
]
}
with patch("h8536.serial_pseudocode.analyze_serial_semantics", return_value=analysis):
text = generate_serial_pseudocode(candidate_payload())
self.assertIn("FRT2 OCIA H'BF23", text)
self.assertIn("H'F9C4 idle_heartbeat_gate_countdown_candidate", text)
self.assertIn("void frt2_ocia_candidate_tick_isr(void)", text)
self.assertIn("MEM8[0xF9C4u] = (u8)(MEM8[0xF9C4u] - 1u);", text)
def test_tx_only_option_omits_rx_functions(self):
text = generate_serial_pseudocode(
candidate_payload(),

View File

@@ -117,6 +117,16 @@ def planned_semantics_payload() -> dict:
instruction(0x3FF9, "CLR.B", "@H'F9B0", [0xF9B0]),
instruction(0x3FFD, "BCLR.B", "#7, @H'FAA5", [0xFAA5]),
instruction(0x4007, "BSET.B", "#7, @H'FAA5", [0xFAA5]),
instruction(0x4046, "TST.B", "@H'F9C4", [0xF9C4]),
instruction(0x404C, "BTST.B", "#7, @H'FAA5", [0xFAA5]),
instruction(0x4050, "BEQ", "loc_4059", targets=[0x4059]),
instruction(0x4052, "TST.B", "@H'F9C3", [0xF9C3]),
instruction(0x405F, "CMP:G.B", "@H'F9B5, R2", [0xF9B5]),
instruction(0x4067, "MOV:G.W", "#H'00, @(-H'0790,R2)"),
instruction(0x406C, "ADD:Q.B", "#1, @H'F9B0", [0xF9B0]),
instruction(0x4070, "BCLR.B", "#7, @H'F9B0", [0xF9B0]),
instruction(0x40E0, "MOV:G.B", "#H'14, @H'F9C4", [0xF9C4]),
instruction(0xBA31, "MOV:G.B", "#H'07, @H'F9C4", [0xF9C4]),
instruction(0xC000, "MOV:G.B", "@H'F861, R1", [0xF861]),
instruction(0xC004, "MOV:G.B", "@H'F862, R2", [0xF862]),
instruction(0xC008, "BSR", "loc_622B", targets=[0x622B]),
@@ -319,6 +329,8 @@ class SerialSemanticsTest(unittest.TestCase):
self.assertIn("faa2", state_text)
self.assertIn("f9b5", state_text)
self.assertIn("f9c0", state_text)
self.assertIn("f9c4", state_text)
self.assertIn("idle_heartbeat_gate_countdown", state_text)
def test_planned_retry_error_model_identifies_retransmit_and_checksum_error(self):
semantics = only_semantics(self, planned_semantics_payload())
@@ -368,6 +380,8 @@ class SerialSemanticsTest(unittest.TestCase):
self.assertIn("baf2", gate_text)
self.assertIn("faa2 == 0", gate_text)
self.assertIn("f9c0 == 0", gate_text)
self.assertIn("f9c4 == 0", gate_text)
self.assertIn("h'00ff", gate_text)
self.assertIn("f9b5 != f9b0", gate_text)
self.assertIn("bb43", gate_text)
self.assertIn("be9e", gate_text)
@@ -377,6 +391,28 @@ class SerialSemanticsTest(unittest.TestCase):
self.assertIn("0x06", gate_text)
self.assertIn("not rom constants", gate_text)
def test_timer_interrupt_model_surfaces_frt2_idle_heartbeat_counter(self):
semantics = only_semantics(
self,
base_payload(
[
instruction(0xBF23, "BCLR.B", "#5, @FRT2_TCSR"),
instruction(0xBF27, "TST.B", "@H'F9C4", [0xF9C4]),
instruction(0xBF2D, "ADD:Q.B", "#-1, @H'F9C4", [0xF9C4]),
instruction(0xBF31, "TST.B", "@H'F9C5", [0xF9C5]),
instruction(0xBF37, "ADD:Q.B", "#-1, @H'F9C5", [0xF9C5]),
]
),
)
timer = semantics["timer_interrupt_model"]
timer_text = semantic_text(timer)
self.assertIn("frt2 ocia", timer_text)
self.assertIn("f9c4", timer_text)
self.assertIn("idle_heartbeat_gate_countdown", timer_text)
self.assertIn("phi/32", timer_text)
def test_missing_serial_reconstruction_candidates_emit_no_protocol_semantics(self):
payload = {
"serial_reconstruction": {"candidates": []},