Table of Contents
OSCA - An FPGA Config For The V6Z80P
By Phil Ruston 2008-2013, (OSCA v676)
OSCA (Old School Computer Architecture) provides a home-computer era hardware platform for the V6Z80P. As well as basic memory and peripheral control, its features include bitmap and character mapped displays, sprites, a raster line synchronized co-pro (“LineCop”), blitter, hardware line draw and four channels of 8 bit digital sound.
Quick Links:
Video Register Index
Video System
Display Window
Sprite System
Blitter
Line Draw
LineCop
Port Index
Audio System
Math Assist
Timer
Interrupts
OSCA ROM
Hardware Control
In OSCA, memory and peripheral controls are accessed via the Z80's In/Out Ports. Video settings such as the palette, display controls, sprite registers etc are accessed via locations in Z80 address space (these can be paged in and out as desired). Sections of Z80 address space are also assigned to video RAM etc.
Z80 Address Space Overview
As the V6Z80P has 512KB of system RAM (and the Z80 can only see 64KB at a time), OSCA allows the user to select the sections of System RAM that appear in Z80 address space. The page size is 32KB and both the lower and upper halves of the Z80 address space can be assigned different pages of system RAM. In addition, the upper memory area can direct CPU writes to an alternative page, making data copying quicker and easier.
Depending on the memory control settings, certain sections of Z80 address space will - instead of system RAM - access components such as video/sprite memory, the video palette etc.
Diagram of Z80 Address space options under OSCA (not to scale):
Z80 Address Space 'Partitions'
$0000-$07FF | First 2KB section of the lower 32KB System RAM Page. Or ROM, Palette, Video Registers, Sprite Control Registers and Maths Table (this is the default mode) |
$0800-$0FFF | The next 2KB Section of the lower 32KB System RAM page (Note: OSCA's default ROM code stipulates that IRQ Mode 1 vector jumps to $A00, and NMI vector jumps to $A03) |
$1000-$1FFF | The next 4KB Section of the lower 32KB System RAM page or a write-only 4KB page of sprite definition RAM (note: Reads between $1000-$1fff always fetch data from System RAM). |
$2000-$3FFF | Next 8KB section of the lower 32KB system RAM page (or the default location of the video RAM access window.) |
$4000-$7FFF | The remaining 16KB Section of the lower 32KB System RAM page |
$8000-$FFFF | The Upper 32KB System RAM page |
Paged RAM areas in Detail
System Memory
The System RAM chip is 512KB. OSCA allows separate 32KB pages of this memory to appear at Z80 $0000-$7fff and $8000-$ffff. The 32KB chunk of system RAM that appears to the Z80 at $0000-$7FFF is set with the port “sys_low_page” and the 32KB of system RAM that appears to the Z80 at $8000-$FFFF is set with the port “sys_mem_select”. If required, WRITES between $8000-$FFFF can access a different page to that of READS in the same range - the write page is set with port “sys_alt_write_page”, and this feature is enabled with bit 4 of “sys_mem_select”.
Writing to Z80 address space between $0000 - $07FF:
$0000 - $01FF | Write only Video Colour Palette or 512 bytes of system RAM 1) |
$0200 - $03FF | Write only Video Control Registers or 512 bytes of system RAM 1) |
$0400 - $05FF | Write only Sprite Control Registers or 512 bytes of system RAM 1) |
$0600 - $07FF | Write only Maths Table or 512 bytes of system RAM 1) |
Reading from Z80 address space between $0000 - $07FF:
$0000 - $01FF | 512 byte boot ROM (contained in the FPGA) or 512 bytes of system RAM 1) |
$0200 - $06FF | 1280 Bytes of System RAM |
$0700 - $07FF | Video Status Register and Maths unit result or 256 bytes of system RAM 1) |
1) Bits in the port “sys_alt_write_page” control whether the ROM and hardware registers or system RAM appears in locations $0000-$07ff. The default scheme (IE: when the port sys_alt_write_page is clear) is:
$0000-$01FF | Write palette but read from ROM |
$0200-$06FF | Write video registers but read from System RAM |
$0700-$07FF | Write video registers but read only the video status register and maths result. |
Video Memory
Video memory is a seperate 512KB RAM chip. It is normally accessed by the CPU through an 8KB window in Z80 address space. The window is located at $2000 by default but can also be at $4000, $6000, $8000, $A000, $C000 or $E000. The location of this window is set with the port “sys_vram_location” and the 8KB section of Video RAM that the window 'contains' is selected by writing to the video register “vreg_vidpage” with bit 7 clear. To access video RAM at the location specified (instead of normal System RAM), video RAM must be paged in by setting bit 6 of the port “sys_mem_select”.
Direct Video Write Mode
(see bit 5 of “sys_mem_select”). This settings allows a 64KB page of video RAM to occupy the entire Z80 address space. This mode overrides all other paging options and forces memory WRITES from the CPU to video RAM at the page selected with bits [5:3] of “vreg_vidpage”). The CPU still reads data from ordinary system RAM (so that program code is always accessible). Because devoting the entire 64KB of Z80 address space to video RAM severely limits the CPU (calls, stack ops, IRQs, system variables etc are not possible) there is the option of excluding the first 4KB of Z80 address space from Direct Video Write Mode (set bit 6 of “sys_mem_select” as well as bit 5) - this allows access to the video registers and 2KB of system RAM at the cost of losing access to the first 4KB of each 64KB video page.
Sprite Memory
Sprite RAM is another seperate RAM chip, this time 128KB in size. It is accessible to the CPU via a 4KB window at $1000-$1FFF. To enable this window, set bit 7 of “sys_mem_select”. Note that in OSCA, sprite RAM is write only and reading from $1000-$1fff always returns values from system RAM. The 4KB page of sprite RAM that is visible to the CPU is selected by writing to the video register “vreg_vidpage” with bit 7 set.
I/O Ports
Ports $00-$2F: System Control / Peripheral / Audio Ports
These port locations are mainly both readable and writeable. However, the read (IN) functions can differ from the write (OUT) functions – see the individual port descriptions for details.
Port $00
Read / Write: “sys_mem_select“
Bit | Description |
---|---|
0:3 | Upper 32KB memory page select (see table below) |
4 | Use alternate page for upper memory writes 2) |
5 | Set Direct Video Write Mode (All Z80 address space memory writes go to VRAM) |
6 | Page in Video RAM at selected location (In DVW Mode: 1 = exclude $0000-$0FFF) |
7 | Page Sprite RAM in at $1000-$1FFF for writes (Ignored in DVW Mode) |
System RAM Upper Page Selection Table:
Bits 3 2 1 0 | Area of System RAM paged into Z80 $8000-$FFFF |
---|---|
0 0 0 0 | $08000 - $0FFFF 3) |
0 0 0 1 | $08000 - $0FFFF 3) |
0 0 1 0 | $10000 - $17FFF |
0 0 1 1 | $18000 - $1FFFF |
0 1 0 0 | $20000 - $27FFF |
0 1 0 1 | $28000 - $2FFFF |
0 1 1 0 | $30000 - $37FFF |
0 1 1 1 | $38000 - $3FFFF |
1 0 0 0 | $40000 - $47FFF |
1 0 0 1 | $48000 - $4FFFF |
1 0 1 0 | $50000 - $57FFF |
1 0 1 1 | $58000 - $5FFFF |
1 1 0 0 | $60000 - $67FFF |
1 1 0 1 | $68000 - $6FFFF |
1 1 1 0 | $70000 - $77FFF |
1 1 1 1 | $78000 - $7FFFF |
Notes
2) When bit 4 (Alternate Write Page) is set, the UPPER 32KB page selection DURING CPU WRITES is taken from Port $0B (”sys_alt_write_page”)
3) Notice 0000 and 0001 both select SYS_RAM $08000-$0FFFF at Z80: $8000-$FFFF. This prevents system RAM locations $00000-$07FFF being paged into the upper memory area. However, it is possible to override this prohibition by setting the “Any Page Mode” bit (bit 5 of port “sys_alt_write_page”) - selection 0000 will then select SYS_RAM $00000-$07FFF at Z80 $8000-$FFFF
The System RAM area where Linecop programs need to be placed are normal memory locations as far as the CPU is concerned, IE: they are not dedicated to the Linecop.
Port $01
Read: “sys_irq_ps2_flags“
Interrupt status flags and PS2 port line status:
Bit | Description |
---|---|
0 | Keyboard IRQ flag (1 = interrupt occurred) |
1 | Mouse IRQ flag (””) |
2 | Timer IRQ flag (“”) |
3 | Video IRQ flag (“”) |
4 | Keyboard Clock line status (input) |
5 | Keyboard Data line status (input) |
6 | Mouse Clock status (input) |
7 | Mouse Data status (input) |
Write: “sys_irq_enable“
Interrupt masks: Allow peripheral status flags to interrupt CPU.
Bit | Description |
---|---|
0 | Keyboard IRQ enable (1 = allow interrupt) |
1 | Mouse IRQ enable (””) |
2 | Timer IRQ enable (“”) |
3 | Video IRQ enable (“”) |
4 | Audio IRQ enable (“”) |
5 | n/u |
6 | Serial IRQ enable(“”) |
7 | No longer used (Legacy use: Master IRQ Enable) |
Port $02
Read: “sys_keyboard_data“
Bits 0:7 - Data byte received from keyboard. Only read this port when the “byte received” flag (bit 0 of sys_irq_ps2_flags) is set or you may get incomplete data.
Write: “sys_clear_irq_flags“
(Bits written with ones clear the relevant IRQ flag)
Bit | Description |
---|---|
0 | Clear Keyboard IRQ flag |
1 | Clear Mouse IRQ flag |
2 | Clear Timer IRQ flag |
3 | n/u |
4 | Clear Audio Channel 0 Loop IRQ Flag |
5 | Clear Audio Channel 1 Loop IRQ Flag |
6 | Clear Audio Channel 2 Loop IRQ Flag |
7 | Clear Audio Channel 3 Loop IRQ Flag |
Notes
- Video IRQ flag is cleared by writing $80 to ”vreg_rasthi”
- Serial IRQ flag is cleared by reading ”sys_serial_port”
Port $03
Read: “sys_mouse_data“
Bits 0:7 - Data byte received from mouse. Only read this port when the “byte received” flag (bit 1 of sys_irq_ps2_flags) is set or you may get incomplete data.
Write: “sys_ps2_joy_control“
Bit | Description |
---|---|
0 | Joystick port 0/1 select - determines which port is active. |
1 | n/u |
2 | n/u |
3 | n/u |
4 | Keyboard Clock Control output - (Set to 1 to PULL DOWN the signal) |
5 | Keyboard Data Control output - (””) |
6 | Mouse Clock Control output - (””) |
7 | Mouse Data Control output - (“”) |
Note: Writing 1 to the keyboard (or mouse) Clock line prevents the next data byte received by the shifter from setting the keyboard (or mouse) IRQ flag (in fact it clears the IRQ flag) - this is to stop bytes written out appearing in the input buffer.
Port $04
Read/write: “sys_serial_port“
Incoming or outgoing data byte for RS232 com port.
Note: When a byte is received, the “serial byte received” flag (bit 6 in Port $05) becomes set (this can cause an IRQ if desired). Reading this port clears the flag.
Port $05
Read: “sys_joy_com_flags“
Joystick inputs and RS232 status.
Bit | Description |
---|---|
0 | Up |
1 | Down |
2 | Left |
3 | Right |
4 | Fire 1 |
5 | Fire 2 |
6 | RS232 Serial Byte received |
7 | RS232 Serial Output Buffer Busy |
Write: “sys_sdcard_ctrl1“
Bit | Description |
---|---|
0:5 | n/u |
6 | Enable FPGA to SD card data output |
7 | SD card port's SPI speed: 0 = 250KHz, 1 = 8MHz |
Port $06
Read/Write: “sys_sdcard_ctrl2“
Bit | Description |
---|---|
0:1 | n/u(ignore / mask these bits when reading. No write function.) |
2 | Read/Write SD card /CS line (active low) |
3 | Read/Write SD card /Power control (active low) |
4:7 | n/u (ignore / mask off these bits when reading. No write function.) |
Port $07
Read: “sys_vreg_read” - Video status register (same value as address $700)
Bit | Description |
---|---|
7 | Interlace field 0 = short field, 1 = long field |
6 | LSB of scanline count |
5 | 60Hz mode (1 = NTSC config / VGA mode jumper installed and 50Hz not forced.) |
4 | Blitter / linedraw status (1 = busy) check before changing relevant registers |
3 | Scanline IRQ flip/flop status (becomes set when scanline = value written into vreg_rastlo/vreg_rasthi) |
2 | Y Window (1 = display area, 0 = border) |
1 | X Window (1 = display area, 0 = border) Note |
0 | Last line (VRT). Reads as 1 during the last line of each frame. |
Note: Remember, in VGA mode each scanline's data is output twice at double the normal frequency. This flag reflects the x-window of the normal PAL/NTSC ~15KHz scanline.
Write: “sys_timer“
The timer internally counts *upwards* from this value at 62.5KHz (ie: every 256 clock ticks) when it overflows, it sets the timer IRQ flag, then reloads the count from the value that was written here.
Note: Writing to this port immediately sets the timer to the written value and clears the internal prescaler count.
Port $08
Read: “sys_audio_flags“
Bit | Description |
---|---|
0 | Channel 0 is playing |
1 | Channel 1 is playing |
2 | Channel 2 is playing |
3 | Channel 3 is playing |
4 | Channel 0 has looped (IE: internal length countdown has reached 0) |
5 | Channel 1 has looped (use bits 7:4 of port 2 to clear these flags) |
6 | Channel 2 has looped (””) |
7 | Channel 3 has looped (””) |
Write: “sys_audio_enable“
Bit | Description |
---|---|
0 | Channel 0 - 1 = Play audio, 0 = Stop audio |
1 | Channel 1 - (””) |
2 | Channel 2 - (””) |
3 | Channel 3 - (””) |
4:7 | n/u |
Port $09
Read: “sys_hw_flags“
Bit | Description |
---|---|
7 | Hardware version serial data (16 bit value) |
6 | SD card serializer status (1 = busy) |
5 | Video Mode jumper status (0 = TV mode, 1 = VGA mode) |
4 | Config EEPROM serializer status (1 = data byte ready for read) |
3 | Configuration PIC's RB7 port status |
2:0 | n/u: Ignore / mask off these undefined bits |
To read the hardware version word, set the address bus bits 11:8 to $0-$F when reading this port (ie: use the “OUT (C),A” instruction, presetting register B with 0 to 15). The bit addressed by B will appear in bit 7 of this port.
Write: “sys_hw_settings“
Bit | Description |
---|---|
0 | NMI switch inhibit. Default = 0, NMI switch enabled |
1 | Reset switch inhibit. Default = 0, Reset switch enabled |
2 | No effect (was originally PAL/NTSC mode select) |
3 | Force VGA to 50Hz mode (no effect in TV mode) |
4:7 | Not used |
Port $0A
Read / Write: “sys_spi_port”
The SPI port is used by the V6Z80P to send bytes serially (MSB first, SPI mode 0) to its SD Card interface. Also, serial data received from the card's D_out line appears here for reading. See also: SPI speed setting in Port $05 and busy flag in port $09.
Port $0B
Read / Write: “sys_alt_write_page” 4)
Bit | Description |
---|---|
0 | Write Page select Bit 0 - Relevant when bit 4 of Port 0 is set |
1 | Write Page select Bit 1 - ”” ”“ |
2 | Write Page select Bit 2 - ”” ”“ |
3 | Write Page select Bit 3 - ”” ”“ |
4 | “Write Palette/Read System RAM” - When set, CPU writes between $0-$1FF are directed to the palette registers, whilst CPU reads in this memory range return bytes from system RAM (instead of the ROM). This mode overrides bit 6 status |
5 | ”Any page mode”. When set this allows the first 32KB of system RAM to be paged into locations $8000-$ffff of Z80 address space when bits 0:3 of “sys_mem_select” are zero. (Originally this was prohibited.) |
6 | “Read/Write First 512 bytes of System RAM” - When set, the system RAM “underneath” the ROM/palette at $000-$1ff is accessible as normal System RAM locations for read and write. Note: If bit 4 is set, this setting is ignored. |
7 | “Access RAM under video registers” - When set, the system RAM “underneath” the video registers at $200 - $7ff is accessible. As well as redirecting CPU writes to system RAM this control causes reads in the range $700-$7ff to return bytes from system RAM (READs from $200-$6ff return bytes from system RAM whether this bit is set or not). This means that the video status register and maths unit result word is not available (however, the video status register can always be read from port 7 - “sys_vreg_read”) |
(Note: uses same 32KB page selection table as “sys_low_page”)
4) To ensure the ROM is paged in upon reset and the system can start, this port register is reset to $00 when the reset button is pressed.
Port $0C
Read: No function
Write: “sys_baud_rate“
Bits: [0:1] = Set serial port RS232 comms speed:
Bits 1 0 | (Decimal) | BAUD RATE |
---|---|---|
0 0 | (0) | 57600 |
0 1 | (1) | 115200 |
1 0 | (2) | 14400 |
1 1 | (3) | 28800 |
Port $0D
Read: “sys_eeprom_byte“
This port contains data bytes from the SPI FPGA configuration EEPROM, which can be read under CPU control. It should be read only when the “serializer status flag” (bit 4 of port ”sys_hw_flags”) is 1. Reading this port clears the internal bit count and status flag.
Handshaking: When a databurst has been initiated (see PIC comms section) the PIC issues a new byte every time the PIC CLOCK (bit 1 of ”sys_pic_comms”) is raised.
Write: “sys_pic_comms“
Bit | Description |
---|---|
0 | Write to Configuration PIC port RB1 (data to PIC) |
1 | Write to Configuration PIC port RA5 (clock to PIC) |
2:7 | Not used, write with zeroes |
Port $0E
Read / Write: “sys_io_pins” (see data direction in port $0F)
Bit | Description |
---|---|
0 | Read/Write data bit for IO pin A |
1 | Read/Write data bit for IO pin B (pulled up) |
2 | Read/Write data bit for IO pin C (on PCBs that support it) |
3:5 | n/u |
6 | Reads: RESET switch status (pulled up)5) - Write: N/A |
7 | Reads: NMI switch status (pulled up)5) - Write: N/A |
5) Only relevant when Reset / NMI functions are disabled by port 9 bits 0:1
Port $0F
Read: No function
Write: “sys_io_dir”
Bit | Description |
---|---|
0 | Data direction for IO pin A (0 = output, 1 = input) |
1 | Data direction for IO pin B ( “” ) |
2 | Data direction for IO pin C (on PCBs that support it) |
3:7 | n/u |
Port $20
Read / Write: “sys_low_page”
Bit | Description |
---|---|
0:3 | Lower 32KB page select |
4:7 | Not Used – mask / ignore these undefined bits |
This port selects which 32KB page from the 512KB system RAM appears at Z80 $0000-$7fff (see table below). Bear in mind the ROM/Palette, video registers and sprite page will still occupy Z80 address space locations: $0000-$01ff, $0200-$07ff and $1000-$1fff respectively regardless of which 32KB page is selected UNLESS these locations are specifically banked out of the address space with their respective control bits.
System RAM Lower Page Selection Table:
Bits 3 2 1 0 | System RAM paged into Z80 $0000-$7FFF |
---|---|
0 0 0 0 | $00000 - $07FFF |
0 0 0 1 | $08000 - $0FFFF |
0 0 1 0 | $10000 - $17FFF |
0 0 1 1 | $18000 - $1FFFF |
0 1 0 0 | $20000 - $27FFF |
0 1 0 1 | $28000 - $2FFFF |
0 1 1 0 | $30000 - $37FFF |
0 1 1 1 | $38000 - $3FFFF |
1 0 0 0 | $40000 - $47FFF |
1 0 0 1 | $48000 - $4FFFF |
1 0 1 0 | $50000 - $57FFF |
1 0 1 1 | $58000 - $5FFFF |
1 1 0 0 | $60000 - $67FFF |
1 1 0 1 | $68000 - $6FFFF |
1 1 1 0 | $70000 - $77FFF |
1 1 1 1 | $78000 - $7FFFF |
Port $21
Read: No function
Write: “sys_vram_location”
Bit | Description |
---|---|
0:2 | Location of the video memory access window in Z80 address space (see table) |
3:7 | Not Used – write with zeroes |
Bits 2 1 0 | VRAM Location |
---|---|
0 0 0 | $2000 6) |
0 0 1 | $2000 6) |
0 1 0 | $4000 |
0 1 1 | $6000 |
1 0 0 | $8000 |
1 0 1 | $a000 |
1 1 0 | $c000 |
1 1 1 | $e000 |
Note: The video window is paged in and out of Z80 address space with bit 6 of ”sys_mem_select“
6) 000 and 001 both set the VRAM window location at Z80 address $2000 (It is not possible to set the VRAM window to Z80 $0000).
Ports $22 - $27
Used by sound system, see following section.
Ports $28-$2A
Maths unit register mirrors (of mult_read, mult_write, mult_index).
Port $28
Read: “sys_mult_read_lo“
Write: “sys_mult_write_lo“
Port $29
Read: “sys_mult_read_hi“
Write: “sys_mult_write_hi“
Port $2A
Read: No function
Write:“sys_mult_index“
Sound System Detail
Note: The audio channel location registers were expanded to 18 bit in OSCA v672. Previously, only 128KB of System RAM ($20000-$3FFFF) was accessible to the audio system.
Port assignments
Ports $10-$1F and $22-$27 are assigned to the audio functions:
Port $10
“audchan0_loc” Location of sound wave in System RAM Bits [bits 16:1 of sample address] (16 bit register)
—
Port $11
“audchan0_len” Length of sound wave (in words) (16 bit register)
—
Port $12
“audchan0_per” Period of sound wave (number of 16MHz clock ticks between sample bytes) (16 bit register)
—
Port $13
“audchan0_vol” Volume of sound wave ($00-$40) (Linear scale: $40 = maximum volume) (8 bit register)
—
Port $14-$17
Same for channel 1 - “audchan1_loc”, “audchan1_len”, “audchan1_per”, “audchan1_vol“
—
Port $18-$1b
Same for channel 2 - “audchan2_loc”, “audchan2_len”, “audchan2_per”, “audchan2_vol“
—
Port $1c-$1f
Same for channel 3 - “audchan3_loc”, “audchan3_len”, “audchan3_per”, “audchan3_vol“
—
Port $22
“aud_panning” (8 bit register)
Bit | Description |
---|---|
0:3 | Select which channels go to the right side |
4:7 | Select which channels go to the left side |
(Each bit position = one channel, IE: bit0=channel0, bit1=channel1 etc)
—
Port $23
Not implemented
—
Port $24
“audchan0_loc_hi” Location (high) of sound wave in System RAM [Bits 18:17] for Channel 0
—
Port $25
“audchan1_loc_hi” Location (high) of sound wave in System RAM [Bits 18:17] for Channel 1
—
Port $26
“audchan2_loc_hi” Location (high) of sound wave in System RAM [Bits 18:17] for Channel 2
—
Port $27
“audchan3_loc_hi” Location (high) of sound wave in System RAM [Bits 18:17] for Channel 3
(These 4 location_high ports are 8 bit registers - only bits 0:1 are used)
Notes
The sound location registers are 18 bits long in total and refer to WORD addresses (not byte addresses) in System RAM (samples need to placed at word boundaries). Basically you write (the actual RAM address / 2) into the location registers.
Samples must be an even number of bytes in length - you write the length in WORDS into the Length registers (IE: Actual byte length/2). Maximum sample length is therefore 128KB.
The location (low), length and period values are 16 bit registers and should be updated by using the Z80 “OUT (C),r” instruction where C is the port number, r holds the low 8 bits of data and B holds the high 8 bits of data. The volume and location (high) ports are 8 bit registers and can be updated with the normal “OUT (n),A” instruction if desired.
The volume is a linear scale value (range: $00 = silence to $40 = full volume).
The sound sample data needs to be in signed 8-bit format and located in System RAM (it is read by the hardware using DMA). The audio hardware ignores any memory paging settings and always reads directly from the system RAM when fetching data.
The sound system operates in a similar fashion to that of the Amiga Hardware, ie: You set the location, length, period and volume of a channel, then start the channel playing with the relevant ”sys_audio_enable” port bit. Once the channel is playing, its Location and Length registers can be updated - the channel will only fetch the new values once its original length value has counted down to zero (or you stop the channel for a while and restart it). If the registers are not changed, the channel simply loops around playing the same sample data until it is stopped.
Because of the way the sound hardware pre-fetches sample data, some care needs to be taken when setting up the registers. The recommended audio register setup procedure is as follows:
- Wait for post-audio DMA time (EG: wait for bit 6 of ”sys_vreg_read” to change)
- Stop the relevant audio channel(s) and write location, length, period and volume.
- Wait for next post-audio DMA time (IE: At least one scanline delay)
- Start the relevant audio channel(s)
(Once a sample is playing, the volume and period registers can generally be updated on the fly without special regard to the DMA timing)
It's possible to use an interrupt to seamlessly switch between two or more sample buffers in order to play long samples without worrying about the exact timing. To do this you would start a channel playing, reload it with the second buffer ready for the first loop as normal, and then set up the audio interrupt so that it occurs
upon the first loop (IE: just as the second buffer starts to play). In the IRQ service routine, you would set the channel location and length to point back to the first buffer, and on subsequent IRQs switch between the two buffers.
Note: The four audio loop flags are OR'd into a single interrupt source. It is up to the IRQ service routine to read bits 4:7 of the ”sys_audio_flags” port, see which bits are set (and clear them on exit from the IRQ).
Other sound related ports (see port breakdown detail above):
Port $01 (”sys_irq_enable”)- to enable/disable the audio IRQ
Port $02 (”sys_clear_irq_flags”)- to clear audio IRQ loop flags
Port $08 (”sys_audio_enable” / ”sys_audio_flags”) - to enable channels / read loop flags.
Calculating The Audio Period Value
The period value is not the same as the Amiga uses, however, it has 4 times the resolution so a close match value can be obtained if desired.
Period = 16,000,000 / desired sample output rate
EG: To playback at 22050Hz, set the period to (16,000,000/22050) = 725
The minimum period value is around 512 (corresponding to a sample rate of ~31KHz) - this is because the hardware only pre-fetches 2 samples during DMA per scanline (and there is a couple of cycles overhead etc).
For pure tones, the output frequency depends on the number of samples in your waveform that constitute one cycle, so if the sample has EG: 16 bytes in one cycle, the period value required to play it at a desired frequency is calculated thus:
Period = 16,000,000 / (samples per cycle * frequency)
Panning
The default set-up is for channels 0 and 2 to go to the left side, whilst channels 1 and 3 go to the right side, but by writing to port $22 ”sys_audio_panning” all 4 channels can be individually directed to the left, right or both sides.
Timer
OSCA has a single 8 bit internal counter which continually counts upwards, once every 256 ticks of the master 16MHz clock (ie: it runs at 62.5 KHz). It is not possible to directly read the value of the counter, instead the system sets a flag every time it overflows. This flag can be used as an interrupt source.
When the counter overflows, it starts counting at whatever value was previously written to it. Writing to the port immediately sets the count to the written value and clears the internal prescaler count.
Relevant port bits:
Port | Name | Bit | Description |
---|---|---|---|
$07 | sys_timer | 7:0 | Period (IE: 256-ticks until overflow) |
$01 | sys_irq_ps2_flags | 2 | (timer has overflowed) |
$01 | sys_irq_enable | 2 | (allow timer IRQ) |
$02 | sys_clear_irq_flags | 2 | (clear timer IRQ) |
Interrupts
OSCA uses interrupt mode 1, therefore all the interrupt sources when enabled cause a jump to $0038. The default OSCA ROM has the instruction JP $0A00 at this location. The following devices can cause interrupts:
Keyboard - Byte received
Mouse - Byte received
Video - Scanline Match
Timer - Overflowed
Audio - Channel Looped
Serial Port - Byte received
Before enabling a new interrupt source, ensure your IRQ handling routine will clear its IRQ flag, otherwise the system will get stuck in an IRQ service-loop.
Keyboard and Mouse interrupts
When a full byte has been received by the internal serial-to-parallel shifter, the keyboard (or mouse) sets its “byte received” flag (see bits 0 and 1 of ”sys_irq_ps2_flags”). This flag in turn pulls the PS/2 Clock line low, meaning that the keyboard (or mouse) is blocked from sending any more data (it has to buffer it internally). If these flags are selected as an interrupt sources (see bits 0/1 of “sys_irq_enable”) then an IRQ will occur. To clear the flag, write to “sys_clear_irq_flags” with bit 0 (keyboard) or 1 (mouse) set after reading port (“sys_keyboard_data” / “sys_mouse_data”).
Video Interrupt
When the internal scanline counter matches the value written7) to “vreg_rastlo” and “vreg_rasthi” a flag is set. To make this flag cause an interrupt, the value written to “sys_irq_enable” should have bit 3 set. To clear the video interrupt flag, write $80 to “vreg_rasthi” (this is a special case which does not affect the contents of the register).
7) The scanline match value must between 1 and the maximum number of lines in the video mode used. (PAL: 1 to 311, VGA/NTSC 1 to 261). Setting the scanline match value to 0 currently causes undesired effects (multiple false matches) due do internal counter limitations.
Timer Interrupt
When the timer has overflowed to 0, a flag will be set (see bit 2 of “sys_irq_ps2_flags”) At this point the timer reloads from the value originally written and continues. To make the overflow flag cause an interrupt, the value written to “sys_irq_enable” should have bit 2 set. To clear the timer IRQ flag write 2 to “sys_clear_irq_flags”.
Audio Interrupt
The audio interrupt can be used to detect looping channels as explained in the audio section. When a channel loops it sets its associated loop flag, all 4 loop flags are OR'd together into a single IRQ source. To enable this IRQ source, the byte written to “sys_irq_enable” should have bit 4 set. To clear the audio interrupt flag, all channels' loop flags must be zero - they are reset by writing to bits 4:7 of sys_clear_irq_flags.
Serial Interrupt
When a byte has been received in the internal shift buffer, a “byte received” flag becomes set (see bit 6 of “sys_joy_com_flags”). To allow this flag to cause an interrupt, the byte written to “sys_irq_enable” should have bit 6 set. Reading from “sys_serial_port” clears the byte received IRQ flag. OSCA only has a single byte serial buffer and no hardware flow control, however (unlike the keyboard and mouse) the value in the shifter is latched into a stable register until it is overwritten when the next complete byte received.
Non-Maskable Interrupts
The default OSCA ROM has the instruction JP $0A03 at $0066 (the NMI Vector). The only NMI source used in OSCA is the “NMI pin” of the expansion header block. When this signal is pulled low (and the hardware NMI inhbit feature is not enabled) the Z80 /NMI line is activated, causing a jump to $66. The “NMI pin” is readable in bit 7 of “sys_io_pins” and its NMI feature can be disabled by setting bit 0 of “sys_hw_settings”.
Hardware Maths Assist – multiply / scale unit
The maths assist unit consists of 3 elements:
- A 256 word table which can hold values (such as sine constants)
- A 16bit value that is multiplied by the values in the table
- An index byte to select which word in the table to multiply by
The multiplication is a sign extended 16 bit x 16 bit operation, but only 16 bits from the resulting 32 bit longword are available to be read, these bits are taken from bit 29 to bit 14. (Therefore care should be taken with the values used in the multiplication operation to 1. get correct results for unscaled multiplies
and 2. prevent overflows.)
The maths unit can be used for fast scaling of values using just the first entry of the table. For example you could write some value to be scaled into the mult_table(0), set the mult_index at 0 and write the scaling factor from 0 to 16384 into “mult_write”. The output from “mult_read” will be mult_table(0) * mult_write/16384
To use the maths unit as a simple 256 x 16bit look-up table, write your 16 bit words in the mult_table, put 16384 into mult_write and simply select the value by setting mult_index and reading from mult_read.
To do an unscaled “multiply by a constant” (using only a single entry in the table) you need to adjust the multiplier and multiplicand to counter the scaling of the output. EG: To multiply $0087 by $0025 you could write $8700 into mult_write - (IE: a simple LSB/MSB byte swap) and $0940 into mult_table(0) (IE: $25 * $40). As the second word needs more processing, this would be your pre-set constant (you could fill the rest of the table with other constants if desired).
The maths unit makes sin/cos calculations straightforward: Write the table with 256 sine values (maximum positive amplitude = 16384), set the multiplier word to coordinate x and the index byte to the angle a, the resulting word will be SIN (a) * x
Registers / locations used by the maths unit:
Register | Description |
---|---|
$208-$209 | “mult_write” 16 bit multiplier (word - write only) |
$20A | “mult_index” Maths table index (byte - write only) |
$600-$7FF | “mult_table” Data table (word - write only) |
$704-$705 | “mult_read” Maths unit result (word - little endian - read only) |
In OSCA v676+ mult_write, mult_read and mult_index are also accessible via IO ports:
Port $28 : sys_mult_read_lo, sys_mult_write_lo
Port $29 : sys_mult_read_hi, sys_mult_write_hi
Port $2A : sys_mult_index
The Video System
There is 512KB of “background video” RAM available (separate to sprite, audio and system memory). It is normally accessible to the CPU in 8KB pages (at Z80 address $2000, $4000, $6000, $8000, $A000, $C000 or $E000 depending on the port setting “sys_vram_location”) with the page selected by the video register “vreg_vidpage”. However, a special write-only mode also allows the entire Z80 address space to be mapped onto a 64KB page of video RAM.
There are two main video modes, bitmap and tilemap - both allow up to 256 colours. The display is normally non-interlaced, PAL ~50Hz or VGA/NTSC ~60Hz (there is an option to force VGA to non-standard 50Hz). CPU access TO VIDEO MEMORY whilst the display window is being generated is blocked by the video system. The different video modes affect how long the CPU is kept waiting - Dual Playfield tilemap mode is the most demanding.
Bitmap "CHUNKY" Mode
In standard chunky bitmap mode pixel data is fetched in a simple 1 byte = 1 pixel fashion (it is called “chunky” as all the bits for each pixel come from the same 'chunk' - IE: location - in memory).
The byte fetched from VRAM represents an index in the video palette, therefore a $00 shows a “palette entry 0” coloured pixel, $01 shows a “palette entry 1” coloured pixel and so on. Data is fetched linearly from VRAM left to right, top to bottom of the screen. The data-fetch start address can be manipulated to achieve smooth scrolling effects.
Line draw system
Fast line draws (125ns per 256 colour pixel) are possible in chunky mode. The hardware requires Bresenham constants, octant number, video start address and length of line to be set up in the line draw registers, then the hardware carries out the algorithm. The registers are doubled up, allowing line set-up data for a new line to be loaded up whilst the previous line is being drawn.
Flood mode
(Simplified in OSCA 672)
Chunky mode also offers “pixel flooding” (see bit 6 of “vreg_vidctrl”) which repeats the last pixel colour to achieve flood fill effects. In this mode, the hardware XOR's each new pixel with the value of the last. The stored colour is reset to zero at the start of each scan line.
Pixel Expand
Finally, in chunky mode pixels can be horizontally expanded from 1 to 8 times (see bit 3 of “vreg_vidctrl” and bits 0:2 “vreg_yhws_bplcount”)
Bitmap "PLANAR" Mode
In planar mode the display can have up to 8 bitplanes which are internally combined to form a palette index for each pixel. As the number of bitplanes can be set (see “vreg_yhws_bplcount”), the maximum number of colours on screen (without reloading the palette registers mid-screen) can be 2,4,8,16,32,64,128 or 256. This can offer efficiencies in terms of video memory use and update speed. The main disadvantage is that fine pixel-level access is more difficult. The bitplane location address registers can be manipulated to achieve scrolling effects etc. (Fine horizontal pixel scrolling is provided via a hardware scroll register (“vreg_xhws”).
The diagram below shows how bitplane data is combined to form a pixel's palette index:
Bitplane Location Registers and Modulo
To set the location of display data, write to the Bitplane Location Registers at $240-$27F (see register index for details). The location registers need only be written once, an internal counter adds an offset internally as the frame is built up (this offset register can be reset at any time). For convenience, there are in fact two sets of location registers - the video hardware uses the set determined by bit 5 of the video register “vreg_vidctrl”.
A modulo register is provided also - this can be used to skip bytes at the end of each scan line - useful for hiding new scroll data, interlaced displays etc.
Tile-Map Modes
In tile-map mode the display is built up in a less direct manner: The hardware reads a look-up table to display a predefined block for each tile position. The blocks can be 8×8 or 16×16 pixels in size (defined in a simple, linear “1 byte = 1 pixel colour index” left to right, top to bottom fashion). Dual playfield capability is provided so that one map can be overlaid on top of the other (pixels of value 0 are taken as transparent). The playfield priority is selectable. Each playfield can be offset by 0-15 pixels in the vertical or horizontal direction allowing smooth scrolling. There are two map buffers per playfield which allows double buffering if desired. The registers “vreg_vidctrl”, “vreg_ext_vidctrl”, “vreg_xhws” and “vreg_yhws_bplcount” contain the control bits for the tilemap modes.
Legacy And Extended Tile Modes
The most basic OSCA tilemap mode limits tiles to 16×16 pixels, indexed with single bytes allowing a maximum of 256 different tiles. Two sets of tile definitions are available with 248 and 256 tile images respectively (Tile set A has fewer definition blocks available because the video memory for blocks 0-7 (VRAM $0-$7ff) is assigned for use as the four available tile maps.) This simplistic system is referred to as “Legacy Tilemap mode”. Extended Tile Mode provides more flexibility and may be a better choice for new programs.
In Legacy Tile Mode the Video RAM is organized as follows:
$00000 - $001FF | Playfield A - tilemap 0 |
$00200 - $003FF | Playfield A - tilemap 1 |
$00400 - $005FF | Playfield B - tilemap 0 |
$00600 - $007FF | Playfield B - tilemap 1 |
$00800 - $0FFFF | Tile definition set A (Tile defs 0-7 cannot be used) |
$10000 - $1FFFF | Tile definition set B (All 256 tiles available) |
The horizontal size of the tile map buffer is always 32 bytes, no matter what the size of the display window.
Extended Tile Mode
In Extended Tilemap Mode (IE: when bit 0 of “vreg_ext_vidctrl” is set), tiles are indexed by 2 bytes and can be 16×16 or 8×8 pixels. There is a single set8) of 2048 16×16 tiles or 8192 8×8 tiles (the “tile set select” bits used in legacy mode have new meanings in Extended Tilemap Mode.) The tile maps are located between at $70000- $73fff in video memory, well clear of the tile definitions which are located at VRAM $0 (The memory space used by the tile maps will produce garbage tiles if indexed). Bits 6:7 of the upper byte of the tile indices control x_mirror (Bit 6) and y_flip (Bit 7) of each tile definition.
8)Although the tile definitions are normally indexed in a straightforward linear manner, there is an option to switch between the definitions held at VRAM $0-$3FFFF and $40000-$7ffff.
In Extended Tile Mode with 16×16 tiles VRAM is organised as follows:
$00000-$6FFFF | Tile definitions (256 bytes each) |
$70000-$701FF | Playfield A Buffer 0 tilemap LSBs |
$70200-$703FF | Playfield A Buffer 1 tilemap LSBs |
$70400-$705FF | Playfield A Buffer 0 tilemap LSBs |
$70600-$707FF | Playfield A Buffer 1 tilemap LSBs |
$70800-$709FF | Playfield B Buffer 0 tilemap MSBs (incl. tile flip control) |
$70A00-$70BFF | Playfield B Buffer 1 tilemap MSBs (“”) |
$70C00-$70DFF | Playfield B Buffer 0 tilemap MSBs (“”) |
$70E00-$70FFF | Playfield B Buffer 1 tilemap MSBs (“”) |
In 16×16 tilemap mode, the width of each horizontal map line is 32 bytes no matter what the size of the display window. Each tilemap has room for 16 rows of tiles (ie: a 256 scanline display), however when using vertical hardware scroll, the maximum y window should be 240 lines to mask off the last tile-line (where the internal tilemap line pointer wraps back to 0).
In Extended Tile Mode with 8×8 tiles VRAM is organised as follows:
$00000-$6FFFF | Tile definitions (64 bytes each) |
$70000-$707FF | Playfield A Buffer 0 tilemap LSBs |
$70800-$70FFF | Playfield A Buffer 0 tilemap MSBs (incl. flip control) |
$71000-$717FF | Playfield A Buffer 1 tilemap LSBs |
$71800-$71FFF | Playfield A Buffer 1 tilemap MSBs (incl. flip control) |
$72000-$727FF | Playfield B Buffer 0 tilemap LSBs |
$72800-$72FFF | Playfield B Buffer 0 tilemap MSBs (incl. flip control) |
$73000-$737FF | Playfield B Buffer 1 tilemap LSBs |
$73800-$73FFF | Playfield B Buffer 1 tilemap MSBs (incl. flip control) |
(Notice that the LSB/MSB arrangement is slightly different to that of the 16×16 mode.)
In 8×8 tilemap mode, the width of each horizontal map line is 64 bytes no matter what the size of display window. The hardware scroll registers still provide a 0-15 pixel offset.
Each tilemap has room for 32 rows of tiles (ie: a 256 scanline display), however when using vertical hardware scroll, the maximum y window should be 248 lines to mask off the last tile-line (where the internal tilemap line pointer wraps back to 0).
Colour palette
The V6Z80P's colour resolution is 12 bit, with 4 bits for red, green and blue (allowing a choice of 4096 colours). OSCA uses an indexed colour system with 256 colour registers. Each palette entry is a 16 bit word with bits arranged as follows:
Bit | Description |
---|---|
15:12 | Unused |
11:8 | Red intensity |
7:4 | Green intensity |
3:0 | Blue intensity |
Example palette writes:
EG: LD HL,$0F0F LD (PALETTE),HL ; Set colour0 (background) to magenta EG: LD HL,$00F0 LD (PALETTE+2),HL ; Set colour1 to green
Notes:
- There are actually two palettes, allowing buffering etc. Either palette can be set to “live” status (IE: accessed by the video hardware to build the video frame) or “target” (IE: receives writes from the CPU / LineCop). This is set with the register “vreg_palette_ctrl”.
- The palettes are write-only and located in the same memory space (0-$1ff) as the FPGA-based ROM. This is convenient since the ROM is read-only and palette is write-only. However, system RAM can be paged into this location if desired, see: “sys_alt_write_page”
Programmable Display Window
The position of the edges of the display window within the maximum visible TV screen are programmable (which - of course - sets the size of the window). There are 16 possible locations each for “X Start”, “X Stop”, “Y Start” and “Y Stop”. Each horizontal step is 16 pixels wide, and each vertical step is 8 lines tall.
The register “vreg_window_size” sets the position of the display window. Note:
both vertical and horizontal location settings are written this same register, its function changes depending on bit 2 of “vreg_rasthi” (see the example at the end of this section).
Horizontal Positions
X_START and X_STOP
When writing to the “vreg_window_size” register to set the horizontal positioning, the byte should be composed as follows:
Bits | Description |
---|---|
7:4 | X_START value |
3:0 | X_STOP value |
(Make sure vreg_rasthi was previously written with a value that has bit 2 set.)
The table below shows all the possible combinations of X_START (the green rows) and X_STOP (blue columns). Where a column and row intersect, the value is the width of the display in pixels. Note: X_Start values 0-4 (in red) are positioned well off the left hand side of the display so should not be used. A good, centralized 320 pixel wide display choice would be the value $8C.
@Aqua:$x0 | @Aqua:$x1 | @Aqua:$x2 | @Aqua:$x3 | @Aqua:$x4 | @Aqua:$x5 | @Aqua:$x6 | @Aqua:$x7 | @Aqua:$x8 | @Aqua:$x9 | @Aqua:$xA | @Aqua:$xB | @Aqua:$xC | @Aqua:$xD | @Aqua:$xE | @Aqua:$xF | |
@Red:$0x | 256 | 272 | 288 | 304 | 320 | 336 | 352 | 368 | 384 | 400 | 416 | 432 | 464 | 480 | 496 | 512 |
@Red:$1x | 240 | 256 | 272 | 288 | 304 | 320 | 336 | 352 | 368 | 384 | 400 | 416 | 432 | 464 | 480 | 496 |
@Red:$2x | 224 | 240 | 256 | 272 | 288 | 304 | 320 | 336 | 352 | 368 | 384 | 400 | 416 | 432 | 464 | 480 |
@Red:$3x | 208 | 224 | 240 | 256 | 272 | 288 | 304 | 320 | 336 | 352 | 368 | 384 | 400 | 416 | 432 | 464 |
@Red:$4x | 192 | 208 | 224 | 240 | 256 | 272 | 288 | 304 | 320 | 336 | 352 | 368 | 384 | 400 | 416 | 432 |
@Lime:$5x | 176 | 192 | 208 | 224 | 240 | 256 | 272 | 288 | 304 | 320 | 336 | 352 | 368 | 384 | 400 | 416 |
@Lime:$6x | 160 | 176 | 192 | 208 | 224 | 240 | 256 | 272 | 288 | 304 | 320 | 336 | 352 | 368 | 384 | 400 |
@Lime:$7x | 144 | 160 | 176 | 192 | 208 | 224 | 240 | 256 | 272 | 288 | 304 | 320 | 336 | 352 | 368 | 384 |
@Lime:$8x | 128 | 144 | 160 | 176 | 192 | 208 | 224 | 240 | 256 | 272 | 288 | 304 | 320 | 336 | 352 | 368 |
@Lime:$9x | 112 | 128 | 144 | 160 | 176 | 192 | 208 | 224 | 240 | 256 | 272 | 288 | 304 | 320 | 336 | 352 |
@Lime:$Ax | 96 | 112 | 128 | 144 | 160 | 176 | 192 | 208 | 224 | 240 | 256 | 272 | 288 | 304 | 320 | 336 |
@Lime:$Bx | 80 | 96 | 112 | 128 | 144 | 160 | 176 | 192 | 208 | 224 | 240 | 256 | 272 | 288 | 304 | 320 |
@Lime:$Cx | 64 | 80 | 96 | 112 | 128 | 144 | 160 | 176 | 192 | 208 | 224 | 240 | 256 | 272 | 288 | 304 |
@Lime:$Dx | 48 | 64 | 80 | 96 | 112 | 128 | 144 | 160 | 176 | 192 | 208 | 224 | 240 | 256 | 272 | 288 |
@Lime:$Ex | 32 | 48 | 64 | 80 | 96 | 112 | 128 | 144 | 160 | 176 | 192 | 208 | 224 | 240 | 256 | 272 |
@Lime:$Fx | 16 | 32 | 48 | 64 | 80 | 96 | 112 | 128 | 144 | 160 | 176 | 192 | 208 | 224 | 240 | 256 |
Theory
The X_Start and X_Stop values correspond to bits [7:4] of an internal low-res pixel clock counter (which counts from 0-511 on each scanline). X_Start values cover the count range 0,16,32,48 etc (the beam actually enters the left side of the visible (PAL TV) display somewhere around count = 96).
X_Stop values cover the count range 256,272,288,304 etc (the raster beam leaves the display somewhere around count 496) Note: X_Stop 0 (count = 256) is not the exact middle of the visible display because the TV's raster beam does not advance during sync periods (at the extreme left of the display).
Vertical Positions
Y_START and Y_STOP
When writing to the vreg_window_size register, the byte should be composed as follows:
Bits | Description |
---|---|
7:4 | Y_START value |
3:0 | Y_STOP value |
(Make sure vreg_rasthi was previously written with a value that has bit 2 clear.)
The table below shows all the possible combinations of Y_START (the green rows) and Y_STOP (blue columns). Where a column and row intersect, the value is the height of the display in scanlines. Note: Y_Start values 0-1 (in red) are above the top of the display so should not be used. A good, centralized 200 line wide display choice would be the value $5A.
@Aqua:$x0 | @Aqua:$x1 | @Aqua:$x2 | @Aqua:$x3 | @Aqua:$x4 | @Aqua:$x5 | @Aqua:$x6 | @Aqua:$x7 | @Aqua:$x8 | @Aqua:$x9 | @Aqua:$xA | @Aqua:$xB | @Aqua:$xC | @Aqua:$xD | @Aqua:$xE | @Aqua:$xF | |
@Red:$0x | 160 | 168 | 176 | 184 | 192 | 200 | 208 | 216 | 224 | 232 | 240 | 248 | 256 | 264 | 272 | 280 |
@Red:$1x | 152 | 160 | 168 | 176 | 184 | 192 | 200 | 208 | 216 | 224 | 232 | 240 | 248 | 256 | 264 | 272 |
@Lime:$2x | 144 | 152 | 160 | 168 | 176 | 184 | 192 | 200 | 208 | 216 | 224 | 232 | 240 | 248 | 256 | 264 |
@Lime:$3x | 136 | 144 | 152 | 160 | 168 | 176 | 184 | 192 | 200 | 208 | 216 | 224 | 232 | 240 | 248 | 256 |
@Lime:$4x | 128 | 136 | 144 | 152 | 160 | 168 | 176 | 184 | 192 | 200 | 208 | 216 | 224 | 232 | 240 | 248 |
@Lime:$5x | 120 | 128 | 136 | 144 | 152 | 160 | 168 | 176 | 184 | 192 | 200 | 208 | 216 | 224 | 232 | 240 |
@Lime:$6x | 112 | 120 | 128 | 136 | 144 | 152 | 160 | 168 | 176 | 184 | 192 | 200 | 208 | 216 | 224 | 232 |
@Lime:$7x | 104 | 112 | 120 | 128 | 136 | 144 | 152 | 160 | 168 | 176 | 184 | 192 | 200 | 208 | 216 | 224 |
@Lime:$8x | 96 | 104 | 112 | 120 | 128 | 136 | 144 | 152 | 160 | 168 | 176 | 184 | 192 | 200 | 208 | 216 |
@Lime:$9x | 88 | 96 | 104 | 112 | 120 | 128 | 136 | 144 | 152 | 160 | 168 | 176 | 184 | 192 | 200 | 208 |
@Lime:$Ax | 80 | 88 | 96 | 104 | 112 | 120 | 128 | 136 | 144 | 152 | 160 | 168 | 176 | 184 | 192 | 200 |
@Lime:$Bx | 72 | 80 | 88 | 96 | 104 | 112 | 120 | 128 | 136 | 144 | 152 | 160 | 168 | 176 | 184 | 192 |
@Lime:$Cx | 64 | 72 | 80 | 88 | 96 | 104 | 112 | 120 | 128 | 136 | 144 | 152 | 160 | 168 | 176 | 184 |
@Lime:$Dx | 56 | 64 | 72 | 80 | 88 | 96 | 104 | 112 | 120 | 128 | 136 | 144 | 152 | 160 | 168 | 176 |
@Lime:$Ex | 48 | 56 | 64 | 72 | 80 | 88 | 96 | 104 | 112 | 120 | 128 | 136 | 144 | 152 | 160 | 168 |
@Lime:$Fx | 40 | 48 | 56 | 64 | 72 | 80 | 88 | 96 | 104 | 112 | 120 | 128 | 136 | 144 | 152 | 160 |
Theory
The Y_Start and Y_Stop values correspond to bits [6:3] of the internal scanline counter. Y_Start covers the range of scanlines 0-120 and Y_Stop covers the range of scanlines 160-280.
PAL/NTSC/VGA
Because PAL/NTSC/VGA use differing amounts of scanlines to make up a full display, if Y_START and Y_STOP values are chosen to vertically centre a window, that window will be offset in other video modes. If vertical centring is important, a program must detect the video mode and adjust Y_Start and Y_Stop accordingly - note that this will also mean adjusting your Sprite and Linecop coordinates. The 60Hz mode flag in the Video Status Register can assist if the correction is to be done automatically, or a display adjust routine can be provided in user programs.
Example code - Display Window setting:
To set the vertical start and stop positions of the display window, first write “vreg_rasthi” with bit 2 clear, then write an appropriate value from the tables above to “vreg_window_size”. To set the horizontal positions, write “vreg_rasthi” with bit 2 set, then write an appropriate value for the horizontal setting to “vreg_window_size”.
EG: ld a,$00 ld (vreg_rasthi),a ; selects vertical window setting ld a,$5a ld (vreg_window_size),a ; sets vertical window size/position (200 lines) ld a,$04 ld (vreg_rasthi),a ; selects horizontal window setting ld a,$8c ld (vreg_window_size),a ; sets horizontal window size/position (320 pixels
Sprites
There is 128KB of dedicated RAM for sprite definitions (enough space for 512 16×16 256 colour sprite blocks). This (write only) memory is accessed in 4KB pages through a window in Z80 address space located at $1000-$1FFF. The 4KB page from within the total 128KB sprite RAM is selected by the register “vreg_vidpage” (write the page number 0-31, with bit 7 set). The 4KB window is opened and closed with Bit 7 of the port “sys_mem_select”.
(Note: When the CPU reads from $1000-$1FFF, data is ALWAYS returned from system RAM regardless of the setting in “sys_mem_select”.)
Sprite definition memory accepts writes at full speed when the sprites are disabled. When sprites are enabled, writes from the CPU are forced to wait until the sprite hardware releases the RAM buses. The length of the wait during contention depends how busy the sprite hardware is on any given scanline. Access to the sprite control
registers is never subject to contention.
Each sprite definition block is 256 bytes long, with pixel data in a linear format “1 byte = 1 pixel” fashion, left to right, top to bottom of the sprite definition. Colour index zero is considered transparent by the sprite video hardware. Sprites are always 16 pixels wide but can be up to 240 pixels tall (the height of an
individual sprite (in 16 line blocks) is set by its control register). The additional definition data for sprites taller than 16 lines comes from the blocks of sprite data following the specified sprite definition. Sprite images can be individually mirrored horizontally by setting a bit in their control registers - see detail below.
There is enough time on any one scanline to show 55 sprites, but there are 127 sprite control registers. Therefore, this can be thought of as hardware multiplexing: Because it is uncommon for 55 sprites to appear on the same line, sprite images from all the registers will normally be displayed if spread throughout the entire display window.
A double buffering mode allows half the sprite registers to be updated whilst the sprite hardware builds the display from the other half (the buffers are typically switched by the user's program each frame). This removes possible on-screen glitches and/or the need to dump data to the sprite registers off-screen. Bits in “vreg_sprctrl” are used operate this mode.
Sprite to Background Priority
In the most basic mode, non-zero sprite pixels appear in front of all background display data. However there are controls to allow sprites to be masked by sections of the background colour palette. For the purposes of priority, the background palette is divided into 16 x 16 colour groups, sprites can then appear in front or behind one or more of these groups. There are two sets of background mask selections, sprites use the mask set based on bit 7 of their pixel colours. To dynamically switch a sprite from one set of masks to the other (EG: to move it from foreground to background) bits from a sprite’s control register can be used to modify its
pixels’ MSBs (without this it would be necessary to have copies of the same sprites drawn using different colour indexes and switch the definitions.)
The main priority control “interleave mode” is bit 1 of “vreg_sprctrl”, when this bit is zero, sprite pixels appear in front of all background data. When this bit is set to one, sprites use their pixel colour index MSBs to select one of the two background mask sets.
To assist with individual sprite priorities, an option called “modify_colour_MSBs” is available, this is enabled by bit 4 in “vreg_sprctrl”. When set, the MSB of each individual sprite's height control is sacrificed and transferred the MSB of all the sprite’s pixel colours. Of course, the sprite would normally change colour if the palette indices 1-127 and 129-255 are different, therefore another control bit is available that forces sprites only to be shown using colours 1-127 (IE: their pixel colour MSBs are reset to zero, but only after the priority has been determined). This option is called “fix_colours_low” and is enabled with bit 6 of “vreg_sprctrl”. (As these options sacrifice the MSB of the sprites’ height settings, it limits how tall a sprite can be.)
One last sprite feature is “matte mode” - this forces all non-zero pixels in a sprite to a single colour, an effect which is sometimes used in games to indicate a character has taken a hit. The feature is enabled with bit 5 in “vreg_sprctrl”, afterwards bit 2 of each sprite’s height control register is used to switch individual sprites to single colour. Internally, this feature sets bits 0:6 of the sprite pixels (but not bit 7, as that could affect its priority). Therefore all the sprite’s pixels will be shown as colour 127 or colour 255 depending on the sprite’s original bit 7 (if in doubt simply make palette colours 127 and 255 the same). As another bit of the sprite height control register is sacrificed, sprites are limited to 16/32/48/64 pixels tall (also 128,144, 160,176 if “modify_colour_MSBs” is not required.)
In general, bear in mind the sprite layer is generated separately (and concurrently) to the background, and because priorities work solely on colour indexes care must be taken (especially in dual playfield tile mode) when designing the colour palette and the order in which sprites are assigned to registers.
Background priority mask registers
The mask registers are located at $280-$28F. Only two bits of each location are used: Bit 0 is the mask for “level 0” (where a sprite’s colour index MSB is 0) and Bit 1 is the mask for “level 1” (where a sprite’s colour index MSB is 1). The register at $280 holds the masks for background colours 0-15, the register at $281 holds the masks for background colours 16-31, the register at $282 holds the masks for background colours 32-47 and so on.
Where each register mask bit is zero, the range of colours it will represents will appear behind sprites. Where a mask bit is one, the range of colours it represents will appear in front of sprites.
An example:
$280: (background 00-0f) = 00b $288: (background 80-8f) = 01b $281: (background 10-1f) = 00b $289: (background 90-9f) = 01b $282: (background 20-2f) = 00b $28A: (background a0-af) = 01b $283: (background 30-3f) = 00b $28B: (background b0-bf) = 01b $284: (background 40-4f) = 00b $28C: (background c0-cf) = 01b $285: (background 50-5f) = 00b $28D: (background d0-df) = 01b $286: (background 60-6f) = 00b $28E: (background e0-ef) = 01b $287: (background 70-7f) = 00b $28F: (background f0-ff) = 01b
In the above example (when interleave mode is enabled) sprites with pixel indexes in the range 00-7f will be occluded by background colours 80-ff. This is the default OSCA setting.
Sprite Control Registers (4 bytes per sprites)
Memory addresses of registers:
$400-$5fb (127 registers - single set of sprite control registers)
or
$400-$4fb (Register bank 0) (63 registers - double buffered register mode)
$500-$5fb (Register bank 1) (63 registers - double buffered register mode)
Each sprite register is 4 sequential bytes:
$00 : X coordinate (low 8 bits)
$01 : Height, MSBs and Mirror control (see below)
$02 : Y coordinate (low 8 bits)
$03 : Definition block number (low 8 bits)
Breakdown of sub-register $01:
Bits | Description |
---|---|
7:4 | Height of sprite (in blocks of 16 lines) If 0000, the sprite is 240 pixels tall. |
3 | X Mirror enable |
2 | Definition MSB |
1 | Y coordinate MSB |
0 | X coordinate MSB |
Sprite Coordinates
Sprite X and Y coordinate registers are 9 bits in size (the MSBs are stored in the second byte of the sprite's register group as shown above). The coordinate origin positions are based on the internal line counter and horizontal pixel counter as follows:
- A sprite's leftmost pixel position coincides with the leftmost display window pixel when its x coordinate = (Window_X_START * 16) - 1
- A sprite's topmost line position coincides with first display scanline when its y coordinate = (Window_Y_START * 8) + 1
EG: When using a display window [X_START=8, Y_START=5], a sprite appears in the extreme top/left of the display window when its coordinates are: x=127, y=41
Values below these positions hide parts of the sprite “behind” the border. The Window's X_START and Y_START values naturally determine how much of a sprite can be masked by the border. Horizontally, the full width of an individual sprite can be masked many times over on the left. The Y_START value allows a minimum of 16 lines of vertical (top border) masking (when using the recommended minimum Y_START of 2). If more data of a tall sprite is to be masked, then its starting definition block and height can be adjusted.
Sprite Size
As mentioned, sprites are always 16 pixels wide, but can normally be up to 240 pixels high, with a granularity of 16 lines (as set by bits 7:4 of the second byte in each sprites control register). However, some height control bits can be sacrificed for other features:
If “modify_colour_MSBs” mode is enabled (bit 4 in “vreg_sprctrl” = 1) the four bits normally assigned to height work as follows:
Bit | Description |
---|---|
7 | Use priority mask set 0 or 1 |
6 | Height (bit 2) |
5 | Height (bit 1) |
4 | Height (bit 0) |
Therefore in this mode sprites can be 16,32,48,64,80,96,112 (or 240 when the three height bits are all zero) lines tall.
If “matte_mode” is enabled (bit 5 in “vreg_sprctrl” = 1) the four bits normally assigned to height work as follows:
Bit | Description |
---|---|
7 | Height (bit 3) |
6 | Force non-zero colours to 127 or 255 |
5 | Height (bit 1) |
4 | Height (bit 0) |
Therefore in this mode sprites can be 16,32,48,64, and 128,144,160,176 lines tall (64 lines are used when bit 0 and 1 are zero).
If both “matte_mode” and “modify_colour_MSBs” modes are selected, the four bits normally used for height are interpreted as follows:
Bit | Description |
---|---|
7 | Use priority mask set 0 or 1 |
6 | Force non-zero colours to either 127 or 255 |
5 | Height (bit 1) |
4 | Height (bit 0) |
In this mode sprites can be 16,32,48,64 lines tall (64 lines are used when bit 0 and 1 are zero).
Line Sync'd Co-Pro
(“LineCop”)
This simple co-processor allows changes to be made to the display at specific scan lines without having to use manual CPU interrupts (it operates using DMA cycle-stealing in a similar manner to the sound system). The LineCop system follows a program, there are 3 main instructions and each instruction is two bytes long. The upper bits of the MSB determines what kind of instruction it is:
Bit | 15 | 14 | 13 | 12 | 11 | 10 | 09 | 08 | 07 | 06 | 05 | 04 | 03 | 02 | 01 | 00 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
1 | 1 | x | x | x | x | x | L | L | L | L | L | L | L | L | L | = Wait for line L | |
1 | 0 | x | x | x | R | R | R | R | R | R | R | R | R | R | R | = Select Register R | |
0 | a | b | c | x | x | x | x | D | D | D | D | D | D | D | D | = Write D to Register |
x = Don't care
L = Line to wait for (minimum: line 1)
R = Register to update ($0-$7FF)
D = Data byte to write to selected register
a = If set: After Write, increment selected register
b = If set: After write, increment wait line (and wait)
c = If set: After write, reload LineCop Program Counter from Start Location Registers
Wait and Register Select instructions take 2 clock cycles to perform, Register Writes take 3 cycles.
The start address of the Linecop program is held in registers $20D: linecop_addr0 (bits 7:0) $20E: linecop_addr1 (bits 15:8) and $20F: linecop_addr2 (18:16) - there are some special considerations when writing to these registers:
- Linecop programs must be aligned at even bytes in memory, however bit 0 of the address (as written to linecop_addr0) is the Linecop hardware enable control. Therefore, the value written to this register needs be an ODD number if the linecop is to run.
- Hardware register $20f (linecop_addr2) uses the same physical address as vreg_palette_ctrl. Bit 7 selects which register is updated: 1 = write to Linecop address, 0 = Write to the palette register.
- Write to linecop_addr0 last, so that all the other address bytes are populated whilst the linecop is inactive.
An example of setting the linecop address:
LD C,$02 LD DE,$468A ;Linecop address in system memory (C:DE) is $02468A LD IX,linecop_addr0 SET 7,c ;Make sure linecop register selected (set bit 7 of MSB) SET 0,E ;Make sure linecop enable bit is set (set bit 0 of LSB) LD (IX+2),C LD (IX+1),D LD (IX+0),E
The LineCop's internal Program Counter is reloaded from the address held in linecop_addr0-2 at the end of every frame and also when forced by a LineCop write instruction which has bit 12 set. As the LineCop can write to its own location pointers and restart itself, automatic list cycling / page flipping can be achieved. Linecop programs should end with a wait for a line that is never reached, IE: $1FF (Use the instruction code hexcode $C1FF).
When the raster reaches a scanline that matches that set by a LineCop wait instruction, the LineCop steals time from the CPU until it reaches the next Wait instruction. If the end of the scanline is reached first, the Linecop becomes inactive until the next frame.
You should not WAIT for Line 0 as this currently causes undesired effects due to internal counter limitations.
As the LineCop can access all the video registers including blitter etc, beware of malformed LineCop programs causing undesired effects. The linecop only ever writes to the hardware registers - never system RAM - EG: bit 7 of the port “sys_alt_write_page” is irrelevant to the LineCop.
Linecop scanline alignment
Although the Linecop uses the same internal line counter as the sprite system, there can be a difference in the values required to achieve visual alignment between the two systems. This is because each line of sprite data (like the background video) is buffered and displayed on the next line (see hardware timing). Other systems, like the colour palette, however will respond immediately to changes and show effects on the “live” scanline. In addition, some register writes are buffered until the end of a scanline so the effect of a change may not be visible until 2 scanlines later.
An example
A sprite y coordinate is set at $29, and appears at the top line of the display. If we wanted the Linecop to change one of its colours on its top row of pixels we should actually wait for line $2a with the Linecop. (The sprite data line is generated and buffered on line $29, but its pixels only appear a line later on line $2a).
Note: It is possible to use the dual palettes to get around this issue.
Simple LineCop program
Change the background colour to white at scanline $50, and black at scan line $51
Location | Instruction ---------------------- $08000: $c050 ; Wait for line $50 $08002: $8000 ; Select video register $000 (background colour) $08004: $40ff ; Write $ff to colour LSB, increment video register $08006: $20ff ; Write $ff to colour MSB, increment wait line (and wait) $08008: $8000 ; Select video register $000 (background colour) $0800A: $4000 ; Write $00 to colour LSB, increment video register $0800C: $0000 ; Write $00 to colour MSB $0800E: $C1FF ; Wait for line $1FF (Never reached - end of LineCop program)
Note that LineCop instruction WORDS are shown for clarity above - the BYTE ordering is Z80 standard little endian, therefore - as bytes - the code above would be listed:
$08000: $50,$c0, $00,$80, $ff,$40, $ff,$20, $00,$80, $00,$40, $00,$00, $ff,$c1
The LineCop program above can be started with the Z80 code:
LD A,$0 LD HL,$8000 ; LineCop program Address in A:HL = $08000 SET 7,A ; bit 7 of MSB must be set for writing to vreg_linecop_addr2 SET 0,L ; bit 0 of LSB must be set if the linecop is to run LD IX,linecop_addr0 LD (IX+2),A ; Set address MSB LD (IX+1),H LD (IX+0),L ; Set address LSB and start
The LineCop can be stopped with the Z80 code:
LD A,$0 ; Zero the accumulator LD (linecop_addr0),A ; When bit 0 of this register is zero, the linecop is stopped.
Blitter
OSCA’s blitter is a simple data copying engine that operates within Video RAM. It can transfer data around video memory much faster than the CPU (it copies one byte every 2 system clocks, compared with the Z80's LDIR instruction @ 1 byte per 21 clocks). Also unlike the CPU, it has 18 bit address registers so is not subject to banking issues.
The blitter can run in ascending or descending address order and has modulo registers so that a rectangular area within a larger overall array can be shifted in one operation. Source bytes that equal zero can be skipped if required - this is mainly useful in Chunky pixel mode where it can be used to mask out “transparent” pixels (eg: drawing “bobs”).
Blits take priority over access to video RAM from the CPU (but obviously the video datafetch at the start of each scanline still pauses the blitter). As long as the CPU does not access video RAM whilst a blit is underway, it will continue running its program from system RAM.
The blitter's control registers are located at $210-$21a, there are registers for:
- Source Address
- Destination Address
- Height of blit
- Width of blit
- Source modulo (bytes to skip at end of each line)
- Destination modulo (“”)
- Control (transparency mode, direction of blit, MSBs etc)
A blit operation begins whenever the width register is written. Because the CPU is not stopped by the blitter (unless it tries to access Video RAM) it can be important to test that the blitter is not already running before writing to blitter registers. A flag bit is provided in “sys_vreg_read” (bit4) for this purpose (note: some blit registers CAN be safely overwritten once a blit has started - See the blitter register list for detailed information.)
Example blitter set-up code
; Copy continuous 16KB block from VRAM $00000 to VRAM $08000 wait_blit: in a,(sys_vreg_read) bit 4,a ;make sure blitter is not busy jr nz,wait_blit ld a,$00 ;put source address in A:HL ld hl,$0000 ;source is at VRAM $00000 ld (blit_src_loc),hl ;set source address [15:0] ld (blit_src_msb),a ;set source address MSB [18:16] ld a,$00 ;put source modulo in A ld (blit_src_mod),a ;set source modulo ld a,$00 ;put dest address in A:HL ld hl,$8000 ;destination is VRAM $08000 ld (blit_dst_loc),hl ;set dest address [15:0] ld (blit_dst_msb),a ;set dest address MSB [18:16] ld a,$00 ;put dest modulo in A ld (blit_dst_mod),a ;set dest modulo ld a,%01000000 ;set blitter control to ascending mode, modulo ld (blit_misc),a ;high bits set to zero, transparency: off ld a,64-1 ;put height-1 of blit in A ld (blit_height),a ;set height of blit ld a,256-1 ;put width-1 of blit in A ld (blit_width),a ;set width of blit and start blit
Line Draw
OSCA contains a vector line drawing engine which can be used in chunky pixel mode. In a similar fashion to the blitter, this system takes priority over access to video memory and leaves the CPU free to do other things (it cannot run concurrently with the blitter, however).
The line draw hardware is an implementation of the standard Bresenham algorithm. Set up is a two-stage operation: Firstly, a table of constants must be set up - these values will be common to all lines on a given display window size so normally only needs to be set once as part of an initialization routine. This table is located at $230-$231 and is comprised of eight 16 bit constants:
a. (65536-Window width)+1
b. (65536-Window width)-1
c. Window width + 1
d. Window width - 1
e. 1
f. 65535
g. 65536 - Window width
h. Window width
Once the constants table has been set up, each line to be drawn requires registers to be set for:
- Start Address in VRAM
- Length of line
- Bresenham values
- Colour
The Bresenham value calculations do mean a certain amount of software overhead per each line to be drawn but the pay-off in speed (of the actual drawing) is more than worthwhile. The Bresenham values required are:
Constant: 2 x (dy-dx) or: 2 x (dx-dy) depending on octant
Constant: 2 x (dy) or: 2 x (dx) depending on octant
An “octant code” is required, this comprises of one bit for each of the following:
a. dx sign
b. dy sign
c. dy ⇒ dx
(Note: dx, dy = delta_x, delta_y)
The line colour is set by the register: $20b - linedraw_colour
See the linedraw register list for full details of the register set-up requirements.
Resolution
The normal horizontal pixel resolution in TV mode is 8MHz and 16MHz in VGA mode - this means there can be approximately a maximum of 368 pixels visible on any one scan line. As there is spare bandwidth in TV mode, there is the option to increase the pixel rate to 16MHz, at the expense of colour depth (reduced to 16 colours max) – see bit 3 of “vreg_ext_vidctrl” - this is referred to as Hi-Res Mode. In this mode, the colour index of each normal pixel is split into two 4-bit indices (7:4 = Leftside hi-res pixel, 3:0 = Rightside hi-res pixel). This process is applied at final output stage of the pixel colour look-up and is merely a “filter” instead of a “real” hi-res mode. Because the sprite priority system is unaware of this filter, it still processes priorities based on 8 bit pixel values - therefore sprite interleaving should not be used in hi-res mode.
The vertical resolution varies depending on the TV/monitor (PAL has approx 256 visible lines, less for NTSC and VGA). The TV modes allow interlacing (see bit 2 of “vreg_ext_vidctrl”) this adjusts the frame timing causing the TV to offset the lines of every other frame by half the thickness of a scanline. (Bit 7 of “sys_vreg_read” shows which field is currently being displayed.)
Video Hardware Timing
As can be seen from the diagram below, each video line is internally buffered until the next scan line when it actually displayed. The sprites and background video layers are generated simultaneously to separate buffers and combined when the line is eventually clocked out to the display.
Writing to most video registers will have an immediate visible effect (EG: writes to the colour palette, unless double buffered) but some will not visibly change anything until the following scan line as changes are automatically synchronized to the start of a new scan line, these include:
- “Live Palette Select” (in vreg_palette_ctrl)
- “Live Bitplane Location Register Set Select” (also “Playfield A map buffer select” when in Tilemap mode) (bit 5 in vreg_vidctrl)
- “Tilemap Mode Select” (bit 0 in vreg_vidctrl)
- “Chunky or Planar Bitmap Mode Select” (also “Dual Playfield On/Off Select” when in Tile Map mode) (bit 7 in vreg_vidctrl)
- “Display Window Right Side Position” (in vreg_window_size)
A note concerning updating bitmap location pointers: Each line begins generation before the LineCop DMA starts, therefore even if the bitmap location registers are changed with the linecop at the start of a line (IE: immediately following a LineCop WAIT instruction) there will be some pixels at the start of the displayed line that were fetched before the pointers were changed. This can be avoided using double buffering via the 2nd bitmap location register set, alternatively the unwanted pixels can be masked with a wide border etc.
Scan Line Timing Detail
Each scanline lasts for 1024 16MHz clock cycles in PAL TV mode (1016 cycles in NTSC or VGA modes). At the start of each line the CPU is taken offline by a BUSREQ signal for audio DMA. (There is short, variable delay of a few cycles following a Bus Request whilst waiting for an acknowledgment from the CPU). Once the audio system is online, it lasts around 20 clock cycles. Following on, the LineCop has control of the main system bus for as long as the necessary to complete the LineCop operations required on that line. Afterwards, the CPU continues normally (as long as it not forced to wait by it accessing video or sprite memory during active display lines).
Scanline Timing:
Bitmap and sprite video data is fetched simultaneously and concurrently with the CPU running normally as the three systems have their own memory buses. The time each system requires to build its internal buffer is variable and depends on the amount of data on a particular scan line. As mentioned, the CPU only has to wait if it tries to access bitmap or sprite memory whilst data is being fetched by the relevant system.
The blitter and linedraw systems are also forced to wait during the bitmap fetch part of a scan line. These systems have priority over the CPU in accessing video memory when its free (not being read by the video system) and force the CPU to wait if the CPU is trying access bitmap video memory at the same time. Active blits are paused at the “last 16 cycles” point, ensuring the CPU is not waiting at the start of a scanline (which could cause complications with the DMA functions). A paused blit continues after the bitmap layer data fetch period.
Video Frequencies etc
Video output:
PAL non-interlaced | 50.0801 Hz, 312 lines per frame |
PAL interlaced | 50 Hz, 625 lines (312 / 313 lines) |
Horizontal frequency | 15625 Hz |
Pixel clock | 8Mhz (Lo res) or 16MHz (Hi-res mode) |
NTSC non-interlaced | 60.106 Hz, 262 lines per frame |
NTSC interlaced | 59.99 Hz, 525 lines (262 / 263 lines) |
Horizontal frequency | 15748 Hz |
Pixel clock | 8Mhz (Lo res) or 16MHz (Hi-res mode) |
VGA 60Hz | 60.106 Hz, 262 double-lines per frame |
Horizontal Frequency | 31496 Hz (line doubled) |
Pixel Clock | 16 Mhz (Low res only) |
VGA 50Hz 8) | 50.4 Hz, 312 double-lines per frame |
Horizontal Frequency | 31496 Hz (line doubled) |
Pixel Clock | 16 Mhz (Low res only) |
8) This is a non-standard mode and may not work on all VGA monitors.
Video Registers - Details
Notes
- The Video Registers are located between $0200-$07ff in Z80 address space, unless paged out using the port: sys_alt_write_page
- All video registers are WRITE ONLY.
Register $200
“vreg_xhws” - Horizontal Hardware Scroll (No effect in Chunky mode)
Bits | Description |
---|---|
7:4 | Playfield B Scroll position (0 - 15 pixels offset to the right) tile mode only |
3:0 | Playfield A / Bitmap Mode Scroll position (0 - 15 pixels offset to the right.) |
Register $201
“vreg_vidctrl” - Playfield Control
Certain bits of this register take on different meanings depending on the video mode:
In Bitmap Mode (IE: When bit 0 is clear):
Bit | Description |
---|---|
7 | Enable “Chunky Pixel” Format (each video data byte fetched is a colour index) |
6 | Enable “Flood Mode” - for use with in Chunky Pixel mode |
5 | Use Bitplane Location Register set A (0) or B (1) for video data |
4 | Not Used |
3 | Horizontal Expand: 0 = Off, 1 = On (For chunky mode only) |
2 | Video Unhibit: 0 = Normal, 1 = Colour index $00 replaces all non-sprite video |
1 | Wide Left Border: 0 = Normal, 1 = Mask first 16 pixels for x-scrolling |
0 | Display mode: 0 = Bitmap Mode, 1 = Tilemap mode |
In Legacy Tile Map Mode (IE: When bit 0 is set and bit 0 of vreg_ext_vidctrl is clear):
Bit | Description |
---|---|
7 | Dual Playfield Enable |
6 | Playfield B Map Buffer Select |
5 | Playfield A Map Buffer Select |
4 | Playfield B Tile Set Select |
3 | Playfield A Tile Set Select |
2 | Video inhibit: 0 = Normal, 1 = Colour index 0 replaces all non-sprite video |
1 | Wide Left Border: 0 = Normal, 1 = Mask first 16 pixels for x-scrolling |
0 | Display mode: 0 = Bitmap Mode, 1 = Tilemap mode |
In Extended Tile Map Mode (IE: When bit 0 is set, and bit 0 of vreg_ext_vidctrl is set):
Bit | Description |
---|---|
7 | Dual Playfield Enable |
6 | Playfield B Map Buffer Select |
5 | Playfield A Map Buffer Select |
4 | Definition swap (0= Normal, 1= tiles at $00000-$3FFFF switched with $40000-$7FFFF) |
3 | Tile size: 0 = Use 16×16 tiles, 1 = Use 8×8 tiles |
2 | Video inhibit: 0 = Normal, 1 = Colour index 0 replaces all non-sprite video |
1 | Wide Left Border: 0 = Normal, 1 = Mask first 16 pixels for x-scrolling |
0 | Display mode: 0 = Bitmap Mode, 1 = Tile map mode |
Register $202
“vreg_window_size” - Display Window Size - Sets size/position of display window.
When Reg_Switch is zero (bit 2 of vreg_rasthi is 0):
Bits | Description |
---|---|
7:4 | Window Top (Y_START) position (see table above for values) |
3:0 | Window Bottom (Y_STOP) position (see table above for values) |
When Reg_Switch is one (bit 2 of vreg_rasthi is 1):
Bits | Description |
---|---|
7:4 | Window Left (X_START) position (see table above for values) |
3:0 | Window Right (X_STOP) position (see table above for values) |
Register $203
“vreg_yhws_bplcount” - Vertical Hardware Scroll Settings / Bitplane count
In Tilemap mode this register sets two separate hardware scroll values, one for each playfield. To set the scroll value for playfield 0, write to this register with bit 7 clear. To set the scroll value for playfield 1, write to this register with bit 7 set.
Bits | Description |
---|---|
7 | Select Playfield 0 / Playfield 1 Y-scroll register |
6:4 | Not used |
3:0 | Vertical Scroll offset value (0 to 15 pixels) |
In Planar Bitmap Mode this register sets the number of bitplanes in the display:
Bits | Description |
---|---|
7 | Write with this bit as ZERO |
6:3 | not used |
2:0 | Number of bitplanes (0= one bitplane to 7= eight bitplanes.) |
In Chunky Bitmap mode this register sets the width of the pixels:
Bits | Description |
---|---|
7 | Write with this bit as ZERO |
6:3 | Not used |
2:0 | Width of pixels (0-7 = 1 to 8 pixels) |
Register $204
“vreg_rasthi” - Raster IRQ MSB, enable, clear & Window XY switch
Bits | Description |
---|---|
7 | Clear Raster IRQ flag 9) |
6:3 | Not Used |
2 | vreg_window_size register X/Y switch (0 = Vertical, 1 = Horizontal) |
1 | No longer used (was: Video IRQ enable, use bit 3 of sys_irq_enable instead) |
0 | Raster IRQ Scanline Position MSB |
9) When this register is written with bit 7 set, the video IRQ flag is cleared and the rest of the register's contents are unchanged.
Register $205
“vreg_rastlo” - Raster IRQ line position
Bits | Description |
---|---|
7:0 | Low 8 bits of Raster IRQ scanline position |
Notes
- The counter used in the line comparison starts 16 lines above the first visible scanline (same as the sprites). Note: PAL, NTSC and VGA have different numbers of scanlines and no interrupt will occur if this register and the MSB in vreg_rasthi are set to a line the display mode never reaches.
- Please use a value above zero. Setting the raster IRQ line to 0 (including the MSB in vreg_rasthi) currently causes undesired effects (multiple interrupts) due to internal counter limitations.
Register $206
“vreg_vidpage” - Video Page Access Control
This register selects which 8KB page of Video RAM appears in the Video RAM Access Window in Z80 address space (default location Z80 $2000-$3FFF). It also determines which 4KB of sprite RAM is to accept writes through the Sprite RAM access window (at Z80 $1000-$1fff).
When the register is written with bit 7 clear: Function is “set video page”.
Bits | Description |
---|---|
7 | Write as 0 |
6 | Not used |
5:0 | Video Page Selection (64 banks - 8KB each) |
(When Direct VRAM Write mode is active, bits 5:3 select the 64KB bank.)
When the register is written with bit 7 set: Function is “Set sprite page”.
Bits | Description |
---|---|
7 | Write as 1 |
6:5 | Not used |
4:0 | Sprite page selection (32 banks - 4KB each) when sprite RAM is paged in |
(Note: Sprite RAM is write only)
Remember, the video and sprite access windows must be enabled with the port sys_mem_select for data to be transferred from the relevant RAMs.
Register $207
“vreg_sprctrl” - Sprite Control
Bit | Description |
---|---|
7 | Not used, write with zero |
6 | “Fix colours low” - when set, only colours $00-$7f are used for sprites |
5 | “Matte Mode Enable” 10) |
4 | “Modify Colour MSBs” 10) |
3 | Enable double buffer sprite register mode |
2 | Register bank select (use with double buffer register mode) 11) |
1 | Priority Interleave: 0 = All sprites are in foreground. 1 = interleave mode |
0 | Global Sprite Enable. All sprites are disabled when this bit is 0 |
10) These modes force the upper bits of individual sprite height control registers to be
reassigned, see the sprite description text above for details.
11) Sprite Register Bank select bit:
Bit | Description |
---|---|
0 | Sprite hardware reads sprite registers $400 - $4fd |
1 | Sprite hardware reads sprite registers $500 - $5fd |
Register $208-$209
“mult_write” - 16 bit multiplier
The signed word written here is multiplied by the word in the multiply table indexed by the following register. 16 bits (29:14) from the resulting 32-bit word are read from “mult_read” (the set of bits was chosen to provide fast scaling.)
Register $20A
“mult_index” - Maths table index (byte)
Selects which word from the look up table is used for the multiplication.
Register $20B
“linedraw_colour” - Line draw colour
Colour table index for line draw hardware. (There is only a single colour register, so to maintain a constant line colour, this value should not be changed whilst a line is being drawn.)
Register $20C
“vreg_ext_vidctrl” - Extended Video Mode Control Bits
Bits | Description |
---|---|
7:4 | n/u |
3 | Enable Hi-Res 16 colour TV mode (no effect in VGA mode) |
2 | Enable interlaced TV mode (no effect on VGA output) |
1 | Flip Dual Playfield priorities (1 = PF B appears behind PF A) |
0 | Use extended tile indexes (1 = on, 0 = “legacy” 8 bit tile indexes) |
Register $20d
“linecop_addr0”- LineCop list address bits 7:1 (bit = enable)
Bits | Description |
---|---|
7:1 | Linecop program location bits: 7:1 (LSB) |
0 | LineCop on/off (1 = enable) |
Register $20E
“linecop_addr1”- LineCop list address bits 15:8
Register $20F
“linecop_addr2” - Linecop list address bits 18:16
Bits | Description |
---|---|
7 | Always set to 1 when writing a linecop address (otherwise vreg_palette_ctrl is selected) |
6:3 | Not currently used (write with zeroes) |
2:0 | Linecop list address bits 18:16 (MSB) |
“vreg_palette_ctrl”- Palette Control
This register allows independent access to two palette control bits: The “live” palette (ie: that currently being used by the video hardware to fetch the colours) and the palette which receives CPU / LineCop writes. So that each setting can be changed independently (without reads) the register uses a bit (1) to select which register is to be accessed and a bit (0) for the data to be written:
Bits | Description |
---|---|
7 | Always write with 0 to select palette control, otherwise linecop_addr2 is selected |
6:2 | Not currently used (write with zeroes) |
1 | Choose to update “Live Palette Reg” (0) or “Target Palette Reg” (1) |
0 | Select palette 0 or 1 |
Note: Changes to the live palette selection take effect at the start of the next scanline, whereas changes to the target palette register take effect immediately.
Register $210-$21a
Blitter set-up
Register $210
“blit_src_loc” - Source Address in VRAM Low 16 bits (little endian)
Register $212
“blit_dst_loc” - Destination Address in VRAM Low 16 bits (little endian)
Register $214
“blit_src_mod” - Source modulo [bits 7:0] 12)
Register $215
“blit_dst_mod” - Destination modulo [bits 7:0] 12)
Register $216
“blit_height” - Number of lines in blit - 1
Register $217
“blit_width” - Width of blit in bytes - 1 [Write starts the blit]
Register $218
“blit_misc” - Misc bits, see assignments below:
Bit | Description |
---|---|
7 | Transparency mode: If 1 then zero value bytes read from source are not written |
6 | Blit Direction: Ascending = 1 / Descending = 0 |
5 | Legacy destination Address MSb [bit 16] - see notes below |
4 | Legacy source Address MSb [bit 16] - see notes below |
3 | Destination Modulo bit 9 (Sign) |
2 | Destination Modulo bit 8 |
1 | Source Modulo bit 9 (Sign) |
0 | Source Modulo bit 8 |
Register $219
“blit_src_msb” - 2:0 highest three bits [18:16] of source address 13)
Register $21a
“blit_dst_msb” - 2:0 highest three bits [18:16] of destination address 13)
13) Bit 16 is internally OR'd with the equivalent bit in “blit_misc”, this is for backwards compatibility. If you are only accessing video addresses < 128KB you can just use the “blit_misc” and ignore these two registers. Conversely if you are generally using the entire VRAM range its best to use these registers and write the MSB bits in “blit_misc” with zeroes.
Writing to “blit_width” actually starts the blit operation. Remember, the height and width values should be the blit dimensions less one.
Blitter Notes
Following a blit, all the registers except “Width” (which has to be re-written anyway) retain the values originally written to them. Most of the blitter registers can be changed once a blit is underway without problems, however the Modulos, Width and Misc registers should not be updated until a blit is finished.
Be wary when leaving the blitter running and returning to other code – this is of course perfectly fine as long as the other code does not go on to access the blitter's registers before it has finished a blit. The blitter status flag (bit 4 in sys_vreg_read) should be examined at relevant points to prevent clashing blitter ops.
12) The modulo is the number of bytes added to the end of the source and destination counters at the end of each line. The polarity of these values is reversed by the blit direction. So if in descending mode, a negative modulo will result in a positive offset.
Register $220-$23f
Line Draw
The line draw system uses registers in the range: $220-$23f. There are two main groups of registers: Control registers that need to be set up for each line, and an overall look-up table that only needs to be set up once on initialization.
The following four control words need to be set up for each line (each is 16 bits long):
$220-$221
linedraw_reg0 - Bresenham register [0]: 2x(dy-dx) [or 2x(dx-dy) 14)]
$222-$223
linedraw_reg1 - Bresenham register [1]: 2x(dy) [or 2x(dx) 14)]
$224-$225
linedraw_reg2 - Start location address of line in VRAM (bits 15:0)
$226-$227
linedraw_reg3 - Composite word, bits assigned as follows:
Bits | Description |
---|---|
15 | Bit 16 of line start location in VRAM (legacy) [See bits 11:9] |
14 | Octant code: Set to 1 if dy ⇒ dx 14) |
13 | Octant code: Set to 1 if dy is negative |
12 | Octant code: Set to 1 if dx is negative |
11:9 | Bits 18:16 of line start location in VRAM (NB: bit 9 is internally OR'd with bit 15) |
8:0 | Line length |
14)
- Registers should be written with positive values, the sign of the value is held in the octant code.
- If bit 14 of the composite word is set (because dy ⇒ dx) then the Bresenham decision [0] register should be loaded with 2 x (dx - dy) and decision register [1] should be loaded with 2 x (dx)
Writing to (the LSB of) this register actually starts the line drawing operation, so the sys_vreg_read should be checked beforehand.
In the above, “dy” and “dx” refer to delta_x and delta_y (IE: y1-y0, x1-x0)
The following four 16 bit registers register addresses have the same function as registers $220-$227 above, but they can hold different values. Having two sets of line-draw registers allows one set to be loaded whilst a previous line draw operation is still running. (The line_draw busy flag in vreg_read only needs to be checked before writing to the LSB of linedraw_reg3 (or linedraw_reg7) which starts the linedraw, or the linedraw_colour
register, if changing colour.
$228-$229
linedraw_reg4 - Same function as $220/1 for register set 2
$22a-$22b
linedraw_reg5 - Same function as $222/3 for register set 2
$22c-$22d
linedraw_reg6 - Same function as $224/5 for register set 2
$22e-$22f
linedraw_reg7 - Same function as $226/7 for register set 2
The following eight 16 bit registers form a look-up table which is used by the internal harware to offset the plot address of each pixel. These registers need only be set once as part of an initialization routine, IE: not per line.
$230-$231
linedraw_lut0 - Pixel offset constant 0 .. (65536-Window width)+1
$232-$233
linedraw_lut1 - Pixel offset constant 1 .. (65536-Window width)-1
$234-$235
linedraw_lut2 - Pixel offset constant 2 .. Window width + 1
$236-$237
linedraw_lut3 - Pixel offset constant 3 .. Window width - 1
$238-$239
linedraw_lut4 - Pixel offset constant 4 .. 1
$23a-$23b
linedraw_lut5 - Pixel offset constant 5 .. 65535
$23c-$23d
linedraw_lut6 - Pixel offset constant 6 .. (65536 - Window width)
$23e-$23f
linedraw_lut7 - Pixel offset constant 7 .. Window width
(See also: Line colour register $20B - linedraw_colour)
Register $240-$27f
Bitplane location
Register $240
bitplane0a_loc (BPl register set A)
Register $244
bitplane1a_loc (BPl register set A)
Register $248
bitplane2a_loc (BPl register set A)
Register $24c
bitplane3a_loc (BPl register set A)
Register $250
bitplane4a_loc (BPl register set A)
Register $254
bitplane5a_loc (BPl register set A)
Register $258
bitplane6a_loc (BPl register set A)
Register $25c
bitplane7a_loc (BPl register set A)
Register $260-$27f
As above for Bitplane location register set B. Bit 5 of “vreg_vidctrl” controls which set is used by the hardware to build the display.
Each bitplane location “register” is 4 bytes long, the data required for each is:
Offset: Data: ------------------------------------------------ + 0 = Location of Bitplane in video RAM [7:0] + 1 = Location of Bitplane in video RAM [15:8] + 2 = Location of Bitplane in video RAM [18:16] + 3 = Reset internal offset counter / Set Modulo - see notes below
Notes
- The value written to the bitplane location registers only needs to be set once, an internal offset counter is added to the address as the frame is built up. There is only one offset counter which acts on all the bitplanes - it can be reset to zero by writing to the 4th byte of the even-numbered bitplane pointers (eg: bitplane0a_loc+3) this actually takes effect at the start of the next scanline.
- In chunky pixel mode, there is only one data-fetch start address, that is: bitplane0a/b
Modulo
The modulo register holds the number of words to skip at the right of each scanline (positive only). This allows a window from within a larger image to be displayed (EG: can be used for scrolling and skipping lines in interlaced displays). There is one modulo register, it is located at the 4th byte of the odd-numbered bitplane registers (EG: bitplane1a+3) - as mentioned, the granularity is 2 bytes so for example a written value of 1 skips 2 bytes each line. The value in this register is internally latched at the start of each scanline and because of the way it is implemented there is an upper limit on the value it can hold (depends on the display mode) - values up to 192 (IE: skip 384 bytes) are OK. There is also a special case: If $FF is written to the modulo register the bitmap offset counter is reset at the start of each scanline, IE: With no other changes, the same line is used over and over.
Register $280-$28F
Sprite priority mask (“priority_registers”)
Register $280
Bit 0: bgnd colours 00-0F mask level 0, Bit 1: bgnd colours 00-0F mask level 1
Register $281
Bit 0: bgnd colours 10-1F mask level 0, Bit 1: bgnd colours 10-1F mask level 1
Register $282
Bit 0: bgnd colours 20-2F mask level 0, Bit 1: bgnd colours 20-2F mask level 1
Register $283
Bit 0: bgnd colours 30-3F mask level 0, Bit 1: bgnd colours 30-3F mask level 1
Register $284
Bit 0: bgnd colours 40-4F mask level 0, Bit 1: bgnd colours 40-4F mask level 1
Register $285
Bit 0: bgnd colours 50-5F mask level 0, Bit 1: bgnd colours 50-5F mask level 1
Register $286
Bit 0: bgnd colours 60-6F mask level 0, Bit 1: bgnd colours 60-6F mask level 1
Register $287
Bit 0: bgnd colours 70-7F mask level 0, Bit 1: bgnd colours 70-7F mask level 1
Register $288
Bit 0: bgnd colours 80-8F mask level 0, Bit 1: bgnd colours 80-8F mask level 1
Register $289
Bit 0: bgnd colours 90-9F mask level 0, Bit 1: bgnd colours 90-9F mask level 1
Register $28A
Bit 0: bgnd colours A0-AF mask level 0, Bit 1: bgnd colours A0-AF mask level 1
Register $28B
Bit 0: bgnd colours B0-BF mask level 0, Bit 1: bgnd colours B0-BF mask level 1
Register $28C
Bit 0: bgnd colours C0-CF mask level 0, Bit 1: bgnd colours C0-CF mask level 1
Register $28D
Bit 0: bgnd colours D0-DF mask level 0, Bit 1: bgnd colours D0-DF mask level 1
Register $28E
Bit 0: bgnd colours E0-EF mask level 0, Bit 1: bgnd colours E0-EF mask level 1
Register $28F
Bit 0: bgnd colours F0-FF mask level 0, Bit 1: bgnd colours F0-FF mask level 1
Maths Assist Table
Register $600-$7FF
“mult_table” WRITE ONLY
This table holds 256 signed 16 bit words. The entry selected by “mult_index” is multiplied by the signed word in “mult_write” and 16 bits [29:14] of the 32 bit result appears in “mult_read“
Register $700
“vreg_read” - READ ONLY Status Register (Also available in port: sys_vreg_read)
Bits | Description |
---|---|
7 | Interlace field 0 = short field, 1 = long field |
6 | LSB of scanline count |
5 | 60Hz mode (1 = NTSC config / VGA mode jumper installed and 50Hz not forced.) |
4 | Blitter / linedraw status (1 = busy). Check before changing relevant registers |
3 | Raster IRQ status (for manual polling) |
2 | Y Window (1 = display area, 0 = border) |
1 | X Window (1 = display area, 0 = border) 15) |
0 | Last line (VRT). Set during the last line of each frame. |
15) Remember, in VGA mode each scanline's data is output twice at double the normal frequency. This flag reflects the x-window of the normal PAL/NTSC ~15KHz scanline.
Register $704-$705
“mult_read” READ ONLY - Maths unit result (little endian):
Bits 29:14 from the longword result of the maths unit operation appear here.
OSCA's ROM
OSCA locates a boot ROM at $0000-$01FF - this is contained within the FPGA config file. At reset, the ROM clears the following ports and video registers:
- palette (IE: background colour = $000)
All interrupts are disabled (IRQs via the Z80 DI instruction and NMIs via bit 0 of sys_hw_settings). The IRQ vector (at $0038) has the instruction “JP $0A00”, and the NMI vector (at $0066) has the instruction “JP $0A03”. OSCA is designed to use Interrupt Mode 1.
The ROM then attempts to load the main boot code from the onboard EEPROM. Two locations are checked:
EEPROM Block 0: $F000 [Primary bootcode location]
EEPROM Block 1: $F000 [Backup bootcode location]
To test for bootcode, a “databurst” command is sent to a PIC microcontroller which responds by sending 3520 bytes from the EEPROM. The ROM reads this data into system RAM $0200 onwards, checks the CRC checksum (held in the last two bytes) and executes it (with a JP $200) if the checksum is good.
(If the CRC doesn't match, the screen flashes magenta and the databurst is requested again from the backup. If this also fails, the screen goes grey indicating that a bootcode file should be downloaded via the serial link at 115KBPS. This grey screen condition can also be forced by holding up, right and fire on a joystick in Port 1 at power up. If at any point the screen flashes yellow, there was a time-out during the EEPROM databurst.)
Placing alternate data at $0000-$01FF
It is possible to have read/write access the system RAM “underneath” the OSCA ROM/Palette at $000-$1ff (see the port ”sys_alt_write_page”). In all there are three options for this memory range:
A) Read ROM / Write palette (default)
B) Read system RAM / write palette (set by bit 4 of sys_alt_write_page)
C) Read System RAM / write system RAM (set by bit 6 of sys_alt_write_page)
The IRQ vectors must be set appropriately in system RAM when non-ROM reads are enabled (and interrupts are required).
INDEX
Port List
(All ports are 8 bit unless stated)
Video Register List:
($000-$6FF are write only registers)
16) Same register location as below, set bit 7 for this function
17) Same register location as above, reset bit 7 for this function