A Traditional CPU Architecture

Are you familiar with the Zilog Z80 CPU? Suppose we expand its general purpose registers to hold 16 bits each, instead of 8. Also, suppose each memory address now holds 16 bits too, instead of the usual 8 (bye bye bytes). With these two assumptions in mind, what sort of CPU architecture can we make that is wildly similar to the Z80?

Please note that this is very easy to adapt to 32-bits data registers and 64-bits address registers.

The Registers

First of all, let's see the registers that are available to the user. We have the usual A, F, B, C, D, E, H, and L. Each holds 16 bits. The register F is the flags register. Then we have the 32-bit registers IX and IY, used for indexed addressing. Finally, we have the SP, or stack pointer, and also the program pointer, named PP. These last two registers also hold 32 bits. Let's call 16 bits a word. Thus, 32-bit registers contain an address of memory that can access 4 gibiwords (or 8 gibibytes).

The processor is always little-endian.

1F1E... 1110 0F0E... 0100
B C BC
D E DE
H L HL
F A AF
XH XL IX
YH YL IY
SP
PP

General Instructions

Each instruction is 16 bits wide followed or not by 16 more bits or perhaps 32, as required, for the instruction to make sense.

NOP

FEDC BA98 7654 3210
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

Do nothing.

CCF

FEDC BA98 7654 3210
0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 1

RCF

FEDC BA98 7654 3210
0 0 0 0 0 0 0 0 0 0 1 1 0 0 1 0

SCF

FEDC BA98 7654 3210
0 0 0 0 0 0 0 0 0 0 1 1 0 0 1 1

Complement carry flag, reset carry flag, and set carry flag.

LDD

FEDC BA98 7654 3210
0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0

LDI

FEDC BA98 7654 3210
0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 1

LDDR

FEDC BA98 7654 3210
0 0 0 0 0 0 0 0 0 0 0 0 1 0 1 0

LDIR

FEDC BA98 7654 3210
0 0 0 0 0 0 0 0 0 0 0 0 1 0 1 1

Load, then decrement or increment, and possibly repeat.

CPD

FEDC BA98 7654 3210
0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0

CPI

FEDC BA98 7654 3210
0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 1

CPDR

FEDC BA98 7654 3210
0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0

CPIR

FEDC BA98 7654 3210
0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1

Compare, then decrement or increment, and possibly repeat.

LD dst, (nn)

FEDC BA98 7654 3210
0 0 0 0 0 0 0 1 0 0 0 0 dst
n
n

Load the dst register with the contents of memory at address nn. If bit 3 is zero, it loads 16 bits into B, C, D, E, H, L, (HL), or A. If bit 3 is one, it loads 32 bits into one of BC, DE, HL, or SP. Please refer to the following table for valid values of dst.

3210
0 0 0 0 B
0 0 0 1 C
0 0 1 0 D
0 0 1 1 E
0 1 0 0 H
0 1 0 1 L
0 1 1 0 (HL)
0 1 1 1 A
1 0 0 0 BC
1 0 0 1 DE
1 0 1 0 HL
1 0 1 1 SP

LD (nn), src

FEDC BA98 7654 3210
0 0 0 0 0 0 0 1 0 0 0 1 src
n
n

Load the contents of the register specified by src into the memory location addressed by nn. Again, if bit 3 is zero, 16 bits are transferred, and if it's one, 32 bits are loaded into nn and nn+1. Refer to the dst table in the previous instruction for the valid values of src - they are the same.

LD dst, n

FEDC BA98 7654 3210
0 0 0 0 0 0 0 1 0 0 1 0 dst
n

LD dst, nn

FEDC BA98 7654 3210
0 0 0 0 0 0 0 1 0 0 1 0 dst
n
n

Load the register dst with the value n. If dst is a 32-bit register (bit 3 holds the value one), load it with the value nn.

LD dst, src

FEDC BA98 7654 3210
0 0 0 0 0 0 1 0 src dst

