Star Rider Main CPU Board
ROM program memory map
|0xE000-0xFFFF||Main ROM program (U52)|
|0xD800-0xDFFF||Board was designed to have a RAM or ROM chip here, but nothing actually was installed in production.|
|0xCC00-0xCFFF||NVRAM, 4-bit (upper 4 bits of data ignored). For the NVRAM to be read, the address must be between 0xCC00-0xCFFF. NVRAM can always be written if the address is between $cd00-$cfff. If MEMPROT' is raised (confirmed on real hardware), the memory between $cc00-$ccff may also be written; otherwise it will be write protected.|
|0xCBF0-0xCBFF||not connected (and if it was, it would be write only)|
|0xCBE0-0xCBEF||Color palette select (write only, only lowest 6 bits matter)|
|0xCBD0-0xCBDF||Video expander + background control (Write only). Bit 0: 0 = background enabled, 1 = background disabled. Bit 1: 0 = expander enabled, 1 = expander disabled|
|0xCBC0-0xCBCF||IMPG' even numbers, XLATE' odd numbers (write only). If IMPG' is active, then the lower 5 bits of whatever is written to the CPU data bus get stored in the Image Page latch (ie it sets the active image page). See the Paging and ROM PCB section for more info about the Image Page. If XLATE' is active, then the lower 6 bits of whatever is written to the CPU data bus get stored in the XLATE (translate probably) latch. See XLATE' for more info.|
|0xCBB0-0xCBBF||DMA (Write only). Controls special blitter chips. Bit 3 of the address is ignored and the ROM program uses 0xCBB8-0xCBBF.|
|0xCBA0-0xCBAF||Vertical Counter. Reads the current value of the vertical counters (see description of them in the timers/counters section). Read only, attempting to write here will have no effect. The lowest 8 bits are read (so the 9th bit is ignored).|
|0xCB90-0xCB9F||Field: High bit will be equal to the inverse of the FIELD line. In other words, Top/odd field is high, bottom/even field is low.|
|0xCB80-0xCB8F||VGG U7 PIA control (80 is periph/dir for A, 81 is control for A, 82 is periph/dir for B, 83 is control for B, this pattern repeats). Port A goes to the video expander, port B goes to the PIF board (laserdisc player).|
|0xC980-0xC9FF||CPU U10 PIA inteface is 0xC980-0xC983, CPU U20 PIA interface is 0xC984-0xC987, this repeats at 0xC990.|
|0xC900-0xC97F||Watchdog conditional reset (see Watchdog section)|
|0xC880-0xC8FF||LED on the main CPU board (writing anywhere to this address changes value displayed by LED).|
|0xC800-0xC87F||Change active page (see page section).|
|0xC000-0xC7FF||Color RAM (see section below about colors)|
|0x0-0x9FFF||See Page section|
Star Rider employs a wacky paging system where it can modify what the address from 0-0x9FFF points to in real-time. This is most likely to workaround the 16-bit address limit of the 6809E CPU. Executable code can be and is paged in and out, making reverse engineering more difficult.
The page value is 4 bits so has a theoretical range of 0-0xF however in practice it appears likely that the bits of the page value are mutually exclusive (ie valid page values would be 0, 1, 2, 4, and 8 only).
CPU address/data bus vs DMA address/data bus
The Star Rider schematics have a concept of a CPU address/data bus and a DMA address/data bus. They are separate but can intermingle under certain conditions.
The DMA address bus includes the custom 'special' blitter chips, the video RAM, and the ROMs on the ROM PCB.
The CPU address bus includes the CPU, the CPU's program ROMs, and the CPU's RAM.
The CPU and DMA data buses do not quite follow the same rules as the address buses.
See the sections about "DMA address bus" and "DMA data bus" for more info.
Writing to the CPU address bus using a range of 0-0x9FFF will always modify video RAM (on the DMA address bus) as long as bit 3 of the page value is not set (ie page value < 8). See DMABUSS' signal below.
Reading from the CPU address bus using a range of 0-0x9FFF is more affected by the active page value than writing and described below.
Gives CPU read access to its own program ROMs. The default page where most of the program ROM is accessible.
Gives CPU access to DMA address bus for the purpose of reading from the ROM PCB. ROM is selected by having address be between 0-0x3FFF or 0x4000-0x7FFF, and by setting the image page (IMPG) 5-bit register.
Gives CPU access to DMA address bus for the purpose of reading from the video RAM. When reading when page 2 is active, the following occurs (assuming address is 0-0x9FFF):
ENBUSS' goes low. Assuming HALT is low, the DMA address bus will equal the CPU address bus. The CPU data bus will get whatever is on the DMA data bus.
Gives CPU read access to its own program ROMs. These ROMs will be an alternate set from page 0, to allow the program ROM to exceed the 64k limit.
|0x4000-0x7FFF||ROM4 (U37, not installed!)|
Page 8 appears to only have been used when burning ROM images (which is apparently something the developers used and which the hardware supports). It should never be set in the production ROM program.
The ROM PCB
The ROM PCB is part of the DMA address bus and is read when OEROM' is active (low). While OEROM' is active, two ROM images can be read (three if you include U46) by setting the image page value (IMPG) and reading from DMA address 0000-0x3FFF or DMA address 0x4000-0x7FFF. The U46 ROM violates this rule and is always accessible by reading from DMA address (not CPU address!) 0xC000-0xFFFF.
CPU access hack: As a hack to allow the CPU to access U46 (since the CPU cannot access any DMA address above 0x9FFF), the hardware will flip the top bit of the CPU address (when converting it to DMA address) if the image page is 0x1E and the lower two bits of the page value are 01. So, for example, the CPU could set the image page to 0x1E, set the page value to 1, and access memory region 0x4000-0x7FFF in order to access U46.
|ROM label||DMA address range||Image Page|
|U46||0xC000-0xFFFF||N/A (see note about CPU access hack)|
The non-volatile RAM on the CPU board is a 5514 CMOS static RAM. It is my understanding that static RAM was (is?) more expensive than the dynamic RAM but did not require the constant refresh that the DRAM required so it was desirable to use with batteries while the machine was powered off.
NVRAM is kept alive by 3 AA batteries. The CS' line on the RAM chip (U9) is kept disabled (high) when the system is powered off or if RESET' is active (low). If RESET' is disabled (high), then a transistor (Q2) will cause CS' to be controlled by U13 and U11 (which is controlled by the current memory address from the CPU). CS' is kept disabled (high) by either the AA batteries or by VCC, either one going through a 1K resistor. CS' gets pulled low only when the output of U13 (pin 6) goes low (when the CPU address is within the NVRAM range described in the memory map above).
See https://sites.google.com/site/delroy666/williams-nvram for an interesting site related to this type of memory.
The watchdog is reset by (usually) writing the bit pattern of 010101 (0x15) to memory location $C900-$C97F. According to the schematic, only the D1-D5 bits of the data byte are actually used, so any byte matching the pattern of ??01010? should be enough to reset the watch dog counter. The counter is a 4-bit counter which increments on the rising edge of END SRN' and appears to trigger a reset when the highest bit goes high (ie when the counter >= 8). Therefore, the watchdog must be "fed" every 7 vsyncs or less to avoid having the game get reset.
I speculate that the reason a specific pattern must be written to the watchdog memory location in order to reset the watchdog counter is so that the watchdog doesn't get accidentally reset if the CPU is off in lala-land running random code.
Here is an LTSpice simulation of Star Rider's watchdog circuitry. WDR is the 'QD' pin from the U22 (LS161) counter from the schematic (it is active high, the schematic implies that it is active low). 'reset' is the active low reset signal that will be sent to things like the cpu, pias, etc. The duration of the 'wdr' pulse is just contrived, not based on actual measurements. The conclusion one may draw is that the watchdog circuitry lengthens out the pulse duration.
It appears that different people worked on different parts of the code in parallel. Evidence of this can be seen when looking at 0xE000 and 0xF000. The beginning of 0xE000 jumps to the regular program boot, while 0xE003 jumps to 0xF000 which begins the self-tests when the game first boots. The ROM is coded to reset to address 0xE003, but during development it most likely was coded to reset to address 0xE000 and skip the self-test stuff. This would allow the guy writing 0xE000 to focus on his own stuff, knowing that he would add in the self-test stuff later. Meanwhile, the code at 0xF000 has several JMP statements which appear in series at the beginning of 0xF000. I can imagine that the guy who wrote the test code at 0xF000 provided these hard-coded addresses to the rest of his team while they were developing. For example, jumping to 0xF000 will start the boot-up self-tests. 0xF003 starts the more elaborate self-tests that can be activated by pressing ADVANCE. 0xF006, 0xF009, 0xF00C, and 0xF00F all jump to other places in the code too. One can also observe that the code immediately before 0xF000 is empty which further suggests that two separate people were working on the code for 0xE000 and 0xF000 in parallel.
- Color test
- RAM test
- ROM test
- NVRAM test
- PIF test
There is a text drawing routine at $5E73. This routine is used heavily by the diagnostics part (and perhaps other parts) of the code.
The usage is fairly simple but was tricky to reverse engineer because the actual implementation uses a callback system which can be confusing.
To use the method, the code loads a pointer to a structure (I'll call it a DrawTextData struct) into Y and then do a JSR to $5E73.
The DrawTextData structure consists of one or more entries. Each entry starts with an "entry type" byte, followed by the entry data. An entry type of 0 terminates the chain.
|2||change the font color|
|4||change the font|
|6||change the destination coordinates|
|8||draw some text|
Here is an example:
ROM3:733D InitialTestsIndicateData:fcb DrawTextLoadDstCoords_6 ROM3:733D ; DATA XREF: GoCmpCC07AndFWith9+380�o ROM3:733D ; the next two bytes will be destination coordinates (X then Y) ROM3:733E fcb $29 ; X ROM3:733F fcb $48 ; H ; Y ROM3:7340 fcb DrawTextLoadFontColor_2 ; load the next byte into B, which is used as the font color ROM3:7341 fcb $44 ; D ROM3:7342 fcb DrawTextLoadFontSrc_4 ; load the next two bytes into U, ROM3:7342 ; which is used as the font src base ROM3:7343 fdb PtrToBigFontBase ROM3:7345 fcb DrawTextRenderString_8 ; Renders the string pointed to by the next two bytes. ROM3:7345 ; The string is non-ascii and is terminated by the ROM3:7345 ; last byte having its high bit set. ROM3:7346 fdb TxtInitialTestsIndicate ; I ROM3:7348 fcb DrawTextDone_0 ; the drawtext callback loop is finished
This will draw at X coordinate 0x29 and Y coordinate 0x48. It will draw using color 0x4 both both the even and odd columns. It will use the big font to draw. And it will draw the text contained at the array at 'TxtInitialTestsIndicate' which is in fact "INITIAL TESTS INDICATE".
The font is not laid out in ASCII format which makes it hard to decipher from a hex editor.
|0x0-0x9||the numbers '0' through '9'|
|0xA||space (white space)|
|0xB-0x24||the letters 'A' through 'Z'|
Can be called via the 4MS timer or VBLANK' depending on how the PIAs are configured. Using Daphne for research, the IRQ appears to be configured to be triggered from VBLANK' (the END SCREEN' line).
Coins, advance switch, auto/manual switch, etc is serviced at $E45B.
- Current value read from U10 PIA PORT A is XOR'd with whatever is stored at $A115, and this is stored at $A116.
- Current value from U10 PIA PORT A is then stored at $A115.
- This XOR'd value is then AND'd with whatever is stored at $A117 (some kind of mask?).
- The result of the AND is stored at $A118.
Biphase data updated at $E414.
May be called when blitter chip completes an operation.
Located at $7C29.
Search To test
Located at $7D79.
Calls $7E75 which appears to initiate a disc search.
Disc search seems to wait indefinitely for either $A1B9 to be non-zero or $A1BC to be zero (see $7E88). This may indicate when a search has finished.
$A1BC is cleared by $1BDD.
Disc Search test
Located at $926A.
Auto-up controls playback direction. Auto-up enabled means forward playback ($93A1). Manual-down means reverse playback ($93A3). Some other condition needs to occur for this to take effect.
Auto-up controls video expander ($92FF).
VGG U7 to PIF U1
|Connection pin||VGG U7 PIA name||PIF U1 PIA name|
PCB modifications not documented in schematics
Pin 13 of U116 rerouted to connect to pin 4 of U117. Notice the cut trace. The original input and the modified input both relate to HSYNC from the laserdisc player (from what I can tell).
Schematic signals of note
Active (low) if page bit 0 is set (ie page value is 1).
Is high when there is bi-phase encoded data (such as the laserdisc picture number) available to be read from the laserdisc video signal. Will be high when vertical line count is 0xB (of the top field), or when the (vertical line count >= 0xF1 and <= 0xFE). Vertical line count is offset from NTSC line count by 7, so a vertical line count of 0xB would be NTSC line 18.
Active when CPU address is between 0xCB00-0xCB7F. Enables the VGG's 6810 128-byte RAM IC for reading unless the BI-PHASE WINDOW is active.
Calculated from VGG 3/5, this suppresses both ROM and laserdisc generated RGB signals from going out to the RGB monitor. (see VGG 1/5)
VGG 1/5, this is low if DMA address >= 0xC000 . DMA address will match CPU address if ENBUSS' is active (low).
If the CPU tries to read from 0xC000 directly, it will get the color palette RAM. However, there is a special case where the CPU can flip the high bit of its DMA address by setting the image page (IMPG') value to 0x1E, and setting the active page value to 1 (ie bit 0 is raised). This is how it reads the ROM PCB's U46.
This gets enabled (low) if CPU is writing to address 0xCBB0-0xCBBF (controlling the special Williams blitter ICs).
DMA Address Bus
DMA Address bus will match the CPU Address bus (ie the CPU can control it) if ENBUSS' is active (low) and if HALT is inactive (low, when the blitter chips are idle).
This gets enabled (low) if:
- Address is 0-0x9FFF AND
- (when CPU is reading) Active page is 1,2,3,5,6, or 7 (ie not 0 or 4, and less than 8)
- (when CPU is writing) Always enabled if active page < 8
DMA Data Bus
DMA Data bus will match the CPU's data bus if ENBUSS' is active (low). Both read and write directions are supported.
This signal indicates read/write direction of the DMA bus.
If the blitter chips are active (HALT being raised), then they drive this signal directly. Else, the signal is derived from a series of gates in conjunction with an open collector output and a pull-up resistor.
The value of this signal is low (write) if:
- CPU R/W' is low (write) AND
- HALT is low (blitter inactive) AND
- WCNT' is active (low)
Else the value gets pulled high by a 4.7k pull-up resistor (meaning it's in read mode).
I believe this stands for Enable [DMA] bus and refers to either writing to the special blitter chips or reading/writing to DMA (video) memory. This gets enabled (low) if:
- HALT (VGG) is low AND
- DMABUSS' is low OR
- DMA' is low (0xCBB0-0xCBBF address)
Same as END SRN'
Same signal as VERT BLANK'
Becomes active (low) in the approximate center of NTSC line 1's HSYNC pulse, and becomes inactive (high) about 1/4 of the way through NTSC line 23's HSYNC pulse. This is confirmed on real hardware.
Top/odd field is low, bottom/even field is high. (The LM1881 field is high for top/odd and low for bottom/even. Confirmed via datasheet and blog posts such as http://my-cool-projects.blogspot.com/2013/06/injecting-data-into-vbi-section-of-ntsc.html )
This will change when VGG VSYNC(DISK)' becomes active (low) which is approximately 3/4 of the way through line 4. This is confirmed on real hardware.
For bottom field, I strongly suspect that VGG VSYNC(DISK)' will tend to go low about 1/4 of the way through line 267, where vblank starts on line 263.
HALT originates from the special Williams blitter chips. It appears to prevent most of the ICs related to video RAM from functioning, presumably to allow the blitter to have full control over the DMA bus. HALT does not halt the main CPU. Instead, it appears to be connected to the main CPU's FIRQ through the PIA (U7?) on the VGG board. If the main CPU attempts to do a DMA-related operation while HALT is active, the operation appears to just get dropped as a NOP. I will hopefully know more later.
Only used by original developer to burn EPROM. Disconnected on production board.
Will be active (low) if:
- Page bit 0 is set (ie page value is 1) AND
- Page bit 1 is not set (ie page value is not 3) AND
- WRX' is active (low) AND
- WCNT' is active (low) AND
- Page bit 1 is not set (this is a redundant check)
I believe that this stands for Output Enable RAM, and it is active low. It defines the conditions when the video RAM will be active on the DMA bus.
It will be active (low) when:
- DMA R/W' is high (read) AND
- Image Page (PP) is 0x1E and (Page value & 3 == 1) OR
- Image Page (PP) is 0x1F OR
- ((Page value & 3 == 2) AND HALT is disabled)
- CS TOP' is disabled (high) AND
- XDMA' is disabled (high)
I believe that this stands for Output Enable ROM, and it is active low. It defines the conditions when the ROM board may be read on the DMA bus.
It will be active (low) when:
- DMA R/W' is high (read) AND
- OERAM' is disabled (high) AND
- XDMA' is disabled (high)
Controlled by a push button on the CPU PCB (or the watchdog timer). Pressing this will reset the main CPU's 6809E, the 2 PIAs on the main CPU board, the PIA on the VGG board, and the two blitter chips on the VGG board.
This value is latched depending on the current vertical count (see timers & counters section). Will changed to active (low) if vertical count & 0xFE is 0. Will changed to inactive (high) if vertical count & 0xFE is 0x10.
Note that this does not quite match the actual vertical blanking period of NTSC which suggests that the vertical count is offset somewhat. Line 23 (where line 1 is the first line in the top field) is typically the first visible line after vertical blank ends.
This is a tricky one for me to understand.
Here's my current take of when it is active (high): E and Q both need to be high, and AVMA needed to be high when E went low previously.
This means that CPU R/W' and the CPU address will already be stable when VMA goes high.
It will be high when the CPU is reading or writing data.
Will be active (low) if:
- ENBUSS' is active (low) AND
- CPU address < 0x8000 OR
- Image page is not 0x1E OR
- Active page value >= 8 (bit 3 is set) OR
- CPU R/W' is high (read mode)
See BI-PHASE WINDOW
Same as the CPU's R/W' being low except the timing window is shorter (more strict). This will be low (active) if E and Q are both high, and R/W' is low.
Basically, this is active when the CPU has safely put the byte to be written on the data bus.
VGG 1/5 XMDA will be active(low) if DMA' is active (low) and HALT (vgg) is inactive (low).
It is active when CPU is attempting to write to 0xCBB0-0xCBBF (blitter chips) and the blitter chips aren't already busy doing a previous operation.
Probably stands for translate.
Value associated with this signal (when memory address is written) is 6 bits and gets latched into U12 on VGG 1/5. This value gets passed into PROMs U10 and U11, and may (or may not) modify data read from the ROM PCB. The use case for this seems to be to change specific colors read from the ROMs that contain bitmap images. For example, this can be used to change all colors of 0 to a color of 7 (of XLATE is set to 1) so that there is no transparent background. Since this is done by hardware, no CPU penalty occurs.
An XLATE value of 0 causes no change from the original bitmap to occur.
The color RAM is at 0xC000-0xC7FF. Each color is described by two bytes (16-bit color palette). The first byte contains green (high nibble) and red (low nibble), and the second byte contains blue (low) and luma (high). So for example, if I write to 0xC000, I would be writing the red and green information of the first color. If I then wrote to 0xC001, I'd be writing the blue and luma information of the first color.
When CBE0-CBEF written to, it updates U94 on the VGG board (74LS374 octal register) with the current 8-bit value on the data bus. Only the bottom 6 bits are used. The output of this register is enabled as long as the CPU's memory address is not in the 0xC000-0xC7FF range.
The value written here is latched as the upper 6 bits of the color RAM internal address (used by the hardware to lookup a color value).
The lower 4 bits are changed by the graphics hardware at will and refer to the active color index.
This means that each color palette can hold 16 colors and there are 64 total color palettes available. (seems like it would've been a lot cooler if there had been 16 total color palettes with 64 colors each!)
Color 0 is the transparent laserdisc background (see VGG 4/5 schmatic A2).
Timers and Counters
The master clock is 24 MHz and is located on VGG 3/5.
The master clock immediately gets divided in half to create a 12 MHz clock. This clock is used to update an 8-bit flip flop (U101), to generate a 6 MHz clock, and to influence the CAS' signal.
This 32-byte PROM is like a fancy gate array to describe how the 6 MHz clock will influence a bunch of signals. The high bit of the address (bit 4) alternates with each clock. The lower 4 bits of the address get the value of the lower 4 bits of the data that the address contains, hence the lower 4 bits of all data are a GOTO.
Q0-Q7 of this image is confirmed to be accurate using real hardware. The other signals are assumed accurate but have not been vetted as thoroughly.
On VGG 3/5, there is an 8-bit counter on the far-left, about halfway down. This counter increments (via a 4 MHz clock) until it hits 228 at which point it lowers the "INCREMENT VERTICAL COUNT" line and waits. More details later, hopefully.
The vertical counters are three 4-bit counters for a theoretical size of 12-bits. However, only the lower 9 bits are used. The lower 8-bits correspond to VA6-VA13 on the schematic. The 9th bit apparently does not ever make it to the video address bus.
By sniffing vsync and the vertical counters on the real Star Rider hardware, we've learned that:
- the vertical counter gets clear when the VGG hardware detects a new vsync (middle of NTSC line 4 for the top field)
- the vertical counter is equal to 1 when the NTSC top field line number is equal to 8. Or in other words, vertical count is offset 7 from the NTSC line number (where the first line of the NTSC top field is 1, not 0). Behavior for bottom field is unknown but most likely very similar. This means that a vertical count of 0xB (11) is NTSC line 18, which contains the laserdisc picture number on it. When the vertical count is 0xB, the hardware enables the BI-PHASE WINDOW signal to read the picture number (which is precisely what we would expect).
- When the FIELD line indicates that the bottom field is active, the vertical counter appears to be 255 on NTSC line 525. It then rolls over to 0 on NTSC line 1, 1 on NTSC line 2, 2 on NTSC line 3, 3 on NTSC line 4, then vsync becomes detected, and the vertical count gets reset and held at 0.
- When the FIELD line indicates that the top field is active, the vertical counter may keep counting up until the middle of line 267 which means that it will have a value of (267-7)&0xFF (or 4). This is not confirmed on real hardware (yet).
Why is vertical count offset from NTSC line number by 7? Perhaps it is because NTSC has 262.5 lines per field and the vertical counter has a range of 0-255 so the hardware designers wanted to maximize the visible region of the video RAM.
The video expander will always get disabled when the vertical counters (v.count) exceed 0xB4.
See VGG 3/5 schematics, around A4. There is a section labeled EXPAND CONTROL and two 74LS85's (U61 and U77). Pin 5 (A > B) of U77 will be raised when v.count > 0xB4.
The input 'B' bits for U61 (the least significant digit) is 4. Its compare inputs (pins 2-4) are wired to A==B as the datasheet requires. The inputs for 'A' are VA6-VA9 which are the least significant digits of the vertical counter.
U77 represents the most significant digit. Its compare inputs (pins 2-4) receive the compare outputs from U61. Its input 'B' bits are 0xB. The inputs for 'A' are VA10-VA13 which are more significant digits of the vertical counter. Its A>B output (pin 5) is high when the vertical counter > 0xB4 and thus when the expander should be disabled.
The horizontal counters are two 4-bit counters that make up an 8-bit counter. The bits correspond thusly:
|Bit||Corresponding signal on video address bus|
The horizontal counter increments every 0.5 microseconds. It gets reset at the end of the HSYNC pulse (ie when HSYNC pulse goes high). It is used to generate the HSYNC pulse if the laserdisc's video signal is not present. HSYNC from NTSC signal is supposed to be about 4.7-4.8 microseconds long. (It is almost exactly 4.68uS on raspberry pi + LM1881 CSYNC pulse.) If the board has to generate the HSYNC pulse (due to the lack of a laserdisc video signal), this pulse will be low for exactly 4.5 microseconds.
The horizontal counter will loop from 0-129 continuously. Hsync will be active while counter is 121-129 (9 0.5 microsecond periods, or 4.5 microseconds).
For reference, a horizontal line is supposed to be approximately 63.5 microseconds long. The exact length of a horizontal line is H = (1001 * 1000000 * 2) / (1000 * 60 * 525) microseconds.
If the board is generating HSYNC, its horizontal lines will be exactly 65 microseconds long (130 0.5 microsecond periods).
HSYNC VGG is the pulse that the board will generate if HSYNC DISC is not available. HLOAD is the complement of HSYNC VGG and leads it by 1 clock. HLOAD can be calculated as:
hload = (((hcount & 120) == 120) || ((hcount >= 128) && ((hcount & 1) == 0))) ? 1 : 0;
Or in other words, HLOAD will be high if hcount >= 120 and hcount <= 128, which means HSYNC will be low when hcount >= 121 and hcount <= 129. LOAD will only go low when HLOAD and HSYNC are both low, which only happens when HLOAD goes from high to low. When LOAD goes low, the counter resets to 0.
Each row in the table below represents a positive transition of the 2 MHz input clock.
|Horiz. Count||Microseconds||Hsync VGG||HLOAD||LOAD|
Using the schematics to understand how the video RAM maps to the pixels rendered on the RGB monitor is quite a difficult and slow process.
- Total video memory capacity: 384x256 pixels
- Total video memory that is addressable by the CPU and blitter ICs: 320x256 pixels
^ - this took me FOREVER to figure out! so hard!!!
The video memory is composed of six 8k DRAM chips for a total storage capacity of 49,152 bytes. Each byte contains two pixels, thus giving a total capacity of 98,304 pixels. The video hardware will render 6 pixels every 1 microsecond to the monitor (via a 6 MHz clock). The video hardware also uses the horizontal counters (divided by 2) to keep track of the horizontal position of the CRT gun. The horizontal counters loop from 0-129 which means that horizontal resolution is effectively (129/2 * 6) which is 384. Boom! This took me forever to validate even though I suspected it for quite some time because I had mistakenly calculated the DRAM size as being 2k, not 8k. 98,304 divided by 384 gives us our vertical resolution of 256.
How did I conclude that the addressable video memory is 320x256? I did it by emulating the game far enough to take some screenshots. But it's easier in hindsight. I already knew that the video RAM ranges from 0-0x9FFF so simply dividing 0xA000 by 256 gives 160 bytes per line. And there are 2 pixels per byte, hence 320 pixels per line.
How does the game hardware know where to put these 320 pixels on this 384 pixel line? By the Horizontal PROM, U74.
The 4-bit value here is the 4-bit color index to be rendered to the monitor at any given time. It is influenced solely by the horizontal and vertical counters which are in sync with the video signal's vertical and horizontal sync signals. The color index is translated into an RGB value by the circuitry on VGG 4/5, using the active color palette and the color palette RAM.
This value comes from a set of 74LS166's (known as "pixel shift register"s on VGG 2/5). These registers hold 6 bits each, and there are 4 of them, hence the registers hold 6 pixels at a time. After each set of 6 pixels has been rendered, the next set is loaded in via the SRL' line (active low) and the low->high transition of the 6 MHZ clock.
The pixels will be available on the bus to be loaded in because of the values of the MUX0/MUX1 lines in conjunction with the current value of the horizontal and vertical counters.
These lines determine whether the video memory is being read to be output to the monitor or whether the CPU/DMA has access to the video memory. They also determine whether the DRAM is having it row address set or column address set. If MUX0/MUX1 (when interpreted as a 2 bit number) is 0 or 1, then the video memory is being output to the monitor. If MUX0/MUX1 is 2 or 3, then the CPU/DMA can read/write to the video memory.
|MUX value||Video memory reserved for||Note|
|0||Monitor refresh||DRAM row set. Mux address (MA0-MA7) will be VA0-VA7. (RAS' and E will go low shortly after)|
|1||Monitor refresh||DRAM column set. Mux address (MA0-MA7) will be VA13, VA8, VA9, VA10, VA11, VA12, VA13, 1 [always high] (CAS' will go low shortly after, E will be low)|
|2||CPU/DMA I/O||DRAM row set. Mux address (MA0-MA7) will be PA0, PA1, PA2, PA3, PA4, PA5, A0, A1 (RAS' will go low, E will go high)|
|3||CPU/DMA I/O||DRAM column set. Mux address (MA0-MA7) will be A7, A2, A3, A4, A5, A6, A7, 1 [always high] (CAS' will go low, E will be high)|
NOTE : The 4416 DRAM takes in 8 address bits for its row (ie RAS' going low), but only 6 address bits for its column (ie CAS' going low). The column address bits are A1-A6 (ie A0 and A7 are ignored) which is why having VA13 and A7 be used during MUX value 1 and 3 (respectively) is kind of misleading. It seems to me that these lines could just as easily been tied high and achieved the same result, but I may learn differently later.
NOTE #2 : The DRAM row addresses must be refreshed at least every 4 ms so (ie each row from 0-255 must be asserted with RAS' going low). Perhaps this is why the DRAM row includes all 6 bits of the video address column (VA0-VA5) as well as the least significant bits of the video address row (VA6 and VA7). In other words, the DRAM 'row' is mostly the video address column (or so it appears!).
This isn't totally verified, but I think it's close at least.
On the schematic, each DRAM shares the same 14-bit address, which will change depending on whether video hardware is reading the memory in order to render it to the screen, or if CPU/blitters are doing I/O. The data lines of each DRAM are labeled Zn1-Zn4 where n is 1-6 for each DRAM.
For any given 14-bit address, the DRAMs combined simultaneously present 24-bits on the data bus, or 6 pixels (where each pixel is 4 bits). So when rendering pixels to the screen, the hardware puts a 14-bit address on the DRAM address bus, then reads in 6 pixels into three 8-bit shift registers. After the shift registers have rendered the 6 pixels, the process repeats and the next set of 6 pixels are read.
DRAM 1 will present the first 4-bit pixel, DRAM 2 will present the second pixel, etc.
The schematics have the concept of a video address which is generated exclusively by the hardware. The video address can be thought of as pointing to a buffer where the least significant values are X and the most significant are Y, or in other words, a more standard video buffer where the memory is laid out left-to-right, and top-to-bottom. Another way to think of it is that the video address follows the path of the CRT beam. The video address is calculated by the following method:
Video Address = ((vertical counters & 0xFF) << 6) | ((horizontal counters & 0x7E) >> 1)
The lower 6 bits represent the horizontal coordinate of 6 pixels (where the range is 0-63, 64 * 6 == 384 horizontal pixels) and the upper 8 bits represent the vertical coordinate of the current field.
The upper 8 bits of this address become the DRAM's "row" bits, while the lower 6 bits became the DRAM's "column" bits.
The video address covers a range of 384x256 pixels and is directly related to the video memory being output to the monitor and the position of the CRT beam. The video address cannot be modified by the ROM program. The vertical counters can be read by the ROM program.
|DRAM Column (where each column is a clump of 6 pixels)||VA5 VA4 VA3 VA2 VA1 VA0|
|DRAM Row||VA13 VA12 VA11 VA10 VA9 VA8 VA7 VA6|
Here are some examples of a theoretical video address and its row/column bits.
|V. Count||H. Count||Video Address||Row||Column (6 pixels per value)|
Mapping of CPU/DMA address to video address
The CPU/DMA address is essentially $XXYY where the first byte is the X coordinate (left to right) and the second byte is the Y coordinate (top to bottom).
This mapping is partially handled by the U74 PROM on the VGG board.
Details: The lower 8-bits of the CPU/DMA address are the vertical coordinate on the screen (there are 256 lines). So it's a little backward from the usual standard of having the X coordinate as the least significant part of the word, and the Y coordinate as the most significant.
The upper 8-bits of the CPU/DMA address refer to the horizontal coordinate, but only cover a range of 0-159 (0-0x9F). Remember, that one byte holds 2 pixels, so a range of 0-159 can describe 320 horizontal pixels.
The U74 prom maps the 0-159 horizontal "pixel pair" coordinate to a video address "column" (where each column is 6 bytes wide), and one of three pairs of DRAMS that hold the pixels within that "column".
Here is a decent way to think of it. 'n' is equal to 4 in the U74 prom, so the first 24 pixels of each row are a margin (4*6 == 24).
|Pixel Pair Coordinate (0-159)||Video Address Column (0-63)||Which DRAM group to read/write from|
The U74 prom also adds margins on the left/right side since 0-159 describes a range of 320 pixels. 320/6 is 53.333 but the DRAM memory covers a range of 0-63 so there is some wasted memory (probably intentional to take overscan into account).
The vertical coordinate of the CPU/DMA address maps directly to the vertical counter value. So it is possible for the ROM program to know exact line that the CRT beam is currently on and eliminate visual tearing (see $EAE1 in main ROM program which is the FIRQ handler; it has code to detect the beam position and avoid tearing).
The horizontal coordinate does not map directly but instead goes through U74. So one cannot just write a formula to do this conversion without taking U74's values into account.
When CPU writes to video memory, a full byte is written to two of the six DRAM chips simultaneously (where one nibble goes to one DRAM chip, the other nibble goes to the other). A byte may be written to DRAM 1/2, 3/4, or 5/6. In other words, writing a byte to video memory will change 2 pixels (like you'd expect).
The CPU's address is filtered through U74 PROM. The PROM decides which pair of DRAM chips the address maps to. It may also opt to make the write a NO-OP by not enabling any of the DRAM chips for writing, but from what I have seen, it will never do this if the address is in range of 0-0x9FFF.
More on U74
U74 starts off with 0x4, 0x44, 0x84, 0x5, 0x45, 0x85, 0x6, 0x46, 0x86, ...
Since the lower 6 bits are the video address column, one must shift right by 6 (divide by 0x40) to get the DRAM group.
|Video Address||U74 byte||Video Address Column (0-63)||DRAM group|
The monitor output is labeled 3J2 on the schematics (see VGG1/5 and VGG3/5). Sync pulses are TTL and active high. R,G,B voltage ranges are unconfirmed but monitors such as the G07 have a published range of 0-4V.
Laserdisc picture number / Biphase decoding
NTSC Line 18 is active (ie picture number is available to be read) when VGG U65 pin 13 is raised. This is a useful pin to capture on a logic analyzer. The Manchester data itself can be captured via a logic analyzer from VGG U85 pin 6 (or pin 15 but it may be less reliable).
Biphase decode circuit
When no video signal transition has been received for at least 1.5 uS, this happens:
- The video signal changes (from white to black or black to white)
- U85 pin 6 has the logical value of this change. The change represents the middle of a bit cell, as the Manchester convention defines
- U85 pin 2 will have a delayed version of this same value which will cause U49 pin 8 to pulse high when transitions occur and U37 pin 6 to pulse low briefly when transitions occur.
- U37 pin 6 will pulse low briefly. Since U50 pin 7 was high before this pulse occurred (more than 1.5 uS has elapsed since our last transition), U48 pin 8 will be low which will allow U66 pin 11 to go low, resetting the 4-bit counters stored at U67 and U68.
- Since U37 pin 6 pulsed low, U50 pin 7 will now go low. The 1.5uS window has now begun.
- U100 pin 8 will now mirror the 24 Mhz clock coming in on U100 pin 10 until 1.5 uS elapses. If another transition comes in during this window, it will be ignored because it represents the beginning of the next bit cell and the circuit only cares about the middle of bit cells, where a transition is always guaranteed to occur.
- After 0.5uS has elapsed (U67 counter value is 12), the value from U85 pin 6 will be shifted into U38.
- After 1.5uS has elapsed (U67+U68 counter values combine to make 36), U50 pin 7 will go high. Repeat from the first step.
Here is a commented version of the schematic which has a detailed explanation of how the rest of the circuit works:
Biphase decode example
Here is an example of how the biphase data is decoded for frame 11111.
|0xCB00 Address||Data||Modified by software|
The LED part is a MAN72A.
This is unconfirmed, but it is likely that these bits correspond to the LED segments:
Making a 'fast boot' ROM
Star Rider takes a long time to boot up which is frustrating for testing.
Here's some changes to make it boot much faster while still retaining some diagnostic value.
U52 ROM changes:
// NOP out loop that does slow color 'test'
- 0x1172: from 26 to 12
- 0x1173: from EE to 12
- 0x1178: from 26 to 12
- 0x1179: from E5 to 12
// skip the mandatory ROM test on boot
- 0x118f: from bd to 12
- 0x1190: from f4 to 12
- 0x1191: from 9d to 12
- 0x1192: from b6 to 12
- 0x1193: from a7 to 86
- 0x1194: from 16 to 0
// alter checksum so that U52 ROM test still passes:
- 0x1645: from BE to 37
- 0x1fe0: from FF to C9