Electronics

My various electronics projects

Better Living With Macros

Writing a kernel from scratch is a big project, and it’s even more challenging when you are doing it in pure assembly language. It’s not that any one particular thing is necessarily hard, so much as it is verbose and repetitive. Fortunately it’s not necessary to keep repeating the same code over and over. Any decent assembler will support macros, and the one I am using (as65) has some pretty decent macro support. I’ve used this to create a number of macros that alleviate the most common pain points I have encountered. Let’s take a look at a couple of examples.

Walking a Linked List

Something I am doing quite a bit in my new code is walking linked lists. This is pretty basic data structures task; in C it’s just one line:

        ptr = ptr->next;

In 65816 assembly it looks like this:

        ldyw #OFFSET_OF_NEXT
        lda [ptr],y
        tax
        iny
        iny
        lda [ptr],y
        sta ptr + 2
        stx ptr

Again, it’s not that this is hard, but it’s something I end up doing a lot. Fortunately, with a little macro work, this can be made fairly trivial:

.macro next_entry loc
        lda     [loc]
        tax
        ldyw    #2
        lda     [loc],y
        sta     loc + 2
        stx     loc
.endmacro

This macro makes one assumption; that the pointer to the next entry is always in the first four bytes of the structure. I could make the macro take an optional offset, but all of my structures are designed this way, so I didn’t feel it was necessary.

With this macro, the eight lines above are now one line:

        next_entry ptr

Function Stack Frame Management

Now let’s look at something more in-depth, something that stretches the limits of what’s possible in macros.

On the 65816 it is often desirable to set up a local direct page on the stack, both to make it easy to access stack-based parameters and to support local variables. Setting this all up is fairly boilerplate, so I wrote macros for it. First, let’s look at the macros themselves, which are a bit more involved than the trivial example above:

.macro _BeginDirectPage
        .struct
        .res 1
.endmacro

.macro _StackFrameRTS
        s_dreg    .word
        s_ret     .word
.endmacro

.macro _StackFrameRTL
        s_dreg    .word
        s_ret     .word
        s_bank    .byte
.endmacro

.macro _EndDirectPage
        _pend  .byte
        .endstruct

@lsize := s_dreg - 1
.endmacro

.macro _SetupDirectPage
        phd
        tsc
.if @lsize > 0
        sec
        sbcw    #@lsize
        tcs
.endif
        tcd
.endmacro

.macro _RemoveParams pend
.ifblank pend
          @pend := _pend
.else
          @pend := pend
.endif
.ifdef s_bank
          @psize := @pend - s_bank - 1
.else
          @psize := @pend - s_ret - 2
.endif
.if @psize > 0
.ifdef s_bank
        shortm
        lda     s_bank,s
        sta     s_bank + @psize,s
        longm
.endif
        lda     s_ret,s
        sta     s_ret + @psize,s
        lda     s_dreg,s
        sta     s_dreg + @psize,s
.endif
        tsc
        clc
        adcw    #@psize + @lsize
        tcs
.endmacro

There’s a lot going on here, but it just involves trying to automatically figure out how many bytes of parameters and local variables need to be set up, and how many bytes of stack need removed before returning to the caller.

Starting at the top, the _BeginDirectPage, _EndDirectPage, and _StackFrameRTx macros are defining a STRUCT that generates some function-scoped direct page labels. A struct is an as65 construct that acts sort of like a structure in C, except it’s not allocating memory. Instead, it just defines scoped labels whose values are their offsets in the structure. By default they start at 0, but since our DP actually starts at SP + 1, _BeginDirectPage starts off by reserving an unnamed byte at 0.

Next, the _SetupDirectPage macro takes care of saving the DP, moving the stack pointer down for local variables (if needed), and setting D to point to the new direct page. At this point all parameters and local variables are accessible.

Finally, _RemoveParams cleans up the stack in preparation for returning to the caller. It takes one optional parameter, which is the name of the first output parameter. This is needed to calculate where input parameters end so that the proper number of bytes can be removed from the stack.

Here’s a snippet of my kmalloc() function, takes a single WORD (16-bit) length parameter, and returns a DWORD (32-bit) pointer. It also has two local variables, one WORD and one DWORD.

.proc kmalloc
        _BeginDirectPage
          l_best_size   .word
          l_ptr         .dword
          _StackFrameRTS
          i_size        .word
          o_block       .dword
        _EndDirectPage

        _SetupDirectPage
        ... do stuff ...
        _RemoveParams o_block
        pld
        rts

