Create Your Own Game Console

Introduction

Following on from my project in which I built my own computer, which you can find at Create your own computer, we will now build a game console, which I call GameBird. Having had made a computer using the 8-bit CPU, the 6507, I wanted to move on to using the 6502 which can access 64KB of memory as opposed to the 8KB of the 6507. Another requirement of this project is to simplify the circuit as much as possible and one way to do that is to avoid the need for memory bank switching. However, the graphics output will be complicated and will be talked about in great detail on this page.

Let's look at a brief summary of the game machine's features:

8-bit 6502 CPU @ 1MHz

VIC-II for colour video output

1 channel sound output

Support for 2 joysticks/joypads

Cartridge game support

Library ROM containing common routines

Non-volatile game save memory

Runs off single 5V regulated supply

As with the computer project, GameBird is to use components that would typically be around in the 80's and 90's, so you will not find an Arduino driving the display (the Arduino is great for testing ideas or for debugging, however). But you will find flash memory (which fits within the time era) even if the chip is quite recent as I've tried to use components I already own as much is possible.

You can email me at james.boshikoopa@gmail.com

New: Input Test (16/04/2017)

Sound Test (24/03/2017)

VIC-II Screen Test (22/03/2017)

VIC-II register test (21/02/2017)

6510T AEC Test (17/02/2017)

To return to the main electronics page please click here.

You may also be interested in Create your own computer.

VIC-II test

Although I had already in mind the kind of features I wanted the one thing I knew that would affect the overall design was the choice of how to output graphics. I looked at existing video chips that I had in my collection that were used in computers in the 80's and the only one that seemed the most viable was the VIC-II chip as used in the Commodore 64 (a version was also used in the C128). Let's look at what the VIC-II offers:

320 x 200 graphics area

40 x 25 character text support

16 colours

5 display modes

8 sprites (24 x 21 each)

Basic but if we were to do something similar using logic gates it would be very complex and while an FPGA could be used and indeed they were around in the 80's I wanted to use an existing design because of the amount of time needed to make my own (although that would be very rewarding).

The bad points about the VIC-II are the fixed borders (there are ways to make use of them or remove them) and the fact that it is quite complicated to use the chip in a system but we will go over that later. For now we are going to do a simple test that will check that the VIC-II works and produces a video output.

There are a number of different versions of the VIC-II both for PAL and NTSC but on this page we will be dealing with the most common version for PAL systems which is the 8565. The VIC-II needs a dot clock of 7.88 MHz and a colour clock of 17.73 MHz but fortunately Commodore came up with the 8701 IC which generates the needed clocks from a single 17.73 MHz crystal. Oddly, there seems to be differing pinouts for the 8701 and I believe this is because there are 2 versions of the 8701; the MOS version and the CSG version. The MOS variation is the one I have and tested but you should also be able to use the CSG alternative in the test circuit with the necessary changes.. Speaking of which, here is the circuit:

I constructed the circuit on prototype board as I was concerned that breadboard would be too 'noisy' because of the high frequencies involved.

As mentioned, the 8701 (IC1) uses a 17.73 MHz crystal (for PAL) and generates the dot and colour clocks needed by the 8565 (IC2); the base frequency can be adjusted slightly using VC1 or you can use a fixed capacitor. It is a good idea to check the dot and colour clock outputs using an oscilloscope to make sure they are the correct frequency and are roughly square (colour clock less so).

For this simple test the VIC-II (IC2) is wired without any memory but it will output video signals as well as the CPU clock (pin 17); check that the clock is about 0.98 MHz. The VIC-II generates an S-video signal at its Sync. + lum. (pin 15) and colour (pin 14) outputs but this is not suitable to drive a TV as the outputs are around 5V p-p. In the early versions of the C64 a number of transistors were used to prepare the signals for the monitor and a similar arrangement has been used in the above circuit. Using an oscilloscope check the colour output from the transistor and you should see about 500 mV p-p and a 'burst' waveform as indicated on the circuit diagram. As for the Sync. + lum. output you should get around 1V p-p and a square-like waveform.

If all is good you can connect the video outputs to the S-video input of a TV but most likely you'll get a blank screen as the VIC-II doesn't have any graphical data but the TV should at least detect the signal. I found that if IC2's CS input (pin 10) is held low then randomly when powered up i would see the window area and border in black and white or even colour. The CS pin is used to select a VIC-II internal register and what with the data and address busses left floating it could let random values in which by chance could affect the colour values.

6510T test

We have done a very simple test of the VIC-II graphics chip (see previous section) but to actually do anything useful we still need a CPU. Deciding on how to generate graphics shapes the rest of the system in a number of ways. Firstly, we have to consider what clock signals will be needed and how to produce them; for that we have the 8701 IC and in turn the VIC-II outputs the CPU clock, which is just under 1 MHz. We also have to look at what else the VIC-II needs which is to share memory with the CPU: this is an example of DMA (Direct Memory Access) because the VIC-II accesses memory without the help of the CPU. It does this by making use of an AEC (Address Enable Control) signal to tell the CPU to get off the system busses and a BA (Bus Available) signal to tell the CPU to wait when it needs extra time. Thus ideally I wanted to use a CPU that had both AEC and RDY (the equivalent of the VIC's BA) inputs but unfortunately I did not already have the standard 6510 (based on the 6502) used in the C64 that has the required signals. I did, however, have the 6510T that was used in the Commodore 1551 disk drive which has the AEC input but not the RDY input as it has an on-board 8-bit I/O port instead of the 6-bit I/O port of the 'C64' 6510; the 6510T also lacks an NMI input. To not have the AEC input would mean having to use tri-state buffers or something similar if I was to use the standard 6502 so the 6510T seemed a good choice and I have already figured I could emulate the needed RDY input using clock stretching.

Note: some datasheets refer to the 6510T as the 6510-2.

We have a test circuit (see the circuit that follows) which runs a simple program to check that the 6510T follows the code and outputs the number '0' on a 7-segment LED display by taking advantage of the CPU's on-board I/O port.

IC1, SST39SF020A, is a 256KB flash ROM that I happened to have but because of its large size I had to tie A16 and A17 to GND to make it function as a 64KB ROM. The ROM contains the code for the 6510T (IC2) and you can use any similar ROM of 64KB or higher, making the necessary connection changes as needed.

The CPU (IC2) is wired to the ROM without an address decoder which is not the best practice but will do for this simple test as the CPU handles access to its internal I/O port. Speaking of the port, a 7-segment LED display (LD1) is connected to it via limiting resistors R1 to R7. A clock pulse anywhere between 1 MHz to 2MHz needs to be provided to pin 2 (IC2) which can be the clock output of the VIC-II (see previous section); the minimum clock the datasheet claims is 50 KHz. You will also need a power-on reset pulse to be supplied to IC2 (pin 1) such as by using the power-on reset circuit from:

https://sites.google.com/site/jamesskingdom/Home/computers-exposed/create-your-own-computer#TOC-Power-on-reset

Now for the code:

Reset vector

0xFFFD 0x1F

0xFFFC 0x00

0x1F00 0xA9 LDA #0xFF

0x1F01 0xFF

0x1F02 0x85 STA 0x00 Set CPU port to output

0x1F03 0x00

0x1F04 0xA9 LDA #0x3F

0x1F05 0x3F

0x1F06 0x85 STA 0x01 Write to CPU port to display ‘0’

0x1F07 0x01

LOOP:

0x1F08 0xEA NOP

0x1F09 0xB8 CLV

0x1F0A 0x50 BVC LOOP

0x1F0B 0xFC

The code needs to be written to the ROM using the specified absolute addresses. At reset the CPU will read the reset vector address from memory locations 0xFFFC and 0xFFFD and then execute code from that address (on the 6507 the reset vector is at 0x1FFC and 0x1FFD). Our code sets the CPU's on-board I/O to all outputs by writing 0xFF to address 0x00 (remember, this happens internally). Next, the value 0x3F is written to the port output register at address 0x01 to show the number '0' on the display. Lastly, we enter a loop so that the CPU doesn't crash which is useful for monitoring CPU activity using a logic analyzer.

As the CPU's clock is barely 1MHz I breadboarded the test circuit but of course you could use prototype board if you wanted. To test, power up and you should see almost instantly the number zero appear on the display. If you see something else or the display is blank check over your connections and make sure IC1 and IC2 are getting power. You can also test the clock out of IC2 (pin 40) to make sure it is running (it should be the same frequency and phase as the clock in at pin 2).

6510T AEC Test

Normally, the CPU and VIC-II will happily take turns accessing memory with the CPU accessing the memory when the clock is high and the VIC-II when the clock is low. During the clock high period the AEC signal will be high so the CPU can address the memory and when the clock is low AEC also goes low, taking the CPU off the system busses so the VIC-II can have access. Sometimes the VIC-II needs more time so it tells the CPU to wait by use of its BA signal which will go low. Because the 6510T does not have a RDY input, which would be connected to the VIC-II's BA output, we can simulate the signal by using a technique called clock stretching which in our case lengthens the clock low period, causing the CPU to wait.

To test out the theory take a look at the following circuit which is built up on the preceding test:

The main addition is the inclusion of a 74LS00 chip (IC3) and the use of its 4 NAND gates to generate the AEC signal (emulating the VIC-II) and the clock stretching. Referring also to the waveforms shown with the circuit diagram, you'll see we still have the clock input but instead of going straight to the CPU it instead goes to pin 12 of IC3. The other input to the NAND gate, pin 13, is connected to the RDY input which can be simulated using an astable oscillator (I used a 555 outputting a 35Hz square wave, timing which was picked to cause significant delay). Because the NAND gate's output is inverted by a NAND gate wired as an inverter, effectively the clock and RDY signal are ANDed together. This gives the effect that while RDY is high the CPU receives a clock pulse the same as that which enters the NAND gate but when RDY goes low the CPU's clock drops low and stays low as long as RDY stays low.

To simulate the AEC signal from the VIC-II another 2 NAND gates from IC3 are used and they function such that the AEC signal is the same as the clock that the CPU receives. Originally, pin 4 of IC3 was connected to a switch so I could activate AEC whenever I wanted but whether it's connected to a switch or always held high, AEC will always be the same as the CPU clock so that during the low phase the VIC-II can access the memory. It should be mentioned that when the AEC signal comes from the VIC-II it lags the clock by about 10ns, perhaps to give the CPU time to finish its current operation.

In a system that uses a CPU that has the RDY input (such as the C64) the CPU always gets a continuous clock and it is use of the RDY signal that delays the CPU but it is only valid during a read operation (RDY was intended to help interfacing with slow ROM chips). In our clock stretching example we can pause the CPU regardless of its current operation although it only happens during clock low when the CPU is not normally accessing the busses.

Using the same code as in the previous test, powering up the circuit should result in the same output as in the previous section: a zero is illuminated on the display but this time the CPU is being continually interrupted but doesn't crash. Ideally a logic analyzer should be used to study the various signals-clock in, CPU clock, AEC, and RDY, and system busses-and compare with the waveforms illustrated with the circuit. Initially, I first tested without the RDY signal and found that when AEC went low the address and data bus were still showing the same values but this could have been because the values were being retained as nothing else was driving the busses (capacitance). However, with the use of the RDY signal which causes the CPU to give up the busses for a longer period I saw that the address bus went to zero, the R/W line went low and the data bus showed 0xA9 (which is the value at address 0 of the ROM rather than the CPU's internal port value). Interestingly, the default state of the R/W line on the C64 is high (i.e. read) by use of a pull-up resistor as the VIC-II only ever reads from memory.

