1
0

DTC and SCI improvements

This commit is contained in:
Aiden
2026-05-25 14:22:32 +10:00
parent 62d1c3c876
commit 80819448cf
21 changed files with 13823 additions and 86 deletions

View File

@@ -12,6 +12,12 @@ If you are using the repo-local venv:
.\.venv\Scripts\python.exe h8536_decompiler.py --out build\rom_decompiled.asm --json build\rom_decompiled.json --cycles --callgraph-dot build\callgraph.dot .\.venv\Scripts\python.exe h8536_decompiler.py --out build\rom_decompiled.asm --json build\rom_decompiled.json --cycles --callgraph-dot build\callgraph.dot
``` ```
To turn the structured decompile output into conservative C-like pseudocode:
```powershell
.\.venv\Scripts\python.exe h8536_pseudocode.py build\rom_decompiled.json --out build\rom_pseudocode.c --cycles
```
## What It Does ## What It Does
- Decodes the H8/500 instruction set used by the H8/536. - Decodes the H8/500 instruction set used by the H8/536.
@@ -21,12 +27,17 @@ If you are using the repo-local venv:
- Tracks `LDC.B #xx, BR` along traced control flow so later short absolute `@aa:8` operands can resolve automatically. - Tracks `LDC.B #xx, BR` along traced control flow so later short absolute `@aa:8` operands can resolve automatically.
- Annotates H8/536 register accesses such as `P1DDR`, `SYSCR1`, `WCR`, watchdog, timer/SCI/A-D, and RAM-control registers. - Annotates H8/536 register accesses such as `P1DDR`, `SYSCR1`, `WCR`, watchdog, timer/SCI/A-D, and RAM-control registers.
- Decodes register bitfields and selected hardware semantics for setup writes. - Decodes register bitfields and selected hardware semantics for setup writes.
- Annotates interrupt priority registers and DTC enable routing registers.
- Emits memory-region metadata for vector, DTC, RAM, register-field, and mode-dependent program/external space. - Emits memory-region metadata for vector, DTC, RAM, register-field, and mode-dependent program/external space.
- Parses the DTC vector table described by the manual. - Parses the DTC vector table described by the manual and decodes DTC register-information blocks.
- Tracks SCI setup writes and can infer baud rates from SMR/BRR when `--clock-hz` is supplied.
- Flags/manual-annotates TEMP-register access ordering for FRT and A/D 16-bit peripheral registers.
- Scans unreached ROM ranges for ASCII strings and pointer-table candidates. - Scans unreached ROM ranges for ASCII strings and pointer-table candidates.
- Emits function summaries and a direct-call graph in JSON, with optional Graphviz DOT output. - Emits function summaries and a direct-call graph in JSON, with optional Graphviz DOT output.
- Adds Appendix A cycle estimates to JSON and can append them to ASM comments. - Adds Appendix A cycle estimates to JSON and can append them to ASM comments.
- Summarizes straight-line block timing and backward-branch loop timing when requested.
- Handles the E-clock transfer instructions `MOVFPE` and `MOVTPE`. - Handles the E-clock transfer instructions `MOVFPE` and `MOVTPE`.
- Generates a separate C-like pseudocode view from the JSON, preserving labels, calls, branches, register names, comments, and optional cycle notes.
The generated listing is written to: The generated listing is written to:
@@ -51,9 +62,22 @@ python h8536_decompiler.py --help
- `--linear`: linear-sweep the selected range instead of tracing from vectors. - `--linear`: linear-sweep the selected range instead of tracing from vectors.
- `--start H'1000 --end H'D100`: constrain the decode range. - `--start H'1000 --end H'D100`: constrain the decode range.
- `--br H'FE`: resolve short absolute `@aa:8` operands through a known base-register value. - `--br H'FE`: resolve short absolute `@aa:8` operands through a known base-register value.
- `--clock-hz 16000000`: infer SCI baud rates from manual BRR formulas.
- `--cycles`: append Appendix A cycle estimates to assembly comments. - `--cycles`: append Appendix A cycle estimates to assembly comments.
- `--timing`: include straight-line block and backward-branch loop timing summaries.
- `--callgraph-dot build\callgraph.dot`: write a Graphviz DOT call graph. - `--callgraph-dot build\callgraph.dot`: write a Graphviz DOT call graph.
For pseudocode:
```powershell
python h8536_pseudocode.py --help
```
- `--no-asm`: omit original assembly text from pseudocode line comments.
- `--no-addresses`: omit instruction addresses from pseudocode line comments.
- `--cycles`: include cycle estimates from the JSON.
- `--max-functions N`: emit only the first `N` functions for focused review.
## Code Layout ## Code Layout
- `h8536_decompiler.py`: compatibility wrapper for the CLI. - `h8536_decompiler.py`: compatibility wrapper for the CLI.
@@ -61,9 +85,15 @@ python h8536_decompiler.py --help
- `h8536/decoder.py`: instruction and effective-address decoding. - `h8536/decoder.py`: instruction and effective-address decoding.
- `h8536/tables.py`: manual-derived opcode/vector/register tables. - `h8536/tables.py`: manual-derived opcode/vector/register tables.
- `h8536/vectors.py`: exception and DTC vector parsing. - `h8536/vectors.py`: exception and DTC vector parsing.
- `h8536/dtc.py`: DTC register-information block decoding.
- `h8536/analysis.py`: recursive tracing, linear sweep, labels, function grouping, and call graph analysis. - `h8536/analysis.py`: recursive tracing, linear sweep, labels, function grouping, and call graph analysis.
- `h8536/data_analysis.py`: unreached string and pointer-table candidate scans. - `h8536/data_analysis.py`: unreached string and pointer-table candidate scans.
- `h8536/memory.py`: manual-derived memory-region tagging. - `h8536/memory.py`: manual-derived memory-region tagging.
- `h8536/cycles.py`: Appendix A cycle estimate tables. - `h8536/cycles.py`: Appendix A cycle estimate tables.
- `h8536/timing.py`: block and loop cycle summaries.
- `h8536/sci.py`: SCI setup tracking and baud inference.
- `h8536/peripheral_access.py`: FRT/A-D TEMP-register access analysis.
- `h8536/pseudocode.py`: JSON-to-C-like pseudocode generation.
- `h8536/render.py`: assembly and JSON output. - `h8536/render.py`: assembly and JSON output.
- `h8536/model.py`, `h8536/rom.py`, `h8536/formatting.py`: shared data structures and helpers. - `h8536/model.py`, `h8536/rom.py`, `h8536/formatting.py`: shared data structures and helpers.
- `h8536_pseudocode.py`: pseudocode CLI wrapper.

View File

