import unittest from h8536.model import Instruction from h8536.symbols import discover_symbols, instruction_accesses, symbol_for_address def ins(address, mnemonic, operands="", references=None): return Instruction( address, b"\x00", mnemonic, operands, references=list(references or []), ) class SymbolDiscoveryTest(unittest.TestCase): def test_discovers_ram_symbol_counts_direction_and_widths(self): instructions = { 0x1000: ins(0x1000, "MOV:G.B", "#H'12, @H'F680", [0xF680]), 0x1004: ins(0x1004, "CMP:G.B", "#H'01, @H'F680", [0xF680]), 0x1008: ins(0x1008, "ADD:Q.W", "#1, @H'F680", [0xF680]), } analysis = discover_symbols(instructions) symbols = analysis["symbols"] self.assertEqual(len(symbols), 1) symbol = symbols[0] self.assertEqual(symbol["address"], 0xF680) self.assertEqual(symbol["name"], "ram_F680") self.assertEqual(symbol["region"], "on_chip_ram") self.assertEqual(symbol["kind"], "ram") self.assertEqual(symbol["access_count"], 3) self.assertEqual(symbol["read_count"], 2) self.assertEqual(symbol["write_count"], 2) self.assertEqual(symbol["unknown_count"], 0) self.assertEqual(symbol["width_hints"], ["byte", "word"]) self.assertEqual(symbol["width"], "mixed") self.assertEqual(symbol["first_access"], 0x1000) self.assertEqual(symbol["last_access"], 0x1008) self.assertEqual(symbol_for_address(analysis, 0xF680), "ram_F680") def test_names_program_or_external_memory_and_excludes_registers_by_default(self): instructions = [ ins(0x2000, "MOV:G.W", "@H'1234, R1", [0x1234]), ins(0x2004, "MOV:G.B", "#H'80, @RAMCR", [0xFF11]), ] analysis = discover_symbols(instructions) self.assertEqual([symbol["name"] for symbol in analysis["symbols"]], ["mem_1234"]) symbol = analysis["symbols"][0] self.assertEqual(symbol["region"], "program_or_external") self.assertEqual(symbol["kind"], "memory") self.assertEqual(symbol["read_count"], 1) self.assertIsNone(symbol_for_address(analysis, 0xFF11)) def test_can_include_io_register_symbols_when_requested(self): instructions = [ ins(0x2004, "MOV:G.B", "#H'80, @RAMCR", [0xFF11]), ] analysis = discover_symbols(instructions, include_registers=True) self.assertEqual(len(analysis["symbols"]), 1) symbol = analysis["symbols"][0] self.assertEqual(symbol["address"], 0xFF11) self.assertEqual(symbol["name"], "RAMCR") self.assertEqual(symbol["region"], "register_field") self.assertEqual(symbol["kind"], "register") self.assertEqual(symbol["write_count"], 1) def test_bit_and_clear_operations_use_conservative_directions(self): instructions = [ ins(0x3000, "BSET.B", "#4, @H'F690", [0xF690]), ins(0x3002, "BCLR.B", "#4, @H'F690", [0xF690]), ins(0x3004, "TST.B", "@H'F690", [0xF690]), ins(0x3006, "CLR.B", "@H'F690", [0xF690]), ] analysis = discover_symbols(instructions) symbol = analysis["symbols"][0] self.assertEqual(symbol["read_count"], 3) self.assertEqual(symbol["write_count"], 3) self.assertEqual( [access["direction"] for access in symbol["accesses"]], ["read_write", "read_write", "read", "write"], ) def test_optional_pointer_table_candidates_add_xrefs_without_io_pollution(self): instructions = [ ins(0x4000, "MOV:G.B", "@H'F680, R0", [0xF680]), ] data_candidates = { "pointer_tables": [ { "address": 0x0200, "targets": [0xF680, 0x1234, 0xFF11], }, ], } analysis = discover_symbols(instructions, data_candidates=data_candidates) by_name = {symbol["name"]: symbol for symbol in analysis["symbols"]} self.assertEqual(by_name["ram_F680"]["xref_count"], 1) self.assertEqual(by_name["mem_1234"]["access_count"], 0) self.assertEqual(by_name["mem_1234"]["xref_count"], 1) self.assertNotIn("RAMCR", by_name) def test_instruction_accesses_handles_comma_inside_displacement_operand(self): access = instruction_accesses( ins(0x5000, "MOV:G.B", "@(H'0010,R1), @H'F682", [0xF682]), ) self.assertEqual(access[0]["direction"], "write") self.assertEqual(access[0]["operand"], "@H'F682") if __name__ == "__main__": unittest.main()