147def ChannelArbiterMod(channel_type: Channel, num_inputs: int,
148 output_fifo_depth: int, buffer_inputs: bool,
149 telemetry: bool, mux_pipeline_levels: Optional[int]):
150 """Build a pipelined, list-aware N:1 channel multiplexer module. See the
151 `ChannelArbiter` convenience function for the user-facing entry point and
152 `docs/components/ChannelArbiter.md` for the design."""
154 assert num_inputs >= 2,
"ChannelArbiterMod requires at least two inputs"
155 inner = channel_type.inner_type
159 is_window = isinstance(inner, Window)
161 lowered = inner.lowered_type
162 field_names = [n
for n, _
in lowered.fields]
if isinstance(
163 lowered, StructType)
else None
164 if field_names
is None or "last" not in field_names:
166 "ChannelArbiter can only auto-detect list framing for window types "
167 "whose lowered frame is a struct with a 'last' field; got lowered "
168 f
"type {lowered}. (Serial/union-framed windows are not supported.)")
169 width = lowered.bitwidth
171 width = inner.bitwidth
174 f
"ChannelArbiter requires a fixed-width payload; got {inner}")
180 beat_type = Bits(width)
184 gw = clog2(num_inputs)
188 mux_pipeline_levels))
191 pipe_latency = tree_latency + 1
192 if output_fifo_depth
is not None and \
193 output_fifo_depth <= pipe_latency:
195 f
"output_fifo_depth ({output_fifo_depth}) must be > the pipeline "
196 f
"latency ({pipe_latency})")
198 class ChannelArbiterImpl(Module):
206 inputs = Input(Array(channel_type, num_inputs))
207 output = Output(channel_type)
210 def build(ports) -> None:
214 depth = (pipe_latency + ChannelArbiterImpl._SLACK
215 if output_fifo_depth
is None else output_fifo_depth)
216 cw = max(1, depth.bit_length())
220 def flit_last(typed_sig: Signal) -> BitsSignal:
221 """High when 'typed_sig' is the last flit of its message."""
223 return typed_sig.unwrap()[
"last"]
226 def to_bits(typed_sig: Signal) -> BitsSignal:
227 """Bitcast the payload to raw bits for the datapath."""
229 typed_sig = typed_sig.unwrap()
230 return typed_sig.bitcast(Bits(width))
232 def from_bits(bits: BitsSignal) -> Signal:
233 """Reconstruct the payload from raw bits for the output channel."""
235 return inner.wrap(bits.bitcast(inner.lowered_type))
236 return bits.bitcast(inner)
240 grant = Reg(Bits(gw), clk, rst, name=
"grant")
241 busy = Reg(Bits(1), clk, rst, name=
"busy")
242 rr_ptr = Reg(Bits(gw), clk, rst, name=
"rr_ptr")
243 credit = Reg(UInt(cw), clk, rst, rst_value=depth, name=
"credit")
245 credit_gt0 = credit > UInt(cw)(0)
254 rst_value=(1
if i == 0
else 0),
255 name=f
"grant_oh_{i}")
for i
in range(num_inputs)
257 valids: List[BitsSignal] = []
258 last_bits: List[BitsSignal] = []
259 data_bits: List[BitsSignal] = []
260 for i
in range(num_inputs):
261 chan = ports.inputs[i]
263 chan = chan.buffer(clk, rst, stages=1)
266 ready_i = busy & grant_is[i] & credit_gt0
267 data_i, valid_i = chan.unwrap(ready_i)
268 valids.append(valid_i)
269 last_bits.append(flit_last(data_i))
270 data_bits.append(to_bits(data_i))
273 sel_valid = Mux(grant, *valids)
274 sel_last = Mux(grant, *last_bits)
276 sel_bits = Bits(0)(0)
278 sel_bits =
_select_mux(grant, data_bits, clk, rst, mux_pipeline_levels)
282 launch = busy & sel_valid & credit_gt0
283 msg_end = launch & sel_last
291 out_valid = credit < UInt(cw)(depth)
292 payload_bits = Bits(0)(0)
298 for _
in range(tree_latency):
299 pipe_valid = pipe_valid.reg(clk, rst)
300 pipe_valid = pipe_valid.reg(clk, rst, name=
"pipe_valid")
301 pipe_beat = sel_bits.reg(clk, rst, name=
"pipe_beat")
302 fifo = SeqFIFO(beat_type, depth, clk, rst)
303 fifo.push(pipe_beat, pipe_valid)
304 fifo_pop = Wire(Bits(1),
"arb_pop")
305 out_valid = ~fifo.empty
306 payload_bits = fifo.pop(fifo_pop)
308 out_chan, out_ready = channel_type.wrap(from_bits(payload_bits),
310 ports.output = out_chan
311 pop = out_valid & out_ready
312 if fifo_pop
is not None:
316 next_credit = ((credit + pop.as_uint(cw)).as_uint(cw) -
317 launch.as_uint(cw)).as_uint(cw)
318 credit.assign(next_credit)
323 def round_robin(valid_list: List[BitsSignal], start: BitsSignal,
324 name: str) -> Tuple[BitsSignal, BitsSignal]:
325 """Instantiate a RoundRobinArbiter over `valid_list` (packed into a
326 bit-bus, bit `i` == input `i`) starting from `start`."""
327 valids_vec = BitsSignal.concat(list(reversed(valid_list)))
328 inst = rr_arbiter(valids=valids_vec, start=start, instance_name=name)
329 return inst.winner, inst.any_valid
331 grant_u = grant.as_uint(gw)
332 is_last_idx = grant == Bits(gw)(num_inputs - 1)
333 grant_p1 = Mux(is_last_idx, (grant_u + UInt(gw)(1)).as_bits(gw),
336 winner_idle, any_idle = round_robin(valids, rr_ptr,
"rr_idle")
343 valids_next = [valids[i] & ~grant_is[i]
for i
in range(num_inputs)]
344 winner_next, any_next = round_robin(valids_next, grant_p1,
"rr_next")
346 pick = ~busy & any_idle
347 reend = busy & msg_end
348 grant_if_not_reend = Mux(pick, grant, winner_idle)
349 next_grant = Mux(reend, grant_if_not_reend, winner_next)
350 busy_if_not_reend = Mux(pick, busy, Bits(1)(1))
351 next_busy = Mux(reend, busy_if_not_reend, any_next)
352 next_rr = Mux(reend, rr_ptr, grant_p1)
354 grant.assign(next_grant)
355 busy.assign(next_busy)
356 rr_ptr.assign(next_rr)
362 for i
in range(num_inputs):
363 grant_is[i].assign(next_grant == Bits(gw)(i))
367 Telemetry.report_signal(clk, rst, AppID(
"selectedChannel"), grant)
368 Telemetry.report_signal(clk, rst, AppID(
"busy"), busy)
370 for i
in range(num_inputs):
371 served = Counter(64)(clk=clk,
374 increment=launch & grant_is[i])
375 Telemetry.report_signal(clk, rst, AppID(f
"grantCount_{i}"),
378 total_flits = Counter(64)(clk=clk,
382 Telemetry.report_signal(clk, rst, AppID(
"totalFlits"), total_flits.out)
383 total_msgs = Counter(64)(clk=clk,
387 Telemetry.report_signal(clk, rst, AppID(
"totalMessages"),
389 arb_switches = Counter(64)(clk=clk,
392 increment=pick | (reend & any_next))
393 Telemetry.report_signal(clk, rst, AppID(
"arbSwitches"),
397 cur_len = Counter(32)(clk=clk, rst=rst, clear=msg_end, increment=launch)
398 msg_len = (cur_len.out + UInt(32)(1)).as_uint(32)
399 max_len = Reg(UInt(32), clk, rst, rst_value=0, name=
"max_list_len")
400 is_new_max = msg_end & (msg_len > max_len)
401 max_len.assign(Mux(is_new_max, max_len, msg_len))
402 Telemetry.report_signal(clk, rst, AppID(
"maxListLen"), max_len)
405 occ = (UInt(cw)(depth) - credit).as_uint(cw)
406 inflight_hw = Reg(UInt(cw), clk, rst, rst_value=0, name=
"inflight_hw")
407 is_new_hw = occ > inflight_hw
408 inflight_hw.assign(Mux(is_new_hw, inflight_hw, occ))
409 Telemetry.report_signal(clk, rst, AppID(
"inflightHighWater"),
412 return ChannelArbiterImpl