Homebrew 6502: Part 2

It lives! Adding a temporary clock and the free run

Previously, thanks mostly to dewy-eyed nostalgia, I’d got hold of an 8-bit 6502 CPU and managed to power it up. But that’s about all I’d done, nothing of interest was happening. Time to add a clock signal.

All computers have a clock, a pulse – a digital heartbeat if you like – that triggers the processor to perform each instruction. It requires a good square wave which will be supplied by a crystal can oscillator running at a speedy 1MHz. For now though, I’m going to cheat a little and use an Arduino Nano I have lying around (or rather, a very cheap Chinese Arduino ebay clone) to provide a temporary clock pulse. One of the advantages of these 65C02s from WDC is that they can be clocked extremely slowly or even stopped and still maintain their registers and program state. At the same time as providing a slow clock, I’ll be using this ‘Arduino Nano’ to display the contents of the data bus and address bus. I’ll be able to see exactly what’s happening on the buses on each CPU cycle.

The problem is that with 16 address lines and 8 data lines there isn’t anywhere near enough input pins on the Arduino. So, for now I’ll only be looking at the lower 8 bits of the address bus.

Bits ‘n’ buses

Before I begin, a slight detour as I try to figure out what exactly these address and data buses are. The 6502 has an 8-bit data bus and a 16-bit address bus. This means it can see 64K of address space:

address_bus
16 bits gives us 64K of addressable space
Binary : 0b0 to 0b1111111111111111
Decimal : 0 to 65535
Hex : 0x0000 to 0xFFFF

This address space is taken up by RAM, ROM and any I/O. They are all connected to the same address bus and all share the same address space, although located in specific areas within the same address space.

To access RAM for instance, the CPU will place an address on the address bus, say 0x1000, and either retrieve data from the data bus or put data on the data bus to be written. The 6502 (unlike the Z80 that has separate instructions for IO and Memory) doesn’t know what it’s accessing. It could be RAM, ROM or I/O. It doesn’t care. It’s for us to decide where each component (RAM, ROM or whatever) will be in this address space. This allocation is referred to as the memory map. Usually, RAM is at the lower end (the CPU does expect RAM at 0x0000 to 0x01FF for storing registers and the stack) and ROM at the higher end (up to 0xFFFF) with various Memory mapped ‘stuff’ in between.

There will need to be some address decoding logic to enable or disable the relevant IC depending on the value on the address bus. This is to make sure that the CPU is accessing the correct component as per the memory map.

But I’m getting ahead of myself. Back to the cheaty clock.

Slow, 1Hz(?) clock

I’ve decided I’m going to use an Arduino to generate the clock. The idea being that I’ll be able to slowly step through each CPU cycle, displaying the contents (well, the lower 8 bits for now) of the address bus as I go. Here’s the breadboard circuit for that:

arduino_pulse

I’m sending a clock pulse (the yellow wire) out on A7 to the 6502’s Clock in, pin 37, every second. Pins 9 to 16 on the 6502 (A0 to A7, the low 8 bits of the address bus) are connected directly to 8 input pins on the Arduino. Code on the Arduino then reads the values on these 8 pins and converts them into an 8-bit number and displays it to the terminal.

Here’s the Arduino sketch :


// Pulse A7 and read 8 bit value from 6502's address bus
#define CLOCK A7
#define ADDRA 2
#define ADDRB 3
#define ADDRC 4
#define ADDRD 5
#define ADDRE 6
#define ADDRF 7
#define ADDRG 8
#define ADDRH 9

void setup() {
 pinMode(CLOCK, OUTPUT);
 digitalWrite(CLOCK, LOW);
 Serial.begin(9600);
}