The resulting assembly code looks like this:

        phd
        tsc
        sec
        sbcw  #0006
        tcs
        tcd
        ... do stuff ...
        lda   09,s
        sta   0B,s
        lda   07,s
        sta   09,s
        tsc
        clc
        adcw  #0008
        tcs
        pld
        rts                             

The first part is the result of _SetupDirectPage. It just moves the SP down by six bytes to account for our two local variables, then sets D to SP. If there were no local variables defined the SEC/SBC/TCS lines would be omitted.

At the end is the result of _RemoveParams. It first moves the saved D and return address on the stack up by two bytes, erasing the input parameters. It then moves SP up by eight bytes: 6 for local variables and 2 for the input parameter. After that the code restores DP and returns to the caller.

While these macros are much easier than doing this manually they are not perfect, and still have room for errors:

  • The D register is saved for you, but you must remember to restore it yourself. This was needed for a particular use case I won’t go into here.
  • You have to make sure to use the right _StackFrameRTx macro depending on whether your function returns with RTS or RTL.
  • The ordering of params is vital. Local variables must be listed first, followed by the stack frame, then input parameters, and finally output parameters.
  • If you have output parameters, and fail to pass the name of the first one to _RemoveParams, then it will remove the wrong number of bytes from the stack and the function will crash on RTS/RTL.

These shortcomings are largely due to the limitations of ca65’s macro definitions I have been considering adding a preprocessor step to my build process, either a generic C preprocessor for better macro support, or a custom one that can generate boilerplate code based on some directives in the source file.

Why not C?

Ideally I would write some of my code in a higher-level language such as C, and reserve the pure assembly for time-critical functions such as interrupt handlers. Unfortunately, the C compiler that’s part of my tool chain, cc65, does not generate native 65816 code at this time, making it unusable. In fact,, there are only two C compilers for the 65816 that I’m aware of: the WDC Tools and Calypsi. Both are free for personal use, closed-source tools. Additionally, the WDC Tools are Win32 binaries, so they must be executed via WINE, which makes things messier and slower.

In general I avoid closed source tools as much as possible. It’s not because I’m opposed to them in general (I am), so much as that they tend to become unmaintained. Once that happens it rapidly becomes difficult or impossible to make them run properly on newer Linux distributions. Frequently all that is needed is to recompile the tools against newer libraries, something that’s impossible when the tool is closed source.

In the end, just getting my existing code to assemble under a new tool chain would be a major undertaking, one that I’ve decided is just not worth the effort. if I ever were to make a change, it would probably be to Calypsi, simply because it has native Linux binaries.

(By the way, I have experimented with WDC’s C compiler, and the function setup/exit code it generates it nearly identical to what my macros are generating. I have not tried this yet with Calypsi.)

Conclusions

I am still hopeful that someday somebody will finally create a good, fully open-source C compiler for the 65816. There have been rumblings about adding 65816 support to the llvm-mos project, though to date nothing has been released. If and when this happens it would provide a great ecosystem for 65816 development, and I would be tempted to port my code over at that point.

In the mean time, clever and judicious use of macros has made my work much easier.

An Updated Roadmap for JR/OS

The origins of JR/OS date back to my ill-fated COLE-2 project. I had originally envisioned it as being a simple single-tasking OS that could run on a variety of hardware. There would be a BIOS layer that interfaced to the hardware, with the actual operating system interfacing to the BIOS. JR/OS would not provide much functionality other than device and file system management.

Over the last year it has become apparent that there will probably never will be a plethora of 65816-based systems. It’s just not as popular a target for hobbyists compared to the 6502. I decided that the only systems I should worry about supporting are my own. Thus the BIOS/OS separation fell away to become just JR/OS.

Freed from the constraints of having to run on a variety of differing hardware, a more ambitious goal for JR/OS began to crystallize. JR/OS will be a full multitasking OS with memory protection, virtual memory, and a POSIX user space environment. It goes without saying that this is a much bigger project than my original plans. However, I think the journey will be fun and educational even if it doesn’t get to its destination.

To maximize my chance of success I am going to do this in phases. The idea is to make JR/OS usable for user space programs as quickly as possible. I will then add functionality in manageable chunks. Each phase will add tangible functionality, which should hopefully keep me motivated to keep moving to the next phase.

Required Features

