Yes… I know that I’ve put up the V0.4 revision of the Instruction Set just yesterday, but over night, thank you brain, I’ve mentally gone over a discussion I’ve read on the Usagi Electric Discord that involved someone else making their own instruction set and emulator. And how they were splitting the opcode from the addressing modes.
And… With the instruction set having grown to over 40 instructions in the last revision, I thought that maybe I should change the instruction set to also split the opcode from the addressing modes…
Legend
Rd | Destination Register |
Rs, Rs2 | Source Registers |
PR | Program Counter |
C | Constant Number |
Addr | Memory Address |
ST | Stack Pointer |
F | Flag bit |
MSB | Most Significant Bit |
LSB | Least Significent Bit |
Addressing Modes
Since I have eight bits of opcode available, I want to limit myself to just two bits for the addressing moves, leaving six bits for 64 instructions.
Adressing Mode | Example | Bitcode |
---|---|---|
Immediate (Constants) | ld R00, 0x0F0F | 0b00 |
Direct Local | ld R00, [0x0F0F ] | 0b01 |
Direct (Global) | ld R00, [0x0F0F0F ] | 0b10 |
Register/Indirect | add R00, R01 ld R00 | 0b11 |
I have lumped in the Indirect Addressing mode and the Register Addressing mode together. It depends on the operation, whether it will be Register or Indirect Addressing mode, which will be noted in the Operation List below. There is the possibility of an Indexed Address mode, but that will be only inside the Assembler, not in the actual operations. It will be in the form of ldi R00, 4
which will more or less just insert add ZL, 4
in front of ldi R00
.
OpCode Encoding
With the addition of the addressing modes, I will need to change the OpCode Encoding as well. Depending on the Address Mode, there maybe one or two Words after the actual Opcode.
MSB | LSB | |||||||||||||||
15 | 14 | 13 | 12 | 11 | 10 | 09 | 08 | 07 | 06 | 05 | 04 | 03 | 02 | 01 | 00 | |
Type 1: One word, no registers | ||||||||||||||||
Word 1 | Opcode | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | |||||
Type 2: One Word, one register | ||||||||||||||||
Word 1 | Opcode | Address Mode | Register/Flag | 0 | 0 | 0 | 0 | |||||||||
Type 3: One Word, two registers | ||||||||||||||||
Word 1 | Opcode | Address Mode | Destination Register | Source Register | ||||||||||||
Addressing Mode: Direct Local | ||||||||||||||||
Word 2 | Low Address Word (16 bit) | |||||||||||||||
Addressing Mode: Direct (Global) | ||||||||||||||||
Word 2 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | High Address Word (8 bit) | |||||||
Word 3 | Low Address Word (16 bit) |
Data Transfer Operations (3 Operations)
Command | Mnemonic | Operation | Opcode | Rgister Modes | OpType | Affected Flags |
---|---|---|---|---|---|---|
Load | ld Rd, Adr/C | Rd <= Memory[Addr]/C | 0b000010 | 0b00, 0b01, 0b10, 0b11 (Indirect) | 2 | None |
Load | st Rs, Addr16 | Memory[Addr] <= Rs | 0b000100 | 0b00, 0b01, 0b10, 0b11 (Indirect) | 2 | None |
Move | mv Rd, Rs | Rd <= Rs | 0b001000 | N.A. | 3 | None |
With the new addressing modes, I can easily reduce the eight Operations from V0.4 back down to just three, even getting rid of the Load Constant Operation and the Load Indirect Operation.
Arythmic Logical Operations (13 Operations)
Command | Mnemonic | Operation | Opcode | Register Modes | OpType | Affected Flags |
---|---|---|---|---|---|---|
Add | add Rd, Rs/C | Rd <= Rd + Rs/C | 0b010001 | 0b00, 0b11 (Register) | 3 | C, QC, HC, TC, Z, N, O |
Increment | inc Rd | Rd <= Rd + 1 | 0b010010 | N.A. | 2 | C, QC, HC, TC, Z, N, O |
Substract | sub Rd, Rs/C | Rd <= Rd – Rs/C | 0b010011 |
| 3 | C, QC, HC, TC, Z, N, O |
Decrement | dec Rd | Rd <= Rd – 1 | 0b010100 | N.A. | 2 | C, QC, HC, TC, Z, N, O |
Bitwise AND | and Rd, Rs/C | Rd <= Rd & Rs/C | 0b010101 |
| 3 | Z |
Bitwise OR | or Rd, Rs/C | Rd <= Rd | Rs/C | 0b010110 |
| 3 | Z |
Bitwise XOR | xor Rd, Rs/C | Rd <= Rd ^ Rs/C | 0b010111 |
| 3 | Z |
Bitwise NOT | not Rd | Rd <= ~Rd | 0b011000 | N.A. | 2 | Z |
Logical Shift Left | lsl Rd | Rd <= Rd << 1 LSB <= Carry Carry <= MSB | 0b011001 | N.A. | 2 | C, Z |
Logical Shift Right | lsr Rd | Rd <= Rd >> 1 MSB <= Carry Carry <= LSB | 0b011010 | N.A. | 2 | C, Z |
Logical Rotate Left | lrl Rd | Rd <= Rd << 1 LSB <= MSB | 0b011011 | N.A. | 2 | None |
Logical Rotate Right | lrr Rd | Rd <= Rd >> 1 MSB <= LSB | 0b011100 | N.A. | 2 | None |
Logical Switch Byte | lsb Rd | Rd[Low] <= Rd[High] Rd[High] <= Rd[Low] | 0b011101 | N.A. | 2 | None |
The number of OpCodes for the Arithmetic Operations does not change, but it allows me to add Constants to at least some of the Operations. I have also gotten rid of the specific OpCode Encoding, as I previously had hoped to directly shunt the selection of the ALU operation from the OpCode into the ALU. But I do not think that its possible anymore with the drop of Opcode Length from eight to six bits, and me using the two upper bits to denominate that type of Operation.
Conditional Branch Instructions (10 Operations)
Command | Mnemonic | Operation | Opcode | Register Modes | OpType | Affected Flags |
---|---|---|---|---|---|---|
Branch local if Flag | blf F, Addr | if (F == 0): PCL <= Addr/Z | 0b100000 | 0b01, 0b10, 0b11 (Indirect) | 2 | None |
Branch if not Flag | bnf F, Addr | if (F != 0): PCL <= Addr/Z | 0b100001 | 0b01, 0b10, 0b11 (Indirect) | 2 | None |
Branch if Bit | bb B, Addr | if (B == 0): PC <= Addr/Z | 0b100010 | 0b01, 0b10, 0b11 (Indirect) | 2 | None |
Branch if not Bit | bnb B, Addr | if (B !=0): PC <= Addr/Z | 0b100011 | 0b01, 0b10, 0b11 (Indirect) | 2 | None |
Branch if Zero, Decrement | bzd Rd, Addr | if (Rd == 0): PCL <= Addr/Z else Rd <= Rd – 1 | 0b100100 | 0b01, 0b10, 0b11 (Indirect) | 2 | C, Z, N |
Branch if not Zero, Decrement | bnzd Rd, Addr | if (Rd != 0): PCL <= Addr/Z else Rd <= Rd – 1 | 0b100101 | 0b01, 0b10, 0b11 (Indirect) | 2 | C, Z, N |
Branch if Registers Equal | breq Rs, Rs2, Addr | if (Rs == Rs2): PCL <= Addr/Z | 0b100110 | 0b01, 0b10, 0b11 (Indirect) | 3 | C, Z, N |
Branch if Registers not Equal | brne Rs, Rs2, Addr | if (Rs != Rs2): PCL <= Addr/Z | 0b100111 | 0b01, 0b10, 0b11 (Indirect) | 3 | C, Z, N |
Branch if greater then | bgt Rs, Rs2, Addr | if (Rs > Rs2): PCL <= Addr/Z | 0b101000 | 0b01, 0b10, 0b11 (Indirect) | 3 | C, Z, N |
Branch if greater then or equal | bge Rs, Rs2, Addr | if (Rs >= Rs2): PCL <= Addr/Z | 0b101001 | 0b01, 0b10, 0b11 (Indirect) | 3 | C, Z, N |
Like with the Arithmetic Operations, there is not change in the number of operations here, but it now does allow to do more ‘global’ jumps if there is a branching operations. I will also allow indirect jumps through the Z Index Register.
Other Operations ( 11 Operations)
Command | Mnemonic | Operation | Opcode | Register Modes | OpType | Affected Flags |
---|---|---|---|---|---|---|
Jump | jp Addr | PCL <= Addr/Z | 0b110000 | 0b01, 0b10, 0b11 (Indirect) | 1 | None |
Jump to Subroutine | jls Addr16 | ST <= PC + 1; PCL <= Addr | 0b110001 | 0b01, 0b10 | 1 | None |
Return | ret | PC <= ST | 0b110010 | N.A. | 1 | None |
Return from Interrupt | reti | PC <= ST (see post) | 0b110011 | N.A. | 1 | Any |
Push to Stack | push Rs | ST <= Rs | 0b110100 | N.A. | 2 | None, Any |
Pop from Stack | pop Rd | Rd <= ST | 0b110101 | N.A. | 2 | None, Any |
Set Flag | sf F | F <= 1 | 0b110101 | N.A. | 2 | Any |
Reset Flag | rf F | F <= 0 | 0b110111 | N.A. | 2 | Any |
Set Bit | sb B | R08[B] <= 1 | 0b111000 | N.A. | 2 | None |
Reset Bit | rb B | R08[B] <= 0 | 0b111001 | N.A. | 2 | None |
No operation | nop | No operation | 0xF F | N.A. | 1 | None |
With the new Addressing Modes, I can reduce the number of other operations down to eleven, getting rid of the Indirect Jump, as well as the ‘local’ and ‘global’ jumps I added in V0.4. The other operations do not really change at all. I also decided to just give the nop
a fixed 16 bit opcode, since… Why not? :p
Conclusions
Maybe I should have taken a bigger think for the last v0.4 revision of the Instruction Set, when I added local and global addresses, especially since the same discussion inspired that revision I noted earlier. But it took a while before the penny with the address modes dropped for me.
Maybe I should let things like this stew a little longer in the back of my mind to grow a little more, before committing them to paper/a post.
But, now I am back down to 37 operations, after v0.4 rose up to 45 from 37. A thought might be to see if I could drop the differences between the Flag and Bit Test/Set operations by abusing the Register modes. That way, I would safe another four operations. Potentially I could do the same with Return/Return from Subroutine. That way I could get down to a cool 32 instructions.
With the Address Modes, I can certainly put a lot of potential operations and hide them in the Assembler. I can still have ldi
in the assembler, which would change the operation to an ld
operation for the machine code.