@@ -9,6 +9,8 @@
; - In minimum mode the reset vector at H'0000-H'0001 is a 16-bit PC. ; - In minimum mode the reset vector at H'0000-H'0001 is a 16-bit PC.
; - The register field is H'FE80-H'FFFF; names below come from appendix B. ; - The register field is H'FE80-H'FFFF; names below come from appendix B.
; - @aa:8 short absolute operands use BR as the upper address byte. ; - @aa:8 short absolute operands use BR as the upper address byte.
; - SCI baud inference uses section 14.2.8 BRR formulas when SMR/BRR are known.
; - Pass --clock-hz to convert SCI BRR settings into numeric baud rates.
; - Cycle counts use Appendix A tables A-7/A-8 for on-chip access with no external wait states. ; - Cycle counts use Appendix A tables A-7/A-8 for on-chip access with no external wait states.
; Memory Map ; Memory Map
@@ -138,6 +140,92 @@
; ptrtbl H'B7CC count=3 -> H'F772, H'1627, H'11A0 ; ptrtbl H'B7CC count=3 -> H'F772, H'1627, H'11A0
; ptrtbl H'C212 count=5 -> H'1700, H'1682, H'1664, H'1647, H'1630 ; ptrtbl H'C212 count=5 -> H'1700, H'1682, H'1664, H'1647, H'1630
; Timing Summary
; Straight-line blocks
; block H'1000-H'10CB vec_reset_1000 ins=42 cycles=371 unknown=0
; block H'10CE-H'1356 loc_10CE ins=217 cycles=1416 unknown=0
; block H'15E0-H'15E7 loc_15E0 ins=3 cycles=24-29 unknown=0
; block H'15E9-H'15F6 loc_15E9 ins=5 cycles=30 unknown=0
; block H'15F9-H'15FD loc_15F9 ins=2 cycles=9-14 unknown=0
; block H'15FF-H'1603 loc_15FF ins=2 cycles=11-16 unknown=0
; block H'1605-H'1605 loc_1605 ins=1 cycles=14 unknown=0
; block H'1608-H'160C loc_1608 ins=2 cycles=12-16 unknown=0
; block H'160E-H'160E loc_160E ins=1 cycles=13 unknown=0
; block H'1611-H'1615 loc_1611 ins=2 cycles=11-16 unknown=0
; block H'1617-H'1617 loc_1617 ins=1 cycles=14 unknown=0
; block H'161A-H'1622 loc_161A ins=3 cycles=21-25 unknown=0
; block H'1624-H'1624 loc_1624 ins=1 cycles=13 unknown=0
; block H'1627-H'162B loc_1627 ins=2 cycles=11-16 unknown=0
; block H'162D-H'162D loc_162D ins=1 cycles=14 unknown=0
; block H'1630-H'1634 loc_1630 ins=2 cycles=12-16 unknown=0
; block H'1636-H'1636 loc_1636 ins=1 cycles=13 unknown=0
; block H'1639-H'1639 loc_1639 ins=1 cycles=8 unknown=0
; block H'163D-H'1641 loc_163D ins=2 cycles=9-14 unknown=0
; block H'1643-H'1647 loc_1643 ins=2 cycles=11-16 unknown=0
; block H'1649-H'1649 loc_1649 ins=1 cycles=14 unknown=0
; block H'164C-H'1650 loc_164C ins=2 cycles=12-16 unknown=0
; block H'1652-H'1652 loc_1652 ins=1 cycles=13 unknown=0
; block H'1655-H'1659 loc_1655 ins=2 cycles=11-16 unknown=0
; block H'165B-H'165B loc_165B ins=1 cycles=14 unknown=0
; block H'165E-H'1662 loc_165E ins=2 cycles=12-16 unknown=0
; block H'1664-H'1664 loc_1664 ins=1 cycles=13 unknown=0
; block H'1667-H'166B loc_1667 ins=2 cycles=11-16 unknown=0
; block H'166D-H'166D loc_166D ins=1 cycles=14 unknown=0
; block H'1670-H'1674 loc_1670 ins=2 cycles=12-16 unknown=0
; block H'1676-H'1676 loc_1676 ins=1 cycles=13 unknown=0
; block H'1679-H'167D loc_1679 ins=2 cycles=11-16 unknown=0
; block H'167F-H'167F loc_167F ins=1 cycles=14 unknown=0
; block H'1682-H'1682 loc_1682 ins=1 cycles=9 unknown=0
; block H'1686-H'168A loc_1686 ins=2 cycles=10-14 unknown=0
; block H'168C-H'1690 loc_168C ins=2 cycles=12-16 unknown=0
; block H'1692-H'1692 loc_1692 ins=1 cycles=13 unknown=0
; block H'1695-H'1699 loc_1695 ins=2 cycles=11-16 unknown=0
; block H'169B-H'169B loc_169B ins=1 cycles=14 unknown=0
; block H'169E-H'16A2 loc_169E ins=2 cycles=12-16 unknown=0
; ... 750 more blocks
; Backward-branch loop candidates
; loop H'1A4B-H'1A55 loc_1A4B delay_loop_candidate cycles/iteration=18-28 back_edge=H'1A55
; loop H'1A5B-H'1A65 loc_1A5B delay_loop_candidate cycles/iteration=18-28 back_edge=H'1A65
; loop H'1A7F-H'1A89 loc_1A7F unconditional_loop cycles/iteration=23-28 back_edge=H'1A89
; loop H'1A45-H'1A8B loc_1A45 loop_with_call cycles/iteration=147-182 back_edge=H'1A8B
; loop H'1A90-H'1A94 loc_1A90 counter_delay_loop cycles/iteration=9-18 back_edge=H'1A94
; loop H'1AAF-H'1AB8 loc_1AAF delay_loop_candidate cycles/iteration=17-21 back_edge=H'1AB8
; loop H'1ABC-H'1AC5 loc_1ABC delay_loop_candidate cycles/iteration=16-21 back_edge=H'1AC5
; loop H'1C0E-H'1C22 loc_1C0E loop_with_call cycles/iteration=67-75 back_edge=H'1C22
; loop H'289F-H'2CB6 loc_289F loop_with_call cycles/iteration=97 back_edge=H'2CB6
; loop H'3933-H'395F loc_3933 counter_delay_loop cycles/iteration=87-107 back_edge=H'395F
; loop H'3E68-H'3E74 loc_3E68 unconditional_loop cycles/iteration=26-34 back_edge=H'3E74
; loop H'3E82-H'3E98 loc_3E82 loop_with_call cycles/iteration=70-75 back_edge=H'3E98
; loop H'3EAE-H'3EBD loc_3EAE unconditional_loop cycles/iteration=30-38 back_edge=H'3EBD
; loop H'3F04-H'3F18 loc_3F04 loop_with_call cycles/iteration=55-63 back_edge=H'3F18
; loop H'3F4A-H'3F51 loc_3F4A delay_loop_candidate cycles/iteration=18-23 back_edge=H'3F51
; loop H'3F7C-H'3F80 loc_3F7C counter_delay_loop cycles/iteration=12-17 back_edge=H'3F80
; loop H'3F83-H'3F87 loc_3F83 counter_delay_loop cycles/iteration=11-17 back_edge=H'3F87
; loop H'3F8C-H'3F9D loc_3F8C delay_loop_candidate cycles/iteration=37-42 back_edge=H'3F9D
; loop H'2806-H'3FC8 loc_2806 loop_with_call cycles/iteration=3796-4199 back_edge=H'3FC8
; loop H'3930-H'3FCB loc_3930 loop_with_call cycles/iteration=3496-3854 back_edge=H'3FCB
; loop H'15E0-H'3FCE loc_15E0 loop_with_call cycles/iteration=7589-8520 back_edge=H'3FCE
; loop H'3FB1-H'3FD1 loc_3FB1 loop_with_call cycles/iteration=133 back_edge=H'3FD1
; loop H'4077-H'4091 loc_4077 delay_loop_candidate cycles/iteration=49-58 back_edge=H'4091
; loop H'40BE-H'40DE loc_40BE delay_loop_candidate cycles/iteration=52-56 back_edge=H'40DE
; loop H'4106-H'4182 loc_4106 loop_with_call cycles/iteration=311-315 back_edge=H'4182
; loop H'4187-H'41AD loc_4187 counter_loop cycles/iteration=96-102 back_edge=H'41AD
; loop H'41D5-H'4213 loc_41D5 counter_loop cycles/iteration=214-220 back_edge=H'4213
; loop H'3ECC-H'42C6 loc_3ECC loop_with_call cycles/iteration=2362-2508 back_edge=H'42C6
; loop H'3ECC-H'42FC loc_3ECC loop_with_call cycles/iteration=2450-2596 back_edge=H'42FC
; loop H'3ECC-H'4302 loc_3ECC loop_with_call cycles/iteration=2466-2612 back_edge=H'4302
; loop H'3ECC-H'4308 loc_3ECC loop_with_call cycles/iteration=2482-2628 back_edge=H'4308
; loop H'3ECC-H'432A loc_3ECC loop_with_call cycles/iteration=2571-2717 back_edge=H'432A
; loop H'3ECC-H'4333 loc_3ECC loop_with_call cycles/iteration=2591-2737 back_edge=H'4333
; loop H'3ECC-H'433C loc_3ECC loop_with_call cycles/iteration=2610-2756 back_edge=H'433C
; loop H'3ECC-H'4345 loc_3ECC loop_with_call cycles/iteration=2630-2776 back_edge=H'4345
; loop H'10CE-H'4348 loc_10CE loop_with_call cycles/iteration=10922-11937 back_edge=H'4348
; loop H'19A2-H'43CA loc_19A2 loop_with_call cycles/iteration=7631-8356 back_edge=H'43CA
; loop H'1A35-H'43D6 loc_1A35 loop_with_call cycles/iteration=7458-8150 back_edge=H'43D6
; loop H'1A9C-H'43E2 loc_1A9C loop_with_call cycles/iteration=7288-7935 back_edge=H'43E2
; loop H'1AE4-H'43EE loc_1AE4 loop_with_call cycles/iteration=7186-7815 back_edge=H'43EE
; ... 35 more loops
vec_reset_1000: vec_reset_1000:
1000: 5F FE 80 MOV:I.W #H'FE80, R7 ; cycles=3 1000: 5F FE 80 MOV:I.W #H'FE80, R7 ; cycles=3
@@ -154,12 +242,12 @@ vec_reset_1000:
1034: 15 FE FD 06 84 MOV:G.B #H'84, @SYSCR2 ; SYSCR2 = H'84 (IRQ5E=0 IRQ4E=0 IRQ3E=0 IRQ2E=0 P6PWME=1 P9PWME=0 P9SCI2E=0; enabled P6 PWM); cycles=9 1034: 15 FE FD 06 84 MOV:G.B #H'84, @SYSCR2 ; SYSCR2 = H'84 (IRQ5E=0 IRQ4E=0 IRQ3E=0 IRQ2E=0 P6PWME=1 P9PWME=0 P9SCI2E=0; enabled P6 PWM); cycles=9
1039: 15 FE 90 06 02 MOV:G.B #H'02, @FRT1_TCR ; FRT1_TCR = H'02 (ICIE=0 OCIEB=0 OCIEA=0 OVIE=0 OEB=0 OEA=0 CKS1=1 CKS0=0); cycles=9 1039: 15 FE 90 06 02 MOV:G.B #H'02, @FRT1_TCR ; FRT1_TCR = H'02 (ICIE=0 OCIEB=0 OCIEA=0 OVIE=0 OEB=0 OEA=0 CKS1=1 CKS0=0); cycles=9
103E: 15 FE 91 06 01 MOV:G.B #H'01, @FRT1_TCSR ; FRT1_TCSR = H'01 (ICF=0 OCFB=0 OCFA=0 OVF=0 OLVLB=0 OLVLA=0 IEDG=0 CCLRA=1); cycles=9 103E: 15 FE 91 06 01 MOV:G.B #H'01, @FRT1_TCSR ; FRT1_TCSR = H'01 (ICF=0 OCFB=0 OCFA=0 OVF=0 OLVLB=0 OLVLA=0 IEDG=0 CCLRA=1); cycles=9
1043: 1D FE 92 06 00 MOV:G.W #H'00, @FRT1_FRC_H ; FRT1_FRC_H = H'00; cycles=9 1043: 1D FE 92 06 00 MOV:G.W #H'00, @FRT1_FRC_H ; FRT1_FRC_H = H'00; FRT1_FRC word write; TEMP byte-order hazard avoided; cycles=9
1048: 1D FE 94 07 00 9C MOV:G.W #H'009C, @FRT1_OCRA_L ; FRT1_OCRA_L = H'9C; cycles=11 1048: 1D FE 94 07 00 9C MOV:G.W #H'009C, @FRT1_OCRA_H ; FRT1_OCRA_H = H'9C; FRT1_OCRA word write; TEMP byte-order hazard avoided; cycles=11
104E: 15 FE A0 06 02 MOV:G.B #H'02, @FRT2_TCR ; FRT2_TCR = H'02 (ICIE=0 OCIEB=0 OCIEA=0 OVIE=0 OEB=0 OEA=0 CKS1=1 CKS0=0); cycles=9 104E: 15 FE A0 06 02 MOV:G.B #H'02, @FRT2_TCR ; FRT2_TCR = H'02 (ICIE=0 OCIEB=0 OCIEA=0 OVIE=0 OEB=0 OEA=0 CKS1=1 CKS0=0); cycles=9
1053: 15 FE A1 06 01 MOV:G.B #H'01, @FRT2_TCSR ; FRT2_TCSR = H'01 (ICF=0 OCFB=0 OCFA=0 OVF=0 OLVLB=0 OLVLA=0 IEDG=0 CCLRA=1); cycles=9 1053: 15 FE A1 06 01 MOV:G.B #H'01, @FRT2_TCSR ; FRT2_TCSR = H'01 (ICF=0 OCFB=0 OCFA=0 OVF=0 OLVLB=0 OLVLA=0 IEDG=0 CCLRA=1); cycles=9
1058: 1D FE A2 06 00 MOV:G.W #H'00, @FRT2_FRC_H ; FRT2_FRC_H = H'00; cycles=11 1058: 1D FE A2 06 00 MOV:G.W #H'00, @FRT2_FRC_H ; FRT2_FRC_H = H'00; FRT2_FRC word write; TEMP byte-order hazard avoided; cycles=11
105D: 1D FE A4 07 7A 12 MOV:G.W #H'7A12, @FRT2_OCRA_H ; FRT2_OCRA_H = H'7A12; cycles=9 105D: 1D FE A4 07 7A 12 MOV:G.W #H'7A12, @FRT2_OCRA_H ; FRT2_OCRA_H = H'7A12; FRT2_OCRA word write; TEMP byte-order hazard avoided; cycles=9
1063: 15 FE B0 06 00 MOV:G.B #H'00, @FRT3_TCR ; FRT3_TCR = H'00 (ICIE=0 OCIEB=0 OCIEA=0 OVIE=0 OEB=0 OEA=0 CKS1=0 CKS0=0); cycles=9 1063: 15 FE B0 06 00 MOV:G.B #H'00, @FRT3_TCR ; FRT3_TCR = H'00 (ICIE=0 OCIEB=0 OCIEA=0 OVIE=0 OEB=0 OEA=0 CKS1=0 CKS0=0); cycles=9
1068: 15 FE B1 06 00 MOV:G.B #H'00, @FRT3_TCSR ; FRT3_TCSR = H'00 (ICF=0 OCFB=0 OCFA=0 OVF=0 OLVLB=0 OLVLA=0 IEDG=0 CCLRA=0); cycles=9 1068: 15 FE B1 06 00 MOV:G.B #H'00, @FRT3_TCSR ; FRT3_TCSR = H'00 (ICF=0 OCFB=0 OCFA=0 OVF=0 OLVLB=0 OLVLA=0 IEDG=0 CCLRA=0); cycles=9
106D: 15 FE D0 06 00 MOV:G.B #H'00, @TMR_TCR ; TMR_TCR = H'00 (CMIEB=0 CMIEA=0 OVIE=0 CCLR1=0 CCLR0=0 CKS2=0 CKS1=0 CKS0=0); cycles=9 106D: 15 FE D0 06 00 MOV:G.B #H'00, @TMR_TCR ; TMR_TCR = H'00 (CMIEB=0 CMIEA=0 OVIE=0 CCLR1=0 CCLR0=0 CKS2=0 CKS1=0 CKS0=0); cycles=9
@@ -172,10 +260,10 @@ vec_reset_1000:
1090: 15 FE C9 06 7D MOV:G.B #H'7D, @PWM3_DTR ; PWM3_DTR = H'7D; cycles=9 1090: 15 FE C9 06 7D MOV:G.B #H'7D, @PWM3_DTR ; PWM3_DTR = H'7D; cycles=9
1095: 15 FE D8 06 24 MOV:G.B #H'24, @SCI1_SMR ; SCI1_SMR = H'24 (C/A=0 CHR=0 PE=1 O/E=0 STOP=0 CKS1=0 CKS0=0; SCI async, 8-bit, even parity, 1 stop, clock phi); cycles=9 1095: 15 FE D8 06 24 MOV:G.B #H'24, @SCI1_SMR ; SCI1_SMR = H'24 (C/A=0 CHR=0 PE=1 O/E=0 STOP=0 CKS1=0 CKS0=0; SCI async, 8-bit, even parity, 1 stop, clock phi); cycles=9
109A: 15 FE DA 06 3C MOV:G.B #H'3C, @SCI1_SCR ; SCI1_SCR = H'3C (TIE=0 RIE=0 TE=1 RE=1 CKE1=0 CKE0=0; SCI enables TX,RX, internal clock); cycles=9 109A: 15 FE DA 06 3C MOV:G.B #H'3C, @SCI1_SCR ; SCI1_SCR = H'3C (TIE=0 RIE=0 TE=1 RE=1 CKE1=0 CKE0=0; SCI enables TX,RX, internal clock); cycles=9
109F: 15 FE D9 06 07 MOV:G.B #H'07, @SCI1_BRR ; SCI1_BRR = H'07; cycles=9 109F: 15 FE D9 06 07 MOV:G.B #H'07, @SCI1_BRR ; SCI1_BRR = H'07; SCI1 async 8-bit even parity 1 stop BRR N=7 CKS n=0; baud needs --clock-hz; cycles=9
10A4: 15 FE F0 06 24 MOV:G.B #H'24, @SCI2_SMR ; SCI2_SMR = H'24 (C/A=0 CHR=0 PE=1 O/E=0 STOP=0 CKS1=0 CKS0=0; SCI async, 8-bit, even parity, 1 stop, clock phi); cycles=9 10A4: 15 FE F0 06 24 MOV:G.B #H'24, @SCI2_SMR ; SCI2_SMR = H'24 (C/A=0 CHR=0 PE=1 O/E=0 STOP=0 CKS1=0 CKS0=0; SCI async, 8-bit, even parity, 1 stop, clock phi); cycles=9
10A9: 15 FE F2 06 0C MOV:G.B #H'0C, @SCI2_SCR ; SCI2_SCR = H'0C (TIE=0 RIE=0 TE=0 RE=0 CKE1=0 CKE0=0; SCI enables none, internal clock); cycles=9 10A9: 15 FE F2 06 0C MOV:G.B #H'0C, @SCI2_SCR ; SCI2_SCR = H'0C (TIE=0 RIE=0 TE=0 RE=0 CKE1=0 CKE0=0; SCI enables none, internal clock); cycles=9
10AE: 15 FE F1 06 07 MOV:G.B #H'07, @SCI2_BRR ; SCI2_BRR = H'07; cycles=9 10AE: 15 FE F1 06 07 MOV:G.B #H'07, @SCI2_BRR ; SCI2_BRR = H'07; SCI2 async 8-bit even parity 1 stop BRR N=7 CKS n=0; baud needs --clock-hz; cycles=9
10B3: 15 FE E8 06 19 MOV:G.B #H'19, @ADCSR ; ADCSR = H'19 (ADF=0 ADIE=0 ADST=0 SCAN=1 CKS=1 CH2=0 CH1=0 CH0=1; A/D halt, scan AN0-AN1, 138-state max, ADI disabled); cycles=9 10B3: 15 FE E8 06 19 MOV:G.B #H'19, @ADCSR ; ADCSR = H'19 (ADF=0 ADIE=0 ADST=0 SCAN=1 CKS=1 CH2=0 CH1=0 CH0=1; A/D halt, scan AN0-AN1, 138-state max, ADI disabled); cycles=9
10B8: 15 FE E9 06 7F MOV:G.B #H'7F, @H'FEE9 ; refs H'FEE9 in register_field; cycles=9 10B8: 15 FE E9 06 7F MOV:G.B #H'7F, @H'FEE9 ; refs H'FEE9 in register_field; cycles=9
10BD: 15 FF 10 06 F0 MOV:G.B #H'F0, @WCR ; WCR = H'F0 (WMS1=0 WMS0=0 WC1=0 WC0=0; programmable wait, 0 waits); cycles=9 10BD: 15 FF 10 06 F0 MOV:G.B #H'F0, @WCR ; WCR = H'F0 (WMS1=0 WMS0=0 WC1=0 WC0=0; programmable wait, 0 waits); cycles=9
@@ -1748,7 +1836,7 @@ vec_ad_adi_3D99:
3D9D: 12 3F STM.W {R0,R1,R2,R3,R4,R5}, @-SP ; cycles=24 3D9D: 12 3F STM.W {R0,R1,R2,R3,R4,R5}, @-SP ; cycles=24
3D9F: 15 F6 8A 80 MOV:G.B @H'F68A, R0 ; refs H'F68A in on_chip_ram; cycles=6 3D9F: 15 F6 8A 80 MOV:G.B @H'F68A, R0 ; refs H'F68A in on_chip_ram; cycles=6
3DA3: 04 14 A8 MULXU.B #H'14, R0 ; cycles=19 3DA3: 04 14 A8 MULXU.B #H'14, R0 ; cycles=19
3DA6: 1D FE E0 81 MOV:G.W @ADDRA_H, R1 ; refs ADDRA_H in register_field; cycles=7 3DA6: 1D FE E0 81 MOV:G.W @ADDRA_H, R1 ; ADDRA word read; TEMP byte-order hazard avoided; refs ADDRA_H in register_field; cycles=7
3DAA: A1 10 SWAP.B R1 ; cycles=3 3DAA: A1 10 SWAP.B R1 ; cycles=3
3DAC: A1 12 EXTU.B R1 ; cycles=3 3DAC: A1 12 EXTU.B R1 ; cycles=3
3DAE: F1 CF B6 81 MOV:G.B @(-H'304A,R1), R1 ; cycles=7 3DAE: F1 CF B6 81 MOV:G.B @(-H'304A,R1), R1 ; cycles=7
@@ -1790,7 +1878,7 @@ loc_3DFA:
loc_3E08: loc_3E08:
3E08: 15 F6 8B 80 MOV:G.B @H'F68B, R0 ; refs H'F68B in on_chip_ram; cycles=7 3E08: 15 F6 8B 80 MOV:G.B @H'F68B, R0 ; refs H'F68B in on_chip_ram; cycles=7
3E0C: 04 14 A8 MULXU.B #H'14, R0 ; cycles=19 3E0C: 04 14 A8 MULXU.B #H'14, R0 ; cycles=19
3E0F: 1D FE E2 81 MOV:G.W @ADDRB_H, R1 ; refs ADDRB_H in register_field; cycles=6 3E0F: 1D FE E2 81 MOV:G.W @ADDRB_H, R1 ; ADDRB word read; TEMP byte-order hazard avoided; refs ADDRB_H in register_field; cycles=6
3E13: A1 10 SWAP.B R1 ; cycles=3 3E13: A1 10 SWAP.B R1 ; cycles=3
3E15: A1 12 EXTU.B R1 ; cycles=3 3E15: A1 12 EXTU.B R1 ; cycles=3
3E17: A9 20 ADD:G.W R1, R0 ; cycles=3 3E17: A9 20 ADD:G.W R1, R0 ; cycles=3
@@ -2322,12 +2410,12 @@ loc_4324:
434B: 19 RTS ; cycles=13 434B: 19 RTS ; cycles=13
loc_434C: loc_434C:
434C: 15 FF 00 06 70 MOV:G.B #H'70, @IPRA ; IPRA = H'70; cycles=9 434C: 15 FF 00 06 70 MOV:G.B #H'70, @IPRA ; IPRA = H'70 (irq0 priority=7; irq1 priority=0); cycles=9
4351: 15 FF 01 06 44 MOV:G.B #H'44, @IPRB ; IPRB = H'44; cycles=9 4351: 15 FF 01 06 44 MOV:G.B #H'44, @IPRB ; IPRB = H'44 (irq2/irq3 priority=4; irq4/irq5 priority=4); cycles=9
4356: 15 FF 02 06 66 MOV:G.B #H'66, @IPRC ; IPRC = H'66; cycles=9 4356: 15 FF 02 06 66 MOV:G.B #H'66, @IPRC ; IPRC = H'66 (FRT1 priority=6; FRT2 priority=6); cycles=9
435B: 15 FF 03 06 00 MOV:G.B #H'00, @IPRD ; IPRD = H'00; cycles=9 435B: 15 FF 03 06 00 MOV:G.B #H'00, @IPRD ; IPRD = H'00 (FRT3 priority=0; 8-bit timer priority=0); cycles=9
4360: 15 FF 04 06 50 MOV:G.B #H'50, @IPRE ; IPRE = H'50; cycles=9 4360: 15 FF 04 06 50 MOV:G.B #H'50, @IPRE ; IPRE = H'50 (SCI1 priority=5; SCI2 priority=0); cycles=9
4365: 15 FF 05 06 40 MOV:G.B #H'40, @IPRF ; IPRF = H'40; cycles=9 4365: 15 FF 05 06 40 MOV:G.B #H'40, @IPRF ; IPRF = H'40 (A/D priority=4); cycles=9
436A: 15 FE DA C6 BSET.B #6, @SCI1_SCR ; set RIE (bit 6) of SCI1_SCR; cycles=9 436A: 15 FE DA C6 BSET.B #6, @SCI1_SCR ; set RIE (bit 6) of SCI1_SCR; cycles=9
436E: 15 FE 90 C5 BSET.B #5, @FRT1_TCR ; set OCIEA (bit 5) of FRT1_TCR; cycles=9 436E: 15 FE 90 C5 BSET.B #5, @FRT1_TCR ; set OCIEA (bit 5) of FRT1_TCR; cycles=9
4372: 15 FE A0 C5 BSET.B #5, @FRT2_TCR ; set OCIEA (bit 5) of FRT2_TCR; cycles=9 4372: 15 FE A0 C5 BSET.B #5, @FRT2_TCR ; set OCIEA (bit 5) of FRT2_TCR; cycles=9

File diff suppressed because it is too large Load Diff

View File

