A Ghidra processor module for the OKI 66201 / 66207 / 66301 (nX-8/200) 16-bit MCU family used in pre-1996 Honda OBD1 ECUs.
Disassembles raw ECU ROM images, tracks the word/byte (DD) accumulator mode, and carries full instruction semantics, so you get cross-references, a function graph and readable decompiled C with named peripheral registers:
P2 = 0xff;
TCON0 = TCON0 & 0xfb | 4;
ADSCAN = 0;
bVar5 = *pbVar18 & (&DAT_6cd2)[idx]; /* ROM map lookup */The SLEIGH spec is auto-generated from the authoritative src/66207.op opcode
table (the same table that drives the dasm662 disassembler in
VIRUXE/asm662), so the two decoders stay in
sync — 2606 constructors, full instruction coverage.
# copy the processor module into your Ghidra install
cp -r Ghidra/Processors/OKI66207 "$GHIDRA_INSTALL_DIR/Ghidra/Processors/"
# (re)compile the SLEIGH spec
"$GHIDRA_INSTALL_DIR/support/sleigh" \
"$GHIDRA_INSTALL_DIR/Ghidra/Processors/OKI66207/data/languages/oki66207.slaspec"
"$GHIDRA_INSTALL_DIR/support/sleigh" \
"$GHIDRA_INSTALL_DIR/Ghidra/Processors/OKI66207/data/languages/oki66207_p28.slaspec"Restart Ghidra and import the raw ECU .bin. For the stock P28 ROM, choose
OKI:66207:LE:16:p28; use OKI:66207:LE:16:default when the ROM's VCAL
ABI is unknown.
Seed disassembly from the reset/interrupt/VCAL vector table at 0x0000 (28
little-endian pointers) with the bundled script — that gets the whole ROM walking:
"$GHIDRA_INSTALL_DIR/support/analyzeHeadless" /tmp/proj ecu \
-import P28.bin -processor "OKI:66207:LE:16:p28" \
-scriptPath tools/ghidra_scripts \
-preScript SeedVectors.java \
-postScript ExportC.java out/ # optional: dump all functions to CExportC.java writes out/all.c (every function decompiled) and out/index.txt.
Both specs are generated from the tables in src/; CI fails if the committed
output is stale.
LANG_DIR=Ghidra/Processors/OKI66207/data/languages
python3 tools/gen_sleigh.py src/66207.op "$LANG_DIR/oki66207.slaspec"
python3 tools/gen_pspec.py src/66207_regs.h "$LANG_DIR/oki66207.pspec"- Two address spaces — the OKI has separate program and data memory, both
based at 0. The module models this:
RAM(the default space) holds the loaded ROM image — code, vectors and ROM tables — whileDATAholds the SFRs, RAM and the stacks. Collapsing them into one space is wrong and actively harmful: SFRTM3at data0x3cwould otherwise land on top of the NMI handler at code0x3c.LC/LCB/CMPC/CMPCBread program memory; every other access isDATA. - Named peripherals — the pspec carries the 91 special-function-register
symbols (
P0…P5,TCON0-3,TM0-3,ADSCAN,IRQ,IE,WDT,PWCON0/1…) parsed straight out of asm662's66207_regs.h, plus the reset/interrupt/VCAL vector table, so decompiled output readsTCON0 = TCON0 & 0xfb | 4;rather thanbRAM0040 = .... - DD context — the word(1)/byte(0) accumulator mode is a tracked SLEIGH
context variable, because the same opcode changes length by mode (
0x86is a 3-byteADDin word mode, a 2-byteADDBin byte mode).L/word ops set DD=1,LB/byte ops set DD=0. Ghidra cannot propagate a callee's DD change back to its caller, so thep28variant models the stock P28 ABI guarantee thatVCAL 1returns in word mode. The generic variant makes no VCAL assumption. - Postfix opcodes — the disambiguating operation byte can follow the operand
(e.g.
ADC A,N16[X1]=B0,NL,NH,92); modeled with;-separated multi-token patterns. - Mnemonics — SLEIGH only allows bound operands after the mnemonic, so the
fixed register/addressing signature is folded into one token (
L_A_DP,MOV_LRB_imm) and only the variable operands (immediates, addresses, offsets, branch targets) are shown separately. - Semantics / decompilation — data transfer, arithmetic (ADD/ADC/SUB/SBC),
logic, compare, increment/decrement, shifts/rotates (incl.
SRA), bit ops (SB/RB/MB), stack (PUSHS/POPS/PUSHU/POPU) and all control flow carry real p-code, so the Ghidra decompiler produces readable C, not just a disassembly. Effective addresses model every mode — current-page ((LRB[12:5]<<8)|N8), zero-page,[DP],N16[Xn],±N8[USP]. Flag behaviour is taken from the MSM66201 instruction manual (Chapter 3):ZF,CFand theJCcondition codes are exact — importantlyCFis borrow on subtract/compare (JC LT=C=1,JC GE=C=0), andJRNZdecrements the low byteDPL. - Multiply/divide/BCD —
MUL/MULB((er1,A)=A×er0),DIV/DIVB((er0,A)÷er2, remainder iner1),EXTND(sign-extendAL→A) andDAA/DAS(BCD adjust, using a computed half-carryHC) all emit p-code, so the decompiler renders them as real*///%arithmetic. - ROM table reference —
LC/LCB/CMPC/CMPCBmodel every addressing form (directN16,[reg],[[DP]],[off N8],N16[X1],N16[off N8]…), so Honda's fuel/ignition map lookups decompile as ordinary C array indexing, e.g.(&DAT_6cd2)[idx]. - Register-indirect bit ops —
SBR/RBR/TBR/MBRtake their bit index fromA[2:0](manual Table 3-32) and are modeled as such. - Indirect
J/CAL— the.optable tags indirectCAL [X]as a plain operation, so these are decoded specially: one bracket is stripped and the target is the value of the inner operand. That makes indirect calls/jumps produce real references instead of a bogus edge to address 0. - Flag accuracy — flags follow the manual's per-instruction tables exactly:
shifts/rotates set only CF;
INC/DECset ZF+HC (never CF);CLR/CLRBsetZF=1only for the accumulator; logic ops set only ZF;MOV/L/STand the stack ops set none. The system stack pushes store-then-decrement and pop increments-then-reads (Table 3-7), soSSPpoints at the next free slot. - Known limits —
SMOVI(opcode0x04, absent from the manual's instruction classification) decodes but has no p-code; it appears in neither the OEM P28 nor the D16Z6 ROM.rN/erNare modeled as fixed registers (the LRB-banked register file is not tracked), so byte ops on the accumulator can surfaceextraout_AHartifacts; callee-driven DD changes outside a selected ROM-specific variant cannot be recovered statically. Good enough for reading and reasoning about the code, not a cycle-exact emulator.
Cross-checked against dasm662 on the OEM P28 ROM: identical decode
address-for-address (DD mode + every branch target); whole-ROM auto-analysis
yields ~10,858 instructions / ~426 functions / ~699 call + ~2755 jump refs, and
the decompiler recovers the boot handler's bit-tests, JRNZ loops and call-stack
pushes, and ROM map lookups as C array indexing. See
docs/ghidra-validation-p28.md.
- VIRUXE/asm662 —
asm662/dasm662assembler & disassembler and the66207.opopcode table this module is built from - VIRUXE/oki66k-transpiler — Oki 66k assembly ↔ C transpiler / lifter