CIRCT 23.0.0git
Loading...
Searching...
No Matches
common.py
Go to the documentation of this file.
1# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
2# See https://llvm.org/LICENSE.txt for license information.
3# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
4
5from __future__ import annotations
6from math import ceil
7
8from pycde.common import Clock, Input, InputChannel, Output, OutputChannel, Reset
9from pycde.constructs import (AssignableSignal, ControlReg, Counter, Mux,
10 NamedWire, Wire)
11from pycde import esi
12from pycde.module import Module, generator, modparams
13from pycde.signals import BitsSignal, ChannelSignal, StructSignal
14from pycde.support import clog2
15from pycde.system import System
16from pycde.types import (Array, Bits, Bundle, BundledChannel, Channel,
17 ChannelDirection, StructType, Type, UInt, Window)
18
19from typing import Callable, Dict, List, Tuple
20import typing
21
22MagicNumber = 0x207D98E5_E5100E51 # random + ESI__ESI
23VersionNumber = 0 # Version 0: format subject to change
24
25IndirectionMagicNumber = 0x312bf0cc_E5100E51 # random + ESI__ESI
26IndirectionVersionNumber = 0 # Version 0: format subject to change
27
28
29class ESI_Manifest_ROM(Module):
30 """Module which will be created later by CIRCT which will contain the
31 compressed manifest."""
32
33 module_name = "__ESI_Manifest_ROM"
34
35 clk = Clock()
36 address = Input(Bits(29))
37 # Data is two cycles delayed after address changes.
38 data = Output(Bits(64))
39
40
42 """Wrap the manifest ROM with ESI bundle."""
43
44 clk = Clock()
45 read = Input(esi.MMIO.read.type)
46
47 @generator
48 def build(self):
49 data, data_valid = Wire(Bits(64)), Wire(Bits(1))
50 data_chan, data_ready = Channel(Bits(64)).wrap(data, data_valid)
51 address_chan = self.read.unpack(data=data_chan)['offset']
52 address, address_valid = address_chan.unwrap(data_ready)
53 address_words = address.as_bits(32)[3:] # Lop off the lower three bits.
54
55 rom = ESI_Manifest_ROM(clk=self.clk, address=address_words)
56 data.assign(rom.data)
57 data_valid.assign(address_valid.reg(self.clk, name="data_valid", cycles=2))
58
59
60@modparams
61def HeaderMMIO(manifest_loc: int) -> Module:
62
63 class HeaderMMIO(Module):
64 """Construct the ESI header MMIO adhering to the MMIO layout specified in
65 the ChannelMMIO service implementation."""
66
67 clk = Clock()
68 rst = Reset()
69 read = Input(esi.MMIO.read.type)
70
71 @generator
72 def build(ports):
73 data_chan_wire = Wire(Channel(esi.MMIODataType))
74 input_bundles = ports.read.unpack(data=data_chan_wire)
75 address_chan = input_bundles['offset']
76
77 address_ready = Wire(Bits(1))
78 address, address_valid = address_chan.unwrap(address_ready)
79 address_words = address.as_bits()[3:] # Lop off the lower three bits.
80
81 cycles = Counter(64)(clk=ports.clk,
82 rst=ports.rst,
83 clear=Bits(1)(0),
84 increment=Bits(1)(1),
85 instance_name="cycle_counter")
86
87 # Layout the header as an array.
88 core_freq = System.current().core_freq
89 if core_freq is None:
90 core_freq = 0
91 header = Array(Bits(64), 8)([
92 0, # Generally a good idea to not use address 0.
93 MagicNumber, # ESI magic number.
94 VersionNumber, # ESI version number.
95 manifest_loc, # Absolute address of the manifest ROM.
96 0, # Reserved for future use.
97 cycles.out.as_bits(), # Cycle counter.
98 core_freq, # Core frequency, if known.
99 0,
100 ])
101 header.name = "header"
102 header_response_valid = address_valid # Zero latency read.
103 # Select the appropriate header index.
104 header_out = header[address_words[:3]]
105 header_out.name = "header_out"
106 # Wrap the response.
107 data_chan, data_chan_ready = Channel(esi.MMIODataType).wrap(
108 header_out, header_response_valid)
109 data_chan_wire.assign(data_chan)
110 address_ready.assign(data_chan_ready)
111
112 return HeaderMMIO
113
114
115@modparams
117 data_type: Type, num_outs: int,
118 next_sel_width: int) -> type["ChannelDemuxNImpl"]:
119 """N-way channel demultiplexer for valid/ready signaling. Contains
120 valid/ready registers on the output channels. The selection signal is now
121 embedded in the input channel payload as a struct {sel, data}. Input
122 signals ready when the selected output register is empty."""
123
124 assert num_outs >= 1, "num_outs must be at least 1."
125
126 class ChannelDemuxNImpl(Module):
127 clk = Clock()
128 rst = Reset()
129
130 # Input channel now carries selection along with data.
131 InPayloadType = StructType([
132 ("sel", Bits(clog2(num_outs))),
133 ("next_sel", Bits(next_sel_width)),
134 ("data", data_type),
135 ])
136 inp = Input(Channel(InPayloadType))
137 OutPayloadType = StructType([
138 ("next_sel", Bits(next_sel_width)),
139 ("data", data_type),
140 ])
141 # Outputs are channels of OutPayloadType, which includes both 'next_sel' and 'data' fields.
142 for i in range(num_outs):
143 locals()[f"output_{i}"] = Output(Channel(OutPayloadType))
144
145 @generator
146 def generate(ports) -> None:
147 # Half-stage demux: one register per output channel. Input is ready
148 # when the currently selected output register is empty (not valid).
149 clk = ports.clk
150 rst = ports.rst
151 sel_width = clog2(num_outs)
152
153 # Unwrap input with backpressure from selected output register.
154 input_ready = Wire(Bits(1), name="input_ready")
155 in_payload, in_valid = ports.inp.unwrap(input_ready)
156 in_sel = in_payload.sel
157 in_next_sel = in_payload.next_sel
158 in_data = in_payload.data
159
160 # Track per-output valid regs and build a purely combinational
161 # expression 'selected_valid_expr' = OR_i((sel==i)&valid_i). Avoid
162 # assigning to a Wire multiple times.
163 valid_regs: List[BitsSignal] = []
164 selected_valid_expr = Bits(1)(0)
165
166 for i in range(num_outs):
167 # Write when input transaction targets this output and output not holding data yet.
168 will_write = Wire(Bits(1), name=f"will_write_{i}")
169 write_cond = (in_valid & input_ready & (in_sel == Bits(sel_width)(i)))
170 will_write.assign(write_cond)
171
172 # Data and next_sel registers.
173 out_msg_reg = ChannelDemuxNImpl.OutPayloadType({
174 "next_sel": in_next_sel,
175 "data": in_data
176 }).reg(clk=clk, rst=rst, ce=will_write, name=f"out{i}_msg_reg")
177
178 # Valid register cleared on successful downstream consume.
179 consume = Wire(Bits(1), name=f"consume_{i}")
180 valid_reg = ControlReg(
181 clk=clk,
182 rst=rst,
183 asserts=[will_write],
184 resets=[consume],
185 name=f"out{i}_valid_reg",
186 )
187 valid_regs.append(valid_reg)
188
189 # Channel wrapper.
190 ch_sig, ch_ready = Channel(ChannelDemuxNImpl.OutPayloadType).wrap(
191 out_msg_reg, valid_reg)
192 setattr(ports, f"output_{i}", ch_sig)
193 consume.assign(valid_reg & ch_ready)
194
195 # Accumulate selected_valid expression.
196 selected_valid_expr = (selected_valid_expr | (
197 (in_sel == Bits(sel_width)(i)) & valid_reg)).as_bits()
198
199 # Input ready only when selected output has no valid data latched.
200 input_ready.assign((selected_valid_expr ^ Bits(1)(1)).as_bits())
201
202 def get_out(self, index: int) -> ChannelSignal:
203 return getattr(self, f"output_{index}")
204
205 return ChannelDemuxNImpl
206
207
208@modparams
210 data_type: Type, num_outs: int,
211 branching_factor_log2: int) -> type["ChannelDemuxTree"]:
212 """Pipelined N-way channel demultiplexer for valid/ready signaling. This
213 implementation uses a tree structure of
214 ChannelDemuxN_HalfStage_ReadyBlocking modules to reduce fanout pressure.
215 Supports maximum half-throughput to save complexity and area.
216 """
217
218 root_sel_width = clog2(num_outs)
219 # Simplify algorithm by making sure num_outs is a power of two.
220 num_outs = 2**root_sel_width
221 sel_width = branching_factor_log2
222 fanout = 2**sel_width
223
224 class ChannelDemuxTree(Module):
225 clk = Clock()
226 rst = Reset()
227 # Input now embeds selection bits alongside data.
228 InPayloadType = StructType([
229 ("sel", Bits(clog2(num_outs))),
230 ("data", data_type),
231 ])
232 inp = Input(Channel(InPayloadType))
233
234 # Outputs (data only).
235 for i in range(num_outs):
236 locals()[f"output_{i}"] = Output(Channel(data_type))
237
238 @generator
239 def build(ports) -> None:
240 assert branching_factor_log2 > 0
241 if num_outs == 1:
242 # Strip selection bits and return single channel.
243 setattr(ports, "output_0", ports.inp.transform(lambda p: p.data))
244 return
245
246 def payload_type(sel_width: int, next_sel_width: int) -> Type:
247 return StructType([
248 ("sel", Bits(sel_width)),
249 ("next_sel", Bits(next_sel_width)),
250 ("data", data_type),
251 ])
252
253 def next_sel_width_calc(curr_sel_width) -> int:
254 return max(curr_sel_width - sel_width, 0)
255
256 def payload_next(curr_msg: StructSignal) -> StructSignal:
257 """Given current level payload, produce next level payload by
258 stripping off the top selection bits."""
259
260 next_sel_width = next_sel_width_calc(curr_msg.next_sel.type.width)
261 curr_sel_width = curr_msg.next_sel.type.width
262 new_sel_width = min(curr_sel_width, sel_width)
263 return payload_type(
264 new_sel_width,
265 next_sel_width,
266 )({
267 # Use the MSB bits of next_sel as the next level selection.
268 "sel": (curr_msg.next_sel[next_sel_width:]
269 if curr_sel_width > 0 else Bits(0)(0)),
270 "next_sel": (curr_msg.next_sel[:next_sel_width]
271 if next_sel_width > 0 else Bits(0)(0)),
272 "data": curr_msg.data,
273 })
274
275 current_channels: List[ChannelSignal] = [
276 ports.inp.transform(lambda m: payload_type(0, root_sel_width)({
277 "sel": Bits(0)(0),
278 "next_sel": m.sel,
279 "data": m.data,
280 }))
281 ]
282
283 curr_sel_width = root_sel_width
284 level = 0
285 while len(current_channels) < num_outs:
286 next_level: List[ChannelSignal] = []
287 level_num_outs = min(2**curr_sel_width, fanout)
288 for i, c in enumerate(current_channels):
290 data_type,
291 num_outs=level_num_outs,
292 next_sel_width=next_sel_width_calc(curr_sel_width),
293 )(
294 clk=ports.clk,
295 rst=ports.rst,
296 inp=c.transform(payload_next),
297 instance_name=f"demux_l{level}_i{i}",
298 )
299 for j in range(level_num_outs):
300 next_level.append(dmux.get_out(j))
301 current_channels = next_level
302 curr_sel_width -= sel_width
303 level += 1
304
305 for i in range(num_outs):
306 # Strip off next_sel bits for final output.
307 setattr(
308 ports,
309 f"output_{i}",
310 current_channels[i].transform(lambda p: p.data),
311 )
312
313 def get_out(self, index: int) -> ChannelSignal:
314 return getattr(self, f"output_{index}")
315
316 return ChannelDemuxTree
317
318
319class ChannelMMIO(esi.ServiceImplementation):
320 """MMIO service implementation with MMIO bundle interfaces. Should be
321 relatively easy to adapt to physical interfaces by wrapping the wires to
322 channels then bundles. Allows the implementation to be shared and (hopefully)
323 platform independent.
324
325 Whether or not to support unaligned accesses is up to the clients. The header
326 and manifest do not support unaligned accesses and throw away the lower three
327 bits.
328
329 Only allows for one outstanding request at a time. If a client doesn't return
330 a response, the MMIO service will hang. TODO: add some kind of timeout.
331
332 Implementation-defined MMIO layout:
333 - 0x0: 0 constant
334 - 0x8: Magic number (0x207D98E5_E5100E51)
335 - 0x12: ESI version number (0)
336 - 0x18: Location of the manifest ROM (absolute address)
337
338 - 0x400: Start of MMIO space for requests. Mapping is contained in the
339 manifest so can be dynamically queried.
340
341 - addr(Manifest ROM) + 0: Size of compressed manifest
342 - addr(Manifest ROM) + 8: Start of compressed manifest
343
344 This layout _should_ be pretty standard, but different BSPs may have various
345 different restrictions. Any BSP which uses this service implementation will
346 have this layout, possibly with an offset or address window.
347 """
348
349 clk = Clock()
350 rst = Input(Bits(1))
351
352 cmd = Input(esi.MMIO.read_write.type)
353
354 # Amount of register space each client gets. This is a GIANT HACK and needs to
355 # be replaced by parameterizable services.
356 # TODO: make the amount of register space each client gets a parameter.
357 # Supporting this will require more address decode logic.
358 #
359 # TODO: only supports one outstanding transaction at a time. This is NOT
360 # enforced or checked! Enforce this.
361
362 RegisterSpace = 0x400
363 RegisterSpaceBits = RegisterSpace.bit_length() - 1
364 AddressMask = 0x3FF
365
366 # Start at this address for assigning MMIO addresses to service requests.
367 initial_offset: int = RegisterSpace
368
369 @generator
370 def generate(ports, bundles: esi._ServiceGeneratorBundles):
371 table, manifest_loc = ChannelMMIO.build_table(bundles)
372 ChannelMMIO.build_read(ports, manifest_loc, table)
373 return True
374
375 @staticmethod
376 def build_table(bundles) -> Tuple[Dict[int, AssignableSignal], int]:
377 """Build a table of read and write addresses to BundleSignals."""
378 offset = ChannelMMIO.initial_offset
379 table: Dict[int, AssignableSignal] = {}
380 for bundle in bundles.to_client_reqs:
381 if bundle.port == 'read':
382 table[offset] = bundle
383 bundle.add_record(details={
384 "offset": offset,
385 "size": ChannelMMIO.RegisterSpace,
386 "type": "ro"
387 })
388 offset += ChannelMMIO.RegisterSpace
389 elif bundle.port == 'read_write':
390 table[offset] = bundle
391 bundle.add_record(details={
392 "offset": offset,
393 "size": ChannelMMIO.RegisterSpace,
394 "type": "rw"
395 })
396 offset += ChannelMMIO.RegisterSpace
397 else:
398 assert False, "Unrecognized port name."
399
400 manifest_loc = offset
401 return table, manifest_loc
402
403 @staticmethod
404 def build_read(ports, manifest_loc: int, table: Dict[int, AssignableSignal]):
405 """Builds the read side of the MMIO service."""
406
407 # Instantiate the header and manifest ROM. Fill in the read_table with
408 # bundle wires to be assigned identically to the other MMIO clients.
409 header_bundle_wire = Wire(esi.MMIO.read.type)
410 table[0] = header_bundle_wire
411 HeaderMMIO(manifest_loc)(clk=ports.clk,
412 rst=ports.rst,
413 read=header_bundle_wire)
414
415 mani_bundle_wire = Wire(esi.MMIO.read.type)
416 table[manifest_loc] = mani_bundle_wire
417 ESI_Manifest_ROM_Wrapper(clk=ports.clk, read=mani_bundle_wire)
418
419 # Unpack the cmd bundle.
420 data_resp_channel = Wire(Channel(esi.MMIODataType))
421 counted_output = Wire(Channel(esi.MMIODataType))
422 cmd_channel = ports.cmd.unpack(data=counted_output)["cmd"]
423 counted_output.assign(data_resp_channel)
424
425 # Get the selection index and the address to hand off to the clients.
426 sel_bits, client_cmd_chan = ChannelMMIO.build_addr_read(
427 cmd_channel, len(table), manifest_loc)
428
429 # Build the demux/mux and assign the results of each appropriately.
430 read_clients_clog2 = clog2(len(table))
431 # Combine selection bits and command channel payload into a struct channel for the demux tree.
432 TreeInType = StructType([
433 ("sel", Bits(read_clients_clog2)),
434 ("data", client_cmd_chan.type.inner_type),
435 ])
436 sel_bits_truncated = sel_bits.pad_or_truncate(read_clients_clog2)
437 combined_cmd_chan = client_cmd_chan.transform(
438 lambda cmd, _sel=sel_bits_truncated: TreeInType({
439 "sel": _sel,
440 "data": cmd
441 }))
443 client_cmd_chan.type.inner_type, len(table), branching_factor_log2=2)(
444 clk=ports.clk,
445 rst=ports.rst,
446 inp=combined_cmd_chan,
447 instance_name="client_cmd_demux",
448 )
449 client_cmd_channels = [demux_inst.get_out(i) for i in range(len(table))]
450 client_data_channels = []
451 for (idx, offset) in enumerate(sorted(table.keys())):
452 bundle_wire = table[offset]
453 bundle_type = bundle_wire.type
454 if bundle_type == esi.MMIO.read.type:
455 offset = client_cmd_channels[idx].transform(lambda cmd: cmd.offset)
456 bundle, bundle_froms = esi.MMIO.read.type.pack(offset=offset)
457 elif bundle_type == esi.MMIO.read_write.type:
458 bundle, bundle_froms = esi.MMIO.read_write.type.pack(
459 cmd=client_cmd_channels[idx])
460 else:
461 assert False, "Unrecognized bundle type."
462 bundle_wire.assign(bundle)
463 client_data_channels.append(bundle_froms["data"])
464 resp_channel = esi.ChannelMux(client_data_channels)
465 data_resp_channel.assign(resp_channel)
466
467 @staticmethod
468 def build_addr_read(read_addr_chan: ChannelSignal, num_clients: int,
469 manifest_loc: int) -> Tuple[BitsSignal, ChannelSignal]:
470 """Build a channel for the address read request. Returns the index to select
471 the client and a channel for the masked address to be passed to the
472 clients."""
473
474 # Decoding the selection bits is very simple as of now. This might need to
475 # change to support more flexibility in addressing. Not clear if what we're
476 # doing now it sufficient or not.
477
478 manifest_loc_const = UInt(32)(manifest_loc)
479
480 cmd_ready_wire = Wire(Bits(1))
481 cmd, cmd_valid = read_addr_chan.unwrap(cmd_ready_wire)
482 is_manifest_read = cmd.offset >= manifest_loc_const
483 sel_bits = NamedWire(Bits(32 - ChannelMMIO.RegisterSpaceBits), "sel_bits")
484 # If reading the manifest, override the selection to select the manifest instead.
485 sel_bits.assign(
486 Mux(is_manifest_read,
487 cmd.offset.as_bits()[ChannelMMIO.RegisterSpaceBits:],
488 Bits(32 - ChannelMMIO.RegisterSpaceBits)(num_clients - 1)))
489 regular_client_offset = (cmd.offset.as_bits() &
490 Bits(32)(ChannelMMIO.AddressMask)).as_uint()
491 offset = Mux(is_manifest_read, regular_client_offset,
492 (cmd.offset - manifest_loc_const).as_uint(32))
493 client_cmd = NamedWire(esi.MMIOReadWriteCmdType, "client_cmd")
494 client_cmd.assign(
495 esi.MMIOReadWriteCmdType({
496 "write": cmd.write,
497 "offset": offset,
498 "data": cmd.data
499 }))
500 client_addr_chan, client_addr_ready = Channel(
501 esi.MMIOReadWriteCmdType).wrap(client_cmd, cmd_valid)
502 cmd_ready_wire.assign(client_addr_ready)
503 return sel_bits, client_addr_chan
504
505
506class MMIOIndirection(Module):
507 """Some platforms do not support MMIO space greater than a certain size (e.g.
508 Vitis 2022's limit is 4k). This module implements a level of indirection to
509 provide access to a full 32-bit address space.
510
511 MMIO addresses:
512 - 0x0: 0 constant
513 - 0x8: 64 bit ESI magic number for Indirect MMIO (0x312bf0cc_E5100E51)
514 - 0x10: Version number for Indirect MMIO (0)
515 - 0x18: Location of read/write in the virtual MMIO space.
516 - 0x20: A read from this location will initiate a read in the virtual MMIO
517 space specified by the address stored in 0x18 and return the result.
518 A write to this location will initiate a write into the virtual MMIO
519 space to the virtual address specified in 0x18.
520 """
521 clk = Clock()
522 rst = Reset()
523
524 upstream = Input(esi.MMIO.read_write.type)
525 downstream = Output(esi.MMIO.read_write.type)
526
527 @generator
528 def build(ports):
529 # This implementation assumes there is only one outstanding upstream MMIO
530 # transaction in flight at once. TODO: enforce this or make it more robust.
531
532 reg_bits = 8
533 location_reg = UInt(reg_bits)(0x18)
534 indirect_mmio_reg = UInt(reg_bits)(0x20)
535 virt_address = Wire(UInt(32))
536
537 # Set up the upstream MMIO interface. Capture last upstream command in a
538 # mailbox which never empties to give access to the last command for all
539 # time.
540 upstream_resp_chan_wire = Wire(Channel(esi.MMIODataType))
541 upstream_cmd_chan = ports.upstream.unpack(
542 data=upstream_resp_chan_wire)["cmd"]
543 _, _, upstream_cmd_data = upstream_cmd_chan.snoop()
544
545 # Set up a channel demux to separate the MMIO commands which get processed
546 # locally with ones which should be transformed and fowarded downstream.
547 phys_loc = upstream_cmd_data.offset.as_uint(reg_bits)
548 fwd_upstream = NamedWire(phys_loc == indirect_mmio_reg, "fwd_upstream")
549 local_reg_cmd_chan, downstream_cmd_channel = esi.ChannelDemux(
550 upstream_cmd_chan, fwd_upstream, 2, "upstream_demux")
551
552 # Set up the downstream MMIO interface.
553 downstream_cmd_channel = downstream_cmd_channel.transform(
554 lambda cmd: esi.MMIOReadWriteCmdType({
555 "write": cmd.write,
556 "offset": virt_address,
557 "data": cmd.data
558 }))
559 ports.downstream, froms = esi.MMIO.read_write.type.pack(
560 cmd=downstream_cmd_channel)
561 downstream_data_chan = froms["data"]
562
563 # Process local regs.
564 (local_reg_cmd_valid, local_reg_cmd_ready,
565 local_reg_cmd) = local_reg_cmd_chan.snoop()
566 write_virt_address = (local_reg_cmd_valid & local_reg_cmd_ready &
567 local_reg_cmd.write & (phys_loc == location_reg))
568 virt_address.assign(
569 local_reg_cmd.data.as_uint(32).reg(
570 name="virt_address",
571 clk=ports.clk,
572 ce=write_virt_address,
573 ))
574
575 # Build the pysical MMIO register space.
576 local_reg_resp_array = Array(Bits(64), 4)([
577 0x0, # 0x0
578 IndirectionMagicNumber, # 0x8
579 IndirectionVersionNumber, # 0x10
580 virt_address.as_bits(64), # 0x18
581 ])
582 local_reg_resp_chan = local_reg_cmd_chan.transform(
583 lambda cmd: local_reg_resp_array[cmd.offset.as_uint(2)])
584
585 # Mux together the local register responses and the downstream data to
586 # create the upstream response.
587 upstream_resp = esi.ChannelMux([local_reg_resp_chan, downstream_data_chan])
588 upstream_resp_chan_wire.assign(upstream_resp)
589
590
591@modparams
592def TaggedReadGearbox(input_bitwidth: int,
593 output_bitwidth: int) -> type["TaggedReadGearboxImpl"]:
594 """Build a gearbox to convert the upstream data to the client data
595 type. Assumes a struct {tag, data} and only gearboxes the data. Tag is stored
596 separately and the struct is re-assembled later on."""
597
598 class TaggedReadGearboxImpl(Module):
599 clk = Clock()
600 rst = Reset()
601 in_ = InputChannel(
602 StructType([
603 ("tag", esi.HostMem.TagType),
604 ("data", Bits(input_bitwidth)),
605 ]))
606 out = OutputChannel(
607 StructType([
608 ("tag", esi.HostMem.TagType),
609 ("data", Bits(output_bitwidth)),
610 ]))
611
612 @generator
613 def build(ports):
614 ready_for_upstream = Wire(Bits(1), name="ready_for_upstream")
615 upstream_tag_and_data, upstream_valid = ports.in_.unwrap(
616 ready_for_upstream)
617 upstream_data = upstream_tag_and_data.data
618 upstream_xact = ready_for_upstream & upstream_valid
619
620 # Determine if gearboxing is necessary and whether it needs to be
621 # gearboxed up or just sliced down.
622 if output_bitwidth == input_bitwidth:
623 client_data_bits = upstream_data
624 client_valid = upstream_valid
625 elif output_bitwidth < input_bitwidth:
626 client_data_bits = upstream_data[:output_bitwidth]
627 client_valid = upstream_valid
628 else:
629 # Create registers equal to the number of upstream transactions needed
630 # to fill the client data. Set the output to the concatenation of said
631 # registers.
632 chunks = ceil(output_bitwidth / input_bitwidth)
633 reg_ces = [Wire(Bits(1)) for _ in range(chunks)]
634 regs = [
635 upstream_data.reg(ports.clk,
636 ports.rst,
637 ce=reg_ces[idx],
638 name=f"chunk_reg_{idx}") for idx in range(chunks)
639 ]
640 client_data_bits = BitsSignal.concat(reversed(regs))[:output_bitwidth]
641
642 # Use counter to determine to which register to write and determine if
643 # the registers are all full.
644 clear_counter = Wire(Bits(1))
645 counter_width = clog2(chunks)
646 counter = Counter(counter_width)(clk=ports.clk,
647 rst=ports.rst,
648 clear=clear_counter,
649 increment=upstream_xact)
650 set_client_valid = counter.out == chunks - 1
651 client_xact = Wire(Bits(1))
652 client_valid = ControlReg(ports.clk, ports.rst,
653 [set_client_valid & upstream_xact],
654 [client_xact])
655 client_xact.assign(client_valid & ready_for_upstream)
656 clear_counter.assign(client_xact)
657 for idx, reg_ce in enumerate(reg_ces):
658 reg_ce.assign(upstream_xact &
659 (counter.out == UInt(counter_width)(idx)))
660
661 # Construct the output channel. Shared logic across all three cases.
662 tag_reg = upstream_tag_and_data.tag.reg(ports.clk,
663 ports.rst,
664 ce=upstream_xact,
665 name="tag_reg")
666
667 client_channel, client_ready = TaggedReadGearboxImpl.out.type.wrap(
668 {
669 "tag": tag_reg,
670 "data": client_data_bits,
671 }, client_valid)
672 ready_for_upstream.assign(client_ready)
673 ports.out = client_channel
674
675 return TaggedReadGearboxImpl
676
677
678def HostmemReadProcessor(read_width: int, hostmem_module,
679 reqs: List[esi._OutputBundleSetter]):
680 """Construct a host memory read request module to orchestrate the the read
681 connections. Responsible for both gearboxing the data, multiplexing the
682 requests, reassembling out-of-order responses and routing the responses to the
683 correct clients.
684
685 Generate this module dynamically to allow for multiple read clients of
686 multiple types to be directly accomodated."""
687
688 class HostmemReadProcessorImpl(Module):
689 clk = Clock()
690 rst = Reset()
691
692 # Add an output port for each read client.
693 reqPortMap: Dict[esi._OutputBundleSetter, str] = {}
694 for req in reqs:
695 name = "client_" + req.client_name_str
696 locals()[name] = Output(req.type)
697 reqPortMap[req] = name
698
699 # And then the port which goes to the host.
700 upstream = Output(hostmem_module.read.type)
701
702 @generator
703 def build(ports):
704 """Build the read side of the HostMem service."""
705
706 # If there's no read clients, just return a no-op read bundle.
707 if len(reqs) == 0:
708 upstream_req_channel, _ = Channel(hostmem_module.UpstreamReadReq).wrap(
709 {
710 "tag": 0,
711 "length": 0,
712 "address": 0
713 }, 0)
714 upstream_read_bundle, _ = hostmem_module.read.type.pack(
715 req=upstream_req_channel)
716 ports.upstream = upstream_read_bundle
717 return
718
719 # Since we use the tag to identify the client, we can't have more than 256
720 # read clients. Supporting more than 256 clients would require
721 # tag-rewriting, which we'll probably have to implement at some point.
722 # TODO: Implement tag-rewriting.
723 assert len(reqs) <= 256, "More than 256 read clients not supported."
724
725 # Pack the upstream bundle and leave the request as a wire.
726 upstream_req_channel = Wire(Channel(hostmem_module.UpstreamReadReq))
727 upstream_read_bundle, froms = hostmem_module.read.type.pack(
728 req=upstream_req_channel)
729 ports.upstream = upstream_read_bundle
730 upstream_resp_channel = froms["resp"]
731
732 demux = esi.TaggedDemux(len(reqs), upstream_resp_channel.type)(
733 clk=ports.clk, rst=ports.rst, in_=upstream_resp_channel)
734
735 tagged_client_reqs = []
736 for idx, client in enumerate(reqs):
737 # Find the response channel in the request bundle.
738 resp_type = [
739 c.channel for c in client.type.channels if c.name == 'resp'
740 ][0]
741 demuxed_upstream_channel = demux.get_out(idx)
742
743 # TODO: Should responses come back out-of-order (interleaved tags),
744 # re-order them here so the gearbox doesn't get confused. (Longer term.)
745 # For now, only support one outstanding transaction at a time. This has
746 # the additional benefit of letting the upstream tag be the client
747 # identifier. TODO: Implement the gating logic here.
748
749 # Gearbox the data to the client's data type.
750 client_type = resp_type.inner_type
751 if client_type.data.bitwidth == 0:
752 raise ValueError("Client data type cannot be zero-width. Use a "
753 "single-bit type if no data is needed.")
754
755 gearbox = TaggedReadGearbox(read_width, client_type.data.bitwidth)(
756 clk=ports.clk, rst=ports.rst, in_=demuxed_upstream_channel)
757 client_resp_channel = gearbox.out.transform(lambda m: client_type({
758 "tag": m.tag,
759 "data": m.data.bitcast(client_type.data)
760 }))
761
762 # Assign the client response to the correct port.
763 client_bundle, froms = client.type.pack(resp=client_resp_channel)
764 client_req = froms["req"]
765 tagged_client_req = client_req.transform(
766 lambda r: hostmem_module.UpstreamReadReq({
767 "address": r.address,
768 "length": (client_type.data.bitwidth + 7) // 8,
769 # TODO: Change this once we support tag-rewriting.
770 "tag": idx
771 }))
772 tagged_client_reqs.append(tagged_client_req)
773
774 # Set the port for the client request.
775 setattr(ports, HostmemReadProcessorImpl.reqPortMap[client],
776 client_bundle)
777
778 # Assign the multiplexed read request to the upstream request.
779 # TODO: Don't release a request until the client is ready to accept
780 # the response otherwise the system could deadlock.
781 muxed_client_reqs = esi.ChannelMux(tagged_client_reqs)
782 upstream_req_channel.assign(muxed_client_reqs)
783 HostmemReadProcessorImpl.reqPortMap.clear()
784
785 return HostmemReadProcessorImpl
786
787
788@modparams
789def TaggedWriteGearbox(input_bitwidth: int,
790 output_bitwidth: int) -> type["TaggedWriteGearboxImpl"]:
791 """Build a gearbox to convert the client data to upstream write chunks.
792 Assumes a struct {address, tag, data} and only gearboxes the data. Tag is
793 stored separately and the struct is re-assembled later on."""
794
795 if output_bitwidth % 8 != 0:
796 raise ValueError("Output bitwidth must be a multiple of 8.")
797 input_pad_bits = 0
798 if input_bitwidth % 8 != 0:
799 input_pad_bits = 8 - (input_bitwidth % 8)
800 input_padded_bitwidth = input_bitwidth + input_pad_bits
801
802 class TaggedWriteGearboxImpl(Module):
803 clk = Clock()
804 rst = Reset()
805 in_ = InputChannel(
806 StructType([
807 ("address", UInt(64)),
808 ("tag", esi.HostMem.TagType),
809 ("data", Bits(input_bitwidth)),
810 ]))
811 out = OutputChannel(
812 StructType([
813 ("address", UInt(64)),
814 ("tag", esi.HostMem.TagType),
815 ("data", Bits(output_bitwidth)),
816 ("valid_bytes", Bits(8)),
817 ]))
818
819 num_chunks = ceil(input_padded_bitwidth / output_bitwidth)
820
821 @generator
822 def build(ports):
823 upstream_ready = Wire(Bits(1))
824 ready_for_client = Wire(Bits(1))
825 client_tag_and_data, client_valid = ports.in_.unwrap(ready_for_client)
826 client_data = client_tag_and_data.data
827 if input_pad_bits > 0:
828 client_data = client_data.pad_or_truncate(input_padded_bitwidth)
829 client_xact = ready_for_client & client_valid
830 input_bitwidth_bytes = input_padded_bitwidth // 8
831 output_bitwidth_bytes = output_bitwidth // 8
832
833 # Determine if gearboxing is necessary and whether it needs to be
834 # gearboxed up or just sliced down.
835 if output_bitwidth == input_padded_bitwidth:
836 upstream_data_bits = client_data
837 upstream_valid = client_valid
838 ready_for_client.assign(upstream_ready)
839 tag = client_tag_and_data.tag
840 address = client_tag_and_data.address
841 valid_bytes = Bits(8)(input_bitwidth_bytes)
842 elif output_bitwidth > input_padded_bitwidth:
843 upstream_data_bits = client_data.as_bits(output_bitwidth)
844 upstream_valid = client_valid
845 ready_for_client.assign(upstream_ready)
846 tag = client_tag_and_data.tag
847 address = client_tag_and_data.address
848 valid_bytes = Bits(8)(input_bitwidth_bytes)
849 else:
850 # Create registers equal to the number of upstream transactions needed
851 # to complete the transmission.
852 num_chunks = TaggedWriteGearboxImpl.num_chunks
853 num_chunks_idx_bitwidth = clog2(num_chunks)
854 if input_padded_bitwidth % output_bitwidth == 0:
855 padding_numbits = 0
856 else:
857 padding_numbits = output_bitwidth - (input_padded_bitwidth %
858 output_bitwidth)
859 client_data_padded = BitsSignal.concat(
860 [Bits(padding_numbits)(0), client_data])
861 chunks = [
862 client_data_padded[i * output_bitwidth:(i + 1) * output_bitwidth]
863 for i in range(num_chunks)
864 ]
865 chunk_regs = Array(Bits(output_bitwidth), num_chunks)([
866 c.reg(ports.clk, ce=client_xact, name=f"chunk_{idx}")
867 for idx, c in enumerate(chunks)
868 ])
869 increment = Wire(Bits(1))
870 clear = Wire(Bits(1))
871 counter = Counter(num_chunks_idx_bitwidth)(clk=ports.clk,
872 rst=ports.rst,
873 increment=increment,
874 clear=clear)
875 upstream_data_bits = chunk_regs[counter.out]
876 upstream_valid = ControlReg(ports.clk, ports.rst, [client_xact],
877 [clear])
878 upstream_xact = upstream_valid & upstream_ready
879 clear.assign(upstream_xact & (counter.out == (num_chunks - 1)))
880 increment.assign(upstream_xact)
881 ready_for_client.assign(~upstream_valid)
882 address_padding_bits = clog2(output_bitwidth_bytes)
883 counter_bytes = BitsSignal.concat(
884 [counter.out.as_bits(),
885 Bits(address_padding_bits)(0)]).as_uint()
886
887 # Construct the output channel. Shared logic across all three cases.
888 tag_reg = client_tag_and_data.tag.reg(ports.clk,
889 ce=client_xact,
890 name="tag_reg")
891 addr_reg = client_tag_and_data.address.reg(ports.clk,
892 ce=client_xact,
893 name="address_reg")
894 address = (addr_reg + counter_bytes).as_uint(64)
895 tag = tag_reg
896 valid_bytes = Mux(counter.out == (num_chunks - 1),
897 Bits(8)(output_bitwidth_bytes),
898 Bits(8)((output_bitwidth - padding_numbits) // 8))
899
900 upstream_channel, upstrm_ready_sig = TaggedWriteGearboxImpl.out.type.wrap(
901 {
902 "address": address,
903 "tag": tag,
904 "data": upstream_data_bits,
905 "valid_bytes": valid_bytes
906 }, upstream_valid)
907 upstream_ready.assign(upstrm_ready_sig)
908 ports.out = upstream_channel
909
910 return TaggedWriteGearboxImpl
911
912
913@modparams
914def EmitEveryN(message_type: Type, N: int) -> type['EmitEveryNImpl']:
915 """Emit (forward) one message for every N input messages. The emitted message
916 is the last one of the N received. N must be >= 1."""
917
918 if N < 1:
919 raise ValueError("N must be >= 1")
920
921 class EmitEveryNImpl(Module):
922 clk = Clock()
923 rst = Reset()
924 in_ = InputChannel(message_type)
925 out = OutputChannel(message_type)
926
927 @generator
928 def build(ports):
929 ready_for_in = Wire(Bits(1))
930 in_data, in_valid = ports.in_.unwrap(ready_for_in)
931 xact = in_valid & ready_for_in
932
933 # Fast path: N == 1 -> pass-through.
934 if N == 1:
935 out_chan, out_ready = EmitEveryNImpl.out.type.wrap(in_data, in_valid)
936 ready_for_in.assign(out_ready)
937 ports.out = out_chan
938 return
939
940 counter_width = clog2(N)
941 increment = xact
942 clear = Wire(Bits(1))
943 counter = Counter(counter_width)(clk=ports.clk,
944 rst=ports.rst,
945 increment=increment,
946 clear=clear)
947
948 # Capture last message of the group.
949 last_msg = in_data.reg(ports.clk, ports.rst, ce=xact, name="last_msg")
950
951 hit_last = (counter.out == UInt(counter_width)(N - 1)) & xact
952 out_valid = ControlReg(ports.clk, ports.rst, [hit_last], [clear])
953
954 out_chan, out_ready = EmitEveryNImpl.out.type.wrap(last_msg, out_valid)
955 # Stall input while waiting for downstream to accept the aggregated output.
956 ready_for_in.assign(~(out_valid & ~out_ready))
957 clear.assign(out_valid & out_ready) # Clear after successful emit.
958
959 ports.out = out_chan
960
961 return EmitEveryNImpl
962
963
965 write_width: int, hostmem_module,
966 reqs: List[esi._OutputBundleSetter]) -> type["HostMemWriteProcessorImpl"]:
967 """Construct a host memory write request module to orchestrate the the write
968 connections. Responsible for both gearboxing the data, multiplexing the
969 requests, reassembling out-of-order responses and routing the responses to the
970 correct clients.
971
972 Generate this module dynamically to allow for multiple write clients of
973 multiple types to be directly accomodated."""
974
975 class HostMemWriteProcessorImpl(Module):
976
977 clk = Clock()
978 rst = Reset()
979
980 # Add an output port for each read client.
981 reqPortMap: Dict[esi._OutputBundleSetter, str] = {}
982 for req in reqs:
983 name = "client_" + req.client_name_str
984 locals()[name] = Output(req.type)
985 reqPortMap[req] = name
986
987 # And then the port which goes to the host.
988 upstream = Output(hostmem_module.write.type)
989
990 @generator
991 def build(ports):
992 clk = ports.clk
993 rst = ports.rst
994
995 # If there's no write clients, just create a no-op write bundle
996 if len(reqs) == 0:
997 req, _ = Channel(hostmem_module.UpstreamWriteReq).wrap(
998 {
999 "address": 0,
1000 "tag": 0,
1001 "data": 0,
1002 "valid_bytes": 0,
1003 }, 0)
1004 write_bundle, _ = hostmem_module.write.type.pack(req=req)
1005 ports.upstream = write_bundle
1006 return
1007
1008 assert len(reqs) <= 256, "More than 256 write clients not supported."
1009
1010 upstream_req_channel = Wire(Channel(hostmem_module.UpstreamWriteReq))
1011 upstream_write_bundle, froms = hostmem_module.write.type.pack(
1012 req=upstream_req_channel)
1013 ports.upstream = upstream_write_bundle
1014 upstream_ack_tag = froms["ackTag"]
1015
1016 demuxed_acks = esi.TaggedDemux(len(reqs), upstream_ack_tag.type)(
1017 clk=ports.clk, rst=ports.rst, in_=upstream_ack_tag)
1018
1019 # TODO: re-write the tags and store the client and client tag.
1020
1021 # Build the write request channels and ack wires.
1022 write_channels: List[ChannelSignal] = []
1023 for idx, req in enumerate(reqs):
1024 # Get the request channel and its data type.
1025 reqch = [c.channel for c in req.type.channels if c.name == 'req'][0]
1026 client_type = reqch.inner_type
1027 if isinstance(client_type.data, Window):
1028 client_type = client_type.lowered_type
1029
1030 # Pack up the bundle and assign the request channel.
1031 write_req_bundle_type = esi.HostMem.write_req_bundle_type(
1032 client_type.data)
1033 input_flit_ack = Wire(upstream_ack_tag.type)
1034 bundle_sig, froms = write_req_bundle_type.pack(ackTag=input_flit_ack)
1035
1036 gearbox_mod = TaggedWriteGearbox(client_type.data.bitwidth, write_width)
1037 gearbox_in_type = gearbox_mod.in_.type.inner_type
1038 tagged_client_req = froms["req"]
1039 bitcast_client_req = tagged_client_req.transform(
1040 lambda m: gearbox_in_type({
1041 "tag": m.tag,
1042 "address": m.address,
1043 "data": m.data.bitcast(gearbox_in_type.data)
1044 }))
1045
1046 # Gearbox the data to the client's data type.
1047 gearbox = gearbox_mod(clk=ports.clk,
1048 rst=ports.rst,
1049 in_=bitcast_client_req)
1050 write_channels.append(
1051 gearbox.out.transform(lambda m: m.type({
1052 "address": m.address,
1053 "tag": idx,
1054 "data": m.data,
1055 "valid_bytes": m.valid_bytes
1056 })))
1057
1058 # Count the number of acks received from hostmem for this client
1059 # and only send one back to the client per input.
1060 ack_every_n = EmitEveryN(upstream_ack_tag.type, gearbox_mod.num_chunks)(
1061 clk=clk, rst=rst, in_=demuxed_acks.get_out(idx))
1062 input_flit_ack.assign(ack_every_n.out)
1063
1064 # Set the port for the client request.
1065 setattr(ports, HostMemWriteProcessorImpl.reqPortMap[req], bundle_sig)
1066
1067 # Build a channel mux for the write requests.
1068 muxed_write_channel = esi.ChannelMux(write_channels)
1069 upstream_req_channel.assign(muxed_write_channel)
1070
1071 return HostMemWriteProcessorImpl
1072
1073
1074@modparams
1075def ChannelHostMem(read_width: int,
1076 write_width: int) -> typing.Type['ChannelHostMemImpl']:
1077
1078 class ChannelHostMemImpl(esi.ServiceImplementation):
1079 """Builds a HostMem service which multiplexes multiple HostMem clients into
1080 two (read and write) bundles of the given data width."""
1081
1082 clk = Clock()
1083 rst = Reset()
1084
1085 UpstreamReadReq = StructType([
1086 ("address", UInt(64)),
1087 ("length", UInt(32)), # In bytes.
1088 ("tag", UInt(8)),
1089 ])
1090 read = Output(
1091 Bundle([
1092 BundledChannel("req", ChannelDirection.TO, UpstreamReadReq),
1093 BundledChannel(
1094 "resp", ChannelDirection.FROM,
1095 StructType([
1096 ("tag", esi.HostMem.TagType),
1097 ("data", Bits(read_width)),
1098 ])),
1099 ]))
1100
1101 if write_width % 8 != 0:
1102 raise ValueError("Write width must be a multiple of 8.")
1103 UpstreamWriteReq = StructType([
1104 ("address", UInt(64)),
1105 ("tag", UInt(8)),
1106 ("data", Bits(write_width)),
1107 ("valid_bytes", Bits(8)),
1108 ])
1109 write = Output(
1110 Bundle([
1111 BundledChannel("req", ChannelDirection.TO, UpstreamWriteReq),
1112 BundledChannel("ackTag", ChannelDirection.FROM, UInt(8)),
1113 ]))
1114
1115 @generator
1116 def generate(ports, bundles: esi._ServiceGeneratorBundles):
1117 # Split the read side out into a separate module. Must assign the output
1118 # ports to the clients since we can't service a request in a different
1119 # module.
1120 read_reqs = [req for req in bundles.to_client_reqs if req.port == 'read']
1121 read_proc_module = HostmemReadProcessor(read_width, ChannelHostMemImpl,
1122 read_reqs)
1123 read_proc = read_proc_module(clk=ports.clk, rst=ports.rst)
1124 ports.read = read_proc.upstream
1125 for req in read_reqs:
1126 req.assign(getattr(read_proc, read_proc_module.reqPortMap[req]))
1127
1128 # The write side.
1129 write_reqs = [
1130 req for req in bundles.to_client_reqs if req.port == 'write'
1131 ]
1132 write_proc_module = HostMemWriteProcessor(write_width, ChannelHostMemImpl,
1133 write_reqs)
1134 write_proc = write_proc_module(clk=ports.clk, rst=ports.rst)
1135 ports.write = write_proc.upstream
1136 for req in write_reqs:
1137 req.assign(getattr(write_proc, write_proc_module.reqPortMap[req]))
1138
1139 return ChannelHostMemImpl
1140
1141
1142@modparams
1143def DummyToHostEngine(client_type: Type) -> type['DummyToHostEngineImpl']:
1144 """Create a fake DMA engine which just throws everything away."""
1145
1146 class DummyToHostEngineImpl(esi.EngineModule):
1147
1148 @property
1149 def TypeName(self):
1150 return "DummyToHostEngine"
1151
1152 clk = Clock()
1153 rst = Reset()
1154 input_channel = InputChannel(client_type)
1155
1156 @generator
1157 def build(ports):
1158 pass
1159
1160 return DummyToHostEngineImpl
1161
1162
1163@modparams
1164def DummyFromHostEngine(client_type: Type) -> type['DummyFromHostEngineImpl']:
1165 """Create a fake DMA engine which just never produces messages."""
1166
1167 class DummyFromHostEngineImpl(esi.EngineModule):
1168
1169 @property
1170 def TypeName(self):
1171 return "DummyFromHostEngine"
1172
1173 clk = Clock()
1174 rst = Reset()
1175 output_channel = OutputChannel(client_type)
1176
1177 @generator
1178 def build(ports):
1179 valid = Bits(1)(0)
1180 data = Bits(client_type.bitwidth)(0).bitcast(client_type)
1181 channel, ready = Channel(client_type).wrap(data, valid)
1182 ports.output_channel = channel
1183
1184 return DummyFromHostEngineImpl
1185
1186
1187def ChannelEngineService(
1188 to_host_engine_gen: Callable,
1189 from_host_engine_gen: Callable) -> type['ChannelEngineService']:
1190 """Returns a channel service implementation which calls
1191 to_host_engine_gen(<client_type>) or from_host_engine_gen(<client_type>) to
1192 generate the to_host and from_host engines for each channel. Does not support
1193 engines which can service multiple clients at once."""
1194
1195 class ChannelEngineService(esi.ServiceImplementation):
1196 """Service implementation which services the clients via a per-channel DMA
1197 engine."""
1198
1199 clk = Clock()
1200 rst = Reset()
1201
1202 @generator
1203 def build(ports, bundles: esi._ServiceGeneratorBundles):
1204 clk = ports.clk
1205 rst = ports.rst
1206
1207 def build_engine_appid(client_appid: List[esi.AppID],
1208 channel_name: str) -> str:
1209 appid_strings = [str(appid) for appid in client_appid]
1210 return f"{'_'.join(appid_strings)}.{channel_name}"
1211
1212 def build_engine(bc: BundledChannel, input_channel=None) -> Type:
1213 idbase = build_engine_appid(bundle.client_name, bc.name)
1214 eng_appid = esi.AppID(idbase)
1215 # DMA engines require at least 1 byte of data; substitute Bits(8)
1216 # for zero-width (void) channel types so the engine never sees a
1217 # zero-length transfer.
1218 engine_client_type = bc.channel.inner_type
1219 is_void = (engine_client_type.bitwidth == 0)
1220 if is_void:
1221 engine_client_type = Bits(8)
1222 if bc.direction == ChannelDirection.FROM:
1223 engine_mod = to_host_engine_gen(engine_client_type)
1224 else:
1225 engine_mod = from_host_engine_gen(engine_client_type)
1226 eng_inputs = {
1227 "clk": ports.clk,
1228 "rst": ports.rst,
1229 }
1230 eng_details: Dict[str, object] = {"engine_inst": eng_appid}
1231 if input_channel is not None:
1232 # For void channels, widen the 0-bit input to the 8-bit
1233 # placeholder the engine expects.
1234 if is_void:
1235 input_channel = input_channel.transform(lambda _: Bits(8)(0))
1236 if (engine_mod.input_channel.type.signaling
1237 != input_channel.type.signaling):
1238 input_channel = input_channel.buffer(
1239 clk,
1240 rst,
1241 stages=1,
1242 output_signaling=engine_mod.input_channel.type.signaling)
1243 eng_inputs["input_channel"] = input_channel
1244 if hasattr(engine_mod, "mmio"):
1245 mmio_appid = esi.AppID(idbase + ".mmio")
1246 eng_inputs["mmio"] = esi.MMIO.read_write(mmio_appid)
1247 eng_details["mmio"] = mmio_appid
1248 if hasattr(engine_mod, "hostmem_write"):
1249 eng_inputs["hostmem_write"] = esi.HostMem.write_from_bundle(
1250 esi.AppID(idbase + ".hostmem_write"),
1251 engine_mod.hostmem_write.type)
1252 if hasattr(engine_mod, "hostmem_read"):
1253 eng_inputs["hostmem_read"] = esi.HostMem.read_from_bundle(
1254 esi.AppID(idbase + ".hostmem_read"), engine_mod.hostmem_read.type)
1255 engine = engine_mod(appid=eng_appid, **eng_inputs)
1256 engine_rec = bundles.emit_engine(engine, details=eng_details)
1257 engine_rec.add_record(bundle, {bc.name: {}})
1258 return engine
1259
1260 for bundle in bundles.to_client_reqs:
1261 bundle_type = bundle.type
1262 to_channels = {}
1263 # Create a DMA engine for each channel headed TO the client (from the host).
1264 for bc in bundle_type.channels:
1265 if bc.direction == ChannelDirection.TO:
1266 engine = build_engine(bc)
1267 out_chan = engine.output_channel
1268 # For void channels, narrow the 8-bit placeholder back to 0-bit.
1269 if bc.channel.inner_type.bitwidth == 0:
1270 out_chan = out_chan.transform(lambda _: Bits(0)(0))
1271 to_channels[bc.name] = out_chan
1272
1273 client_bundle_sig, froms = bundle_type.pack(**to_channels)
1274 bundle.assign(client_bundle_sig)
1275
1276 # Create a DMA engine for each channel headed FROM the client (to the host).
1277 for bc in bundle_type.channels:
1278 if bc.direction == ChannelDirection.FROM:
1279 build_engine(bc, froms[bc.name])
1280
1281 return ChannelEngineService
return wrap(CMemoryType::get(unwrap(ctx), baseType, numElements))
Tuple[BitsSignal, ChannelSignal] build_addr_read(ChannelSignal read_addr_chan, int num_clients, int manifest_loc)
Definition common.py:469
generate(ports, esi._ServiceGeneratorBundles bundles)
Definition common.py:370
Tuple[Dict[int, AssignableSignal], int] build_table(bundles)
Definition common.py:376
build_read(ports, int manifest_loc, Dict[int, AssignableSignal] table)
Definition common.py:404
type["ChannelDemuxNImpl"] ChannelDemuxN_HalfStage_ReadyBlocking(Type data_type, int num_outs, int next_sel_width)
Definition common.py:118
HostmemReadProcessor(int read_width, hostmem_module, List[esi._OutputBundleSetter] reqs)
Definition common.py:679
type["ChannelDemuxTree"] ChannelDemuxTree_HalfStage_ReadyBlocking(Type data_type, int num_outs, int branching_factor_log2)
Definition common.py:211
Module HeaderMMIO(int manifest_loc)
Definition common.py:61
type["TaggedWriteGearboxImpl"] TaggedWriteGearbox(int input_bitwidth, int output_bitwidth)
Definition common.py:790
type[ 'DummyToHostEngineImpl'] DummyToHostEngine(Type client_type)
Definition common.py:1143
type[ 'DummyFromHostEngineImpl'] DummyFromHostEngine(Type client_type)
Definition common.py:1164
type[ 'EmitEveryNImpl'] EmitEveryN(Type message_type, int N)
Definition common.py:914
type["TaggedReadGearboxImpl"] TaggedReadGearbox(int input_bitwidth, int output_bitwidth)
Definition common.py:593
type["HostMemWriteProcessorImpl"] HostMemWriteProcessor(int write_width, hostmem_module, List[esi._OutputBundleSetter] reqs)
Definition common.py:966