@@ -32,7 +32,7 @@ extern volatile u8 P7DR; /* 0xFE8E */
extern volatile u8 FRT1_TCR; /* 0xFE90 */ extern volatile u8 FRT1_TCR; /* 0xFE90 */
extern volatile u8 FRT1_TCSR; /* 0xFE91 */ extern volatile u8 FRT1_TCSR; /* 0xFE91 */
extern volatile u16 FRT1_FRC_H; /* 0xFE92 */ extern volatile u16 FRT1_FRC_H; /* 0xFE92 */
extern volatile u16 FRT1_OCRA_L; /* 0xFE94 */ extern volatile u16 FRT1_OCRA_H; /* 0xFE94 */
extern volatile u8 FRT2_TCR; /* 0xFEA0 */ extern volatile u8 FRT2_TCR; /* 0xFEA0 */
extern volatile u8 FRT2_TCSR; /* 0xFEA1 */ extern volatile u8 FRT2_TCSR; /* 0xFEA1 */
extern volatile u16 FRT2_FRC_H; /* 0xFEA2 */ extern volatile u16 FRT2_FRC_H; /* 0xFEA2 */
@@ -180,12 +180,12 @@ void vec_reset_1000(void)
SYSCR2 = (uint8_t)(0x84); /* 1034; MOV:G.B #H'84, @SYSCR2; SYSCR2 = H'84 (IRQ5E=0 IRQ4E=0 IRQ3E=0 IRQ2E=0 P6PWME=1 P9PWME=0 P9SCI2E=0; enabled P6 PWM); cycles=9 */ SYSCR2 = (uint8_t)(0x84); /* 1034; MOV:G.B #H'84, @SYSCR2; SYSCR2 = H'84 (IRQ5E=0 IRQ4E=0 IRQ3E=0 IRQ2E=0 P6PWME=1 P9PWME=0 P9SCI2E=0; enabled P6 PWM); cycles=9 */
FRT1_TCR = (uint8_t)(0x02); /* 1039; MOV:G.B #H'02, @FRT1_TCR; FRT1_TCR = H'02 (ICIE=0 OCIEB=0 OCIEA=0 OVIE=0 OEB=0 OEA=0 CKS1=1 CKS0=0); cycles=9 */ FRT1_TCR = (uint8_t)(0x02); /* 1039; MOV:G.B #H'02, @FRT1_TCR; FRT1_TCR = H'02 (ICIE=0 OCIEB=0 OCIEA=0 OVIE=0 OEB=0 OEA=0 CKS1=1 CKS0=0); cycles=9 */
FRT1_TCSR = (uint8_t)(0x01); /* 103E; MOV:G.B #H'01, @FRT1_TCSR; FRT1_TCSR = H'01 (ICF=0 OCFB=0 OCFA=0 OVF=0 OLVLB=0 OLVLA=0 IEDG=0 CCLRA=1); cycles=9 */ FRT1_TCSR = (uint8_t)(0x01); /* 103E; MOV:G.B #H'01, @FRT1_TCSR; FRT1_TCSR = H'01 (ICF=0 OCFB=0 OCFA=0 OVF=0 OLVLB=0 OLVLA=0 IEDG=0 CCLRA=1); cycles=9 */
FRT1_FRC_H = (uint16_t)(0x00); /* 1043; MOV:G.W #H'00, @FRT1_FRC_H; FRT1_FRC_H = H'00; cycles=9 */ FRT1_FRC_H = (uint16_t)(0x00); /* 1043; MOV:G.W #H'00, @FRT1_FRC_H; FRT1_FRC_H = H'00; FRT1_FRC W write high TEMP access; cycles=9 */
FRT1_OCRA_L = (uint16_t)(0x009C); /* 1048; MOV:G.W #H'009C, @FRT1_OCRA_L; FRT1_OCRA_L = H'9C; cycles=11 */ FRT1_OCRA_H = (uint16_t)(0x009C); /* 1048; MOV:G.W #H'009C, @FRT1_OCRA_H; FRT1_OCRA_H = H'9C; FRT1_OCRA W write high TEMP access; cycles=11 */
FRT2_TCR = (uint8_t)(0x02); /* 104E; MOV:G.B #H'02, @FRT2_TCR; FRT2_TCR = H'02 (ICIE=0 OCIEB=0 OCIEA=0 OVIE=0 OEB=0 OEA=0 CKS1=1 CKS0=0); cycles=9 */ FRT2_TCR = (uint8_t)(0x02); /* 104E; MOV:G.B #H'02, @FRT2_TCR; FRT2_TCR = H'02 (ICIE=0 OCIEB=0 OCIEA=0 OVIE=0 OEB=0 OEA=0 CKS1=1 CKS0=0); cycles=9 */
FRT2_TCSR = (uint8_t)(0x01); /* 1053; MOV:G.B #H'01, @FRT2_TCSR; FRT2_TCSR = H'01 (ICF=0 OCFB=0 OCFA=0 OVF=0 OLVLB=0 OLVLA=0 IEDG=0 CCLRA=1); cycles=9 */ FRT2_TCSR = (uint8_t)(0x01); /* 1053; MOV:G.B #H'01, @FRT2_TCSR; FRT2_TCSR = H'01 (ICF=0 OCFB=0 OCFA=0 OVF=0 OLVLB=0 OLVLA=0 IEDG=0 CCLRA=1); cycles=9 */
FRT2_FRC_H = (uint16_t)(0x00); /* 1058; MOV:G.W #H'00, @FRT2_FRC_H; FRT2_FRC_H = H'00; cycles=11 */ FRT2_FRC_H = (uint16_t)(0x00); /* 1058; MOV:G.W #H'00, @FRT2_FRC_H; FRT2_FRC_H = H'00; FRT2_FRC W write high TEMP access; cycles=11 */
FRT2_OCRA_H = (uint16_t)(0x7A12); /* 105D; MOV:G.W #H'7A12, @FRT2_OCRA_H; FRT2_OCRA_H = H'7A12; cycles=9 */ FRT2_OCRA_H = (uint16_t)(0x7A12); /* 105D; MOV:G.W #H'7A12, @FRT2_OCRA_H; FRT2_OCRA_H = H'7A12; FRT2_OCRA W write high TEMP access; cycles=9 */
FRT3_TCR = (uint8_t)(0x00); /* 1063; MOV:G.B #H'00, @FRT3_TCR; FRT3_TCR = H'00 (ICIE=0 OCIEB=0 OCIEA=0 OVIE=0 OEB=0 OEA=0 CKS1=0 CKS0=0); cycles=9 */ FRT3_TCR = (uint8_t)(0x00); /* 1063; MOV:G.B #H'00, @FRT3_TCR; FRT3_TCR = H'00 (ICIE=0 OCIEB=0 OCIEA=0 OVIE=0 OEB=0 OEA=0 CKS1=0 CKS0=0); cycles=9 */
FRT3_TCSR = (uint8_t)(0x00); /* 1068; MOV:G.B #H'00, @FRT3_TCSR; FRT3_TCSR = H'00 (ICF=0 OCFB=0 OCFA=0 OVF=0 OLVLB=0 OLVLA=0 IEDG=0 CCLRA=0); cycles=9 */ FRT3_TCSR = (uint8_t)(0x00); /* 1068; MOV:G.B #H'00, @FRT3_TCSR; FRT3_TCSR = H'00 (ICF=0 OCFB=0 OCFA=0 OVF=0 OLVLB=0 OLVLA=0 IEDG=0 CCLRA=0); cycles=9 */
TMR_TCR = (uint8_t)(0x00); /* 106D; MOV:G.B #H'00, @TMR_TCR; TMR_TCR = H'00 (CMIEB=0 CMIEA=0 OVIE=0 CCLR1=0 CCLR0=0 CKS2=0 CKS1=0 CKS0=0); cycles=9 */ TMR_TCR = (uint8_t)(0x00); /* 106D; MOV:G.B #H'00, @TMR_TCR; TMR_TCR = H'00 (CMIEB=0 CMIEA=0 OVIE=0 CCLR1=0 CCLR0=0 CKS2=0 CKS1=0 CKS0=0); cycles=9 */
@@ -198,10 +198,10 @@ void vec_reset_1000(void)
PWM3_DTR = (uint8_t)(0x7D); /* 1090; MOV:G.B #H'7D, @PWM3_DTR; PWM3_DTR = H'7D; cycles=9 */ PWM3_DTR = (uint8_t)(0x7D); /* 1090; MOV:G.B #H'7D, @PWM3_DTR; PWM3_DTR = H'7D; cycles=9 */
SCI1_SMR = (uint8_t)(0x24); /* 1095; MOV:G.B #H'24, @SCI1_SMR; SCI1_SMR = H'24 (C/A=0 CHR=0 PE=1 O/E=0 STOP=0 CKS1=0 CKS0=0; SCI async, 8-bit, even parity, 1 stop, clock phi); cycles=9 */ SCI1_SMR = (uint8_t)(0x24); /* 1095; MOV:G.B #H'24, @SCI1_SMR; SCI1_SMR = H'24 (C/A=0 CHR=0 PE=1 O/E=0 STOP=0 CKS1=0 CKS0=0; SCI async, 8-bit, even parity, 1 stop, clock phi); cycles=9 */
SCI1_SCR = (uint8_t)(0x3C); /* 109A; MOV:G.B #H'3C, @SCI1_SCR; SCI1_SCR = H'3C (TIE=0 RIE=0 TE=1 RE=1 CKE1=0 CKE0=0; SCI enables TX,RX, internal clock); cycles=9 */ SCI1_SCR = (uint8_t)(0x3C); /* 109A; MOV:G.B #H'3C, @SCI1_SCR; SCI1_SCR = H'3C (TIE=0 RIE=0 TE=1 RE=1 CKE1=0 CKE0=0; SCI enables TX,RX, internal clock); cycles=9 */
SCI1_BRR = (uint8_t)(0x07); /* 109F; MOV:G.B #H'07, @SCI1_BRR; SCI1_BRR = H'07; cycles=9 */ SCI1_BRR = (uint8_t)(0x07); /* 109F; MOV:G.B #H'07, @SCI1_BRR; SCI1_BRR = H'07; SCI1 async 8-bit even parity 1 stop BRR N=7 CKS n=0; baud needs --clock-hz; cycles=9 */
SCI2_SMR = (uint8_t)(0x24); /* 10A4; MOV:G.B #H'24, @SCI2_SMR; SCI2_SMR = H'24 (C/A=0 CHR=0 PE=1 O/E=0 STOP=0 CKS1=0 CKS0=0; SCI async, 8-bit, even parity, 1 stop, clock phi); cycles=9 */ SCI2_SMR = (uint8_t)(0x24); /* 10A4; MOV:G.B #H'24, @SCI2_SMR; SCI2_SMR = H'24 (C/A=0 CHR=0 PE=1 O/E=0 STOP=0 CKS1=0 CKS0=0; SCI async, 8-bit, even parity, 1 stop, clock phi); cycles=9 */
SCI2_SCR = (uint8_t)(0x0C); /* 10A9; MOV:G.B #H'0C, @SCI2_SCR; SCI2_SCR = H'0C (TIE=0 RIE=0 TE=0 RE=0 CKE1=0 CKE0=0; SCI enables none, internal clock); cycles=9 */ SCI2_SCR = (uint8_t)(0x0C); /* 10A9; MOV:G.B #H'0C, @SCI2_SCR; SCI2_SCR = H'0C (TIE=0 RIE=0 TE=0 RE=0 CKE1=0 CKE0=0; SCI enables none, internal clock); cycles=9 */
SCI2_BRR = (uint8_t)(0x07); /* 10AE; MOV:G.B #H'07, @SCI2_BRR; SCI2_BRR = H'07; cycles=9 */ SCI2_BRR = (uint8_t)(0x07); /* 10AE; MOV:G.B #H'07, @SCI2_BRR; SCI2_BRR = H'07; SCI2 async 8-bit even parity 1 stop BRR N=7 CKS n=0; baud needs --clock-hz; cycles=9 */
ADCSR = (uint8_t)(0x19); /* 10B3; MOV:G.B #H'19, @ADCSR; ADCSR = H'19 (ADF=0 ADIE=0 ADST=0 SCAN=1 CKS=1 CH2=0 CH1=0 CH0=1; A/D halt, scan AN0-AN1, 138-state max, ADI disabled); cycles=9 */ ADCSR = (uint8_t)(0x19); /* 10B3; MOV:G.B #H'19, @ADCSR; ADCSR = H'19 (ADF=0 ADIE=0 ADST=0 SCAN=1 CKS=1 CH2=0 CH1=0 CH0=1; A/D halt, scan AN0-AN1, 138-state max, ADI disabled); cycles=9 */
MEM8[0xFEE9] = (uint8_t)(0x7F); /* 10B8; MOV:G.B #H'7F, @H'FEE9; cycles=9 */ MEM8[0xFEE9] = (uint8_t)(0x7F); /* 10B8; MOV:G.B #H'7F, @H'FEE9; cycles=9 */
WCR = (uint8_t)(0xF0); /* 10BD; MOV:G.B #H'F0, @WCR; WCR = H'F0 (WMS1=0 WMS0=0 WC1=0 WC0=0; programmable wait, 0 waits); cycles=9 */ WCR = (uint8_t)(0xF0); /* 10BD; MOV:G.B #H'F0, @WCR; WCR = H'F0 (WMS1=0 WMS0=0 WC1=0 WC0=0; programmable wait, 0 waits); cycles=9 */
@@ -1629,7 +1629,7 @@ void vec_ad_adi_3D99(void)
push_registers(R0, R1, R2, R3, R4, R5); /* 3D9D; STM.W {R0,R1,R2,R3,R4,R5}, @-SP; cycles=24 */ push_registers(R0, R1, R2, R3, R4, R5); /* 3D9D; STM.W {R0,R1,R2,R3,R4,R5}, @-SP; cycles=24 */
R0 = (uint8_t)(MEM8[0xF68A]); /* 3D9F; MOV:G.B @H'F68A, R0; cycles=6 */ R0 = (uint8_t)(MEM8[0xF68A]); /* 3D9F; MOV:G.B @H'F68A, R0; cycles=6 */
R0 = mulxu8(R0, 0x14); /* 3DA3; MULXU.B #H'14, R0; cycles=19 */ R0 = mulxu8(R0, 0x14); /* 3DA3; MULXU.B #H'14, R0; cycles=19 */
R1 = (uint16_t)(ADDRA_H); /* 3DA6; MOV:G.W @ADDRA_H, R1; cycles=7 */ R1 = (uint16_t)(ADDRA_H); /* 3DA6; MOV:G.W @ADDRA_H, R1; ADDRA W read high TEMP access; cycles=7 */
R1 = swap_bytes(R1); /* 3DAA; SWAP.B R1; cycles=3 */ R1 = swap_bytes(R1); /* 3DAA; SWAP.B R1; cycles=3 */
R1 = zero_extend8(R1); /* 3DAC; EXTU.B R1; cycles=3 */ R1 = zero_extend8(R1); /* 3DAC; EXTU.B R1; cycles=3 */
R1 = (uint8_t)(MEM8[R1 - 0x304A]); /* 3DAE; MOV:G.B @(-H'304A,R1), R1; cycles=7 */ R1 = (uint8_t)(MEM8[R1 - 0x304A]); /* 3DAE; MOV:G.B @(-H'304A,R1), R1; cycles=7 */
@@ -1668,7 +1668,7 @@ loc_3DFA:
loc_3E08: loc_3E08:
R0 = (uint8_t)(MEM8[0xF68B]); /* 3E08; MOV:G.B @H'F68B, R0; cycles=7 */ R0 = (uint8_t)(MEM8[0xF68B]); /* 3E08; MOV:G.B @H'F68B, R0; cycles=7 */
R0 = mulxu8(R0, 0x14); /* 3E0C; MULXU.B #H'14, R0; cycles=19 */ R0 = mulxu8(R0, 0x14); /* 3E0C; MULXU.B #H'14, R0; cycles=19 */
R1 = (uint16_t)(ADDRB_H); /* 3E0F; MOV:G.W @ADDRB_H, R1; cycles=6 */ R1 = (uint16_t)(ADDRB_H); /* 3E0F; MOV:G.W @ADDRB_H, R1; ADDRB W read high TEMP access; cycles=6 */
R1 = swap_bytes(R1); /* 3E13; SWAP.B R1; cycles=3 */ R1 = swap_bytes(R1); /* 3E13; SWAP.B R1; cycles=3 */
R1 = zero_extend8(R1); /* 3E15; EXTU.B R1; cycles=3 */ R1 = zero_extend8(R1); /* 3E15; EXTU.B R1; cycles=3 */
R0 += (uint16_t)(R1); /* 3E17; ADD:G.W R1, R0; cycles=3 */ R0 += (uint16_t)(R1); /* 3E17; ADD:G.W R1, R0; cycles=3 */
@@ -2138,12 +2138,12 @@ void loc_4324(void)
void loc_434C(void) void loc_434C(void)
{ {
IPRA = (uint8_t)(0x70); /* 434C; MOV:G.B #H'70, @IPRA; IPRA = H'70; cycles=9 */ IPRA = (uint8_t)(0x70); /* 434C; MOV:G.B #H'70, @IPRA; IPRA = H'70 (irq0 priority=7; irq1 priority=0); cycles=9 */
IPRB = (uint8_t)(0x44); /* 4351; MOV:G.B #H'44, @IPRB; IPRB = H'44; cycles=9 */ IPRB = (uint8_t)(0x44); /* 4351; MOV:G.B #H'44, @IPRB; IPRB = H'44 (irq2/irq3 priority=4; irq4/irq5 priority=4); cycles=9 */
IPRC = (uint8_t)(0x66); /* 4356; MOV:G.B #H'66, @IPRC; IPRC = H'66; cycles=9 */ IPRC = (uint8_t)(0x66); /* 4356; MOV:G.B #H'66, @IPRC; IPRC = H'66 (FRT1 priority=6; FRT2 priority=6); cycles=9 */
IPRD = (uint8_t)(0x00); /* 435B; MOV:G.B #H'00, @IPRD; IPRD = H'00; cycles=9 */ IPRD = (uint8_t)(0x00); /* 435B; MOV:G.B #H'00, @IPRD; IPRD = H'00 (FRT3 priority=0; 8-bit timer priority=0); cycles=9 */
IPRE = (uint8_t)(0x50); /* 4360; MOV:G.B #H'50, @IPRE; IPRE = H'50; cycles=9 */ IPRE = (uint8_t)(0x50); /* 4360; MOV:G.B #H'50, @IPRE; IPRE = H'50 (SCI1 priority=5; SCI2 priority=0); cycles=9 */
IPRF = (uint8_t)(0x40); /* 4365; MOV:G.B #H'40, @IPRF; IPRF = H'40; cycles=9 */ IPRF = (uint8_t)(0x40); /* 4365; MOV:G.B #H'40, @IPRF; IPRF = H'40 (A/D priority=4); cycles=9 */
SCI1_SCR |= BIT(6); /* 436A; BSET.B #6, @SCI1_SCR; set RIE (bit 6) of SCI1_SCR; cycles=9 */ SCI1_SCR |= BIT(6); /* 436A; BSET.B #6, @SCI1_SCR; set RIE (bit 6) of SCI1_SCR; cycles=9 */
FRT1_TCR |= BIT(5); /* 436E; BSET.B #5, @FRT1_TCR; set OCIEA (bit 5) of FRT1_TCR; cycles=9 */ FRT1_TCR |= BIT(5); /* 436E; BSET.B #5, @FRT1_TCR; set OCIEA (bit 5) of FRT1_TCR; cycles=9 */
FRT2_TCR |= BIT(5); /* 4372; BSET.B #5, @FRT2_TCR; set OCIEA (bit 5) of FRT2_TCR; cycles=9 */ FRT2_TCR |= BIT(5); /* 4372; BSET.B #5, @FRT2_TCR; set OCIEA (bit 5) of FRT2_TCR; cycles=9 */

View File

@@ -8,8 +8,11 @@ from .cycles import annotate_cycles
from .data_analysis import analyze_unreached_data from .data_analysis import analyze_unreached_data
from .decoder import H8536Decoder from .decoder import H8536Decoder
from .formatting import parse_int from .formatting import parse_int
from .peripheral_access import analyze_peripheral_access
from .render import format_callgraph_dot, format_listing, write_json from .render import format_callgraph_dot, format_listing, write_json
from .rom import Rom from .rom import Rom
from .sci import analyze_sci
from .timing import summarize_timing
from .vectors import read_dtc_vectors_max, read_dtc_vectors_min, read_vectors_max, read_vectors_min from .vectors import read_dtc_vectors_max, read_dtc_vectors_min, read_vectors_max, read_vectors_min
@@ -31,10 +34,14 @@ def main() -> int:
parser.add_argument("--end", type=parse_int, default=None, help="decode upper address limit, exclusive") parser.add_argument("--end", type=parse_int, default=None, help="decode upper address limit, exclusive")
parser.add_argument("--entry", type=parse_int, action="append", default=[], help="extra entry point to trace") parser.add_argument("--entry", type=parse_int, action="append", default=[], help="extra entry point to trace")
parser.add_argument("--br", type=parse_int, default=None, help="optional BR value for @aa:8 short absolute operands") parser.add_argument("--br", type=parse_int, default=None, help="optional BR value for @aa:8 short absolute operands")
parser.add_argument("--clock-hz", type=parse_int, default=None, help="oscillator clock in Hz for SCI baud inference")
parser.add_argument("--linear", action="store_true", help="linear-sweep the selected range instead of tracing from vectors") parser.add_argument("--linear", action="store_true", help="linear-sweep the selected range instead of tracing from vectors")
parser.add_argument("--cycles", action="store_true", help="append Appendix A cycle estimates to assembly comments") parser.add_argument("--cycles", action="store_true", help="append Appendix A cycle estimates to assembly comments")
parser.add_argument("--timing", action="store_true", help="include straight-line block and loop cycle summaries")
parser.add_argument("--callgraph-dot", type=Path, default=None, help="optional Graphviz DOT call graph output") parser.add_argument("--callgraph-dot", type=Path, default=None, help="optional Graphviz DOT call graph output")
args = parser.parse_args() args = parser.parse_args()
if args.clock_hz is not None and args.clock_hz <= 0:
parser.error("--clock-hz must be positive")
data = args.rom.read_bytes() data = args.rom.read_bytes()
rom = Rom(data) rom = Rom(data)
@@ -65,6 +72,9 @@ def main() -> int:
annotate_cycles(instructions, args.mode) annotate_cycles(instructions, args.mode)
data_candidates = analyze_unreached_data(rom, instructions, args.start, end) data_candidates = analyze_unreached_data(rom, instructions, args.start, end)
call_graph = build_call_graph(instructions, vectors, labels) call_graph = build_call_graph(instructions, vectors, labels)
timing_summary = summarize_timing(instructions, labels, call_graph) if args.timing else None
sci_analysis = analyze_sci(instructions, clock_hz=args.clock_hz)
peripheral_access = analyze_peripheral_access(instructions)
args.out.parent.mkdir(parents=True, exist_ok=True) args.out.parent.mkdir(parents=True, exist_ok=True)
args.out.write_text( args.out.write_text(
@@ -78,7 +88,10 @@ def main() -> int:
traced=not args.linear, traced=not args.linear,
dtc_vectors=dtc_vectors, dtc_vectors=dtc_vectors,
data_candidates=data_candidates, data_candidates=data_candidates,
timing_summary=timing_summary,
show_cycles=args.cycles, show_cycles=args.cycles,
sci_analysis=sci_analysis,
peripheral_access=peripheral_access,
), ),
encoding="utf-8", encoding="utf-8",
) )
@@ -92,6 +105,9 @@ def main() -> int:
dtc_vectors=dtc_vectors, dtc_vectors=dtc_vectors,
data_candidates=data_candidates, data_candidates=data_candidates,
call_graph=call_graph, call_graph=call_graph,
timing_summary=timing_summary,
sci_analysis=sci_analysis,
peripheral_access=peripheral_access,
) )
if args.callgraph_dot: if args.callgraph_dot:
args.callgraph_dot.parent.mkdir(parents=True, exist_ok=True) args.callgraph_dot.parent.mkdir(parents=True, exist_ok=True)

