CIRCT 24.0.0git
Loading...
Searching...
No Matches
test_bsp_common.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
5import pytest
6
7from pycde import Module
8
9from esiaccel.bsp.common import (ChannelEngineService, ChannelMMIO,
10 HostMemReadReqSplitter, TaggedWriteGearbox)
11from esiaccel.bsp.cosim import CosimBSP, CosimBSP_DMA
12from esiaccel.bsp.dma import OneItemBuffersFromHost, OneItemBuffersToHost
13
14
15class _Bundle:
16
17 def __init__(self, port, options=None):
18 self.port = port
19 self.options = options or {}
20 self.records = []
21
22 def add_record(self, details):
23 self.records.append(details)
24
25
27
28 def __init__(self, *bundles):
29 self.to_client_reqs = bundles
30
31
33 default = _Bundle("read")
34 custom = _Bundle("read_write", {"size": 0x1203})
35
36 table, manifest_loc = ChannelMMIO.build_table(_Bundles(default, custom))
37
38 assert list(table) == [0x100, 0x2000]
39 assert default.records == [{"offset": 0x100, "size": 0x100, "type": "ro"}]
40 assert custom.records == [{"offset": 0x2000, "size": 0x2000, "type": "rw"}]
41 # The manifest lands directly above the last client, rounded to a power of
42 # two so a single prefix test separates it from the client space.
43 assert manifest_loc == 0x4000
44
45
47 """The manifest window must not be a fixed reservation: it follows whatever
48 the clients actually use so the BAR stays as small as possible."""
49
50 small, = _Bundles(_Bundle("read", {"size": 8})).to_client_reqs
51 _, small_loc = ChannelMMIO.build_table(_Bundles(small))
52 assert small_loc == 0x200
53
54 big = _Bundle("read", {"size": 1 << 20})
55 _, big_loc = ChannelMMIO.build_table(_Bundles(big))
56 assert big_loc == 1 << 21
57
58
60 table, manifest_loc = ChannelMMIO.build_table(
61 _Bundles(_Bundle("read"), _Bundle("read", {"size": 0x1203})))
62 last_base = list(table)[-1]
63 last_size = table[last_base][0]
64
65 assert manifest_loc >= last_base + last_size
66 # A power-of-two base means 'address has a bit set above the clients' is the
67 # whole decode: no comparator, and the region can't overflow.
68 assert manifest_loc & (manifest_loc - 1) == 0
69
70
71@pytest.mark.parametrize("size", [True, 1.5, "256", 0, -8])
73 with pytest.raises(ValueError, match="option 'size'"):
74 ChannelMMIO.build_table(_Bundles(_Bundle("read", {"size": size})))
75
76
78 monkeypatch.setattr(ChannelMMIO, "initial_offset", 0xFFFF_FFF8)
79 with pytest.raises(ValueError, match="exceeds the 32-bit space"):
80 ChannelMMIO.build_table(_Bundles(_Bundle("read", {"size": 8})))
81
82
84
85 class Top(Module):
86 pass
87
88 engines = (OneItemBuffersToHost, OneItemBuffersFromHost)
89 channel_service = ChannelEngineService(*engines)
90 with pytest.raises(ValueError, match="dma_engine_pair and channel_service"):
91 CosimBSP(Top, dma_engine_pair=engines, channel_service=channel_service)
92
93
95 """An engine with a `hostmem_read_list` port gets a burst-read connection
96 to HostMem: the design compiles only if the service wires that port."""
97 from pycde import (Clock, Input, InputChannel, OutputChannel, Reset, System,
98 esi, generator)
99 from pycde.types import Bits, Bundle, BundledChannel, Channel, ChannelDirection
100
101 word = Bits(64)
102 read_list_type = Bundle([
103 BundledChannel("req", ChannelDirection.FROM,
104 esi.HostMem.read_req_burst_type(16)),
105 BundledChannel("resp", ChannelDirection.TO,
106 esi.HostMem.read_window(word, 1)),
107 ])
108
109 def to_host(client_type):
110
111 class ToHost(esi.EngineModule):
112
113 @property
114 def TypeName(self):
115 return "ReadListTestToHost"
116
117 clk = Clock()
118 rst = Reset()
119 input_channel = InputChannel(client_type)
120
121 @generator
122 def build(ports):
123 pass
124
125 return ToHost
126
127 def from_host(client_type):
128
129 class FromHost(esi.EngineModule):
130
131 @property
132 def TypeName(self):
133 return "ReadListTestFromHost"
134
135 clk = Clock()
136 rst = Reset()
137 hostmem_read_list = Input(read_list_type)
138 output_channel = OutputChannel(client_type)
139
140 @generator
141 def build(ports):
142 # Read one word at address 0 forever and forward each word.
143 req_type = esi.HostMem.read_req_burst_type(16)
144 req, _ = Channel(req_type).wrap(
145 req_type({
146 "address": 0,
147 "tag": 0,
148 "length": 1
149 }),
150 Bits(1)(1))
151 resp = ports.hostmem_read_list.unpack(req=req)["resp"]
152 ports.output_channel = resp.transform(
153 lambda w: w.unwrap()["data"][0].bitcast(client_type))
154
155 return FromHost
156
157 class Top(Module):
158 clk = Clock()
159 rst = Reset()
160
161 @generator
162 def build(ports):
163 words = esi.ChannelService.from_host(esi.AppID("words"), word)
164 esi.ChannelService.to_host(esi.AppID("echo"), words)
165
166 bsp = CosimBSP(Top, channel_service=ChannelEngineService(to_host, from_host))
167 System(bsp, output_directory=str(tmp_path)).compile()
168
169
170@pytest.mark.parametrize("max_outstanding", [1, 2, 64])
171@pytest.mark.parametrize("word_bits", [8, 64, 256, 512, 2048, 4096])
172@pytest.mark.parametrize("round_read_lengths", [False, True])
173def test_read_req_splitter_generates(tmp_path, word_bits, max_outstanding,
174 round_read_lengths):
175 """Build and compile `HostMemReadReqSplitter` for a range of word widths
176 and outstanding-request limits (no simulation)."""
177 from pycde import Clock, Input, Module, Output, Reset, System, generator
178 from pycde.types import Bits, Channel, StructType, UInt
179
180 req_t = Channel(
181 StructType([("address", UInt(64)), ("length", UInt(32)),
182 ("tag", UInt(8))]))
183 resp_t = Channel(
184 StructType([("tag", UInt(8)), ("data", Bits(word_bits)),
185 ("last", Bits(1))]))
186 splitter = HostMemReadReqSplitter(req_t, resp_t, max(256, word_bits // 8),
187 max_outstanding, round_read_lengths)
188
189 class Top(Module):
190 clk = Clock()
191 rst = Reset()
192 req_in = Input(req_t)
193 resp_in = Input(resp_t)
194 req_out = Output(req_t)
195 resp_out = Output(splitter.resp_out.type)
196
197 @generator
198 def build(ports):
199 s = splitter(clk=ports.clk,
200 rst=ports.rst,
201 req_in=ports.req_in,
202 resp_in=ports.resp_in)
203 ports.req_out = s.req_out
204 ports.resp_out = s.resp_out
205
206 System(Top, output_directory=str(tmp_path)).compile()
207
208
210 from pycde.types import Bits, Channel, StructType, UInt
211 req_t = Channel(
212 StructType([("address", UInt(64)), ("length", UInt(32)),
213 ("tag", UInt(8))]))
214 resp_t = Channel(
215 StructType([("tag", UInt(8)), ("data", Bits(64)), ("last", Bits(1))]))
216 with pytest.raises(AssertionError):
217 HostMemReadReqSplitter(req_t, resp_t, 256, 0)
218
219
220@pytest.mark.parametrize("builder", [CosimBSP, CosimBSP_DMA])
221@pytest.mark.parametrize("width", [True, 64.0, "64", 0, -8, 1, 4, 7, 24, 72])
223
224 class Top(Module):
225 pass
226
227 with pytest.raises(ValueError, match="power of two and at least 8"):
228 builder(Top, hostmem_width=width)
229
230
231@pytest.mark.parametrize("builder", [CosimBSP, CosimBSP_DMA])
232@pytest.mark.parametrize("width", [None, 8, 64, 256, 512, 2048, 4096])
233def test_cosim_forwards_hostmem_width(monkeypatch, builder, width):
234 import esiaccel.bsp.cosim as cosim
235
236 class Top(Module):
237 pass
238
239 channel_hostmem = cosim.ChannelHostMem
240 configurations = []
241
242 def capture(**kwargs):
243 configurations.append(kwargs)
244 return channel_hostmem(**kwargs)
245
246 monkeypatch.setattr(cosim, "ChannelHostMem", capture)
247 builder(Top, **({"hostmem_width": width} if width is not None else {}))
248 expected = width if width is not None else 64
249 assert configurations == [{
250 "read_width": expected,
251 "write_width": expected,
252 "max_read_request_bytes": max(256, expected // 8),
253 "max_write_payload_bytes": max(256, expected // 8),
254 "max_outstanding_reads": 4,
255 "round_read_lengths": False,
256 }]
257
258
259@pytest.mark.parametrize("width", [8, 64, 256, 512, 2048, 4096])
260def test_write_gearbox_generates(tmp_path, width):
261 from pycde import Clock, Input, Output, Reset, System, generator
262
263 gearbox = TaggedWriteGearbox(width * 3 + 13, width, max(256, width // 8))
264
265 class Top(Module):
266 clk = Clock()
267 rst = Reset()
268 req = Input(gearbox.in_.type)
269 resp = Output(gearbox.out.type)
270
271 @generator
272 def build(ports):
273 ports.resp = gearbox(clk=ports.clk, rst=ports.rst, in_=ports.req).out
274
275 System(Top, output_directory=str(tmp_path)).compile()
return wrap(CMemoryType::get(unwrap(ctx), baseType, numElements))
@ Input
Definition HW.h:42
@ Output
Definition HW.h:42
static StringAttr append(StringAttr base, const Twine &suffix)
Return a attribute with the specified suffix appended.
__init__(self, port, options=None)
test_channel_mmio_manifest_tracks_client_space()
test_channel_mmio_places_manifest_after_clients()
test_channel_mmio_rejects_address_overflow(monkeypatch)
test_channel_engine_service_connects_read_list(tmp_path)
test_write_gearbox_generates(tmp_path, width)
test_cosim_rejects_invalid_hostmem_width(builder, width)
test_cosim_forwards_hostmem_width(monkeypatch, builder, width)
test_read_req_splitter_rejects_zero_outstanding()
test_read_req_splitter_generates(tmp_path, word_bits, max_outstanding, round_read_lengths)
test_cosim_bsp_rejects_conflicting_channel_services()
test_channel_mmio_rejects_invalid_size(size)