Sega System 24 Hardware Notes (2013-06-16)

From Sega Retro

Logo-txt.svg
This is a copy of an "unofficial" document containing original research, for use as a source on Sega Retro. This page likely exists for historical purposes - the contents should ideally be copy-edited and wikified to make better use of Sega Retro's software.
Original source: https://web.archive.org/web/20140318183124/cgfm2.emuviews.com/new/s24hw.txt


 Sega System 24 Hardware Notes
 (C) 2013 Charles MacDonald

 [6/16/13]
 - Added ABSEL override mode description.
 - Added line scroll description.
 - Clarified I/O chip address connections.
 - Fixed some typos.

 [4/1/13]
 - Initial release.

 ----------------------------------------------------------------------------
 System overview
 ----------------------------------------------------------------------------

 Memory map

 Custom chip 315-5295 at IC5 does address decoding for both CPUs:

 000000-07FFFF : BIOS ROM (CPU B DRAM if accessed by CPU B)
 080000-0FFFFF : CPU A DRAM
 100000-1FFFFF : BIOS ROM (accessible to both CPUs)
 200000-3FFFFF : 315-5292 (Tilemap chip)
 400000-5FFFFF : 315-5294 (Mixer chip and color RAM)
 600000-7FFFFF : 315-5293 (Sprite chip)
 800000-9FFFFF : 315-5296 (I/O chip)
 A00000-AFFFFF : 315-5295 (315-5295 at IC7, timer and interrupt controller)
 B00000-BFFFFF : Expansion (CN2, /EXCS0)
 C00000-CFFFFF : Expansion (CN2, /EXCS1)
 D00000-DFFFFF : Expansion (CN5, /EXCS2)
 E00000-EFFFFF : Expansion (To test point)
 F00000-F3FFFF : CPU B DRAM
 F40000-F7FFFF : CPU B DRAM
 F80000-FBFFFF : CPU A DRAM
 FC0000-FFFFFF : CPU A DRAM

 DRAM is 256K, mirrored twice in each 512K region it is mapped to.
 Some versions of the BIOS test to see if the DRAM is 512K or 256K.
 See the board revisions section for more details.

 ROM is 256K, mirrored repeatedly throughout the 1MB area it is assigned to.

 Bus structure

 The various components in the memory map are split across two independent
 busses in the system

 T-bus (CPU A)
 - CPU A DRAM
 - BIOS ROM
 - Interrupt controller and timer (IC7) ("INTC")
 - I/O chip
 - YM2151
 - CN2 expansion connector (System 24 specific I/O, FDC, and ROM boards)
 - CN5 expansion connector (Compatible with earlier Sega I/O boards)

 P-bus (CPU B)
 - CPU B DRAM
 - Tilemap generator
 - Object generator
 - Mixer chip
 - Color RAM

 System timing

 Wait states for CPU A when accessing memory with CPU B held
 in the reset state (/RESP=L).

 BIOS ROM               : No wait states.
 INTC registers         : No wait states.
 Color RAM / Mixer regs : No wait states.
 CN5 expansion area     : No wait states.
 Tilemap registers      : No wait states.
 CN2 expansion areas    : Subject to external DTACK pin connection.
                          No wait states for the I/O, FDC, ROM boards.
 I/O chip               : 1 clock added per access.
 Tilemap RAM (either)   : 6 clocks added per access.
                          This happens regardless of horizontal or
                          vertical blanking or if layers are blanked.
 DRAM (CPU A work RAM)  : Every 20us an access has 4 clocks added.
 DRAM (CPU B work RAM)  : Every 20us an access has 4 clocks added.
 Sprite RAM             : Variable. When rendering it ranges from 3 to 22
                          clocks added, when idle the minimum is 3 clocks
                          added per access (e.g. 1st command is $C000).

 Wait states for CPU A when CPU B is running.

 CPU A accessing same   : 4 clocks added per access.
 T-bus address as CPU B

 CPU A accessing same   : 4 clocks added per access.
 P-bus address as CPU B

 CPU A accessing other  : ? (Seems unclear, but different than above cases)
 T-bus than CPU B

 CPU A accessing other  : ? (Seems unclear, but different than above cases)
 P-bus than CPU B

 The DRAM used for work RAM (MB81464) needs to be refreshed every 4ms, and
 it seems like the system refreshes it much faster than necessary.

 ----------------------------------------------------------------------------
 Board revisions
 ----------------------------------------------------------------------------

 837-6442

 Original board type.

 837-6442-01

 New board type. It has the following changes compared to the original
 board:

 - Added 7805 regulator which provides a +5V source derived from the +12V
   input from the edge connector. This is used for the audio section
   exclusively.

 - Added IC83 (74HC245) to buffer port H of the I/O chip and in turn drive
   the audio DAC's R-2R resistor array. This was needed now that the I/O
   chip and audio have their own +5V supplies and are no longer common.

 - IC77 has been replaced with a SOIC equivalent to free up room
   for newly added IC83.

 - Added IC84 (74HC04) to buffer the 10 MHz clock output of IC65 (74F74)
   which goes to both CPUs and the 315-5295 at IC5. Two gates are used
   to provide a non-inverted clock; the remaining four gates are unused.

 - Narrow DIP switches are used instead of the full size ones.

 - Sprite RAM is implemented with eight MB81464 DRAMs (64Kx4 each) soldered
   to the board like the original version, but now an array of four sockets
   (IC85-88) have been added for MB81C4256 DRAMs. Jumper J1 and resistors
   R64 and R65 have been added to enable one of the two DRAM banks.

   The MB81C4256 is 256Kx4 compared to the MB81464 which is 64Kx4, so if the
   MB81C4256s are installed and enabled, sprite RAM is 512K instead of 256K.

   When jumper J1 connects pins 2-3 ("256"), the MB81464 bank is enabled.
   When it connects pins 1-2 ("1M"), the MB81C4256 bank is enabled. If the
   jumper is left unconnected both banks are disabled. Disabled bank(s)
   respond to writes but not reads.

   While this change allowed the hardware to transition to MB81C4256
   DRAMs as they became cheaper and/or more available, it seems unusual that
   they'd permanently install MB81464s and provide sockets for the
   MB81C4256s as well as have a circuit that supported both being installed
   and operating at the same time. In theory it might have been an option to
   support older software that wouldn't work with 512K of RAM being
   installed, but it isn't known if any games have such an issue.

 Other specifics for this revision:

 Jumper J1
 1 - ("1M")  - To ZIP sockets pin 1 (IC85-88) (MB81C4256 OE#) and R64 pullup.
 2 - From 315-5293 pin 26 (/OE)
 3 - ("256") - To ZIP sockets pin 5 (IC42-48) (MB81464 OE#) and R65 pullup.

 MB81C5256 address line connections:

 Pin 11 (A0) - Pin 31 (CGAD8) 
 Pin 12 (A1) - Pin 33 (CGAD6)
 Pin 13 (A2) - Pin 34 (CGAD5)
 Pin 14 (A3) - Pin 35 (CGAD4)
 Pin 16 (A4) - Pin 32 (CGAD7)
 Pin 17 (A5) - Pin 36 (CGAD3)
 Pin 18 (A6) - Pin 37 (CGAD2)
 Pin 19 (A7) - Pin 38 (CGAD1)
 Pin 20 (A8) - Pin 39 (CGAD0)

 On the original board CGAD8 is unconnected and goes to a test point.

 837-6442-02

 Used by Quiz Ghost Hunter. Seems almost identical to revision 1 board.
 It uses four 2-pin DIP MB81C4256's for a total of 512K of sprite RAM.
 The old MB81464 ZIP array, MB81C4256 ZIP sockets, and DRAM selection
 jumper are removed.

 BIOS checks for board revisions

 The EPR-12186/12187 BIOS tests for 512K of CPU A work RAM by determining
 if RAM address $0C0000 is a mirror of $080000. Revisions 0 and 1 do not
 have extra CPU work RAM. It isn't known how much RAM the revision 2 board
 has, but it was produced much later after this BIOS was developed.

 This check that the BIOS performs may have been included to support a
 development system with more RAM, or for a planned board revision for
 more RAM that was never released.

 ----------------------------------------------------------------------------
 I/O chip
 ----------------------------------------------------------------------------

 Overview

 The 315-5296 I/O chip is similar in design to the Sony CXD1095Q I/O chip
 used on earlier boards. It has the following features:

 - Eight 8-bit I/O ports (A through H)
 - Three output-only pins (CNT2-0)
 - Chip select for a peripheral device (/FMCS)
 - Clock output for a peripheral device (CKOT)
 - Programmable clock divider

 Reset state

 After a reset, all I/O ports are configured as inputs.

 Address decoding

 The chip has a 6-bit address bus assigned as follows:

 $00-$1F : I/O chip internal locations
 $20-$3F : Unused by I/O chip; /FMCS asserted for this range.

 Of the first 32 locations, only the first 16 are used and the latter
 16 have no function.

 Register map

 $00 : Port A data
 $01 : Port B data
 $02 : Port C data
 $03 : Port D data
 $04 : Port E data
 $05 : Port F data
 $06 : Port G data
 $07 : Port H data
 $08 : Identifier #1
 $09 : Identifier #2
 $0A : Identifier #3
 $0B : Identifier #4
 $0C : Mode control register #1 (mirror)
 $0D : Mode control register #2 (mirror)
 $0E : Mode control register #1
 $0F : Mode control register #2
 $10-$1F : Unused
 $20-$3F : /FMCS area

 Identifier

 These locations return the ASCII values "SEGA" when read.

 I/O ports

 Each I/O port consists of an 8-bit output latch and 8-bit input buffer.
 Regardless of the port direction, writing to a register loads the
 port's output latch.

 If the port is an input, reading returns the real-time state of the pins
 through the input buffer. If the port is an output, reading returns the
 current value of the output latch.

 Control register #1

 MSB   LSB
 dd-- ---- : CKOT clock divider (0= CLK/4, 1= CLK/8, 2= CLK/16, 3= CLK/2)
 --dd ---- : CNT2 clock divider (0= CLK/4, 1= CLK/8, 2= CLK/16, 3= CLK/2)
 ---- e--- : CNT2 output mode (1= Clock output, 0= Programmable output)
 ---- -210 : Output level of CNT2-0 pins (1= high, 0= low)

 This register defines how CNT2-0 operate, if CNT2 is a secondary clock
 output, and the frequency of the primary and secondary clock ouputs.

 When CNT2 is configured as clock output, bit 2 of this register has
 no effect on the output level of CNT2.

 Control register #2

 MSB   LSB
 h--- ---- : Direction of I/O port H (1= output, 0= input)
 -g-- ---- : Direction of I/O port G (1= output, 0= input)
 --f- ---- : Direction of I/O port F (1= output, 0= input)
 ---e ---- : Direction of I/O port E (1= output, 0= input)
 ---- d--- : Direction of I/O port D (1= output, 0= input)
 ---- -c-- : Direction of I/O port C (1= output, 0= input)
 ---- --b- : Direction of I/O port B (1= output, 0= input)
 ---- ---a : Direction of I/O port A (1= output, 0= input)

 This register defines the direction of each I/O port.

 System 24 specifics

 The 68000 address lines are connected to the chip in the following manner:

 I/O    68K
 A0     A1
 A1     A2
 A2     A3
 A3     A4
 A4     A5
 A5     A8

 This segments the 64-byte space into two regions:

 $800001-$80003F : I/O chip registers
 $800101-$80013F : /FMCS area

 Register map

 $800001 : Port A data
 $800003 : Port B data
 $800005 : Port C data
 $800007 : Port D data
 $800009 : Port E data
 $80000B : Port F data
 $80000D : Port G data
 $80000F : Port H data
 $800011 : Identifier #1
 $800013 : Identifier #2
 $800015 : Identifier #3
 $800017 : Identifier #4
 $800019 : Mode control register #1 (mirror)
 $80001B : Mode control register #2 (mirror)
 $80001D : Mode control register #1
 $80001F : Mode control register #2
 $800021-$80003F : Unused

 $800101-$80013F : /FMCS area, specifically:
 $800101 : YM2151 address 0
 $800103 : YM2151 address 1

 I/O port summary

 Port A : Optocoupled inputs from edge connector (active low).

 Port B : Optocoupled inputs from edge connector (active low).

 Port C : Connected via bus transceiver to edge connector pins.
          Output pin CNT0 controls the direction (0= input, 1= output).

 Port D : Output, bits 7-4 driven by LS367 to CN3 pins (digital outputs,
          TTL level) and bits 3-0 driven by ULN2003 to edge connector
          (open collector, high current outputs).

 Port E : Optocoupled inputs from edge connector (active-low).

 Port F : DIP switch #1 inputs.

 Port G : DIP switch #2 inputs.

 Port H : Output to 8-bit audio DAC.

 I/O chip pin connections

 - CLK driven at 16 MHz.
 - CNT2 connected to YM2151 /IC. (reset line)
 - CNT1 connected to 315-5295 (IC5) pin 81 (/RESP)
   This is the CPU B reset line, 0= reset, 1= running.
 - CNT0 connected to 74LS245 (IC77) DIR.
 - CKOT connected to YM2151 clock input.
                                      
 I/O chip initialization

 The BIOS initializes the I/O chip as follows:

    MOVE.B   #$04,$0080001D
    MOVE.B   #$00,$00800007
    MOVE.B   #$88,$0080001F

 This initializes the system as follows:

 - Ports D,H are outputs, remaining are inputs.
 - CNT2 is driven high, CNT1-0 are driven low.
   Meaning CPU B is held in a reset state (/RESP=L) and is inactive.
 - CNT2 is a programmable output and not a clock output.
 - CKOT outputs a an 8 MHz clock to the YM2151 (16 MHz / 2).
 - Port D outputs are driven low.

 ----------------------------------------------------------------------------
 Timer and interrupt controller
 ----------------------------------------------------------------------------

 Custom chip 315-5295 (IC7) has an interrupt controller and timer
 peripheral. It has four internal registers that are word-wide.

 Register overview

 $A00000 : ---- cccc cccc cccc : Timer reload count
 $A00002 : ---- ---- ---- --mm : Timer mode
 $A00004 : ---- ---- --65 432- : CPU A interrupt enable (1= enabled, 0=disabled)
 $A00006 : ---- ---- --65 432- : CPU B interrupt enable (1= enabled, 0=disabled)

 Reading any address returns the real time state of the timer count.

 Interrupt sources

 Level 2 : YM2151 /IRQ (/INT0)
 Level 3 : Timer
 Level 4 : Tilemap /XINT (/INT1)
 Level 5 : Sprite /DMAO and /DMAI tied together (/INT2)
 Level 6 : CN2 expansion connector (/INT3)

 Interrupt request behavior

 The timer interrupt request signal is asserted until the corresponding
 CPU interrupt enable register is read or written, either by byte or word
 access. Typically software will read that register and discard the value
 to acknowledge the interrupt as follows:

 __level3_irq:
        tst.w   $A00004
        rte

 One CPU can acknowledge an interrupt for the other one, for example CPU A
 can acknowledge a timer interrupt on CPU B by writing or reading $A00006.

 The V-Blank interrupt request signal from the tilemap chip is asserted for
 exactly one scanline, or 656 cycles. If the interrupt handler exits
 before that duration is over the interrupt will immediately be
 triggered again.

 The sprite interrupt request signal from the sprite chip is asserted for
 exactly one scanline, or 656 cycles. If the interrupt handler exits
 before that duration is over the interrupt will immediately be
 triggered again.

 Timer

 The timer is a 12-bit up-counter. The counter can be read back by reading
 any word offset in $A00000. When the counter overflows it is reloaded
 with the last value written to $A00000. Writing to that register while
 the timer is counting does not affect it until the next overflow and
 reload.

 The timer's clock source can be an 8 MHz clock that is synchronous to
 the 16 MHz pixel clock, and the HOUT signal generated by the tilemap
 chip that is pulsed once per scanline.

 The counter counts up on the positive edge of the selected clock signal.
 When the count is $0FFF and a positive edge is detected, the counter
 is loaded with the value of the reload register and an IRQ is requested.
 The timer continues to count even if the interrupt is not processed or
 not acknowledged.

 There are four timer modes as follows:

 0 - Timer off.

     There is a bug in the hardware; if the reload count is $0FFF an
     interrupt is generated just like mode 1. E.g. one interrupt triggered
     on every scanline.

 1 - Scanline timer

     The timer clock source is HOUT. If HOUT is not generated the counter
     state will not change.

 2 - Pixel timer

     The timer behaves like mode 3, but the count is also reloaded on the
     rising edge of HOUT. This allows you to trigger an interrupt at a
     specific location within a scanline, specified in units of two pixels.

     The timer clock source is 8 MHz. If HOUT is not generated the counter
     state will behave exactly like mode 3.

     Valid reload count values in this mode are:

     0000-0EB7 : No interrupts generated (counter reloaded before it expires)
     0EB8      : 1 interrupt per scanline
     :
     0F5B      : 1 interrupt per scanline
     0F5C      : 2 interrupts per scanline
     :
     0F92      : 2 interrupts per scanline
     0FFF      : Interrupt triggered at every 8 MHz clock

     As the counter reloads after each interrupt, using large
     counts will cause the interrupt to trigger multiple times in one
     scanline.

     Adjusting the position of HOUT shifts the location that the interrupt
     is triggered at on a given scanline.

 3 : 8 MHz clock

     The counter increases on the rising edge of the 8 MHz clock.

     The CPU can't process interrupts fast enough at some of the
     highest timer counts.

 Comments in mode 2

 The problem with mode 2 is that it does not function as a one-shot timer,
 so for large count values the timer will overflow multiple times per
 scanline and you'll get several interrupts on a single line.

 In theory mode 2 would allow for a positional interrupt, but because of
 this reload behavior you are limited to counts that position the
 interrupt in the right half of the screen only, unless you have a more
 complex interrupt handler that disables itself for other interrupts after
 the first one.

 For a true positional interrupt at the cost of distorting the sprites,
 you can use timer mode 1 and adjust the HOUT register. This gives
 very fine (16 MHz) positioning rather than the coarse positioning
 of mode 2 without the troublesome reload behavior.

 ----------------------------------------------------------------------------
 Mixer chip and color RAM
 ----------------------------------------------------------------------------

 The mixer chip combined the output of three pixel data busses (tilemap A,
 tilemap B, sprite framebuffer) and generates indices in color RAM
 specifying RGB color data to be displayed for each pixel on the screen.

 Address map

 400000-403FFF : Color RAM, more specifically:
 400000-401FFF : Tilemap palettes (256 palettes of 16 colors)
 402000-4021FF : Sprite palette (256 colors)
 402200-403FFF : Unused RAM
 404000-407FFF : Mixer chip internal registers (16 locations)

 Color RAM format

 MSB             LSB
 s--- ---- ---- ---- : Shadow (0) or highlight (1) select.
 -b-- bbbb ---- ---- : Blue component bits 0,4,3,2,1.
 --g- ---- gggg ---- : Green component bits 0,4,3,2,1.
 ---r ---- ---- rrrr : Red component bits 0,4,3,2,1.

 Registers

 Each register is 3 bits (D2-D0) and can be read and written.

 404001 : Background A priority code for low priority tiles
 404003 : Background A priority code for high priority tiles

 404005 : Window A priority code for low priority tiles
 404007 : Window A priority code for high priority tiles

 404009 : Background B priority code for low priority tiles
 40400B : Background B priority code for high priority tiles

 40400D : Window B priority code for low priority tiles
 40400F : Window B priority code for high priority tiles

 404011 : Sprite priority code for pens $C0-$FF
 404013 : Sprite priority code for pens $80-$BF
 404015 : Sprite priority code for pens $40-$7F
 404017 : Sprite priority code for pens $00-$3F

 404019 : Control register #1
 40401B : Control register #2
 40401D : Control register #1 (mirror)
 40401F : Control register #2 (mirror)

 Control register #1

 D2 : ?
 D1 : ?
 D0 : ABSEL mode enable (1= on, 0= off)

 Control register #2

 D2 : Unused (connects from pin 37 "EXT" to test point near C118)
      0= Test point driven low, 1= Driven high.
 D1 : Screen flip (1= on, 0= off) (from pin 36 "EX0" labelled "2P")
 D0 : Screen blanking (1= on, 0= off)

 Priority ordering

 A priority of 0 is the lowest, 7 is the highest. If priority codes are
 equal the mixer chip uses a hardwired priority ordering.

 ----------------------------------------------------------------------------
 Tilemap chip
 ----------------------------------------------------------------------------

 Overview

 The 315-5292 generates two tilemap layers and all display timing signals
 for the system.

 Terminology

 Planes A, B - The two scrolling tilemaps generated by the chip.

 Background  - The primary tilemap associated with a given plane.

 Window      - The alternate tilemap associated with a given plane.

 Layer       - Either the background or the window of a plane.

 Mask        - A bitmap that selects if the background tilemap or
               window tilemap should be shown within a 8x1 pixel
               area of the display.

 Individual pixel data for planes A and B are fed to the mixer chip. This
 data includes the pixel value (4 bits), palette number (8 bits), priority
 flag (1 bit) and background/window indicator (1 bit).

 The mixer chip assigns a 3-bit priority code based on a pixel's priority
 flag, then evaluates the priority of pixel data from both planes to
 determine which plane appears on top.

 Bit 0 of mixer control register #2 enables use of the ABSEL signal, which
 the tilemap chip generates at a user-defined pixel position on every
 scanline. To the left of this point plane A is shown exclusively, to
 the right plane B is shown exclusively. If this mode is not used, the ABSEL
 signal position can still be freely defined but it has no impact on the
 display.

 Layer transparency

 Normal display mode (ABSEL=0)

 Zero value pixels from the topmost layer are considered transparent.
 Zero value pixels from the bottommost layer show color 0 of the specified
 palette.

 If the topmost layer is blanked, it is considered transparent.
 If the bottommost layer is blanked, it is filled with color 0 of palette 0.

 ABSEL display mode (ABSEL=1)

 Pixel 0 is not transparent, so color 0 of the associated palette is shown.
 If any layer is blanked, it is filled with color 0 of palette 0.

 Memory map

 200000-20FFFF : VRAM (64K)
 220000-23FFFF : ABSEL register (any word offset)
 240000-25FFFF : HOUT register  (any word offset)
 260000-26FFFF : VOUT register  (any word offset)
 270000-27FFFF : Display mode register (any word offset)
 280000-2FFFFF : Tile pattern RAM (512K; 128K present)

 VRAM memory map

 200000-201FFF : Plane A background name table
 202000-203FFF : Plane A window name table 
 204000-205FFF : Plane B background name table
 206000-207FFF : Plane B window name table 
 208000-2083FF : Plane A background line scroll table 
 208400-2087FF : Plane A window line scroll table 
 208800-208BFF : Plane B background line scroll table
 208C00-208FFF : Plane B window line scroll table
 209000-209FFF : (Unused)
 20A000-20A00F : Video registers ("soft" registers stored in RAM)
 20A010-20BFFF : (Unused)
 20C000-20CFFF : Plane A window mask
 20D000-20DFFF : Plane B window mask
 20E000-20FFFF : (Unused)
 280000-29FFFF : Tile pattern RAM (128K)
 2A0000-2BFFFF : Tile pattern RAM (mirror)
 2C0000-2DFFFF : Tile pattern RAM (mirror)
 2E0000-2FFFFF : Tile pattern RAM (mirror)

 Name table data

 MSB             LSB
 p--- ---- ---- ---- : Priority bit (0= low, 1= high)
 -ccc cccc c--- ---- : Palette number (0-255)
 --nn nnnn nnnn nnnn : Tile number (0-16383) [Other hardware]
 ---- nnnn nnnn nnnn : Tile number (0-4095)  [System 24]

 System 24 only has 128K of 512K RAM installed for tile patterns. The
 name field is 14 bits, but only the lower 12 bits select the tile to
 be displayed.

 The palette and tile number fields overlap so palette numbers and tile
 numbers cannot be freely defined in all cases.

 Video registers

 20A000 : Background A horizontal scroll
 20A002 : Window A horizontal scroll
 20A004 : Background B horizontal scroll
 20A006 : Window B horizontal scroll

 20A008 : Background A vertical scroll
 20A00A : Window A vertical scroll
 20A00C : Background B vertical scroll
 20A00E : Window B vertical scroll

 These registers are latched close to the negative edge of H-Sync on
 the last line of the frame (the scanline before the first line of the
 active display). While the specific timings aren't known, it seems
 the registers at $20A008 are read before $20A000.

 This latching behavior prevents mid-frame changes to the display from
 taking effect. One workaround is to use the line scroll table function.

 Horizontal scroll register

 MSB             LSB
 s--- ---- ---- ---- : Line scroll enable (1= on, 0= off)
 ---- --xx xxxx xxxx : Horizontal scroll

 If line scrolling is enabled the scroll field has no effect on the display.

 Vertical scroll register

 MSB             LSB
 e--- ---- ---- ---- : Layer enable (1= blanked, 0= shown)
 -ss- ---- ---- ---- : Playfield size (for backgrounds only, not windows)
 ---- --yy yyyy yyyy : Vertical scroll

 Line scrolling

 The line scroll tables consist of 512 entries of 16-bit words with the
 following format:

 MSB             LSB
 e--- ---- ---- ---- : ABSEL override flag (Plane A window only)
 ---- --xx xxxx xxxx : Horizontal scroll

 If line scrolling is enabled the horizontal scroll value specified here
 is used instead of the one in the soft registers at $20A00x.

 ABSEL override function

 The plane A window line scroll table has a secondary function. When ABSEL
 mode is enabled and bit 15 of a particular line scroll entry is set,
 plane A is shown for the entire width of the corresponding scanline
 regardless of the ABSEL position. Plane B will not be visible on that line.

 This allows the screen to be split horizontally by the ABSEL register
 into two halves, and vertically into any number of segments on a line by
 line basis. This function works regardless of line scrolling being enabled
 or not, or the plane A window layer being enabled or not. So even if
 line scroll was disabled for all layers and the plane A window was disabled,
 this function still operates as described.

 From a hardwawre point of view, the state of bit 15 is output on the
 DSPABH pin to the mixer chip for a duration of one scanline (656 pixels).
 If consecutive lines have this bit set, the output remains high with
 no gaps. Since the tilemap chip doesn't know about the state of the ABSEL
 mode bit in the mixer chip it will control DSPABH even if ABSEL mode
 is disabled.

 Playfield size field

 The playfield size is defined as follows

 D14 D13
  0   0   : 64x64 (Normal)

            A reset mask bit shows the background name table using background
            scroll registers.

            A set mask bit shows the window name table using window
            scroll registers.

  0   1   : 64x128 (Tall playfield)

            The 64x128 playfield uses the background name table for the top
            64x64 area, and the window name table for the bottom 64x64 area.
            This forms a composite 64x128 layer using both name tables.

            A reset mask bit shows the composite layer using background
            scroll registers.

            A set mask bit shows the composite layer using window scroll
            registers.

            If the background and window scroll registers are programmed
            identically you can't distinguish areas where the mask bits
            are set or reset.

  1   0   : 128x64 (Wide playfield)

            The 128x64 playfield uses the background name table for the left
            64x64 area, and the window name table for the right 64x64 area.
            This forms a composite 128x64 layer using both name tables.

            A reset mask bit shows the composite layer using background
            scroll registers.

            A set mask bit shows the composite layer using window scroll
            registers.

            If the background and window scroll registers are programmed
            identically you can't distinguish areas where the mask bits
            are set or reset.

  1   1   : 64x64 (Window-only)

            A reset mask bit shows the window name table using background
            scroll registers.

            A set mask bit shows the window name table using window
            scroll registers.

            The background name table cannot be displayed in this mode.

 Display quirks

 The same hardware that is used to render the background layer is used for
 the window layer. This causes a problem when the lower 3 bits of the
 horizontal scroll are nonzero between tiles that are adjacent to each other
 and come from different layers as specified by the mask bit.

 The 8-pixel shift registers are only loaded once every 8 pixels. So in this
 case the shift register will still contain data from the left tile, which
 is shifted out into the right tile.

 If the layer to the left has a scroll value of zero, then the shift
 register is empty. Pixels 1-7 of the layer to the right are filled in
 with the background color of the left tile instead.

 For example if the background is displayed and the window is blanked, and
 the mask bit is set, a blank tile from the window is shown. However the
 hardware is still loading data from the window which was ultimately
 not displayed, and if the horizontal scroll is 1-7, you'll see the
 unblanked window data shifted out into the background until the shift
 registers reload with background tile data.

 Tilemap hardware registers

 Registers are write only.

 ABSEL register ($220000)

 MSB             LSB
 ---- --xx xxxx xxxx : Pulse width of ABSEL signal.

 See the timing section below for specifics.
 
 HOUT register ($240000)

 MSB             LSB
 ---- --xx xxxx xxxx : Horizontal position of HOUT pulse. 

 VOUT register ($260000)

 MSB             LSB
 ---- ---y yyyy yyyy : Vertical position of VOUT pulse.

 Mode register ($270000)

 MSB             LSB
 ---- ---- ---- ---m : Sync mode (0= normal, 1= invalid)

 Tilemap signals

 Tilemap and mixer interface

 SA0-SA3        4-bit pixel data from tile pattern RAM.
                To mixer input SA3-0. (SB3-0)

 SA4-SA12       8-bit palette number from name table (bits 14-7).
                To mixer input SA12-4. (SB12-4)

 SA13           1-bit priority field from name table bit 15.
                To mixer input FA (FB).

 SKA            Background/window indicator.
                To mixer input WA (WB).

 ABSEL          Overlapping or side-by-side background indicator.
                To mixer input AXBV.

 DSPABH         ABSEL override flag. Outputs the state of bit 15 of the
                current scanline in the Plane A Window line scroll table
                for one scanline (656 pixels).
                To mixer input AXBH.

 ----------------------------------------------------------------------------
 System timing
 ----------------------------------------------------------------------------

 Display timing

 656 pixels per scanline:

  69 pixels from /HSYNC high to /BLANK high (left border)
 496 pixels from /BLANK high to /BLANK low (active display)
  43 pixels from /BLANK low to /HSYNC low (right border)
  48 pixels from /HSYNC low to /HSYNC high (horizontal sync. pulse)
 
 424 scanlines per frame:

  25 scanlines from /VSYNC high to /BLANK high (top border)
 384 scanlines from /BLANK high to /BLANK low (active display)
  11 scanlines from /BLANK low to /VSYNC low (bottom border)
   4 scanlines from /VSYNC low to /VSYNC high (vertical sync. pulse)

 The pixel clock is 16 MHz, giving an effetive frame rate of 57.52
 frames per second.

 In the invalid display mode, horizontal timings are the same, vertical
 timings are approximately as follows.

 512 scanlines per frame:
  64 scanlines blanked
  35 scanlines visible
   4 lines sync during active display
 409 scanlines visible

 There are some other quirks in this mode such as positioning of the
 interrupts, two VOUT pulses per frame which retriggers the sprite
 rendering, etc. This mode is not useful.

 ABSEL timing

 Ranges for this 10-bit counter are:

 $0000 : Plane B fills the whole screen
 $0001 : 1 pixels plane A, 495 pixels plane B
 $00F8 : 248 pixels plane A, 248 pixels plane B
 $01EF : 495 pixels plane A, 1 pixel plane B
 $01F0-: Plane A fills the whole screen
-$03FF : Plane A fills the whole screen

 ABSEL is generated ahead of the blanking and sync signals by the tilemap
 generator because the mixer chip needs a few pixel clock cycles to process
 the data. It leads those signals by 4 pixels. The actual pulse width
 is can be wider than the display, but only the ranges listed above have
 any impact on the screen.

 Interrupt timing

 The V-Blank interrupt is generated on line 383 of 424, the last visible
 scanline of the active display. The interrupt request line is asserted on
 the negative edge of H-Sync before blanking is disabled, and is held for one
 scanline (656 pixels) such that it is negated on the negative edge
 of H-Sync of the next scanline, line 384.

 The sprite interrupt is generated 15 scanlines after the line where VOUT
 is pulsed. On the scanline where it is going to trigger, the interrupt
 request line is asserted 35 pixels after the negative edge of HOUT, and is
 held for one scanline (656 pixels) such that it is negated 35 pixels after
 the negative edge of HOUT on the next scanline.

 Note that if the V-Blank or sprite interrupt handlers exit before one
 scanline's worth of time is up, the interrupt line will still be asserted
 and the interrupt will be triggered again on the same line. Typically
 the V-Blank interrupt will do enough processing for this time to elapse,
 but some care may be needed with the sprite interrupt handler which may
 not have much to do before returning.

 When the timer uses HOUT as the clock source, the interrupt request line
 is is asserted on the rising edge of HOUT (e.g. one pixel after HOUT is
 pulsed) and held until the corresponding CPU interrupt enable register
 is read or written.

 Example timings using the BIOS defaults:

 HOUT = FFC6
 VOUT = FFF0

 - V-Blank interrupt occurs on line 383 (last active line of display)
 - VOUT pulsed on line 409
 - HOUT pulsed 39 pixels after the falling edge of HSYNC
 - Sprite interrupt occurs on line 424, 74 pixels after the falling edge
   of HSYNC (35 pixels after the falling edge of HOUT).

 ----------------------------------------------------------------------------
 Sprites
 ----------------------------------------------------------------------------

 Command list

 Each command is 16 bits. The command counter is 11 bits wide, so only
 2048 commands starting at address zero can be processed. This restricts
 the command list to the first 32K of sprite RAM.

 Draw command

 00 : 00z- -lll llll llll : Independent zoom enable, link
 02 : wwww wwww hhhh hhhh : Horizontal zoom, vertical zoom
 04 : --nn nnnn nnnn nnnn : Tile number
 06 : pp-- cccc cccc cccc : Global pen modifier bits, CLUT address
 08 : vhhh yyyy yyyy yyyy : Vertical flip, sprite height, Y-coordinate
 0A : hwww xxxx xxxx xxxx : Horizontal flipp, sprite width, X-coordinate
 0C : ---- ---- ---- ---- : Unused
 0E : ---- ---- ---- ---- : Unused

 Color look-up tables (CLUTs)

 CLUTs are 16 bytes and can be positioned anywhere within the first 64K
 of sprite RAM. The sprite chip loads a CLUT into internal memory if
 the CLUT specified in drawing command is different from the one used
 by the last drawing command. The pen modifier function (see below) can
 also force a CLUT reload even if two sprites have identical CLUTs
 specified.

 For example you can force a reload of the CLUT on every sprite by
 alternating bit 0 of word #6 for each draw command.

 Sprite size

 The 3-bit size field is as follows:

 0 : 8 pixels
 1 : 16 pixels
 2 : 32 pixels
 3 : 64 pixels
 4 : 128 pixels
 5 : 256 pixels
 6 : 512 pixels
 7 : 1024 pixels

 Coordinates

 Sprites are positioned in a 4096x4096 virtual space. Position 0,0 is
 coordinate (0,0) of the framebuffer, $0FFF is one pixel to the left/
 one pixel above off screen. Sprites drawn near the edges of that
 virtual space wrap around, so you can draw a 64x64 sprite at (4000,4000)
 and part of it will appear in the upper left corner of the display.

 Zooming

 Normally the horizontal zoom value specified the zoom value for both
 axes. If bit 13 is set, they can be independently specified. Zoom
 values are:

 0F : 1/4 size
 1F : 1/2 size
 3F : Normal size as specified by width/height
 7F : 2x size
 FF : 4x size

 A zoom word of $7B4E zooms a 256x256 sprite to exactly 496x384 pixels.

 Rendering timing

 There is enough time to process all 2048 commands. There is also enough
 time to render 2048 8x8 sprites, but it is near the upper limit of how
 much drawing time is available. There is also enough time to process
 1024 clip commands and 1024 8x8 sprite draw commands when they are
 interleaved.

 Drawing time goes down sharply as larger sized sprites or scaled-up
 sprites are used.

 If a sprite is 1024 pixels wide and 512 or 1024 pixels tall, there is only
 enough time to draw about 272 rows of 1024 pixels across to the framebuffer.

 A scaled-down sprite takes the same amount of time to draw as a normal
 sized one. A scaled-up sprite takes more time to draw proportional to
 the final scaled size.

 Sprites that are off-screen take the same amount of time to draw as
 an on-screen sprite.

 Other comments on rendering

 For performance reasons it seems like end codes should have been added to
 this system. They were included in the similar sprite hardware of System 32
 and the Saturn.

 Similarly there is little feedback about the rendering state and no way
 to manually control framebuffer updates. So you can't lower the rendering
 rate to 30 Hz and draw more objects in two passes. This limitation was
 accounted for in the Saturn.

 For a double buffered display, an ideal method of preparing sprites is to
 have two sprite command lists in RAM, and change the link field of the
 first entry to point to one or the other. This would allow you to completely
 modify one list while the hardware renders from the other.

 Even though there is enough time to draw 2048 sprites, the sprite chip
 can also only process 2048 commands. If you split the RAM into two lists
 you can have two lists of about 1024 entries each. This artificially halves
 the on-screen sprite count just because there aren't enough addressable
 commands left, even though there is sufficient drawing time. If the command
 list counter was 13 bits instead of 12 this limit would not exist. Given
 how large sprite RAM can be (256K to 512K) it seems unusual to limit the
 commands to the first 32K and CLUTs to the first 64K.

 Skip command

 00 : 10-- ---- ---- ---- : Skip command
 00 : ---- -lll llll llll : Link field
 02 : ---- ---- ---- ---- : Unused
 04 : ---- ---- ---- ---- : Unused
 06 : ---- ---- ---- ---- : Unused
 08 : ---- ---- ---- ---- : Unused
 0A : ---- ---- ---- ---- : Unused
 0C : ---- ---- ---- ---- : Unused
 0E : ---- ---- ---- ---- : Unused

 The skip command provides a quick way to disable sprites by setting bit
 15 of their lead entry. All other fields are unused, and processing
 continues with the next command specified by the link field.

 End command

 00 : 11-- ---- ---- ---- : End command
 02 : ---- ---- ---- ---- : Unused
 04 : ---- ---- ---- ---- : Unused
 06 : ---- ---- ---- ---- : Unused
 08 : ---- ---- ---- ---- : Unused
 0A : ---- ---- ---- ---- : Unused
 0C : ---- ---- ---- ---- : Unused
 0E : ---- ---- ---- ---- : Unused

 This command ends rendering. No more commands will be parsed until
 the next frame.

 Rendering can also end when you run out of drawing time (too many large
 objects rendered), or if the link fields create a loop. When this happens
 the loop is repeatedly processed until rendering time is over.

 Clip command

 00 : 01-- ---- ---- ---- : Clip command
 00 : ---- -lll llll llll : Link field
 02 : hv-- ---- ---- ---- : Horizontal flip flag, vertical flip flag (1=on)
 02 : --c- ---- ---- ---- : Clip mode (1=inclusive, 0=exclusive)
 02 : ---- ---- ---- --pp : Global pen modifier bits
 04 : ---- ---t tttt tttt : Top scanline of clipping rectangle
 06 : ---- ---l llll llll : Left pixel column of clipping rectangle
 08 : ---- ---b bbbb bbbb : Bottom scanline of clipping rectangle
 0A : ---- ---r rrrr rrrr : Right pixel column of clipping rectangle
 0C : ---- ---- ---- ---- : Unused
 0E : ---- ---- ---- ---- : Unused

 Flip flags

 The flip flags affect how the framebuffer is addressed, it isn't related
 to the per-sprite flip flags.

 If the horizontal flip bit is set, the X position to draw at is 511-X.
 If the vertical flip bit is set, the Y position to draw at is 383-Y.

 For example if both flip bits are set, a sprite at (0,0) that would normally
 drawn to the right and downwards from that position is now drawn at
 (511,383) to the left and upwards from that new position.

 Clip mode

 If the clip mode is set to 0 (inclusive), the clipping rectangle defines
 an area where sprites can be drawn. Sprites outside that area are not drawn.

 If the clip mode is set to 1 (exclusive), the clipping rectangle defines
 an area where sprites cannot be drawn. Sprites outside that area are drawn.

 If the top coordinate is larger than the bottom coordinate, or if the left
 coordinate is larger than the right coordinate, the clipping function
 is disabled regardles of the clip mode.

 If the top coordinate is equal to the bottom coordinate, or if the left
 coordinate is equal to the right coordinate, clipping is enabled for a
 single pixel (point, column, or row) area.

 For example if all clip coordinates have the same value, the clip area
 is a single pixel at that coordinate.

 Clipping does not speed up sprite rendering, even if a sprite is partially
 or entirely clipped.

 There is only enough state for one clipping window; the hardware doesn't
 support two clip windows (e.g. an inclusive and exclusive window).

 Global pen modifier

 This function modifies bits 7,6 of the 8-bit pixel written to the
 framebuffer. It is controlled globally by the clip command and locally
 by the draw command as follows:

 - Clip command, word #4, bits 1,0 
 - Draw command, word #4, bits 15,14

 The possible settings are:

 Clip  Draw   Function 
 0000  0000   No change
 0000  4000   No change
 0000  8000   No change
 0000  C000   No change

 0001  0000   Reset bit 6
 0001  4000   Set bit 6
 0001  8000   Reset bit 6
 0001  C000   Set bit 6

 0002  0000   Reset bit 7
 0002  4000   Reset bit 7
 0002  8000   Set bit 7
 0002  C000   Set bit 7

 0003  0000   Reset bits 7,6
 0003  4000   Reset bit 7, set bit 6
 0003  8000   Set bit 7, reset bit 6
 0003  C000   Set bits 7,6

 The processing flow is as follows:

 1. Read pixel nibble from tile data, look up corresponding value in CLUT
 2. If CLUT value is zero treat pixel as transparent
 3. Modify CLUT index, look up value in CLUT
 4. If CLUT value is zero treat pixel as transparent

 Because transparency is evaluated twice, this ensures a pixel that maps
 to a CLUT value of $00 is always transparent, even if the modification
 would have made it opaque.

 Conversely the modifications can make an opaque pen transparent; such
 as if the CLUT value was $C0 and the modifier to reset bits 7,6 was used.

 This function simplifies modifying the priority (or palette) for sprites
 without having to go through and modify multiple sprite attributes in RAM.

 Cache behavior

 Even if two sprites have the same CLUT specified in draw command word #4,
 if the pen modifier function changes the final CLUT used during rendering
 then the CLUT cache is reloaded.

 Issuing a clip command itself does not affect the last cached CLUT value.

 Framebuffer scanning

 When the sprite chip scans out the framebuffer data to be combined with the
 tilemap chip's pixel stream, this scanning operation needs to be
 synchronized to the display signals. To do this, the sprite chip inputs
 three signals from the tilemap chip:

 - 16 MHz pixel clock.
 - HOUT, a one pixel wide negative pulse.
 - VOUT, a one scanline (656 pixel) wide negative pulse.

 HOUT is generated by a comparator that compares the horizontal pixel
 counter to a 16-bit register at $240000. When the count matches the
 register contents, HOUT is asserted. This happens on every scanline
 of the display, or 424 times per frame

 VOUT is generated by a comparator that compares the vertical scanline
 counter to a 16-bit register at $260000. When the count matches the
 register contents, VOUT is asserted. This happens once per frame.

 If the HOUT and VOUT registers are programmed to have invalid values such
 that one or both signals are not pulsed, the sprite generator uses the
 last valid settings they were set to. This allows the sprite chip to
 continue to scan out the framebuffer with a complete absence of any
 video synchronization signals other than the pixel clock.

 To accomplish this, I can only assume it has its own internal
 pixel and scanline counters. By counting durations between HOUT and VOUT
 pulses it can determine the frame extents:

 - Two HOUT pulses are 656 pixels apart
 - Two VOUT pulses are 424 scanlines apart

 This allows it to continue to generate internal timing signals even
 when they aren't supplied externally.

 Using HOUT and VOUT

 Changing these values allow the framebuffer layer to be scrolled relative
 to the tilemap layer. However this also affects when the sprite chip
 interrupt is generated relative to the V-Blank interrupt. It seems to
 be more useful as a way to center the 512x384 framebuffer within
 the 496x384 visible display.

 Based on the display width and height as defined by the HOUT and VOUT
 pulses, which define an internal screen state, the sprite chip only
 outputs the 512x384 framebuffer for the upper left corner of the
 656x424 frame.

 If the HOUT and VOUT registers are set up so that a non-visible portion of
 the framebuffer is shown in the active display, the sprite chip outputs a
 zero value on the pixel bus. You do not see any framebuffer data that is
 repeated within that area, nor is garbage shown.

 This would allow adjustment of HOUT and VOUT to "shake" the sprite layer
 for a game effect without revealing repeated data or garbage outside of
 the framebuffer extents.

 With the BIOS defaults of HOUT=$FFC6, VOUT=$FFF0, this aligns framebuffer
 position (8,0) with display position (0,0), which provides an 8-pixel wide
 off-screen area to the left of the visible portion of the framebuffer.

 To map framebuffer position (0,0) to display position (0,0) a value
 of $FFCE should be used for HOUT.

 HOUT can be changed per-scanline to change the framebuffer horizontal
 scroll on every scanline. VOUT seems to be latched at multiple points in
 a frame outside of the active display, but at least during the active
 display it cannot be used to adjust the vertical scrolling on a line by
 line basis.

 Settings for HOUT and VOUT ranges

 VOUT ranges

 This is a 9-bit register. Pulse locations:

 0000 : Pulse triggered on first line of active display
 017F : Pulse triggered on last line of active display
 0180-
 01D7 : No match
 01D8 : Pulse triggered on first line of vertical blanking
 01FF : Last line of vertical blanking
 0200 : Pulse triggered on first line of active display

 HOUT ranges

 This is a 10-bit register. Pulse locations:

 0000 :  97 pixels from falling edge of H-sync
 0001 :  98 pixels from falling edge of H-sync
 0014 : 117 pixels from falling edge of H-sync (synch. with +ve edge of BLANK)
 0204 : 613 pixels from falling edge of H-sync (synch. with -ve edge of BLANK)
 022E : 656 pixels from falling edge of H-sync
 022F :   0 pixels from falling edge of H-sync (synch. with -ve edge of SYNC)
 0230-: No match
-039F : No match
 03A0 :   1 pixels from falling edge of H-sync
 03CF :  48 pixels from falling edge of H-sync (synch. with +ve edge of SYNC)
 03FF :  96 pixels from falling edge of H-sync

 ----------------------------------------------------------------------------
 End
 ----------------------------------------------------------------------------