SE3K Survivors Experience 3000

VOLUME C -- HARDWARE REFERENCE

SC-3000 BASIC Level III-B — part of the SE3K documentation library, also readable inside the emulator.

============================================================================
SC-3000  BASIC LEVEL III-B
VOLUME C -- HARDWARE REFERENCE
============================================================================

  Level      : intermediate/advanced -- basic Z80 knowledge helpful
  References : Vol. D (ROM routines)  *  Vol. E (disassembly)

TABLE OF CONTENTS
----------------------------------------------------------------------------

  Chapter 1  SC-3000 Architecture ...................................... C-2
  Chapter 2  VDP -- TMS9918A (video processor) .......................... C-6
             2.10  All display modes -- mode-bit table and register summary
             2.11  Mode 0: Graphics I (32x24 chars, 8-colour groups)
             2.12  Mode 2: Multicolour (64x48 fat-pixel grid)
             2.13  VRAM access timing (CPU constraints)
             2.14  Hardware interface (TMS9918A pins, SC-3000 connections)
             2.15  ML practical examples
                     2.15.1  Switch to Mode 0 from ML / return to SCREEN 2
                     2.15.2  Redefine a character pattern (Modes 0 & 1)
                     2.15.3  Define and place an 8x8 sprite (Screen 2)
                     2.15.4  Define a 16x16 sprite
             2.16  Undocumented mode combinations (experimental)
  Chapter 3  PSG -- SN76489AN (sound processor) ......................... C-22
             3.1-3.5  Basic operation, frequency, noise, BASIC access
             3.6      Write protocol -- detailed behaviour (Maxim, 2003)
             3.7      Noise generator -- LFSR implementation (Maxim, 2003)
             3.8      Initial state and SC-3000 boot behaviour (Maxim, 2003)
             3.9      Signal imperfection -- output voltage decay (Maxim, 2003)
             3.10     Sample playback techniques (Maxim, 2003)
  Chapter 4  PPI -- Intel 8255 (peripheral interface) ................... C-32
  Chapter 5  Joystick .................................................. C-48
  Chapter 6  SC-3000H Hardware Notes (C. MacDonald, 2002-2004) ......... C-56
             6.2.3b  RGB connector (8-pin DIN, PAL/RGB models)
             6.3.5   Cartridge bus timing (service manual Fig. 2-1 -- 2-3)
  Chapter 7  Serial Printer Interface (practical reference) ............. C-72
             7.1  Overview -- signals and BASIC commands
             7.2  Connector pinout -- dual source comparison
             7.3  BASIC interface -- how LPRINT works internally (PUTADR)
             7.4  Checking printer status from ML
             7.5  Sending a character via the ROM handler ($7008)
             7.6  Physical wire protocol -- bit encoding and timing
             7.7  Timing diagram -- "Hello World\r" (logic analyzer view)
             7.8  Emulator interception -- capturing LPRINT output in software

CHAPTER 1 -- SC-3000 ARCHITECTURE

CHAPTER 1 -- SC-3000 ARCHITECTURE
============================================================================

1.1 CPU

  1.1  CPU
  ----------------------------------------------------------------------------

    Zilog Z80A
    Clock  : 3.579545 MHz  (NTSC colorburst / 1)
    Bus    : 8-bit data, 16-bit address
    Refresh: automatic DRAM refresh via /RFSH pin
    Power  : DC 9 V +0.5 V / 850 mA  (minimum ripple voltage >= 7.5 V)

1.2 Memory map ($0000-$FFFF)

  1.2  Memory map ($0000-$FFFF)
  ----------------------------------------------------------------------------

    $0000-$7FFF   ROM  (32 KB -- BASIC Level III-B cartridge)
    $8000-$87FF   RAM  (2 KB -- present on all SC-3000 models)
    $8000-$BFFF   RAM  (16 KB -- with 16KB RAM expansion)
    $8000-$FFFF   RAM  (32 KB -- with SF-7000 or 32KB expansion)

    Notes:
      * POKE to $0000-$7FFF is ignored by BASIC (ROM is not writable)
      * BASIC uses $8000-$97FF as its working area (Vol. D ch. 10)
      * BASIC programs start at $9800
      * With only 2 KB, the RAM available for programs is minimal

1.3 I/O map ($00-$FF)

  1.3  I/O map ($00-$FF)
  ----------------------------------------------------------------------------

    Port    Chip           Dir   Function
    ------  -------------  ----  ----------------------------------------
    $7F     SN76489AN PSG  W     PSG data write (write-only)
    $BE     TMS9918A VDP   R/W   VRAM data (read or write)
    $BF     TMS9918A VDP   R/W   Write VDP register; read status
    $DC     8255 PPI       R     Port A -- keyboard / joystick P1 data
    $DD     8255 PPI       R     Port B -- keyboard / joystick P2 data
    $DE     8255 PPI       R/W   Port C -- control signals
    $DF     8255 PPI       W     PPI control register

1.4 Interrupts

  1.4  Interrupts
  ----------------------------------------------------------------------------

    /INT  Generated by the VDP at the end of every frame.
          Frequency: 60 Hz (NTSC) / 50 Hz (PAL)
          Z80 vector: $0038  ->  JP $6806  ->  BASIC interrupt handler
          Uses: incrementing the TIME$ timer (TIMEIV/$948D),
                keyboard auto-repeat, updating BRKFLG and SPCFLG.

    /NMI  Not used by standard SC-3000 hardware.
          Z80 vector: $0066  ->  JP $6803

1.5 Block diagram

  1.5  Block diagram
  ----------------------------------------------------------------------------

    +-------------+    Z80 Bus     +-------------+
    |  Z80A CPU   |<-------------->|  ROM 32 KB  | $0000-$7FFF
    |  3.58 MHz   |                +-------------+
    +------+------+
           | Z80 Bus             +-------------+
           +-------------------->|  RAM 2-32KB | $8000-$FFFF
           |                     +-------------+
           | I/O $BE/$BF         +-------------+    +----------+
           +-------------------->| TMS9918A    |<-->| VRAM 16K |
           |                     |    VDP      |    +----------+
           |                     |   Video ----+--> TV/RGB signal
           |                     |   /INT -----+--> Z80 /INT
           |                     +-------------+
           | I/O $7F             +-------------+
           +-------------------->| SN76489AN   |--> Audio (mono)
           |                     |    PSG      |
           |                     +-------------+
           | I/O $DC-$DF         +-------------+
           +-------------------->| Intel 8255  |--> Keyboard matrix
                                 |    PPI      |--> Cassette (CMT)
                                 +-------------+--> Serial printer
                                                --> Joystick P1/P2

CHAPTER 2 -- VDP -- TMS9918A (VIDEO PROCESSOR)

CHAPTER 2 -- VDP -- TMS9918A (VIDEO PROCESSOR)
============================================================================

2.1 Features

  2.1  Features
  ----------------------------------------------------------------------------

    Chip    : Texas Instruments TMS9918A (NTSC) / TMS9928A (PAL)
    VRAM    : 16 KB separate from CPU RAM (not visible to the Z80)
    Ports   : $BE = data, $BF = control/status
    Output  : composite / RGB (depends on hardware version)
    /INT    : signal to Z80 at every VBlank (end of frame)

    Display modes supported by SC-3000 BASIC:
      SCREEN 1  ->  Text     40x25 characters  (6x8 pixels/char)
      SCREEN 2  ->  Graphics 256x192 pixels    (2 colours per 8 pixels)

2.2 I/O port access

  2.2  I/O port access
  ----------------------------------------------------------------------------

    Port $BF -- write VDP register
    ----------------------------------------------------------------------------
    Writing a VDP register requires TWO consecutive bytes:
      1st byte: value to write into the register
      2nd byte: $80 | register_number   (bit 7 = 1 -> register write)

    Example -- write $C2 into register R1:
      LD   A, $C2
      OUT  ($BF), A        ; value first
      LD   A, $80 | 1      ; = $81
      OUT  ($BF), A        ; register number second

    Port $BF -- read status register
    ----------------------------------------------------------------------------
    A read from $BF returns the status register and clears the interrupt
    flag:
      IN   A, ($BF)
      ; bit 7 (F)  : 1 = VBlank interrupt pending
      ; bit 6 (5S) : 1 = 5th sprite on a scanline (overflow)
      ; bit 5 (C)  : 1 = sprite coincidence (overlap)
      ; bits 4-0   : number of the 5th out-of-range sprite

    Port $BE -- VRAM access
    ----------------------------------------------------------------------------
    Before reading or writing VRAM, set the address via $BF (see
    section 2.7). Each access to $BE automatically advances the VRAM
    address by 1.

    IMPORTANT: insert at least 1 us between two consecutive VRAM accesses
    (2 NOP cycles at 3.58 MHz). The ROM uses 4-5 guard NOPs
    (see VRAMWR/VRAMRD in Vol. D ch. 5).

2.3 VDP registers R0-R7

  2.3  VDP registers R0-R7
  ----------------------------------------------------------------------------

    All registers are write-only (except status read from $BF).

    +----+-------------------------------------------------------------+
    | R0 |  Bit 1 (M3): Graphics II mode selection                     |
    |    |  Bit 0 (EXT): external video (normally 0)                   |
    |    |  Bits 7-2: 0                                                |
    |    |  Screen 1: $00   Screen 2: $02                              |
    +----+-------------------------------------------------------------+
    | R1 |  Bit 7 (4/16K): VRAM size     -> always 1 (16K)             |
    |    |  Bit 6 (BL):    display active -> 1=on, 0=blank             |
    |    |  Bit 5 (IE):    interrupt enable -> 1=enable VBlank /INT     |
    |    |  Bit 4 (M2):    mode bit 2                                  |
    |    |  Bit 3 (M1):    mode bit 1                                  |
    |    |  Bit 2:         0                                           |
    |    |  Bit 1 (SI):    sprite size  0=8x8, 1=16x16                |
    |    |  Bit 0 (MAG):   sprite zoom  0=1x,  1=2x                   |
    |    |                                                             |
    |    |  Mode selection (M3 in R0, M2 and M1 in R1):               |
    |    |    M3=0 M2=0 M1=0  ->  Mode 0: Graphics I  (32x24 chars)    |
    |    |    M3=0 M2=0 M1=1  ->  Mode 1: Text        (40x24 chars)    |
    |    |    M3=0 M2=1 M1=0  ->  Mode 2: Multicolour (not used)       |
    |    |    M3=1 M2=0 M1=0  ->  Mode 3: Graphics II (256x192 px)     |
    |    |                                                             |
    |    |  Screen 1 (text, IE enabled):  $D0  (10110000)             |
    |    |  Screen 2 (gfx,  IE enabled):  $C2  (11000010)             |
    +----+-------------------------------------------------------------+
    | R2 |  Name table base (bits 3-0):  addr = value x $0400         |
    |    |  Screen 1: $00  -> name table at $0000                      |
    |    |  Screen 2: $06  -> name table at $1800                      |
    +----+-------------------------------------------------------------+
    | R3 |  Colour table base (bits 7-0):  addr = value x $0040       |
    |    |  Graphics I and Graphics II only (M3=1: addr = R3 x $0040) |
    |    |  Screen 2 (Graphics II): $FF  -> colour table at $3FC0      |
    |    |  (In M3=1 mode, R3 and R4 use all bits to address a        |
    |    |   6144-byte colour table at $2000)                          |
    +----+-------------------------------------------------------------+
    | R4 |  Pattern generator table base (bits 2-0): addr = val x $800|
    |    |  Screen 1: $03  -> pattern table at $1800                   |
    |    |  Screen 2: $03  -> pattern table at $0000                   |
    |    |  (In Graphics II mode the field has 2 usable bits)         |
    +----+-------------------------------------------------------------+
    | R5 |  Sprite attribute table base (bits 6-0): addr = val x $80  |
    |    |  Screen 2: $36  -> sprite attr at $1B00                     |
    +----+-------------------------------------------------------------+
    | R6 |  Sprite pattern table base (bits 2-0): addr = val x $800   |
    |    |  Screen 2: $07  -> sprite pattern at $3800                  |
    +----+-------------------------------------------------------------+
    | R7 |  Backdrop and text colour (hi nibble=fg, lo nibble=bg)      |
    |    |  Used in text mode for character colour and as backdrop     |
    |    |  colour in all modes.                                       |
    +----+-------------------------------------------------------------+

    IMPORTANT -- the R2/R6 values above ($06/$07) are the conventional
    textbook TMS9918 example, valid ONLY if you program the registers
    yourself via raw OUT $BF sequences (as in the ML examples further
    below in this chapter, which do set R2=$06/R6=$07 explicitly and are
    self-consistent).
    If instead you enter Screen 2 the normal way -- the BASIC "SCREEN 2,2"
    statement, or calling the ROM's SCRINIT ($39DF) + DSPGRP ($39E2) --
    the actual registers written by THIS ROM (verified by disassembly)
    are DIFFERENT:
      R2 = $0E  -> name table at $3800 (not $1800)
      R6 = $03  -> sprite pattern table at $1800 (not $3800)
      R5 = $76  -> sprite attribute table at $3B00 (not $1B00)
    Any BASIC/ML code that relies on SCRINIT/DSPGRP but assumes the
    conventional $1800/$3800 mapping will write to the wrong VRAM area
    (typically an invisible/blank screen). This was the root cause of a
    real bug in GRAPHICS_MASTER.txt and the game tutorial chapters --
    see manual_v2/VOL_G_TAPE_MASTERING.txt section 7 point 5.
    |    |  BASIC default: $F5 (white on grey) or similar             |
    +----+-------------------------------------------------------------+

2.4 SCREEN 1 -- Text mode (40x25 characters)

  2.4  SCREEN 1 -- Text mode (40x25 characters)
  ----------------------------------------------------------------------------

    Features:
      * 40 columns x 25 rows (rows 0-24 in BASIC)
      * Each character: 6 pixels x 8 pixels
      * 1 fg/bg colour pair for the whole screen (from R7)
      * No direct access to individual pixels

    VRAM map -- Screen 1:
    +-----------------+------------------------------------------------+
    | $0000-$03E7     | Name table (1000 bytes = 40x25)                |
    |                 | Address = row x 40 + column                    |
    |                 | Byte = ASCII code of the character             |
    +-----------------+------------------------------------------------+
    | $0800-$0FFF     | (unused in Screen 1)                           |
    +-----------------+------------------------------------------------+
    | $1800-$1FFF     | Pattern table (256 chars x 8 bytes = 2048 B)   |
    |                 | Each char: 8 bytes, 1 byte per row             |
    |                 | Bit 1=visible pixel, Bit 0=hidden pixel        |
    |                 | This block is VRAMSV (swapped with RAM)        |
    +-----------------+------------------------------------------------+

    Calculating the VRAM address of a screen character:
      addr = $0000 + (row x 40) + column
      Example: row 12, column 20  ->  $0000 + 480 + 20 = $01F4

    Modifying a character pattern (via direct VRAM):
      addr_pattern = $1800 + (ASCII_code x 8) + inner_row
      Example: character 'A' (ASCII 65), row 0:  $1800 + 520 = $1A08