void loop() {
delay(1000);
// send clock output high
digitalWrite(CLOCK, HIGH);

// read value on lower half of address bus
byte a =  digitalRead(ADDRA);
byte b =  digitalRead(ADDRB);
byte c =  digitalRead(ADDRC);
byte d =  digitalRead(ADDRD);
byte e =  digitalRead(ADDRE);
byte f =  digitalRead(ADDRF);
byte g =  digitalRead(ADDRG);
byte h =  digitalRead(ADDRH);

byte address_value  = h << 7;
address_value  += g << 6;
address_value  += f << 5;
address_value  += e << 4;
address_value  += d << 3;
address_value  += c << 2;
address_value  += b << 1;
address_value  += a;

// display value

Serial.print("Lower address bus : ");
Serial.print(h);
Serial.print(g);
Serial.print(f);
Serial.print(e);
Serial.print(d);
Serial.print(c);
Serial.print(b);
Serial.print(a);
Serial.print(" : 0x");
Serial.print(address_value, HEX);
Serial.println();
digitalWrite(CLOCK, LOW);
}

Apparently what should happen is that when the 6502 is reset (button pushed) the first thing it does is go and look at the reset vector, an area of memory at 0xFFFC - 0xFFFD (usually ROM) that holds the start address. The 6502 places 0xFFFC on its address bus and retrieves the value stored there via the data bus. It then does the same for location 0xFFFD. The two resulting bytes are combined into a start address which it happily jumps to. If 0xFFFC contains 0x00 and 0xFFFD, 0xF0 the CPU would jump to address 0xF000 on reset. By the way, this is low byte first or 'little endian'.

I pushed the button, I'm expecting 0xFC and 0xFD (as I'm looking at only the lower half the address bus) to appear on the serial monitor.

No, junk.

Further reading revealed that it takes 7 cycles before the CPU gets around to looking at the reset vector at 0xFFFC/D. I waited.

wow

Success!

I've tried it several times just to make sure it isn't just random junk, but each time, 0xFC and 0xFD appears on the 7th clock.

What now, let's put something on the data bus!

The free run

Every homebrew computer builders’ initiation is the CPU Free Run. By hard wiring the data bus to a set value the CPU can be forced to execute the same instruction, usually the NOP instruction, over and over. All the CPU will be doing is incrementing the program counter, fetching an instruction from the data bus and executing it. It will run through all address space, wrap around and carry on, forever. The value needed on the data bus is important. The NOP instruction is a do nothing command or NO OPeration and on the 6502 it equates to 0xEA in hex or 11101010 in binary. Here's the updated breadboard with our NOP / 11101010 hardwired to the 8 data pins.

arduino_hardwired

So what should happen when this is started? With 0xEA permanently fixed on the data bus the byte returned from the reset vector at address 0xFFFC will be 0xEA and from 0xFFFD another 0xEA, the start address will therefore be 0xEAEA. The fact that there's no RAM or ROM or anything physically attached at 0xEAEA doesn't matter. When the CPU looks at this address to get an instruction from the data bus, you guessed it, it'll get another 0xEA. It will execute this instruction (NOP, nothing happens), add one to the program counter and goes off to 0xEAEB. Guess what? It gets another 0xEA, executes it, adds one to the program counter... etc. etc. forever and ever.

freerun

Genuinely excited to see reset vector 0xFC, 0xFD and then the hard wired 0xEA appear on the address bus! You can see the byte of the address bus is duplicated this is because its using one cycle to actually execute the NOP instruction before it fetches the next address. It's working perfectly!

I celebrated by adding a little OLED screen I had lying around. I can now view the address bus directly without the serial monitor on my PC.
22412595257_c5cf1ff09e_o

Next, I need to point the bus to some ROM or RAM… ideally both but let's not get carried away.

More homebrew 6502

Part 1 : 8-bit Nostalgia and powering up the 6502

Part 2 : It lives! Adding a temporary clock and the free run

Part 3 : Memory map, address decoding and adding a ROM

Part 4 : Building an EEPROM programmer

Part 5 : More address decoding, adding RAM

Leave a Reply

Your email address will not be published. Required fields are marked *