Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

80 Commits
 
 
 
 

Repository files navigation

IGS PGM Tech Scroll

Overview

IGS PolyGame Master is a cartridge based system designed for use in an arcade cabinet, with a typical JAMMA pinout.

Main board

From a programmer's perspective, the important components located on the motherboard are:

Cartridge

A cartridge consists of two Printed Circuit Boards.

Top board contains:

  • 16 bit P ROM with main CPU program with 23 bit address space (max 16 MB),
  • 16 bit T ROM with tile graphics with 23 bit address space (max 16 MB),
  • Cartridge-dependent add-ons mapped to main CPU address space,
  • Different custom ASICs for copy protection purposes (to do)

Bottom board contains:

  • 8 bit M ROM with audio samples data with 24 bit address space (max 16 MB)
  • 16 bit B ROM with sprite pixel masks and pixel color offsets with 23 bit address space (max 16 MB)
  • 15 bit A ROM with sprite pixel color data with 25 bit address space (max 64 MB)

Logical components layout

The main CPU is memory-mapped into its address-space: BIOS, work RAM, video / palette RAM, Z80 interface, Z80 work RAM, I/O registers, external P ROM and cartridge-dependent add-ons.

The secondary CPU has access to its work RAM, main CPU interface and sound chip interface.

The video chip has access to video / palette RAM, internal 2 MB of tile data ROM, and external T, B and A ROMs

The sound chip has access to internal 2 MB audio samples ROM and external M ROM

68000 Memory map

address rage mirroring description
$000000-$01ffff $0e0000 internal BIOS
$100000-$7fffff - P program ROM and cartridge-dependent add-ons
$700006-$700007 - W/O irq4 ack
$800000-$81ffff $0e0000 main work RAM
$900000-$907fff $0f8000 video RAM
$a00000-$a01fff $0fe000 palette RAM
$b00000-$b0ffff $0f0000 video registers
$c00000-$c0000f $0e7ff0 Z80 interface and RTC regs
$c08000-$c08007 $0e7ff8 I/O regs
$c10000-$c1ffff $0e0000 Z80 RAM
$d00000-$ffffff - Cartridge-dependent add-ons

Internal BIOS

The machine starts up from its internal BIOS. If no cartridge is inserted (or the test button is pressed), the test menu is launched. Without a cartridge it is mirrored to the first 8MB of address space.

Program ROM

P ROM must start from first 128 entries of Motorola 68000 exception vector table and some mandatory fields.

offset size description
$000 $04 initial stack pointer
$004 $04 program start address
$070 $04 irq4, configured by cartridge, e.g. coin insertion
$078 $04 irq6, VBL interrupt
$200 $20 IGS PGM PLATFORM GAMES\0\0\0\0\0\0\0\0\0\0
$220 $10 zero padded game name
$230 $0a version string
$23a $04 initialization routine address
$23e $0a date
$248 $08 time

Each word of image file must have its bytes swapped.

initialization

According to snake source code:

; initialization routine
; very similar to the USER routine in Neo-Geo
fn_initialize_fn
	; same w/py2k2, photoy2k, kov2, dmnfront, probably others
	; link    A6, #$0
	; move.l  D2, -(A7)

	; move.w ($8,A6), D2	; select what this function does...
	; if (D2 == 0) initialize (set up) NV RAM
	; if (D2 == 1) operator setting menu (soft dips)
	; if (D2 == 2) seems to do nothing? (bra.s	lb_skip_initialize)
	; if (D2 == 3) seems to do nothing?	(bra.s	lb_skip_initialize)
	; if (D2 >= 4) skip initialize ?
lb_skip_initialize
	; move.l  (A7)+, D2
	; unlk    A6
	rts

IRQ4 Ack

IRQ6 VBL

Main work RAM

Work RAM usage:

address range description
$800000-$8009ff buffer for 256 sprites 10 bytes each
$800a00-$81ffff general usage

Video RAM

address range description
$900000-$900fff definition of 64*16 background layer, 2 words each tile
$904000-$905fff definition of 64*32 text layer, 2 words each character
$907000-$9077ff row scroll RAM

The IGS023 controls CPU access to video RAM. During the horizontal blanking period all of the text layer RAM for the current scanline is read, then during the visible portion of the scanline the background layer RAM is accessed as needed. The current row scroll value is read during the horizontal sync pulse, briefly interrupting the text layer reads. Normally the IGS023 will de-assert the 68000's DTACK if the CPU tries to access video RAM while the IGS023 is accessing it, this will cause the CPU to stall until the IGS023 is done. Setting bit $0400 of the $b0e000 control flags disables this behavior, giving the CPU priority access to video RAM. While this does allow for faster CPU access, it does cause visual artifacts because the IGS023 will read whatever value is at the address the CPU is pointing to. It does not pause it's processing while the CPU is accessing the video RAM.

Palette RAM

address range description
$a00000-$a007ff 32 * 2 bytes x 32 sprite palettes
$a00800-$a00fff 32 * 2 bytes x 32 background palettes
$a01000-$a011ff 16 * 2 bytes x 32 text palettes
$a01200-$a01fff unused palette RAM

68000 CPU access to palette RAM goes through the IGS023, however the CPU has priority access. Reading or writing to palette RAM during the visible part of the frame will cause visual artifacts on screen because the IGS023 will read whatever color value the CPU is currently accessing instead of the real value it wants to display. Games typically update palette data during the vertical blank to avoid this. You can see what this looks like by running the memory test in the BIOS.