The operation depends on the values of bits 3 and 7. If both these bits are 0, the operation loads the 16-bit contents of register src into register dst. If bit 7 is 0, and the other is 1, the memory address pointed to by dst is loaded with the contents of register src. In this case, the instruction should be written as LD (dst), src. If, on the other hand, bit 7 is 1 and bit 3 is 0, then register dst is loaded with the memory contents of the address pointed to by src. The instruction should then be written as LD dst, (src). Finally, if both bits are 1, the 32-bit register dst is loaded with the contents of the 32-bit register src. The instruction should in this case be written as LD dst, src.

EX dst, src

FEDC BA98 7654 3210
0 0 0 0 0 0 1 1 src dst

Exchange contents of src with that of dst. Again the operation depends on the values of bits 3 and 7. See LD just above for the details. You'll notice there's quite a lot of repeated (like EX A, B and EX B, A) and redundant (like EX A, A) functionality here, which can be removed if it's easier to implement. A similar remark applies to LD above.

ADD dst, src

FEDC BA98 7654 3210
0 0 0 0 0 1 0 0 src dst

ADC dst, src

FEDC BA98 7654 3210
0 0 0 0 0 1 0 1 src dst

SUB dst, src

FEDC BA98 7654 3210
0 0 0 0 0 1 1 0 src dst

SBC dst, src

FEDC BA98 7654 3210
0 0 0 0 0 1 1 1 src dst

Add dst with src and place the result in dst; add with carry; subtract; and subtract with carry. See LD above for details about src and dst.

MULLU dst, src

FEDC BA98 7654 3210
0 0 0 0 1 0 0 0 src dst

MULL dst, src

FEDC BA98 7654 3210
0 0 0 0 1 0 0 1 src dst

MULHU dst, src

FEDC BA98 7654 3210
0 0 0 0 1 0 1 0 src dst

MULH dst, src

FEDC BA98 7654 3210
0 0 0 0 1 0 1 1 src dst

DIVU dst, src

FEDC BA98 7654 3210
0 0 0 0 1 1 0 0 src dst

DIV dst, src

FEDC BA98 7654 3210
0 0 0 0 1 1 0 1 src dst

REMU dst, src

FEDC BA98 7654 3210
0 0 0 0 1 1 1 0 src dst

REM dst, src

FEDC BA98 7654 3210
0 0 0 0 1 1 1 1 src dst

MULLU performs an unsigned multiplication between dst and src and stores the lower 16 bits of the result in dst. MULHU performs an unsigned multiplication between dst and src and stores the upper 16 bits of the result into dst. MULL and MULH are similar, only they perform signed multiplication. DIVU is unsigned division, DIV is signed division (both of dst by src); REMU divides (unsigned) dst by (unsigned) src and stores the remainder of that division in dst, and REM is the signed remainder operation.

AND dst, src

FEDC BA98 7654 3210
0 0 0 1 0 0 0 0 src dst

OR dst, src

FEDC BA98 7654 3210
0 0 0 1 0 0 0 1 src dst

XOR dst, src

FEDC BA98 7654 3210
0 0 0 1 0 0 1 0 src dst

CP dst, src

FEDC BA98 7654 3210
0 0 0 1 0 0 1 1 src dst

These are the usual logical operations AND, OR, and XOR, along with the instruction CP, which compares dst with src and updates the flags register accordingly.

BIT bb, dst

FEDC BA98 7654 3210
0 0 0 1 0 1 0 0 bb dst

CPL bb, dst

FEDC BA98 7654 3210
0 0 0 1 0 1 0 1 bb dst

RES bb, dst

FEDC BA98 7654 3210
0 0 0 1 0 1 1 0 bb dst

SET bb, dst

FEDC BA98 7654 3210
0 0 0 1 0 1 1 1 bb dst

These are the operations on the bits of 16-bit registers (that is, bit 3 is always zero). bb is any bit from 00 to 15. BIT sets the flags according to the value of bit bb in register dst; CPL complements the given bit; RES sets it to zero; and SET sets it to one.

