EMMA-2
Last Update: 2023-07-09
> Background
Since I built the original HackBrick Pro (an Apple-1 replica) in spring 2015, I have designed about six more 6502-based systems. All of these systems have been fairly resource limited, normally using 32k RAM and an 8k EEPROM with some miscellaneous I/O ICs scattered around. Most of these systems were built on breadboards, and thus fairly short-lived. In the back of my mind, however, I have been pushing around the idea of building a 65816 (originally 6502) based system that had a memory management system, a lot more than 32k ram, capability of running multiple process threads, hardware interrupt vector remapping, DMA, HDD support, graphics, ...
In May, 2021 I decided to take a week off from using my (modern) computer which usually I spent a good chunk of each day staring at. During the week of no computer, I ran out of things to do (such as organizing the stacks of paper from the semester which had just finished), so I figured that I would work a bit on that "GUI" computer. I already had the datasheets printed for all the common ICs that I was planning on using, so I set to work drawing up schematic sheet for each section that I thought would be useful. At the end of the week, I started the process of transferring the hand-drawn schematics to KiCad so I could do the PCB layout for the system. Throughout the transfer (and routing process), I tried to fix any bugs that were present so that the "REV A" boards could be the only revision needed (with the potential for a few bodge wires, but nothing substantial). By mid August, I was confident that the main logic board was in a state that I could order it, so I whipped up a basic I/O card over the last few days of summer break and ordered everything needed to assemble the system.
> System Architecture
The system is centered around the 65816 CPU that WDC sells. The CPU is a 8/16-bit upgrade to the 6502, meaning that code that runs on the 6502 will run on the 65816. This makes porting old subroutines to the new system significantly easier.
MMU and VM
The CPU also adds an upper 8 address lines (for 24 total address lines) which are called the "bank." These banks provided an easy way to create a memory management unit (MMU) for setting up a virtual memory (VM) system. Each bank is 64k in size and allows a single bank to be allocated for most threads. In addition to the bank address bits, the MMU also takes in 7 bits from the "HWPID" register so that each process can have fully isolated memory from all other processes. To determine which banks of physical ram to map to the virtual bank addresses provided by the CPU, there is two 32k ram ICs that make up the page table and the permissions table. The permissions stored in the table are fed into some logic to generate an ABORT signal to the CPU to prevent the currently executing instruction from modifying any data that it was operating on.
Interrupts
Onboard, there is an interrupt controller which allows for 8 potential interrupt sources and allows for masking of interrupts based on priority. The design is based off this one:
Prioritised Interrupts - 6502.org
The output from the interrupt controller is fed into the CPU's IRQ (maskable interrupt) input.
The NMI (nonmaskable interrupt) input to the CPU is fed from the IRQ line on a 65C22 VIA which is intended to be used as a jiffy timer since the VIA contains a 16 bit timer that can run in free running "auto reload" mode.
The remaining hardware pertaining to interrupts (not including reset) would be the COP vector trap/remapping circuit. The 65816 adds the COP (COProcessor) instruction which is intended to be trapped by some sort of coprocessor in the system, such as an FPU, like on old x86-based systems. As far as I can tell, there are no 65816 coprocessors available, so there is a small state machine onboard that detects when the CPU executes the COP instruction, latches the signature byte, and waits for the CPU to access the COP software interrupt vector. At this point, the circuit remaps the vector address read by the CPU to an index in a lookup table in the main ROM area (actually RAM when the MMU is enabled), allowing direct access to the correct vector without the need for software to find the COP's signature. The intent of this circuit was to provide a simple interface to create system calls. For example, you could run 'COP 0' to call a routine that prints a string to a buffer or 'COP 24' to fork the current process.
I/O Cards
There are 6 I/O card slots available for the user to plug in whatever I/O devices they can come up with. (For example: I/O, sound, video, networking, DMA, disk controller) Each slot is given a specific IRQ priority but the address that each is selected at is user-selectable via the pads on board. This allows each card to be placed in the range $4800-$7fff in $0400-byte increments. Additionally, there is a "sound card" select signal that maps to a 16k block ($0000-$3fff) and a "gpu card" select that maps to a 32k ($8000-$ffff) block. All I/O cards are accessible via bank $ff when the system is in a privileged mode. When the system is in HWPID $00, the I/O at $4000-$7fff is addressable directly in bank $00 to decrease program size and increase access speed.
**General IO Card**
The motherboard itself does not contain any form of IO and as such, to have the computer do anything useful, this card is needed. It provides two RS232 serial ports via the 16750 UARTs and two 20-pin parallel ports via the 65C22 VIAs. Since each of these perpherial ICs has a interrupt request line, there is an interrupt controller (the same design as the one on the motherboard) which feeds the card slot's interrupt request pin. There are jumpers onboard the card to select the priorities of each of the devices. Additionally, there is some "perfboard" area in case I need to add another IO device to the card.
**DISK and RTC Card**
The newest card is the IDE harddisk and RTC card. This allows the system to interface to a 'proper' storage device and have a form of time keeping while the system is off. The disk interface is a bit more complicated than most IDE interfaces for 8-bit computers since I wanted some "hardware acceleration" for accessing sectors on disk. The IDE disk has several registers that can be read or written to from the CPU. These registers are mapped into their own IO memory region. Much like a UART, the IDE disk has a data write and data read register (like the THR/RHR on a 16550). The hardware address mapping circuitry mirrors these data registers across a full 512-byte memory range. This means that the CPU can make use of the MVN/MVP block copy instructions to rapidly copy data to/from the disk (1/7 the CPU's clock rate). Additionally, this could be used to simplify a DMA interface, whenever I get around to designing that system.
As for the RTC, the chip has its own IO memory range. Backup power is supplied via a few-farad supercap that is slowly charged through a resistor-diode circuit when the system has power. There is also a 32K ram socket on the card since the RTC has a backup supply output for keeping the contents of external memories. Unfortunately, the quiescent current, even in sleep, for the RAM I have is so high that the capacitor only lasts about a hour before the memory loses state. As such, the ram is current not populated. The plan was to use the RAM to store boot configuration parameters (such as whether to default to booting off disk or going straight into the BIOS monitor.) The RAM could either be replaced with a form of non-volatile storage or the capacitor could potentially be upgraded to a small secondary lithium cell with a charge controller.
Privilege Modes
The system supports two privilege levels: Privileged and Unprivileged.
- Privileged: Entered when the system is in HWPID $00, in an interrupt service routine (ISR), or the currently executing program has the "D" bit set in its MMU table entry for bank $ff. This allows access to the I/O hardware.
- Unprivileged: Only has direct access to memory and system calls must be used to interact with the rest of the system.
Documentation
There is some documentation available for the system:
REV A Main logic board schematic
REV A IO Card schematic
REV A Disk and RTC Card schematic
General "User Guide" overview of system
> Pictures
> PWQ (Potentially Wondered Questions)
Q: Why discreet logic ICs and not an FPGA? This makes the board so large, lowers the max clock speed, and increases the cost of the overall system.
A: Two reasons for the discrete logic:
- (1) Most of my digital-logic related classes use Verilog for designing circuits, and I thought it would be fun to try to create the computer out of discrete parts with the limitation of only having certain types of gates available.
- (2) Search for (almost) any computer from the 1970s or arcade PCB from the 1980s, and you will find very large boards with tons of ICs on them. This means, of course, that if I was going to build a "retro" computer, I had to go that route.
Q: Is there an EMMA-1?
A: Yes there is! It is a 65C02 based system that has 6 card slots, like the EMMA-2. The main board only contains a power-on-reset monitor, the CPU, and some bus transceivers. The system was planned to be as flexible as physically possible so I could use it as a general purpose workbench computer. I think that it was this lack of definition of the system that allowed me to never create any cards for it (save for a resistor NOP generator with an onboard oscillator, which showed that I could get the 6502 to execute NOPs at up to 32 MHz before I ran out of frequencies to try).
I've added a page about EMMA-1 here:
EMMA-1 Project Page
Q: What does EMMA stand for/mean?
A: The Electronic Machine for Multiple Applications. Originally created for use in the very general-purpose EMMA-1, I kept the name for the new machine since the system still has expandability to pretty much whatever hardware I would want to put into it.
Q: What does the debugging process look like?
A: This is a bit of a tough question. Ideally, there would be no issues with the hardware and only software would need to be debugged. Unfortunately, since none of the hardware was simulated or in any way tested (aside from *thinking* about it) before ordering and building up the boards, there is bound to be a few bugs. The first thing I always do after building up a board is to make sure that the voltages on the board are what I expect. (e.g., VCC is 5V at all points, GND is 0V at all points). From there, I usually write a simple program that is the bare minimum to get some form of IO operational so that the system can tell me its state. In this case, that consisted of writing to one of the 8-bit ports on a VIA on the IO card. I connected a few LEDs to the port and the program simply increments the output register's value at key points in program execution. Whatever number the leds stopped at is approximately the point where there is an issue. (And to further complicate things, all the original code for EMMA-2 was written using HEIPA, an assembler I created, but did not really test, meaning there's another variable to have to simultaneously debug). Anyway, my main friend in the hardware debugging process was, believe it or not, just a simple logic probe. This is a device that changes its beep sound based on what the probed signal is doing. No fancy logic analyzers required. There was one time when I cracked out the two-channel oscillscope to check something but it pretty much just confirmed the logic probe results. Here are a few common faults that I look for in 65xx systems:
- The high bits in the CPU's address bus cycle at 1/7 of the clock rate. Most likely, your CPU is executing repeated BRKs or IRQs. In the case of BRKs, your address decoding is not enabling your code (probably in ROM) at the correct time to be read (since BRK is all 00's). For IRQs, check the IRQB input to the CPU. If it's stuck low all the time, then you either forgot a pullup resistor or one of your prepherial devices is not properly configured (which *might* be a software issue).
- A crystal resonator (especially for UARTS) only occasionally starts up and runs. Double check your IC's datasheet. You may have forgotten the shunt resistor or have the wrong capacitors on the crystal. This happened on the IO card: I was using one manufacturer's datasheet as the programming and electrical reference but using another manufacturer's IC. It turns out that the driver circuits differ enough that a missing 1M shunt resistor makes all the difference (and would have saved months of headache).
- [Generically] some chip is not working! Look at each of the input signals (including VCC AND GND). Do they look like what you would expect? If not, what is generating these signals? Are those circuits receiving the data you would expect? Keep tracing the erronous signal back to its source. Chances are, you made a mistake in your logic equations or schematic. If you don't find a schematic mistake, check for solder bridges, clean off any extra flux on the board, and if all else fails, try pulling the chips from the last one in the chain to the first and see if any have shorted inputs (or shorted outputs). These stuck-at faults are pretty easy to detect with the logic probe since the signal just stays constant. Sometimes, you will have a chip which has a floating output instead of a stuck-at condition. In this case, you either forgot to connect the output enable or your soldering or ESD killed the output driver for that pin (I've done this a few times). Another cause of floating values could be just a poor connection. Does pushing on parts of the circuit cause it to start working? If so, try reflowing the solder joints in that area or if you are on a solderless breadboard, try moving the jumpers over a column (or try an entirely new breadboard). Sometimes, breadboards can be incredibly finicky. In the case of the HackBrick Pro, I just moved the (non-working) design to a soldered perfboard and it started working. Lastly, make sure that *every* chip has a 0.01-0.1uF ceramic capacitor across the power supply pins. This is especially important for CMOS devices since they pull a relatively large transient current during state changes. (See: https://files.zrcn.org/studystuff/power_supply_bypassing.pdf)
- When powering up the system, the serial port loses the first few characters. If your system uses a charge pump device for driving the serial port (EMMA-2 uses a few of these), they typically take up to a second or two to get up to full voltage, especially if they are running near their maximum rated capacitance. Performing a reset on the computer (but not a power cycle) should not lose the first few characters if this is the issue since the charge pump does not need to build up a high voltage. Additionally, some UARTs such as the excellent NS16C2752 require a certain number of clock cycles to perform an internal power-on reset operation. This may take a few seconds with very low oscillator frequencies. (This UART is not used on EMMA, as I did not know about it at the time.)
> Updates
2022-01-28:
- Added IDE disk detection, boot sector loading, and verification of magic numbers.
- Added prompt to ask whether or not to boot off the disk.
- Can now boot off the disk and run code loaded from it!
2022-01-26:
- Added ASCII output to the block eXAMination mode in the BIOS monitor
- Also expanded monitor output to 16 bytes per line (from 8)
- IDE interface seems to be working (direct registers). Tested by peeking/poking values with the monitor.
- Found out that I forgot the pulldown resistors on the IDE address lines, so I patched those in. These are needed in the block transfer mode to hold the address lines at 0 so the data reg is always accessed, regardless of the address. (The buffers go High-Z, allowing the resistors to take over).
2022-01-22:
- Ported the Woz monitor to the system. Once the boot process is done initializing the MMU and reading the RTC, it enters the monitor.
- Also let me test writing to the RTC and setting the time. Looks like that is working.
2022-01-20:
- Reading of the RTC is working! Now displays HW time in boot messages.
- Also added a subroutine which prepends "[ INFO ]" or "[ WARN ]" for system boot messages.
2022-01-19:
- Moved OS out of boot ROM (over the last month).
- Changed the UART software to use IRQs instead of polling, allowing for asynch buffering of text to be sent and received.
- Added 1M shunt resistor across 16C550 UARTs' crystals. The lack was causing the oscillators to sometimes not start.
- Connected /BAUDOUT to RCLK on both 16C550s to give the receiving shift register a clock.
- Connected /DCD to /TXRDY on both 16C550s, allowing the CPU to check if the Tx FIFO is full (since the LSR flag only indicates "not empty").
2022-01-06:
- Changed logic board mounting screws from socket head cap screws to pan head machine screws for lower profile.
2022-01-05:
- Got the Disk/RTC boards in the mail and soldered them together.
2022-11-13:
- Created a small demo monophonic music tracker which uses VIA1's TIMER1 and PB7 for the output
2021-11-12:
- Added memory bank count detection for available memory
2021-09-21:
- Multiple threads are running!
- Manually copied a program into another bank of memory (bank 1), told the MMU to allocate it to HWPID 1, then manually setup the stack.
- NMI is connected to the VIA0 IRQ pin, which is used as a 100Hz jiffy timer. When this fires, bit 0 of HWPID is inverted (switching processes), then returns control to the process in the current memory bank.
2021-09-19:
- With 8 LEDs connected to VIA1 on the I/O card, I can bounce a single LED on back and forth.
2021-09-05:
- Populated logic and I/O boards' sockets with actual ICs.
- Mounted logic board into the box (and it fits!). Currently using socket head cap screws but will change these out in the future.
2021-09-03:
- Sanded and glued the box together today
2021-09-01:
- Cut out the wood box today on the laser (1/4" ply)
- Also 3D printed the board standoffs
2021-08-31:
- Forgot to solder the RAM socket last time and it was on the other side of campus, so did that today.
2021-08-28:
- Soldered the entire main board (took about 3.5 hours - not too bad). It's looking cooler by the day.
2021-08-26:
- Soldered one I/O card - appears that I ordered 11MHz crystals instead of 29.4912 MHz ones. Oops...
2021-08-24:
- Soldered up all daughter boards for adapting SMD parts to the DIP pinouts.
2021-08-23:
- Boards received! (And they are HUGE!) Also got parts.
2021-08-20:
- Got Garth's RAM module in the mail today. He does a really nice job with these. Might have to order a few more...
2021-08-15:
- Ordered PCBs and parts ("FINAL REV A" design)