Outline#
In this week’s lab you will:
- Make design decisions for implementing new features to the QuAC CPU.
- Implement conditional execution of instructions based on the status of the flag register.
- Make the program counter a destination operand for instructions to automatically modify program (control) flow.
Preparation#
In preparation for this lab, you should make sure to have the working automatic CPU circuit, cpu.dig
, and all of its sub-circuits from CPU, Part II.
This lab has no template files. You should continue from the previous lab’s circuit files.
Introduction#
Last week we constructed a fully automatic CPU that fetches instructions from memory and executes them. Our CPU can now load data words from memory into registers, perform simple arithmetic operations on the data words, and store data words to memory. Unfortunately, the programs we can write for our CPU are relatively simple and boring. Specifically, we lack a way to conditionally execute instructions based on the result of an earlier instruction. We also lack a way to write conditional statements: if/else statements, switch/case statements, and for/while loops.
So far, we have guided you with implementing specific instructions in the QuAC ISA. You will need to work out the design details for the remaining features in the base QuAC ISA on your own from now onward. There are multiple ways to approach the implementation of instructions and other elements in the ISA. As you make your decisions think about the advantages and disadvantages of competing approaches.
Keep notes on the design and implementation choices you make, as you will need these later when writing your assignment report.
We have been ignoring the four flags produced by the ALU: negative (N), zero (Z), carry (C), and overflow (V) since CPU, Part I. These flags are called condition flags or status flags. We will now use these flags to perform conditional execution. Each arithmetic instruction changes these flags based on the results of the ALU operation. The subsequent instructions can then execute conditionally, based on the status of the flags. In the QuAC specification, a special register called the flag register stores the four status flags.
Exercise 1: Flag Register#
We first add another register, the flag register, into which the flags are stored when an instruction is executed. The flag register should have the following properties:
- The flag register may be read like any other register.
- Writing to the flag register is undefined behaviour.
- If any arithmetic/logic instruction (
add
,sub
,and
,orr
) is executed, the flags are updated to the flags generated by the ALU. - If any non-ALU instruction (
str
,ldr
,movl
,seth
) is executed, the flags should be left unchanged. - The register code for the flag register is
101
, with mnemonicfl
. - The flag register is 16 bits wide, and the flags are stored as follows.
15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Undefined | V | C | N | Z |
- Z - Zero flag
- N - Negative flag
- C - Carry flag
- V - Overflow flag
- Undefined - Reserved for future use
We update the table of register codes as follows:
Code | Mnemonic | Meaning | Behaviour |
---|---|---|---|
000 | rz |
Zero Register | Always reads as zero, even after being written to. |
001 | r1 |
Register 1 | General purpose register. |
010 | r2 |
Register 2 | General purpose register. |
011 | r3 |
Register 3 | General purpose register. |
100 | r4 |
Register 4 | General purpose register. |
101 | fl |
Flag register | Behaves as described above. |
110 | - | Undefined | Any operation with this register is undefined. |
111 | - | Undefined | Any operation with this register is undefined. |
Now, you need to make suitable adjustments to the CPU microarchitecture yourself to implement the flag register and the associated behaviour.
In cpu.dig
and control_unit.dig
, make suitable changes to the CPU design
and add a flag register, labelled fl
, that satisfies the properties
mentioned above.
You need to consider where to physically locate the flag register.
- Option 1: Top-level schematic
- If so, how do we read from it when executing an instruction?
- Option 2: Inside the register file
- If so, how does the ALU write to it after each instruction?
- Option 3: Inside the ALU
- In fact, do not do this! The ALU should purely be a combinational circuit.
You need to think and report how the choices above affect the implementation complexity.
You might need to make some changes to the register file to allow the ALU to write, and the CPU to read from the flag register.
Also, you likely need to add another 1-bit control signal (we suggest the name
FLEN
) that controls if the flag register is updated (written) for the
currently executing instruction. Make the necessary changes to the control
unit.
You should modify the control unit test cases to also test FLEN
.
Exercise 2: Conditionally Execute on Zero#
Now that we have a register to store the flags in, we can modify our ISA to conditionally execute an instruction based on the status of the flag register.
We modify the instruction encoding, and define the eleventh bit to be the conditional cond
bit.
Register Operands Format (R-Format)#
15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
op |
cond |
rd |
0 | ra |
0 | rb |
Immediate Format (I-Format)#
15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
op |
cond |
rd |
imm8 |
- If the
cond
bit in the instruction is zero, the instruction is executed as normal. - If the
cond
bit in the instruction is one, then:- If the Z flag in the flag register is one, then the instruction is executed as normal.
- If the Z flag in the flag register is zero, then the instruction does nothing (no changes to the register file or memory).
All instructions can now execute conditionally, and an instruction is denoted as being conditional on the Z flag by appending eq
to the end of the name of the instruction. This suffix is handled by the assembler; your CPU only needs to worry about the cond
bit in the resulting machine code.
Syntax | Semantic | Machine Code |
I-Format Instructions | ||
movl rd, imm8 |
rd ≔ #imm8 |
0000 <cond> <rd> <imm8> |
R-Format Memory Instructions | ||
str rd, [ra] |
[ra] ≔ rd |
0100 <cond> <rd> 0 <ra> 0000 |
ldr rd, [ra] |
rd ≔ [ra] |
0101 <cond> <rd> 0 <ra> 0000 |
R-Format ALU Instructions | ||
add rd, ra, rb |
rd ≔ ra + rb |
1000 <cond> <rd> 0 <ra> 0 <rb> |
sub rd, ra, rb |
rd ≔ ra - rb |
1001 <cond> <rd> 0 <ra> 0 <rb> |
and rd, ra, rb |
rd ≔ ra & rb |
1010 <cond> <rd> 0 <ra> 0 <rb> |
orr rd, ra, rb |
rd ≔ ra | rb |
1011 <cond> <rd> 0 <ra> 0 <rb> |
All the above instructions may now have the suffix eq
added to indicate setting the cond
bit to 1 in the machine code. E.g., add
has cond == 0
and addeq
is add
but with cond == 1
.
In control_unit.dig
, modify the control unit to implement conditional
execution on the zero flag.
The control unit will need to read the Z bit from the flag register
as an additional input.
You may want to first add an additional control signal EXEC
that is internal to the control unit, and indicates whether
the current instruction should be executed or not. You can
then use the value of EXEC
to decide what should happen
to the other control signals.
As a bonus, you can also output EXEC
from the control unit
and add a status LED Components -> IO -> LED to
keep track of the computer.
Of course, you should test EXEC
too.
Exercise 3: Modifying Program Flow#
As it stands, our computer is a little boring, as there’s no way to control the
flow of the program. Our computer executes instruction sequentially (i.e., one
after the other) until it runs out of instructions. To implement loops and make
the computer perform different tasks based on the input, we need the ability to
modify the program counter during execution. Doing so is called changing the
program flow. Modifying the pc
register makes the computer execute a
different part of the program.
We would like the program counter to have the following behaviour:
- The program counter is assigned register code
111
and mnemonicpc
. - Reads and writes to the
pc
register are defined in the same way as for any other general purpose register. - The address of the current instruction to execute lives in the program counter.
- If the current instruction (i.e., one about to be executed) does not overwrite the program
counter, then the program counter is incremented (
pc := pc+1
) on the next clock cycle.
We modify the table of register codes to allow the program counter to be a valid source and destination operand for all instructions.
Code | Mnemonic | Meaning | Behaviour |
---|---|---|---|
000 | rz |
Zero Register | Always reads as zero, even after being written to. |
001 | r1 |
Register 1 | General purpose register. |
010 | r2 |
Register 2 | General purpose register. |
011 | r3 |
Register 3 | General purpose register. |
100 | r4 |
Register 4 | General purpose register. |
101 | fl |
Flag register | Behaves as described above. |
110 | - | Undefined | Any operation with this register is undefined. |
111 | pc |
Program counter | Behaves as described above. |
In cpu.dig
, modify the CPU to allow all instructions to read and write
to the pc
.
If the current instruction executes conditionally, and has the pc
as the destination register (addeq pc, r2, r3
), and the instruction do not execute, we still increment pc := pc+1
as normal. Can you see why this is the case?
Although the pc
register is logically part of the register file, it is read and written on every cycle independent of the normal register file operation. A question arises if we should build pc
as a standalone register or as part of the register file. Consider the pros and cons of both approaches in your implementation.
- Option 1:
pc
inside the register file- How do we interface the
pc
register with memory for fetching instructions?
- How do we interface the
- Option 2:
pc
outside the register file- How do instructions read from and write to the
pc
?
- How do instructions read from and write to the
- Option 3:
pc
is somewhere else- We leave this to your creativity!
Exercise 4: The seth
Instruction#
There is one last instruction, namely seth
, short for set high (byte),
left to do to have the full functionality of the base ISA.
Syntax | Meaning | Machine Code |
---|---|---|
seth rd, imm8 |
See below | 0001 <cond> <rd> <imm8> |
This instruction can be used to move an 8-bit value into the most significant byte of a register, leaving the least significant byte unchanged.
For example, if r1 = 0x12
and we executed seth r1, 0x34
, the result would be that r1 = 0x3412
. Formally, seth rd, imm8
performs the operation rd := (#imm8 << 8) | (rd & 0xff)
.
In cpu.dig
, modify the CPU to allow execution of the seth
instruction.
Conclusion#
We hope the last three weeks were fulfilling for you and that you learned a lot. If you have made it through all this, you now have a working CPU that satisfies the QuAC ISA for the assignment.
Now is a good time to document your design decisions to implement all the instructions. Note that you will need to recall all these details when writing the report for the assignment.
You have been pushing your work to GitLab, right?