VIC-II register test

We are now ready to combine what we have done so far and have the VIC-II do something useful by actually communicating with it via the CPU. This test so far has been the most difficult but I have at least got it to the point where it does what I had planned it to do even if not in the way I had first hoped.

I started off by writing some code to read in each of the VIC-II's 47 registers so that their start-up values could be seen using my logic analyzer. As the VIC-II doesn't have a reset input and I have no idea if the chip contains an internal reset I was curious to know the default register values which turned out to be different from what C64 BASIC (for e.g.) sets them to. This makes sense as the VIC-II starts up with the screen off (the border covers the whole screen) which would not be a good default for a computer.

Using the light pen input to the VIC-II I could see that the register responsible for sensing the signal (reg. 0x19 bit 3) was getting set when the input was low and clear when the signal was high. Note that the light pen signal will only generate an interrupt if the corresponding bit is set in register 0x1A. In addition, the light pen is only sensed once each frame, something that turned out to be useful for timing as you'll see later on.

Happy that I was able to read in the VIC-II register values I thought a good test would be to change the border colour based on whether the light pen signal was detected. This was when I saw white dots raining down on the border or the background colour when I also tried that, and these white dot I believe are what C64 users affectionately call 'sparkles'. What happened was in the early days of the C64 these sparkles were spotted and while at first the VIC chip was blamed, Commodore eventually found that a ROM was outputting incorrect data to the VIC. I did a lot of research online but I have come to the conclusion that the VIC-II is at least part at fault. I was changing the border colour as the border was been drawn and the VIC doesn't like that which causes it to output grey pixels at random points. However, you should be able to change the border colour at a fast rate to generate the famous border stripe pattern. I got around the problem by using the spare I/O pin on the CPU (bit 7) to decide if to change the border colour and then only to change colour when the light pen signal is detected. Because I held the light pen input permanently low and because the light pen flag is set once each frame I had a crude way to update the border colour once a frame.

Let's now look at the circuit:

To simplify the circuit a bit I've left off the 8701 circuit and the transistor stages for driving a TV; these can be found in the 'VIC-II' test section. The VIC-II (IC1)'s AEC signal is connected directly to the 6510T CPU (IC2) whereas the BA line from IC1 has to go through part of IC4, 2 NAND gates, to produce the RDY signal that IC2 lacks, using clock stretching, which we looked at in the previous test. It is address lines A0-A5 of IC1 which are used to access the VIC-II's internal registers so they are connected to the CPU's address bus. When AEC is low the VIC's address lines A0-A5 output address values but during this time the CPU is off the system busses anyway. D0-D7 of IC1 allow for transfer of data to/from the VIC's internal registers when the chip is selected (using CS, pin 10) and AEC is high. The VIC-II has additional data lines, D8-D11, which are used to read in graphical values but they are held low using R3-R6 otherwise we'd get a glitchy screen because we have no RAM connected.

The flash memory, (IC3) is also connected to the address and data bus but it uses a NAND gate wired as an inverter (part of IC5) so that the ROM's output is disabled if we tried to write to it while it was selected. The R/W line from IC2 controls data flow to/from IC1 and IC3 and it is held high using R2 so that when AEC is low the default state is for memory to be read (as nothing other than the CPU reads memory it's not really needed but is good practice). We have a switch, SW1, connected to bit 7 of the CPU's port to provide input and it is pulled low using R14.

To do the address decoding we use a number of NAND gates which are part of IC4 and IC5. The memory map is as follows:

0x8000 to 0xFFFF ROM

0x0100 to 0x012E VIC-II registers

0x0000 to 0x0001 6510T I/O

The CPU handles addressing its internal I/O port but for the VIC and ROM I chose addresses that would be easy to decode. When accessing ROM, address line A15 is always set just as when the CPU outputs a VIC address A8 is always set, then we just knock out VIC select if A15 is also set. This is handled by 2 NAND gates (1/4 IC4 and 2/4 IC4) but the remaining 3 NAND gates ensure that ROM CS and VIC-II CS only go low when the CPU clock is high to prevent either of those chips from being selected while the VIC is in control of the system busses (remember that the VIC always takes turns with the CPU, its CS is only for accessing its internal registers). These extra NAND gates did help to remove a banding effect (blue stripes) on the border which were in addition to the sparkles already mentioned.

This is the code which will need to be burned to the ROM using the absolute addresses specified:

Reset vector

0xFFFD 0x80

0xFFFC 0x00

0x8000 0xA9 LDA #0x7F

0x8001 0x7F

0x8002 0x85 STA 0x00 Set CPU port to all outputs except bit 7

0x8003 0x00

0x8004 0xA9 LDA #0x3F

0x8005 0x3F