2.5 SCREEN 2 -- Graphics mode (256x192 pixels)

  2.5  SCREEN 2 -- Graphics mode (256x192 pixels)
  ----------------------------------------------------------------------------

    Features:
      * 256 columns x 192 rows of pixels
      * 2 colours per group of 8 horizontal pixels (1 byte)
      * A bit=1 -> "bit-1 colour"; a bit=0 -> "bit-0 colour"
      * The screen is divided into 3 vertical "thirds" of 64 rows each

    VRAM map -- Screen 2 (conventional TMS9918 layout; see the IMPORTANT
    note in section 2.3/2.4's register table -- if you entered this mode
    via SCRINIT+DSPGRP or BASIC's "SCREEN 2,2", the name table and sprite
    pattern table addresses below are SWAPPED on this ROM: name table is
    actually at $3800 and sprite pattern at $1800):
    +-----------------+------------------------------------------------+
    | $0000-$17FF     | Pattern table (3 x 256 chars x 8 bytes)        |
    |                 | Third 0: $0000-$07FF (pixel rows 0-63)         |
    |                 | Third 1: $0800-$0FFF (pixel rows 64-127)       |
    |                 | Third 2: $1000-$17FF (pixel rows 128-191)      |
    |                 | Each byte: 8 pixels (bit7=left, bit0=right)    |
    +-----------------+------------------------------------------------+
    | $1800-$1AFF     | Name table (768 bytes = 3 x 256)               |
    |                 | Third 0: $1800-$18FF (sequence 0,1,2...255)    |
    |                 | Third 1: $1900-$19FF (same sequence)           |
    |                 | Third 2: $1A00-$1AFF (same sequence)           |
    |                 | Normally preloaded $00,$01,...,$FF to map       |
    |                 | patterns 1:1 to pixels                         |
    +-----------------+------------------------------------------------+
    | $1B00-$1B7F     | Sprite attribute table (32 sprites x 4 bytes)  |
    +-----------------+------------------------------------------------+
    | $2000-$37FF     | Colour table (3 x 256 chars x 8 bytes = 6144 B)|
    |                 | Same structure as the pattern table            |
    |                 | Each byte: hi nibble=fg, lo nibble=bg          |
    |                 | Corresponds to 8 pixels of the same row        |
    +-----------------+------------------------------------------------+
    | $3800-$3FFF     | Sprite pattern table (256 patterns x 8 bytes)  |
    |                 | For 16x16 sprites: 4 blocks of 8 bytes         |
    +-----------------+------------------------------------------------+

    Finding the address of a pixel (x, y):
      third      = y / 64            (0, 1 or 2)
      char_row   = y MOD 8           (0-7 = inner row within the byte)
      col_byte   = x / 8             (0-31 = which byte in the row)

    Correct formula for the pattern table:
      idx        = (y MOD 64) / 8 x 32 + (x / 8)   ; block index
      addr_patt  = $0000 + third x $0800 + idx x 8 + (y MOD 8)
      bit_mask   = $80 >> (x MOD 8)                 ; bit to set

    Formula for the colour table (same index):
      addr_col   = $2000 + third x $0800 + idx x 8 + (y MOD 8)
      byte       = (fg_nibble << 4) | bg_nibble

    Note: the PLOTPT and PLOTLN routines (Vol. D ch. 6) perform these
    calculations automatically. Using them is recommended.

2.6 Sprite system

  2.6  Sprite system
  ----------------------------------------------------------------------------

    Hardware limits:
      * Maximum 32 sprites simultaneously
      * Maximum 4 sprites visible on the same scanline
      * The 5th sprite on a scanline is not displayed
        (bit 6 of the status register is set)

    Sprite attribute table ($1B00-$1B7F, 4 bytes per sprite):
    +--------+-------------------------------------------------------+
    | Byte 0 | Y -- vertical coordinate (0-191; 208=$D0 = hidden)     |
    |        | Display starts from row Y+1                           |
    +--------+-------------------------------------------------------+
    | Byte 1 | X -- horizontal coordinate (0-255)                     |
    +--------+-------------------------------------------------------+
    | Byte 2 | Pattern number (0-255 for 8x8; multiple of 4 for 16x16)|
    +--------+-------------------------------------------------------+
    | Byte 3 | Bit 7 (EC): early clock -> shift sprite by -32 pixels  |
    |        | Bits 3-0: colour (1-15; 0=transparent)               |
    +--------+-------------------------------------------------------+

    Display priority: sprite with lower number is in front.
    Sizes: MAG=0->8x8 px, MAG=1->16x16 px, MAG=2->8x8 zoomx2,
           MAG=3->16x16 zoomx2.

2.7 Direct VRAM access

  2.7  Direct VRAM access
  ----------------------------------------------------------------------------

    VRAM write sequence:
      1. Write the low byte of the address to $BF
      2. Write the high byte to $BF  with bit 6=0  (write mode)
      3. Write data bytes to $BE (auto-increment after each write)

      ; Example: write $AB to VRAM address $1800
      LD   A, $00          ; low byte of $1800
      OUT  ($BF), A
      LD   A, $18 | $40    ; high byte $18, bit6=0 -> write = $58
      OUT  ($BF), A
      LD   A, $AB          ; data
      OUT  ($BE), A

    VRAM read sequence:
      1. Write the low byte to $BF
      2. Write the high byte to $BF  with bit 6=1  (read mode)
      3. Read bytes from $BE after a short delay

      ; Example: read from $1800
      LD   A, $00
      OUT  ($BF), A
      LD   A, $18 | $00    ; bit6=0 -> read = $18 (do not use $40)
      OUT  ($BF), A
      NOP : NOP            ; minimum guard (~0.5 us)
      IN   A, ($BE)        ; data read

    IMPORTANT: do not access VRAM during active display without
    disabling interrupts (DI/EI). The ROM routines VRAMADRW,
    VRAMADRR, VRAMWR, VRAMRD (Vol. D ch. 5) handle timing correctly.

2.8 TMS9918A 16-colour palette

  2.8  TMS9918A 16-colour palette
  ----------------------------------------------------------------------------

    The palette is fixed (not programmable). Approximate RGB values:

    Index  BASIC name       R    G    B    Notes
    ------  -------------  ---  ---  ---  --------------------------
      0     Transparent      0    0    0   transparent background
      1     Black            0    0    0
      2     Medium green    36  139   36
      3     Light green     94  198   94
      4     Dark blue       85   85  183
      5     Light blue     128  128  212
      6     Dark red       183   79   63
      7     Cyan            79  193  193
      8     Medium red     212   85   79
      9     Light red      255  113  100
     10     Dark yellow    212  193   79
     11     Light yellow   217  210  112
     12     Dark green      36  116   36
     13     Magenta        183   79  173
     14     Grey           183  183  183
     15     White          255  255  255

    Colour 0 is transparent: it shows the backdrop (R7 low nibble).
    Colour 1 (black) and 0 (transparent) look identical but behave
    differently with sprites: colour 0 in sprites is always transparent.

2.9 VBlank interrupt

  2.9  VBlank interrupt
  ----------------------------------------------------------------------------

    The VDP generates a /INT signal to the Z80 at the end of every frame.
    To enable it: bit 5 (IE) of R1 = 1.

    The BASIC interrupt handler (at $6806) performs:
      * Incrementing the software timer (TIMEA/$948B, TIMEIV/$948D)
      * Updating TIMES/$948E, TIMEM/$948F, TIMEH/$9490
      * Reading the keyboard matrix (KEYREAD -> updates KEYIM0-8)
      * Keyboard auto-repeat
      * Updating BRKFLG and SPCFLG flags

    To read the interrupt flag (and clear it):
      IN   A, ($BF)        ; reads VDP status, clears the flag
      BIT  7, A            ; bit7=1 if VBlank was pending

2.10 All display modes -- mode-bit table and register summary

  2.10  All display modes -- mode-bit table and register summary
  ----------------------------------------------------------------------------

    The TMS9918A has three mode bits: M3 (bit 1 of R0), M2 (bit 4 of R1),
    M1 (bit 3 of R1).  Their combination selects one of four documented
    display modes.

    M3  M2  M1   Name            Resolution          Sprites  BASIC cmd
    --  --  --   ------------    ------------------  -------  ---------
     0   0   0   Mode 0 (G1)     32x24 chars (8x8)   yes      --
     0   0   1   Mode 1 (Text)   40x24 chars (6x8)   no       SCREEN 1
     0   1   0   Mode 2 (MC)     64x48 fat pixels    yes      --
     1   0   0   Mode 3 (G2)     256x192 bitmap       yes      SCREEN 2

    Register values for all four modes (16 KB VRAM, BL=1, IE=1):

    Reg   Mode 0 (G1)  Mode 1 (Text)  Mode 2 (MC)  Mode 3 (G2) *
    ---   -----------  -------------  -----------  -----------
    R0    $00           $00            $00           $02
    R1    $E0           $D0            $F0           $C2
    R2    $05 ($1400)   $00 ($0000)    $05 ($1400)   $0E ($3800) *
    R3    $80 ($2000)   -- (unused)    -- (unused)   $FF ($3FC0)
    R4    $03 ($1800)   $03 ($1800)    $00 ($0000)   $03 ($0000)
    R5    $36 ($1B00)   -- (no sprite) $36 ($1B00)   $76 ($3B00) *
    R6    $07 ($3800)   -- (no sprite) $07 ($3800)   $03 ($1800) *
    R7    $01 (border)  $F4 (wh/gr)    $01 (border)  $01 (border)

    * CORRECTED from the generic TMS9918 textbook values (R2=$06/$1800,
      R5=$36/$1B00, R6=$07/$3800) after disassembling this ROM's actual
      SCRINIT ($39DF) and DSPGRP ($39E2) -- the routines behind BASIC's
      "SCREEN 2,2". This ROM swaps name table and sprite pattern table
      relative to the conventional layout: name table is at $3800 and
      sprite pattern table at $1800. See the IMPORTANT note in section
      2.3/2.4 for detail; any code assuming the textbook values will
      write tile/graphics data to the wrong VRAM area (blank screen).

    Notes:
      * Mode 1 (Text): R4 address field uses only bits 2-0, addr = val x $800.
        BASIC uses R4=$03 -> $1800 (same as Mode 0 above, reusing the ROM
        character set already loaded there).
      * Mode 3 (G2): R3 and R4 together address a 6144-byte colour and
        pattern table; R3=$FF means the full upper half of VRAM.
      * R7 in Text mode: hi nibble=foreground colour, lo nibble=background
        colour for all characters.  BASIC default: $F4 (white on grey).

2.11 Mode 0: Graphics I (32x24 chars, 8-colour groups)

  2.11  Mode 0: Graphics I (32x24 chars, 8-colour groups)
  ----------------------------------------------------------------------------

    Graphics I is the "native" tile mode of the TMS9918A.  It is not
    accessible via a BASIC SCREEN command on the SC-3000 but can be entered
    directly from ML (see section 2.15.1).

    Features:
      * 32 columns x 24 rows = 768 character cells
      * Each cell: 8x8 pixels, character code 0-255
      * 256 user-definable patterns (pattern generator table)
      * 16 colours available; colour assigned per group of 8 character codes
      * Sprites: 32 sprites, max 4 per scanline (same as Mode 3)

    VRAM map -- Mode 0 (using the register values in section 2.10):

    Address range    Size   Description
    ---------------  -----  --------------------------------------------------
    $0000-$02FF       768B  Name table (32x24 char codes; R2=$00)
    $1800-$1FFF      2048B  Pattern generator (256 chars x 8 bytes; R4=$03)
    $2000-$201F        32B  Colour table (32 groups x 1 byte; R3=$80)
    $1B00-$1B7F       128B  Sprite attribute table (32 sprites; R5=$36)
    $3800-$3FFF      2048B  Sprite pattern table (R6=$07)

    Name table: each byte is the character code (0-255) to display at that
    cell.  Row-major order: cell (col, row) is at offset row*32 + col.

    Colour table: 32 bytes, one per group of 8 character codes.
      Byte N applies to char codes N*8 through N*8+7.
      Bit layout of each colour byte:
        bits 7-4 : foreground colour (1-15; 0=transparent)
        bits 3-0 : background colour (0-15)
      Example: $F1 = white (15) foreground on black (1) background.

    Pattern generator table: one entry per character, 8 bytes each.
      byte[0] = pixel row 0 (topmost), byte[7] = pixel row 7.
      Bit 7 = leftmost pixel, bit 0 = rightmost pixel.
      A '1' bit renders in the foreground colour; '0' in background colour.

    Compared to Mode 3 (Graphics II):
      * Mode 0 uses ONE pattern generator shared by the whole screen; Mode 3
        has THREE independent pattern generators (one per vertical third).
      * Mode 0 colour granularity: one colour pair per 8 char codes.  In
        Mode 3, every 8-pixel row of every character has its own colour byte.
      * Mode 0 needs only 32 colour bytes; Mode 3 needs 6144.

2.12 Mode 2: Multicolour (64x48 fat-pixel grid)

  2.12  Mode 2: Multicolour (64x48 fat-pixel grid)
  ----------------------------------------------------------------------------

    Multicolour mode divides the 256x192 screen into a 64x48 grid of
    "fat pixels", each 4x4 real pixels in size.  Every fat pixel can be
    assigned an independent colour from the 16-colour palette.

    Features:
      * 64 columns x 48 rows of independently-coloured fat pixels
      * Each fat pixel: 4x4 real pixels, one of 16 colours
      * No per-pixel resolution; no colour-pairs constraint
      * Sprites: supported (same attribute/pattern tables as Modes 0 and 3)

    Organisation: the 64x48 fat pixel grid maps to 32x24 character cells
    (same name table as Mode 0).  Each character cell contains a 2x2 sub-
    grid of fat pixels (2 wide x 2 tall = 4 fat pixels per character).

    VRAM map -- Mode 2:

    Address range    Size   Description
    ---------------  -----  --------------------------------------------------
    $0000-$07FF      2048B  Pattern/colour table (32x24 x 8 bytes; R4=$00)
    $1400-$16FF       768B  Name table (32x24 char codes; R2=$05)
    $1B00-$1B7F       128B  Sprite attribute table (R5=$36)
    $3800-$3FFF      2048B  Sprite pattern table (R6=$07)
    R3 (colour table): not used in Mode 2.

    Pattern/colour table format (8 bytes per character entry):
      Byte 0: colours for scan rows 0-3 (top fat-pixel row of the cell)
              bits 7-4 = LEFT fat pixel colour
              bits 3-0 = RIGHT fat pixel colour
      Byte 2: colours for scan rows 4-7 (bottom fat-pixel row of the cell)
              bits 7-4 = LEFT fat pixel colour
              bits 3-0 = RIGHT fat pixel colour
      Bytes 1, 3, 4, 5, 6, 7: not read by the VDP in this mode.

    Addressing a fat pixel at grid position (fx, fy) where fx: 0-63, fy: 0-47:
      char_col  = fx / 2           ; which character column (0-31)
      char_row  = fy / 2           ; which character row (0-23)
      cell_code = name_table[char_row * 32 + char_col]   ; typically 0-255
      byte_ofs  = (fy MOD 2) * 2   ; 0 = top row, 2 = bottom row
      nibble    = (fx MOD 2)       ; 0 = left (hi nibble), 1 = right (lo nibble)
      addr      = $0000 + cell_code * 8 + byte_ofs
      colour    = (VRAM[addr] >> (4 - nibble*4)) AND $0F

    Practical note: for a fixed 1:1 mapping (each fat pixel independently
    addressable), preload the name table with cell codes 0-255 in row-major
    order (cells 0-31 in row 0, 32-63 = 0-31 repeated in row 1, etc., since
    there are only 256 codes for 768 cells).  A common approach uses a tiled
    arrangement where each tile block shares a pattern entry but has its
    fat pixel colours set individually via the colour bytes.