129
h8536/dtc.py Normal file
View File

@@ -0,0 +1,129 @@
from __future__ import annotations
from typing import TypedDict
from .formatting import h16
from .memory import region_for
from .rom import Rom
from .tables import IO_REGISTERS
DTC_REGISTER_INFO_SIZE = 8
DTC_RESERVED_MODE_MASK = 0x1FFF
class DtcEndpointInfo(TypedDict):
address: int
text: str
name: str | None
region: str
increment: bool
increment_step: int
class DtcModeInfo(TypedDict):
raw: int
size: str
bytes_per_transfer: int
source_increment: bool
destination_increment: bool
source_increment_step: int
destination_increment_step: int
reserved: int
reserved_set: bool
class DtcCountInfo(TypedDict):
raw: int
transfers: int
bytes: int
zero_means_65536: bool
class DtcRegisterInfo(TypedDict, total=False):
address: int
valid: bool
error: str
dtmr: int
dtsr: int
dtdr: int
dtcr: int
mode: DtcModeInfo
source: DtcEndpointInfo
destination: DtcEndpointInfo
count: DtcCountInfo
def _endpoint(address: int, increment: bool, increment_step: int) -> DtcEndpointInfo:
name = IO_REGISTERS.get(address)
return {
"address": address,
"text": name or h16(address),
"name": name,
"region": region_for(address).name,
"increment": increment,
"increment_step": increment_step if increment else 0,
}
def _mode(dtmr: int) -> DtcModeInfo:
size = "word" if dtmr & 0x8000 else "byte"
bytes_per_transfer = 2 if size == "word" else 1
source_increment = bool(dtmr & 0x4000)
destination_increment = bool(dtmr & 0x2000)
source_step = bytes_per_transfer if source_increment else 0
destination_step = bytes_per_transfer if destination_increment else 0
reserved = dtmr & DTC_RESERVED_MODE_MASK
return {
"raw": dtmr,
"size": size,
"bytes_per_transfer": bytes_per_transfer,
"source_increment": source_increment,
"destination_increment": destination_increment,
"source_increment_step": source_step,
"destination_increment_step": destination_step,
"reserved": reserved,
"reserved_set": reserved != 0,
}
def decode_dtc_register_info(rom: Rom, address: int) -> DtcRegisterInfo:
"""Decode the four-word DTMR/DTSR/DTDR/DTCR block pointed to by a DTC vector."""
end = address + DTC_REGISTER_INFO_SIZE - 1
if end > 0xFFFF:
return {
"address": address,
"valid": False,
"error": f"register information block {h16(address)}+{DTC_REGISTER_INFO_SIZE} exceeds page 0",
}
if not rom.contains(address, DTC_REGISTER_INFO_SIZE):
return {
"address": address,
"valid": False,
"error": f"register information block {h16(address)}-{h16(end)} is outside ROM image",
}
dtmr = rom.u16(address)
dtsr = rom.u16(address + 2)
dtdr = rom.u16(address + 4)
dtcr = rom.u16(address + 6)
mode = _mode(dtmr)
transfers = 0x10000 if dtcr == 0 else dtcr
return {
"address": address,
"valid": True,
"dtmr": dtmr,
"dtsr": dtsr,
"dtdr": dtdr,
"dtcr": dtcr,
"mode": mode,
"source": _endpoint(dtsr, mode["source_increment"], mode["source_increment_step"]),
"destination": _endpoint(dtdr, mode["destination_increment"], mode["destination_increment_step"]),
"count": {
"raw": dtcr,
"transfers": transfers,
"bytes": transfers * mode["bytes_per_transfer"],
"zero_means_65536": dtcr == 0,
},
}

View File

@@ -1,7 +1,7 @@
from __future__ import annotations from __future__ import annotations
from .model import EA from .model import EA
from .tables import IO_BITFIELDS, IO_REGISTERS from .tables import IO_BITFIELDS, IO_DTE_BITS, IO_PRIORITY_FIELDS, IO_REGISTERS
def h8(value: int) -> str: def h8(value: int) -> str:
@@ -66,6 +66,12 @@ def label_or_h(address: int, labels: dict[int, str]) -> str:
def _bitfield_name(address: int, bit: int) -> str | None: def _bitfield_name(address: int, bit: int) -> str | None:
priority_name = _priority_bit_name(address, bit)
if priority_name:
return priority_name
dte_name = _dte_bit_name(address, bit)
if dte_name:
return dte_name
return IO_BITFIELDS.get(address, {}).get(bit) return IO_BITFIELDS.get(address, {}).get(bit)
@@ -89,6 +95,54 @@ def _adcsr_semantics(value: int) -> str:
return f"A/D {state}, {mode} {channels}, {conversion}, {interrupt}" return f"A/D {state}, {mode} {channels}, {conversion}, {interrupt}"
def _priority_bit_name(address: int, bit: int) -> str | None:
for shift, source in IO_PRIORITY_FIELDS.get(address, ()):
if shift <= bit <= shift + 2:
return f"{source} priority bit {bit - shift}"
if address in IO_PRIORITY_FIELDS and bit in (7, 3):
return "reserved priority bit"
return None
def _dte_bit_name(address: int, bit: int) -> str | None:
fields = IO_DTE_BITS.get(address)
if fields is None:
return None
source = fields.get(bit)
return f"{source} DTC enable" if source else "reserved DTE bit"
def _ipr_semantics(address: int, value: int) -> str:
parts: list[str] = []
for shift, source in IO_PRIORITY_FIELDS[address]:
priority = (value >> shift) & 0x07
parts.append(f"{source} priority={priority}")
reserved = _set_bit_numbers(value, 0x88)
if reserved:
parts.append(f"reserved bits {reserved} should be 0")
return "; ".join(parts)
def _dte_semantics(address: int, value: int) -> str:
fields = IO_DTE_BITS[address]
parts = [
f"{source} {'DTC enabled' if value & (1 << bit) else 'CPU interrupt'}"
for bit, source in sorted(fields.items(), reverse=True)
]
assigned_mask = 0
for bit in fields:
assigned_mask |= 1 << bit
reserved = _set_bit_numbers(value, (~assigned_mask) & 0xFF)
if reserved:
parts.append(f"reserved bits {reserved} should be 0")
return "; ".join(parts)
def _set_bit_numbers(value: int, mask: int) -> str:
bits = [str(bit) for bit in range(7, -1, -1) if value & mask & (1 << bit)]
return ", ".join(bits)
def _sci_smr_semantics(value: int) -> str: def _sci_smr_semantics(value: int) -> str:
mode = "sync" if value & 0x80 else "async" mode = "sync" if value & 0x80 else "async"
char_len = "7-bit" if value & 0x40 else "8-bit" char_len = "7-bit" if value & 0x40 else "8-bit"
@@ -157,6 +211,10 @@ def _rstcsr_semantics(value: int) -> str:
def _semantic_values(address: int, value: int) -> str: def _semantic_values(address: int, value: int) -> str:
if address in IO_PRIORITY_FIELDS:
return _ipr_semantics(address, value)
if address in IO_DTE_BITS:
return _dte_semantics(address, value)
if address == 0xFEE8: if address == 0xFEE8:
return _adcsr_semantics(value) return _adcsr_semantics(value)
if address in (0xFED8, 0xFEF0): if address in (0xFED8, 0xFEF0):

256
h8536/peripheral_access.py Normal file
View File

@@ -0,0 +1,256 @@
from __future__ import annotations
from collections.abc import Mapping
from dataclasses import dataclass
from .formatting import h16
from .model import Instruction
MANUAL_REFERENCES = [
"Manual/0900766b802125d0.md:12185 FRT FRC/OCRA/OCRB/ICR use TEMP for 16-bit CPU access",
"Manual/0900766b802125d0.md:12193 FRT byte access order is upper byte then lower byte",
"Manual/0900766b802125d0.md:12212 OCRA/OCRB reads are direct; writes still use TEMP",
"Manual/0900766b802125d0.md:17546 A/D ADDRA-ADDRD lower byte is accessed through TEMP",
"Manual/0900766b802125d0.md:17556 A/D full-result byte reads must be upper byte then lower byte",
]
@dataclass(frozen=True)
class TempRegisterPair:
name: str
high: int
low: int
module: str
read_only: bool = False
high_only_read_ok: bool = False
direct_read_without_temp: bool = False
TEMP_REGISTER_PAIRS: tuple[TempRegisterPair, ...] = (
TempRegisterPair("FRT1_FRC", 0xFE92, 0xFE93, "FRT TEMP"),
TempRegisterPair("FRT1_OCRA", 0xFE94, 0xFE95, "FRT TEMP", direct_read_without_temp=True),
TempRegisterPair("FRT1_OCRB", 0xFE96, 0xFE97, "FRT TEMP", direct_read_without_temp=True),
TempRegisterPair("FRT1_ICR", 0xFE98, 0xFE99, "FRT TEMP", read_only=True),
TempRegisterPair("FRT2_FRC", 0xFEA2, 0xFEA3, "FRT TEMP"),
TempRegisterPair("FRT2_OCRA", 0xFEA4, 0xFEA5, "FRT TEMP", direct_read_without_temp=True),
TempRegisterPair("FRT2_OCRB", 0xFEA6, 0xFEA7, "FRT TEMP", direct_read_without_temp=True),
TempRegisterPair("FRT2_ICR", 0xFEA8, 0xFEA9, "FRT TEMP", read_only=True),
TempRegisterPair("FRT3_FRC", 0xFEB2, 0xFEB3, "FRT TEMP"),
TempRegisterPair("FRT3_OCRA", 0xFEB4, 0xFEB5, "FRT TEMP", direct_read_without_temp=True),
TempRegisterPair("FRT3_OCRB", 0xFEB6, 0xFEB7, "FRT TEMP", direct_read_without_temp=True),
TempRegisterPair("FRT3_ICR", 0xFEB8, 0xFEB9, "FRT TEMP", read_only=True),
TempRegisterPair("ADDRA", 0xFEE0, 0xFEE1, "A/D TEMP", read_only=True, high_only_read_ok=True),
TempRegisterPair("ADDRB", 0xFEE2, 0xFEE3, "A/D TEMP", read_only=True, high_only_read_ok=True),
TempRegisterPair("ADDRC", 0xFEE4, 0xFEE5, "A/D TEMP", read_only=True, high_only_read_ok=True),
TempRegisterPair("ADDRD", 0xFEE6, 0xFEE7, "A/D TEMP", read_only=True, high_only_read_ok=True),
)
TEMP_PAIR_BY_ADDRESS = {
address: (pair, byte_name)
for pair in TEMP_REGISTER_PAIRS
for address, byte_name in ((pair.high, "high"), (pair.low, "low"))
}
READ_ONLY_ROOTS = {"BTST", "CMP:E", "CMP:G", "CMP:I", "MOVFPE", "TST"}
WRITE_ROOTS = {"BCLR", "BNOT", "BSET", "CLR", "MOV:G", "MOV:S", "MOVTPE", "NEG", "NOT"}
def analyze_peripheral_access(instructions: Mapping[int, Instruction]) -> dict[str, object]:
annotations: dict[int, list[str]] = {}
warnings: list[dict[str, object]] = []
instruction_metadata: dict[int, list[dict[str, object]]] = {}
pending: dict[tuple[str, str], dict[str, object]] = {}
for address in sorted(instructions):
ins = instructions[address]
accesses = _instruction_accesses(ins)
if not accesses:
continue
instruction_metadata[address] = [_public_access(access) for access in accesses]
for access in accesses:
note, warning = _analyze_access(access, pending)
if note:
annotations.setdefault(address, []).append(note)
if warning:
warnings.append(warning)
for item in pending.values():
pair = item["pair"]
assert isinstance(pair, TempRegisterPair)
if _pending_high_byte_is_suspicious(pair, str(item["direction"])):
warnings.append(
{
"address": item["address"],
"register": pair.name,
"severity": "warning",
"message": (
f"{pair.name} high byte access was not followed by low byte; "
"manual recommends word access or high-byte then low-byte"
),
"manual": MANUAL_REFERENCES,
},
)
return {
"manual_references": MANUAL_REFERENCES,
"warnings": warnings,
"annotations": {
address: "; ".join(parts)
for address, parts in sorted(annotations.items())
},
"instructions": instruction_metadata,
}
def peripheral_comment_for_instruction(analysis: Mapping[str, object] | None, address: int) -> str:
if not analysis:
return ""
annotations = analysis.get("annotations")
if not isinstance(annotations, Mapping):
return ""
comment = annotations.get(address)
return str(comment) if comment else ""
def peripheral_metadata_for_instruction(
analysis: Mapping[str, object] | None,
address: int,
) -> list[dict[str, object]]:
if not analysis:
return []
instructions = analysis.get("instructions")
if not isinstance(instructions, Mapping):
return []
metadata = instructions.get(address)
return metadata if isinstance(metadata, list) else []
def peripheral_json_payload(analysis: Mapping[str, object] | None) -> dict[str, object]:
if not analysis:
return {"manual_references": MANUAL_REFERENCES, "warnings": []}
return {
"manual_references": analysis.get("manual_references", MANUAL_REFERENCES),
"warnings": analysis.get("warnings", []),
}
def _analyze_access(
access: dict[str, object],
pending: dict[tuple[str, str], dict[str, object]],
) -> tuple[str, dict[str, object] | None]:
pair = access["pair"]
assert isinstance(pair, TempRegisterPair)
direction = str(access["direction"])
byte = str(access["byte"])
size = str(access["size"])
key = (pair.name, direction)
if size == "W" and byte == "high":
pending.pop(key, None)
return f"{pair.name} word {direction}; TEMP byte-order hazard avoided", None
if byte == "high":
pending[key] = access
if direction == "read" and pair.high_only_read_ok:
return f"{pair.name} high-byte read; valid 8-bit result, read low byte next for full 10-bit value", None
if direction == "read" and pair.direct_read_without_temp:
return f"{pair.name} high-byte read; OCRA/OCRB reads do not use TEMP", None
return f"{pair.name} high-byte {direction}; next low-byte access completes TEMP transfer", None
previous = pending.pop(key, None)
if previous is not None:
return f"{pair.name} low-byte {direction}; completes high->low TEMP transfer", None
warning = {
"address": access["address"],
"register": pair.name,
"severity": "warning",
"message": (
f"{pair.name} low byte {direction} before matching high byte; "
"TEMP may contain stale or unrelated data"
),
"manual": MANUAL_REFERENCES,
}
return f"{pair.name} low-byte {direction} before high byte; check TEMP ordering", warning
def _pending_high_byte_is_suspicious(pair: TempRegisterPair, direction: str) -> bool:
if direction == "read" and pair.high_only_read_ok:
return False
if direction == "read" and pair.direct_read_without_temp:
return False
return True
def _instruction_accesses(ins: Instruction) -> list[dict[str, object]]:
size = _mnemonic_size(ins.mnemonic)
direction = _access_direction(ins)
if direction is None:
return []
accesses: list[dict[str, object]] = []
for ref in ins.references:
pair_info = TEMP_PAIR_BY_ADDRESS.get(ref)
if pair_info is None:
continue
pair, byte = pair_info
if size == "W" and ref == pair.low:
byte = "low_unaligned"
accesses.append(
{
"address": ins.address,
"instruction": ins.text,
"register": pair.name,
"pair": pair,
"high_address": pair.high,
"low_address": pair.low,
"referenced_address": ref,
"referenced_address_hex": h16(ref),
"byte": byte,
"size": size,
"direction": direction,
},
)
return accesses
def _public_access(access: dict[str, object]) -> dict[str, object]:
return {
key: value
for key, value in access.items()
if key != "pair"
}
def _access_direction(ins: Instruction) -> str | None:
root = _mnemonic_root(ins.mnemonic)
if root in READ_ONLY_ROOTS:
return "read"
if root in {"BCLR", "BNOT", "BSET", "CLR", "NEG", "NOT"}:
return "write"
if root in {"ADD:Q", "ADD:G", "ADDX", "AND", "OR", "SUB", "SUBS", "SUBX", "XOR"}:
return "write"
if root in {"MOV:G", "MOV:S", "MOVTPE"}:
return "write" if _destination_operand(ins.operands).startswith("@") else "read"
if root in {"MOV:L", "MOV:F", "MOVFPE"}:
return "read"
if root == "STC":
return "write"
if root == "LDC":
return "read"
return None
def _destination_operand(operands: str) -> str:
if "," not in operands:
return operands.strip()
return operands.rsplit(",", 1)[1].strip()
def _mnemonic_root(mnemonic: str) -> str:
return mnemonic.rsplit(".", 1)[0]
def _mnemonic_size(mnemonic: str) -> str:
return "W" if mnemonic.endswith(".W") else "B"