0x8006 0x85 STA 0x01 Write to CPU port to display ‘0’

0x8007 0x01

0x8008 0xA9 LDA #0x10

0x8009 0x10

0x800A 0x8D STA 0x0111 Turn screen on (VIC reg. 0x11)

0x800B 0x11

0x800C 0x01

0x800D 0xA9 LDA #0x05 Green

0x800E 0x05

0x800F 0x8D STA 0x0120 Change border colour

0x8010 0x20

0x8011 0x01

WAIT_UPDATE:

0x8012 0xA9 LDA #0x08

0x8013 0x08

0x8014 0x2D AND 0x0119 Light gun signal?

0x8015 0x19

0x8016 0x01

0x8017 0xF0 BEQ WAIT_UPDATE

0x8018 0xF9

0x8019 0xA9 LDA #0x08

0x801A 0x08

0x801B 0x8D STA 0x0119 Clear light gun flag

0x801C 0x19

0x801D 0x01

0x801E 0x24 BIT 0x01 Check CPU port bit (switch)

0x801F 0x01 

0x8020 0x30 BMI BLUE_COL Make border blue

0x8021 0x07

0x8022 0xA9 LDA #0x05 Green

0x8023 0x05

0x8024 0x8D STA 0x0120 Change border colour

0x8025 0x20

0x8026 0x01

0x8027 0xD0 BNE WAIT_UPDATE

0x8028 0xE9

BLUE_COL:

0x8029 0xA9 LDA #0x06 Blue

0x802A 0x06

0x802B 0x8D STA 0x0120 Change border colour

0x802C 0x20

0x802D 0x01

0x802E 0xD0 BNE WAIT_UPDATE

0x802F 0xE2

At reset the CPU starts to execute code from address 0x8000 which sets the 6510T's port to all outputs bar bit 7 which becomes an input as we have our switch connected to it. Next, a zero is put on the LED display, which has proved to be very useful as we can see very quickly if the CPU was at least successful in starting up. At 0x8008 we turn the VIC display on by setting bit 4 of register 0x11 as the display defaults to off, which black border and background. The starting colour for the border is green, which has the value 0x05, and this is done from address 0x800D.

We enter a loop at address 0x8012 which looks for the light gun signal by checking bit 3 of register 0x19 by making use of a mask loaded into the accumulator (value 0x08). If the bit is set control moves to 0x8019 which sets the bit we just checked so that the VIC knows we have dealt with the flag. If we do not clear the flag by setting it the flag will stay set (as we have the light pen signal always held low the flag will be set again next frame).

At 0x801E we look to see if the switch is pressed and this is done by using a BIT test which will set the minus flag if bit 7 of the value being tested is set. If the switch is pressed we jump to BLUE_COL and change the border colour to blue by writing 0x06 to register 0x20, before returning to the start of the loop. If the switch was not pressed we make sure the border colour is green and then move to the beginning of the loop.

For the test, power on, and as well as seeing the zero on the LED display on your TV you should get a green border and black background (the middle area). Pressing SW1 should change the border to blue and releasing the switch will revert the border back to green. If you get nothing on your TV and the LED display is blank that is a sign that the CPU is having trouble starting up. Check over your connections and the code, and look for the 'vital signs' - power, CPU clock, etc. Only getting output on the LED display and not on the TV could mean there is a problem with the VIC chip or the connection to the TV including the transistor output stages.

While this exercise has been a success it has not been without problems and it remains that there is a fault in either reading or writing to VIC registers at certain times. Originally I used an INC instruction to continually change the border colour when the switch is pressed but it would quickly get stuck on black. Oddly, if I followed the INC instruction with an LDA to the same memory location (which I did to view the register value on my logic analyzer) the colour continually changed; the program worked correctly. It didn't seem to be a timing problem as I tried replacing the LDA with NOP instructions but that didn't help.

I've done many tests using my logic analyzer and checked the timings which all seem to at least meet the minimum requirements. Due to time restraints I've left it as it is but will continue to look into the fault as I move on to the next test.

VIC-II Screen Test

For this test we will use the VIC-II to display a number of coloured blocks in text mode which is not as simple as you may think, in fact it proved to be very difficult to get working. To use text mode it has to be enabled by writing to one of the VIC's registers and we have the option of either 24 or 25 rows, and either 38 or 40 columns. The VIC, however, behaves as if the screen is always in 40 x 25 mode but when set to less rows or columns the border is extended to hide the unused character spaces. A use of the lower row/column setting is for smooth scrolling as it gives you a window on a slightly large area which can then be scrolled using the VIC's registers.

In text mode the screen memory is 1000 bytes (40 x 25; remember that effectively the VIC is always in 40 x 25 mode) and the screen's start location can be set to anywhere within the VIC's 16KB memory area, in increments of 1024 bytes as it has to be a binary value. Every byte of the screen memory contains a character index (pointer) that selects a graphic from the character generator memory. A value of 0x00 would display character 1, 0x01 character 2, and so on. The first byte of the screen memory is the top left-hand corner and the last byte is the bottom right-hand corner.

The character generator memory is 2048 bytes as a maximum of 256 characters are supported and each character is 8 x 8 pixels (8 pixels fit in a byte and each character is made up of 8 bytes). The character generator memory's start address can be set to a position in the VIC's 16KB memory in 2048 increments. Each lot of 8 bytes defines what the character looks like, from the first line of the character at the first byte to the last row of the character at the 8th byte (so, the 9th byte is the first row of the 2nd character, etc.). The characters are defined by using a logic 0 in the bit pattern to chose the background colour and a logic 1 to select the foreground colour from the corresponding location in the colour RAM (see below).

A 1000 byte memory region is needed for the colour RAM with each byte selecting one of the 16 colours (0x00 to 0x0F) to use for the characters where there is a logic 1 in its bit pattern. The first byte selects the colour to use for the character at the top-left hand corner of the screen and the last byte determines the colour of the character at the bottom right-hand corner of the screen. As far as the VIC is concerned, the colour RAM always starts at memory location 0 even though the screen/character generator memory can be at that location too. However, the VIC retrieves the colours from its data lines D8-D11 which doesn't clash with the other memory that uses D0-D7.

Here is a list of the colour values:

0x00 Black

0x01 White

0x02 Red

0x03 Cyan

0x04 Violet

0x05 Green

0x06 Blue

0x07 Yellow

0x08 Orange

0x09 Brown

0x0A Light red

0x0B Grey 1

0x0C Grey 2

0x0D Light green

0x0E Light blue

0x0F Grey 3

As you shall see later on the colours do not appear quite what you may expect them to look like. Note also that the VIC does support multi-colour mode but we will be using single colour mode.

Time to check out the circuit:

Whereas normally you would have the character generator in ROM I opted to put it along with the screen memory into a single RAM chip, which is a LH5164A-10L (IC8) 8K x 8 SRAM, wired as 4K. Through the difficulty of getting text mode to work I've learnt how important timing is for the RAM chips. It seems that the RAM chips need to have an access time of between 100ns and 150ns (which is the same for the C64) which is why I've used the LH5164A-10L which has 100ns access time. I've also had to 'qualify' the R/W line for IC8 and the colour RAM (IC9) using 2 NAND gates (1/2 IC11 and 2/2 IC11) so that RAM is only written to when the CPU is active (CPU clk is high). Ideally as soon as the VIC takes control the CPU should have no affect on the rest of the circuit but in reality there is a changeover time and it's possible the RAM could get corrupted.

The colour RAM is a SRM2016C-15 (IC9) 2K x 8 SRAM with access time of 150ns. Ideally you would use a 1K x 4 SRAM chip but I did not have one fast enough so I settled with a SRM2016C-15 wired as 1K and the unused data lines (D4-D7) pulled low using R3-R6 so the lines aren't floating when the chip is in write mode. Both RAM chips are accessed the normal way by the CPU (via the address and data bus) but because of the VIC (IC1) chip's multiplexed address bus its address bus has to be demultiplexed using a 74LS573 (IC6) octal latch. What happens is that address lines A0-A7 are latched into the 74LS573 using the RAS signal from the VIC and then the remainder of the address (A8-A11) is placed on the address bus by the VIC chip.