2.13 VRAM access timing (CPU constraints)

  2.13  VRAM access timing (CPU constraints)
  ----------------------------------------------------------------------------

    The TMS9918A has a dedicated 16-bit VRAM bus (separate from the Z80
    data bus).  The CPU accesses VRAM only via the I/O ports $BE/$BF.

    Internal clock: the VDP runs at 10.7386 MHz (= 3 x NTSC colour burst).
    The Z80 runs at 3.5795 MHz (= 1/3 of VDP clock).

    Critical timing rules (from TI TMS9918A Application Manual):

    1.  Two-byte register/address write (port $BF):
          Write byte 1 to $BF.
          Wait AT LEAST 1 Z80 machine cycle (NOPx1, ~279 ns) before the
          second write.  The ROM typically waits 0 cycles (back-to-back OUT
          instructions are separated by ~1.5 µs of opcode fetch, which is
          sufficient).

    2.  Address setup to first VRAM data access (port $BE):
          After the second $BF write, wait AT LEAST 2 µs before the first
          $BE access.
          At 3.58 MHz, 2 µs = ~7 Z80 T-states.  One NOP = 4 T-states.
          The ROM guard (VRAMADRW/VRAMADRR) uses 2 NOP instructions (~2.2 µs).
          Safe minimum: 2 x NOP.

    3.  Consecutive VRAM data reads (port $BE, read mode):
          The VDP needs time to increment its address counter and prepare the
          next byte.  Wait AT LEAST 8 µs between consecutive reads.
          At 3.58 MHz: 8 µs = ~29 T-states = ~7 NOP instructions.
          The ROM's VRAMRD uses 5 guard NOPs and an additional LD operation
          (~6.5 µs total), which is marginally safe.  For reliable operation
          use at least 5 NOPs between reads.

    4.  Consecutive VRAM data writes (port $BE, write mode):
          Minimum: 8 µs between writes (~29 T-states).
          The ROM's VRAMWR guard: 4-5 NOPs + surrounding instructions.
          For maximum write speed from ML: 5 NOPs minimum.
          For burst writes (large block copy), a DJNZ loop with 4 NOPs
          per iteration gives ~9 µs/byte at 3.58 MHz (safe).

    5.  Status register read (port $BF, read):
          Can be performed at any time without timing restriction relative
          to VRAM operations.  Clears the VBlank interrupt flag (bit 7).

    6.  Active display vs blanking:
          VRAM access during active display does NOT corrupt the display
          (the VDP has internal arbitration) but timing rules still apply.
          The safest approach is to access VRAM during VBlank only (inside
          the interrupt handler after reading the status register, or while
          BL=0 via R1 bit 6).

    Recommended ML idiom for safe VRAM write:

      ; Set VRAM address for write (example: $1800)
      LD   A, $00           ; low byte
      OUT  ($BF), A
      LD   A, $18 | $40     ; high byte | write bit ($40)
      OUT  ($BF), A
      NOP : NOP             ; >=2 µs setup (2 NOPs = ~2.2 µs)

      ; Write loop (5 NOPs between writes = ~8 µs)
      LD   B, count
      LD   HL, source
    WRLOOP:
      LD   A, (HL)
      OUT  ($BE), A
      INC  HL
      NOP : NOP : NOP : NOP : NOP
      DJNZ WRLOOP

2.14 Hardware interface (TMS9918A pins, SC-3000 connections)

  2.14  Hardware interface (TMS9918A pins, SC-3000 connections)
  ----------------------------------------------------------------------------

    Pin group     Pin name(s)   Dir   SC-3000 connection
    ------------  -----------   ---   ---------------------------------------
    CPU data bus  CD0-CD7       I/O   Z80 data bus D0-D7
    Chip select   /CSR          IN    /IORQ AND /RD AND (port addr $BE or $BF)
                  /CSW          IN    /IORQ AND /WR AND (port addr $BE or $BF)
    Mode/addr     MODE          IN    A0 of Z80 address bus
                                      0 ($BE) = VRAM data port
                                      1 ($BF) = control/status port
    Interrupt     /INT          OUT   Z80 /INT pin (active low, VBlank)
    Video out     COMVID        OUT   Composite video signal (TMS9918A NTSC)
                  Y, R-Y, B-Y  OUT   Component video (TMS9928A PAL only)
                  CSYNC         OUT   Composite sync (TMS9928A)
    Crystal       XTAL1/XTAL2   --    10.7386 MHz crystal (NTSC)
                                       (PAL: TMS9929A uses 17.734 MHz)
    VRAM bus      AD0-AD13      OUT   14-bit VRAM address bus (not CPU-visible)
                  D0-D7 (VRAM) I/O   VRAM data bus (not CPU-visible)
    Supply        VCC/GND        --   +5V / 0V

    Notes:
      * GROMCLK and CPUCLK outputs of the TMS9918A are not used on SC-3000.
      * EXTVDP (external video mix) is tied high (inactive) on SC-3000.
      * The TMS9918A (NTSC models) generates composite video internally.
        The TMS9928A (PAL/RGB models used in SC-3000H PAL versions) outputs
        separate colour-difference signals; a separate encoder generates PAL.
      * Port decoding: address bits A7-A1 select $BE ($10111110) or $BF.
        Bit A0 connects to the VDP MODE pin to switch between data and control.

2.15 ML practical examples

  2.15  ML practical examples
  ----------------------------------------------------------------------------

2.15.1 Switch to Mode 0 (Graphics I) and back to Screen 2

  2.15.1  Switch to Mode 0 (Graphics I) and back to Screen 2
  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    ; ------------------------------------------------------------------
    ; SWITCH_G1 -- activate Mode 0 (Graphics I, 32x24)
    ; Using: name table at $0000, patterns at $1800 (BASIC char set),
    ;        colour table at $2000, sprites from $1B00/$3800.
    ; Destroys: A
    ; ------------------------------------------------------------------
    SWITCH_G1:
        LD   A, $00 : OUT ($BF), A : LD A, $80 : OUT ($BF), A  ; R0=$00 (M3=0)
        LD   A, $E0 : OUT ($BF), A : LD A, $81 : OUT ($BF), A  ; R1=$E0 (G1,IE,BL)
        LD   A, $00 : OUT ($BF), A : LD A, $82 : OUT ($BF), A  ; R2=$00 -> $0000
        LD   A, $80 : OUT ($BF), A : LD A, $83 : OUT ($BF), A  ; R3=$80 -> $2000
        LD   A, $03 : OUT ($BF), A : LD A, $84 : OUT ($BF), A  ; R4=$03 -> $1800
        LD   A, $36 : OUT ($BF), A : LD A, $85 : OUT ($BF), A  ; R5=$36 -> $1B00
        LD   A, $07 : OUT ($BF), A : LD A, $86 : OUT ($BF), A  ; R6=$07 -> $3800
        LD   A, $01 : OUT ($BF), A : LD A, $87 : OUT ($BF), A  ; R7=$01 (blk bdr)
        RET

    After SWITCH_G1 the screen shows 32 columns x 24 rows using BASIC's
    built-in character set (patterns at $1800).  The name table at $0000
    must be preloaded before calling this routine, otherwise random VRAM
    content is displayed.

    Fill the name table with spaces (ASCII $20 = 32):

    FILL_NAME:
        LD   A, $00 : OUT ($BF), A   ; address low = $00
        LD   A, $40 : OUT ($BF), A   ; address high = $00 | $40 (write)
        NOP : NOP
        LD   BC, 768                  ; 32 x 24 = 768 cells
        LD   A, $20                   ; space character
    FNLOOP:
        OUT  ($BE), A
        NOP : NOP : NOP : NOP : NOP
        DEC  BC
        LD   A, B
        OR   C
        JR   NZ, FNLOOP
        RET

    Set all 32 colour groups to white-on-black ($F1):

    FILL_COL:
        LD   A, $00 : OUT ($BF), A
        LD   A, $60 : OUT ($BF), A   ; $2000 = $80 * $40 -> high=$20, wr=$40 -> $60
        NOP : NOP
        LD   B, 32
        LD   A, $F1
    FCLOOP:
        OUT  ($BE), A
        NOP : NOP : NOP : NOP : NOP
        DJNZ FCLOOP
        RET

    Return to Screen 2 (Mode 3, Graphics II) after use:

    BACK_TO_G2:
        LD   A, $02 : OUT ($BF), A : LD A, $80 : OUT ($BF), A  ; R0=$02 (M3=1)
        LD   A, $C2 : OUT ($BF), A : LD A, $81 : OUT ($BF), A  ; R1=$C2 (G2,IE)
        LD   A, $06 : OUT ($BF), A : LD A, $82 : OUT ($BF), A  ; R2=$06 -> $1800
        LD   A, $FF : OUT ($BF), A : LD A, $83 : OUT ($BF), A  ; R3=$FF
        LD   A, $03 : OUT ($BF), A : LD A, $84 : OUT ($BF), A  ; R4=$03 -> $0000
        LD   A, $36 : OUT ($BF), A : LD A, $85 : OUT ($BF), A  ; R5=$36 -> $1B00
        LD   A, $07 : OUT ($BF), A : LD A, $86 : OUT ($BF), A  ; R6=$07 -> $3800
        LD   A, $F5 : OUT ($BF), A : LD A, $87 : OUT ($BF), A  ; R7=$F5 (BASIC dflt)
        RET

2.15.2 Redefine a character pattern (Modes 0 and 1)

  2.15.2  Redefine a character pattern (Modes 0 and 1)
  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    In Mode 0 (Graphics I) and Mode 1 (Text), character patterns are stored
    at $1800 on the SC-3000.  Each character uses 8 consecutive bytes.
    Address of pattern for ASCII code C: $1800 + C * 8.

    Example: redefine ASCII '$' (code 36, $24) as a smiley face.
    New pixel data (8 rows x 8 bits):

      Row  Binary    Hex    Visual (. = 0, X = 1)
       0   00111100  $3C    ..XXXX..
       1   01000010  $42    .X....X.
       2   10100101  $A5    X.X..X.X
       3   10000001  $81    X......X
       4   10100101  $A5    X.X..X.X
       5   10011001  $99    X..XX..X
       6   01000010  $42    .X....X.
       7   00111100  $3C    ..XXXX..

    ; Redefine pattern for '$' ($24 = 36)
    ; Pattern address = $1800 + 36*8 = $1800 + $120 = $1920
    REDEF_DOLLAR:
        LD   A, $20          ; low byte of $1920
        OUT  ($BF), A
        LD   A, $19 | $40    ; = $59  (high byte | write flag)
        OUT  ($BF), A
        NOP : NOP
        LD   HL, SMILEY_DATA
        LD   B, 8
    RDLOOP:
        LD   A, (HL)
        OUT  ($BE), A
        INC  HL
        NOP : NOP : NOP : NOP : NOP
        DJNZ RDLOOP
        RET

    SMILEY_DATA:  DB $3C,$42,$A5,$81,$A5,$99,$42,$3C

    After calling REDEF_DOLLAR, every '$' character on screen immediately
    shows the new smiley shape (VRAM writes take effect on the next scan
    line).  Revert by restoring the original pattern from ROM (copy 8 bytes
    from ROM address $1920 to VRAM $1920 using the VRAMWR routine).

2.15.3 Define and place an 8x8 sprite (Screen 2 / Mode 3)

  2.15.3  Define and place an 8x8 sprite (Screen 2 / Mode 3)
  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    Sprites in Screen 2 (Mode 3) use two VRAM areas:
      Sprite pattern table  $3800  (R6=$07)   -- pixel patterns
      Sprite attribute table $1B00  (R5=$36)  -- position, number, colour

    Each sprite pattern: 8 bytes (one per row, bit7=leftmost pixel).
    Each attribute entry: 4 bytes per sprite (sprite 0 at $1B00, etc.):
      Byte 0: Y coordinate (0-191; $D0 = off-screen/hidden)
      Byte 1: X coordinate (0-255)
      Byte 2: pattern number (0-255 for 8x8; must be multiple of 4 for 16x16)
      Byte 3: bits 7=EC (early clock, shifts -32 px), bits 3-0=colour (1-15)

    Example: define sprite 0 as a small rocket and place it at X=120, Y=80.

    Rocket shape (8x8, points upward):
      Row  Binary    Hex
       0   00011000  $18    ...XX...
       1   00111100  $3C    ..XXXX..
       2   01111110  $7E    .XXXXXX.
       3   11111111  $FF    XXXXXXXX
       4   01111110  $7E    .XXXXXX.
       5   00100100  $24    ..X..X..
       6   00100100  $24    ..X..X..
       7   01100110  $66    .XX..XX.

    ; Step 1: write sprite pattern 0 to $3800
    DEF_ROCKET:
        LD   A, $00 : OUT ($BF), A
        LD   A, $38 | $40 : OUT ($BF), A  ; $78 = write to $3800
        NOP : NOP
        LD   HL, ROCKET_PAT
        LD   B, 8
    RKTLOOP:
        LD   A, (HL)
        OUT  ($BE), A
        INC  HL
        NOP : NOP : NOP : NOP : NOP
        DJNZ RKTLOOP
        RET

    ROCKET_PAT:  DB $18,$3C,$7E,$FF,$7E,$24,$24,$66

    ; Step 2: set sprite 0 attribute (Y=80, X=120, pattern=0, colour=15 white)
    ; Note: display starts at Y+1, so write Y=79 ($4F) to show at row 80.
    SET_ROCKET:
        LD   A, $00 : OUT ($BF), A
        LD   A, $1B | $40 : OUT ($BF), A  ; $5B = write to $1B00
        NOP : NOP
        LD   A, 79              ; Y = 79 -> displayed at row 80
        OUT  ($BE), A
        NOP : NOP : NOP : NOP : NOP
        LD   A, 120             ; X = 120
        OUT  ($BE), A
        NOP : NOP : NOP : NOP : NOP
        LD   A, 0               ; pattern number 0
        OUT  ($BE), A
        NOP : NOP : NOP : NOP : NOP
        LD   A, 15              ; colour 15 = white (no EC bit)
        OUT  ($BE), A
        RET

    To hide all 32 sprites (e.g. during initialisation), write $D0 to the
    Y byte of sprite 0:

    HIDE_SPRITES:
        LD   A, $00 : OUT ($BF), A
        LD   A, $5B : OUT ($BF), A         ; write to $1B00
        NOP : NOP
        LD   A, $D0                        ; $D0 = off-screen sentinel
        OUT  ($BE), A
        RET

    The TMS9918A stops processing the sprite list when it encounters $D0
    in a Y byte.  Writing $D0 to sprite 0's Y is sufficient to hide them all.

