Outline#

In this week’s lab you will:

  1. Make design decisions for implementing new features to the QuAC CPU.
  2. Implement conditional execution of instructions based on the status of the flag register.
  3. 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 mnemonic fl.
  • 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 mnemonic pc.
  • 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?
  • Option 2: pc outside the register file
    • How do instructions read from and write to the pc?
  • 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?

bars search times arrow-up