I have also had to use a 74LS541 (IC7) bus driver that is only enabled during CPU time (by using a NAND gate, 1/4 IC4, wired as an inverter) to control CPU address access to the VIC. This is necessary to avoid a short of the octal latch which would otherwise has its inputs D0-D7 connected to the CPU address bus as well as the VIC address lines and the octal latch's outputs Q0-Q7 which is also connected to the CPU address lines as well as the RAM (IC8). You will see that Commodore had to use a similar technique with the C64.

As already mentioned the VIC chip accesses the colour information through its extra data lines D8-D11 so they are connected to the colour RAM's data lines D0-D3. However, for the CPU to be able to read or write to the colour RAM we have to map the data lines to the CPU's data bus. In the C64 they used a 4066 quad bilateral switch which has active high enable inputs (E0-E3) and automatically allows data to flow in either direction without the need for a direction signal. After trying to use a 74LS245 to do the same thing which worked but resulted in 4 wasted buffers I bought some 4066 IC's and replaced the 74LS245 with a 4066, IC10 in the circuit. During CPU time the 4066 is enabled which is simply done by connecting E0-E3 to the AEC control line from the VIC.

Before we talk about the address decoder let's go over the CPU memory map:

0x8000 to 0xFFFF ROM

0x1C00 to 0x1FFF RAM (general purpose)

0x1800 to 0x1BFF Screen memory RAM

0x1000 to 0x17FF Character generator RAM

0x0800 to 0x0BFF Colour RAM

0x0100 to 0x012E VIC-II registers

0x0000 to 0x0001 6510T internal I/O

The character generator, screen memory and general purpose RAM are all part of the same RAM chip (IC8).

The address decoder is handled by a single PLD (Programmable Logic Device) chip, IC5, a GAL16V8A-15 (15ns propagation delay), which is similar to the GAL16V8B but does not have internal pull-up resistors. I had previously used a GAL16V8B in my computer project but found that I had no more, however, I found I had a GAL16V8A. I designed the configuration using ispLEVER and programmed the chip using the MiniPro programmer. I'll present the address decoder logic circuit but will not share the project file unless someone really wants it. You can put the design in your own PLD or make the address decoder out of discrete logic if you like.

The way the address decoder works is by looking at certain address lines and enabling a chip associated with the address line but priorities are used so that only one chip is enabled at a time during CPU time. When the VIC is active both the colour RAM and main RAM is enabled and that is why both the screen memory and character generator are both in a single RAM chip so that during VIC time we don't have to decode the VIC address.

If you look at the CPU memory map above you will see that when A8 is high the VIC is being accessed such is the case for address 0x0100, for example. If A15 is high (e.g. address 0x8000) then the ROM is being accessed but if the ROM address 0x8100 was presented both the ROM and VIC would be selected. This is why there is extra logic to disable a chip that has a lower priority, in this case, the VIC chip. The order of priorities from lowest to highest are:

VIC

Colour RAM

Main RAM

ROM

The CPU clock is taken into consideration so that the chip select signals only go low when the CPU is active (the CPU clock is high) but the colour RAM and main RAM are enabled always during VIC time (CPU clock is low), which is why there are 2 AND gates.

Next up is the code which needs to be written to the ROM:

;Reset vector

0xFFFD 0x80

0xFFFC 0x00

;Set up CPU port to all output except P7

0x8000 0xA9 lda #0x7f

0x8001 0x7F

0x8002 0x85 sta 0x00

0x8003 0x00

;Display '0' on LED display

0x8004 0xA9 lda #0x3f

0x8005 0x3F

0x8006 0X85 sta 0x01

0x8007 0X01

;Turn screen on, set to 25 rows and set default vert scroll

0x8008 0xA9 lda #0x1B

0x8009 0x1B

0x800A 0x8D sta 0x0111

0x800B 0x11

0x800C 0x01

;Set to 40 columns

0x800D 0xA9 lda #0x08

0x800E 0x08

0x800F 0x8D sta 0x0116

0x8010 0X16

0x8011 0X01

;Set VIC memory config

0x8012 0xA9 lda #0x20

0x8013 0x20

0x8014 0x8D sta 0x0118

0x8015 0x18

0x8016 0x01

;Clear screen memory to zeroes

0x8017 0xA2 ldx #0x00

0x8018 0x00

0x8019 0xA9 lda #0x00

0x801A 0X00

screen_fill:

0x801B 0x9D sta 0x1800,x

0x801C 0x00

0x801D 0x18

0x801E 0x9D sta 0x1900,x

0x801F 0x00

0x8020 0x19

0x8021 0x9D sta 0x1a00,x

0x8022 0x00

0x8023 0x1A

0x8024 0x9D sta 0x1b00,x

0x8025 0x00

0x8026 0X1B

0x8027 0xE8 inx

0x8028 0xD0 bne screen_fill

0x8029 0xF1

;Define first char as solid space (value 0)

0x802A 0xA2 ldx #0x00

0x802B 0x00

0x802C 0xA9 lda #0x00

0x802D 0X00

char_fill:

0x802E 0x9D sta 0x1000,x

0x802F 0x00

0x8030 0x10

0x8031 0xE8 inx

0x8032 0xE0 cpx #0x08

0x8033 0x08

0x8034 0xD0 bne char_fill

0x8035 0xF8

;Define second char as solid block (value 1)

0x8036 0xA2 ldx #0x00

0x8037 0x00

0x8038 0xA9 lda #0xff

0x8039 0xFF

char_fill_2:

0x803A 0x9D sta 0x1008,x

0x803B 0x08

0x803C 0X10

0x803D 0xE8 inx

0x803E 0xE0 cpx #0x08

0x803F 0x08

0x8040 0xD0 bne char_fill_2

0x8041 0xF8

;Set first line to different colours

0x8042 0xA2 ldx #0x00

0x8043 0x00

colour_fill:

0x8044 0x8A txa

0x8045 0x9D sta 0x0800,x

0x8046 0x00

0x8047 0x08

0x8048 0xE8 inx

0x8049 0xE0 cpx #0x28

0x804A 0x28

0x804B 0xD0 bne colour_fill

0x804C 0xF7

;Store solid block on first line

0x804D 0xA2 ldx #0x00

0x804E 0x00

0x804F 0xA9 lda #0x01

0x8050 0x01

line_fill:

0x8051 0x9D sta 0x1800,x

0x8052 0x00

0x8053 0x18

0x8054 0XE8 inx

0x8055 0xE0 cpx #0x28

0x8056 0x28

0x8057 0xD0 bne line_fill

0x8058 0xF8

;Store solid block in bottom 2 corners of screen

0x8059 0xA9 lda #0x01

0x805A 0x01

0x805B 0x8D sta 0x1bc0

0x805C 0xC0

0x805D 0x1B

0x805E 0x8D sta 0x1be7

0x805F 0xE7

0x8060 0x1B

;Set bottom left block to yellow, bottom right to light blue

0x8061 0xA9 lda #0x07

0x8062 0x07 

0x8063 0x8D sta 0x0bc0

0x8064 0xC0

0x8065 0x0B

0x8066 0xA9 lda #0x0e

0x8067 0x0E

0x8068 0x8D sta 0x0be7

0x8069 0xE7

0x806A 0x0B

;Set border colour to green

0x806B 0xA9 lda #0x05

0x806C 0x05

0x806D 0x8D sta 0x0120

0x806E 0x20

0x806F 0x01

;Nothing else to do

_loop:

0x8070 0xEA nop

0x8071 0xD0 bne _loop

0x8072 0xFD

At the start of the program we set up the CPU I/O port and output a '0' on the LED display as before so that we know the CPU at least is doing something; note that while the switch is still in place the code doesn't use it. Next we turn the screen on, set it to text mode with 25 rows, and no vertical scroll - which oddly is 0x03 - using VIC reg 0x11. We set the VIC to 40 columns (reg 0x16) and via reg 0x18 we set the character generator to start at address 0 and the screen memory to start at address 0x0800. Although the VIC has its own memory map because we are using the same RAM chip for both the character generator and screen memory there relative position must be the same for both the VIC and CPU.

