• Sometimes You Have To Rebuild Everything

    Colin Maykish09/22/2022 at 02:03 1 comment

    Second handmade revision of Mackerel on a backplane

    The rat's nest of wires I've been calling the Mackerel 68k has been completely rebuilt. It's now a slightly smaller rat's nest, but it's on a much nicer backplane with more thought given to the bus pinout, power delivery, and noise reduction.

    I have a few 8-bit computer designs in various states of development, so I built a backplane I could use for all of them.

    New backplane for 8-bit computers

    The big updates are more power and ground pins dispersed throughout the bus, a 4-layer PCB with solid power/ground planes internally, and a MCP2221A USB-serial converter built in.
    Since the pinout of the backplane is completely different from the first iteration of Mackerel, I rebuilt the component boards as well. The RAM/ROM board design is unlikely to change, so I committed it to a PCB. I'm actually using one and three-quarters of these boards for a total of 3.5MB of RAM. The remaining 512KB of available address space is used by the ROM and the serial port.

    2 MB SRAM, 512KB ROM with programmable address decoder

    The CPU is still the same 52-pin 68008, but the address decoder and glue logic has moved to an FPGA. This is ridiculous overkill, but I wanted to rule out some stability issues that I thought may have been caused by the EPM7064 CPLD I was using previously. Since the Upduino is a 3.3v part, I connected it to the system bus via 74HC245 bus transceivers acting as level shifters.

    Finally, I replaced the 68901 and the classic 68681 with a single XR68C681. The MFP was working alright as a system timer, but the serial port was just too slow. The 68681 can technically run at 115200, but requires a weird hack involving the timer circuit to generate the right clock. The XR68C681 has native support for 115200 baud without resorting to hacks, leaving the timer free to use as a periodic interrupt. It does this beautifully.

    On the programmable logic side, I simplified the address decoding and removed all of the DTACK generation logic (I just grounded the pin for now). All of my components are capable of running at the 8 MHz supported by the CPU without any wait states, so this just makes things simpler.

    The rebuild was a huge success. I spend some time updating my Linux kernel code to use the new DUART for serial output and the system timer, compiled, and loaded the kernel into RAM over the serial port. On the first boot, I got farther than I ever did on the old system.

    Still getting an error, but it's a new and exciting error:

    Jumping to 0x8000
    5Linux version 4.4.0-uc0 (colin@ada) (gcc version 11.3.0 (GCC) ) #3 Wed Sep 21 20:42:55 EDT 2022
    6Herring-8 68k support by Colin Maykish 
    6Flat model support (C) 1998,1999 Kenneth Albanowski, D. Jeff Dionne
    7On node 0 totalpages: 888
    7free_area_init_node: node 0, pgdat 000e8064, node_mem_map 000ff100
    7  DMA zone: 7 pages used for memmap
    7  DMA zone: 0 pages reserved
    7  DMA zone: 888 pages, LIFO batch:0
    7pcpu-alloc: s0 r0 d32768 u32768 alloc=1*32768
    7pcpu-alloc: [0] 0 
    6Built 1 zonelists in Zone order, mobility grouping off.  Total pages: 881
    5Kernel command line: 
    6PID hash table entries: 1024 (order: 0, 4096 bytes)
    6Dentry cache hash table entries: 1024 (order: 0, 4096 bytes)
    6Inode-cache hash table entries: 1024 (order: 0, 4096 bytes)
    6Memory: 2480K/3552K available (787K kernel code, 30K rwdata, 80K rodata, 36K init, 41K bss, 1072K reserved, 0K cma-reserved)
    5Virtual kernel memory layout:
        vector  : 0x00000000 - 0x00000400   (   1 KiB)
        kmap    : 0x00000000 - 0xffffffff   (4095 MiB)
        vmalloc : 0x00000000 - 0xffffffff   (4095 MiB)
        lowmem  : 0x00008000 - 0x00380000   (   3 MiB)
          .init : 0x000e9000 - 0x000f2000   (  36 KiB)
          .text : 0x00008000 - 0x000cced0   ( 788 KiB)
          .data : 0x000cced0 - 0x000e8a00   ( 111 KiB)
          .bss  : 0x000f2000 - 0x000fc568   (  42 KiB)
    6clocksource: timer: mask: 0xffffffff max_cycles: 0xffffffff, max_idle_ns: 1911260446275000 ns
    6Calibrating delay loop... 0.69 BogoMIPS (lpj=3459)
    6pid_max: default: 32768 minimum:...
    Read more »

  • Planning Ahead: Freerunning MC68030

    Colin Maykish08/24/2022 at 14:14 1 comment

    I've been slowly making progress toward booting Linux on the 68008 and I have some updates to share there soon, but in the meantime, I wanted to start planning for the future. As part of a recent PCB order, I made a breakout board for the 68030 CPU. I already have a pair of the processors in my collection, but no good way to to test them. Wiring up 100+ pins by hand is not my idea of fun, so I put together this simple design:

    It's hard to get much simpler. This board breaks out all the (useful) control lines and the address and data bus. Combined with a big pile of jumper wires, it's a useful test to show that my CPUs actually work. I could not find any sockets that perfectly match the 68030 footprint, so I used single row machined pin headers packed together. The CPU pins fit nicely in these and they can be arranged in a grid pattern.

    Here's a quick video of the 68030 freerunning:

  • Moving Past Memory Issues

    Colin Maykish08/07/2022 at 19:41 8 comments

    After spending many hours building various combinations of kernel versions and configurations, I've finally got Mackerel progressing past the memory initialization errors. I tried 6 six different kernels, multiple compiler versions, and every config setting I could find. Almost all of those build combinations produced the same result: either crashes in the allocator code or tons of page state errors on the console.

    Eventually I got the idea that maybe the kernel and the config were fine and something else was wrong. I still wasn't convinced I could rule out hardware issues, but I added some basic memory tests to my bootloader code and couldn't find any issues. Additionally, Stephen Moody was able to boot the same kernel code on his 68k board and got stuck in roughly the same place.

    The only thing left to blame was the binary image itself. When the Linux kernel compiles, it produces a vmlinux file as the main output. Then, based on the platform, you need to turn this into a binary that the CPU can load into RAM (or ROM) and execute. This involves using objcopy to convert the file to an executable.

    uCLinux provides a bunch of examples of this process as the final step in the Makefile. Ultimately, this was my problem. The board I selected as a template expected the kernel to run from ROM. Even though I disabled the ROM and adjusted the memory mapping in the kernel config to run from RAM, this final image generation step still produced a binary tuned for the Arcturus uCsimm, not the Mackerel.

    The solution, then, was to simplify the image generation command:

    m68k-elf-objcopy -O binary vmlinux images/image.bin

    That's it... That was a few weeks worth of debugging kernel code. I took for granted that because the image file was booting and doing something, it must have been fine, but the memory map I gave to the kernel and the placement of data in the executable were not in agreement.

    A frustrating experience, but a valuable one. Even though my kernel debugging wasn't directly productive, I learned a ton about Linux internals and that should help with the next steps: hardware timers, interrupts, serial drivers, and filesystems. That's pretty much the list of remaining tasks.

    Anyway, I'm happy to say I've got my board booting to the infamous "Calibrating delay loop..." message which means I'm ready to implement a timer and interrupt logic.

    5Linux version 3.10.108 (mackerel@4b9e0bcb9c18) (gcc version 4.9.2 (GCC) ) #28 Sun Aug 7 18:57:42 UTC 2022
    Mackerel 68k support by Colin Maykish <crmaykish@gmail.com>
    6Flat model support (C) 1998,1999 Kenneth Albanowski, D. Jeff Dionne
    7On node 0 totalpages: 496
    7free_area_init_node: node 0, pgdat 0011b84c, node_mem_map 00150100
    7  DMA zone: 4 pages used for memmap
    7  DMA zone: 0 pages reserved
    7  DMA zone: 496 pages, LIFO batch:0
    7pcpu-alloc: s0 r0 d32768 u32768 alloc=1*32768
    7pcpu-alloc: [0] 0 
    Built 1 zonelists in Zone order, mobility grouping off.  Total pages: 492
    5Kernel command line: 
    6PID hash table entries: 16 (order: -6, 64 bytes)
    6Dentry cache hash table entries: 1024 (order: 0, 4096 bytes)
    6Inode-cache hash table entries: 1024 (order: 0, 4096 bytes)
    5Sorting __ex_table...
    6Memory: 528k/528k available (868k kernel code, 544k data, 44k init)
    5Virtual kernel memory layout:
        vector  : 0x00000000 - 0x00000400   (   1 KiB)
        kmap    : 0x00000000 - 0xffffffff   (4095 MiB)
        vmalloc : 0x00000000 - 0xffffffff   (4095 MiB)
        lowmem  : 0x00008000 - 0x001f8000   (   1 MiB)
          .init : 0x0011d000 - 0x00128000   (  44 KiB)
          .text : 0x00008000 - 0x000e0810   ( 867 KiB)
          .data : 0x000e0810 - 0x0011c180   ( 239 KiB)
          .bss  : 0x00128000 - 0x0014d2a8   ( 149 KiB)
    6SLUB: HWalign=16, Order=0-3, MinObjects=0, CPUs=1, Nodes=8
    6Calibrating delay loop...

    For reference, I'm using Linux v3.10 (without any uCLinux libraries or code) compiled with gcc v4.9.2 and binutils v2.25. All built on Debian Jessie. There's a Dockerfile with the full environment in the main...

    Read more »

  • Debugging the Linux Boot Process

    Colin Maykish07/22/2022 at 21:11 6 comments

    It has been many hours of compiling, booting, and debugging and I have not made much progress. In the last log, I had the very start of the Linux kernel code running on the 68008, but it was failing early and without much information. I needed more output.

    After digging through the kernel code, it became clear that printk() was the tool for the job. This function is the kernel's version of the familiar printf(), but it handles logs a little differently. Instead of directly outputting messages, printk() acts a log buffer until the kernel boots far enough to create a serial console. Once the console is available, any logs stored up from before that will be printed. I spent some time writing a simple console driver and attempting to get the kernel to use it, but my issues started before the console is even loaded.

    To get around this, I cheated. I replaced the contents of the printk() function with dumb printf()-style code, immediately writing the log messages to the serial port and skipping all the buffering and console business. Now the kernel can at least talk. This is what it says:

    5Linux version 4.4.0-uc0 (mackerel@c14f97401618) (gcc version 4.9.2 (GCC) ) #1 Fri Jul 22 20:27:01 UTC 2022
    6Flat model support (C) 1998,1999 Kenneth Albanowski, D. Jeff Dionne
    7On node 0 totalpages: 512
    7free_area_init_node: node 0, pgdat 000b50cc, node_mem_map 000cd000
    7  DMA zone: 4 pages used for memmap
    7  DMA zone: 0 pages reserved
    7  DMA zone: 512 pages, LIFO batch:0
    7pcpu-alloc: s0 r0 d32768 u32768 alloc=1*32768
    7pcpu-alloc: [0] 0 6Built 0 zonelists in  order, mobility grouping off.  Total pages: 0
    5Kernel command line: console=debug,earlyprintk=debug
    6PID hash table entries: 1024 (order: 0, 4096 bytes)
    6Dentry cache hash table entries: 1024 (order: 0, 4096 bytes)
    6Inode-cache hash table entries: 1024 (order: 0, 4096 bytes)
    6Memory: 1144K/0K available (613K kernel code, 31K rwdata, 80K rodata, 36K init, 45K bss, 4294966152K reserved, 0K cma-reserved)
    5Virtual kernel memory layout:
        vector  : 0x00000000 - 0x00000400   (   1 KiB)
        kmap    : 0x00000000 - 0xffffffff   (4095 MiB)
        vmalloc : 0x00000000 - 0xffffffff   (4095 MiB)
        lowmem  : 0x00000000 - 0x00200000   (   2 MiB)
          .init : 0x000b6000 - 0x000bf000   (  36 KiB)
          .text : 0x00000400 - 0x00099bc0   ( 614 KiB)
          .data : 0x00099bc0 - 0x000b5a60   ( 112 KiB)
          .bss  : 0x000bf000 - 0x000ca674   (  46 KiB) 

    This is as far as it gets. Occasionally it will also crash and show a stack trace related to the mm_init() code. It seems pretty clear that there is an issue related to the memory. My first thought is that there is something wrong with the image file I'm generating or with the kernel configuration settings related to ROM/RAM sizes and locations. Seeing the "0K available" message is concerning. I have 2 full megabytes of RAM available from 0x000000 to 0x200000. My bootloader copies the Linux image into RAM starting at 0x400 (leaving the vector addresses alone) and then jumps there.

    There is definitely more debugging work I can do in this area, but the other problem I'm having is harder to solve: hardware instability. Unsurprisingly, a bunch of vintage components on perfboards soldered together and connected on a backplane is not the pinnacle of reliability I would like. My RAM board in particular seems finicky. Rearranging the individual chips in the sockets can change the stability. The crashes I see (outside of Linux kernel problems) show up as illegal instruction exceptions or address errors, more evidence that something is off with the RAM hardware. One other possibility is that MFP or decoder CPLD is showing up on the CPU buses when it shouldn't be. This would also look a lot like a RAM problem.

    In a last ditch effort to avoid blaming the hardware, I built and booted an older version of Linux (3.10.0) with and older version of gcc, but I got almost identical results. Turns out the Linux kernel startup changes very slowly between versions. The strategy at this point is two-fold: I need to deep dive into...

    Read more »

  • Linux on the 68008: Signs of Life

    Colin Maykish03/08/2022 at 03:55 3 comments

    I’ve compiled Linux hundreds of times, but it’s almost always automated behind a build system of some sort. I don’t spend that much time configuring it once it’s working as expected and I’ve spent even less time parsing through the source code.  The last few days have been eye-opening in more ways than one. Since the last log, I’ve managed to shave the Linux image file down to 1.5MB. It will now fit comfortably in the 2MB of RAM currently installed on Mackerel, but will it run?

    CH376S USB Module
    CH376S USB Module connected to the 68008 bus

    The first thing to do was to hook up the CH376S USB module again. I haven’t used this since the first prototype build, but it’s essentially a USB-to-parallel adapter with support for FAT filesystems. The setup involves hooking it up to the CPU’s 8-bit data bus, connecting some control lines, and memory-mapping it like any other peripheral. There’s a command/response API for querying the file system and transferring data. My current implementation takes about 3 minutes to transfer the entire 1.5MB Linux image into RAM, which is terrible, but it’s a lot less terrible than sending it over a 9600 baud serial port. I added an option in the bootloader to automatically copy the image file into RAM and jump to the entry point.

    At this point, Linux should be running, but you can’t tell. There’s no console activity, but the 68901’s LEDs do eventually show the pattern for “unhandled exception”, so something happened. Let’s figure out how far it went.

    The Linux startup process varies depending on the CPU architecture. Fortunately, the m68k version is pretty straightforward. In the kernel source code, there’s a file called linux/arch/m68k/68000/head.S. This is the starting point, i.e. the assembly function called _start is defined here. This function will be put directly at the start of the compiled image. In this case it ends up at memory address 0x8000 and jumping to that address will kick off the boot process.

    Since I have no idea if this code is actually running properly, I hacked in a few assembly statements to update the LEDs on the 68901 as the code progressed through _start. This at least proves that object code in the image file is executing and it gives me a place to start debugging. I haven’t actually implemented a serial driver or any platform-specific initialization code yet, but I’m just hoping to see something work. After the slow process of recompiling the kernel and booting from the USB drive, the LED lights show the expected pattern and then quickly change to the “unhandled exception” pattern again. Something happened!

    Tracing through the code further, _start eventually calls jsr start_kernel. This is where the official jump to the Linux kernel happens. The start_kernel() function is defined in linux/init/main.c. Taking a similar approach to the assembly code, I added a few LED pattern commands in here, only this time written in C. Rinse and repeat, success! I know the CPU is at least getting to the start of the Linux kernel initialization code.

    By now, the LED patterns are becoming cumbersome, but I’m still no closer to a working serial driver. Time to cheat! My serial output code for the 68901 is really simple, so I just copy-pasted the bare minimum code right into the main.c kernel source. This does not give Linux a way to communicate, but it acts like a more useful LED display, the old “printf debugging” routine. Sprinkling mfp_puts() messages all over the place and rebuilding gave me a better idea what was happening:

    Booting from USB...
    Image loaded at 0x8000
    Booting image...
    Read more »

  • Baby Steps Towards Linux on a 68008

    Colin Maykish03/04/2022 at 01:22 0 comments

    Choosing a Version

    If you Google around for "68000 linux", it won't be too long before you find some mention of uCLinux. uCLinux is a project that supports a handful of CPUs without a memory management unit which makes it perfect for the 68008 (and most of the early 68k family). Several other homebrew 68k projects have used this “distribution” with some success, but a lot of them are now approaching a decade old and uCLinux itself seems to have been abandoned around 2016. The releases are still hosted on SourceForge, with the newest one building on v4 of the Linux kernel.

    Collecting the Tools

    So what does it actually take to compile uCLinux into something Mackerel can execute? Where does it even start? I’ve already been using a gcc-based cross-compiler for the m68k platform and that won’t change. However, I’m going to start over and rebuild the toolchain from an older version with the hope that tools from around 2016 will have better compatibility with code from the same era. I did this whole process in a Debian 9 virtual machine.

    The first step is to build binutils and gcc as a cross-compiler (https://wiki.osdev.org/GCC_Cross-Compiler is a good reference for this). I settled on binutils v2.27 and gcc v5.5.0, both from around 2016 . Now I’m basically back to where I started but with older versions of my cross-compiler and tools.

    In theory, there’s nothing stopping me from cloning the Linux kernel source, setting up a config manually, and building any version I like that targets the 68k. The problem is that the kernel is only one part of a full Linux system. I also need a standard library, applications, and a file system set up, not to mention drivers and startup code that will be specific to the Mackerel hardware. This is where uCLinux comes in. There are a few dozen preset configurations for known hardware. For example, Atari and Amiga have prebuilt configs that should build a full bootable system image. Let’s start with one of these to make sure the toolchain is working and to iron out any build issues before attempting to customize things further.

    Let's Just Compile Something!

    The uCLinux build process is pretty straight forward: run make menuconfig and select the vendor and board. Then run make. If all goes well, there’s an image file waiting at the other end. I chose the Arcturus uCsimm board for no particular reason other than the config seemed to be pretty barebones. The first dozen or so build attempts ended in failure for various reasons. 

    At first, uCLinux was attempting to use the wrong compiler. I updated the vendors/config/m68knommu/config.arch file to point to the correct prefix, i.e. m68k-elf-, the prefix for the toolchain I built previously.

    Then the build failed due to some compiler errors in the startup code. I had to comment out most of the interrupt handling code in ints.c and remove the call to the config_BSP() function from the setup_no.c file. Obviously, this will need to be fixed at some point, but I want to see a successful build before I write a bunch of driver code.


    After a lot of trial and error with different combinations of toolchains, uCLinux releases, and configs, I have a Linux image:

    -rw-r--r-- 1 colin colin 2.4M Mar  3 12:49 image.bin
    -rwxr-xr-x 1 colin colin 204K Mar  3 12:49 linux.data
    -rwxr-xr-x 1 colin colin 1.2M Mar  3 12:49 linux.text
    -rw-r--r-- 1 colin colin 997K Mar  3 12:49 romfs.img

    What does this image.bin file actually contain? According to the config, it should be a Linux kernel and a filesystem image containing busybox and some other basic Linux applications. Let’s confirm.

    Looking at the Makefile for the Arcturus uCsimm configuration, there’s a make image step that runs after all the compiling is done. This step is stripping various sections from the linux ELF file and combining it together with the ROM filesystem into a single image.bin file.

    The linux ELF is really just object code with some metadata wrapped around it. Using objdump...

    Read more »

  • Four Times the RAM, Same Great Price

    Colin Maykish02/27/2022 at 23:34 0 comments

    After spending all afternoon soldering, Mackerel now has a full 2 megabytes of super fast SRAM. I decided to go with the ISSI IS61C5128AL RAM chips for this project. They're pretty similar in function to the more common AS6C4008 series from Alliance, but they are rated for a blistering 10ns access time, more than 5x faster than the Alliance RAM. They're also slightly cheaper. The downside is that they're only available in surface mount packages. By the time the adapter boards are factored in, the cost difference is negligible, but the speed is still really nice. No wait states here!

    The SOJ-36 packages are not really that bad to solder by hand. With lots of liquid flux, I was able to get (in my humble opinion) really nice joints connecting them to the DIP breakout boards. These breakout boards won't really be necessary when I start designing PCBs, but they do make the ICs easy to reuse during prototyping.

    I've also thought about designing a SOP-36 to DIP-32 adapter which would convert the footprint of these ISSI chips to match the more standard DIP-32 SRAM pinout, allowing either style of SRAM to be used in the same socket.

    Here's the full RAM board with four chips installed for a total of 2048KB:

    The back side is a little less attractive:

    I'm not sure how well this wiring arrangement will hold up at higher clock speeds, but I was able to verify reads and writes to the entire 2MB range using a simple test program.

    One thing I don't really like with this design is that all of the chip-select logic is off-board. Every SRAM chip has its chip-select line connected to a separate pin on the backplane. I've got plenty of pins to spare at the moment, but if this is made into a PCB, I would like to move the address decoding for each individual module on board, freeing up a bunch of backplane connections.

  • Hardware Timers, Vectored Interrupts, Exception Handling in C

    Colin Maykish02/27/2022 at 01:10 0 comments

    With the MFP serial port working reliably, the next requirement for a barebones Linux system is a constant timer to allow the kernel to do context switching and multitasking. It's fortunate, then, that the MFP also provides four independent timers which can trigger interrupts on the 68008 CPU. It can also be configured to trigger interrupts for other reasons (serial port data available, GPIO input levels changing, etc.), but for now, a constant timer will be a big step forward toward the main goal of booting Linux.

    Vectored Interrupts

    The MC68000 series of CPUs supports vectored interrupts. It’s worth taking a minute to understand what that means. It took me a few days to really internalize this idea. In most 8-bit CPUs from a generation before the 68k, interrupts are not vectored, but polled. For example, when a peripheral wants to interrupt the 6502 CPU, it asserts the IRQ pin. The CPU stops what it was doing, stores some internal state to the stack, and then calls the interrupt handling code. Since there’s only one interrupt pin, all of the peripherals have to share. So when the CPU is ready to handle the interrupt, it has to ask each peripheral if it was the cause of the interrupt and then choose the appropriate action to take when the source is found. This is simple to design on the hardware side since every peripheral can connect in parallel to the IRQ pin, but it means the interrupt handling code is more complicated and potentially slower.

    Vectored interrupts, on the other hand, require more hardware, but they provide a way for the CPU to know exactly where each interrupt comes from without having to ask each potential source. On the 68k, this is done with an asynchronous interrupt system that prioritizes interrupts according to level.

    Instead of one IRQ pin, there are three IPL inputs representing a binary number from 0 to 7. These are the interrupt levels where 0 means no interrupt and 7 is a non-maskable interrupt. The minimum interrupt level can be set in software and when the CPU detects an interrupt at or above its minimum level, it will start the interrupt handling process. As with polled interrupts, it will finish executing the current instruction, store some state to the stack, and then find the appropriate handler function to call. The main advantage, though, over polled interrupts is that instead of asking every peripheral, the CPU expects the location of the interrupt handler to be exposed on the CPU bus by the peripheral. In other words, when the CPU acknowledges that it’s ready to handle the interrupt, the peripheral that made the request tells the CPU which handler to call. This has to be set up ahead of time in code of course, but this one-time setup simplifies and speeds up interrupt handling for the lifetime of the program. It also means interrupt handlers can be changed programmatically, which gives a lot of flexibility to the code.

    There are some details I left out (that will come up in the implementation section), but this is the description of vectored interrupts I wish I had when I started working on this. I hope it’s clear enough to be helpful to someone in the same position.

    Design Changes and More Logic To Implement

    The first step to getting interrupts going is to bring in some additional control lines into play. The MFP has an IRQ output that must be mapped to one of the 7 interrupt levels on the CPU. Since it is currently the only source of interrupts, the IRQ pin is connected directly to IPL2. All three IPL pins have a pullup resistor, so this means the MFP will cause a level 4 interrupt. If more than three interrupt levels are ever needed, something like a 3-to-8 decoder would be necessary to use the whole range.

    There are three pins on the 68008, FC0 through FC2, that, when all high, indicate the CPU is acknowledging an interrupt. This will be called the /IACK signal. It is used internally on the CPLD, but also connected to the MFP’s IACK pin. When the CPU is ready to handle...

    Read more »

  • Improving MC68901 UART Stability

    Colin Maykish02/24/2022 at 02:32 3 comments

    In the last log I complained about the 68901 MFP chip and the issues I was having with its UART in particular (spoiler alert: it was my fault). The issue of general unreliability and random crashing is definitely one of the hardest types to debug, but it also indicates to me that it's probably not actually the fault of the MFP since it's not failing in a repeatable way.

    There were two problems I was seeing:

    1. The UART is dropping bytes when transferring text and programs from the host PC to Mackerel.
    2. Occasionally, the whole system will just lock up in the middle of a long running program.

    Testing the Programmable Logic

    I figured the best place to start was to make sure all of my CPLD logic was correct. Aside from the address decoding which is trivial to test, the CPLD is also generating a /BOOT signal to temporarily map the ROM to address 0x0000 for the first eight memory reads and the RAM to 0x0000 thereafter. It seemed unlikely that this was affecting the MFP, but let's confirm it's working as expected.

    This logic is implemented in Verilog. I'm almost certain there's an easier way to do this, since a lot of designs get by with nothing but a shift register. Still wrapping my head around Verilog and programmable logic in general...

    // Generate the BOOT signal for the first 8 memory accesses after reset
    reg BOOT = 1'b0;
    reg [3:0] bus_cycles = 0;
    reg got_cycle = 1'b0;
    always @(posedge CLK) begin
        if (~RST) begin 
            bus_cycles = 0;
            BOOT <= 1'b0;
        else begin
            if (~BOOT) begin
                if (~AS) begin
                    if(~got_cycle) begin
                        bus_cycles <= bus_cycles + 4'b1;
                        got_cycle <= 1'b1;
                else begin 
                    got_cycle <= 1'b0;
                    if (bus_cycles > 4'd8) BOOT <= 1'b1;
    /BOOT signal
    1) /RESET signal, 2) /BOOT signal, 3) /AS strobe, 4) CPU clock

    Overly complicated or not, the output seems to be in order. The boot signal (purple) goes high after the first 8 memory accesses following a reset. I'm not sure about the bumps in the rising edge of /AS (blue), but the signal is obviously good enough for the CPLD, so let's move on.

    The MFP is currently the only peripheral in the system that produces its own DTACK response. The RAM and ROM are both fast enough that, until now, DTACK was tied to ground and the whole bus cycle was effectively synchronous. At low clock speeds, the MFP does actually seem to work with DTACK grounded, but since I'm having issues with it, this obviously needs to be implemented correctly.

    The logic is simple: if the MFP is selected, the CPU's DTACK line should be controlled by the MFP DTACK response signal. If anything else is selected, i.e. RAM or ROM, just hold DTACK low.

    In Verilog, this simplifies down to:

    // Generate DTACK signal
    assign DTACK = ~MFPEN & DTACK_MFP;

    This logic will get more complex when other devices are added or if wait states become necessary as clock speeds increase. For now, though, it's easy to validate.

    DTACK signal generation
    1) CPU DTACK signal, 2) MFP DTACK response signal, 3) MFP chip-enable signal

    This is also looking alright. Trace 3 is the MFP chip-select line, active low. When the MFP is enabled, DTACK (trace 1) is held high until the MFP pulls it down (trace 2). When the MFP is no longer selected, DTACK goes back to ground.

    When In Doubt, Slow Everything Down (and add capacitors)

    In addition to properly implementing DTACK, I "reinforced" the boards with more decoupling capacitors and I replaced the clock generation on the CPLD with a dedicated 2 MHz oscillator as close to the CPU clock pin as possible. I thought it would be nice to have a high frequency oscillator feed the CPLD and then generate a system clock from that, but the generated clock signal being output by the CPLD was only hitting around 3v peak-to-peak instead of the 5v I would expect. According to the datasheet, the 68008 will recognize anything over 2.4v as high, but I didn't want to take any chances and decided to feed the CPU clock line directly with a nice clean clock signal from its own oscillator. This also means the...

    Read more »

  • Prototype PCBs, CPLDs, and UART Issues

    Colin Maykish02/23/2022 at 00:44 1 comment

    A Better Backplane

    In the long run, I don't know if Mackerel will be a backplane system or a single-board computer, but while the project is still in the prototyping phase, having interchangeable component cards on a backplane does make development easier. The first build (shown in the previous log) repurposed a 40 pin backplane from an earlier project of mine, but I quickly ran out of pins and space on the PCBs for all of the ICs and interconnects I need. To give Mackerel some more room to grow, I designed a really simple 80-pin backplane and matching prototype PCBs.

    80-pin backplane
    Six of the pins are designated as VCC and GND, but that still leaves 74 pins available for data signals.

    PLCC Breakout Boards

    Another bottleneck in the previous build was using the DIP-48 version of the 68008 CPU. Using the PLCC package gets you two more address pins, an upgrade from 1MB to 4MB of address space. I'm not sure of the minimum RAM requirements to run something like uCLinux, I'm sure it depends on the version and features. I've seen projects boot ancient versions of Linux with as little as 512KB, but I'd prefer to target something more recent. Linux has not gotten any smaller...

    Anyway, PLCC packages are not the hardest surface mount chips to work with, but they are more difficult to use in prototypes, so I designed some PLCC-to-DIP adapters for the various sizes.

    PLCC breakout adapters
    PLCC-44, -52, -64, and -84 breakouts

    The first two (PLCC-44 an PLCC-52) fit nicely into a normal breadboard or perfboard. Technically so does the PLCC-68 version, but it takes up two lanes on a breadboard which makes it a little space inefficient. The MC68EC000 uses this package. That CPU has the option for an 8-bit bus and supports higher clock speeds and more address space. It might be a nice upgrade to the 68008 at some point.

    I also ordered a PLCC-84 breakout, but this is proving to be less useful. The only ICs I currently have in that package are some non-functional EPM7128 CPLDs.

    CPLD Development

    Speaking of CPLDs, I've swapped out the multiple GALs for a single EPM7064SLC44 CPLD. These run at 5V and can be programmed using Quartus 13.1 and a cheap USB blaster. This version of Quartus is still available for download on the Intel website and runs just fine on Windows 10. The USB blaster is a $5 clone from eBay. It also works fine.

    I put together a breakout board for the JTAG header, a clock, and some I/O to make chip testing and programming faster. The PLCC sockets seem pretty fragile and they're expensive enough that I don't want to squander them, so moving around the whole DIP adapter seemed like an acceptable compromise.

    EPM7064 Dev Board
    Breaking out the JTAG header and adding some I/O for testing

    As far as the actual programmable logic, I've used this CPLD to implement:

    • Two clocks from the 50 MHz oscillator
      • one for the CPU (5 MHz)
      • one for the MFP (1.25 MHz)
    • Address decoding for memory and MFP peripheral
    • /BOOT signal to map ROM to 0x0000 at reset
    • DTACK generation - no wait states for ROM or RAM, MFP DTACK when MFP is selected

    State of the Hardware

    Basically, I've rebuilt the first prototype of Mackerel on the new 80-pin backplane and moved all the control logic onto the CPLD.

    Core system boards
    Top left: CPU and CPLD, top right: SRAM board, bottom left: MC68901 multi-function peripheral, bottom right: 32KB ROM

    Here it is assembled on the backplane.

    Mackerel prototype 2 on the 80-pin backplane
    Mackerel prototype 2 on the 80-pin backplane

    Here's a mostly accurate schematic of the current system. This ignores the ROM and RAM, but those are mapped as expected, address and data bus to the CPU with chip-select signals coming from the CPLD. Nothing about this design is finalized. A lot of the routing is just there for convenience. The system bus on the backplane headers is not well thought out yet. I'm still just adding signals as needed to get them connected between the component boards. There is plenty of room for consolidation once I've proven out the design.

    Problems with the MFP UART

    The only I/O in the system is the MC68901 MFP board. In...

    Read more »