CIRCT 24.0.0git
Loading...
Searching...
No Matches
cosim.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 typing import Callable, Optional, Tuple, Type
6
7from pycde.common import AppID, Clock, Input, Output
8from pycde.module import Module, generator
9from pycde.support import get_user_loc
10from pycde.system import System
11from pycde.signals import ChannelSignal
12from pycde.types import (Bits, Channel, StructType, UInt)
13from pycde.constructs import Wire
14from pycde import esi
15
16from .common import (ChannelEngineService, ChannelHostMem, ChannelMMIO,
17 DEFAULT_MAX_READ_REQUEST_BYTES,
18 DEFAULT_MAX_WRITE_PAYLOAD_BYTES, DesignResetController,
19 ResetCycles, TelemetryMMIO)
20from .dma import OneItemBuffersFromHost, OneItemBuffersToHost
21
22from pycde.circt import ir
23from pycde.circt.dialects import esi as raw_esi
24
25from pathlib import Path
26
27__root_dir__ = Path(__file__).parent.parent
28
29
31 user_module: Type[Module],
32 dma_engine_pair: Optional[Tuple[Callable, Callable]] = None,
33 channel_service: Optional[Type[esi.ServiceImplementation]] = None,
34 max_outstanding_reads: int = 4,
35 hostmem_width: int = 64,
36) -> Module:
37 """Wrap and return a cosimulation 'board support package' containing
38 'user_module'.
39
40 By default, channels use the native cosim transport. 'dma_engine_pair' is a
41 (to_host_gen, from_host_gen) pair used by ChannelEngineService to create one
42 DMA engine per channel.
43
44 For custom implementations, including engines shared by multiple channels,
45 'channel_service' accepts an esi.ServiceImplementation subclass, not an
46 instance. It must have exactly the input ports 'clk = Clock()' and
47 'rst = Reset()'. The BSP instantiates it with decl=None for the builtin
48 ChannelService and appid=esi.AppID("__channel_engines"). Its generator may
49 issue esi.MMIO and esi.HostMem requests, which the BSP will satisfy.
50
51 Passing both 'dma_engine_pair' and 'channel_service' raises ValueError.
52
53 'max_outstanding_reads' is the per-client limit on logical HostMem read
54 requests in flight. The cosim host services each read request in order, so
55 more than one is safe; the default of 4 keeps the multi-outstanding read path
56 exercised in simulation.
57
58 'hostmem_width' is the upstream HostMem read and write width in bits. It must
59 be a power of two and at least 8. The host runtime discovers it automatically.
60 Wider beats reduce transport messages for wide transfers, without combining
61 separate narrow requests or changing completion semantics.
62 """
63 if (type(hostmem_width) is not int or hostmem_width < 8 or
64 hostmem_width & (hostmem_width - 1)):
65 raise ValueError("hostmem_width must be a power of two and at least 8")
66 if dma_engine_pair is not None and channel_service is not None:
67 raise ValueError(
68 "dma_engine_pair and channel_service are mutually exclusive")
69 if dma_engine_pair is not None:
70 channel_service = ChannelEngineService(*dma_engine_pair)
71
72 class ESI_Cosim_UserTopWrapper(Module):
73 """Wrap the user module along with 'standard' service generators so that
74 those generators can issue their own service requests to be picked up by the
75 actual top-level catch-all cosim service generator."""
76
77 clk = Clock()
78 rst = Input(Bits(1))
79
80 mmio = Input(esi.MMIO.read_write.type)
81
82 HostMemWidth = hostmem_width
83
84 ChannelHostMemModule = ChannelHostMem(
85 read_width=HostMemWidth,
86 write_width=HostMemWidth,
87 max_read_request_bytes=max(DEFAULT_MAX_READ_REQUEST_BYTES,
88 HostMemWidth // 8),
89 max_write_payload_bytes=max(DEFAULT_MAX_WRITE_PAYLOAD_BYTES,
90 HostMemWidth // 8),
91 max_outstanding_reads=max_outstanding_reads,
92 round_read_lengths=False)
93
94 hostmem_read = ChannelHostMemModule.read
95 hostmem_write = ChannelHostMemModule.write
96
97 # Asserted for one cycle when the design requests a reset (via an MMIO write
98 # to the header). Consumed by the top-level module which performs the reset.
99 # NOTE: must be declared after 'hostmem_read'/'hostmem_write' so it does not
100 # perturb their output port indices (they alias the sub-module's shared
101 # Output objects, whose '.idx' is reused for the instance output lookup).
102 reset_request = Output(Bits(1))
103
104 @generator
105 def build(ports):
106 user_module(clk=ports.clk, rst=ports.rst)
107 TelemetryMMIO(esi.Telemetry,
108 appid=esi.AppID("__telemetry"),
109 clk=ports.clk,
110 rst=ports.rst)
111
112 if channel_service is not None:
113 channel_service(None,
114 appid=esi.AppID("__channel_engines"),
115 clk=ports.clk,
116 rst=ports.rst)
117
118 mmio = ChannelMMIO(esi.MMIO,
119 appid=esi.AppID("__cosim_mmio"),
120 clk=ports.clk,
121 rst=ports.rst,
122 cmd=ports.mmio)
123 ports.reset_request = mmio.reset_request
124
125 # Instantiate a hostmem service generator which multiplexes requests to a
126 # either a single read or write channel. Get those channels and transform
127 # them into callbacks.
128 hostmem = ESI_Cosim_UserTopWrapper.ChannelHostMemModule(
129 decl=esi.HostMem,
130 appid=esi.AppID("__cosim_hostmem"),
131 clk=ports.clk,
132 rst=ports.rst)
133 ports.hostmem_read = hostmem.read
134 ports.hostmem_write = hostmem.write
135
136 class ESI_Cosim_Top(Module):
137 clk = Clock()
138 rst = Input(Bits(1))
139
140 @generator
141 def build(ports):
142 System.current().platform = "cosim"
143
144 mmio_read_write = esi.FuncService.get(
145 esi.AppID("__cosim_mmio_read_write"), esi.MMIO.read_write.type)
146
147 # The design can request a reset via an MMIO write to the header. Once
148 # requested, reset the entire design (but not the cosim link) after a
149 # fixed number of cycles. The reset controller is driven by the external
150 # reset only so its countdown is not disturbed by the reset it generates.
151 design_reset = Wire(Bits(1))
152 reset_pending = Wire(Bits(1))
153 combined_rst = ports.rst | design_reset
154
155 # Once a reset has been requested, stop accepting *new* MMIO transactions
156 # so that none is in flight when the design (including the MMIO plane) is
157 # actually reset 'ResetCycles' cycles later. Any already-accepted
158 # transaction has that window to drain; a command held here is released
159 # once the reset completes and the MMIO plane is back up. The gate is
160 # purely combinational off the (registered) 'reset_pending', so it blocks
161 # at a clean cycle boundary: when pending is high the command is not
162 # presented downstream and the host sees back-pressure (so it retains the
163 # command), and neither side completes a handshake. This logic lives in
164 # the external-reset domain so it is not cleared by the reset it guards.
165 mmio_resp = Wire(Channel(esi.MMIODataType))
166 host_cmd = mmio_read_write.unpack(data=mmio_resp)['cmd']
167 gate_ready = Wire(Bits(1))
168 cmd_payload, cmd_valid = host_cmd.unwrap(gate_ready)
169 fwd_valid = (cmd_valid & ~reset_pending).as_bits()
170 gated_cmd, fwd_ready = host_cmd.type.wrap(cmd_payload, fwd_valid)
171 gate_ready.assign((fwd_ready & ~reset_pending).as_bits())
172 gated_mmio, gated_froms = esi.MMIO.read_write.type.pack(cmd=gated_cmd)
173 mmio_resp.assign(gated_froms['data'])
174
175 wrapper = ESI_Cosim_UserTopWrapper(clk=ports.clk,
176 rst=combined_rst,
177 mmio=gated_mmio)
178 reset_controller = DesignResetController(ResetCycles)(
179 clk=ports.clk, rst=ports.rst, reset_request=wrapper.reset_request)
180 design_reset.assign(reset_controller.design_reset)
181 reset_pending.assign(reset_controller.reset_pending)
182
183 # While a reset is pending, drop host-driven responses to the design's
184 # hostmem service. In-flight requests issued before the reset will have
185 # their responses arrive after the design (and its hostmem tag/demux
186 # state) has been reset; delivering them would corrupt the fresh state or
187 # hang on a response the reset design never consumes. Draining them here
188 # (assert ready toward the host, never present a valid downstream) keeps
189 # the host response channels flowing. This lives in the external-reset
190 # domain so it is not cleared by the reset it guards.
191 def drop_while_resetting(chan: ChannelSignal) -> ChannelSignal:
192 src_ready = Wire(Bits(1))
193 payload, valid = chan.unwrap(src_ready)
194 fwd_valid = (valid & ~reset_pending).as_bits()
195 gated, dn_ready = chan.type.wrap(payload, fwd_valid)
196 src_ready.assign((dn_ready | reset_pending).as_bits())
197 return gated
198
199 resp_channel = esi.ChannelService.from_host(
200 esi.AppID("__cosim_hostmem_read_resp"),
201 StructType([
202 ("tag", UInt(8)),
203 ("data", Bits(ESI_Cosim_UserTopWrapper.HostMemWidth)),
204 ("last", Bits(1)),
205 ]))
206 req = wrapper.hostmem_read.unpack(
207 resp=drop_while_resetting(resp_channel))['req']
208 esi.ChannelService.to_host(esi.AppID("__cosim_hostmem_read_req"), req)
209
210 ack_wire = Wire(Channel(UInt(8)))
211 write_req = wrapper.hostmem_write.unpack(ackTag=ack_wire)['req']
212 ack_tag = esi.CallService.call(esi.AppID("__cosim_hostmem_write"),
213 write_req, UInt(8))
214 ack_wire.assign(drop_while_resetting(ack_tag))
215
216 cosim_svc = raw_esi.ServiceInstanceOp(
217 result=[],
218 appID=AppID("cosim")._appid,
219 service_symbol=None,
220 impl_type=ir.StringAttr.get("cosim"),
221 inputs=[ports.clk.value, ports.rst.value],
222 loc=get_user_loc())
223 core_freq = System.current().core_freq
224 if core_freq is not None:
225 cosim_svc.operation.attributes[
226 "esi.core_clock_frequency_hz"] = ir.IntegerAttr.get(
227 ir.IntegerType.get_unsigned(64), core_freq)
228
229 return ESI_Cosim_Top
230
231
232def CosimBSP_DMA(user_module: Type[Module],
233 max_outstanding_reads: int = 4,
234 hostmem_width: int = 64) -> Module:
235 """CosimBSP with one-item DMA engines; accepts the same HostMem widths."""
236 return CosimBSP(user_module,
237 channel_service=ChannelEngineService(OneItemBuffersToHost,
238 OneItemBuffersFromHost),
239 max_outstanding_reads=max_outstanding_reads,
240 hostmem_width=hostmem_width)
@ Input
Definition HW.h:42
@ Output
Definition HW.h:42
Module CosimBSP(Type[Module] user_module, Optional[Tuple[Callable, Callable]] dma_engine_pair=None, Optional[Type[esi.ServiceImplementation]] channel_service=None, int max_outstanding_reads=4, int hostmem_width=64)
Definition cosim.py:36
Module CosimBSP_DMA(Type[Module] user_module, int max_outstanding_reads=4, int hostmem_width=64)
Definition cosim.py:234