77 """Construct the ESI header MMIO adhering to the MMIO layout specified in
78 the ChannelMMIO service implementation."""
82 read = Input(esi.MMIO.read_write.type)
85 reset_request = Output(Bits(1))
91 data_chan_wire = Wire(Channel(esi.MMIODataType))
92 input_bundles = ports.read.unpack(data=data_chan_wire)
93 cmd_chan = input_bundles[
'cmd']
98 cmd_ready = Wire(Bits(1))
99 s1_to_s2_xact = Wire(Bits(1))
100 cmd_raw, cmd_valid = cmd_chan.unwrap(cmd_ready)
103 s1_load = cmd_valid & cmd_ready
104 cmd = cmd_raw.reg(clk, rst, ce=s1_load, name=
"cmd")
105 s1_valid = ControlReg(clk,
108 resets=[s1_to_s2_xact],
111 cmd_ready.assign(~s1_valid)
113 address_words = cmd.offset.as_bits()[3:]
114 slot = address_words[:3]
116 cycles = Counter(64)(clk=ports.clk,
119 increment=Bits(1)(1),
120 instance_name=
"cycle_counter")
123 core_freq = System.current().core_freq
124 if core_freq
is None:
126 header = Array(Bits(64), 8)([
132 cycles.out.as_bits(),
136 header.name =
"header"
139 s2_valid = Wire(Bits(1))
140 data_chan_ready = Wire(Bits(1))
141 s2_xact = s2_valid & data_chan_ready
143 s1_to_s2_xact.assign(s1_valid & ~s2_valid)
145 header_out = header[slot].reg(clk=clk,
152 asserts=[s1_to_s2_xact],
154 name=
"header_out_valid"))
156 data_chan, data_chan_ready_sig = Channel(esi.MMIODataType).
wrap(
157 header_out, s2_valid)
158 data_chan_wire.assign(data_chan)
159 data_chan_ready.assign(data_chan_ready_sig)
165 reset_detect = (cmd.write & (slot == Bits(3)(7)) &
166 (cmd.data == Bits(64)(ResetMagicNumber)))
167 ports.reset_request = reset_detect & s1_to_s2_xact
174 data_type: Type, num_outs: int,
175 next_sel_width: int) -> type[
"ChannelDemuxNImpl"]:
176 """N-way channel demultiplexer for valid/ready signaling. Contains
177 valid/ready registers on the output channels. The selection signal is now
178 embedded in the input channel payload as a struct {sel, data}. Input
179 signals ready when the selected output register is empty."""
181 assert num_outs >= 1,
"num_outs must be at least 1."
183 class ChannelDemuxNImpl(Module):
188 InPayloadType = StructType([
189 (
"sel", Bits(clog2(num_outs))),
190 (
"next_sel", Bits(next_sel_width)),
193 inp = Input(Channel(InPayloadType))
194 OutPayloadType = StructType([
195 (
"next_sel", Bits(next_sel_width)),
199 for i
in range(num_outs):
200 locals()[f
"output_{i}"] = Output(Channel(OutPayloadType))
203 def generate(ports) -> None:
208 sel_width = clog2(num_outs)
211 input_ready = Wire(Bits(1), name=
"input_ready")
212 in_payload, in_valid = ports.inp.unwrap(input_ready)
213 in_sel = in_payload.sel
214 in_next_sel = in_payload.next_sel
215 in_data = in_payload.data
220 valid_regs: List[BitsSignal] = []
221 selected_valid_expr = Bits(1)(0)
223 for i
in range(num_outs):
225 will_write = Wire(Bits(1), name=f
"will_write_{i}")
226 write_cond = (in_valid & input_ready & (in_sel == Bits(sel_width)(i)))
227 will_write.assign(write_cond)
230 out_msg_reg = ChannelDemuxNImpl.OutPayloadType({
231 "next_sel": in_next_sel,
233 }).reg(clk=clk, rst=rst, ce=will_write, name=f
"out{i}_msg_reg")
236 consume = Wire(Bits(1), name=f
"consume_{i}")
237 valid_reg = ControlReg(
240 asserts=[will_write],
242 name=f
"out{i}_valid_reg",
244 valid_regs.append(valid_reg)
247 ch_sig, ch_ready = Channel(ChannelDemuxNImpl.OutPayloadType).
wrap(
248 out_msg_reg, valid_reg)
249 setattr(ports, f
"output_{i}", ch_sig)
250 consume.assign(valid_reg & ch_ready)
253 selected_valid_expr = selected_valid_expr | (
254 (in_sel == Bits(sel_width)(i)) & valid_reg)
257 input_ready.assign(selected_valid_expr ^ Bits(1)(1))
259 def get_out(self, index: int) -> ChannelSignal:
260 return getattr(self, f
"output_{index}")
262 return ChannelDemuxNImpl
267 data_type: Type, num_outs: int,
268 branching_factor_log2: int) -> type[
"ChannelDemuxTree"]:
269 """Pipelined N-way channel demultiplexer for valid/ready signaling. This
270 implementation uses a tree structure of
271 ChannelDemuxN_HalfStage_ReadyBlocking modules to reduce fanout pressure.
272 Supports maximum half-throughput to save complexity and area.
275 root_sel_width = clog2(num_outs)
277 num_outs = 2**root_sel_width
278 sel_width = branching_factor_log2
279 fanout = 2**sel_width
281 class ChannelDemuxTree(Module):
285 InPayloadType = StructType([
286 (
"sel", Bits(clog2(num_outs))),
289 inp = Input(Channel(InPayloadType))
292 for i
in range(num_outs):
293 locals()[f
"output_{i}"] = Output(Channel(data_type))
296 def build(ports) -> None:
297 assert branching_factor_log2 > 0
300 setattr(ports,
"output_0", ports.inp.transform(
lambda p: p.data))
303 def payload_type(sel_width: int, next_sel_width: int) -> Type:
305 (
"sel", Bits(sel_width)),
306 (
"next_sel", Bits(next_sel_width)),
310 def next_sel_width_calc(curr_sel_width) -> int:
311 return max(curr_sel_width - sel_width, 0)
313 def payload_next(curr_msg: StructSignal) -> StructSignal:
314 """Given current level payload, produce next level payload by
315 stripping off the top selection bits."""
317 next_sel_width = next_sel_width_calc(curr_msg.next_sel.type.width)
318 curr_sel_width = curr_msg.next_sel.type.width
319 new_sel_width = min(curr_sel_width, sel_width)
325 "sel": (curr_msg.next_sel[next_sel_width:]
326 if curr_sel_width > 0
else Bits(0)(0)),
327 "next_sel": (curr_msg.next_sel[:next_sel_width]
328 if next_sel_width > 0
else Bits(0)(0)),
329 "data": curr_msg.data,
332 current_channels: List[ChannelSignal] = [
333 ports.inp.transform(
lambda m: payload_type(0, root_sel_width)({
340 curr_sel_width = root_sel_width
342 while len(current_channels) < num_outs:
343 next_level: List[ChannelSignal] = []
344 level_num_outs = min(2**curr_sel_width, fanout)
345 for i, c
in enumerate(current_channels):
348 num_outs=level_num_outs,
349 next_sel_width=next_sel_width_calc(curr_sel_width),
353 inp=c.transform(payload_next),
354 instance_name=f
"demux_l{level}_i{i}",
356 for j
in range(level_num_outs):
357 next_level.append(dmux.get_out(j))
358 current_channels = next_level
359 curr_sel_width -= sel_width
362 for i
in range(num_outs):
367 current_channels[i].transform(
lambda p: p.data),
370 def get_out(self, index: int) -> ChannelSignal:
371 return getattr(self, f
"output_{index}")
373 return ChannelDemuxTree
377 regions: Tuple[Tuple[int, Optional[int]],
378 ...]) -> type[
"MMIOPrefixRouterImpl"]:
379 """Build a pipelined address-prefix tree which routes MMIO commands.
381 Each ``(base, size)`` must describe a disjoint, power-of-two-aligned block.
382 The last region may pass ``size=None`` to claim every address at or above its
383 base; the manifest uses this since its size isn't known during generation.
384 Internal nodes test one address bit and register both outgoing paths. Leaves
385 replace the global address with the block-local low bits, so no subtractor is
386 needed (the open-ended region is the one exception).
388 For four 0x100-byte regions at 0x0/0x100/0x200/0x300 plus an open-ended
389 region at 0x400: the open-ended region is peeled off first, then each level
390 splits the remaining candidates in half, so depth is ``O(log N)`` rather than
391 one level per region::
393 cmd ──▶ addr[31:10]≠0 ──1──▶ @0x400 (open ended, offset -= 0x400)
405 @0x0 @0x100 @0x200 @0x300
407 The four leaves all take their client-local offset as ``addr[7:0]``.
410 assert len(regions) > 1,
"MMIO routing requires at least two regions"
414 open_ended_base: Optional[int] =
None
415 sized_regions = regions
416 if regions[-1][1]
is None:
417 open_ended_base = regions[-1][0]
418 assert open_ended_base > 0
and \
419 open_ended_base & (open_ended_base - 1) == 0, \
420 "an open-ended MMIO region must start at a power-of-two address"
421 sized_regions = regions[:-1]
423 for base, size
in sized_regions:
424 assert size > 0
and size & (size - 1) == 0, \
425 "MMIO region sizes must be powers of two"
426 assert base % size == 0,
"MMIO regions must be aligned to their size"
428 a_base + a_size <= b_base
429 for (a_base, a_size), (b_base, _)
in zip(sized_regions, regions[1:])), \
430 "MMIO regions must be sorted and disjoint"
432 entries = [(idx, base, size)
for idx, (base, size)
in enumerate(sized_regions)
435 def build_tree(node_entries):
436 if len(node_entries) == 1:
437 return node_entries[0][0]
439 lowest_fixed_bit = max(size.bit_length() - 1
for _, _, size
in node_entries)
441 for bit
in range(31, lowest_fixed_bit - 1, -1):
442 zeros = [entry
for entry
in node_entries
if not (entry[1] >> bit) & 1]
443 if zeros
and len(zeros) != len(node_entries):
445 (abs(len(node_entries) - 2 * len(zeros)), -bit, bit, zeros))
446 assert candidates,
"unable to distinguish disjoint MMIO regions"
447 _, _, bit, zeros = min(candidates)
448 zero_indices = {entry[0]
for entry
in zeros}
449 ones = [entry
for entry
in node_entries
if entry[0]
not in zero_indices]
450 return bit, build_tree(zeros), build_tree(ones)
452 tree = build_tree(entries)
454 class MMIOPrefixRouterImpl(Module):
457 inp = Input(Channel(esi.MMIOReadWriteCmdType))
458 for idx
in range(len(regions)):
459 locals()[f
"output_{idx}"] = Output(Channel(esi.MMIOReadWriteCmdType))
463 """Route through one registered address-bit branch per tree level."""
465 def new_demux(command_channel: ChannelSignal, select, name: str):
469 demux_input = command_channel.transform(
470 lambda cmd, _sel=select, _type=Demux.InPayloadType: _type({
472 "next_sel": Bits(0)(0),
475 return Demux(clk=ports.clk,
480 def route(node, command_channel: ChannelSignal, path: str):
481 if isinstance(node, int):
482 _, size = regions[node]
483 local_width = size.bit_length() - 1
486 local_offset = cmd.offset.as_bits()[:local_width].pad_or_truncate(
488 return esi.MMIOReadWriteCmdType({
490 "offset": local_offset,
494 setattr(ports, f
"output_{node}", command_channel.transform(localize))
497 bit, zero_node, one_node = node
498 demux = new_demux(command_channel,
499 lambda cmd, _bit=bit: cmd.offset.as_bits()[_bit],
500 f
"prefix_{path}_bit{bit}")
502 demux.get_out(0).transform(
lambda p: p.data), path +
"0")
504 demux.get_out(1).transform(
lambda p: p.data), path +
"1")
506 client_channel = ports.inp
507 if open_ended_base
is not None:
511 split_bit = open_ended_base.bit_length() - 1
513 ports.inp,
lambda cmd: cmd.offset.as_bits()[split_bit:].or_reduce(),
514 f
"prefix_above_bit{split_bit}")
516 def localize_open_ended(payload):
518 local_offset = (cmd.offset - UInt(32)(open_ended_base)).as_uint(32)
519 return esi.MMIOReadWriteCmdType({
521 "offset": local_offset,
525 setattr(ports, f
"output_{len(regions) - 1}",
526 split.get_out(1).transform(localize_open_ended))
527 client_channel = split.get_out(0).transform(
lambda p: p.data)
529 route(tree, client_channel,
"")
531 def get_out(self, index: int) -> ChannelSignal:
532 return getattr(self, f
"output_{index}")
534 return MMIOPrefixRouterImpl
1008 output_bitwidth: int) -> type[
"ConcatReadGearboxImpl"]:
1009 """Concatenate ``ceil(OUT/IN)`` consecutive engine words into one client
1010 element wider than the word (``OUT > IN``). Serves single-message reads (any
1011 ``OUT > IN``; the low ``OUT`` bits of the concatenation are the element) and
1012 contiguous list reads whose element is a whole number of output_bitwidth
1013 (``OUT % IN == 0``, so elements never straddle). ``valid_bytes`` is unused:
1014 such lists have no partial words and a single element is one flit. Straddling
1015 lists use `ShiftReadGearbox`."""
1017 if input_bitwidth <= 0
or input_bitwidth % 8 != 0:
1018 raise ValueError(
"engine word width must be a positive multiple of 8 bits")
1019 if output_bitwidth <= input_bitwidth:
1020 raise ValueError(
"ConcatReadGearbox requires output > input")
1022 in_bytes = input_bitwidth // 8
1023 vb_width = clog2(in_bytes)
1025 class ConcatReadGearboxImpl(Module):
1030 (
"tag", esi.HostMem.TagType),
1031 (
"data", Bits(input_bitwidth)),
1032 (
"valid_bytes", UInt(vb_width)),
1035 out = OutputChannel(
1037 (
"tag", esi.HostMem.TagType),
1038 (
"data", Bits(output_bitwidth)),
1044 ready_for_upstream = Wire(Bits(1), name=
"ready_for_upstream")
1047 in_reg = ports.in_.buffer(ports.clk, ports.rst, stages=1)
1048 up, upstream_valid = in_reg.unwrap(ready_for_upstream)
1049 upstream_data = up.data
1050 upstream_last = up.last
1051 upstream_xact = ready_for_upstream & upstream_valid
1056 chunks = ceil(output_bitwidth / input_bitwidth)
1057 counter_width = clog2(chunks)
1058 reg_ces = [Wire(Bits(1))
for _
in range(chunks)]
1060 upstream_data.reg(ports.clk,
1063 name=f
"chunk_reg_{idx}")
for idx
in range(chunks)
1065 client_data_bits = BitsSignal.concat(reversed(regs))[:output_bitwidth]
1073 counter = Wire(UInt(counter_width), name=
"chunk_counter")
1074 client_xact = Wire(Bits(1))
1075 set_client_valid = counter == UInt(counter_width)(chunks - 1)
1077 Counter(counter_width)(clk=ports.clk,
1079 clear=(upstream_xact & set_client_valid) |
1080 (client_xact & ~upstream_xact),
1081 increment=upstream_xact,
1082 instance_name=
"chunk_counter").out)
1083 client_valid = ControlReg(ports.clk, ports.rst,
1084 [set_client_valid & upstream_xact],
1086 for idx, reg_ce
in enumerate(reg_ces):
1087 reg_ce.assign(upstream_xact & (counter == UInt(counter_width)(idx)))
1089 client_last = upstream_last.reg(ports.clk,
1093 tag_reg = up.tag.reg(ports.clk,
1098 client_channel, client_ready = ConcatReadGearboxImpl.out.type.wrap(
1101 "data": client_data_bits,
1102 "last": client_last,
1104 client_xact.assign(client_valid & client_ready)
1105 ready_for_upstream.assign(~client_valid | client_ready)
1106 ports.out = client_channel
1108 return ConcatReadGearboxImpl
1113 output_bitwidth: int) -> type[
"DepackReadGearboxImpl"]:
1114 """Unpack a byte-aligned element that divides the engine word
1115 (``OUT % 8 == 0`` and ``IN % OUT == 0``) from a contiguous list response. Each
1116 word holds ``IN/OUT`` gap-free elements that never straddle, so a counter
1117 drives a parts:1 element mux -- no shifter (e.g. 32b/64b, 64b/256b).
1118 ``valid_bytes`` locates the last element in the burst's (possibly partial)
1119 final word. Straddling relationships use `ShiftReadGearbox`."""
1121 if input_bitwidth % 8 != 0:
1122 raise ValueError(
"engine word width must be a multiple of 8 bits")
1123 if output_bitwidth == 0
or output_bitwidth % 8 != 0 \
1124 or input_bitwidth % output_bitwidth != 0:
1126 "DepackReadGearbox requires a byte-aligned element that divides the "
1129 in_bytes = input_bitwidth // 8
1131 vb_width = clog2(in_bytes)
1132 count_width = clog2(in_bytes + 1)
1133 parts = input_bitwidth // output_bitwidth
1134 elem_bytes = output_bitwidth // 8
1136 class DepackReadGearboxImpl(Module):
1141 (
"tag", esi.HostMem.TagType),
1142 (
"data", Bits(input_bitwidth)),
1143 (
"valid_bytes", UInt(vb_width)),
1146 out = OutputChannel(
1148 (
"tag", esi.HostMem.TagType),
1149 (
"data", Bits(output_bitwidth)),
1155 client_ready = Wire(Bits(1), name=
"client_ready")
1156 up_ready = Wire(Bits(1), name=
"up_ready")
1159 in_reg = ports.in_.buffer(ports.clk, ports.rst, stages=1)
1160 up, up_valid = in_reg.unwrap(up_ready)
1161 client_xact = up_valid & client_ready
1165 last_in_word = Bits(1)(1)
1166 client_data = up.data
1168 idx_width = clog2(parts)
1169 idx = Reg(UInt(idx_width),
1178 real_valid_bytes = (up.valid_bytes + UInt(1)(1)).as_uint(count_width)
1179 consumed = ((idx + UInt(1)(1)) *
1180 UInt(count_width)(elem_bytes)).as_uint(count_width)
1181 last_in_word = consumed == real_valid_bytes
1183 word_parts = Array(Bits(output_bitwidth), parts)([
1184 up.data[k * output_bitwidth:(k + 1) * output_bitwidth]
1185 for k
in range(parts)
1187 client_data = word_parts[idx]
1189 Mux(last_in_word, (idx + UInt(1)(1)).as_uint(idx_width),
1190 UInt(idx_width)(0)))
1193 up_ready.assign(client_xact & last_in_word)
1194 client_channel, client_ready_sig = DepackReadGearboxImpl.out.type.wrap(
1197 "data": client_data,
1198 "last": (up.last & last_in_word).as_bits(),
1200 client_ready.assign(client_ready_sig)
1201 ports.out = client_channel
1203 return DepackReadGearboxImpl
1208 output_bitwidth: int) -> type[
"ShiftReadGearboxImpl"]:
1209 """Universal fallback: unpack a contiguous, byte-packed element stream (a
1210 `read_list` response) for ANY ``(input_bitwidth, output_bitwidth)`` pair.
1212 Elements are packed at their natural byte stride ``stride = ceil(OUT/8)``
1213 bytes, so element k begins at wire bit ``k*stride*8`` and, in general,
1214 straddles engine-word boundaries at an arbitrary bit offset. A byte-addressed
1215 shift-register accumulator realigns each element across words. This is correct
1216 for every width relationship; `SliceReadGearbox`, `ConcatReadGearbox` and
1217 `DepackReadGearbox` are optimizations that avoid this barrel shifter for the
1218 regular (non-straddling) cases.
1220 Each input word carries ``valid_bytes`` (how many of its bytes are real) and
1221 ``last``. Both are framed to one whole `read_list` request rather than to the
1222 transport: `HostMemReadReqSplitter` drops the per-chunk framing of the reads
1223 it issues and re-derives these from the request's total length, so only the
1224 request's final word is ever partial. That length is ``num_elements *
1225 stride``, so tracking real bytes lets the gearbox emit exactly the right
1226 elements and place the list-terminating ``last`` on the final one -- no
1227 padding element is ever emitted."""
1229 if input_bitwidth % 8 != 0:
1230 raise ValueError(
"engine word width must be a multiple of 8 bits")
1231 if output_bitwidth <= 0:
1232 raise ValueError(
"client element width must be positive")
1233 in_bytes = input_bitwidth // 8
1234 stride_bytes = (output_bitwidth + 7) // 8
1235 stride_bits = stride_bytes * 8
1237 buf_bytes = stride_bytes + in_bytes
1238 buf_bits = buf_bytes * 8
1240 vb_width = clog2(in_bytes)
1241 cnt_width = clog2(buf_bytes + 1)
1244 offset_width = clog2(stride_bytes + 1)
1246 class ShiftReadGearboxImpl(Module):
1251 (
"tag", esi.HostMem.TagType),
1252 (
"data", Bits(input_bitwidth)),
1253 (
"valid_bytes", UInt(vb_width)),
1256 out = OutputChannel(
1258 (
"tag", esi.HostMem.TagType),
1259 (
"data", Bits(output_bitwidth)),
1265 client_ready = Wire(Bits(1), name=
"client_ready")
1266 up_ready = Wire(Bits(1), name=
"up_ready")
1269 in_reg = ports.in_.buffer(ports.clk, ports.rst, stages=1)
1270 up, up_valid = in_reg.unwrap(up_ready)
1272 from pycde.circt.dialects
import comb
1276 buffer = Reg(Bits(buf_bits),
1281 count = Reg(UInt(cnt_width),
1286 saw_last = Wire(Bits(1), name=
"saw_last")
1291 has_room = count <= UInt(cnt_width)(buf_bytes - in_bytes)
1292 up_ready.assign(has_room & ~saw_last)
1293 up_xact = up_ready & up_valid
1296 client_valid = count >= UInt(cnt_width)(stride_bytes)
1297 client_xact = client_valid & client_ready
1303 added = Mux(up_xact,
1304 UInt(cnt_width)(0), (up.valid_bytes.as_uint(cnt_width) +
1305 UInt(1)(1)).as_uint(cnt_width))
1306 after_add = (count + added).as_uint(cnt_width)
1307 after_emit = (after_add -
1308 UInt(cnt_width)(stride_bytes)).as_uint(cnt_width)
1309 set_saw_last = (up_xact & up.last).as_bits()
1310 is_final_slot = (after_emit == UInt(cnt_width)(0))
1311 burst_ending = saw_last | set_saw_last
1312 client_last = client_valid & burst_ending & is_final_slot
1313 final_emit = client_xact & client_last
1321 append_off = count.as_bits()[0:offset_width]
1322 shamt = BitsSignal.concat([append_off,
1323 Bits(3)(0)]).pad_or_truncate(buf_bits)
1324 word_ext = up.data.pad_or_truncate(buf_bits)
1325 shifted_word = BitsSignal(
1326 comb.ShlOp(word_ext.value, shamt.value).result, Bits(buf_bits))
1327 appended = buffer | Mux(up_xact, Bits(buf_bits)(0), shifted_word)
1328 drained = appended[stride_bits:buf_bits].pad_or_truncate(buf_bits)
1330 Mux(final_emit, Mux(client_xact, appended, drained),
1335 count.assign(Mux(client_xact, after_add, after_emit))
1338 ControlReg(ports.clk, ports.rst, [set_saw_last], [final_emit]))
1340 tag_reg = up.tag.reg(ports.clk, ports.rst, ce=up_xact, name=
"tag_reg")
1341 client_channel, client_ready_sig = ShiftReadGearboxImpl.out.type.wrap(
1344 "data": buffer[0:output_bitwidth],
1345 "last": client_last,
1347 client_ready.assign(client_ready_sig)
1348 ports.out = client_channel
1350 return ShiftReadGearboxImpl
1391 resp_channel_type: Channel, max_chunk_bytes: int):
1392 """Split oversized host memory read requests into request-sized chunks before
1393 arbitration and reassemble the per-chunk responses into a single logical
1396 A burst read (`read_list`) can request many more bytes than a single upstream
1397 read request can carry. This module breaks such a request into
1398 `max_chunk_bytes`-sized (word-aligned) chunks addressed sequentially from the
1399 base. Splitting here -- *before* the requests
1400 are arbitrated onto the shared upstream read channel -- lets each client's
1401 chunks interleave with other clients' requests, so one large burst does not
1402 monopolize host memory bandwidth.
1404 On the response path the per-chunk end-of-list markers are dropped and a
1405 single burst-final `last` is re-derived from the total transfer length, so the
1406 gearbox and client see one contiguous response stream identical to an unsplit
1409 Only one logical request is in flight at a time (matching the read processor's
1410 one-outstanding-transaction-per-client model): a new request is not accepted
1411 until the current burst's chunks have all been issued and its responses have
1412 fully drained. This will be a performance limiter.
1413 TODO: make this able to issue >1 one read at a time.
1415 req_channel_type: channel of the upstream read request {address, length
1417 resp_channel_type: channel of the upstream response {tag, data, last}.
1418 max_chunk_bytes: largest per-chunk byte count; must be > 0 and a multiple of
1419 the response word size.
1421 assert max_chunk_bytes > 0
1423 req_struct = req_channel_type.inner_type
1424 resp_struct = resp_channel_type.inner_type
1425 req_fields = dict(req_struct.fields)
1426 addr_width = req_fields[
"address"].bitwidth
1427 length_width = req_fields[
"length"].bitwidth
1428 tag_type = req_fields[
"tag"]
1429 word_bytes = dict(resp_struct.fields)[
"data"].bitwidth // 8
1430 word_shift = clog2(word_bytes)
1431 words_width = length_width - word_shift
1435 vb_width = clog2(word_bytes)
1436 resp_fields = dict(resp_struct.fields)
1437 resp_out_struct = StructType([
1438 (
"tag", resp_fields[
"tag"]),
1439 (
"data", resp_fields[
"data"]),
1440 (
"valid_bytes", UInt(vb_width)),
1443 resp_out_channel_type = Channel(resp_out_struct)
1445 class HostMemReadReqSplitterImpl(Module):
1448 req_in = Input(req_channel_type)
1449 req_out = Output(req_channel_type)
1450 resp_in = Input(resp_channel_type)
1451 resp_out = Output(resp_out_channel_type)
1460 emit_busy = Wire(Bits(1), name=
"emit_busy")
1461 resp_busy = Wire(Bits(1), name=
"resp_busy")
1462 cur_addr = Wire(UInt(addr_width), name=
"cur_addr")
1463 remaining = Wire(UInt(length_width), name=
"remaining")
1464 tag_reg = Wire(tag_type, name=
"tag_reg")
1465 words_left = Wire(UInt(words_width), name=
"words_left")
1467 idle = (~emit_busy) & (~resp_busy)
1470 req_ready = Wire(Bits(1))
1471 req_payload, req_valid = ports.req_in.unwrap(req_ready)
1472 accept = idle & req_valid
1473 req_ready.assign(accept)
1475 max_chunk = UInt(length_width)(max_chunk_bytes)
1476 chunk_len = Mux(remaining > max_chunk, remaining, max_chunk)
1477 last_chunk = remaining <= max_chunk
1485 chunk_len_out = chunk_len
1487 chunk_words = (chunk_len + UInt(length_width)(word_bytes - 1)
1488 ).as_bits()[word_shift:].as_uint(words_width)
1489 chunk_len_out = BitsSignal.concat(
1490 [chunk_words.as_bits(), Bits(word_shift)(0)]).as_uint(length_width)
1492 req_out_ch, req_out_ready = req_channel_type.wrap(
1494 "address": cur_addr,
1495 "length": chunk_len_out,
1498 ports.req_out = req_out_ch
1499 chunk_xact = emit_busy & req_out_ready
1503 rst, [accept], [chunk_xact & last_chunk],
1504 name=
"emit_busy_reg"))
1507 cur_addr_incr = (cur_addr +
1508 chunk_len.as_uint(addr_width)).as_uint(addr_width)
1510 Mux(accept, Mux(chunk_xact, cur_addr, cur_addr_incr),
1511 req_payload.address).reg(clk,
1514 ce=accept | chunk_xact,
1515 name=
"cur_addr_reg"))
1518 remaining_dec = (remaining - chunk_len).as_uint(length_width)
1520 Mux(accept, Mux(chunk_xact, remaining, remaining_dec),
1521 req_payload.length).reg(clk,
1524 ce=accept | chunk_xact,
1525 name=
"remaining_reg"))
1527 tag_reg.assign(req_payload.tag.reg(clk, rst, ce=accept, name=
"tag_reg_r"))
1532 total_words = ((req_payload.length + UInt(length_width)(word_bytes - 1)
1533 ).as_bits()[word_shift:]).as_uint(words_width)
1535 words_before_last = (total_words -
1536 UInt(words_width)(1)).as_uint(words_width)
1537 bytes_before_last = BitsSignal.concat(
1538 [words_before_last.as_bits(),
1539 Bits(word_shift)(0)]).as_uint(length_width)
1540 final_valid_bytes = (req_payload.length - bytes_before_last -
1541 UInt(length_width)(1)).as_uint(vb_width).reg(
1542 clk, rst, ce=accept, name=
"final_valid_bytes")
1543 resp_ready = Wire(Bits(1))
1544 resp_payload, resp_valid = ports.resp_in.unwrap(resp_ready)
1545 is_final_word = words_left == UInt(words_width)(1)
1546 resp_out_ch, resp_out_ready = resp_out_channel_type.wrap(
1554 UInt(vb_width)(word_bytes - 1), final_valid_bytes),
1558 ports.resp_out = resp_out_ch
1559 resp_ready.assign(resp_out_ready)
1560 resp_xact = resp_valid & resp_out_ready
1563 words_dec = (words_left - UInt(words_width)(1)).as_uint(words_width)
1565 Mux(accept, Mux(resp_xact, words_left, words_dec),
1566 total_words).reg(clk,
1569 ce=accept | resp_xact,
1570 name=
"words_left_reg"))
1574 rst, [accept], [resp_xact & is_final_word],
1575 name=
"resp_busy_reg"))
1577 return HostMemReadReqSplitterImpl
1583 reqs: List[esi._OutputBundleSetter],
1584 max_read_request_bytes: int = DEFAULT_MAX_READ_REQUEST_BYTES):
1585 """Construct a host memory read request module to orchestrate the the read
1586 connections. Responsible for both gearboxing the data, multiplexing the
1587 requests, reassembling out-of-order responses and routing the responses to the
1590 Generate this module dynamically to allow for multiple read clients of
1591 multiple types to be directly accomodated."""
1593 class HostmemReadProcessorImpl(Module):
1598 reqPortMap: Dict[esi._OutputBundleSetter, str] = {}
1600 name =
"client_" + req.client_name_str
1601 locals()[name] = Output(req.type)
1602 reqPortMap[req] = name
1605 upstream = Output(hostmem_module.read.type)
1609 """Build the read side of the HostMem service."""
1613 upstream_req_channel, _ = Channel(hostmem_module.UpstreamReadReq).
wrap(
1619 upstream_read_bundle, _ = hostmem_module.read.type.pack(
1620 req=upstream_req_channel)
1621 ports.upstream = upstream_read_bundle
1628 assert len(reqs) <= 256,
"More than 256 read clients not supported."
1631 upstream_req_channel = Wire(Channel(hostmem_module.UpstreamReadReq))
1632 upstream_read_bundle, froms = hostmem_module.read.type.pack(
1633 req=upstream_req_channel)
1634 ports.upstream = upstream_read_bundle
1635 upstream_resp_channel = froms[
"resp"]
1641 demux = esi.TaggedDemux(len(reqs), upstream_resp_channel.type)(
1642 clk=ports.clk, rst=ports.rst, in_=upstream_resp_channel)
1644 word_bytes = read_width // 8
1645 tagged_client_reqs = []
1646 for idx, client
in enumerate(reqs):
1649 c.channel
for c
in client.type.channels
if c.name ==
'resp'
1651 demuxed_upstream_channel = demux.get_out(idx)
1658 client_type = resp_type.inner_type
1659 is_list = isinstance(client_type, Window)
1666 lowered = client_type.lowered_type
1667 lowered_fields = dict(lowered.fields)
1668 element_type = lowered_fields[
"data"]
1669 element_bits = element_type.bitwidth
1670 data_size_type = lowered_fields[
"data_size"]
1671 if element_bits == 0:
1672 raise ValueError(
"read_list element type cannot be zero-width.")
1674 if client_type.data.bitwidth == 0:
1675 raise ValueError(
"Client data type cannot be zero-width. Use a "
1676 "single-bit type if no data is needed.")
1677 element_bits = client_type.data.bitwidth
1680 elem_stride_bytes = (element_bits + 7) // 8
1690 max_chunk_bytes = (max_read_request_bytes // word_bytes) * word_bytes
1692 splitter_resp = Wire(gearbox_mod.in_.type)
1693 gearbox = gearbox_mod(clk=ports.clk, rst=ports.rst, in_=splitter_resp)
1697 client_resp_channel = gearbox.out.transform(
1698 lambda m, lowered=lowered, element_type=element_type,
1699 data_size_type=data_size_type, client_type=client_type:
1703 "data": m.data.bitcast(element_type),
1704 "data_size": data_size_type(0),
1707 client_bundle, froms = client.type.pack(resp=client_resp_channel)
1708 client_req = froms[
"req"]
1709 logical_req = client_req.transform(
1710 lambda r, idx=idx, elem_stride_bytes=elem_stride_bytes:
1711 hostmem_module.UpstreamReadReq({
1714 "length": (r.length * UInt(64)
1715 (elem_stride_bytes)).as_uint(32),
1721 client_resp_channel = gearbox.out.transform(
1722 lambda m, client_type=client_type: client_type({
1724 "data": m.data.bitcast(client_type.data)
1726 client_bundle, froms = client.type.pack(resp=client_resp_channel)
1727 client_req = froms[
"req"]
1728 logical_req = client_req.transform(
1729 lambda r, idx=idx, elem_stride_bytes=elem_stride_bytes:
1730 hostmem_module.UpstreamReadReq({
1731 "address": r.address,
1732 "length": UInt(32)(elem_stride_bytes),
1738 logical_req.type, demuxed_upstream_channel.type,
1739 max_chunk_bytes)(clk=ports.clk,
1742 resp_in=demuxed_upstream_channel)
1743 splitter_resp.assign(splitter.resp_out)
1744 tagged_client_req = splitter.req_out
1746 tagged_client_reqs.append(tagged_client_req)
1749 setattr(ports, HostmemReadProcessorImpl.reqPortMap[client],
1761 muxed_client_reqs = ChannelArbiter(tagged_client_reqs,
1764 mux_pipeline_levels=2,
1765 pipelined_scheduler=
True,
1767 upstream_req_channel.assign(muxed_client_reqs)
1768 HostmemReadProcessorImpl.reqPortMap.clear()
1770 return HostmemReadProcessorImpl
1775 max_burst_bytes: int) -> type[
"TaggedWriteGearboxImpl"]:
1776 """Build a gearbox to convert the client data to upstream write chunks.
1777 Assumes a struct {address, tag, data} and only gearboxes the data. Tag is
1778 stored separately and the struct is re-assembled later on.
1780 'max_burst_bytes' caps a single contiguous upstream write transaction (a
1781 max-payload-size analog): when an element spans more than 'max_burst_bytes',
1782 its engine words are split into multiple <= 'max_burst_bytes' transactions by
1783 emitting the framing 'last' at each boundary. 0 disables the cap."""
1785 if output_bitwidth % 8 != 0:
1786 raise ValueError(
"Output bitwidth must be a multiple of 8.")
1788 if input_bitwidth % 8 != 0:
1789 input_pad_bits = 8 - (input_bitwidth % 8)
1790 input_padded_bitwidth = input_bitwidth + input_pad_bits
1793 max_burst_words = (max_burst_bytes //
1794 (output_bitwidth // 8))
if max_burst_bytes
else 0
1796 assert (max_burst_words & (max_burst_words - 1)) == 0, \
1797 "max_burst_bytes / (output_bitwidth // 8) must be a power of two"
1799 class TaggedWriteGearboxImpl(Module):
1804 (
"address", UInt(64)),
1805 (
"tag", esi.HostMem.TagType),
1806 (
"data", Bits(input_bitwidth)),
1808 out = OutputChannel(
1810 (
"address", UInt(64)),
1811 (
"tag", esi.HostMem.TagType),
1812 (
"data", Bits(output_bitwidth)),
1813 (
"valid_bytes", Bits(8)),
1817 num_chunks = ceil(input_padded_bitwidth / output_bitwidth)
1821 upstream_ready = Wire(Bits(1))
1822 ready_for_client = Wire(Bits(1))
1823 client_tag_and_data, client_valid = ports.in_.unwrap(ready_for_client)
1824 client_data = client_tag_and_data.data
1825 if input_pad_bits > 0:
1826 client_data = client_data.pad_or_truncate(input_padded_bitwidth)
1827 client_xact = ready_for_client & client_valid
1828 input_bitwidth_bytes = input_padded_bitwidth // 8
1829 output_bitwidth_bytes = output_bitwidth // 8
1833 if output_bitwidth == input_padded_bitwidth:
1834 upstream_data_bits = client_data
1835 upstream_valid = client_valid
1836 ready_for_client.assign(upstream_ready)
1837 tag = client_tag_and_data.tag
1838 address = client_tag_and_data.address
1839 valid_bytes = Bits(8)(input_bitwidth_bytes)
1841 elif output_bitwidth > input_padded_bitwidth:
1842 upstream_data_bits = client_data.as_bits(output_bitwidth)
1843 upstream_valid = client_valid
1844 ready_for_client.assign(upstream_ready)
1845 tag = client_tag_and_data.tag
1846 address = client_tag_and_data.address
1847 valid_bytes = Bits(8)(input_bitwidth_bytes)
1852 num_chunks = TaggedWriteGearboxImpl.num_chunks
1853 num_chunks_idx_bitwidth = clog2(num_chunks)
1854 if input_padded_bitwidth % output_bitwidth == 0:
1857 padding_numbits = output_bitwidth - (input_padded_bitwidth %
1859 client_data_padded = BitsSignal.concat(
1860 [Bits(padding_numbits)(0), client_data])
1862 client_data_padded[i * output_bitwidth:(i + 1) * output_bitwidth]
1863 for i
in range(num_chunks)
1865 chunk_regs = Array(Bits(output_bitwidth), num_chunks)([
1866 c.reg(ports.clk, ce=client_xact, name=f
"chunk_{idx}")
1867 for idx, c
in enumerate(chunks)
1869 increment = Wire(Bits(1))
1870 clear = Wire(Bits(1))
1871 counter = Counter(num_chunks_idx_bitwidth)(clk=ports.clk,
1873 increment=increment,
1875 upstream_data_bits = chunk_regs[counter.out]
1876 upstream_valid = ControlReg(ports.clk, ports.rst, [client_xact],
1878 upstream_xact = upstream_valid & upstream_ready
1879 clear.assign(upstream_xact & (counter.out == (num_chunks - 1)))
1880 increment.assign(upstream_xact)
1881 ready_for_client.assign(~upstream_valid)
1882 address_padding_bits = clog2(output_bitwidth_bytes)
1883 counter_bytes = BitsSignal.concat(
1884 [counter.out.as_bits(),
1885 Bits(address_padding_bits)(0)]).as_uint()
1888 tag_reg = client_tag_and_data.tag.reg(ports.clk,
1891 addr_reg = client_tag_and_data.address.reg(ports.clk,
1894 address = (addr_reg + counter_bytes).as_uint(64)
1896 elem_end = counter.out == (num_chunks - 1)
1897 valid_bytes = Mux(elem_end,
1898 Bits(8)(output_bitwidth_bytes),
1899 Bits(8)((output_bitwidth - padding_numbits) // 8))
1900 if max_burst_words
and num_chunks > max_burst_words:
1906 burst_shift = clog2(max_burst_words)
1907 burst_end = counter.out.as_bits()[:burst_shift].and_reduce()
1908 last = elem_end | burst_end
1912 upstream_channel, upstrm_ready_sig = TaggedWriteGearboxImpl.out.type.wrap(
1916 "data": upstream_data_bits,
1917 "valid_bytes": valid_bytes,
1920 upstream_ready.assign(upstrm_ready_sig)
1921 ports.out = upstream_channel
1923 return TaggedWriteGearboxImpl
1927def EmitEveryN(message_type: Type, N: int) -> type[
'EmitEveryNImpl']:
1928 """Emit (forward) one message for every N input messages. The emitted message
1929 is the last one of the N received. N must be >= 1."""
1932 raise ValueError(
"N must be >= 1")
1934 class EmitEveryNImpl(Module):
1937 in_ = InputChannel(message_type)
1938 out = OutputChannel(message_type)
1942 ready_for_in = Wire(Bits(1))
1943 in_data, in_valid = ports.in_.unwrap(ready_for_in)
1944 xact = in_valid & ready_for_in
1948 out_chan, out_ready = EmitEveryNImpl.out.type.wrap(in_data, in_valid)
1949 ready_for_in.assign(out_ready)
1950 ports.out = out_chan
1953 counter_width = clog2(N)
1954 counter_clear = Wire(Bits(1))
1955 counter = Counter(counter_width)(clk=ports.clk,
1958 clear=counter_clear)
1961 last_msg = in_data.reg(ports.clk, ports.rst, ce=xact, name=
"last_msg")
1963 hit_last = (counter.out == UInt(counter_width)(N - 1)) & xact
1964 counter_clear.assign(hit_last)
1966 emit_accepted = Wire(Bits(1))
1967 out_valid = ControlReg(ports.clk, ports.rst, [hit_last], [emit_accepted])
1969 out_chan, out_ready = EmitEveryNImpl.out.type.wrap(last_msg, out_valid)
1971 ready_for_in.assign(~(out_valid & ~out_ready))
1972 emit_accepted.assign(out_valid & out_ready)
1974 ports.out = out_chan
1976 return EmitEveryNImpl
1982 reqs: List[esi._OutputBundleSetter],
1983 max_write_payload_bytes: int = DEFAULT_MAX_WRITE_PAYLOAD_BYTES
1984) -> type[
"HostMemWriteProcessorImpl"]:
1985 """Construct a host memory write request module to orchestrate the the write
1986 connections. Responsible for both gearboxing the data, multiplexing the
1987 requests, reassembling out-of-order responses and routing the responses to the
1990 Generate this module dynamically to allow for multiple write clients of
1991 multiple types to be directly accomodated."""
1993 class HostMemWriteProcessorImpl(Module):
1999 reqPortMap: Dict[esi._OutputBundleSetter, str] = {}
2001 name =
"client_" + req.client_name_str
2002 locals()[name] = Output(req.type)
2003 reqPortMap[req] = name
2006 upstream = Output(hostmem_module.write.type)
2016 size_width = clog2(write_width // 8)
2020 req, _ = Channel(hostmem_module.UpstreamWriteReq).
wrap(
2028 write_bundle, _ = hostmem_module.write.type.pack(req=req)
2029 ports.upstream = write_bundle
2032 assert len(reqs) <= 256,
"More than 256 write clients not supported."
2034 upstream_req_channel = Wire(Channel(hostmem_module.UpstreamWriteReq))
2035 upstream_write_bundle, froms = hostmem_module.write.type.pack(
2036 req=upstream_req_channel)
2037 ports.upstream = upstream_write_bundle
2038 upstream_ack_tag = froms[
"ackTag"]
2040 demuxed_acks = esi.TaggedDemux(len(reqs), upstream_ack_tag.type)(
2041 clk=ports.clk, rst=ports.rst, in_=upstream_ack_tag)
2046 write_channels: List[ChannelSignal] = []
2047 for idx, req
in enumerate(reqs):
2049 reqch = [c.channel
for c
in req.type.channels
if c.name ==
'req'][0]
2050 client_type = reqch.inner_type
2051 input_flit_ack = Wire(upstream_ack_tag.type)
2053 if isinstance(client_type, Window):
2058 bundle_sig, wfroms = req.type.pack(ackTag=input_flit_ack)
2059 windowed_req = wfroms[
"req"]
2060 lowered = client_type.lowered_type
2061 array_type = dict(lowered.fields)[
"data"]
2062 element_bits = array_type.element_type.bitwidth
2067 elem_stride = (element_bits + 7) // 8
2070 max_write_payload_bytes)
2071 gearbox_in_type = gearbox_mod.in_.type.inner_type
2075 ready_for_frame = Wire(Bits(1))
2076 frame_win, frame_valid = windowed_req.unwrap(ready_for_frame)
2077 frame = frame_win.unwrap()
2078 frame_xact = frame_valid & ready_for_frame
2079 elem_clear = Wire(Bits(1))
2080 elem_counter = Counter(64)(clk=ports.clk,
2083 increment=frame_xact)
2084 elem_clear.assign(frame_xact & frame[
"last"])
2085 elem_addr = (frame[
"address"] +
2086 elem_counter.out * UInt(64)(elem_stride)).as_uint(64)
2087 gearbox_in_chan, gearbox_in_ready = Channel(gearbox_in_type).
wrap(
2089 "tag": frame[
"tag"],
2090 "address": elem_addr,
2091 "data": frame[
"data"][0].bitcast(gearbox_in_type.data),
2093 ready_for_frame.assign(gearbox_in_ready)
2094 gearbox = gearbox_mod(clk=ports.clk,
2096 in_=gearbox_in_chan)
2099 write_req_bundle_type = esi.HostMem.write_req_bundle_type(
2101 bundle_sig, sfroms = write_req_bundle_type.pack(ackTag=input_flit_ack)
2103 write_width, max_write_payload_bytes)
2104 gearbox_in_type = gearbox_mod.in_.type.inner_type
2105 bitcast_client_req = sfroms[
"req"].transform(
2106 lambda m, git=gearbox_in_type: git({
2108 "address": m.address,
2109 "data": m.data.bitcast(git.data)
2111 gearbox = gearbox_mod(clk=ports.clk,
2113 in_=bitcast_client_req)
2115 write_channels.append(
2116 gearbox.out.transform(
2117 lambda m, idx=idx: hostmem_module.UpstreamWriteReq({
2124 "data_size": (m.valid_bytes.as_uint() - UInt(8)
2125 (1)).as_bits()[:size_width],
2132 ack_every_n =
EmitEveryN(upstream_ack_tag.type, gearbox_mod.num_chunks)(
2133 clk=clk, rst=rst, in_=demuxed_acks.get_out(idx))
2134 input_flit_ack.assign(ack_every_n.out)
2137 setattr(ports, HostMemWriteProcessorImpl.reqPortMap[req], bundle_sig)
2148 muxed_write_channel = ChannelArbiter(write_channels,
2151 mux_pipeline_levels=2,
2152 pipelined_scheduler=
True,
2154 upstream_req_channel.assign(muxed_write_channel)
2156 return HostMemWriteProcessorImpl
2163 max_read_request_bytes: int = DEFAULT_MAX_READ_REQUEST_BYTES,
2164 max_write_payload_bytes: int = DEFAULT_MAX_WRITE_PAYLOAD_BYTES
2165) -> typing.Type[
'ChannelHostMemImpl']:
2167 class ChannelHostMemImpl(esi.ServiceImplementation):
2168 """Builds a HostMem service which multiplexes multiple HostMem clients into
2169 two (read and write) bundles of the given data width."""
2174 UpstreamReadReq = StructType([
2175 (
"address", UInt(64)),
2176 (
"length", UInt(32)),
2181 BundledChannel(
"req", ChannelDirection.TO, UpstreamReadReq),
2183 "resp", ChannelDirection.FROM,
2185 (
"tag", esi.HostMem.TagType),
2186 (
"data", Bits(read_width)),
2191 if write_width % 8 != 0:
2192 raise ValueError(
"Write width must be a multiple of 8.")
2193 UpstreamWriteReq = StructType([
2194 (
"address", UInt(64)),
2196 (
"data", Bits(write_width)),
2197 (
"data_size", Bits(clog2(write_width // 8))),
2202 BundledChannel(
"req", ChannelDirection.TO, UpstreamWriteReq),
2203 BundledChannel(
"ackTag", ChannelDirection.FROM, UInt(8)),
2207 def generate(ports, bundles: esi._ServiceGeneratorBundles):
2212 req
for req
in bundles.to_client_reqs
2213 if req.port
in (
'read',
'read_list')
2216 read_reqs, max_read_request_bytes)
2217 read_proc = read_proc_module(clk=ports.clk, rst=ports.rst)
2218 ports.read = read_proc.upstream
2219 for req
in read_reqs:
2220 req.assign(getattr(read_proc, read_proc_module.reqPortMap[req]))
2224 req
for req
in bundles.to_client_reqs
if req.port ==
'write'
2228 max_write_payload_bytes)
2229 write_proc = write_proc_module(clk=ports.clk, rst=ports.rst)
2230 ports.write = write_proc.upstream
2231 for req
in write_reqs:
2232 req.assign(getattr(write_proc, write_proc_module.reqPortMap[req]))
2234 return ChannelHostMemImpl
2319def ChannelEngineService(
2320 to_host_engine_gen: Callable,
2321 from_host_engine_gen: Callable) -> type[
'ChannelEngineService']:
2322 """Returns a channel service implementation which calls
2323 to_host_engine_gen(<client_type>) or from_host_engine_gen(<client_type>) to
2324 generate the to_host and from_host engines for each channel. Does not support
2325 engines which can service multiple clients at once.
2327 Individual service requests may override the default engine pair by passing
2328 `options={"engine": "pkg.mod.attr"}` at the service-request call site (e.g.
2329 `HostComms.some_bundle(AppID(...), options={"engine": "..."})`). The path
2330 is resolved by `_resolve_engine_pair` and must yield a
2331 `(to_host_engine_gen, from_host_engine_gen)` tuple with the same call shape
2332 as the defaults; the override applies to every channel of that request's
2336 class ChannelEngineService(esi.ServiceImplementation):
2337 """Service implementation which services the clients via a per-channel DMA
2344 def build(ports, bundles: esi._ServiceGeneratorBundles):
2348 def build_engine_appid(client_appid: List[
esi.AppID],
2349 channel_name: str) -> str:
2350 appid_strings = [str(appid)
for appid
in client_appid]
2351 return f
"{'_'.join(appid_strings)}.{channel_name}"
2353 def build_engine(bc: BundledChannel,
2354 bundle_to_host_gen: Callable,
2355 bundle_from_host_gen: Callable,
2356 input_channel=
None) -> Type:
2357 idbase = build_engine_appid(bundle.client_name, bc.name)
2362 engine_client_type = bc.channel.inner_type
2363 is_void = (engine_client_type.bitwidth == 0)
2365 engine_client_type = Bits(8)
2366 if bc.direction == ChannelDirection.FROM:
2367 engine_mod = bundle_to_host_gen(engine_client_type)
2369 engine_mod = bundle_from_host_gen(engine_client_type)
2374 eng_details: Dict[str, object] = {
"engine_inst": eng_appid}
2375 if input_channel
is not None:
2379 input_channel = input_channel.transform(
lambda _: Bits(8)(0))
2380 if (engine_mod.input_channel.type.signaling
2381 != input_channel.type.signaling):
2382 input_channel = input_channel.buffer(
2386 output_signaling=engine_mod.input_channel.type.signaling)
2387 eng_inputs[
"input_channel"] = input_channel
2388 if hasattr(engine_mod,
"mmio"):
2389 mmio_appid =
esi.AppID(idbase +
".mmio")
2390 eng_inputs[
"mmio"] = esi.MMIO.read_write(mmio_appid)
2391 eng_details[
"mmio"] = mmio_appid
2392 if hasattr(engine_mod,
"hostmem_write"):
2393 eng_inputs[
"hostmem_write"] = esi.HostMem.write_from_bundle(
2395 engine_mod.hostmem_write.type,
2396 options=bundle.options)
2397 if hasattr(engine_mod,
"hostmem_read"):
2398 eng_inputs[
"hostmem_read"] = esi.HostMem.read_from_bundle(
2400 engine_mod.hostmem_read.type,
2401 options=bundle.options)
2402 engine = engine_mod(appid=eng_appid, **eng_inputs)
2403 engine_rec = bundles.emit_engine(engine, details=eng_details)
2404 engine_rec.add_record(bundle, {bc.name: {}})
2407 for bundle
in bundles.to_client_reqs:
2412 engine_override = bundle.options.get(
"engine")
2413 if engine_override
is None:
2414 bundle_to_host_gen = to_host_engine_gen
2415 bundle_from_host_gen = from_host_engine_gen
2420 bundle_type = bundle.type
2423 for bc
in bundle_type.channels:
2424 if bc.direction == ChannelDirection.TO:
2425 engine = build_engine(bc, bundle_to_host_gen, bundle_from_host_gen)
2426 out_chan = engine.output_channel
2428 if bc.channel.inner_type.bitwidth == 0:
2429 out_chan = out_chan.transform(
lambda _: Bits(0)(0))
2430 to_channels[bc.name] = out_chan
2432 client_bundle_sig, froms = bundle_type.pack(**to_channels)
2433 bundle.assign(client_bundle_sig)
2436 for bc
in bundle_type.channels:
2437 if bc.direction == ChannelDirection.FROM:
2438 build_engine(bc, bundle_to_host_gen, bundle_from_host_gen,
2441 return ChannelEngineService