Next the screen is cleared by writing zeroes to the memory starting at address 0x1800 but because a variable in memory can only go from 0 to 255 we use 4 separate stores starting at 0x1800, 0x1900, 0x1A00, and 0x1B00. This way we can clear 1024 bytes (24 bytes more than the actual screen memory) with one loop. Using a couple more loops we define the first character as a space (all zeroes) and the second character as a solid block (all ones) by writing to addresses starting at 0x1000.

So that we can actually see some characters and we can check all the colours are working we use another loop which writes to the first 40 colour memory locations which an increasing value starting at 0. Although the colour RAM is only 4 bit because of the nature of 8 bit values effectively we will get a repeating value from 0 to 15. What we end up with is the 16 colours repeated twice and then the first 8 colours to finish off the line. That only sets the colours, however, so to see something actually displayed we write 1 to the first 40 screen memory locations to display a solid block at the top of the screen using the colours mentioned.

To further test the screen and colour memory we place a yellow block in the bottom left corner and a light blue block at the bottom right. As before, we must set the colour for the desired location in the colour memory and the character value in the screen memory but it doesn't matter whether you write to the colour or screen memory first. I found that the bottom right block was not the exact same blue as in the top area of the screen but it could be that colours 'bleed' into each other thus changing the colour slightly.

To finish off, the border colour is set to green so that we have a frame around the screen and can clearly see the border. We then enter a loop of doing nothing just so the CPU doesn't crash reading random instructions.

For testing, when you power up you should see a '0' on the LED display and on your TV there should be a green border and black screen along with the repeating coloured blocks at the top of the screen and the yellow block at the bottom left and the light blue block at the bottom right. The trouble I was having earlier on was that there would be a few random undefined characters on the screen, which had started happening after I had added the colour RAM. It turned out to be due to 2 faults, one of which was the colour RAM was too slow (originally I tried using a 300ns chip) which was possibly holding on to the data bus too long. The second problem was the power supply, which was one of those cheap breadboard power supplies, which at almost 0.5A the voltage had dropped to 4.1V. I tried a SMPS and that fixed the issue as the voltage now dropped to just 4.8V (I did try other RAM again but too slow RAM still caused the random character bug).

Sound Test

Getting the graphics to work using the VIC-II chip was complex but for sound I used a much simpler approach that gives us a total of 8 sounds. Before going into detail take a look at the circuit:

The sound generation is based around a 74LS624 voltage controlled oscillator (IC12) which generates a square wave output at Y (Z is the complement) whose frequency is set by the voltage at the FC (Frequency Control) input and the capacitor connected across the CX1 and CX2 inputs. The actual frequency can further be adjusted by changing the voltage at the RNG (Range) input, which I have done using a preset variable resistor, PVR1.

The 6510T doesn't have any form of analogue output but we can convert its digital port lines into a voltage using a simple resistor ladder made up of R8-R13 whose values have been chosen such that an increasing value placed on port lines P0-P2 results in an increasing frequency. That is, a value of 0 results in the lowest frequency sound and a value of 7 selects the highest frequency sound.

The CPU can also turn the sound on or off via P3 using the 74LS624 IC's EN input by either setting the line low to turn the sound on or high to turn the sound off. I found that oddly with the CPU controlling the 74LS624's EN input the audio output was worse than if I had taken EN straight to ground. I figured that noise was being picked up on the long wires I was using for prototyping and that could be causing the oscillator to become unstable. The fix I found was to use a ferrite bead (FB1) and capacitor (C14) combination to fix the sound issue.

A very simple buffer transistor arrangement consisting of TR1, R14, R15, and C15 reduces the output voltage of the 74LS624 to a much lower level (about 1.2V p-p) that is suitable for connecting to a TV or amplifier. Also to note that the 7-segment LED display has been replaced with a single LED (LED1) to act as a simple status LED so it's clear that the CPU has started up. Everything else is the same as in the VIC-II Screen Test section circuit.

Time for the code:

Reset vector

0xFFFD 0x80

0xFFFC 0x00

;Set up CPU port to all outputs

0x8000 0xA9 lda #0xff

0x8001 0xff

0x8002 0x85 sta 0x00

0x8003 0x00

;Turn on status LED and turn sound off

0x8004 0xA9 lda #0x18

0x8005 0x18

0x8006 0X85 sta 0x01

0x8007 0X01

;Turn screen on, set to 25 rows and set default vert scroll

0x8008 0xA9 lda #0x1B

0x8009 0x1B

0x800A 0x8D sta 0x0111

0x800B 0x11

0x800C 0x01

;Set to 40 columns

0x800D 0xA9 lda #0x08

0x800E 0x08

0x800F 0x8D sta 0x0116

0x8010 0X16

0x8011 0X01

;Set VIC memory config

0x8012 0xA9 lda #0x20

0x8013 0x20

0x8014 0x8D sta 0x0118

0x8015 0x18

0x8016 0x01

;Clear screen memory to zeroes

0x8017 0xA2 ldx #0x00

0x8018 0x00

0x8019 0xA9 lda #0x00

0x801A 0X00

screen_fill:

0x801B 0x9D sta 0x1800,x

0x801C 0x00

0x801D 0x18

0x801E 0x9D sta 0x1900,x

0x801F 0x00

0x8020 0x19

0x8021 0x9D sta 0x1a00,x

0x8022 0x00

0x8023 0x1A

0x8024 0x9D sta 0x1b00,x

0x8025 0x00

0x8026 0X1B

0x8027 0xE8 inx

0x8028 0xD0 bne screen_fill

0x8029 0xF1

;Define first char as solid space (value 0)

0x802A 0xA2 ldx #0x00

0x802B 0x00

0x802C 0xA9 lda #0x00

0x802D 0X00

char_fill:

0x802E 0x9D sta 0x1000,x

0x802F 0x00

0x8030 0x10

0x8031 0xE8 inx

0x8032 0xE0 cpx #0x08

0x8033 0x08

0x8034 0xD0 bne char_fill

0x8035 0xF8

;Define second char as solid block (value 1)

0x8036 0xA2 ldx #0x00

0x8037 0x00

0x8038 0xA9 lda #0xff

0x8039 0xFF

char_fill_2:

0x803A 0x9D sta 0x1008,x

0x803B 0x08

0x803C 0X10

0x803D 0xE8 inx

0x803E 0xE0 cpx #0x08

0x803F 0x08

0x8040 0xD0 bne char_fill_2

0x8041 0xF8

;Set first line to different colours

0x8042 0xA2 ldx #0x00

0x8043 0x00

colour_fill:

0x8044 0x8A txa

0x8045 0x9D sta 0x0800,x

0x8046 0x00

0x8047 0x08

0x8048 0xE8 inx

0x8049 0xE0 cpx #0x28

0x804A 0x28

0x804B 0xD0 bne colour_fill

0x804C 0xF7

;Store solid block on first line

0x804D 0xA2 ldx #0x00

0x804E 0x00

0x804F 0xA9 lda #0x01

0x8050 0x01

line_fill:

0x8051 0x9D sta 0x1800,x

0x8052 0x00

0x8053 0x18

0x8054 0XE8 inx

0x8055 0xE0 cpx #0x28

0x8056 0x28

0x8057 0xD0 bne line_fill

0x8058 0xF8

;Store solid block in bottom 2 corners of screen

0x8059 0xA9 lda #0x01

0x805A 0x01

0x805B 0x8D sta 0x1bc0

0x805C 0xC0

0x805D 0x1B

0x805E 0x8D sta 0x1be7

0x805F 0xE7

0x8060 0x1B

;Set bottom left block to yellow, bottom right to light blue

0x8061 0xA9 lda #0x07

0x8062 0x07 

0x8063 0x8D sta 0x0bc0

0x8064 0xC0

0x8065 0x0B

0x8066 0xA9 lda #0x0e

0x8067 0x0E

0x8068 0x8D sta 0x0be7

0x8069 0xE7

0x806A 0x0B

;Set border colour to green

0x806B 0xA9 lda #0x05

0x806C 0x05

0x806D 0x8D sta 0x0120

0x806E 0x20

0x806F 0x01

;Clear variables