Before I delve into the phases of this road map i want to go over some requirements I have. These are in addition to the core OS features such as multitasking and memory protection:

  1. The system must be usable at power-on even without booting from storage. This means booting into a ROM-based BASIC interpreter.
  2. The BASIC interpreter must support storage (LOAD, SAVE, OPEN, etc).
  3. If storage is detected at boot time the system must attempt to boot from it.
  4. The system monitor must always be available.

If it is not obvious I am basing my design on the Apple II (or more accurately the IIGS). This should not be surprising, given my stated desire to create a modern evolution of the IIGS.

With these requirements out of the way let’s take a look at how I’m planning to build all this.

Phase 1: User space environment

The first phase is the easiest, and the one on which I will be starting work this week. In this phase I will get the user space environment fleshed out enough that it’s possible to write functional applications.

For me to consider this phase “done” the user space must provide the following minimum features:

  1. Full console I/O
  2. Memory management (allocate and deallocate memory)
  3. Device access (serial ports, SPI devices, SD cards)

Much, if not most, of this is already implemented in some form, but there are some gaps to plug. The largest of these is that memory allocation needs to be wrapped in some sort of standard library. This is because memory management will eventually move to user space, and I want the change to be transparent.

Phase 2: File system support

The next step will be to implement a file system. The ability to load and save files is critical for writing any truly usable software. Since the hardware only supports SD cards it makes sense to use FAT32 as my file system. The current JR/OS code already has some support for this: FAT32 partitions are detected and enumerated at boot time.

It is in this phase that I’ll also be adding the ability to automatically boot from storage. If no boot media is found then the built-in BASIC will be launched.

Phase 3: Preemptive multitasking

This is where things will start getting really interesting. In this phase JR/OS will gain support for multiple processes and preemptive multitasking, but without virtual memory or memory protection. In this sense it will resemble GNO/ME.

It is also in this phase that JR/OS will implement multiple user IDs and multiple privilege levels. Theoretically at this point JR/OS could support multiple simultaneous users by spawning a console on the second serial port. However, I am not actively planning on implementing this at this point.

One thing I will need to iron out in this phase is how to support the system monitor going forward. Currently the monitor is basically a user application running with root privileges. It uses the JR/OS direct page and stack, and directly accesses some JR/OS internals. The monitor will need converted to a regular application here and divorced as much as possible from JR/OS itself.

Phase 4: Virtual memory support

This phase will begin once JRC-2 hardware is available, as it will require a PMMU. In this phase I will move memory management into the user space runtime. JR/OS will continue to manage its own memory allocations, but only provide VM support for user space.

Phases 5+: ???

Beyond phase 4 my road map is currently undefined. A major part of these phases will likely be making fleshing out POSIX support in user space.

I may add networking support here, though it would probably be limited to a PPP link. I don’t even know where to begin building an Ethernet card.

Another very lofty goal that would fit in here somewhere is to implement a IIGS-compatibility mode in JR/OS. This is purely a pie-in-the-sky idea right now. I have barely scratched the surface of how this might be accomplished.

Final Thoughts

As I mentioned at the start, this is a very ambitious road map. I can’t even begin to estimate how long it will take, especially considering my unpredictable periods of burnout. Only time will tell…

JRC-1, One Year Later

JRC-1

It has been just over one year since I assembled my first build of JRC-1. It’s also been that long since my last significant post here, but despite the radio silence I have been making steady progress and I’d like to share that now.

Firmware Updates

First, and most importantly, the firmware has seen a lot of improvement. This includes:

  • I made the ROM code 16-bit clean. Much of the ROM code originated on my COLE-1 board, which was a plain 65C02, and had been updated just enough to work on the 65816. Now, everything except low-level driver code runs in 16-bit mode by default.
  • I implemented a device manager and registered the built-in hardware as devices.
  • There is now a functioning SD card driver for reading and writing blocks.
  • Implemented the initial scaffolding of a DOS, including scanning devices at startup and reading DOS-style partition tables.
  • Created a built-in mini assembler reminiscent of the Apple IIGS mini assembler. There is still work to be done here but it is already usable for writing small programs.
  • I created an Apple IIGS-style memory manager to manage allocation of memory to both the OS and user space programs
  • The code base got a fair amount of reorganization as the amount of code continues to grow. This includes not only physical file layout but also changes to how the various layers of the operating system (core OS, device drivers, DOS, and the system monitor) interoperate.
  • Added a Makefile target for the memSIM2 ROM emulator. This USB device emulates the physical EEPROM and allows me to upload new code directly to the board (including a reset afterward) with no fuss. Testing a new ROM image can now be done in just seconds.