652
h8536/pseudocode.py Normal file
View File

@@ -0,0 +1,652 @@
from __future__ import annotations
import argparse
import json
import re
from dataclasses import dataclass
from pathlib import Path
from typing import Any
JsonObject = dict[str, Any]
BRANCH_CONDITIONS = {
"BRN": "0",
"BHI": "!C && !Z",
"BLS": "C || Z",
"BCC": "!C",
"BCS": "C",
"BNE": "!Z",
"BEQ": "Z",
"BVC": "!V",
"BVS": "V",
"BPL": "!N",
"BMI": "N",
"BGE": "N == V",
"BLT": "N != V",
"BGT": "!Z && (N == V)",
"BLE": "Z || (N != V)",
}
@dataclass(frozen=True)
class PseudocodeOptions:
include_asm: bool = True
include_addresses: bool = True
include_cycles: bool = False
emit_declarations: bool = True
max_functions: int | None = None
def generate_pseudocode(
payload: JsonObject,
*,
source_name: str = "",
options: PseudocodeOptions | None = None,
) -> str:
opts = options or PseudocodeOptions()
instructions = list(payload.get("instructions", []))
label_names = _collect_label_names(payload)
functions = _function_nodes(payload, instructions, label_names)
if opts.max_functions is not None:
functions = functions[: opts.max_functions]
lines: list[str] = []
lines.extend(_file_header(source_name, payload))
if opts.emit_declarations:
lines.extend(_declarations(instructions, functions, label_names))
by_address = {int(ins["address"]): ins for ins in instructions}
all_addresses = sorted(by_address)
emitted: set[int] = set()
for function in functions:
function_lines, used_addresses = _render_function(function, by_address, label_names, opts)
if function_lines:
lines.extend(function_lines)
emitted.update(used_addresses)
orphan_addresses = [address for address in all_addresses if address not in emitted]
if orphan_addresses:
lines.extend(_render_orphan_block(orphan_addresses, by_address, label_names, opts))
return "\n".join(lines).rstrip() + "\n"
def load_pseudocode_input(path: Path) -> JsonObject:
with path.open("r", encoding="utf-8") as handle:
payload = json.load(handle)
if not isinstance(payload, dict) or "instructions" not in payload:
raise ValueError(f"{path} does not look like h8536_decompiler JSON output")
return payload
def write_pseudocode(input_path: Path, output_path: Path, options: PseudocodeOptions) -> None:
payload = load_pseudocode_input(input_path)
output_path.parent.mkdir(parents=True, exist_ok=True)
output_path.write_text(
generate_pseudocode(payload, source_name=str(input_path), options=options),
encoding="utf-8",
)
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(
description="Generate conservative C-like pseudocode from h8536_decompiler JSON output.",
)
parser.add_argument(
"input",
nargs="?",
type=Path,
default=Path("build/rom_decompiled.json"),
help="structured JSON emitted by h8536_decompiler.py",
)
parser.add_argument(
"--out",
type=Path,
default=Path("build/rom_pseudocode.c"),
help="pseudocode output path",
)
parser.add_argument("--no-asm", action="store_true", help="omit original assembly from line comments")
parser.add_argument("--no-addresses", action="store_true", help="omit instruction addresses from line comments")
parser.add_argument("--cycles", action="store_true", help="include cycle estimates when present in JSON")
parser.add_argument("--no-declarations", action="store_true", help="omit register/function declarations")
parser.add_argument("--max-functions", type=int, default=None, help="emit only the first N functions")
args = parser.parse_args(argv)
options = PseudocodeOptions(
include_asm=not args.no_asm,
include_addresses=not args.no_addresses,
include_cycles=args.cycles,
emit_declarations=not args.no_declarations,
max_functions=args.max_functions,
)
write_pseudocode(args.input, args.out, options)
print(f"wrote {args.out}")
return 0
def _file_header(source_name: str, payload: JsonObject) -> list[str]:
vector_count = len(payload.get("vectors", []))
function_count = len(payload.get("call_graph", {}).get("nodes", []))
instruction_count = len(payload.get("instructions", []))
source = f" from {source_name}" if source_name else ""
return [
"/*",
f" * H8/536 C-like pseudocode{source}",
" *",
" * This is a conservative structural translation of the decompiler JSON.",
" * Helpers such as set_flags_cmp8(), MEM8[], BIT(), C/Z/N/V, and",
" * return_from_interrupt() are pseudocode placeholders, not a runtime ABI.",
" *",
f" * vectors: {vector_count}, functions: {function_count}, instructions: {instruction_count}",
" */",
"",
"#include <stdint.h>",
"",
"typedef uint8_t u8;",
"typedef uint16_t u16;",
"",
"#define BIT(n) (1u << (n))",
"extern volatile u8 MEM8[0x10000];",
"extern volatile u16 MEM16[0x10000];",
"",
"u16 R0, R1, R2, R3, R4, R5, R6, R7;",
"u16 SR;",
"u8 CCR, BR, EP, DP, TP;",
"int C, Z, N, V;",
"",
]
def _declarations(instructions: list[JsonObject], functions: list[JsonObject], labels: dict[int, str]) -> list[str]:
lines: list[str] = []
registers = _referenced_io_registers(instructions)
if registers:
lines.append("/* H8/536 register field symbols used by this ROM. */")
for name, (address, width) in sorted(registers.items(), key=lambda item: item[1][0]):
c_type = "u16" if width == 16 else "u8"
lines.append(f"extern volatile {c_type} {c_identifier(name)}; /* 0x{address:04X} */")
lines.append("")
if functions:
lines.append("/* Function entry points discovered from vectors and call targets. */")
for function in functions:
label = labels.get(int(function["start"]), str(function.get("label", "")))
lines.append(f"void {c_identifier(label)}(void);")
lines.append("")
return lines
def _referenced_io_registers(instructions: list[JsonObject]) -> dict[str, tuple[int, int]]:
registers: dict[str, tuple[int, int]] = {}
for ins in instructions:
width = _size_bits(_mnemonic_size(str(ins.get("mnemonic", ""))))
for ref in ins.get("references", []):
name = ref.get("name")
if not name:
continue
address = int(ref["address"])
old = registers.get(name)
old_width = old[1] if old else 8
registers[name] = (address, max(old_width, width))
return registers
def _collect_label_names(payload: JsonObject) -> dict[int, str]:
labels: dict[int, str] = {}
for vector in payload.get("vectors", []):
target = vector.get("target")
label = vector.get("target_label")
if target is not None and label:
labels[int(target)] = c_identifier(str(label))
for node in payload.get("call_graph", {}).get("nodes", []):
start = int(node["start"])
labels[start] = c_identifier(str(node.get("label") or _label_for(start)))
for ins in payload.get("instructions", []):
for target in ins.get("targets", []):
labels.setdefault(int(target), c_identifier(_label_for(int(target))))
return labels
def _function_nodes(
payload: JsonObject,
instructions: list[JsonObject],
labels: dict[int, str],
) -> list[JsonObject]:
nodes = [dict(node) for node in payload.get("call_graph", {}).get("nodes", [])]
if nodes:
nodes.sort(key=lambda node: int(node["start"]))
return nodes
if not instructions:
return []
start = int(min(ins["address"] for ins in instructions))
end = int(max(ins["address"] for ins in instructions))
return [
{
"start": start,
"end": end,
"label": labels.get(start, _label_for(start)),
"sources": [],
"instruction_count": len(instructions),
"calls": [],
"unresolved_calls": 0,
},
]
def _render_function(
function: JsonObject,
by_address: dict[int, JsonObject],
labels: dict[int, str],
opts: PseudocodeOptions,
) -> tuple[list[str], set[int]]:
start = int(function["start"])
end = int(function.get("end", start))
addresses = [address for address in sorted(by_address) if start <= address <= end]
if not addresses:
return [], set()
name = c_identifier(labels.get(start, str(function.get("label") or _label_for(start))))
local_targets = _local_target_addresses(addresses, by_address) | {
address for address in addresses if address in labels
}
lines = [f"void {name}(void)", "{"]
sources = function.get("sources") or []
if sources:
lines.append(f" /* vector sources: {', '.join(str(source) for source in sources)} */")
for address in addresses:
if address in local_targets and address != start:
lines.append(f"{labels.get(address, _label_for(address))}:")
ins = by_address[address]
statement = _translate_instruction(ins, labels)
comment = _line_comment(ins, opts)
lines.append(f" {statement}{comment}")
lines.append("}")
lines.append("")
return lines, set(addresses)
def _render_orphan_block(
addresses: list[int],
by_address: dict[int, JsonObject],
labels: dict[int, str],
opts: PseudocodeOptions,
) -> list[str]:
lines = ["void unreached_or_unowned_code(void)", "{"]
local_targets = _local_target_addresses(addresses, by_address) | {
address for address in addresses if address in labels
}
for address in addresses:
if address in local_targets:
lines.append(f"{labels.get(address, _label_for(address))}:")
ins = by_address[address]
lines.append(f" {_translate_instruction(ins, labels)}{_line_comment(ins, opts)}")
lines.append("}")
lines.append("")
return lines
def _local_target_addresses(addresses: list[int], by_address: dict[int, JsonObject]) -> set[int]:
address_set = set(addresses)
targets: set[int] = set()
for address in addresses:
for target in by_address[address].get("targets", []):
target = int(target)
if target in address_set:
targets.add(target)
return targets
def _translate_instruction(ins: JsonObject, labels: dict[int, str]) -> str:
mnemonic = str(ins.get("mnemonic", ""))
operands = str(ins.get("operands", ""))
kind = str(ins.get("kind", "normal"))
ops = split_operands(operands)
base = _mnemonic_base(mnemonic)
size = _mnemonic_size(mnemonic)
if kind == "return":
if ops:
return f"return_with_stack_adjust({_format_operand(ops[0], size)});"
return "return;"
if kind == "rte":
return "return_from_interrupt();"
if kind == "sleep":
return "sleep_until_interrupt();"
if kind == "call":
return _call_statement(ins, labels, ops)
if kind in {"branch", "jump"}:
return _branch_or_jump_statement(ins, labels, ops, base)
if base.startswith("."):
return f"emit_data({_quoted(str(ins.get('text', mnemonic)))});"
if base in {"MOV", "MOV:G", "MOV:I", "MOV:E", "MOV:L", "MOV:S", "MOV:F"} and len(ops) == 2:
source = _format_operand(ops[0], size)
dest = _format_operand(ops[1], size, lvalue=True)
return f"{dest} = {_cast(source, size)};"
if base in {"MOVFPE"} and len(ops) == 2:
source = _format_operand(ops[0], size)
dest = _format_operand(ops[1], size, lvalue=True)
return f"{dest} = read_eclock({source});"
if base in {"MOVTPE"} and len(ops) == 2:
source = _format_operand(ops[0], size)
dest = _format_operand(ops[1], size, lvalue=True)
return f"write_eclock({dest}, {source});"
if base in {"ADD", "ADD:G", "ADD:Q", "ADDS"} and len(ops) == 2:
return _binary_update(ops, "+=", size)
if base in {"SUB", "SUBS"} and len(ops) == 2:
return _binary_update(ops, "-=", size)
if base == "OR" and len(ops) == 2:
return _binary_update(ops, "|=", size)
if base == "AND" and len(ops) == 2:
return _binary_update(ops, "&=", size)
if base == "XOR" and len(ops) == 2:
return _binary_update(ops, "^=", size)
if base in {"ADDX", "SUBX", "MULXU", "DIVXU"} and len(ops) == 2:
source = _format_operand(ops[0], size)
dest = _format_operand(ops[1], size, lvalue=True)
helper = _helper_name(base, size)
return f"{dest} = {helper}({dest}, {source});"
if base in {"CMP", "CMP:G", "CMP:I", "CMP:E"} and len(ops) == 2:
source = _format_operand(ops[0], size)
dest = _format_operand(ops[1], size)
return f"{_helper_name('set_flags_cmp', size)}({dest}, {source});"
if base == "TST" and len(ops) == 1:
return f"{_helper_name('set_flags_tst', size)}({_format_operand(ops[0], size)});"
if base == "CLR" and len(ops) == 1:
return f"{_format_operand(ops[0], size, lvalue=True)} = 0;"
if base == "NEG" and len(ops) == 1:
target = _format_operand(ops[0], size, lvalue=True)
return f"{target} = -{target};"
if base == "NOT" and len(ops) == 1:
target = _format_operand(ops[0], size, lvalue=True)
return f"{target} = ~{target};"
if base in {"SHAL", "SHLL"} and len(ops) == 1:
target = _format_operand(ops[0], size, lvalue=True)
return f"{target} <<= 1;"
if base in {"SHAR", "SHLR"} and len(ops) == 1:
target = _format_operand(ops[0], size, lvalue=True)
return f"{target} >>= 1;"
if base in {"ROTL", "ROTR", "ROTXL", "ROTXR"} and len(ops) == 1:
target = _format_operand(ops[0], size, lvalue=True)
return f"{target} = {_helper_name(base.lower(), size)}({target});"
if base == "SWAP" and len(ops) == 1:
target = _format_operand(ops[0], size, lvalue=True)
return f"{target} = swap_bytes({target});"
if base == "EXTU" and len(ops) == 1:
target = _format_operand(ops[0], size, lvalue=True)
return f"{target} = zero_extend8({target});"
if base == "EXTS" and len(ops) == 1:
target = _format_operand(ops[0], size, lvalue=True)
return f"{target} = sign_extend8({target});"
if base in {"BSET", "BCLR", "BNOT", "BTST"} and len(ops) == 2:
return _bit_statement(base, ops, size)
if base == "LDC" and len(ops) == 2:
source = _format_operand(ops[0], size)
dest = _format_operand(ops[1], size, lvalue=True)
return f"{dest} = {_cast(source, size)};"
if base == "STC" and len(ops) == 2:
source = _format_operand(ops[0], size)
dest = _format_operand(ops[1], size, lvalue=True)
return f"{dest} = {_cast(source, size)};"
if base == "ORC" and len(ops) == 2:
return _binary_update(ops, "|=", size)
if base == "ANDC" and len(ops) == 2:
return _binary_update(ops, "&=", size)
if base == "XORC" and len(ops) == 2:
return _binary_update(ops, "^=", size)
if base == "LDM" and len(ops) == 2:
return f"pop_registers({_register_list_argument(ops[1])});"
if base == "STM" and len(ops) == 2:
return f"push_registers({_register_list_argument(ops[0])});"
if base == "LINK" and len(ops) == 2:
return f"link_frame({_format_operand(ops[1], size)});"
if base == "UNLK":
return "unlink_frame();"
if base == "TRAPA" and ops:
return f"trap({_format_operand(ops[0], size)});"
if base == "TRAP/VS":
return "trap_vs();"
if base == "NOP":
return "/* nop */;"
return f"asm_{_safe_token(base)}({_quoted(str(ins.get('text') or mnemonic))});"
def _branch_or_jump_statement(ins: JsonObject, labels: dict[int, str], ops: list[str], base: str) -> str:
target = _target_label(ins, labels)
if base in {"BRA", "JMP", "PJMP"}:
if target:
return f"goto {target};"
expr = _format_operand(ops[0], "") if ops else "unknown_target"
return f"goto_indirect({expr});"
if base.startswith("SCB/"):
register = _format_operand(ops[0], "") if ops else "R?"
cond = base.split("/", 1)[1].lower()
return f"if (scb_{cond}({register})) goto {target or 'unknown_target'};"
condition = BRANCH_CONDITIONS.get(base, f"cond_{_safe_token(base)}()")
return f"if ({condition}) goto {target or 'unknown_target'};"
def _call_statement(ins: JsonObject, labels: dict[int, str], ops: list[str]) -> str:
target = _target_label(ins, labels)
if target:
return f"{target}();"
expr = _format_operand(ops[0], "") if ops else "unknown_target"
return f"call_indirect({expr});"
def _target_label(ins: JsonObject, labels: dict[int, str]) -> str:
targets = ins.get("targets", [])
if targets:
target = int(targets[0])
return labels.get(target, _label_for(target))
return ""
def _binary_update(ops: list[str], operator: str, size: str) -> str:
source = _format_operand(ops[0], size)
dest = _format_operand(ops[1], size, lvalue=True)
return f"{dest} {operator} {_cast(source, size)};"
def _bit_statement(base: str, ops: list[str], size: str) -> str:
bit = _format_operand(ops[0], size)
dest = _format_operand(ops[1], size, lvalue=True)
bit_expr = f"BIT({bit})"
if base == "BSET":
return f"{dest} |= {bit_expr};"
if base == "BCLR":
return f"{dest} &= ~{bit_expr};"
if base == "BNOT":
return f"{dest} ^= {bit_expr};"
return f"set_flags_btst({dest}, {bit});"
def split_operands(operands: str) -> list[str]:
if not operands:
return []
parts: list[str] = []
start = 0
depth = 0
for idx, char in enumerate(operands):
if char in "({":
depth += 1
elif char in ")}" and depth:
depth -= 1
elif char == "," and depth == 0:
parts.append(operands[start:idx].strip())
start = idx + 1
parts.append(operands[start:].strip())
return [part for part in parts if part]
def _format_operand(operand: str, size: str, *, lvalue: bool = False) -> str:
op = _replace_h_literals(operand.strip())
if op.startswith("#"):
return op[1:]
if op.startswith("@(") and op.endswith(")"):
inner = op[2:-1]
pieces = split_operands(inner)
if len(pieces) == 2:
disp, reg = pieces
offset = f"{reg} - {disp[1:]}" if disp.startswith("-") else f"{reg} + {disp}"
return f"{_mem_name(size)}[{offset}]"
if re.fullmatch(r"@-R[0-7]", op):
return f"{_mem_name(size)}[--{op[2:]}]"
if re.fullmatch(r"@R[0-7]\+", op):
return f"{_mem_name(size)}[{op[1:-1]}++]"
if re.fullmatch(r"@R[0-7]", op):
return f"{_mem_name(size)}[{op[1:]}]"
if op.startswith("@BR:"):
return f"{_mem_name(size)}[(BR << 8) | {op[4:]}]"
if op.startswith("@0x"):
return f"{_mem_name(size)}[{op[1:]}]"
if op.startswith("@"):
return c_identifier(op[1:])
if op.startswith("{") and op.endswith("}"):
return _register_list_argument(op)
if re.fullmatch(r"loc_[0-9A-Fa-f]{4}", op):
return c_identifier(op)
if re.fullmatch(r"[A-Za-z_][A-Za-z0-9_/\?]*", op):
return c_identifier(op)
return op
def _cast(expr: str, size: str) -> str:
if size == "B":
return f"(uint8_t)({expr})"
if size == "W":
return f"(uint16_t)({expr})"
return expr
def _line_comment(ins: JsonObject, opts: PseudocodeOptions) -> str:
parts: list[str] = []
if opts.include_addresses:
parts.append(f"{int(ins['address']):04X}")
if opts.include_asm:
text = str(ins.get("text") or _instruction_text(ins))
parts.append(text)
comment = str(ins.get("comment") or "").strip()
if comment:
parts.append(comment)
parts.extend(_metadata_comments(ins))
if opts.include_cycles and ins.get("cycles"):
parts.append(_cycle_summary(ins["cycles"]))
if not parts:
return ""
return " /* " + "; ".join(_sanitize_comment(part) for part in parts) + " */"
def _metadata_comments(ins: JsonObject) -> list[str]:
comments: list[str] = []
sci = ins.get("sci")
if isinstance(sci, dict):
for inference in sci.get("inferences", []):
if isinstance(inference, dict) and inference.get("comment"):
comments.append(str(inference["comment"]))
for access in ins.get("peripheral_access", []):
if not isinstance(access, dict):
continue
register = access.get("register")
direction = access.get("direction")
size = access.get("size")
byte = access.get("byte")
if register and direction:
comments.append(f"{register} {size} {direction} {byte} TEMP access")
return comments
def _instruction_text(ins: JsonObject) -> str:
mnemonic = str(ins.get("mnemonic", ""))
operands = str(ins.get("operands", ""))
return f"{mnemonic} {operands}".strip()
def _cycle_summary(cycles: JsonObject) -> str:
if "cycles" in cycles:
return f"cycles={cycles['cycles']}"
if "not_taken" in cycles and "taken" in cycles:
return f"cycles={cycles['not_taken']}/{cycles['taken']} nt/t"
return "cycles=?"
def _mnemonic_base(mnemonic: str) -> str:
return mnemonic.rsplit(".", 1)[0] if "." in mnemonic else mnemonic
def _mnemonic_size(mnemonic: str) -> str:
suffix = mnemonic.rsplit(".", 1)[-1] if "." in mnemonic else ""
if suffix in {"B", "W"}:
return suffix
if mnemonic.startswith("CMP:I"):
return "W"
if mnemonic.startswith("CMP:E"):
return "B"
return ""
def _size_bits(size: str) -> int:
return 16 if size == "W" else 8
def _mem_name(size: str) -> str:
return "MEM16" if size == "W" else "MEM8"
def _helper_name(base: str, size: str) -> str:
suffix = {"B": "8", "W": "16"}.get(size, "")
return f"{_safe_token(base)}{suffix}"
def _register_list_argument(operand: str) -> str:
inner = operand.strip().strip("{}")
regs = [c_identifier(part.strip()) for part in inner.split(",") if part.strip()]
return ", ".join(regs) if regs else "/* empty */"
def _replace_h_literals(text: str) -> str:
return re.sub(r"H'([0-9A-Fa-f]+)", lambda match: "0x" + match.group(1).upper(), text)
def c_identifier(name: str) -> str:
cleaned = re.sub(r"[^0-9A-Za-z_]", "_", name.strip())
cleaned = re.sub(r"_+", "_", cleaned).strip("_")
if not cleaned:
cleaned = "unnamed"
if cleaned[0].isdigit():
cleaned = "_" + cleaned
return cleaned
def _safe_token(text: str) -> str:
return c_identifier(text).lower()
def _label_for(address: int) -> str:
return f"loc_{address:04X}"
def _quoted(text: str) -> str:
return json.dumps(text)
def _sanitize_comment(text: str) -> str:
return str(text).replace("*/", "* /").replace("\r", " ").replace("\n", " ")

