SE3K Survivors Experience 3000

PART II -- INTEL 8035 FIRMWARE DISASSEMBLY

Sega SP-400 Plotter / Printer — part of the SE3K documentation library, also readable inside the emulator.

PART II -- INTEL 8035 FIRMWARE DISASSEMBLY
------------------------------------------------------------------------------
File      : sp400_8035.bin
Chip      : Intel 8035  (MCS-48 family, no internal ROM)
Device    : SEGA SP-400  --  serial front-end controller
ROM size  : 4096 bytes (4 KB, upper half is mirror of lower)
Assembler : MCS-48 / Intel 8035 mnemonics
------------------------------------------------------------------------------
; ════════════════════════════════════════════════════════════════════════════
; FILE    : sp400_8035.bin
; CPU     : Intel 8035  (MCS-48 family — external ROM variant of the 8048)
; ROM     : 4 096 bytes; second half (0x800–0xFFF) is an exact mirror
;           of the first half — only the first 2 KB is listed below.
;
; ── 8035 Architecture Quick Reference ───────────────────────────────────────
; • 8-bit accumulator (A)
; • 8 working registers R0–R7 in two banks (RB0 / RB1)
; • 64 bytes internal RAM; 256 bytes data-memory via MOVX
; • 12-bit program counter (4 KB address space)
; • 1 × 8-bit programmable timer/event counter
; • Two 8-bit I/O ports (P1, P2) + an 8-bit bidirectional BUS port
; • Two testable input pins T0, T1
; • Interrupt vector: external INT → $003, timer → $007
;
; ── Special Address Map ──────────────────────────────────────────────────────
; $000  Reset / power-on entry point
; $003  External interrupt vector  (unused in this ROM)
; $007  Timer interrupt vector     (unused in this ROM)
; $100  Main program (reached via JMP at $000)
;
; ── P2 Pin Assignment (inferred) ─────────────────────────────────────────────
; P2.5 (bit mask 0x20) – CLOCK output (driven LOW / HIGH by firmware)
; P2.6 (bit mask 0x40) – DATA_IN from host (polled for serial receive)
;
; ── Firmware Summary ─────────────────────────────────────────────────────────
; This firmware implements a bit-banged serial receiver.
; It reads a stream of bytes from P2.6 (DATA_IN), building each byte
; MSB-first by sampling the line 8 times (with inter-bit delays) and
; rotating each bit into R5 via the carry flag.  After all 37 (0x25) bytes
; have been collected they are stored inverted (XOR 0xFF) in internal RAM
; starting at 0x10.  The CLOCK line (P2.5) is toggled to produce ACK pulses.
; Three small busy-wait subroutines ($19E, $1A6, $1AD) provide timing.
; ════════════════════════════════════════════════════════════════════════════
; ════════════════════════════════════════════════════════════════
; RESET VECTOR  ($000)
; The 8035 starts execution here on power-on / reset.
; The entire first page ($000–$0FF) is otherwise blank, so the
; firmware immediately jumps to the actual code at $100.
; ════════════════════════════════════════════════════════════════
$000:  24 00     JMP  $100              ; reset vector → jump to main code
; ────────────────────────────────────────────────────────────
; $002: [0xFF × 254]  (blank / erased ROM)
; ────────────────────────────────────────────────────────────
; ════════════════════════════════════════════════════════════════
; INITIALISATION  ($100–$105)
;
; Set up the three key variables:
;   R2 = 0x00  – byte counter (counts received bytes, 0..37)
;   R0 = 0x10  – RAM write pointer (starts at internal RAM 0x10)
;   R1 = 0x10  – secondary pointer / bit-shift counter
; ════════════════════════════════════════════════════════════════
$100:  BA 00     MOV R2,#00H            ; init: byte counter = 0
$102:  B8 10     MOV R0,#10H            ; init: RAM write ptr = 0x10
$104:  B9 10     MOV R1,#10H            ; init: R1 (shift counter) = 0x10
; ════════════════════════════════════════════════════════════════
; SERIAL SYNC LOOP  ($106–$129)
;
; This block implements the synchronisation phase of a bit-banged
; serial receive protocol on port P2.
;
; P2 pin map (inferred from masks):
;   P2.5 (mask 0x20) – CLOCK line (output, driven by this CPU)
;   P2.6 (mask 0x40) – DATA_IN / READY line (input from host)
;   P2.3 (mask 0x08) – used to detect a new frame start in R1
;
; Protocol flow:
;   1. Pull CLOCK low (ANL P2, #DFH  → clear bit 5).
;   2. Poll DATA_IN (P2.6); if high → abort to error handler $12B.
;   3. If this is the very first byte (R2==0), wait until
;      DATA_IN is low (start-bit / SYNC detection).
;   4. Load R1 register value and check its bit 3.
;      Bit3=1 → R1 is already set; Bit3=0 → re-initialise R1=0x10.
;   5. Read DATA_IN again (abort if high) then output R1 to P1
;      (sends the current bit-shift value to the display / latch).
;   6. Drive CLOCK low then high (ANL→ORL on P2.6) → one clock pulse.
;   7. Decrement R2 via DJNZ R2 and repeat from step 4 until
;      R2 reaches 0, then fall through to read the next byte group.
; ════════════════════════════════════════════════════════════════
$106:  9A DF     ANL P2,#DFH            ; pull P2.5 low  → CLOCK line LOW
$108:  0A        IN A,P2                ; sample port P2
$109:  92 2B     JB6 $12B               ; if P2.6 (DATA_IN) is HIGH → abort/error
$10B:  FA        MOV A,R2               ; check byte counter
$10C:  C6 06     JNZ $106               ; if not first byte, loop back and wait
$10E:  0A        IN A,P2                ; sample P2 again
$10F:  F2 08     JB5 $108               ; wait while P2.5 is HIGH (sync: wait for start bit)
$111:  0A        IN A,P2                ; sample P2
$112:  92 2B     JB6 $12B               ; DATA_IN high again → abort
$114:  F9        MOV A,R1               ; check current R1 shift value
$115:  D2 19     JB3 $119               ; if R1.bit3 set, pointer still valid; skip re-init
$117:  24 1B     JMP  $11B              ; jump over re-init
$119:  B9 10     MOV R1,#10H            ; reset R1 to 0x10
$11B:  0A        IN A,P2                ; sample P2
$11C:  92 2B     JB6 $12B               ; DATA_IN high → abort
$11E:  F1        MOV A,@R1              ; load byte at address R1 (look-up / shift reg read)
$11F:  39        OUTL P1,A              ; send current shift value out on P1 (data latch / display)
$120:  0A        IN A,P2                ; sample P2
$121:  92 2B     JB6 $12B               ; DATA_IN high → abort
$123:  9A BF     ANL P2,#BFH            ; clear P2.6 (clock low before pulse)
$125:  8A 40     ORL P2,#40H            ; set  P2.6 high → rising edge = one clock pulse to latch
$127:  CA 19     DJNZ R2, $119          ; repeat R2 times (shift-out loop)
$129:  24 08     JMP  $108              ; restart sync from CLOCK-low step
; ════════════════════════════════════════════════════════════════
; MAIN RECEIVE LOOP  ($12B–$198)
;
; Reads 8 bytes of serial data from P2.6 (DATA_IN), sampling each
; bit after a short inter-bit delay, and stores them in internal
; RAM starting at the address held in R0 (initially 0x10).
;
; For each bit of each byte:
;   • IN A,P2             – sample the port
;   • RLC A × 4           – shift bit 6 up into the carry flag
;                           (P2.6 is the data line; 4 left-rotates
;                            move bit 6 → bit 2 → ... → carry)
;   • MOV A,R5 / RRC A / MOV R5,A
;                         – shift carry into R5 from the MSB side
;                           (builds the received byte MSB-first)
;   • CALL $19E           – inter-bit delay (~39 CPU cycles)
;
; After 8 bits (the 8 CALL $19E blocks $13C–$184):
;   • ADD A,#DBH          – equivalent to subtracting 0x25; tests
;                           whether byte count R2 has reached 0x25
;                           (37 decimal); carry set → done
;   • MOV @R0,A           – write received byte to RAM[R0]
;   • ORL P2,#20H         – assert CLOCK high (ACK pulse to host)
;   • INC R0              – advance RAM write pointer
;   • JB3 $197            – if R0 bit3 still set, pointer is valid;
;                           otherwise reset R0 to 0x10 (wrap-around)
;
; Loop exit: when carry is set after ADD A,#DBH, jump to $199
; (wait for host to de-assert DATA_IN then restart from $108).
; ════════════════════════════════════════════════════════════════
$12B:  34 AD     CALL $1AD              ; short delay before reading byte stream
$12D:  0A        IN A,P2                ; sample P2
$12E:  92 32     JB6 $132               ; DATA_IN high → skip to receive loop
$130:  24 99     JMP  $199              ; jump into receive loop
$132:  34 A6     CALL $1A6              ; longer delay before first bit sample
$134:  0A        IN A,P2                ; sample P2 – bit 7
$135:  F7        RLC A                  ; shift P2.6 toward carry: RLC ×4
$136:  F7        RLC A                  ;   ...
$137:  F7        RLC A                  ;   ...
$138:  F7        RLC A                  ;   ..  carry now holds bit 7
$139:  FD        MOV A,R5               ; load accumulator for shift-in
$13A:  67        RRC A                  ; rotate carry into MSB of R5 (build byte MSB-first)
$13B:  AD        MOV R5,A               ; save shifted byte
$13C:  34 9E     CALL $19E              ; inter-bit delay
$13E:  0A        IN A,P2                ; sample P2 – bit 6
$13F:  F7        RLC A                  ; shift P2.6 → carry ×4
$140:  F7        RLC A
$141:  F7        RLC A
$142:  F7        RLC A
$143:  FD        MOV A,R5               ; load partial byte
$144:  67        RRC A                  ; shift carry into R5
$145:  AD        MOV R5,A               ; save
$146:  00        NOP                    ; padding NOP
$147:  34 9E     CALL $19E              ; inter-bit delay
$149:  0A        IN A,P2                ; sample P2 – bit 5
$14A:  F7        RLC A
$14B:  F7        RLC A
$14C:  F7        RLC A
$14D:  F7        RLC A
$14E:  FD        MOV A,R5               ; load partial byte
$14F:  67        RRC A                  ; shift carry into R5
$150:  AD        MOV R5,A               ; save
$151:  34 9E     CALL $19E              ; inter-bit delay
$153:  0A        IN A,P2                ; sample P2 – bit 4
$154:  F7        RLC A
$155:  F7        RLC A
$156:  F7        RLC A
$157:  F7        RLC A
$158:  FD        MOV A,R5               ; load partial byte
$159:  67        RRC A                  ; shift carry into R5
$15A:  AD        MOV R5,A               ; save
$15B:  00        NOP                    ; padding NOP
$15C:  34 9E     CALL $19E              ; inter-bit delay
$15E:  0A        IN A,P2                ; sample P2 – bit 3
$15F:  F7        RLC A
$160:  F7        RLC A
$161:  F7        RLC A
$162:  F7        RLC A
$163:  FD        MOV A,R5               ; load partial byte
$164:  67        RRC A                  ; shift carry into R5
$165:  AD        MOV R5,A               ; save
$166:  34 9E     CALL $19E              ; inter-bit delay
$168:  0A        IN A,P2                ; sample P2 – bit 2
$169:  F7        RLC A
$16A:  F7        RLC A
$16B:  F7        RLC A
$16C:  F7        RLC A
$16D:  FD        MOV A,R5               ; load partial byte
$16E:  67        RRC A                  ; shift carry into R5
$16F:  AD        MOV R5,A               ; save
$170:  00        NOP                    ; padding NOP
$171:  34 9E     CALL $19E              ; inter-bit delay
$173:  0A        IN A,P2                ; sample P2 – bit 1
$174:  F7        RLC A
$175:  F7        RLC A
$176:  F7        RLC A
$177:  F7        RLC A
$178:  FD        MOV A,R5               ; load partial byte
$179:  67        RRC A                  ; shift carry into R5
$17A:  AD        MOV R5,A               ; save
$17B:  34 9E     CALL $19E              ; inter-bit delay
$17D:  0A        IN A,P2                ; sample P2 – bit 0
$17E:  F7        RLC A
$17F:  F7        RLC A
$180:  F7        RLC A
$181:  F7        RLC A
$182:  FD        MOV A,R5               ; load partial byte
$183:  67        RRC A                  ; shift carry into R5 → byte complete
$184:  AD        MOV R5,A               ; save completed byte
$185:  FA        MOV A,R2               ; load byte counter
$186:  03 DB     ADD A,#DBH             ; add 0xDB = subtract 0x25 (check if 37 bytes received)
$188:  F6 99     JC $199                ; carry set → all bytes done → end-of-transfer wait
$18A:  1A        INC R2                 ; advance byte counter
$18B:  FD        MOV A,R5               ; load completed byte for XOR
$18C:  D3 FF     XRL A,#FFH             ; XOR with 0xFF → invert all bits (data transformation)
$18E:  A0        MOV @R0,A              ; store inverted byte to RAM at R0
$18F:  8A 20     ORL P2,#20H            ; assert P2.5 (CLOCK/ACK) high → ACK pulse to host
$191:  18        INC R0                 ; advance RAM write pointer
$192:  F8        MOV A,R0               ; test pointer R0.bit3
$193:  D2 97     JB3 $197               ; bit3 set → pointer still in valid range
$195:  24 99     JMP  $199              ; jump to end-of-transfer wait (buffer full)
$197:  B8 10     MOV R0,#10H            ; reset pointer to 0x10 (wrap RAM buffer)
; ════════════════════════════════════════════════════════════════
; END-OF-TRANSFER WAIT  ($199–$19C)
;
; After all bytes are received (or on any abort), wait here until
; the host de-asserts DATA_IN (P2.6 goes LOW), then restart the
; whole receive sequence from $108.
; ════════════════════════════════════════════════════════════════
$199:  0A        IN A,P2                ; sample P2
$19A:  92 99     JB6 $199               ; spin until DATA_IN goes LOW (host de-asserts)
$19C:  24 08     JMP  $108              ; restart serial receive from step 2
; ════════════════════════════════════════════════════════════════
; SUBROUTINE: DELAY_MEDIUM  ($19E–$1A5)
;
; Busy-wait loop. Counts down from 13 (0x0D) to 0 using DEC A.
; Each loop iteration: MOV A,#0DH (1 cy) + DEC A (1 cy) + JNZ (2 cy)
; Total: 1 + 13 × 3 = ~40 machine cycles.
; Used as the inter-bit sampling delay in the serial receive loop.
; ════════════════════════════════════════════════════════════════
$19E:  23 0D     MOV A,#0DH             ; load counter = 13
$1A0:  07        DEC A                  ; decrement counter
$1A1:  96 A0     JNZ $1A0               ; loop until zero
$1A3:  00        NOP                    ; NOP (delay padding)
$1A4:  00        NOP                    ; NOP (delay padding)
$1A5:  83        RET                    ; return to caller
; ════════════════════════════════════════════════════════════════
; SUBROUTINE: DELAY_LONG  ($1A6–$1AC)
;
; Same structure; counts from 15 (0x0F).
; Total: ~46 machine cycles.
; Called once before reading the data byte stream.
; ════════════════════════════════════════════════════════════════
$1A6:  23 0F     MOV A,#0FH             ; load counter = 15
$1A8:  07        DEC A                  ; decrement counter
$1A9:  96 A8     JNZ $1A8               ; loop until zero
$1AB:  00        NOP                    ; NOP (delay padding)
$1AC:  83        RET                    ; return to caller
; ════════════════════════════════════════════════════════════════
; SUBROUTINE: DELAY_SHORT  ($1AD–$1B2)
;
; Counts from 7 (0x07).
; Total: ~22 machine cycles.
; Called before the main data receive phase.
; ════════════════════════════════════════════════════════════════
$1AD:  23 07     MOV A,#07H             ; load counter = 7
$1AF:  07        DEC A                  ; decrement counter
$1B0:  96 AF     JNZ $1AF               ; loop until zero
$1B2:  83        RET                    ; return to caller
; ────────────────────────────────────────────────────────────
; $1B3: [0xFF × 1613]  (blank / erased ROM)
; ────────────────────────────────────────────────────────────
==============================================================================

HITACHI HD6805V1 ROM DISASSEMBLY

                       HITACHI HD6805V1 ROM DISASSEMBLY
==============================================================================