After I finished with Geremia, I had to pick up another personal project to spend some time soldering and coding. I found out that mixing hardware and software is much more fun than just coding for me.
I am an old guy, and I grew up in the Commodore 64 and ZX Spectrum era of computing. My first computer was a Sinclair ZX 80, shining with its Z80 microprocessor at 3.25 Mhz, 1 Kb of RAM, and a connection to my home TV.
I had a lot of fun and have always been on the Spectrum side of computing.
It was about time to play with the other side of the coin, the 6502 microprocessor.
This is how Abacuc was born—a breadboard computer based on the 65C02 microprocessor. Why Abacuc? It’s a citation from a character that appeared in the “Brancaleone alle crociate” movie. Abacuc was a short man who carried inside a small chest while the armada was traveling to conquer the city of Aurocastro. As I always say, if you want to understand how Italy works, you should look carefully at a few movies. “Brancaleone alle crociate” is one of them. “Un borghese piccolo piccolo”, “Il marchese del Grillo” and “Amici miei” are the other movies.
First steps – Is it feasible?
I looked around for some documentation and reference design. The 6502 was designed by MOS Technology and launched in 1975. Sixteen bits address bits and eight bits data bits with a clock from 1 Mhz to 3 Mhz.
Building a computer with this microprocessor is challenging but not rocket science. Get the microprocessor, connect it to RAM and ROM, put a 6522 Versatile Interface Adapter in place, connect a keyboard and a display, and you are done. Ultimately, this is what Steve Jobs and Steve Wozniak did with the Apple 1. Nevertheless, this is definitely out of my capabilities in terms of electronics. I had to find a different way.
I researched and found that the 6502 microprocessor is still manufactured and sold. I checked online and found a modern version: Western Design Center W65C02. It is available in a 40-pin PDIP package that can easily fit on a breadboard. The most exciting thing was that this new version is a full static core, something the original MOS 6503 was not. This is definitely a big deal to me since I can stop the microprocessor clock at any time without losing the internal state. That’s absolutely cool. I can single-step machine code while running and look around internal data and status without compromising the operations of the microprocessor. It also supports a frequency of up to 14 Mhz.
This would be my next pet project.
I ordered the microprocessor for something less than ten dollars, and while waiting for the delivery, I started reading the Western Design Center W65C02 datasheet. It was a well-written document that gave me a clear idea of how to make it work.
The main issue remains. I don’t know enough about electronics to design a working circuit.
I was sitting in my studio, that sometimes turns into a lab. I was thinking about this when my eyes spotted an old Raspberry Pi Model 2B that I had just recovered from an aging Home Assistant experiment I did in the past before moving it to an Intel Nuc.
Wait! I am good at software, and the RPi has plenty of GPIO ports I can use. It has a decent amount of RAM and runs at 900 MHz.
I could use the RPi to simulate the hardware I could not design. I can read and write the address bus and the data bus, manage interrupts and non-maskable interrupts via software, simulate RAM and ROM, simulate hardware devices, and deal with all the other control signals of the W65C02 via software.
Using a 50-dollar device to run a 10-dollar machine is not the most convenient thing in the world, but it will be a lot of fun.
It could work! (Did you get the citation? Young Frankenstein, 1974)
I returned to the W65C02 datasheet to check how many GPIOs I needed.
Here’s the count:
- 16 bits for the Address Bus
- 8 bits for the Data Bus
- 1 bit for the PWM clock
- 8 bits for SOB, VPB, RDY, IRQB, NMIB, SYNC, RESB and BE
Total: 33 bits. Unfortunately, the Raspberry Pi has only 26 GPIO pins.
I had three MCP23017 16-bit I/O Expander breakout boards from a previous project. Using two of them, I could satisfy the project’s needs. The only problem is that the maximum speed of the MCP 23017 is 1.7 Mhz over the I2C interface. The Apple 1 was running at 1 MHz; there is no big issue here. I could also swap the 23017 with the 23S17, allowing a maximum speed of 10 MHz over the SPI interface.
I must also consider the maximum speed of the I2C bus, plus the software overhead. Ok, this system is not going to be fast.
The W650C02 has been delivered, and I can start putting some wires on the breadboard.
The first design I want to try is to wire the Address Bus, the Data Bus, the RWB pin, and the PHI2 clock.
The RDY, IRQB, NMIB, BE, SOB, and RESB will be forced high, connecting them to 3.3v from the RPi.
I also want some blinking LEDs to show me the content of the Address Bus and the Data Bus. I will use three 10 LEDs LED bars.
The result is a mess of wires, but it should work fine.
Python is the language of choice. I could use the smbus2 library to interface with the microprocessor. C would be much faster, but speed will not be an absolute need for now.
Here’s the basic pseudo-code:
Init the GPIO pins Init the MCP pins of MCP1 Bank A and Bank B Init the MCP pins of MCP2 Bank A and Bank B allocate 65536 bytes of memory and init it to 0x00 set location 0XFFFC to 0x00 set location 0XFFFD to 0x80 load program into memory at location 0x8000 reset W65C02 while true: read RWB pin read addressbus clock_rise if (RWB == 1) # Processor wants to READ read_data = memory[addressbus] write read_data to databus else # Processor wants to WRITE databus = read databus memory[addressbus] = databus clock_fall
This turned out in 200 lines of Python code.
As you may have noticed from the pseudo-code, I am not using the RPi PWM clock. I wanted more control over what was happening.
Surprisingly, it worked after a few adjustments.
This is my first 6502 assembler program that I wrote to test Ababuc:
ROM_START .equ $8000 RESET_VECTOR .equ $fffc .org ROM_START ; Origin address Start: LDX #$00 ; Initialize X register to 0 STX $D000 ; Store the count at memory location 0x0D00 Loop: INX ; Increment X register STX $D000 ; Store the updated count CPX #$01 ; Compare X to 0x01 BCC Loop ; Branch back to Loop if X < 1 JMP Start ; Start over .org RESET_VECTOR .word $8000
I compiled it using the vasm6502_oldstyle assembler by running:
vasm6502_oldstyle -wdc02 -Fbin -dotdir count.s -L count.lst -o count.bin
If you run a “
hexdump -C” on count.bin, this is what you get:
00000000 a2 00 8e 00 d0 e8 8e 00 d0 e0 01 90 f8 4c 00 80 |.............L..| 00000010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| * 00007ff0 00 00 00 00 00 00 00 00 00 00 00 00 00 80 |..............| 00007ffe
The program to load into memory starting at 0x800 is then:
code = [0xa2, 0x00, 0x8e, 0x00, 0xd0, 0xe8, 0x8e, 0x00, 0xd0, 0xe0, 0x01, 0x90, 0xf8, 0x4c, 0x00, 0x80, 0x00]
It is not yet the case to post the Python code. It is rough and does not do all that is needed. I wanted proof of concept to check I was on the right path.
I will make the final design publicly available when it is finished.