Video Registers

address range description
$b00000-$b00fff buffer for 256 sprites 16 bytes each copied by sprite DMA
$b01000-$b0103f zoom table, 16 entries * 4 bytes each, W/O.
$b02000-$b02001 background scroll up
$b03000-$b03001 background scroll left
$b04000-$b04001 BG layer scaling
$b05000-$b05001 text scroll up
$b06000-$b06001 text scroll left
$b07000-$b07001 screen scanline, R/O
$b0e000-$b0e001 control flags

$b0e000 Control flags

..dcba98765432.0
  │││││││└┤│││ └─ $0001: sprite dma enable - pulse 0->1 to trigger
  │││││││ │││└─── $0004: irq4 clear to ack, set to enable. Triggered every 62 scanlines (3.968 ms), not synced to VBL
  │││││││ ││└──── $0008: irq6 clear to ack, set to enable. Triggered each VBL
  │││││││ │└───── $0010: ? all games set this
  │││││││ └────── $0060: ? all games except CAVE set this, but seems to serve no purpose
  ││││││└──────── $0080: ? causes system to lose video synch
  │││││└───────── $0100: ? shows garbage on screen for all except background
  ││││└────────── $0200: ? disable everything except background layer
  │││└─────────── $0400: prioritize CPU access to video RAM
  ││└──────────── $0800: disable text layer
  │└───────────── $1000: disable background layer
  └────────────── $2000: disable high priority sprites

$b04000 BG layer scaling

.....a9876543210
     │└───┤└───┴─ $001f: horizontal scaling from 50% to 200%, 0x10 being 100%
     │    └────── $03e0: vertical scaling from 50% to 200%, 0x200 being 100%
     └─────────── $0400: unknown, set by some games

Z80 interface and RTC regs

address range description
$c00002-$c00003 R/W sound latch 1; main-CPU write asserts Z80 /NMI
$c00004-$c00005 R/W sound latch 2
$c00006-$c00007 R/W calendar
$c00008-$c00009 R/W Z80 reset: $a659 halts, $5050 runs
$c0000a-$c0000b R/W Z80 bus control: $45d3 grants the Z80 RAM bus to the main CPU
$c0000c-$c0000d R/W sound latch 3

I/O regs

address range description
$c08000-$c00001 R/O Player 1 & 2 controls
$c08002-$c00003 R/O Player 3 & 4 controls
$c08004-$c00005 R/O Extra controls
$c08006-$c00007 R/O Dip switches

All inputs are active low.

$c08000 Player 1 & 2 controls

fedcba9876543210
│││││││││││││││└─ $0001: P1 START
││││││││││││││└── $0002: P1 UP
│││││││││││││└─── $0004: P1 DOWN
││││││││││││└──── $0008: P1 LEFT
│││││││││││└───── $0010: P1 RIGHT
││││││││││└────── $0020: P1 A
│││││││││└─────── $0040: P1 B
││││││││└──────── $0080: P1 C
│││││││└───────── $0100: P2 START
││││││└────────── $0200: P2 UP
│││││└─────────── $0400: P2 DOWN
││││└──────────── $0800: P2 LEFT
│││└───────────── $1000: P2 RIGHT
││└────────────── $2000: P2 A
│└─────────────── $4000: P2 B
└──────────────── $8000: P2 C

$c08002 Player 3 & 4 controls

fedcba9876543210
│││││││││││││││└─ $0001: P3 START
││││││││││││││└── $0002: P3 UP
│││││││││││││└─── $0004: P3 DOWN
││││││││││││└──── $0008: P3 LEFT
│││││││││││└───── $0010: P3 RIGHT
││││││││││└────── $0020: P3 A
│││││││││└─────── $0040: P3 B
││││││││└──────── $0080: P3 C
│││││││└───────── $0100: P4 START
││││││└────────── $0200: P4 UP
│││││└─────────── $0400: P4 DOWN
││││└──────────── $0800: P4 LEFT
│││└───────────── $1000: P4 RIGHT
││└────────────── $2000: P4 A
│└─────────────── $4000: P4 B
└──────────────── $8000: P4 C

$c08004 Extra controls

...cba9876543210
   ││││││││││││└─ $0001: P1 COIN
   │││││││││││└── $0002: P2 COIN
   ││││││││││└─── $0004: P3 COIN
   │││││││││└──── $0008: P4 COIN
   ││││││││└───── $0010: P1/2 TEST
   │││││││└────── $0020: P1/2 SERVICE
   ││││││└─────── $0040: P3/4 TEST
   │││││└──────── $0080: P3/4 SERVICE
   ││││└───────── $0100: P1 D
   │││└────────── $0200: P2 D
   ││└─────────── $0400: P3 D
   │└──────────── $0800: P4 D
   └───────────── $1000: RESET

$c08006 Dip switches

........7..43210
        │  ││││└─ $0001: Test mode (1-ON,0-OFF)
        │  │││└── $0002: Music (0-ON,1-OFF)
        │  ││└─── $0004: Voice (0-ON,1-OFF)
        │  │└──── $0008: Free Play (0-ON,1-OFF)
        │  └───── $0010: Stop Mode (0-ON,1-OFF)
        └──────── $0080: QC mode (1-ON,0-OFF)

