New Instruction Set?
I’m sort of at a crossroads now, with the basic hardware working, and I need to decide what to do next. There’s still some remaining hardware work, of course: the keyboard and real-time clock, but neither of those is essential. Then there are possible hardware extras, like video. And there’s the entire operating system and software library to consider. My eventual goal is to support multiple programs running at once, managed by a multi-tasking OS.
I find myself gravitating towards a rewrite of the BMOW instruction set. Since the machine is microcoded, there’s nothing specifically tying the hardware to the choice and syntax of instructions. The present instruction set is nearly identical to the 6502 instruction set, which seems like a reasonable fit, but I’m getting more and more annoyed with it. I’ve been looking at the 68000 instruction set, and while its hardware assumptions (e.g. 32-bit registers) don’t match BMOW very well, it feels like a good foundation for a cleaner, simpler instruction set.
My main gripes with the current 6502-style instruction set are:
- Every combination of operation and operand is a unique instruction, which feels clunky and difficult to follow. Loading the X register is LDX, storing Y is STY, transferring A to Y is TAY. Compare this with a 68000-style MOVE SRC, DST. So the previous examples would like something like MOVE (A0), X, MOVE Y, (A0), MOVE A, Y.
- Some obvious-seeming instructions are just missing. You can push A on the stack with PHA, but there’s no PHX or PHY. There’s no instruction to add two registers: only to add a register to a value in memory.
- There are no stack-relative addressing modes. Support for the stack pointer in general is pretty minimal. This makes sense for the 6502’s single-byte stack pointer, but BMOW has a full 24-bit pointer for the stack.
- There’s no concept of an address stored in a register. The ability to auto-increment and decrement address registers is similarly hidden from the assembly programmer.
- All the opcodes are three letters. This is a small thing, but it’s annoying and makes the assembly less readable. It’s not like there’s some budget for opcode name lengths.
I’m thinking of rewriting BMOW’s microcode to implement a new instruction set much closer to the 68000’s. To the assembly programmer, BMOW would appear as three 8-bit data registers (the current A, X, and Y) and two 24-bit address registers (the AR and SP, which presently are visible only at the microcode level). The 8-bit T register would remain hidden. I think I still need it as a temporary location for implementing certain instructions in microcode, but if not, I could expose it as a fourth data register.
Exposing the address registers is the most powerful change. Consider a hypothetical program to copy 100 bytes between two memory locations. First the 6502-style:
LDX #99 loop: LDA $4000, X STA $5000, X DEX BPL loopThen the 68000-style program:
MOVE #99, D0 MOVE $4000, A0 MOVE $5000, A1 loop: MOVE (A0)+, (A1)+ SUB #1, D0BGE loop
It may not be obvious, but I could implement the 68000-style instructions to run this program far faster. For instance, that MOVE (A0)+, (A1)+ could be done in 3 clock cycles, compared with 10-12 for the LDA/STA pair, because the address registers are already set up and no address arithmetic is needed. Furthermore, the 68000-style program could be a subroutine in which the actual source and destination addresses are computed at runtime, while the 6502-style program forces the addresses to be known at assembly time, or to use even slower indirect, indexed addressing.
There are some downsides to replacing the instruction set with a new 68000-style one:
- Performance may be worse in some cases with a 68000-style instruction set. In general, such an instruction set would have slightly simpler instructions, and so would require more instructions to accomplish the same work as a 6502 instruction set. For example, the 6502 instruction LDA $4000 would be translated as the two 68000 instructions MOVE $4000, A0 and MOVE (A0), D0.
- The hardware can’t easily support indexed address modes like MOVE 4(A0), D0. That instruction is supposed to load a value from a location in memory 4 bytes beyond where A0 points. That means adding 4 to A0, but there’s no place to store the result of that addition other than back into A0 or A1. So the microcode would need to save and restore the old value of A0 (slow), or make indexed address modes actually change the address register (an unwanted change in semantics), or drop support for that address mode entirely.
- All software and tools would need to be written from scratch. Currently I’m using a modified 6502 assembler, and I can sort of run existing 6502 programs with a little work. But a BMOW-68000 would be different enough from a real 68000 that I’d need to write my own assembler, and couldn’t hope to run existing 68000 programs.
One other option would be to create an instruction set modeled on the 65816, the 16-bit successor to the 6502. It does add stack-relative addressing, as well as some of the obvious-but-missing instructions from the 6502. It still strikes me as rather clunky and non-symmetric, but I should probably take a deeper look at it before jumping to conclusions.
Read 5 comments and join the conversation5 Comments so far
Leave a reply. For customer support issues, please use the Customer Support link instead of writing comments.
hmmm. I’d have gone for something more x86 like mov dest, src, push ax, push bx, pop ax, and stuffs like that. hmmmmmmm.
In terms of MOVE, x86 is the same as 68000, except the order of the operands is reversed. As for the rest of x86 assembly, ugh, no thanks. All those segment registers and EAX, AX, AH, AL stuff is pretty ugly.
I also think that using a generic address register for the stack is more flexible than having explicit push and pop instructions. You can use A1 as the stack pointer, and push a byte with MOVE D0, -(A1) and pop it with MOVE (A1)+, D0. But then if you save off the value of A1 somewhere, you can use it temporarily as another general purpose address register, as long as you don’t need to access the stack. That can be a major win on a machine with very few address registers like BMOW.
I see, I don’t like segments at all, but I do like having lots of registers to work with, however, I could understand the kind of wire wrap nightmare that could create.
Steve,
One thing you might consider is to redo your instruction set in conjunction with building your language tools. I’ve always liked FORTH-like languages, and you can create some interesting special-purpose instructions to speed things. If you don’t want to do a full LCC or GCC C compiler retargeting, you might consider the old Small-C compiler. The version “Small C for the UNIX platform” at http://www.cpm.z80.de/small_c.html is especially easy to retarget. There is only one file to change, and you can get it going within minutes. It is also a good fit for BMOW in that internally it targets a virtual machine with two general purpose registers and a stack pointer. As distributed, it has retargeting examples for the 68K, 8080, 6809 and Vax. The generated code is pretty crappy, but it works – and you are in the position to change your instruction set instead of changing the compiler. Also, you can siginficantly clean up the output with a more agressive peephole optimization pass.
Check it out.
…Bill
Thanks for the pointer, Small-C looks like a great starting point! I’ll gladly take crappy generated code, if it works and I can build improvements on it later.
I’ve more-or-less decided to abandon the idea of a new instruction set. The more I looked at the details, the more it felt like trying to fit a square peg into a round hole. I concluded that with microcode changes only (no hardware changes), I couldn’t really support any of the more interesting instruction schemes I could imagine. My hardware implementation proves to have bound me more tightly to a 6502-style instruction set than I originally realized. Ultimately every instruction must be a single byte, and there’s really no good way to use pre- or post-bytes to modify the instruction’s behavior, source/destination, or addressing mode.
What I’ll probably do now is fill in the remainder of the BMOW opcode table with instructions from the 65816, a successor to the 6502 that’s binary compatible, and adds stack-relative addressing and some other handy stuff. Then I’ll work on a Small-C or other compiler, and probably add a few more special-case instructions to help optimize common cases in compiler-generated code, like maybe a special instruction for creating stack frames.