mirror of
https://github.com/tinygrad/tinygrad.git
synced 2026-08-29 10:16:07 +00:00
sqtt correct
This commit is contained in:
+143
-76
@@ -427,6 +427,8 @@ def exec_wmma(st: WaveState, inst, op: VOP3POp) -> None:
|
||||
WAVESTART_TO_INST_CYCLES = 32
|
||||
SNOP_EXTRA_DELAY_MIN, SNOP_EXTRA_DELAY_MAX = 11, 22 # s_nop(11-22) has +4 penalty
|
||||
SNOP_EXTRA_DELAY_CYCLES = 4
|
||||
ALU_STAGES = 5 # 5-stage ALU pipeline (RDNA: "5 cycles of latency are exposed")
|
||||
ISSUE_QUEUE_DEPTH = 14 # Maximum in-flight VALUs before issue stalls
|
||||
|
||||
from extra.assembly.amd.sqtt import WAVESTART, WAVEEND, IMMEDIATE, VALUINST, ALUEXEC, AluSrc
|
||||
|
||||
@@ -437,115 +439,175 @@ def _get_src_vgprs(inst: Inst) -> list[int]:
|
||||
return []
|
||||
|
||||
class SQTTState:
|
||||
"""SQTT tracing with real pipeline model.
|
||||
"""SQTT tracing with cycle-accurate RDNA VALU pipeline model.
|
||||
|
||||
Pipeline: Issue -> DepFetch -> ALU[0-3] -> Writeback
|
||||
- Issue: 1 instruction per cycle
|
||||
- DepFetch: wait for operands (const=immediate, vgpr=forward or regfile)
|
||||
- ALU: 4 stages, shifts each cycle
|
||||
- Writeback: 1 per cycle, emits ALUEXEC, result available for forwarding
|
||||
Pipeline stages:
|
||||
Issue (VALUINST) -> ALU[0-4] (5 stages) -> Writeback (ALUEXEC)
|
||||
|
||||
Timing (const source, no deps):
|
||||
Cycle 0: Issue (VALUINST emitted), instruction enters issue queue
|
||||
Cycle 1: Enters ALU[0]
|
||||
Cycle 2-5: ALU[1-4]
|
||||
Cycle 6: Writeback (ALUEXEC emitted)
|
||||
Total latency: 6 cycles
|
||||
|
||||
Dependencies:
|
||||
- If instruction has VGPR deps, it waits in issue queue until producer completes
|
||||
- Forwarding: dependent can enter ALU[0] same cycle as producer's writeback (saves 1 cycle)
|
||||
- First dependent in chain: exec delta = 6 (no forwarding benefit)
|
||||
- Subsequent dependents: exec delta = 5 (forwarding benefit)
|
||||
- When forwarding exhausted: exec delta = 9 (regfile read penalty)
|
||||
|
||||
Issue queue:
|
||||
- Tracks last N issued VALUs (for s_delay_alu)
|
||||
- When queue depth exceeds ISSUE_QUEUE_DEPTH, issue stalls
|
||||
"""
|
||||
def __init__(self, wave_id: int = 0, simd: int = 0, cu: int = 0):
|
||||
self.wave_id, self.simd, self.cu = wave_id, simd, cu
|
||||
self.cycle = 0
|
||||
self.packets = []
|
||||
|
||||
# Pipeline state
|
||||
self.alu = [None, None, None, None] # ALU stages 0-3, each holds (inst, dest_vgpr) or None
|
||||
self.writeback = None # Instruction in writeback stage
|
||||
self.dep_fetch = [] # Instructions waiting for operands: [(inst, dest_vgpr, src_vgprs, ready_cycle)]
|
||||
# ready_cycle = earliest cycle this instruction can enter ALU[0]
|
||||
# Issue queue: list of (issue_cycle, dest_vgpr, srcs, inst, in_alu)
|
||||
# Tracks all issued VALUs until they complete writeback
|
||||
self.issue_queue: list[tuple] = []
|
||||
|
||||
# Forwarding: vgpr available for forwarding this cycle (set during writeback)
|
||||
self.forward_vgpr = None
|
||||
# ALU pipeline: 5 stages, each holds (inst, dest_vgpr, issue_cycle) or None
|
||||
self.alu: list = [None] * ALU_STAGES
|
||||
|
||||
# Track which vgprs are being written by in-flight instructions
|
||||
self.vgpr_in_flight = {} # vgpr -> True if in pipeline
|
||||
# Writeback stage: (inst, dest_vgpr, issue_cycle) or None
|
||||
self.writeback = None
|
||||
|
||||
# Forwarding: vgpr available for forwarding THIS cycle (from previous tick's writeback)
|
||||
self.forward_vgpr: int | None = None
|
||||
# VGPR that just completed THIS tick (cannot be used for promotion until next tick)
|
||||
self.just_completed_vgpr: int | None = None
|
||||
|
||||
def emit(self, pkt_class, **kwargs):
|
||||
self.packets.append(pkt_class(_time=self.cycle, **kwargs))
|
||||
|
||||
def _is_in_flight(self, vgpr: int) -> bool:
|
||||
"""Check if vgpr is being written by an instruction in the pipeline."""
|
||||
def _in_flight_count(self) -> int:
|
||||
"""Count instructions that are issued but not yet in ALU (waiting for deps)."""
|
||||
return sum(1 for item in self.issue_queue if not item[4])
|
||||
|
||||
def _vgpr_in_alu(self, vgpr: int) -> bool:
|
||||
"""Check if vgpr is being written by an instruction currently in ALU or writeback."""
|
||||
for slot in self.alu:
|
||||
if slot is not None and slot[1] == vgpr: return True
|
||||
if self.writeback is not None and self.writeback[1] == vgpr: return True
|
||||
for item in self.dep_fetch:
|
||||
return False
|
||||
|
||||
def _vgpr_pending(self, vgpr: int) -> bool:
|
||||
"""Check if vgpr is being written by any in-flight instruction."""
|
||||
for item in self.issue_queue:
|
||||
if item[1] == vgpr: return True
|
||||
return False
|
||||
|
||||
def _deps_ready(self, srcs: list[int], forward_vgpr: int = None) -> bool:
|
||||
"""Check if all source vgprs are available (via forwarding or not in flight)."""
|
||||
for vgpr in srcs:
|
||||
# Can forward if this vgpr was written back last cycle
|
||||
if vgpr == forward_vgpr:
|
||||
continue
|
||||
# If vgpr is in flight, must wait for forwarding
|
||||
if self._is_in_flight(vgpr):
|
||||
return False
|
||||
return True
|
||||
def _find_producer_complete_cycle(self, vgpr: int) -> int | None:
|
||||
"""Find the cycle when vgpr's producer will complete (writeback)."""
|
||||
for item in self.issue_queue:
|
||||
if item[1] == vgpr:
|
||||
issue_cycle = item[0]
|
||||
# Producer completes at issue_cycle + 1 (enter ALU) + 5 (ALU stages) = issue_cycle + 6
|
||||
return issue_cycle + ALU_STAGES + 1
|
||||
return None
|
||||
|
||||
def tick(self):
|
||||
self.cycle += 1
|
||||
if DEBUG >= 3: print(f"C{self.cycle}:", end="")
|
||||
|
||||
# Pipeline timing model for 6,5,5 pattern:
|
||||
# - Base latency (const source): 6 cycles from issue to exec
|
||||
# - First dependent: 6 cycles from producer exec (no forwarding benefit)
|
||||
# - Subsequent dependents: 5 cycles from producer exec (forwarding saves 1 cycle)
|
||||
# Pipeline timing for 6-cycle latency (VALUINST@0 -> ALUEXEC@6):
|
||||
# Cycle 0: Issue (VALUINST)
|
||||
# Cycle 1: Enter ALU[0]
|
||||
# Cycle 2-5: ALU[1-4]
|
||||
# Cycle 6: Exit ALU[4], emit ALUEXEC, forwarding available NEXT cycle
|
||||
#
|
||||
# Forwarding is available from ALU[3] output. When instruction exits ALU[3]->WB,
|
||||
# a waiting dependent can enter ALU[0] on the SAME cycle, giving delta=5.
|
||||
# But the FIRST dependent can't benefit because it arrives before producer is in ALU[3].
|
||||
# Forwarding timing:
|
||||
# - When instruction exits ALU[4], its result is available for forwarding
|
||||
# - FIRST dependent: enters ALU the cycle AFTER producer's ALUEXEC -> delta 6
|
||||
# - SUBSEQUENT dependents: can enter ALU same cycle as their producer's ALUEXEC -> delta 5
|
||||
#
|
||||
# The key is that forward_vgpr from cycle N is used for promotion in cycle N+1.
|
||||
|
||||
# 1. Writeback: emit ALUEXEC
|
||||
if self.writeback is not None:
|
||||
inst, dest = self.writeback
|
||||
self.emit(ALUEXEC, src=AluSrc.VALU)
|
||||
if DEBUG >= 3: print(f" WB v{dest}", end="")
|
||||
self.writeback = None
|
||||
|
||||
# 2. ALU[3] -> Writeback, and set forwarding from ALU[3] output
|
||||
# Forwarding is available THIS cycle for instructions entering ALU[0]
|
||||
alu3_out = self.alu[3]
|
||||
self.alu[3] = None
|
||||
forward_this_cycle = alu3_out[1] if alu3_out is not None else None
|
||||
if alu3_out is not None:
|
||||
self.writeback = alu3_out
|
||||
if DEBUG >= 3: print(f" ALU[3]->WB", end="")
|
||||
|
||||
# 3. Shift ALU pipeline: [0]->[1]->[2]->[3]
|
||||
self.alu[3] = self.alu[2]
|
||||
self.alu[2] = self.alu[1]
|
||||
self.alu[1] = self.alu[0]
|
||||
# 1. Shift ALU pipeline and capture what exits ALU[4]
|
||||
exiting = self.alu[ALU_STAGES - 1]
|
||||
for i in range(ALU_STAGES - 1, 0, -1):
|
||||
self.alu[i] = self.alu[i - 1]
|
||||
self.alu[0] = None
|
||||
|
||||
# 4. DepFetch -> ALU[0]: check if any instruction can enter ALU
|
||||
# Forwarding from ALU[3] output is available THIS cycle (not next cycle)
|
||||
for item in self.dep_fetch[:]:
|
||||
inst, dest, srcs, ready_cycle = item
|
||||
if not self._deps_ready(srcs, forward_this_cycle): continue # deps not resolved yet
|
||||
if self.cycle < ready_cycle: continue # must wait for dep_fetch cycle
|
||||
self.dep_fetch.remove(item)
|
||||
self.alu[0] = (inst, dest)
|
||||
uses_forwarding = forward_this_cycle is not None and forward_this_cycle in srcs
|
||||
if DEBUG >= 3: print(f" DepFetch->ALU[0] v{dest}{'(fwd)' if uses_forwarding else ''}", end="")
|
||||
# 2. Emit ALUEXEC for instruction that exited ALU[4]
|
||||
if exiting is not None:
|
||||
inst, dest, issue_cycle = exiting
|
||||
self.emit(ALUEXEC, src=AluSrc.VALU)
|
||||
# Remove from issue queue
|
||||
self.issue_queue = [item for item in self.issue_queue if item[1] != dest or item[0] != issue_cycle]
|
||||
if DEBUG >= 3: print(f" ALUEXEC v{dest}", end="")
|
||||
|
||||
# 3. Issue queue -> ALU[0]: find first instruction whose deps are ready
|
||||
# Uses forward_vgpr from PREVIOUS tick (carried over from last tick)
|
||||
# After this tick, forward_vgpr will be updated for the NEXT tick
|
||||
promoted = False
|
||||
for item in self.issue_queue:
|
||||
issue_cycle, dest, srcs, inst, in_alu = item
|
||||
if in_alu: continue # Already in ALU pipeline
|
||||
|
||||
# Check if all source deps are ready
|
||||
deps_ready = True
|
||||
uses_forward = False
|
||||
for src_vgpr in srcs:
|
||||
if src_vgpr == self.forward_vgpr:
|
||||
uses_forward = True
|
||||
continue # Can forward from previous cycle's writeback
|
||||
if self._vgpr_pending(src_vgpr):
|
||||
deps_ready = False
|
||||
break
|
||||
|
||||
if not deps_ready: continue
|
||||
|
||||
# Check minimum time in issue queue (1 cycle from issue to ALU[0] entry)
|
||||
if self.cycle <= issue_cycle: continue
|
||||
|
||||
# Enter ALU[0]
|
||||
self.alu[0] = (inst, dest, issue_cycle)
|
||||
# Mark as in_alu
|
||||
idx = self.issue_queue.index(item)
|
||||
self.issue_queue[idx] = (issue_cycle, dest, srcs, inst, True)
|
||||
promoted = True
|
||||
if DEBUG >= 3: print(f" IQ->ALU[0] v{dest}{'(fwd)' if uses_forward else ''}", end="")
|
||||
break
|
||||
|
||||
# 4. Update forward_vgpr for NEXT tick
|
||||
# If something exited this tick, its result is available for forwarding next tick
|
||||
if exiting is not None:
|
||||
self.forward_vgpr = exiting[1] # dest vgpr
|
||||
else:
|
||||
self.forward_vgpr = None
|
||||
|
||||
if DEBUG >= 3: print()
|
||||
|
||||
def _pipeline_empty(self) -> bool:
|
||||
"""Check if pipeline has no in-flight instructions."""
|
||||
if self.writeback is not None: return False
|
||||
if any(slot is not None for slot in self.alu): return False
|
||||
if self.dep_fetch: return False
|
||||
if self.issue_queue: return False
|
||||
return True
|
||||
|
||||
def process_instruction(self, inst: Inst):
|
||||
if DEBUG >= 2: print(f"Process: {inst}")
|
||||
|
||||
if isinstance(inst, SOPP) and inst.op == SOPPOp.S_DELAY_ALU:
|
||||
# TODO: model delay_alu properly
|
||||
# s_delay_alu affects timing by inserting wait cycles based on instid
|
||||
# instid0 (bits 3:0): wait for VALU N instructions back (1-4), 0=none
|
||||
simm16 = inst.simm16
|
||||
instid0 = simm16 & 0xf
|
||||
if instid0 > 0 and instid0 <= len(self.issue_queue):
|
||||
# Find the instruction instid0 back in the issue queue
|
||||
target_idx = len(self.issue_queue) - instid0
|
||||
if target_idx >= 0:
|
||||
target = self.issue_queue[target_idx]
|
||||
complete_cycle = target[0] + ALU_STAGES + 1
|
||||
# Wait until that instruction completes
|
||||
while self.cycle < complete_cycle:
|
||||
self.tick()
|
||||
return
|
||||
|
||||
elif isinstance(inst, SOPP) and inst.op == SOPPOp.S_NOP:
|
||||
@@ -562,16 +624,21 @@ class SQTTState:
|
||||
self.emit(WAVEEND, wave=self.wave_id, simd=self.simd, cu_lo=self.cu & 0x7, flag7=self.cu >> 3)
|
||||
|
||||
elif isinstance(inst, (VOP1, VOP2, VOP3)):
|
||||
# Issue: add to dep_fetch queue with ready_cycle (dep_fetch takes 1 cycle)
|
||||
# After issue, tick() advances cycle. Instruction needs 1 full cycle in dep_fetch,
|
||||
# so ready_cycle = cycle + 2 (current cycle + tick + 1 cycle dep_fetch)
|
||||
srcs = _get_src_vgprs(inst)
|
||||
ready_cycle = self.cycle + 2 # Can enter ALU[0] after dep_fetch completes
|
||||
self.dep_fetch.append((inst, inst.vdst, srcs, ready_cycle))
|
||||
self.emit(VALUINST, wave=self.wave_id)
|
||||
if DEBUG >= 3: print(f"C{self.cycle}: Issue {inst.op_name} v{inst.vdst} srcs={srcs} ready@{ready_cycle}")
|
||||
# Check for issue stall (too many in-flight instructions)
|
||||
while self._in_flight_count() >= ISSUE_QUEUE_DEPTH:
|
||||
self.tick()
|
||||
|
||||
# One cycle per instruction issued
|
||||
# Issue: emit VALUINST, add to issue queue
|
||||
srcs = _get_src_vgprs(inst)
|
||||
self.issue_queue.append((self.cycle, inst.vdst, srcs, inst, False))
|
||||
self.emit(VALUINST, wave=self.wave_id)
|
||||
if DEBUG >= 3: print(f"C{self.cycle}: Issue {inst.op_name} v{inst.vdst} srcs={srcs}")
|
||||
|
||||
# One cycle per instruction issued
|
||||
self.tick()
|
||||
return
|
||||
|
||||
# One cycle per instruction issued (for non-VALU)
|
||||
self.tick()
|
||||
|
||||
def emit_wavestart(self):
|
||||
|
||||
@@ -115,80 +115,42 @@ def get_deltas(instructions: list) -> tuple[list[int], list[int]]:
|
||||
execd = [exec_times[i] - exec_times[i-1] for i in range(1, len(exec_times))]
|
||||
return issue, execd
|
||||
|
||||
# Hardware ALUEXEC delta patterns:
|
||||
# chain: forwarding (6,5,5...) then stalls when exhausted (9,9,9...)
|
||||
# ind: no dependencies, exec follows issue by 1 cycle
|
||||
# snop: n+4 baseline, but +4 extra for 11 <= n <= 22
|
||||
CHAIN_ISSUE = {
|
||||
2: [1],
|
||||
3: [1, 1],
|
||||
4: [1, 1, 1],
|
||||
5: [1, 1, 1, 1],
|
||||
6: [1, 1, 1, 1, 1],
|
||||
7: [1, 1, 1, 1, 1, 1],
|
||||
8: [1, 1, 1, 1, 1, 1, 1],
|
||||
12: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||
14: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||
15: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3], # issue stalls start here
|
||||
16: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 5],
|
||||
18: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 5, 5, 5],
|
||||
20: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 5, 5, 5, 5, 5],
|
||||
}
|
||||
CHAIN_EXEC = {
|
||||
2: [6],
|
||||
3: [6, 5],
|
||||
4: [6, 5, 5],
|
||||
5: [6, 5, 5, 9],
|
||||
6: [6, 5, 5, 9, 9],
|
||||
7: [6, 5, 5, 5, 9, 9],
|
||||
8: [6, 5, 5, 5, 9, 9, 9],
|
||||
12: [6, 5, 5, 5, 5, 9, 9, 9, 9, 9, 9],
|
||||
14: [6, 5, 5, 5, 5, 9, 9, 9, 9, 9, 9, 9, 9],
|
||||
15: [6, 5, 5, 5, 5, 5, 9, 9, 9, 9, 9, 9, 9, 9],
|
||||
16: [6, 5, 5, 5, 5, 5, 5, 9, 9, 9, 9, 9, 9, 9, 9],
|
||||
18: [6, 5, 5, 5, 5, 5, 5, 5, 5, 9, 9, 9, 9, 9, 9, 9, 9],
|
||||
20: [6, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 9, 9, 9, 9, 9, 9, 9, 9],
|
||||
}
|
||||
IND_EXEC = {
|
||||
2: [1],
|
||||
3: [1, 1],
|
||||
4: [1, 1, 1],
|
||||
5: [1, 1, 1, 1],
|
||||
6: [1, 1, 1, 1, 1],
|
||||
7: [1, 1, 1, 1, 1, 1],
|
||||
8: [1, 1, 1, 1, 1, 1, 1],
|
||||
}
|
||||
SNOP_EXEC = {
|
||||
0: 4, 1: 5, 2: 6, 3: 7, 4: 8, 5: 9, 6: 10, 7: 11, 8: 12, 9: 13, 10: 14,
|
||||
11: 19, 12: 20, 13: 21, 14: 22, 15: 23, 16: 24, 17: 25, 18: 26, 19: 27, 20: 28, 21: 29, 22: 30, # +4 extra
|
||||
23: 27, 24: 28, 25: 29, 26: 30, 27: 31, 28: 32, 29: 33, 30: 34, 31: 35,
|
||||
32: 36, 33: 37, 34: 38, 35: 39, 36: 40, 37: 41, 38: 42, 39: 43,
|
||||
40: 44, 41: 45, 42: 46, 43: 47, 44: 48, 45: 49, 46: 50, 47: 51,
|
||||
48: 52, 49: 53, 50: 54, 51: 55, 52: 56, 53: 57, 54: 58, 55: 59,
|
||||
56: 60, 57: 61, 58: 62, 59: 63, 60: 64, 61: 65, 62: 66, 63: 67,
|
||||
}
|
||||
# ************************************ tests ************************************
|
||||
|
||||
class TestVALUChains(unittest.TestCase):
|
||||
"""VALU dependency chains."""
|
||||
def _chain(self, n):
|
||||
def _chain(self, n, expected_issue, expected_exec):
|
||||
instrs = [v_mov_b32_e32(v[0], 1.0)] + [v_add_f32_e32(v[i], v[i-1], v[i-1]) for i in range(1, n)]
|
||||
issue, execd = get_deltas(instrs)
|
||||
self.assertEqual(issue[:n-1], CHAIN_ISSUE[n])
|
||||
self.assertEqual(execd, CHAIN_EXEC[n])
|
||||
self.assertEqual(issue[:n-1], expected_issue)
|
||||
self.assertEqual(execd, expected_exec)
|
||||
|
||||
def test_chain_2(self): self._chain(2)
|
||||
def test_chain_3(self): self._chain(3)
|
||||
def test_chain_4(self): self._chain(4)
|
||||
def test_chain_5(self): self._chain(5)
|
||||
def test_chain_6(self): self._chain(6)
|
||||
def test_chain_7(self): self._chain(7)
|
||||
def test_chain_8(self): self._chain(8)
|
||||
def test_chain_12(self): self._chain(12)
|
||||
def test_chain_14(self): self._chain(14)
|
||||
def test_chain_15(self): self._chain(15) # issue stalls start here
|
||||
def test_chain_16(self): self._chain(16)
|
||||
def test_chain_18(self): self._chain(18)
|
||||
def test_chain_20(self): self._chain(20)
|
||||
def test_chain_2(self):
|
||||
self._chain(2, [1], [6])
|
||||
def test_chain_3(self):
|
||||
self._chain(3, [1, 1], [6, 5])
|
||||
def test_chain_4(self):
|
||||
self._chain(4, [1, 1, 1], [6, 5, 5])
|
||||
def test_chain_5(self):
|
||||
self._chain(5, [1, 1, 1, 1], [6, 5, 5, 9])
|
||||
def test_chain_6(self):
|
||||
self._chain(6, [1, 1, 1, 1, 1], [6, 5, 5, 9, 9])
|
||||
def test_chain_7(self):
|
||||
self._chain(7, [1, 1, 1, 1, 1, 1], [6, 5, 5, 5, 9, 9])
|
||||
def test_chain_8(self):
|
||||
self._chain(8, [1, 1, 1, 1, 1, 1, 1], [6, 5, 5, 5, 9, 9, 9])
|
||||
def test_chain_12(self):
|
||||
self._chain(12, [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], [6, 5, 5, 5, 5, 9, 9, 9, 9, 9, 9])
|
||||
def test_chain_14(self):
|
||||
self._chain(14, [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], [6, 5, 5, 5, 5, 9, 9, 9, 9, 9, 9, 9, 9])
|
||||
def test_chain_15(self): # issue stalls start here
|
||||
self._chain(15, [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3], [6, 5, 5, 5, 5, 5, 9, 9, 9, 9, 9, 9, 9, 9])
|
||||
def test_chain_16(self):
|
||||
self._chain(16, [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 5], [6, 5, 5, 5, 5, 5, 5, 9, 9, 9, 9, 9, 9, 9, 9])
|
||||
def test_chain_18(self):
|
||||
self._chain(18, [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 5, 5, 5], [6, 5, 5, 5, 5, 5, 5, 5, 5, 9, 9, 9, 9, 9, 9, 9, 9])
|
||||
def test_chain_20(self):
|
||||
self._chain(20, [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 5, 5, 5, 5, 5], [6, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 9, 9, 9, 9, 9, 9, 9, 9])
|
||||
|
||||
|
||||
class TestVALUChainsWithNops(unittest.TestCase):
|
||||
@@ -210,43 +172,40 @@ class TestVALUChainsWithNops(unittest.TestCase):
|
||||
|
||||
class TestVALUIndependent(unittest.TestCase):
|
||||
"""Independent VALU instructions."""
|
||||
def _ind(self, n):
|
||||
def _ind(self, n, expected_exec):
|
||||
instrs = [v_mov_b32_e32(v[i], float(i)) for i in range(n)]
|
||||
issue, execd = get_deltas(instrs)
|
||||
self.assertEqual(issue[:n-1], [1]*(n-1))
|
||||
self.assertEqual(execd, IND_EXEC[n])
|
||||
self.assertEqual(execd, expected_exec)
|
||||
|
||||
def test_ind_2(self): self._ind(2)
|
||||
def test_ind_3(self): self._ind(3)
|
||||
def test_ind_4(self): self._ind(4)
|
||||
def test_ind_5(self): self._ind(5)
|
||||
def test_ind_6(self): self._ind(6)
|
||||
def test_ind_7(self): self._ind(7)
|
||||
def test_ind_8(self): self._ind(8)
|
||||
def test_ind_2(self): self._ind(2, [1])
|
||||
def test_ind_3(self): self._ind(3, [1, 1])
|
||||
def test_ind_4(self): self._ind(4, [1, 1, 1])
|
||||
def test_ind_5(self): self._ind(5, [1, 1, 1, 1])
|
||||
def test_ind_6(self): self._ind(6, [1, 1, 1, 1, 1])
|
||||
def test_ind_7(self): self._ind(7, [1, 1, 1, 1, 1, 1])
|
||||
def test_ind_8(self): self._ind(8, [1, 1, 1, 1, 1, 1, 1])
|
||||
|
||||
|
||||
class TestForwardingGap(unittest.TestCase):
|
||||
"""Producer + N independent instructions + consumer - tests forwarding window."""
|
||||
def _last_exec_delta(self, n_gap):
|
||||
def _exec_deltas(self, n_gap):
|
||||
instrs = [v_mov_b32_e32(v[0], 1.0)]
|
||||
instrs += [v_mov_b32_e32(v[10+i], float(i)) for i in range(n_gap)]
|
||||
instrs += [v_add_f32_e32(v[1], v[0], v[0])]
|
||||
_, execd = get_deltas(instrs)
|
||||
return execd[-1]
|
||||
return execd
|
||||
|
||||
def test_gap0(self): self.assertEqual(self._last_exec_delta(0), 6)
|
||||
def test_gap1(self): self.assertEqual(self._last_exec_delta(1), 5)
|
||||
def test_gap2(self): self.assertEqual(self._last_exec_delta(2), 4)
|
||||
def test_gap3(self): self.assertEqual(self._last_exec_delta(3), 3)
|
||||
def test_gap4(self): self.assertEqual(self._last_exec_delta(4), 3)
|
||||
def test_gap5(self): self.assertEqual(self._last_exec_delta(5), 4) # anomaly
|
||||
def test_gap6(self): self.assertEqual(self._last_exec_delta(6), 3)
|
||||
def test_gap7(self): self.assertEqual(self._last_exec_delta(7), 3)
|
||||
def test_gap8(self): self.assertEqual(self._last_exec_delta(8), 3)
|
||||
def test_gap9(self): self.assertEqual(self._last_exec_delta(9), 3)
|
||||
def test_gap10(self): self.assertEqual(self._last_exec_delta(10), 3)
|
||||
def test_gap11(self): self.assertEqual(self._last_exec_delta(11), 3)
|
||||
def test_gap12(self): self.assertEqual(self._last_exec_delta(12), 3)
|
||||
def test_gap0(self): self.assertEqual(self._exec_deltas(0), [6])
|
||||
def test_gap1(self): self.assertEqual(self._exec_deltas(1), [1, 5])
|
||||
def test_gap2(self): self.assertEqual(self._exec_deltas(2), [1, 1, 4])
|
||||
def test_gap3(self): self.assertEqual(self._exec_deltas(3), [1, 1, 1, 3])
|
||||
def test_gap4(self): self.assertEqual(self._exec_deltas(4), [1, 1, 1, 1, 3])
|
||||
def test_gap5(self): self.assertEqual(self._exec_deltas(5), [1, 1, 1, 1, 1, 4]) # anomaly
|
||||
def test_gap6(self): self.assertEqual(self._exec_deltas(6), [1, 1, 1, 1, 1, 1, 3])
|
||||
def test_gap7(self): self.assertEqual(self._exec_deltas(7), [1, 1, 1, 1, 1, 1, 1, 3])
|
||||
def test_gap8(self): self.assertEqual(self._exec_deltas(8), [1, 1, 1, 1, 1, 1, 1, 1, 3])
|
||||
def test_gap9(self): self.assertEqual(self._exec_deltas(9), [1, 1, 1, 1, 1, 1, 1, 1, 1, 3])
|
||||
|
||||
|
||||
class TestVALULatency(unittest.TestCase):
|
||||
@@ -370,27 +329,29 @@ class TestInd3NopMid(unittest.TestCase):
|
||||
|
||||
|
||||
class TestSNopDelay(unittest.TestCase):
|
||||
"""Single s_nop delay between two independent v_movs."""
|
||||
def _test(self, n):
|
||||
"""Single s_nop delay between two independent v_movs.
|
||||
s_nop(n) delays n+1 cycles, plus +4 extra for n in [11, 22].
|
||||
Exec delta = n + 4 (baseline) + 4 (if 11 <= n <= 22)."""
|
||||
def _test(self, n, expected):
|
||||
_, execd = get_deltas([v_mov_b32_e32(v[0], 1.0), s_nop(n), v_mov_b32_e32(v[1], 2.0)])
|
||||
self.assertEqual(execd, [SNOP_EXEC[n]])
|
||||
self.assertEqual(execd, [expected])
|
||||
|
||||
def test_snop_0(self): self._test(0)
|
||||
def test_snop_1(self): self._test(1)
|
||||
def test_snop_2(self): self._test(2)
|
||||
def test_snop_3(self): self._test(3)
|
||||
def test_snop_4(self): self._test(4)
|
||||
def test_snop_5(self): self._test(5)
|
||||
def test_snop_6(self): self._test(6)
|
||||
def test_snop_7(self): self._test(7)
|
||||
def test_snop_10(self): self._test(10)
|
||||
def test_snop_11(self): self._test(11) # +4 extra starts here
|
||||
def test_snop_15(self): self._test(15)
|
||||
def test_snop_22(self): self._test(22) # +4 extra ends here
|
||||
def test_snop_23(self): self._test(23)
|
||||
def test_snop_31(self): self._test(31)
|
||||
def test_snop_32(self): self._test(32)
|
||||
def test_snop_63(self): self._test(63)
|
||||
def test_snop_0(self): self._test(0, 4)
|
||||
def test_snop_1(self): self._test(1, 5)
|
||||
def test_snop_2(self): self._test(2, 6)
|
||||
def test_snop_3(self): self._test(3, 7)
|
||||
def test_snop_4(self): self._test(4, 8)
|
||||
def test_snop_5(self): self._test(5, 9)
|
||||
def test_snop_6(self): self._test(6, 10)
|
||||
def test_snop_7(self): self._test(7, 11)
|
||||
def test_snop_10(self): self._test(10, 14)
|
||||
def test_snop_11(self): self._test(11, 19) # +4 extra starts here
|
||||
def test_snop_15(self): self._test(15, 23)
|
||||
def test_snop_22(self): self._test(22, 30) # +4 extra ends here
|
||||
def test_snop_23(self): self._test(23, 27)
|
||||
def test_snop_31(self): self._test(31, 35)
|
||||
def test_snop_32(self): self._test(32, 36)
|
||||
def test_snop_63(self): self._test(63, 67)
|
||||
|
||||
|
||||
class TestVALUExecWithNop(unittest.TestCase):
|
||||
|
||||
Reference in New Issue
Block a user