Hardware Updates

There have only been two hardware changes in the last year:

  • I installed a bodge wire from the 65SPI’s external clock input to PB7 on the VIA. SD cards (at least the one I am using) cannot reliably be initialized without starting with a slower SPI clock. The bodge allows the VIA to be programmed to provide a slow initialization clock for SD card initialization; once the card is initialized it switches back to the faster, PHI2-based clock.
  • I installed a 16 MHz crystal in the board, bringing the system clock up to 8 MHz. The board has been rock-solid at this speed for several months now. This is already better than I expected, and it’s possible I might be able to squeeze out another 1-2 MHz down the road.

Next Steps

My top priority right now is to finish the mini assembler, as having the ability to easily write code directly on the system makes testing ideas much easier. I am hoping to have this completed by the new year.

After the mini assembler I have a number of subprojects that need attention:

  • Building out the scaffolding for supporting task switching (multiple processes).
  • Fleshing out the user space environment so that actual user space programs can be written
  • Implementing a FAT32 driver
  • Developing a BASIC interpreter application. This will require the user space environment to be usable.

I also have an updated road map for the future of JR/OS as a whole. I had originally had that here, but this post was getting rather long, so look for another new post very shortly.

JRC-1 Has Officially Arrived!

I’m happy to report that the JRC-1 boards arrived a few days ago, and exactly as I predicted I had a build up and running by Christmas. Here it is, in all of its black solder mask glory:

JRC-1 as of 2021-12-23

I guess the third time’s a charm, because this is my third full build, and the board booted up with no hardware issues. It’s currently running at 4 MHz; it should run faster but I have not tried yet.

The board is not perfect; there are a couple minor problems with the layout. The footprints for the DS1813s (the reset and NMI controllers) are reversed, and the footprints for the expansion slots don’t fit the slot connectors I have. Neither problem is critical and both have easy workarounds, so at this point I am considering this design final. All of the the hardware design files (KiCad project and CPLD Verilog project), as well as the latest firmware code, are in the JRC-1 GitHub repository.

With the board up and running I am turning my attention to the firmware. Currently I’m working on cleaning up the code so that I can start the major work. Next up is getting the SD card driver working, and then work on JR/OS. That’s where the real fun begins!

A Quick JRC-1 Update

Well folks, today the JRC-1 project reached a major milestone: the design is complete, and parts and PC boards have been ordered!

KiCad 3-D rendering of the final board design.

I should to have everything in my hands in about a week, and with some luck, a working assembled board a few days after that.

Ordering PC boards is always a bit nerve wracking for me. One can never be 100% sure that a board is going to work until you get them back from the fab house and try assembling one. In addition, the final JRC-1 board comes in at 200×120 mm, which is considerably larger (and more expensive) than any of my previous designs. Still, I am very relieved to have finally hit this milestone, and assuming no major problems arise with the design I can shift my focus to creating the BIOS and operating system.

Over the next few days I will be copying most of the information from my private JRC-1 planning document over to the JRC-1 home page. This will include information such as the memory layout, component information, and details on the expansion slots and user port. Stay tuned.

Expansion Bus Woes

When I first start planning the JRC-1, my main goal was to produce a simple base design with which I could experiment with more advanced features down the road. Something akin to the original IBM PC, except with a 65816. Meeting that goal meant I needed some sort of expansion bus.

The Design

Before I started any design work I ran my original plans by the folks at the 6502 forums. There are a lot of very smart people there who have experience with these things, and I trust their advice. The recommendation was that I not bring the system buses out of the slots. Instead, I should create an expansion bus behind a 6522 VIA.

After a few iterations I settled on a bus design. My bus looked very similar to the typical bus you might find on a RAM or ROM chip. It has six address lines, eight data lines, a R/W signal, a slot enable, and an I/O strobe. A pulse on the I/O strobe signals the card to perform a transaction. This design was chosen to interface to as many available peripheral chips as possible with minimal additional logic. It also fits on a single VIA, so I could test the design on COLE-1+.

The Experiment

At this point I need to mention that the deal-breaker application for any expansion bus is that I can create a video card for it. Sp. I adapted the TIVI code that I wrote for the TinyFPGA board to run on my ULX3S and hooked it up to COLE-1+. The results were….less than encouraging.