RL dst

FEDC BA98 7654 3210
0 0 0 1 1 0 0 0 0 0 0 0 dst

RLC dst

FEDC BA98 7654 3210
0 0 0 1 1 0 0 0 0 0 0 1 dst

RR dst

FEDC BA98 7654 3210
0 0 0 1 1 0 0 0 0 0 1 0 dst

RRC dst

FEDC BA98 7654 3210
0 0 0 1 1 0 0 0 0 0 1 1 dst

SLA dst

FEDC BA98 7654 3210
0 0 0 1 1 0 0 0 0 1 0 0 dst

SLL dst

FEDC BA98 7654 3210
0 0 0 1 1 0 0 0 0 1 0 1 dst

SRA dst

FEDC BA98 7654 3210
0 0 0 1 1 0 0 0 0 1 1 0 dst

SRL dst

FEDC BA98 7654 3210
0 0 0 1 1 0 0 0 0 1 1 1 dst

Bit 3 is always zero, that is, these operations are valid only for 16-bit registers. They are: rotate left, rotate left through carry, rotate right, rotate right through carry, shift left arithmetic, shift left logical, shift right arithmetic, and shift right logical. SLA and SLL perform the same operation.

CPL dst

FEDC BA98 7654 3210
0 0 0 1 1 0 0 0 1 0 0 0 dst

NEG dst

FEDC BA98 7654 3210
0 0 0 1 1 0 0 0 1 0 0 1 dst

Bit 3 is always zero, that is, these operations are valid only for 16-bit registers. They are one's complement and two's complement.

BCD dst

FEDC BA98 7654 3210
0 0 0 1 1 0 0 0 1 0 1 0 dst

Convert the number in dst to binary coded decimal format. The number must be between 0 and 9999, otherwise an overflow occurs. Bit 3 is always zero.

RAND dst

FEDC BA98 7654 3210
0 0 0 1 1 0 0 0 1 0 1 1 dst

