Skip to content

Commit 380b2ad

Browse files
authored
[ESI] Add hostmem write support to cosim (#8059)
Adds cosim support to both the cosim BSP in PyCDE and the corresponding support in the runtime. As with hostmem reads, write only supports one client and no gearboxing.
1 parent dc9a543 commit 380b2ad

File tree

9 files changed

+234
-31
lines changed

9 files changed

+234
-31
lines changed

frontends/PyCDE/integration_test/esitester.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,52 @@ def construct(ports):
114114
mem_data_ce.assign(hostmem_read_resp_valid)
115115

116116

117+
class WriteMem(Module):
118+
"""Writes a cycle count to host memory at address 0 in MMIO upon each MMIO
119+
transaction."""
120+
clk = Clock()
121+
rst = Reset()
122+
123+
@generator
124+
def construct(ports):
125+
cmd_chan_wire = Wire(Channel(esi.MMIOReadWriteCmdType))
126+
resp_ready_wire = Wire(Bits(1))
127+
cmd, cmd_valid = cmd_chan_wire.unwrap(resp_ready_wire)
128+
mmio_xact = cmd_valid & resp_ready_wire
129+
130+
write_loc_ce = mmio_xact & cmd.write & (cmd.offset == UInt(32)(0))
131+
write_loc = Reg(UInt(64),
132+
clk=ports.clk,
133+
rst=ports.rst,
134+
rst_value=0,
135+
ce=write_loc_ce)
136+
write_loc.assign(cmd.data.as_uint())
137+
138+
response_data = write_loc.as_bits()
139+
response_chan, response_ready = Channel(Bits(64)).wrap(
140+
response_data, cmd_valid)
141+
resp_ready_wire.assign(response_ready)
142+
143+
mmio_rw = esi.MMIO.read_write(appid=AppID("WriteMem"))
144+
mmio_rw_cmd_chan = mmio_rw.unpack(data=response_chan)['cmd']
145+
cmd_chan_wire.assign(mmio_rw_cmd_chan)
146+
147+
tag = Counter(8)(clk=ports.clk, rst=ports.rst, increment=mmio_xact)
148+
149+
cycle_counter = Counter(64)(clk=ports.clk,
150+
rst=ports.rst,
151+
increment=Bits(1)(1))
152+
153+
hostmem_write_req, _ = esi.HostMem.wrap_write_req(
154+
write_loc,
155+
cycle_counter.out.as_bits(),
156+
tag.out,
157+
valid=mmio_xact.reg(ports.clk, ports.rst))
158+
159+
hostmem_write_resp = esi.HostMem.write(appid=AppID("WriteMem_hostwrite"),
160+
req=hostmem_write_req)
161+
162+
117163
class EsiTesterTop(Module):
118164
clk = Clock()
119165
rst = Reset()
@@ -122,6 +168,7 @@ class EsiTesterTop(Module):
122168
def construct(ports):
123169
PrintfExample(clk=ports.clk, rst=ports.rst)
124170
ReadMem(clk=ports.clk, rst=ports.rst)
171+
WriteMem(clk=ports.clk, rst=ports.rst)
125172

126173

127174
if __name__ == "__main__":

frontends/PyCDE/src/pycde/bsp/common.py

Lines changed: 73 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@
55
from __future__ import annotations
66

77
from ..common import Clock, Input, Output, Reset
8-
from ..constructs import AssignableSignal, ControlReg, NamedWire, Wire
8+
from ..constructs import AssignableSignal, NamedWire, Wire
99
from .. import esi
1010
from ..module import Module, generator, modparams
1111
from ..signals import BitsSignal, BundleSignal, ChannelSignal
1212
from ..support import clog2
1313
from ..types import (Array, Bits, Bundle, BundledChannel, Channel,
14-
ChannelDirection, StructType, Type, UInt)
14+
ChannelDirection, StructType, UInt)
1515

1616
from typing import Dict, List, Tuple
1717
import typing
@@ -266,34 +266,100 @@ class ChannelHostMemImpl(esi.ServiceImplementation):
266266
clk = Clock()
267267
rst = Reset()
268268

