Skip to content

Commit

Permalink
Merge branch 'dev' into 'main'
Browse files Browse the repository at this point in the history
Merge Dev

* Implemented pipelline stalling
* Added the performance metrics tooltip to Toy
* Exit codes now get printed again
* assembly parser: fix jal instruction - interpret target address as absolute value
* handle python package as normal asset
* CLI print adjustments

See merge request es/lehre/rechnerarchitektur/architecture-simulator!64
  • Loading branch information
mikuhn committed Apr 24, 2024
2 parents 98c0d41 + 1eac0a1 commit d3c028a
Show file tree
Hide file tree
Showing 24 changed files with 796 additions and 61 deletions.
3 changes: 0 additions & 3 deletions .github/workflows/python-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,6 @@ jobs:
pip install build
- name: Build package
run: python -m build
- name: Copy package to 'webgui'
run: |
cp dist/*.whl webgui/public/
- name: Use Node.js
uses: actions/setup-node@v3
with:
Expand Down
2 changes: 0 additions & 2 deletions .gitlab-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,6 @@ build_webgui:
- "build_py"
image: node:20-alpine
script:
- mkdir -p webgui/public
- cp dist/*.whl webgui/public
- npm install
- npm run build-dev-atreus
artifacts:
Expand Down
9 changes: 5 additions & 4 deletions architecture_simulator/cli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ def main():
while True:
read_command = []
try:
read_command = session.prompt(">>>").strip().lower().split()
read_command = session.prompt(">>> ").strip().lower().split()
except KeyboardInterrupt:
break
except EOFError:
Expand Down Expand Up @@ -173,10 +173,8 @@ def display(sim: Union[ToySimulation, RiscvSimulation], display_mode: str) -> st
if sim.mode == "five_stage_pipeline":
res += five_stage_pipeline_repr(sim.state.pipeline.pipeline_registers)
res += hline
res += f"PC: {sim.state.program_counter} | Instruction at PC: {'#####' if not sim.state.instruction_at_pc() else str(sim.state.instruction_memory.read_instruction(sim.state.program_counter))}\n"
res += f"PC: {hex(sim.state.program_counter)} | Instruction at PC: {'#####' if not sim.state.instruction_at_pc() else str(sim.state.instruction_memory.read_instruction(sim.state.program_counter))}\n"
res += hline
res += "Instruction Memory:\n"
res += instr_mem_repr(sim)
res += hline
res += "Performance Metrics:\n"
res += sim.state.performance_metrics.__repr__()
Expand Down Expand Up @@ -598,6 +596,9 @@ def __call__(
output = "The provided file caused a parsing exception:\n"
output += e.__repr__() + "\n"
return CommandResult(output, sim)
if isinstance(new_sim, RiscvSimulation):
print("Instruction Memory:")
print(instr_mem_repr(new_sim))
# Excecute load or step argument
if "-run" in command:
run_command = RunCommand()
Expand Down
5 changes: 3 additions & 2 deletions architecture_simulator/isa/riscv/instruction_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -416,13 +416,14 @@ def access_register_file(


class JTypeInstruction(RiscvInstruction):
def __init__(self, rd: int, imm: int, **args):
def __init__(self, rd: int, imm: int, abs_addr: int, **args):
super().__init__(**args)
self.rd = rd
self.imm = (imm & (2**20) - 1) - (imm & 2**20) # 21-bit sext
self.abs_addr = abs_addr # only used in __repr__

def __repr__(self) -> str:
return f"{self.mnemonic} x{self.rd}, {self.imm}"
return f"{self.mnemonic} x{self.rd}, {self.abs_addr}"

def control_unit_signals(self) -> ControlUnitSignals:
return ControlUnitSignals(
Expand Down
3 changes: 3 additions & 0 deletions architecture_simulator/isa/riscv/riscv_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -748,11 +748,14 @@ def _write_instructions(self) -> None:
line_number=line_number,
line=line,
)
if line_parsed.get("imm"):
imm_val -= address_count

instructions.append(
instruction_class(
rd=self._convert_register_name(line_parsed.rd),
imm=imm_val,
abs_addr=imm_val + address_count,
)
)
elif issubclass(instruction_class, instruction_types.CSRTypeInstruction):
Expand Down
4 changes: 2 additions & 2 deletions architecture_simulator/isa/riscv/rv32i_instructions.py
Original file line number Diff line number Diff line change
Expand Up @@ -1217,8 +1217,8 @@ def alu_compute(


class JAL(JTypeInstruction):
def __init__(self, rd: int, imm: int):
super().__init__(rd, imm, mnemonic="jal")
def __init__(self, rd: int, imm: int, abs_addr: int):
super().__init__(rd, imm, abs_addr, mnemonic="jal")

def behavior(
self, architectural_state: RiscvArchitecturalState
Expand Down
71 changes: 71 additions & 0 deletions architecture_simulator/uarch/riscv/pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ def __init__(
PipelineRegister()
] * self.num_stages

# if != None: [index of stage to cause stall, remaining duration of stall]
self.stalled: list[int] | None = None
# holds the old contents of pipeline registers that are used as input for stalled stages
self.stalled_pipeline_regs: list[PipelineRegister] | None = None

def step(self):
"""the pipeline step method, this is the central part of the pipeline! Every time it is called, it does one
whole step of the pipeline, and every stage gets executed once in their execution ordering
Expand All @@ -50,11 +55,43 @@ def step(self):
next_pipeline_registers = [None] * self.num_stages
for index in self.execution_ordering:
try:
if self.stalled is not None:
if index == 0: # first stage must not be recomputed when stalling
next_pipeline_registers[0] = self.pipeline_registers[0]
continue
elif (
index == self.stalled[0] + 1
): # first stage after the stalled stages must get an empty PipelineRegister while stalling
tmp = self.pipeline_registers[self.stalled[0]]
self.pipeline_registers[self.stalled[0]] = PipelineRegister()
next_pipeline_registers[index] = self.stages[index].behavior(
pipeline_registers=self.pipeline_registers,
index_of_own_input_register=(index - 1),
state=self.state,
)
self.pipeline_registers[self.stalled[0]] = tmp
continue
elif (
index <= self.stalled[0]
): # other stalled stages should get the old PipelineRegister values stored in stalled_pipeline_regs
tmp = self.pipeline_registers[index - 1]
self.pipeline_registers[index - 1] = self.stalled_pipeline_regs[
index - 1
]
next_pipeline_registers[index] = self.stages[index].behavior(
pipeline_registers=self.pipeline_registers,
index_of_own_input_register=(index - 1),
state=self.state,
)
self.pipeline_registers[index - 1] = tmp
continue

next_pipeline_registers[index] = self.stages[index].behavior(
pipeline_registers=self.pipeline_registers,
index_of_own_input_register=(index - 1),
state=self.state,
)

except Exception as e:
if index - 1 >= 0:
raise InstructionExecutionException(
Expand All @@ -68,8 +105,36 @@ def step(self):
)
else:
raise

# Check if a stage has produced a meaningfull stall signal
for index, pipeline_register in reversed(
list(enumerate(next_pipeline_registers))
):
if (
pipeline_register is not None
and pipeline_register.stall_signal is not None
and (self.stalled is None or index > self.stalled[0])
):
self.stalled = [index, pipeline_register.stall_signal.duration + 1]
self.state.performance_metrics.stalls += 1
break

# keep PipelineRegister values in stalled_pipeline_regs
if self.stalled and self.stalled_pipeline_regs is None:
self.stalled_pipeline_regs = self.pipeline_registers[: self.stalled[0]]

for reg in self.stalled_pipeline_regs:
reg.is_of_stalled_value = True

self.pipeline_registers = next_pipeline_registers

# Check if done stalling
if self.stalled is not None:
self.stalled[1] -= 1
if self.stalled[1] == 0:
self.stalled = None
self.stalled_pipeline_regs = None

# if one of the stages wants to flush, do so (starting from the back makes sense)
for index, pipeline_register in reversed(
list(enumerate(self.pipeline_registers))
Expand All @@ -84,6 +149,12 @@ def step(self):
PipelineRegister()
] * num_to_flush
self.state.program_counter = flush_signal.address

# Unstall stages that have been flushed
if self.stalled is not None and self.stalled[0] < num_to_flush:
self.stalled = None
self.stalled_pipeline_regs = None

break # break since we don't care about the previous stages

def is_empty(self) -> bool:
Expand Down
6 changes: 5 additions & 1 deletion architecture_simulator/uarch/riscv/pipeline_registers.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

if TYPE_CHECKING:
from architecture_simulator.isa.riscv.instruction_types import RiscvInstruction
from .stages import FlushSignal
from .stages import FlushSignal, StallSignal


@dataclass
Expand All @@ -19,8 +19,12 @@ class PipelineRegister:
instruction: RiscvInstruction = field(default_factory=EmptyInstruction)
address_of_instruction: Optional[int] = None
flush_signal: Optional[FlushSignal] = None
stall_signal: Optional[StallSignal] = None
abbreviation = "Single"

# True, if the register is being separately preserved by the pipeline for stalling
is_of_stalled_value: bool = False


@dataclass
class InstructionFetchPipelineRegister(PipelineRegister):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ class RiscvPerformanceMetrics(PerformanceMetrics):
branch_count: int = 0
procedure_count: int = 0
flushes: int = 0
stalls: int = 0
cycles: int = 0

def __repr__(self) -> str:
Expand All @@ -25,6 +26,7 @@ def __repr__(self) -> str:
representation += f"branches: {self.branch_count}\n"
representation += f"procedures: {self.procedure_count}\n"
representation += f"cycles: {self.cycles}\n"
representation += f"stalls: {self.stalls}\n"
representation += f"flushes: {self.flushes}\n"
if not self.instruction_count == 0:
representation += f"cycles per instruction: {(self.cycles / self.instruction_count):.2f}\n"
Expand Down
35 changes: 21 additions & 14 deletions architecture_simulator/uarch/riscv/stages.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ def behavior(
write_register = pipeline_register.instruction.get_write_register()

# Data Hazard Detection
flush_signal = None
stall_signal = None
if self.detect_data_hazards:
# Put all the write registers of later stages, that are not done ahead of this stage into a list
write_registers_of_later_stages = [
Expand All @@ -168,10 +168,7 @@ def behavior(
continue
if register_read_addr_1 == register or register_read_addr_2 == register:
assert pipeline_register.address_of_instruction is not None
flush_signal = FlushSignal(
inclusive=True,
address=pipeline_register.address_of_instruction,
)
stall_signal = StallSignal(2)
break

# gets the control unit signals that are generated in the ID stage
Expand All @@ -186,7 +183,7 @@ def behavior(
write_register=write_register,
control_unit_signals=control_unit_signals,
branch_prediction=pipeline_register.branch_prediction,
flush_signal=flush_signal,
stall_signal=stall_signal,
pc_plus_instruction_length=pipeline_register.pc_plus_instruction_length,
address_of_instruction=pipeline_register.address_of_instruction,
)
Expand Down Expand Up @@ -239,21 +236,23 @@ def behavior(
)

# ECALL needs some special behavior (flush and print to output)
flush_signal = None
stall_signal = None
if isinstance(pipeline_register.instruction, ECALL):
# assume that all further stages need to be empty
for other_pr in pipeline_registers[index_of_own_input_register + 1 : -1]:
# assume that all further stages need to be empty, unless this stage is already stalled and the value of the next register is only for display purposes
for other_pr in pipeline_registers[
index_of_own_input_register
+ 1
+ int(pipeline_register.is_of_stalled_value) : -1
]:
if not isinstance(other_pr.instruction, EmptyInstruction):
assert pipeline_register.address_of_instruction is not None
flush_signal = FlushSignal(
True, pipeline_register.address_of_instruction
)
stall_signal = StallSignal(2)
break
if flush_signal is None:
if stall_signal is None:
pipeline_register.instruction.behavior(state)

return ExecutePipelineRegister(
flush_signal=flush_signal,
stall_signal=stall_signal,
instruction=pipeline_register.instruction,
alu_in_1=alu_in_1,
alu_in_2=alu_in_2,
Expand Down Expand Up @@ -571,3 +570,11 @@ class FlushSignal:
inclusive: bool
# address to return to
address: int


@dataclass
class StallSignal:
"""A signal that this stage and all previous stages should be stalled for a duration of cycles"""

# how many cycles to stall
duration: int
3 changes: 0 additions & 3 deletions build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,6 @@ set -e

# build python package
python -m build
# ensure public folder is present and copy python package
mkdir -p webgui/public/
cp dist/*.whl webgui/public/
# ensure custom markers are present in the svg
python build_svg.py
# ensure js packages are available and run npm scripts
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "architecture-simulator"
version = "0.1.0"
version = "1.3.0"
readme = "README.md"
requires-python = ">=3.10"
dependencies = ["pyparsing","fixedint","prompt-toolkit"]
Expand Down
Loading

0 comments on commit d3c028a

Please sign in to comment.