This small system has been tested, and it works!
The aim of this page is to show you how to breadboard a computer, step by step, for the purpose of understanding how a computer system works. The result will not be a general-purpose computer suitable for actual work, but one which includes all the essential parts and could be used for control duties. Besides the processor, it will have read-and-write memory, read-only memory, and input-output. The great challenge is avoiding complexity, since computer systems can become very unwieldy when you are dealing with many address and data bus lines. Breadboarding is an added challenge. Therefore, we shall go to lengths to avoid tangles of wiring, but even so will not always succeed.
The microprocessor I have chosen is the 6504. This is essentially the same as the 6502, except that it comes in a 28-pin DIP instead of a 40-pin. Its 13 address lines gives a memory space of 16K, ample for our purposes. If you cannot find a 6504 (or similar), then a 6502 will certainly do, but it will take up more space on your breadboard. It will be very useful for you to obtain a data sheet for the 6502, and an explanation of its programming. A data sheet for the 6522 is also desirable.
You will need a fairly large breadboard, but not necessarily one of the expensive large ones. I used a board with two 63-row units side by side and power busses, which was large enough. There will be a photograph of the system when the film can be developed and scanned. Put the oscillator in the upper right-hand corner, the 74LS138 just below it, then the 6504, then the 6810, and finally the 27C64 ROM. The 6522 VIA goes at the bottom of the left-hand side. The busses go from 6504 to 6810 to 6522 to 27C64. The 5 V regulated power supply should furnish at least 1 A (the system will draw about 300 mA). Use 0.1 μF capacitors liberally to despike the supply, especially near processor and memory chips. The usual miscellaneous components should be available, such as 0.1 μF capacitors, 4.7k resistors (pullups), 330 or 470 ohm resistors (for LED's), and LED's. A few TTL logic chips will be necessary, such as 74LS138, 74LS04, 74LS08, 74LS32 and 74LS00. A logic probe will be a great help, especially one that shows activity.
The desired system will contain the processor (6504), memory (MCM6810) and I/O (6522) as its principal parts. For a practical computer, the program should be in ROM (27C64), so to go this far you will need to be able to program a ROM, which means having available a programmer and a UV eraser. However, I shall try to go as far as possible without the need of such expensive extras.
The 6502 was developed in the early 1970's by engineers who escaped from Motorola to follow their own ideas as MOSTEK. The processor was chosen for the Apple II, ensuring its fame. It is similar to the Motorola 6800 in some respects, and also to the PDP-11, but struck off on an independent path. It was the first processor to be developed specifically for handling high-level data structures (arrays), and which used pipelining in its instruction execution. The number of instructions was purposely reduced, and those that remained had as many features in common as possible, something that was carried further in RISC processors. All this makes the 6502 fun to program and use, and it is still an excellent learning tool, besides being a practical processor for embedded systems.
The 6502 was second-sourced by Synertek Systems Corp., who promoted the processor very well, and also by Rockwell International and NCR.
Motorola insisted that a 16-bit address, such as $240A (the $ will precede a hexadecimal number here) be stored sequentially in memory as 24 0A. However, a processor fetches bytes in sequence, so the 24 would have to be read and stored, then the 0A read, before any processing could be done. Motorola insisted this was the only way it could be done, an attitude not unfamiliar with those familiar with the military. The liberated 6502 engineers stored the address in the logical way, as 0A 24, so that the OA could be fetched and work begun on it, while the second byte was being read. Intel also stores low byte low, high byte high, incidentally. This matter of how a 16-bit quantity ("word") is stored is worth remembering.
A microprocessor includes everything necessary for a computer except for the bulky bits (memory) and application-specific bits (input-output, I/O). Its job is to output addresses sequentially (incrementing, by convention), read the bytes there, and interpret them as instructions, which it then executes. The first byte of an instruction, the opcode, not only identifies the operation, but also the number of associated following bytes that are either addresses or data for the same instruction. These instructions, only binary numbers, form the program--a microprocessor is a program-executing machine. While it is awake, a microprocessor must have a program to execute, or it dies.
The microprocessor does amazingly few things. It works entirely with binary numbers, which are either data, or tell where data is, addresses. It copies data from place to place. It performs binary addition of two data items, and by extension also subtraction, multiplication and division. It does bit-by-bit logical operations, such as AND, OR and EOR (exclusive-OR), complements bits, and shifts them right or left. It can alter the course of its program ("branch") by testing whether a number is zero or not, positive or negative, greater than or less than or equal. The 6502 has 55 or 56 distinctive instructions in all, a relatively small number. No other processor does anything different in principle.
The 6502 has a small number of internal registers, places where binary numbers can be stored. The program counter (PC) is 16 bytes wide; all the rest hold one byte. The stack pointer (S) points to a byte on Page 1, that is, to a byte whose address is from 0100 to 01FF, where the last two digits are supplied by S. When a byte is pushed on the stack, it is written at the address in S, and then S is decremented. Pulling a byte from the stack is done in exactly the reverse order. S is incremented, and the byte there is read. The accumulator (A) holds one of the operands in every operation, and most of the processing instructions refer to it. The index registers X, Y hold auxiliary numbers used in addressing, and can also serve as temporary storage when necessary. There is also the flags register (P), whose contents are used in making comparisons. The programmer works principally with A, X and Y, and does not usually handle the others (P, S, PC) explicitly.
In all 650X processors, the uppermost six bytes of memory are the non-maskable interrupt (NMI), reset, and interrupt (IRQ) vectors, in that order. They should point to the routines used to handle each event. An interrupt, whether NMI or IRQ, begins (if enabled) at the end of the current instruction. The PC (which is pointing to the next instruction) is pushed on the stack, followed by the flags register P. Upon executing RTI ($40), the exact reverse occurs. On a jump to subroutine, JSR, only the PC is pushed on the stack, but it points to the last byte of the JSR, not to the next instruction. Upon RTS ($60), the first thing is to pull the PC and increment it.
The interrupt disable flag, I, is bit 2 in the flags register. Clearing it, with CLI (&58), permits the processor to recognize interrupts. Setting it, with SEI (&78) causes the processor to ignore the state of the IRQ pin. The decimal flag, D, is bit 3 in the flags register. Clearing it, with CLD ($D8) means the processor will do arithmetic normally, but if you set it, with SED ($F8), you will get BCD arithmetic, and, usually, chaos. You must clear the carry flag with CLC ($18) before doing an add, and set it with SEC ($38) before doing a subtraction, since these instructions either add carry, or borrow from carry, automatically. These three flags are the only ones you can both set and clear independently. Opcodes have been given here to give you a feeling for what they are like.
All the programming we need can be done straightforwardly by hand ("machine language"). This has a similar value as the arithmetic learned by children in school, which teaches a familiarity for number just as we want to develop a familiarity for opcodes. For real work, we use an assembler program, just as the pupil uses a calculator, and mostly for the same reason--accuracy. For our purposes, we can document a program by a handwritten listing in three columns: the hex address, the assembly language mnemonic, and the bytes. For example, 0000 JMP 0000 4C 00 00. The next line will start with 0003. The use of an assembler not only is fast and accurate, but it promotes documentation and the construction of a library of routines.
The first step is to see if the microprocessor can be brought to life, and carries out its fundamental duties of reading instructions from sequential memory access and executing them. After the power is connected, consider the pins one by one and see that each input is satisfied by a suitable connection. Some may require GND, while others should be pulled to a high level for normal operation. Those that require being pulled high should be connected to +5 through a 4.7k resistor. This is preferred to direct connection with +5, and will allow the pin to be pulled low (for example, by a pulser) if necessary. In the present case, RES and IRQ are such inputs. At RST, we connect a pushbutton and a 10 μF capacitor. The capacitor holds this pin low for a while when the power is turned on, so the processor can come to life in an orderly manner. If we want to start over from the beginning at any later time, it is only necessary to press the button.
Pin 27, φo IN, can be used in an oscillator circuit, or can be fed with a square wave from an external oscillator. In this case, we use a crystal clock oscillator that produces a good TTL-level square wave. This input can also be fed from the TTL output of a function generator if it is desired to vary the frequency. The 6504 is specified to work with clock frequencies from 1 MHz down to 250 kHz, but will work at much lower frequencies for test purposes.
The data will be hard-wired to the data bus pins, so that the processor always reads the same thing, $EA, for any read cycle. This is the NOP (no operation) opcode for the processor. It is executed by going on to the next instruction by incrementing the address bus by 1. The same instruction will always appear, so the processor will increment through its complete address space, from $0000 to $1FFF, 16 KB. The 6502 will go through 64 KB, since address bus is a full 16 bits, but the 6504 has a 12-bit address bus. When the power is first turned on, or the RESET button is pressed, the processor will begin execution at address $EAEA, and go from there.
Construct the circuit, and turn on the power. Check that the oscillator is working, and look at &phi:2 on the oscilloscope. This is the clock signal that goes high in the second half of the 1 μs bus cycle, and is used to synchronize read and write operations. The R/W signal should always be at logic high, since no writes are made. Each address line should go alternately high and low, the low lines varying the most rapidly, and the high ones the most slowly. If you wish, connect a buffer (like a 74LS05) and LED's to look at the high address lines (say, AB9-AB12). With a 1 MHz clock, all the LED's will appear to be on. Only when the clock is slowed down to something like 10 kHz can you see the LED's do binary counting. The 6504 is not meant to operate at such a low frequency, but it seems to do all right here.
This is called a "free run." It can always be used to check the address circuitry, since the computer goes through all the addresses in sequence. However, we have nothing connected at this point, so it merely demonstrates that the processor is working.
It would be nice to verify that the RESET is taking place properly, and that the processor can execute a more practical program. A suitable short program is a tight loop, where the processor executes a jump instruction that is pointed back to the jump instruction. For the 6504, the jump instruction is JMP ADL ADH, where JMP is the byte $4C, while ADH and ADL are the high and low bytes of the target of the jump, the address of the next instruction. If we have the processor read $4C at location $0000, $00 at location $0001, and $00 at location $0002, it will interpret and execute the jump, fetching the next instruction from location $0000, which is, of course, just the jump instruction again. The processor will spend all its time at these three addresses.
The circuit for the tight loop is shown at the left. The only addition to the free run circuit is one 3-input NOR gate from a 74LS27. Its output is high when the three low address bits are zero, and otherwise zero. When connected as shown, the processor will read $00 except at addresses that are a multiple of 8, where it will read $4C. The $00 is definitely not a NOP, so if the processor ever executes it as an instruction (BRK) we will know it (the R/W line will pulse low, for one thing).
On reset, the processor reads from locations $1FFC and $1FFD (that is, puts these addresses on the address bus and does a read) and interprets the results as the low and high bytes of the location of the first byte of the program. With this circuit, $0000 is read, since neither of these addresses ends in three 0's. The processor puts $0000 in its program counter (PC), and fetches the next instruction byte from there. This is $4C (since the address does end in three 0's), which the processor interprets as a JMP, with two address bytes to follow. These two following bytes are $00, so we are in the loop.
When this circuit is running, check the address pins with a logic probe. All will be found to be low, except AB0 and AB1, which will be pulsing. This indicates that the processor has properly reset, and is in the desired tight loop. The loop is short enough that you can observe any signals that you wish on the oscilloscope. Use the ouput of the NOR gate as a trigger, so the trace begins with the fetch of the JMP instruction. It is easy to watch the address increment as the three bytes are fetched, and also to watch the values on the data inputs.
Our system will not require a great deal of memory. What we want is memory that can be used with a minimum of wiring and accessories. First, we will attach a little read-and-write memory (RAM) and use it for all our purposes. 128 bytes will be sufficient for the final system, with 64 bytes devoted to the stack, and 64 bytes of scratchpad memory. At the moment, we shall also store short test programs in RAM to help get us up and running. This means we will need some way of storing data in the RAM when it is powered up, and then allow the processor to access it. The circuit that does this is called a ROM Emulator, and we will have to build an elementary one.
Choosing the memory chip is not as easy as it might appear, since most memory is now made for general-purpose computers that require megabytes of fast memory. We want static memory (SRAM), which holds its data as long as power is applied without the need for refreshing. We also want CMOS memory that will work with the 6504 without buffering, needs only a +5 V supply, and has an access time of 450 ns or less. Finally, it would be convenient to have an 8-bit wide memory, so that only one package is necessary. You should take advantage of any suitable chip you may have around, for which you can also find the data sheets.
I will use the MCM6810, a 128 x 8 chip designed by Motorola for use with its 6800 series processors. This means it stores 128 bytes at addresses from $00 to $7F. If you put any address on the 7 address pins, then the corresponding data appears on the 8 data pins. The time from a change of address to the appearance of the corresponding data is called the access time of the memory. Actually, it is the maximum access time, since each address has, in general, a different access time depending on where it is in the memory array. An access time of 450 ns was appropriate to processors with a 1 μs cycle time, assuming that the proper address would appear midway in the cycle, and the data would be read or written at the end of the cycle.
Since the data pins are connected to the processor's data bus, they must be put into a high-impedance state at all times except when the processor expects them to drive the bus during a read cycle. There are two control lines for the memory chip, the chip select (CS) and the read-write (RW) lines. When RW is high, data appears when CS is active. The 6810 has six CS pins, four active low and two active high. All must be active to say that CS is active.
When RW is low, CS has a different function. Instead of activating the data outputs, it strobes the data on the data bus into the current address on the trailing edge of its pulse. Different memory chips may work in different ways, but something like this occurs in any case. This happens to be the way the 6810 works.
When the 6810 is connected to the processor in the normal way, the RW line is connected to the R/W signal of the processor, which takes its proper value at the start of the cycle. CS is active during φ2, which is the clock signal produced by the processor that is high during the last half of the bus cycle. If the 6810 were the only thing on the bus (and it will be for our initial test) one can simply use φ2 as CS. In a more general case, CS will combine both φ2 and an address decode depending on the current address output by the processor. Different devices on the bus will have different address decodes.
The circuit for a ROM Emulator is shown above. The pinout of the 6810 is also shown. It is quite simple, with 8 data pins, 7 address pins, RW, and six CS pins. Note that all the CS pins are tied active except for CS0, which becomes the effective CS pin, its active state high. There is a 3PDT switch (or three SPDT switches) that selects between load, when bytes can be loaded into the memory, and run, when the 6810 is connected to the 6504. In the run state, CS is supplied by φ2 and RW by R/W, directly from the 6504. A0-A6 are connected to the low 7 address lines (the others are left unconnected for the moment), and D0-D7 to the 8 data lines. I made this connection by a flat cable with 24-pin DIP plugs on each end, from the ROM Emulator to a place on the system breadboard laid out just as if the 6810 were there. When the actual system is complete, the plug will be removed and the 6810 inserted in its place. No modifications will be required.
In the load state, the flat cable is unplugged, so that the processor pins will not be driven by the values we put on the data and address lines. It would be possible to use two more 74HC541's to isolate the processor when necessary, or to use 1-of-2 data selectors, but this would introduce still more complication. Be sure the function switch is at "run" before connecting the processor, and that the processor is disconnected before the function switch is moved to "load."
It is very convenient to have hex switches available that can be set in terms of hex digits instead of bit by bit, but you can do it all by putting wires in +5 and ground as appropriate. To load a byte, select the address (and check it with a logic probe). Put the byte on the data lines. Move the read/write switch the write. Now the byte should appear on the LED's, and you can verify that it is correct. Finally, pulse CS high. I used a debounced pushbutton to get a good edge; it is not necessary that it be debounced. Move the read/write switch to read, and set CS high. The LED's should show what is in memory at the current address, which should be what you just loaded. Repeat for as many bytes as you want to load. When you are finished, check each byte again, since it is necessary to be perfectly correct.
When wiring, it is a good idea to ring out each line (test each line for continuity) from processor on the system breadboard, to the 6810 on the ROM breadboard. I thought I was being very careful, checking that the LED's properly showed the hex switch values and corresponded to the proper lines. However, I read the LED's as D7-D0 from left to right, when they were actually connected D0-D7. Nothing worked, of course, until I finally grasped what was the matter. Just keep grinding away, checking and rechecking, and you will finally succeed if your principles are correct. There is no partial credit in computers--not all right is the same as all wrong.
Now we take a momentous step that gives us an actual computer--we add memory to our system. This is done by plugging in the ROM emulator. All the signals still come directly from or to the processor. The RAM should be loaded with a simple test program, and a good one will be the tight loop that we previously did by hard-wiring. Store $4C $00 $00 in locations $00-$02, and $00 in locations $7C and $7D. Note that the reset vector at $FFFC-$FFFD will be read from these locations. For this test, disconnect the RW signal at some point, so that the processor can only read from the emulator. If something should go wrong, and the processor write, the bytes we so carefully loaded may be written over. I know, because this happened to me the first time I turned it on and the processor found $32 (undefined) instead of $4C (JMP) at $00.
Plug in the ROM emulator, and apply power and clock to the processor. Reset if necessary. Now all address lines should be low except A0 and A1, which should pulse. Check with the oscilloscope to see that the addresses cycle as expected.
Unlike the case of the hard-wired tight loop, we can put the loop anywhere in memory. You might try loading $4C $20 $00 at locations $20-$22, and $20 $00 at $7C-$7D. A more ambitious program can also be run, but without I/O, it is difficult to do anything remarkable.
The 6504 has 13 address lines, but so far we have been using only 7. It is possible to divide the 8 KB address space into eight 1 KB blocks, such that whenever the processor emits an address in one of the blocks, a signal called an address decode is available. The circuit is shown at the right, using the very useful 74LS138 decoder. We have eight active-low signals available, each one indicating that the processor has emitted an address pointing to one of the blocks. These signals are active as soon as the address is established, at the beginning of each cycle. The three selector inputs, A, B and C, are connected to A10, A11 and A12. Note that A is the low-order bit, and C the high-order bit. The selected output then goes low. In this circuit, the chip is always enabled, so the propagation delay is at most 41 ns, usually less.
For the final system, the address decodes will be used as shown. The lowest block, $0000-$03FF, will host the RAM. With the 6810, we will use only the lowest 128 bytes, $0000-$007F, however. The second block, $0400-$07FF, will host I/O circuits, specifically a 6522 Versatile Interface Adapter (VIA). The highest block, $1C00-$1FFF, will host the ROM. There are still five unused blocks for expansion. This establishes the memory map of our system.
When running with only the RAM emulating ROM, we arrange that RAM is accessed only for addresses in the 1 KB blocks $0000-$03FF (program) and $1C00-$1FFF (reset vector). Only the lowest 7 bits of the address are effective, since RAM is connected only to them. Thus, $02BA and $1F3A access the same byte in RAM, since the lowest 7 bits are 0111010 in either case. We must combine the decodes for the two blocks so that RAM is selected for either of the blocks. We will use Y0 and Y7, corresponding to the lowest and highest 1 KB. These active-low signals are combined by an AND gate (74HC08), so that the output is low when either input is low (note that this is a negative-logic OR operation). This output can then be provided to one of the active-low chip selects for the 6810, say CS1, while φ2 still is connected to CS0. With this change, the system should operate just as it has done. The reset vector will be loaded using the highest 1 K, while the loop runs in the lowest 1 K.
This is a good place to discuss more address decoding possiblilities. The bus signal R/W distinguishes between read and write cycles. It is valid along with the addresses for the complete cycle, and can be considered an additional address line. We may require address decodes that are qualified as read and write decodes. For example, it makes no sense to write to ROM, so ROM can be enabled only in read cycles, leaving its addresses free for write cycles if this is desired. In our case, we never write to ROM except by accident, so it is not necessary to qualify the decode. If we do write by accident, there will only be a bus contention between the 6504 and the ROM that should have no deleterious effects (outside of clumsiness). For neatness, we could eliminate the possibility by using a read decode for ROM. Ways to create read and write decodes are shown at the left. The enables of the 74LS138 are used appropriately. Note that they can be connected either to R/W or to addresses, whatever is needed to achieve the desired result.
As a simple exercise, how would you get alternate read and write decodes for the blocks from $00 to $0C? The answer is shown for blocks from $10 to $1C.
The bus signals R/W and φ2 often find considerable use, but they can only be loaded by 1 TTL input. In our system, they will not be excessively loaded, so they can be used as they come from the 6504. If the loading is excessive, or we require inverted signals, they must be buffered, as shown in the figure at the right. Each 74LS04 inverter introduces a propagation delay of 15 ns, or 30 ns for two. This is little matter for R/W, but in 6500 systems, the timing of φ2 can be critical (it should not remain high at the end of the cycle) and has to be carefully watched.
The figure shows a way to qualify the decode with R/W using a 74LS32. Something similar very often has to be done, where the LS32 acts like a negative-logic AND gate, so the output is low only when both inputs are low. The ROM decode can be modified in this way, but it requires that we add an LS04 inverter and an LS32 OR gate (there is room on the breadboard). At the right, the decode is qualified by φ2, so that it is active only during the last half of the cycle. Such a signal is called a strobe. Chips that take φ2 do not require strobes, and neither does ROM, which only offers data as needed for someone else to strobe in. The RAM, however, can use a strobe to acquire data at the proper point in the cycle (when φ2 goes low). This strobe can also serve to enable the chip on a read cycle. With the 6810, we use one of the active-high chip selects (CS0) for φ2, and any active low chip select for the decode, so that the qualification is done internally.
At this point, add the decoder 74HC138 and the 74HC08 gate to the system, and verify that it still operates as usual. Write a program that accesses each of the blocks, for example with a STA Absolute instruction, $8D ADL ADH, where ADL and ADH are the low and high bytes of the address, within a loop. This requires loading RAM with 28 bytes using the emulator, a considerable but not impossible job. I have found it very annoying to use a breadboarded emulator, and you may give it up if you have facilities for programming a ROM. When the program is run, the address decodes should occur in the expected order.
There are many different kinds of Input-Output (I/O) circuits and system utilities such as timers, shift registers, analog-to-digital and digital-to-analog converters, keyboards, LED displays, video displays and so on. The purpose of the system is, indeed, to perform some useful sort of I/O. We will begin with a powerful chip, the Versatile Interface Adapter, or VIA. These began as Peripheral Interface Adapters, PIA's, which provided latches for output bits and buffers for input bits, interfacing with the system data bus. There were usually two 8-bit ports that could be configured as inputs or outputs by the program, and perhaps some handshaking lines to control data transfers. These 40-pin packages replaced a number of discrete latches and logic, greatly simplifying interfaces.
The PIA evolved into the VIA by the addition of internal timers and a shift register, with programmability that allows the chip to adapt to different requirements. To the system, the VIA appears like 16 memory locations or registers that can be read or written. The two lowest, $0 and $1, access ports B and A, respectively. There is nothing special about the word "port," which only arises because data is handled in bytes. Each of the 16 bits in the two ports can be separately configured as an input or an output, by writing to locations $2 and $3, the data direction registers DDRB and DDRA. A "1" means an output, a "0" an input. It would have been a better mnemonic the other way round, but this choice is compelled by the fact that RESET makes all bits zero, and it is safer to have the pins acting as inputs, and not active, at this time, before they are configured by software.
The connections for the VIA in our system are shown at the right. The right-hand side of the chip is mostly devoted to the bus interface, with 4 address lines, 8 data lines, two bus signals, the address decode, and the IRQ output. There are two chip select pins, and we choose the one for active-low decodes. The left-hand side contains the peripheral bits. PA0-PA7 and PB0-PB7 are the bits for ports A and B. Port B has active pull-up and will drive Darlington transistors. The others, as outputs, will drive one TTL input. This means that for any serious use, they must be buffered. On RESET, all the peripheral pins come up as high-impedance inputs for safety. Any of the port bits, CA2 and CB2 can be configured as inputs or outputs. CA1 and CB1 are always inputs. I found that a normal NMOS 6522 worked in the system, but a CMOS 65C22 usually did not. The problem is logic levels, certain of which were not high enough to satisfy the 65C22, but I did not investigate further. The diagram shows four logic switches on PA0-PA3 as inputs, and an LED on PB7 as an output. Of course, more can be added if required.
Locations $4 and $5 are the low-order and high-order bytes, respectively, of timer 1. When location $5 is written, the counter starts, decrementing at the rate of φ2. These registers actually change as the count proceeds, as can be determined by reading them on the fly. Locations $6 and $7 contain the bytes that are to be reloaded in locations $4 and $5 when the timer times out, and it is configured to go on counting. They can be written to at any time, and the values will be used the next time the timer times out. Locations $8 and $9 comprise timer 2, but there is no analogue to locations $6 and $7 for timer 2. If you rewrite a timer before it times out (reaches zero) it will continue to count down from there, and can be prevented from timing out if it is rewritten in time. Location $A is the shift register, which we shall not discuss here.
The timers (and shift register) are controlled by the bits in location $B, the Auxiliary Control Register (ACR). If bit 7 is "1" timer 1 will affect the bit PB7, making it go low while it counts, or complementing it when the timer times out, producing a square wave. This use of PB7 overrides its configuration in DDRB. If bit 6 is "0" the timer will issue an interrupt when it times out, then continue counting, wrapping around, so that the time since the interrupt can be determined by reading the counter. If bit 6 is "1" the timer will be reloaded when it times out, issuing an interrupt and complementing PB7. Timer 2's special thing is counting down with pulses applied to PB6. It does this if bit 5 is a "1," otherwise giving continuous timed interrupts. The other bits of the ACR control the shift register, or control latching of values applied to the ports by edges on CA1 and CB1, the control inputs. Latching at port A is enabled by a "1" at bit 0, and at port B by a "1" at bit 1. We will need only to configure timer 1 for continuous interrupts and square wave output on PB7, which means writing $C0 to location $B in the VIA.
All the talk about interrupts should not concern you. Register $E, the Interrupt Enable Register (IER), will contain all zeros on RESET, and this masks all interrupts from the VIA, so the IRQ pin will stay high and not request an interrupt. Reading Register $D, the Interrupt Flag Register (IFR), will tell you what interrupts have been requested. These interrupt facilities are very useful, saving a great deal of trouble, but we shall not require them here.
Register $C, the Peripheral Control Register (PCR), configures the inputs CA1 and CB1, and the input/outputs CA2 and CB2 for their various possible duties, as interrupt request lines, latching lines, and outputs of handshaking information. All zeros in this register sets the control lines up as inputs with a negative active edge.
To use read-only memory, you need facilities for programming and erasing EPROM's. I run a programmer on an IBM 5150 (PC), and have a small UV eraser. The programmer includes a plug-in card, an external ZIF (zero-insertion-force) socket, and a program. You simply edit the program in a buffer, then check the EPROM for complete erasure, and program it, which takes only a few seconds. Then the EPROM is verified against the buffer, and if it passes, you are ready to go. This is much easier and less error-prone than using a rudimentary ROM emulator, but it costs more. If you plan to make embedded systems, however, you will definitely need an EPROM programmer. Commercial stand-alone programmers are ridiculously expensive.
The EPROM I had originally intended to use, the 2716, is a 2k x 8 EPROM, the first of its breed with a single +5 V supply. The real pioneer was the 2708, 1k x 8, with -12, -5 and +5 supplies, which required 25-27 V for programming. The 2716 is just right for small systems, and the 24-pin package is standard and easy to use. Anticipating trouble locating examples, I hoarded a reasonable supply. Unfortunately, my programmer will not program such ancient hardware, with its 25 V programming voltage, so I had to find an alternative. The programmer will stoop to programming a 27C64, however, so I substituted this EPROM. It is an 8k x 8 EPROM, not large by modern standards, but has an address space equal to that of the 6504. Its access time is 300 ns, amply short. The easiest way to use it is to connect all 13 address lines, A0-A12, so that it duplicates the 6504's address space. We will simply use what we want, and let the rest rest. All that is necessary is to connect a decode for any 1 KB block we want to use, which must include block 7, the highest, for the reset vectors. Initially, then, we shall use addresses $1C00-$1FFF, adding more as required. The connections are shown at the left.
The 28-pin package simply adds four pins at the top. The pinout of the lower 24 pins is exactly as with a 2716, except that A11 appears instead of the programming voltage, and pin 26, which was Vcc, is a no-connection. Pin 20 is called a chip enable, CE, but it is really a standby pin. When it is high, the chip is allowed to sleep, drawing only about 100 μA. The chip wakes up when CE is brought low, and now draws about 30 mA. Memory chips are great current hogs. The 6504 itself draws about 140 mA, and the 6810 80 mA. The real chip enable is pin 22, which activates the data outputs. It makes no sense to control CE in our system, since we will be running from the ROM practically all the time.
When erased, all bytes in the EPROM are $FF. I first filled the programmer buffer with $FF, to save the programmer a lot of work. At the top, vectors of $00, $1C were loaded from $1FFA on. This handles all the vectors, although the 6504 has no NMI. If interrupts were to be used, the interrrupt vector at $1FFA would have a different value than the reset vector. Now, beginning at $1C00, the program $A9 $C0 $8D $0B $04 $A9 $FF $8D $04 $04 $8D $05 $04 $8D $00 $08 $4C $0D $1C, 19 bytes, was loaded. This is LDA #C0, STA 040B (programming ACR), LDA #FF, STA 0404, STA 0405 (loading timer 1 with the maximum value), STA 0800 (an arbitrary write to exercise the decode and R/W), JMP 1C0D (do the STA 0800 again). It is a temptation to write a more ambitious program, but it is hard enough to find out what has gone wrong with even a short program if it does not work. As has been said before, there is no need for an assembler for such simple programs, and the exercise is informative.
When the ROM is inserted and the power turned on, the LED connected to PB7 begins to blink rapidly. Actually, starting with $0000 actually gives the longest countdown, but I did not want to take chances, so the blink is a microsecond faster. The blink rate should prove to be about 8 Hz. Using a logic probe, I found that the block 2 ($08) decode was pulsing, as was the R/W line. The addresses were in the proper limited range from $1C0D-$1C12. The RAM has not been tested, but since it samples the address and data busses before the ROM and the VIA, it should also be working. In short, we now have a functioning system with processor, ROM, RAM and I/O to experiment upon. If it does not work the first time, persevere, checking everything out, even starting with a free run if necessary, which will exercise all the decodes. If you have to do this, disconnect the OE from the ROM so it will not fight with your hard-wired $EA. Breadboards are specially subject to wires that have crept out of their holes.
Another procedure is to connect RES to a function generator that will give a periodic reset every millisecond or so. This is plenty of time for the program to run, and you can watch what happens on an oscilloscope. I have a Hitachi V-1050F 100 MHz scope with the best delay system I have seen. It is possible to investigate every cycle from the beginning and find out what is wrong. This method is as powerful as using a logic analyzer, and much cheaper. When everything finally is right, the system will run.
Composed by J. B. Calvert
Created 2 June 2002
Last revised 8 June 2002