CIRCT 23.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 DesignResetController, ResetCycles)
18from .dma import OneItemBuffersFromHost, OneItemBuffersToHost
19
20from pycde.circt import ir
21from pycde.circt.dialects import esi as raw_esi
22
23from pathlib import Path
24
25__root_dir__ = Path(__file__).parent.parent
26
27
28def CosimBSP(
29 user_module: Type[Module],
30 dma_engine_pair: Optional[Tuple[Callable, Callable]] = None,
31) -> Module:
32 """Wrap and return a cosimulation 'board support package' containing
33 'user_module'"""
34
35 class ESI_Cosim_UserTopWrapper(Module):
36 """Wrap the user module along with 'standard' service generators so that
37 those generators can issue their own service requests to be picked up by the
38 actual top-level catch-all cosim service generator."""
39
40 clk = Clock()
41 rst = Input(Bits(1))
42
43 mmio = Input(esi.MMIO.read_write.type)
44
45 # If this gets changed, update 'Cosim.cpp' in the runtime.
46 HostMemWidth = 64
47
48 ChannelHostMemModule = ChannelHostMem(read_width=HostMemWidth,
49 write_width=HostMemWidth)
50
51 hostmem_read = ChannelHostMemModule.read
52 hostmem_write = ChannelHostMemModule.write
53
54 # Asserted for one cycle when the design requests a reset (via an MMIO write
55 # to the header). Consumed by the top-level module which performs the reset.
56 # NOTE: must be declared after 'hostmem_read'/'hostmem_write' so it does not
57 # perturb their output port indices (they alias the sub-module's shared
58 # Output objects, whose '.idx' is reused for the instance output lookup).
59 reset_request = Output(Bits(1))
60
61 @generator
62 def build(ports):
63 user_module(clk=ports.clk, rst=ports.rst)
64 esi.TelemetryMMIO(esi.Telemetry,
65 appid=esi.AppID("__telemetry"),
66 clk=ports.clk,
67 rst=ports.rst)
68
69 if dma_engine_pair is not None:
70 ChannelEngineService(dma_engine_pair[0], dma_engine_pair[1])(
71 None,
72 appid=esi.AppID("__channel_engines"),
73 clk=ports.clk,
74 rst=ports.rst)
75
76 mmio = ChannelMMIO(esi.MMIO,
77 appid=esi.AppID("__cosim_mmio"),
78 clk=ports.clk,
79 rst=ports.rst,
80 cmd=ports.mmio)
81 ports.reset_request = mmio.reset_request
82
83 # Instantiate a hostmem service generator which multiplexes requests to a
84 # either a single read or write channel. Get those channels and transform
85 # them into callbacks.
86 hostmem = ESI_Cosim_UserTopWrapper.ChannelHostMemModule(
87 decl=esi.HostMem,
88 appid=esi.AppID("__cosim_hostmem"),
89 clk=ports.clk,
90 rst=ports.rst)
91 ports.hostmem_read = hostmem.read
92 ports.hostmem_write = hostmem.write
93
94 class ESI_Cosim_Top(Module):
95 clk = Clock()
96 rst = Input(Bits(1))
97
98 @generator
99 def build(ports):
100 System.current().platform = "cosim"
101
102 mmio_read_write = esi.FuncService.get(
103 esi.AppID("__cosim_mmio_read_write"), esi.MMIO.read_write.type)
104
105 # The design can request a reset via an MMIO write to the header. Once
106 # requested, reset the entire design (but not the cosim link) after a
107 # fixed number of cycles. The reset controller is driven by the external
108 # reset only so its countdown is not disturbed by the reset it generates.
109 design_reset = Wire(Bits(1))
110 reset_pending = Wire(Bits(1))
111 combined_rst = ports.rst | design_reset
112
113 # Once a reset has been requested, stop accepting *new* MMIO transactions
114 # so that none is in flight when the design (including the MMIO plane) is
115 # actually reset 'ResetCycles' cycles later. Any already-accepted
116 # transaction has that window to drain; a command held here is released
117 # once the reset completes and the MMIO plane is back up. The gate is
118 # purely combinational off the (registered) 'reset_pending', so it blocks
119 # at a clean cycle boundary: when pending is high the command is not
120 # presented downstream and the host sees back-pressure (so it retains the
121 # command), and neither side completes a handshake. This logic lives in
122 # the external-reset domain so it is not cleared by the reset it guards.
123 mmio_resp = Wire(Channel(esi.MMIODataType))
124 host_cmd = mmio_read_write.unpack(data=mmio_resp)['cmd']
125 gate_ready = Wire(Bits(1))
126 cmd_payload, cmd_valid = host_cmd.unwrap(gate_ready)
127 fwd_valid = (cmd_valid & ~reset_pending).as_bits()
128 gated_cmd, fwd_ready = host_cmd.type.wrap(cmd_payload, fwd_valid)
129 gate_ready.assign((fwd_ready & ~reset_pending).as_bits())
130 gated_mmio, gated_froms = esi.MMIO.read_write.type.pack(cmd=gated_cmd)
131 mmio_resp.assign(gated_froms['data'])
132
133 wrapper = ESI_Cosim_UserTopWrapper(clk=ports.clk,
134 rst=combined_rst,
135 mmio=gated_mmio)
136 reset_controller = DesignResetController(ResetCycles)(
137 clk=ports.clk, rst=ports.rst, reset_request=wrapper.reset_request)
138 design_reset.assign(reset_controller.design_reset)
139 reset_pending.assign(reset_controller.reset_pending)
140
141 # While a reset is pending, drop host-driven responses to the design's
142 # hostmem service. In-flight requests issued before the reset will have
143 # their responses arrive after the design (and its hostmem tag/demux
144 # state) has been reset; delivering them would corrupt the fresh state or
145 # hang on a response the reset design never consumes. Draining them here
146 # (assert ready toward the host, never present a valid downstream) keeps
147 # the host response channels flowing. This lives in the external-reset
148 # domain so it is not cleared by the reset it guards.
149 def drop_while_resetting(chan: ChannelSignal) -> ChannelSignal:
150 src_ready = Wire(Bits(1))
151 payload, valid = chan.unwrap(src_ready)
152 fwd_valid = (valid & ~reset_pending).as_bits()
153 gated, dn_ready = chan.type.wrap(payload, fwd_valid)
154 src_ready.assign((dn_ready | reset_pending).as_bits())
155 return gated
156
157 resp_channel = esi.ChannelService.from_host(
158 esi.AppID("__cosim_hostmem_read_resp"),
159 StructType([
160 ("tag", UInt(8)),
161 ("data", Bits(ESI_Cosim_UserTopWrapper.HostMemWidth)),
162 ]))
163 req = wrapper.hostmem_read.unpack(
164 resp=drop_while_resetting(resp_channel))['req']
165 esi.ChannelService.to_host(esi.AppID("__cosim_hostmem_read_req"), req)
166
167 ack_wire = Wire(Channel(UInt(8)))
168 write_req = wrapper.hostmem_write.unpack(ackTag=ack_wire)['req']
169 ack_tag = esi.CallService.call(esi.AppID("__cosim_hostmem_write"),
170 write_req, UInt(8))
171 ack_wire.assign(drop_while_resetting(ack_tag))
172
173 cosim_svc = raw_esi.ServiceInstanceOp(
174 result=[],
175 appID=AppID("cosim")._appid,
176 service_symbol=None,
177 impl_type=ir.StringAttr.get("cosim"),
178 inputs=[ports.clk.value, ports.rst.value],
179 loc=get_user_loc())
180 core_freq = System.current().core_freq
181 if core_freq is not None:
182 cosim_svc.operation.attributes[
183 "esi.core_clock_frequency_hz"] = ir.IntegerAttr.get(
184 ir.IntegerType.get_unsigned(64), core_freq)
185
186 return ESI_Cosim_Top
187
188
189def CosimBSP_DMA(user_module: Type[Module]) -> Module:
190 return CosimBSP(user_module,
191 dma_engine_pair=(OneItemBuffersToHost,
192 OneItemBuffersFromHost))