Bit 3 is always zero, that is, this operation is valid only for 16-bit registers. It places a random number in dst. (Similar to the Z80 LD A, R instruction, only here we don't have R).

DEC dst

FEDC BA98 7654 3210
0 0 0 1 1 0 0 0 1 1 0 0 dst

INC dst

FEDC BA98 7654 3210
0 0 0 1 1 0 0 0 1 1 0 1 dst

Decrement or increment 16- or 32-bit registers.

PUSH reg

FEDC BA98 7654 3210
0 0 0 1 1 0 0 0 1 1 1 0 reg

POP reg

FEDC BA98 7654 3210
0 0 0 1 1 0 0 0 1 1 1 1 reg

Bit 3 is always one, i.e., only 32-bit values can be pushed and popped. The relevant registers are given in the following table.

3210
1 0 0 0 BC
1 0 0 1 DE
1 0 1 0 HL
1 0 1 1 AF

JR i

FEDC BA98 7654 3210
0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0
i

JR cc, i

FEDC BA98 7654 3210
0 0 1 0 0 0 0 1 cc 0 0 0 0
i

JR dst

FEDC BA98 7654 3210
0 0 1 0 0 0 1 0 0 0 0 0 dst

JR cc, dst

FEDC BA98 7654 3210
0 0 1 0 0 0 1 1 cc dst

These are the jump relative instructions. i is a number between -32768 and 32767. dst is a signed 16-bit register, that is, bit 3 is always zero. cc is the condition upon which a jump is taken. It can be any of the following values.

3210
0 0 0 0 NZ or NE (not zero, or not equal)
0 0 0 1 Z or EQ (zero, or equal)
0 0 1 0 P (plus)
0 0 1 1 M (minus)
0 1 0 0 NO (no overflow)
0 1 0 1 O (overflow)
0 1 1 0 PO (parity odd)
0 1 1 1 PE (parity even)
1 0 0 0 NC or AE (no carry, or above or equal)
1 0 0 1 C or B (carry, or below)
1 0 1 0 A (above)
1 0 1 1 BE (below or equal)
1 1 0 0 G (greater)
1 1 0 1 LE (less or equal)
1 1 1 0 GE (greater or equal)
1 1 1 1 L (less)

Above and below are used after operations with unsigned numbers, and less and greater with signed ones.

JA nn

FEDC BA98 7654 3210
0 0 1 0 0 1 0 0 0 0 0 0 0 0 0 0
n
n

JA cc, nn

FEDC BA98 7654 3210
0 0 1 0 0 1 0 1 cc 0 0 0 0
n
n

JA dst

FEDC BA98 7654 3210
0 0 1 0 0 1 1 0 0 0 0 0 dst

JA cc, dst

FEDC BA98 7654 3210
0 0 1 0 0 1 1 1 cc dst

The jump absolute instructions require a 32-bit address to jump to, either a literal nn or a register dst (bit 3 is always one, but SP is not valid). The conditions cc are the same as the ones for jump relative, above.

DJNZ i

FEDC BA98 7654 3210
0 0 1 0 1 0 0 0 0 0 0 0 0 0 0 0
i

DJNZ dst

FEDC BA98 7654 3210
0 0 1 0 1 0 1 0 0 0 0 0 dst

Decrement B and jump if it is not zero to a relative destination: i between -32768 and 32767, or a signed value in dst. Bit 3 of field dst is always zero.

CALL nn

FEDC BA98 7654 3210
0 0 1 0 1 1 0 0 0 0 0 0 0 0 0 0
n
n

CALL cc, nn

FEDC BA98 7654 3210
0 0 1 0 1 1 0 1 cc 0 0 0 0
n
n

CALL dst

FEDC BA98 7654 3210
0 0 1 0 1 1 1 0 0 0 0 0 dst

CALL cc, dst

FEDC BA98 7654 3210
0 0 1 0 1 1 1 1 cc dst

These are the usual CALL instructions. dst is always 32 bits, that is bit 3 is always one, with the exception that SP is not a valid dst.

RET

FEDC BA98 7654 3210
0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0

RET cc

FEDC BA98 7654 3210
0 0 1 1 0 0 0 1 cc 0 0 0 0

Return from subroutine.

RST pp

FEDC BA98 7654 3210
0 0 1 1 0 0 1 0 0 0 0 0 pp

This calls the subroutine at memory address related to pp and given by the following table. pp should be written as 0x00, 0x10, 0x20, and so on, not as 0, 1, 2, etc.

3210
0 0 0 0 0x00
0 0 0 1 0x10
0 0 1 0 0x20
0 0 1 1 0x30
0 1 0 0 0x40
0 1 0 1 0x50
0 1 1 0 0x60
0 1 1 1 0x70
1 0 0 0 0x80
1 0 0 1 0x90
1 0 1 0 0xA0
1 0 1 1 0xB0
1 1 0 0 0xC0
1 1 0 1 0xD0
1 1 1 0 0xE0
1 1 1 1 0xF0

IX Instructions

To obtain the IX instructions, simply substitute the bits FE of the previous opcodes from 00 to 01, but only for the instructions that deal with H, L, (HL), and HL. (All others should be invalid or reserved?) The registers dst and src then become as in the following table.

3210
0 0 0 0 B
0 0 0 1 C
0 0 1 0 D
0 0 1 1 E
0 1 0 0 XH
0 1 0 1 XL
0 1 1 0 (IX + i)
0 1 1 1 A
1 0 0 0 BC
1 0 0 1 DE
1 0 1 0 IX
1 0 1 1 SP

The additional register reg (for PUSH and POP instructions) is as in the following table. Probably, all other values should be invalid or reserved.

3210
1 0 1 0 IX

IY Instructions

This is the same as the IX instructions, only now bits FE of the instruction opcode contain 10 instead of 01.