blob: 11998db8edea6f2762e5c7bb375564a8634787d6 [file] [log] [blame]
from typing import Any, Dict, Iterable, Sequence
from buffered_queue import BufferedQueue
import counter
from counter import Counter
from instruction import Instruction
import interfaces
# TODO(b/261690182): rename the SchedUnit
class SchedUnit(interfaces.SchedUnit):
"""Issue unit model."""
def __init__(self, config: Dict[str, Any]):
super().__init__("SC")
self._decode_rate = config.get("decode_rate")
self._branch_prediction = config["branch_prediction"]
self._fetch_unit = None
self._exec_unit = None
## Current states
self._queues = {}
self._branch_stalling = False
## Next state
self._next_branch_stalling = None
def add_queue(self, uid: str, desc) -> None:
self._queues[uid] = BufferedQueue(desc.get("size"))
def connect(self, fetch_unit: interfaces.FetchUnit,
exec_unit: interfaces.ExecUnit) -> None:
self._fetch_unit = fetch_unit
self._exec_unit = exec_unit
# Implements interfaces.SchedUnit
@property
def queues(self) -> Iterable[BufferedQueue[Instruction]]:
return self._queues.values()
# Implements interfaces.SchedUnit
def pending(self) -> int:
return sum(len(q) for q in self._queues.values())
# Implements interfaces.SchedUnit
def reset(self, cntr: Counter) -> None:
super().reset(cntr)
# TODO(sflur): implement proper reset
cntr.stalls[self.name] = 0
for uid, q in self._queues.items():
cntr.utilizations[uid] = counter.Utilization(q.size)
# Implements interfaces.SchedUnit
def tick(self, cntr: Counter) -> None:
super().tick(cntr)
if self._branch_stalling:
self.log("queuing stalled: unresolved branch")
return
for _ in range(self._decode_rate if self._decode_rate
else len(self._fetch_unit.queue)):
if not self._fetch_unit.queue:
# Fetch queue is empty
break
fetched_instr = self._fetch_unit.queue.peek()
if not fetched_instr:
# A None instruction in the fetch queue stands for instruction
# that the functional simulator did not execute (or fetch), so
# we don't know what instruction that was, or how it behaved.
# In a real uarch this instruction will take some resources
# until the uarch figures out it should be evicted.
# TODO(sflur): count these instructions and apply some
# proportional penalty to the performance TBM reports?
self._fetch_unit.queue.dequeue()
continue
# Check if we need to flush pending instructions.
if fetched_instr.is_flush and (self.pending() or
self._exec_unit.pending()):
# TODO(sflur): Currently flush instructions wait in the fetch
# queue, is that the right place to wait in?
cntr.stalls[self.name] += 1
self.log(f"queueing stalled: flush in effect: {fetched_instr}")
break
if fetched_instr.is_nop:
self.log(f"retired NOP instruction: {fetched_instr}")
self._fetch_unit.queue.dequeue()
cntr.retired_instruction_count += 1
continue
qid = self._exec_unit.get_issue_queue_id(fetched_instr)
# Check if the queue is available.
if self._queues[qid].is_buffer_full():
cntr.stalls[self.name] += 1
self.log(f"queueing stalled: '{qid}' is full")
break
# TODO(sflur): instead of check_conflicts, we could add the
# instructions to the scoreboard at this point.
if not self.check_conflicts(fetched_instr, qid):
# TODO(sflur): the blocking instruction is still in the fetch
# queue, maybe move it somewhere else?
cntr.stalls[self.name] += 1
self.log("queueing stalled: conflict with queued instruction")
break
# It is safe to queue the instruction.
self._queues[qid].buffer(fetched_instr)
self._fetch_unit.queue.dequeue()
cntr.utilizations[qid].count += 1
self.log(f"instruction '{fetched_instr}' queued")
if fetched_instr.is_branch:
cntr.branch_count += 1
if self._branch_prediction == "none":
self._branch_stalling = True
break
# Implements interfaces.SchedUnit
def tock(self, cntr: Counter) -> None:
super().tock(cntr)
for q in self._queues.values():
q.flush()
if self._next_branch_stalling is not None:
self._branch_stalling = self._next_branch_stalling
self._next_branch_stalling = None
for uid, q in self._queues.items():
cntr.utilizations[uid].occupied += len(q)
def check_conflicts(self, new_instr: Instruction, qid: str) -> bool:
"""Check if `instr` conflicts with other instructions.
Check whether it is safe to reorder `instr` wrt the instructions
already in other queues.
There is no need to check conflicts with instructions that are already
in execution pipes, as that is handled by the scoreboard.
Args:
new_instr: fetched instructions.
qid: the dispatch queue the instruction will be placed in.
Returns:
True if there are no conflicts, False otherwise.
"""
for name, q in self._queues.items():
if name == qid:
# skip the queue new_instr is going to, as it's an in-order
# queue.
continue
for instr in q.chain():
if new_instr.conflicts_with(instr):
return False
return True
# Implements interfaces.SchedUnit
def branch_resolved(self) -> None:
if self.phase == interfaces.CyclePhase.TICK:
self._next_branch_stalling = False
else:
assert self.phase == interfaces.CyclePhase.TOCK
self._branch_stalling = False
# Implements interfaces.SchedUnit
def print_state_detailed(self, file) -> None:
for uid, dq in self._queues.items():
if dq:
queue_str = ", ".join(str(i) for i in reversed(dq))
else:
queue_str = "-"
print(f"[qu-{uid}] {queue_str}", file=file)
# Implements interfaces.SchedUnit
def get_state_three_valued_header(self) -> Sequence[str]:
return self._queues.keys()
# Implements interfaces.SchedUnit
def get_state_three_valued(self,vals: Sequence[str]) -> Sequence[str]:
return [q.pp_three_valued(vals) for q in self._queues.values()]