33# Copyright (c) 2025 Rivos, Inc.
44# SPDX-License-Identifier: Apache2
55
6- """QEMU OT tool to generate machine definition for OT device .
6+ """Generate machine definitions for OpenTitan top .
77
88 :author: Emmanuel Blot <[email protected] > 99"""
1313from os import listdir
1414from os .path import (abspath , basename , commonprefix , dirname , isdir , isfile ,
1515 join as joinpath )
16- from textwrap import dedent , indent
1716from traceback import format_exception
1817from typing import Any , NamedTuple , TextIO
1918import re
2625# ruff: noqa: E402
2726from ot .util .arg import ArgError
2827from ot .util .log import configure_loggers
29- from ot .util .misc import HexInt , camel_to_snake_uppercase , classproperty
28+ from ot .util .misc import HexInt , camel_to_snake_uppercase , classproperty , redent
3029
3130try :
3231 _HJSON_ERROR = None
@@ -50,7 +49,7 @@ class QEMUSignal(NamedTuple):
5049 index : int
5150
5251
53- class QEMUAutoTop :
52+ class AutoTop :
5453 """Helper class to generate QEMU machine definition from Top definitions.
5554 """
5655
@@ -97,20 +96,6 @@ def load(self, ot_dir: str) -> None:
9796 self ._load_all_alerts (hjson )
9897 self ._load_pinmux (hjson )
9998
100- @classmethod
101- def redent (cls , text : str , spc : int = 0 , strip_end : bool = False ) -> str :
102- """Utility function to re-indent code string.
103-
104- :param text: the text to re-indent
105- :param spc: the number of leading empty space chars to prefix lines
106- :param strip_end: whether to strip trailing whitespace and newline
107- """
108- text = dedent (text .lstrip ('\n ' ))
109- text = indent (text , ' ' * spc )
110- if strip_end :
111- text = text .rstrip (' ' ).rstrip ('\n ' )
112- return text
113-
11499 @classmethod
115100 def device_name (cls , name : str , discard_aon : bool = False ) -> str :
116101 """Generate the devices name.
@@ -123,40 +108,40 @@ def device_name(cls, name: str, discard_aon: bool = False) -> str:
123108 pname = pname .replace ('_AON' , '' )
124109 return cls .DEVICE_MAP .get (pname , pname )
125110
126- # pylint: disable=no-self-argument
127111 @classproperty
128- def languages (cls ) -> list [str ]:
129- """Report which language generations are supported.
112+ def outkinds (cls ) -> list [str ]:
113+ """Report which generation output formats are supported.
130114 """
115+ # pylint: disable=no-self-argument
131116 prefix = 'generate_'
132- return list ( sorted ( f [ len (prefix ):]
133- for f in dir ( cls ) if f .startswith (prefix )))
117+ return [ f . removeprefix (prefix ) for f in dir ( cls )
118+ if f .startswith (prefix )]
134119
135- def generate_c (self , prefix : str , tfp : TextIO ) -> None :
120+ def generate_qemu (self , prefix : str , tfp : TextIO ) -> None :
136121 """Generate a QEMU template file or machine definition.
137122
138123 :param prefix: prefix for C definition
139124 :param tfp: output file stream
140125 """
141126 lprefix = prefix .lower ()
142- self ._generate_c_dev_enum (prefix , tfp )
143- self ._generate_c_pinmux (prefix , tfp )
127+ self ._generate_qemu_dev_enum (prefix , tfp )
128+ self ._generate_qemu_pinmux (prefix , tfp )
144129 print (f'static const IbexDeviceDef { lprefix } _devices[] = {{' , file = tfp )
145130 print ('/* clang-format off */' , file = tfp )
146131 for devname in sorted (self ._devices , key = self ._device_address ):
147- self ._generate_c_devices (prefix , devname , tfp )
132+ self ._generate_qemu_devices (prefix , devname , tfp )
148133 print ('/* clang-format on */' , file = tfp )
149134 print ('};' , file = tfp )
150135
151- def generate_rust (self , _ : str , tfp : TextIO ) -> None :
136+ def generate_bmtest (self , _ : str , tfp : TextIO ) -> None :
152137 """Generate a Rust template file for machine definition.
153138
154139 :param tfp: output file stream
155140 """
156- self ._generate_rust_base_addresses (tfp )
157- self ._generate_rust_interrupts (tfp )
158- self ._generate_rust_alerts (tfp )
159- self ._generate_rust_pinmux (tfp )
141+ self ._generate_bmtest_base_addresses (tfp )
142+ self ._generate_bmtest_interrupts (tfp )
143+ self ._generate_bmtest_alerts (tfp )
144+ self ._generate_bmtest_pinmux (tfp )
160145
161146 def _load_devices (self , topdir : str ) -> None :
162147 iptopdir = f'{ topdir } /ip'
@@ -318,18 +303,18 @@ def _get_mbox_index(self, udev: str) -> int:
318303 self ._mbox_indices [udev ] = len (self ._mbox_indices )
319304 return self ._mbox_indices [udev ]
320305
321- def _generate_c_dev_enum (self , prefix : str , tfp : TextIO ) -> None :
306+ def _generate_qemu_dev_enum (self , prefix : str , tfp : TextIO ) -> None :
322307 lines = []
323308 uprefix = prefix .upper ()
324309 tprefix = prefix .title ().replace ('_' , '' )
325310 print (f'enum { tprefix } Device {{' , file = tfp )
326311 for dev in sorted (self ._devices ):
327312 lines .append (f'{ uprefix } _DEV_{ dev } ' )
328- code = self . redent (',\n ' .join (lines ), 4 )
313+ code = redent (',\n ' .join (lines ), 4 )
329314 print (code , file = tfp )
330315 print ('}\n ' , file = tfp )
331316
332- def _generate_c_pinmux (self , prefix : str , tfp : TextIO ) -> None :
317+ def _generate_qemu_pinmux (self , prefix : str , tfp : TextIO ) -> None :
333318 tprefix = prefix .title ().replace ('_' , '' )
334319 for ioname , pinmux in self ._pinmux .items ():
335320 lines = []
@@ -342,16 +327,17 @@ def _generate_c_pinmux(self, prefix: str, tfp: TextIO) -> None:
342327 lines .append (f'{ u_ioname } _{ us_ion } , /* { val } */' )
343328 max_val = max (val , max_val )
344329 lines .append (f'{ u_ioname } _COUNT, /* { max_val + 1 } */' )
345- code = self . redent ('\n ' .join (lines ), 4 )
330+ code = redent ('\n ' .join (lines ), 4 )
346331 print (code , file = tfp )
347332 print ('}\n ' , file = tfp )
348333
349- def _generate_c_devices (self , prefix : str , dev : str , tfp : TextIO ) -> None :
334+ def _generate_qemu_devices (self , prefix : str , dev : str , tfp : TextIO ) \
335+ -> None :
350336 lines : list [str ] = []
351337 uprefix = prefix .upper ()
352- irq_defs = self ._generate_c_irq_defs (uprefix , dev )
353- alert_defs = self ._generate_c_alert_defs (uprefix , dev )
354- mmap_defs = self ._generate_c_mmap_defs (dev )
338+ irq_defs = self ._generate_qemu_irq_defs (uprefix , dev )
339+ alert_defs = self ._generate_qemu_alert_defs (uprefix , dev )
340+ mmap_defs = self ._generate_qemu_mmap_defs (dev )
355341 if dev not in self ._devices :
356342 self ._log .warning ('%s not in devices' , dev )
357343 lines .append (f'[{ uprefix } _DEV_{ dev } ] = {{' )
@@ -382,20 +368,20 @@ def _generate_c_devices(self, prefix: str, dev: str, tfp: TextIO) -> None:
382368 lines .append (f' .type = TYPE_OT_{ devbase } ,' )
383369 if mmap_defs :
384370 lines .append (' .memmap = MEMMAPENTRIES(' )
385- lines .append (self . redent (',\n ' .join (mmap_defs ), 8 ))
371+ lines .append (redent (',\n ' .join (mmap_defs ), 8 ))
386372 lines .append (' ),' )
387373 if irq_defs or alert_defs :
388374 defs = []
389375 defs .extend (irq_defs )
390376 defs .extend (alert_defs )
391377 lines .append (' .gpio = IBEXGPIOCONNDEFS(' )
392- lines .append (self . redent (',\n ' .join (defs ), 8 ))
378+ lines .append (redent (',\n ' .join (defs ), 8 ))
393379 lines .append (' ),' )
394380 lines .append ('},' )
395- code = self . redent ('\n ' .join (lines ), 4 )
381+ code = redent ('\n ' .join (lines ), 4 )
396382 print (code , file = tfp )
397383
398- def _generate_c_mmap_defs (self , device : str ) -> list [str ]:
384+ def _generate_qemu_mmap_defs (self , device : str ) -> list [str ]:
399385 # sorting memory range the weird way (hack ahead)
400386 # we want the IBEX bus to be seen first (vs. debug or external buses)
401387 # and appear in the logical address order
@@ -412,20 +398,20 @@ def _sort_mems(dev):
412398 mmaps .append (f'{{ .base = 0x{ dev .base :{width }x} u }}' )
413399 return mmaps
414400
415- def _generate_c_alert_defs (self , prefix : str , dev : str ) -> list [str ]:
401+ def _generate_qemu_alert_defs (self , prefix : str , dev : str ) -> list [str ]:
416402 alerts = []
417403 for pos , alert in enumerate (self ._alerts .get (dev , [])):
418404 alerts .append (f'{ prefix } _GPIO_ALERT({ pos } , { alert .index } )' )
419405 return alerts
420406
421- def _generate_c_irq_defs (self , prefix : str , dev : str ) -> list [str ]:
407+ def _generate_qemu_irq_defs (self , prefix : str , dev : str ) -> list [str ]:
422408 irqs = []
423409 for pos , irq in enumerate (self ._interrupts .get (dev , [])):
424410 irqs .append (f'{ prefix } _GPIO_SYSBUS_IRQ({ pos } , PLIC, '
425411 f'{ irq .index } )' )
426412 return irqs
427413
428- def _generate_rust_base_addresses (self , tfp ):
414+ def _generate_bmtest_base_addresses (self , tfp ):
429415 utop = self ._topname .upper ()
430416 print ('pub mod base_addresses {' , file = tfp )
431417 for dev in sorted (self ._devices , key = self ._device_address ):
@@ -456,7 +442,7 @@ def _generate_rust_base_addresses(self, tfp):
456442 file = tfp )
457443 print ('}\n ' , file = tfp )
458444
459- def _generate_rust_interrupts (self , tfp ):
445+ def _generate_bmtest_interrupts (self , tfp ):
460446 irqs : dict [str , int ] = {}
461447 if not self ._interrupts :
462448 return
@@ -475,7 +461,7 @@ def _generate_rust_interrupts(self, tfp):
475461 print (f' pub const COUNT: usize = { max_val + 1 } ;' , file = tfp )
476462 print ('}\n ' , file = tfp )
477463
478- def _generate_rust_alerts (self , tfp ) -> None :
464+ def _generate_bmtest_alerts (self , tfp ) -> None :
479465 alerts : dict [str , int ] = {}
480466 if not self ._alerts :
481467 return
@@ -490,7 +476,7 @@ def _generate_rust_alerts(self, tfp) -> None:
490476 print (f' pub const COUNT: usize = { max_val } ;' , file = tfp )
491477 print ('}\n ' , file = tfp )
492478
493- def _generate_rust_pinmux (self , tfp ) -> None :
479+ def _generate_bmtest_pinmux (self , tfp ) -> None :
494480 for ioname , pinmux in self ._pinmux .items ():
495481 if not pinmux :
496482 continue
@@ -510,7 +496,10 @@ def main():
510496 desc = sys .modules [__name__ ].__doc__ .split ('.' , 1 )[0 ].strip ()
511497 argparser = ArgumentParser (description = f'{ desc } .' )
512498 try :
513- languages = list (QEMUAutoTop .languages )
499+ default_outkind = 'qemu'
500+ outkinds = sorted (AutoTop .outkinds ,
501+ key = lambda n : '' if n == default_outkind else n )
502+ assert len (outkinds ) > 0
514503 top = argparser .add_argument_group (title = 'Top' )
515504 top .add_argument ('opentitan' , nargs = '?' , metavar = 'OTDIR' ,
516505 help = 'OpenTitan root directory' )
@@ -522,10 +511,10 @@ def main():
522511 help = 'output file name' )
523512 files .add_argument ('-p' , '--prefix' , default = 'ot_dj_soc' ,
524513 help = 'constant prefix (default: ot_dj_soc)' )
525- files .add_argument ('-l ' , '--language ' , choices = languages ,
526- default = languages [0 ],
527- help = f'output file language '
528- f'(default: { languages [0 ]} )' )
514+ files .add_argument ('-k ' , '--out-kind ' , choices = outkinds ,
515+ default = outkinds [0 ],
516+ help = f'output file format '
517+ f'(default: { outkinds [0 ]} )' )
529518 extra = argparser .add_argument_group (title = 'Extras' )
530519 extra .add_argument ('-v' , '--verbose' , action = 'count' ,
531520 help = 'increase verbosity' )
@@ -545,9 +534,9 @@ def main():
545534 argparser .error ('OTDIR is required is no top file is specified' )
546535 if not isdir (ot_dir ):
547536 argparser .error ('Invalid OpenTitan root directory' )
548- atop = QEMUAutoTop (args .top )
537+ atop = AutoTop (args .top )
549538 atop .load (ot_dir )
550- getattr (atop , f'generate_{ args .language } ' )(args .prefix ,
539+ getattr (atop , f'generate_{ args .out_kind } ' )(args .prefix ,
551540 args .output or sys .stdout )
552541
553542 except ArgError as exc :
0 commit comments