Multitasking Success
After a LOT of fiddling, I finally pulled off a successful multitasking demo. It’s not the most exciting thing in the world to watch, but once you understand what’s happening, you’ll appreciate the nerd factor. The photo below shows BMOW powering-up and starting two processes, which are then multitasked to create the appearance of simultaneous execution. First the boot code prints “boot” to prove that the machine is alive, and then a few punctuation marks that serve as status codes while it’s initializing the two processes. Following that is the output from the processes themselves. One prints the letters A-M, and the other prints the digits 0-9.
The two processes have different delay lengths between each printed character, which is why the sequence doesn’t appear strictly as letter-number-letter-number. The differing delays were intended to help expose race conditions: wait long enough, and the two processes should attempt to print to the LCD at the same time, exposing any possible bugs with resource contention.
How it Works
BMOW multitasking required writing a simple process manager and scheduler, and takes advantage of the machine’s semi-protected memory banks. BMOW memory is organized into banks of 64K each, with the OS occupying bank 0, and other processes each occupying a private bank. Code executing outside of bank 0 is unable to directly view or modify memory in other banks. It can only invoke specific system procedures to do such work on its behalf, using the JSP instruction (jump to system procedure).
At power-on time, the boot code copies the letters program into bank 1, and the numbers program into bank 2. It then calls a scheduler routine to add process table entries for the two processes. This routine sets up some initial stack data in the appropriate bank, to mimic what would be on the stack if the process had already been running, but was suspended by an interrupt. It then initializes the process table entry to contain the process’ stack pointer value, and marks that table entry as active. Finally, the boot code sets the real-time clock to generate an interrupt every 2 ms, and halts.
Two milliseconds later, the first interrupt is triggered. The interrupt service routine first pushes the current register contents and program counter value onto the stack. Since each process (each bank) has a separate stack, this means the registers are saved onto the stack of whatever process was interrupted. Then the ISR checks to see if it’s a timer interrupt (it could also be keyboard or USB). If it’s a timer interrupt, it calls a scheduler routine to find a new process to run.
The scheduler stores the value of the stack pointer into the current process’ table entry. Then it scans the process table, beginning at the next entry after the current process, looking for active entries. When it finds one, it copies the value in the process table entry into the stack pointer, and then executes the RTI instruction (return from interrupt). This instruction pops the program counter and register contents off the stack, and resumes execution of the new process where it left off.
Each process knows nothing about the other processes, and thinks that it’s in complete control of a 64K machine. From the point of view of the process, it can’t even tell that anything special happens when it’s suspended and restored, except that some time has passed that can’t be accounted for. It’s a bit like an alien abduction for a BMOW process.
Observant readers may have wondered: where do the letters and numbers programs come from? On a typical computer, the OS loads and runs programs from disk or some other storage medium, but BMOW has none. For this demo, the programs are compiled separately, and then embedded into the OS itself, which is stored in ROM. The programs appear as data to the OS, and it just blindly copies their bytes into the appropriate memory banks. This works, but obviously isn’t an ideal solution, because it ties the OS and the demo programs together into a single unit. It’s a reasonable work-around until BMOW gets some storage medium that can be altered independently of the OS boot ROM.
The Road from Here to There
Planning everything out and writing the scheduler code was actually pretty simple. I had the multitasking demo working fine on the BMOW simulator a week ago, but on the real hardware, none of the processes would start. Then I procrastinated looking into it further, and built the Robo-Flower instead (see previous post).
It turned out that there were multiple hardware problems to overcome. After a lot of experimentation, I was able to determine that the processes weren’t starting because their program codes weren’t correctly copied into their memory banks. A bug in the memory copy routine, maybe? But then why wouldn’t it show up in the simulator? I tried writing a simpler memory copy routine just for this purpose, and it worked, but I was stumped as to why, and the simpler routine wouldn’t work in the general case.
Finally I noticed that my original copy routine was doing an indirect load through a pointer that happened to be stored at an address ending in 7F hex. Reading the two byte pointer required incrementing the address from 7F to 80 in order to read the high byte. On a hunch, I changed the program slightly to store the pointer at 80 instead (incrementing to 81 for the high byte), and it worked!
This looked suspiciously like a very nasty clock glitch problem I ran into a few months ago, that laid waste to BMOW for a while. In that case, the low byte of the program counter would sometimes increment from FF to 01, or from 7F to 81, as if it were being incremented twice. Now I had the address register doing the same thing. I “fixed” the problem with the PC by making a nonsensical change to the GAL equations that implemented it, but I never really resolved the underlying issue. Unfortunately when I made the same change to the address register GAL equations, it didn’t help.
Back when the PC incrementing problem occurred, I was pretty sure it was due to some noise or signal reflection on the clock wire. I tried a couple of quick signal termination tests, but they didn’t help, and I didn’t follow-up at the time. Now I was considering more signal termination experiments again, when it occurred to me to wonder why I had my clock signal daisy-chained to so many chips to begin with. The clock signal comes from a buffer with eight outputs, so I could easily run a new clock line to some of the chips, shortening the existing clock line, and hopefully reducing noise and reflections. All it took were a few minutes with the wire wrap tool, and it worked like a champ! I don’t know why I didn’t do that in the first place.
I wasn’t quite out of the woods yet, though. The two processes ran now, but the output was a mess. Every 10-20 characters was some garbage symbol rather than a letter or number, and the display didn’t word-wrap properly. After an initial heart attack, I quickly realized this was likely the result of a bug in the microcode for BRK (break) that I fixed a while back, where the X register wasn’t correctly preserved across interrupts. Although I’d made a fix in the microcode source a few weeks ago, I’d postponed reprogramming the micro-ROMs, since every time I touch them something bad seems to happen. This time proved to be no exception: I reprogrammed the micro-ROMs, powered up the machine, and it failed to boot at all. After a second heart attack, I tried pushing and prodding the micro-ROMs until BMOW came back to life, which it eventually did with successful results. Obviously there’s a loose connection somewhere on one of the micro-ROM pins, which will undoubtedly come back to haunt me later.
Future Plans
I’m pretty happy with how the multitasking demo worked out, and I can now check off one of the “stretch goals” listed on the About BMOW page. To really finish the multitasking support, though, there’s still a fair bit of work left to be done. For one, there’s no way to stop a process once it’s started! There’s also no way to get info about running processes. A lot of time is wasted in the scheduler and the system procedure interface that could be optimized. There’s no test-and-set atomic operation that can be used as a semaphore. There’s no dedicated stack for interrupt handling: it uses the interrupted process’ stack, which means if that process had an invalid SP, the whole machine will crash.
Really the whole JSP (jump to system procedure) interface for calling OS routines needs to be reworked. JSP is implemented as a jump to an address in bank 0. Supply a bad address, and it will crash. This should really be replaced by some kind of indexed trap system, where JSP takes a trap number as a parameter rather than an address.
At this point, I’m going to archive what I’ve got, and then turn BMOW back into a single-process machine. Without a windowed graphical display or multiple output terminals, there’s not really any compelling use case for multitasking. But running only a single process on a multitasking-capable machine is very inefficient, at least how I’ve implemented it. The scheduler overhead and the indirection of the JSP interface eat up a lot of time that could otherwise be better-spent by the single process.
I’ll try to avoid doing anything major that would prevent switching back to multi-tasking in the future, so for now I’ll just disable the scheduler interrupt, and maybe provide some lighter-weight interface to the OS routines. I’ll keep the 64K bank model for the time being, since I don’t yet have any examples that would benefit from more than 64K, and changing it would be non-trivial. In particular I’d need to change the microcode for all the instructions to expect 3-byte addresses instead of 2-byte ones, which would add an extra clock cycle to the execution time of any instruction that manipulates an address. Worse, certain address-manipulation instructions might become impossible for 3-byte addresses unless I added a second temporary register somehow. So I’ll push those problems off into the future, and concentrate on other things.
Read 1 comment and join the conversation1 Comment so far
Leave a reply. For customer support issues, please use the Customer Support link instead of writing comments.
What’s up Dear, are you truly visiting this website daily, if
so afterward you will without doubt take fastidious experience.