2.15.4 Define a 16x16 sprite

  2.15.4  Define a 16x16 sprite
  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    Enable 16x16 sprites by setting bit 1 (SI) of R1:
      For Screen 2 with 16x16 sprites: R1 = $C2 | $02 = $C6.

    A 16x16 sprite uses 4 consecutive 8-byte pattern blocks (N, N+1, N+2,
    N+3) where N must be a multiple of 4.  Layout of the 4 blocks:

      Block N+0:  rows 0-7,  columns 0-7   (top-left  quadrant)
      Block N+1:  rows 0-7,  columns 8-15  (top-right quadrant)
      Block N+2:  rows 8-15, columns 0-7   (bottom-left  quadrant)
      Block N+3:  rows 8-15, columns 8-15  (bottom-right quadrant)

    The attribute entry uses the base pattern number N (e.g. N=0, 4, 8...).

    Example: define a 16x16 smiley face starting at pattern slot 0.
    The four 8-byte blocks together form a 16x16 pixel image.

    ; Enable 16x16 sprite mode (modify R1)
    SET_16X16:
        LD   A, $C6 : OUT ($BF), A : LD A, $81 : OUT ($BF), A  ; SI=1
        RET

    ; Write all four 8x8 quadrants to $3800-$381F (patterns 0-3)
    DEF_SMILEY16:
        LD   A, $00 : OUT ($BF), A
        LD   A, $78 : OUT ($BF), A         ; write to $3800
        NOP : NOP
        LD   HL, SMILEY16_DATA
        LD   B, 32                          ; 4 x 8 bytes
    SM16LOOP:
        LD   A, (HL)
        OUT  ($BE), A
        INC  HL
        NOP : NOP : NOP : NOP : NOP
        DJNZ SM16LOOP
        RET

    ; Sprite 0 attribute: Y=80, X=100, pattern=0, colour=14 (grey)
    SET_SP16:
        LD   A, $00 : OUT ($BF), A
        LD   A, $5B : OUT ($BF), A
        NOP : NOP
        LD   A, 79  : OUT ($BE), A          ; Y
        NOP : NOP : NOP : NOP : NOP
        LD   A, 100 : OUT ($BE), A          ; X
        NOP : NOP : NOP : NOP : NOP
        LD   A, 0   : OUT ($BE), A          ; pattern 0 (base of 4-block set)
        NOP : NOP : NOP : NOP : NOP
        LD   A, 14  : OUT ($BE), A          ; colour 14 (grey)
        RET

    SMILEY16_DATA:
        ; Top-left (block 0, cols 0-7, rows 0-7)
        DB $07,$1F,$3F,$7F,$7F,$FF,$FF,$FF
        ; Top-right (block 1, cols 8-15, rows 0-7)
        DB $E0,$F8,$FC,$FE,$FE,$FF,$FF,$FF
        ; Bottom-left (block 2, cols 0-7, rows 8-15)
        DB $FF,$FF,$FF,$7F,$7F,$3F,$1F,$07
        ; Bottom-right (block 3, cols 8-15, rows 8-15)
        DB $FF,$FF,$FF,$FE,$FE,$FC,$F8,$E0

2.16 Undocumented mode combinations (experimental)

  2.16  Undocumented mode combinations (experimental)
  ----------------------------------------------------------------------------

    The TMS9918A decodes only 4 of the 8 possible M3/M2/M1 combinations.
    The remaining 4 produce undefined behaviour that varies between chip
    revisions and individual units.  The table below summarises observations
    from the MSX and SC-3000 hobbyist community (not guaranteed on all chips):

    M3  M2  M1   Typical observed behaviour
    --  --  --   -------------------------------------------------------
     0   1   1   Text mode wins; M2 is ignored.  Screen shows 40x24 text.
                 Border colour from R7 may render incorrectly on some chips.
     1   1   0   Graphics II pattern-table access but colour table addressing
                 follows the Multicolour formula.  Colour data looks corrupted.
                 Some chips show a stripe-distorted Graphics I image instead.
     1   0   1   Text mode wins; M1 overrides M3.  Sprites disabled.
                 Pattern addressing may use the Graphics II tables, giving
                 garbled text characters on some revisions.
     1   1   1   Text mode wins (M1 highest priority).  Screen is blank or
                 shows text with randomised pattern data.

    General rule: when M1=1, it tends to assert Text mode regardless of the
    other bits.  When M1=0 and M2=M3=1, the behaviour is most chip-dependent.

    EXPERIMENTAL TECHNIQUE: mid-frame mode switching
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    Changing R0 (M3) mid-frame by synchronising to the scanline counter
    (counted in the VBlank interrupt handler) allows different regions of
    the screen to use different modes.  Classic example: Graphics II for
    the game area + Text mode for a status bar.

    Skeleton (synchronise mode switch to a target scan line):

      ; In the VBlank handler -- count down to target line
      LD   A, (SCANLINE_CTR)
      DEC  A
      LD   (SCANLINE_CTR), A
      JR   NZ, VBLANK_DONE

      ; Target line reached: switch to Text for status bar (top 24 rows)
      LD   A, $00 : OUT ($BF), A : LD A, $80 : OUT ($BF), A  ; R0 M3=0
      LD   A, $D0 : OUT ($BF), A : LD A, $81 : OUT ($BF), A  ; R1 Text

      ; ... render status bar into text-mode VRAM ...

      ; At next VBlank, switch back to Graphics II
    VBLANK_DONE:
      RET

    Caveats:
      * Scan-line synchronisation requires tight interrupt timing; BASIC's
        interrupt handler adds jitter.  Use DI/EI to stabilise if needed.
      * The VDP latches the mode bits at the START of each frame.  On some
        chip revisions, mid-frame R1 writes only take effect on the NEXT
        frame.  Test on real hardware before relying on this behaviour.
      * On the SC-3000 emulator (web), mid-frame mode switching works only if
        the emulator processes VDP register writes between rendered scan lines.

CHAPTER 3 -- PSG -- SN76489AN (SOUND PROCESSOR)

CHAPTER 3 -- PSG -- SN76489AN (SOUND PROCESSOR)
============================================================================

3.1 Features

  3.1  Features
  ----------------------------------------------------------------------------

    Chip    : Texas Instruments SN76489AN
    Port    : $7F  (write-only -- no reading possible)
    Clock   : 3.579545 MHz  (same clock as the Z80)
    Channels: 3 tone channels (square wave) + 1 noise channel
    Volume  : 4 bits per channel (attenuation 0=maximum, 15=silent)
    Output  : mono, summed across channels

    The PSG is a write-only device: it is not possible to read the
    current register state via I/O. To track current values, keep
    them in RAM variables.

3.2 Command format

  3.2  Command format
  ----------------------------------------------------------------------------

    Each operation requires 1 or 2 bytes written to $7F.

    BYTE TYPE 1 -- LATCH (bit7=1): selects register and provides the
    low 4 bits of the data:
    +---+---+---+---+---+---+---+---+
    | 1 | r2| r1| r0| t |d3 |d2 |d1 |d0|
    +---+---+---+---+---+---+---+---+
      Bit 7    : 1 = latch byte
      Bits 6-5 : channel select (r2-r1-r0 = 3 bits -> see table below)
      Bit 4    : type (0=frequency, 1=volume)
      Bits 3-0 : low 4 bits of data

    BYTE TYPE 2 -- DATA (bit7=0): provides the high 6 bits of the
    frequency (used only for tone frequency registers, not for volume):
    +---+---+---+---+---+---+---+---+
    | 0 | x |d9 |d8 |d7 |d6 |d5 |d4 |
    +---+---+---+---+---+---+---+---+
      Bit 7    : 0 = data byte
      Bits 5-0 : high 6 bits of frequency divisor (d9-d4)

    Register map (bits 6-4 of the latch byte):
    +----------+----------------------------------------------------+
    | 000  (0) | Tone 1 -- frequency   (10 bits: latch d3-d0, data d9-d4)|
    | 001  (1) | Tone 1 -- volume      (4 bits: d3-d0; 0=max, 15=off)   |
    | 010  (2) | Tone 2 -- frequency                                     |
    | 011  (3) | Tone 2 -- volume                                        |
    | 100  (4) | Tone 3 -- frequency                                     |
    | 101  (5) | Tone 3 -- volume                                        |
    | 110  (6) | Noise  -- control     (see section 3.4)                 |
    | 111  (7) | Noise  -- volume      (4 bits; 0=max, 15=off)           |
    +----------+----------------------------------------------------+

    Example -- write volume=0 (maximum) on Tone 1 (register 1):
      LD   A, %10010000    ; bit7=1 latch, bits6-5=00 ch1, bit4=1 vol, d3-d0=0000
      OUT  ($7F), A        ; -> $90

    Example -- silence all channels:
      LD   A, $9F          ; Tone 1 vol = 15 (off)
      OUT  ($7F), A
      LD   A, $BF          ; Tone 2 vol = 15
      OUT  ($7F), A
      LD   A, $DF          ; Tone 3 vol = 15
      OUT  ($7F), A
      LD   A, $FF          ; Noise vol = 15
      OUT  ($7F), A

3.3 Tone channels -- frequency calculation

  3.3  Tone channels -- frequency calculation
  ----------------------------------------------------------------------------

    Formula:
      f_out = f_clock / (32 x N)
      N     = divisor (10 bits, range 1-1023; N=0 -> maximum frequency)
      f_clock = 3.579545 MHz

    Deriving N from a note:
      N = round( f_clock / (32 x f_note) )
      N = round( 3.579545e6 / (32 x f_note) )

    Practical examples:
      A4 (440 Hz)    ->  N = 3579545 / (32x440)  = 254.25  -> N=254
      C5 (523 Hz)    ->  N = 3579545 / (32x523)  = 213.8   -> N=214
      G4 (392 Hz)    ->  N = 3579545 / (32x392)  = 285.4   -> N=285

    Divisor N table for notes (octaves 3-6):
    +-------+------+------+------+------+
    | Note  | Oct3 | Oct4 | Oct5 | Oct6 |
    +-------+------+------+------+------+
    | C     |  853 |  426 |  213 |  107 |
    | C#    |  805 |  402 |  201 |  101 |
    | D     |  759 |  379 |  190 |   95 |
    | D#    |  716 |  358 |  179 |   90 |
    | E     |  676 |  338 |  169 |   85 |
    | F     |  638 |  319 |  160 |   80 |
    | F#    |  602 |  301 |  151 |   75 |
    | G     |  568 |  284 |  142 |   71 |
    | G#    |  536 |  268 |  134 |   67 |
    | A     |  506 |  253 |  127 |   63 |
    | A#    |  477 |  239 |  119 |   60 |
    | B     |  451 |  225 |  113 |   56 |
    +-------+------+------+------+------+

    Setting the frequency (2 bytes to $7F):
      ; Tone 1, frequency C4 (N=426 = $1AA)
      ; N bits: 9 8 7 6 5 4 | 3 2 1 0
      ;       : 0 1 1 0 1 0 | 1 0 1 0
      LD   A, %10000000 | ((426 & $F) )   ; = $8A  (latch, ch0, freq, lo4)
      OUT  ($7F), A
      LD   A, (426 >> 4) & $3F            ; = $1A  (data, hi6)
      OUT  ($7F), A

    Note: the BASIC SOUND c, f, v command uses the value f as frequency in Hz.
    The ROM internally converts Hz -> divisor N before writing to the PSG.
    The frequency table in Vol. B lists values in Hz (262=C4, 440=A4).

3.4 Noise channel

  3.4  Noise channel
  ----------------------------------------------------------------------------

    Register 6 (noise control) has this format in the latch byte:
    +-----------------------------------------------------------------+
    | Bits 3-2: NOT used (always 0)                                   |
    | Bit 1  : NF  noise type  0=periodic (tone), 1=white (random)   |
    | Bits 1-0: FB  shift rate:                                       |
    |              00 = N/512  (~7 kHz)                               |
    |              01 = N/1024 (~3.5 kHz)                             |
    |              10 = N/2048 (~1.75 kHz)                            |
    |              11 = Tone 3 frequency                              |
    +-----------------------------------------------------------------+

    Correspondence with BASIC SOUND c, f, v:
      c=4  ->  white noise (NF=1); f selects shift rate (0-2) or Tone3
      c=5  ->  periodic noise (NF=0); f selects shift rate

    Latch byte for noise control: $E0 | (NF << 2) | shift_rate
      SOUND 4,0  ->  white noise, shift 0   ->  $E4
      SOUND 4,1  ->  white noise, shift 1   ->  $E5
      SOUND 4,2  ->  white noise, shift 2   ->  $E6
      SOUND 4,3  ->  white noise + Tone3    ->  $E7
      SOUND 5,0  ->  periodic, shift 0      ->  $E0
      SOUND 5,1  ->  periodic, shift 1      ->  $E1

3.5 Direct access from BASIC with OUT

  3.5  Direct access from BASIC with OUT
  ----------------------------------------------------------------------------

    Using OUT to access the PSG bypasses the ROM: BASIC does not track
    values set manually. This is acceptable when managing audio entirely
    from ML or when using CALL.

    Example: tone A4 (440 Hz) on channel 1, maximum volume
      ; N = round(3579545 / (32x440)) = 254 = $FE -> lo4=$E, hi6=$0F
      10 OUT &H7F, &H8E    : REM Tone1 freq lo (ch1, freq, lo4=$E)
      20 OUT &H7F, &H0F    : REM Tone1 freq hi (hi6=$0F)
      30 OUT &H7F, &H90    : REM Tone1 volume = 0 (maximum)

    Example: silence all channels from BASIC
      10 OUT &H7F,&H9F : OUT &H7F,&HBF : OUT &H7F,&HDF : OUT &H7F,&HFF

    Note: after SOUND or BEEP, PSG registers contain the values set by
    the ROM. OUT overwrites only the specified registers.

3.6 Write protocol -- detailed behaviour (Maxim, 2003)

  3.6  Write protocol -- detailed behaviour  (Maxim, 2003)
  ----------------------------------------------------------------------------

    The SN76489 contains 8 internal registers:

      Register  Type     Width   Description
      --------  -------  ------  --------------------------------------
        0       Tone 1   10 bit  Frequency divisor, channel 1
        1       Vol  1    4 bit  Attenuation, channel 1  (0=max, F=off)
        2       Tone 2   10 bit  Frequency divisor, channel 2
        3       Vol  2    4 bit  Attenuation, channel 2
        4       Tone 3   10 bit  Frequency divisor, channel 3
        5       Vol  3    4 bit  Attenuation, channel 3
        6       Noise     4 bit  Control byte  (see section 3.4)
        7       Vol  N    4 bit  Attenuation, noise channel

    Two write formats exist.  The currently-latched register is retained
    between writes; a DATA byte always affects the last register selected
    by a LATCH byte.

    LATCH byte  %1 cc t dddd
      bit  7    : must be 1
      bits 6-5  : channel select  (cc: 00=ch1, 01=ch2, 10=ch3, 11=noise)
      bit  4    : type            (t:  0=frequency, 1=volume/control)
      bits 3-0  : low 4 data bits (dddd)

    DATA byte   %0 x dddddd
      bit  7    : must be 0
      bit  6    : ignored (x)
      bits 5-0  : high 6 data bits (for tone frequency registers only)

    Assembly of a 10-bit tone divisor (N):
      LATCH byte carries N bits 3-0  (LSB side)
      DATA  byte carries N bits 9-4  (MSB side)
      -> full N = (data_byte & $3F) << 4 | (latch_byte & $0F)

    IMPORTANT -- common emulator misconception:
      DATA bytes are NOT silently ignored when the latched register is a
      volume or noise register.  The chip still latches the DATA byte and
      applies its bits 5-0 to the register (which, being only 4 bits wide,
      takes bits 3-0 of the data byte).  Emulators that discard DATA bytes
      for non-tone registers will produce correct audio only by accident.

    Example -- set Tone 1 to N=300 ($12C):
      ; N bits: 9-4 = $0C = 001100   bits 3-0 = $C = 1100
      LD   A, %10001100    ; LATCH: ch1 freq, lo4=$C  -> $8C
      OUT  ($7F), A
      LD   A, %00001100    ; DATA:  hi6=%001100        -> $0C
      OUT  ($7F), A

    Example -- set Tone 2 volume to 7 (half attenuation):
      LD   A, %10110111    ; LATCH: ch2 vol, value=7   -> $B7
      OUT  ($7F), A
      ; No DATA byte needed for a volume register

