In this post I describe the hardware design of the TGL-6502 emulator board. The full schematic is available in the GitHub repository (PDF), it might be worth opening it in another window for reference while reading this post.
I started this project as a challenge to see how much I could get out of the 8 pin DIP version of LPC810 microcontroller from NXP. This is one of the few ARM based controllers available in DIP format (the only other one I know of is also from NXP - the LPC1114.
Some time ago I bought a Adafruit LPC810 Starter Kit and then later a handful of discrete LPC810 chips to fill up an unrelated order. Late last year there was a small flood of 8 bit emulator projects on G+ so I thought it would be an interesting challenge to try and implement a usable 6502 emulator on the chip.
I had a few simple design goals in mind when I started:
- It had to be usable. This means it needed sufficient RAM and ROM to run more than basic demos on the emulated processor and needed to run fast enough to be usable interactively.
- Console IO through a serial port using a simple USB to serial adapter.
- Some basic IO was required - blinky lights and buttons on the front case that could be controlled by the emulated processor.
- There had to be some room for expansion so extra hardware functionality could be plugged in later without redesigning the board.
The resulting hardware design provides all of this functionality.
The LPC810 is a pretty decent microcontroller - a 30MHz ARM Cortex-M0+ with 4K of flash and 1K of RAM. The low pin count and limited flash size make it a bit more challenging to work with but the integrated peripherals and decent clock speed make up for that to some extent.
The use of a switch matrix to assign peripheral pin functions to specific physical pins on the chip (or leaving them disconnected altogether) is another useful feature - this can make it easier to design a PCB layout as you can select a pin that is more suitable for routing rather than being forced to use a specific one. The ability to leave a function disconnected from any physical pin also came in very useful.
Another benefit of the chip is that you don't need any special hardware to program it. The NXP chips come with a built in serial bootloader, on RESET the bootloader is started and if the bootloader entry pin (PIO_01, Pin 5) is held low the serial port is initialised and the chip will enter bootloader mode. If the bootloader entry pin is held high or left floating the user code will be started instead. The TGL-6502 has a jumper on the board to enable programming mode on start up allowing me to reload the flash without removing the chip from the circuit.
There are only 6 GPIO pins available for use, two of these are needed for the serial interface which leaves 4 for general IO. Because flash and RAM on the chip are so limited I was going to need external RAM and EEPROM chips to provide memory space for the emulator and an IO expander to provide additional GPIO pins.
This meant I could use either I2C (2 pins for the I2C bus and 2 pins for general IO) or SPI (3 pins for the bus and a single slave select pin - all general IO would have to go through the IO expander). I2C is a lot slower than SPI which would negatively impact the speed of the emulator and I found that RAM chips with an I2C interface are a lot smaller than those with SPI so SPI became the bus of choice.
I2C and SPI are methods of interfacing with peripherial ICs over a synchronous serial connection. Although slower than a parallel digital connection these methods minimise the pin count required for the interface and allow multiple chips to share the same serial bus.
On the I2C bus each chip has a unique address which is sent on the bus to determine the receiver. SPI devices do not have an address, instead each chip has an individual chip select (CS) line to determine which one should be active.
I would need at least 3 SPI devices for the core (RAM, ROM and IO) and it made sense to treat the expansion port as an SPI device as well with it's own select line. The problem was that I only had a single select line available on the CPU itself so I had to figure out a way to generate additional ones.
My solution was to use the processor pin as the select line for an 8 bit SPI IO expander (the Microchip MCP23S08) and use some IO pins on it as the additional select lines. I made the additional select lines active high (set to '1' to select the attached device) and combined them with the master select from the CPU through a NAND gate to generate the active low slave select lines to the memory chips and expansion slot.
You can see how the selection logic works in the image above. To modify the pin outputs on the IO expander we pull the master CS line from the CPU low and perform the SPI transaction to set the required slave select (SEL0 to SEL2) high. When the master CS line goes high again both inputs to the associated NAND gate will be high which will pull the select line to target SPI device (CS0 to CS2) low. The next SPI transaction will be read by the selected device and ignored by the IO expander.
The SPI peripheral module on the LPC810 has a slave select output pin which is automatically pulled low when an SPI transaction occurs. We really don't want this to happen as it would always select the IO expander and not the peripheral device we actually want to talk to. To get around this I use the switch matrix to leave that pin disconnected from the physical pins and use a normal GPIO output to control the select line directly.
Memory is provided by a 23LC1024 1Mbit SRAM chip and a 25AA1025 1Mbit EEPROM. Both of these are 8 pin DIP chips with memory arranged as 128K x 8 bit bytes. Having both chips the same size makes it a little bit easier to implement the firmware even though you are unlikely to use the full 128K of simulated ROM.
This architecture makes memory access very slow unfortunately - to read or write a single byte of memory requires a 3 byte SPI transaction to the IO expander to activate the appropriate select line followed by a 5 byte transaction to the memory chip to read or write the memory location and a final 3 byte transaction to IO expander to deselect the memory chip again, a total of 11 bytes per byte read or written by the 6502. At 10Mhz (the maximum rate of the IO expander) this takes about 9 microseconds or about 113,000 memory operations per second which puts an upper limit on how many instructions per second we can emulate. To put this in perspective a real 6502 can do a memory operation in half a clock cycle, at 1MHz that is 1 microsecond or 9 times faster than what our emulator is capable of.
No retro computer is complete without some blinky lights and a big red button, on the TGL-6502 these are provided on the front panel in the form of 2 LEDs and a push button which are accessed through 3 of the IO expander pins and available to the emulated CPU though a byte in the memory mapped IO area.
The LEDs are driven from the 5V supply through a NPN transistor as a low side switch. The base of each transistor is connected to an output pin on the IO expander through a current limiting resistor - setting the pin high turns on the LED, setting it low turns it off. The button is connected to an input with a pull up resistor - pushing the button pulls the input low.
This design is more defensive than necessary - the IO expander can source 25mA on a single output pin with a maximum of 125mA total for all pins, more than enough to drive two 20mA LEDs. The reason for decoupling them and driving them from the 5V supply is to avoid sudden increase in current demand on the 3.3V regulator - the rest of the components (RAM, EEPROM, IO expander and CPU) draw around 25mA, turning on a single LED nearly doubles that. The regulator I am using should be capable of handling that but better to be safe.
The front panel driver circuitry (transistors and current limiting resistors) is on a small daughter board that attaches to a pin header block on the main PCB. This design choice is left over from my original prototype, in retrospect I would have been better off putting it all on the main PCB and just having pin headers for the LEDs and button - this would allow more flexibility in the case design.
The expansion port is a 9 pin female socket on the main PCB. The port has 3 power lines (5V, 3.3V and ground), the SPI bus connections (MISO, MOSI, SCK and select) and 2 GPIO pins. All signals are 3.3V so any 5V devices will need to include a level shifter to avoid damaging the main board.
Power Regulation and Serial Interface
All the logic on the main board is running at 3.3V, I am using a USB to serial cable much like this one from Adafruit. that provides the the 5V power from the USB port but keeps the signaling at 3.3V so they can be directly connected to the Tx/Rx pins on the LPC810. If you are using a different cable make sure that the signal level is 3.3V, if you connect 5V signals you run a good chance of destroying the LPC810.
I generate the 3.3V level through a MCP1702 LDO regulator. This can deliver up to 250mA at 3.3V - more than enough for this circuit. The rest of the power supply consists of a pin header for the main power switch and a power indicator LED with it's associated current limiting resistor.
The schematic and PCB layout were done with DesignSpark PCB and the project files are available in the GitHub repository. DesignSpark is a free tool but is only available for Windows. The schematic and PCB layout files are also available in PDF format in the hardware/RevB/mfr directory.
The PCB is single sided and can be etched using the toner transfer method or by milling with a CNC machine. If you want to send the board out for production it would probably be best to redesign it as a double sided board - you should be able to reduce the board size considerably then as well.
To make this easy to assemble I have used through hole components throughout the design. As you can see from the photos I also use DIP sockets for all the ICs, this makes it easier to remove the chips if you have to make any fixes to the board due to bad etching or other issues.
I won't relist the BOM here, you can refer to the schematic for a parts list. Everything I've used is readily available though, there are no hard to get or expensive parts in the list - the total component cost should come in under $AU 25 if you have to buy all of them new.
Overall this is not a very complex circuit and it makes good use of the available IO pins and other functionality of the components used. The only 'wasted' component on the board is a spare NAND gate.
As a challenge to get everything working with the LPC810 I am very happy with it and playing with 6502 development is a lot of fun. So much so that I am considering a redesign with more focus on performance - perhaps using a LPC1114 or a PIC32 as the main processor. The extra pins would allow me to get rid of the IO expander and save the overhead of the extra SPI transactions to address the memory and provide a way to provide persistent storage via an SD card interface as standard. The extra memory in those chips would let me implement a caching system to reduce the number of SPI transactions for memory access as well - with those optimisations you could approach the speed of a real 6502 running at 1MHz.
All in all it was a very interesting design challenge that I had a lot of fun with. If you build your own or use it as a basis for your own design please let me know in the comments - I would love to see people using it.