Since I keep feeling like I should really be adding more content to this blog-thing, I decided I should try to explain the why and wherefore of one of my projects-of-little-purpose.
The project I've chosen, since I seem to be having fun with it, right now (That will probably change, when I start having to work on the boring bits of it, though.), is cow16.
A later post will go into the painfully gritty^W^Wfun of programming it!
I'll start at the beginning, because that seems like a good place to start; it's a fairly crude, horrible mess of a simple toy processor.
Its design is fairly simple (leaving out how horribly complex it would be to actually implement in hardware; but that's irrelevant, to me.); based around a stack-machine.
By default, they're 10 deep. Nothing I'm likely to do whilst experimenting is likely to run into that limit; but it's easy to increase it later.
- Data, where all the operations take their arguments from and return output to.
- Return; used to handle functions. Whenever a function is called, the address of the instruction after the call is pushed onto this stack so that, when the called function returns, the code can jump back to just after where it left off.
- Interrupt. This is where the -- as of this post, unimplemented -- interrupt service routines will take their arguments from, and load data back to when they return. It's separated to prevent values returned from ISRs from becoming entangled with user data; since interrupts can (in theory) occur at any time.
- PC, the program counter, holding -- surprise -- the index into memory of the currently executing instruction.
is_p, holding the index into the corresponding stack.
- status, which is full of flags for various things. Currently, only RUN (Used to enable/disable execution), and INT (Enable/disable interrupts.)
35 fixed-width not-really-encoded instructions.
Thankfully, they're pretty simple, because all code has to be hand-assembled, at the moment. Writing an assembler, or a compiler, is being left as an exercise for a rainy month sometime after I finish hammering the processor itself into something I'm reasonably happy with.
They're a word wide, the lowest 9 bits specify the opcode (Giving 512 opcodes to play with), and the upper 7 specify a processor type.
Yes, a processor type; so that co-processors of different types can be stuck into the system, each interpreting the 512 opcodes differently, and what happens is decided by which processor is selected by the upper 7 bits.
No, I haven't figured that bit out yet. That's likely to be one of the un-fun things to do. It will most likely be a parallel bus that the word gets shoved down; and each processor masks out instructions that aren't for its type.
A Dash of Memory
It also has 65535 16-bit words of main memory in a flat address space, some of which are eaten up by various little things, which include:
- 256 words of BIOS code.
- A little memory-mapped I/O. (Two 16-bit I/O ports, and their control 'registers'.)
- A word taken up by a pointer to the interrupt vector table that I have yet to implement.
- The actual, optional, interrupt vector table; again, not implemented, since interrupts in general aren't implemented yet.
However, the rest of the memory is available for user code and data. A massive 65,272 words. Just imagine the possibilities!
Accessing individual bytes is left as an exercise for the victim^W reader. (Hint: Shifts and XOR. "Not efficient, or easy to use; but it was easy to implement." is our motto here!)
A Pinch of I/O
At the moment, it's really only O. There are two I/O ports, both 16 bits wide ("Because it was easy to implement like that."), that just display in the UI.
The intent is for them to somehow (Yup, another thing I haven't thought out all that much.) be connected to other (external) programs that pretend to be devices. UNIX sockets spring to mind here, but I'm not sure I particularly like that idea; it just seems somewhat overcomplicated. ("Oh, and the rest of this thing isn't?!" Shush.)
The currently implemented UI looks kinda neat when I run the simple I/O test BIOS, though. Blinkenlights! (Even though all it does is increment a counter and write it out to PORT A.)
Figuring out how to make I/O actually work as I/O is pretty high on my list of things to do. (Mostly because all the fun stuff comes from interacting with the world.)
And a list of bugs longer than my arm
Okay, it's not quite that bad. There are only a few bugs that I've found, and I already have a plan to deal with most of them.
I'm rather surprised there aren't more; but I'm sure I'll find some when I try to do anything interesting with it.
Large chunks of the spec have now managed to become stable enough that they can be almost relied upon not to be changed; the list of opcodes, for example.
And, probably, the memory map; but don't quote me on that bit. It's my toy project, I make no promises about stability, and reserve the right to make massive changes (Like how I just rewrote it from the ground up to get cow16) with little to no notice.
If you don't like it, go write your own. In fact, go write your own anyway; it's fun. (Most of the time. Except when you have to sit and think through the hard bits that are blocking you from playing with the fun bits.)