Interrupts Project
Interrupts Project
Project 2 - Interrupts
1 Introduction
We have spent the last few weeks implementing our 32-bit datapath. The simple 32-bit LC-5500 is capable
of performing advanced computational tasks and logical decision making. Now it is time for us to move on
to something more advanced—the upgraded LC-5500a enables the ability for programs to be interrupted.
Your assignment is to fully implement and test interrupts using the provided datapath and CircuitSim. You
will hook up the interrupt and data lines to the new timer device, modify the datapath and microcontroller
to support interrupt operations, and write an interrupt handler to operate this new device. You will also
use the tiny, inexpensive LC-5500a as an embedded system to monitor a kitchen appliance.
2 Requirements
Before you begin, please ensure you have done the following:
• Download the proper version of CircuitSim. A copy of CircuitSim is available under Files on Grade-
scope. You may also download it from the CircuitSim website (https://ra4king.github.io/CircuitSim/).
In order to run CircuitSim, Java must be installed. If you are a Mac user, you may need to right-click
on the JAR file and select “Open” in the menu to bypass Gatekeeper restrictions.
• CircuitSim is still under development and may have unknown bugs. Please back up your work using
some form of version control, such as a local/private git repository or Dropbox. Do not use public
git repositories; it is against the Georgia Tech Honor Code.
• The LC-5500a assembler is written in Python. If you do not have Python 2.6 or newer installed on
your system, you will need to install it before you continue.
Datapath
DrData
DATA
Microcontroller INT
INTA INTA
Timer Device Other Devices
For this assignment, you will add interrupt support to the LC-5500a datapath. Then, you will test your new
capabilities to handle interrupts using an external timer device.
Work in the LC-5500a.sim file. If you wish to use your existing datapath, make a copy with this name,
and add the devices we provided.
by using a specific gate to act like a pull-down resistor so that there is always a value asserted (See
Appendix C for more information)..
3. When a device receives an IntAck signal, it will drive a 32-bit device ID onto the I/O Data Bus. To
prevent devices from interfering with the processor, the I/O Data Bus is attached to the Main Bus
with a tri-state driver. Create this driver and the bus, and attach the microcontroller’s DrDATA
signal to the driver.
4. Modify the datapath so that the PC starts at 0x08 when the processor is reset. Normally the PC
starts at 0x00, however we need to make space for the interrupt vector table (IVT). Therefore, when
you actually load in the test code that you will write, it needs to start at 0x08. Please make sure that
your solution ensures that datapath can never execute from below 0x08 - or in other words, force the
PC to drive the value 0x08 if the PC is pointing in the range of the interrupt vector table.
5. Create hardware to support selecting the register $k0 within the microcode. This is needed by some
interrupt related instructions. Because we need to access $k0 outside of regular instructions, we cannot
use the Rx / Ry / Rz bits. HINT: Use only the register selection bits that the main ROM already
outputs to select $k0. Notice that there is an unused input to the RegSel multiplexer.
1. Be sure to read the appendix on the microcontroller before starting this section.
2. Modify the microcontroller to support asserting four new signals:
(a) LdEnInt & EnInt to control whether interrupts are enabled/disabled. You will use these 2
signals to control the value of your interrupts enabled register.
(b) IntAck to send an interrupt acknowledge to the device.
(c) DrDATA to drive the value on the I/O Data Bus to the Main Bus.
3. Extend the size of the ROM accordingly.
4. Add the fourth ROM described in Appendix B: Microcontrol Unit to handle onInt.
5. Modify the FETCH macrostate microcode so that we actively check for interrupts. Normally this is
done within the INT macrostate (as described in Chapter 4 of the book and in the lectures) but we
are rolling this functionality in the FETCH macrostate for the sake of simplicity. You can accomplish
this by doing the following:
(a) First check to see if the CPU should be interrupted. To be interrupted, two conditions must be
true: (1) interrupts are enabled (i.e., the IE register must hold a ’1’), and (2), a device must be
asserting a ’1’ on the INT signal line.
(b) If not, continue with FETCH normally.
(c) If the CPU should be interrupted, then perform the following:
i. Save the current PC to the register $k0.
ii. Disable interrupts.
iii. Assert the interrupt acknowledge signal (IntAck). Next, drive the device ID from the I/O
Data Bus and use it to index into the interrupt vector table to retrieve the new PC value.
The device will drive its device ID onto the I/O Data Bus one clock cycle after it receives
the IntAck signal.
iv. This new PC value should then be loaded into the PC.
Note: onInt works in the same manner that CmpOut did in Project 1. The processor
should branch to the appropriate microstate depending on the value of onInt. onInt
should be true when interrupts are enabled AND when there is an interrupt to be
acknowledged. Note: The mode bit mechanism and user/kernel stack separation
discussed in the textbook has been omitted for simplicity.
6. Implement the microcode for three new instructions for supporting interrupts as described in Chapter
4. These are the EI, DI, and RETI instructions. You need to write the microcode in the main ROM
controlling the datapath for these three new instructions. Keep in mind that:
(a) EI sets the IE register to 1.
(b) DI sets the IE register to 0.
(c) RETI loads $k0 into the PC, and enables interrupts.
You should refer to Chapter 4 of the textbook to see how to write a correct interrupt handler. As detailed
in that chapter, your handler will need to do the following:
1. First save the current value of $k0 (the return address to where you came from to the current handler)
2. Enable interrupts (which should have been disabled implicitly by the processor within the INT macrostate).
3. Save the state of the interrupted program.
4. Implement the actual work to be done in the handler. In the case of this project, we want you to
increment a counter variable in memory, which we have already provided.
5. Restore the state of the original program and return using RETI.
The handler you have written for the timer device should run every time the device’s interrupt is triggered.
Make sure to write the handler such that interrupts can be nested. With that in mind, interrupts should be
disabled for as few instructions as possible within the handlers.
You will need to do the following:
1. Write the interrupt handler (should follow the above instructions or simply refer to Chapter 4 in your
book). In the case of this project, we want the interrupt handler to keep track of time in memory at
the predetermined location: 0xFFFF
2. Load the starting address of the first handler you just implemented in prj2.s into the interrupt vector
table at the appropriate addresses (the table is indexed using the device ID of the interrupting device).
Test your design before moving onto the next section. If it works correctly, you should see the value
at address 0xFFFF in memory increment as the program runs.
Project 2 CS 2200 - Systems and Networks Spring 2023
Datapath
Datapath
DAR LdDAR
DrData
ADDR
DATA
Microcontroller INT
INTA INTA
Timer Device Distance Tracker Other Devices
Figure 2: Interrupt Hardware for the LC-5500a with Basic I/O Support
Eager to put your newfound knowledge of device interrupts from CS2200 to good use, you decide to apply
what you’ve learned to your engineering passion: distance tracker. You are interested to know the maximum
and minimum distance in that area.
You’ve rigged up a device that is able to report the current distance measured to an LC-5500a processor via
an interrupt. There’s only one issue: as of right now, your datapath can detect when an external device is
ready to interrupt the processor, but it cannot retrieve data from external devices.
In this phase of the project, you will add functionality for device-addressed input. You will then make use
of this functionality by adding a device simulating a distance tracker and writing a simple handler for the
device.
2. Modify the microcontroller to support a new control signal, LdDAR. This signal will be used in order
to enable writing to the DAR.
3. Implement the IN instruction in your microcode. This instruction takes a device address an immediate
offset (IR[19:0]), loads it into the DAR, and writes the value on the data bus into a register. When
it is done, it must clear the DAR (since interrupts use the data bus to communicate device IDs).
Examine the format of the IN instruction and consider what signals you might raise in order to write
a constant zero into the DAR.
6 Autograder
Similar to the autograder for Project 1, Project 2 autograder will execute your prj2.s using your datapath
and simulate the interrupt handling process. It will tell you if your handler codes complete their jobs. Your
final grade will not be determined by whether you pass the autograder or not. Feel free to use it as a tool to
help you debug your circuit and assembly code, but you won’t be able to rely on it entirely. You must still
figure out which part of your datapath/microcode/handler code is not functioning as expected. If you want
to use the autograder, you must follow a few rules:
• Don’t rename the components that already exist
• Name your IE register as “IE”
• Name your Interrupt ROM as “INT”
• Use only one clock globally.
• Use only one RAM as memory.
• Don’t change the layout of the microcode Excel sheet
• If you changed the constants in devices for debugging purposes, remember to change them back.
If the autograder fails you, please first double-check if you meet all the rules above and those in Project 1. If
the autograder points you to a device handler, try to load the assembled HEX of your prj2.s program into
your RAM, clock it until the device interrupts, and check state by state to see if any component goes wrong
when handling that interrupt. Sometimes, you may not reproduce the error when that device interrupts for
the first time. In that case, you need to monitor all the following interrupts of that device to locate the
problem.
When you get an error message, please first try to reproduce it locally and think about what you observe.
If you still don’t know how to approach it, come to office hours or make a private post with a detailed
explanation of your attempts of debugging, instead of just a post with the error message and a screenshot
of your datapath. TAs won’t be able to help you solve the problem with that little amount of information.
7 Deliverables
To submit your project, you need to upload the following files to Gradescope:
• CircuitSim datapath file (LC-5500a.sim)
• Microcode file (microcode.xlsx)
• Assembly code (prj2.s)
If you are missing any of those files, you will get a 0, so make sure that you have uploaded all
three of them.
Always re-download your assignment from Gradescope after submitting to ensure that all
necessary files were properly uploaded. If what we download does not work, you will get a 0
regardless of what is on your machine.
This project will be demoed. In order to receive full credit, you must sign up for a demo slot and
complete the demo. We will announce when demo times are released.
Project 2 CS 2200 - Systems and Networks Spring 2023
8.1 Registers
The LC-5500a has 16 general-purpose registers. While there are no hardware-enforced restraints on the uses
of these registers, your code is expected to follow the conventions outlined below.
1. Register 0 is always read as zero. Any values written to it are discarded. Note: for the purposes of
this project, you must implement the zero register. Regardless of what is written to this register, it
should always output zero.
2. Register 1 is used to hold the target address of a jump. It may also be used by pseudo-instructions
generated by the assembler.
3. Register 2 is where you should store any returned value from a subroutine call.
4. Registers 3 - 5 are used to store function/subroutine arguments. Note: registers 2 through 8 should
be placed on the stack if the caller wants to retain those values. These registers are fair game for the
callee (subroutine) to trash.
5. Registers 6 - 8 are designated for temporary variables. The caller must save these registers if they
want these values to be retained.
6. Registers 9 - 11 are saved registers. The caller may assume that these registers are never tampered
with by the subroutine. If the subroutine needs these registers, then it should place them on the stack
and restore them before they jump back to the caller.
7. Register 12 is reserved for handling interrupts. While it should be implemented, it otherwise will not
have any special use on this assignment.
Project 2 CS 2200 - Systems and Networks Spring 2023
8. Register 13 is the everchanging top of the stack stack; it keeps track of the top of the activation
record for a subroutine.
9. Register 14 is the anchor point of the activation frame. It is used to point to the first address on the
activation record for the currently executing process.
10. Register 15 is used to store the address a subroutine should return to when it is finished executing.
Encoding
31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
Operation
DR = SR1 + SR2;
Description
The ADD instruction obtains the first source operand from the SR1 register. The second source operand is
obtained from the SR2 register. The second operand is added to the first source operand, and the result is
stored in DR.
8.3.2 NAND
Assembler Syntax
NAND DR, SR1, SR2
Encoding
31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
Operation
DR = ~(SR1 & SR2);
Description
The NAND instruction performs a logical NAND (AND NOT) on the source operands obtained from SR1
and SR2. The result is stored in DR.
HINT: A logical NOT can be achieved by performing a NAND with both source operands the same.
For instance,
NAND DR, SR1, SR1
...achieves the following logical operation: DR ← SR1.
Project 2 CS 2200 - Systems and Networks Spring 2023
8.3.3 ADDI
Assembler Syntax
ADDI DR, SR, immval20
Encoding
31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
0010 DR SR immval20
Operation
DR = SR + SEXT(immval20);
Description
The ADDI instruction obtains the first source operand from the SR register. The second source operand is
obtained by sign-extending the immval20 field to 32 bits. The resulting operand is added to the first source
operand, and the result is stored in DR.
8.3.4 LW
Assembler Syntax
LW DR, offset20(BaseR)
Encoding
31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
Operation
DR = MEM[BaseR + SEXT(offset20)];
Description
An address is computed by sign-extending bits [19:0] to 32 bits and then adding this result to the contents
of the register specified by bits [23:20]. The 32-bit word at this address is loaded into DR.
Project 2 CS 2200 - Systems and Networks Spring 2023
8.3.5 SW
Assembler Syntax
SW SR, offset20(BaseR)
Encoding
31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
Operation
MEM[BaseR + SEXT(offset20)] = SR;
Description
An address is computed by sign-extending bits [19:0] to 32 bits and then adding this result to the contents
of the register specified by bits [23:20]. The 32-bit word obtained from register SR is then stored at this
address.
8.3.6 BR
Assembler Syntax
BR offset20
Encoding
31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
Operation
PC = incrementedPC + offset20
Description
A branch is unconditionally taken. The PC will be set to the sum of the incremented PC (since we have
already undergone fetch) and the sign-extended offset[19:0].
Project 2 CS 2200 - Systems and Networks Spring 2023
8.3.7 JALR
Assembler Syntax
JALR RA, AT
Encoding
31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
0110 RA AT unused
Operation
RA = PC;
PC = AT;
Description
First, the incremented PC (address of the instruction + 1) is stored into register RA. Next, the PC is loaded
with the value of register AT, and the computer resumes execution at the new PC.
8.3.8 HALT
Assembler Syntax
HALT
Encoding
31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
0111 unused
Description
The machine is brought to a halt and executes no further instructions.
8.3.9 BLT
Assembler Syntax
BLT SR1, SR2
Encoding
31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
Operation
if (SR1 < SR2) {
PC = incrementedPC + SEXT(PCoffset20)
}
Description
This is a conditional branch that will be taken only if the value of the register SR1 is less that then value of
the SR2 register. The PC will be set to the sum of the incremented PC (since we have already undergone
fetch) and the sign-extended offset[19:0].
Project 2 CS 2200 - Systems and Networks Spring 2023
8.3.10 LEA
Assembler Syntax
LEA DR, label
Encoding
31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
Operation
DR = PC + SEXT(PCoffset20);
Description
An address is computed by sign-extending bits [19:0] to 32 bits and adding this result to the incremented
PC (address of instruction + 1). It then stores the computed address into register DR.
8.3.11 PUSH
Assembler Syntax
PUSH SR
Encoding
31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
Operation
X = SR
$SP = $SP - 1
MEM[$SP] = X
Description
Decrements the stack pointer and stores the value of the source register SR at the memory location of the
stack pointer. Note: the operation of the decrement must happen before the store in order to ensure that
the stack pointer always points to the top of the stack, but be sure to take special care when pushing the
stack pointer itself.
Project 2 CS 2200 - Systems and Networks Spring 2023
8.3.12 POP
Assembler Syntax
POP DR
Encoding
31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
Operation
X = MEM[$SP]
$SP = $SP + 1
DR = X
Description
Loads the value from memory pointed to by the stack pointer into DR and increments the value of the stack
pointer. Note: the operation of the increment must happen after the load in order to ensure that the stack
pointer always points to the top of the stack.
8.3.13 EI
Assembler Syntax
EI
Encoding
31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1100 unused
Operation
IE = 1;
Description
The Interrupts Enabled register is set to 1, enabling interrupts.
Project 2 CS 2200 - Systems and Networks Spring 2023
8.3.14 DI
Assembler Syntax
DI
Encoding
31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1101 unused
Operation
IE = 0;
Description
The Interrupts Enabled register is set to 0, disabling interrupts.
8.3.15 RETI
Assembler Syntax
RETI
Encoding
31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1110 unused
Operation
PC = $k0;
IE = 1;
Description
The PC is restored to the return address stored in $k0. The Interrupts Enabled register is set to 1, enabling
interrupts.
Project 2 CS 2200 - Systems and Networks Spring 2023
8.3.16 IN
Assembler Syntax
IN DR, DeviceADDR
Encoding
31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
Operation
DAR = addr20;
DR = DeviceData;
DAR = 0;
Description
The value in addr20 is sign-extended to determine the 32-bit device address. This address is then loaded
into the Device Address Register (DAR). The processor then reads a single word value off the device data
bus, and writes this value to the DR register. The DAR is then reset to zero, ending the device bus cycle.
Project 2 CS 2200 - Systems and Networks Spring 2023
As you can see, there are three different locations that the next state can come from: part of the output
from the previous state (main ROM), the sequencer ROM, and the condition ROM. The mux controls which
of these sources gets through to the state register. If the previous state’s “next state” field determines where
to go, neither the OPTest nor ChkCmp signals will be asserted. If the opcode from the IR determines the
next state (such as at the end of the FETCH state), the OPTest signal will be asserted. If the comparison
circuitry determines the next state (such as in the BLT instruction), the ChkCmp signal will be asserted.
Note that these two signals should never be asserted at the same time since nothing is input into the “11”
pin on the MUX.
The sequencer ROM should have one address per instruction, and the condition ROM should have one
address for condition true and one for condition false.
Before getting down to specifics you need to determine the control scheme for the datapath. To do this
examine each instruction, one by one, and construct a finite state bubble diagram showing exactly what
control signals will be set in each state. Also determine what are the conditions necessary to pass from one
state to the next. You can experiment by manually controlling your control signals on the bus you’ve created
in Phase 1 - Implementing a Basic Interrupt to make sure that your logic is sound.
Once the finite state bubble diagram is produced, the next step is to encode the contents of the Control Unit
ROM. Then you must design and build (in CircuitSim) the Control Unit circuit which will contain the three
ROMs, a MUX, and a state register. Your design will be better if it allows you to single step and ensure
that it is working properly. Finally, you will load the Control Unit’s ROMs with the hexadecimal generated
by your filled out microcode.xlsx.
Note that the input address to the ROM uses bit 0 for the lowest bit of the current state and 5 for the
highest bit for the current state.