There are three branches in this repo, for diffrent usages.
The frontend is built for pipelined version.
To use the frontend, clone the frontline branch.
For Sequential
cd sequential
make
./y86-simulater < test/example.yo > example.json
or:
cd sequential
make
python3 test.py --bin build/backend
For Pipeline
cd pipelined
cmake .
make
./y86-simulater < test/example.yo > example.json
For frontend
- Open with QT creator
- set test file path
widget.cpp: 13
- build and run
- click start
- click next to step
readin.cpp
To read and parse hexadecimal data from an input file, storing this data into the memory map.
Firstly, it parse each line of the input file according to the position of the colon
and delimiter
. Then, it converts the hexadecimal string into a binary string and stores it into the memory map.
We use address
as key
and a special data structur containing vector data
and vector rdata
as value
to store the data, handling the endianess by storing bytes in reverse order in the data array. The benefit of this design is that we can easily access the data in the memory map by address and check validity of the address.
read and readByte
It computes the block address and the offset, checks the validity of the address, and then constructs the 64-bit value or byte from the memory blocks.
When we readByte
, the address is not necessarily aligned, so we need to read the data in proper block and the next block, and then merge
them together, extracting
the block we need.
write and writeByte
This funtion validates the address alignment and writes the 64-bit value or byte value into the memory map, handling the endianess by storing bytes in reverse order in the data array.
Single Byte Instructions (halt, nop, ret):
If the instruction is halt
, nop
, or ret
, the function increments the PC by 1 (this->PC + 1), indicating that the instruction size is 1 byte.
Two Byte Instructions (rrmovq, opq, pushq, popq):
If the instruction is rrmovq
, opq
, pushq
, and popq
, an additional byte is read from memory. The PC is incremented by 2 to move to the next instruction.
Ten Byte Instructions (irmovq, rmmovq, mrmovq):
These instructions involve reading another byte for register information and then a 64-bit value (valC
), which is read from memory starting from the address PC + 2.
Nine Byte Instructions (jxx, call):
For jxx
(jump) and call instructions, a 64-bit value (valC
) is read from memory starting from PC + 1.
Default Case (Invalid Instruction):
If the instruction code doesn't match any of the known instructions, the function sets a status (STAT_INS
) indicating an invalid instruction.
No Operation for Certain Instructions (halt, nop, irmovq, jxx):
No decoding is needed.
General Register-to-Register and Memory Operations (rrmovq, rmmovq, mrmovq, opq):
Involve two registers. The values of these registers (rA
and rB
) are fetched from the register file (RF
) and stored in valA
and valB
respectively.
Stack Operations (call, ret, popq):
These instructions involve stack manipulations. The stack pointer register (rsp
) is used to get values for both valA
and valB
.
Stack Push Operation (pushq
):
For the pushq
instruction, the value of the register specified in rA
is fetched into valA
, and the stack pointer register's value (rsp
) is fetched into valB, which aligns with the push operation's behavior, where a value is pushed onto the stack pointed by rsp
.
Default Case (Invalid Instruction):
Sets a status (STAT_INS
).
Halt (halt):
Sets the CPU status to STAT_HLT.
No Operation (nop):
Performs no action.
Register-to-Register Move or Conditional Jump (rrmovq, jxx):
Assigns valA
to valE
. For jxx
, it also sets the condition flag with set_Cnd()
. This is consistent with move operations and preparing for conditional jumps.
Immediate to Register Move (irmovq):
Directly assigns the immediate value (valC
) to valE
.
Memory Move Operations (rmmovq, mrmovq):
Calculates valE
as the sum of valB
and valC
.
Arithmetic and Logical Operations (opq):
Performs a specified operation (like add
, subtract
, etc.) and gets the result in valE
. It also sets condition codes with set_CC
as needed.
Call and Push to Stack (call, pushq):
Decrease the stack pointer (stored in valB
) by 8 to make space on the stack.
Return from Call and Pop from Stack (ret, popq):
Increase the stack pointer by 8, removing the top value from the stack or returning from a call.
Default Case (Invalid Instruction):
Sets the status (STAT_INS
) to indicate an invalid instruction.
Halt (halt):
No action is taken.
Simple Instructions (nop, rrmovq, irmovq, rmmovq, mrmovq, opq, pushq, popq):
For these instructions, the PC
is updated to valP
.
Conditional Jump (jxx):
Updates the PC
based on the condition flag (Cnd
). If the condition is true, PC is set to valC
(usually a jump target address); otherwise, it's set to valP
(the next sequential instruction).
Function Call (call):
Sets the PC
to valC
.
Function Return (ret):
Sets the PC
to valM
, which is the return address
.
No Operation (halt, nop, rmmovq, jxx):
No write-back action is required.
Register-to-Register Move (rrmovq):
Writes valE
to register rB
if the condition (Cnd
) is true; otherwise, it writes the original value of rB
(valB
) back to rB
.
Immediate to Register Move (irmovq), Arithmetic and Logical Operations (opq):
Writes the result stored in valE
directly to register rB
.
Memory to Register Move (mrmovq):
Writes the memory content (valM
) to register rA
. This is typical for load operations from memory into a register.
Stack Operations (call, ret, pushq):
Updates the stack pointer register (rsp
) with the new value (valE
).
Stack Pop Operation (popq):
Similar to other stack operations, updates the stack pointer register (rsp
). Additionally, writes the memory content (valM
) to register rA
.
-
Uppercase letters represent registers of the stage, such as
E_dstE
representing the register before theexecute
stage -
Lowercase letters represent output ports. This is because we need to simulate the values passed between components on the physical layer in a serial program. For example,
e_dstE
represents the output value after theexecute
stage. This value is not directly accessed but is passed on to other hardware components.
void CPU::run() {
while (true) {
fetch();
decode();
execute();
memory_access();
write_back();
forward();
control();
pass_params();
if (this->STAT != STAT_AOK) break;
this->record_stage();
}
this->record_stage();
}
The functions fetch
, decode
, execute
, memory_access
, and write_back
simulate the serialization of five components within the same clock cycle. The order is variable and does not affect the output.
In pipeline design, the control of the program_counter
is also implemented in the fetch
stage, with the specific sequence as follows:
Select_PC
: Choose the current round'sPC
from three sources.fetch
: Fetch an instruction from memory.Predict_PC
: Predict the next round'sPC
, and the output isf_PredPC
(actually a wire connection, not a physical register, denoted in lowercasef
). IfF_stall==false
, the value off_PredPC
is written toF_PredPC
; otherwise, it is not, achieving the effect of astall
.
Similar to the sequential
implementation, values are primarily stored in registers or in output ports.
According to the design in the book, this stage is split into three main sub-stages: ALU_A
, ALU_B
, and ALU
. Based on the corresponding value, E_set_CC
sets the corresponding conditional codes
. It is worth noting that when m_stat != STAT_AOK || W_stat != STAT_AOK
, E_set_CC
is disabled (this is because an erroneous instruction has been executed, and the change to visible conditional codes
should be immediately disabled to prevent unexpected behavior in the processor). After E_set_CC
, E_set_Cnd
determines the value of e_Cnd
based on conditional codes
and E_ifun
. This decision determines whether jmpXX
and cmov
instructions are executed.
forward
implements forwarding, simulating the Fwd_B
and Sel+Fwd_A
components. It determines whether to forward based on the values in the required registers and completes the forwarding operation.
control
handles four cases:
- Processing
ret
- Load/use hazard
- Mispredicted branches
- Exceptions (halt, invalid memory access, invalid instructions)
It detects the corresponding conditions and sets the appropriate bubble
or stall
registers.
pass_params
simulates the part of the clock rising edge where register values change. The value transfer includes both from register to register and from output values to registers. The setting of stall
and bubble
takes effect here, determining whether certain values should be transferred based on the corresponding conditions.
Modify the io part of the original code, and add the following functions:
On clicking the start
button, the program will read the file and initialize the memory. Then, it will start the simulation continuously till the end of the program.
Additionally, we store the temporary values of the registers and instructions such as icode
and ifun
in each clock cycle. Finally, we will get total vector containing all stages of the program.
On clicking the next
button, the program performs one clock cycle of simulation and updates the values of the registers and instructions, which is similar to the si
command in gdb
.
For clarity, we use names of the operations and registers instead of the corresponding values.