Project 3 EECS 370 (Fall 2025)
|
Worth:
|
100 Points
|
Points
|
|
Assigned:
|
Thursday,October 23rd,2025
|
|
|
Checkpoint Due:
|
Thursday,November 6th,2025
|
5
|
|
Due:
|
Thursday,November 14th,2025
|
95
|
0. Starter Code
Starter code for project 3,the LC2K pipelined simulator.
|
starter_3.tar.gz files
|
Description
|
|
Makefile
|
Makefile to compile the project
|
|
p3spec.as
|
Spec test case assembly file
|
|
p3spec.out.correct
|
Correct pipelined simulator output for spec test case
|
|
starter_simulator.c
|
Starter code for the LC-2K pipelined simulator
|
1.Purpose
This project is intended to help you understand in detail how a pipelined implementation works. You will write a cycle-accurate behavioral simulator for a pipelined implementation of the LC-2K, complete with data forwarding and simple branch prediction.
2. LC-2K Pipelined Implementation
2.1. Datapath
Lecture Pipeline vs. Project 3 Pipeline
The lecture pipeline has internal forwarding for the register file, while the project 3 pipeline does not. This has the following implications:
For the project 3 pipeline, we add the WBEND pipeline register, after the write-back stage. This is because we do not have internal forwarding for the project 3 pipeline.
For the project 3 pipeline, we will need to add 3 noop instructions to avoid data hazards instead of the 2 noop instructions needed for the lecture pipeline
On this note, the pipeline simulator simulates the lecture pipeline instead of the project 3 pipeline.
You will use a clocking scheme mimicking real-life processors (e.g., register file and memory writes require the data to be present for the whole cycle).
2.2. jalr
You will not implement the jalr instruction from LC2K. Taking out jalr eliminates several dependencies. No submitted test cases should include jalr.
2.3. Memory
In the struct stateStruct there are two arrays representing memory: instrMem and dataMem.
Just like project 1, we can access memory directly as an array. The key difference from project 1 is the separation of data and instruction memory.
When the program starts, the starter code will read the machine-code file into BoTH instrMem and dataMem arrays (i.e., they will initially have the same contents).
During execution, you will need to fetch instructions from instrMem and perform. load/stores using dataMem. That is, instrMem will never change after the program starts, but dataMem will change.
2.4. Pipeline Registers
For project 3, we provide structs representing various values held in the pipeline registers. You are required to use these pipeline register structs as printState() will print out their contents for grading.
Note that the instruction gets passed down the pipeline in its entirety.
You are free to add additional member variables, but do not remove any member variables from the pipeline register structs.
3. Problem
3.1. Basic Structure
Your task is to write a pipelined simulator for the LC-2K.
The starter code contains a while loop. Each iteration through the while loop executes one cycle:
At the beginning of the cycle, the complete state of the machine is printed using printState().Notice how printState() is passed the state variable. The state variable represents the current state of the processor.
In the body of the loop, you will figure out what the new state of the machine (memory, registers, and pipeline registers) will be at the end of the cycle. In short, you will compute the newState, depending on the values found in state.
At the end of the loop, we have the following statement: state = newState. This statement sets the current state of the processor, state, to the values we computed in newstate during this cycle. This simulates the positive edge of the clock cycle.
Specific guidelines for state and newstate:
state should never appear on the left-hand side of an assignment (except for array subscripts), and newState should never appear on the right-hand side of an assignment.
Reasoning for state and newstate:
Conceptually, all clocked components (pipeline registers, etc.) of a datapath compute their new states simultaneously with combinational logic. Since statements in C execute sequentially rather than simultaneously, you will need two state variables: state and newstate. This is so we can mimic a clocking scheme used by real processors.
How to use state and newState:
Each stage of the pipeline will modify the newstate variable using the current values in the state variable. In the body of the loop, you will use newstate only as the target of an assignment and you will use state only as the source of an assignment (e.g., newstate.... state...). For example, in the ID stage, you might have the following statement:
newState.IDEX.instr = state.IFID.instr //transfer the instruction in the IFID register to the IDEX register
Your simulator must be pipelined. This means that the work of carrying out an instruction should be done in different stages of the pipeline as described in lecture. The execution of multiple instructions should be overlapped. The ID stage should be the only stage that reads the register file; the other stages must get the register values from a pipeline register. If it violates these criteria, your project will not pass any test cases.
At the start of the program, initialize the pc and all registers to zero. Initialize the instruction field in all pipeline registers to the noop instruction (exiceeeee). A noop must travel through the pipeline, even though it has no effect on the state of the pipeline.
The easiest way to start is to first write your simulator so that it does not account for data or branch hazards. This will allow you to get started right away. Of course, the simulator will only be able to correctly run assembly-language programs that have no hazards. It is thus the responsibility of the assembly-language programmer to insert noop instructions so that there are no data or branch hazards. This strategy is called avoidance. This will require putting noop s in assembly-language programs after a branch and a number of noop s in an assembly-language program before a dependent data operation. (It is a good exercise to figure out the minimum number needed in each situation.) Keep in mind that the project checkpoint tells you if you are passing cases that avoid hazards (specifically, by removing all branches and by inserting noop s for data dependencies). Then you can finish your implementation by accounting for data hazards and control hazards
3.2. Halting
At what point does the pipelined computer know to stop? It's incorrect to stop as soon as a halt instruction is fetched because if an earlier branch was actually taken, then the halt would be squashed.
In the example below, beq start will always branch to the start label. However, the halt instruction will enter our pipeline, as we don't resolve branches until the MEM stage.
Example demonstrating a taken branch causing a halt to be squashed
1 start ...
2 beq 0 0 start
3 done halt
4
To solve this problem, the starter code stops when the halt instruction reaches the MEMwB register. This ensures that previously executed instructions have completed, and it also ensures that the machine won't branch around this halt instruction. Note how the final printState() call will print the final state of the machine before the check for a haltinstruction.