269-
UpstreamReq = StructType([
269+
UpstreamReadReq = StructType([
270270
("address", UInt(64)),
271271
("length", UInt(32)),
272272
("tag", UInt(8)),
273273
])
274274
read = Output(
275275
Bundle([
276-
BundledChannel("req", ChannelDirection.TO, UpstreamReq),
276+
BundledChannel("req", ChannelDirection.TO, UpstreamReadReq),
277277
BundledChannel(
278278
"resp", ChannelDirection.FROM,
279279
StructType([
280280
("tag", UInt(8)),
281281
("data", Bits(read_width)),
282282
])),
283283
]))
284+
UpstreamWriteReq = StructType([
285+
("address", UInt(64)),
286+
("tag", UInt(8)),
287+
("data", Bits(write_width)),
288+
])
289+
write = Output(
290+
Bundle([
291+
BundledChannel("req", ChannelDirection.TO, UpstreamWriteReq),
292+
BundledChannel("ackTag", ChannelDirection.FROM, UInt(8)),
293+
]))
284294

285295
@generator
286296
def generate(ports, bundles: esi._ServiceGeneratorBundles):
287297
read_reqs = [req for req in bundles.to_client_reqs if req.port == 'read']
288298
ports.read = ChannelHostMemImpl.build_tagged_read_mux(ports, read_reqs)
299+
write_reqs = [
300+
req for req in bundles.to_client_reqs if req.port == 'write'
301+
]
302+
ports.write = ChannelHostMemImpl.build_tagged_write_mux(ports, write_reqs)
303+
304+
@staticmethod
305+
def build_tagged_write_mux(
306+
ports, reqs: List[esi._OutputBundleSetter]) -> BundleSignal:
307+
"""Build the write side of the HostMem service."""
308+
309+
# If there's no write clients, just return a no-op write bundle
310+
if len(reqs) == 0:
311+
req, _ = Channel(ChannelHostMemImpl.UpstreamWriteReq).wrap(
312+
{
313+
"address": 0,
314+
"tag": 0,
315+
"data": 0
316+
}, 0)
317+
write_bundle, _ = ChannelHostMemImpl.write.type.pack(req=req)
318+
return write_bundle
319+
320+
# TODO: mux together multiple write clients.
321+
assert len(reqs) == 1, "Only one write client supported for now."
322+
323+
# Build the write request channels and ack wires.
324+
write_channels: List[ChannelSignal] = []
325+
write_acks = []
326+
for req in reqs:
327+
# Get the request channel and its data type.
328+
reqch = [c.channel for c in req.type.channels if c.name == 'req'][0]
329+
data_type = reqch.inner_type.data
330+
assert data_type == Bits(
331+
write_width
332+
), f"Gearboxing not yet supported. Client {req.client_name}"
333+
334+
# Write acks to be filled in later.
335+
write_ack = Wire(Channel(UInt(8)))
336+
write_acks.append(write_ack)
337+
338+
# Pack up the bundle and assign the request channel.
339+
write_req_bundle_type = esi.HostMem.write_req_bundle_type(data_type)
340+
bundle_sig, froms = write_req_bundle_type.pack(ackTag=write_ack)
341+
tagged_client_req = froms["req"]
342+
req.assign(bundle_sig)
343+
write_channels.append(tagged_client_req)
344+
345+
# TODO: re-write the tags and store the client and client tag.
346+
347+
# Build a channel mux for the write requests.
348+
tagged_write_channel = esi.ChannelMux(write_channels)
349+
upstream_write_bundle, froms = ChannelHostMemImpl.write.type.pack(
350+
req=tagged_write_channel)
351+
ack_tag = froms["ackTag"]
352+
# TODO: decode the ack tag and assign it to the correct client.
353+
write_acks[0].assign(ack_tag)
354+
return upstream_write_bundle
289355

290356
@staticmethod
291357
def build_tagged_read_mux(
292358
ports, reqs: List[esi._OutputBundleSetter]) -> BundleSignal:
293359
"""Build the read side of the HostMem service."""
294360

295361
if len(reqs) == 0:
296-
req, req_ready = Channel(ChannelHostMemImpl.UpstreamReq).wrap(
362+
req, req_ready = Channel(ChannelHostMemImpl.UpstreamReadReq).wrap(
297363
{
298364
"tag": 0,
299365
"length": 0,
@@ -305,7 +371,7 @@ def build_tagged_read_mux(
305371
# TODO: mux together multiple read clients.
306372
assert len(reqs) == 1, "Only one read client supported for now."
307373

308-
req = Wire(Channel(ChannelHostMemImpl.UpstreamReq))
374+
req = Wire(Channel(ChannelHostMemImpl.UpstreamReadReq))
309375
read_bundle, froms = ChannelHostMemImpl.read.type.pack(req=req)
310376
resp_chan_ready = Wire(Bits(1))
311377
resp_data, resp_valid = froms["resp"].unwrap(resp_chan_ready)
@@ -335,7 +401,7 @@ def build_tagged_read_mux(
335401

336402
# Assign the multiplexed read request to the upstream request.
337403
req.assign(
338-
client_req.transform(lambda r: ChannelHostMemImpl.UpstreamReq({
404+
client_req.transform(lambda r: ChannelHostMemImpl.UpstreamReadReq({
339405
"address": r.address,
340406
"length": 1,
341407
"tag": r.tag

frontends/PyCDE/src/pycde/bsp/cosim.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,12 @@ def build(ports):
7272
resp_wire.type)
7373
resp_wire.assign(data)
7474

75+
ack_wire = Wire(Channel(UInt(8)))
76+
write_req = hostmem.write.unpack(ackTag=ack_wire)['req']
77+
ack_tag = esi.CallService.call(esi.AppID("__cosim_hostmem_write"),
78+
write_req, UInt(8))
79+
ack_wire.assign(ack_tag)
80+
7581
class ESI_Cosim_Top(Module):
7682
clk = Clock()
7783
rst = Input(Bits(1))

frontends/PyCDE/src/pycde/esi.py

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -519,24 +519,34 @@ class _HostMem(ServiceDecl):
519519
("tag", UInt(8)),
520520
])
521521

522-
WriteReqType = StructType([
523-
("address", UInt(64)),
524-
("tag", UInt(8)),
525-
("data", Any()),
526-
])
527-
528522
def __init__(self):
529523
super().__init__(self.__class__)
530524

525+
def write_req_bundle_type(self, data_type: Type) -> Bundle:
526+
"""Build a write request bundle type for the given data type."""
527+
write_req_type = StructType([
528+
("address", UInt(64)),
529+
("tag", UInt(8)),
530+
("data", data_type),
531+
])
532+
return Bundle([
533+
BundledChannel("req", ChannelDirection.FROM, write_req_type),
534+
BundledChannel("ackTag", ChannelDirection.TO, UInt(8))
535+
])
536+
537+
def write_req_channel_type(self, data_type: Type) -> StructType:
538+
"""Return a write request struct type for 'data_type'."""
539+
return StructType([
540+
("address", UInt(64)),
541+
("tag", UInt(8)),
542+
("data", data_type),
543+
])
544+
531545
def wrap_write_req(self, address: UIntSignal, data: Type, tag: UIntSignal,
532546
valid: BitsSignal) -> Tuple[ChannelSignal, BitsSignal]:
533547
"""Create the proper channel type for a write request and use it to wrap the
534548
given request arguments. Returns the Channel signal and a ready bit."""
535-
inner_type = StructType([
536-
("address", UInt(64)),
537-
("tag", UInt(8)),
538-
("data", data.type),
539-
])
549+
inner_type = self.write_req_channel_type(data.type)
540550
return Channel(inner_type).wrap(
541551
inner_type({
542552
"address": address,
@@ -548,10 +558,10 @@ def write(self, appid: AppID, req: ChannelSignal) -> ChannelSignal:
548558
"""Create a write request to the host memory out of a request channel."""
549559
self._materialize_service_decl()
550560

551-
write_bundle_type = Bundle([
552-
BundledChannel("req", ChannelDirection.FROM, _HostMem.WriteReqType),
553-
BundledChannel("ackTag", ChannelDirection.TO, UInt(8))
554-
])
561+
# Extract the data type from the request channel and call the helper to get
562+
# the write bundle type for the req channel.
563+
req_data_type = req.type.inner_type.data
564+
write_bundle_type = self.write_req_bundle_type(req_data_type)
555565

556566
bundle = cast(
557567
BundleSignal,

frontends/PyCDE/src/pycde/signals.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -809,8 +809,9 @@ def unpack(self, **kwargs: ChannelSignal) -> Dict[str, ChannelSignal]:
809809
raise ValueError(
810810
f"Missing channel values for {', '.join(from_channels.keys())}")
811811

812-
unpack_op = esi.UnpackBundleOp([bc.channel._type for bc in to_channels],
813-
self.value, operands)
812+
with get_user_loc():
813+
unpack_op = esi.UnpackBundleOp([bc.channel._type for bc in to_channels],
814+
self.value, operands)
814815

815816
to_channels_results = unpack_op.toChannels
816817
ret = {

frontends/PyCDE/src/pycde/types.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -858,9 +858,10 @@ def pack(
858858
if len(to_channels) > 0:
859859
raise ValueError(f"Missing channels: {', '.join(to_channels.keys())}")
860860

861-
pack_op = esi.PackBundleOp(self._type,
862-
[bc.channel._type for bc in from_channels],
863-
operands)
861+
with get_user_loc():
862+
pack_op = esi.PackBundleOp(self._type,
863+
[bc.channel._type for bc in from_channels],
864+
operands)
864865

865866
return BundleSignal(pack_op.bundle, self), Bundle.PackSignalResults(
866867
[_FromCirctValue(c) for c in pack_op.fromChannels], self)

frontends/PyCDE/test/test_esi.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -308,8 +308,8 @@ def build(ports):
308308
# CHECK-NEXT: [[R5:%.+]] = hwarith.constant 0 : ui256
309309
# CHECK-NEXT: [[R6:%.+]] = hw.struct_create ([[R0]], [[R4]], [[R5]]) : !hw.struct<address: ui64, tag: ui8, data: ui256>
310310
# CHECK-NEXT: %chanOutput_0, %ready_1 = esi.wrap.vr [[R6]], %false : !hw.struct<address: ui64, tag: ui8, data: ui256>
311-
# CHECK-NEXT: [[R7:%.+]] = esi.service.req <@_HostMem::@write>(#esi.appid<"host_mem_write_req">) : !esi.bundle<[!esi.channel<!hw.struct<address: ui64, tag: ui8, data: !esi.any>> from "req", !esi.channel<ui8> to "ackTag"]>
312-
# CHECK-NEXT: %ackTag = esi.bundle.unpack %chanOutput_0 from [[R7]] : !esi.bundle<[!esi.channel<!hw.struct<address: ui64, tag: ui8, data: !esi.any>> from "req", !esi.channel<ui8> to "ackTag"]>
311+
# CHECK-NEXT: [[R7:%.+]] = esi.service.req <@_HostMem::@write>(#esi.appid<"host_mem_write_req">) : !esi.bundle<[!esi.channel<!hw.struct<address: ui64, tag: ui8, data: ui256>> from "req", !esi.channel<ui8> to "ackTag"]>
312+
# CHECK-NEXT: %ackTag = esi.bundle.unpack %chanOutput_0 from [[R7]] : !esi.bundle<[!esi.channel<!hw.struct<address: ui64, tag: ui8, data: ui256>> from "req", !esi.channel<ui8> to "ackTag"]>
313313
# CHECK: esi.service.std.hostmem @_HostMem
314314
@unittestmodule(esi_sys=True)
315315
class HostMemReq(Module):

0 commit comments

Comments
 (0)