View File

@@ -4,14 +4,57 @@ import json
from pathlib import Path from pathlib import Path
from .cycles import cycle_comment from .cycles import cycle_comment
from .dtc import DtcEndpointInfo, DtcRegisterInfo
from .formatting import h16, label_for from .formatting import h16, label_for
from .memory import MEMORY_REGIONS, region_for from .memory import MEMORY_REGIONS, region_for
from .model import Instruction from .model import Instruction
from .peripheral_access import (
peripheral_comment_for_instruction,
peripheral_json_payload,
peripheral_metadata_for_instruction,
)
from .rom import Rom from .rom import Rom
from .sci import sci_comment_for_instruction, sci_json_payload, sci_metadata_for_instruction
from .tables import IO_REGISTERS from .tables import IO_REGISTERS
from .timing import format_timing_summary
from .vectors import DtcVectorEntry from .vectors import DtcVectorEntry
def _dtc_endpoint_text(endpoint: DtcEndpointInfo) -> str:
address = endpoint["address"]
text = endpoint["text"]
return f"{text} ({h16(address)})" if text != h16(address) else text
def _dtc_register_lines(vector_addr: int, entry: DtcVectorEntry, info: DtcRegisterInfo) -> list[str]:
target = entry["register_info_address"]
if not info.get("valid"):
error = info.get("error", "register information unavailable")
return [f"; {h16(vector_addr)} {entry['source']:<24} {h16(target)} unavailable: {error}"]
mode = info["mode"]
source = info["source"]
destination = info["destination"]
count = info["count"]
lines = [
(
f"; {h16(vector_addr)} {entry['source']:<24} {h16(target)} "
f"{mode['size']} x{count['transfers']} ({count['bytes']} bytes): "
f"{_dtc_endpoint_text(source)} -> {_dtc_endpoint_text(destination)} "
f"[src+={mode['source_increment_step']}, dst+={mode['destination_increment_step']}]"
),
(
f"; DTMR={h16(info['dtmr'])} DTSR={h16(info['dtsr'])} "
f"DTDR={h16(info['dtdr'])} DTCR={h16(info['dtcr'])}"
),
]
if mode["reserved_set"]:
lines.append(f"; warning: DTMR reserved bits set ({h16(mode['reserved'])})")
if count["zero_means_65536"]:
lines.append("; DTCR raw zero means an initial transfer count of 65536")
return lines
def _reference_comment(ins: Instruction) -> str: def _reference_comment(ins: Instruction) -> str:
parts: list[str] = [] parts: list[str] = []
for address in ins.references: for address in ins.references:
@@ -31,7 +74,10 @@ def format_listing(
traced: bool, traced: bool,
dtc_vectors: dict[int, DtcVectorEntry] | None = None, dtc_vectors: dict[int, DtcVectorEntry] | None = None,
data_candidates: dict[str, list[dict[str, object]]] | None = None, data_candidates: dict[str, list[dict[str, object]]] | None = None,
timing_summary: dict[str, list[dict[str, object]]] | None = None,
show_cycles: bool = False, show_cycles: bool = False,
sci_analysis: dict[str, object] | None = None,
peripheral_access: dict[str, object] | None = None,
) -> str: ) -> str:
lines: list[str] = [] lines: list[str] = []
lines.append("; H8/536 ROM disassembly") lines.append("; H8/536 ROM disassembly")
@@ -45,6 +91,9 @@ def format_listing(
lines.append("; - In minimum mode the reset vector at H'0000-H'0001 is a 16-bit PC.") lines.append("; - In minimum mode the reset vector at H'0000-H'0001 is a 16-bit PC.")
lines.append("; - The register field is H'FE80-H'FFFF; names below come from appendix B.") lines.append("; - The register field is H'FE80-H'FFFF; names below come from appendix B.")
lines.append("; - @aa:8 short absolute operands use BR as the upper address byte.") lines.append("; - @aa:8 short absolute operands use BR as the upper address byte.")
lines.append("; - SCI baud inference uses section 14.2.8 BRR formulas when SMR/BRR are known.")
if sci_analysis and sci_analysis.get("clock_hz") is None:
lines.append("; - Pass --clock-hz to convert SCI BRR settings into numeric baud rates.")
if show_cycles: if show_cycles:
lines.append("; - Cycle counts use Appendix A tables A-7/A-8 for on-chip access with no external wait states.") lines.append("; - Cycle counts use Appendix A tables A-7/A-8 for on-chip access with no external wait states.")
lines.append("") lines.append("")
@@ -63,6 +112,10 @@ def format_listing(
target = entry["register_info_address"] target = entry["register_info_address"]
lines.append(f"; {h16(vector_addr)} {entry['source']:<24} -> {h16(target)}") lines.append(f"; {h16(vector_addr)} {entry['source']:<24} -> {h16(target)}")
lines.append("") lines.append("")
lines.append("; DTC Register Information")
for vector_addr, entry in sorted(dtc_vectors.items()):
lines.extend(_dtc_register_lines(vector_addr, entry, entry["register_info"]))
lines.append("")
if data_candidates: if data_candidates:
strings = data_candidates.get("strings", []) strings = data_candidates.get("strings", [])
@@ -81,6 +134,9 @@ def format_listing(
) )
lines.append("") lines.append("")
if timing_summary:
lines.extend(format_timing_summary(timing_summary))
for address in sorted(instructions): for address in sorted(instructions):
ins = instructions[address] ins = instructions[address]
if address in labels: if address in labels:
@@ -92,6 +148,8 @@ def format_listing(
part part
for part in ( for part in (
ins.comment, ins.comment,
sci_comment_for_instruction(sci_analysis, address),
peripheral_comment_for_instruction(peripheral_access, address),
_reference_comment(ins) if not ins.comment else "", _reference_comment(ins) if not ins.comment else "",
cycle_comment(ins.cycles) if show_cycles else "", cycle_comment(ins.cycles) if show_cycles else "",
) )
@@ -111,6 +169,9 @@ def write_json(
dtc_vectors: dict[int, DtcVectorEntry] | None = None, dtc_vectors: dict[int, DtcVectorEntry] | None = None,
data_candidates: dict[str, list[dict[str, object]]] | None = None, data_candidates: dict[str, list[dict[str, object]]] | None = None,
call_graph: dict[str, object] | None = None, call_graph: dict[str, object] | None = None,
timing_summary: dict[str, list[dict[str, object]]] | None = None,
sci_analysis: dict[str, object] | None = None,
peripheral_access: dict[str, object] | None = None,
) -> None: ) -> None:
payload = { payload = {
"vectors": [ "vectors": [
@@ -130,8 +191,23 @@ def write_json(
], ],
"data_candidates": data_candidates or {"strings": [], "pointer_tables": []}, "data_candidates": data_candidates or {"strings": [], "pointer_tables": []},
"call_graph": call_graph or {"nodes": [], "edges": []}, "call_graph": call_graph or {"nodes": [], "edges": []},
"timing_summary": timing_summary or {"blocks": [], "loops": []},
"sci": sci_json_payload(sci_analysis),
"peripheral_access": peripheral_json_payload(peripheral_access),
"instructions": [ "instructions": [
{ _instruction_payload(ins, sci_analysis, peripheral_access)
for ins in (instructions[addr] for addr in sorted(instructions))
],
}
path.write_text(json.dumps(payload, indent=2), encoding="utf-8")
def _instruction_payload(
ins: Instruction,
sci_analysis: dict[str, object] | None = None,
peripheral_access: dict[str, object] | None = None,
) -> dict[str, object]:
payload: dict[str, object] = {
"address": ins.address, "address": ins.address,
"address_region": region_for(ins.address).name, "address_region": region_for(ins.address).name,
"bytes": ins.raw.hex().upper(), "bytes": ins.raw.hex().upper(),
@@ -153,10 +229,13 @@ def write_json(
"comment": ins.comment, "comment": ins.comment,
"valid": ins.valid, "valid": ins.valid,
} }
for ins in (instructions[addr] for addr in sorted(instructions)) sci_metadata = sci_metadata_for_instruction(sci_analysis, ins.address)
], if sci_metadata:
} payload["sci"] = sci_metadata
path.write_text(json.dumps(payload, indent=2), encoding="utf-8") peripheral_metadata = peripheral_metadata_for_instruction(peripheral_access, ins.address)
if peripheral_metadata:
payload["peripheral_access"] = peripheral_metadata
return payload
def format_callgraph_dot(call_graph: dict[str, object]) -> str: def format_callgraph_dot(call_graph: dict[str, object]) -> str:

375
h8536/sci.py Normal file
View File

@@ -0,0 +1,375 @@
from __future__ import annotations
from collections.abc import Mapping
from .formatting import parse_int
from .model import Instruction
SCI_REGISTERS: dict[str, dict[str, int]] = {
"SCI1": {"SMR": 0xFED8, "BRR": 0xFED9, "SCR": 0xFEDA},
"SCI2": {"SMR": 0xFEF0, "BRR": 0xFEF1, "SCR": 0xFEF2},
}
SCI_REGISTER_BY_ADDRESS = {
address: (channel, register)
for channel, registers in SCI_REGISTERS.items()
for register, address in registers.items()
}
FORMULAS = {
"async": "B = clock_hz / (64 * 2^(2n) * (N + 1))",
"sync": "B = clock_hz / (8 * 2^(2n) * (N + 1))",
}
MANUAL_REFERENCES = [
"Manual/0900766b802125d0.md:15837 SMR selects SCI mode and CKS1/CKS0 internal clock source",
"Manual/0900766b802125d0.md:16027 SCR.CKE1 selects internal or external clock source",
"Manual/0900766b802125d0.md:16177 BRR and SMR.CKS determine the baud-rate generator",
"Manual/0900766b802125d0.md:16303 asynchronous BRR formula",
"Manual/0900766b802125d0.md:16379 synchronous BRR formula",
"Manual/0900766b802125d0.md:16410 SCI clock source selection tables",
]
_READ_ONLY_ROOTS = {"BTST", "CMP:E", "CMP:G", "CMP:I", "MOVFPE", "TST"}
def analyze_sci(
instructions: Mapping[int, Instruction],
clock_hz: int | None = None,
) -> dict[str, object]:
"""Track SCI setup writes in listing order and infer conservative baud metadata."""
state: dict[str, dict[str, int | None]] = {
channel: {"SMR": None, "BRR": None, "SCR": None} for channel in SCI_REGISTERS
}
channels: dict[str, dict[str, list[dict[str, object]]]] = {
channel: {"writes": [], "configurations": []} for channel in SCI_REGISTERS
}
annotations: dict[int, str] = {}
instruction_metadata: dict[int, dict[str, object]] = {}
seen_configurations: set[tuple[object, ...]] = set()
for address in sorted(instructions):
ins = instructions[address]
writes = _apply_instruction(ins, state)
if not writes:
continue
affected_channels = sorted({str(write["channel"]) for write in writes})
instruction_entry: dict[str, object] = {"writes": writes}
for write in writes:
channel = str(write["channel"])
channels[channel]["writes"].append(write)
emitted: list[dict[str, object]] = []
for channel in affected_channels:
inference = _infer_channel(channel, state[channel], clock_hz)
if inference is None:
continue
key = _configuration_key(channel, inference, clock_hz)
if key in seen_configurations:
continue
seen_configurations.add(key)
inference = {**inference, "address": ins.address, "instruction": ins.text}
channels[channel]["configurations"].append(inference)
emitted.append(inference)
if emitted:
annotation = "; ".join(str(item["comment"]) for item in emitted if item.get("comment"))
if annotation:
annotations[ins.address] = annotation
instruction_entry["inferences"] = emitted
instruction_metadata[ins.address] = instruction_entry
return {
"clock_hz": clock_hz,
"formulas": FORMULAS,
"manual_references": MANUAL_REFERENCES,
"channels": channels,
"annotations": annotations,
"instructions": instruction_metadata,
}
def sci_comment_for_instruction(analysis: Mapping[str, object] | None, address: int) -> str:
if not analysis:
return ""
annotations = analysis.get("annotations")
if not isinstance(annotations, Mapping):
return ""
comment = annotations.get(address)
return str(comment) if comment else ""
def sci_metadata_for_instruction(analysis: Mapping[str, object] | None, address: int) -> dict[str, object] | None:
if not analysis:
return None
instructions = analysis.get("instructions")
if not isinstance(instructions, Mapping):
return None
metadata = instructions.get(address)
return metadata if isinstance(metadata, dict) else None
def sci_json_payload(analysis: Mapping[str, object] | None) -> dict[str, object]:
if not analysis:
return {
"clock_hz": None,
"formulas": FORMULAS,
"manual_references": MANUAL_REFERENCES,
"channels": {channel: {"writes": [], "configurations": []} for channel in SCI_REGISTERS},
}
return {
"clock_hz": analysis.get("clock_hz"),
"formulas": analysis.get("formulas", FORMULAS),
"manual_references": analysis.get("manual_references", MANUAL_REFERENCES),
"channels": analysis.get("channels", {}),
}
def _apply_instruction(ins: Instruction, state: dict[str, dict[str, int | None]]) -> list[dict[str, object]]:
root = _mnemonic_root(ins.mnemonic)
if root in _READ_ONLY_ROOTS:
return []
size = _mnemonic_size(ins.mnemonic)
base_address = _destination_base_address(ins, size)
if base_address is None:
return []
operation = root
value: int | None
if root in {"BCLR", "BNOT", "BSET"}:
bit = _immediate_bit(ins.operands)
value = None
targets = list(_target_registers(base_address, "B"))
if bit is not None and len(targets) == 1:
channel, register, _address = targets[0]
current = state[channel][register]
if current is not None:
if root == "BSET":
value = current | (1 << bit)
elif root == "BCLR":
value = current & ~(1 << bit)
else:
value = current ^ (1 << bit)
value &= 0xFF
return _record_writes(ins, targets, value, operation, state)
if root == "CLR":
value = 0
else:
value = _immediate_source_value(ins.operands)
if value is None and root == "NOT":
targets = list(_target_registers(base_address, size))
if len(targets) == 1:
channel, register, _address = targets[0]
current = state[channel][register]
value = None if current is None else (~current) & 0xFF
targets = list(_target_registers(base_address, size))
if not targets:
return []
if root in {"CMP", "CMP:E", "CMP:G", "CMP:I", "TST"}:
return []
return _record_writes(ins, targets, value, operation, state)
def _record_writes(
ins: Instruction,
targets: list[tuple[str, str, int]],
value: int | None,
operation: str,
state: dict[str, dict[str, int | None]],
) -> list[dict[str, object]]:
width = max(len(targets), 1)
writes: list[dict[str, object]] = []
for index, (channel, register, register_address) in enumerate(targets):
byte_value = _byte_for_target(value, width, index)
state[channel][register] = byte_value
event: dict[str, object] = {
"address": ins.address,
"instruction": ins.text,
"channel": channel,
"register": register,
"register_address": register_address,
"operation": operation,
"value": byte_value,
}
if byte_value is not None:
event["value_hex"] = _hex8(byte_value)
writes.append(event)
return writes
def _target_registers(base_address: int, size: str) -> list[tuple[str, str, int]]:
width = 2 if size == "W" else 1
targets: list[tuple[str, str, int]] = []
for offset in range(width):
address = base_address + offset
register = SCI_REGISTER_BY_ADDRESS.get(address)
if register is None:
continue
channel, name = register
targets.append((channel, name, address))
return targets
def _destination_base_address(ins: Instruction, size: str) -> int | None:
destination = _destination_operand(ins.operands)
if destination is None or not destination.startswith("@"):
return None
width = 2 if size == "W" else 1
for address in ins.references:
if any((address + offset) in SCI_REGISTER_BY_ADDRESS for offset in range(width)):
return address
return None
def _destination_operand(operands: str) -> str | None:
operands = operands.strip()
if not operands:
return None
if "," not in operands:
return operands
return operands.rsplit(",", 1)[1].strip()
def _immediate_source_value(operands: str) -> int | None:
source = operands.rsplit(",", 1)[0].strip() if "," in operands else ""
if not source.startswith("#"):
return None
try:
return parse_int(source[1:])
except ValueError:
return None
def _immediate_bit(operands: str) -> int | None:
source = operands.rsplit(",", 1)[0].strip() if "," in operands else ""
if not source.startswith("#"):
return None
try:
bit = parse_int(source[1:])
except ValueError:
return None
return bit if 0 <= bit <= 7 else None
def _mnemonic_root(mnemonic: str) -> str:
return mnemonic.split(".", 1)[0]
def _mnemonic_size(mnemonic: str) -> str:
if mnemonic.endswith(".W"):
return "W"
return "B"
def _byte_for_target(value: int | None, width: int, index: int) -> int | None:
if value is None:
return None
shift = 8 * (width - index - 1)
return (value >> shift) & 0xFF
def _infer_channel(
channel: str,
registers: Mapping[str, int | None],
clock_hz: int | None,
) -> dict[str, object] | None:
smr = registers.get("SMR")
brr = registers.get("BRR")
scr = registers.get("SCR")
if smr is None or brr is None:
return None
mode = "sync" if smr & 0x80 else "async"
n = smr & 0x03
denominator = _baud_denominator(mode, n, brr)
clock_source = "unknown" if scr is None else ("external" if scr & 0x02 else "internal")
inference: dict[str, object] = {
"channel": channel,
"mode": mode,
"mode_summary": _mode_summary(smr),
"smr": smr,
"smr_hex": _hex8(smr),
"brr": brr,
"brr_hex": _hex8(brr),
"scr": scr,
"scr_hex": _hex8(scr) if scr is not None else None,
"cks_n": n,
"cks_divisor": 1 << (2 * n),
"denominator": denominator,
"clock_source": clock_source,
"formula": FORMULAS[mode],
}
if clock_hz is None:
inference["baud_bps"] = None
inference["confidence"] = "partial"
inference["reason"] = "clock_hz_missing"
inference["comment"] = (
f"{channel} {_mode_summary(smr)} BRR N={brr} CKS n={n}; baud needs --clock-hz"
)
return inference
if clock_source == "external":
inference["baud_bps"] = None
inference["confidence"] = "partial"
inference["reason"] = "external_clock_selected"
inference["comment"] = (
f"{channel} {_mode_summary(smr)} external clock selected; "
f"BRR N={brr} CKS n={n}, internal baud not inferred"
)
return inference
baud = clock_hz / denominator
inference["baud_bps"] = baud
inference["confidence"] = "high" if clock_source == "internal" else "partial"
inference["reason"] = None if clock_source == "internal" else "scr_clock_source_unknown"
noun = "baud" if clock_source == "internal" else "baud generator"
source_note = "; SCR clock source unknown" if clock_source == "unknown" else ""
inference["comment"] = (
f"{channel} {_mode_summary(smr)} {noun} {_format_bps(baud)} "
f"(BRR N={brr}, CKS n={n}, clock={clock_hz} Hz{source_note})"
)
return inference
def _configuration_key(channel: str, inference: Mapping[str, object], clock_hz: int | None) -> tuple[object, ...]:
return (
channel,
inference.get("smr"),
inference.get("brr"),
inference.get("clock_source"),
clock_hz,
)
def _baud_denominator(mode: str, n: int, brr: int) -> int:
base = 8 if mode == "sync" else 64
return base * (1 << (2 * n)) * (brr + 1)
def _mode_summary(smr: int) -> str:
if smr & 0x80:
return "sync"
char_len = "7-bit" if smr & 0x40 else "8-bit"
if smr & 0x20:
parity = "odd parity" if smr & 0x10 else "even parity"
else:
parity = "no parity"
stop = "2 stop" if smr & 0x08 else "1 stop"
return f"async {char_len} {parity} {stop}"
def _format_bps(baud: float) -> str:
rounded = round(baud)
if abs(baud - rounded) < 0.001:
return f"{rounded} bps"
return f"{baud:.3f}".rstrip("0").rstrip(".") + " bps"
def _hex8(value: int) -> str:
return f"H'{value & 0xFF:02X}"

View File

@@ -43,10 +43,12 @@ IO_REGISTERS: dict[int, str] = {
0xFE91: "FRT1_TCSR", 0xFE91: "FRT1_TCSR",
0xFE92: "FRT1_FRC_H", 0xFE92: "FRT1_FRC_H",
0xFE93: "FRT1_FRC_L", 0xFE93: "FRT1_FRC_L",
0xFE94: "FRT1_OCRA_L", 0xFE94: "FRT1_OCRA_H",
0xFE95: "FRT1_OCRB_L", 0xFE95: "FRT1_OCRA_L",
0xFE96: "FRT1_ICR_H", 0xFE96: "FRT1_OCRB_H",
0xFE97: "FRT1_ICR_L", 0xFE97: "FRT1_OCRB_L",
0xFE98: "FRT1_ICR_H",
0xFE99: "FRT1_ICR_L",
0xFEA0: "FRT2_TCR", 0xFEA0: "FRT2_TCR",
0xFEA1: "FRT2_TCSR", 0xFEA1: "FRT2_TCSR",
0xFEA2: "FRT2_FRC_H", 0xFEA2: "FRT2_FRC_H",
@@ -129,6 +131,54 @@ IO_REGISTERS: dict[int, str] = {
} }
IO_PRIORITY_FIELDS: dict[int, tuple[tuple[int, str], ...]] = {
0xFF00: ((4, "irq0"), (0, "irq1")),
0xFF01: ((4, "irq2/irq3"), (0, "irq4/irq5")),
0xFF02: ((4, "FRT1"), (0, "FRT2")),
0xFF03: ((4, "FRT3"), (0, "8-bit timer")),
0xFF04: ((4, "SCI1"), (0, "SCI2")),
0xFF05: ((4, "A/D"),),
}
IO_DTE_BITS: dict[int, dict[int, str]] = {
0xFF08: {
4: "irq0",
0: "irq1",
},
0xFF09: {
5: "irq3",
4: "irq2",
1: "irq5",
0: "irq4",
},
0xFF0A: {
6: "FRT1 OCIB",
5: "FRT1 OCIA",
4: "FRT1 ICI",
2: "FRT2 OCIB",
1: "FRT2 OCIA",
0: "FRT2 ICI",
},
0xFF0B: {
6: "FRT3 OCIB",
5: "FRT3 OCIA",
4: "FRT3 ICI",
1: "8-bit timer CMIB",
0: "8-bit timer CMIA",
},
0xFF0C: {
6: "SCI1 TXI",
5: "SCI1 RXI",
2: "SCI2 TXI",
1: "SCI2 RXI",
},
0xFF0D: {
4: "A/D ADI",
},
}
_FRT_TCR_BITS = { _FRT_TCR_BITS = {
7: "ICIE", 7: "ICIE",
6: "OCIEB", 6: "OCIEB",

214
h8536/timing.py Normal file
View File

@@ -0,0 +1,214 @@
from __future__ import annotations
from .formatting import h16, label_for
from .model import Instruction
def summarize_timing(
instructions: dict[int, Instruction],
labels: dict[int, str] | None = None,
call_graph: dict[str, object] | None = None,
) -> dict[str, list[dict[str, object]]]:
labels = labels or {}
addresses = sorted(instructions)
if not addresses:
return {"blocks": [], "loops": []}
starts = _block_starts(instructions, addresses, labels, call_graph)
blocks = [_summarize_block(start, instructions, labels, starts) for start in sorted(starts)]
loops = _summarize_loops(instructions, labels)
return {
"blocks": [block for block in blocks if block["instruction_count"]],
"loops": loops,
}
def cycle_bounds(cycles: dict[str, object] | None) -> tuple[int, int] | None:
if not cycles:
return None
if "cycles" in cycles:
value = int(cycles["cycles"])
return value, value
if "cycles_min" in cycles and "cycles_max" in cycles:
return int(cycles["cycles_min"]), int(cycles["cycles_max"])
if "not_taken" in cycles and "taken" in cycles:
values = [int(cycles["not_taken"]), int(cycles["taken"])]
return min(values), max(values)
if "trap_not_taken" in cycles and "trap_taken" in cycles:
values = [int(cycles["trap_not_taken"]), int(cycles["trap_taken"])]
return min(values), max(values)
if "false" in cycles and "count_minus_1" in cycles and "taken" in cycles:
values = [int(cycles["false"]), int(cycles["count_minus_1"]), int(cycles["taken"])]
return min(values), max(values)
return None
def _block_starts(
instructions: dict[int, Instruction],
addresses: list[int],
labels: dict[int, str],
call_graph: dict[str, object] | None,
) -> set[int]:
address_set = set(addresses)
starts = {addresses[0]}
starts.update(address for address in labels if address in address_set)
for node in (call_graph or {}).get("nodes", []):
start = int(node["start"])
if start in address_set:
starts.add(start)
for ins in instructions.values():
starts.update(target for target in ins.targets if target in address_set)
if ins.kind in {"branch", "jump", "return", "rte", "sleep", "invalid"}:
next_address = ins.address + max(ins.size, 1)
if next_address in address_set:
starts.add(next_address)
return starts
def _summarize_block(
start: int,
instructions: dict[int, Instruction],
labels: dict[int, str],
starts: set[int],
) -> dict[str, object]:
addresses = []
pc = start
cycles_min = 0
cycles_max = 0
unknown_cycles = 0
terminator: Instruction | None = None
while pc in instructions:
if addresses and pc in starts:
break
ins = instructions[pc]
addresses.append(pc)
bounds = cycle_bounds(ins.cycles)
if bounds is None:
unknown_cycles += 1
else:
cycles_min += bounds[0]
cycles_max += bounds[1]
terminator = ins
next_pc = pc + max(ins.size, 1)
if ins.kind in {"branch", "jump", "return", "rte", "sleep", "invalid"}:
break
if next_pc in starts:
break
pc = next_pc
end = addresses[-1] if addresses else start
return {
"start": start,
"end": end,
"label": labels.get(start, label_for(start)),
"instruction_count": len(addresses),
"cycles_min": cycles_min,
"cycles_max": cycles_max,
"unknown_cycles": unknown_cycles,
"terminator": terminator.text if terminator else "",
"targets": list(terminator.targets if terminator else []),
}
def _summarize_loops(
instructions: dict[int, Instruction],
labels: dict[int, str],
) -> list[dict[str, object]]:
loops: list[dict[str, object]] = []
for ins in sorted(instructions.values(), key=lambda item: item.address):
for target in ins.targets:
if target > ins.address or target not in instructions:
continue
body = [
address
for address in sorted(instructions)
if target <= address <= ins.address
]
if not body:
continue
cycles_min = 0
cycles_max = 0
unknown_cycles = 0
has_call = False
for address in body:
body_ins = instructions[address]
has_call = has_call or body_ins.kind == "call"
bounds = cycle_bounds(body_ins.cycles)
if bounds is None:
unknown_cycles += 1
else:
cycles_min += bounds[0]
cycles_max += bounds[1]
loops.append(
{
"start": target,
"end": ins.address,
"label": labels.get(target, label_for(target)),
"back_edge": ins.address,
"back_edge_text": ins.text,
"instruction_count": len(body),
"cycles_min": cycles_min,
"cycles_max": cycles_max,
"unknown_cycles": unknown_cycles,
"has_call": has_call,
"kind": _loop_kind(ins, has_call),
},
)
return loops
def _loop_kind(instruction: Instruction, has_call: bool) -> str:
if instruction.mnemonic.startswith("SCB/"):
return "counter_delay_loop" if not has_call else "counter_loop"
if instruction.mnemonic in {"BRA", "BRN"}:
return "unconditional_loop" if not has_call else "loop_with_call"
return "delay_loop_candidate" if not has_call else "loop_with_call"
def format_timing_summary(summary: dict[str, list[dict[str, object]]], *, max_items: int = 40) -> list[str]:
lines: list[str] = []
blocks = summary.get("blocks", [])
loops = summary.get("loops", [])
if not blocks and not loops:
return lines
lines.append("; Timing Summary")
if blocks:
lines.append("; Straight-line blocks")
for block in blocks[:max_items]:
lines.append(
"; block "
f"{h16(int(block['start']))}-{h16(int(block['end']))} "
f"{str(block['label']):<20} "
f"ins={block['instruction_count']:<3} "
f"cycles={_cycle_range(block)} "
f"unknown={block['unknown_cycles']}",
)
if len(blocks) > max_items:
lines.append(f"; ... {len(blocks) - max_items} more blocks")
if loops:
lines.append("; Backward-branch loop candidates")
for loop in loops[:max_items]:
lines.append(
"; loop "
f"{h16(int(loop['start']))}-{h16(int(loop['end']))} "
f"{str(loop['label']):<20} "
f"{loop['kind']:<22} "
f"cycles/iteration={_cycle_range(loop)} "
f"back_edge={h16(int(loop['back_edge']))}",
)
if len(loops) > max_items:
lines.append(f"; ... {len(loops) - max_items} more loops")
lines.append("")
return lines
def _cycle_range(item: dict[str, object]) -> str:
minimum = int(item["cycles_min"])
maximum = int(item["cycles_max"])
if minimum == maximum:
return str(minimum)
return f"{minimum}-{maximum}"

View File

@@ -2,6 +2,7 @@ from __future__ import annotations
from typing import TypedDict from typing import TypedDict
from .dtc import DtcRegisterInfo, decode_dtc_register_info
from .rom import Rom from .rom import Rom
from .tables import VECTOR_NAMES_MIN from .tables import VECTOR_NAMES_MIN
@@ -11,6 +12,7 @@ class DtcVectorEntry(TypedDict):
source: str source: str
register_info_address: int register_info_address: int
target: int target: int
register_info: DtcRegisterInfo
DTC_VECTOR_NAMES_MIN: dict[int, str] = { DTC_VECTOR_NAMES_MIN: dict[int, str] = {
@@ -71,12 +73,13 @@ def read_vectors_max(rom: Rom) -> dict[int, tuple[str, int]]:
return vectors return vectors
def _dtc_entry(vector_address: int, source: str, target: int) -> DtcVectorEntry: def _dtc_entry(rom: Rom, vector_address: int, source: str, target: int) -> DtcVectorEntry:
return { return {
"vector_address": vector_address, "vector_address": vector_address,
"source": source, "source": source,
"register_info_address": target, "register_info_address": target,
"target": target, "target": target,
"register_info": decode_dtc_register_info(rom, target),
} }
@@ -88,7 +91,7 @@ def read_dtc_vectors_min(rom: Rom) -> dict[int, DtcVectorEntry]:
target = rom.u16(addr) target = rom.u16(addr)
if target in (0x0000, 0xFFFF): if target in (0x0000, 0xFFFF):
continue continue
vectors[addr] = _dtc_entry(addr, source, target) vectors[addr] = _dtc_entry(rom, addr, source, target)
return vectors return vectors
@@ -100,5 +103,5 @@ def read_dtc_vectors_max(rom: Rom) -> dict[int, DtcVectorEntry]:
target = rom.u16(addr + 2) target = rom.u16(addr + 2)
if target in (0x0000, 0xFFFF): if target in (0x0000, 0xFFFF):
continue continue
vectors[addr] = _dtc_entry(addr, source, target) vectors[addr] = _dtc_entry(rom, addr, source, target)
return vectors return vectors

5
h8536_pseudocode.py Normal file
View File

@@ -0,0 +1,5 @@
from h8536.pseudocode import main
if __name__ == "__main__":
raise SystemExit(main())

103
tests/test_dtc.py Normal file
View File

@@ -0,0 +1,103 @@
import json
import tempfile
import unittest
from pathlib import Path
from h8536.render import format_listing, write_json
from h8536.rom import Rom
from h8536.vectors import read_dtc_vectors_min
def _manual_sci1_rxi_rom() -> Rom:
data = bytearray([0xFF] * 0xFB88)
data[0x00A2:0x00A4] = bytes([0xFB, 0x80])
data[0xFB80:0xFB88] = bytes(
[
0x20,
0x00, # DTMR: byte transfer, destination increments
0xFE,
0xDD, # DTSR: SCI1_RDR
0xFC,
0x00, # DTDR: receive buffer
0x00,
0x80, # DTCR: 128 transfers
],
)
return Rom(bytes(data))
class DtcDecodeTest(unittest.TestCase):
def test_decodes_manual_sci1_receive_register_information(self):
vectors = read_dtc_vectors_min(_manual_sci1_rxi_rom())
entry = vectors[0x00A2]
info = entry["register_info"]
self.assertTrue(info["valid"])
self.assertEqual(entry["source"], "sci1_rxi")
self.assertEqual(info["dtmr"], 0x2000)
self.assertEqual(info["mode"]["size"], "byte")
self.assertFalse(info["mode"]["source_increment"])
self.assertTrue(info["mode"]["destination_increment"])
self.assertEqual(info["source"]["address"], 0xFEDD)
self.assertEqual(info["source"]["name"], "SCI1_RDR")
self.assertEqual(info["destination"]["address"], 0xFC00)
self.assertEqual(info["destination"]["region"], "on_chip_ram")
self.assertEqual(info["count"]["transfers"], 128)
self.assertEqual(info["count"]["bytes"], 128)
def test_invalid_register_information_pointer_is_reported_conservatively(self):
data = bytearray([0xFF] * 0x0200)
data[0x00A2:0x00A4] = bytes([0xFB, 0x80])
vectors = read_dtc_vectors_min(Rom(bytes(data)))
info = vectors[0x00A2]["register_info"]
self.assertFalse(info["valid"])
self.assertIn("outside ROM image", info["error"])
def test_zero_count_represents_65536_transfers(self):
data = bytearray([0xFF] * 0x0200)
data[0x00C0:0x00C2] = bytes([0x01, 0x00])
data[0x0100:0x0108] = bytes([0xC0, 0x00, 0x01, 0x20, 0xFE, 0x80, 0x00, 0x00])
info = read_dtc_vectors_min(Rom(bytes(data)))[0x00C0]["register_info"]
self.assertEqual(info["mode"]["size"], "word")
self.assertEqual(info["mode"]["source_increment_step"], 2)
self.assertEqual(info["count"]["transfers"], 65536)
self.assertEqual(info["count"]["bytes"], 131072)
self.assertTrue(info["count"]["zero_means_65536"])
def test_listing_and_json_include_decoded_register_information(self):
rom = _manual_sci1_rxi_rom()
dtc_vectors = read_dtc_vectors_min(rom)
listing = format_listing(
Path("rom.bin"),
rom,
{},
{},
{},
"min",
traced=True,
dtc_vectors=dtc_vectors,
)
self.assertIn("; DTC Register Information", listing)
self.assertIn("sci1_rxi", listing)
self.assertIn("byte x128", listing)
self.assertIn("SCI1_RDR (H'FEDD) -> H'FC00", listing)
with tempfile.TemporaryDirectory() as tmpdir:
path = Path(tmpdir) / "out.json"
write_json(path, {}, {}, {}, dtc_vectors=dtc_vectors)
payload = json.loads(path.read_text(encoding="utf-8"))
json_info = payload["dtc_vectors"][0]["register_info"]
self.assertEqual(json_info["mode"]["size"], "byte")
self.assertEqual(json_info["source"]["name"], "SCI1_RDR")
self.assertEqual(json_info["count"]["transfers"], 128)
if __name__ == "__main__":
unittest.main()

View File

@@ -0,0 +1,50 @@
import unittest
from h8536.decoder import H8536Decoder
from h8536.rom import Rom
def decode(data: list[int]):
return H8536Decoder(Rom(bytes(data), base=0)).decode(0)
def mov_b_immediate_to_abs16(address: int, value: int):
return decode([0x15, (address >> 8) & 0xFF, address & 0xFF, 0x06, value])
def bit_op_abs16(address: int, op: int):
return decode([0x15, (address >> 8) & 0xFF, address & 0xFF, op])
class InterruptAnnotationTest(unittest.TestCase):
def test_ipra_write_decodes_irq_priority_levels(self):
instruction = mov_b_immediate_to_abs16(0xFF00, 0x75)
self.assertEqual(instruction.text, "MOV:G.B #H'75, @IPRA")
self.assertEqual(instruction.comment, "IPRA = H'75 (irq0 priority=7; irq1 priority=5)")
def test_iprf_write_decodes_ad_priority_and_reserved_bits(self):
instruction = mov_b_immediate_to_abs16(0xFF05, 0xF8)
self.assertEqual(instruction.text, "MOV:G.B #H'F8, @IPRF")
self.assertEqual(instruction.comment, "IPRF = H'F8 (A/D priority=7; reserved bits 7, 3 should be 0)")
def test_dtee_write_decodes_dtc_routing_by_interrupt_source(self):
instruction = mov_b_immediate_to_abs16(0xFF0C, 0x24)
self.assertEqual(instruction.text, "MOV:G.B #H'24, @DTEE")
self.assertEqual(
instruction.comment,
"DTEE = H'24 (SCI1 TXI CPU interrupt; SCI1 RXI DTC enabled; "
"SCI2 TXI DTC enabled; SCI2 RXI CPU interrupt)",
)
def test_dtea_bit_set_names_dtc_enable_source(self):
instruction = bit_op_abs16(0xFF08, 0xC4)
self.assertEqual(instruction.text, "BSET.B #4, @DTEA")
self.assertEqual(instruction.comment, "set irq0 DTC enable (bit 4) of DTEA")
if __name__ == "__main__":
unittest.main()

View File

@@ -0,0 +1,90 @@
import json
import tempfile
import unittest
from pathlib import Path
from h8536.model import Instruction
from h8536.peripheral_access import analyze_peripheral_access, peripheral_comment_for_instruction
from h8536.render import write_json
from h8536.tables import IO_REGISTERS
class PeripheralAccessTest(unittest.TestCase):
def test_frt1_manual_register_map_has_full_16_bit_pairs(self):
self.assertEqual(IO_REGISTERS[0xFE94], "FRT1_OCRA_H")
self.assertEqual(IO_REGISTERS[0xFE95], "FRT1_OCRA_L")
self.assertEqual(IO_REGISTERS[0xFE98], "FRT1_ICR_H")
def test_frt_word_access_is_marked_as_temp_safe(self):
instructions = {
0x1000: Instruction(
0x1000,
bytes.fromhex("1DFEA2051234"),
"MOV:G.W",
"#H'1234, @FRT2_FRC_H",
references=[0xFEA2],
),
}
analysis = analyze_peripheral_access(instructions)
self.assertIn("TEMP byte-order hazard avoided", peripheral_comment_for_instruction(analysis, 0x1000))
self.assertEqual(analysis["warnings"], [])
def test_low_byte_read_before_high_byte_warns(self):
instructions = {
0x1000: Instruction(
0x1000,
bytes.fromhex("15FEE105"),
"MOV:G.B",
"@ADDRA_L, R0",
references=[0xFEE1],
),
}
analysis = analyze_peripheral_access(instructions)
self.assertEqual(len(analysis["warnings"]), 1)
self.assertIn("low byte read before matching high byte", analysis["warnings"][0]["message"])
self.assertIn("check TEMP ordering", peripheral_comment_for_instruction(analysis, 0x1000))
def test_ad_upper_byte_read_alone_is_allowed_for_8_bit_accuracy(self):
instructions = {
0x1000: Instruction(
0x1000,
bytes.fromhex("15FEE005"),
"MOV:G.B",
"@ADDRA_H, R0",
references=[0xFEE0],
),
}
analysis = analyze_peripheral_access(instructions)
self.assertEqual(analysis["warnings"], [])
self.assertIn("valid 8-bit result", peripheral_comment_for_instruction(analysis, 0x1000))
def test_json_includes_top_level_warnings_and_instruction_metadata(self):
instructions = {
0x1000: Instruction(
0x1000,
b"",
"MOV:G.B",
"@ADDRA_L, R0",
references=[0xFEE1],
),
}
analysis = analyze_peripheral_access(instructions)
with tempfile.TemporaryDirectory() as tmp:
path = Path(tmp) / "out.json"
write_json(path, instructions, {}, {}, peripheral_access=analysis)
payload = json.loads(path.read_text(encoding="utf-8"))
self.assertEqual(payload["peripheral_access"]["warnings"][0]["register"], "ADDRA")
instruction = payload["instructions"][0]
self.assertEqual(instruction["peripheral_access"][0]["register"], "ADDRA")
if __name__ == "__main__":
unittest.main()

126
tests/test_pseudocode.py Normal file
View File

@@ -0,0 +1,126 @@
import unittest
from h8536.pseudocode import PseudocodeOptions, generate_pseudocode, split_operands
class PseudocodeTest(unittest.TestCase):
def test_split_operands_keeps_displacement_expression_together(self):
self.assertEqual(split_operands("@(H'04,R6), R0"), ["@(H'04,R6)", "R0"])
self.assertEqual(split_operands("{R0,R1}, @-SP"), ["{R0,R1}", "@-SP"])
def test_generates_c_like_function_from_decompiler_json(self):
payload = {
"vectors": [{"address": 0, "name": "reset", "target": 0x0100, "target_label": "vec_reset_0100"}],
"call_graph": {
"nodes": [
{
"start": 0x0100,
"end": 0x0110,
"label": "vec_reset_0100",
"sources": ["reset"],
"instruction_count": 5,
"calls": [0x0200],
},
{
"start": 0x0200,
"end": 0x0200,
"label": "loc_0200",
"sources": [],
"instruction_count": 1,
"calls": [],
},
],
"edges": [],
},
"instructions": [
{
"address": 0x0100,
"text": "MOV:G.B #H'FF, @P1DDR",
"mnemonic": "MOV:G.B",
"operands": "#H'FF, @P1DDR",
"kind": "normal",
"targets": [],
"references": [{"address": 0xFE80, "name": "P1DDR", "region": "register_field"}],
"comment": "P1DDR = H'FF",
"peripheral_access": [
{
"register": "FRT2_FRC",
"direction": "write",
"size": "W",
"byte": "high",
},
],
},
{
"address": 0x0105,
"text": "MOV:G.B #H'80, @RAMCR",
"mnemonic": "MOV:G.B",
"operands": "#H'80, @RAMCR",
"kind": "normal",
"targets": [],
"references": [{"address": 0xFF11, "name": "RAMCR", "region": "register_field"}],
"comment": "RAMCR = H'80",
"sci": {
"inferences": [
{"comment": "SCI1 async baud 31250 bps"},
],
},
},
{
"address": 0x010A,
"text": "BNE loc_0110",
"mnemonic": "BNE",
"operands": "loc_0110",
"kind": "branch",
"targets": [0x0110],
"references": [],
"comment": "",
},
{
"address": 0x010C,
"text": "BSR loc_0200",
"mnemonic": "BSR",
"operands": "loc_0200",
"kind": "call",
"targets": [0x0200],
"references": [],
"comment": "",
},
{
"address": 0x0110,
"text": "RTS",
"mnemonic": "RTS",
"operands": "",
"kind": "return",
"targets": [],
"references": [],
"comment": "",
},
{
"address": 0x0200,
"text": "RTS",
"mnemonic": "RTS",
"operands": "",
"kind": "return",
"targets": [],
"references": [],
"comment": "",
},
],
}
text = generate_pseudocode(payload, options=PseudocodeOptions())
self.assertIn("void vec_reset_0100(void)", text)
self.assertIn("P1DDR = (uint8_t)(0xFF);", text)
self.assertIn("RAMCR = (uint8_t)(0x80);", text)
self.assertIn("SCI1 async baud 31250 bps", text)
self.assertIn("FRT2_FRC W write high TEMP access", text)
self.assertIn("if (!Z) goto loc_0110;", text)
self.assertIn("loc_0200();", text)
self.assertIn("loc_0110:", text)
self.assertIn("return;", text)
if __name__ == "__main__":
unittest.main()

122
tests/test_sci_inference.py Normal file
View File

@@ -0,0 +1,122 @@
import json
import tempfile
import unittest
from pathlib import Path
from h8536.model import Instruction
from h8536.render import format_listing, write_json
from h8536.rom import Rom
from h8536.sci import analyze_sci, sci_comment_for_instruction
def sci1_setup(scr: int = 0x3C) -> dict[int, Instruction]:
return {
0x1000: Instruction(
0x1000,
bytes.fromhex("15FED80624"),
"MOV:G.B",
"#H'24, @SCI1_SMR",
references=[0xFED8],
comment="SCI1_SMR = H'24",
),
0x1005: Instruction(
0x1005,
bytes([0x15, 0xFE, 0xDA, 0x06, scr]),
"MOV:G.B",
f"#H'{scr:02X}, @SCI1_SCR",
references=[0xFEDA],
comment=f"SCI1_SCR = H'{scr:02X}",
),
0x100A: Instruction(
0x100A,
bytes.fromhex("15FED90607"),
"MOV:G.B",
"#H'07, @SCI1_BRR",
references=[0xFED9],
comment="SCI1_BRR = H'07",
),
}
class SciInferenceTest(unittest.TestCase):
def test_async_internal_baud_uses_manual_brr_formula(self):
analysis = analyze_sci(sci1_setup(), clock_hz=16_000_000)
config = analysis["channels"]["SCI1"]["configurations"][0]
self.assertEqual(config["mode"], "async")
self.assertEqual(config["cks_n"], 0)
self.assertEqual(config["brr"], 7)
self.assertEqual(config["baud_bps"], 31_250)
self.assertEqual(config["formula"], "B = clock_hz / (64 * 2^(2n) * (N + 1))")
self.assertIn("SCI1 async 8-bit even parity 1 stop baud 31250 bps", config["comment"])
def test_missing_clock_keeps_baud_partial(self):
analysis = analyze_sci(sci1_setup(), clock_hz=None)
comment = sci_comment_for_instruction(analysis, 0x100A)
config = analysis["channels"]["SCI1"]["configurations"][0]
self.assertIsNone(config["baud_bps"])
self.assertEqual(config["reason"], "clock_hz_missing")
self.assertIn("baud needs --clock-hz", comment)
self.assertNotIn("31250 bps", comment)
def test_external_clock_selection_suppresses_internal_baud(self):
analysis = analyze_sci(sci1_setup(scr=0x3E), clock_hz=16_000_000)
config = analysis["channels"]["SCI1"]["configurations"][0]
self.assertIsNone(config["baud_bps"])
self.assertEqual(config["clock_source"], "external")
self.assertEqual(config["reason"], "external_clock_selected")
self.assertIn("external clock selected", config["comment"])
def test_scr_bit_writes_are_tracked_without_repeating_same_baud(self):
instructions = sci1_setup()
instructions[0x1010] = Instruction(
0x1010,
bytes.fromhex("15FEDAC7"),
"BSET.B",
"#7, @SCI1_SCR",
references=[0xFEDA],
comment="set TIE (bit 7) of SCI1_SCR",
)
analysis = analyze_sci(instructions, clock_hz=16_000_000)
writes = analysis["channels"]["SCI1"]["writes"]
self.assertEqual([write["register"] for write in writes], ["SMR", "SCR", "BRR", "SCR"])
self.assertEqual(writes[-1]["value"], 0xBC)
self.assertNotIn(0x1010, analysis["annotations"])
def test_listing_preserves_existing_comment_and_appends_sci_comment(self):
instructions = sci1_setup()
analysis = analyze_sci(instructions, clock_hz=16_000_000)
listing = format_listing(
Path("rom.bin"),
Rom(b"\xFF" * 0x20),
instructions,
{},
{},
"min",
traced=False,
sci_analysis=analysis,
)
self.assertIn("SCI1_BRR = H'07; SCI1 async 8-bit even parity 1 stop baud 31250 bps", listing)
def test_json_includes_top_level_and_instruction_sci_metadata(self):
instructions = sci1_setup()
analysis = analyze_sci(instructions, clock_hz=16_000_000)
with tempfile.TemporaryDirectory() as tmp:
path = Path(tmp) / "out.json"
write_json(path, instructions, {}, {}, sci_analysis=analysis)
payload = json.loads(path.read_text(encoding="utf-8"))
self.assertEqual(payload["sci"]["channels"]["SCI1"]["configurations"][0]["baud_bps"], 31_250)
brr_instruction = next(item for item in payload["instructions"] if item["address"] == 0x100A)
self.assertEqual(brr_instruction["sci"]["inferences"][0]["brr"], 7)
if __name__ == "__main__":
unittest.main()

View File

@@ -0,0 +1,47 @@
import unittest
from h8536.cycles import annotate_cycles
from h8536.model import Instruction
from h8536.timing import cycle_bounds, summarize_timing
class TimingSummaryTest(unittest.TestCase):
def test_cycle_bounds_use_branch_min_and_max(self):
self.assertEqual(cycle_bounds({"not_taken": 3, "taken": 8}), (3, 8))
self.assertEqual(cycle_bounds({"false": 3, "count_minus_1": 4, "taken": 8}), (3, 8))
def test_summarizes_backward_scb_loop_candidate(self):
instructions = {
0x0100: Instruction(0x0100, b"\x58\x02\x00", "MOV:I.W", "#H'0200, R0"),
0x0103: Instruction(0x0103, b"\x01\xB8\xFD", "SCB/F", "R0, loc_0103", kind="branch", targets=[0x0103]),
0x0106: Instruction(0x0106, b"\x19", "RTS", kind="return", fallthrough=False),
}
annotate_cycles(instructions, "min")
summary = summarize_timing(instructions, {0x0103: "loc_0103"})
self.assertEqual(summary["loops"][0]["start"], 0x0103)
self.assertEqual(summary["loops"][0]["kind"], "counter_delay_loop")
self.assertEqual(summary["loops"][0]["cycles_min"], 3)
self.assertEqual(summary["loops"][0]["cycles_max"], 9)
def test_summarizes_straight_line_block_to_branch(self):
instructions = {
0x0200: Instruction(0x0200, b"\x58\x00\x01", "MOV:I.W", "#H'0001, R0"),
0x0203: Instruction(0x0203, b"\x26\x02", "BNE", "loc_0207", kind="branch", targets=[0x0207]),
0x0205: Instruction(0x0205, b"\x19", "RTS", kind="return", fallthrough=False),
0x0207: Instruction(0x0207, b"\x19", "RTS", kind="return", fallthrough=False),
}
annotate_cycles(instructions, "min")
summary = summarize_timing(instructions, {0x0207: "loc_0207"})
first = summary["blocks"][0]
self.assertEqual(first["start"], 0x0200)
self.assertEqual(first["end"], 0x0203)
self.assertEqual(first["cycles_min"], 6)
self.assertEqual(first["cycles_max"], 11)
if __name__ == "__main__":
unittest.main()