Solving Problems One-by-One
I left off last week with my revised PikaPC host card briefly running code but crashing before it could fully boot to the forth prompt. As best as I could tell it was crashing when returning from a subroutine, which could indicate main memory still was not working.
I started by probing the DRAM control signals to verify that memory bank was getting properly initialized as DRAM and accessed as main memory. I could see the DRAM RAS and CAS signals immediately start running regular refresh cycles as soon as the bank was configured, so it was indeed using that bank as DRAM. What I could not see however, was any attempts to read or write main memory before the machine crashed.
I went back and double-checked my bank configuration settings and reread the section of the manual on configuring memory banks. There it was, right in the manual:
PPC403GA is an embedded chip, not necessarily intended for general-purpose computing. As such, it has some helpful features that a general-purpose CPU, like the PPC603, would not have. One of those helpful features is the Bus Interface Unit (BIU), which provides the memory banks, DRAM control, etc. The BIU defines SRAM/peripheral banks as starting at a minimum address of 0x7000,0000, and that's what I had used when I defined all the addresses ppcforth should use. However what that line in the manual says is any banks defined as DRAM start at a minimum address of 0x0000,0000.
I updated ppcforth to point to the new memory address range and it booted to the forth prompt!
… and immediately crashed as soon as I typed anything.
This was an interesting bug that took me a while to track down. I started by disabling cache so I could capture what code it was running when it crashed. Sitting at the forth prompt, it was in a tight busy loop waiting for a new character to come from the serial port, w4char. So I pressed a key to see where it jumped and what code it ran next. It never loaded any code beyond that w4char loop.
I took a detour here to write a simple memory test just to make sure main memory was actually functional (my first time sitting down and writing anything significant with PowerPC assembly!). It passed without issue. It's not an exhaustive test, but it does at least confirm that a word written to memory could at least be read back some time later. So that wasn't the problem. I needed to track down what actually happens when a character is received over serial.
The ppcforth code was written to use hardware interrupts for receiving serial data so I had to learn how interrupts work on PowerPC. PPC uses an exception vector table similar to M68k, where each type of exception triggers the CPU to load from a specific offset into the table. Unlike M68k however, the CPU does not load an address for the interrupt handler from this table. Instead, each vector is a 256 byte (64 instruction) block of code. If more than 64 instructions are needed, the programmer will then need to branch to the rest of the interrupt handler located elsewhere in memory. That's what ppcforth does; it adds a single branch instruction at each vector to the interrupt handler.
PowerPC is a RISC architecture. It very strictly adheres to the rule that each instruction is one word, and one word only. In M68k, a jump instruction could be one word followed by a long word with the full address for the jump. On PowerPC that is not allowed. The branch address is embedded into the branch instruction and thus a branch is limited to 24 bits. With my ROM above address 0x7000,0000 and my RAM down in the 0x0000,0000 range, a branch instruction could not reach from the table in RAM to the handler in ROM.
This is not a unique limitation for a branch instruction. It's fairly common for branch instructions to have a specified range, forwards or backwards, that it can branch to. This is often supplemented by something like the M68k absolute jump instruction. PowerPC does not have an absolute jump instruction, at least not that I could find. The only option is to load an address into a register, copy that register to the Link Register (which is generally used to store the return address for short subroutines), then branch to Link Register. Instead of loading a single branch instruction into the vector table, I would have to load code to load the handler address into the Link Register and then branch.
Or I could just run everything from RAM. I tried the former option; it was more complicated and it crashed. I didn't bother trying to debug it. I just wrote the code to copy ROM into RAM on startup and run from there. That got me to a functional Forth prompt and I was able to run programs!
The next thing to try was test the host card with my video card to see if it would work. It did not. Nor did any of my other cards.
This is the same bus I've been running on my prototype, it should just work, right? Well, almost. Comparing schematics I found two discrepancies — CS2# and CS3# were swapped (a problem I remember fixing on the prototype but never carried over to the host card), and two address signals were wired incorrectly (PPC403 uses them as byte write strobes normally, but can also use them as address inputs during external DMA cycles). The first problem I could fix easily by reassigning input pins on the bus control CPLD. For the second I needed to cut a couple traces and bodge the signals to ground.
And with that, I was able to initialize video and draw on screen using my video card with the new host card. With almost no time to spare, PikaPC phase 2 is running in time for VCF Southwest 2026 this weekend! I will be exhibiting both the PikaPC prototype and phase 2, along with my Wrap030 project.
I still have more work I want to do on the project, such as getting my keyboard/mouse & disk cards running, but those will have to wait until after the show.