blob: 80dd0506bb13a15672c73e0db8af026616fb840e [file] [log] [blame]
MichaƂ Mazurek545460f2023-02-17 17:19:47 +01001# Copyright lowRISC contributors.
2# Licensed under the Apache License, Version 2.0, see LICENSE for details.
3# SPDX-License-Identifier: Apache-2.0
4"""This contains a class which is used to help generate `top_{name}.rs`.
5"""
6from collections import OrderedDict, defaultdict
7from typing import Dict, List, Optional, Tuple
8
9from mako.template import Template
10from reggen.ip_block import IpBlock
11
12from .lib import Name, get_base_and_size
13
14RUST_FILE_EXTENSIONS = (".rs")
15
16
17class MemoryRegion(object):
18 def __init__(self, name: Name, base_addr: int, size_bytes: int):
19 assert isinstance(base_addr, int)
20 self.name = name
21 self.base_addr = base_addr
22 self.size_bytes = size_bytes
23 self.size_words = (size_bytes + 3) // 4
24
25 def base_addr_name(self):
26 return self.name + Name(["base", "addr"])
27
28 def offset_name(self):
29 return self.name + Name(["offset"])
30
31 def size_bytes_name(self):
32 return self.name + Name(["size", "bytes"])
33
34 def size_words_name(self):
35 return self.name + Name(["size", "words"])
36
37
38class RustEnum(object):
39 def __init__(self, name, repr_type=None):
40 self.name = name
41 self.enum_counter = 0
42 self.finalized = False
43 self.repr_type = repr_type
44 self.constants = []
45
46 def repr(self) -> str:
47 if isinstance(self.repr_type, int):
48 return "u" + str(self.repr_type)
49 else:
50 return self.repr_type
51
52 def add_constant(self, constant_name, docstring=""):
53 assert not self.finalized
54 full_name = constant_name
55 value = self.enum_counter
56 self.enum_counter += 1
57 self.constants.append((full_name, value, docstring))
58 return full_name
59
60 def add_last_constant(self, docstring=""):
61 assert not self.finalized
62
63 full_name = Name(["End"])
64 _, last_val, _ = self.constants[-1]
65
66 self.constants.append((full_name, last_val + 1, r"\internal " + docstring))
67 self.finalized = True
68
69 def render(self):
70 template = ("% if enum.repr_type: \n"
71 "#[repr(${enum.repr()})]\n"
72 "% endif \n"
73 "pub enum ${enum.name.as_rust_type()} {\n"
74 "% for name, value, docstring in enum.constants:\n"
75 " % if len(docstring) > 0 : \n"
76 " /// ${docstring}\n"
77 " % endif \n"
78 " ${name.as_rust_enum()} = ${value},\n"
79 "% endfor\n"
80 "}")
81 return Template(template).render(enum=self)
82
83
84class RustArrayMapping(object):
85 def __init__(self, name, output_type_name):
86 self.name = name
87 self.output_type_name = output_type_name
88
89 self.mapping = OrderedDict()
90
91 def add_entry(self, in_name, out_name):
92 self.mapping[in_name] = out_name
93
94 def render_definition(self):
95 template = (
96 "pub const ${mapping.name.as_rust_const()}: "
97 "[${mapping.output_type_name.as_rust_type()}; ${len(mapping.mapping)}] = [\n"
98 "% for in_name, out_name in mapping.mapping.items():\n"
99 " // ${in_name.as_rust_enum()} ->"
100 " ${mapping.output_type_name.as_rust_type()}::${out_name.as_rust_enum()}\n"
101 " ${mapping.output_type_name.as_rust_type()}::${out_name.as_rust_enum()},\n"
102 "% endfor\n"
103 "];")
104 return Template(template).render(mapping=self)
105
106
107class TopGenRust:
108 def __init__(self, top_info, name_to_block: Dict[str, IpBlock]):
109 self.top = top_info
110 self._top_name = Name(["top"]) + Name.from_snake_case(top_info["name"])
111 self._name_to_block = name_to_block
112 self.regwidth = int(top_info["datawidth"])
113
114 # The .c file needs the .h file's relative path, store it here
115 self.module_path = None
116
117 self._init_plic_targets()
118 self._init_plic_mapping()
119 self._init_alert_mapping()
120 self._init_pinmux_mapping()
121 self._init_pad_mapping()
122 self._init_pwrmgr_wakeups()
123 self._init_rstmgr_sw_rsts()
124 self._init_pwrmgr_reset_requests()
125 self._init_clkmgr_clocks()
126 self._init_mmio_region()
127
128 def devices(self) -> List[Tuple[Tuple[str, Optional[str]], MemoryRegion]]:
129 '''Return a list of MemoryRegion objects for devices on the bus
130
131 The list returned is pairs (full_if, region) where full_if is itself a
132 pair (inst_name, if_name). inst_name is the name of some IP block
133 instantiation. if_name is the name of the interface (may be None).
134 region is a MemoryRegion object representing the device.
135
136 '''
137 ret = [] # type: List[Tuple[Tuple[str, Optional[str]], MemoryRegion]]
138 # TODO: This method is invoked in templates, as well as in the extended
139 # class TopGenCTest. We could refactor and optimize the implementation
140 # a bit.
141 self.device_regions = defaultdict(dict)
142 for inst in self.top['module']:
143 block = self._name_to_block[inst['type']]
144 for if_name, rb in block.reg_blocks.items():
145 full_if = (inst['name'], if_name)
146 full_if_name = Name.from_snake_case(full_if[0])
147 if if_name is not None:
148 full_if_name += Name.from_snake_case(if_name)
149
150 name = self._top_name + full_if_name
151 base, size = get_base_and_size(self._name_to_block,
152 inst, if_name)
153
154 region = MemoryRegion(name, base, size)
155 self.device_regions[inst['name']].update({if_name: region})
156 ret.append((full_if, region))
157
158 return ret
159
160 def memories(self):
161 ret = []
162 for m in self.top["memory"]:
163 ret.append((m["name"],
164 MemoryRegion(self._top_name +
165 Name.from_snake_case(m["name"]),
166 int(m["base_addr"], 0),
167 int(m["size"], 0))))
168
169 for inst in self.top['module']:
170 if "memory" in inst:
171 for if_name, val in inst["memory"].items():
172 base, size = get_base_and_size(self._name_to_block,
173 inst, if_name)
174
175 # name = self._top_name + Name.from_snake_case(val["label"])
176 name = Name.from_snake_case(val["label"])
177 region = MemoryRegion(name, base, size)
178 ret.append((val["label"], region))
179
180 return ret
181
182 def _init_plic_targets(self):
183 enum = RustEnum(self._top_name + Name(["plic", "target"]))
184
185 for core_id in range(int(self.top["num_cores"])):
186 enum.add_constant(Name(["ibex", str(core_id)]),
187 docstring="Ibex Core {}".format(core_id))
188
189 enum.add_last_constant("Final number of PLIC target")
190
191 self.plic_targets = enum
192
193 def _init_plic_mapping(self):
194 """We eventually want to generate a mapping from interrupt id to the
195 source peripheral.
196
197 In order to do so, we generate two enums (one for interrupts, one for
198 sources), and store the generated names in a dictionary that represents
199 the mapping.
200
201 PLIC Interrupt ID 0 corresponds to no interrupt, and so no peripheral,
202 so we encode that in the enum as "unknown".
203
204 The interrupts have to be added in order, with "none" first, to ensure
205 that they get the correct mapping to their PLIC id, which is used for
206 addressing the right registers and bits.
207 """
208 sources = RustEnum(self._top_name + Name(["plic", "peripheral"]), self.regwidth)
209 interrupts = RustEnum(self._top_name + Name(["plic", "irq", "id"]), self.regwidth)
210 plic_mapping = RustArrayMapping(
211 self._top_name + Name(["plic", "interrupt", "for", "peripheral"]),
212 sources.name)
213
214 unknown_source = sources.add_constant(Name(["unknown"]),
215 docstring="Unknown Peripheral")
216 none_irq_id = interrupts.add_constant(Name(["none"]),
217 docstring="No Interrupt")
218 plic_mapping.add_entry(none_irq_id, unknown_source)
219
220 # When we generate the `interrupts` enum, the only info we have about
221 # the source is the module name. We'll use `source_name_map` to map a
222 # short module name to the full name object used for the enum constant.
223 source_name_map = {}
224
225 for name in self.top["interrupt_module"]:
226 source_name = sources.add_constant(Name.from_snake_case(name),
227 docstring=name)
228 source_name_map[name] = source_name
229
230 sources.add_last_constant("Number of PLIC peripheral")
231
232 # Maintain a list of instance-specific IRQs by instance name.
233 self.device_irqs = defaultdict(list)
234 for intr in self.top["interrupt"]:
235 # Some interrupts are multiple bits wide. Here we deal with that by
236 # adding a bit-index suffix
237 if "width" in intr and int(intr["width"]) != 1:
238 for i in range(int(intr["width"])):
239 name = Name.from_snake_case(intr["name"]) + Name([str(i)])
240 irq_id = interrupts.add_constant(name,
241 docstring="{} {}".format(
242 intr["name"], i))
243 source_name = source_name_map[intr["module_name"]]
244 plic_mapping.add_entry(irq_id, source_name)
245 self.device_irqs[intr["module_name"]].append(intr["name"] +
246 str(i))
247 else:
248 name = Name.from_snake_case(intr["name"])
249 irq_id = interrupts.add_constant(name, docstring=intr["name"])
250 source_name = source_name_map[intr["module_name"]]
251 plic_mapping.add_entry(irq_id, source_name)
252 self.device_irqs[intr["module_name"]].append(intr["name"])
253
254 interrupts.add_last_constant("Number of Interrupt ID.")
255
256 self.plic_sources = sources
257 self.plic_interrupts = interrupts
258 self.plic_mapping = plic_mapping
259
260 def _init_alert_mapping(self):
261 """We eventually want to generate a mapping from alert id to the source
262 peripheral.
263
264 In order to do so, we generate two enums (one for alerts, one for
265 sources), and store the generated names in a dictionary that represents
266 the mapping.
267
268 Alert Handler has no concept of "no alert", unlike the PLIC.
269
270 The alerts have to be added in order, to ensure that they get the
271 correct mapping to their alert id, which is used for addressing the
272 right registers and bits.
273 """
274 sources = RustEnum(self._top_name + Name(["alert", "peripheral"]), self.regwidth)
275 alerts = RustEnum(self._top_name + Name(["alert", "id"]), self.regwidth)
276 alert_mapping = RustArrayMapping(
277 self._top_name + Name(["alert", "for", "peripheral"]),
278 sources.name)
279
280 # When we generate the `alerts` enum, the only info we have about the
281 # source is the module name. We'll use `source_name_map` to map a short
282 # module name to the full name object used for the enum constant.
283 source_name_map = {}
284
285 for name in self.top["alert_module"]:
286 source_name = sources.add_constant(Name.from_snake_case(name),
287 docstring=name)
288 source_name_map[name] = source_name
289
290 sources.add_last_constant("Final number of Alert peripheral")
291
292 self.device_alerts = defaultdict(list)
293 for alert in self.top["alert"]:
294 if "width" in alert and int(alert["width"]) != 1:
295 for i in range(int(alert["width"])):
296 name = Name.from_snake_case(alert["name"]) + Name([str(i)])
297 irq_id = alerts.add_constant(name,
298 docstring="{} {}".format(
299 alert["name"], i))
300 source_name = source_name_map[alert["module_name"]]
301 alert_mapping.add_entry(irq_id, source_name)
302 self.device_alerts[alert["module_name"]].append(alert["name"] +
303 str(i))
304 else:
305 name = Name.from_snake_case(alert["name"])
306 alert_id = alerts.add_constant(name, docstring=alert["name"])
307 source_name = source_name_map[alert["module_name"]]
308 alert_mapping.add_entry(alert_id, source_name)
309 self.device_alerts[alert["module_name"]].append(alert["name"])
310
311 alerts.add_last_constant("The number of Alert ID.")
312
313 self.alert_sources = sources
314 self.alert_alerts = alerts
315 self.alert_mapping = alert_mapping
316
317 def _init_pinmux_mapping(self):
318 """Generate Rust enums for addressing pinmux registers and in/out selects.
319
320 Inputs/outputs are connected in the order the modules are listed in
321 the hjson under the "mio_modules" key. For each module, the corresponding
322 inouts are connected first, followed by either the inputs or the outputs.
323
324 Inputs:
325 - Peripheral chooses register field (pinmux_peripheral_in)
326 - Insel chooses MIO input (pinmux_insel)
327
328 Outputs:
329 - MIO chooses register field (pinmux_mio_out)
330 - Outsel chooses peripheral output (pinmux_outsel)
331
332 Insel and outsel have some special values which are captured here too.
333 """
334 pinmux_info = self.top['pinmux']
335 pinout_info = self.top['pinout']
336
337 # Peripheral Inputs
338 peripheral_in = RustEnum(self._top_name + Name(['pinmux', 'peripheral', 'in']),
339 self.regwidth)
340 i = 0
341 for sig in pinmux_info['ios']:
342 if sig['connection'] == 'muxed' and sig['type'] in ['inout', 'input']:
343 index = Name([str(sig['idx'])]) if sig['idx'] != -1 else Name([])
344 name = Name.from_snake_case(sig['name']) + index
345 peripheral_in.add_constant(name, docstring='Peripheral Input {}'.format(i))
346 i += 1
347
348 peripheral_in.add_last_constant('Number of peripheral input')
349
350 # Pinmux Input Selects
351 insel = RustEnum(self._top_name + Name(['pinmux', 'insel']), self.regwidth)
352 insel.add_constant(Name(['constant', 'zero']),
353 docstring='Tie constantly to zero')
354 insel.add_constant(Name(['constant', 'one']),
355 docstring='Tie constantly to one')
356 i = 0
357 for pad in pinout_info['pads']:
358 if pad['connection'] == 'muxed':
359 insel.add_constant(Name([pad['name']]),
360 docstring='MIO Pad {}'.format(i))
361 i += 1
362 insel.add_last_constant('Number of valid insel value')
363
364 # MIO Outputs
365 mio_out = RustEnum(self._top_name + Name(['pinmux', 'mio', 'out']))
366 i = 0
367 for pad in pinout_info['pads']:
368 if pad['connection'] == 'muxed':
369 mio_out.add_constant(Name.from_snake_case(pad['name']),
370 docstring='MIO Pad {}'.format(i))
371 i += 1
372 mio_out.add_last_constant('Number of valid mio output')
373
374 # Pinmux Output Selects
375 outsel = RustEnum(self._top_name + Name(['pinmux', 'outsel']), self.regwidth)
376 outsel.add_constant(Name(['constant', 'zero']),
377 docstring='Tie constantly to zero')
378 outsel.add_constant(Name(['constant', 'one']),
379 docstring='Tie constantly to one')
380 outsel.add_constant(Name(['constant', 'high', 'z']),
381 docstring='Tie constantly to high-Z')
382 i = 0
383 for sig in pinmux_info['ios']:
384 if sig['connection'] == 'muxed' and sig['type'] in ['inout', 'output']:
385 index = Name([str(sig['idx'])]) if sig['idx'] != -1 else Name([])
386 name = Name.from_snake_case(sig['name']) + index
387 outsel.add_constant(name, docstring='Peripheral Output {}'.format(i))
388 i += 1
389
390 outsel.add_last_constant('Number of valid outsel value')
391
392 self.pinmux_peripheral_in = peripheral_in
393 self.pinmux_insel = insel
394 self.pinmux_mio_out = mio_out
395 self.pinmux_outsel = outsel
396
397 def _init_pad_mapping(self):
398 """Generate Rust enums for order of MIO and DIO pads.
399
400 These are needed to configure pad specific configurations such as
401 slew rate and other flags.
402 """
403 direct_enum = RustEnum(self._top_name + Name(["direct", "pads"]))
404
405 muxed_enum = RustEnum(self._top_name + Name(["muxed", "pads"]))
406
407 pads_info = self.top['pinout']['pads']
408 muxed = [pad['name'] for pad in pads_info if pad['connection'] == 'muxed']
409
410 # The logic here follows the sequence done in toplevel_pkg.sv.tpl.
411 # The direct pads do not enumerate directly from the pinout like the muxed
412 # ios. Instead it follows a direction from the pinmux perspective.
413 pads_info = self.top['pinmux']['ios']
414 direct = [pad for pad in pads_info if pad['connection'] != 'muxed']
415
416 for pad in (direct):
417 name = f"{pad['name']}"
418 if pad['width'] > 1:
419 name = f"{name}{pad['idx']}"
420
421 direct_enum.add_constant(
422 Name.from_snake_case(name))
423 direct_enum.add_last_constant("Number of valid direct pad")
424
425 for pad in (muxed):
426 muxed_enum.add_constant(
427 Name.from_snake_case(pad))
428 muxed_enum.add_last_constant("Number of valid muxed pad")
429
430 self.direct_pads = direct_enum
431 self.muxed_pads = muxed_enum
432
433 def _init_pwrmgr_wakeups(self):
434 enum = RustEnum(self._top_name + Name(["power", "manager", "wake", "ups"]))
435
436 for signal in self.top["wakeups"]:
437 enum.add_constant(
438 Name.from_snake_case(signal["module"]) +
439 Name.from_snake_case(signal["name"]))
440
441 enum.add_last_constant("Number of valid pwrmgr wakeup signal")
442
443 self.pwrmgr_wakeups = enum
444
445 # Enumerates the positions of all software controllable resets
446 def _init_rstmgr_sw_rsts(self):
447 sw_rsts = self.top['resets'].get_sw_resets()
448
449 enum = RustEnum(self._top_name + Name(["reset", "manager", "sw", "resets"]))
450
451 for rst in sw_rsts:
452 enum.add_constant(Name.from_snake_case(rst))
453
454 enum.add_last_constant("Number of valid rstmgr software reset request")
455
456 self.rstmgr_sw_rsts = enum
457
458 def _init_pwrmgr_reset_requests(self):
459 enum = RustEnum(self._top_name + Name(["power", "manager", "reset", "requests"]))
460
461 for signal in self.top["reset_requests"]["peripheral"]:
462 enum.add_constant(
463 Name.from_snake_case(signal["module"]) +
464 Name.from_snake_case(signal["name"]))
465
466 enum.add_last_constant("Number of valid pwrmgr reset_request signal")
467
468 self.pwrmgr_reset_requests = enum
469
470 def _init_clkmgr_clocks(self):
471 """
472 Creates RustEnums for accessing the software-controlled clocks in the
473 design.
474
475 The logic here matches the logic in topgen.py in how it instantiates the
476 clock manager with the described clocks.
477
478 We differentiate "gateable" clocks and "hintable" clocks because the
479 clock manager has separate register interfaces for each group.
480 """
481 clocks = self.top['clocks']
482
483 gateable_clocks = RustEnum(self._top_name + Name(["gateable", "clocks"]))
484 hintable_clocks = RustEnum(self._top_name + Name(["hintable", "clocks"]))
485
486 c2g = clocks.make_clock_to_group()
487 by_type = clocks.typed_clocks()
488
489 for name in by_type.sw_clks.keys():
490 # All these clocks start with `clk_` which is redundant.
491 clock_name = Name.from_snake_case(name).remove_part("clk")
492 docstring = "Clock {} in group {}".format(name, c2g[name].name)
493 gateable_clocks.add_constant(clock_name, docstring)
494 gateable_clocks.add_last_constant("Number of Valid Gateable Clock")
495
496 for name in by_type.hint_clks.keys():
497 # All these clocks start with `clk_` which is redundant.
498 clock_name = Name.from_snake_case(name).remove_part("clk")
499 docstring = "Clock {} in group {}".format(name, c2g[name].name)
500 hintable_clocks.add_constant(clock_name, docstring)
501 hintable_clocks.add_last_constant("Number of Valid Hintable Clock")
502
503 self.clkmgr_gateable_clocks = gateable_clocks
504 self.clkmgr_hintable_clocks = hintable_clocks
505
506 def _init_mmio_region(self):
507 """
508 Computes the bounds of the MMIO region.
509
510 MMIO region excludes any memory that is separate from the module configuration
511 space, i.e. ROM, main SRAM, and flash are excluded but retention SRAM,
512 spi_device memory, or usbdev memory are included.
513 """
514 memories = [region.base_addr for (_, region) in self.memories()]
515 # TODO(#14345): Remove the hardcoded "rv_dm" name check below.
516 regions = [
517 region for ((dev_name, _), region) in self.devices()
518 if region.base_addr not in memories and dev_name != "rv_dm"
519 ]
520 # Note: The memory interface of the retention RAM is in the MMIO address space,
521 # which we prefer since it reduces the number of ePMP regions we need.
522 mmio = range(min([r.base_addr for r in regions]),
523 max([r.base_addr + r.size_bytes for r in regions]))
524 self.mmio = MemoryRegion(self._top_name + Name(["mmio"]), mmio.start,
525 mmio.stop - mmio.start)