The whole setup was terribly unreliable. Sustained writes would frequently result in missed or duplicated bus transactions and thus garbled frame buffer data. Hooking up a scope did not reveal anything wrong at the signal level. The signals were very clean, and there was very little ringing or cross-talk. Manually slowing things down in code did not help at all, either.

I have spent many hours in the past week trying to fix this issue without success. My working theory is that the relatively high rise/fall times on the strobe line may be problematic. At 50 ns it might very well be causing metastability issues inside the FPGA. Given enough time I could probably make this work, but my frustration level has reached maximum.

Moving Forward

After a lot of thought I’ve come to realization that my expansion bus is turning into the kind of project-killing feature that doomed COLE-2. The irony here is that I was trying to avoid this exact issue by having expansion slots in the first place!

If I want JRC-1 to actually be finished and built, then I need to move past this. Instead of trying to come up with the perfect design on my first try, I’m just going to build what I really want, and if it is unstable, or fails outright, then I learn from that and design a better version.

And so I have decided to go with an ISA-style bus that provides access to buffered versions of the main board buses. This should work fine for at least a couple of MHz, and anecdotal evidence from folks using RC2014 and ISA backplanes suggests I may be able to hit 8 MHz without too much trouble. The only way to tell for sure is to build the dang thing and see what happens!

COLE-1+ is alive

I’m happy to report that the first build of COLE-1+ is up and running! JLCPCB created some beautiful boards and delivered them almost exactly a week after I put in my order. Here’s a photo of the assembled board:

There were only two fixes I had to make to the board after delivery. One is to add the missing pullup resistor on the ACIA IRQ line that I mentioned in my previous post; the other was a bodge wire to bring the A11 address line to the GAL. With those two fixes the board works exactly as expected.

Not only does this board run its original firmware just fine, it also runs a slightly modified version of the COLE2 firmware, and this is the firmware version I am going to use going forward. My plan is to make this a universal BIOS for all of my future 65816 builds. This will allow me to continue development on the firmware while I plan out and design JRC-1.

In the end I am very happy with this build. I may do a re-spin of it down the road to correct the two errors I had to correct, and possibly add a power switch, just so that I can turn the unit off to pull the EEPROM without having to disconnect the USB serial cable. But, for now I’m going to concentrate on the firmware and on the JRC-1 design.

Introducing JRC-1…and COLE-1+

Apologies for the long silence on the blog; I’ve had work and health issues that ate up a considerable amount of time during the first half of the year. Fortunately that’s all over and I am returning to my many projects.

JRC-1

First, I’d like to introduce the JRC-1, or Josh’s Retro Computer 1. This is a direct successor to COLE-2, with the aim of actually bringing the design to completion. Along those lines I ditched some of the more complex features of COLE-2, including built-in video and the game/joystick ports. Instead, it has an expansion bus with three slots that can be used to add features down the road.

Unlike its predecessor, JRC-1 is a 5V design. I had previously moved to 3.3V in order to gain access to larger RAMs, but it added complexity, as some parts of the design still needed to be 5V.

At the moment JRC-1 is still in the early planning stage. Once I am confident my design can be implemented I will begin work on the schematic and PC board. I am planning to skip the home prototyping stage for this project, and instead go directly to a manufactured PCB.

COLE-1+

As part of planning out JRC-1 I have been testing some design ideas by using my original COLE-1 board. However, that board is a bit problematic: it has power instability issues, and the footprint for the 6850 is wrong and required an ugly hack.

Since having a good test bed for my design ideas is important, I decided to spend a few hours fixing up the COLE-1 design a bit:

  • The 6850 now has the correct footprint
  • Added a large electrolytic capacitor to help smooth out the power supply
  • Replaced all of the discrete logic with a single 22V10D GAL. This also allowed me to tighten up the address decoding to make all but the bottom 1K of the ROM available.
  • Swapped out the CPU for a 65C816, because it was very easy to do so.
  • Removed the dedicated 1.8432 MHz crystal for the 6850. Instead, the CPU and the 6850 now share a 3.6864 MHz crystal.
  • Reduced the serial port speed to 57.6k bps, due to the use of a double-speed clock crystal.
  • Made the board layout more compact, and made all ICs align vertically.
  • Added some silkscreen to label the pins on the expansion headers.

In the interest of time I used the FreeRouting autorouter to route the board. It actually did a pretty decent job, and I only needed to make a few tweaks to some via locations. I may give it a try for JRC-1 down the road just to see how well it works on a larger design.