With the QC mode dip switch set, you can hold A+B when turning on the PGM to access the QC test menu. QC menu options are selected by pressing 1P START rather than 1P A as with the normal operator test menu. You can also hold B+C down in QC mode when turning on the PGM to access cartridge-dependent additional test elements (eg. protection ASIC test, tile ROM test).

Z80 memory map

The whole Z80 address space is occupied by RAM, that is populated by main CPU.

Z80 I/O map

address range description
$8000-$8003 ICS 2115 interface (4 registers)
$8100-$81ff sound latch 3
$8200-$82ff sound latch 1; Z80 read clears /NMI
$8400-$84ff sound latch 2

Main CPU ↔ Z80 communication

The Z80 is a sound coprocessor with no ROM of its own: its entire address space is RAM that the main CPU populates. Three hardware primitives connect the two CPUs:

Everything above these — command formats, mailboxes, handshakes — is software convention layered on top of these primitives.

Shared Z80 RAM window

The main CPU sees the 64 kB of Z80 RAM through the $c10000-$c1ffff window (see Z80 RAM). The window is 16-bit while the Z80 is byte addressed, so a byte stream written by the main CPU is laid out as:

  • even Z80 address → high byte ([15:8]) of the main-CPU word,
  • odd Z80 address → low byte ([7:0]).

So when uploading Z80 code or data, byte 0 goes in the high half of the first word, byte 1 in the low half, byte 2 in the high half of the second word, and so on. The main CPU can only access this RAM while it owns the bus (see below).

Bus arbitration

The Z80 RAM is shared, so the main CPU must take the bus from the Z80 before touching the window and give it back afterwards. Two registers drive this:

register value effect
$c00008 Z80 reset $a659 assert Z80 reset (halt)
$5050 release Z80 reset (run); also pulses the ICS2115 reset
$c0000a Z80 bus control $45d3 request the Z80 RAM bus for the main CPU (asserts BUSRQ)
other (eg. $0a0a) leave the Z80 running

Writing $45d3 to $c0000a asserts the Z80 BUSRQ. The Z80 finishes its current machine cycle, tri-states its address / data / control buses and asserts BUSAK; only then does the main CPU own the $c10000 window. Writing any other value (eg. $0a0a) releases BUSRQ; the Z80 reclaims its buses and resumes from the exact instruction it was paused on — taking the bus this way is non-destructive.

There are two acquisition paths:

  • Z80 held in reset ($c00008 = $a659): the bus is granted immediately, because a reset Z80 is not running and cannot acknowledge. Used while uploading the driver.
  • Z80 running: the main CPU must wait for BUSAK before the window is valid. As there is no ready flag to poll, drivers insert a short fixed delay after writing $45d3. Used to poke RAM while the sound driver runs.

A typical upload is therefore: assert reset and request the bus → fill / write Z80 RAM → release the bus, then release reset to start the Z80 from $0000.

Sound latches

Three 16-bit latch registers are shared between the main CPU and the Z80. Each is a single register, readable and writable from both sides; the hardware enforces no direction, so producer / consumer roles are purely software convention. The Z80 sees only the low byte.

latch main CPU Z80 I/O hardware side effect
1 $c00002 $8200 main-CPU write asserts Z80 /NMI; Z80 read clears it
2 $c00004 $8400 none
3 $c0000c $8100 none

Only latch 1 has a side effect, which makes it the natural "doorbell": the main CPU writes a command (or a token) to $c00002, the resulting /NMI wakes the Z80, and the Z80 reads $8200 to both consume the value and clear the interrupt. Latches 2 and 3 are plain shared bytes with no signalling.

Interrupts

The Z80 has two interrupt sources, conventionally used under interrupt mode 1:

line vector source
/NMI $0066 main-CPU write to sound latch 1 ($c00002); cleared by Z80 read of $8200
/INT $0038 ICS2115 IRQ line

So the maskable interrupt belongs to the sound chip, while the non-maskable interrupt belongs to the host. A driver typically handles incoming main-CPU commands in the /NMI handler and ICS2115 events in the /INT handler.

Video chip operation

Video generation is handled by a custom video chip IGS023. It generates 448 x 224 resolution (10 MHz pixel clock) display with standard 4:3 display aspect ratio.

Display is composed of three graphics layers:

  1. Background tiles layer
  2. Sprites layer
  3. Foreground text layer

Text layer is drawn always on top. Each sprite has a priority setting enabling it to be drawn below or above tiles layer.

Palettes

Graphics of each layer is palettized with each own individual palette. Each tile, text character or sprite has 5-bit index to one of 32 palettes. Background layer and sprites layer has 5-bit pixels determining a color of each pixel as a one of 32 colors each, whereas text layer has 4-bit pixels which translates to 16 colors.

All palette tables has common 15-bit palette format:

Palette format

.rrrrrgggggbbbbb
 └───┤└───┤└───┴─ $001f: blue color component
     │    └────── $03e0: green color component
     └─────────── $7c00: red color component

Sprites layer palette

Sprites layer is defined with 5-bit palette index for each sprite with 5-bit color index into each palette for each pixel which gives 32 palettes of 32 16-bit colors entries mapped for the main CPU into the address range $a00000-$a007ff.

