Plus Too
I’ve been looking further into the idea of building a working hardware clone of a classic Macintosh, and I think I can do it. I’ve decided to target the Macintosh Plus for hardware replication, and am tentatively calling this project the Plus Too. (Other name ideas: RePlus, Replimac, BMOW++.) Inspired by projects like the Replica 1, Minimig, and C-One, the goal is to build a functionally identical copy of the original Mac, but using modern hardware parts. Such a system would be a true hardware clone rather than an emulator. In essence, it would be a new model of Macintosh.
Why the Mac Plus, instead of the very first Macintosh model, the Mac 128K? It turns out that the hardware in the Mac 128K, 512K, 512Ke, and Plus is virtually identical. That means it’s possible for the Plus Too to replicate any of those machines by setting a few configuration switches.
- The 128K and 512K differ only in the amount of installed RAM.
- The 512Ke and Plus differ only in the amount of installed RAM, and the presence of a SCSI port on the Plus. Assuming I choose not to replicate the SCSI port, then the only difference is RAM.
- The first set of twins differ from the second only in their ROM images, and the use of a 400K/800K floppy drive on the 512Ke/Plus instead of the original 400K-only drive. If you put a Plus’s ROM into a 512K, it turns it into a 512Ke capable of using the 800K drive.
This means I can build a single machine with a 400K/800K drive replica, selectable ROM image, and selectable RAM size, that can behave like any of the four classic Macs. If configured as a 128K or 512K then the drive will only work with 400K disk images.
Drawing the Line Between Old and New
All of the hardware in the classic Macs is memory-mapped, meaning that the CPU talks to the serial controller, VIA, and other components by reading and writing to specific addresses in its address space, just as if they were RAM locations. These components can also “talk” to the CPU by triggering interrupts at appropriate times. I’m choosing to draw the line between the original Mac and my reimplementation here, at the level of memory maps and interrupts. For example, the original Mac received data from the serial controller by reading a set of memory locations beginning at $9FFFF8. In my replica, as long as something drives reasonable values onto the data bus when the CPU reads from address $9FFFF8, it makes no difference whether it’s actually a serial controller, an FPGA, a microcontroller, or something else. In practice, the replica hardware can be completely different from the original Mac hardware, as long as it exhibits the same behavior with respect to memory accesses and interrupts.
By drawing the line there, I’ve chosen an approach similar to Minimig’s rather than Replica 1’s. The external ports on my Mac clone will not be electrically or logically compatible with original Mac peripherals like the keyboard, mouse, or external disk drive. Instead, it will likely use a PS/2 keyboard and mouse, and an SD card filled with disk images instead of a real floppy disk. The alternative of extending the replication all the way down to the level of external ports would have far more difficult, and also would have necessitated the use of real vintage Mac peripherals (or replicating those as well).
A side note: There seems to be significant interest in the vintage Mac community in an external floppy drive replica that can work with a real Mac. While this project won’t address that need, it may provide some knowledge and expertise that could be used to help reach that goal.
Floppy Drive Replica
In my previous post I predicted that the floppy drive replica would be the most difficult part of the project, and I still believe this is true. Fortunately, I discovered that the multi-emulator MESS has a Mac emulator containing a hardware-level simulation of the Mac’s IWM floppy controller and the Sony 3.5″ drive mechanism and media. That should provide me with all the details I need in order to build a floppy drive replica using an SD card. From a quick glance through the relevant sections of the MESS source code, however, this will not be an easy task.
Broadly, my plan is to fill an SD memory card with floppy disk images in DiskCopy 4.2 format, the most popular disk image format for Mac emulators. A microcontroller connected to a small LCD will run some kind of menu program, enabling the user to browse the list of disk images and choose which disk is inserted into the virtual floppy drive. This functionality would be completely independent of the Mac replica, and would work even when the Mac was not running.
A CPLD or FPGA would mimic the Mac’s IWM floppy controller. When the Mac sent the IWM a command to move the head to a particular track, the FPGA would pass this information to the microcontroller, which would load the appropriate subsection of the selected disk image from the SD card. This data would then be encoded by the microcontroller into the format you’d find on a real floppy: sync bytes, GCR encoding, prologues, epilogues, sector numbers, checksums, etc. The encoded data would be stored in a track buffer: a RAM buffer about 8K in size, located either in the microcontroller or the FPGA.
When the Mac sent the IWM a command to read the track, the FPGA or microcontroller would return the contents of this buffer, one byte at a time, with a delay of about 2 microseconds per bit. The track buffer would be treated as a ring buffer, with the same 8K data sequence being returned over and over until the Mac moved on to a new track.
Simulating the floppy at this level is a complex project, and if it bogs down, I reserve the right to take a different approach that will likely be easier, but less authentic. Most Mac emulators other than MESS don’t simulate the floppy hardware, but instead patch the ROM and replace the floppy driver altogether. The floppy driver is a standard Mac OS driver with an interface that does things like request 512 bytes from sector X, to be returned in a pre-allocated buffer. If I map the SD card’s pins into some unused portion of the Mac’s address space, then I could write a replacement floppy driver in 68000 assembly that bit-bangs communication with the SD card to retrieve the data. This would eliminate the need to worry about sync bytes, GCR, and so forth. However, it would still be necessary to write 68000-code to parse the SD card’s FAT file structure and the DiskCopy 4.2 image format.
Components
While it’s too soon to start defining the specific components required, a few pieces will definitely be on the bill of materials:
- 68SEC000 CPU – While I could theoretically run a 68000 soft-core like ao68000 inside an FPGA, I prefer to use a real CPU. This frees me from having to worry about possible compatibility problems with a simulated 68000 core, and from having to use an FPGA powerful enough for such a core. Instead, I will very likely use a 68SEC000, which is the only 68000 variant that works at 3.3V and is easy to interface to modern 3.3V FPGAs. The 68SEC000 does use a different 2-wire bus protocol than the vanilla 68000’s 3-wire protocol, which may be an issue. I need to investigate this further, but since I’ve drawn the abstraction line at the memory address level, I think it shouldn’t matter.
- Microcontroller – Regardless of which path I take for floppy drive replication, I will still need a microcontroller for the SD card disk image menu and virtual insertion/ejecting of disk images. This could be anything, but I will probably choose a member of the AVR family like the ATMega328P used in the Arduino, since I already have experience with it and the necessary programming hardware. If more RAM is needed for the floppy track buffer or other purposes, a larger member of the same family might work, like the ATMega1284P.
- FPGA or CPLD – A programmable logic device will implement address decoding, video and sound generation, and simulation of original components like the VIA and serial interface. I will probably start by making a rough version of the design in Verilog, to get a general idea of how much logic resources it will need. If it’s not excessive, then I can use the same Max II CPLD that is the heart of Tiny CPU. If the logic needs are greater than a CPLD can meet, then a Cyclone II FPGA is probably the next best choice.
- SRAM – 1MB SRAM is cheap enough that there’s no reason for me to mess around with DRAM and worry about memory refreshes. It also means I can stop and single-step the clock if need be.
- Flash ROM – A 512KB Flash ROM will provide enough space to hold the Mac 128K/512K ROM image (64KB), the 512Ke/Plus ROM image (128KB), and any other debugging or monitor software that I might want to write. In-circuit reprogramming of the Flash ROM should be possible using JTAG indirect programming, as I did for Tiny CPU, but that’s very slow. Maybe I can use the microcontroller to program the Flash, or use a ZIF socket for the ROM so it can be programmed with an external programmer.
Debugging and Breakpoints
As far as I can tell, the 68000 does not support hardware breakpoints. While I’m no expert, I believe the method normally used by debuggers on the 68000 is to overwrite the instruction where a breakpoint is desired with an interrupt instruction. Then when the interrupt handler is invoked, the original instruction is restored. Obviously this won’t work when debugging startup routines executing from ROM, and at any rate I lack any easy way to set breakpoints or run interrupt handlers in software. Instead, I will likely need to build my own debugging support into the machine.
One approach would be to use the FPGA to facilitate breakpoints. Since the FPGA will be generating the bus acknowledge signals for each memory request, it could make the CPU wait indefinitely if it didn’t provide the expected acknowledgement. If the FPGA detected a memory access to a particular address, it could switch to a “single step” mode where bus acknowledgements are only generated when the user presses a button, one ack per push. The breakpoint address could be hard-coded into the FPGA’s Verilog code (not ideal), or provided interactively somehow through switches or with the help of the microcontroller.
There’s a lot to think about here– could this scheme tell the difference between a data read and an instruction fetch? How could you examine the contents of registers or memory once a breakpoint was hit?
Getting Started
There’s so much work to do here, it’s not clear where to start. The wisest path would probably be to start with the riskiest part first: the floppy replication. I’ve been looking through the details of the MESS Mac emulator (which emulates the actually floppy hardware) and the Mini vMac emulator (which instead patches the ROM with a new floppy driver). In order to confirm that I fully understand the operation of the IWM and the floppy before I build any hardware, I would test my understanding through modifications of the Mini vMac source code. The idea would be to remove the ROM patch from Mini vMac, and write emulator code to directly emulate the floppy hardware, using the information gained from examining MESS as a guide. If I could do that successfully, and it worked in the emulator with the unmodified ROM, then chances are good I could build the same functionality again in hardware. Hardware-level floppy simulation is actually a project that’s desired by the author of Mini vMac, but has not yet been done, so it has some inherent value as well.
Despite the likely wisdom of that approach, I feel reluctant to begin a big hardware project with a big software one first. I’d much rather start by designing a board with the parts I think I’ll need, building it, and then developing the FPGA logic and microcontroller code after the fact. This is clearly a riskier approach, since I might find some oversight or limitation in my hardware that required building a second version of the board. I might even get halfway through and discover that the whole project was much more difficult than I’d expected, and abandon it unfinished. Yet while I appreciate the value of upfront planning, I’ve never been one to dwell too long in the planning stage, preferring instead to just jump in and get started. With this approach I should be able to get something basic working fairly quickly, such as hardware that boots far enough to display a “sad mac” or boot error code on the display. That would probably be the first and last time I’d ever be thrilled to see an OS crash.
Read 12 comments and join the conversation12 Comments so far
Leave a reply. For customer support issues, please use the Customer Support link instead of writing comments.
The FC0..2 bits differentiate between data and instruction fetch. To make breakpoints work in ROM, when the breakpoint address matched on instruction fetch, your logic could drive a trap instruction onto the data bus and leave the chip enable for the RAM and ROM disabled (or if you have space in the ROM to spare, have the logic control the ROM bank and force a read of a bank filled with nothing but trap instructions).
A little simpler on the hardware would be to force a bus error by asserting BERR#, which would give you access to the registers also, but there’s not enough context for the 68000 to properly restart all instructions, so this is only worthwhile if you don’t mind a breakpoint being terminal.
@Erik – As always, your comments are right on the money! Both of those ideas sound promising.
Driving a trap instruction onto the bus would invoke the trap handler installed by the Mac OS, wouldn’t it? So I’d need to modify the ROM to change the trap handler address, and add a new trap handler that does something like display register contents in LEDs.
How would asserting BERR# give access to the registers? It was my understanding that it caused the CPU to either retry the instruction, or halt.
I used to have a plus, along with some other classic Macs, and I thought it was one of the ones that had the “programmer’s/interrupt key” [1] on the side that you could press to send a top level interrupt and launch Macsbug/Microbug [2] (the debugger). If it does, doesn’t that imply that you wouldn’t need to hack in your own ROM patched FPGA debugger?
[1] http://en.wikipedia.org/wiki/Programmer%27s_key
[2] http://everything2.com/title/MicroBug
Good thought. A user interrupt switch isn’t the same as a breakpoint at a specific address, although the FPGA could probably press a virtual interrupt switch when it detected a fetch from the address of interest, triggering the debugger.
I think this would work fine for debugging user programs once the system was basically finished and working, but couldn’t really be used to debug the system itself during construction, or the ROM routines. If the simulated Mac hardware isn’t working correctly, then a debugger running on that hardware probably wouldn’t work either.
If BERR# is asserted while HALT# is not asserted, it will start handling exception vector 2 so you’d have to hook that somehow, just like you would with the trap. At that point the software can do whatever it needs to to dump the registers. The processor only restarts the instruction automatically if HALT# was asserted with BERR#. Otherwise, it’s up to the software to figure how to proceed. (BERR# was typically used by an external MMU to signal a page fault.)
The halting case only occurs if BERR# is asserted again while the processor is pushing things onto the stack and/or fetching the exception vector before the exception handler starts (a double bus fault condition).
In any case, the control logic needs to be able to map the ROM to start at address 0 during start-up so that the reset vector and initial stack pointer is handled correctly. The same logic could also temporarily map the ROM there again (with a special bank selected) during the breakpoint processing so that it could fetch your custom trap/BERR# vector without having to do any patching of the original ROMs.
I haven’t tried it myself, but in principle, it would be possible to glue your floppy Verilog into Mini vMac using the Verilog PLI. That would let you test your IWM emulation with real Mac software fully in simulation. The same could go for your other peripherals.
Hmm, it looks as though the 68000 has a prefetch queue of sorts, 4 bytes in size, so instructions are actually fetched before they’re executed. That means I can’t trigger an instruction breakpoint by watching the address bus, or at least not accurately.
Even with a prefetch queue, if the address matches the breakpoint address when FC1 is high and FC0 is low, you can still force the opcode for a trap or illegal instruction onto the data bus and just let it work its way through the queue; you just won’t know if the breakpoint was actually reached until you also see a supervisor data read cycle of the corresponding exception vector.
In any case, I thought the prefetch queue was only on the 68010 and higher, although I guess it could be something back-ported to the 68SEC000 version. Where are you seeing the prefetch queue mentioned?
I read about the 68000 prefetch here: http://pasti.fxatari.com/68kdocs/68kPrefetch.html
The thought of the 68000 prefetching more than just the next word surprised me enough that I was contemplating hooking up the logic analyzer and taking a look at the bus cycles myself. But in the linked article it looks like the matter has been carefully researched already. This has been educational.
Take a look at http://www.minimig.net/viewtopic.php?f=6&t=411 – this guy designed a mostly working Mac Plus clone for the Minimig.
Interesting! That forum thread is pretty short on details, and I almost *don’t* want to look at his design code so I can continue mine untainted. 🙂