Create Your Own Computer

Introduction

When most people talk of building a computer they usually mean buying a motherboard, fitting expansion cards and putting the whole thing into a case. This page is not about that type of building (although that is a very rewarding experience), it's about making your own simple computer down to the component level. This may seem like a daunting task and indeed there is a lot to consider and understand but on this page I will take you through the process step-by-step. I do assume some previous understanding of electronics but even if you do not have that it is still good to follow if you can to grasp an understanding of the basics. Please do email me if you have any comments, questions or suggestions or if you have found any errors. You can email me at:

james.boshikoopa@gmail.com

I feel that in a world of microcontrollers, such as the Arduino board, it can be all too easy to get a system up and running. Don't get me wrong, this is not necessarily a bad thing and it opens up electronics and programming to a wider range of people, even those who would not normally get into that type of area. But designing and building a computer system from the most basic level is very rewarding and along the way, microcontrollers can be used to help with this.

To go into more detail, the aim of this project is to build an 8-bit computer with all the fundamental parts such as CPU, memory, and, input and output devices as modules that are connected to each other. If you are confident enough you can add in extra parts or exchange components for others to meet your needs. This computer could actually become a games console or some other system, and this is something that will be discussed on this page. It is intended that each section is followed in order but it may be that only certain sections interest you so you may want to jump to those parts.

Generally, I test each part on breadboard and then when I'm happy it's working I make a soldered version which then ends up being connected to the next circuit on breadboard. There is a lot of chance for errors so be very careful and double check everything, especially looking for short circuits. As the circuits on this page run off 5V you can use a USB charger (best not use a computer USB port without isolation) and you can buy small USB devices that display the USB voltage and current. Not only is that helpful for easily seeing how much current is being drawn by your circuit but also should you see a very high current draw it could be an indication of a mistake (such as a short).

Note: where you see '0x' or '$' before a value it means it's a hexadecimal value. A '/' denotes an active low signal (e.g. /RES).

New: Moving forward (16/02/2017)

6507: simple calculator using a keypad and LED display (02/02/2017)

6507: Increase/decrease value on LED display V2 (19/10/2016)

6507: Increase/decrease value on LED display (02/09/2016)

Testing the 6507 CPU with Flash Memory, IO and RAM (26/08/2016)

Testing the 6507 CPU with Flash Memory and IO (19/08/2016)

Testing the 6507 CPU with Flash Memory (10/08/2016)

Address and data bus buffering (22/04/2015)

Update: Reading and writing memory (10/04/2015)

Update: Input and Output (19/04/2014)

Update: Clock Generator (10/03/2014)

Reading and writing memory (06/02/2013)

Clock Generator (01/02/2013)

Power on reset (30/01/2013)

Power supply (16/10/2012)

Input and Output

Capacitor Backed SRAM Module

Testing a SRAM chip

Testing a 6507 8-bit CPU

8-bit CPU Monitor

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

8-bit CPU Monitor

At the very beginning with this computer it is going to be very difficult to see exactly what is happening and whether everything is working as it should be. This is where the 8-bit CPU monitor comes in as it displays the current values of the CPU's address and data bus in real time. Using the device, if you were to clock the CPU slowly you could read what values are on the buses and check that the CPU is outputting what it should be.

Have a look at the block diagram of the CPU monitor below:

It uses a PIC microcontroller as the 'heart' of the system that is programmed to read the address and data bus values and then output them to an HD44780 compatible LCD module. You could use a different microcontroller if you wanted to or even an Arduino. The programmer is shown because if the microcontroller you use can easily be updated, such as by ICSP (In-circuit Serial Programming), then by providing a suitable connection you can update the firmware even after the system has been built.

The LCD module is operated in 4-bit mode to reduce the number of connections between the PIC and the LCD. The fewer I/O lines that are required, the less soldering will be needed, and the cheaper the PIC you can buy. However, there is then the matter of reading the 24-bits from the address (16-bit) and data bus (8-bit). Fortunately, this can be lowered to just 3 I/O connections by the use of a number of shift registers that convert the parallel data from the address and data bus to serial data for the PIC.

The circuit diagram that follows shows in much more detail how the CPU monitor works and although it may seem complicated it still is fairly simple. Three 4021 8-bit shift registers (IC1-IC3) have their parallel load inputs (P0-P7) connected to the address and data bus inputs. 

To read the address and data buses the PIC must first tell the shift registers to load values from its parallel inputs, which is done using the PL input of the 4021's, connected to the PIC's RA1 I/O connection. After that has been done, the current bit to be read appears on Q7 of the first 4021 (IC1), read in using the PIC's RA0 I/O line. The PIC only then has to clock the 4021's using their CP input wired to the PIC's RA2 I/O pin, and read the bit at RA0 after every clock pulse.

So that all 24-bits can be read in the third 4021 (IC3) feeds data to the second 4021 (IC2) via DS, which is the digital serial input. The same goes for the second 4021 (IC2) and the first 4021 (IC1) with only the third 4021 having its DS input held low so that if more bits were to be read it would give zeroes. If you wanted to read in more bits, such as from another data bus, it would be a simple matter of adding another 4021 and updating the PIC's firmware.

