CIRCT 23.0.0git
Loading...
Searching...
No Matches
dma.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
6
7from pycde.common import Clock, Input, InputChannel, OutputChannel, Reset
8from pycde.constructs import ControlReg, Mux, NamedWire, Wire
9from pycde.module import modparams, generator
10from pycde.types import Bits, Channel, StructType, Type, UInt
11from pycde.support import clog2
12from pycde import esi
13
14
15@modparams
16def OneItemBuffersToHost(client_type: Type):
17 """Create a simple, non-performant DMA-based channel communication. Protocol:
18
19 1) Host sends address of buffer address via MMIO write.
20 2) Device writes data on channel with a byte '1' to said buffer address.
21 3) Host polls the last byte in buffer for '1'.
22 4) Data is copied out of buffer, last byte is set to '0', goto 1.
23
24 Future improvement: support more than one buffer at once."""
25
26 class OneItemBuffersToHost(esi.EngineModule):
27
28 @property
29 def TypeName(self):
30 return "OneItemBuffersToHost"
31
32 clk = Clock()
33 rst = Reset()
34 # The channel whose messages we are sending to the host.
35 input_channel = InputChannel(client_type)
36
37 # Since we cannot produce service requests (shortcoming of the ESI
38 # compiler), the module (usually a service implementation) must issue the
39 # service requests for us then connect the input ports.
40 mmio = Input(esi.MMIO.read_write.type)
41 xfer_data_type = StructType([("valid", Bits(8)),
42 ("client_data", client_type)])
43 hostmem_write = Input(esi.HostMem.write_req_bundle_type(xfer_data_type))
44
45 @generator
46 def build(ports):
47 clk = ports.clk
48 rst = ports.rst
49
50 # Set up the MMIO interface to receive the buffer locations.
51 mmio_resp_chan = Wire(Channel(Bits(64)))
52 mmio_rw = ports.mmio
53 mmio_cmd_chan_raw = mmio_rw.unpack(data=mmio_resp_chan)['cmd']
54 mmio_cmd_chan, mmio_cmd_fork_resp = mmio_cmd_chan_raw.fork(clk, rst)
55
56 # Create a response channel which always responds with 0.
57 mmio_resp_data = NamedWire(Bits(64)(0), "mmio_resp_data")
58 mmio_resp_chan.assign(
59 mmio_cmd_fork_resp.transform(lambda _: mmio_resp_data))
60
61 # Create a mailbox for each register. Overkill for one register, but may
62 # be useful later on.
63 _, _, mmio_cmd = mmio_cmd_chan.snoop()
64 num_sinks = 2
65 mmio_offset_words = NamedWire((mmio_cmd.offset.as_bits()[3:]).as_uint(),
66 "mmio_offset_words")
67 addr_above = mmio_offset_words >= UInt(32)(num_sinks)
68 addr_is_zero = mmio_offset_words == UInt(32)(0)
69 force_to_null = NamedWire(addr_above | ~addr_is_zero | mmio_cmd.write,
70 "force_to_null")
71 cmd_sink_sel = Mux(force_to_null,
72 Bits(clog2(num_sinks))(0),
73 mmio_offset_words.as_bits()[:clog2(num_sinks)])
74 mmio_data_only_chan = mmio_cmd_chan.transform(lambda m: m.data)
75 mailbox_names = ["null", "buffer_loc"]
76 demuxed = esi.ChannelDemux(mmio_data_only_chan, cmd_sink_sel, num_sinks)
77 mailbox_mod = esi.Mailbox(Bits(64))
78 mailboxes = [
79 mailbox_mod(clk=clk,
80 rst=rst,
81 input=c,
82 instance_name="mailbox_" + name)
83 for name, c in zip(mailbox_names, demuxed)
84 ]
85 [_, buffer_loc] = mailboxes
86
87 # Join the buffer location channel with the input channel and send a write
88 # message to hostmem.
89 next_buffer_loc_chan = buffer_loc.output
90 hostwr_type = esi.HostMem.write_req_channel_type(
91 OneItemBuffersToHost.xfer_data_type)
92 hostwr_joined = Channel.join(next_buffer_loc_chan, ports.input_channel)
93 hostwr = hostwr_joined.transform(lambda joined: hostwr_type({
94 "address": joined.a.as_uint(),
95 "tag": 0,
96 "data": {
97 "valid": 1,
98 "client_data": joined.b
99 },
100 }))
101 ports.hostmem_write.unpack(req=hostwr)
102
103 return OneItemBuffersToHost
104
105
106def OneItemBuffersFromHost(client_type: Type):
107 """Create a simple, non-performant DMA-base from host communication channel.
108
109 1) Host sends address of buffer address via MMIO write to register 0x08.
110 2) Host sends address of completion address via MMIO write to register 0x10.
111 3) Device reads data from said buffer and sends down the channel. Only keeps
112 one transfer outstanding by buffering the read response locally before
113 forwarding it.
114 4) Device writes '1' to the first byte of the completion buffer to signal that
115 the transfer is done.
116 """
117
118 class OneItemBuffersFromHost(esi.EngineModule):
119
120 @property
121 def TypeName(self):
122 return "OneItemBuffersFromHost"
123
124 clk = Clock()
125 rst = Reset()
126
127 # The channel whose messages we are sending to the host.
128 output_channel = OutputChannel(client_type)
129
130 # Since we cannot produce service requests (shortcoming of the ESI
131 # compiler), the module (usually a service implementation) must issue the
132 # service requests for us then connect the input ports.
133 mmio = Input(esi.MMIO.read_write.type)
134 xfer_data_type = Bits(8)
135 hostmem_write = Input(esi.HostMem.write_req_bundle_type(xfer_data_type))
136 hostmem_read = Input(esi.HostMem.read_bundle_type(client_type))
137
138 @generator
139 def build(ports):
140 clk = ports.clk
141 rst = ports.rst
142
143 # Set up the MMIO interface to receive the buffer locations.
144 mmio_resp_chan = Wire(Channel(Bits(64)))
145 mmio_rw = ports.mmio
146 mmio_cmd_chan_raw = mmio_rw.unpack(data=mmio_resp_chan)['cmd']
147 mmio_cmd_chan, mmio_cmd_fork_resp = mmio_cmd_chan_raw.fork(clk, rst)
148
149 # Create a response channel which always responds with 0.
150 mmio_resp_data = NamedWire(Bits(64)(0), "mmio_resp_data")
151 mmio_resp_chan.assign(
152 mmio_cmd_fork_resp.transform(lambda _: mmio_resp_data))
153
154 # Route the MMIO command
155 _, _, mmio_cmd = mmio_cmd_chan.snoop()
156 mailbox_names = ["null", "buffer_loc", "completion_addr"]
157 num_sinks = len(mailbox_names)
158 mmio_offset_words = NamedWire((mmio_cmd.offset.as_bits()[3:]).as_uint(),
159 "mmio_offset_words")
160 addr_above = mmio_offset_words >= UInt(32)(num_sinks)
161 addr_is_zero = mmio_offset_words == UInt(32)(0)
162 force_to_null = NamedWire(addr_above | ~addr_is_zero | mmio_cmd.write,
163 "force_to_null")
164 cmd_sink_sel = Mux(force_to_null,
165 Bits(clog2(num_sinks))(0),
166 mmio_offset_words.as_bits()[:clog2(num_sinks)])
167 mmio_data_only_chan = mmio_cmd_chan.transform(lambda m: m.data)
168 demuxed = esi.ChannelDemux(mmio_data_only_chan, cmd_sink_sel, num_sinks)
169 # Mailbox is intentionally always-ready and overwrite-on-write: if
170 # software updates either address too early, the new value replaces the
171 # stale one instead of backpressuring MMIO.
172 mailbox_mod = esi.Mailbox(Bits(64))
173 mailboxes = [
174 mailbox_mod(clk=clk,
175 rst=rst,
176 input=c,
177 instance_name="mailbox_" + name)
178 for name, c in zip(mailbox_names, demuxed)
179 ]
180 [_, buffer_loc, completion_loc] = mailboxes
181 buffer_loc_for_read = buffer_loc.output
182
183 output_chan = Wire(Channel(client_type))
184 ports.output_channel = output_chan
185 output_buf_ready = Wire(Bits(1), name="output_buf_ready")
186
187 # Only keep one outstanding transfer. Once we can buffer a response
188 # locally, issue the host read even if the downstream consumer has not
189 # raised ready yet.
190 read_req_accept = Wire(Bits(1), name="read_req_accept")
191 buffer_loc_data, buffer_loc_valid = buffer_loc_for_read.unwrap(
192 read_req_accept)
193 read_req_valid = NamedWire(buffer_loc_valid & output_buf_ready,
194 "read_req_valid")
195 read_req, read_req_ready = Channel(esi.HostMem.ReadReqType).wrap(
196 esi.HostMem.ReadReqType({
197 "address": buffer_loc_data.as_uint(),
198 "tag": 0,
199 }), read_req_valid)
200 read_req_xact = NamedWire(read_req_valid & read_req_ready,
201 "read_req_xact")
202 read_req_accept.assign(read_req_xact)
203
204 # Buffer the read response locally so forwarding the data and issuing the
205 # completion write do not both need the shared HostMem write path in the
206 # same cycle.
207 read_resp = ports.hostmem_read.unpack(req=read_req)['resp']
208 output_ready = Wire(Bits(1), name="output_ready")
209 output_valid_reset = Wire(Bits(1), name="output_valid_reset")
210 read_resp_ready = Wire(Bits(1), name="read_resp_ready")
211 read_resp_msg, read_resp_valid = read_resp.unwrap(read_resp_ready)
212 read_resp_xact = NamedWire(read_resp_valid & read_resp_ready,
213 "read_resp_xact")
214 output_valid = ControlReg(
215 clk=clk,
216 rst=rst,
217 asserts=[read_resp_xact],
218 resets=[output_valid_reset],
219 name="output_valid",
220 )
221 output_data = read_resp_msg.data.reg(clk=clk,
222 rst=rst,
223 ce=read_resp_xact,
224 name="output_data")
225 output_buf_ready.assign((~output_valid | output_ready).as_bits())
226 read_resp_ready.assign(output_buf_ready)
227 output_valid_reset.assign(output_valid & output_ready)
228 output_buf_chan, output_chan_ready = Channel(client_type).wrap(
229 output_data, output_valid)
230 output_chan.assign(output_buf_chan)
231 output_ready.assign(output_chan_ready)
232
233 # Once the data has been read into the local buffer, issue the completion
234 # write independently of when the downstream consumer accepts that data.
235 completion_pending_reset = Wire(Bits(1), name="completion_pending_reset")
236 completion_pending = ControlReg(
237 clk=clk,
238 rst=rst,
239 asserts=[read_resp_xact],
240 resets=[completion_pending_reset],
241 name="completion_pending",
242 )
243
244 write_ch_type = esi.HostMem.write_req_channel_type(
245 OneItemBuffersFromHost.xfer_data_type)
246 completion_loc_ready = Wire(Bits(1), name="completion_loc_ready")
247 completion_loc_data, completion_loc_valid = completion_loc.output.unwrap(
248 completion_loc_ready)
249 completion_write_valid = NamedWire(
250 completion_loc_valid & completion_pending, "completion_write_valid")
251 write_done_chan, completion_write_ready = Channel(write_ch_type).wrap(
252 write_ch_type({
253 "address": completion_loc_data.as_uint(),
254 "tag": 0,
255 "data": Bits(8)(1)
256 }), completion_write_valid)
257 completion_write_xact = NamedWire(
258 completion_write_valid & completion_write_ready,
259 "completion_write_xact")
260 completion_loc_ready.assign(completion_write_xact)
261 completion_pending_reset.assign(completion_write_xact)
262
263 # Unpack the write port, but ignore the write confirmation.
264 ports.hostmem_write.unpack(req=write_done_chan)
265
266 return OneItemBuffersFromHost
return wrap(CMemoryType::get(unwrap(ctx), baseType, numElements))