The Cat-644 is a computer I've been developing around an Atmega 644 microcontroller. It has been a long running side project that I get out every once in a while to work on. I've decided to work on it some more for this round of the retrochallenge.
I left off last time by implementing the beginnings of a tree and list-based data store on an SD card. It is sort of a filesystem, but more like a LISP tree. The root node of the filesystem has a block of data (about 240 bytes), a pointer to the next node, and a pointer to the first child node. You can think of each linked list of nodes as a file, and a list of lists to be a folder. It is all really just a giant binary tree, where each node contains some data and two branches. Applications are directly in control of how their data is stored on disk. Applications can sort and organize their data in binary trees, lists, etc.
In a previous retrochallenge, I also created a memory allocator that uses the AVR SRAM as temporary storage for a much larger handle-based heap stored in external RAM. Malloc and Free are similar to C, but the physical memory blocks are reference-counted. A program can also Release a pointer, which frees a physical reference to it. That memory is not deallocated, just a candidate for being moved around in memory or swapped out to external ram. (Maybe even potentially disk?) When a pointer is Released, a handle is returned. That handle can later be used to Grab the object, where Grab returns a real physical void*. This is similar to virtual memory, but there are no page faults, instead applications explicitly control their working set.
I am listing all the things I need to do to finish the OS for this computer.
We are about halfway through the retrochallenge. This is what I've managed to get done so far:
When the computer starts up, a short C program runs after the hardware and drivers are initialized. This program displays a "@>" prompt, and accepts a few short commands. The motivation behind this was to be able to boot bytecode programs without having to reflash the AVR... previously the bytecode was copied from the ROM.
Working shell commands:I added several syscalls reachable from the bytecode interpreter. The previous demo has only basic keyboard/screen terminal style syscalls: read1, write1, and ready, which are the equivalent of getc, putc and kbhit from C. These are the syscalls currently supported:
Character Device Syscalls: Operate on serial port, keyboard (readonly) and screen. The screen device, if asked to read, will read from the keyboard and forward them to the application. Programs that read/write from a character device work either the screen, or over the serial port.
Graphics Syscalls: These are implied direct to the video driver.
The OS has several memory syscalls. On the surface, alloc and free look like classic malloc. However, progams should take advance of the handle api. An object that is not immediately needed can be "released." The OS is free to move released objects around... when the working set of memory exceeds internal SRAM, objects will be moved to external memory. Data structures like trees and lists are preferable on this OS... the tree can be much larger than the internal 4k SRAM, if the application uses handles for its next/prev and parent/child pointers. Applications should pass handles around until it actually needs to access the data. At any time, the program can call "hgrab" on a handle to get a real pointer to it. This may trigger a copy from XRAM. The pointer is valid until the object is released again. If the system it out of internal memory, and too many objects are grabbed at once, grab can fail. Programs should grab only what is needed, when its needed. There are some more calls I want to implement, mostly as optimization opportunties. For instance, if an object was grabbed and released without writing to it, there is no reason to copy it back to XRAM; there needs to be a read-only version of hgrab.
In the last retrochallenge, I got a simple linked list tree filesystem partly running. This is not yet available to the bytecode interpreter. I need to add a directory API on top the the tree API, so that the root can contain a list of named subtrees. These, I suppose, would roughly be directories. Each subtree can then either be a named list of subtrees (I suppose subdirectories), or a program-specific tree structure. (Files)
I have enough syscalls to interact with the screen and keyboard, including sprites. Instead of diving into disk syscalls, I want to write a simple game. The best way to keep me motivated on a project is to get it actually doing something.
The microcontroller chosen for this project, the ATMega, can only execute programs out of ROM. I knew that limitation from the beginning; I have a bytecode interpreter embedded in OS that I have been extending during this retrochallenge. As I write short programs for it, I realize there are some operations that are inconvenient, so I have been adjusting the instruction set as I go along.
The interpreter is a direct threaded-code interpreter. One unusual feature of this interpreter is that the opcode of the instruction is actually part of the address of the instruction handler. The upper byte address of the handlers are fixed: The entry points of the interpreter must be in a table 256 instructions and aligned along a page boundaty. Some short, common instructions like add, are embedded directly in the interpreter table, whereas instructions that already take longer, like multiplication, are JMPed to the actual handler.
Here is what the assembly language for the emulated 16-bit CPU. I will add comments and explain how this works soon. This program is the main loop animating several rows of sprites moving left and right on the screen. I intend to fill this out into a Space Invaders clone.
FINDDEV=1 WRITE1=2 READ1=3 DRAWSPRITE=5 UMOD=9 CLEARSCREEN=6 ldi a 0 syscall @CLEARSCREEN draw: ldi a @sprite offset a mov c a #c has sprite ldi b 20 nextrow: ldi d @apos offset d lda d next: push a push b push c #save pos and sprite swp d ldi a @eraser offset a mov c a swp d add $ffff swp b add $ffff swp b syscall @DRAWSPRITE #draw sprite c at a,b pop c pop b pop a syscall @DRAWSPRITE #draw sprite c at a,b ldi d 16 add d #add increment #compare to 200 + apos push a ldi d @apos offset d lda d add 200 mov d a pop a cmp d jb @next ##next row swp b # a has y coord ldi d 16 add d #add to it ldi d 120 cmp d #compare jae @donerows swp b jmpr @nextrow donerows: ldi a @adir offset a ldb a #b=adir ldi d @apos offset d lda d #a=apos add b #a+=b sta d #apos=a #check if need to flip direction to - ldi b 30 cmp b jb @AA ldi a $ffff ldi d @adir offset d sta d AA: #check of need to flip to plus ldi b 8 cmp b jnz @noflip ldi a $0001 ldi d @adir offset d sta d noflip: jmpr @draw #halt: #jmpr @halt apos: word 16 adir: word 1 bpos: word 16 playerpos: word 120 sprite: byte $5 byte $5 data $ff03ff03ff data $0303ff0303 data $ffff03ffff data $0c03ff030c data $03ff0cff03 psprite: byte $7 byte $6 data $ffffff0cffffff data $ffff0cff0cffff data $ffff0cff0cffff data $ff0cffffff0cff data $ff0cffffff0cff data $0cffffffffff0c eraser: byte $7 byte $7 data 00000000000000 data 00000000000000 data 00000000000000 data 00000000000000 data 00000000000000 data 00000000000000 data 00000000000000
This has been a very long-running project. It has been in perpetual breadboard mode. Now that most of the focus is now on software rather than hardware, it is time to actually complete the hardware. There might be slight modifications in the future, but this is where I call it: This is the final 1.0 hardware design.
The enclosure is an ancient emptied out modem. I bought it WeirdStuff years ago when they were still in business. The front panel is a thin piece of wood, which eventually will be stained. On the front is just a power switch and reset button. I will probably add a power LED, and possibly an SD Card slot. I am unsure if I want it to be an internal only hard disk, or make it removable more like a floppy.
The circuit board takes up only about half the space. There is plenty of room for possible expansion. The thin wood works OK for the front panel, but for the back panel I want to reinforce it with something else.
Here is a short video clip of the computer running:
I had a lot of fun getting this project going again for 2024. Once I realized that my focus was more on software, it really needed to be put in a box and turned into a computer that can just sit on my desk. When its on a breadboard, it takes up a lot of desk space, and is a very fragile. It is easy to disconnect something or short out a connection. The project feels complete in a way. Now it' is just another computer on my desk.