The final result of this work is officially the COLE-1+, as the feature set is almost identical to COLE-1. Boards are already on order from JLCPCB; I should have them in about a week. Hopefully I didn’t mess up anything this time around!

(Nope! It seems I left off a pullup resistor for the 6850’s IRQ line. Sigh. Fortunately this will be a very simple and relatively clean fix during assembly.)

Moving on from COLE-2

Over the last few days I have been thinking a lot about COLE-2’s future, and I kept asking myself if it is worth taking this project all the way to a final build. After much debate I finally admitted to myself that it’s not.

This project is a stepping stone, part of a long-term plan to build my dream 65816 computer. Think of it as the Apple IIGS upgrade I wish Apple had produced 30 years ago. Each design iterates on the previous one until my goal is reached. Since COLE-2 is not the end goal, once I build it it will end up in the same drawer as COLE-1 when I start my next design. Thus I’ve decided not to pursue further work on this hardware design.

As a stepping stone I think this project has done very well for me. I learned how to design a 65816 system, how to program GALs and CPLDs, and even some FPGA skills. Now it’s time for me to take the next step towards my final goal.

The Joys of Programmable Logic

My next design will be built around the ULX3S, an FPGA development board based on the Lattice ECP5. Compared to the iCE40 I used for COLE-2’s video controller the ECP5 is a beast; it sports over 84,000 LUTs compared to the paltry 7,680 on the iCE40. With that level of resources available I can build the entire system inside the FPGA itself.

Nothing is 100% finalized yet, but my plan is to connect a 65816 directly to the FPGA. The FPGA will provide the CPU with all necessary resources, such as RAM, ROM, and I/O. Future iterations may even replace the CPU with a soft core inside the FPGA, which could let it reach speeds that a physical 65816 simply can’t achieve.

The beauty of this design is that I can iterate on it without changing the hardware at all. In theory this design can be anything once it’s built, even a IIGS-compatible build, just by changing the FPGA’s code. I think that level of freedom will keep me occupied for a very long time!

As a bonus this new design will be quite small. This will make it very easy for me to push it aside when I want to use my workbench for other projects for a while.

I don’t have a name for this project yet, other than “not COLE-3”. I’ll be thinking of names as I wait for my ULX3S to arrive, which should be before the end of the month. I’ll announce the name at that point.

New Year’s Update

Happy New Year to everyone reading this much belated status update! I apologize for the radio silence, but I took a break from this project over the summer. I came back to it en force in late November, taking advantage of my holiday time off to really sink my teeth into it again. Quite a few things changed, so let’s dive right into it.

It’s Alive….Alive!

My first goal when coming back to the project was to get the new build booting to a serial console. To make this easier I threw together a bus analyzer using a Raspberry Pi and some MCP23017 I2C I/O expanders that I had in my parts bin. With two of them, plus some Python code, I was able to have the RPi monitor the CPU buses while pulsing the system clock. This severely cut down the time it took for me to find the various wiring mistakes, and it was useful enough that I hope to someday create a more permanent version of it for future builds.

At this point I had a booting system, but it was not very stable. I partly solved this by switching (no pun intended) to a better bench power supply. The big change, though, was getting rid of all of the discrete logic, which brings us to…

The COLE System Controller

The CSC is the core of the system and is responsible for managing the system buses. It’s implemented on a Xilinx XC9572XL CPLD, and it’s responsible for:

  • Bank address latching
  • High address line generation (A16-A18)
  • Chip selection for RAM, ROM, and I/O
  • IRQ aggregation

In the previous build, the bank address was latched by a 74ACT73, which remained open (transparent) during Φ2 low, but closed and latched the bank address on the Φ2 rising edge. This is the design recommended by WDC in their application notes for the 65816.

When I first implemented this design in the CSC it made the system even more unstable. However, by switching my design to latch the bank address on the Φ2 rising edge the system suddenly became rock solid I don’t know the reason for this, but since it works I am going to leave it as-is. The only real downside to this design is that my address decoding is not as fast as it could be, and thus I would need faster RAM and ROM for any given clock speed.

TIVI

With the system booting reliably I next turned my attention to getting video into a working state again. The new video controller is called the TIVI, or (TI)ny (V)ideo (I)nterface, and is implemented on a TinyFPGA.

