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,
37 """Wrap and return a cosimulation 'board support package' containing
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.
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.
51 Passing both 'dma_engine_pair' and 'channel_service' raises ValueError.
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.
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.
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:
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)
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."""
80 mmio =
Input(esi.MMIO.read_write.type)
82 HostMemWidth = hostmem_width
84 ChannelHostMemModule = ChannelHostMem(
85 read_width=HostMemWidth,
86 write_width=HostMemWidth,
87 max_read_request_bytes=max(DEFAULT_MAX_READ_REQUEST_BYTES,
89 max_write_payload_bytes=max(DEFAULT_MAX_WRITE_PAYLOAD_BYTES,
91 max_outstanding_reads=max_outstanding_reads,
92 round_read_lengths=
False)
94 hostmem_read = ChannelHostMemModule.read
95 hostmem_write = ChannelHostMemModule.write
102 reset_request =
Output(Bits(1))
106 user_module(clk=ports.clk, rst=ports.rst)
112 if channel_service
is not None:
113 channel_service(
None,
123 ports.reset_request = mmio.reset_request
128 hostmem = ESI_Cosim_UserTopWrapper.ChannelHostMemModule(
133 ports.hostmem_read = hostmem.read
134 ports.hostmem_write = hostmem.write
136 class ESI_Cosim_Top(
Module):
142 System.current().platform =
"cosim"
144 mmio_read_write = esi.FuncService.get(
145 esi.AppID(
"__cosim_mmio_read_write"), esi.MMIO.read_write.type)
151 design_reset = Wire(Bits(1))
152 reset_pending = Wire(Bits(1))
153 combined_rst = ports.rst | design_reset
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'])
175 wrapper = ESI_Cosim_UserTopWrapper(clk=ports.clk,
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)
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())
199 resp_channel = esi.ChannelService.from_host(
203 (
"data", Bits(ESI_Cosim_UserTopWrapper.HostMemWidth)),
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)
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"),
214 ack_wire.assign(drop_while_resetting(ack_tag))
216 cosim_svc = raw_esi.ServiceInstanceOp(
218 appID=AppID(
"cosim")._appid,
220 impl_type=ir.StringAttr.get(
"cosim"),
221 inputs=[ports.clk.value, ports.rst.value],
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)