scoreboarding 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- scoreboarding/__init__.py +51 -0
- scoreboarding/cli.py +189 -0
- scoreboarding/engine.py +347 -0
- scoreboarding/model.py +144 -0
- scoreboarding/py.typed +0 -0
- scoreboarding/render.py +83 -0
- scoreboarding-0.1.0.dist-info/METADATA +211 -0
- scoreboarding-0.1.0.dist-info/RECORD +11 -0
- scoreboarding-0.1.0.dist-info/WHEEL +4 -0
- scoreboarding-0.1.0.dist-info/entry_points.txt +2 -0
- scoreboarding-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""scoreboarding -- pure-Python Thornton Scoreboarding (CDC 6600) simulator.
|
|
2
|
+
|
|
3
|
+
Public API::
|
|
4
|
+
|
|
5
|
+
from scoreboarding import FunctionalUnit, Instruction, run, render_trace, Trace
|
|
6
|
+
|
|
7
|
+
Example::
|
|
8
|
+
|
|
9
|
+
from scoreboarding import FunctionalUnit, Instruction, run, render_trace
|
|
10
|
+
|
|
11
|
+
fus = [
|
|
12
|
+
FunctionalUnit(name="Load1", kind="load", latency=2),
|
|
13
|
+
FunctionalUnit(name="Mult1", kind="mult", latency=10),
|
|
14
|
+
FunctionalUnit(name="Add1", kind="add", latency=2),
|
|
15
|
+
FunctionalUnit(name="Div1", kind="div", latency=40),
|
|
16
|
+
]
|
|
17
|
+
program = [
|
|
18
|
+
Instruction(op="LD", dest="F6", src1="R2", src2=""),
|
|
19
|
+
Instruction(op="LD", dest="F2", src1="R3", src2=""),
|
|
20
|
+
Instruction(op="MULT", dest="F0", src1="F2", src2="F4"),
|
|
21
|
+
Instruction(op="SUB", dest="F8", src1="F6", src2="F2"),
|
|
22
|
+
Instruction(op="DIV", dest="F10", src1="F0", src2="F6"),
|
|
23
|
+
Instruction(op="ADD", dest="F6", src1="F8", src2="F2"),
|
|
24
|
+
]
|
|
25
|
+
trace = run(program, functional_units=fus)
|
|
26
|
+
print(render_trace(trace))
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
from scoreboarding.engine import run
|
|
30
|
+
from scoreboarding.model import (
|
|
31
|
+
CycleSnapshot,
|
|
32
|
+
FunctionalUnit,
|
|
33
|
+
FunctionalUnitStatus,
|
|
34
|
+
Instruction,
|
|
35
|
+
InstructionResult,
|
|
36
|
+
InstructionStatus,
|
|
37
|
+
Trace,
|
|
38
|
+
)
|
|
39
|
+
from scoreboarding.render import render_trace
|
|
40
|
+
|
|
41
|
+
__all__ = [
|
|
42
|
+
"CycleSnapshot",
|
|
43
|
+
"FunctionalUnit",
|
|
44
|
+
"FunctionalUnitStatus",
|
|
45
|
+
"Instruction",
|
|
46
|
+
"InstructionResult",
|
|
47
|
+
"InstructionStatus",
|
|
48
|
+
"Trace",
|
|
49
|
+
"render_trace",
|
|
50
|
+
"run",
|
|
51
|
+
]
|
scoreboarding/cli.py
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
"""Command-line interface for the scoreboarding simulator.
|
|
2
|
+
|
|
3
|
+
Program text format
|
|
4
|
+
-------------------
|
|
5
|
+
Lines starting with '#' or blank lines are ignored.
|
|
6
|
+
|
|
7
|
+
Functional unit declarations (must come before instructions)::
|
|
8
|
+
|
|
9
|
+
FU <name> <kind> <latency>
|
|
10
|
+
|
|
11
|
+
Example::
|
|
12
|
+
|
|
13
|
+
FU Load1 load 2
|
|
14
|
+
FU Mult1 mult 10
|
|
15
|
+
FU Add1 add 2
|
|
16
|
+
FU Div1 div 40
|
|
17
|
+
|
|
18
|
+
Instruction lines::
|
|
19
|
+
|
|
20
|
+
<op> <dest>, <src1>, <src2>
|
|
21
|
+
<op> <dest>, <src1> # single-source (loads etc.)
|
|
22
|
+
|
|
23
|
+
Example::
|
|
24
|
+
|
|
25
|
+
LD F6, R2
|
|
26
|
+
MULT F0, F2, F4
|
|
27
|
+
ADD F6, F8, F2
|
|
28
|
+
|
|
29
|
+
Usage::
|
|
30
|
+
|
|
31
|
+
scoreboarding program.txt
|
|
32
|
+
scoreboarding --snapshots program.txt
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
from __future__ import annotations
|
|
36
|
+
|
|
37
|
+
import argparse
|
|
38
|
+
import sys
|
|
39
|
+
|
|
40
|
+
from scoreboarding.engine import run as engine_run
|
|
41
|
+
from scoreboarding.model import FunctionalUnit, Instruction
|
|
42
|
+
from scoreboarding.render import render_trace
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _parse_program(text: str) -> tuple[list[FunctionalUnit], list[Instruction]]:
|
|
46
|
+
"""Parse program text into functional units and instructions.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
text: Full program text with FU declarations followed by instructions.
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
A tuple of (functional_units, instructions).
|
|
53
|
+
|
|
54
|
+
Raises:
|
|
55
|
+
ValueError: If a line cannot be parsed.
|
|
56
|
+
"""
|
|
57
|
+
functional_units: list[FunctionalUnit] = []
|
|
58
|
+
instructions: list[Instruction] = []
|
|
59
|
+
|
|
60
|
+
for raw_line in text.splitlines():
|
|
61
|
+
line = raw_line.strip()
|
|
62
|
+
if not line or line.startswith("#"):
|
|
63
|
+
continue
|
|
64
|
+
|
|
65
|
+
if line.upper().startswith("FU "):
|
|
66
|
+
parts = line.split()
|
|
67
|
+
if len(parts) != 4:
|
|
68
|
+
raise ValueError(
|
|
69
|
+
f"FU declaration must be 'FU <name> <kind> <latency>', got: {line!r}"
|
|
70
|
+
)
|
|
71
|
+
try:
|
|
72
|
+
latency = int(parts[3])
|
|
73
|
+
except ValueError:
|
|
74
|
+
raise ValueError(
|
|
75
|
+
f"FU latency must be an integer, got: {parts[3]!r}"
|
|
76
|
+
) from None
|
|
77
|
+
functional_units.append(
|
|
78
|
+
FunctionalUnit(name=parts[1], kind=parts[2].lower(), latency=latency)
|
|
79
|
+
)
|
|
80
|
+
continue
|
|
81
|
+
|
|
82
|
+
# Instruction line: "OP DEST, SRC1" or "OP DEST, SRC1, SRC2"
|
|
83
|
+
# Split op from the rest.
|
|
84
|
+
space_idx = line.find(" ")
|
|
85
|
+
if space_idx == -1:
|
|
86
|
+
raise ValueError(f"Cannot parse instruction line: {line!r}")
|
|
87
|
+
op = line[:space_idx].strip()
|
|
88
|
+
rest = line[space_idx:].strip()
|
|
89
|
+
|
|
90
|
+
# Split operands by comma.
|
|
91
|
+
operands = [p.strip() for p in rest.split(",")]
|
|
92
|
+
if len(operands) < 2:
|
|
93
|
+
raise ValueError(
|
|
94
|
+
f"Instruction must have dest and at least one source: {line!r}"
|
|
95
|
+
)
|
|
96
|
+
dest = operands[0]
|
|
97
|
+
src1 = operands[1]
|
|
98
|
+
src2 = operands[2] if len(operands) >= 3 else ""
|
|
99
|
+
instructions.append(Instruction(op=op, dest=dest, src1=src1, src2=src2))
|
|
100
|
+
|
|
101
|
+
return functional_units, instructions
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def run(argv: list[str]) -> int:
|
|
105
|
+
"""Entry point for the CLI, accepting an explicit argv list.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
argv: Command-line arguments (excluding the program name).
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
Exit code (0 = success, non-zero = error).
|
|
112
|
+
"""
|
|
113
|
+
parser = argparse.ArgumentParser(
|
|
114
|
+
prog="scoreboarding",
|
|
115
|
+
description=(
|
|
116
|
+
"Cycle-exact Thornton Scoreboarding (CDC 6600) simulator. "
|
|
117
|
+
"Reads a program file and prints the pipeline timing table."
|
|
118
|
+
),
|
|
119
|
+
)
|
|
120
|
+
parser.add_argument(
|
|
121
|
+
"program",
|
|
122
|
+
metavar="FILE",
|
|
123
|
+
help="Path to program text file (or '-' to read from stdin).",
|
|
124
|
+
)
|
|
125
|
+
parser.add_argument(
|
|
126
|
+
"--snapshots",
|
|
127
|
+
action="store_true",
|
|
128
|
+
default=False,
|
|
129
|
+
help="Print per-cycle state snapshots after the timing table.",
|
|
130
|
+
)
|
|
131
|
+
args = parser.parse_args(argv)
|
|
132
|
+
|
|
133
|
+
try:
|
|
134
|
+
if args.program == "-":
|
|
135
|
+
text = sys.stdin.read()
|
|
136
|
+
else:
|
|
137
|
+
with open(args.program) as fh:
|
|
138
|
+
text = fh.read()
|
|
139
|
+
except OSError as exc:
|
|
140
|
+
print(f"scoreboarding: error reading file: {exc}", file=sys.stderr)
|
|
141
|
+
return 1
|
|
142
|
+
|
|
143
|
+
try:
|
|
144
|
+
functional_units, instructions = _parse_program(text)
|
|
145
|
+
except ValueError as exc:
|
|
146
|
+
print(f"scoreboarding: parse error: {exc}", file=sys.stderr)
|
|
147
|
+
return 1
|
|
148
|
+
|
|
149
|
+
if not functional_units:
|
|
150
|
+
print(
|
|
151
|
+
"scoreboarding: no functional units declared"
|
|
152
|
+
" -- add 'FU <name> <kind> <latency>' lines.",
|
|
153
|
+
file=sys.stderr,
|
|
154
|
+
)
|
|
155
|
+
return 1
|
|
156
|
+
|
|
157
|
+
if not instructions:
|
|
158
|
+
print("scoreboarding: no instructions found.", file=sys.stderr)
|
|
159
|
+
return 1
|
|
160
|
+
|
|
161
|
+
try:
|
|
162
|
+
trace = engine_run(
|
|
163
|
+
instructions,
|
|
164
|
+
functional_units=functional_units,
|
|
165
|
+
capture_snapshots=args.snapshots,
|
|
166
|
+
)
|
|
167
|
+
except (ValueError, RuntimeError) as exc:
|
|
168
|
+
print(f"scoreboarding: simulation error: {exc}", file=sys.stderr)
|
|
169
|
+
return 1
|
|
170
|
+
|
|
171
|
+
print(render_trace(trace))
|
|
172
|
+
|
|
173
|
+
if args.snapshots and trace.snapshots:
|
|
174
|
+
print()
|
|
175
|
+
for snap in trace.snapshots:
|
|
176
|
+
print(f"-- Cycle {snap.cycle} --")
|
|
177
|
+
for i, ist in enumerate(snap.instruction_status):
|
|
178
|
+
issue_s = str(ist.issue) if ist.issue is not None else "-"
|
|
179
|
+
ro_s = str(ist.read_operands) if ist.read_operands is not None else "-"
|
|
180
|
+
ec_s = str(ist.execute_complete) if ist.execute_complete is not None else "-"
|
|
181
|
+
wr_s = str(ist.write_result) if ist.write_result is not None else "-"
|
|
182
|
+
print(f" [{i}] Issue={issue_s} RO={ro_s} EC={ec_s} WR={wr_s}")
|
|
183
|
+
|
|
184
|
+
return 0
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def main() -> None:
|
|
188
|
+
"""Entry point installed as the 'scoreboarding' console script."""
|
|
189
|
+
sys.exit(run(sys.argv[1:]))
|
scoreboarding/engine.py
ADDED
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
"""Cycle-exact Scoreboarding simulator engine.
|
|
2
|
+
|
|
3
|
+
Implements Thornton's Scoreboarding algorithm as used in the CDC 6600 (1964),
|
|
4
|
+
with four pipeline stages and three tracking tables.
|
|
5
|
+
|
|
6
|
+
Stage checks (per Thornton):
|
|
7
|
+
|
|
8
|
+
ISSUE
|
|
9
|
+
Preconditions (checked in program order -- no instruction may issue while an
|
|
10
|
+
earlier instruction is stalled):
|
|
11
|
+
1. No structural hazard: the required functional unit is not busy.
|
|
12
|
+
2. No WAW hazard: no currently-active instruction will write the same
|
|
13
|
+
destination register.
|
|
14
|
+
|
|
15
|
+
READ OPERANDS
|
|
16
|
+
An instruction that has been issued waits here until BOTH source operands are
|
|
17
|
+
available. An operand is available when no earlier-issued instruction is still
|
|
18
|
+
going to write it (qj/qk = None AND rj/rk = True). This stage resolves RAW.
|
|
19
|
+
|
|
20
|
+
EXECUTE
|
|
21
|
+
The instruction occupies its functional unit for exactly `latency` cycles
|
|
22
|
+
starting the cycle after operands are read.
|
|
23
|
+
|
|
24
|
+
WRITE RESULT
|
|
25
|
+
Precondition -- no WAR hazard: every earlier-issued instruction that reads
|
|
26
|
+
the destination register as a source must have already completed its READ
|
|
27
|
+
OPERANDS stage. Once this holds, the result is written to the register file,
|
|
28
|
+
RegisterResultStatus is cleared, and the functional unit is freed.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
from __future__ import annotations
|
|
32
|
+
|
|
33
|
+
import copy
|
|
34
|
+
from dataclasses import dataclass, field
|
|
35
|
+
|
|
36
|
+
from scoreboarding.model import (
|
|
37
|
+
CycleSnapshot,
|
|
38
|
+
FunctionalUnit,
|
|
39
|
+
FunctionalUnitStatus,
|
|
40
|
+
Instruction,
|
|
41
|
+
InstructionResult,
|
|
42
|
+
InstructionStatus,
|
|
43
|
+
Trace,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@dataclass
|
|
48
|
+
class _SimInstruction:
|
|
49
|
+
"""Internal per-instruction mutable state."""
|
|
50
|
+
|
|
51
|
+
instruction: Instruction
|
|
52
|
+
program_index: int
|
|
53
|
+
status: InstructionStatus = field(default_factory=InstructionStatus)
|
|
54
|
+
fu_name: str = "" # which FU was assigned at issue
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _op_kind(op: str) -> str:
|
|
58
|
+
"""Map an operation name to its functional-unit kind.
|
|
59
|
+
|
|
60
|
+
Recognises load/store, add/sub, mult/div, and falls back to op.lower().
|
|
61
|
+
"""
|
|
62
|
+
op_upper = op.upper()
|
|
63
|
+
if op_upper in ("LD", "LW", "LOAD", "ST", "SW", "STORE"):
|
|
64
|
+
return "load"
|
|
65
|
+
if op_upper in ("ADD", "ADDD", "ADDI", "ADDF"):
|
|
66
|
+
return "add"
|
|
67
|
+
if op_upper in ("SUB", "SUBD", "SUBI", "SUBF"):
|
|
68
|
+
return "add" # SUB shares the add/subtract unit by convention
|
|
69
|
+
if op_upper in ("MULT", "MUL", "MULTD", "MULF"):
|
|
70
|
+
return "mult"
|
|
71
|
+
if op_upper in ("DIV", "DIVD", "DIVF"):
|
|
72
|
+
return "div"
|
|
73
|
+
return op.lower()
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def run(
|
|
77
|
+
program: list[Instruction],
|
|
78
|
+
*,
|
|
79
|
+
functional_units: list[FunctionalUnit],
|
|
80
|
+
capture_snapshots: bool = False,
|
|
81
|
+
) -> Trace:
|
|
82
|
+
"""Simulate program using Thornton's Scoreboarding algorithm.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
program: Instructions to execute, in program order.
|
|
86
|
+
functional_units: Available functional units.
|
|
87
|
+
capture_snapshots: When True, record a full CycleSnapshot each cycle.
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
A Trace with per-instruction cycle stamps and optional snapshots.
|
|
91
|
+
|
|
92
|
+
Raises:
|
|
93
|
+
ValueError: If an instruction's operation has no matching functional unit.
|
|
94
|
+
"""
|
|
95
|
+
if not program:
|
|
96
|
+
return Trace(results=[], snapshots=[], total_cycles=0)
|
|
97
|
+
|
|
98
|
+
# Validate that every instruction has a matching FU kind.
|
|
99
|
+
fu_by_kind: dict[str, list[FunctionalUnit]] = {}
|
|
100
|
+
for fu in functional_units:
|
|
101
|
+
fu_by_kind.setdefault(fu.kind, []).append(fu)
|
|
102
|
+
|
|
103
|
+
for instr in program:
|
|
104
|
+
kind = _op_kind(instr.op)
|
|
105
|
+
if kind not in fu_by_kind:
|
|
106
|
+
raise ValueError(
|
|
107
|
+
f"No functional unit of kind '{kind}' for operation '{instr.op}'"
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
# Build per-FU status table.
|
|
111
|
+
fu_status: dict[str, FunctionalUnitStatus] = {
|
|
112
|
+
fu.name: FunctionalUnitStatus() for fu in functional_units
|
|
113
|
+
}
|
|
114
|
+
fu_latency: dict[str, int] = {fu.name: fu.latency for fu in functional_units}
|
|
115
|
+
|
|
116
|
+
# Register-result-status table: maps register name -> FU name that will write it.
|
|
117
|
+
# None means no pending write (register holds its committed value).
|
|
118
|
+
register_result: dict[str, str | None] = {}
|
|
119
|
+
|
|
120
|
+
# Internal instruction list.
|
|
121
|
+
sim_instrs: list[_SimInstruction] = [
|
|
122
|
+
_SimInstruction(instruction=instr, program_index=i)
|
|
123
|
+
for i, instr in enumerate(program)
|
|
124
|
+
]
|
|
125
|
+
|
|
126
|
+
# Index of the next instruction to issue (in-order issue pointer).
|
|
127
|
+
next_to_issue: int = 0
|
|
128
|
+
snapshots: list[CycleSnapshot] = []
|
|
129
|
+
cycle = 0
|
|
130
|
+
|
|
131
|
+
# Run until every instruction has a WriteResult.
|
|
132
|
+
while not _all_done(sim_instrs):
|
|
133
|
+
cycle += 1
|
|
134
|
+
|
|
135
|
+
# Determine which instructions attempt write-result, read-operands,
|
|
136
|
+
# and execute this cycle. ISSUE is handled last because it depends on
|
|
137
|
+
# the current-cycle FU state after potential WriteResults free units.
|
|
138
|
+
|
|
139
|
+
# --- WRITE RESULT ---
|
|
140
|
+
# For each issued instruction that has completed execution and not yet
|
|
141
|
+
# written its result, check the WAR condition.
|
|
142
|
+
for si in sim_instrs:
|
|
143
|
+
if not _can_attempt_write(si):
|
|
144
|
+
continue
|
|
145
|
+
dest = si.instruction.dest
|
|
146
|
+
# WAR check: every earlier-issued instruction that lists `dest` as a
|
|
147
|
+
# source (fj or fk) must have already read its operands (rj/rk = False
|
|
148
|
+
# means it already consumed that operand; the flag is True only while
|
|
149
|
+
# waiting to read).
|
|
150
|
+
war_blocked = False
|
|
151
|
+
for other in sim_instrs:
|
|
152
|
+
if other is si:
|
|
153
|
+
continue
|
|
154
|
+
if other.status.issue is None:
|
|
155
|
+
continue
|
|
156
|
+
if other.status.issue >= (si.status.issue or 0):
|
|
157
|
+
# Only earlier-issued instructions can cause WAR.
|
|
158
|
+
continue
|
|
159
|
+
if other.status.read_operands is not None:
|
|
160
|
+
# Already read operands; no WAR from this instruction.
|
|
161
|
+
continue
|
|
162
|
+
fu_other = fu_status.get(other.fu_name)
|
|
163
|
+
if fu_other is None:
|
|
164
|
+
continue
|
|
165
|
+
# Check if the other instruction is waiting to read dest.
|
|
166
|
+
if fu_other.fj == dest and fu_other.rj:
|
|
167
|
+
war_blocked = True
|
|
168
|
+
break
|
|
169
|
+
if fu_other.fk == dest and fu_other.rk:
|
|
170
|
+
war_blocked = True
|
|
171
|
+
break
|
|
172
|
+
|
|
173
|
+
if war_blocked:
|
|
174
|
+
continue
|
|
175
|
+
|
|
176
|
+
# Write result.
|
|
177
|
+
si.status.write_result = cycle
|
|
178
|
+
|
|
179
|
+
# Clear register-result-status if this FU is still the producer.
|
|
180
|
+
if register_result.get(dest) == si.fu_name:
|
|
181
|
+
register_result[dest] = None
|
|
182
|
+
|
|
183
|
+
# Update Qj/Qk of any waiting instruction that depended on this FU.
|
|
184
|
+
for other in sim_instrs:
|
|
185
|
+
if other.status.issue is None or other.status.read_operands is not None:
|
|
186
|
+
continue
|
|
187
|
+
fu_other = fu_status.get(other.fu_name)
|
|
188
|
+
if fu_other is None:
|
|
189
|
+
continue
|
|
190
|
+
if fu_other.qj == si.fu_name:
|
|
191
|
+
fu_other.qj = None
|
|
192
|
+
fu_other.rj = True
|
|
193
|
+
if fu_other.qk == si.fu_name:
|
|
194
|
+
fu_other.qk = None
|
|
195
|
+
fu_other.rk = True
|
|
196
|
+
|
|
197
|
+
# Free the functional unit.
|
|
198
|
+
fu_status[si.fu_name].reset()
|
|
199
|
+
|
|
200
|
+
# --- READ OPERANDS ---
|
|
201
|
+
# An instruction reads operands when both sources are ready.
|
|
202
|
+
for si in sim_instrs:
|
|
203
|
+
if si.status.issue is None or si.status.read_operands is not None:
|
|
204
|
+
continue
|
|
205
|
+
fu_st = fu_status[si.fu_name]
|
|
206
|
+
# Both operands available when qj=None,rj=True AND qk=None,rk=True.
|
|
207
|
+
# For single-source instructions src2="" has rk=True and qk=None by
|
|
208
|
+
# construction.
|
|
209
|
+
if fu_st.qj is None and fu_st.rj and fu_st.qk is None and fu_st.rk:
|
|
210
|
+
si.status.read_operands = cycle
|
|
211
|
+
# Mark operands as consumed (rj=rk=False) so WAR tracking works.
|
|
212
|
+
fu_st.rj = False
|
|
213
|
+
fu_st.rk = False
|
|
214
|
+
|
|
215
|
+
# --- EXECUTE COMPLETE ---
|
|
216
|
+
# Mark execute_complete when the latency has elapsed since execute_start.
|
|
217
|
+
for si in sim_instrs:
|
|
218
|
+
if si.status.read_operands is None or si.status.execute_complete is not None:
|
|
219
|
+
continue
|
|
220
|
+
fu_st = fu_status[si.fu_name]
|
|
221
|
+
# Start executing the cycle after read-operands.
|
|
222
|
+
if fu_st.execute_start is None and si.status.read_operands < cycle:
|
|
223
|
+
fu_st.execute_start = si.status.read_operands + 1
|
|
224
|
+
if fu_st.execute_start is not None:
|
|
225
|
+
elapsed = cycle - fu_st.execute_start + 1
|
|
226
|
+
if elapsed >= fu_latency[si.fu_name]:
|
|
227
|
+
si.status.execute_complete = cycle
|
|
228
|
+
|
|
229
|
+
# --- ISSUE ---
|
|
230
|
+
# In-order: attempt to issue the next unissued instruction.
|
|
231
|
+
if next_to_issue < len(sim_instrs):
|
|
232
|
+
si = sim_instrs[next_to_issue]
|
|
233
|
+
instr = si.instruction
|
|
234
|
+
kind = _op_kind(instr.op)
|
|
235
|
+
|
|
236
|
+
# Find a free FU of the right kind.
|
|
237
|
+
candidate_fu: str | None = None
|
|
238
|
+
for fu in functional_units:
|
|
239
|
+
if fu.kind == kind and not fu_status[fu.name].busy:
|
|
240
|
+
candidate_fu = fu.name
|
|
241
|
+
break
|
|
242
|
+
|
|
243
|
+
if candidate_fu is not None:
|
|
244
|
+
# Check WAW: no active instruction may write the same destination.
|
|
245
|
+
waw_blocked = False
|
|
246
|
+
for other in sim_instrs:
|
|
247
|
+
if other is si:
|
|
248
|
+
continue
|
|
249
|
+
if (
|
|
250
|
+
other.status.issue is not None
|
|
251
|
+
and other.status.write_result is None
|
|
252
|
+
and other.instruction.dest == instr.dest
|
|
253
|
+
):
|
|
254
|
+
waw_blocked = True
|
|
255
|
+
break
|
|
256
|
+
|
|
257
|
+
if not waw_blocked:
|
|
258
|
+
# Issue.
|
|
259
|
+
si.status.issue = cycle
|
|
260
|
+
si.fu_name = candidate_fu
|
|
261
|
+
next_to_issue += 1
|
|
262
|
+
|
|
263
|
+
fu_st = fu_status[candidate_fu]
|
|
264
|
+
fu_st.busy = True
|
|
265
|
+
fu_st.op = instr.op
|
|
266
|
+
fu_st.fi = instr.dest
|
|
267
|
+
|
|
268
|
+
# Source 1 (fj).
|
|
269
|
+
fu_st.fj = instr.src1
|
|
270
|
+
producing_fj = register_result.get(instr.src1)
|
|
271
|
+
if producing_fj is not None:
|
|
272
|
+
fu_st.qj = producing_fj
|
|
273
|
+
fu_st.rj = False
|
|
274
|
+
else:
|
|
275
|
+
fu_st.qj = None
|
|
276
|
+
fu_st.rj = True
|
|
277
|
+
|
|
278
|
+
# Source 2 (fk).
|
|
279
|
+
if instr.src2:
|
|
280
|
+
fu_st.fk = instr.src2
|
|
281
|
+
producing_fk = register_result.get(instr.src2)
|
|
282
|
+
if producing_fk is not None:
|
|
283
|
+
fu_st.qk = producing_fk
|
|
284
|
+
fu_st.rk = False
|
|
285
|
+
else:
|
|
286
|
+
fu_st.qk = None
|
|
287
|
+
fu_st.rk = True
|
|
288
|
+
else:
|
|
289
|
+
# No second source (e.g. single-operand load).
|
|
290
|
+
fu_st.fk = ""
|
|
291
|
+
fu_st.qk = None
|
|
292
|
+
fu_st.rk = True
|
|
293
|
+
|
|
294
|
+
# Mark this FU as the future producer of dest.
|
|
295
|
+
register_result[instr.dest] = candidate_fu
|
|
296
|
+
|
|
297
|
+
# --- CAPTURE SNAPSHOT ---
|
|
298
|
+
if capture_snapshots:
|
|
299
|
+
snapshots.append(
|
|
300
|
+
CycleSnapshot(
|
|
301
|
+
cycle=cycle,
|
|
302
|
+
instruction_status=[copy.deepcopy(s.status) for s in sim_instrs],
|
|
303
|
+
fu_status=copy.deepcopy(fu_status),
|
|
304
|
+
register_result=dict(register_result),
|
|
305
|
+
)
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
# Safety valve: cap at a generous but finite cycle count.
|
|
309
|
+
max_cycles = 10 + sum(fu_latency[fu.name] for fu in functional_units) * len(
|
|
310
|
+
sim_instrs
|
|
311
|
+
)
|
|
312
|
+
if cycle > max_cycles:
|
|
313
|
+
raise RuntimeError(
|
|
314
|
+
f"Simulation did not terminate within {max_cycles} cycles -- "
|
|
315
|
+
"possible deadlock in the program or functional unit configuration."
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
total = max(
|
|
319
|
+
(s.status.write_result for s in sim_instrs if s.status.write_result is not None),
|
|
320
|
+
default=0,
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
results = [
|
|
324
|
+
InstructionResult(
|
|
325
|
+
instruction=si.instruction,
|
|
326
|
+
issue=si.status.issue or 0,
|
|
327
|
+
read_operands=si.status.read_operands or 0,
|
|
328
|
+
execute_complete=si.status.execute_complete or 0,
|
|
329
|
+
write_result=si.status.write_result or 0,
|
|
330
|
+
)
|
|
331
|
+
for si in sim_instrs
|
|
332
|
+
]
|
|
333
|
+
|
|
334
|
+
return Trace(results=results, snapshots=snapshots, total_cycles=total)
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
def _all_done(sim_instrs: list[_SimInstruction]) -> bool:
|
|
338
|
+
"""Return True when every instruction has completed WriteResult."""
|
|
339
|
+
return all(si.status.write_result is not None for si in sim_instrs)
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def _can_attempt_write(si: _SimInstruction) -> bool:
|
|
343
|
+
"""Return True when si is eligible to attempt WriteResult this cycle."""
|
|
344
|
+
return (
|
|
345
|
+
si.status.execute_complete is not None
|
|
346
|
+
and si.status.write_result is None
|
|
347
|
+
)
|
scoreboarding/model.py
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"""Data model for the Scoreboarding simulator.
|
|
2
|
+
|
|
3
|
+
Three tables track simulation state:
|
|
4
|
+
- InstructionStatus: per-instruction cycle stamps for each stage.
|
|
5
|
+
- FunctionalUnitStatus: per-FU state (busy, operands, ready flags, etc.).
|
|
6
|
+
- RegisterResultStatus: which FU will produce each register's next value.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from dataclasses import dataclass, field
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class FunctionalUnit:
|
|
16
|
+
"""Declares one physical functional unit available to the processor.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
name: Unique identifier, e.g. "Add1", "Mult1", "Load1".
|
|
20
|
+
kind: Operation class this unit handles, e.g. "add", "mult", "load", "div".
|
|
21
|
+
latency: Number of cycles the execute stage occupies (>= 1).
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
name: str
|
|
25
|
+
kind: str
|
|
26
|
+
latency: int
|
|
27
|
+
|
|
28
|
+
def __post_init__(self) -> None:
|
|
29
|
+
if self.latency < 1:
|
|
30
|
+
raise ValueError(
|
|
31
|
+
f"FunctionalUnit '{self.name}': latency must be >= 1, got {self.latency}"
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass
|
|
36
|
+
class Instruction:
|
|
37
|
+
"""One instruction in the program.
|
|
38
|
+
|
|
39
|
+
For single-source instructions (e.g. loads), pass the base register as
|
|
40
|
+
src1 and an empty string "" as src2.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
op: Operation name, e.g. "LD", "MULT", "ADD", "SUB", "DIV".
|
|
44
|
+
dest: Destination register, e.g. "F6".
|
|
45
|
+
src1: First source register (or base register for loads).
|
|
46
|
+
src2: Second source register; pass "" if not applicable.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
op: str
|
|
50
|
+
dest: str
|
|
51
|
+
src1: str
|
|
52
|
+
src2: str
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@dataclass
|
|
56
|
+
class InstructionStatus:
|
|
57
|
+
"""Cycle stamps for one instruction's four pipeline stages.
|
|
58
|
+
|
|
59
|
+
A value of None means the stage has not yet occurred.
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
issue: int | None = None
|
|
63
|
+
read_operands: int | None = None
|
|
64
|
+
execute_complete: int | None = None
|
|
65
|
+
write_result: int | None = None
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@dataclass
|
|
69
|
+
class FunctionalUnitStatus:
|
|
70
|
+
"""Runtime state of one functional unit (the FU status table).
|
|
71
|
+
|
|
72
|
+
Fields mirror those in Thornton's original scoreboard description:
|
|
73
|
+
- busy: whether the unit is currently occupied.
|
|
74
|
+
- op: operation being performed.
|
|
75
|
+
- fi: destination register name.
|
|
76
|
+
- fj, fk: source register names.
|
|
77
|
+
- qj, qk: names of FUs that will produce fj/fk (None = already available).
|
|
78
|
+
- rj, rk: True when fj/fk are ready to read (operand available and not yet consumed).
|
|
79
|
+
- execute_start: cycle when execution started (None until started).
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
busy: bool = False
|
|
83
|
+
op: str = ""
|
|
84
|
+
fi: str = ""
|
|
85
|
+
fj: str = ""
|
|
86
|
+
fk: str = ""
|
|
87
|
+
qj: str | None = None
|
|
88
|
+
qk: str | None = None
|
|
89
|
+
rj: bool = False
|
|
90
|
+
rk: bool = False
|
|
91
|
+
execute_start: int | None = None
|
|
92
|
+
|
|
93
|
+
def reset(self) -> None:
|
|
94
|
+
"""Return this FU to the idle state."""
|
|
95
|
+
self.busy = False
|
|
96
|
+
self.op = ""
|
|
97
|
+
self.fi = ""
|
|
98
|
+
self.fj = ""
|
|
99
|
+
self.fk = ""
|
|
100
|
+
self.qj = None
|
|
101
|
+
self.qk = None
|
|
102
|
+
self.rj = False
|
|
103
|
+
self.rk = False
|
|
104
|
+
self.execute_start = None
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
@dataclass
|
|
108
|
+
class CycleSnapshot:
|
|
109
|
+
"""Full simulator state at the end of one cycle.
|
|
110
|
+
|
|
111
|
+
Useful for step-by-step educational replay.
|
|
112
|
+
"""
|
|
113
|
+
|
|
114
|
+
cycle: int
|
|
115
|
+
instruction_status: list[InstructionStatus]
|
|
116
|
+
fu_status: dict[str, FunctionalUnitStatus]
|
|
117
|
+
register_result: dict[str, str | None]
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
@dataclass
|
|
121
|
+
class InstructionResult:
|
|
122
|
+
"""Final cycle stamps for one instruction after simulation completes."""
|
|
123
|
+
|
|
124
|
+
instruction: Instruction
|
|
125
|
+
issue: int
|
|
126
|
+
read_operands: int
|
|
127
|
+
execute_complete: int
|
|
128
|
+
write_result: int
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
@dataclass
|
|
132
|
+
class Trace:
|
|
133
|
+
"""Full simulation output.
|
|
134
|
+
|
|
135
|
+
Attributes:
|
|
136
|
+
results: Per-instruction cycle stamps in program order.
|
|
137
|
+
snapshots: Optional per-cycle state snapshots (populated when
|
|
138
|
+
capture_snapshots=True in run()).
|
|
139
|
+
total_cycles: Cycle number of the final WriteResult.
|
|
140
|
+
"""
|
|
141
|
+
|
|
142
|
+
results: list[InstructionResult]
|
|
143
|
+
snapshots: list[CycleSnapshot] = field(default_factory=list)
|
|
144
|
+
total_cycles: int = 0
|
scoreboarding/py.typed
ADDED
|
File without changes
|
scoreboarding/render.py
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""Render a Trace as a human-readable timing table."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from scoreboarding.model import InstructionResult, Trace
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _result_to_row(r: InstructionResult) -> tuple[str, str, str, str, str]:
|
|
9
|
+
"""Convert one InstructionResult to a tuple of string columns."""
|
|
10
|
+
src2_part = f", {r.instruction.src2}" if r.instruction.src2 else ""
|
|
11
|
+
instr_str = f"{r.instruction.op} {r.instruction.dest}, {r.instruction.src1}{src2_part}"
|
|
12
|
+
return (
|
|
13
|
+
instr_str,
|
|
14
|
+
str(r.issue),
|
|
15
|
+
str(r.read_operands),
|
|
16
|
+
str(r.execute_complete),
|
|
17
|
+
str(r.write_result),
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def render_trace(trace: Trace) -> str:
|
|
22
|
+
"""Return a formatted timing table for the given Trace.
|
|
23
|
+
|
|
24
|
+
Each row shows one instruction and the four pipeline-stage cycle numbers:
|
|
25
|
+
Issue, ReadOperands, ExecuteComplete, WriteResult.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
trace: The simulation output from run().
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
A multi-line string suitable for printing to a terminal.
|
|
32
|
+
"""
|
|
33
|
+
if not trace.results:
|
|
34
|
+
return "(empty program)"
|
|
35
|
+
|
|
36
|
+
header_instr = "Instruction"
|
|
37
|
+
header_issue = "Issue"
|
|
38
|
+
header_ro = "ReadOps"
|
|
39
|
+
header_ec = "ExecComp"
|
|
40
|
+
header_wr = "WriteResult"
|
|
41
|
+
|
|
42
|
+
rows = [_result_to_row(r) for r in trace.results]
|
|
43
|
+
|
|
44
|
+
col0_w = max(len(header_instr), *(len(row[0]) for row in rows))
|
|
45
|
+
col1_w = max(len(header_issue), *(len(row[1]) for row in rows))
|
|
46
|
+
col2_w = max(len(header_ro), *(len(row[2]) for row in rows))
|
|
47
|
+
col3_w = max(len(header_ec), *(len(row[3]) for row in rows))
|
|
48
|
+
col4_w = max(len(header_wr), *(len(row[4]) for row in rows))
|
|
49
|
+
|
|
50
|
+
sep = (
|
|
51
|
+
"+"
|
|
52
|
+
+ "-" * (col0_w + 2)
|
|
53
|
+
+ "+"
|
|
54
|
+
+ "-" * (col1_w + 2)
|
|
55
|
+
+ "+"
|
|
56
|
+
+ "-" * (col2_w + 2)
|
|
57
|
+
+ "+"
|
|
58
|
+
+ "-" * (col3_w + 2)
|
|
59
|
+
+ "+"
|
|
60
|
+
+ "-" * (col4_w + 2)
|
|
61
|
+
+ "+"
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
def fmt_row(c0: str, c1: str, c2: str, c3: str, c4: str) -> str:
|
|
65
|
+
return (
|
|
66
|
+
f"| {c0:<{col0_w}} "
|
|
67
|
+
f"| {c1:>{col1_w}} "
|
|
68
|
+
f"| {c2:>{col2_w}} "
|
|
69
|
+
f"| {c3:>{col3_w}} "
|
|
70
|
+
f"| {c4:>{col4_w}} |"
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
lines = [
|
|
74
|
+
sep,
|
|
75
|
+
fmt_row(header_instr, header_issue, header_ro, header_ec, header_wr),
|
|
76
|
+
sep,
|
|
77
|
+
]
|
|
78
|
+
for row in rows:
|
|
79
|
+
lines.append(fmt_row(row[0], row[1], row[2], row[3], row[4]))
|
|
80
|
+
lines.append(sep)
|
|
81
|
+
lines.append(f"Total cycles: {trace.total_cycles}")
|
|
82
|
+
|
|
83
|
+
return "\n".join(lines)
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: scoreboarding
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Pure-Python cycle-exact simulator of Thornton's Scoreboarding (CDC 6600) in-order dynamic scheduling, with structural, WAR, and WAW hazard detection and per-instruction traces for computer architecture education.
|
|
5
|
+
Project-URL: Homepage, https://github.com/amaar-mc/scoreboarding
|
|
6
|
+
Project-URL: Repository, https://github.com/amaar-mc/scoreboarding
|
|
7
|
+
Project-URL: Issues, https://github.com/amaar-mc/scoreboarding/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/amaar-mc/scoreboarding/blob/main/CHANGELOG.md
|
|
9
|
+
Author: Amaar Chughtai
|
|
10
|
+
License: MIT License
|
|
11
|
+
|
|
12
|
+
Copyright (c) 2026 Amaar Chughtai
|
|
13
|
+
|
|
14
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
15
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
16
|
+
in the Software without restriction, including without limitation the rights
|
|
17
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
18
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
19
|
+
furnished to do so, subject to the following conditions:
|
|
20
|
+
|
|
21
|
+
The above copyright notice and this permission notice shall be included in all
|
|
22
|
+
copies or substantial portions of the Software.
|
|
23
|
+
|
|
24
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
25
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
26
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
27
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
28
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
29
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
30
|
+
SOFTWARE.
|
|
31
|
+
License-File: LICENSE
|
|
32
|
+
Keywords: cdc-6600,computer-architecture,cpu-simulator,education,hazards,in-order,instruction-scheduling,pipeline,scoreboarding,simulation
|
|
33
|
+
Classifier: Development Status :: 3 - Alpha
|
|
34
|
+
Classifier: Intended Audience :: Education
|
|
35
|
+
Classifier: Intended Audience :: Science/Research
|
|
36
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
37
|
+
Classifier: Programming Language :: Python :: 3
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
39
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
40
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
41
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
42
|
+
Classifier: Topic :: Education
|
|
43
|
+
Classifier: Topic :: Scientific/Engineering
|
|
44
|
+
Classifier: Typing :: Typed
|
|
45
|
+
Requires-Python: >=3.10
|
|
46
|
+
Provides-Extra: dev
|
|
47
|
+
Requires-Dist: hatchling>=1.25; extra == 'dev'
|
|
48
|
+
Requires-Dist: mypy>=1.11; extra == 'dev'
|
|
49
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
50
|
+
Requires-Dist: ruff>=0.6; extra == 'dev'
|
|
51
|
+
Description-Content-Type: text/markdown
|
|
52
|
+
|
|
53
|
+
# scoreboarding
|
|
54
|
+
|
|
55
|
+
<p align="center">
|
|
56
|
+
<img src="assets/logo.png" alt="scoreboarding logo" width="160">
|
|
57
|
+
</p>
|
|
58
|
+
|
|
59
|
+
Pure-Python cycle-exact simulator of Thornton's **Scoreboarding** algorithm,
|
|
60
|
+
as implemented in the CDC 6600 (1964). Designed for computer architecture
|
|
61
|
+
education: readable source, zero runtime dependencies, and per-instruction
|
|
62
|
+
cycle-number traces.
|
|
63
|
+
|
|
64
|
+
> PyPI publish pending (package-upload quota). `pip install scoreboarding`
|
|
65
|
+
> will work once the first release is live. Install from source in the meantime.
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## What is Scoreboarding?
|
|
70
|
+
|
|
71
|
+
Scoreboarding is an **in-order issue, out-of-order execution** dynamic
|
|
72
|
+
scheduling technique. The processor issues instructions one at a time
|
|
73
|
+
(program order), but lets them read operands and execute independently once
|
|
74
|
+
their hazards clear. A central scoreboard -- three tables -- tracks every
|
|
75
|
+
in-flight instruction and enforces the classic CDC 6600 hazard rules without
|
|
76
|
+
any register renaming.
|
|
77
|
+
|
|
78
|
+
### The Four Stages
|
|
79
|
+
|
|
80
|
+
| Stage | What happens | Hazard checked |
|
|
81
|
+
|---|---|---|
|
|
82
|
+
| **Issue** | Assign instruction to a free FU | Structural (FU busy) + WAW (another active insn writes same dest) |
|
|
83
|
+
| **Read Operands** | Read both source registers | RAW (stall until producing FU has written result) |
|
|
84
|
+
| **Execute** | Occupy the FU for its full latency | -- |
|
|
85
|
+
| **Write Result** | Commit result to register file, free FU | WAR (stall until every earlier reader has read its operand) |
|
|
86
|
+
|
|
87
|
+
### Three Tracking Tables
|
|
88
|
+
|
|
89
|
+
1. **Instruction Status** -- per-instruction cycle stamps (Issue / ReadOperands / ExecuteComplete / WriteResult).
|
|
90
|
+
2. **Functional Unit Status** -- per-FU: busy flag, op, destination (Fi), sources (Fj, Fk), producing FUs (Qj, Qk), ready flags (Rj, Rk).
|
|
91
|
+
3. **Register Result Status** -- which FU will next write each register (None once written).
|
|
92
|
+
|
|
93
|
+
### How it differs from Tomasulo
|
|
94
|
+
|
|
95
|
+
| | Scoreboarding | Tomasulo |
|
|
96
|
+
|---|---|---|
|
|
97
|
+
| Issue order | In order | In order |
|
|
98
|
+
| Execution order | Out of order | Out of order |
|
|
99
|
+
| WAW handling | Stall Issue | Register renaming (RS tags) |
|
|
100
|
+
| WAR handling | Stall Write Result | Eliminated by renaming |
|
|
101
|
+
| RAW handling | Stall Read Operands | Stall in RS until CDB broadcast |
|
|
102
|
+
| Register renaming | No | Yes (via reservation stations) |
|
|
103
|
+
| Broadcast mechanism | Central scoreboard | Common Data Bus |
|
|
104
|
+
|
|
105
|
+
See the sibling package [tomasulo](https://github.com/amaar-mc/tomasulo) for the
|
|
106
|
+
Tomasulo out-of-order scheduler with register renaming.
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## Install
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
# From source (until PyPI release):
|
|
114
|
+
git clone https://github.com/amaar-mc/scoreboarding
|
|
115
|
+
cd scoreboarding
|
|
116
|
+
uv pip install -e ".[dev]"
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## Usage
|
|
122
|
+
|
|
123
|
+
### Python API
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
from scoreboarding import FunctionalUnit, Instruction, run, render_trace
|
|
127
|
+
|
|
128
|
+
fus = [
|
|
129
|
+
FunctionalUnit(name="Load1", kind="load", latency=2),
|
|
130
|
+
FunctionalUnit(name="Mult1", kind="mult", latency=10),
|
|
131
|
+
FunctionalUnit(name="Add1", kind="add", latency=2),
|
|
132
|
+
FunctionalUnit(name="Div1", kind="div", latency=40),
|
|
133
|
+
]
|
|
134
|
+
|
|
135
|
+
program = [
|
|
136
|
+
Instruction(op="LD", dest="F6", src1="R2", src2=""),
|
|
137
|
+
Instruction(op="LD", dest="F2", src1="R3", src2=""),
|
|
138
|
+
Instruction(op="MULT", dest="F0", src1="F2", src2="F4"),
|
|
139
|
+
Instruction(op="SUB", dest="F8", src1="F6", src2="F2"),
|
|
140
|
+
Instruction(op="DIV", dest="F10", src1="F0", src2="F6"),
|
|
141
|
+
Instruction(op="ADD", dest="F6", src1="F8", src2="F2"),
|
|
142
|
+
]
|
|
143
|
+
|
|
144
|
+
trace = run(program, functional_units=fus)
|
|
145
|
+
print(render_trace(trace))
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Example timing table
|
|
149
|
+
|
|
150
|
+
```
|
|
151
|
+
+---------------------+-------+---------+----------+-------------+
|
|
152
|
+
| Instruction | Issue | ReadOps | ExecComp | WriteResult |
|
|
153
|
+
+---------------------+-------+---------+----------+-------------+
|
|
154
|
+
| LD F6, R2 | 1 | 1 | 2 | 3 |
|
|
155
|
+
| LD F2, R3 | 3 | 3 | 4 | 5 |
|
|
156
|
+
| MULT F0, F2, F4 | 4 | 5 | 14 | 15 |
|
|
157
|
+
| SUB F8, F6, F2 | 4 | 5 | 6 | 7 |
|
|
158
|
+
| DIV F10, F0, F6 | 5 | 15 | 54 | 55 |
|
|
159
|
+
| ADD F6, F8, F2 | 8 | 8 | 9 | 16 |
|
|
160
|
+
+---------------------+-------+---------+----------+-------------+
|
|
161
|
+
Total cycles: 16
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### CLI
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
# Use the bundled example:
|
|
168
|
+
scoreboarding examples/classic.txt
|
|
169
|
+
|
|
170
|
+
# Enable per-cycle snapshots:
|
|
171
|
+
scoreboarding --snapshots examples/classic.txt
|
|
172
|
+
|
|
173
|
+
# Read from stdin:
|
|
174
|
+
cat examples/classic.txt | scoreboarding -
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Program file format:
|
|
178
|
+
|
|
179
|
+
```
|
|
180
|
+
# Comments start with #
|
|
181
|
+
FU Load1 load 2
|
|
182
|
+
FU Mult1 mult 10
|
|
183
|
+
FU Add1 add 2
|
|
184
|
+
FU Div1 div 40
|
|
185
|
+
|
|
186
|
+
LD F6, R2
|
|
187
|
+
LD F2, R3
|
|
188
|
+
MULT F0, F2, F4
|
|
189
|
+
SUB F8, F6, F2
|
|
190
|
+
DIV F10, F0, F6
|
|
191
|
+
ADD F6, F8, F2
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
## Development
|
|
197
|
+
|
|
198
|
+
```bash
|
|
199
|
+
uv run pytest -q
|
|
200
|
+
uv run ruff check .
|
|
201
|
+
uv run mypy src
|
|
202
|
+
uv build
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
CI runs on Python 3.10, 3.11, 3.12, 3.13 via GitHub Actions.
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
## License
|
|
210
|
+
|
|
211
|
+
MIT -- see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
scoreboarding/__init__.py,sha256=NNn0ApX5cldroPqduB-hI0KJ1Q6FdLBBZiRme5-VgLM,1494
|
|
2
|
+
scoreboarding/cli.py,sha256=6PBtYrI3nqM4onLDPjsZT4baiMcv60oFE95aTfQvR_E,5639
|
|
3
|
+
scoreboarding/engine.py,sha256=Nppk6ekyVsipvS-MsAOb-YhZdmBoYqinafvZsMn8tfE,13109
|
|
4
|
+
scoreboarding/model.py,sha256=SONQ41KH-UhxGxQaS7Fl0Tbz1BRqaDBPbESx27EsKg4,3911
|
|
5
|
+
scoreboarding/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
scoreboarding/render.py,sha256=rjmKVIwga2frNdGKQNCfBg7kSaoy6-5BXw3D4HdY4kM,2417
|
|
7
|
+
scoreboarding-0.1.0.dist-info/METADATA,sha256=NczuAVXyJlefKrnuj4qeshswfY0d-f74wu7SGixeKvA,7554
|
|
8
|
+
scoreboarding-0.1.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
9
|
+
scoreboarding-0.1.0.dist-info/entry_points.txt,sha256=3i_fhrWHz9Z5flBtTx3mCHXFdYAByv09KM1jp9vCapg,57
|
|
10
|
+
scoreboarding-0.1.0.dist-info/licenses/LICENSE,sha256=NORTVJb4x206RXQkpZt_vpj8I7aZL6Vn26BvTOq6J40,1071
|
|
11
|
+
scoreboarding-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Amaar Chughtai
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|