⭅ Previous (6502 Wiring) | Next (Emulating Program ROM) ⭆ |
Last time we wired up a rough prototype of the Chiplab for a 6502. Due to limited pins on our atmega based Arduino board, we were unable to interact with all the pins on the 6502. This meant we couldnt see the entire address bus, so only had a narrow view of what the 6502 was doing.
This time we’ll work around this limitation with some extra electronics.
Note, this prototype is built with an Arduino board which is based around an Atmega microcontroller. I’ll usually use “atmega” to refer to the microcontroller we’re using to interface with the 6502.
The 6502 has more pins than atmega chip we’re using to interface with it. Ideally we could use a single pin on the atmega for reading multiple pins off the 6502. And that is exactly what a shift register will allow us to do.
A shift register is sort of like memory for a limited number of bits. They are often used to convert a parallel signal into a serial signal (when reading), or a serial signal into parallel (when writing). A different shift register chip will be needed for each of these scenarios.
We’ll use a parallel to serial shift register to allow reading more pins than we have available on the atmega. This can be more easily understood by looking at the pins on the shift register. I’ve used a SN74HC165 chip. Datasheet here.
__ __
Shift,!Load 1 | V | 16 Supply
Clock 2 | | 15 Clock Inhibit
E 3 | | 14 D
F 4 | | 13 C
G 5 | | 12 B
H 6 | | 11 A
!Q_h 7 | | 10 Serial_In
GND 8 |___| 9 Q_h
The chip will do one of two things, controlled by the first pin. If the signal is logic high voltage (close to supply), it will shift. If it is low, it will load.
The chip manages an 8 bit memory. During a load, it sets the 8 bits based on what is observed on the 8 parallel data pins:
7 6 5 4 3 2 1 0
H G F E D C B A
The shift register then allows this value to be read back, one bit a a time, using the output pin (Q_h). After loading a value into the shift register, we want our atmega / Arduino to set pin 1 high, to request shift instead of loading. Each time the clock goes from low to high, Serial will present us a different value of the value loaded earlier.
With this single shift register, we need 3 IO pins (clock, shift!load, and serial) to read 8 data pins. Definitely an improvement.
A nice thing here though is that as long as we want to read the signals all at
once, we can share the clock
and shift!load
pins across multiple shift
registers.
This shift register is only for reading. Fortunately, the 6502 address pins are
output only, so we will only ever want to read these pins. We can then use two
shift registers to cover the entire 6502 address bus. By sharing the clock
and
shift!load
pins, we only need 4 IO pins on the atmega to read 16 pins off
the 6502.
Here’s the relevant portion of the schematic:
The only pins I have not mentioned yet are the clock inhibit and !out_h. We dont need these for the design, so they’re pulled low for proper functioning.
One shift register is used for the least signficant 8 bits of the address bus, and the other for the most significant 8 bits. We’ll read a pair of bits per cycle, and reconstruct the original 16 bit value in code on the atmega.
Here’s a snippet of what that looks like:
void shift_reset() {
digitalWrite(SHIFT_SHIFT_ELSE_LOAD, 0);
digitalWrite(SHIFT_CLOCK, 0);
shift_delay();
}
void shift_delay() {
delayMicroseconds(1000);
}
void shift_load() {
digitalWrite(SHIFT_SHIFT_ELSE_LOAD, 1);
shift_delay();
digitalWrite(SHIFT_SHIFT_ELSE_LOAD, 0);
shift_delay();
digitalWrite(SHIFT_SHIFT_ELSE_LOAD, 1);
shift_delay();
}
void shift_shift() {
digitalWrite(SHIFT_CLOCK, 0);
shift_delay();
digitalWrite(SHIFT_CLOCK, 1);
shift_delay();
}
uint16_t read_addr() {
// address comes from shift register.
shift_load();
uint16_t low = 0;
uint16_t high = 0;
for (int p = 0; p < 8; p++) {
low <<= 1;
low |= digitalRead(SHIFT_ADDR_LOW_BIT);
high <<= 1;
high |= digitalRead(SHIFT_ADDR_HIGH_BIT);
shift_shift();
}
return (high << 8) | low;
}
The corresponding bits are loaded from the two shift registers at the same time. Since the output is bit H, with the way we have wired the shift registers we’ll get the more significant bits (bit 7 and bit 15) first. The first bit is available immediately, which is why we dont need to toggle the clock first.
We read a pair of bits 8 times, shifting in the new values to the low and high variables. Once we have read the 8 pairs, we can recombine these two 8 bit values to make a 16 bit value corresponding to the 16 bit address bus from the 6502.
Now that we can read the full address bus, lets rerun the reset experiment from last time. Lets make sure the reset is actually working as we suspect. The new output looks like:
resetting...
a=0xC0C2 r=1
a=0xC0C2 r=1
a=0xC0C2 r=1
a=0xC0C2 r=1
a=0xC0C2 r=1
reset.
a=0xC0C2 r=1
a=0xC0C2 r=1
a=0xFFFF r=1
a=0xC0C3 r=1
a=0x1FA r=1
a=0x1F9 r=1
a=0x1F8 r=1
a=0xFFFC r=1 <- Reset vector lower read
a=0xFFFD r=1 <- Reset vector upper read
a=0xC0C0 r=1 <- first read at start address
a=0xC0C1 r=1 <- reading sequentially
a=0xC0C2 r=1
a=0xC0C3 r=1
a=0xC0C4 r=1
Indeed, our setup is resetting the 6502, and we can see it executing starting at our hard-wired reset vector address.
Now that we can read the full address bus, we can meaningfully control the 6502. Next up we’ll emulate an attached rom chip, and feed in a program to observe the behavior of the 6502.
⭅ Previous (6502 Wiring) | Next (Emulating Program ROM) ⭆ |