0x8070 0xA9 lda #0x00

0x8071 0x00

;Current sound number

0x8072 0x8D sta 0x1C00

0x8073 0x00

0x8074 0x1C

;Timing counter

0x8075 0x8D sta 0x1C01

0x8076 0x01

0x8077 0x1C

;Turn on sound (keep status LED on)

0x8078 0xA9 lda #0x10

0x8079 0x10

0x807A 0x85 sta 0x01

0x807B 0x01

next_sound:

;Get current sound number

0x807C 0xAD lda 0x1C00

0x807D 0x00

0x807E 0x1C

;Keep sound and status LED on

0x807F 0x09 ora #0x10

0x8080 0x10

;Set sound to play

0x8081 0x85 sta 0x01

0x8082 0x01

wait_update:

0x8083 0xA9 lda #0x08

0x8084 0x08

0x8085 0x2D and 0x0119

0x8086 0x19

0x8087 0x01

0x8088 0xF0 beq wait_update

0x8089 0xF9

0x808A 0xA9 lda #0x08

0x808B 0x08

0x808C 0x8D sta 0x0119

0x808D 0x19

0x808E 0x01

;Waited for long enough?

0x808F 0xEE inc 0x1C01

0x8090 0x01

0x8091 0x1C

0x8092 0xAD lda 0x1C01

0x8093 0x01

0x8094 0x1C

0x8095 0xC9 cmp #0x64

0x8096 0x64

0x8097 0xD0 bne wait_update

0x8098 0xEA

;Reset timing counter

0x8099 0xA9 lda #0x00

0x809A 0x00

0x809B 0x8D sta 0x1C01

0x809C 0x01

0x809D 0x1C

0x809E 0xEE inc 0x1C00

0x809F 0x00

0x80A0 0x1C

0x80A1 0xAD lda 0x1C00

0x80A2 0x00

0x80A3 0x1C

0x80A4 0xC9 cmp #0x08

0x80A5 0x08

0x80A6 0xD0 bne next_sound

0x80A7 0xD4

;Go back to first sound

0x80A8 0xA9 lda #0x00

0x80A9 0x00

0x80AA 0x8D sta 0x1C00

0x80AB 0x00

0x80AC 0x1C

0x80AD 0xF0 beq next_sound

0x80AE 0xCD

Based on the code from before, the first change we have is at address 0x8004 onward where we turn the status LED on and the sound off by writing to the CPU's on-board I/O port. The code that follows you should recognise until we get to 0x8070 where we clear 2 variables to 0. The variable at address 0x1C00 (8-bit) remembers the current sound to play from 0 for the first sound to 7 for the last sound. At address 0x1C01 (8-bit) we have a timing counter that goes from 0 to 99.

