more serial improvements
This commit is contained in:
@@ -2,6 +2,7 @@ import json
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
from h8536.serial_pseudocode import (
|
||||
SerialPseudocodeOptions,
|
||||
@@ -13,6 +14,27 @@ from h8536.serial_pseudocode import (
|
||||
def candidate_payload() -> dict:
|
||||
return {
|
||||
"instructions": [],
|
||||
"dtc_vectors": [],
|
||||
"sci": {
|
||||
"channels": {
|
||||
"SCI1": {
|
||||
"configurations": [
|
||||
{
|
||||
"mode": "async",
|
||||
"mode_summary": "async 8-bit even parity 1 stop",
|
||||
"smr": 0x24,
|
||||
"smr_hex": "H'24",
|
||||
"brr": 0x07,
|
||||
"brr_hex": "H'07",
|
||||
"scr": 0x3C,
|
||||
"scr_hex": "H'3C",
|
||||
"clock_source": "internal",
|
||||
"baud_bps": None,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
"sci_protocol": {
|
||||
"manual_references": [
|
||||
"Manual/0900766b802125d0.md:15794 RDR receive data register",
|
||||
@@ -87,8 +109,20 @@ def candidate_payload() -> dict:
|
||||
"confidence_reason": "RX count, copy, and checksum-validation evidence were observed",
|
||||
"caveat": "candidate frame means six consecutive bytes, not a proven delimited packet",
|
||||
"comment": "candidate/evidence-supported SCI1 6-byte RX frame hypothesis",
|
||||
"rx_error_handling": {
|
||||
"error_latch_address": 0xFAA4,
|
||||
"error_latch_address_hex": "H'FAA4",
|
||||
"error_latch_bit": 7,
|
||||
"fallthrough_to_rx_byte_path": True,
|
||||
"rdrf_clear_before_rdr_read": True,
|
||||
"summary": "SCI1 ERI marks a physical receive error and continues into RXI byte capture.",
|
||||
"manual_caveat": "The ROM clears RDRF before reading RDR; preserve that observed order.",
|
||||
"evidence_addresses_hex": ["H'BB57", "H'BB69", "H'BB6D"],
|
||||
},
|
||||
"evidence_addresses_hex": {
|
||||
"rx_rdr_read": ["H'BB6D"],
|
||||
"rx_rdrf_clear_before_rdr_read": ["H'BB69", "H'BB6D"],
|
||||
"rx_eri_falls_through_to_rxi": ["H'BB57", "H'BB5B", "H'BB5F", "H'BB63", "H'BB69", "H'BB6D"],
|
||||
"rx_xor_checksum_validation": ["H'BBD6", "H'BBEC"],
|
||||
},
|
||||
},
|
||||
@@ -121,6 +155,9 @@ class SerialPseudocodeTest(unittest.TestCase):
|
||||
self.assertIn("focused SCI RX/TX pseudocode from rom.json", text)
|
||||
self.assertIn("SCI1 TX 6-byte frame at H'F858-H'F85D", text)
|
||||
self.assertIn("SCI1 RX 6-byte frame captured at H'F868-H'F86D", text)
|
||||
self.assertIn("8E1 SCI characters carry the 6 protocol bytes", text)
|
||||
self.assertIn("SMR=H'24, BRR=H'07, SCR=H'3C", text)
|
||||
self.assertIn("No SCI1 RXI/TXI DTC vector entries are present", text)
|
||||
self.assertIn("MAX202 pin 11 traces to H8 pin 66", text)
|
||||
self.assertIn("Manual/0900766b802125d0.md:15823 TDR transmit data register", text)
|
||||
self.assertIn("#define TX_FRAME(n) MEM8[(u16)(0xF858u + (n))]", text)
|
||||
@@ -132,6 +169,9 @@ class SerialPseudocodeTest(unittest.TestCase):
|
||||
self.assertIn("SCI1_SSR &= (u8)~SCI_SSR_RDRF;\n byte = SCI1_RDR;", text)
|
||||
self.assertIn("RX_CAPTURE(RX_INDEX) = byte;", text)
|
||||
self.assertIn("return sci1_process_rx_candidate_frame();", text)
|
||||
self.assertIn("RX_ERROR_LATCH |= RX_ERROR_LATCH_PHYSICAL_ERROR;", text)
|
||||
self.assertIn("sci1_rx_byte_received_candidate_isr();", text)
|
||||
self.assertIn("RX error handling: SCI1 ERI marks a physical receive error", text)
|
||||
self.assertIn("rx_xor_checksum_validation: H'BBD6, H'BBEC", text)
|
||||
|
||||
def test_generates_candidate_protocol_semantics_switch(self):
|
||||
@@ -145,6 +185,110 @@ class SerialPseudocodeTest(unittest.TestCase):
|
||||
self.assertIn("case 0x01u:", text)
|
||||
self.assertIn("candidate_read_value(logical_index, value);", text)
|
||||
|
||||
def test_surfaces_refined_semantic_candidates(self):
|
||||
analysis = {
|
||||
"protocol_semantics": [
|
||||
{
|
||||
"confidence": "medium",
|
||||
"confidence_score": 0.7,
|
||||
"commands": [
|
||||
{
|
||||
"command_value": 0,
|
||||
"command_value_hex": "0x00",
|
||||
"name_candidate": "set_value_acked",
|
||||
"summary": "candidate acknowledged set",
|
||||
"effects": [
|
||||
{
|
||||
"kind": "table_write_candidate",
|
||||
"target_candidate": "primary_value_table_candidate",
|
||||
"source_candidate": "RX[3:4] value bytes",
|
||||
"table_base_hex": "H'E000",
|
||||
}
|
||||
],
|
||||
}
|
||||
],
|
||||
"command_effects": [
|
||||
{
|
||||
"command_value": 0,
|
||||
"command_value_hex": "0x00",
|
||||
"name_candidate": "set_value_acked",
|
||||
"summary": "Candidate acknowledged set writes value bytes.",
|
||||
"effects": [
|
||||
{
|
||||
"kind": "table_write_candidate",
|
||||
"target_candidate": "primary_value_table_candidate",
|
||||
"source_candidate": "RX[3:4] value bytes",
|
||||
"table_base_hex": "H'E000",
|
||||
"evidence_addresses_hex": ["H'C010"],
|
||||
}
|
||||
],
|
||||
"evidence_addresses_hex": ["H'C000"],
|
||||
}
|
||||
],
|
||||
"response_schemas": [
|
||||
{
|
||||
"response_id": "response_at_C030",
|
||||
"bytes": [
|
||||
{"byte": "byte0", "source_expression": "0x04"},
|
||||
{"byte": "byte1", "source_expression": "RX[1]"},
|
||||
],
|
||||
"evidence_addresses_hex": ["H'C01C", "H'C024"],
|
||||
}
|
||||
],
|
||||
"logical_table_map_candidates": [
|
||||
{
|
||||
"name_candidate": "primary_value_table_candidate",
|
||||
"logical_base_address_hex": "H'E000",
|
||||
"element_candidate": "word_value",
|
||||
"observed_accesses": ["write"],
|
||||
"evidence_addresses_hex": ["H'C010"],
|
||||
}
|
||||
],
|
||||
"state_variable_candidates": [
|
||||
{
|
||||
"name_candidate": "serial_session_flags_candidate",
|
||||
"address": 0xFAA2,
|
||||
"address_hex": "H'FAA2",
|
||||
"read_count": 1,
|
||||
"write_count": 2,
|
||||
"bit_candidates": [7],
|
||||
"evidence_addresses_hex": ["H'C018"],
|
||||
}
|
||||
],
|
||||
"retry_error_model": {
|
||||
"checksum_failure_path": {
|
||||
"condition_candidate": "0x5A-seeded XOR over RX[0..4] differs from RX[5]",
|
||||
"error_target": "loc_BE29",
|
||||
},
|
||||
"retry_path": {
|
||||
"counter_address_hex": "H'FAA6",
|
||||
"threshold_candidate": 2,
|
||||
"summary": "Candidate retry path stages a command 0x07 response.",
|
||||
},
|
||||
"command_0x07_path": {
|
||||
"summary": "Candidate explicit command 0x07 path copies previous TX frame bytes.",
|
||||
},
|
||||
"evidence_addresses_hex": ["H'BBD6", "H'BE29"],
|
||||
},
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
with patch("h8536.serial_pseudocode.analyze_serial_semantics", return_value=analysis):
|
||||
text = generate_serial_pseudocode(candidate_payload())
|
||||
|
||||
self.assertIn("command effects:", text)
|
||||
self.assertIn("effect: table_write_candidate; target primary_value_table_candidate", text)
|
||||
self.assertIn("response schemas:", text)
|
||||
self.assertIn("response_at_C030: byte0=0x04; byte1=RX[1]", text)
|
||||
self.assertIn("table map candidates:", text)
|
||||
self.assertIn("primary_value_table_candidate at H'E000", text)
|
||||
self.assertIn("state variable candidates:", text)
|
||||
self.assertIn("serial_session_flags_candidate H'FAA2: reads 1, writes 2; bits 7", text)
|
||||
self.assertIn("retry/error model candidate:", text)
|
||||
self.assertIn("checksum path: 0x5A-seeded XOR over RX[0..4] differs from RX[5] -> loc_BE29", text)
|
||||
self.assertIn("candidate effect: table_write_candidate; target primary_value_table_candidate", text)
|
||||
|
||||
def test_tx_only_option_omits_rx_functions(self):
|
||||
text = generate_serial_pseudocode(
|
||||
candidate_payload(),
|
||||
|
||||
@@ -88,6 +88,11 @@ class SerialReconstructionTest(unittest.TestCase):
|
||||
|
||||
def test_candidate_sci1_rx_frame_length_and_checksum_validation_pattern(self):
|
||||
instructions = {
|
||||
0x4FF0: ins(0x4FF0, "BSET.B", "#7, @H'FAA4", [0xFAA4]),
|
||||
0x4FF4: ins(0x4FF4, "BCLR.B", "#5, @SCI1_SSR", [0xFEDC]),
|
||||
0x4FF8: ins(0x4FF8, "BCLR.B", "#4, @SCI1_SSR", [0xFEDC]),
|
||||
0x4FFC: ins(0x4FFC, "BCLR.B", "#3, @SCI1_SSR", [0xFEDC]),
|
||||
0x4FFE: ins(0x4FFE, "BCLR.B", "#6, @SCI1_SSR", [0xFEDC]),
|
||||
0x5000: ins(0x5000, "MOV:G.B", "@SCI1_RDR, R0", [0xFEDD]),
|
||||
0x5004: ins(0x5004, "MOV:G.B", "R0, @(-H'0798,R1)", []),
|
||||
0x5008: ins(0x5008, "ADD:Q.B", "#1, R1", []),
|
||||
@@ -119,11 +124,24 @@ class SerialReconstructionTest(unittest.TestCase):
|
||||
self.assertEqual(candidate["checksum_address"], 0xF865)
|
||||
self.assertEqual(candidate["checksum_seed"], 0x5A)
|
||||
self.assertIn("no explicit header", candidate["confidence_reason"])
|
||||
self.assertIn("rx_rdrf_clear_before_rdr_read", candidate["evidence_addresses"])
|
||||
self.assertIn("rx_eri_falls_through_to_rxi", candidate["evidence_addresses"])
|
||||
self.assertTrue(candidate["rx_error_handling"]["fallthrough_to_rx_byte_path"])
|
||||
self.assertTrue(candidate["rx_error_handling"]["rdrf_clear_before_rdr_read"])
|
||||
self.assertEqual(candidate["rx_error_handling"]["error_latch_address"], 0xFAA4)
|
||||
|
||||
comment = serial_reconstruction_comment_for_instruction(analysis, 0x5138)
|
||||
self.assertIn("candidate/evidence-supported SCI1 6-byte RX frame", comment)
|
||||
self.assertIn("checksum H'F865", comment)
|
||||
self.assertIn("confidence high", comment)
|
||||
self.assertIn(
|
||||
"ROM clears SCI1 SSR.RDRF before reading SCI1_RDR",
|
||||
serial_reconstruction_comment_for_instruction(analysis, 0x4FFE),
|
||||
)
|
||||
self.assertIn(
|
||||
"SCI1 ERI latches FAA4.bit7",
|
||||
serial_reconstruction_comment_for_instruction(analysis, 0x4FF0),
|
||||
)
|
||||
|
||||
def test_lone_tdr_write_does_not_emit_reconstruction(self):
|
||||
instructions = {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import unittest
|
||||
from typing import Any
|
||||
|
||||
from h8536.serial_semantics import analyze_serial_semantics
|
||||
|
||||
@@ -60,6 +61,92 @@ def only_semantics(testcase: unittest.TestCase, payload: dict) -> dict:
|
||||
return analysis["protocol_semantics"][0]
|
||||
|
||||
|
||||
def semantic_items(value: Any) -> list[Any]:
|
||||
if isinstance(value, list):
|
||||
return value
|
||||
if isinstance(value, dict):
|
||||
return list(value.values())
|
||||
return []
|
||||
|
||||
|
||||
def command_item(items: list[Any], command: int) -> Any:
|
||||
for item in items:
|
||||
if not isinstance(item, dict):
|
||||
continue
|
||||
command_value = item.get("command_value", item.get("command"))
|
||||
if command_value == command:
|
||||
return item
|
||||
if isinstance(command_value, str) and command_value.lower() == f"0x{command:02x}":
|
||||
return item
|
||||
return None
|
||||
|
||||
|
||||
def semantic_text(value: Any) -> str:
|
||||
if isinstance(value, dict):
|
||||
return " ".join(
|
||||
f"{key} {semantic_text(item)}"
|
||||
for key, item in value.items()
|
||||
).lower()
|
||||
if isinstance(value, list):
|
||||
return " ".join(semantic_text(item) for item in value).lower()
|
||||
return str(value).lower()
|
||||
|
||||
|
||||
def planned_semantics_payload() -> dict:
|
||||
return base_payload(
|
||||
[
|
||||
instruction(0xBF00, "MOV:G.B", "@H'F860, R0", [0xF860]),
|
||||
instruction(0xBF04, "AND.B", "#H'07, R0"),
|
||||
instruction(0xBF08, "CMP:E.B", "#H'00, R0"),
|
||||
instruction(0xBF0C, "BEQ", "loc_C000", targets=[0xC000]),
|
||||
instruction(0xBF10, "CMP:E.B", "#H'01, R0"),
|
||||
instruction(0xBF14, "BEQ", "loc_C100", targets=[0xC100]),
|
||||
instruction(0xBF18, "CMP:E.B", "#H'06, R0"),
|
||||
instruction(0xBF1C, "BEQ", "loc_C600", targets=[0xC600]),
|
||||
instruction(0xBF20, "CMP:E.B", "#H'07, R0"),
|
||||
instruction(0xBF24, "BEQ", "loc_C700", targets=[0xC700]),
|
||||
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]),
|
||||
instruction(0xC00C, "MOV:G.W", "@H'F863, R3", [0xF863]),
|
||||
instruction(0xC010, "MOV:G.W", "R3, @H'F900", [0xF900]),
|
||||
instruction(0xC014, "MOV:G.W", "R3, @H'F920", [0xF920]),
|
||||
instruction(0xC018, "MOV:G.B", "#H'01, @H'FAA2", [0xFAA2]),
|
||||
instruction(0xC01C, "MOV:G.B", "#H'04, @H'F850", [0xF850]),
|
||||
instruction(0xC020, "MOV:G.B", "@H'F861, R4", [0xF861]),
|
||||
instruction(0xC024, "MOV:G.B", "R4, @H'F851", [0xF851]),
|
||||
instruction(0xC028, "MOV:G.B", "@H'F862, R5", [0xF862]),
|
||||
instruction(0xC02C, "MOV:G.B", "R5, @H'F852", [0xF852]),
|
||||
instruction(0xC030, "BSR", "loc_BA26", targets=[0xBA26]),
|
||||
instruction(0xC100, "MOV:G.B", "@H'F861, R1", [0xF861]),
|
||||
instruction(0xC104, "MOV:G.B", "@H'F862, R2", [0xF862]),
|
||||
instruction(0xC108, "BSR", "loc_622B", targets=[0x622B]),
|
||||
instruction(0xC10C, "MOV:G.W", "@H'F900, R3", [0xF900]),
|
||||
instruction(0xC110, "MOV:G.B", "#H'04, @H'F850", [0xF850]),
|
||||
instruction(0xC114, "MOV:G.B", "@H'F861, R4", [0xF861]),
|
||||
instruction(0xC118, "MOV:G.B", "R4, @H'F851", [0xF851]),
|
||||
instruction(0xC11C, "MOV:G.B", "@H'F862, R5", [0xF862]),
|
||||
instruction(0xC120, "MOV:G.B", "R5, @H'F852", [0xF852]),
|
||||
instruction(0xC124, "MOV:G.W", "R3, @H'F853", [0xF853]),
|
||||
instruction(0xC128, "MOV:G.B", "@H'F9B5, R6", [0xF9B5]),
|
||||
instruction(0xC12C, "BSR", "loc_BA26", targets=[0xBA26]),
|
||||
instruction(0xC600, "MOV:G.B", "@H'F861, R1", [0xF861]),
|
||||
instruction(0xC604, "MOV:G.B", "@H'F862, R2", [0xF862]),
|
||||
instruction(0xC608, "BSR", "loc_622B", targets=[0x622B]),
|
||||
instruction(0xC60C, "MOV:G.W", "@H'F863, R3", [0xF863]),
|
||||
instruction(0xC610, "MOV:G.W", "R3, @H'F940", [0xF940]),
|
||||
instruction(0xC614, "MOV:G.B", "#H'01, @H'F980", [0xF980]),
|
||||
instruction(0xC618, "MOV:G.B", "#H'01, @H'F9C0", [0xF9C0]),
|
||||
instruction(0xC700, "MOV:G.B", "#H'07, @H'F850", [0xF850]),
|
||||
instruction(0xC704, "MOV:G.B", "@H'F861, R1", [0xF861]),
|
||||
instruction(0xC708, "MOV:G.B", "R1, @H'F851", [0xF851]),
|
||||
instruction(0xC70C, "BSR", "loc_BA26", targets=[0xBA26]),
|
||||
instruction(0xC800, "CMP:E.B", "@H'F865, R7", [0xF865]),
|
||||
instruction(0xC804, "BNE", "loc_C700", targets=[0xC700]),
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
class SerialSemanticsTest(unittest.TestCase):
|
||||
def test_detects_low_three_bit_command_dispatch(self):
|
||||
payload = base_payload(
|
||||
@@ -139,6 +226,76 @@ class SerialSemanticsTest(unittest.TestCase):
|
||||
[0xF850, 0xF851, 0xF852, 0xF853, 0xF854],
|
||||
)
|
||||
|
||||
def test_planned_command_effects_include_core_command_behaviors(self):
|
||||
semantics = only_semantics(self, planned_semantics_payload())
|
||||
|
||||
self.assertIn("command_effects", semantics)
|
||||
effects = semantic_items(semantics["command_effects"])
|
||||
|
||||
command_0 = command_item(effects, 0x00)
|
||||
self.assertIsNotNone(command_0)
|
||||
command_0_text = semantic_text(command_0)
|
||||
self.assertIn("write", command_0_text)
|
||||
self.assertIn("primary_value_table", command_0_text)
|
||||
self.assertIn("current_value_table", command_0_text)
|
||||
|
||||
command_1 = command_item(effects, 0x01)
|
||||
self.assertIsNotNone(command_1)
|
||||
command_1_text = semantic_text(command_1)
|
||||
self.assertIn("read", command_1_text)
|
||||
self.assertIn("response", command_1_text)
|
||||
|
||||
command_6 = command_item(effects, 0x06)
|
||||
self.assertIsNotNone(command_6)
|
||||
command_6_text = semantic_text(command_6)
|
||||
self.assertIn("write", command_6_text)
|
||||
self.assertIn("secondary_value_table", command_6_text)
|
||||
|
||||
def test_planned_response_schema_tracks_immediates_and_rx_copies(self):
|
||||
semantics = only_semantics(self, planned_semantics_payload())
|
||||
|
||||
self.assertIn("response_schema", semantics)
|
||||
schema_text = semantic_text(semantics["response_schema"])
|
||||
|
||||
self.assertIn("tx", schema_text)
|
||||
self.assertIn("byte0", schema_text)
|
||||
self.assertIn("0x04", schema_text)
|
||||
self.assertIn("byte1", schema_text)
|
||||
self.assertIn("rx[1]", schema_text)
|
||||
self.assertIn("byte2", schema_text)
|
||||
self.assertIn("rx[2]", schema_text)
|
||||
|
||||
def test_planned_table_map_candidates_name_known_tables(self):
|
||||
semantics = only_semantics(self, planned_semantics_payload())
|
||||
|
||||
self.assertIn("table_map_candidates", semantics)
|
||||
table_text = semantic_text(semantics["table_map_candidates"])
|
||||
|
||||
self.assertIn("primary_value_table", table_text)
|
||||
self.assertIn("current_value_table", table_text)
|
||||
self.assertIn("secondary_value_table", table_text)
|
||||
self.assertIn("flag_table", table_text)
|
||||
|
||||
def test_planned_state_variable_candidates_include_known_addresses(self):
|
||||
semantics = only_semantics(self, planned_semantics_payload())
|
||||
|
||||
self.assertIn("state_variable_candidates", semantics)
|
||||
state_text = semantic_text(semantics["state_variable_candidates"])
|
||||
|
||||
self.assertIn("faa2", state_text)
|
||||
self.assertIn("f9b5", state_text)
|
||||
self.assertIn("f9c0", state_text)
|
||||
|
||||
def test_planned_retry_error_model_identifies_retransmit_and_checksum_error(self):
|
||||
semantics = only_semantics(self, planned_semantics_payload())
|
||||
|
||||
self.assertIn("retry_error_model", semantics)
|
||||
retry_text = semantic_text(semantics["retry_error_model"])
|
||||
|
||||
self.assertIn("retransmit", retry_text)
|
||||
self.assertIn("0x07", retry_text)
|
||||
self.assertIn("checksum_error_response", retry_text)
|
||||
|
||||
def test_missing_serial_reconstruction_candidates_emit_no_protocol_semantics(self):
|
||||
payload = {
|
||||
"serial_reconstruction": {"candidates": []},
|
||||
|
||||
Reference in New Issue
Block a user