9 hours ago •
I'm starting to become really nervous about the breadboard-based nature of the project as it grows and becomes more complex. This was really brought home to me a few weeks ago when I had to transport the board to work where I was demoing it to some interested colleagues - the transport went fine and the board made it there and back in one piece, but it was pretty nerve-wracking plugging it in a hoping it still worked!
For this reason, I've started to think about doing a PCB design with the board as it stands today. This would have expansion bus capabilities, allowing me to continue to design new bits (e.g. graphics) on breadboard, but would cement the working core of the computer onto a proper, reliable board.
Before I can do that though, I've decided to revisit some of my earlier decisions in light of this new plan - specifically, the address decoder and glue logic should probably be handled by programmable logic rather than discreet logic as they currently are.
The current design (with lots of 7400-series chips) works well, but is pretty power-hungry and takes up a lot of board space. Previously this didn't really matter, but if I'm now going to be spending $5-$10 per square inch of board space, I want to make sure I'm making the most of those inches. Replacing many of the fourteen 7400s with two or three PLDs makes a lot of sense now - it'll mean cheaper boards, and less soldering when the time comes to assemble them.
My reason for using discrete logic initially was for the learning experience, and I'm really glad I did it. I'm a lot more familiar with the 7400-series now, and have become used to designing circuits with them. I've found numerous uses for them in other projects too.
But going forward, it's time to cut the chip count. With that in mind, I've gotten hold of a bunch of Atmel ATF16V8BQL CPLDs, and started learning to use them (I've never done CPLDs before, so all good for more learning!)
My first stab has been the address decoder (CUPL code here - but go easy on me, this is my first time!) and, in basic testing, it seems to work exactly as it should. It even fixes a couple of bugs in the existing address decoder, including making the EXPANSIONSEL line work sanely and gating the whole thing off with the CPU's /AS line.
Even better, it now takes up one twenty-pin DIP rather than a whole bunch of 14-pin DIPs, and uses less power. I've not actually integrated it into the build yet so may find it doesn't work in some horrible way, but according to the datasheet it should be plenty fast enough (faster than the discreet logic design) and as I say in manual testing it seems to perform perfectly, so I'm not expecting any big surprises (I know, I know, famous last words...)
10 hours ago •
It's been a few weeks since I've updated here as I've not had as much time to spend on the project as I'd like, but some progress has been made on the software side - I'm now using a proper toolchain for the firmware, consisting of GCC and VASM, so no more Easy68k weirdness to trip me up!
I've also fleshed out the Serial output via the MC68901 such that it is now completely interrupt driven. This replaces the polling it was doing previously. Whenever the software writes to the serial output, the data is placed into a ring buffer and the MC68901's vectored interrupts drive the actual output from there.
The serial driver is implemented partly in a mixture of C and assembly - the C code is here and the assembly is here. The top level directory for the firmware is here and contains all the code, the Make build, and bits and bobs like the linker script that ties everything together.
This was all done in a bit of a rush as I was giving a talk on the computer (and demoing it) at work so I wanted to have some level of sophistication. It's all since been cleaned up and is now pretty nice (in my opinion). The next step in the plan is to enable the RX serial line, and then put in a simple protocol (probably xmodem initially) to allow software to be uploaded via serial link, freeing me from the 16K ROM limit and allowing me to develop the OS without pulling the ROMS for programming every time I iterate :)
06/18/2019 at 08:02 •
In my last log, I talked about how I was getting interrupts to work, but that I hadn't yet made them work properly. It appeared that, no matter what the interrupt source in the MFP it would always present $FF as the vector during IACK. This was especially strange because the vector base set in the MFP's VR register is $40, so where the $FF was coming from was a mystery.
I suspected my DTACK generator wasn't robust enough as it didn't take IACK into account, so my first step was to redesign the DTACK generator to take that line into account. No EAGLE schematic yet but in Logisim the design looks like this:
This also adds in a way to generate the MFP's DS signal during IACK since the MC68010 doesn't appear to assert LDS during this time, and without DS the MFP won't generate DTACK.
This improved things somewhat, but it still wasn't working. Breaking out the probe and looking at the data lines during IACK was telling:
A glance at the mess that is D0 (and to an extent D1 and D3) told me what the problem was - I know the bus doesn't float at this point (especially because it's still not stable after the MFP asserts DTACK), so this can only be contention.
Sure enough, further tracing revealed that, due to the design of the address decoder, the ROMCS lines also get asserted for a short while during an IACK cycle. This is because I didn't gate the address decoder off with anything which, while not exactly efficient, was good enough at the time. Gating it off with AS wouldn't help, as the CPU asserts AS during IACK. I realised I could gate it off with the inverted IACK signal from the new circuit from the shot above.
That looked much better:
Nice clean edges, and tracing all the data lines showed the MFP was putting 0100 0101 on the bus, which is $45, exactly the right vector for the MFPs Timer C interrupt.
A quick fix in the software to put the handler on vector $45, and all is working perfectly. Just need to tidy it up on the board and wire it up correctly, and I can move on to the software support for the interrupts. Currently it's just thrown together on an extra board, and looks like this:
06/16/2019 at 21:35 •
Now I have the MC68901 MFP integrated and the UART is working, I spent a bit of time this weekend setting up Timer C of the MFP as a timer tick I can use in the OS for multitasking.
For testing purposes, I've set the timer up at about 18Hz and have the interrupt handler just flash an LED connected to MFP GPIO 0. That way, I can visually see that the handler is being run. The code that sets up the MFP now looks like this (I've also upped the UART to 19200 baud):
* Initialise MFP * * Trashes: D0 * Modifies: MFP Regs INITMFP: * GPIOs move.b #$FF, MFP_DDR ; All GPIOs are output * Timer setup - Timer D controls serial clock, C is kernel tick move.b #$00, MFP_TCDR ; Timer C count is 0 (equivalent to 255) for 18Hz move.b #$0C, MFP_TDDR ; Timer D count is 12 for 19.2KHz move.b #$71, MFP_TCDCR ; Enable timer C with /200 and D with /4 prescaler * USART setup move.b #$08, MFP_UCR ; Fundamental clock, async, 8N1 move.b #$05, MFP_TSR ; Set pin state high and enable transmitter * Interrupt setup - Enable timer C interrupt for kernel tick move.l #MFP_VECBASE, D0 move.b D0, MFP_VR or.b #$20, MFP_IERB ; Enable Timer C interrupt... move.b #$20, MFP_IMRB ; ... and unmask it. rts
The code for the handler is very simple:
TICK_HANDLER: bchg.b #0, MFP_GPDR rte
Because I'm using vectored interrupts, this should be on vector 0x45 (MFP_VECBASE is 0x40 and Timer C is at offset 5 in the MFP), but because of a limitation of my IO DTACK generation it's not actually working that way - the IO DTACK circuit doesn't take account of /IACK and so /DTACK is immediately asserted, presumably before the MFP can put the vector on the bus. This causes the vector to always be FF, so for now the handler is just mapped there for testing.
To actually fix this, I'll need to revisit the /IODTACK generator. I've not put anything down in EAGLE yet, but my plan is to build something like the following:
06/09/2019 at 21:44 •
After a trying weekend working on the m68k build (see the previous two logs for details) I finally reached the "Hello, World" stage in the board's development. That's right, the UART is now working and integrated with the USB to TTL converter I'm using at the moment.
Once I'd worked out how to get the UART properly initialised, I hooked up the analyzer to the TCLK and SO lines of the 68901 and ran a simple bit of code that would just output "Ok". Here's the trace from that:
The "Ok" characters were moved into the UART data register as immediates. Expanding this to support sending out a null-terminated message exposed a wiring mistake in the board - it was the first time I'd done byte-sized reads from ROM space and it turned out I'd wired the chip selects backwards to the address decoder. With that fixed, I was able to output characters from a constant string in a loop:
Finally, by hooking up the USB converter and plugging it in (to my Windows laptop as the cable is too short to reach my main machine), I was able to verify the results in a terminal:
And just like that, the rosco_m68k now has a way to do output. The challenges and annoyances of the weekend are forgiven - onward and upward!
06/08/2019 at 20:24 •
So I talked in the previous log about how I had some issues while integrating the MC68901 MFP that I initially assumed were hardware-related, but that turned out to be software. Once I got past that, I hoped it would be plain sailing. Boy, was I wrong.---------- more ----------
This morning, I took delivery of a 1.8432MHz crystal, which is perfect for generating the standard Baud rates we're all used to. I was quite excited to hook this up and then write a bit of code to just send something, anything, via the UART and a cp2102 UART-to-USB board I had lying around.
I hooked up the crystal, and quickly put together a bit of code to set up the timers and enable the transmit side of the UART. Needless to say, it didn't work first time. No problem, I didn't really expect it to. I still had the BIN files of the previous ROMs kicking around so I burned them back to the chips so I could watch the lights blink while I fixed the code. It was at this point that hilarity ensued.
The board was dead. The ROMs can't have burned correctly, so I burned them again. Still nothing.
I assumed that the jury-rigged EEPROM burner I built from a NodeMCU and some flip-flops must have stopped working. I don't have another one, so I built a quick circuit that would let me manually flip the address lines and see the output on LEDs. The first and last few bytes in the ROM matched with the bytes in the BIN I'd burned.
At this point, I ordered a real EEPROM burner, just to be on the safe side. Also because I was tired of using my home-built one which still required me to recompile the NodeMCU firmware to change the data that got burned to the ROM.
I spent a few hours checking and rechecking all the connections around the ROMs in case I've dislodged something while pulling them for programming.
I'm now thinking I've killed the ROMs with ESD or something when I pulled them from the board to burn them. I order more ROMs, just to be on the safe side.
Again I'm deep down a rabbit hole with only a multimeter and cheap logic analyzer for company. The readings I'm getting are all kinds of weird (at one point I'm assuming the 68010 is broken, because I'm not seeing the first word read of the initial SSP - turns out the logic analyzer just needed to be unplugged and plugged back in again).
Finally, just as I'm about to give up until the new ROMs arrive, I decide to try one last ditch thing (which really ought to have been my first-ditch thing, but oh well) and delete all the old BIN files and build them from source.
Bingo. All working fine again.
The "previous ROM" BIN file turns out to actually be an incorrectly-named version of a much earlier memory test program I wrote, which spools through the entire address space. This includes the expansion space, which now doesn't have any DTACK generation (since the changes I made to support the MFP) and helpfully no longer has a working indicator LED (because I've kind-of abandoned the idea of expansion space in the current revision due to the output line from the decoder being not-really fit for purpose, more on that in another log).
So all the while I thought the board was dead, it was really waiting patiently for a DTACK that would never come, in an address-range that I don't have an indicator for. I did think it was weird that the run LED never went out, but just didn't connect the dots.
Oh well, you live and learn...
06/08/2019 at 19:16 •
It's been a very frustrating couple of days on the m68k project. All my own fault, of course.
Some background: I've been hooking up the MC68901 MFP which will give me some GPIO capabilities, a UART (actually a USART but I'm not using the S), some timers and support for vectored interrupts. All was going pretty well - it's a lot less wiring than most of the other chips on the board (five address lines, eight data, a few control lines and a bit of extra glue logic).---------- more ----------
Because I want to use it for vectored interrupts, its eight data lines have to be wired to the lower half of the MC68000's data bus. The 68901's data strobe is then hooked up to /LDS on the CPU, allowing the chip to supply the needed data at autovectoring time. This set-up means that the MFP's registers are all accessed at odd addresses - in my case, it's mapped into IO space so the registers start at 0xF80001 and occupy all the odd addresses up to 0xF8002F. For the code, I decided to put these into a separate file I could include in the main assembler source (extract below).
; MFP GPIO Registers MFP_AER equ MFPBASE+$03 MFP_DDR equ MFPBASE+$05 ; ... etc ...
With all the wiring done I added a bit of code to the existing ram test program (on Github) to initialise the MFPs GPIO and toggle one of the lines after each run through the RAM, allowing me to hook up an LED and get some visual feedback that the MFP was working.
In short, it didn't work. Worse, it made the whole board hang. Time to start debugging then!
I quickly realised (thanks to my address decoder blinkenlights) that it was hung waiting for DTACK, for an address in IO space. I've built a simple circuit that takes care of that specific situation (as the MFP likes to generate DTACK itself) so immediately assumed that wasn't working. I don't (yet) have a watchdog on DTACK to generate a bus error, so if DTACK isn't acknowledged, currently the CPU will just wait forever.
I broke out the meter and the analyzer and got to work to find out why the IO DTACK generator wasn't working. I discovered it was working fine.
I checked the 68901 was receiving the requisite 4MHz clock from the divider I put on the system clock. It was.
I checked the voltages around the board, and noticed they were still a bit shaky. In light of this I switched from the old daisy-chained power setup that has grown with the board to a star setup and threw some more capacitors on. The voltages were now rock solid (still a bit more drop than I'd have liked but well within tolerances) and the board still didn't work.
I checked all the connections, and found I'd accidentally wired the output of timer D to one of the address lines instead of the transmitter clock line. Since I wasn't enabling the timers yet I figured this wasn't the problem, but fixed it anyway. It wasn't the problem.
Eventually I realised that the CPU wasn't asserting LDS as expected for an odd address, and was instead asserting UDS. Cue several WTFs, more continuity checks to make sure I hadn't wired UDS and LDS backwards into the bus (I hadn't), and a serious amount of head scratching.
After a (long) while spent re-reading the CPU datasheets, staring at the code, checking various connections and swearing, my mistake dawned on me. You see, the extract of code above is the fixed version. The original had lines like this:
MFP_GPDR equ MFPBASE + $01
Which, with the assembler I'm using (I'm still using Easy68k for quick stuff as it has a convenient way to dump odd/even binaries) means "Set MFP_GPDR to 0xF80000, and then ignore the rest of the line because it's a comment". 0xF80000 is an even address, and all the registers were mapped there. The CPU is asserting UDS correctly instead of LDS. Lack of LDS means the MFP isn't aware it's supposed to be generating DTACK, and so it doesn't.
Cue major facepalm. Removing the spaces fixed it right up, the code ran perfectly, and the LED on the GPIO line flashed away merrily. I'd also fixed the power issues (which I've been putting off for a while) and tidied things up a bit, so I considered it a win (admittedly, a hard-won one).
You'd think after struggling with that for the best part of a day, getting the UART working so I can finally see something on a screen would be a piece of cake, right? Well, you'd be wrong. Stay tuned for part two...
05/26/2019 at 10:26 •
Now that the board is reliably running code (more on that below) I'm into the next stage of planning, which will involve hooking up the MC68901 MFP (Multi-function Peripheral). This will give me some interrupt timers (meaning I'll have a tick for the kernel to use) and a USART which I'm going to use for the initial IO to an external terminal (via a USB converter).
Most of the work here will be simply connecting it to the bus, but it will mean DTACK-grounded isn't going to cut it any more, at least when /IOSEL is asserted. With that in mind, I designed a small circuit that will look after DTACK for me:---------- more ----------
This is super-simple, and is effectively the same as having DTACK grounded when RAM or ROM is selected (ignoring a bit of propagation delay of course). When IO is selected, however, it allows DTACK to be controlled by an external active-low signal, just like the one the MC68901 provides.
Gating the whole thing with AS is probably not necessary (and may even be undesirable - I won't really know until I build the thing and look at it with the analyzer) but felt cleaner.
Of course this is still the minimum-viable solution, but it will work for now. As the system grows DTACK generation may well become an issue again, and if it gets much more complicated I have a vague plan to move to an arrangement where DTACK can be generated by any one of a number of open-collectors which I'll wire-OR together.
A (hopefully) final update on reliability
Following my last couple of logs, I thought I'd nailed reliability. However, I noticed that it would still go wrong very occasionally. Everything would run fine for anything up to an hour or two, and then it would go wrong and end up asserting HALT.
This was really annoying, and I was thinking of all kinds of strange and exotic things - interference, weird timing-related things, a faulty chip? Then, while looking at something else with the multimeter I noticed that the voltages on the board were all over the place. The daisy-chained power connections between the breadboards were the problem, and putting in a couple of jumpers directly between the two ends of the whole system cured it (longer-term I plan to redo the power connections so there's no daisy-chaining).
Just goes to show that in homebrew computers, as in almost everything else, debugging should start from the basics. I'm happy to report that the board has been running now for a solid 24 hours without a glitch.
05/19/2019 at 12:47 •
As I mentioned in yesteday's log, I finally got the computer to run some code from ROM, but that is was unreliable and always seemed to wander off into oblivion after some time (a few tens-of-thousands of microseconds).---------- more ----------
From doing a lot of poking and prodding, and running trace after trace with the analyzer, I thought this was related to timing. My ROMs should be fast enough (just) to run with DTACK grounded, but clearly something was off.
For testing, I jury-rigged a DTACK generator using a 74LS93 binary counter in 3-bit mode. It looks like this:
05/18/2019 at 18:24 •
After a frustrating few days, the rosco_m68k is now successfully running code from the EEPROMs! It turns out I'd wired the ROMs to the wrong half of the data bus (facepalm, endianness again), and also needed to make a few other tweaks to get it working.---------- more ----------
The board currently looks like this (with a few not-yet-permanent fixes):
The key differences are:
DTACK is no longer grounded. The upper left breadboard in the picture contains a (temporary, but possibly soon to be permanently wired on the main board) DTACK generator based on a 74LS93 binary counter (in 3-bit mode) and a NOR gate. This is driven by the AS line (which drives the counters reset line) and the CLK, with DTACK being generated by the negated output of Q1, Q2 or Q3 depending on how long the delay needs to be (hardwired configuration right now).
Theres also an extra breadboard plonked on top of the main board in this pic, which is just a bank of resistors that are weakly pulling the data bus high.
Both of these changes are attempting to improve reliability. While the system does properly boot every time and starts running code, it occasionally wandered off into the wilderness and ended up double faulting (and asserting the HALT line).
The DTACK generation seems to have helped quite a bit in this regard - without it, the system felt a lot less reliable that it does with it. Sadly though it's still not 100% - although it will often run for several minutes it usually ends up halting for some reason.
But for now, I'm just happy it's doing anything at all - I've been a few days figuring out what was going wrong, and with only 8 channels on my logic analyzer it's been a challenge to try and see what the various bus lines were doing.