At 0x8078 we turn the sound on and then enter a loop called next_sound which plays each sound in turn. We start by getting the current sound number from 0x1C00 and OR it with 0x10 so the status LED stays on, before writing the resulting value to the CPU I/O port. For timing I've once again taken advantage of keeping the VIC-II (IC1) chip's light pen input (LP) low so that the VIC-II generates an interrupt every frame (only the interrupt flag gets set, the CPU doesn't actually get interrupted). At wait_update we check for the flag being set (VIC register 0x19) and when it does get set we manually clear the flag. Now we look to see if the counter at address 0x1C01 has reached its limit, as at 50 FPS (for PAL) 100 counts should be about 2 seconds. If the counter hasn't reached its limit we wait for the light pen interrupt again otherwise we reset the timing counter and increase the sound number variable which is set back to 0 if we have already played all sounds.

After the code has been written to the ROM, turn the power on and you should see the familiar coloured blocks and green border from the previous test. You should also hear 8 sounds being played of progressively higher frequency before repeating. You can adjust the base sound frequency using PVR1 but you will more or less get a few rough low frequency sounds that could be used for sound effects, followed by much higher frequency sounds that could be used in a very simple tune.

If you are getting no sound from the circuit check the audio output with an oscilloscope to see if there is a signal. If using a TV you will need to connect the audio signal either to the red (right) or white (left) phono sound input connector. I should also mention that most TV's will not output any audio if they do not sense a video input so make sure you are getting a visual output.

Input Test

We are nearing the end of this project and soon we'll be able to run our own games on our very own game console. The aim of this test is to be able to respond to input from the player via a controller or joystick so that 2 players can control their own coloured block on the screen. It will be very simple with no boundary checking, just the minimum to check the input works.

Rather than make our own controllers we will use existing ones although feel free to make your own if you please. Our console will support classic Atari, Sega Master System and Sega Mega Drive (Genesis) controllers, as they all use a very similar pinout and D-sub connector, and should be easy to obtain. GameBird can handle 4 directions (up, down, left, right) and 2 trigger buttons which should be enough for most games. Note, however, that Atari joysticks tend to only have 1 trigger button so you would be better off with using Master System/Mega Drive controllers for this test.

We will now look at the address decoder which has now become much more complex and adds support for the VIA chip for I/O:

Designing an address decoder using logic in this way may not be the best approach and indeed it took me many tries to get it right. The address decoder is programmed into the GAL16V8A PLD, which you will see in the circuit diagram later. It uses logic to select the correct chip based on the input address placed by the CPU but to simplify things the colour RAM and main RAM is always selected when the VIC is active. It is in principle similar to the address decoder presented in the VIC-II Screen Test section.

Make sure the PLD matches the pinout in the circuit diagram (see below) or change as necessary.

As well as adding a chip select line for the VIA, 32KB RAM is supported (RAM_CS) which can be used for general purpose data as well as for the screen and colour information. I had intended to use the 6522 VIA but as will be talked about shortly I had to ditch it and use the 6525 instead, as well as the 82C54 for timing.

Take a look at the memory map:

0x8000 to 0xFFFF ROM

0x2000 to 0x7FFF RAM

0x1800 to 0x1FFF Character generator (RAM)

0x1400 to 0x17FF Screen (RAM)

0x0C00 to 0x13FF RAM

0x0800 to 0x0BFF Colour RAM

0x0200 to 0x07FF VIC-II registers

0x0100 to 0x01FF Stack (RAM)

0x0020 to 0x00FF RAM

0x0018 to 0x001F Timer (82C54)

0x0010 to 0x0017 I/O (6525)

0x0002 to 0x000F RAM

0x0000 to 0x0001 I/O (6510)

The main RAM sits at the lower 32KB of memory with the colour RAM, VIC, timer and I/O switching in as needed, which means main RAM at those places isn't available. The ROM takes up the upper 32KB of memory and while we only have 1 ROM chip at the moment, later on we will split the upper 32KB in half for the library ROM (which contains common routines and startup code) and the game ROM. You will see from the code listing when we get to it that I have placed the library ROM code in the top 16KB of the upper 32KB and the game code in the lower 16KB. When we get to adding 2 ROM chip select signals the ROM's will be selected as needed.

The 82C54 timer only has 4 registers so it gets repeated twice from 0x0018 to 0x001B and 0x001C to 0x001F; the code will only use 0x0018 to 0x001B. This is similar for the VIC-II which has 47 registers but is mapped into memory space 0x0200 to 0x07FF to help simplify the address decoder, which means it gets repeated many times.

Now we get on to the circuit diagram:

The main RAM chip is the W24257-15 (IC8), a 32KBx8 SRAM IC which replaces the LH5164A RAM chip used in the previous test. The W24257-15 caused trouble as it was too fast having access time of only 15ns. The workaround was to use an active delay line, EPA1140-250 (IC16), which delays the R/W line by about 150ns for both the main RAM (W24257-15) and colour RAM (SRM2016C-15). With access time of 150ns the SRM2016C-15 shouldn't need the R/W line delay but I haven't tried it without. The 2 NAND gates, 1/4 and 2/4 74LS00 (IC11) make sure that the R/W signal only goes low during CPU time.

Because of now using 32K RAM I had to change the VIC memory configuration but I was getting a messed up display. I realised that the problem was because of the way the VIC address bus is demultiplexed the VIC can only access 4K. I tested my theory by using a couple of NAND gates (2/4 and 3/4, 74LS00, IC15) so that during CPU time A12 was passed to the RAM but during VIC time A12 would always be high. Note that this only works because of where the RAM is placed in memory, ideally you would design the address decoder to decode the address during VIC time.

When working on the code I adjusted it to use subroutines to make the code easier to understand and to ease changes but when powered up the display was glitchy, constantly changing. I had a feeling it was because I had moved the code to higher up in memory, to 0xC000 onward as I plan to have the game cart start at 0x8000. The significance of 0xC000 is that A15 and A14 are both set. If I took the main RAM's A14 low the display would not be glitchy. I figured that somehow the A14 line was remaining high from a ROM access during CPU time until VIC time when the RAM is accessed. I solved the problem by using 1K pull-down resistors on A12-A15 (R16-R19) to ensures those address lines go low quickly during VIC time because of the VIC not driving them when it is active. Interestingly, I found that on the C64, address lines A12-A15 are pulled high, which maps the ROM chips high in memory.

My intention was to use a 6522 VIA to read both joypads, as I was already familiar with using the chip, and I thought it would be a simple matter of connecting the 6522 to the system but I could not get it working. For debugging I added code to change the colour of a block on the screen based on the value written from port A. Even if it was the case that the VIA was ignoring the writes to it to set it up the VIA defaults to all inputs on port A. I tried lots of testing and used 3 different 6522 IC's but it still appeared to the system as dead. I tested the VIA 'by hand' and found it to be working and even tried an additional power supply with common gnd connection but that didn't help either. Eventually, after looking with my logic analyzer and checking with the 6522 datasheet I saw that the VIA CS should go low 180ns before the CPU clk goes high, which wasn't happening. I updated the address decoder to not gate the VIA CS with CPU clock but the CS signal was still going low too late. Also, I tried feeding a slower clock to the 6522 by dividing the CPU clock by 2 using a 7474 but that was no help either. The datasheet says the minimum clock is 100KHz. What did work was using an active delay line to stall the clock to the 6522; this had the effect that reads from the 6522 would work but writes wouldn't. Looking with my LA it seems the 6510 doesn't output the address of the 6522 as soon as it should. The problem is possibly because when the CPU clock is low the VIC is active and outputs an address and the CPU has little time to output an address when control is given back. The C64 avoids the problem because it uses 6526 IC's which expect CS to go low after CPU clock goes high.

I looked online and tried a suggested circuit using a 7474 flip-flop with the dot clock but although reads from the 6522 worked, writes didn't. I did a variation by using a 7474 F/F to divide the colour clock down to a similar frequency to the dot clock before using the second F/F to do the same as before but got the same result. I even tried running the 6522 at 2MHz by dividing the dot clock twice (6522A is 2MHz) and that got reads and writes to work but not every time, possibly because the chip select was lasting too long.

I ended up using the 6525 (IC13) tri-port chip which doesn't contain any timers but has no problem with CS going low after the CPU clock goes high. The 6525 does not appear to have built-in pull-up resistors and floating inputs go low (which the datasheet agrees with). A nice feature of the 6525 is that its third port (port C) can be used to generate an interrupt from 1 of 5 interrupt sources. In this test, however, port C is configured as outputs just so they aren't in a floating state.

As already mentioned, Atari-like joysticks and joypads are supported which includes Master System and Mega Drive controllers. The MD controller is slightly more complicated as it internally uses a multiplexer to support 3 trigger buttons. By supplying 5V to the controller and setting select to +5V we can read the 4 directions as well as button B and C. Regardless of what type of controller is used they all work by pulling a signal low when a button is pressed so we use resistors to pull the inputs high for when a button isn't pressed. These resistors are RN1 and RN2, both 10K, two resistor networks that contain a number of resistors with a single common connection but you can use individual resistors if you like. Because the MD controller has internal 10K pull-up resistors on the direction and trigger buttons we will get 5K effective pull-up resistance on each button input.

Using the 6522 would have had the advantage that it has built-in timers so now that I've had to use the 6525 I looked for a chip that could provide timing. I came across the very versatile timer IC, the CS82C54, a version of Intel's 82C54, which contains 3 programmable down counters, each of which has its own independent clock inputs (8MHz max), gate inputs (enable/disable counting), and outputs. Each of the timers can be set to 1 of 6 modes, and can generate an interrupt at a set interval. By using the CS82C54 for timing we no longer need to use the light pen input trick for timing so LP (IC1) is held high using R1 in case we need it on the future.

For now, save changing the address decoder, both the 6525 and the 82C54 use the same chip select line (VIA_CS) originally for the 6522 but with some extra logic to select between the I/O and timer based on the selected address. The NAND gates that decode VIA_CS for the 6525 are 1/4, 3/4, 4/4, 74LS00, IC11, IC15 and the NAND gates for the timer to do the decoding are 1/4, 4/4, 74LS00, IC15, IC17. You can see it is whether address line A3 is set or not that chooses if the I/O or timer is selected.

The 82C54 was designed to be connected to an 8088/8086 CPU and that is why it has separate RD and WR inputs. I found that for writing to the chip to work I had to use the delayed and qualified R/W signal, R/W(2) and for reading I had to invert R/W (2/4, 74LS00, IC17) from the CPU. I had, however, first tested the 82C54 using an Arduino uno to make sure I could get the various modes and counters working. See Arduino to 82C54 Test.

When it came to getting the timer to work in GameBird it was not straightforward. Not only did I have an issue with the system completely going down, which seemed to be caused by faulty RAM, I had the problem of the blocks moving fast after the delay rather than there being a delay between each move. I fed counter 0 with the clock from the VIC (the clock output of the 6510 has pauses due to the BA signal) and set its count so that counter 0 clocks counter 1 every 20ms. Then, to wait for about 0.5s we need to wait for 25 counts so we set the timer to start at 50 and then keep reading the counter until it reaches 25. You could start the count at 25 and wait until 0 but I kept the value of 50 while trying out different timings. However, the counter doesn't load the new count until after 1 clock pulse which for counter 1 is 20ms which is a long time for the CPU which was seeing that the required count had been reached from last time and thus moved the block at high speed. Fortunately, the timer has the means to read a flag which says whether the new count has been loaded so after we set the new count we wait for the count to be valid and then keep checking the counter to see if it has reached the required value. This is not the best way to handle the timing as the CPU spends a lot of time checking a slow timer but for this test we have nothing else to do. Using an interrupt would be one way to let the CPU get on with other things while waiting on the timer.

It is my intention to later use counter 2 of the 82C54  to generate the sound, replacing the VCO, so we can get more sounds and reduce the chip count by 1 as I've had to add the 82C54 because of not being able to use the 6522, which contains timers.

I have put the code files into a zipped file, GameBird_input_test.zip, which you will find for download at the bottom of this page. In contains a text file, input_test.txt, which is the program listing and a hex file, input_test.bin, for programming the ROM (the file also contains random data that was previously on the chip from its original use). Bear in mind the hex file is for a 256KB ROM so if you are using a smaller ROM you will need to trim the file. A ROM smaller than 64KB will have to have the addresses in the code changed as the ROM is connected to all the CPU address lines.

When the system starts up it will boot from address 0xC000 which sets up the stack before calling two initialisation routines, INIT_IO and INIT_GFX, but we also call TEST_GFX as it defines the characters we need, so for this test we will always get the coloured blocks that we are used to. At INIT_IO we set the CPU on-board I/O to all outputs apart from P6 and then we turn the status LED (LED1) on and the sound off. Next up we put port A and B of the 6525 to inputs a for the first 6 bits each (0-5) and bits 6-7 become outputs just so they aren't floating. Port C is special as it can either function like port A/B or it can be used for interrupt sources. I enable port C as a normal port by setting the 6525 to mode 0 and then I set port C to all outputs. Routine INIT_IO calls INIT_TIMER which sets up counter 0 to binary as a rate generator (outputs a pulse when the counter reaches 1) and puts in the initial count so we get a pulse every 20ms. As for counter 1 it is also set to binary and is set to software triggered strobe mode so that we can get the counter going again by writing a new count value to it. Although it's not necessary I write an initial count to counter 1 as originally I was going to have it generate a pulse continually like with counter 0.

At 0xC00D the code checks to see if P6 of the CPU I/O is low and if so it jumps to the game code starting at 0x8000. If P6 is high code is instead transferred to the sound test (plays each sound in turn) by jumping to SOUND_TEST. The sounds play faster than before as the sound test uses the same timer routine as the main game program. On the circuit you will see that P6 of the CPU (IC2) is pulled high by pull-up resistor R20 but turning on switch SW1 will ground the input. This way, when we add the game cartridge it can tell the system it is present and if there is no cartridge connected GameBird will enter the A/V test mode.

Starting at address 0x8000 the game program checks controllers 1 and 2, letting the players move their coloured block across the screen. Trigger buttons 1 and 2 are also checked and do the same for both players; holding trig 1 will change the border colour and holding trig 2 will cycle through the background colours. Remember that colour values are only 4-bit so we can happily increase (or decrease) a 4-bit value as if it were 8-bit and still go through all available colours.

I originally wrote the code for just 1 player to get me started and then converted to 2 players by making use of the 1P variables that the functions already were using and then copying over 1P values to update the first player and then 2P values to update the second player. After the values have been updated we copy them back to the 1P or 2P memory area depending on which player is being updated.

There are 2 ways we can store the player's block position on the screen; as X/Y positions or actual addresses - after much thought I went with addresses as it makes updating the positions fairly simple. These addresses are stored at:

P1: Screen: 0x2A-2B. Colour: 0x2E-2F

P2: Screen: 0x2C-2D. Colour: 0x30-31

From address 0x8000 in the code we call COPY_MEM_SMALL to copy from P1_P2_SCRN_COL_STRT_ADDR P1 and P2 start addresses for screen and colour to 0x2A - 0x31. At 0x8015 we store the 1P controller address P1_CON_ADDR, that is port A in the 6525, to addresses 0x32-33 so that we can index both 1P and 2P controller data easily (port A and B, respectively). At 0x801F we copy the 1P screen and colour addresses (0x2A-0x2B, 0x2E-0x2F) to addresses 0x40-0x43 but I didn't end up using those memory locations as I went through a number of different ways to access 1P and 2P values. What I ended up with was using 0x22-23 for the current player's pointer to the screen address and 0x26-0x27 for the current player's pointer to the colour address.

Into the main loop at 0x8033 and we store zero at 0x34 so that we update 1P; a value of 1 will allow 2P to be updated. Next is an inner loop from 0x8037 which gets the current player number from address 0x34 and then goes on to call COPY_PLY_CUR_POINT to copy the necessary player values. After that we call RESPD_INP to check for button presses and either move the coloured block or change the border or background colour. The DISP_BLKS routine displays the player blocks in their current positions and COPY_PLY_NEW_POINT copies the new values for the current player. Lastly we check that both players have been updated and if not we update the next player or go back to updating 1P after a delay by calling WAIT_SHORT.

In COPY_PLY_CUR_POINT we use register Y, moved to X, so we can index 0x2A or 0x2E to get the screen and colour RAM addresses to copy to 0x22-0x23 and 0x26-0x27. Routine COPY_PLY_NEW_POINT does the opposite: it copies from 0x22-23 and 0x26-27 to 0x2A-2B and 0x2E-2F, indexed.

DISP_BLKS checks register Y to see whose player block is to be displayed and sets blue for 1P block or light red for 2P block. As well as setting the colour the routine also places the block by writing 0x01 to the screen RAM. See how we index 0x22 to get at the current player's pointer to screen address and 0x26 to get the current player's pointer to colour address. For e.g., the instruction:

sta ($22),Y

Will store the value in register A at the address pointed to by memory locations 0x22-0x23 for 1P or 0x24-0x25 for 2P (Y holds the player number).

For checking player input we have RESPD_INP which first checks the 4 directions using CHECK_DIR then the two triggers by calling CHECK_TRIG. Starting with CHECK_DIR we test for each direction by calling:

IS_UP_PRESSED, IS_DOWN_PRESSED, IS_LEFT_PRESSED and IS_RIGHT_PRESSED. All these routines do is call GET_PLY_INPUT which takes in Y the player number and then it is a matter of using AND to see if a direction button is pressed, in which case the bit will be 0. So back to CHECK_DIR we do a BEQ if the direction button has been pressed.

Branching to P1_DOWN_PRESSED, P1_RIGHT_PRESSED, P1_UP_PRESSED, or P1_LEFT_PRESSED (remember, it handles 2P as well) we place in register A either 1 to move 1 block left/right or 0x28 (1 row of characters) to move 1 row up/down. A call to MOVE_P1_BLK_INC moves a block right or down and a call to MOVE_P1_BLK_DEC moves a block left or up.

Before calling those functions, however, REMOVE_BLK is made use of to do the opposite of  DISP_BLKS by writing 0 to the current player's current screen address but there is no need to remove the colour value as character 0 is blank.

Back to MOVE_P1_BLK_INC and MOVE_P1_BLK_DEC they are very similar and have the job of increasing or decreasing the 16-bit screen and colour addresses for the current player.

At CHECK_TRIG we test for the 2 trigger buttons and if trig 1 is pressed we increase memory 0x0220 which is the border colour or if trig 2 is pressed we increase 0x0221, the background colour.

The timing is provided by WAIT_SHORT which is a blocking function that doesn't return until 0.5 seconds have passed. It sets the starting value for counter 1 to 0x32, waits for the new count to become valid and then keeps checking for the timer to reach 0x19. As already pointed out, you could start on 0x19 but I had planned for a 1s delay which would have required the full 50 counts.

The sound test code is the same as before but uses the new WAIT_SHORT routine instead of timing from the light pen trigger, however, some of the code using variable 0x1001 has been left in, previously used for timing the sounds. I'll get around to tidying up the code in future tests.

With the code written to the ROM and SW1 open, power up and you should see the test coloured blocks and hear the 8 sounds being played quickly one after another. Turn off, close SW1, power on again and now as well as the test blocks you should also see a blue block to left of the screen for 1P and a light red block to the right for 2P. Using the direction buttons on the 1P controller the blue block should move and pressing the direction buttons on the 2P controller should move the light red block. See that there is a delay before a block moves and that it continues to move at a set speed. Next, check that pressing 1P or 2P trigger 1 buttons results in the border colour changing and the trigger 2 buttons cause the background colour to change.

There is no boundary checking on the blocks so moving past the right of the screen will move the block to the start of the next row just as going beyond the extreme of the left side will have the block end up at the end of the previous row. If you direct a block past the bottom or the top of the screen the program may glitch up or crash. Pressing 2 direction buttons at once will not cause diagonal movement as only one direction is updated at a time but it wouldn't be too difficult to change the code to handle diagonal movement by checking the other buttons rather than exiting after a button press is detected.

If you have breadboarded the circuit like I have it can be very difficult to find problems but there are a number of things to check that are quite basic, such as the power supply-are there any big voltage drops on any of the power rails? I measured about 600mA was being drawn and that can cause the voltage to drop greatly if a simple power supply is used - it's better to use a SMPS which shouldn't drop its output voltage so much. Often it's a simple error that we overlook such as a chip not getting power so check the power pins of the IC's with your multimeter.

Using an oscilloscope or logic analyzer you can check the various clocks are working and that the R/W line, etc, is changing. Look very carefully over the connections and if need be disable a non-essential part (e.g. the 6525) to see if that makes a difference. It's possible that a chip is wrongly being selected so ensure the correct chips are connected to the correct chip select lines.

I had an issue where I would get a blank or green screen upon startup and sometimes a sound would play even though it wasn't in test mode. I checked the RAM, CPU and VIC and they all seemed fine so I added code to see if the CPU was getting stuck somewhere. It seemed to be that the CPU wasn't getting far enough to turn on the screen as I tested by modding the code so the CPU did a direct call to INIT_GFX rather than a jsr. With another look I found a wire had come out of the 74LS573 and with that put back and the original code programmed again now the system started up correctly. It made sense that the 74LS573 if misbehaving could output an incorrect address for the RAM but it's odd that it could cause the CPU to have trouble returning from a jsr.

All content of this and related pages is copyright (c) James S. 2017