Background layer palette

Background layer is defined with 5-bit palette index for each tile with 5-bit color index into each palette for each pixel which gives 32 palettes of 32 16-bit colors entries mapped for the main CPU into the address range $a00800-$a00fff. Last 31th color entry in each palette denotes transparent pixel, as a result each tile pixel can have 31 different colors.

Text layer palette

Text layer is defined with 5-bit palette index for each character with 4-bit color index into each palette for each pixel which gives 32 palettes of 16 16-bit colors entries mapped for the main CPU into the address range $a01000-$a011ff. Last 15th color entry in each palette denotes transparent pixel, as a result each character pixel can have 15 different colors.

Background tiles layer

Background tiles layer is displayed unless it is disabled with control flags register.

Background tile format

Each tile is a 32x32 square of 5-bit pixels defined in 32 rows of 32 pixels where each row is packed into 20 bytes. Each tile occupies 32*20 = 640 ($280) bytes. Tiles data shares space with text data within text/tiles T ROM.

Background tilemap

Tile map is located in the video memory mapped into the main CPU address space range $900000-$903fff. It is 16 kB arranged in 64 rows of 64 tiles each, where each tile occupies two words defined as:

$0 nnnnnnnnnnnnnnnn 
   └──────────────┴─ $ffff: tile number

$2 ........yxppppp. 
           ││└───┴── $003e: palette number
           │└─────── $0040: x-flip
           └──────── $0080: y-flip

where:

  • tile number multipied by tile size of $280 bytes gives an offset into the T ROM,
  • palette number multipied by palette size of $40 bytes gives an offset into the background palette,
  • x-flip flips the tile horizontally,
  • y-flip flips the tile vertically.

Background tilemap scrolling

To make the background tilemap scroll vertically, edit $b020000 (word) - increment it to make the layer scroll down, and decrement it to make it scroll up. To make the background tilemap scroll horizontally, edit $b03000 (word) - increment it to make the layer scroll to the right, and decrement it to make it scroll to the left.

Rowscroll can be used on the background tilemap: each line can be drawn with a horizontal offset. This is used in games such as Martial Masters (True Lotus Master stage), Espgaluda (Kakusei toggle), or the BIOS introduction itself (see below). In the range $907000-$9077ff, each word controls the offset of one line, for a total of 512 lines from top to bottom. Increment an offset to shift it to the right, and decrement it to shift it to the left.

NOTE: The BIOS introduction screen writes $0010 in the $9070c0-$9070ff range in order to offset the "PolyGame Master" text 16 pixels to the right. As such, make sure to write $0000 to this range in order to avoid unexpected background tilemap rendering.

Sprites layer

There are a maximum of 256 sprites on the PGM and they are copied via DMA from the first 2560 bytes of work RAM (10 bytes per sprite) to the internal sprite registers every frame. The sprite definition consists of 5 words of packed bits per sprite stored sequentially in memory:

$0 mttttxxxxxxxxxxx 
   |└──┤└─────────┴─ $07FF: X position (11 bit signed)
   |   └──────────── $7800: Horizontal Zoom/Shrink table select
   └──────────────── $8000: Horizontal Zoom/Shrink mode select

$2 mtttt.yyyyyyyyyy 
   |└──┤ └────────┴─ $07FF: Y position (10 bit signed)
   |   └──────────── $7800: Vertical Zoom/Shrink table select
   └──────────────── $8000: Vertical Zoom/Shrink mode select

$4 .vhpppppmxxxxxxx
    ||└───┤|└─────┴─ $007F: Sprite mask B ROM address MSB
    ||    |└──────── $0080: Priority mode (Over(0) or Under(1) background)
    ||    └───────── $1f00: Palette number
    |└────────────── $2000: Horizontal flip
    └─────────────── $4000: Vertical flip

$6 xxxxxxxxxxxxxxxx
   └──────────────┴─ $ffff: Sprite mask B ROM address LSB

$8 .wwwwwwhhhhhhhhh*
    └────┤└───────┴─ $01ff: Sprite height
         └────────── $7e00: Sprite width (in 16 pixel units)
  • Last sprite entry marker is $8 = .000000000000000.

Scaling

t-bits select an entry in an internal zoom table while m selects the operation mode as grow when set and shrink otherwise.

The zoom tables which are used for scaling the sprites have been determined by analysing bus access on the mask ROM bus:

   Normal   Flipped
 0 AAAAAAAA AAAAAAAA
 1 A8AAAAAA AAAAAA2A
 2 A8AAA8AA AA2AAA2A
 3 A8A8A8AA AA2A2A2A
 4 A8A8A8A8 2A2A2A2A
 5 88A8A8A8 2A2A2A22
 6 88A888A8 2A222A22
 7 888888A8 2A222222
 8 88888888 22222222
 9 80888888 22222202
10 80888088 22022202
11 80808088 22020202
12 80808080 02020202
13 80008080 02020002
14 80008000 00020002
15 00008000 00020000
16 00000000 00000000
17 00010000 00010000
18 00010001 00010001
19 01010001 00010101
20 01010101 01010101
21 01110101 01011101
22 01110111 11011101
23 11110111 11011111
24 11111111 11111111
25 11511111 11111511
26 11511151 15111511
27 51511151 15111515
28 51515151 15151515
29 51555151 15155515
30 51555155 55155515
31 55555155 55155555

