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 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,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.30.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ tomasulo = tomasulo.cli:main
@@ -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.