Ideally, the CPU monitor would be run off batteries perhaps with the option to also run off an external power source, such a regulated 5V mains step-down supply. From my own tests I found that the CPU monitor drew just 3mA, which shows the advantage of using CMOS based chips (the PIC and the 4021's) as well as the LCD module (which has its own low power microcontroller).

For the address and data bus a socket with 25 connections will be required, since a ground wire will need to be included. Fortunately, there is the DB-25 D-sub connector, which was commonly used for old style serial and parallel ports on computers. It is up to you how you want to wire such as connector but for guidance I have listed the arrangement I used:

The GND (ground) connection could go through the metal shield as well or instead of pin 25. I used a female connector on the CPU monitor since the typical parallel port printer cables I had plenty of use a male connector.

With so many inputs involved it is a very good idea to test that the CPU monitor is outputting the right values. The CPU monitor doesn't feature any built-in pull-down resistors since it is designed to be connected to a CPU. However, you can solder a set of switches and pull down resistors to test the CPU monitor. The circuit diagram can be found below:

It's a very simple circuit but there are a lot of components involved. For the switches it would be a good idea to use the DIL (Dual In-line) type which feature 8 switches each.

Power supply

The power supply has been designed to give 5V DC at a maximum of 1A to power all the circuits. The approach is to use a 7805 positive 5V regulator to regulate a higher DC, unregulated voltage from a mains step-down power supply external to the computer. A simple switch will turn the power on or off, which is indicated by a 'power on' LED.

The circuit diagram for the power supply is shown below:

D1 protects against the input voltage being connected the wrong way round and FS1 will blow if too much current is drawn. In the early stages of the project when there are only a few circuits powered you could use a low rated fuse as there will be much less current drawn under normal use. The 7805, IC1, regulates the voltage so that is stays around 5V; the IC will need a suitable heatsink if it is to deliver the maximum amount of current.

Testing a 6507 8-bit CPU

The 6502 was a very popular 8-bit CPU in the 80's and I'm sure, it still is even today, amongst hobbyists. While it is inferior compared to other, similar CPU's, such as the Z80, on the plus side it is a much better candidate for testing due to its simplicity. So why use a 6507 you may ask? The 6507 is a 6502 that has been restricted so that it can only access 8K of memory and it is missing certain I/O connections, such as the IRQ and NMI interrupts. What this means is that the 6507 is even easier to use than the 6502 yet it is essentially a 6502 so the transition from 6507 to 6502 won't be too difficult.

I can't remember where I got my 6507 from, and the fact that it had been in a chip holder is quite curious. But whatever system it was from it appears to be functional from the tests I have done, which I will share with you now.

The test circuit for using the 6507 can be found below:

The data and address bus of the 6507 (IC2) are connected to the 8-bit CPU monitor (please see the section elsewhere on this page) but since the 6507 only has 13 address lines, the remaining 3 (A13 to A15) of the CPU monitor's address input are connected to ground.

The 6507's RDY input is tied to +5V so that the CPU runs normally; it can be used by memory chips to tell the 6507 that it's not yet ready to deliver the requested data. To reset the 6507 the reset input (RES) has to be taken low, which is done using SW1 (pulled up to logic 1 via R2 when the switch is not pressed).

To indicate when the 6507 is reading of writing, an LED is connected to the 6507's R/W output. The LED will light when the 6507 is reading data but the LED will be off when the 6507 is writing data. Lastly, a 555 timer (IC1) provides a slow clock source (about 0.7Hz) so that we can see the 6507 operating. Using a 555 is not the best way to clock a CPU but it is good enough for this test.

When power is applied press SW1 to reset the 6507. You should then see on the CPU monitor's screen a number of different values on the address bus. After the 6th value, the address value should be 0x1FFC, 0x1FFD and then 0x0000 (followed by other values).

Memory locations 0x1FFC and 0x1FFD are the addresses of the reset vector (a byte each make up the full address), that is, the address of the code to be executed when the 6507 is reset. So, upon reset, the 6507 fetches the address of the reset code from from memory (at addresses 0x1FFC and 0x1FFD) and then jumps to that memory location. Since no memory has been connected to the 6507 it reads in zeroes and thus jumps to address 0x0000. From there the 6507 will execute whatever instructions it finds (not that there are any, it will just read in zeroes) and end up getting into a right mess.

Below you can see that I put together a board that houses the 6507 CPU on a chip holder with a number of connectors for interfacing with it. I wired up a DB-25 D-sub connector so that it could be plugged into the CPU board and the CPU monitor.

It might not seem much, but this simple test shows a very low-level working of the 6507 and demonstrates that it works. It also illustrates what a typical CPU does and how this most basic action is crucial to the correct running of a computer system. Now that we know the 6507 is starting up correctly it can be attached to some memory.

Testing an SRAM chip

Any computer system needs RAM memory to temporary store data and since SRAM requires no refresh circuitry it makes sense to start off with an SRAM chip. Keeping the 6507 CPU in mind, which can only access 8K of memory and since I happened to have a MT5C6408 8Kx8 SRAM chip I decided to go with that memory chip. There is actually an almost identical SRAM IC, part number UM6164, which has the same pinout and characteristics as the MT5C6408, so the UM6164 could also be used in this test.

The way the SRAM chip will be tested is by viewing two different memory locations (address 0 and 1) using the 8-bit CPU monitor (also covered on this page). The two memory locations will then be written to and checked that they stay at the values put there. If that is the case, it is a good sign that the SRAM IC is working.

The test circuit can be found below:

To help with testing the SRAM chip the 8-bit CPU monitor is used to view the address and data values. Since we are only going to test two memory locations, all address lines apart from A0 of the MT5C6408 (IC1) are held low. A0 is pulled down by R1 so that address 0 is selected but when SW1 is pressed, it brings A0 high, switching to address 1

The MT5C6408 has two Chip Enable inputs, CE1 and CE2, which are usually used by an address decoder to enable the memory chip whenever the CPU outputs a certain range of addresses. For this test, CE1 is kept low and CE2 is taken high so that IC1 is always enabled. If CE1 is held high or CE2 goes low, MT5C6408 will be in standby mode (that is, deactivated) and the data bus (DQ1 to DQ8) will be in high impedance mode, disconnecting anything connected to the data bus. This also happens if the Output Enable (OE) input is taken high while IC1 is not in standby mode.

With the circuit set-up and the CPU monitor on you should see that the address reads '0000' but the data value will be some random number. If you turn the power off and back on again, by chance the data value will be different. This is because RAM IC's start up in an unknown state but the CPU during start-up can clear this memory, when used in a typical computer system.

By pressing SW1, the address value should change to '0001' and the data value will likely be different, as the second memory location will, like all the others, have a random value in it. With SW1 released, press SW2, which puts the MT5C6408 into write mode. You should see the data value become zero (if it wasn't already) and when you press SW1 the data value should still be the same as before. What you have done is written zero to the first memory location since DQ1 to DQ8 are pulled low by resistors R2-R9. When the MT5C6408 is in read mode (when SW2 is not pressed), R2-R9 act as load resistors. We can't just tie DQ1 to DQ8 to GND or +5V because when the MT5C6408 is in read mode, DQ1 to DQ8 are outputs and would be damaged if connected directly to the power supply.

The second test is to press SW1 to select the second memory location and then push SW2 to write zero to that memory location, which will cause the data value to become zero. Now when you release SW1 you should see the data value is zero and when you press SW1 the data value should also be zero. You have now written zero to two memory locations and tested that the SRAM IC works.

Now that we both have a working CPU and SRAM chip the two can be connected together, making an almost complete, very basic, computer system. However, we need an easy way to get a short computer program into the RAM for the CPU to execute so that are a number of steps to take first before connecting the two together.

Capacitor Backed SRAM Module

While still in the early stages of this computer project we need a way to give the CPU some instructions which need to be stored in some form of immediate memory. SRAM seems an obvious choice since it can be written to easily and changed with little trouble. However, we cannot write directly to an SRAM chip while interfaced with the CPU. Actually, we could, but it would require additional hardware.

The solution is to create a RAM module which will keep its data even after power is removed. Traditionally, the technique would be to use a battery (often a 3V CR2032 cell) which powers the SRAM IC when the main power is removed (a number of diodes or other components switch out the battery when external power is connected). This method was used with many memory cards for some games consoles but it has a number of disadvantages. For one thing, batteries eventually run out and must be replaced but this is not always an easy thing to do in the case of batteries which are soldered directly to the circuit board (they can be de-soldered but the battery must be protected against too much heat from the soldering iron). Another problem with batteries is that they are bulky and often take up more space than the memory chip.

The alternative is to use a so-called super capacitor, a capacitor with much higher capacitance than the older types of capacitors (which typically had capacitances no higher than in the micro-farads range). While it won't keep the data in the memory IC as long as a battery will it's a lot safer and will give enough time to load the data into the SRAM chip and then connect it to the CPU. Another bonus is that the super capacitor should never need replacing, unlike a battery.

While the SRAM module is disconnected from the CPU circuit it will run off the electricity stored in the super capacitor. The memory chip will be configured in such a way that while it is not connected to another circuit it is in standby mode, which uses a lot less power than when active. By connecting the SRAM module to the CPU circuit it will use the CPU's power and charge the super capacitor (the charge time will be a few seconds).

The circuit diagram follows; it is not much different to the MT5C6408 test circuit with the key difference that a super capacitor, C2, is connected across the supply lines (as well as the decoupling capacitor, C1). The capacitor C2 is what supplies power to the RAM chip, IC1, when not connected to a power supply. Another change from the test circuit is that the control inputs (CE1, CE2, OE and WE) are connected in such a way that if they are not connected to an external circuit, IC1 is put into standby mode.

If you are to solder this circuit using a chip holder for the MT5C6408 then a word of caution should you want to remove the IC after power has flowed through this circuit. Even with power removed from the circuit, C2 may still have a charge, which could harm the MT5C6408 if it was to be taken out. The solution is to discharge C2 first by connecting a low value resistor (about 150 ohms) to the circuit's power supply connections. Monitor the voltage using a voltmeter and remove the resistor when the voltage reaches close to OV (note that removing the resistor may cause the voltage to go up slightly).

Power on reset

When you turn on the computer it needs to start up in a known state and while you could reset the computer manually yourself (and indeed was the case with some old computers) it's not difficult to create a circuit which will trigger the reset at power up. However, should you need to reset the computer yourself after the automatic reset, that can still be done by pressing a switch.

Have a look at the circuit for the power on reset, below:

The circuit is based around a 555 timer IC (IC1) operated in monostable mode which is triggered by an RC arrangement that itself acts as a time delay; pressing SW1 re-triggers the 555. Because the trigger pulse needs to be active low, TR1 inverts the output of the 555. You could use an inverter (NOT) logic gate but since the circuit is designed to be modular, several inverters would be wasted.

To test the circuit it's best to use an oscilloscope to check that the /RES output is square, going from high to low for around 100ms (but maybe somewhat more, I measured about 400ms), before returning high once more. You should check this when power is first turned on (although depending on your oscilloscope that could be tricky) and whenever the reset switch is pressed.

The power on reset circuit also needs to be tested with the CPU which can be done by using the same set-up as described in the 'Testing a 6507 8-bit CPU section' but with the reset switch (SW1) replaced with the output of the power on reset circuit (/RES); there is no need for the pull-up resistor, R2. Check that at power up the CPU starts up the same way as when you first tested the 6507.

Clock Generator

The CPU, and other components, will need a timing signal to keep them synchronized, which is produced by the clock generator circuit. Typically, the clock generator uses a quartz crystal for accurate timing. Since the CPU and I/O (Input/Output) run at 1MHz the clock generator will need to output a 1MHz signal. To identify the maximum speed of a 6507 or 6522 (the I/O), check if there is a letter after the IC number written on the IC (e.g., 6507A). If there is an 'A' the frequency is 2MHz; if there is a 'B' the frequency is 3MHz, and so on. If there is a 'P' or no letter at all then the frequency is 1 MHz (the 'P' refers to the chip package type).

To help with debugging the computer, I had planned to allow the clock generator to be dropped to 1Hz as well a mode to produce the clock pulses manually using a switch. Through tests I have found out that is not possible without using a different method. The original type of 6502 also has a minimum clock speed but the CMOS version can be clocked down to 1Hz.

It turns out-or at least according to the datasheet-that the 6507 needs a minimum clock speed of 50KHz. From researching online the reason given is because the CPU needs to refresh its DRAM memory. What I have found out though, as you can see from the 'Testing a 6507 8-bit CPU' section that the CPU will at least carry out its reset procedure even with a slow clock. Apparently, this could cause harm to the 6507 as also could a slow reset pulse (due to a possible bug), which leads on to the next part. I couldn't work out why the 555 power on reset (see 'Power on reset' section) wouldn't reset the 6507 even though I could reset the 6507 manually as in the test circuit (using a switch). Through some tests I worked out that because of the slow clock the 6507 needed a longer reset pulse; a two second pulse worked. If you are concerned about harming the 6507 then it would be best to not do the tests but so far I have had no problems.

To give the greatest accuracy normally a clock generator is based around a quartz crystal but sometimes at a speed much higher than the main system clock. For e.g., in some microcomputers the clock was fed into the video chip which then outputted a lower speed clock for the CPU.

While a simple clock generator can be made from a couple of logic gates, breadboarding such a circuit can cause problems since breadboard can cause interference with high frequency circuits. At first I thought this was the problem I was having but since getting a better oscilloscope I found that the circuit was actually producing roughly square waves at the correct frequency. For this reason it is very important that you have access to a high bandwidth oscilloscope.

I had also looked into using a Voltage-Controlled Oscillator (VCO) based around the 74624 which lets you alter the frequency based on an input voltage. Although it worked when using a capacitor for timing when it came to using a quartz crystal the VCO's output became severely distorted when the frequency was adjusted from the quartz crystal's frequency, most likely as the quartz only has one true frequency.

The reason for trying a VCO was that I did not have a 1MHz crystal but instead of buying one I wanted to make use of one of the many crystals I already had. Because of this I had at least tried a VCO under the assumption that I could lower the frequency to 1MHz which I now know I cannot.

Going back to the logic based clock generator I had breadboarded I then proceeded to add a clock divider using a 74LS74. The clock generator creates a 4MHz clock pulse which the clock divider halves to 2MHz using one half of the 74LS74 and then to 1MHz using the second half of the 74LS74. This way I have the main system clock of 1MHz but also a 2MHz and 4MHz clock for faster devices I may then add to the computer. Or, if I later use a faster CPU I'll already have a higher frequency clock for it.

You can see the circuit diagram below:

The crystal oscillator part of the circuit is based on a common oscillator circuit which uses three inverter gates (part of the 74LS04, IC1), a 4MHz crystal, two resistors and a capacitor. The 4MHz clock pulse is fed into a D-type flip-flop (half of the 74LS74, IC2) which halves the frequency to 2MHZ which in turn is halved by the second flip-flop to 1MHz. Each of the clock signals are provided as outputs along with an inverted version, as to make use of the remaining three NOT gates in the 74LS04. This means that the circuit creates two phase, non-overlapping (while one output is high the other is low and vice versa) clock outputs each for 4MHz, 2MHz and 1MHz.

The resistors R3-R8 ensure that the high output level is close to 5V.

When building this circuit it would be best to get the crystal oscillator part working first and then test with the clock divider portion. You will need to use an oscilloscope to check that the frequency of each output is correct, that the high level is close to 5V and that of each of the two outputs they are an inversion of each other.

I found that the output frequency of each pair was slightly lower than it should have been but this could be corrected by introducing a variable capacitor into the crystal oscillator section, which would allow the frequency to be altered slightly.

Input and Output

A computer wouldn't be much good if the user couldn't give it instructions or get information back. Unfortunately, most traditional CPU's don't have built in support for input and output that the user will appreciate (keyboard, display, and so on). When a CPU is combined with input and output devices in a single chip it is known as a microcontroller, such as the PIC used for the CPU monitor. In fact, a microcontroller is a complete computer on a chip as in addition to the aforementioned elements it also includes RAM and ROM on-board.

Since we are using a CPU for this computer we will need to include a chip that is capable of interfacing both with the user and the CPU. For this we will be using the 6522 Versatile Interface Adapter (VIA) which was very popular with the microcomputers of the 1980's but even today it can be of great use. It contains two 8-bit ports (Port A and Port B) of which each I/O line can individually be programmed as either input or output. There are also two 16-bit timers and an 8-bit shift register for converting between parallel and serial. You may have noticed that a typical microcontroller, such as a PIC, includes these items so again a microcontroller is very much a computer on a chip.

The 6522 contains 16 internal registers which are used both for reading the current state of the 6522 and for updating its ports as well as for setting a number of options. The registers are selected using RS0 to RS3 which in a computer system are normally connected to the CPU's address bus. In this way, the 6522 appears as memory to the CPU, so it is classed as memory-mapped I/O. This is very convenient since the CPU can easily read and update the I/O but logic will be needed to select the 6522 when the CPU wants to address it, as with other types of memory.

Test port A as input

To test the 6522 I used a fairly simple circuit I had devised to test Port A as all inputs but it did not work at first until I had made some changes, which taught me two main things about the 6522, which will be explained shortly. The simplest way to test the 6522 is to have it output the Port A value on the data bus and since Port A and B default to inputs at reset only one of the 6522's internal registers would need to be selected, which is the Port A register. That is, there is no need to change Port A to inputs which would require accessing another of the 6522's registers.

By updating Port A (set as inputs) using switches the CPU monitor should have shown the value of Port A but no matter what I set the switches to the data bus would always show 0xFF. Through investigation and research online I found that the 6522 has internal pull-up resistors on Port A and B. This is not mentioned in the 6522 datasheet but is suggested in one of the circuit diagrams in the datasheet. So, by using pull-down resistors of my own it was having no effect on the state of Port A. Thus all I needed to do was remove my pull-down resistors and rewire the switches so that they take the Port A lines low, which caused a change on the data bus.

Oddly, I found that although an LED connected to one of the data bus lines reflected the Port A state, when I used the CPU monitor it flicked between 0xFF and the Port A value. By looking at the read timing waveform in the 6522 datasheet I could see that the value (in this case the Port A value) is only placed on the data bus briefly. I can only reason that the CPU monitor is able to pick up the very short change because it runs at such a faster speed than the 6522 but the LCD is updated much slower and therefore the change can be seen-with an LED the change would be far too quick to see. I did use my oscilloscope with one channel connected to the 6522 clock input and the second channel wired to one of the 6522's data bus pins and saw that the waveform was about right.

With the circuit now working, turning the switches on and off causes the necessary value changes as shown on the CPU monitor. Because of the inverted logic (Port A internally pulled high and the switches pull Port A lines low) turning a switch on results in a logic 0 signal while turning a switch off is a logic 1 input.

You can view the circuit diagram below:

A number of switches (SW2-SW9) act as the inputs for the 6522's port A (PA0 to PA7); R6-R13 protect the 6522 in case the port A lines are acting as outputs and one of the switches was turned on which would otherwise cause a short. On a related note, CA1, CA2, CB1 and CB2 are additional I/O which can be set as input or output and as such without having to worry about their start-up or current state, each of R2-R5 act as a pull down or load resistor as needed.

A reset switch, SW1, is provided to restart the 6522 in a known state although I found that whenever I powered the circuit the 6522 had already reset itself (at least that seemed to be so since Port A was already set to all inputs). The interrupt output, /IRQ, is unused in this circuit and as it is the 6507 CPU doesn't have an IRQ input. As for the 6522's chip select inputs, CS1 and /CS2, the 6522 will only be active when CS1 is held high and /CS2 is held low. This is useful so that the 6522 can be selected when the CPU requests access to it.

As this test circuit is testing Port A as inputs only by putting the Port A value on the data bus the 6522 only needs to be in read mode which is why R/W is held high. The CPU monitor is used to view the Port A value by connecting the 6522's data bus (D0-D7) to the CPU monitor's data inputs; the address inputs are unused and are connected to ground.

The register select inputs, RS0-RS3 are connected such that register 1 is selected, which is the Port A register. Register 15 can also be used and will make no difference for this test circuit so there is no need to go into more detail at this stage.

The clock generator, as described in the clock generator section, produces the clock pulses for the 6522 which are fed into its phase 2 input (pin 25). For this test circuit it doesn't matter which 1MHz phase output from the clock generator is used.

I have tested the circuit both on a R6522 and SY6522A and they both worked; there are a few more types of 6522 which I don't have but they should also work. To test the 6522 connect the power to the circuit and press the reset switch (SW1). Setting SW2-SW9 should change the value on the CPU monitor, remembering that turning the switch on is logic 0 and off is logic 1. As mentioned earlier, the value on the CPU monitor will switch between 0xFF and the Port A value.

In the photo that follows you can see my test set-up of the 6522 as previously described:

At the top-left is the clock generator module; at the bottom left are the port A switches; the breadboarded 6522 is at the bottom middle; the CPU monitor is at the bottom right and lastly the power supply is at the top right.

Test port A as output

The next test is to make sure port A can behave as outputs and to do this I have modified the test circuit from Test port A as input. You can see the complete circuit below:

The CPU now monitors the 6522's port A and the switches are connected to the data bus. When power is supplied to the circuit press SW1 to reset the VIA which should result in 0xFF being shown as the data bus value on the CPU monitor (since Port A is now acting as inputs and are being held high by the internal pull-ups).

Close SW2 so that the Port A direction register is selected (register 3) and put switches SW4-SW11 to the open position (logic level 1) to set all Port A lines to output (0=input; 1=output). Press SW3 to save the changes and you'll see that the CPU monitor will now show 0x00 (all outputs at logic level 0).

Next, open SW2 to select the port A register and set SW4-SW11 to different combinations (open=logic 1; closed=logic 0) and press SW3 to set the chosen state of Port A, which will be reflected on the CPU monitor. Note that the value on the CPU monitor will be stable unlike with the Test port A as input experiment.

The resistors R8-R15 protect the data bus when it's in output mode (SW3 not pressed) while any of the switches SW4-SW11 are closed. Also to add is that the data bus appears to have some form of internal pull-up which is why there are no pull-up/pull-down resistors included in the circuit for the data bus).

There are other tests we can do on the 6522 as it has a second port (port B) and other features such as the timers but at this stage if the first two tests work then that is a very good sign that the 6522 is behaving as it should. We can do additional tests once the 6522 is connected to the CPU.

Reading and writing memory

Reading from a ROM

For the CPU to be able to do anything it needs a program stored in some form of memory; RAM or ROM. As we have seen, after the CPU is sent a reset signal it will fetch the address of the reset vector. Thus, not only will we need to store a program in memory we will need to also put the address of the reset routine.

While we could use a ROM chip, using RAM should be more accessible to most and RAM will be needed later on when we need to write a program where the CPU will write to a number of memory locations, so RAM will be needed for that. Also, to have a ROM and RAM chip would require address decoding, which will be covered later.

While this project may seem recent it has actually be on and off for a long time. Some while ago I built a 16-bit counter for reading and writing to memory. It uses four 7493 counters to allow memory devices of up to 64KB to sequentially be accessed. The 16-bit counter is then used with a device to control the clock and reset signals as well as to read or write to the memory. Originally I used an Amiga but this time round, I have used an Arduino.

Have a look at the circuit diagram for the 16-bit counter below:

Each 7493 handles 4 bits of the memory chip's address bus and in turn clocks the next 7493; a 'clk out' signal is provided so that more counters can be added. The way to use the 16-bit counter with a memory IC is first to reset the counters by taking 'rst in' high and then back low. Address 0 will then be selected, allowing the data from that memory location to be read from or written to. To move to the next address, 'clk in' needs to be taken from high to low. Then the next byte can be read or written to. Memory access doesn't have to start at address 0 as the counters can be reset and then advanced to a certain address before reading or writing takes place. However, this is quite a slow way to access the memory as the counter has to go through each address in turn so higher addresses will take longer to reach.

Here you can see the soldered circuit I did back in 2007:

Note the ferrite bead on the address wires at the top; there is also a ferrite bead at the other end to help lower interference from the long wires.

The counter can be tested using the 8-bit CPU monitor (the data bus is not needed so pull-down resistors need to be used) with some form of astable to clock the counter. Once we are sure the counter is working the next step is to read values from a ROM which we know the data it contains, to check that the circuit is correctly reading the values. I had a number of EPROMs (the type with a window) containing BBC micro programs and figured that if I could get a copy from somewhere I could compare the two. Unfortunately, the copies I found where of a different version, as indicated by the version text at the beginning of the ROM. However, most of the values were the same when I read them and were only different for the version number text string and some of the data after (since my ROMs are a different version then likely there would be changes to some of the data). So, after reading some of the data from two different software ROMs I was happy that the correct values were read in.

The circuit below shows how the counter is connected to a 27128 16KBx8 EPROM along with an Arduino:

You can use an Arduino Uno, Nano (which plugs into breadboard easily so would be a good choice) or other suitable Arduino.

The Arduino powers the counter and the EPROM (assumes that the Arduino is powered by a USB port so it should have no trouble supplying the needed current). The Arduino controls the reset and clock of the counter; I found I had to use a pull-down resistor on the clock line otherwise the counter was ignoring the clock pulses (this could be because of the long wires from the counter or an interface issue between the Arduino and the counter). In this way, the counter selects each of the EPROM's memory locations (the counter's A14 and A15 outputs are not needed since the 27128 is only 16KB). The Arduino then reads in a byte from the EPROM.

You can find the software I wrote for the Arduino at the bottom of this page; click on the 'Memory_reader_1V0.zip' link.

The photo that follows shows the my Arduino connected to the EPROM (containing a copy of Nova CAD written for the BBC micro); the counter is out of view.

New technology with old; the Arduino Nano on the left and the EPROM (dated 1983) on the right.

Alternative counter circuit

Unfortunately my 16-bit counter recently stopped working and after testing I found that 3 of the 4 7493's had become faulty but considering the IC's were 45 years old and I had got them second hand they had done very well. Seeing that new 7493's were quite expensive I looked at what other counters I had and found I had two 74HCT4040 which are 12-bit binary counters and are TTL compatible. So I tested and then soldered a circuit to become a 24-bit counter, the diagram of which follows:

The circuit is quite simple compared to the 7493 version but I left out the pull-down resistor on the reset input (it can be added if need be) and there is no clock out but that is easy enough to include by using Q11 of IC2 just as Q11 of IC1 advances IC2. As with the 16-bit counter you can test it using an astable and the CPU monitor.

Reading and writing to RAM

Once we are able to read from a ROM chip the next step is to read from and write to a RAM IC so that we can fill it with data that the CPU can then use, which will be part of another test. Since we can already read from ROM we can build on that in terms of the circuit and the software. The biggest change is making use of the RAM's write enable input and storing to the RAM a range of values to check it is working.

What follows is the circuit diagram:

The circuit uses the MT5C6408 RAM chip that is used in the Capacitor backed SRAM module and conveniently the RAM IC has a very similar pinout to the 27128 that was used in the Reading from a ROM test. As the MT5C6408 has a capacity of 8K compared to the 27128 which is 16K, one less output from the binary counter is needed.

The Arduino's D12 I/O configured as an output has beeen made use of so that data can be stored to the RAM chip by taking /WE low, just as we did in the Testing a SRAM chip section. As for the resistors R2-R9 they protect the Arduino and the RAM IC should the RAM be in read mode while the Arduino's D2-D9 lines are in output mode, by limiting the current. This shouldn't normally happen but could come about because of an error while developing the software or if future changes are made to the code.

The Arduino code for this test can be found at the bottom of the page and is called 'Memory_reader_writer_1V0.zip'. The software writes to the first 256 memory locations the values 0 to 255. This is very important as it not only tests the binary counter but also that all 8 bits are working since they are all used for the values 0 to 255.

Next, the values stored in first 256 memory locations are displayed in the serial monitor and should be the values 0 to 255. After that, 256 memory locations are dumped to the serial monitor from address 5 and each should have the values 5, 6, 7, etc. in order up to 255, followed by random values (since we haven't written to any memory locations beyond address 255).

You will see that there is some commented out code in the doTest() function that you can put back in if you want to. The code writes 0's to the first 256 memory locations by passing true to the doWriteTest() function; pass false to write the address value (i.e. 0 to 255 for addresses 0 to 255).

Address and data bus buffering

One of the biggest steps in getting our computer to work will be to write a simple program to the RAM and then get the CPU to execute the instructions. The problem that we will have is that we cannot read or write to the RAM while the CPU is connected (as that could cause harm) and it would not be a good idea to write to the RAM and then connect the CPU as that would not be practical.

What we can do is use digital buffers between the CPU and RAM, and the Arduino and RAM, which will let either the CPU read/write to RAM while the Arduino is isolated from the buses or the Arduino read/write to the RAM while the CPU is taken off the buses. So, rather than having to physically remove components from the circuit to prevent conflict we can electrically isolate them using a property of the buffers called tri-state or high impedance. That is, in addition to the two binary values 0 and 1 there is a third state which effectively removes the buffer from the circuit and in turn anything connected to its inputs.

Note that buffers are useful for more than just isolating signals, they can also be used to drive bigger loads than what would normally be possible elsewhere in a circuit.

The buffer circuit is shown below and is capable of driving and isolating a bi-directional 8-bit data bus and a uni-directional 16-bit address bus:

We will need two of these buffer circuits; one for communication between the Arduino and RAM, and another for data transfer between the CPU and RAM.

The 74LS245 is an octal bus transceiver and this simply means it contains two lots of 8 buffers which allows data to flow in a direction set by the state of the DIR input. If we connect the DIR input to the CPU's R/W output or an output from an Arduino it will control the direction of communication. As for the EN input, when this goes high the 74LS245 enters high impedance mode and its outputs will be disconnected from whatever they are connected to.

For the address bus we have two 74LS244 IC's which are octal buffers and they only support data flow in one direction as they have just one lot of 8 buffers. However, internally they are split up into two sets of four buffers, which is why there are two enable inputs (1G and 2G) but we are using them as a single set of 8 buffers.

Although the CPU we are using has an address bus smaller than 16 bits we will need to buffer additional signals.

Testing the 6507 CPU with Flash Memory

A CPU needs to follow instructions in memory and usually there is at least a minimum amount of code stored in ROM that forms part of the computer's startup routine. There are a number of options when it comes to ROM's and two good choices are EEPROM and flash, of which I have chosen flash as I happened to have such a chip spare. You will of course need a programmer but fortunately you can buy cheap programmers that can read and write EEPROM's and flash memory chips.

Here is the circuit:

The flash memory chip is a W29EE011, is 128Kx8, and more than enough considering the 6507 can only access 8K, but for test purposes it is fine and is a memory chip I so happened to have. As we know, when the 6507 is reset it will fetch the reset address from 0x1FFC, 0x1FFD, jump to that address and execute whatever instructions are there. For our simple test we will have the CPU execute a single instruction which will load a value from ROM into the 6507's accumulator. To do this we need to write the following values to the flash ROM (Address/Data):

0x1FFD 0x1F

0x1FFC 0xF0

0x1FF0 0xAD

0x1FF1 0xE0

0x1FF2 0x1F

0x1FE0 0xC8

I used the MiniPro programmer which you can read about at:

https://sites.google.com/site/jamesskingdom/Home/electronics-by-james-s/tools-for-working-with-electronics#TOC-TL866CS-MiniPro-EPROM-programmer

At 0x1FFC, 0x1FFD we have the address of the reset routine: 0x1FF0 (low byte is always followed by high byte when the CPU loads an address). At addresses 0x1FF0 to 0x1FF2 we have the instruction LDA 0x1FE0, which loads the value at address 0x1FE0 into the accumulator. The value at 0x1FE0 is 0xC8, but you can use any value you want. When you reset the 6507 you should see the normal reset values on the CPU monitor including the CPU loading 0xF0 from 0x1FFC and then 0x1F from 0x1FFD. Next you'll see 0xAD (LDA instruction) at address 0x1FF0, then 0xE0 at 0x1FF1, then 0x1F at 0x1FF2, followed by 0xC8 at 0x1FE0. Lastly you'll see whatever value is at address 0x1FF3 as the CPU continues the program (which at this point is garbage unless you placed additional instructions). If you did see all those values in the correct order then the test worked otherwise check over your connections and try again. Remember that you need to hold reset down for a few seconds or use the power-on reset module.

The only other thing to add is that a single inverter (IC4) has been used so that it is not possible for the CPU to write to the flash chip should there be an error in the code. If this did happen it could harm either the CPU or flash IC although in professional designs that use ROM chips that don't even have an OE input there doesn't seem to be any protection against the CPU writing to a ROM chip.

Testing the 6507 CPU with Flash Memory and IO

Up to this point we have been able to get the CPU to run a very simple program which we have closely followed by using the CPU monitor. Now we will get the CPU to do something useful by making the 6522 VIA toggle its port A lines on and off, monitored by a logic analyzer (or you can use an oscilloscope if it has 8 channels). We need to run the 6522 at full speed as it needs a minimum clock speed of about 100KHz (CMOS version, however, should be able to run slower), which would be too fast to see changes on the CPU monitor anyway, which is the reason why a logic analyzer needs to be used. Since we have a clock generator that starts at 1MHz, which is what the 6507 can handle, we might as well use 1MHz.

Take a look at the circuit:

I have built upon the circuit in the previous section and added the 6522 VIA which is IC3. The flash chip (IC2) and the VIA share the same data and address bus with the 6522 also connected to the /RES and R/W lines of the CPU. The clock output of IC1 (pin 28), which feeds into IC3 (pin 25) is actually in phase but is buffered and helps with the 6522's internal timing. As for the second inverter (2/6 IC4) it behaves as a very simple address decoder: when A12 is set (address is 0x1000 to 0x1FFF) the flash chip (IC2) is accessed by the CPU but when A12 is low (address 0x0000 to 0x0FFF) the 6522 (IC3 is accessed) and the VIA will effectively be mirrored multiple times up the memory map. The importance of having the VIA at the bottom of the memory is that it is in 'page zero' which can be accessed a bit easier by the 6507 (one byte addresses can be used).

We need to write the following values to the flash ROM. You only need to write the hex values but the instructions and comments are provided to help you understand what is happening.

(Address/Data/Instruction/Comments)

0x1FFD 0x1F

0x1FFC 0xE0

0x1FE0 0xA9 LDA #0xFF

0x1FE1 0xFF

0x1FE2 0x85 STA 0x03 Set port A output

0x1FE3 0x03

0x1FE4 0x85 STA 0x01 Turn on all port A bits

0x1FE5 0x01

0x1FE6 0xA9 LDA #0x00

0x1FE7 0x00

0x1FE8 0x85 STA 0x01 Turn off all port A bits

0x1FE9 0x01

0x1FEA 0xA9 LDA #0xFF

0x1FEB 0xFF

0x1FEC 0xD0 BNE 0x1FE4 Repeat

0x1FED 0XF6

The program turns on/off all ports A lines at high speed after setting port A to all outputs. Using your logic analyzer you should see the 8 port lines go high and low at the same time at a frequency of about 62KHz and with a low time of about 11 microseconds and high time of about 5 microseconds. You can see this below:

The reason the low time is longer than the high time is mainly because of the branch instruction, BNE, at address 0X1FEC, which takes as long as a store instruction (STA).

Although you can write simple programs by hand in assembly language you can take advantage of online assemblers and disassemblers such as:

https://skilldrick.github.io/easy6502/

Not only do assemblers generate the hex code for you, if there is also a debugger available (such as provided at the site linked above) you can check for errors even before you run the code on your hardware.

I, however, managed to get the branch wrong so while the above code is correct in the code I originally wrote it branched back to 0x1FE2 instead of 0x1FE4 meaning that the port A lines stayed low for an extra 3 cycles, which is the timing you see in the screenshot above. You can see how writing time critical programs can be difficult but we should not depend on timing of instructions due to how long an instruction takes to execute as the slightest change could mess up the timing.

A nice thing about the layout of the 6522 pins is that they are in a logical format so soldering a 6522 board should be simple.

Testing the 6507 CPU with Flash Memory, IO and RAM

With a CPU, ROM, RAM and IO we now have a working computer even if the only output is still a logic analyzer as this time the program will write increasing values to 256 bytes of RAM and then output the values to the 6522's port A so we can check the values were written correctly to the RAM. Take a look at the circuit:

The main addition to the previous circuit is the inclusion of the RAM chip, IC7, the MT5C6408 which we had previously tested. Although it is 8K we can only use a small part of it because the ROM has been mapped in and even less so to simplify the address decoder. This is the resulting memory map:

0x0000 to 0x000F IO (6522, IC3)

0x0010 to 0x01FF RAM (MT5C6408, IC7)

0x1000 to 0x1FFF ROM (W29EE011, IC2)

So we have just 496 bytes for the RAM but this is enough to write to the first 256 bytes for test purposes but we do cross over into page 1 (0x0100 to 0x01FF) so we use load and store instructions that can handle 16 bit addresses. The CPU's stack actually occupies page 1 but as we are not using any instructions involving the stack it isn't a problem.

The address decoder is made up of 6 OR gates (IC5 and IC6) and two inverters (IC4) which detect if any of the address lines A4 to A8 are set to 1 and A12 isn't set and if so enables the RAM (IC7). If A12 isn't set and none of the address lines A4 to A8 are at logic level 1 then the IO (IC3) is selected. You can see how the address decoder can get complicated even in quite simple scenarios but there are a number of ways to handle address decoding, which we will look at in other sections. Note that although all of the RAM chip address lines are connected to the CPU they are not all used but it does mean there is no need to tie unused address inputs to a set logic level.

Next, let's look at the code:

Reset vector

0x1FFD 0x1F

0x1FFC 0xE0

Write values 0 to 255 to 256 RAM locations starting at 0x0010

0x1FE0 0xA9  LDA #0xFF

0x1FE1 0xFF

0x1FE2 0x85 STA 0x03

0x1FE3 0x03

START:

0x1FE4 0xA2 LDX #0x00

0x1FE5 0x00

WRITE:

0x1FE6 0x8A TXA

0x1FE7 0x9D STA 0x0010,X

0x1FE8 0x10

0x1FE9 0x00

0x1FEA 0xE8 INX

0x1FEB 0xD0 BNE WRITE

0x1FEC 0xF9

Read 256 memory locations from 0x0010 and output to 6522 port A

0x1FED 0xA2 LDX #0x00

0x1FEE 0x00

READ:

0x1FEF 0xBD LDA 0x0010,X

0x1FF0 0x10

0x1FF1 0x00

0x1FF2 0x85 STA 0x01

0x1FF3 0x01

0x1FF4 0xE8 INX

0x1FF5 0xD0 BNE READ

0x1FF6 0xF8

0x1FF7 0xF0 BEQ START

0x1FF8 0xE8

The code is split into two main parts; the first section which fills 256 memory locations with the values 0 to 256 and the second part which outputs the values we wrote to memory to the 6522's port A; these tests repeat continuously. What this test checks is that the RAM can be read from and written to and that the address, data and control lines to the RAM chip are correct.

This is the output that you should see on your logic analyzer:

Port A bit 0 will change the most frequently right down to bit 7 which only changes state once; as you go across the bit values form the numbers 0 to 255. The output of the values to the port A lines lasts about 3ms, then you will see a delay on your logic analyzer while data is written to the RAM again, and then the pattern of port A lines will show up again.

It would be good at this point to see how much current the circuit uses in total and by using a USB voltage/current monitor it reported 430mA. By looking at datasheets I got the following values for some of the main components of the circuit:

>6507 70 ~ 160mA

>W29EE011 25mA

>MT5C6408 ~180mA

>6522 ~130mA

If you are using a more modern version of the 6522 then the current consumption should be a lot lower and because the memory parts are switching on and off at high speed and not all the IC's are on at once that reduces the total current draw.

6507: Increase/decrease value on LED display

Adding on to the previous section we will do a test which lets us increase or decrease a value on a 7-segment display by pressing one of two buttons. This means we will have a user friendly form of output and input, we will test the CPU's stack by use of a number of function calls, we will use one of the 6522's timers to create a delay, and we will debounce the switches.

The circuit diagram follows:

The 7-segment LED display. LD1, is connected to port A of the 6522 (IC3) through a number of limiting resistors (R5-R11). The input side is more complicated as we need to clean the signal from the switches which is known as switch debouncing. Without the switch debouncing the 6522 as in turn the 6507 would see multiple switch presses every time a switch was actually pressed. We do the cleaning up by using two halves of the 556 (which effectively contains two 555 timers) connected as monostable timers. The idea is, when one of the timers is triggered by a switch the output of the monostable will go high briefly before returning low. However, we want a very brief pulse which will likely be shorter than the time it takes to press the switch which will cause the timer to trigger again. We can get around the problem and force the user to release the switch and press it again for the next count by the use of a resistor a capacitor in parallel (R13 and C11; R16 and C13 which creates a small pulse to trigger the timer just once with each switch press.

Here is the code:

Reset vector

0x1FFD 0x1F

0x1FFC 0x00

0x1F00 0xA9 LDA #0xFF

0x1F01 0xFF

0x1F02 0x85 STA 0x03 Set port A to output

0x1F03 0x03

0x1F04 0xA9 LDA #0x00

0x1F05 0x00

0x1F06 0x85 STA 0x02 Set port B to input

0x1F07 0x02

0x1F08 0xA2 LDX #0xff

0x1F09 0xFF

0x1F0A 0x9A TXS Init. stack pointer

0x1F0B 0x85 STA 0x10 Set counter to 0

0x1F0C 0x10

0x1F0D 0xF8 SED Set BCD mode

LOOP:

0x1F0E 0x20 JSR CHECK_BUTTONS

0x1F0F 0x40

0x1F10 0x1F

0x1F11 0x20 JSR UPDATE_DISPLAY

0x1F12 0x30

0x1F13 0x1F

0x1F14 0x20 JSR WAIT

0x1F15 0x60

0x1F16 0x1F

0x1F17 0xB8 CLV

0x1F18 0x50 BVC LOOP

0x1F19 0xF4

UPDATE_DISPLAY:

0x1F30 0xA6 LDX 0x10 Get counter

0x1F31 0x10

0x1F32 0xBD LDA 0x1EE0,x Get 7-segment value

0x1F33 0xE0

0x1F34 0x1E

0x1F35 0x85 STA 0x01 Output to port A

0x1F36 0x01

0x1F37 0x60 RTS

CHECK_BUTTONS:

0x1F40 0x24 BIT 0x00 Test port B

0x1F41 0x00

0x1F42 0x30 BMI DOWN_PRESSED

0x1F43 0x0D

0x1F44 0x70 BVS UP_PRESSED

0x1F45 0x01

0x1F46 0x60 RTS

UP_PRESSED:

0x1F47 0xA5 LDA 0x10 Get counter

0x1F48 0x10

0x1F49 0x18 CLC

0x1F4A 0x69 ADC #0x01 Increase by 1

0x1F4B 0x01

0x1F4C 0x29 AND #0x0f Only need right digit

0x1F4D 0x0F

0x1F4E 0x85 STA 0x10 Update counter

0x1F4F 0x10

0x1F50 0x60 RTS

DOWN_PRESSED:

0x1F51 0xA5 LDA 0x10 Get counter

0x1F52 0x10

0x1F53 0x38 SEC

0x1F54 0xE9 SBC #0x01 Decrease by 1

0x1F55 0x01

0x1F56 0x29 AND #0x0f Only need right digit

0x1F57 0x0F

0x1F58 0x85 STA 0x10 Update counter

0x1F59 0x10

0x1F5A 0x60 RTS

WAIT:

1F60 0xA9 LDA #0x00

1F61 0x00

1F62 0x85 STA 0x0B Set timer mode (one shot)

1F63 0x0B

1F64 0xA9 LDA #0xFF

1F65 0xFF

1F66 0x85 STA 0x08 Set timer low value

1F67 0x08

1F68 0x85 STA 0x09 Set timer high value

1F69 0x09

1F6A 0xA9 LDA #0x20 Timer mask

1F6B 0x20

_WAIT:

1F6C 0x24 BIT 0x0D Time up?

1F6D 0x0D

1F6E 0xF0 BEQ _WAIT

1F6F 0xFC

1F70 0xA5 LDA 0x08 Clear timer 2

1F71 0x08

1F72 0x60 RTS

7-segment look-up table

0X1EE0 0x3F ‘0’

0X1EE1 0x06 ‘1’

0X1EE2 0x5B ‘2’

0X1EE3 0x4F ‘3’

0X1EE4 0x66 ‘4’

0X1EE5 0x6D ‘5’

0X1EE6 0x7D ‘6’

0X1EE7 0x07 ‘7’

0X1EE8 0x7F ‘8’

0X1EE9 0x67 ‘9’

At the start of the program we set up port A as output as we have the display connected to it and port B as input as we have 2 switches connected to it (bit 6 and bit 7); remember that the ports have pull-up resistors so we can leave the unused port lines 'floating'. The stack pointer is set to 0xFF which is the starting point for the stack and the stack will 'grow' as we push items on it such as happens when the return address for a JSR is put on the stack. We also clear our counter (address 0x10) to 0; the counter will be used to remember the current value to show on the display, from 0 to 9.

The instruction SED puts the CPU into BCD mode which is useful for working with single digits such as for displays. In BCD mode, if we decrease a memory location or register containing the value 0 by 1 it will become 0x99, not 0xff. Or if we increase a value with the value 9 by one the result will be 0x10 not 0x0a. If we get rid of the left 4 bits we will have a value that is only ever 0 to 9 but can wrap around. The alternative is to use binary mode and use compare instructions to test for underflow or overflow.

In the main program we call three functions which check for the up and down button pressed (CHECK_BUTTONS), update the 7-segment display (UPDATE_DISPLAY), and wait so we don't respond too quickly to the buttons (WAIT). After the three function calls we loop around but because there is no branch always instruction we branch on the condition that the overflow flag was cleared, using CLV.

To respond to user input, which is just the up and down buttons, the function CHECK_BUTTONS is called, which is the longest of the function calls. The reason why the buttons are connected to bit 6 and 7 of the 6522's port B is that the BIT instruction will set the minus and overflow flags to the same state as bits 6 and 7 of the memory location (port B), regardless of what value is in the accumulator. The we can branch depending on whether the minus or overflow flags are set and increase or decrease our counter values as needed.

The UPDATE_DISPLAY routine simply uses the counter stored at memory location 0x10 as an index into the 7-segment look-up table that starts at address 0X1EE0. So if, for example, the current counter value is 0 the 7-segment value will be loaded from address 0X1EE0. Each value in the look-up table is simply the combination of bit values representing each segment.The value loaded from the look-up table is then copied to the 6522's port A which will update the display immediately.

Lastly, the wait routine makes use of the 6522's timer 2 in one shot mode to wait its maximum value which is about 66ms which is done by putting 0xff in the low and high timer memory addresses. When we write the high value the timer starts counting down and we use a bit test against a mask value to see if the 6522 actually timed out. While using the 6522 datasheet as reference I mistook the register numbers as hex only realising later my mistake and corrected the code. Fortunately, because the display was only ever increasing or decreasing by one just once I could see that the program was getting stuck at the timer which led me to find the problem.

When you power up the circuit you should briefly see '8' on the display which is then quickly replaced by a '0'; because the 6522 ports start up as inputs which are pulled up they consequently turn on all display segments. Pressing the up switch should cause the number displayed to increase by 1 and when 9 is reached be replaced with 0. The down button should lower the value by 1 and when 0 is reached 9 should then appear. If the numbers do not change when either of the switches are pressed then check your wiring, especially the 556 (IC8).

The greatest difficulty I had with this test was the timing as the code waits about 66ms so the 556 timer outputs should go high for at least that amount and with the values I chose you should get a delay of about 110ms but due to component tolerances I measured 87ms. What I found was the responsiveness of the switches changed depending on when they were pressed and it was hard to get the timing just right for every time a switch was pressed. A longer duration for the monostables would probably help but it cannot be too long that the 6507 detects the input lines multiple times for each switch press.

6507: Increase/decrease value on LED display V2

In version 2 we will use a full address decoder by making use of a programmable logic device (PLD) and we will also make use of a technique called Memory banking (a.k.a. bank switching) to be able to access more than 8KB of memory; these will both be explained in detail in the following sections. This iteration of the project proved to be the most difficult as I ran into a number of problems but thanks to my purchase of a 32 channel logic analyzer, the Leaptronix LA-100P, I was able to diagnose and fix the computer circuit. To read more about the logic analyzer please see:

https://sites.google.com/site/jamesskingdom/Home/electronics-by-james-s/tools-for-working-with-electronics#TOC-Leaptronix-LA-100P-Logic-Analyser-

You can read about the issues I encountered as you read on.

Memory Bank Switching

Memory bank switching that lets us divide the RAM and ROM into equal areas called banks and switch them in as needed and we can make full use of the 6507's 8KB address space. This will effectively give us more than 8KB but as far as the CPU is concerned there is only ever 8KB maximum available for it to use. Rather than switch in different parts of the same chip we could however switch in different chips so that is something you may want to consider for your own design.

The memory arrangement is made clear in this image:

The memory map on the left shows the entire 8KB as seen but the CPU, broken up into RAM and ROM sections. The upper 4KB is dedicated to ROM, split up into a fixed 2KB bank and a switchable 2KB bank. Note that the reset vector (and the reset code) has to be located in the fixed 2KB bank as at start up we will not know which switchable bank has been selected (although we could set it in hardware using the reset signal). Another reason for having a fixed ROM bank is that with code running in a switchable bank we cannot change banks without the CPU getting stuck, unless we had multiple switchable banks. The lower 4KB is made up of RAM with 2KB of switchable RAM and 2KB of fixed RAM, however, between the fixed RAM is the 6522 I/O, the banking memory selection and the CPU stack, all of which need to always be available.

In the middle of the image above is the ROM memory map which has on the left how the ROM sees the addresses and on the right is how the CPU sees the addresses. By changing A11 and A12 of the address bus we can convert the addresses the CPU outputs (on the right) to different addresses that the ROM sees (addresses on the left), allowing us to select different parts of the ROM. Although the ROM is actually 128KB we treat is as just 8KB to simplify the design. A similar approach is used with the RAM to handle banking, as seen in the memory map on the right (above) so that we can switch in any of the RAM banks as needed. This does mean that we will be using addresses which will have different meaning depending on which banks have been switched in.

While we can use 74' or 4000 series logic for address decoding and memory banking we soon find that many chips are needed and it becomes difficult to change the design after soldering. Fortunately, there are PLD (Programmable Logic Device) IC's that contain programmable logic that can simulate many logic gates and be altered as needed. However, many modern PLD's are surface mount devices and thus are not as friendly as DIP packages and especially at breadboard stage an adapter would be needed, which can be expensive. The ATF2500C is a PLD available in DIP package and supported by the Wincupl software but a suitable programmer can set you back serious money.

After much searching I got put off buying a new PLD and looked at PLD's I already had, one of which is a GAL16V8B, a Generic Array Logic chip. Not only is the GAL programmable using the TL866 programmer that I was already using in this project but it is also supported by ispLEVER, downloadable from:

http://www.latticesemi.com/ispleverclassic

This will not be a tutorial for ispLEVER so you will need read up about it yourself but it is quite straightforward to use and you will find useful tips about using it in this section.

Note that by default ispLEVER hides GAL devices so to make them selectable you must select the check box 'Show obsolete devices' in the Device selector window. There is no GAL16V8B option but there is GAL16V8D which seems to work the same; I don't know the exact differences based on the last letter but the B version does has built-in active pull-ups whereas the A version supposedly doesn't. It is a good idea to select '20PDIP' in the 'Package type:' drop-down so that the chip report shows the correct pinout for the DIP version.

The GAL16V8B has 10 dedicated inputs, 8 I/O pins, propagation delay of 15ns (the speed is the last 2 numbers of the full part number), and can be programmed just 100 times, which is very low by today's standards. When I first tested my GAL16V8B using the TL866 programmer I was able to read from it and it appeared to show a design. The chip is from 1992 based on the datasheet and it is claimed the design will be retained for 20+ years so they got that right. Anyway, I programmed the chip with a design from ispLEVER using the JEDEC file (.JED) it produces but reading it back or verifying it did not work. I found that to successfully program the IC using the TL866 I had to uncheck the 'Encrypt ch' box before programming.

I will present the memory banking diagram that I created using ispLEVER so that you can understand how it works and use it for your own PLD but I will not attach the project; if someone does want it can I can email it to them.

The design is split into 3 main parts; the ROM section, the RAM section and the ROM/RAM select. Both the ROM and the RAM have two flip-flops each which set the state of the ROM/RAM's A11 and A12 address lines when switchable memory is being used. If fixed memory is being addressed, however, the RAM/ROM receive the A11 and A12 signals from the CPU unaffected; this routing is handled by the multiplexers. As we never have ROM and RAM selected at the same time the ROM_CS input picks whether the A11 and A12 signals come from the RAM or ROM section.

To write to the flip-flops the GAL is selected by the address decoder (signal LATCH EN), the CPU places the memory selection values on the data bus (D0 & D1 for ROM selection, D2 & D3 for RAM selection), and a clock signal to R/W latches the values into the flip-flops. A limitation of the GAL is that you cannot gate a clock input to a flip-flop which went against my idea of using the CPU's R/W signal for the flip-flop clock which I would have controlled using LATCH_EN. So instead, the GAL R/W continually receives the CPU's clock and when the GAL is selected for writing it will latch in the data on the data bus and when the latch is not selected is will just latch back into the flip-flops the current values, so there is no need to gate the flip-flop clocks.

With the design you have to set by the ROM and RAM selections at once as there is no way to read the current flip-flop values. I did think of adding such an ability but it was just as well I didn't as I later found a bug in my address decoder which caused both the ROM and banking GAL to be selected at the same time. On the plus side, writing both ROM and RAM selections together is quicker than reading in the current value, changing it, and writing it back.

The following table lists all the flip-flop combinations for memory selection:

Value (0x) ROM RAM

0                2        F

1                1        F

2                0        F

3                F        F

4                2        0

5                1        0

6                0        0

7                F        0

8                2        1

9                1        1

A                0        1

B                F        1

C                2        2

D                1        2

E                0        2

F                F        2

Where 0/1/2 is the selected bank for the RAM/ROM. Note that a side effect of the design is that the fixed bank can be selected. as represented by an 'F'.

The pinout for the banking GAL will be listed below, so make sure your design matches otherwise you will need to adjust the main circuit diagram (further down this page). It is a good idea to test the GAL on its own using a simulation in ispLEVER before programming the GAL, as well as on breadboard on its own after it's programmed. Note that the pinout may change if you alter your design.

GAL16V8B memory banking pinout

1        R/W                   20    VCC

2        A12                   19    (N/C)

3        A11                   18    (N/C)

4        ROM_CS          17    RAM_ROM_A11

5        D1                    16    RAM_ROM_A12

6        LATCH_EN       15    OE_1

7        D0                    14    OE_2

8        D2                    13    OE_3

9        D3                    12    OE_4

10      GND                 11    (N/C)

For some reason ispLEVER insists on the GAL having output enable inputs (OE_1 to OE_4) but they can just be tied to ground.

Address decoder

The address decoder also makes use of a GAL16V8B, and selects the RAM, ROM, VIA (6522) and banking GAL IC's based on the CPU's current address value (never more than 1 at once). The logic is fairly simple and indeed could be improved but as it is it works and can be seen in the image that follows:

ROM selection is the most straightforward because whether A12 is at logic 1 or not determines if ROM is to be addressed. For RAM and VIA selection I used logic to detect the range of addresses that are needed for them and to disable each other as needed-remember that the chip selections are active low. The banking GAL (LATCH_EN) is a little bit different as it uses just a single address and even so I had forgotten to use A12 to disable the banking GAL when ROM is accessed (fixed in the version you see above). What this meant was when address 0x1010 was read as part of the program code it rewrote the banking flip-flops (remember that the flip-flops are continually being clocked) which altered the address to the effect that the CPU saw the wrong op-code at the last moment and executed the wrong instruction. As already mentioned, the bug has been removed but I may not have seen it had I not by chance used the address 0x1010 for the code.

This is the pinout for the address decoder GAL:

GAL16V8B address decoder pinout

1        A12                   20    VCC

2        A11                   19    RAM_CS

3        A10                   18    LATCH_CS

4        A9                     17    A1

5        A8                     16    ROM_CS

6        A7                     15    VIA_CS

7        A6                     14    A0

8        A5                     13    A4

9        A3                     12    (N/C)

10      GND                  11    A2

The code

Previously, our code let us increase or decrease the value on a 7 segment display by using 2 of the inputs connected to the 6522 VIA. The new code does the same but tests the various ROM and RAM banks that can be switched in. Take a look at the code, which needs to be burned to the ROM (these are absolute addresses):

Reset vector

0x1FFD 0x1F

0x1FFC 0x00

(Fixed ROM bank)

Set up VIA and stack

0x1F00 0xA9 LDA #0xFF

0x1F01 0xFF

0x1F02 0x85 STA 0x03 Set port A to output

0x1F03 0x03

0x1F04 0xA9 LDA #0x00

0x1F05 0x00

0x1F06 0x85 STA 0x02 Set port B to input

0x1F07 0x02

0x1F08 0xA2 LDX #0xff

0x1F09 0xFF

0x1F0A 0x9A TXS Init. stack pointer

Set memory banks

0x1F0B 0xA9 LDA #0x06 Bank 0 (ROM), Bank 0 (RAM)

0x1F0C 0x06

0x1F0D 0x85 STA 0x10 Switch banks

0x1F0E 0x10

0x1F0F 0xA9 LDA #0x00

0x1F10 0x00

0x1F11 0x8D  STA 0x0800 Set display counter to 0

0x1F12 0x00

0X1F13 0x08

0x1F14 0xA9 LDA #0x0A Bank 0 (ROM), Bank 1 (RAM)

0x1F15 0x0A

0x1F16 0x85 STA 0x10 Switch banks

0x1F17 0x10

Copy 7 seg look-up table to RAM

0x1F18 0xA2 LDX #0x00 Set counter start value

0x1F19 0x00

COPY_TABLE:

0x1F1A 0xBD LDA 0x1000,X Get 7 seg value

0x1F1B 0x00

0x1F1C 0x10

0x1F1D 0x9D STA 0x0800,X Copy to RAM

0x1F1E 0x00

0x1F1F 0x08

0x1F20 0xE8 INX Next byte

0x1F21 0xE0 CPX #0x0A Copied all bytes?

0x1F22 0x0A

0x1F23 0xD0 BNE COPY_TABLE

0x1F24 0xF5

0x1F25 0xF8 SED Set BCD mode

LOOP:

0x1F26 0xA9 LDA #0x04 Bank 2 (ROM), Bank 0 (RAM)

0x1F27 0x04

0x1F28 0x85 STA 0x10 Switch banks

0x1F29 0x10

0x1F2A 0x20 JSR CHECK_BUTTONS

0x1F2B 0x00

0x1F2C 0x10

0x1F2D 0xA9 LDA #0x05 Bank 1 (ROM), Bank 0 (RAM)

0x1F2E 0x05

0x1F2F 0x85 STA 0x10 Switch banks

0x1F30 0x10

0x1F31 0x20 JSR UPDATE_DISPLAY

0x1F32 0x00

0x1F33 0x10

0x1F34 0xA9 LDA #0x04 Bank 2 (ROM), Bank 0 (RAM)

0x1F35 0x04

0x1F36 0x85 STA 0x10 Switch banks

0x1F37 0x10

0x1F38 0x20 JSR WAIT

0x1F39 0x60

0x1F3A 0x10

0x1F3B 0xB8 CLV

0x1F3C 0x50 BVC LOOP

0x1F3D 0xE8

(Bank 1 ROM)

UPDATE_DISPLAY:

0x0800 0xA9 LDA #0x05 Bank 1 (ROM), Bank 0 (RAM)

0x0801 0x05

0x0802 0x85 STA 0x10 Switch banks

0x0803 0x10

0x0804 0xAE LDX 0x0800 Get counter

0x0805 0x00

0x0806 0x08

0x0807 0xA9 LDA #0x09 Bank 1 (ROM), Bank 1 (RAM)

0x0808 0x09

0x0809 0x85 STA 0x10 Switch banks

0x080A 0x10

0x080B 0xBD LDA 0x0800,x Get 7-segment value

0x080C 0x00

0x080D 0x08

0x080E 0x85 STA 0x01 Output to port A

0x080F 0x01

0x0810 0x60 RTS

(Bank 2 ROM)

CHECK_BUTTONS:

0x0000 0xA9 LDA #0x04 Bank 2 (ROM), Bank 0 (RAM)

0x0001 0x04

0x0002 0x85 STA 0x10 Switch banks

0x0003 0x10

0x0004 0x24 BIT 0x00 Test port B

0x0005 0x00

0x0006 0x30 BMI DOWN_PRESSED

0x0007 0x0F

0x0008 0x70 BVS UP_PRESSED

0x0009 0x01

0x000A 0x60 RTS

UP_PRESSED:

0x000B 0xAD LDA 0x0800 Get counter

0x000C 0x00

0x000D 0x08

0x000E 0x18 CLC

0x000F 0x69 ADC #0x01 Increase by 1

0x0010 0x01

0x0011 0x29 AND #0x0f Only need right digit

0x0012 0x0F

0x0013 0x8D STA 0x0800 Update counter

0x0014 0x00

0x0015 0x08

0x0016 0x60 RTS

DOWN_PRESSED:

0x0017 0xAD LDA 0x0800 Get counter

0x0018 0x00

0x0019 0x08

0x001A 0x38 SEC

0x001B 0xE9 SBC #0x01 Decrease by 1

0x001C 0x01

0x001D 0x29 AND #0x0f Only need right digit

0x001E 0x0F

0x001F 0x8D STA 0x0800 Update counter

0x0020 0x00

0x0021 0x08

0x0022 0x60 RTS

(Bank 2 ROM)

WAIT:

0x0060 0xA9 LDA #0x00

0x0061 0x00

0x0062 0x85 STA 0x0B Set timer mode (one shot)

0x0063 0x0B

0x0064 0xA9 LDA #0xFF

0x0065 0xFF

0x0066 0x85 STA 0x08 Set timer low value

0x0067 0x08

0x0068 0x85 STA 0x09 Set timer high value

0x0069 0x09

0x006A 0xA9 LDA #0x20 Timer mask

0x006B 0x20

_WAIT:

0x006C 0x24 BIT 0x0D Time up?

0x006D 0x0D

0x006E 0xF0 BEQ _WAIT

0x006F 0xFC

0x0070 0xA5 LDA 0x08 Clear timer 2

0x0071 0x08

0x0072 0x60 RTS

(Bank 0 ROM)

7-segment look-up table

0X1000 0x3F ‘0’

0X1001 0x06 ‘1’

0X1002 0x5B ‘2’

0X1003 0x4F ‘3’

0X1004 0x66 ‘4’

0X1005 0x6D ‘5’

0X1006 0x7D ‘6’

0X1007 0x07 ‘7’

0X1008 0x7F ‘8’

0X1009 0x67 ‘9’

We start with the reset vector which precedes the fixed ROM bank that contains the start-up code which copies the 7-segment look-up table from bank 0 ROM to bank 1 RAM. Notice how we change both the ROM and RAM banks at the same time by writing to address 0x0010. We then enter the main loop which calls three functions; CHECK_BUTTONS (bank 2 ROM), UPDATE_DISPLAY (bank 1 ROM), and WAIT (bank 2 ROM). The only other thing to mention is that the RAM location that remembers the current digit to display is located in bank 0 RAM.

After writing the values to the ROM make sure that they have all been written correctly; when testing I found that the CPU was reading in some values wrong. I checked the ROM again and indeed there were incorrect values; after burning the ROM again the values were now correct.

The circuit

The main changes to the circuit compared to the previous version is the addition of the two GAL chips for address decoding and bank switching, and some extra logic. Note that all of the discrete logic gates are NAND gates for simplicity, with just two gates unused. Take a look at the circuit:

IC9 is the first GAL chip which takes the address lines from the CPU and generates the necessary chip select lines LATCH_EN, ROM_CS, RAM_CS, and VIA_CS. LATCH_EN refers to the memory banking GAL, IC8, which early on in my design was just a couple of discrete latches. IC8 controls the A11 and A12 lines of the RAM (IC6) and ROM (IC2) so that different banks of the chips can be chosen based on the current memory banking settings.

Another problem I had early on was that I could not set the memory banking settings, that is, the flip-flops in IC8 weren't being set correctly. What I found was that while RAM chips allow a 'window' for the data to become valid, that was not the case for the GAL which was much simpler in operation. My workaround was to use both the 2MHz and 1MHz with some simple logic (IC4 gates 1 to 2, both NAND) to generate a clock pulse (banking clk) which would latch the data into IC8 when the CPU's clock is high, which is when the data from the CPU should be valid. Using the 2MHz clock ANDed with the 1MHz clock we can get a signal halfway through the 1MHz high period, right where the actual valid data window starts. Just to be extra sure, however, I added another gate (a NAND gate, so the combination of the two NAND gates becomes an AND gate) whose propagation delay puts the signal even closer to the 6507 requirements.

Even with the banking working I had an odd fault whereby zero would show on the 7-segment display as just the 'e' segment, which has the value 0x10. What was happening, I found, was that the RAM was corrupted (the program copies the look-up table to the RAM) because the 6507 does dummy writes. The fix is to make sure that RAM only ever gets written to when the CPU clock is high, but as already mentioned, RAM is a bit more 'forgiving' so we just need to make sure RW goes low only when the CPU clock is high. This is achieved by using 2 NAND gates (IC4 and IC5, gates 4 and 1) to generate the RW signal for the RAM (IC6) that only goes low if the CPU RW signal is low and the CPU clock is high.

Testing

There should be no difference in operation to the previous version; applying power should cause a '0' to appear on the 7-segment display, and then pressing UP (SW2) should cause the number to increase and pressing DOWN (SW1) should see a decrease in the number on the display. If you do not get such a response you may have to use a logic analyzer to monitor the various signals. If you see an '8' briefly lit on the display which then blanks that is a good sign that the ROM is at least being read since the VIA has been set up. Should the display light up '0' but you cannot get the number to change you can always bypass the 556 and check the two inputs to the VIA directly by taking one input high and the other low; the value should continually change. If you have breadboarded the circuit then look not just for loose connections but also long wires which can be shortened-try to keep wires to below 10cm.

Construction

It's all very well to be able to build the computer on breadboard and that has its own set of challenges. When it comes to soldering the circuit, however, a lot of thought needs to go into soldering the components onto a board. CAD helps a lot but I opted to go with a hand-drawn layout which then changed as I soldered each part as I saw a better way to place the components. I roughly estimated the size of the circuit board based on the size of the modules that I had previously made and I ended up with a more than big enough board; once cut down it measured 52x67 holes. I used the type of prototype board which has solder pads (and is double sided) rather than strips, which I find to be the most flexible. Of course, you can make your own PCB if you like.

It's a good idea to solder each major part in turn and test before moving on to the next part. Check with a multimeter for shorts and continuity, with the chips removed. I've listed the order of the parts that I soldered and tested and hints of what you should check for using a logic analyzer.

POR - reset signal when power connected and when push reset button.

Clock generator/divider - produces 4MHz, 2MHz and 1MHz signals including inverted versions.

CPU - tries to read reset vector address but reads in zeroes as there is no memory.

Misc. logic (74LS00 x2) - generates the control signals for RAM, ROM and banking GAL.

Address decoder - even without the ROM connected you should see the ROM_CS go low when reading the reset vector and RAM_CS when the CPU reads in instructions at low addresses because of there being no memory.

Banking - controls A11 and A12 correctly for RAM and ROM when accessing fixed memory (reset vector).

ROM - CPU executes instructions from the ROM and correctly reads in the 7-segment look-up table when copying to RAM. Because there is no RAM, the CPU won't return correctly from the first function call.

RAM - returns correctly from function calls and reads the counter variable correctly.

VIA - LED display updated correctly and responds to buttons.

If, when powering up the display shows an '8' then that suggests an issue with the ROM. If the display briefly shows an '8' and then blanks that could indicate a problem with the RAM but at least the VIA is being written to as part of the start-up code. Random segments appearing on the display every time you power up is a good sign the ROM and VIA are working but the RAM is playing up. Check over again with your multimeter and use a logic analyzer to see if the ROM is being read correctly and that the RAM is read from and written to with the correct values.

After soldering the ROM socket (I used a ZIF for easy removal for updating the code) I found using my logic analyzer that the data bus was stuck with value 0x04. That suggested to me that D2 was held high so having had just soldered the ROM and banking GAL chip holders one of them was at fault. It turned out that one of the D2 wires had shorted with the +5V line, so a simple fix.

Probably what I found most difficult with soldering the circuit was the address and data lines which are used by a good number of the chips. Routing all those wires, even with a double sided board, was difficult and some of the wires ended up being quite long but without using a proper PCB it will always be challenging with prototype board. It's a good idea to have the wires a bit longer than necessary should they need to be moved slightly or should they need to be removed as you'll end up with lots of wires on each other.

Now for a look at my soldered computer:

I soldered the main parts to the board you see on the right but as I plan to further develop for the computer I kept the switch debouncer separate on the breadboard on the left. On the soldered board I've labelled the various parts and the ports and I have named the computer 'S8BC' which stands for Simple 8-bit Computer. Note that the ZIF socket for the ROM is bigger than was needed simply because it was cheaper.

At the top is the CPU bus header, at the top left the reset signals, below that the clock signals and at the bottom left the VIA connections. I had it in my mind early on that the headers would not only allow for expansion but would also come in very handy for testing. If you like, you could put the board in to some kind of box with the power supply and provide some connectors, such as a DB-25 for the VIA so it becomes a 'user port'. Here is a suggested pinout for the user port (you will find the numbering on the connector):

1 0V            14 PB0

2 PA0          15 PB1

3 PA1          16 PB2

4 PA2          17 PB3

5 PA3          18 PB4

6 PA4          19 PB5

7 PA5          20 PB6

8 PA6          21 PB7

9 PA7          22 CB1

10 CA1        23 CB2

11 CA2        24 0V

12 /IRQ       25 0V

13 +5V

All connections, including the +5V and 0V lines, come from the VIA header. If you do use a DB-25 or similar connector it is a good idea to use a female connector on the computer side as it's easy to short male pins should something metallic make its way into the connector.

6507: simple calculator using a keypad and LED display

By now we have our computer working and we are able to respond to a number of switches and output a value to an LED display. We are going to build on what we did previously but do something a little more useful. By adding a keypad but keeping the 7-segment LED display we will be able to add two numbers or subtract one number from another.

Here is the circuit:

You will see that the LED display, LD1, is still connected to port A of the 6522 (IC3) but we now have a keypad, KPD1, connected to port B of the 6522. You can buy a keypad or like I did, solder up your own; just make sure that it is configured the same as it is in the circuit above. The 12 switches are connected in a matrix form such that one leg of a switch is part of a row and the other leg is part of a column. The way we detect a switch press is by activating each column in turn (PB0-PB2 which are outputs) and for each column we read in the row value (PB4-PB7 which are inputs). So, if for example, if key 2 is pressed it will read in the value 0x10 when PB1 is high. The use of the resistors, R12-R15, make sure that unless a switch is pressed PB4-PB7 will be low. The downside of this technique is that you can get a 'ghosting' effect when multiple keys are pressed, however, for this part of the project we will assume only one key is pressed at once.

Time to look at the code which has changed somewhat from the previous version; as before the code needs to be burned to the ROM using the absolute addresses specified:

Reset vector

0x1FFD 0x1F

0x1FFC 0x00

 

(Fixed ROM bank)

Set up VIA and stack

0x1F00 0xA9 LDA #0xFF

0x1F01 0xFF

0x1F02 0x85 STA 0x03 Set port A to output

0x1F03 0x03

0x1F04 0xA9 LDA #0x0F

0x1F05 0x0F

0x1F06 0x85 STA 0x02 Set port B to output/input

0x1F07 0x02

0x1F08 0xA2 LDX #0xff

0x1F09 0xFF

0x1F0A 0x9A TXS Init. stack pointer

Set memory banks

0x1F0B 0xA9  LDA #0x06   Bank 0 (ROM), Bank 0 (RAM)

0x1F0C 0x06

0x1F0D 0x85  STA 0x10     Switch banks

0x1F0E 0x10

Clear variables to 0

0x1F0F 0xA9  LDA #0x00

0x1F10 0x00

0x1F11 0xA2  LDX #0x00   Set counter start value

0x1F12 0x00

CLEAR_VAR:

0x1F13 0x9D STA 0x0800,X Set variable to 0

0x1F14 0x00

0x1F15 0x08

0x1F16 0xE8  INX               Next byte

0x1F17 0xE0  CPX #0x07 Cleared all bytes?

0x1F18 0x07

0x1F19 0xD0  BNE CLEAR_VAR

0x1F1A 0xF8

0x1F1B 0xA9  LDA #0x0A Bank 0 (ROM), Bank 1 (RAM)

0x1F1C 0x0A

0x1F1D 0x85   STA 0x10     Switch banks

0x1F1E 0x10

Copy 7 seg look-up table and keypad mapping table to RAM

0x1F1F 0xA2  LDX #0x00   Set counter start value

0x1F20 0x00

COPY_TABLE:

0x1F21 0xBD LDA 0x1000,X         Get table value

0x1F22 0x00

0x1F23 0x10

0x1F24 0x9D STA 0x0800,X         Copy to RAM

0x1F25 0x00

0x1F26 0x08

0x1F27 0xE8  INX               Next byte

0x1F28 0xE0  CPX #0x16 Copied all bytes?

0x1F29 0x16

0x1F2A 0xD0  BNE COPY_TABLE

0x1F2B 0xF5

 

LOOP:

0x1F2C 0xA9  LDA #0x04   Bank 2 (ROM), Bank 0 (RAM)

0x1F2D 0x04

0x1F2E 0x85   STA 0x10     Switch banks

0x1F2F 0x10

0x1F30 0x20 JSR CHECK_BUTTONS

0x1F31 0x00

0x1F32 0x10

0x1F33 0xA9  LDA #0x05   Bank 1 (ROM), Bank 0 (RAM)

0x1F34 0x05

0x1F35 0x85   STA 0x10     Switch banks

0x1F36 0x10

0x1F37 0x20 JSR UPDATE_DISPLAY

0x1F38 0x00

0x1F39 0x10

0x1F3A 0xA9  LDA #0x04   Bank 2 (ROM), Bank 0 (RAM)

0x1F3B 0x04

0x1F3C 0x85   STA 0x10     Switch banks

0x1F3D 0x10

0x1F3E 0x20 JSR WAIT

0x1F3F 0xA0

0x1F40 0x10

0x1F41 0xB8 CLV

0x1F42 0x50 BVC LOOP

0x1F43 0xE8

 

(Bank 1 ROM)

UPDATE_DISPLAY:

0x0800 0xA9   LDA #0x05   Bank 1 (ROM), Bank 0 (RAM)

0x0801 0x05

0x0802 0x85   STA 0x10     Switch banks

0x0803 0x10

0x0804 0xAE LDX 0x0800    Get counter

0x0805 0x00

0x0806 0x08

0x0807 0xA9   LDA #0x09   Bank 1 (ROM), Bank 1 (RAM)

0x0808 0x09

0x0809 0x85   STA 0x10     Switch banks

0x080A 0x10

0x080B 0xBD LDA 0x0800,x Get 7-segment value

0x080C 0x00

0x080D 0x08

0x080E 0x85 STA 0x01 Output to port A

0x080F 0x01

0x0810 0x60 RTS

 

(Bank 2 ROM)

CHECK_BUTTONS:

0x0000 0xA9   LDA #0x04   Bank 2 (ROM), Bank 0 (RAM)

0x0001 0x04

0x0002 0x85   STA 0x10     Switch banks

0x0003 0x10

0x0004 0xA9   LDA #0x00   Column offset start value

0x0005 0x00

0x0006 0x8D  STA 0x0802

0x0007 0x02

0x0008 0x08

0x0009 0xA9   LDA #0x01   Start on column 1

0x000A 0x01

0x000B 0x8D  STA 0x0801

0x000C 0x01

0x000D 0x08

 

CHECK_ROWS:

0x000E 0x85   STA 0x00 Output to port B to set column

0x000F 0x00

0x0010 0xA5   LDA 0x00 Read port B to get row

0x0011 0x00

0x0012 0x29 AND #0xF0 Ignore columns

0x0013 0xF0

0x0014 0xF0   BEQ NEXT_COL

0x0015 0x4E

0x0016 0x4A LSR Move to low nibble to use as row index

0x0017 0x4A LSR

0x0018 0x4A LSR

0x0019 0x4A LSR

0x001A 0xC9 CMP #0x04 Row 3 high?

0x001B 0x04

0x001C 0xD0 BNE NOT_ROW_R3

0x001D 0x04

0x001E 0xA9 LDA #0x03 Set index for row 3

0x001F 0x03

0x0020 0xD0 BNE CAL_COL_INDEX

0x0021 0x06

 

NOT_ROW_R3:

0x0022 0xC9 CMP #0x08   Row 4 high?

0x0023 0x08

0x0024 0xD0 BNE CAL_COL_INDEX

0x0025 0x02

0x0026 0xA9 LDA #0x04 Set index for row 4

0x0027 0x04

 

CAL_COL_INDEX:

0x0028 0x18 CLC

0x0029 0x6D ADC 0x0802 Calculate mapping table offset

0x002A 0x02

0x002B 0x08

0x002C 0xAA TAX

0x002D 0xA9  LDA #0x08   Bank 2 (ROM), Bank 1 (RAM)

0x002E 0x08

0x002F 0x85   STA 0x10     Switch banks

0x0030 0x10

0x0031 0xBC  LDY 0x0809,x   Get keypad number value

0x0032 0x09

0x0033 0x08

0x0034 0xA9   LDA #0x04   Bank 2 (ROM), Bank 0 (RAM)

0x0035 0x04

0x0036 0x85   STA 0x10     Switch banks

0x0037 0x10

Was + or - pressed?

0x0038 0xC0 CPY #0x0A Was ‘+’ pressed?

0x0039 0x0A

0x003A 0xF0 BEQ DO_ADD Add both numbers

0x003B 0x41

0x003C 0xC0 CPY #0x0B Was ‘-’ pressed?

0x003D 0x0B

0x003E 0xF0 BEQ DO_SUB Subtract both numbers

0x003F 9x48

A number was pressed

0x0040 0xAD LDA 0x0806 Was a key previously released?

0x0041 0x06

0x0042 0x08

0x0043 0xD0 BNE NO_NUM_UPDATE

0x0044 0x1E

0x0045 0x8C  STY 0x0800 Update counter (display value)

0x0046 0x00

0x0047 0x08

0x0048 0xAD LDA 0x0805 Which number to update

0x0049 0x05

0x004A 0x08

0x004B 0xD0 BNE UPDATE_NUM_2 Update number 2

0x004C 0x06

0x004D 0x8C  STY 0x0803 Update number 1

0x004E 0x03

0x004F 0x08

0x0050 0xB8 CLV

0x0051 0x50 BVC SWITCH_NUM

0x0052 0x03

UPDATE_NUM_2:

0x0053 0x8C  STY 0x0804 Update number 1

0x0054 0x04

0x0055 0x08

SWITCH_NUM:

Change which number to update

0x0056 0xAD LDA 0x0805 Which number to update

0x0057 0x05

0x0058 0x08

0x0059 0x49 EOR #0x01 Toggle bit 1 (toggle 0/1)

0x005A 0x01

0x005B 0x8D STA 0x0805 Remember the next number to update

0x005C 0x05

0x005D 0x08

0x005E 0xA9 LDA #0x01

0x005F 0x01

0x0060 0x8D STA 0x0806 Set flag because a key was pressed

0x0061 0x06

0x0062 0x08

NO_NUM_UPDATE:

0x0063 0x60 RTS

 

NEXT_COL:

0x0064 0xAD LDA 0x0802    Get column offset value

0x0065 0x02

0x0066 0x08

0x0067 0x18 CLC

0x0068 0x69 ADC #0x04   Each column has 4 values

0x0069 0x04

0x006A 0x8D STA 0x0802 Update column offset value

0x006B 0x02

0x006C 0x08

0x006D 0x0E ASL 0x0801   Next column value

0x006E 0x01

0x006F 0x08

0x0070 0xAD LDA 0x0801 Get column value

0x0071 0x01

0x0072 0x08

0x0073 0xC9 CMP #0x08 Checked all columns?

0x0074 0x08

0x0075 0xD0 BNE CHECK_ROWS Check rows

0x0076 0x97

No button has been pressed

0x0077 0xA9  LDA #0x00

0x0078 0x00

0x0079 0x8D STA 0x0806 Remember no key is pressed

0x007A 0x06

0x007B 0x08

0x007C 0x60 RTS

DO_ADD: Add both numbers and display the result

0x007D 0xAD LDA 0x0803 Load number 1

0x007E 0x03

0x007F 0x08

0x0080 0x18 CLC

0x0081 0x6D ADC 0x0804 Add to number 2

0x0082 0x04

0x0083 0x08

0x0084 0x8D STA 0x0800 Display result

0x0085 0x00

0x0086 0x08

0x0087 0x60 RTS

DO_SUB: Subtract both numbers and display the result

0x0088 0xAD LDA 0x0803 Load number 1

0x0089 0x03

0x008A 0x08

0x008B 0x38 SEC

0x008C 0xED SBC 0x0804 Subtract number 2 from number 1

0x008D 0x04

0x008E 0x08

0x008F 0x8D STA 0x0800 Display result

0x0090 0x00

0x0091 0x08

0x0092 0x60 RTS

 

(Bank 2 ROM)

WAIT:

0x00A0 0xA9 LDA #0x00

0x00A1 0x00

0x00A2 0x85 STA 0x0B Set timer mode (one shot)

0x00A3 0x0B

0x00A4 0xA9 LDA #0xFF

0x00A5 0xFF

0x00A6 0x85 STA 0x08 Set timer low value

0x00A7 0x08

0x00A8 0x85 STA 0x09 Set timer high value

0x00A9 0x09

0x00AA 0xA9 LDA #0x20 Timer mask

0x00AB 0x20

_WAIT:

0x00AC 0x24 BIT 0x0D Time up?

0x00AD 0x0D

0x00AE 0xF0 BEQ _WAIT

0x00AF 0xFC

0x00B0 0xA5 LDA 0x08 Clear timer 2

0x00B1 0x08

0x00B2 0x60 RTS

 

(Bank 0 ROM)

7-segment look-up table

0X1000 0x3F ‘0’

0X1001 0x06 ‘1’

0X1002 0x5B ‘2’

0X1003 0x4F ‘3’

0X1004 0x66 ‘4’

0X1005 0x6D ‘5’

0X1006 0x7D ‘6’

0X1007 0x07 ‘7’

0X1008 0x7F ‘8’

0X1009 0x67 ‘9’

 

Keypad mapping table

0X100A 0x03

0X100B 0x06

0X100C 0x09

0X100D 0x0A Special key 1

0X100E 0x02

0X100F 0x05

0X1010 0x08

0X1011 0x00

0X1012 0x01

0X1013 0x04

0X1014 0x07

0X1015 0x0B Special key 2

Starting with the init. code, at address 0x1F0F onward we clear a number of variables in RAM bank 0 using a simple loop. The variables used by the code are:

0x0800 Value to put on LED display.

0x0801 Current column bit value (1, 2. or 4).

0x0802 Current column offset (0, 4, or 8).

0x0803 First value to add/subtract.

0x0804 Second value to add/subtract.

0x0805 Which number to update (0=first number; 1=second number).

0x0806 Is a key currently pressed? (0=no key pressed; 1=key pressed.)

Also, during init., we copy to RAM (bank 1) a keypad mapping table as well as the 7-segment table as before; the mapping table maps the key presses to actual numbers (0 to 9) and operators (+/-). This way we can modify the mapping table to suit the keypad being used without having to change the keypad wiring. Lastly, the only other difference in the startup code is that port B has the column connections set to outputs and the row connections set to inputs, and we do not enter BCD mode.

The main loop is the same as before although the WAIT routine has moved further down in memory as CHECK_BUTTONS is now much longer; UPDATE_DISPLAY and WAIT have stayed the same. In the CHECK_BUTTONS routine we start off by setting up 2 column values; one that will be written directly to the 6522's port B to enable a column (0x0801) and another which will be used as an offset for the mapping table (0x0802).

For each column (port B bits 0-2) we read in the current row value (port B bits 4 to 7) - by making use of bit 3 also we could read a 4 x 4 keypad. To check a row we read in port B and AND the value with 0xF0 so we ignore the column value, and if the result is 0 we try the next column (NEXT_COL). If the value is non-zero we shift it right 4 times so instead of having a value from 0x10 to 0x80 we now have a value from 0x01 to 0x08, After the shifting, if the value is 0x01 or 0x02 then it can be used as an offset but if the value is 0x04 we change it to 0x03 or if the value is 0x08 it is modified to 0x04.

Next, at CAL_COL_INDEX, we add the row offset value to 0x0802 (column offset) to create the final offset for the mapping table that was copied to memory location 0x0810 but you will see at address 0x0031 we use 0x0809 as the base address because the minimum offset value is 1 (that is, column 1 which has an offset of 0 and key '3' pressed which has the value of 0x01). Then it is a matter of loading the key value from the mapping table and then checking whether it is a number value (0 to 9), or an operator add (value 10) or subtract (value 11).

If we have detected a numerical key then we see if a switch was previously released which is done by checking the flag at 0x0806. This flag is cleared to 0 if no key is pressed and set to 1 if a button is pressed. The flag effectively acts as a switch debouncer as you have to release a button before pressing another for it to be registered otherwise the same value would be used for both numbers as you would not be able to release the switch quick enough. Then it is a matter of copying the key value to the display variable (0x0800) so it is visible on the LED display and we also copy the value to either the number 1 variable (0x0803) or the second number variable (0x0804), The variable at address 0x0805 remembers which number to update and we simply toggle between 0 and 1 which represent number 1 and 2 respectively by using the EOR (Exclusive-OR) function (address 0x0059).

From address 0x0038 onward we check to see if the '+' button is pressed which causes a jump to DO_ADD, or if the '-' button was pressed which triggers a jump to DO_SUB. Both DO_ADD and DO_SUB are very similar; they load number 1 from 0x0803 and either add or subtract number 2 (0x0804) and put the result in 0x0800 so the result is seen on the display. Because we do not use a carry when adding we clear the carry flag using CLC but when subtracting, the carry is set by using SEC.

To test, power on the system and you should see a zero illuminated on the display but if you get something else, such as an '8' or a blank display check you entered the code correctly, or if you see another number ensure that the pull-down resistors for the keypad are working. If you have the zero showing, press each of the number buttons and you should see the corresponding number displayed. When you press the '+' key you should see the result of the last two numbers entered added up and if you press '-' you should get the subtraction result displayed (remember that the operations are on the 2 stored numbers so one operation doesn't affect another). Because only numbers 0-9 can be displayed, if the result is less than 0 or greater than 9 you will see an odd character displayed. This is due to the result causing an address to be calculated outside the 7-segment look-up table. You could extend the table to include hex characters or add code to indicate an overflow/underflow condition.

Moving forward

As simple as it may be we have a working computer and we can continue to create software and hardware for it which in turn will teach us more about computing. Two of the major qualities missing from our computer are sound and graphics and while sound is quite straightforward to add, driving a TV or monitor is a lot more difficult. This will be tackled in a new project in which we will build our own game console which you can find at:

Create your own game console

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