Registers
- There are 8 16-bit general-purpose registers, named
r0
throughr7
.- Just like MIPS,
r0
is special - reading it always gives 0 and writing to it does nothing. - Similar to MIPS, the
jal
instruction is hard-wired to use a specific register as the return address,r7
.
- Just like MIPS,
- There is the 16-bit program counter (PC).
- There is the 16-bit display (DISP) used to display numbers, which is writable with the
put
instruction. - Finally there is a 32-bit product register (PROD) readable with the
mfp
instruction.
Memory
This is a Harvard (2-memory) architecture. This allows us to implement it as a single-cycle machine.
The instruction memory is a word-addressed read-only memory with 16 address bits and 20 data bits. Unlike MIPS, the instruction memory is not byte-addressed. Instead, each address holds 1 instruction, meaning the memory can hold 65536 instructions. This also means that the “next instruction” is at PC + 1, not PC + 4.
The data memory is a word-addressed RAM with 16 address bits and 16 data bits. Again this is unlike MIPS which is a byte-addressed architecture. This memory can hold 65536 16-bit words.
Instruction formats
All instructions are 16 bits long. There are actually four instruction formats, with three being similar to MIPS.
- The lowest 2 bits of R-type instructions are simply not used. You don’t need to extract them.
- L-, I-, and J-type all have an immediate, but they are all different sizes. (Your control will handle this.)
- The
rd
,rs
, andrt
fields hold register numbers. So ifrd == 4
, then that meansr4
. - I-type instructions have a further complication: they only have an
rd
field, but many need to read from a register as well. So for I-type instructions,rd
will pull double duty by specifying the destination and the first source register! (Your control will handle this, too.)
The instruction set
- The Mnemonic column shows how the instruction is written.
- The Operation column shows how the instruction works. That’s what you implement!
- The
<-
means “store the value on the right into the thing on the left.”- it’s like an assignment operator in a programming language, but..
- if there are multiple
<-
operators in one instruction, they all happen simultaneously.
a : b
means “the bits ofa
concatenated with the bits ofb
.”x[4:0]
or whatever means “bits 4 through 0 (the lowest 5 bits) of x”. It’s like you’re taking a “slice” of the value.- You can use a Splitter or a Bit Extender to do this.
REG[x]
means “the register in the register file whose number is given by x.”RAM[x]
means “the value in RAM at memory address x.”sxt_n(x)
means “sign-extendx
ton
bits”. E.g.sxt_16(imm8)
means “sign-extend theimm8
field to 16 bits.”zxt_n(x)
means “zero-extendx
ton
bits”, similarly.
Basic instructions
The tutorial walks you through implementing these instructions so I won’t say much here.
Format | Opcode | Mnemonic | Operation |
---|---|---|---|
R | 00000 |
hlt |
disable PC register and turn on HALT LED |
R | 00001 |
put rs |
DISP <- REG[rs] |
I | 00010 |
li rd, imm8 |
REG[rd] <- sxt_16(imm8) |
I | 00011 |
lui rd, imm8 |
REG[rd] <- (imm8 : REG[rd][7:0]) |
ALU operations
Format | Opcode | Mnemonic | Operation |
---|---|---|---|
R | 00100 |
add rd, rs, rt |
REG[rd] <- REG[rs] + REG[rt] |
R | 00101 |
sub rd, rs, rt |
REG[rd] <- REG[rs] - REG[rt] |
R | 00110 |
and rd, rs, rt |
REG[rd] <- REG[rs] & REG[rt] |
R | 00111 |
or rd, rs, rt |
REG[rd] <- REG[rs] | REG[rt] |
R | 01000 |
xor rd, rs, rt |
REG[rd] <- REG[rs] ^ REG[rt] |
R | 01001 |
not rd, rt |
REG[rd] <- ~REG[rt] (see below) |
R | 01010 |
shl rd, rs, rt |
REG[rd] <- REG[rs] << REG[rt][3:0] (see below) |
R | 01011 |
shr rd, rs, rt |
REG[rd] <- REG[rs] >>> REG[rt][3:0] (see below) |
I | 01100 |
adi rd, imm8 |
REG[rd] <- REG[rd] + sxt_16(imm8) |
I | 01101 |
sbi rd, imm8 |
REG[rd] <- REG[rd] - sxt_16(imm8) |
I | 01110 |
ani rd, imm8 |
REG[rd] <- REG[rd] & zxt_16(imm8) |
I | 01111 |
ori rd, imm8 |
REG[rd] <- REG[rd] | zxt_16(imm8) |
I | 10000 |
xri rd, imm8 |
REG[rd] <- REG[rd] ^ zxt_16(imm8) |
I | 10001 |
sli rd, imm8 |
REG[rd] <- REG[rd] << imm8[3:0] (see below) |
I | 10010 |
sri rd, imm8 |
REG[rd] <- REG[rd] >>> imm8[3:0] (see below) |
The not
instruction does not use rs
or REG[rs]
at all. It does bitwise NOT on the second input to the ALU. If you followed the directions in lab 7, your ALU already does this correctly.
Notes on shifting:
- For all the shift instructions (
shl
,shr
,sli
,sri
), if you followed the directions in lab 7, your ALU already gets the least significant bits of the shift distance; you don’t have to do anything else to make it work. - Both right-shift instructions (
shr
,sri
) use>>>
which is logical (unsigned) right shift. - For the immediate shifts (
sli
,sri
), because you are only getting the least significant bits ofimm8
, it does not matter whether it is sign- or zero-extended. Do whatever is simpler.
Memory access
Format | Opcode | Mnemonic | Operation |
---|---|---|---|
L | 10011 |
ld rd, [rs+imm5] |
REG[rd] <- RAM[REG[rs] + sxt_16(imm5)] |
L | 10100 |
st rd, [rs+imm5] |
RAM[REG[rs] + sxt_16(imm5)] <- REG[rd] |
Tricky: for st
and only for st
, rd
is actually used as the second register to read out of the register file… so it takes the place of rt
. (Your control handles this.)
When implementing these, think to yourself: do you really need another component to do the addition for the memory address? ;) (Remember the interconnect slides…)
Jumps
Format | Opcode | Mnemonic | Operation |
---|---|---|---|
J | 10101 |
j target |
PC <- zxt_16(target) |
R | 10110 |
jr rs |
PC <- REG[rs] |
J | 10111 |
jal target |
REG[7] <- PC + 1 (see below!)PC <- zxt_16(target) |
R | 11000 |
jlr rs |
REG[7] <- PC + 1 (see below!)PC <- REG[rs] |
There are actually two function call instructions: jal
, which jumps to an immediate address, and jlr
, which jumps to an address in a register just like jr
. (MIPS has an instruction like this too, called jalr
.)
Two important things about jal
and jlr
:
- They are hard-wired to use register
r7
as the return address register. Your control handles this. - Don’t forget that they write to the register file. It’s really common for people to forget that!
Comparisons and Branches
Format | Opcode | Mnemonic | Operation |
---|---|---|---|
R | 11001 |
slt rd, rs, rt |
if(REG[rs] < REG[rt]) REG[rd] <- 1; else REG[rd] <- 0; |
R | 11010 |
sle rd, rs, rt |
if(REG[rs] <= REG[rt]) REG[rd] <- 1; else REG[rd] <- 0; |
R | 11011 |
seq rd, rs, rt |
if(REG[rs] == REG[rt]) REG[rd] <- 1; else REG[rd] <- 0; |
I | 11100 |
bez rd, imm8 |
if(REG[rd] == 0) PC <- PC + sxt_16(imm8); else PC <- PC+1; |
I | 11101 |
bnz rd, imm8 |
if(REG[rd] != 0) PC <- PC + sxt_16(imm8); else PC <- PC+1; |
All comparisons should be done as signed arithmetic. That means any Comparator component you use must be set to do signed (“2’s complement”) comparisons, or else certain tests will break!
Multiplication
Format | Opcode | Mnemonic | Operation |
---|---|---|---|
R | 11110 |
mul rs, rt |
PROD <- REG[rs] * REG[rt] |
I | 11111 |
mfp rd, imm8 |
if(imm8 == 0) REG[rd] <- PROD[15:0]; else REG[rd] <- PROD[31:16] |
mul
starts the multiplier unit.
mfp
stands for “move from product.” mfp rd, 0
gets the lower 16 bits of the product; mfp rd, 1
gets the upper 16. It will pause the rest of the CPU if the product is not done being computed yet.