CIRCT 23.0.0git
Loading...
Searching...
No Matches
xrt.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 pycde.common import Clock, Input, InputChannel, Output, OutputChannel
6from pycde.constructs import Mux, Wire
7from pycde.module import Module, generator
8from pycde.signals import BitsSignal, Struct
9from pycde.system import System
10from pycde.support import clog2
11from pycde.types import Array, Bits, Channel, UInt
12from pycde import esi
13
14from .common import (ChannelEngineService, ChannelHostMem, ChannelMMIO,
15 MMIOIndirection, Reset)
16from .dma import OneItemBuffersFromHost, OneItemBuffersToHost
17
18import glob
19import pathlib
20import shutil
21
22__dir__ = pathlib.Path(__file__).parent
23
24
25class AXI_Lite_Read_Resp(Struct):
26 data: Bits(32)
27 resp: Bits(2)
28
29
30AxiMMIOAddrWidth = 20
31
32
33class MMIOSel(Struct):
34 write: Bits(1)
35 upper: Bits(1)
36
37
38class MMIOIntermediateCmd(Struct):
39 upper: Bits(1)
40 write: Bits(1)
41 offset: UInt(32)
42 data: esi.MMIODataType
43
44
45class MMIOAxiWriteCombine(Module):
46 """MMIO AXI Lite writes on XRT are 32 bits, but the MMIO service expects 64.
47 Furthermore, there are two separate channels for writes: address and data. All
48 four transactions must take place before an ESI MMIO write is considered
49 complete."""
50
51 clk = Clock()
52 rst = Reset()
53 write_address = InputChannel(Bits(AxiMMIOAddrWidth))
54 write_data = InputChannel(Bits(32))
55
56 cmd = OutputChannel(MMIOIntermediateCmd)
57 write_resp = OutputChannel(Bits(2))
58
59 @generator
60 def build(ports):
61 write_joined = Channel.join(ports.write_address, ports.write_data)
62 write, resp = write_joined.fork(ports.clk, ports.rst)
63 ports.write_resp = resp.transform(lambda x: Bits(2)(0))
64
65 sel = Wire(Bits(1))
66 [write_lo_chan, write_hi_chan] = esi.ChannelDemux(write, sel, 2)
67
68 write_lo = esi.Mailbox(write.type)("write_lo",
69 clk=ports.clk,
70 rst=ports.rst,
71 input=write_lo_chan)
72 write_hi = esi.Mailbox(write.type)("write_hi",
73 clk=ports.clk,
74 rst=ports.rst,
75 input=write_hi_chan)
76
77 # The correct order of the write is low bits, high bits. Detect and mitigate
78 # some runtime sync errors by checking the addresses.
79 sel.assign((write_lo.valid & (write_lo.data.a[2] == Bits(1)(0))) |
80 (write_hi.valid & (write_hi.data.a[2] == Bits(1)(1))))
81
82 joined = Channel.join(write_lo.output, write_hi.output)
83 cmd = joined.transform(lambda x: MMIOIntermediateCmd(
84 write=Bits(1)(1),
85 offset=(x.a.a & ~Bits(AxiMMIOAddrWidth)(0x7)).as_uint(32),
86 data=Bits(64)(BitsSignal.concat([x.b.b, x.a.b])),
87 upper=Bits(1)(0)))
88 ports.cmd = cmd
89
90
91class MMIOAxiReadWriteMux(Module):
92 clk = Clock()
93 rst = Reset()
94 read_address = InputChannel(Bits(AxiMMIOAddrWidth))
95 write_address = InputChannel(Bits(AxiMMIOAddrWidth))
96 write_data = InputChannel(Bits(32))
97 write_resp = OutputChannel(Bits(2))
98
99 cmd = OutputChannel(esi.MMIOReadWriteCmdType)
100 sel = OutputChannel(MMIOSel)
101
102 @generator
103 def build(ports):
104 read_cmd = ports.read_address.transform(lambda x: MMIOIntermediateCmd(
105 write=Bits(1)(0),
106 offset=(x & ~Bits(AxiMMIOAddrWidth)(0x7)).as_uint(32),
107 data=Bits(64)(0),
108 upper=(x[0x2])))
109 write_combine = MMIOAxiWriteCombine("writeCombine",
110 clk=ports.clk,
111 rst=ports.rst,
112 write_address=ports.write_address,
113 write_data=ports.write_data)
114 ports.write_resp = write_combine.write_resp
115 merged_cmd = read_cmd.type.merge(read_cmd, write_combine.cmd)
116
117 merged_cmd_a, merged_cmd_b = merged_cmd.fork(ports.clk, ports.rst)
118 ports.sel = merged_cmd_b.transform(
119 lambda x: MMIOSel(write=x.write, upper=x.upper))
120 ports.cmd = merged_cmd_a.transform(lambda x: esi.MMIOReadWriteCmdType({
121 "write": x.write,
122 "offset": x.offset,
123 "data": x.data,
124 }))
125
126
128 clk = Clock()
129 rst = Reset()
130 sel = InputChannel(MMIOSel)
131 data = InputChannel(esi.MMIODataType)
132
133 read_data = OutputChannel(AXI_Lite_Read_Resp)
134
135 @generator
136 def build(ports):
137 data_sel = Channel.join(ports.data, ports.sel)
138 data_sel_ready = Wire(Bits(1))
139 data_sel_data, data_sel_valid = data_sel.unwrap(data_sel_ready)
140 data = data_sel_data.a
141 sel = data_sel_data.b
142
143 # Read channel output
144 read_data = AXI_Lite_Read_Resp(data=Mux(sel.upper, data[0:32], data[32:64]),
145 resp=Bits(2)(0))
146 read_valid = data_sel_valid & ~sel.write & data_sel_ready
147 read_chan, read_ready = Channel(AXI_Lite_Read_Resp).wrap(
148 read_data, read_valid)
149 ports.read_data = read_chan
150
151 # Write response channel output
152 write_resp_data = data[0:2]
153 write_resp_valid = data_sel_valid & sel.write & data_sel_ready
154 write_resp_chan, write_resp_ready = Channel(Bits(2)).wrap(
155 write_resp_data, write_resp_valid)
156 ports.write_resp = write_resp_chan
157
158 # Only if both are ready do we accept data.
159 data_sel_ready.assign(read_ready & write_resp_ready)
160
161
162def XrtBSP(user_module):
163 """Use the Xilinx RunTime (XRT) shell to implement ESI services and build an
164 image or emulation package.
165 How to use this BSP:
166 - Wrap your top PyCDE module in `XrtBSP`.
167 - Run your script. This BSP will write a 'build package' to the output dir.
168 This package contains a Makefile.xrt.mk which (given a proper Vitis dev
169 environment) will compile a hw image or hw_emu image. It is a free-standing
170 build package -- you do not need PyCDE installed on the same machine as you
171 want to do the image build.
172 - To build the `hw` image, run 'make -f Makefile.xrt TARGET=hw'. If you want
173 an image which runs on an Azure NP-series instance, run the 'azure' target
174 (requires an Azure subscription set up with as per
175 https://learn.microsoft.com/en-us/azure/virtual-machines/field-programmable-gate-arrays-attestation).
176 This target requires a few environment variables to be set (which the Makefile
177 will tell you about).
178 - To build a hw emulation image, run make with TARGET=hw_emu.
179 - At "runtime" set XCL_EMULATION_MODE=hw_emu.
180 - Validated ONLY on Vitis 2023.1. Known to NOT work with Vitis <2022.1.
181 - Vitis spins up a number of jobs and can easily consume all available memory.
182 - Specify the JOBS make variable to limit the number of jobs.
183 - To adjust the desired clock frequency, set the FREQ (in MHz) make variable.
184 """
185
186 class XrtTop(Module):
187 ap_clk = Clock()
188 ap_resetn = Input(Bits(1))
189
190 HostMemAddressWidth = 64
191 HostMemIdWidth = 8
192 HostMemDataWidth = 512
193
194 ############################################################################
195 # AXI4-Lite slave interface for MMIO
196 ############################################################################
197
198 s_axi_control_AWVALID = Input(Bits(1))
199 s_axi_control_AWREADY = Output(Bits(1))
200 s_axi_control_AWADDR = Input(Bits(20))
201 s_axi_control_WVALID = Input(Bits(1))
202 s_axi_control_WREADY = Output(Bits(1))
203 s_axi_control_WDATA = Input(Bits(32))
204 s_axi_control_WSTRB = Input(Bits(32 // 8))
205 s_axi_control_ARVALID = Input(Bits(1))
206 s_axi_control_ARREADY = Output(Bits(1))
207 s_axi_control_ARADDR = Input(Bits(20))
208 s_axi_control_RVALID = Output(Bits(1))
209 s_axi_control_RREADY = Input(Bits(1))
210 s_axi_control_RDATA = Output(Bits(32))
211 s_axi_control_RRESP = Output(Bits(2))
212 s_axi_control_BVALID = Output(Bits(1))
213 s_axi_control_BREADY = Input(Bits(1))
214 s_axi_control_BRESP = Output(Bits(2))
215
216 ############################################################################
217 # AXI4 master interface for host DMA
218 ############################################################################
219
220 # Write side
221 # Address req channel
222 m_axi_gmem_AWVALID = Output(Bits(1))
223 m_axi_gmem_AWREADY = Input(Bits(1))
224 m_axi_gmem_AWADDR = Output(Bits(HostMemAddressWidth))
225 m_axi_gmem_AWID = Output(Bits(HostMemIdWidth))
226 m_axi_gmem_AWLEN = Output(Bits(8))
227 m_axi_gmem_AWSIZE = Output(Bits(3))
228 m_axi_gmem_AWBURST = Output(Bits(2))
229
230 # Data req channel
231 m_axi_gmem_WVALID = Output(Bits(1))
232 m_axi_gmem_WREADY = Input(Bits(1))
233 m_axi_gmem_WDATA = Output(Bits(HostMemDataWidth))
234 m_axi_gmem_WSTRB = Output(Bits(HostMemDataWidth // 8))
235 m_axi_gmem_WLAST = Output(Bits(1))
236
237 # Resp channel
238 m_axi_gmem_BVALID = Input(Bits(1))
239 m_axi_gmem_BREADY = Output(Bits(1))
240 m_axi_gmem_BRESP = Input(Bits(2))
241 m_axi_gmem_BID = Input(Bits(HostMemIdWidth))
242
243 # Read side
244 # Address req channel
245 m_axi_gmem_ARVALID = Output(Bits(1))
246 m_axi_gmem_ARREADY = Input(Bits(1))
247 m_axi_gmem_ARADDR = Output(UInt(HostMemAddressWidth))
248 m_axi_gmem_ARID = Output(UInt(HostMemIdWidth))
249 m_axi_gmem_ARLEN = Output(UInt(8))
250 m_axi_gmem_ARSIZE = Output(Bits(3))
251 m_axi_gmem_ARBURST = Output(Bits(2))
252
253 # Data resp channel
254 m_axi_gmem_RVALID = Input(Bits(1))
255 m_axi_gmem_RREADY = Output(Bits(1))
256 m_axi_gmem_RDATA = Input(Bits(HostMemDataWidth))
257 m_axi_gmem_RLAST = Input(Bits(1))
258 m_axi_gmem_RID = Input(UInt(HostMemIdWidth))
259 m_axi_gmem_RRESP = Input(Bits(2))
260
261 @generator
262 def construct(ports):
263 System.current().platform = "fpga"
264 clk = ports.ap_clk
265 rst = ~ports.ap_resetn
266
267 ChannelEngineService(OneItemBuffersToHost, OneItemBuffersFromHost)(
268 None, appid=esi.AppID("__channel_engines"), clk=clk, rst=rst)
269
270 # Set up the MMIO service and tie it to the AXI-lite channels.
271 read_address, arready = Channel(ports.s_axi_control_ARADDR.type).wrap(
272 ports.s_axi_control_ARADDR, ports.s_axi_control_ARVALID)
273 ports.s_axi_control_ARREADY = arready
274 write_address, awready = Channel(ports.s_axi_control_AWADDR.type).wrap(
275 ports.s_axi_control_AWADDR, ports.s_axi_control_AWVALID)
276 ports.s_axi_control_AWREADY = awready
277 write_data, wready = Channel(ports.s_axi_control_WDATA.type).wrap(
278 ports.s_axi_control_WDATA, ports.s_axi_control_WVALID)
279 ports.s_axi_control_WREADY = wready
280
281 user_module(clk=clk, rst=rst)
282 esi.TelemetryMMIO(esi.Telemetry,
283 appid=esi.AppID("__telemetry"),
284 clk=clk,
285 rst=rst)
286
287 data = Wire(Channel(esi.MMIODataType))
288 rw_mux = MMIOAxiReadWriteMux(clk=clk,
289 rst=rst,
290 read_address=read_address,
291 write_address=write_address,
292 write_data=write_data)
293 sel = rw_mux.sel.buffer(clk, rst, 1)
294 rw_demux = MMIOAxiReadWriteDemux(clk=clk, rst=rst, data=data, sel=sel)
295
296 cmd, froms = esi.MMIO.read_write.type.pack(cmd=rw_mux.cmd)
297 data.assign(froms["data"])
298
299 indirect_mmio = MMIOIndirection(clk=clk, rst=rst, upstream=cmd)
300 ChannelMMIO(esi.MMIO,
301 appid=esi.AppID("__xrt_mmio"),
302 clk=clk,
303 rst=rst,
304 cmd=indirect_mmio.downstream)
305
306 rdata, rvalid = rw_demux.read_data.unwrap(ports.s_axi_control_RREADY)
307 ports.s_axi_control_RVALID = rvalid
308 ports.s_axi_control_RDATA = rdata.data
309 ports.s_axi_control_RRESP = rdata.resp
310
311 wrresp_data, wrresp_valid = rw_mux.write_resp.unwrap(
312 ports.s_axi_control_BREADY)
313 ports.s_axi_control_BVALID = wrresp_valid
314 ports.s_axi_control_BRESP = wrresp_data
315
316 # Construct the host memory interface.
317 XrtTop.construct_hostmem(ports)
318
319 # Copy additional sources
320 sys = System.current()
321 sys.add_packaging_step(XrtTop.package)
322
323 @staticmethod
324 def construct_hostmem(ports):
325 """Construct the host memory interface"""
326 rst = ~ports.ap_resetn
327
328 # Instantiate a hostmem service generator which multiplexes requests to a
329 # single read and single write channel.
330 HostMemDataWidthBytes = XrtTop.HostMemDataWidth // 8
331 hostmem = ChannelHostMem(read_width=XrtTop.HostMemDataWidth,
332 write_width=XrtTop.HostMemDataWidth)(
333 decl=esi.HostMem,
334 appid=esi.AppID("__xrt_hostmem"),
335 clk=ports.ap_clk,
336 rst=rst)
337
338 ##########################################################################
339 # Read path
340 ##########################################################################
341
342 read_resp_data_type = hostmem.read.type.resp.inner
343 read_resp_wire = Wire(Channel(read_resp_data_type))
344 read_req = hostmem.read.unpack(resp=read_resp_wire)['req']
345
346 # Unwrap the read request channel and tie it to the AXI4 master interface.
347 read_req_data, read_req_valid = read_req.unwrap(ports.m_axi_gmem_ARREADY)
348 ports.m_axi_gmem_ARVALID = read_req_valid
349 ports.m_axi_gmem_ARADDR = read_req_data.address
350 ports.m_axi_gmem_ARID = read_req_data.tag
351 # TODO: Test reads > HostMemDataWidthBytes. I'm pretty sure this is wrong
352 # for lengths >512bits but initially we won't be transferring >512 bits.
353 ports.m_axi_gmem_ARSIZE = clog2(HostMemDataWidthBytes)
354 ports.m_axi_gmem_ARLEN = ((read_req_data.length - 1) /
355 HostMemDataWidthBytes).as_uint(8)
356 ports.m_axi_gmem_ARBURST = Bits(2)(1) # INCR
357
358 # Wrap up the read response data into a channel.
359 read_resp_data = read_resp_data_type({
360 "tag": ports.m_axi_gmem_RID,
361 "data": ports.m_axi_gmem_RDATA,
362 })
363 read_resp, read_resp_ready = read_resp_wire.type.wrap(
364 read_resp_data, ports.m_axi_gmem_RVALID)
365 ports.m_axi_gmem_RREADY = read_resp_ready
366 read_resp_wire.assign(read_resp)
367
368 ##########################################################################
369 # Write path
370 ##########################################################################
371
372 write_ack_wire = Wire(Channel(esi.HostMem.TagType))
373 write_req = hostmem.write.unpack(ackTag=write_ack_wire)['req']
374 address_req, data_req = write_req.fork(ports.ap_clk, rst)
375
376 # Write address channels
377 address_req_data, address_req_valid = address_req.unwrap(
378 ports.m_axi_gmem_AWREADY)
379 ports.m_axi_gmem_AWVALID = address_req_valid
380 ports.m_axi_gmem_AWADDR = address_req_data.address.as_bits()
381 ports.m_axi_gmem_AWID = address_req_data.tag.as_bits()
382 ports.m_axi_gmem_AWLEN = Bits(8)(0) # Single xact burst.
383 ports.m_axi_gmem_AWSIZE = clog2(HostMemDataWidthBytes)
384 ports.m_axi_gmem_AWBURST = Bits(2)(1) # INCR
385
386 # Write data channels
387 data_req_data, data_req_valid = data_req.unwrap(ports.m_axi_gmem_WREADY)
388 ports.m_axi_gmem_WVALID = data_req_valid
389 ports.m_axi_gmem_WDATA = data_req_data.data
390
391 wstrb_lookup_table_len = HostMemDataWidthBytes + 1
392 wstrb_lookup_table = Array(Bits(HostMemDataWidthBytes),
393 wstrb_lookup_table_len)([
394 Bits(HostMemDataWidthBytes)(2**vb - 1)
395 for vb in range(wstrb_lookup_table_len)
396 ])
397 # TODO: Single writes only support HostMemDataWidthBytes bytes. Fine for
398 # now since the gearbox takes data down to 512 bits. The `valid_bytes`
399 # field, however, is 8 bits, so it theoretically supports up to 256 bytes.
400 # We should probably break this up into multiple transactions.
401 ports.m_axi_gmem_WSTRB = wstrb_lookup_table[
402 data_req_data.valid_bytes.as_uint(clog2(wstrb_lookup_table_len))]
403 ports.m_axi_gmem_WLAST = Bits(1)(1)
404
405 # Write ack channel
406 write_ack, write_ack_ready = Channel(esi.HostMem.TagType).wrap(
407 ports.m_axi_gmem_BID.as_uint(), ports.m_axi_gmem_BVALID)
408 write_ack_wire.assign(write_ack)
409 ports.m_axi_gmem_BREADY = write_ack_ready
410
411 @staticmethod
412 def package(sys: System):
413 """Assemble a 'build' package which includes all the necessary build
414 collateral (about which we are aware), build/debug scripts, and the
415 generated runtime."""
416
417 sv_sources = glob.glob(str(__dir__ / '*.sv'))
418 tcl_sources = glob.glob(str(__dir__ / '*.tcl'))
419 for source in sv_sources + tcl_sources:
420 shutil.copy(source, sys.hw_output_dir)
421
422 shutil.copy(__dir__ / "Makefile.xrt.mk",
423 sys.output_directory / "Makefile.xrt.mk")
424 shutil.copy(__dir__ / "xrt_package.tcl",
425 sys.output_directory / "xrt_package.tcl")
426 shutil.copy(__dir__ / "xrt.ini", sys.output_directory / "xrt.ini")
427 shutil.copy(__dir__ / "xrt_vitis.cfg",
428 sys.output_directory / "xrt_vitis.cfg")
429 shutil.copy(__dir__ / "xsim.tcl", sys.output_directory / "xsim.tcl")
430
431 return XrtTop
return wrap(CMemoryType::get(unwrap(ctx), baseType, numElements))