The TinyFPGA is a wonderful and inexpensive piece of hardware, but it does suffer from one major problem: a lack of user-accessible I/O pins. There are far too few pins available to connect it to any sort of external RAM. Fortunately, the TinyFPGA has 16 KB of on-chip dual-port block RAM. which is just enough for text and basic graphics.

Since the video RAM is on-chip the TIVI chip operates like many old-school CRTC chips, and provides registers for reading and writing video RAM. This isn’t as bad as it sounds; through the use of an auto-incrementing address register the CPU can read/write contiguous chunks of video RAM at full speed.

The output from the TIVI is an analog VGA signal with a resolution of 640×400 pixels, at a refresh rate of 85 Hz. I chose this mode because it uses a 31.5 MHz pixel clock, which the TinyFPGA’s PLL can synthesize exactly. Standard 640×480, by contrast, uses a 25.175 MHz clock, but the closest value the PLL can generate is 25 MHz. While it’s close, and it sort of works with my LCD monitor, it is technically out of spec.

Due to the small amount of available VRAM the TIVI can’t output an actual 640×400 image. The current design implements two video modes:

  • 80×25 text, with 8 background and 16 foreground colors, hardware blink, a programmable hardware cursor, and a programmable font.
  • 160×100 graphics in 6-bit color.

The text mode uses an 8×16 font stored in the high 4K of VRAM. The BIOS loads the font from ROM at startup, and when switching from graphics mode back to text mode.

Down the road I would like to try adding hardware scrolling, at least for text mode. I may also add a higher res mode such as 320×200, but this is not a priority for me.

The Speed Force

In this new build the TIVI generates the system clock by dividing its 63 MHz master clock by a programmable divisor. The default value is 24, which produces a system clock of 63/((24+1)*2) = 63/50 = 1.26 MHz. I chose this value because breadboard builds aren’t the best for grounding and noise, and 1.26 MHz is an easily attainable target in these conditions.

With the TIVI chip now accessible by the CPU I was able to twiddle the divisor after boot and see how fast I could push the system. So far I have had good success running with a divisor as low as 4, which equates to a system clock of 6.3 MHz! This exceeds my original design goal by a whopping 20%. It may be able to run a bit faster, but the next lower divisor produces 7.87 MHz, which instantly freezes the system.

The COLE Input Controller

The CIC is an ATmega 328p that handles all user input, including the keyboard, the mouse, and both game pads. It connects to the rest of the system via SPI. It’s similar to the 8042 keyboard controller in IBM PCs; it handles the low-level communications with devices, freeing the CPU for other tasks. It also manages the system’s RESET and NMI signals. This includes the initial power-on reset function previously handled by a dedicated IC.

Unlike my previous build the CIC does not do PS/2 scan code conversions, nor does it manage the LEDs automatically. Instead, raw PS/2 scan codes are fed to the CPU, which is now responsible for key mapping and LED management. It does, however, watch for some special key sequence and act upon them. Pressing Ctrl-Alt-Delete will reset the system, and Ctrl-Alt-Break will toggle the NMI line. Since the CIC handles these directly they are guaranteed to work even when the rest of the system is non-responsive.

The SPI/65B

My original build used two 6522 VIAs. One was dedicated to receiving data from the CIC, plus a bit-banged SPI implementation. The second VIA was reserved solely to the user port. There were a couple of problems with this design, however.

First, there was no way to send data to the CIC. This meant the BIOS couldn’t change key repeat rates, mouse resolution, or the keyboard LED states.

Second, the bit-banged SPI was really slow, and very CPU intensive.

Fortunately for me I stumbled upon André Fachat’s SPI/65B project, a VHDL rewrite of Daryl Rictor’s 65SPI hardware SPI controller. Daryl’s original design is wonderful, but it is written in ABEL, which is no longer supported by the Xilinx tools. André rewrote Daryl’s implementation in VHDL, with a few bug fixes and enhancements.

I was able to upload André’s design to an extra CPLD, and after a few minor tweaks I finally had working hardware SPI. This has now replaced one of the VIAs in my build, leaving just the user port VIA.

For maximum compatibility the SPI interface is running at 5V via a small level shifter. The CPLD itself is 5V tolerant, so the level shifting is only necessary on the output signals.

Now What?

With the final hardware design finally coming together, it’s almost time to start work on a PC board design. I would like get the first (and hopefully only) PCB design done and built by this spring.

There’s a lot of hard work ahead, but I’m looking forward to it!