3.7 Noise generator -- LFSR implementation (Maxim, 2003)

  3.7  Noise generator -- LFSR implementation  (Maxim, 2003)
  ----------------------------------------------------------------------------

    The noise channel uses a 16-bit linear feedback shift register (LFSR).
    On every clock tick (divided by the selected shift rate) the register
    is shifted right by one bit.  The bit that falls off becomes the output
    sample.  A new bit is fed into the MSB.

    PERIODIC noise (NF=0):
      The feedback bit is simply the bit that fell off the LSB end
      (i.e. the LFSR wraps around).  This produces a repeating pattern.

    WHITE noise (NF=1):
      The feedback bit is the XOR (parity) of selected "tapped" bit
      positions within the register.  This produces a pseudo-random
      sequence.

    SC-3000H tapped bits: 1 and 2
    (verified from MacDonald's hardware analysis)

    LFSR initial value: $8000  (on hardware reset)
    LFSR width       : 16 bits

    Correct pseudocode for one noise clock step (white noise, SC-3000H):

      feedback = parity( lfsr & $0006 )   ; bits 1 and 2
      lfsr     = (lfsr >> 1) | (feedback << 15)
      output   = lfsr & 1                  ; before shift: lfsr & 1

    where parity(x) = x ^ (x >> 8) ^ (x >> 4) ^ (x >> 2) ^ (x >> 1),
    reduced to a single bit (LSB of result).

    WRONG approach (produces incorrect output):
      feedback = lfsr & 1  (then XOR with a fixed constant)
    This common error generates a different sequence and must be avoided
    in emulators targeting SC-3000H compatibility.

3.8 Initial state and SC-3000 boot behaviour (Maxim, 2003)

  3.8  Initial state and SC-3000 boot behaviour  (Maxim, 2003)
  ----------------------------------------------------------------------------

    On hardware reset the SN76489 initialises as follows:

      All volume registers   : $F  (fully attenuated -- all channels silent)
      All tone registers     : $000 (divisor 0)
      Noise register         : $00

    SC-3000 note: the ROM does not explicitly write to all PSG registers
    at startup, so the PSG may be in an undefined state if the system
    was previously running a cartridge.  Code that
    relies on a clean PSG state should always silence all channels and
    reset all frequency registers explicitly:

      ; Initialise PSG: silence all channels, clear Tone 1
      LD   A, $9F : OUT ($7F), A   ; Vol 1 = $F (off)
      LD   A, $BF : OUT ($7F), A   ; Vol 2 = $F (off)
      LD   A, $DF : OUT ($7F), A   ; Vol 3 = $F (off)
      LD   A, $FF : OUT ($7F), A   ; Vol N = $F (off)
      LD   A, $80 : OUT ($7F), A   ; Tone 1 freq latch = 0
      LD   A, $00 : OUT ($7F), A   ; Tone 1 freq data  = 0
      LD   A, $A0 : OUT ($7F), A   ; Tone 2 freq latch = 0
      LD   A, $00 : OUT ($7F), A   ; Tone 2 freq data  = 0
      LD   A, $C0 : OUT ($7F), A   ; Tone 3 freq latch = 0
      LD   A, $00 : OUT ($7F), A   ; Tone 3 freq data  = 0

3.9 Signal imperfection -- output voltage decay (Maxim, 2003)

  3.9  Signal imperfection -- output voltage decay  (Maxim, 2003)
  ----------------------------------------------------------------------------

    The SN76489 does not generate a perfect square wave.  The output
    voltage of each channel decays toward 0 V between transitions.  This
    means that:

      * Very low-frequency tones sound slightly distorted (the waveform
        is not symmetric).
      * The effective DC level of a playing channel is not 0.

    In practice this is inaudible at normal music and SFX frequencies
    (above ~50 Hz) and rarely affects SC-3000 software.

3.10 Sample playback techniques (Maxim, 2003)

  3.10  Sample playback techniques  (Maxim, 2003)
  ----------------------------------------------------------------------------

    The SN76489 can approximate digital audio despite being a programmable
    sound generator.  Two techniques are used in practice:

    PWM -- Pulse Width Modulation  (1-bit resolution)
      1. Set all three tone channels to frequency divisor N=0 (maximum
         frequency: ~112 kHz, above audible range).
      2. Alternate volume between 0 (maximum) and $F (silence) on a tone
         channel at the desired sample rate.
      The rapidly-toggled channel acts as a 1-bit DAC; low-pass filtering
      (inherent in the speaker) produces the audio signal.

      ; Toggle Tone 1 between max and silent (called at sample rate):
        LD  A, $90 : OUT ($7F), A   ; Vol 1 = 0 (on)
        ; ... wait sample period ...
        LD  A, $9F : OUT ($7F), A   ; Vol 1 = $F (off)

    PCM -- 4-bit DAC via volume register
      Use a single channel's volume register as a 4-bit output value.
      Update it at the sample rate with successive 4-bit samples.
      The volume register steps are not perfectly linear in amplitude
      (they are approximately 2 dB apart), but the result is intelligible
      for voice or simple waveforms.

      Effective sample rate is limited by the CPU time available to
      continuously write to the PSG.  At 3.579 MHz, a practical upper
      limit for BASIC is ~5-8 kHz; assembly routines can reach ~20 kHz.

3.11 Credits -- SN76489 extended notes

  3.11  Credits -- SN76489 extended notes
  ----------------------------------------------------------------------------

    Sections 3.6-3.10 are based on:

      "SN76489 notes"
      by Maxim  (maxim@mwos.cjb.net)
      Last updated: 21 April 2003

    Used for educational and documentation purposes.
    Original document: http://www.smspower.org/Development/SN76489

CHAPTER 4 -- PPI -- INTEL 8255 (PERIPHERAL INTERFACE)

CHAPTER 4 -- PPI -- INTEL 8255 (PERIPHERAL INTERFACE)
============================================================================

4.1 Features

  4.1  Features
  ----------------------------------------------------------------------------

    Chip     : Intel 8255A (or compatible)
    Ports    : $DC (Port A), $DD (Port B), $DE (Port C), $DF (Control)
    Function : interface between the Z80 CPU and physical peripherals:
               keyboard matrix, cassette CMT, serial printer, joystick

    Port configuration (input/output) is set by the ROM at initialisation
    via the Control Register ($DF).

4.2 Port configuration in the SC-3000

  4.2  Port configuration in the SC-3000
  ----------------------------------------------------------------------------

    Based on ROM disassembly analysis (KEYREAD $4918, keyboard scan $48A3):

    Port A ($DC)  INPUT    Reads keyboard data (matrix rows, columns 0-5)
    Port B ($DD)  INPUT    Reads keyboard data (matrix rows, columns 4-7),
                           joystick, and CMT data read (bit 7)
    Port C ($DE)  OUTPUT   Bits 2-0: keyboard column select
                           Bit 3:    CMT motor relay
                           Bit 4:    CMT data write
                           Bits 7-5: serial printer (data/reset/feed)

    Note: the exact Port C configuration (which nibble is input/output)
    depends on the value written to the Control Register at startup.

4.3 Port C ($DE) -- bit map

  4.3  Port C ($DE) -- bit map
  ----------------------------------------------------------------------------

    Bit  Dir   Signal          Description
    ---  ----  --------------  -----------------------------------------
     0   OUT   KEY_COL0        Keyboard column select (bit 0)
     1   OUT   KEY_COL1        Keyboard column select (bit 1)
     2   OUT   KEY_COL2        Keyboard column select (bit 2)
     3   OUT   CMT_MOTOR       Cassette motor relay (1=on, 0=off)
     4   OUT   CMT_WRITE       Cassette data write (FSK serial bit)
     5   OUT   PRINTER_DATA    Serial printer data  (DIN pin 3)
     6   OUT   PRINTER_RESET   Serial printer reset (DIN pin 4)
     7   OUT   PRINTER_FEED    Serial printer feed  (DIN pin 5)

    (CMT data READ is on Port B bit 7 / $DD, not on Port C.)

    From KEYREAD disassembly ($4918):
      IN  A,($DE)     ; read current Port C
      AND $F8         ; preserve bits 7-3, clear bits 2-0 (column=0)
      LD  C, A        ; save state (with column=0)
      LD  B, 6        ; 6 iterations (columns 0-5)
    loop:
      OUT ($DE), C    ; set current column
      CALL $4964      ; read row data (IN A,($DC)) -> A
      LD  (HL), A     ; save in KEYIM[i]
      INC C           ; next column (bits 2-0 increment 0->1->2...->5)
      INC HL
      DJNZ loop

4.4 Keyboard matrix

  4.4  Keyboard matrix
  ----------------------------------------------------------------------------

    Structure: 8 columns x 8 rows = 64 key positions
    Column select: bits 2-0 of Port C (values 0-7)
    Row read: Port A ($DC) for columns 0-5, Port B ($DD) for 5-7
    Logic: bit=0 -> key pressed (inverted logic -- pull-up resistors)

    RAM image (updated by the interrupt handler every frame):
      KEYIM0 ($9460) -- column 0 data
      KEYIM1 ($9461) -- column 1 data
      KEYIM2 ($9462) -- column 2 data
      KEYIM3 ($9463) -- column 3 data
      KEYIM4 ($9464) -- column 4 data
      KEYIM5 ($9465) -- column 5 data
      KEYIM6 ($9466) -- column 6 data (joystick P1 directions)
      KEYIM7 ($9467) -- column 7 data (joystick P2 directions)
      KEYIMC ($9468) -- control byte (joystick P1/P2 buttons, BREAK)

    Keyboard matrix layout (from ROM analysis -- SC-3000H/SF-7000 full keyboard):
    +-----+------------------------------------------------------------+
    | Col |  Bit7   Bit6   Bit5   Bit4   Bit3   Bit2   Bit1   Bit0    |
    +-----+------------------------------------------------------------+
    |  0  |  I      K      ,      Kana   Z      A      Q      1       |
    |  1  |  O      L      .      SPC    X      S      W      2       |
    |  2  |  P      ;      /      CLR    C      D      E      3       |
    |  3  |  @      :      RO     DEL    V      F      R      4       |
    |  4  |  [      ]      v      --      B      G      T      5       |
    |  5  |  --      CR     <-      --      N      H      Y      6       |
    |  6  |  --      ^      ->      --      M      J      U      7       |
    |  7  |  8      9      0      -      ^      \      GRP    BREAK   |
    +-----+------------------------------------------------------------+
    Bit=1 after CPL (in KEYREAD) = key PRESSED.

    NOTE: this layout is for the SC-3000H/SF-7000 keyboard. On the base
    SC-3000 (reduced chicklet keyboard) many positions correspond to
    different keys or to connected joysticks. Columns 6-7 in particular
    share bits with joystick P1/P2 direction inputs (on the directional
    arrow pins).

    Reading a specific key from ML:
      ; Example: read key '1' (column 0, bit 1)
      CALL $4A00         ; KEYREAD -- update KEYIM0-8
      LD   A, ($9460)    ; KEYIM0 -- column 0
      BIT  1, A          ; bit 1 = key '1'
      JR   Z, pressed    ; Z=1 if bit=0 -> key pressed

    Or simply use INKEYS (CALL $4A36) or KEYIN1 ($4A51).

4.5 BRKFLG and SPCFLG -- special keys

  4.5  BRKFLG and SPCFLG -- special keys
  ----------------------------------------------------------------------------

    BRKFLG ($9469): BREAK key flag
      0 = not pressed
      1 = pressed (set by the KEYREAD routine / interrupt handler)
    Check with: CALL $4A03 (BRKCHK) -> Cf=1 if pressed

    SPCFLG ($946A): SPACE key flag
      Similar to BRKFLG.
    Check with: CALL $4A06 (SPCCHK) -> Cf=1 if pressed

4.6 Cassette interface (CMT -- Cassette Magnetic Tape)

  4.6  Cassette interface (CMT -- Cassette Magnetic Tape)
  ----------------------------------------------------------------------------

    The physical signal is FSK audio (Frequency Shift Keying):
      bit '1' -> 2400 Hz  (H tone)
      bit '0' -> 1200 Hz  (L tone)
      Baud rate : 1200 baud
    The analogue comparator converts the audio signal to the digital bit
    read by CMT_READ (Port B bit 7, I/O $DD).

    Electrical levels (3.5 mm 2P mini-jack):
      Output : 20 mV P-P  ±5 mV   (to cassette recorder MIC input)
      Input  : >= 1 V P-P          (from cassette recorder EAR output)

    Signals involved:
      CMT_MOTOR (Port C bit 3): 1=motor on, 0=motor off
      CMT_WRITE (Port C bit 4): write data bit (FSK-modulated)
      CMT_READ  (Port B bit 7): read data bit (from comparator)

    Data structure on tape:
    +------------------------------------------------------------------+
    | Leader    : 3600 x bit '1' (approx. 1.5 sec -- read sync)       |
    | Sync byte : $16 (or similar)                                    |
    | Header    : filename (16 ASCII bytes) + type + length            |
    | Leader 2  : second sequence of '1's                             |
    | Data      : tokenised program bytes                             |
    | Checksum  : verification byte                                   |
    +------------------------------------------------------------------+

    Motor control from BASIC:
      MOTOR 0      motor OFF (Port C bit 3 = 0)
      MOTOR 1      motor ON  (Port C bit 3 = 1)   ; any non-zero value
      MOTOR        toggle

    Direct ML motor control (as used by the ROM, $7EC8/$7ED1):
      IN   A, ($DE)
      OR   $08         ; set bit 3 -> motor ON
      OUT  ($DE), A
      ; --- or ---
      IN   A, ($DE)
      AND  $F7         ; clear bit 3 -> motor OFF
      OUT  ($DE), A

    ROM routines for CMT (Vol. D ch. 6):
      BYTERD  ($3A06)  -- read 1 byte from tape -> A
      BYTEWD  ($3A12)  -- write 1 byte A to tape
      LEADSR  ($3A00)  -- wait for leader signal (256 x '1')
      LEADWR  ($3A0F)  -- write leader signal (3600 x '1')
      WVSCAN  ($39FD)  -- FSK waveform scan
      CDELAY  ($3A03)  -- calibrated delay: 279 ns x C

4.7 Serial printer interface

  4.7  Serial printer interface
  ----------------------------------------------------------------------------

    The protocol used is synchronous serial with strobe.

    Signals involved:
      PRINTER_STROBE (Port C bit 7): handshake pulse
      Data: sent via Port A or Port B (verify with LPRINT handler
             disassembly)

    BASIC redirects LPRINT by setting PUTADR ($82A0) = $7008.
    The routine at $7008 manages the physical handshake with the printer.

    Checking printer busy: see 7.4 -- BUSY is Port B bit 6 (PB6),
    read with IN A,($DD) / BIT 6,A.

    (An earlier draft attributed this to a "BRKCHK" routine at $48A3.
    That was wrong twice over: $48A3 is NOT BRKCHK -- the real break-check
    is $5933, reached via SYS $4A03 -- and $48A3 does not read printer
    status at all. $48A3 is a keyboard key-scanner: it selects keyboard
    columns 5 and 6 in turn and tests Port B bit 3 (key-row data):
      IN   A, ($DE)
      AND  $F8
      OR   $05        ; select keyboard column 5
      OUT  ($DE), A
      CALL $4972      ; settle delay
      IN   A, ($DD)   ; Port B: bit 3 = key-row data, NOT printer busy
      BIT  3, A
    )

4.8 Port C bit 3 -- cassette motor (NO PSG "sound gate")

  4.8  Port C bit 3 -- cassette motor (NO PSG "sound gate")
  ----------------------------------------------------------------------------

    Earlier drafts labelled Port C bit 3 a PSG "sound gate". ROM analysis
    disproves this: an exhaustive scan shows the ONLY routines that touch
    bit 3 are the cassette-motor primitives $7EC8/$7ED1/$7EDA (see 4.6).
    The SN76489 PSG has no hardware gate on the PPI -- audio is silenced
    by writing the volume/attenuation registers on port $7F (see Chapter 3).

    So bit 3 is the cassette motor relay:
      IN   A, ($DE)
      OR   $08         ; set bit 3 -> cassette motor ON
      OUT  ($DE), A

CHAPTER 5 -- JOYSTICK

CHAPTER 5 -- JOYSTICK
============================================================================

5.1 D-Sub 9-pin connector

  5.1  D-Sub 9-pin connector
  ----------------------------------------------------------------------------

    Standard Atari/Sega pinout (compatible with Mega Drive):
    Active-low signals (contact with GND = pressed).

    Pin  Signal    RAM image      Description
    ---  --------  -------------  -------------------------
      1  UP        KEYIM6 bit 6   P1 up    (same bit as ^ on SC-3000H)
      2  DOWN      KEYIM4 bit 5   P1 down  (same bit as v on SC-3000H)
      3  LEFT      KEYIM5 bit 5   P1 left  (same bit as <- on SC-3000H)
      4  RIGHT     KEYIM6 bit 5   P1 right (same bit as -> on SC-3000H)
      5  (VCC)     --              +5V power supply
      6  TRG1      KEYIMC bit 0   P1 button 1 (KANA position on SC-3000H)
      7  (n.c.)    --
      8  GND       --              Ground
      9  TRG2      KEYIMC bit 2   P1 button 2 (CTRL position on SC-3000H)

    Joystick P2: directions in KEYIM7 (bit6=UP, bit5=RIGHT etc.);
    buttons TRG1 -> KEYIMC bit 1 (FUNC), TRG2 -> KEYIMC bit 3 (SHIFT).

    KEYIMC ($9468) -- modifier keys / fire buttons byte:
    +------+--------------------------------------------------------+
    | Bit  | SC-3000H keyboard  | SC-3000 base (joystick connected) |
    +------+--------------------+-----------------------------------+
    |  0   | KANA               | TRG1 P1                           |
    |  1   | FUNC               | TRG1 P2                           |
    |  2   | CTRL               | TRG2 P1                           |
    |  3   | SHIFT              | TRG2 P2                           |
    |  7   | CTRL (dup.)        | --                                 |
    +------+--------------------+-----------------------------------+
    Bit=1 = key pressed (active-high logic, after CPL in KEYREAD).
    Built by KEYREAD: bit0=~PortA_col0_bit4, bit1=~PortB_col5_bit0,
    bit2=~PortB_col6_bit2, bit3=~PortB_col6_bit3.

    NOTE: the TRG1/TRG2 correspondence requires confirmation on physical
    hardware.

5.2 STICK() and STRIG() -- returned values

  5.2  STICK() and STRIG() -- returned values
  ----------------------------------------------------------------------------

    STICK(f)  -- f=1 joystick P1, f=2 joystick P2
    +---+---------------+   +---+----------------------+
    | 0 | no direction  |   | 5 | down                 |
    | 1 | up            |   | 6 | down-left            |
    | 2 | up-right      |   | 7 | left                 |
    | 3 | right         |   | 8 | up-left              |
    | 4 | down-right    |   +---+----------------------+
    +---+---------------+

    STRIG(f)  -- f=1 joystick P1, f=2 joystick P2
      0 = no button
      1 = button 1 (TRG1)
      2 = button 2 (TRG2)
      3 = both buttons

5.3 Direct read with INP()

  5.3  Direct read with INP()
  ----------------------------------------------------------------------------

    Joystick directions are in bytes KEYIM6 ($9466) and KEYIM7 ($9467),
    updated by the interrupt handler. Reading from BASIC:

      10 J = PEEK(&H9466)       : REM P1 directions (active bits=0)
      20 IF (J AND 1)=0 THEN PRINT "UP"
      30 IF (J AND 2)=0 THEN PRINT "DOWN"
      40 IF (J AND 4)=0 THEN PRINT "LEFT"
      50 IF (J AND 8)=0 THEN PRINT "RIGHT"

    Safe alternative: STICK(1) and STRIG(1) use the already-updated
    RAM image and return processed values.

    For the most responsive read (bypasses the RAM image):
      CALL $4A00        ; KEYREAD -- immediately update KEYIM0-8
      LET J = PEEK(&H9466)  ; then read KEYIM6

CHAPTER 6 -- SC-3000H HARDWARE NOTES

CHAPTER 6 -- SC-3000H HARDWARE NOTES
============================================================================

  Source: Charles MacDonald, 2002-2004.
  Original title: "Sega SC-3000H hardware notes".
  Reproduced with attribution (see #6.9). Last revision: 2004-07-29.

  This chapter consolidates hardware-level information about the SC-3000H
  that complements the other chapters of this volume. It covers the physical
  components, connector pinouts, cartridge memory maps, port decoding quirks,
  the complete keyboard matrix, the MITEC-2 custom chip, and the cassette
  interface chip.

6.1 Component list

  6.1  Component list
  ----------------------------------------------------------------------------

    Manufacturer       Part                Description
    -----------------  ------------------  ----------------------------------
    Fujitsu            MB82128-15          2Kx8 SRAM (Z80 work RAM)
    Fujitsu            MB74LS145           1-of-10 decoder (keyboard row sel.)
    Mitsubishi         MN74HC04            Hex inverter (clock divider)
    Motorola           MCM4517P15 (x8)     16Kx1 Page Mode DRAM (VDP VRAM)
    NEC                uPD9255AC-2         Programmable peripheral interface
    SEGA               MITEC-2             Custom chip (Z80 address decoding)
    Sharp              LH0080A             CPU (Z80-A compatible)
    Texas Instruments  SN76489AN           Programmable sound generator
    Texas Instruments  TMS9929ANL          Video display processor (PAL)

    Both SN76489A and TMS9929A are the 'A' revision:
      * TMS9929 (no suffix) lacks Graphics II mode -- added in TMS9929A.
      * SN76489 (no suffix) has a different noise generator than SN76489A.

    Clock:
      Master clock: 10.738635 MHz -- used directly by TMS9929A.
      The hex inverter (MN74HC04) plus analog components divides the master
      by 3 to produce the Z80 and SN76489A clock of 3.579545 MHz.

6.2 Physical connectors

  6.2  Physical connectors
  ----------------------------------------------------------------------------

6.2.1 Joystick connectors (male DB9 on back panel)

  6.2.1  Joystick connectors (male DB9 on back panel)

    Pin diagram:

      1 2 3 4 5
       6 7 8 9

    Pin  Signal
    ---  ------------------------------------------------------------------
     1   Joystick up
     2   Joystick down
     3   Joystick left
     4   Joystick right
     5   Unused (not connected)
     6   Trigger left
     7   Unused (not connected)
     8   Common (active-LOW when row 7 of keyboard matrix selected)
     9   Trigger right

    Pin 8 is the common side of all joystick switches. It is driven LOW by
    output line 7 of the 74LS145 when row 7 is selected (enabling joystick
    input) and HIGH otherwise.

    Caution: this differs from SMS/Genesis which use pin 8 as GND and pin 5
    as +5V. Original SMS joypads work, but autofire, programmable, or
    Genesis/MegaDrive pads are not guaranteed to be compatible.

6.2.2 Printer connector (female 7-pin DIN on back panel)

  6.2.2  Printer connector (female 7-pin DIN on back panel)

    Pin diagram:

         1       7
        2         6
          3     5
             4

    Pin  Signal
    ---  ------------------------------------------------------------------
     1   Unused (not connected)
     2   PPI PC5 (DATA output)
     3   PPI PC7 (/FEED output)
     4   PPI PB6 (BUSY input)
     5   PPI PC6 (/RESET output)
     6   PPI PB5 (FAULT input)
     7   GND

6.2.3 Audio/Video connector (female 5-pin DIN on back panel)

  6.2.3  Audio/Video connector (female 5-pin DIN on back panel)

    NTSC and PAL composite models:

    Pin diagram:

        1         5
          2     4
             3

    Pin  Signal
    ---  ------------------------------------------------------------------
     1   Audio out
     2   Ground
     3   Composite video out
     4   Ground
     5   Ground

    Signal levels (from SC-3000 service manual):
      Audio     : load >= 10 kOhm; level 1 V max +0.2 V
      Composite : recommended load 75 Ohm; level 0.9 Vp-p +0.1 V

    Note: MacDonald (2002) lists pin 1 as composite and a "pin 6" as audio.
    The 5-pin DIN has no pin 6; the Sega service manual assignment above is
    from the primary source and is considered authoritative.

    Note: a US SMS 2 power supply and standard Genesis/MegaDrive composite
    A/V cable (composite + mono audio) are compatible with the SC-3000H.

  6.2.3b  RGB connector (female 8-pin DIN -- PAL/RGB models only)

    The SC-3000 PAL and RGB variants use an 8-pin DIN for RGB output.
    This connector is NOT present on the standard NTSC composite SC-3000.

    Pin  Signal
    ---  ------------------------------------------------------------------
     1   Audio
     2   Ground
     3   Composite sync (SYNC)
     4   Horizontal sync (HSYNC)
     5   Vertical sync (VSYNC)
     6   Red (R)
     7   Green (G)
     8   Blue (B)

6.2.4 Internal keyboard ribbon cable (CN7 and CN8)

  6.2.4  Internal keyboard ribbon cable (CN7 and CN8)

    Pin numbering left (1) to right (11) for both connectors.

    CN7
    Pin  Signal
    ---  ------------------------------------------------------------------
     1   74LS145 output line 0   (keyboard row select, output)
     2   74LS145 output line 1
     3   74LS145 output line 2
     4   74LS145 output line 3
     5   74LS145 output line 4
     6   74LS145 output line 5
     7   74LS145 output line 6
     8   PPI PA0                 (keyboard data, input)
     9   PPI PA1
    10   PPI PA2
    11   PPI PA3

    CN8
    Pin  Signal
    ---  ------------------------------------------------------------------
     1   PPI PA4
     2   PPI PA5
     3   PPI PA6
     4   PPI PA7
     5   PPI PB0
     6   PPI PB1
     7   PPI PB2
     8   PPI PB3
     9   Ground
    10   Not used by keyboard (connected to power switch)
    11   RESET key -> MITEC-2 pin 3 (NMI-IN)

    CN7 pins 1-7: outputs to keyboard row select (74LS145).
    CN7 pins 8-11 and CN8 pins 1-8: inputs with pull-up resistors for key data.

6.2.5 Cartridge connector (44-pin)

  6.2.5  Cartridge connector (44-pin)

    Solder side (A-row)
    ----------------------------------------------------------------------------
    A01-A14 : Z80 address bus A0-A13
    A15-A22 : Z80 data bus D0-D7

    Components side (B-row)
    Pin   Signal
    ----  ----------------------------------------------------------------
    B01   +5V
    B02   +5V
    B03   Work RAM /CS (pull-up; tied HIGH disables work RAM at $C000-$FFFF)
    B04   /EXM1 (chip select for $8000-$BFFF)
    B05   MEMORY /RD (/MREQ AND /RD)
    B06   MEMORY /WR (/MREQ AND /WR)
    B07   I/O PORT /RD (/IORQ AND /RD)
    B08   I/O PORT /WR (/IORQ AND /WR)
    B09   Unused
    B10   Z80 /MREQ
    B11   /CONT (PPI PB4 via pull-up)
    B12   /RAS0 (DRAM row address strobe, bank 0)
    B13   /CAS0 (DRAM column address strobe, bank 0)
    B14   CA7 (DRAM column address bit 7)
    B15   /RAS1 (DRAM row address strobe, bank 1)
    B16   /CAS1 (DRAM column address strobe, bank 1)
    B17   /RCSEL (row/column select: 0=row, 1=column)
    B18   Z80 A14
    B19   Z80 A15
    B20   Unused
    B21   GND
    B22   GND

6.3 Cartridge memory maps

  6.3  Cartridge memory maps
  ----------------------------------------------------------------------------

    The MITEC-2 provides /CS for $8000-$BFFF (/EXM1) and $C000-$FFFF
    (work RAM). The rest of the decoding is up to the cartridge.
    Unused address ranges return the high byte of the address bus
    (e.g. reading $C1AB returns $C1).

6.3.1 Safari Hunting

  6.3.1  Safari Hunting

    16K ROM ($0000-$3FFF, mirrored at $4000-$7FFF)
    2K RAM ($8000-$BFFF, mirrored every 2K)
    2K Work RAM ($C000-$FFFF, mirrored every 2K)

    ROM /OE = Z80 A15 (ROM enabled when A15=0), ROM /CS = /MREQ.
    RAM /CS = /EXM1 (B04).

6.3.2 BASIC Level III A

  6.3.2  BASIC Level III A

    32K ROM ($0000-$7FFF)
    16K DRAM ($8000-$BFFF, 2x TMS4416)
    2K Work RAM ($C000-$FFFF, mirrored every 2K)
    -- or with work RAM disabled and remaining DRAM populated:
    32K DRAM ($C000-$FFFF, latter 16K)

    ROM /OE = B05 (MEMORY /RD), ROM /CS = 74LS32 gate (enabled when
    Z80 A15=0 and /MREQ active).

6.3.3 Other cartridge types (from external sources)

  6.3.3  Other cartridge types (from external sources)

    Terebi Oekaki
      $0000-$1FFF : ROM
      $6000       : Graphics tablet axis select
      $8000       : Pen pressure / axis conversion busy flag
      $A000       : Axis data

    SF-7000
      $0000-$3FFF : ROM (8K, mirrored)
      $0000-$FFFF : RAM (48K from $4000, or 64K from $0000 after ROM swap)
      On power-up, $0000-$3FFF is ROM; the SF-7000's PPI can swap it out.

6.3.4 DRAM interface (MITEC-2 output signals)

  6.3.4  DRAM interface (MITEC-2 output signals)

    MITEC pin  Cartridge pin  Signal   Description
    ---------  -------------  -------  ----------------------------------
    19         B14            CA7      DRAM column address bit 7
    20         B17            /RCSEL   Row/column select (0=row, 1=column)
    21         B12            /RAS0    Row address strobe, bank 0
    22         B15            /RAS1    Row address strobe, bank 1
    23         B13            /CAS0    Column address strobe, bank 0
    24         B16            /CAS1    Column address strobe, bank 1

    Address multiplexing for TMS4416 16Kx4 DRAMs:

    DRAM pin   Row (/RCSEL=0)   Column (/RCSEL=1)
    --------   --------------   -----------------
    A0         GND              Z80 A0
    A1         Z80 A8           Z80 A1
    A2         Z80 A9           Z80 A2
    A3         Z80 A10          Z80 A3
    A4         Z80 A11          Z80 A4
    A5         Z80 A12          Z80 A5
    A6         Z80 A13          Z80 A6
    A7         GND              CA7

    Bits 0 and 7 of the row are unused (GND) to allow expansion to 64Kx4
    DRAMs, which use those bits for address bits 14 and 15.

6.3.5 Cartridge bus timing (from SC-3000 service manual, Fig. 2-1 -- 2-3)

  6.3.5  Cartridge bus timing  (from SC-3000 service manual, Fig. 2-1 -- 2-3)

    These are the timing constraints that cartridge hardware must satisfy.
    All values in nanoseconds; scale 10 ns/mm in the original timing charts.

    M1 cycle (opcode fetch):
      ROM / SRAM  /OE  assertion to data valid : 270 ns max
      ROM         access time from /MREQ       : 375 ns max
      DRAM        tRAC                         : 290 ns max
      DRAM        tCAC                         : 140 ns max

    Memory read/write cycle:
      ROM / SRAM  /OE  assertion to data valid : 370 ns max
      SRAM        tWP (write pulse width)      : 290 ns max
      ROM / SRAM  access time from /MREQ       : 520 ns max
      SRAM        from /WE to data valid        : 670 ns max
      DRAM        tRAC                         : (not specified separately)
      DRAM        tCAC                         : 255 ns max

    I/O read/write cycle:
      I/O access time (device must respond)    : 545 ns max

6.4 I/O port decoding

  6.4  I/O port decoding
  ----------------------------------------------------------------------------

    The PPI, VDP, and PSG are enabled according to the port address range:

    Range    PPI  VDP  PSG  Data returned on read
    -------  ---  ---  ---  ----------------------------------------------
    $00-1F    Y    Y    Y   PPI + VDP (some bits corrupted -- bus conflict)
    $20-3F    N    Y    Y   VDP
    $40-5F    Y    N    Y   PPI
    $60-7F    N    N    Y   Last value from memory refresh cycle (see #6.5)
    $80-9F    Y    Y    N   PPI + VDP (bus conflict)
    $A0-BF    N    Y    N   VDP        ($BE = data, $BF = status/register)
    $C0-DF    Y    N    N   PPI        ($DC/$DD/$DE/$DF -- see Ch. 4)
    $E0-FF    N    N    N   Last value from memory refresh cycle (see #6.5)

    For write operations, data goes to all enabled devices simultaneously.
    The PSG has no readable registers; its presence does not affect read data.
    When PPI and VDP are both enabled, the PPI drives the bus but some bits
    are corrupted by the VDP attempting to drive simultaneously.

6.5 Z80 memory refresh behavior

  6.5  Z80 memory refresh behavior
  ----------------------------------------------------------------------------

    During every opcode fetch the Z80 performs an automatic DRAM refresh
    cycle (T3/T4 states): /RFSH goes low, the IR register pair drives the
    address bus, and /MREQ pulses low. This looks like a memory read but
    uses /RFSH instead of /RD.

    Most SC-3000 cartridges wire ROM /OE = /MREQ and ROM /CS = A15, so the
    ROM responds to refresh cycles in $0000-$7FFF and places data on the bus.
    The MITEC-2 does not enable work RAM during refresh, so reads to
    $C000-$FFFF during refresh return the last value left on the data bus.

    This directly affects ports $60-$7F and $E0-$FF: no device drives the
    bus for those reads, so the value returned is whatever was left on the
    bus from the preceding refresh cycle.

    Example (port $E0 read, IR=$2A01, ROM data at $2A00 = $AB, $CD, $EF):

      Opcode fetch #1 ($ED): refresh reads $CD from $2A01
      Opcode fetch #2 ($78): refresh reads $EF from $2A02
      I/O read from $E0:     no device drives bus -> returns $EF

    PSG write address ($7F) falls in the $60-$7F range. The PSG is write-
    only; reading $7F returns the memory refresh value, not PSG data.

6.6 Keyboard matrix and PPI ports

  6.6  Keyboard matrix and PPI ports
  ----------------------------------------------------------------------------

6.6.1 PPI port assignments (see Ch. 4 for register-level detail)

  6.6.1  PPI port assignments (see Ch. 4 for register-level detail)

    Port A (input) -- PA7-PA0 with pull-up resistors, active-low
      All 8 bits: keyboard/gamepad row data

    Port B (input) -- PB6-PB0 pull-up active-low; PB7 active-high
      D7 : Cassette-IN signal (active-high)
      D6 : BUSY from printer port
      D5 : FAULT from printer port
      D4 : /CONT from cartridge connector pin B11
      D3-D0 : keyboard/gamepad row data

    Port C (output)
      D7 : /FEED output -> printer pin 3
      D6 : /RESET output -> printer pin 5
      D5 : DATA output -> printer pin 2
      D4 : Unused
      D3 : Cassette-OUT signal
      D2-D0 : Keyboard row select -> 74LS145 (selects 1 of 8 rows)

    Default initialisation: write $92 to PPI control register (ports A and B
    as inputs, port C as output), write $07 to port C (select row 7 =
    gamepad row). Reading port C returns the last written value.
    The PPI control register always returns $FF when read.

6.6.2 Key matrix (8 rows x 12 columns)

  6.6.2  Key matrix (8 rows x 12 columns)

    Data is returned active-low (0 = key pressed, 1 = released).

                PPI Port A                       PPI Port B
    Row  D0   D1   D2   D3   D4   D5   D6   D7   D0   D1   D2   D3
    ---  ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----
     0   '1'  'Q'  'A'  'Z'  ED   ','  'K'  'I'  '8'  ---  ---  ---
     1   '2'  'W'  'S'  'X'  SPC  '.'  'L'  'O'  '9'  ---  ---  ---
     2   '3'  'E'  'D'  'C'  HC   '/'  ';'  'P'  '0'  ---  ---  ---
     3   '4'  'R'  'F'  'V'  ID   PI   ':'  '@'  '-'  ---  ---  ---
     4   '5'  'T'  'G'  'B'  ---  DA   ']'  '['  '^'  ---  ---  ---
     5   '6'  'Y'  'H'  'N'  ---  LA   CR   ---  YEN  ---  ---  FNC
     6   '7'  'U'  'J'  'M'  ---  RA   UA   ---  BRK  GRP  CTL  SHF
     7   1U   1D   1L   1R   1TL  1TR  2U   2D   2L   2R   2TL  2TR

    Key abbreviations:
      ED  = ENG DIER'S       SPC = Spacebar        HC  = HOME CLR
      ID  = INS DEL          PI  = pi symbol        DA  = Down arrow
      LA  = Left arrow       RA  = Right arrow      UA  = Up arrow
      CR  = Enter (CR)       YEN = Yen symbol       BRK = BREAK
      GRP = GRAPH            CTL = CTRL             FNC = FUNC
      SHF = SHIFT            --- = Unused (always 1)
      1U/1D/1L/1R = Joystick 1 Up/Down/Left/Right
      1TL/1TR     = Joystick 1 Trigger Left/Right
      2U/2D/2L/2R = Joystick 2 Up/Down/Left/Right
      2TL/2TR     = Joystick 2 Trigger Left/Right

6.6.3 Key ghosting

  6.6.3  Key ghosting

    The keyboard uses a passive matrix with no diodes. Two ghosting rules:

    Rule 1 (row ghost): if two or more keys in the same row are held, any
    key in another row that shares a column will cause all other keys in
    those columns to appear pressed.
    Example: holding 1, Q, A, Z and pressing 2 also reports W, S, X.

    Rule 2 (column ghost): if two or more keys in the same column are held,
    any key in another column that shares a row will cause all other keys in
    those rows to appear pressed.
    Example: holding 1, 2, 3, 4 and pressing Q also reports W, E, R.

    The joystick ports share rows/columns and are subject to the same rules.

6.7 TMS9929A notes

  6.7  TMS9929A notes
  ----------------------------------------------------------------------------

    * Register write: bits 6-3 of the second byte written to $BF are
      ignored by the VDP.

    * Data written to the data port ($BE) is also copied to the VRAM read
      buffer (pre-loads the read latch).

    * Bits 7-3 of register #0 and bit 2 of register #1 have no function.

    * Bit 0 of register #0 does not blank the screen; instead it distorts
      display synchronisation. It was intended for external video input,
      which is unused on the SC-3000H.

    * Bit 7 of register #1 selects 4K vs 8K/16K VRAM addressing mode.
      The SC-3000H uses 16K DRAMs (MCM4517P15); always use 8/16K mode.

    VRAM address multiplexing (bit 7 reg #1):

    VDP bus  4K mode (bit7=0)  8/16K mode (bit7=1)
    -------  ----------------  ----------------------
    Row  AD0  VA6              VA7
    Row  AD1  VA7              VA8
    Row  AD2  VA8              VA9
    Row  AD3  VA9              VA10
    Row  AD4  VA10             VA11
    Row  AD5  VA11             VA12
    Row  AD6  VA13             VA13
    Col  AD0  VA0              VA0
    Col  AD1  VA1              VA1
    Col  AD2  VA2              VA2
    Col  AD3  VA3              VA3
    Col  AD4  VA4              VA4
    Col  AD5  VA5              VA5
    Col  AD6  VA12             VA6

    In 4K mode all 16K of VRAM are still accessible, but at different
    physical addresses.

6.8 Custom chip pinouts

  6.8  Custom chip pinouts
  ----------------------------------------------------------------------------

6.8.1 Cassette interface chip (11-pin SIP, epoxy-covered PCB)

  6.8.1  Cassette interface chip (11-pin SIP, epoxy-covered PCB)

    Pin  Signal
    ---  ------------------------------------------------------------------
     1   From CASSETTE-IN miniplug jack (analog input)
     2   Input from PPI PC3 (tape data out from BASIC)
     3   To CASSETTE-OUT miniplug jack (analog output)
     4   Ground
     5   Output to PPI PB7 (digitised tape data in)
     6   Unused
     7   Unused
     8   Unused
     9   Unknown (connected to a transistor and analog components)
    10   Unused
    11   +5V

    This chip digitises incoming tape signals for PPI PB7 and converts
    PPI PC3 output to analog for tape recording.

6.8.2 SEGA MITEC-2 (28-pin DIP)

  6.8.2  SEGA MITEC-2 (28-pin DIP)

              +----v----+
   Z80 /NMI  |01 o x 28| VCC
   Z80 /MREQ |02 i o 27| TMS9929A /CSW
      NMI-IN |03 i o 26| TMS9929A /CSR
     Z80 /RD |04 i o 25| SN76489A /CE
     Z80 /WR |05 i o 24| /CAS1
   Z80 /IORQ |06 i o 23| /CAS0
   Z80 /RFSH |07 i o 22| /RAS1
  MEMORY /RD |08 o o 21| /RAS0
  MEMORY /WR |09 o o 20| /RCSEL
 I/O PORT /RD|10 o o 19| CA7
 I/O PORT /WR|11 o i 18| Z80 A7
    Z80 A14  |12 i i 17| Z80 A6
    Z80 A15  |13 i o 16| /EXM1
         GND |14 x o 15| Work RAM /CS
              +---------+

    NMI-IN (pin 3): connected to keyboard RESET key.
    MEMORY /RD, /WR (pins 8-9): drive 2Kx8 work RAM and cartridge B05/B06.
    I/O PORT /RD, /WR (pins 10-11): drive 8255 PPI and cartridge B07/B08.
    TMS9929A /CSR, /CSW (pins 26-27): VDP has no chip select, so MITEC-2
      generates separate read/write enables.
    SN76489A /CE (pin 25): PSG /WE and /READY are both wired to Z80 /WAIT.
      MITEC-2 asserts /CE when the Z80 writes to the PSG port; PSG /READY
      then holds /WAIT low while the PSG latches the data byte.

6.9 Miscellaneous notes

  6.9  Miscellaneous notes
  ----------------------------------------------------------------------------

    * The SN76489A is not reset on power-up. After a power cycle it replays
      whatever sounds were last written. From a cold boot it emits a medium-
      volume tone. Always silence all channels at program start (see Ch. 3).

    * The Z80 interrupt mode: when an interrupt occurs the value on the data
      bus is unpredictable (similar to reading $60-$7F; see #6.5). Therefore
      interrupt modes 0 and 2 cannot be used reliably. The SC-3000 BASIC ROM
      uses interrupt mode 1 ($0038). To use IM 2, one technique is to fill
      257 bytes starting at the I register value with the same byte so that
      the vector address is deterministic regardless of bus noise.

6.10 Credits and disclaimer

  6.10  Credits and disclaimer
  ----------------------------------------------------------------------------

    Source document: "Sega SC-3000H hardware notes"
    Author: Charles MacDonald
    Website: http://cgfm2.emuviews.com
    Copyright 2002-2004 Charles MacDonald (unpublished work)

    Credits from original document:
      Omar Cornut -- MEKA emulator, cartridge/AV pinouts, Terebi Oekaki info
      Marc Le Dourain -- SF-7000 webpage (PPI and keyboard information)
      Sean Young -- TMS9918 documentation

    Disclaimer: information from this chapter is used with attribution as
    requested by the author. For the original document visit the author's
    website. This chapter may not be redistributed separately without the
    author's consent.

CHAPTER 7 -- SERIAL PRINTER INTERFACE (PRACTICAL REFERENCE)

CHAPTER 7 -- SERIAL PRINTER INTERFACE (PRACTICAL REFERENCE)
============================================================================

  This chapter consolidates the printer-related information scattered
  across Chapters 4 and 6, adds connector pinout notes from the Sega
  service manual, and explains the ROM hooks used by LPRINT.

7.1 Overview

  7.1  Overview
  ----------------------------------------------------------------------------

    The SC-3000 exposes a serial printer interface via the 7-pin DIN
    connector on the back panel.  Five active signals are routed through
    the 8255A PPI:

    Signal    PPI pin   Dir   Description
    --------  -------   ---   ------------------------------------------
    DATA      PC5       OUT   Serial data bit sent to printer
    /FEED     PC7       OUT   Line feed request to printer
    /RESET    PC6       OUT   Reset printer
    BUSY      PB6       IN    Printer busy (1 = not ready)
    FAULT     PB5       IN    Printer fault (1 = error condition)

    Electrical level: TTL, fan-out 1 TTL  (Sega service manual).

    BASIC commands that use the printer:
      LPRINT  -- formatted output to printer  (same syntax as PRINT)
      LLIST   -- program listing to printer   (same syntax as LIST)
      HCOPY   -- copy current text screen to printer

    Syntax details: Vol. A1 ch. 4.10, 4.11 and ch. 1 (LLIST).

7.2 Connector pinout -- dual source comparison

  7.2  Connector pinout -- dual source comparison
  ----------------------------------------------------------------------------

    Female 7-pin DIN on the back panel.  Two conflicting pinouts exist
    in the available documentation.  Both are reproduced; the Sega
    service manual version is the primary source.

    SERVICE MANUAL  (Sega, primary source):
    Pin  PPI signal   Dir   Function
    ---  ----------   ---   ----------------------------------------
     1   PB5          IN    FAULT  (printer fault/paper-out)
     2   PB6          IN    BUSY   (printer not ready)
     3   PC5          OUT   DATA   (serial data to printer)
     4   PC6          OUT   RESET  (reset printer)
     5   PC7          OUT   FEED   (line feed)
     6   GND          --    Ground
     7   NC           --    Not connected

    MacDONALD  (hardware analysis, 2002):
    Pin  PPI signal   Dir   Function
    ---  ----------   ---   ----------------------------------------
     1   --           --    Unused (not connected)
     2   PC5          OUT   DATA  output
     3   PC7          OUT   /FEED output
     4   PB6          IN    BUSY  input
     5   PC6          OUT   /RESET output
     6   PB5          IN    FAULT input
     7   GND          --    Ground

    The two tables differ only in which physical pin carries each signal
    (possibly a front-vs-back pin numbering convention, or a hardware
    revision).  The PPI bit assignments are consistent between both
    sources.

7.3 BASIC interface -- how LPRINT works internally

  7.3  BASIC interface -- how LPRINT works internally
  ----------------------------------------------------------------------------

    BASIC routes all character output through a RAM pointer:

      PUTADR  $82A0  (2 bytes)  -- address of the active character output
                                   routine; normally points to the screen
                                   write handler

    When LPRINT or LLIST is executing, BASIC temporarily sets:
      PUTADR = $7008

    The routine at $7008 manages the physical printer handshake: it polls
    BUSY, serialises the character byte on PC5 (DATA), and pulses the
    strobe.  When the statement ends, PUTADR is restored to the screen
    handler.

    Redirecting LPRINT to a custom handler from ML:
      ; Save original PUTADR first
      LD   HL, ($82A0)
      LD   (saved_putadr), HL
      ; Install custom handler
      LD   HL, my_printer_handler
      LD   ($82A0), HL
      ; ... execute LPRINT statements ...
      ; Restore
      LD   HL, (saved_putadr)
      LD   ($82A0), HL

7.4 Checking printer status from ML

  7.4  Checking printer status from ML
  ----------------------------------------------------------------------------

    BUSY is PB6 (bit 6 of Port B, $DD).  FAULT is PB5 (bit 5):

      IN   A, ($DD)
      BIT  6, A            ; test BUSY (PB6)
      JR   NZ, printer_not_ready

      IN   A, ($DD)
      BIT  5, A            ; test FAULT (PB5)
      JR   NZ, printer_fault

    Note: the routine at $48A3 is sometimes mistaken for printer-status
    reading -- it switches Port C to keyboard column 5 and reads Port B --
    but it is a keyboard key-scanner (tests PB3 in columns 5-6), NOT a
    printer-status read and NOT BRKCHK (the real break-check is $5933, via
    SYS $4A03).  For direct printer ML access the simple IN ($DD) / BIT 6
    approach above is sufficient.

7.5 Sending a character via the ROM handler

  7.5  Sending a character via the ROM handler
  ----------------------------------------------------------------------------

    The safest way to output a character to the printer from ML is to
    dispatch through PUTADR after setting it to the ROM printer handler:

      ; Redirect PUTADR to ROM printer handler
      LD   HL, $7008
      LD   ($82A0), HL

      ; Output one character (ASCII code in A)
      LD   A, 'A'           ; example: print 'A'
      LD   HL, ($82A0)
      CALL_HL:
        PUSH HL
        RET                 ; equivalent to JP (HL)

    Simpler idiom (the ROM call entry point accepts character in A):
      LD   A, charcode
      CALL $7008            ; call printer handler directly

    The PUTADR vector is also the target of the LPRINT bytecode handler,
    so any character sent through $7008 is subject to the same BUSY wait
    and handshake that LPRINT uses.

7.6 Physical wire protocol -- bit encoding and timing

  7.6  Physical wire protocol -- bit encoding and timing
  ----------------------------------------------------------------------------

    Source: ROM disassembly of $7008-$7099 in BASIC.rom (Level III-B cart).

    SIGNAL LINE: DATA = PC5 of 8255 PPI, controlled via port $DF in BSR mode.
      BSR commands:  OUT ($DF), $0B  ->  SET   PC5 = 1  (DATA HIGH)
                     OUT ($DF), $0A  ->  RESET PC5 = 0  (DATA LOW)

    IDLE STATE: DATA = HIGH (1).

    PER-CHARACTER SEQUENCE:

      1.  BUSY WAIT
            Poll PB6 (Port $DD bit 6).  Spin until BUSY = 0 (printer ready).
            Interrupts remain ENABLED during polling.

      2.  CRITICAL SECTION START
            DI  (disable interrupts for the duration of the bit stream)

      3.  PRE-STROBE  (~12.8 µs)
            SET DATA HIGH  (OUT $DF,$0B).
            CDELAY(C = $0E = 14)  =>  279 ns × 14 = 3.9 µs + loop overhead
            Actual measured gap from ROM timing: ~12.8 µs.

      4.  BIT STREAM  (8 bits, LSB first, INVERTED logic)
            For each bit b0..b7 of the character byte:
              If bit = 1:  SET   DATA LOW   (OUT $DF,$0A)   -- char 1 -> wire LOW
              If bit = 0:  SET   DATA HIGH  (OUT $DF,$0B)   -- char 0 -> wire HIGH
            Each bit period: CDELAY(C = $2E = 46) => 279 ns × 46 = 12.8 µs
              + Z80 loop overhead (~23 cycles = 6.4 µs) = ~19.2 µs per half-step
              Total per bit: ~36 µs  =>  ~27.8 kbps effective bit rate.

      5.  END-OF-BYTE STROBE  (~27.9 µs)
            RESET DATA LOW  (OUT $DF,$0A).
            CDELAY(C = $64 = 100)  =>  279 ns × 100 = 27.9 µs.

      6.  CRITICAL SECTION END
            EI  (re-enable interrupts)

    PRINTABLE RANGE:
      Characters with ASCII code < $20 (space) or > $7E are replaced with
      space (0x20) by the ROM at $702A before the bit stream is sent.
      Exception: $0D (CR) is handled by the caller (LPRINT/LLIST) and
      triggers a line feed; it is not passed to the character sender.

    INTER-CHARACTER GAP:
      After EOB the printer asserts BUSY (PB6 = 1) while it processes the
      character.  The next character's BUSY-WAIT loop (step 1) blocks until
      the printer clears BUSY.  Duration is printer-dependent.

    TIMING SUMMARY (per character, excluding BUSY wait):
      Phase         Duration      DATA state
      -----------   ----------    ----------------------------------------
      Idle          --            HIGH
      PRE           ~12.8 µs      HIGH  (held, giving printer start signal)
      bit 0 (LSB)   ~36 µs        HIGH if char_bit=0 / LOW if char_bit=1
      bit 1         ~36 µs        (same rule)
      ...
      bit 7 (MSB)   ~36 µs        (same rule)
      EOB           ~27.9 µs      LOW   (end-of-byte strobe, always LOW)
      Inter-char    printer-dep.  (BUSY line HIGH, then LOW when ready)

    TOTAL WIRE TIME per character (no BUSY wait):
      12.8 + (8 × 36) + 27.9  =  ~328.7 µs  (~3 045 chars/s theoretical max)

7.7 Timing diagram -- "Hello World\r" (logic analyzer view)

  7.7  Timing diagram -- "Hello World\r"  (logic analyzer view)
  ----------------------------------------------------------------------------

    Scale: each cell [ ] = 1 bit period (~36 µs).  PRE and EOB are shown
    as half-width markers (·).  BUSY is shown per character as a block
    between EOB and the next PRE; actual BUSY duration is printer-dependent.

    Wire encoding rule:  char bit 1 -> DATA LOW (L);  char bit 0 -> DATA HIGH (H).

    SINGLE CHARACTER DETAIL  (character 'H' = 0x48):

      Bit name:  IDLE  PRE  b0  b1  b2  b3  b4  b5  b6  b7  EOB  BUSY
      DATA:       H     H    H   H   H   L   H   H   L   H    L    ~

      Time (µs):  ...  12.8  36  36  36  36  36  36  36  36  27.9  (var)
                            |<-------- 288 µs bit stream -------->|

      'H' = 0x48 = 0100 1000 binary.
      LSB first:  b0=0 b1=0 b2=0 b3=1 b4=0 b5=0 b6=1 b7=0
      Inverted:   b0=H b1=H b2=H b3=L b4=H b5=H b6=L b7=H

    MULTI-SIGNAL WAVEFORM  (character 'A' = 0x41 = 0100 0001 b)
    Format: - = HIGH,  _ = LOW,  | = signal edge (transition).

      Bit values (LSB first):  b0=1  b1=0  b2=0  b3=0  b4=0  b5=0  b6=1  b7=0
      Wire (0->H, 1->L):       b0=L  b1=H  b2=H  b3=H  b4=H  b5=H  b6=L  b7=H

      Phase:  IDLE+PRE  b0        b1  b2  b3  b4  b5         b6    b7   EOB  BUSY
      us:        0      12.8     48.8 84.8 121 157 193       229   265   301  329+

    DATA (PC5)  --------|     |-------------------------|     |-----|
                        |_____|                         |_____|     |____________

    BUSY (PB6)                                                           |-------
                _________________________________________________________|
               L (printer ready throughout character frame)           H (busy)

                ^ DI (Z80 IRQ off)                                       ^ EI
    (Z80 interrupts disabled for entire bit-stream; re-enabled after EOB)

    FULL STRING "Hello World\r" -- DATA line only:

    Char  Hex   b0  b1  b2  b3  b4  b5  b6  b7   Wire stream (· = PRE/EOB boundary)
    ----  ----  --  --  --  --  --  --  --  --   -------------------------------------
    'H'   0x48   0   0   0   1   0   0   1   0   H · H  H  H  L  H  H  L  H · L
    'e'   0x65   1   0   1   0   0   1   1   0   H · L  H  L  H  H  L  L  H · L
    'l'   0x6C   0   0   1   1   0   1   1   0   H · H  H  L  L  H  L  L  H · L
    'l'   0x6C   0   0   1   1   0   1   1   0   H · H  H  L  L  H  L  L  H · L
    'o'   0x6F   1   1   1   1   0   1   1   0   H · L  L  L  L  H  L  L  H · L
    ' '   0x20   0   0   0   0   0   1   0   0   H · H  H  H  H  H  L  H  H · L
    'W'   0x57   1   1   1   0   1   0   1   0   H · L  L  L  H  L  H  L  H · L
    'o'   0x6F   1   1   1   1   0   1   1   0   H · L  L  L  L  H  L  L  H · L
    'r'   0x72   0   1   0   0   1   1   1   0   H · H  L  H  H  L  L  L  H · L
    'l'   0x6C   0   0   1   1   0   1   1   0   H · H  H  L  L  H  L  L  H · L
    'd'   0x64   0   0   1   0   0   1   1   0   H · H  H  L  H  H  L  L  H · L
    '\r'  0x0D   1   0   1   1   0   0   0   0   H · L  H  L  L  H  H  H  H · L

    Legend:
      H  = DATA HIGH (wire voltage ~+5 V TTL)
      L  = DATA LOW  (wire voltage ~0 V TTL)
      ·  = PRE (DATA HIGH, ~12.8 µs) before b0 / EOB (DATA LOW, ~27.9 µs) after b7
      BUSY signal (not shown above) goes HIGH after each · L and returns LOW
      before the next character's · H can begin.

    NOTE on '\r':  the ASCII carriage return (0x0D) is sent to the physical
    wire by LPRINT/LLIST as an actual byte.  Some printers interpret 0x0D
    as carriage return; others require 0x0A (LF) or 0x0D+0x0A (CRLF).
    The ROM does not append 0x0A automatically.

7.8 Emulator interception -- capturing LPRINT output in software

  7.8  Emulator interception -- capturing LPRINT output in software
  ----------------------------------------------------------------------------

    In a JavaScript SC-3000 emulator the serial printer protocol never
    produces actual wire transitions.  Instead, the PPI port writes to
    $DF are silently discarded.  To capture LPRINT output as text,
    intercept the call BEFORE the bit-serialisation loop runs.

    RECOMMENDED HOOK POINT:  the PUTCHAR dispatcher.

      PUTCHAR lives at ROM address $2400 (or similar; check your ROM build).
      Every character sent to any output device -- screen or printer --
      passes through this point.  At the hook:
        * cpu.PC   === PUTCHAR_ADDR   (e.g. 0x2400)
        * cpu.A    === character code (0x20-0x7E for printable ASCII)
        * ram[0x82A0] | (ram[0x82A1] << 8) === 0x7008  -->  printer active

    ALTERNATIVE HOOK POINT:  the ROM printer handler entry.

      Hook directly at 0x7008.  Every time the emulator is about to enter
      the printer bit-stream loop:
        * cpu.PC === 0x7008
        * cpu.A  === character code

      This is simpler; no PUTADR check needed.  Any call to $7008 is a
      printer output call.

    JAVASCRIPT EXAMPLE  (hook at $7008):

      const PRINTER_ENTRY = 0x7008;
      let printerBuffer = '';

      cpu.addBreakpoint(PRINTER_ENTRY, () => {
        const ch = cpu.A & 0xFF;
        if (ch >= 0x20 && ch <= 0x7E) {
          printerBuffer += String.fromCharCode(ch);
        } else if (ch === 0x0D) {
          printerBuffer += '\n';
        }
        // Skip the ROM printer routine -- advance PC past it
        // Option A: set PC to the return address already on the stack
        cpu.PC = cpu.popWord();   // equivalent to RET
        return true;              // signal: hook consumed this call
      });

      // To copy to clipboard when printer is done:
      function flushPrinter() {
        navigator.clipboard.writeText(printerBuffer);
        printerBuffer = '';
      }

    NOTES:
      * The hook must fire BEFORE the DI / bit-loop so interrupts are
        not left disabled if the loop is skipped.
      * Non-printable characters (< 0x20 or > 0x7E) are already replaced
        with space by ROM code at $702A before reaching $7008; the hook
        at $7008 will receive 0x20 for those characters.
      * HCOPY sends characters in screen order (left-right, top-bottom),
        including trailing spaces on each line.  Post-process with
        trimEnd() on each line if compact output is preferred.
      * LLIST adds CR (0x0D) at the end of each BASIC line.

============================================================================