The 5 bit number made from m as the high bit and t as the low bits is the index into the above table.

For unflipped sprites sample each bit in the table entry from 31 down to 0 circularly for each line rendered. When in shrink mode the sprite line after the current line will be skipped if the bit is set and in grow mode the current line will be duplicated if the bit is set. If not set the next line is rendered as normal.

Vertically flipped sprites start sampling the zoom table entry at the bit number given by the low 5 bits of the sprite height. If the height is 32, for example, zoom table sampling will start at bit 0 and continue as for normal sprites above.

Vertically flipped sprites terminate one line early in shrink mode if the zoom bit is set on the last line. This only happens for vertically flipped sprites, unflipped sprites render the last line as expected.

Foreground text layer

The foreground text layer is logically the same as the background layer but the tile (or character) size is 8x8 pixels and limited to 16 colours per tile. It can be too disabled with control flags register.

Character tilemap

Character tile map is located in the video memory mapped into the main CPU address space range $904000-$905fff. It is 8 kB arranged in 64 rows of 32 tiles each.

Character tile format

Each tile is a 8x8 pixels in size and comprises of 8 rows of 8 pixels packed into 4 bytes each row stored contiguously in memory. Each nibble represents a palette index into the palette specified by the text layer tile entry, the lowest order nibble is displayed first on screen (the leftmost pixel).

bbbbaaaa 
└──┤└──┴─ $0f: leftmost pixel
   └───── $f0: rightmost pixel

Each tile occupies 8*4 = 32 bytes. Text layer tile data shares space with background tile data within text/tiles T ROM. The address of the tile within the T ROM is calculated as the tile number (0-$ffff) multiplied by 32. This gives an effective address space of 2MB (0-$1FFFFF) for text layer tiles.

Text / tiles T ROM

The T ROM on the top (PROG) PCB can hold upto 16MB of data which is accessed by the PGM as 8Mx16. Both background and text tile data are stored in the T ROM.

Sprite Data ROMs

Sprites are split accross two different ROMs on the bottom (CHAR) PCB. The B ROM holds the address of the colour data and the transparency information for the sprite while the A ROM holds the palette index colour information. Information about the sprites size, location, palette and the address of the sprite data in the B ROM are part of the sprite definition. A combination of this information and the data in the A and B ROMs are used to render the sprite.

Bitmask B ROM

The first long-word read by the sprite engine (given by the sprite entry) from the B ROM is the address of the colour data from the A ROM. Following this is a bitmask defining the visibility (1) or transparency (0) of each pixel in the sprite. The sprite engine reads the B ROM pixel visibility sequentially, one word at a time (sprites are a multiple of 16 pixels wide), and processes the bits from LSB to MSB. Therefore the LSB of each word of visibility information is the leftmost pixel processed and the MSB is the rightmost.

The opacity bitmap is followed by the long-word with the address of the end of the colour data of given sprite. It enables rendering the sprite flipped vertically - the sprite engine processes the entry in B ROM backwards from the end (the end can be found knowing the dimensions of the sprite) drawing the sprite from right to left.

Sprite color A ROM

Sprite colour information is stored as 5 bits per pixel and packed into 3 pixels per word. Colour information is read sequentially when drawing sprites and is also processed from LSB to MSB.

.cccccbbbbbaaaaa 
 └───┤└───┤└───┴─ $001f: palette index (leftmost pixel)
     |    └────── $03e0: palette index
     └─────────── $7c00: palette index (rightmost pixel)

Bit 15 of the A ROM is physically unconnected on the CHAR PCB.

Audio chip operation

