I finally decided to go with a full 16-bit computer, after four years of prototyping an 8-bit system. Why? Well, 16-bit instructions give us a lot of "resources" to work with, without having to split instructions across multiple words. 16-bit registers and ALU simplify a lot of maths, and make it trivial to manipulate addresses. 16-bit instructions also simplify a lot of hardware concerns, including breaking instructions up over several IR fetches and very limited identifiers for devices (registers, SRAM, etc.)
With 16-bits to work with, we are able to dedicate 5-bits to identification giving us a maximum of thirty-two devices. For example, the "A-Register" may be device 00001, while an SRAM bank may be device 00110. Device 00000 will be the "Zero-Register, effectively identifying no device and allowing the pull down resistors to control the bus.
With 5-bit identification, the first instruction to consider is a MOV, or copy instruction between two devices.
MOV 00001 00010 - Output Enable for device 1, and Write Enable for device 2
Source and destination identifiers take up ten binary digits, leaving six more, so these six will be available for all move/copy instructions. Where a source and destination are not required... an increment instruction, for example... more digits are obviously available. So, knowing this, how do we decide how an instruction is laid out?
We need five digits for the source and five digits for the destination when moving or copying values between devices. This can be as simple as copying values between two registers. Note that this also works for SRAM, EEPROMs, etc, except that in those cases the addressing needs to be setup ahead of time.
A store instruction immediately stores a value, encoded as part of the instruction, to the destination device. The first bit of an instruction can be used to determine when an immediate store is called, the next ten digits (numbers up to 1023) are the value, and the final five digits are the destination device.
Assuming the ALU is always connected to the A-Register and B-Register as inputs, we can assign 5-digits to the mode and signals, with the other 5-digits indicating a destination to store the result.
Mode Signal Destination
0 0001 01110
Because instructions can be variable... we do not always have a "source" device, for example... it is important to create a sensible guide.
1vvvvvvvvvvyyyyy - Store the 10-bit value "vvvvvvvvvv" into the "yyyyy" device. This may often be an 8-bit number, but 10-bits can be available in this instruction and those extra bits are the difference between a maximum of 255 and 1023.
010000xxxxxyyyyy - Write the 16-bit value stored in the "xxxxx" device to the bus, and write that value to "yyyyy".
001000mssssyyyyy - Execute the ALU function indicated by mode "m" and signal "ssss". and write the 16-bit result to the "yyyyy" device.
This is just a start, but I do like 5-digit identifiers as that means we can have 32 devices... way more than needed. The original 8-bit design was going to have 3-digit identifiers, limiting us to nine separate devices. That can work, but is limiting and would greatly impact the difficulty of hand-coding useful software. With thirty-two devices, we can have the ALU, A-Register, B-Register, several general purpose data and address registers, several address pointers (including the Program Counter), multiple banks of SRAM and EEPROMs... even things like sound and video cards, or input devices.
I need to give some thought to conditionals... branches, jumps, whatever we want to call them. These commands will follow a similar design as above, but will be conditional based on a flag (carry, negative, zero, etc). The remainder of the instruction can be an offset (requiring the ALU to calculate a destination address by adding or subtracting that offset from the PC), or perhaps a device containing that address (a Jump Register is loaded with an address, then that address is put onto the address bus *if* the condition is met.