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 gearbox = TaggedReadGearbox(read_width, client_type.data.bitwidth)(
752 clk=ports.clk, rst=ports.rst, in_=demuxed_upstream_channel)
753 client_resp_channel = gearbox.out.transform(lambda m: client_type({
754 "tag": m.tag,
755 "data": m.data.bitcast(client_type.data)
756 }))
757
758 # Assign the client response to the correct port.
759 client_bundle, froms = client.type.pack(resp=client_resp_channel)
760 client_req = froms["req"]
761 tagged_client_req = client_req.transform(
762 lambda r: hostmem_module.UpstreamReadReq({
763 "address": r.address,
764 "length": (client_type.data.bitwidth + 7) // 8,
765 # TODO: Change this once we support tag-rewriting.
766 "tag": idx
767 }))
768 tagged_client_reqs.append(tagged_client_req)
769
770 # Set the port for the client request.
771 setattr(ports, HostmemReadProcessorImpl.reqPortMap[client],
772 client_bundle)
773
774 # Assign the multiplexed read request to the upstream request.
775 # TODO: Don't release a request until the client is ready to accept
776 # the response otherwise the system could deadlock.
777 muxed_client_reqs = esi.ChannelMux(tagged_client_reqs)
778 upstream_req_channel.assign(muxed_client_reqs)
779 HostmemReadProcessorImpl.reqPortMap.clear()
780
781 return HostmemReadProcessorImpl
782
783
784@modparams
785def TaggedWriteGearbox(input_bitwidth: int,
786 output_bitwidth: int) -> type["TaggedWriteGearboxImpl"]:
787 """Build a gearbox to convert the client data to upstream write chunks.
788 Assumes a struct {address, tag, data} and only gearboxes the data. Tag is
789 stored separately and the struct is re-assembled later on."""
790
791 if output_bitwidth % 8 != 0:
792 raise ValueError("Output bitwidth must be a multiple of 8.")
793 input_pad_bits = 0
794 if input_bitwidth % 8 != 0:
795 input_pad_bits = 8 - (input_bitwidth % 8)
796 input_padded_bitwidth = input_bitwidth + input_pad_bits
797
798 class TaggedWriteGearboxImpl(Module):
799 clk = Clock()
800 rst = Reset()
801 in_ = InputChannel(
802 StructType([
803 ("address", UInt(64)),
804 ("tag", esi.HostMem.TagType),
805 ("data", Bits(input_bitwidth)),
806 ]))
807 out = OutputChannel(
808 StructType([
809 ("address", UInt(64)),
810 ("tag", esi.HostMem.TagType),
811 ("data", Bits(output_bitwidth)),
812 ("valid_bytes", Bits(8)),
813 ]))
814
815 num_chunks = ceil(input_padded_bitwidth / output_bitwidth)
816
817 @generator
818 def build(ports):
819 upstream_ready = Wire(Bits(1))
820 ready_for_client = Wire(Bits(1))
821 client_tag_and_data, client_valid = ports.in_.unwrap(ready_for_client)
822 client_data = client_tag_and_data.data
823 if input_pad_bits > 0:
824 client_data = client_data.pad_or_truncate(input_padded_bitwidth)
825 client_xact = ready_for_client & client_valid
826 input_bitwidth_bytes = input_padded_bitwidth // 8
827 output_bitwidth_bytes = output_bitwidth // 8
828
829 # Determine if gearboxing is necessary and whether it needs to be
830 # gearboxed up or just sliced down.
831 if output_bitwidth == input_padded_bitwidth:
832 upstream_data_bits = client_data
833 upstream_valid = client_valid
834 ready_for_client.assign(upstream_ready)
835 tag = client_tag_and_data.tag
836 address = client_tag_and_data.address
837 valid_bytes = Bits(8)(input_bitwidth_bytes)
838 elif output_bitwidth > input_padded_bitwidth:
839 upstream_data_bits = client_data.as_bits(output_bitwidth)
840 upstream_valid = client_valid
841 ready_for_client.assign(upstream_ready)
842 tag = client_tag_and_data.tag
843 address = client_tag_and_data.address
844 valid_bytes = Bits(8)(input_bitwidth_bytes)
845 else:
846 # Create registers equal to the number of upstream transactions needed
847 # to complete the transmission.
848 num_chunks = TaggedWriteGearboxImpl.num_chunks
849 num_chunks_idx_bitwidth = clog2(num_chunks)
850 if input_padded_bitwidth % output_bitwidth == 0:
851 padding_numbits = 0
852 else:
853 padding_numbits = output_bitwidth - (input_padded_bitwidth %
854 output_bitwidth)
855 client_data_padded = BitsSignal.concat(
856 [Bits(padding_numbits)(0), client_data])
857 chunks = [
858 client_data_padded[i * output_bitwidth:(i + 1) * output_bitwidth]
859 for i in range(num_chunks)
860 ]
861 chunk_regs = Array(Bits(output_bitwidth), num_chunks)([
862 c.reg(ports.clk, ce=client_xact, name=f"chunk_{idx}")
863 for idx, c in enumerate(chunks)
864 ])
865 increment = Wire(Bits(1))
866 clear = Wire(Bits(1))
867 counter = Counter(num_chunks_idx_bitwidth)(clk=ports.clk,
868 rst=ports.rst,
869 increment=increment,
870 clear=clear)
871 upstream_data_bits = chunk_regs[counter.out]
872 upstream_valid = ControlReg(ports.clk, ports.rst, [client_xact],
873 [clear])
874 upstream_xact = upstream_valid & upstream_ready
875 clear.assign(upstream_xact & (counter.out == (num_chunks - 1)))
876 increment.assign(upstream_xact)
877 ready_for_client.assign(~upstream_valid)
878 address_padding_bits = clog2(output_bitwidth_bytes)
879 counter_bytes = BitsSignal.concat(
880 [counter.out.as_bits(),
881 Bits(address_padding_bits)(0)]).as_uint()
882
883 # Construct the output channel. Shared logic across all three cases.
884 tag_reg = client_tag_and_data.tag.reg(ports.clk,
885 ce=client_xact,
886 name="tag_reg")
887 addr_reg = client_tag_and_data.address.reg(ports.clk,
888 ce=client_xact,
889 name="address_reg")
890 address = (addr_reg + counter_bytes).as_uint(64)
891 tag = tag_reg
892 valid_bytes = Mux(counter.out == (num_chunks - 1),
893 Bits(8)(output_bitwidth_bytes),
894 Bits(8)((output_bitwidth - padding_numbits) // 8))
895
896 upstream_channel, upstrm_ready_sig = TaggedWriteGearboxImpl.out.type.wrap(
897 {
898 "address": address,
899 "tag": tag,
900 "data": upstream_data_bits,
901 "valid_bytes": valid_bytes
902 }, upstream_valid)
903 upstream_ready.assign(upstrm_ready_sig)
904 ports.out = upstream_channel
905
906 return TaggedWriteGearboxImpl
907
908
909@modparams
910def EmitEveryN(message_type: Type, N: int) -> type['EmitEveryNImpl']:
911 """Emit (forward) one message for every N input messages. The emitted message
912 is the last one of the N received. N must be >= 1."""
913
914 if N < 1:
915 raise ValueError("N must be >= 1")
916
917 class EmitEveryNImpl(Module):
918 clk = Clock()
919 rst = Reset()
920 in_ = InputChannel(message_type)
921 out = OutputChannel(message_type)
922
923 @generator
924 def build(ports):
925 ready_for_in = Wire(Bits(1))
926 in_data, in_valid = ports.in_.unwrap(ready_for_in)
927 xact = in_valid & ready_for_in
928
929 # Fast path: N == 1 -> pass-through.
930 if N == 1:
931 out_chan, out_ready = EmitEveryNImpl.out.type.wrap(in_data, in_valid)
932 ready_for_in.assign(out_ready)
933 ports.out = out_chan
934 return
935
936 counter_width = clog2(N)
937 increment = xact
938 clear = Wire(Bits(1))
939 counter = Counter(counter_width)(clk=ports.clk,
940 rst=ports.rst,
941 increment=increment,
942 clear=clear)
943
944 # Capture last message of the group.
945 last_msg = in_data.reg(ports.clk, ports.rst, ce=xact, name="last_msg")
946
947 hit_last = (counter.out == UInt(counter_width)(N - 1)) & xact
948 out_valid = ControlReg(ports.clk, ports.rst, [hit_last], [clear])
949
950 out_chan, out_ready = EmitEveryNImpl.out.type.wrap(last_msg, out_valid)
951 # Stall input while waiting for downstream to accept the aggregated output.
952 ready_for_in.assign(~(out_valid & ~out_ready))
953 clear.assign(out_valid & out_ready) # Clear after successful emit.
954
955 ports.out = out_chan
956
957 return EmitEveryNImpl
958
959
961 write_width: int, hostmem_module,
962 reqs: List[esi._OutputBundleSetter]) -> type["HostMemWriteProcessorImpl"]:
963 """Construct a host memory write request module to orchestrate the the write
964 connections. Responsible for both gearboxing the data, multiplexing the
965 requests, reassembling out-of-order responses and routing the responses to the
966 correct clients.
967
968 Generate this module dynamically to allow for multiple write clients of
969 multiple types to be directly accomodated."""
970
971 class HostMemWriteProcessorImpl(Module):
972
973 clk = Clock()
974 rst = Reset()
975
976 # Add an output port for each read client.
977 reqPortMap: Dict[esi._OutputBundleSetter, str] = {}
978 for req in reqs:
979 name = "client_" + req.client_name_str
980 locals()[name] = Output(req.type)
981 reqPortMap[req] = name
982
983 # And then the port which goes to the host.
984 upstream = Output(hostmem_module.write.type)
985
986 @generator
987 def build(ports):
988 clk = ports.clk
989 rst = ports.rst
990
991 # If there's no write clients, just create a no-op write bundle
992 if len(reqs) == 0:
993 req, _ = Channel(hostmem_module.UpstreamWriteReq).wrap(
994 {
995 "address": 0,
996 "tag": 0,
997 "data": 0,
998 "valid_bytes": 0,
999 }, 0)
1000 write_bundle, _ = hostmem_module.write.type.pack(req=req)
1001 ports.upstream = write_bundle
1002 return
1003
1004 assert len(reqs) <= 256, "More than 256 write clients not supported."
1005
1006 upstream_req_channel = Wire(Channel(hostmem_module.UpstreamWriteReq))
1007 upstream_write_bundle, froms = hostmem_module.write.type.pack(
1008 req=upstream_req_channel)
1009 ports.upstream = upstream_write_bundle
1010 upstream_ack_tag = froms["ackTag"]
1011
1012 demuxed_acks = esi.TaggedDemux(len(reqs), upstream_ack_tag.type)(
1013 clk=ports.clk, rst=ports.rst, in_=upstream_ack_tag)
1014
1015 # TODO: re-write the tags and store the client and client tag.
1016
1017 # Build the write request channels and ack wires.
1018 write_channels: List[ChannelSignal] = []
1019 for idx, req in enumerate(reqs):
1020 # Get the request channel and its data type.
1021 reqch = [c.channel for c in req.type.channels if c.name == 'req'][0]
1022 client_type = reqch.inner_type
1023 if isinstance(client_type.data, Window):
1024 client_type = client_type.lowered_type
1025
1026 # Pack up the bundle and assign the request channel.
1027 write_req_bundle_type = esi.HostMem.write_req_bundle_type(
1028 client_type.data)
1029 input_flit_ack = Wire(upstream_ack_tag.type)
1030 bundle_sig, froms = write_req_bundle_type.pack(ackTag=input_flit_ack)
1031
1032 gearbox_mod = TaggedWriteGearbox(client_type.data.bitwidth, write_width)
1033 gearbox_in_type = gearbox_mod.in_.type.inner_type
1034 tagged_client_req = froms["req"]
1035 bitcast_client_req = tagged_client_req.transform(
1036 lambda m: gearbox_in_type({
1037 "tag": m.tag,
1038 "address": m.address,
1039 "data": m.data.bitcast(gearbox_in_type.data)
1040 }))
1041
1042 # Gearbox the data to the client's data type.
1043 gearbox = gearbox_mod(clk=ports.clk,
1044 rst=ports.rst,
1045 in_=bitcast_client_req)
1046 write_channels.append(
1047 gearbox.out.transform(lambda m: m.type({
1048 "address": m.address,
1049 "tag": idx,
1050 "data": m.data,
1051 "valid_bytes": m.valid_bytes
1052 })))
1053
1054 # Count the number of acks received from hostmem for this client
1055 # and only send one back to the client per input.
1056 ack_every_n = EmitEveryN(upstream_ack_tag.type, gearbox_mod.num_chunks)(
1057 clk=clk, rst=rst, in_=demuxed_acks.get_out(idx))
1058 input_flit_ack.assign(ack_every_n.out)
1059
1060 # Set the port for the client request.
1061 setattr(ports, HostMemWriteProcessorImpl.reqPortMap[req], bundle_sig)
1062
1063 # Build a channel mux for the write requests.
1064 muxed_write_channel = esi.ChannelMux(write_channels)
1065 upstream_req_channel.assign(muxed_write_channel)
1066
1067 return HostMemWriteProcessorImpl
1068
1069
1070@modparams
1071def ChannelHostMem(read_width: int,
1072 write_width: int) -> typing.Type['ChannelHostMemImpl']:
1073
1074 class ChannelHostMemImpl(esi.ServiceImplementation):
1075 """Builds a HostMem service which multiplexes multiple HostMem clients into
1076 two (read and write) bundles of the given data width."""
1077
1078 clk = Clock()
1079 rst = Reset()
1080
1081 UpstreamReadReq = StructType([
1082 ("address", UInt(64)),
1083 ("length", UInt(32)), # In bytes.
1084 ("tag", UInt(8)),
1085 ])
1086 read = Output(
1087 Bundle([
1088 BundledChannel("req", ChannelDirection.TO, UpstreamReadReq),
1089 BundledChannel(
1090 "resp", ChannelDirection.FROM,
1091 StructType([
1092 ("tag", esi.HostMem.TagType),
1093 ("data", Bits(read_width)),
1094 ])),
1095 ]))
1096
1097 if write_width % 8 != 0:
1098 raise ValueError("Write width must be a multiple of 8.")
1099 UpstreamWriteReq = StructType([
1100 ("address", UInt(64)),
1101 ("tag", UInt(8)),
1102 ("data", Bits(write_width)),
1103 ("valid_bytes", Bits(8)),
1104 ])
1105 write = Output(
1106 Bundle([
1107 BundledChannel("req", ChannelDirection.TO, UpstreamWriteReq),
1108 BundledChannel("ackTag", ChannelDirection.FROM, UInt(8)),
1109 ]))
1110
1111 @generator
1112 def generate(ports, bundles: esi._ServiceGeneratorBundles):
1113 # Split the read side out into a separate module. Must assign the output
1114 # ports to the clients since we can't service a request in a different
1115 # module.
1116 read_reqs = [req for req in bundles.to_client_reqs if req.port == 'read']
1117 read_proc_module = HostmemReadProcessor(read_width, ChannelHostMemImpl,
1118 read_reqs)
1119 read_proc = read_proc_module(clk=ports.clk, rst=ports.rst)
1120 ports.read = read_proc.upstream
1121 for req in read_reqs:
1122 req.assign(getattr(read_proc, read_proc_module.reqPortMap[req]))
1123
1124 # The write side.
1125 write_reqs = [
1126 req for req in bundles.to_client_reqs if req.port == 'write'
1127 ]
1128 write_proc_module = HostMemWriteProcessor(write_width, ChannelHostMemImpl,
1129 write_reqs)
1130 write_proc = write_proc_module(clk=ports.clk, rst=ports.rst)
1131 ports.write = write_proc.upstream
1132 for req in write_reqs:
1133 req.assign(getattr(write_proc, write_proc_module.reqPortMap[req]))
1134
1135 return ChannelHostMemImpl
1136
1137
1138@modparams
1139def DummyToHostEngine(client_type: Type) -> type['DummyToHostEngineImpl']:
1140 """Create a fake DMA engine which just throws everything away."""
1141
1142 class DummyToHostEngineImpl(esi.EngineModule):
1143
1144 @property
1145 def TypeName(self):
1146 return "DummyToHostEngine"
1147
1148 clk = Clock()
1149 rst = Reset()
1150 input_channel = InputChannel(client_type)
1151
1152 @generator
1153 def build(ports):
1154 pass
1155
1156 return DummyToHostEngineImpl
1157
1158
1159@modparams
1160def DummyFromHostEngine(client_type: Type) -> type['DummyFromHostEngineImpl']:
1161 """Create a fake DMA engine which just never produces messages."""
1162
1163 class DummyFromHostEngineImpl(esi.EngineModule):
1164
1165 @property
1166 def TypeName(self):
1167 return "DummyFromHostEngine"
1168
1169 clk = Clock()
1170 rst = Reset()
1171 output_channel = OutputChannel(client_type)
1172
1173 @generator
1174 def build(ports):
1175 valid = Bits(1)(0)
1176 data = Bits(client_type.bitwidth)(0).bitcast(client_type)
1177 channel, ready = Channel(client_type).wrap(data, valid)
1178 ports.output_channel = channel
1179
1180 return DummyFromHostEngineImpl
1181
1182
1183def ChannelEngineService(
1184 to_host_engine_gen: Callable,
1185 from_host_engine_gen: Callable) -> type['ChannelEngineService']:
1186 """Returns a channel service implementation which calls
1187 to_host_engine_gen(<client_type>) or from_host_engine_gen(<client_type>) to
1188 generate the to_host and from_host engines for each channel. Does not support
1189 engines which can service multiple clients at once."""
1190
1191 class ChannelEngineService(esi.ServiceImplementation):
1192 """Service implementation which services the clients via a per-channel DMA
1193 engine."""
1194
1195 clk = Clock()
1196 rst = Reset()
1197
1198 @generator
1199 def build(ports, bundles: esi._ServiceGeneratorBundles):
1200 clk = ports.clk
1201 rst = ports.rst
1202
1203 def build_engine_appid(client_appid: List[esi.AppID],
1204 channel_name: str) -> str:
1205 appid_strings = [str(appid) for appid in client_appid]
1206 return f"{'_'.join(appid_strings)}.{channel_name}"
1207
1208 def build_engine(bc: BundledChannel, input_channel=None) -> Type:
1209 idbase = build_engine_appid(bundle.client_name, bc.name)
1210 eng_appid = esi.AppID(idbase)
1211 if bc.direction == ChannelDirection.FROM:
1212 engine_mod = to_host_engine_gen(bc.channel.inner_type)
1213 else:
1214 engine_mod = from_host_engine_gen(bc.channel.inner_type)
1215 eng_inputs = {
1216 "clk": ports.clk,
1217 "rst": ports.rst,
1218 }
1219 eng_details: Dict[str, object] = {"engine_inst": eng_appid}
1220 if input_channel is not None:
1221 if (engine_mod.input_channel.type.signaling
1222 != input_channel.type.signaling):
1223 input_channel = input_channel.buffer(
1224 clk,
1225 rst,
1226 stages=1,
1227 output_signaling=engine_mod.input_channel.type.signaling)
1228 eng_inputs["input_channel"] = input_channel
1229 if hasattr(engine_mod, "mmio"):
1230 mmio_appid = esi.AppID(idbase + ".mmio")
1231 eng_inputs["mmio"] = esi.MMIO.read_write(mmio_appid)
1232 eng_details["mmio"] = mmio_appid
1233 if hasattr(engine_mod, "hostmem_write"):
1234 eng_inputs["hostmem_write"] = esi.HostMem.write_from_bundle(
1235 esi.AppID(idbase + ".hostmem_write"),
1236 engine_mod.hostmem_write.type)
1237 if hasattr(engine_mod, "hostmem_read"):
1238 eng_inputs["hostmem_read"] = esi.HostMem.read_from_bundle(
1239 esi.AppID(idbase + ".hostmem_read"), engine_mod.hostmem_read.type)
1240 engine = engine_mod(appid=eng_appid, **eng_inputs)
1241 engine_rec = bundles.emit_engine(engine, details=eng_details)
1242 engine_rec.add_record(bundle, {bc.name: {}})
1243 return engine
1244
1245 for bundle in bundles.to_client_reqs:
1246 bundle_type = bundle.type
1247 to_channels = {}
1248 # Create a DMA engine for each channel headed TO the client (from the host).
1249 for bc in bundle_type.channels:
1250 if bc.direction == ChannelDirection.TO:
1251 engine = build_engine(bc)
1252 to_channels[bc.name] = engine.output_channel
1253
1254 client_bundle_sig, froms = bundle_type.pack(**to_channels)
1255 bundle.assign(client_bundle_sig)
1256
1257 # Create a DMA engine for each channel headed FROM the client (to the host).
1258 for bc in bundle_type.channels:
1259 if bc.direction == ChannelDirection.FROM:
1260 build_engine(bc, froms[bc.name])
1261
1262 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:786
type[ 'DummyToHostEngineImpl'] DummyToHostEngine(Type client_type)
Definition common.py:1139
type[ 'DummyFromHostEngineImpl'] DummyFromHostEngine(Type client_type)
Definition common.py:1160
type[ 'EmitEveryNImpl'] EmitEveryN(Type message_type, int N)
Definition common.py:910
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:962