The ICS2115 WaveFront is a wavetable synthesizer with 32 voices. It reads sample data from the M ROM, applies a per-voice volume envelope and pan, and mixes the voices to a stereo DAC. The Z80 drives it through four I/O ports; samples play back from mask ROM (the chip's DMA channel is unused on PGM).

Host interface

The chip is reached through four Z80 I/O ports. Registers are accessed indirectly: the register number is written to the select port, then its data is read or written on the data ports.

address description
$8000 R/O IRQ / status register
$8001 R/W register-number select
$8002 R/W data low byte, or full 16-bit word
$8003 R/W data high byte

The chip's registers are internally 16-bit, accessed in halves. The byte lane follows the register bank, the boundary being $40:

  • synthesizer registers $00-$3F carry 8-bit data in the high byte ($8003); this includes the global ActiveOsc ($0e) / IRQV ($0f),
  • general registers $40-$7F carry 8-bit data in the low byte ($8002),
  • 16-bit registers span both halves: low byte on $8002, high byte on $8003.

The select port latches the register number until overwritten, so consecutive data accesses hit the same register. The status busy bit is unreliable on PGM; drivers do not poll it and instead space accesses with a short fixed delay.

$8000 Status register

7.5.3210
│ │ │││└─ $01: timer IRQ pending
│ │ ││└── $02: voice IRQ pending (oscillator or volume ramp)
│ │ │└─── $04: DMA IRQ (unused on PGM)
│ │ └──── $08: emulation IRQ (unused on PGM)
│ └────── $40: busy — unreliable on PGM
└──────── $80: any IRQ active

Clock and sample rate

The chip runs at 33.8688 MHz (= 768 × 44.1 kHz). One synthesis pipeline is time-multiplexed across the voices, 32 cycles each, so the sample rate depends on the active voice count held in ActiveOsc ($0e):

sample_rate = 33.8688 MHz / ((active_osc + 1) × 32)
active voices ActiveOsc ($0e) sample rate
32 $1f 33 075 Hz
24 $17 44 100 Hz

The rate is global and also sets pitch — playback_freq = fc × sample_rate / 1024 — so changing ActiveOsc ($0e) retunes every voice; it is set once at init. PGM uses 32 voices.

Register map

Only the registers used on PGM are listed here. The oscillator and volume registers are per voice (one multiplexed set): the voice number is written to OscNumber ($4f) before they are accessed. The remaining registers are global.

Oscillator registers (per voice):

register mnemonic description
$00 OscConf oscillator configuration
$01 OscFC playback frequency, 6.9 fixed point
$02-$03 OscStrtH/L loop start address, high / low
$04-$05 OscEndH/L loop end address, high / low
$0a-$0b OscAccH/L current sample address, high / low
$10 OscCtl key on ($00) / key off ($0f)
$11 OscSAddr sample bank (saddr)

Volume and envelope registers (per voice):

register mnemonic description
$06 VIncr volume ramp increment
$07 VStart volume ramp start, EEEEMMMM
$08 VEnd volume ramp end, EEEEMMMM
$09 VolAcc current volume, logarithmic
$0c OscPan pan: $00 left, $ff right, $7f centre
$0d VCtl volume ramp control
$12 VMode volume-envelope step law, [1:0] (see VIncr)

Global registers:

register mnemonic description
$0e ActiveOsc R/W active voices: $1f = 32 voices
$0f IRQV R/O interrupt source
$40-$41 Timer1/2 R/W timer 1 / 2 preset; a read clears that timer's IRQ
$42 Timer1PreS W/O timer 1 prescaler
$43 Timer2PreS_S R/W timer 2 prescaler (W) / timer status (R)
$4a DOCIntCS W/O master timer IRQ enable ($01)
$4d SysCtrl R/W system control; bits 0 & 2 = master run gate (must be set)
$4f OscNumber W/O voice select: the voice number precedes per-voice register access

Oscillator

Each voice plays a sample by advancing a 20.9 fixed-point position accumulator (OscAccH/L ($0a-$0b)) at a rate set by the frequency counter OscFC ($01). The integer part addresses the sample; the 9-bit fraction drives linear interpolation between adjacent samples. The full ROM address is (saddr << 20) | (acc >> 9), with saddr from OscSAddr ($11), which selects a 1 MB bank; the 20-bit index addresses within it and does not carry into saddr, so a single sample is confined to one 1 MB bank. Playback starts from the value written to accumulator (OscAccH/L ($0a-$0b)), on reaching the end the oscillator loops, reflects (bidirectional), or — with looping disabled — stops the voice and clamps to the boundary defined by OscStrtH/L ($02-$03) (start) and OscEndH/L ($04-$05) (end). Reaching a boundary can also raise an interrupt.

OscConf ($00)

76543.10
│││││ └┴─ $03: sample format (see below)
││││└──── $08: loop enable
│││└───── $10: bidirectional loop
││└────── $20: oscillator boundary IRQ enable
│└─────── $40: reverse playback direction
└──────── $80: oscillator IRQ pending (hardware)

The format field [1:0] selects the sample type:

[1:0] format
00 8-bit linear
01 µ-law
10 16-bit linear
11 white noise

Format 11 is a free-running noise generator: it ignores ROM and emits an LFSR value whose pitch tracks OscFC ($01).

Volume, pan, envelope

The volume engine runs on a 26-bit accumulator whose value is the logarithm of loudness, so adding a constant step gives a perceptually even (dB-linear) ramp. A per-voice ramp sweeps it between VStart ($07) and VEnd ($08) by adding the VIncr ($06) step each tick; software builds ADSR by chaining ramp segments via the volume IRQ. OscPan ($0c) attenuates one channel before the final amplitude. PGM output is monophonic, though — the main board sums the chip's two channels (see mixing) — so pan only sets a voice's level in the mono mix (centre = both channels, hard pan = one), not a stereo position.

Volume scale

After the ramp, the final loudness reads the accumulator's top 12 bits as a float — the top 4 bits are the exponent, the next 8 the mantissa:

amplitude ≈ (256 + mantissa) × 2^exponent / 512

Each register reaches the accumulator from the top:

field accumulator bits
exponent [25:22] top 4 bits
mantissa [21:14] next 8 bits — with the exponent, the 12-bit amplitude index
VolAcc ($09) [25:10] top 16 bits — host read / write
VStart / VEnd [25:18] top 8 bits — ramp endpoints

So VStart / VEnd are the high byte of VolAcc, written as 8-bit EEEEMMMM (exponent nibble + the top 4 mantissa bits):

76543210
└──┤└──┴─ $0f: mantissa (top 4 of the 8 index mantissa bits)
   └───── $f0: exponent — each step ≈ doubling (~6 dB)

Low values are silent; $00-$01 are effectively silence. The ramp drives VolAcc from VStart << 8 to VEnd << 8; VolAcc's low byte and the bits below add finer position the endpoints cannot express. Similarly to sample position the initial volume value must be written to VolAcc.

VIncr ($06)

A single 8-bit value setting the volume-envelope step added to VolAcc ($09) each tick. VMode ($12) selects how the value maps to a step:

VMode ($12)[1:0] law step step LSB
00 exponential, slow 2^(VIncr / 32) bit 0
10 linear VIncr << 10 bit 10
01 / 11 exponential, fast 2^((VIncr + 256) / 32) — ×256 vs 00 bit 8

In the exponential laws every +32 in VIncr doubles the step (32 sub-steps per octave); larger steps ramp the volume faster. VMode[1] is a no-op there (0111). The step LSB column is the lowest accumulator bit a mode reaches: 00 accumulates at the full 26-bit resolution (down to bit 0), 10 only at the host-visible top 16 bits (bit 10). A step large enough to overflow VolAcc ($09) wraps it back below VEnd, so the boundary check is missed and the ramp free-runs instead of stopping.

VCtl ($0d)

76543210
│││││││└─ $01: ramp done (hardware sets on completion)
││││││└── $02: stop ramp
│││││└─── $04: rollover (read below)
││││└──── $08: ramp loop enable
│││└───── $10: bidirectional ramp
││└────── $20: volume ramp IRQ enable
│└─────── $40: ramp direction — 0 up, 1 down
└──────── $80: volume ramp IRQ pending (hardware)

rollover (bit 2) is a software-settable flag that the hardware clears when the volume ramp reaches its boundary (VEnd / VStart), which makes it a synchronisation signal. The BIOS uses it for voice teardown: it sets the bit, collapses the ramp (VStart ($07) = VEnd ($08)) so the boundary is reached at once, then polls bit 2 until the hardware clears it — confirming the engine has serviced the voice.

Programming a voice

A voice is programmed in this order:

  1. the voice is selected through OscNumber ($4f);
  2. OscCtl ($10) = $0f stops it;
  3. oscillator registers: OscFC ($01) frequency, OscSAddr ($11) bank, OscAccH/L ($0a-$0b) start position, OscStrtH/L ($02-$03) start, OscEndH/L ($04-$05) end;
  4. volume / pan: OscPan ($0c) pan, VIncr ($06) increment, VStart ($07) start, VEnd ($08) end, VolAcc ($09) current;
  5. OscConf ($00) configuration (format + loop), VCtl ($0d) ramp control;
  6. OscCtl ($10) = $00 keys it on.

Chip initialization

The init sequence is:

  • SysCtrl ($4d) bits 0 and 2 form the master run gate — until both are set the output is muted to silence, so they must be enabled;
  • ActiveOsc ($0e) = $1f — 32 active voices (33.075 kHz);
  • per voice: OscCtl ($10) = $0f (stop), OscConf ($00) = $00, VCtl ($0d) = $03 (ramp stopped), VStart ($07) = VEnd ($08) = $01 (silent);
  • DOCIntCS ($4a) = $01 enables the master IRQ gate.

The PGM BIOS wraps this in a longer SysCtrl ($4d) / MemCfg_Rev ($4c) dance whose remaining bits are undocumented; reproducing it is the safe option on real hardware.

Audio interrupts

The ICS2115 IRQ line drives the Z80 maskable /INT ($0038). It is level-sensitive: asserted while any enabled source is pending — (enabled system source) | per-voice (enable & pending) over all voices.

IRQV ($0f) reports the first pending voice and is polled until it returns $ff:

76543210
│││└───┴─ $1f: interrupting voice number
││└────── $20: always 1
│└─────── $40: clear = volume-ramp IRQ on this voice
└──────── $80: clear = oscillator IRQ on this voice

Clearing the pending bit alone does not acknowledge a voice IRQ: if the source condition persists, the level interrupt re-asserts at once. The enable bit must be cleared as well (OscConf ($00) &= ~$a0 / VCtl ($0d) &= ~$a0), or the voice stopped. DOCIntCS ($4a) is the master timer IRQ gate.

Timers

Two programmable timers raise periodic interrupts, counting down at the master clock (≈ 29.5 ns per tick) while the run gate (SysCtrl ($4d) bits 0 & 2) is set. Each has an 8-bit preset (Timer1 ($40) / Timer2 ($41)) and an 8-bit prescaler; the two use different period formulas:

Timer 1:  period = ((mult + 1) × (preset + 1)) << (4 + shift)    master-clock ticks
Timer 2:  period =              (preset + 1)    << (4 + shift)

Timer 2 ignores the prescaler multiplier. Achievable periods: Timer 1 ≈ 1.9 µs … 495 ms, Timer 2 ≈ 0.95 µs … 15.5 ms. The mult and shift fields share the prescaler byte but mean different things.

Timer1PreS ($42):

76543210
│ │└───┴─ $1f: mult — period × (n + 1); Timer 1 also counts only when non-zero
└─┴────── $e0: shift — period × 2^(4 + n)

Timer2PreS_S ($43) — write:

76543210
│ │││└─┴─ $07: unused
│ ││└──── $08: Timer 1 IRQ enable
│ │└───── $10: Timer 2 enable + IRQ enable
└─┴────── $e0: shift — Timer 2 period × 2^(4 + n)

A read of $43 returns the status: bits [1:0] = each timer's IRQ pending. Timers auto-reload; a timer's IRQ is cleared by a read of its preset ($40 / $41).

Mixing and output

Each voice contributes sample × volume (after pan) (shifted down to normalise) unless it has been stopped (OscCtl ($10) key-off). The contributing voices are summed in a wider accumulator — which gives headroom for many voices — and the sum is saturated to 16-bit stereo. The chip's DAC output is a serial, MSB-first, left/right-multiplexed stream (BCK = XTLI / 4); on PGM the main board sums these left and right channels into a single mono signal.

Cartridge pinout

Top PROG board

Left

nr bottom top nr
31 +5V +5V 32
30 PA14_OUT TA13 33
29 ? TA12 34
28 ? TA11 35
27 PA_13 TA10 36
26 ? TA9 37
25 PA12_OUT TA8 38
24 PA11_OUT TA7 39
23 PA10_OUT TA6 40
22 PA9_OUT TA5 41
21 PA8_OUT TA4 42
20 TD0 TA3 43
19 TD1 TA2 44
18 TD2 TA1 45
17 TD3 TA0 46
16 TD4 TA22 47
15 TD5 TA21 48
14 PGM_TILE_EN# TA20 49
13 TD6 TA19 50
12 TD7 TA18 51
11 TD8 TA17 52
10 TD9 TA16 53
9 TILE_CS# TA15 54
8 TD15 TA14 55
7 TD14 GND 56
6 TD13 GND 57
5 TD12 GND 58
4 TD11 GND 59
3 TD10 GND 60
2 ? CLK20 61
1 GND GND 62
signal notes
PGM_TILE_EN# Driven low to enable tile output from the PGM onboard tile ROM (default), driven high when cart is outputting tile data (TD15-0)
TILE_CS# Enables tile output on KOVSH cart, pulled down and not used on other games
CLK20 20Mhz system clock

Right

nr bottom top nr
31 +5V +5V 32
30 AS# PA23 33
29 ? PA22 34
28 BLANK# PA21 35
27 PGM_PRG_EN# PA20 36
26 WR# PA19 37
25 ? PA18 38
24 RESET# PA17 39
23 RD# PA16 40
22 ? PA15 41
21 ? PA14 42
20 PD15 PA13 43
19 PD14 PA12 44
18 PD0 PA11 45
17 PD1 PA10 46
16 PD2 PA9 47
15 PD3 PA8 48
14 PD4 PA7 49
13 PD5 PA6 50
12 PD6 PA5 51
11 PD7 PA4 52
10 PD8 PA1 53
9 PD13 PA2 54
8 PD12 PA3 55
7 PD11 U8 56
6 PD10 +5V 57
5 PD9 +5V 58
4 ? +5V 59
3 PA2_OUT +5V 60
2 PA1_OUT +5V 61
1 GND +5V 62
signal notes
AS# 68000 address strobe
PGM_PRG_EN# Driven low to enable program data output from the PGM onboard BIOS ROM (default), driven high when cart is outputting program data (PD15-0)
WR# Driven low when 68000 is writing to the bus
RESET# Driven low when system is in reset, connected to reset switch on PGM motherboard
RD# Driven low when 68000 is reading from the bus
BLANK# Driven low at the beginning of each renderered line (?) and during vblank. 224 short low bursts and 1 long per frame. Sprite DMA happens at line 221.

Bottom CHAR board

Left

nr bottom top nr
31 +5V +5V 32
30 BA17 BA14 33
29 BA20 BA16 34
28 BA19 BA15 35
27 BA22 BA18 36
26 BA21 +5V 37
25 AA8 BD15 38
24 AA22 BD14 39
23 AA9 AA7 40
22 AA23 AA6 41
21 AA10 AA5 42
20 AA24 AA4 43
19 AA14 AA3 44
18 AA15 AA2 45
17 BD0 AA1 46
16 BD1 AA0 47
15 BD2 +5V 48
14 BD3 BA0 49
13 BD4 BA1 50
12 BD5 BA2 51
11 BD6 BA3 52
10 BD7 BA4 53
9 BD8 BA5 54
8 BD13 BA7 55
7 GND BA8 56
6 BD12 BA9 57
5 BD11 BA10 58
4 BD10 BA11 59
3 BD9 BA12 60
2 BA6 BA13 61
1 GND GND 62

Right

nr bottom top nr
31 +5V +5V 32
30 AA17 MA9 33
29 AA16 MA10 34
28 AA19 MA11 35
27 AA18 GND 36
26 AA21 MD7 37
25 AA20 MD6 38
24 MD5 MA12 39
23 MD4 MA13 40
22 MD3 MA14 41
21 MD2 MA15 42
20 MD1 MA16 43
19 MD0 MA17 44
18 MA0 MA18 45
17 MA1 MA19 46
16 MA2 MA20 47
15 MA3 MA23 48
14 MA4 MA22 49
13 MA5 MA21 50
12 AD7 MA6 51
11 AD6 MA8 52
10 AD5 MA7 53
9 AD4 AD14 54
8 AD0 AD13 55
7 AD1 AD12 56
6 AD2 AD11 57
5 AD3 AD10 58
4 AA11 AD9 59
3 AA12 AD8 60
2 AA13 INT_M_ROM_OE 61
1 GND GND 62

References

About

A technical description of IGS PolyGame Master system

Resources

Stars

16 stars

Watchers

4 watching

Forks

Releases

Packages

Contributors