tomasulo 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.
- tomasulo/__init__.py +24 -0
- tomasulo/cli.py +210 -0
- tomasulo/model.py +118 -0
- tomasulo/py.typed +0 -0
- tomasulo/simulator.py +311 -0
- tomasulo/trace.py +61 -0
- tomasulo-0.1.0.dist-info/METADATA +179 -0
- tomasulo-0.1.0.dist-info/RECORD +11 -0
- tomasulo-0.1.0.dist-info/WHEEL +4 -0
- tomasulo-0.1.0.dist-info/entry_points.txt +2 -0
- tomasulo-0.1.0.dist-info/licenses/LICENSE +21 -0
tomasulo/__init__.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Pure-Python simulator of Tomasulo's out-of-order instruction scheduling algorithm."""
|
|
2
|
+
|
|
3
|
+
from .model import (
|
|
4
|
+
CycleSnapshot,
|
|
5
|
+
Instruction,
|
|
6
|
+
InstructionResult,
|
|
7
|
+
RegisterStatus,
|
|
8
|
+
StationState,
|
|
9
|
+
Trace,
|
|
10
|
+
)
|
|
11
|
+
from .simulator import TomasuloSim
|
|
12
|
+
from .trace import render_trace
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"CycleSnapshot",
|
|
16
|
+
"Instruction",
|
|
17
|
+
"InstructionResult",
|
|
18
|
+
"RegisterStatus",
|
|
19
|
+
"StationState",
|
|
20
|
+
"TomasuloSim",
|
|
21
|
+
"Trace",
|
|
22
|
+
"render_trace",
|
|
23
|
+
]
|
|
24
|
+
__version__ = "0.1.0"
|
tomasulo/cli.py
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
"""Command-line interface for the Tomasulo simulator.
|
|
2
|
+
|
|
3
|
+
Input format (plain text, one instruction per line):
|
|
4
|
+
OP DEST SRC1 SRC2
|
|
5
|
+
|
|
6
|
+
Lines starting with '#' are comments. Blank lines are ignored.
|
|
7
|
+
|
|
8
|
+
Example:
|
|
9
|
+
LOAD F6 R2 R0
|
|
10
|
+
LOAD F2 R3 R0
|
|
11
|
+
MUL F0 F2 F4
|
|
12
|
+
SUB F8 F6 F2
|
|
13
|
+
DIV F10 F0 F6
|
|
14
|
+
ADD F6 F8 F2
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import sys
|
|
20
|
+
|
|
21
|
+
from . import __version__
|
|
22
|
+
from .model import Instruction
|
|
23
|
+
from .simulator import TomasuloSim
|
|
24
|
+
from .trace import render_trace
|
|
25
|
+
|
|
26
|
+
_DEFAULT_STATIONS: dict[str, int] = {"add": 3, "mult": 2, "load": 3}
|
|
27
|
+
_DEFAULT_LATENCIES: dict[str, int] = {
|
|
28
|
+
"ADD": 2,
|
|
29
|
+
"SUB": 2,
|
|
30
|
+
"MUL": 10,
|
|
31
|
+
"DIV": 40,
|
|
32
|
+
"LOAD": 2,
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _parse_program(text: str) -> list[Instruction]:
|
|
37
|
+
"""Parse program text into a list of Instructions.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
text: Raw program text with one instruction per non-comment line.
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
Ordered list of Instruction objects.
|
|
44
|
+
|
|
45
|
+
Raises:
|
|
46
|
+
ValueError: If a line cannot be parsed as an instruction.
|
|
47
|
+
"""
|
|
48
|
+
instructions: list[Instruction] = []
|
|
49
|
+
for lineno, raw in enumerate(text.splitlines(), start=1):
|
|
50
|
+
line = raw.strip()
|
|
51
|
+
if not line or line.startswith("#"):
|
|
52
|
+
continue
|
|
53
|
+
parts = line.split()
|
|
54
|
+
if len(parts) != 4:
|
|
55
|
+
raise ValueError(
|
|
56
|
+
f"line {lineno}: expected 'OP DEST SRC1 SRC2', got {line!r}"
|
|
57
|
+
)
|
|
58
|
+
op, dest, src1, src2 = parts
|
|
59
|
+
instructions.append(Instruction(op=op.upper(), dest=dest, src1=src1, src2=src2))
|
|
60
|
+
return instructions
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def run(argv: list[str]) -> int:
|
|
64
|
+
"""Parse arguments, run simulation, print timing table.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
argv: Command-line argument list (without the program name).
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
Exit code: 0 on success, 1 on usage error.
|
|
71
|
+
"""
|
|
72
|
+
import argparse
|
|
73
|
+
|
|
74
|
+
parser = argparse.ArgumentParser(
|
|
75
|
+
prog="tomasulo",
|
|
76
|
+
description="Simulate Tomasulo's out-of-order instruction scheduling algorithm.",
|
|
77
|
+
)
|
|
78
|
+
parser.add_argument(
|
|
79
|
+
"program",
|
|
80
|
+
nargs="?",
|
|
81
|
+
metavar="FILE",
|
|
82
|
+
help="text file containing the instruction program (reads stdin if omitted)",
|
|
83
|
+
)
|
|
84
|
+
parser.add_argument(
|
|
85
|
+
"--version",
|
|
86
|
+
action="version",
|
|
87
|
+
version=f"tomasulo {__version__}",
|
|
88
|
+
)
|
|
89
|
+
parser.add_argument(
|
|
90
|
+
"--add-stations",
|
|
91
|
+
type=int,
|
|
92
|
+
default=3,
|
|
93
|
+
metavar="N",
|
|
94
|
+
help="number of ADD/SUB reservation stations (default 3)",
|
|
95
|
+
)
|
|
96
|
+
parser.add_argument(
|
|
97
|
+
"--mult-stations",
|
|
98
|
+
type=int,
|
|
99
|
+
default=2,
|
|
100
|
+
metavar="N",
|
|
101
|
+
help="number of MUL/DIV reservation stations (default 2)",
|
|
102
|
+
)
|
|
103
|
+
parser.add_argument(
|
|
104
|
+
"--load-stations",
|
|
105
|
+
type=int,
|
|
106
|
+
default=3,
|
|
107
|
+
metavar="N",
|
|
108
|
+
help="number of LOAD reservation stations (default 3)",
|
|
109
|
+
)
|
|
110
|
+
parser.add_argument(
|
|
111
|
+
"--add-latency",
|
|
112
|
+
type=int,
|
|
113
|
+
default=2,
|
|
114
|
+
metavar="C",
|
|
115
|
+
help="execution cycles for ADD/SUB (default 2)",
|
|
116
|
+
)
|
|
117
|
+
parser.add_argument(
|
|
118
|
+
"--mul-latency",
|
|
119
|
+
type=int,
|
|
120
|
+
default=10,
|
|
121
|
+
metavar="C",
|
|
122
|
+
help="execution cycles for MUL (default 10)",
|
|
123
|
+
)
|
|
124
|
+
parser.add_argument(
|
|
125
|
+
"--div-latency",
|
|
126
|
+
type=int,
|
|
127
|
+
default=40,
|
|
128
|
+
metavar="C",
|
|
129
|
+
help="execution cycles for DIV (default 40)",
|
|
130
|
+
)
|
|
131
|
+
parser.add_argument(
|
|
132
|
+
"--load-latency",
|
|
133
|
+
type=int,
|
|
134
|
+
default=2,
|
|
135
|
+
metavar="C",
|
|
136
|
+
help="execution cycles for LOAD (default 2)",
|
|
137
|
+
)
|
|
138
|
+
parser.add_argument(
|
|
139
|
+
"--snapshots",
|
|
140
|
+
action="store_true",
|
|
141
|
+
help="print per-cycle reservation station snapshots after the timing table",
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
args = parser.parse_args(argv)
|
|
145
|
+
|
|
146
|
+
if args.program is not None:
|
|
147
|
+
try:
|
|
148
|
+
with open(args.program, encoding="utf-8") as f:
|
|
149
|
+
text = f.read()
|
|
150
|
+
except OSError as exc:
|
|
151
|
+
print(f"tomasulo: cannot open file: {exc}", file=sys.stderr)
|
|
152
|
+
return 1
|
|
153
|
+
else:
|
|
154
|
+
text = sys.stdin.read()
|
|
155
|
+
|
|
156
|
+
try:
|
|
157
|
+
program = _parse_program(text)
|
|
158
|
+
except ValueError as exc:
|
|
159
|
+
print(f"tomasulo: parse error: {exc}", file=sys.stderr)
|
|
160
|
+
return 1
|
|
161
|
+
|
|
162
|
+
if not program:
|
|
163
|
+
print("tomasulo: no instructions found", file=sys.stderr)
|
|
164
|
+
return 1
|
|
165
|
+
|
|
166
|
+
stations: dict[str, int] = {
|
|
167
|
+
"add": args.add_stations,
|
|
168
|
+
"mult": args.mult_stations,
|
|
169
|
+
"load": args.load_stations,
|
|
170
|
+
}
|
|
171
|
+
latencies: dict[str, int] = {
|
|
172
|
+
"ADD": args.add_latency,
|
|
173
|
+
"SUB": args.add_latency,
|
|
174
|
+
"MUL": args.mul_latency,
|
|
175
|
+
"DIV": args.div_latency,
|
|
176
|
+
"LOAD": args.load_latency,
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
sim = TomasuloSim(stations=stations, latencies=latencies)
|
|
180
|
+
trace = sim.run(program=program)
|
|
181
|
+
|
|
182
|
+
print(render_trace(trace=trace))
|
|
183
|
+
|
|
184
|
+
if args.snapshots:
|
|
185
|
+
print()
|
|
186
|
+
for snap in trace.snapshots:
|
|
187
|
+
print(f"=== Cycle {snap.cycle} ===")
|
|
188
|
+
if snap.cdb_tag is not None:
|
|
189
|
+
print(f" CDB: {snap.cdb_tag} = {snap.cdb_value}")
|
|
190
|
+
for st in snap.stations:
|
|
191
|
+
if st.busy:
|
|
192
|
+
qj = st.qj or "-"
|
|
193
|
+
qk = st.qk or "-"
|
|
194
|
+
rem = st.remaining if st.remaining is not None else "-"
|
|
195
|
+
print(
|
|
196
|
+
f" [{st.tag}] {st.op} Qj={qj} Qk={qk} rem={rem}"
|
|
197
|
+
)
|
|
198
|
+
status_entries = [
|
|
199
|
+
f"{r}={v}" for r, v in snap.register_status.table.items() if v is not None
|
|
200
|
+
]
|
|
201
|
+
if status_entries:
|
|
202
|
+
print(f" RegStatus: {', '.join(status_entries)}")
|
|
203
|
+
print()
|
|
204
|
+
|
|
205
|
+
return 0
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def main() -> int:
|
|
209
|
+
"""Console entry point."""
|
|
210
|
+
return run(sys.argv[1:])
|
tomasulo/model.py
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"""Data model dataclasses for the Tomasulo simulator."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass
|
|
9
|
+
class Instruction:
|
|
10
|
+
"""One instruction in the program.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
op: Opcode string -- "ADD", "SUB", "MUL", "DIV", or "LOAD".
|
|
14
|
+
dest: Destination register name, e.g. "F0".
|
|
15
|
+
src1: First source register, e.g. "F2". For LOAD, this is the base
|
|
16
|
+
address register (e.g. "R2").
|
|
17
|
+
src2: Second source register, e.g. "F4". For LOAD, this field is
|
|
18
|
+
unused by the algorithm; pass "R0" by convention.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
op: str
|
|
22
|
+
dest: str
|
|
23
|
+
src1: str
|
|
24
|
+
src2: str
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass
|
|
28
|
+
class InstructionResult:
|
|
29
|
+
"""Timing record for one issued instruction.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
instruction: The original instruction.
|
|
33
|
+
issue_cycle: Cycle in which the instruction was issued to a station.
|
|
34
|
+
exec_complete_cycle: Cycle in which execution finished (remaining
|
|
35
|
+
countdown reached zero).
|
|
36
|
+
write_cycle: Cycle in which the result was broadcast on the CDB and
|
|
37
|
+
written to the register file.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
instruction: Instruction
|
|
41
|
+
issue_cycle: int
|
|
42
|
+
exec_complete_cycle: int
|
|
43
|
+
write_cycle: int
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@dataclass
|
|
47
|
+
class StationState:
|
|
48
|
+
"""Snapshot of one reservation station at the end of a cycle.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
tag: Unique station identifier, e.g. "add1" or "mult2".
|
|
52
|
+
unit_type: Functional unit type -- "add", "mult", or "load".
|
|
53
|
+
busy: Whether the station is occupied.
|
|
54
|
+
op: Opcode currently held, or "" when not busy.
|
|
55
|
+
vj: Value of first operand when available, or None.
|
|
56
|
+
vk: Value of second operand when available, or None.
|
|
57
|
+
qj: Tag of the station producing the first operand, or None when ready.
|
|
58
|
+
qk: Tag of the station producing the second operand, or None when ready.
|
|
59
|
+
dest: This station's own tag (used as the CDB broadcast tag).
|
|
60
|
+
remaining: Execution cycles still needed, or None when not executing.
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
tag: str
|
|
64
|
+
unit_type: str
|
|
65
|
+
busy: bool
|
|
66
|
+
op: str
|
|
67
|
+
vj: float | None
|
|
68
|
+
vk: float | None
|
|
69
|
+
qj: str | None
|
|
70
|
+
qk: str | None
|
|
71
|
+
dest: str
|
|
72
|
+
remaining: int | None
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@dataclass
|
|
76
|
+
class RegisterStatus:
|
|
77
|
+
"""Snapshot of the register result-status table at the end of a cycle.
|
|
78
|
+
|
|
79
|
+
Maps each register name to the tag of the station that will write it,
|
|
80
|
+
or None if the register value is current (no pending write).
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
table: Dictionary from register name to station tag or None.
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
table: dict[str, str | None]
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@dataclass
|
|
90
|
+
class CycleSnapshot:
|
|
91
|
+
"""Complete simulator state captured at the end of one cycle.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
cycle: The cycle number this snapshot represents.
|
|
95
|
+
stations: Snapshot of every reservation station.
|
|
96
|
+
register_status: The register result-status table.
|
|
97
|
+
cdb_tag: Tag broadcast on the CDB this cycle, or None if no write.
|
|
98
|
+
cdb_value: Value broadcast on the CDB this cycle, or None if no write.
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
cycle: int
|
|
102
|
+
stations: list[StationState]
|
|
103
|
+
register_status: RegisterStatus
|
|
104
|
+
cdb_tag: str | None
|
|
105
|
+
cdb_value: float | None
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
@dataclass
|
|
109
|
+
class Trace:
|
|
110
|
+
"""Complete simulation output.
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
results: Per-instruction timing records, in program order.
|
|
114
|
+
snapshots: Per-cycle state snapshots, one per simulated cycle.
|
|
115
|
+
"""
|
|
116
|
+
|
|
117
|
+
results: list[InstructionResult]
|
|
118
|
+
snapshots: list[CycleSnapshot] = field(default_factory=list)
|
tomasulo/py.typed
ADDED
|
File without changes
|
tomasulo/simulator.py
ADDED
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
"""Tomasulo out-of-order instruction scheduling simulator.
|
|
2
|
+
|
|
3
|
+
Stage order each cycle: WRITE, then EXECUTE, then ISSUE.
|
|
4
|
+
|
|
5
|
+
Hazard handling:
|
|
6
|
+
- RAW: a station records the tag (Qj/Qk) of the station that will produce a
|
|
7
|
+
missing operand. When that station broadcasts on the CDB, all waiting
|
|
8
|
+
stations pick up the value and clear the tag.
|
|
9
|
+
- WAR and WAW: handled implicitly by register renaming. Each instruction
|
|
10
|
+
renames its destination register to the station tag it was issued to.
|
|
11
|
+
The register result-status table always records the LAST writer. An
|
|
12
|
+
earlier in-flight station only commits its result to the register file
|
|
13
|
+
if the status table still points at that station when it writes back.
|
|
14
|
+
|
|
15
|
+
CDB structural hazard: at most one result is broadcast per cycle. If multiple
|
|
16
|
+
stations finish on the same cycle the station with the lowest index (in
|
|
17
|
+
declaration order) wins; others wait and try again the following cycle.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
from dataclasses import dataclass, field
|
|
23
|
+
|
|
24
|
+
from .model import (
|
|
25
|
+
CycleSnapshot,
|
|
26
|
+
Instruction,
|
|
27
|
+
InstructionResult,
|
|
28
|
+
RegisterStatus,
|
|
29
|
+
StationState,
|
|
30
|
+
Trace,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
# Map opcode string to functional unit type.
|
|
34
|
+
_UNIT_TYPE: dict[str, str] = {
|
|
35
|
+
"ADD": "add",
|
|
36
|
+
"SUB": "add",
|
|
37
|
+
"MUL": "mult",
|
|
38
|
+
"DIV": "mult",
|
|
39
|
+
"LOAD": "load",
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass
|
|
44
|
+
class _Station:
|
|
45
|
+
"""Live reservation station (mutable internal state)."""
|
|
46
|
+
|
|
47
|
+
tag: str
|
|
48
|
+
unit_type: str
|
|
49
|
+
busy: bool = field(default=False)
|
|
50
|
+
op: str = field(default="")
|
|
51
|
+
vj: float | None = field(default=None)
|
|
52
|
+
vk: float | None = field(default=None)
|
|
53
|
+
qj: str | None = field(default=None)
|
|
54
|
+
qk: str | None = field(default=None)
|
|
55
|
+
# remaining == None -> not executing yet (operands not ready or not set)
|
|
56
|
+
# remaining == N > 0 -> N cycles left before exec_complete
|
|
57
|
+
# remaining == 0 -> execution finished; waiting for CDB slot
|
|
58
|
+
remaining: int | None = field(default=None)
|
|
59
|
+
# Index of the program instruction currently held (for result tracking).
|
|
60
|
+
insn_index: int | None = field(default=None)
|
|
61
|
+
|
|
62
|
+
def snapshot(self) -> StationState:
|
|
63
|
+
"""Return an immutable snapshot of this station."""
|
|
64
|
+
return StationState(
|
|
65
|
+
tag=self.tag,
|
|
66
|
+
unit_type=self.unit_type,
|
|
67
|
+
busy=self.busy,
|
|
68
|
+
op=self.op,
|
|
69
|
+
vj=self.vj,
|
|
70
|
+
vk=self.vk,
|
|
71
|
+
qj=self.qj,
|
|
72
|
+
qk=self.qk,
|
|
73
|
+
dest=self.tag,
|
|
74
|
+
remaining=self.remaining,
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
def clear(self) -> None:
|
|
78
|
+
"""Reset station to idle state."""
|
|
79
|
+
self.busy = False
|
|
80
|
+
self.op = ""
|
|
81
|
+
self.vj = None
|
|
82
|
+
self.vk = None
|
|
83
|
+
self.qj = None
|
|
84
|
+
self.qk = None
|
|
85
|
+
self.remaining = None
|
|
86
|
+
self.insn_index = None
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
# Default register value when a register has not been written.
|
|
90
|
+
def _default_reg_value() -> float:
|
|
91
|
+
return 0.0
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class TomasuloSim:
|
|
95
|
+
"""Simulate Tomasulo's algorithm on a given program.
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
stations: Mapping from unit type ("add", "mult", "load") to the number
|
|
99
|
+
of reservation stations of that type.
|
|
100
|
+
latencies: Mapping from opcode to the number of execution cycles
|
|
101
|
+
required (e.g. {"ADD": 2, "SUB": 2, "MUL": 10, "DIV": 40,
|
|
102
|
+
"LOAD": 2}).
|
|
103
|
+
"""
|
|
104
|
+
|
|
105
|
+
def __init__(self, stations: dict[str, int], latencies: dict[str, int]) -> None:
|
|
106
|
+
self._station_counts = stations
|
|
107
|
+
self._latencies = latencies
|
|
108
|
+
|
|
109
|
+
def run(self, program: list[Instruction]) -> Trace:
|
|
110
|
+
"""Simulate the program and return a full Trace.
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
program: List of instructions in program order.
|
|
114
|
+
|
|
115
|
+
Returns:
|
|
116
|
+
A Trace containing per-instruction timing and per-cycle snapshots.
|
|
117
|
+
"""
|
|
118
|
+
if not program:
|
|
119
|
+
return Trace(results=[], snapshots=[])
|
|
120
|
+
|
|
121
|
+
# Build ordered list of all stations (order determines CDB tie-break).
|
|
122
|
+
all_stations: list[_Station] = []
|
|
123
|
+
for unit_type, count in self._station_counts.items():
|
|
124
|
+
for i in range(1, count + 1):
|
|
125
|
+
all_stations.append(_Station(tag=f"{unit_type}{i}", unit_type=unit_type))
|
|
126
|
+
|
|
127
|
+
# Register result-status table: reg -> station tag that will write it.
|
|
128
|
+
reg_status: dict[str, str | None] = {}
|
|
129
|
+
|
|
130
|
+
# Register file: reg -> current value.
|
|
131
|
+
reg_file: dict[str, float] = {}
|
|
132
|
+
|
|
133
|
+
# Per-instruction timing records (indexed by program order).
|
|
134
|
+
issue_cycle: list[int] = [0] * len(program)
|
|
135
|
+
exec_complete_cycle: list[int] = [0] * len(program)
|
|
136
|
+
write_cycle: list[int] = [0] * len(program)
|
|
137
|
+
|
|
138
|
+
# Index of next instruction to issue.
|
|
139
|
+
issue_ptr = 0
|
|
140
|
+
|
|
141
|
+
snapshots: list[CycleSnapshot] = []
|
|
142
|
+
|
|
143
|
+
cycle = 0
|
|
144
|
+
|
|
145
|
+
while True:
|
|
146
|
+
cycle += 1
|
|
147
|
+
|
|
148
|
+
cdb_tag: str | None = None
|
|
149
|
+
cdb_value: float | None = None
|
|
150
|
+
|
|
151
|
+
# ------------------------------------------------------------------
|
|
152
|
+
# STAGE 1: WRITE
|
|
153
|
+
# Find the station that finished executing and can write to the CDB.
|
|
154
|
+
# Tie-break: lowest index in all_stations wins (first in list).
|
|
155
|
+
# A station is ready to write when remaining == 0.
|
|
156
|
+
# ------------------------------------------------------------------
|
|
157
|
+
write_station: _Station | None = None
|
|
158
|
+
for st in all_stations:
|
|
159
|
+
if st.busy and st.remaining == 0:
|
|
160
|
+
write_station = st
|
|
161
|
+
break # first (lowest-index) wins
|
|
162
|
+
|
|
163
|
+
if write_station is not None:
|
|
164
|
+
idx = write_station.insn_index
|
|
165
|
+
assert idx is not None # busy station always has insn_index set
|
|
166
|
+
|
|
167
|
+
# All computed values are 0.0 in this scheduling simulator.
|
|
168
|
+
result_value = 0.0
|
|
169
|
+
|
|
170
|
+
cdb_tag = write_station.tag
|
|
171
|
+
cdb_value = result_value
|
|
172
|
+
write_cycle[idx] = cycle
|
|
173
|
+
|
|
174
|
+
# Update register file if this station is still the last writer.
|
|
175
|
+
# Iterate over a snapshot of keys to allow value mutation.
|
|
176
|
+
for reg in list(reg_status.keys()):
|
|
177
|
+
if reg_status[reg] == write_station.tag:
|
|
178
|
+
reg_file[reg] = result_value
|
|
179
|
+
reg_status[reg] = None
|
|
180
|
+
|
|
181
|
+
# Forward result to every waiting station.
|
|
182
|
+
for st in all_stations:
|
|
183
|
+
if st.busy:
|
|
184
|
+
if st.qj == write_station.tag:
|
|
185
|
+
st.vj = result_value
|
|
186
|
+
st.qj = None
|
|
187
|
+
if st.qk == write_station.tag:
|
|
188
|
+
st.vk = result_value
|
|
189
|
+
st.qk = None
|
|
190
|
+
|
|
191
|
+
# Free the writing station.
|
|
192
|
+
write_station.clear()
|
|
193
|
+
|
|
194
|
+
# ------------------------------------------------------------------
|
|
195
|
+
# STAGE 2: EXECUTE
|
|
196
|
+
# Count down remaining cycles for stations currently executing.
|
|
197
|
+
# "Currently executing" means remaining is not None and > 0.
|
|
198
|
+
# Stations whose operands just became available in the WRITE phase
|
|
199
|
+
# above have remaining == None and are NOT started this cycle
|
|
200
|
+
# (next-cycle rule).
|
|
201
|
+
# ------------------------------------------------------------------
|
|
202
|
+
for st in all_stations:
|
|
203
|
+
if not st.busy or st.remaining is None or st.remaining == 0:
|
|
204
|
+
continue
|
|
205
|
+
# remaining > 0: count down one cycle.
|
|
206
|
+
st.remaining -= 1
|
|
207
|
+
if st.remaining == 0:
|
|
208
|
+
assert st.insn_index is not None
|
|
209
|
+
exec_complete_cycle[st.insn_index] = cycle
|
|
210
|
+
|
|
211
|
+
# After the decrement pass, start any station whose operands are
|
|
212
|
+
# now ready but had not yet begun executing (remaining is None).
|
|
213
|
+
# These stations will begin next cycle (remaining set to latency now
|
|
214
|
+
# so the decrement pass will fire next cycle).
|
|
215
|
+
for st in all_stations:
|
|
216
|
+
if st.busy and st.remaining is None and st.qj is None and st.qk is None:
|
|
217
|
+
assert st.insn_index is not None
|
|
218
|
+
st.remaining = self._latencies[st.op]
|
|
219
|
+
|
|
220
|
+
# ------------------------------------------------------------------
|
|
221
|
+
# STAGE 3: ISSUE
|
|
222
|
+
# Issue at most one instruction per cycle in program order.
|
|
223
|
+
# Stall if no free station of the required type is available.
|
|
224
|
+
# ------------------------------------------------------------------
|
|
225
|
+
if issue_ptr < len(program):
|
|
226
|
+
insn = program[issue_ptr]
|
|
227
|
+
unit_type = _UNIT_TYPE[insn.op]
|
|
228
|
+
|
|
229
|
+
# Find a free station of the correct type.
|
|
230
|
+
free_station: _Station | None = None
|
|
231
|
+
for st in all_stations:
|
|
232
|
+
if st.unit_type == unit_type and not st.busy:
|
|
233
|
+
free_station = st
|
|
234
|
+
break
|
|
235
|
+
|
|
236
|
+
if free_station is not None:
|
|
237
|
+
free_station.busy = True
|
|
238
|
+
free_station.op = insn.op
|
|
239
|
+
free_station.insn_index = issue_ptr
|
|
240
|
+
issue_cycle[issue_ptr] = cycle
|
|
241
|
+
|
|
242
|
+
if insn.op == "LOAD":
|
|
243
|
+
# LOAD: src1 is the base register; src2 is unused.
|
|
244
|
+
# Both operand slots are ready immediately (no RAW for
|
|
245
|
+
# address computation in this model).
|
|
246
|
+
base_val = reg_file.get(insn.src1, _default_reg_value())
|
|
247
|
+
free_station.vj = base_val
|
|
248
|
+
free_station.qj = None
|
|
249
|
+
free_station.vk = 0.0
|
|
250
|
+
free_station.qk = None
|
|
251
|
+
else:
|
|
252
|
+
# src1
|
|
253
|
+
src1_producer = reg_status.get(insn.src1)
|
|
254
|
+
if src1_producer is not None:
|
|
255
|
+
free_station.qj = src1_producer
|
|
256
|
+
free_station.vj = None
|
|
257
|
+
else:
|
|
258
|
+
free_station.vj = reg_file.get(insn.src1, _default_reg_value())
|
|
259
|
+
free_station.qj = None
|
|
260
|
+
|
|
261
|
+
# src2
|
|
262
|
+
src2_producer = reg_status.get(insn.src2)
|
|
263
|
+
if src2_producer is not None:
|
|
264
|
+
free_station.qk = src2_producer
|
|
265
|
+
free_station.vk = None
|
|
266
|
+
else:
|
|
267
|
+
free_station.vk = reg_file.get(insn.src2, _default_reg_value())
|
|
268
|
+
free_station.qk = None
|
|
269
|
+
|
|
270
|
+
# Register renaming: dest register now maps to this station.
|
|
271
|
+
reg_status[insn.dest] = free_station.tag
|
|
272
|
+
|
|
273
|
+
# If operands are already ready at issue time, arm the
|
|
274
|
+
# countdown so execution begins next cycle.
|
|
275
|
+
if free_station.qj is None and free_station.qk is None:
|
|
276
|
+
free_station.remaining = self._latencies[insn.op]
|
|
277
|
+
|
|
278
|
+
issue_ptr += 1
|
|
279
|
+
|
|
280
|
+
# ------------------------------------------------------------------
|
|
281
|
+
# Capture snapshot at the end of the cycle.
|
|
282
|
+
# ------------------------------------------------------------------
|
|
283
|
+
station_snaps = [st.snapshot() for st in all_stations]
|
|
284
|
+
reg_snap = RegisterStatus(table=dict(reg_status))
|
|
285
|
+
snapshots.append(
|
|
286
|
+
CycleSnapshot(
|
|
287
|
+
cycle=cycle,
|
|
288
|
+
stations=station_snaps,
|
|
289
|
+
register_status=reg_snap,
|
|
290
|
+
cdb_tag=cdb_tag,
|
|
291
|
+
cdb_value=cdb_value,
|
|
292
|
+
)
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
# ------------------------------------------------------------------
|
|
296
|
+
# Termination: all instructions issued AND all stations idle.
|
|
297
|
+
# ------------------------------------------------------------------
|
|
298
|
+
if issue_ptr >= len(program) and all(not st.busy for st in all_stations):
|
|
299
|
+
break
|
|
300
|
+
|
|
301
|
+
results = [
|
|
302
|
+
InstructionResult(
|
|
303
|
+
instruction=program[i],
|
|
304
|
+
issue_cycle=issue_cycle[i],
|
|
305
|
+
exec_complete_cycle=exec_complete_cycle[i],
|
|
306
|
+
write_cycle=write_cycle[i],
|
|
307
|
+
)
|
|
308
|
+
for i in range(len(program))
|
|
309
|
+
]
|
|
310
|
+
|
|
311
|
+
return Trace(results=results, snapshots=snapshots)
|
tomasulo/trace.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""Rendering functions for Tomasulo simulation traces."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from .model import Trace
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def render_trace(trace: Trace) -> str:
|
|
9
|
+
"""Render a simulation trace as a human-readable timing table.
|
|
10
|
+
|
|
11
|
+
Each row shows one instruction with its issue, execution-complete, and
|
|
12
|
+
write-result cycle numbers.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
trace: The Trace returned by TomasuloSim.run().
|
|
16
|
+
|
|
17
|
+
Returns:
|
|
18
|
+
A multi-line string containing the formatted timing table.
|
|
19
|
+
"""
|
|
20
|
+
if not trace.results:
|
|
21
|
+
return "(no instructions)"
|
|
22
|
+
|
|
23
|
+
col_widths = {
|
|
24
|
+
"index": 4,
|
|
25
|
+
"op": 5,
|
|
26
|
+
"dest": 5,
|
|
27
|
+
"src1": 5,
|
|
28
|
+
"src2": 5,
|
|
29
|
+
"issue": 6,
|
|
30
|
+
"exec": 14,
|
|
31
|
+
"write": 6,
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
header = (
|
|
35
|
+
f"{'#':>{col_widths['index']}}"
|
|
36
|
+
f" {'Op':<{col_widths['op']}}"
|
|
37
|
+
f" {'Dest':<{col_widths['dest']}}"
|
|
38
|
+
f" {'Src1':<{col_widths['src1']}}"
|
|
39
|
+
f" {'Src2':<{col_widths['src2']}}"
|
|
40
|
+
f" {'Issue':>{col_widths['issue']}}"
|
|
41
|
+
f" {'ExecComplete':>{col_widths['exec']}}"
|
|
42
|
+
f" {'Write':>{col_widths['write']}}"
|
|
43
|
+
)
|
|
44
|
+
separator = "-" * len(header)
|
|
45
|
+
|
|
46
|
+
lines: list[str] = [header, separator]
|
|
47
|
+
for i, r in enumerate(trace.results):
|
|
48
|
+
insn = r.instruction
|
|
49
|
+
row = (
|
|
50
|
+
f"{i + 1:>{col_widths['index']}}"
|
|
51
|
+
f" {insn.op:<{col_widths['op']}}"
|
|
52
|
+
f" {insn.dest:<{col_widths['dest']}}"
|
|
53
|
+
f" {insn.src1:<{col_widths['src1']}}"
|
|
54
|
+
f" {insn.src2:<{col_widths['src2']}}"
|
|
55
|
+
f" {r.issue_cycle:>{col_widths['issue']}}"
|
|
56
|
+
f" {r.exec_complete_cycle:>{col_widths['exec']}}"
|
|
57
|
+
f" {r.write_cycle:>{col_widths['write']}}"
|
|
58
|
+
)
|
|
59
|
+
lines.append(row)
|
|
60
|
+
|
|
61
|
+
return "\n".join(lines)
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tomasulo
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Pure-Python simulator of Tomasulo's out-of-order instruction scheduling: reservation stations, common data bus, and register renaming, with cycle-by-cycle traces for computer architecture education.
|
|
5
|
+
Project-URL: Homepage, https://github.com/amaar-mc/tomasulo
|
|
6
|
+
Project-URL: Repository, https://github.com/amaar-mc/tomasulo
|
|
7
|
+
Project-URL: Issues, https://github.com/amaar-mc/tomasulo/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/amaar-mc/tomasulo/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: common-data-bus,computer-architecture,cpu-simulator,education,instruction-scheduling,out-of-order,register-renaming,reservation-stations,simulation,tomasulo
|
|
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
|
+
# tomasulo
|
|
54
|
+
|
|
55
|
+
Pure-Python simulator of Tomasulo's out-of-order instruction scheduling algorithm. Implements reservation stations, a Common Data Bus (CDB), and register renaming for computer architecture education.
|
|
56
|
+
|
|
57
|
+
Zero runtime dependencies. Requires Python 3.10+.
|
|
58
|
+
|
|
59
|
+
## Installation
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
pip install tomasulo
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Or with [uv](https://github.com/astral-sh/uv):
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
uv pip install tomasulo
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Quick start
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
from tomasulo import Instruction, TomasuloSim, render_trace
|
|
75
|
+
|
|
76
|
+
program = [
|
|
77
|
+
Instruction(op="LOAD", dest="F6", src1="R2", src2="R0"),
|
|
78
|
+
Instruction(op="LOAD", dest="F2", src1="R3", src2="R0"),
|
|
79
|
+
Instruction(op="MUL", dest="F0", src1="F2", src2="F4"),
|
|
80
|
+
Instruction(op="SUB", dest="F8", src1="F6", src2="F2"),
|
|
81
|
+
Instruction(op="DIV", dest="F10", src1="F0", src2="F6"),
|
|
82
|
+
Instruction(op="ADD", dest="F6", src1="F8", src2="F2"),
|
|
83
|
+
]
|
|
84
|
+
|
|
85
|
+
sim = TomasuloSim(
|
|
86
|
+
stations={"add": 3, "mult": 2, "load": 3},
|
|
87
|
+
latencies={"ADD": 2, "SUB": 2, "MUL": 10, "DIV": 40, "LOAD": 2},
|
|
88
|
+
)
|
|
89
|
+
trace = sim.run(program=program)
|
|
90
|
+
print(render_trace(trace=trace))
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Output:
|
|
94
|
+
|
|
95
|
+
```
|
|
96
|
+
# Op Dest Src1 Src2 Issue ExecComplete Write
|
|
97
|
+
---------------------------------------------------------------------
|
|
98
|
+
1 LOAD F6 R2 R0 1 3 4
|
|
99
|
+
2 LOAD F2 R3 R0 2 4 5
|
|
100
|
+
3 MUL F0 F2 F4 3 15 16
|
|
101
|
+
4 SUB F8 F6 F2 4 6 7
|
|
102
|
+
5 DIV F10 F0 F6 5 56 57
|
|
103
|
+
6 ADD F6 F8 F2 6 9 10
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## CLI
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
tomasulo examples/sample_program.txt
|
|
110
|
+
tomasulo examples/sample_program.txt --snapshots
|
|
111
|
+
tomasulo --help
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Program file format -- one instruction per line:
|
|
115
|
+
|
|
116
|
+
```
|
|
117
|
+
# comments are ignored
|
|
118
|
+
LOAD F6 R2 R0
|
|
119
|
+
LOAD F2 R3 R0
|
|
120
|
+
MUL F0 F2 F4
|
|
121
|
+
SUB F8 F6 F2
|
|
122
|
+
DIV F10 F0 F6
|
|
123
|
+
ADD F6 F8 F2
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Public API
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
@dataclass
|
|
130
|
+
class Instruction:
|
|
131
|
+
op: str # "ADD", "SUB", "MUL", "DIV", "LOAD"
|
|
132
|
+
dest: str # destination register e.g. "F0"
|
|
133
|
+
src1: str # first source register; for LOAD: base address register
|
|
134
|
+
src2: str # second source register; for LOAD: unused, pass "R0" by convention
|
|
135
|
+
|
|
136
|
+
@dataclass
|
|
137
|
+
class InstructionResult:
|
|
138
|
+
instruction: Instruction
|
|
139
|
+
issue_cycle: int
|
|
140
|
+
exec_complete_cycle: int
|
|
141
|
+
write_cycle: int
|
|
142
|
+
|
|
143
|
+
@dataclass
|
|
144
|
+
class Trace:
|
|
145
|
+
results: list[InstructionResult]
|
|
146
|
+
snapshots: list[CycleSnapshot]
|
|
147
|
+
|
|
148
|
+
class TomasuloSim:
|
|
149
|
+
def __init__(self, stations: dict[str, int], latencies: dict[str, int]) -> None: ...
|
|
150
|
+
def run(self, program: list[Instruction]) -> Trace: ...
|
|
151
|
+
|
|
152
|
+
def render_trace(trace: Trace) -> str: ...
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Algorithm
|
|
156
|
+
|
|
157
|
+
Stage order each cycle: **WRITE, then EXECUTE, then ISSUE.**
|
|
158
|
+
|
|
159
|
+
Hazards handled:
|
|
160
|
+
|
|
161
|
+
- **RAW**: a station waits on the tag (Qj/Qk) of the station that will produce a missing operand. When that station broadcasts on the CDB the value is forwarded and the tag is cleared.
|
|
162
|
+
- **WAR and WAW**: handled by register renaming. Each issued instruction renames its destination to the station tag. Only the last writer commits to the register file.
|
|
163
|
+
- **Structural**: at most one result per cycle on the CDB. Ties broken by lowest station index (declaration order).
|
|
164
|
+
|
|
165
|
+
See `docs/architecture.md` for a full description.
|
|
166
|
+
|
|
167
|
+
## Development
|
|
168
|
+
|
|
169
|
+
```
|
|
170
|
+
uv venv && uv pip install -e ".[dev]"
|
|
171
|
+
uv run pytest -q
|
|
172
|
+
uv run ruff check .
|
|
173
|
+
uv run mypy src
|
|
174
|
+
uv build
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## License
|
|
178
|
+
|
|
179
|
+
MIT. Copyright (c) 2026 Amaar Chughtai.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
tomasulo/__init__.py,sha256=WQB9GYM_I0866iJsBf4t6vygRfotu5PGtBYRq6QJtDo,484
|
|
2
|
+
tomasulo/cli.py,sha256=NhRdAwwwaxqlpuIezn-UzHvpiSDyk4KOEMJHCpI5jws,5678
|
|
3
|
+
tomasulo/model.py,sha256=eOW8taAU709ID9iEI5bQJbv5nmLOsMb25gmmVluMI3A,3453
|
|
4
|
+
tomasulo/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
+
tomasulo/simulator.py,sha256=czdhM8IAIrOZTs3Djhbh-17H9qnF7r0dVno3xlYMq_Q,12430
|
|
6
|
+
tomasulo/trace.py,sha256=R8-A9a0PYWDArI-AwsQmm3JvH6KkuCOcrIPJFbeyusg,1762
|
|
7
|
+
tomasulo-0.1.0.dist-info/METADATA,sha256=AfggJmdG7esNf1kwPkJgZmL2XBzwo0kbClCRGqBFXk0,6237
|
|
8
|
+
tomasulo-0.1.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
9
|
+
tomasulo-0.1.0.dist-info/entry_points.txt,sha256=GYoV39qebKhNTyIAjyf_kbIzTtuZbj039zvfQrEU3LQ,47
|
|
10
|
+
tomasulo-0.1.0.dist-info/licenses/LICENSE,sha256=NORTVJb4x206RXQkpZt_vpj8I7aZL6Vn26BvTOq6J40,1071
|
|
11
|
+
tomasulo-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.
|