CIRCT 23.0.0git
Loading...
Searching...
No Matches
test_channel_arbiter.py
Go to the documentation of this file.
1# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
2# See https://llvm.org/LICENSE.txt for license information.
3# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
4"""Cosim integration tests for `esiaccel.components.ChannelArbiter`.
5
6Runs under Verilator via the `@cosim_test` harness. Two DUTs (built by
7`hw/channel_arbiter.py`) are exercised:
8
9 * `arbiter_test` / `arbiter_test_odd`: host-driven single-flit multiplexers
10 with a power-of-two ('balanced') and non-power-of-two ('unbalanced') input
11 count. The host writes distinct tagged values to each input and checks that
12 every value comes out of the single output exactly once (no loss /
13 duplication) and that every input is served.
14
15 * `list_test`: two in-hardware list-window producers contend for the
16 arbiter; a hardware checker verifies message contiguity and streams a
17 per-message report back to the host.
18
19 * `token_test`: several in-hardware zero-width (`i0`) token producers, each
20 emitting a fixed number of tokens, contend for the arbiter. A hardware
21 counter reports a running ordinal per delivered token so the host can
22 confirm every token is delivered exactly once and every producer is served.
23"""
24
25from __future__ import annotations
26
27from collections import Counter
28from pathlib import Path
29
30import esiaccel
31from esiaccel.accelerator import AcceleratorConnection
32from esiaccel.cosim.pytest import cosim_test
33
34HW_DIR = Path(__file__).resolve().parent / "hw"
35
36NUM_INPUTS = 4 # power-of-two ("balanced") input count.
37ODD_NUM_INPUTS = 3 # non-power-of-two ("unbalanced") input count.
38PIPE_NUM_INPUTS = 6 # input count for the pipelined-mux-tree variant.
39TOKEN_NUM_INPUTS = 5 # input count for the zero-width (i0) token variant.
40TOKENS_PER_INPUT = 8 # tokens each producer emits in the token test.
41
42
43def _check_mux(conn: AcceleratorConnection, dut_name: str,
44 num_inputs: int) -> None:
45 """Drive an N-input host mux and check every value is delivered exactly
46 once and every input is served."""
47 acc = conn.build_accelerator()
48 dut = acc.children[esiaccel.AppID(dut_name)]
49 ins = [dut.ports[esiaccel.AppID(f"in_{i}")] for i in range(num_inputs)]
50 out = dut.ports[esiaccel.AppID("out")]
51 for p in ins:
52 p.connect()
53 out.connect()
54
55 # The value encodes its source input in the upper bits (i << 16) and a round
56 # counter in the low bits, so the received multiset uniquely identifies every message.
57 rounds = 6
58 writes = [
59 ((i << 16) | r, ins[i]) for r in range(rounds) for i in range(num_inputs)
60 ]
61
62 # Keep the number of in-flight (written-but-not-yet-read) messages strictly
63 # below the output FIFO depth. This keeps a couple of inputs backlogged at
64 # once (so the round-robin arbiter has to choose between them) while
65 # avoiding the write-a-burst-before-reading deadlock.
66 max_in_flight = 2
67 sent: list[int] = []
68 recv: list[int] = []
69 wi = 0
70 while len(recv) < len(writes):
71 while wi < len(writes) and (len(sent) - len(recv)) < max_in_flight:
72 value, port = writes[wi]
73 port.write(value)
74 sent.append(value)
75 wi += 1
76 recv.append(out.read().result())
77
78 assert sorted(recv) == sorted(sent), \
79 "arbiter dropped, duplicated or corrupted a value"
80
81 # Every input (decoded by v >> 16) is served exactly `rounds` times.
82 by_src = Counter(v >> 16 for v in recv)
83 for i in range(num_inputs):
84 assert by_src[i] == rounds, \
85 f"input {i} served {by_src[i]} times, expected {rounds}"
86
87
88@cosim_test(HW_DIR / "channel_arbiter.py")
90
91 def test_mux_correctness(self, conn: AcceleratorConnection) -> None:
92 """Balanced (power-of-two) input count: every value appears once."""
93 _check_mux(conn, "arbiter_test", NUM_INPUTS)
94
96 conn: AcceleratorConnection) -> None:
97 """Unbalanced (non-power-of-two) input count: the array-indexed mux over
98 N < 2**clog2(N) elements and the round-robin wrap still deliver every
99 value exactly once."""
100 _check_mux(conn, "arbiter_test_odd", ODD_NUM_INPUTS)
101
102 def test_mux_correctness_pipelined(self, conn: AcceleratorConnection) -> None:
103 """Pipelined selection mux tree: the multi-cycle mux latency (absorbed by a
104 deeper output FIFO + credit counter) must still deliver every value exactly
105 once."""
106 _check_mux(conn, "arbiter_test_pipe", PIPE_NUM_INPUTS)
107
108 def test_list_contiguity(self, conn: AcceleratorConnection) -> None:
109 """Contending multi-flit list messages are never interleaved."""
110 acc = conn.build_accelerator()
111 dut = acc.children[esiaccel.AppID("list_test")]
112 report = dut.ports[esiaccel.AppID("report")]
113 report.connect()
114
115 seen_src: set[int] = set()
116 num_reports = 40
117 for _ in range(num_reports):
118 value = report.read().result()
119 err = (value >> 24) & 0x1
120 src = (value >> 16) & 0xff
121 assert err == 0, \
122 f"hardware detected interleaved list flits (report {value:#010x})"
123 seen_src.add(src)
124
125 # Both contending producers (src 1 and src 2) must get through.
126 assert seen_src == {1, 2}, \
127 f"expected both sources to be served, saw {sorted(seen_src)}"
128
129 def test_token_conservation(self, conn: AcceleratorConnection) -> None:
130 """Zero-width (`i0`) token payloads: the credit-counter-as-buffer path
131 delivers every token exactly once (no loss/duplication/reorder) and serves
132 every producer -- the total is only reachable if no producer is starved."""
133 acc = conn.build_accelerator()
134 dut = acc.children[esiaccel.AppID("token_test")]
135 report = dut.ports[esiaccel.AppID("token_report")]
136 report.connect()
137
138 total = TOKEN_NUM_INPUTS * TOKENS_PER_INPUT
139 for expected in range(1, total + 1):
140 value = report.read().result()
141 assert value == expected, \
142 f"token {expected} arrived as {value} (loss / duplication / reorder)"
static mlir::Operation * resolve(Context &context, mlir::SymbolRefAttr sym)
None test_mux_correctness_pipelined(self, AcceleratorConnection conn)
None test_mux_correctness_unbalanced(self, AcceleratorConnection conn)
None _check_mux(AcceleratorConnection conn, str dut_name, int num_inputs)