In my last post, I’ve taken a few initial thoughts regarding opcodes. Added to that come first inklings to wanting to try my hand at writing an assembler in Python. But, to do that, I think I need to add at least one new command to load constant values from memory into memory, without having to resort to playing around with specific addresses.
I am also thinking I am keeping the split between the four forms of operations and use the upper nibble to designate the command type. Practically, that will mean I can tell a store operation from an add operation just by the upper nibble of the machine code.
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 |
Data Transfer Operations
Command | Mnemonic | Operation | Opcode | Affected Flags |
---|---|---|---|---|
Load Direct | ld Rd, Addr | Rd <= Memory[Addr] | 0b00000000/0x00 | None |
Load Indirect | ldi Rd | Rd <= Memory[Z] | 0b00000001/0x01 | None |
Load Constant | ldc Rd, C | Rd <= C (PC+1) | 0b00000010/0x02 | None |
Store Direct | st Rs, Addr | Memory[Addr] <= Rs | 0b00000100/0x04 | None |
Store Indirect | sti Rs | Memory[Z] <= Rs | 0b00000101/0x05 | None |
Move | mv Rd, Rs | Rd <= Rs | 0b00001000/0x08 | None |
The Load Constant is the new operation, meant to directly load any 16 bit constant into a register, allowing to get around any Z register shenanigans.
Arythmic Logical Operations
Command | Mnemonic | Operation | Opcode | Affected Flags |
---|---|---|---|---|
Add | add Rd, Rs | Rd <= Rd + Rs | 0b01000101/0x45 | C, QC, HC, TC, Z, N, O |
Increment | inc Rd | Rd <= Rd + 1 | 0b01010101/0x55 | C, QC, HC, TC, Z, N, O |
Substract | sub Rd, Rs | Rd <= Rd – Rs | 0b01100101/0x65 | C, QC, HC, TC, Z, N, O |
Decrement | dec Rd | Rd <= Rd – 1 | 0b01110101/0x75 | C, QC, HC, TC, Z, N, O |
Bitwise AND | and Rd, Rs | Rd <= Rd & Rs | 0b01000010/0x42 | Z |
Bitwise OR | or Rd, Rs | Rd <= Rd | Rs | 0b01000011/0x43 | Z |
Bitwise XOR | xor Rd, Rs | Rd <= Rd ^ Rs | 0b01000001/0x41 | Z |
Bitwise NOT | not Rd | Rd <= ~Rd | 0b01000000/0x40 | Z |
Logical Shift Left | lsl Rd | Rd <= Rd << 1 LSB <= Carry Carry <= MSB | 0b01000110/0x46 | C, Z |
Logical Shift Right | lsr Rd | Rd <= Rd >> 1 MSB <= Carry Carry <= LSB | 0b01000111/0x47 | C, Z |
Logical Rotate Left | lrl Rd | Rd <= Rd << 1 LSB <= MSB | 0b01010110/0x56 | None |
Logical Rotate Right | lrr Rd | Rd <= Rd >> 1 MSB <= LSB | 0b01010111/0x57 | None |
Logical Switch Byte | lsb Rd | Rd[Low] <= Rd[High] Rd[High] <= Rd[Low] | 0b01001000/0x48 | None |
The Opcodes for the Arythmic Logic Operations look a bit strange, but that is mostly because I want to push the lowest three bits of the Opcode directly into the ALU and the other three bits can then be used to distinguish between the other operations that are similar.
Like Addition, Sustraction making use of the same logic circuits, for example. I also just realised that I can use two of those bits to directly push into the ALU to set the type of ‘addition’. Bit 5 for indicating whether or not the operation is a substractive operation , while Bit 4 indicated of the operation is an increment/decrement.
The same is true for the Shift and Rotate operations, as they make use of the same logic, with the only difference being whether the shift happens through the carry bit or not.
The Logic Switch Byte is new, meant to switch the two bytes of the 16 bit words used by the DWMC-16 quickly, an operation useful if a program needs to push/pul data from an 8 bit IO device.
Conditional Branch Instructions
Command | Mnemonic | Operation | Opcode | Affected Flags |
---|---|---|---|---|
Branch if Flag | bf F, Addr | if (F == 0): PC <= Addr | 0b10000000/0x80 | None |
Branch if not Flag | bnf F, Addr | if (F != 0): PC <= Addr | 0b10000001/0x81 | None |
Branch if Zero, Decrement | bzd Rd, Addr | if (Rd == 0): PC <= Addr else Rd <= Rd – 1 | 0b10000010/0x82 | C, Z, N |
Branch if not Zero, Decrement | bnzd Rd, Addr | if (Rd != 0): PC <= Addr else Rd <= Rd – 1 | 0b10000011/0x83 | C, Z, N |
Branch if Registers Equal | breq Rs, Rs2, Addr | if (Rs == Rs2): PC <= Addr | 0b10000100/0x84 | C, Z, N |
Branch if Registers not Equal | brne Rs, Rs2, Addr | if (Rs != Rs2): PC <= Addr | 0b10000101/0x85 | C, Z, N |
Branch if greater then | bgt Rs, Rs2, Addr | if (Rs > Rs2): PC <= Addr | 0b10000110/0x86 | C, Z, N |
Branch if greater then or equal | bge Rs, Rs2, Addr | if (Rs >= Rs2): PC <= Addr | 0b10000111/0x87 | C, Z, N |
Other Operations
Command | Mnemonic | Operation | Opcode | Affected Flags |
---|---|---|---|---|
Jump Direct | jmp Addr | PC <= Addr | 0b11000000/0xC0 | None |
Jump Indirect | jmpi | PC <= Z | 0b11000001/0xC1 | None |
Jump to Subroutine | jms Addr | ST <= PC + 1; PC <= Addr | 0b11000010/0xC2 | None |
Return | ret | PC <= ST | 0b11000011/0xC3 | None |
Return from Interrupt | reti | PC <= ST (see last post) | 0b11000100/0xC4 | Any |
Push to Stack | push Rs | ST <= Rs | 0b11000101/0xC5 | None, Any |
Pop from Stack | pop Rd | Rd <= ST | 0b11000110/0xC6 | None, Any |
Set Flag | sf F | F <= 1 | 0b11000111/0xC7 | Any |
Reset Flag | rf F | F <= 0 | 0b11001000/0xC8 | Any |
No operation | nop | No operation | 0b11111111/0xFF | None |
The Return from Interrupt operation does what it says on the tin, and was discussed in my previous post.
Conclusion
With that, the number of instructions, for now, raises to 37 again, after my last modification of the initial instruction set by slashing four operations.
So variable length instructions? 8 bit op code then 1 or more 8 bit operands?
Yeah, one or two words of instructions. Though I may include operations with full 14 bit addresses for three bytes of instruction. A 16 bit address will only address a 64kWord sector of the main memory.
8 bit opcode, followed by zero, one or two 4 bit operands for registers (or flags) and then one or two words for direct addressing
See the last part of this post.