78 """Construct the ESI header MMIO adhering to the MMIO layout specified in
79 the ChannelMMIO service implementation."""
83 read =
Input(esi.MMIO.read_write.type)
86 reset_request =
Output(Bits(1))
92 data_chan_wire = Wire(Channel(esi.MMIODataType))
93 input_bundles = ports.read.unpack(data=data_chan_wire)
94 cmd_chan = input_bundles[
'cmd']
99 cmd_ready = Wire(Bits(1))
100 s1_to_s2_xact = Wire(Bits(1))
101 cmd_raw, cmd_valid = cmd_chan.unwrap(cmd_ready)
104 s1_load = cmd_valid & cmd_ready
105 cmd = cmd_raw.reg(clk, rst, ce=s1_load, name=
"cmd")
106 s1_valid = ControlReg(clk,
109 resets=[s1_to_s2_xact],
112 cmd_ready.assign(~s1_valid)
114 address_words = cmd.offset.as_bits()[3:]
115 slot = address_words[:3]
117 cycles = Counter(64)(clk=ports.clk,
120 increment=Bits(1)(1),
121 instance_name=
"cycle_counter")
124 core_freq = System.current().core_freq
125 if core_freq
is None:
127 header = Array(Bits(64), 8)([
133 cycles.out.as_bits(),
137 header.name =
"header"
140 s2_valid = Wire(Bits(1))
141 data_chan_ready = Wire(Bits(1))
142 s2_xact = s2_valid & data_chan_ready
144 s1_to_s2_xact.assign(s1_valid & ~s2_valid)
146 header_out = header[slot].reg(clk=clk,
153 asserts=[s1_to_s2_xact],
155 name=
"header_out_valid"))
157 data_chan, data_chan_ready_sig = Channel(esi.MMIODataType).
wrap(
158 header_out, s2_valid)
159 data_chan_wire.assign(data_chan)
160 data_chan_ready.assign(data_chan_ready_sig)
166 reset_detect = (cmd.write & (slot == Bits(3)(7)) &
167 (cmd.data == Bits(64)(ResetMagicNumber)))
168 ports.reset_request = reset_detect & s1_to_s2_xact
175 data_type: Type, num_outs: int,
176 next_sel_width: int) -> type[
"ChannelDemuxNImpl"]:
177 """N-way channel demultiplexer for valid/ready signaling. Contains
178 valid/ready registers on the output channels. The selection signal is now
179 embedded in the input channel payload as a struct {sel, data}. Input
180 signals ready when the selected output register is empty."""
182 assert num_outs >= 1,
"num_outs must be at least 1."
184 class ChannelDemuxNImpl(
Module):
189 InPayloadType = StructType([
190 (
"sel", Bits(clog2(num_outs))),
191 (
"next_sel", Bits(next_sel_width)),
194 inp =
Input(Channel(InPayloadType))
195 OutPayloadType = StructType([
196 (
"next_sel", Bits(next_sel_width)),
200 for i
in range(num_outs):
201 locals()[f
"output_{i}"] =
Output(Channel(OutPayloadType))
204 def generate(ports) -> None:
209 sel_width = clog2(num_outs)
212 input_ready = Wire(Bits(1), name=
"input_ready")
213 in_payload, in_valid = ports.inp.unwrap(input_ready)
214 in_sel = in_payload.sel
215 in_next_sel = in_payload.next_sel
216 in_data = in_payload.data
221 valid_regs: List[BitsSignal] = []
222 selected_valid_expr = Bits(1)(0)
224 for i
in range(num_outs):
226 will_write = Wire(Bits(1), name=f
"will_write_{i}")
227 write_cond = (in_valid & input_ready & (in_sel == Bits(sel_width)(i)))
228 will_write.assign(write_cond)
231 out_msg_reg = ChannelDemuxNImpl.OutPayloadType({
232 "next_sel": in_next_sel,
234 }).reg(clk=clk, rst=rst, ce=will_write, name=f
"out{i}_msg_reg")
237 consume = Wire(Bits(1), name=f
"consume_{i}")
238 valid_reg = ControlReg(
241 asserts=[will_write],
243 name=f
"out{i}_valid_reg",
245 valid_regs.append(valid_reg)
248 ch_sig, ch_ready = Channel(ChannelDemuxNImpl.OutPayloadType).
wrap(
249 out_msg_reg, valid_reg)
250 setattr(ports, f
"output_{i}", ch_sig)
251 consume.assign(valid_reg & ch_ready)
254 selected_valid_expr = selected_valid_expr | (
255 (in_sel == Bits(sel_width)(i)) & valid_reg)
258 input_ready.assign(selected_valid_expr ^ Bits(1)(1))
260 def get_out(self, index: int) -> ChannelSignal:
261 return getattr(self, f
"output_{index}")
263 return ChannelDemuxNImpl
268 data_type: Type, num_outs: int,
269 branching_factor_log2: int) -> type[
"ChannelDemuxTree"]:
270 """Pipelined N-way channel demultiplexer for valid/ready signaling. This
271 implementation uses a tree structure of
272 ChannelDemuxN_HalfStage_ReadyBlocking modules to reduce fanout pressure.
273 Supports maximum half-throughput to save complexity and area.
276 root_sel_width = clog2(num_outs)
278 num_outs = 2**root_sel_width
279 sel_width = branching_factor_log2
280 fanout = 2**sel_width
282 class ChannelDemuxTree(
Module):
286 InPayloadType = StructType([
287 (
"sel", Bits(clog2(num_outs))),
290 inp =
Input(Channel(InPayloadType))
293 for i
in range(num_outs):
294 locals()[f
"output_{i}"] =
Output(Channel(data_type))
297 def build(ports) -> None:
298 assert branching_factor_log2 > 0
301 setattr(ports,
"output_0", ports.inp.transform(
lambda p: p.data))
304 def payload_type(sel_width: int, next_sel_width: int) -> Type:
306 (
"sel", Bits(sel_width)),
307 (
"next_sel", Bits(next_sel_width)),
311 def next_sel_width_calc(curr_sel_width) -> int:
312 return max(curr_sel_width - sel_width, 0)
314 def payload_next(curr_msg: StructSignal) -> StructSignal:
315 """Given current level payload, produce next level payload by
316 stripping off the top selection bits."""
318 next_sel_width = next_sel_width_calc(curr_msg.next_sel.type.width)
319 curr_sel_width = curr_msg.next_sel.type.width
320 new_sel_width = min(curr_sel_width, sel_width)
326 "sel": (curr_msg.next_sel[next_sel_width:]
327 if curr_sel_width > 0
else Bits(0)(0)),
328 "next_sel": (curr_msg.next_sel[:next_sel_width]
329 if next_sel_width > 0
else Bits(0)(0)),
330 "data": curr_msg.data,
333 current_channels: List[ChannelSignal] = [
334 ports.inp.transform(
lambda m: payload_type(0, root_sel_width)({
341 curr_sel_width = root_sel_width
343 while len(current_channels) < num_outs:
344 next_level: List[ChannelSignal] = []
345 level_num_outs = min(2**curr_sel_width, fanout)
346 for i, c
in enumerate(current_channels):
349 num_outs=level_num_outs,
350 next_sel_width=next_sel_width_calc(curr_sel_width),
354 inp=c.transform(payload_next),
355 instance_name=f
"demux_l{level}_i{i}",
357 for j
in range(level_num_outs):
358 next_level.append(dmux.get_out(j))
359 current_channels = next_level
360 curr_sel_width -= sel_width
363 for i
in range(num_outs):
368 current_channels[i].transform(
lambda p: p.data),
371 def get_out(self, index: int) -> ChannelSignal:
372 return getattr(self, f
"output_{index}")
374 return ChannelDemuxTree
378 regions: Tuple[Tuple[int, Optional[int]],
379 ...]) -> type[
"MMIOPrefixRouterImpl"]:
380 """Build a pipelined address-prefix tree which routes MMIO commands.
382 Each ``(base, size)`` must describe a disjoint, power-of-two-aligned block.
383 The last region may pass ``size=None`` to claim every address at or above its
384 base; the manifest uses this since its size isn't known during generation.
385 Internal nodes test one address bit and register both outgoing paths. Leaves
386 replace the global address with the block-local low bits, so no subtractor is
387 needed (the open-ended region is the one exception).
389 For four 0x100-byte regions at 0x0/0x100/0x200/0x300 plus an open-ended
390 region at 0x400: the open-ended region is peeled off first, then each level
391 splits the remaining candidates in half, so depth is ``O(log N)`` rather than
392 one level per region::
394 cmd ──▶ addr[31:10]≠0 ──1──▶ @0x400 (open ended, offset -= 0x400)
406 @0x0 @0x100 @0x200 @0x300
408 The four leaves all take their client-local offset as ``addr[7:0]``.
411 assert len(regions) > 1,
"MMIO routing requires at least two regions"
415 open_ended_base: Optional[int] =
None
416 sized_regions = regions
417 if regions[-1][1]
is None:
418 open_ended_base = regions[-1][0]
419 assert open_ended_base > 0
and \
420 open_ended_base & (open_ended_base - 1) == 0, \
421 "an open-ended MMIO region must start at a power-of-two address"
422 sized_regions = regions[:-1]
424 for base, size
in sized_regions:
425 assert size > 0
and size & (size - 1) == 0, \
426 "MMIO region sizes must be powers of two"
427 assert base % size == 0,
"MMIO regions must be aligned to their size"
429 a_base + a_size <= b_base
430 for (a_base, a_size), (b_base, _)
in zip(sized_regions, regions[1:])), \
431 "MMIO regions must be sorted and disjoint"
433 entries = [(idx, base, size)
for idx, (base, size)
in enumerate(sized_regions)
436 def build_tree(node_entries):
437 if len(node_entries) == 1:
438 return node_entries[0][0]
440 lowest_fixed_bit = max(size.bit_length() - 1
for _, _, size
in node_entries)
442 for bit
in range(31, lowest_fixed_bit - 1, -1):
443 zeros = [entry
for entry
in node_entries
if not (entry[1] >> bit) & 1]
444 if zeros
and len(zeros) != len(node_entries):
446 (abs(len(node_entries) - 2 * len(zeros)), -bit, bit, zeros))
447 assert candidates,
"unable to distinguish disjoint MMIO regions"
448 _, _, bit, zeros = min(candidates)
449 zero_indices = {entry[0]
for entry
in zeros}
450 ones = [entry
for entry
in node_entries
if entry[0]
not in zero_indices]
451 return bit, build_tree(zeros), build_tree(ones)
453 tree = build_tree(entries)
455 class MMIOPrefixRouterImpl(
Module):
458 inp =
Input(Channel(esi.MMIOReadWriteCmdType))
459 for idx
in range(len(regions)):
460 locals()[f
"output_{idx}"] =
Output(Channel(esi.MMIOReadWriteCmdType))
464 """Route through one registered address-bit branch per tree level."""
466 def new_demux(command_channel: ChannelSignal, select, name: str):
470 demux_input = command_channel.transform(
471 lambda cmd, _sel=select, _type=Demux.InPayloadType: _type({
473 "next_sel": Bits(0)(0),
476 return Demux(clk=ports.clk,
481 def route(node, command_channel: ChannelSignal, path: str):
482 if isinstance(node, int):
483 _, size = regions[node]
484 local_width = size.bit_length() - 1
487 local_offset = cmd.offset.as_bits()[:local_width].pad_or_truncate(
489 return esi.MMIOReadWriteCmdType({
491 "offset": local_offset,
495 setattr(ports, f
"output_{node}", command_channel.transform(localize))
498 bit, zero_node, one_node = node
499 demux = new_demux(command_channel,
500 lambda cmd, _bit=bit: cmd.offset.as_bits()[_bit],
501 f
"prefix_{path}_bit{bit}")
503 demux.get_out(0).transform(
lambda p: p.data), path +
"0")
505 demux.get_out(1).transform(
lambda p: p.data), path +
"1")
507 client_channel = ports.inp
508 if open_ended_base
is not None:
512 split_bit = open_ended_base.bit_length() - 1
514 ports.inp,
lambda cmd: cmd.offset.as_bits()[split_bit:].or_reduce(),
515 f
"prefix_above_bit{split_bit}")
517 def localize_open_ended(payload):
519 local_offset = (cmd.offset - UInt(32)(open_ended_base)).as_uint(32)
520 return esi.MMIOReadWriteCmdType({
522 "offset": local_offset,
526 setattr(ports, f
"output_{len(regions) - 1}",
527 split.get_out(1).transform(localize_open_ended))
528 client_channel = split.get_out(0).transform(
lambda p: p.data)
530 route(tree, client_channel,
"")
532 def get_out(self, index: int) -> ChannelSignal:
533 return getattr(self, f
"output_{index}")
535 return MMIOPrefixRouterImpl
1009 output_bitwidth: int) -> type[
"ConcatReadGearboxImpl"]:
1010 """Concatenate ``ceil(OUT/IN)`` consecutive engine words into one client
1011 element wider than the word (``OUT > IN``). Serves single-message reads (any
1012 ``OUT > IN``; the low ``OUT`` bits of the concatenation are the element) and
1013 contiguous list reads whose element is a whole number of output_bitwidth
1014 (``OUT % IN == 0``, so elements never straddle). ``valid_bytes`` is unused:
1015 such lists have no partial words and a single element is one flit. Straddling
1016 lists use `ShiftReadGearbox`."""
1018 if input_bitwidth <= 0
or input_bitwidth % 8 != 0:
1019 raise ValueError(
"engine word width must be a positive multiple of 8 bits")
1020 if output_bitwidth <= input_bitwidth:
1021 raise ValueError(
"ConcatReadGearbox requires output > input")
1023 in_bytes = input_bitwidth // 8
1024 vb_width = max(1, clog2(in_bytes))
1026 class ConcatReadGearboxImpl(
Module):
1031 (
"tag", esi.HostMem.TagType),
1032 (
"data", Bits(input_bitwidth)),
1033 (
"valid_bytes", UInt(vb_width)),
1036 out = OutputChannel(
1038 (
"tag", esi.HostMem.TagType),
1039 (
"data", Bits(output_bitwidth)),
1045 ready_for_upstream = Wire(Bits(1), name=
"ready_for_upstream")
1048 in_reg = ports.in_.buffer(ports.clk, ports.rst, stages=1)
1049 up, upstream_valid = in_reg.unwrap(ready_for_upstream)
1050 upstream_data = up.data
1051 upstream_last = up.last
1052 upstream_xact = ready_for_upstream & upstream_valid
1057 chunks = ceil(output_bitwidth / input_bitwidth)
1058 counter_width = clog2(chunks)
1059 reg_ces = [Wire(Bits(1))
for _
in range(chunks)]
1061 upstream_data.reg(ports.clk,
1064 name=f
"chunk_reg_{idx}")
for idx
in range(chunks)
1066 client_data_bits = BitsSignal.concat(reversed(regs))[:output_bitwidth]
1074 counter = Wire(UInt(counter_width), name=
"chunk_counter")
1075 client_xact = Wire(Bits(1))
1076 set_client_valid = counter == UInt(counter_width)(chunks - 1)
1078 Counter(counter_width)(clk=ports.clk,
1080 clear=(upstream_xact & set_client_valid) |
1081 (client_xact & ~upstream_xact),
1082 increment=upstream_xact,
1083 instance_name=
"chunk_counter").out)
1084 client_valid = ControlReg(ports.clk, ports.rst,
1085 [set_client_valid & upstream_xact],
1087 for idx, reg_ce
in enumerate(reg_ces):
1088 reg_ce.assign(upstream_xact & (counter == UInt(counter_width)(idx)))
1090 client_last = upstream_last.reg(ports.clk,
1094 tag_reg = up.tag.reg(ports.clk,
1099 client_channel, client_ready = ConcatReadGearboxImpl.out.type.wrap(
1102 "data": client_data_bits,
1103 "last": client_last,
1105 client_xact.assign(client_valid & client_ready)
1106 ready_for_upstream.assign(~client_valid | client_ready)
1107 ports.out = client_channel
1109 return ConcatReadGearboxImpl
1114 output_bitwidth: int) -> type[
"DepackReadGearboxImpl"]:
1115 """Unpack a byte-aligned element that divides the engine word
1116 (``OUT % 8 == 0`` and ``IN % OUT == 0``) from a contiguous list response. Each
1117 word holds ``IN/OUT`` gap-free elements that never straddle, so a counter
1118 drives a parts:1 element mux -- no shifter (e.g. 32b/64b, 64b/256b).
1119 ``valid_bytes`` locates the last element in the burst's (possibly partial)
1120 final word. Straddling relationships use `ShiftReadGearbox`."""
1122 if input_bitwidth % 8 != 0:
1123 raise ValueError(
"engine word width must be a multiple of 8 bits")
1124 if output_bitwidth == 0
or output_bitwidth % 8 != 0 \
1125 or input_bitwidth % output_bitwidth != 0:
1127 "DepackReadGearbox requires a byte-aligned element that divides the "
1130 in_bytes = input_bitwidth // 8
1132 vb_width = max(1, clog2(in_bytes))
1133 count_width = clog2(in_bytes + 1)
1134 parts = input_bitwidth // output_bitwidth
1135 elem_bytes = output_bitwidth // 8
1137 class DepackReadGearboxImpl(
Module):
1142 (
"tag", esi.HostMem.TagType),
1143 (
"data", Bits(input_bitwidth)),
1144 (
"valid_bytes", UInt(vb_width)),
1147 out = OutputChannel(
1149 (
"tag", esi.HostMem.TagType),
1150 (
"data", Bits(output_bitwidth)),
1156 client_ready = Wire(Bits(1), name=
"client_ready")
1157 up_ready = Wire(Bits(1), name=
"up_ready")
1160 in_reg = ports.in_.buffer(ports.clk, ports.rst, stages=1)
1161 up, up_valid = in_reg.unwrap(up_ready)
1162 client_xact = up_valid & client_ready
1166 last_in_word = Bits(1)(1)
1167 client_data = up.data
1169 idx_width = clog2(parts)
1170 idx = Reg(UInt(idx_width),
1179 real_valid_bytes = (up.valid_bytes + UInt(1)(1)).as_uint(count_width)
1180 consumed = ((idx + UInt(1)(1)) *
1181 UInt(count_width)(elem_bytes)).as_uint(count_width)
1182 last_in_word = consumed == real_valid_bytes
1184 word_parts = Array(Bits(output_bitwidth), parts)([
1185 up.data[k * output_bitwidth:(k + 1) * output_bitwidth]
1186 for k
in range(parts)
1188 client_data = word_parts[idx]
1190 Mux(last_in_word, (idx + UInt(1)(1)).as_uint(idx_width),
1191 UInt(idx_width)(0)))
1194 up_ready.assign(client_xact & last_in_word)
1195 client_channel, client_ready_sig = DepackReadGearboxImpl.out.type.wrap(
1198 "data": client_data,
1199 "last": (up.last & last_in_word).as_bits(),
1201 client_ready.assign(client_ready_sig)
1202 ports.out = client_channel
1204 return DepackReadGearboxImpl
1209 output_bitwidth: int) -> type[
"ShiftReadGearboxImpl"]:
1210 """Universal fallback: unpack a contiguous, byte-packed element stream (a
1211 `read_list` response) for ANY ``(input_bitwidth, output_bitwidth)`` pair.
1213 Elements are packed at their natural byte stride ``stride = ceil(OUT/8)``
1214 bytes, so element k begins at wire bit ``k*stride*8`` and, in general,
1215 straddles engine-word boundaries at an arbitrary bit offset. A byte-addressed
1216 shift-register accumulator realigns each element across words. This is correct
1217 for every width relationship; `SliceReadGearbox`, `ConcatReadGearbox` and
1218 `DepackReadGearbox` are optimizations that avoid this barrel shifter for the
1219 regular (non-straddling) cases.
1221 Each input word carries ``valid_bytes`` (how many of its bytes are real) and
1222 ``last``. Both are framed to one whole `read_list` request rather than to the
1223 transport: `HostMemReadReqSplitter` drops the per-chunk framing of the reads
1224 it issues and re-derives these from the request's total length, so only the
1225 request's final word is ever partial. That length is ``num_elements *
1226 stride``, so tracking real bytes lets the gearbox emit exactly the right
1227 elements and place the list-terminating ``last`` on the final one -- no
1228 padding element is ever emitted."""
1230 if input_bitwidth % 8 != 0:
1231 raise ValueError(
"engine word width must be a multiple of 8 bits")
1232 if output_bitwidth <= 0:
1233 raise ValueError(
"client element width must be positive")
1234 in_bytes = input_bitwidth // 8
1235 stride_bytes = (output_bitwidth + 7) // 8
1236 stride_bits = stride_bytes * 8
1238 buf_bytes = stride_bytes + in_bytes
1239 buf_bits = buf_bytes * 8
1241 vb_width = max(1, clog2(in_bytes))
1242 cnt_width = clog2(buf_bytes + 1)
1245 offset_width = clog2(stride_bytes + 1)
1247 class ShiftReadGearboxImpl(
Module):
1252 (
"tag", esi.HostMem.TagType),
1253 (
"data", Bits(input_bitwidth)),
1254 (
"valid_bytes", UInt(vb_width)),
1257 out = OutputChannel(
1259 (
"tag", esi.HostMem.TagType),
1260 (
"data", Bits(output_bitwidth)),
1266 client_ready = Wire(Bits(1), name=
"client_ready")
1267 up_ready = Wire(Bits(1), name=
"up_ready")
1270 in_reg = ports.in_.buffer(ports.clk, ports.rst, stages=1)
1271 up, up_valid = in_reg.unwrap(up_ready)
1273 from pycde.circt.dialects
import comb
1277 buffer = Reg(Bits(buf_bits),
1282 count = Reg(UInt(cnt_width),
1287 saw_last = Wire(Bits(1), name=
"saw_last")
1292 has_room = count <= UInt(cnt_width)(buf_bytes - in_bytes)
1293 up_ready.assign(has_room & ~saw_last)
1294 up_xact = up_ready & up_valid
1297 client_valid = count >= UInt(cnt_width)(stride_bytes)
1298 client_xact = client_valid & client_ready
1304 added = Mux(up_xact,
1305 UInt(cnt_width)(0), (up.valid_bytes.as_uint(cnt_width) +
1306 UInt(1)(1)).as_uint(cnt_width))
1307 after_add = (count + added).as_uint(cnt_width)
1308 after_emit = (after_add -
1309 UInt(cnt_width)(stride_bytes)).as_uint(cnt_width)
1310 set_saw_last = (up_xact & up.last).as_bits()
1311 is_final_slot = (after_emit == UInt(cnt_width)(0))
1312 burst_ending = saw_last | set_saw_last
1313 client_last = client_valid & burst_ending & is_final_slot
1314 final_emit = client_xact & client_last
1322 append_off = count.as_bits()[0:offset_width]
1323 shamt = BitsSignal.concat([append_off,
1324 Bits(3)(0)]).pad_or_truncate(buf_bits)
1325 word_ext = up.data.pad_or_truncate(buf_bits)
1326 shifted_word = BitsSignal(
1327 comb.ShlOp(word_ext.value, shamt.value).result, Bits(buf_bits))
1328 appended = buffer | Mux(up_xact, Bits(buf_bits)(0), shifted_word)
1329 drained = appended[stride_bits:buf_bits].pad_or_truncate(buf_bits)
1331 Mux(final_emit, Mux(client_xact, appended, drained),
1336 count.assign(Mux(client_xact, after_add, after_emit))
1339 ControlReg(ports.clk, ports.rst, [set_saw_last], [final_emit]))
1341 tag_reg = up.tag.reg(ports.clk, ports.rst, ce=up_xact, name=
"tag_reg")
1342 client_channel, client_ready_sig = ShiftReadGearboxImpl.out.type.wrap(
1345 "data": buffer[0:output_bitwidth],
1346 "last": client_last,
1348 client_ready.assign(client_ready_sig)
1349 ports.out = client_channel
1351 return ShiftReadGearboxImpl
1392 resp_channel_type: Channel,
1393 max_chunk_bytes: int,
1394 max_outstanding: int = 1,
1395 round_read_lengths: bool =
True):
1396 """Split oversized host memory read requests into request-sized chunks before
1397 arbitration and reassemble the per-chunk responses into a single logical
1400 A burst read (`read_list`) can request many more bytes than a single upstream
1401 read request can carry. This module breaks such a request into
1402 `max_chunk_bytes`-sized (word-aligned) chunks addressed sequentially from the
1403 base. Splitting here -- *before* the requests
1404 are arbitrated onto the shared upstream read channel -- lets each client's
1405 chunks interleave with other clients' requests, so one large burst does not
1406 monopolize host memory bandwidth.
1408 On the response path the per-chunk end-of-list markers are dropped and a
1409 single burst-final `last` (plus the final word's `valid_bytes`) is re-derived
1410 from each logical request's length, so the gearbox and client see one
1411 contiguous response stream per request, identical to an unsplit read.
1413 Up to `max_outstanding` logical requests may be in flight at once. On accept,
1414 each request's {word count, final-word valid bytes} is queued in a metadata
1415 FIFO (depth `max_outstanding`); the response path frames each burst from the
1416 head of that FIFO. This **requires the upstream to return response words in
1417 request order** (no interleaving of different requests' words) -- the
1418 response `tag` is passed through from `resp_in` and is not used for matching.
1419 A new request is accepted whenever the chunk emitter is free (or is issuing
1420 its final chunk this cycle) and fewer than `max_outstanding` requests are in
1421 flight; draining responses does not block accepting. Throughput: a
1422 single-chunk request can be accepted every cycle, chunks are issued one per
1423 cycle, and responses flow one word per cycle with no bubble between bursts.
1424 No latency is added over the single-request version. There is no
1425 combinational path from `resp_in` to `req_in`'s ready; with
1426 `max_outstanding` > 1, `req_out`'s ready feeds `req_in`'s ready.
1428 Zero-length requests are not supported.
1430 req_channel_type: channel of the upstream read request {address, length
1432 resp_channel_type: channel of the upstream response {tag, data, last}.
1433 max_chunk_bytes: largest per-chunk byte count; must be > 0 and a multiple of
1434 the response word size.
1435 max_outstanding: maximum number of logical requests in flight (accepted
1436 but whose final response word has not yet been delivered). The default of 1
1437 serializes requests.
1438 round_read_lengths: round chunk lengths up to a whole response word for
1439 transports requiring word-granular reads. Disable for byte-granular
1440 transports such as cosim, which zero-pad a partial final response word.
1442 assert max_chunk_bytes > 0
1443 assert max_outstanding >= 1
1445 req_struct = req_channel_type.inner_type
1446 resp_struct = resp_channel_type.inner_type
1447 req_fields = dict(req_struct.fields)
1448 addr_width = req_fields[
"address"].bitwidth
1449 length_width = req_fields[
"length"].bitwidth
1450 tag_type = req_fields[
"tag"]
1451 word_bytes = dict(resp_struct.fields)[
"data"].bitwidth // 8
1452 word_shift = clog2(word_bytes)
1453 words_width = length_width - word_shift
1457 vb_width = max(1, clog2(word_bytes))
1458 resp_fields = dict(resp_struct.fields)
1459 resp_out_struct = StructType([
1460 (
"tag", resp_fields[
"tag"]),
1461 (
"data", resp_fields[
"data"]),
1462 (
"valid_bytes", UInt(vb_width)),
1465 resp_out_channel_type = Channel(resp_out_struct)
1470 meta_struct = StructType([
1471 (
"words_m1", UInt(words_width)),
1472 (
"final_vb", UInt(vb_width)),
1473 (
"single", Bits(1)),
1475 inflight_width = clog2(max_outstanding + 1)
1477 class HostMemReadReqSplitterImpl(
Module):
1480 req_in =
Input(req_channel_type)
1481 req_out =
Output(req_channel_type)
1482 resp_in =
Input(resp_channel_type)
1483 resp_out =
Output(resp_out_channel_type)
1491 emit_busy = Wire(Bits(1), name=
"emit_busy")
1492 cur_addr = Wire(UInt(addr_width), name=
"cur_addr")
1493 remaining = Wire(UInt(length_width), name=
"remaining")
1496 last_chunk = Wire(Bits(1), name=
"last_chunk")
1497 tag_reg = Wire(tag_type, name=
"tag_reg")
1498 inflight_full = Wire(Bits(1), name=
"inflight_full")
1500 max_chunk = UInt(length_width)(max_chunk_bytes)
1501 chunk_len = Mux(last_chunk, max_chunk, remaining)
1508 if word_shift == 0
or not round_read_lengths:
1509 chunk_len_out = chunk_len
1511 chunk_words = (chunk_len + UInt(length_width)(word_bytes - 1)
1512 ).as_bits()[word_shift:].as_uint(words_width)
1513 chunk_len_out = BitsSignal.concat(
1514 [chunk_words.as_bits(), Bits(word_shift)(0)]).as_uint(length_width)
1516 req_out_ch, req_out_ready = req_channel_type.wrap(
1518 "address": cur_addr,
1519 "length": chunk_len_out,
1522 ports.req_out = req_out_ch
1523 chunk_xact = emit_busy & req_out_ready
1524 emit_done = chunk_xact & last_chunk
1530 if max_outstanding > 1:
1531 emitter_free = ~emit_busy | emit_done
1533 emitter_free = ~emit_busy
1534 req_ready = Wire(Bits(1))
1535 req_payload, req_valid = ports.req_in.unwrap(req_ready)
1536 req_ready.assign(emitter_free & ~inflight_full)
1537 accept = req_valid & req_ready
1540 ControlReg(clk, rst, [accept], [emit_done], name=
"emit_busy_reg"))
1545 cur_addr_incr = (cur_addr +
1546 UInt(addr_width)(max_chunk_bytes)).as_uint(addr_width)
1548 Mux(accept, cur_addr_incr,
1549 req_payload.address).reg(clk,
1550 ce=accept | chunk_xact,
1551 name=
"cur_addr_reg"))
1552 remaining_dec = (remaining - max_chunk).as_uint(length_width)
1554 Mux(accept, remaining_dec,
1555 req_payload.length).reg(clk,
1556 ce=accept | chunk_xact,
1557 name=
"remaining_reg"))
1560 two_chunks = min(2 * max_chunk_bytes, (1 << length_width) - 1)
1562 Mux(accept, remaining <= UInt(length_width)(two_chunks),
1563 req_payload.length <= max_chunk).reg(clk,
1564 ce=accept | chunk_xact,
1565 name=
"last_chunk_reg"))
1566 tag_reg.assign(req_payload.tag.reg(clk, ce=accept, name=
"tag_reg_r"))
1571 len_m1 = (req_payload.length -
1572 UInt(length_width)(1)).as_uint(length_width)
1574 words_m1 = len_m1.as_uint(words_width)
1575 final_vb = UInt(vb_width)(0)
1577 words_m1 = len_m1.as_bits()[word_shift:].as_uint(words_width)
1578 final_vb = len_m1.as_bits()[:word_shift].as_uint(vb_width)
1579 meta_in = meta_struct({
1580 "words_m1": words_m1,
1581 "final_vb": final_vb,
1582 "single": req_payload.length <= UInt(length_width)(word_bytes),
1588 meta_push = Wire(Bits(1), name=
"meta_push")
1589 meta_pop = Wire(Bits(1), name=
"meta_pop")
1590 if max_outstanding == 1:
1592 meta_head = meta_in.reg(clk, ce=meta_push, name=
"meta_reg")
1593 meta_empty = ~ControlReg(
1594 clk, rst, [meta_push], [meta_pop], name=
"meta_valid")
1596 meta_fifo = SeqFIFO(meta_struct, max_outstanding, clk, rst)
1597 meta_fifo.push(meta_in, meta_push)
1598 meta_head = meta_fifo.pop(meta_pop)
1599 meta_empty = meta_fifo.empty
1606 cur_valid = Wire(Bits(1), name=
"cur_valid")
1607 cur_words = Wire(UInt(words_width), name=
"cur_words")
1608 cur_last = Wire(Bits(1), name=
"cur_last")
1609 cur_vb = Wire(UInt(vb_width), name=
"cur_vb")
1614 resp_ready = Wire(Bits(1))
1615 resp_payload, resp_valid = ports.resp_in.unwrap(resp_ready)
1616 resp_out_ch, resp_out_ready = resp_out_channel_type.wrap(
1624 UInt(vb_width)(word_bytes - 1), cur_vb),
1628 ports.resp_out = resp_out_ch
1629 resp_ready.assign(resp_out_ready)
1630 resp_xact = resp_valid & resp_out_ready
1631 burst_done = resp_xact & cur_last
1636 framer_free = ~cur_valid | burst_done
1637 load_head = ~meta_empty & framer_free
1638 bypass = accept & meta_empty & framer_free
1639 load = load_head | bypass
1640 meta_pop.assign(load_head)
1641 meta_push.assign(accept & ~bypass)
1642 next_meta = Mux(load_head, meta_in, meta_head)
1644 ControlReg(clk, rst, [load], [burst_done], name=
"cur_valid_reg"))
1645 cur_words_dec = (cur_words - UInt(words_width)(1)).as_uint(words_width)
1647 Mux(load, cur_words_dec,
1648 next_meta[
"words_m1"]).reg(clk,
1649 ce=load | resp_xact,
1650 name=
"cur_words_reg"))
1652 Mux(load, cur_words == UInt(words_width)(1),
1653 next_meta[
"single"]).reg(clk,
1654 ce=load | resp_xact,
1655 name=
"cur_last_reg"))
1656 cur_vb.assign(next_meta[
"final_vb"].reg(clk, ce=load, name=
"cur_vb_reg"))
1661 inflight = Wire(UInt(inflight_width), name=
"inflight")
1662 one = UInt(inflight_width)(1)
1663 inflight_inc = (inflight + one).as_uint(inflight_width)
1664 inflight_dec = (inflight - one).as_uint(inflight_width)
1665 inflight_next = Mux(accept, Mux(burst_done, inflight, inflight_dec),
1666 Mux(burst_done, inflight_inc, inflight))
1668 inflight_next.reg(clk, rst, rst_value=0, name=
"inflight_reg"))
1669 inflight_full.assign(
1670 (inflight_next == UInt(inflight_width)(max_outstanding)).reg(
1671 clk, rst, rst_value=0, name=
"inflight_full_reg"))
1673 return HostMemReadReqSplitterImpl
1679 reqs: List[esi._OutputBundleSetter],
1680 max_read_request_bytes: int = DEFAULT_MAX_READ_REQUEST_BYTES,
1681 max_outstanding_reads: int = 1,
1682 round_read_lengths: bool =
True):
1683 """Construct a host memory read request module to orchestrate the the read
1684 connections. Responsible for both gearboxing the data, multiplexing the
1685 requests, reassembling out-of-order responses and routing the responses to the
1688 `max_outstanding_reads` bounds the number of logical read requests each
1689 client may have in flight (see `HostMemReadReqSplitter`). Values above 1
1690 require the upstream to return each client's responses in request order.
1692 Generate this module dynamically to allow for multiple read clients of
1693 multiple types to be directly accomodated."""
1695 class HostmemReadProcessorImpl(
Module):
1700 reqPortMap: Dict[esi._OutputBundleSetter, str] = {}
1702 name =
"client_" + req.client_name_str
1703 locals()[name] =
Output(req.type)
1704 reqPortMap[req] = name
1707 upstream =
Output(hostmem_module.read.type)
1711 """Build the read side of the HostMem service."""
1715 upstream_req_channel, _ = Channel(hostmem_module.UpstreamReadReq).
wrap(
1721 upstream_read_bundle, _ = hostmem_module.read.type.pack(
1722 req=upstream_req_channel)
1723 ports.upstream = upstream_read_bundle
1730 assert len(reqs) <= 256,
"More than 256 read clients not supported."
1733 upstream_req_channel = Wire(Channel(hostmem_module.UpstreamReadReq))
1734 upstream_read_bundle, froms = hostmem_module.read.type.pack(
1735 req=upstream_req_channel)
1736 ports.upstream = upstream_read_bundle
1737 upstream_resp_channel = froms[
"resp"]
1743 demux = esi.TaggedDemux(len(reqs), upstream_resp_channel.type)(
1744 clk=ports.clk, rst=ports.rst, in_=upstream_resp_channel)
1746 word_bytes = read_width // 8
1747 tagged_client_reqs = []
1748 for idx, client
in enumerate(reqs):
1751 c.channel
for c
in client.type.channels
if c.name ==
'resp'
1753 demuxed_upstream_channel = demux.get_out(idx)
1761 client_type = resp_type.inner_type
1762 is_list = isinstance(client_type, Window)
1769 lowered = client_type.lowered_type
1770 lowered_fields = dict(lowered.fields)
1771 element_type = lowered_fields[
"data"]
1772 element_bits = element_type.bitwidth
1773 data_size_type = lowered_fields[
"data_size"]
1774 if element_bits == 0:
1775 raise ValueError(
"read_list element type cannot be zero-width.")
1777 if client_type.data.bitwidth == 0:
1778 raise ValueError(
"Client data type cannot be zero-width. Use a "
1779 "single-bit type if no data is needed.")
1780 element_bits = client_type.data.bitwidth
1783 elem_stride_bytes = (element_bits + 7) // 8
1793 max_chunk_bytes = (max_read_request_bytes // word_bytes) * word_bytes
1795 splitter_resp = Wire(gearbox_mod.in_.type)
1796 gearbox = gearbox_mod(clk=ports.clk, rst=ports.rst, in_=splitter_resp)
1800 client_resp_channel = gearbox.out.transform(
1801 lambda m, lowered=lowered, element_type=element_type,
1802 data_size_type=data_size_type, client_type=client_type:
1806 "data": m.data.bitcast(element_type),
1807 "data_size": data_size_type(0),
1810 client_bundle, froms = client.type.pack(resp=client_resp_channel)
1811 client_req = froms[
"req"]
1812 logical_req = client_req.transform(
1813 lambda r, idx=idx, elem_stride_bytes=elem_stride_bytes:
1814 hostmem_module.UpstreamReadReq({
1817 "length": (r.length * UInt(64)
1818 (elem_stride_bytes)).as_uint(32),
1824 client_resp_channel = gearbox.out.transform(
1825 lambda m, client_type=client_type: client_type({
1827 "data": m.data.bitcast(client_type.data)
1829 client_bundle, froms = client.type.pack(resp=client_resp_channel)
1830 client_req = froms[
"req"]
1831 logical_req = client_req.transform(
1832 lambda r, idx=idx, elem_stride_bytes=elem_stride_bytes:
1833 hostmem_module.UpstreamReadReq({
1834 "address": r.address,
1835 "length": UInt(32)(elem_stride_bytes),
1841 logical_req.type, demuxed_upstream_channel.type, max_chunk_bytes,
1842 max_outstanding_reads,
1843 round_read_lengths)(clk=ports.clk,
1846 resp_in=demuxed_upstream_channel)
1847 splitter_resp.assign(splitter.resp_out)
1848 tagged_client_req = splitter.req_out
1850 tagged_client_reqs.append(tagged_client_req)
1853 setattr(ports, HostmemReadProcessorImpl.reqPortMap[client],
1868 muxed_client_reqs = ChannelArbiter(tagged_client_reqs,
1871 mux_pipeline_levels=2,
1872 pipelined_scheduler=
True,
1874 upstream_req_channel.assign(muxed_client_reqs)
1875 HostmemReadProcessorImpl.reqPortMap.clear()
1877 return HostmemReadProcessorImpl
1882 max_burst_bytes: int) -> type[
"TaggedWriteGearboxImpl"]:
1883 """Build a gearbox to convert the client data to upstream write chunks.
1884 Assumes a struct {address, tag, data} and only gearboxes the data. Tag is
1885 stored separately and the struct is re-assembled later on.
1887 'max_burst_bytes' caps a single contiguous upstream write transaction (a
1888 max-payload-size analog): when an element spans more than 'max_burst_bytes',
1889 its engine words are split into multiple <= 'max_burst_bytes' transactions by
1890 emitting the framing 'last' at each boundary. 0 disables the cap."""
1892 if output_bitwidth % 8 != 0:
1893 raise ValueError(
"Output bitwidth must be a multiple of 8.")
1895 if input_bitwidth % 8 != 0:
1896 input_pad_bits = 8 - (input_bitwidth % 8)
1897 input_padded_bitwidth = input_bitwidth + input_pad_bits
1898 valid_bytes_type = Bits(clog2(output_bitwidth // 8 + 1))
1901 max_burst_words = (max_burst_bytes //
1902 (output_bitwidth // 8))
if max_burst_bytes
else 0
1904 assert (max_burst_words & (max_burst_words - 1)) == 0, \
1905 "max_burst_bytes / (output_bitwidth // 8) must be a power of two"
1907 class TaggedWriteGearboxImpl(
Module):
1912 (
"address", UInt(64)),
1913 (
"tag", esi.HostMem.TagType),
1914 (
"data", Bits(input_bitwidth)),
1916 out = OutputChannel(
1918 (
"address", UInt(64)),
1919 (
"tag", esi.HostMem.TagType),
1920 (
"data", Bits(output_bitwidth)),
1921 (
"valid_bytes", valid_bytes_type),
1925 num_chunks = ceil(input_padded_bitwidth / output_bitwidth)
1929 upstream_ready = Wire(Bits(1))
1930 ready_for_client = Wire(Bits(1))
1931 client_tag_and_data, client_valid = ports.in_.unwrap(ready_for_client)
1932 client_data = client_tag_and_data.data
1933 if input_pad_bits > 0:
1934 client_data = client_data.pad_or_truncate(input_padded_bitwidth)
1935 client_xact = ready_for_client & client_valid
1936 input_bitwidth_bytes = input_padded_bitwidth // 8
1937 output_bitwidth_bytes = output_bitwidth // 8
1941 if output_bitwidth == input_padded_bitwidth:
1942 upstream_data_bits = client_data
1943 upstream_valid = client_valid
1944 ready_for_client.assign(upstream_ready)
1945 tag = client_tag_and_data.tag
1946 address = client_tag_and_data.address
1947 valid_bytes = valid_bytes_type(input_bitwidth_bytes)
1949 elif output_bitwidth > input_padded_bitwidth:
1950 upstream_data_bits = client_data.as_bits(output_bitwidth)
1951 upstream_valid = client_valid
1952 ready_for_client.assign(upstream_ready)
1953 tag = client_tag_and_data.tag
1954 address = client_tag_and_data.address
1955 valid_bytes = valid_bytes_type(input_bitwidth_bytes)
1960 num_chunks = TaggedWriteGearboxImpl.num_chunks
1961 num_chunks_idx_bitwidth = clog2(num_chunks)
1962 if input_padded_bitwidth % output_bitwidth == 0:
1965 padding_numbits = output_bitwidth - (input_padded_bitwidth %
1967 client_data_padded = BitsSignal.concat(
1968 [Bits(padding_numbits)(0), client_data])
1970 client_data_padded[i * output_bitwidth:(i + 1) * output_bitwidth]
1971 for i
in range(num_chunks)
1974 c.reg(ports.clk, ce=client_xact, name=f
"chunk_{idx}")
1975 for idx, c
in enumerate(chunks)
1977 increment = Wire(Bits(1))
1978 clear = Wire(Bits(1))
1979 counter = Counter(num_chunks_idx_bitwidth)(clk=ports.clk,
1981 increment=increment,
1985 mux_values = chunk_regs + [chunk_regs[-1]] * (
1986 (1 << num_chunks_idx_bitwidth) - num_chunks)
1987 for level
in range(num_chunks_idx_bitwidth):
1989 Mux(counter.out.as_bits()[level], mux_values[i],
1990 mux_values[i + 1])
for i
in range(0, len(mux_values), 2)
1992 upstream_data_bits = mux_values[0]
1993 upstream_valid = ControlReg(ports.clk, ports.rst, [client_xact],
1995 upstream_xact = upstream_valid & upstream_ready
1996 clear.assign(upstream_xact & (counter.out == (num_chunks - 1)))
1997 increment.assign(upstream_xact)
1998 ready_for_client.assign(~upstream_valid)
1999 address_padding_bits = clog2(output_bitwidth_bytes)
2000 counter_bytes = BitsSignal.concat(
2001 [counter.out.as_bits(),
2002 Bits(address_padding_bits)(0)]).as_uint()
2005 tag_reg = client_tag_and_data.tag.reg(ports.clk,
2008 addr_reg = client_tag_and_data.address.reg(ports.clk,
2011 address = (addr_reg + counter_bytes).as_uint(64)
2013 elem_end = counter.out == (num_chunks - 1)
2015 elem_end, valid_bytes_type(output_bitwidth_bytes),
2016 valid_bytes_type((output_bitwidth - padding_numbits) // 8))
2017 if max_burst_words
and num_chunks > max_burst_words:
2023 burst_shift = clog2(max_burst_words)
2024 burst_end = (counter.out.as_bits()[:burst_shift].and_reduce()
2025 if burst_shift
else Bits(1)(1))
2026 last = elem_end | burst_end
2030 upstream_channel, upstrm_ready_sig = TaggedWriteGearboxImpl.out.type.wrap(
2034 "data": upstream_data_bits,
2035 "valid_bytes": valid_bytes,
2038 upstream_ready.assign(upstrm_ready_sig)
2039 ports.out = upstream_channel
2041 return TaggedWriteGearboxImpl
2045def EmitEveryN(message_type: Type, N: int) -> type[
'EmitEveryNImpl']:
2046 """Emit (forward) one message for every N input messages. The emitted message
2047 is the last one of the N received. N must be >= 1."""
2050 raise ValueError(
"N must be >= 1")
2052 class EmitEveryNImpl(
Module):
2055 in_ = InputChannel(message_type)
2056 out = OutputChannel(message_type)
2060 ready_for_in = Wire(Bits(1))
2061 in_data, in_valid = ports.in_.unwrap(ready_for_in)
2062 xact = in_valid & ready_for_in
2066 out_chan, out_ready = EmitEveryNImpl.out.type.wrap(in_data, in_valid)
2067 ready_for_in.assign(out_ready)
2068 ports.out = out_chan
2071 counter_width = clog2(N)
2072 counter_clear = Wire(Bits(1))
2073 counter = Counter(counter_width)(clk=ports.clk,
2076 clear=counter_clear)
2079 last_msg = in_data.reg(ports.clk, ports.rst, ce=xact, name=
"last_msg")
2081 hit_last = (counter.out == UInt(counter_width)(N - 1)) & xact
2082 counter_clear.assign(hit_last)
2084 emit_accepted = Wire(Bits(1))
2085 out_valid = ControlReg(ports.clk, ports.rst, [hit_last], [emit_accepted])
2087 out_chan, out_ready = EmitEveryNImpl.out.type.wrap(last_msg, out_valid)
2089 ready_for_in.assign(~(out_valid & ~out_ready))
2090 emit_accepted.assign(out_valid & out_ready)
2092 ports.out = out_chan
2094 return EmitEveryNImpl
2100 reqs: List[esi._OutputBundleSetter],
2101 max_write_payload_bytes: int = DEFAULT_MAX_WRITE_PAYLOAD_BYTES
2102) -> type[
"HostMemWriteProcessorImpl"]:
2103 """Construct a host memory write request module to orchestrate the the write
2104 connections. Responsible for both gearboxing the data, multiplexing the
2105 requests, reassembling out-of-order responses and routing the responses to the
2108 Generate this module dynamically to allow for multiple write clients of
2109 multiple types to be directly accomodated."""
2111 class HostMemWriteProcessorImpl(
Module):
2117 reqPortMap: Dict[esi._OutputBundleSetter, str] = {}
2119 name =
"client_" + req.client_name_str
2120 locals()[name] =
Output(req.type)
2121 reqPortMap[req] = name
2124 upstream =
Output(hostmem_module.write.type)
2134 size_width = clog2(write_width // 8)
2138 req, _ = Channel(hostmem_module.UpstreamWriteReq).
wrap(
2146 write_bundle, _ = hostmem_module.write.type.pack(req=req)
2147 ports.upstream = write_bundle
2150 assert len(reqs) <= 256,
"More than 256 write clients not supported."
2152 upstream_req_channel = Wire(Channel(hostmem_module.UpstreamWriteReq))
2153 upstream_write_bundle, froms = hostmem_module.write.type.pack(
2154 req=upstream_req_channel)
2155 ports.upstream = upstream_write_bundle
2156 upstream_ack_tag = froms[
"ackTag"]
2158 demuxed_acks = esi.TaggedDemux(len(reqs), upstream_ack_tag.type)(
2159 clk=ports.clk, rst=ports.rst, in_=upstream_ack_tag)
2164 write_channels: List[ChannelSignal] = []
2165 for idx, req
in enumerate(reqs):
2167 reqch = [c.channel
for c
in req.type.channels
if c.name ==
'req'][0]
2168 client_type = reqch.inner_type
2169 input_flit_ack = Wire(upstream_ack_tag.type)
2171 if isinstance(client_type, Window):
2176 bundle_sig, wfroms = req.type.pack(ackTag=input_flit_ack)
2177 windowed_req = wfroms[
"req"]
2178 lowered = client_type.lowered_type
2179 array_type = dict(lowered.fields)[
"data"]
2180 element_bits = array_type.element_type.bitwidth
2185 elem_stride = (element_bits + 7) // 8
2188 max_write_payload_bytes)
2189 gearbox_in_type = gearbox_mod.in_.type.inner_type
2193 ready_for_frame = Wire(Bits(1))
2194 frame_win, frame_valid = windowed_req.unwrap(ready_for_frame)
2195 frame = frame_win.unwrap()
2196 frame_xact = frame_valid & ready_for_frame
2197 elem_clear = Wire(Bits(1))
2198 elem_counter = Counter(64)(clk=ports.clk,
2201 increment=frame_xact)
2202 elem_clear.assign(frame_xact & frame[
"last"])
2203 elem_addr = (frame[
"address"] +
2204 elem_counter.out * UInt(64)(elem_stride)).as_uint(64)
2205 gearbox_in_chan, gearbox_in_ready = Channel(gearbox_in_type).
wrap(
2207 "tag": frame[
"tag"],
2208 "address": elem_addr,
2209 "data": frame[
"data"][0].bitcast(gearbox_in_type.data),
2211 ready_for_frame.assign(gearbox_in_ready)
2212 gearbox = gearbox_mod(clk=ports.clk,
2214 in_=gearbox_in_chan)
2217 write_req_bundle_type = esi.HostMem.write_req_bundle_type(
2219 bundle_sig, sfroms = write_req_bundle_type.pack(ackTag=input_flit_ack)
2221 write_width, max_write_payload_bytes)
2222 gearbox_in_type = gearbox_mod.in_.type.inner_type
2223 bitcast_client_req = sfroms[
"req"].transform(
2224 lambda m, git=gearbox_in_type: git({
2226 "address": m.address,
2227 "data": m.data.bitcast(git.data)
2229 gearbox = gearbox_mod(clk=ports.clk,
2231 in_=bitcast_client_req)
2233 write_channels.append(
2234 gearbox.out.transform(
2235 lambda m, idx=idx: hostmem_module.UpstreamWriteReq({
2242 "data_size": (m.valid_bytes.as_uint() - UInt(size_width + 1)
2243 (1)).as_bits()[:size_width],
2250 ack_every_n =
EmitEveryN(upstream_ack_tag.type, gearbox_mod.num_chunks)(
2251 clk=clk, rst=rst, in_=demuxed_acks.get_out(idx))
2252 input_flit_ack.assign(ack_every_n.out)
2255 setattr(ports, HostMemWriteProcessorImpl.reqPortMap[req], bundle_sig)
2266 muxed_write_channel = ChannelArbiter(write_channels,
2269 mux_pipeline_levels=2,
2270 pipelined_scheduler=
True,
2272 upstream_req_channel.assign(muxed_write_channel)
2274 return HostMemWriteProcessorImpl
2281 max_read_request_bytes: int = DEFAULT_MAX_READ_REQUEST_BYTES,
2282 max_write_payload_bytes: int = DEFAULT_MAX_WRITE_PAYLOAD_BYTES,
2283 max_outstanding_reads: int = 1,
2284 round_read_lengths: bool =
True) -> typing.Type[
'ChannelHostMemImpl']:
2285 """HostMem service multiplexing every client onto one read and one write
2286 bundle. `max_outstanding_reads` is the per-client limit on logical read
2287 requests in flight (see `HostMemReadReqSplitter`); values above 1 require the
2288 upstream to return each client's read responses in request order.
2289 `round_read_lengths` selects word-granular upstream reads (the default);
2290 byte-granular transports can disable it to avoid reading padding bytes."""
2292 class ChannelHostMemImpl(esi.ServiceImplementation):
2293 """Builds a HostMem service which multiplexes multiple HostMem clients into
2294 two (read and write) bundles of the given data width."""
2299 UpstreamReadReq = StructType([
2300 (
"address", UInt(64)),
2301 (
"length", UInt(32)),
2306 BundledChannel(
"req", ChannelDirection.TO, UpstreamReadReq),
2308 "resp", ChannelDirection.FROM,
2310 (
"tag", esi.HostMem.TagType),
2311 (
"data", Bits(read_width)),
2316 if write_width % 8 != 0:
2317 raise ValueError(
"Write width must be a multiple of 8.")
2318 UpstreamWriteReq = StructType([
2319 (
"address", UInt(64)),
2321 (
"data", Bits(write_width)),
2322 (
"data_size", Bits(clog2(write_width // 8))),
2327 BundledChannel(
"req", ChannelDirection.TO, UpstreamWriteReq),
2328 BundledChannel(
"ackTag", ChannelDirection.FROM, UInt(8)),
2332 def generate(ports, bundles: esi._ServiceGeneratorBundles):
2337 req
for req
in bundles.to_client_reqs
2338 if req.port
in (
'read',
'read_list')
2341 read_reqs, max_read_request_bytes,
2342 max_outstanding_reads,
2344 read_proc = read_proc_module(clk=ports.clk, rst=ports.rst)
2345 ports.read = read_proc.upstream
2346 for req
in read_reqs:
2347 req.assign(getattr(read_proc, read_proc_module.reqPortMap[req]))
2351 req
for req
in bundles.to_client_reqs
if req.port ==
'write'
2355 max_write_payload_bytes)
2356 write_proc = write_proc_module(clk=ports.clk, rst=ports.rst)
2357 ports.write = write_proc.upstream
2358 for req
in write_reqs:
2359 req.assign(getattr(write_proc, write_proc_module.reqPortMap[req]))
2361 return ChannelHostMemImpl
2447 to_host_engine_gen: Callable,
2448 from_host_engine_gen: Callable) -> type[
'ChannelEngineService']:
2449 """Returns a channel service implementation which calls
2450 to_host_engine_gen(<client_type>) or from_host_engine_gen(<client_type>) to
2451 generate the to_host and from_host engines for each channel. Does not support
2452 engines which can service multiple clients at once.
2454 Individual service requests may override the default engine pair by passing
2455 `options={"engine": "pkg.mod.attr"}` at the service-request call site (e.g.
2456 `HostComms.some_bundle(AppID(...), options={"engine": "..."})`). The path
2457 is resolved by `_resolve_engine_pair` and must yield a
2458 `(to_host_engine_gen, from_host_engine_gen)` tuple with the same call shape
2459 as the defaults; the override applies to every channel of that request's
2464 """Service implementation which services the clients via a per-channel DMA
2471 def build(ports, bundles: esi._ServiceGeneratorBundles):
2475 def build_engine_appid(client_appid: List[
esi.AppID],
2476 channel_name: str) -> str:
2477 appid_strings = [str(appid)
for appid
in client_appid]
2478 return f
"{'_'.join(appid_strings)}.{channel_name}"
2480 def build_engine(bc: BundledChannel,
2481 bundle_to_host_gen: Callable,
2482 bundle_from_host_gen: Callable,
2483 input_channel=
None) -> Type:
2484 idbase = build_engine_appid(bundle.client_name, bc.name)
2489 engine_client_type = bc.channel.inner_type
2490 is_void = (engine_client_type.bitwidth == 0)
2492 engine_client_type = Bits(8)
2493 if bc.direction == ChannelDirection.FROM:
2494 engine_mod = bundle_to_host_gen(engine_client_type)
2496 engine_mod = bundle_from_host_gen(engine_client_type)
2501 eng_details: Dict[str, object] = {
"engine_inst": eng_appid}
2502 if input_channel
is not None:
2506 input_channel = input_channel.transform(
lambda _: Bits(8)(0))
2507 if (engine_mod.input_channel.type.signaling
2508 != input_channel.type.signaling):
2509 input_channel = input_channel.buffer(
2513 output_signaling=engine_mod.input_channel.type.signaling)
2514 eng_inputs[
"input_channel"] = input_channel
2515 if hasattr(engine_mod,
"mmio"):
2516 mmio_appid =
esi.AppID(idbase +
".mmio")
2517 eng_inputs[
"mmio"] = esi.MMIO.read_write(mmio_appid)
2518 eng_details[
"mmio"] = mmio_appid
2519 if hasattr(engine_mod,
"hostmem_write"):
2520 eng_inputs[
"hostmem_write"] = esi.HostMem.write_from_bundle(
2522 engine_mod.hostmem_write.type,
2523 options=bundle.options)
2524 if hasattr(engine_mod,
"hostmem_read"):
2525 eng_inputs[
"hostmem_read"] = esi.HostMem.read_from_bundle(
2527 engine_mod.hostmem_read.type,
2528 options=bundle.options)
2529 if hasattr(engine_mod,
"hostmem_read_list"):
2530 eng_inputs[
"hostmem_read_list"] = esi.HostMem.read_list_from_bundle(
2531 esi.AppID(idbase +
".hostmem_read_list"),
2532 engine_mod.hostmem_read_list.type,
2533 options=bundle.options)
2534 engine = engine_mod(appid=eng_appid, **eng_inputs)
2535 engine_rec = bundles.emit_engine(engine, details=eng_details)
2536 engine_rec.add_record(bundle, {bc.name: {}})
2539 for bundle
in bundles.to_client_reqs:
2544 engine_override = bundle.options.get(
"engine")
2545 if engine_override
is None:
2546 bundle_to_host_gen = to_host_engine_gen
2547 bundle_from_host_gen = from_host_engine_gen
2552 bundle_type = bundle.type
2555 for bc
in bundle_type.channels:
2556 if bc.direction == ChannelDirection.TO:
2557 engine = build_engine(bc, bundle_to_host_gen, bundle_from_host_gen)
2558 out_chan = engine.output_channel
2560 if bc.channel.inner_type.bitwidth == 0:
2561 out_chan = out_chan.transform(
lambda _: Bits(0)(0))
2562 to_channels[bc.name] = out_chan
2564 client_bundle_sig, froms = bundle_type.pack(**to_channels)
2565 bundle.assign(client_bundle_sig)
2568 for bc
in bundle_type.channels:
2569 if bc.direction == ChannelDirection.FROM:
2570 build_engine(bc, bundle_to_host_gen, bundle_from_host_gen,
2573 return ChannelEngineService