Merge pull request #1496 from MateuszKarlic/json2renode-update

json2renode: Multicore configuration support
This commit is contained in:
enjoy-digital 2022-11-09 15:52:31 +01:00 committed by GitHub
commit 7157b4c5e8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 214 additions and 85 deletions

View File

@ -1,6 +1,6 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
""" """
Copyright (c) 2019-2021 Antmicro <www.antmicro.com> Copyright (c) 2019-2022 Antmicro <www.antmicro.com>
Renode platform definition (repl) and script (resc) generator for LiteX SoC. Renode platform definition (repl) and script (resc) generator for LiteX SoC.
@ -20,6 +20,14 @@ import argparse
# and should not be generated automatically # and should not be generated automatically
non_generated_mem_regions = ['ethmac', 'csr'] non_generated_mem_regions = ['ethmac', 'csr']
def get_soc_interrupt_parent(csr):
'''
We assume that for multi-core setups plic will be used hence cpu0 is enough
'''
if 'plic' in csr['memories']:
return 'plic'
else:
return 'cpu0'
def get_descriptor(csr, name, size=None): def get_descriptor(csr, name, size=None):
res = { 'base': csr['csr_bases'][name], 'constants': {} } res = { 'base': csr['csr_bases'][name], 'constants': {} }
@ -110,7 +118,8 @@ ethmac: Network.LiteX_Ethernet{} @ {{
interrupt_name = '{}_interrupt'.format(name) interrupt_name = '{}_interrupt'.format(name)
if interrupt_name in csr['constants']: if interrupt_name in csr['constants']:
result += ' -> cpu@{}\n'.format( result += ' -> {}@{}\n'.format(
get_soc_interrupt_parent(csr),
csr['constants'][interrupt_name]) csr['constants'][interrupt_name])
result += """ result += """
@ -204,8 +213,54 @@ def get_cpu_type(csr):
return (kind, variant) return (kind, variant)
def get_cpu_count(csr):
return csr['constants']['config_cpu_count']
def generate_cpu(csr, time_provider): vexriscv_common_kind = {
'name': 'VexRiscv',
'variants': {
'linux': {
'properties': ['cpuType: "rv32ima"', 'privilegeArchitecture: PrivilegeArchitecture.Priv1_10'],
},
'i': {
'properties': ['cpuType: "rv32i"'],
},
'im': {
'properties': ['cpuType: "rv32im"'],
},
'ima': {
'properties': ['cpuType: "rv32ima"'],
},
'imac': {
'properties': ['cpuType: "rv32imac"'],
},
'others': {
'properties': ['cpuType: "rv32im"'],
}
},
'supports_time_provider': True,
}
cpu_kinds = {
'vexriscv': vexriscv_common_kind,
'vexriscv_smp': vexriscv_common_kind,
'picorv32': {
'name': 'PicoRV32',
'properties': ['cpuType: "rv32imc"'],
},
'minerva': {
'name': 'Minerva',
},
'ibex': {
'name': 'IbexRiscV32',
},
'cv32e40p': {
'name': 'CV32E40P',
'supports_time_provider': True,
'properties': ['cpuType: "rv32imc"'],
}
}
def generate_cpu(csr, time_provider, number_of_cores):
""" Generates definition of a CPU. """ Generates definition of a CPU.
Returns: Returns:
@ -213,63 +268,39 @@ def generate_cpu(csr, time_provider):
""" """
kind, variant = get_cpu_type(csr) kind, variant = get_cpu_type(csr)
if kind == 'vexriscv' or kind == 'vexriscv_smp': try:
result = """ cpu = cpu_kinds[kind]
cpu: CPU.VexRiscv @ sysbus
cpu_string = f'{cpu["name"]} @ sysbus\n'
def unpack_properties(prop_list):
return ''.join([f' {prop}\n' for prop in prop_list])
if 'properties' in cpu:
cpu_string += unpack_properties(cpu["properties"])
if 'variants' in cpu:
variant = cpu['variants'].get(variant)
if variant is None:
variant = cpu['variants'].get('others', [])
if 'properties' in variant:
cpu_string += unpack_properties(variant["properties"])
except KeyError:
raise Exception(f'Unsupported cpu type: {kind}')
result = ''
for cpu_id in range(0, number_of_cores):
result += f"""
cpu{cpu_id}: CPU.{cpu_string.strip()}
hartId: {cpu_id}
""" """
if variant == 'linux': if cpu.get('supports_time_provider', False):
result += """ result += f' timeProvider: {time_provider}\n'
cpuType: "rv32ima"
privilegeArchitecture: PrivilegeArchitecture.Priv1_10
"""
elif variant in ["i", "im", "ima", "imac"]:
result += """
cpuType: "rv32{}"
""".format(variant)
else:
result += """
cpuType: "rv32im"
"""
if time_provider:
result += """
timeProvider: {}
""".format(time_provider)
return result return result
elif kind == 'picorv32':
return """
cpu: CPU.PicoRV32 @ sysbus
cpuType: "rv32imc"
"""
elif kind == 'minerva':
return """
cpu: CPU.Minerva @ sysbus
"""
elif kind == 'ibex':
return """
cpu: CPU.IbexRiscV32 @ sysbus
"""
elif kind == 'cv32e40p':
result = """
cpu: CPU.CV32E40P @ sysbus
"""
if variant == 'standard':
result += """
cpuType: "rv32imc"
"""
else:
result += """
cpuType: "rv32imc"
"""
if time_provider:
result += """
timeProvider: {}
""".format(time_provider)
return result
else:
raise Exception('Unsupported cpu type: {}'.format(kind))
def generate_peripheral(csr, name, **kwargs): def generate_peripheral(csr, name, **kwargs):
""" Generates definition of a peripheral. """ Generates definition of a peripheral.
@ -298,7 +329,7 @@ def generate_peripheral(csr, name, **kwargs):
for constant, val in peripheral['constants'].items(): for constant, val in peripheral['constants'].items():
if 'ignored_constants' not in kwargs or constant not in kwargs['ignored_constants']: if 'ignored_constants' not in kwargs or constant not in kwargs['ignored_constants']:
if constant == 'interrupt': if constant == 'interrupt':
result += ' -> cpu@{}\n'.format(val) result += ' -> {}@{}\n'.format(get_soc_interrupt_parent(csr), val)
else: else:
result += ' {}: {}\n'.format(constant, val) result += ' {}: {}\n'.format(constant, val)
@ -413,31 +444,37 @@ mmc_controller: SD.LiteSDCard{} @ {{
return result return result
def generate_clint(clint, frequency): def generate_clint(clint, frequency, number_of_cores):
# TODO: this is configuration for VexRiscv - add support for other CPU types # TODO: this is configuration for VexRiscv - add support for other CPU types
result = """ result = """
clint: IRQControllers.CoreLevelInterruptor @ {} clint: IRQControllers.CoreLevelInterruptor @ {}
frequency: {} frequency: {}
[0, 1] -> cpu@[101, 100] numberOfTargets: {}
""".format(generate_sysbus_registration(clint, """.format(generate_sysbus_registration(clint,
skip_braces=True, skip_braces=True,
skip_size=True), skip_size=True),
frequency) frequency, number_of_cores)
for cpu_id in range(0, number_of_cores):
result += f" [{2 * cpu_id}, {2 * cpu_id + 1}] -> cpu{cpu_id}@[101, 100]\n"
return result return result
def generate_plic(plic): def generate_plic(plic, number_of_cores):
# TODO: this is configuration for linux-on-litex-vexriscv - add support for other CPU types # TODO: this is configuration for linux-on-litex-vexriscv - add support for other CPU types
result = """ result = """
plic: IRQControllers.PlatformLevelInterruptController @ {} plic: IRQControllers.PlatformLevelInterruptController @ {}
[0, 1] -> cpu@[11, 9]
numberOfSources: 31 numberOfSources: 31
numberOfContexts: 2 numberOfContexts: {}
prioritiesEnabled: false prioritiesEnabled: false
""".format(generate_sysbus_registration(plic, """.format(generate_sysbus_registration(plic,
skip_braces=True, skip_braces=True,
skip_size=True)) skip_size=True),
2 * number_of_cores)
for cpu_id in range(0, number_of_cores):
result += f" [{2 * cpu_id}, {2 * cpu_id + 1}] -> cpu{cpu_id}@[11, 9]\n"
return result return result
@ -489,6 +526,67 @@ def get_clock_frequency(csr):
# has different names # has different names
return csr['constants']['config_clock_frequency' if 'config_clock_frequency' in csr['constants'] else 'system_clock_frequency'] return csr['constants']['config_clock_frequency' if 'config_clock_frequency' in csr['constants'] else 'system_clock_frequency']
def generate_gpio_port(csr, name, **kwargs):
peripheral = get_descriptor(csr, name)
ints = ''
if 'interrupt' in kwargs:
ints += f' IRQ -> {get_soc_interrupt_parent(csr)}@{kwargs["interrupt"]}'
if 'connections' in kwargs:
for irq, target in zip(kwargs['connections'], kwargs['connections_targets']):
ints += f' {irq} -> {target}\n'
result = """
gpio_{}: GPIOPort.LiteX_GPIO @ {}
type: {}
enableIrq: {}
{}
""".format(name,
generate_sysbus_registration(peripheral, skip_braces=True),
kwargs['type'],
'true' if kwargs['type'] != 'Type.Out' else 'false',
ints)
return result
def generate_switches(csr, name, **kwargs):
interrupt_name = '{}_interrupt'.format(name)
result = generate_gpio_port(csr, name, **{'type': 'Type.In', 'interrupt': csr['constants'][interrupt_name], **kwargs})
# Litex puts 4 by default into dts (litex_json2dts)
count = int(csr['constants'].get(f'{name}_ngpio', 4))
for i in range(0, count):
result += """
{name}_{i}: Miscellaneous.Button @ {periph} {i}
-> {periph}@{i}
""".format(name=name, periph=f"gpio_{name}", i=i)
return result
def generate_leds(csr, name, **kwargs):
# Litex puts 4 by default into dts (litex_json2dts)
count = int(csr['constants'].get(f'{name}_ngpio', 4))
result = generate_gpio_port(csr, name,
**{'type': 'Type.Out',
'connections': [*range(0, count)],
'connections_targets': [f'leds_{i}@0' for i in range(0, count)],
**kwargs})
for i in range(0, count):
result += """
{name}_{i}: Miscellaneous.LED @ {periph} {i}
""".format(name=name, periph=f"gpio_{name}", i=i)
return result
def handled_peripheral(csr, name, **kwargs):
"""
Use this to silence warnings about unsupported peripherals, that are in reality emulated by other peripheral
e. g. mmc, ethmac
"""
return ''
peripherals_handlers = { peripherals_handlers = {
'uart': { 'uart': {
@ -521,7 +619,7 @@ peripherals_handlers = {
}, },
'interrupts': { 'interrupts': {
# IRQ #100 in Renode's VexRiscv model is mapped to Machine Timer Interrupt # IRQ #100 in Renode's VexRiscv model is mapped to Machine Timer Interrupt
'IRQ': lambda: 'cpu@100' 'IRQ': lambda: 'cpu0@100'
} }
}, },
'ddrphy': { 'ddrphy': {
@ -559,12 +657,37 @@ peripherals_handlers = {
'handler': generate_video_framebuffer, 'handler': generate_video_framebuffer,
}, },
'video_framebuffer_vtg': { 'video_framebuffer_vtg': {
'handler': lambda *args, **kwargs: "", # This is handled by generate_video_framebuffer 'handler': handled_peripheral, # This is handled by generate_video_framebuffer
} },
'leds': {
'handler': generate_leds,
},
'switches': {
'handler': generate_switches,
},
'mmcm': {
'handler': generate_peripheral,
'model': 'Miscellaneous.LiteX_MMCM',
'model_CSR32': 'Miscellaneous.LiteX_MMCM_CSR32',
'ignored_constants': ['lock_timeout', 'drdy_timeout'],
},
'ethphy': {
'handler': handled_peripheral # by generate_ethmac
},
# handled by generate_mmc
'sdblock2mem': {
'handler': handled_peripheral
},
'sdmem2block': {
'handler': handled_peripheral
},
'sdcore': {
'handler': handled_peripheral
},
} }
def genereate_etherbone_bridge(name, address, port): def generate_etherbone_bridge(name, address, port):
# FIXME: for now the width is fixed to 0x800 # FIXME: for now the width is fixed to 0x800
return """ return """
{}: EtherboneBridge @ sysbus <{}, +0x800> {}: EtherboneBridge @ sysbus <{}, +0x800>
@ -572,7 +695,7 @@ def genereate_etherbone_bridge(name, address, port):
""".format(name, hex(address), port) """.format(name, hex(address), port)
def generate_repl(csr, etherbone_peripherals, autoalign): def generate_repl(csr, etherbone_peripherals, autoalign, number_of_cores):
""" Generates platform definition. """ Generates platform definition.
Args: Args:
@ -592,7 +715,6 @@ def generate_repl(csr, etherbone_peripherals, autoalign):
""" """
result = "" result = ""
# RISC-V CPU in Renode requires memory region size # RISC-V CPU in Renode requires memory region size
# to be a multiple of 4KB - this is a known limitation # to be a multiple of 4KB - this is a known limitation
# (not a bug) and there are no plans to handle smaller # (not a bug) and there are no plans to handle smaller
@ -611,16 +733,16 @@ def generate_repl(csr, etherbone_peripherals, autoalign):
time_provider = None time_provider = None
if 'clint' in csr['memories']: if 'clint' in csr['memories']:
result += generate_clint(csr['memories']['clint'], csr['constants']['config_clock_frequency']) result += generate_clint(csr['memories']['clint'], csr['constants']['config_clock_frequency'], number_of_cores)
time_provider = 'clint' time_provider = 'clint'
if 'plic' in csr['memories']: if 'plic' in csr['memories']:
result += generate_plic(csr['memories']['plic']) result += generate_plic(csr['memories']['plic'], number_of_cores)
if not time_provider and 'cpu' in csr['csr_bases']: if not time_provider and 'cpu' in csr['csr_bases']:
time_provider = 'cpu_timer' time_provider = 'cpu_timer'
result += generate_cpu(csr, time_provider) result += generate_cpu(csr, time_provider, number_of_cores)
for name, address in csr['csr_bases'].items(): for name, address in csr['csr_bases'].items():
if name not in peripherals_handlers: if name not in peripherals_handlers:
@ -631,7 +753,7 @@ def generate_repl(csr, etherbone_peripherals, autoalign):
if name in etherbone_peripherals: if name in etherbone_peripherals:
# generate an etherbone bridge for the peripheral # generate an etherbone bridge for the peripheral
port = etherbone_peripherals[name] port = etherbone_peripherals[name]
result += genereate_etherbone_bridge(name, address, port) result += generate_etherbone_bridge(name, address, port)
pass pass
else: else:
# generate an actual model of the peripheral # generate an actual model of the peripheral
@ -674,6 +796,8 @@ def filter_memory_regions(raw_regions, alignment=None, autoalign=[]):
size_mismatch = r['size'] % alignment size_mismatch = r['size'] % alignment
address_mismatch = r['base'] % alignment address_mismatch = r['base'] % alignment
def print_autoalign_hint():
print('Hint: use "--auto-align {}" to force automatic alignement of the region'.format(r['name']))
if address_mismatch != 0: if address_mismatch != 0:
if r['name'] in autoalign: if r['name'] in autoalign:
r['original_address'] = r['base'] r['original_address'] = r['base']
@ -681,6 +805,7 @@ def filter_memory_regions(raw_regions, alignment=None, autoalign=[]):
print('Re-aligning `{}` memory region base address from {} to {} due to limitations in Renode'.format(r['name'], hex(r['original_address']), hex(r['base']))) print('Re-aligning `{}` memory region base address from {} to {} due to limitations in Renode'.format(r['name'], hex(r['original_address']), hex(r['base'])))
else: else:
print('Error: `{}` memory region base address ({}) is not aligned to {}. This configuration cannot be currently simulated in Renode'.format(r['name'], hex(r['size']), hex(alignment))) print('Error: `{}` memory region base address ({}) is not aligned to {}. This configuration cannot be currently simulated in Renode'.format(r['name'], hex(r['size']), hex(alignment)))
print_autoalign_hint()
sys.exit(1) sys.exit(1)
if size_mismatch != 0: if size_mismatch != 0:
@ -690,6 +815,7 @@ def filter_memory_regions(raw_regions, alignment=None, autoalign=[]):
print('Extending `{}` memory region size from {} to {} due to limitations in Renode'.format(r['name'], hex(r['original_size']), hex(r['size']))) print('Extending `{}` memory region size from {} to {} due to limitations in Renode'.format(r['name'], hex(r['original_size']), hex(r['size'])))
else: else:
print('Error: `{}` memory region size ({}) is not aligned to {}. This configuration cannot be currently simulated in Renode'.format(r['name'], hex(r['size']), hex(alignment))) print('Error: `{}` memory region size ({}) is not aligned to {}. This configuration cannot be currently simulated in Renode'.format(r['name'], hex(r['size']), hex(alignment)))
print_autoalign_hint()
sys.exit(1) sys.exit(1)
if r['size'] == 0: if r['size'] == 0:
@ -730,7 +856,7 @@ def find_memory_region(memory_regions, address):
return None return None
def generate_resc(csr, args, flash_binaries={}, tftp_binaries={}): def generate_resc(csr, number_of_cores, args, flash_binaries={}, tftp_binaries={}):
""" Generates platform definition. """ Generates platform definition.
Args: Args:
@ -756,14 +882,15 @@ showAnalyzer sysbus.uart
showAnalyzer sysbus.uart Antmicro.Renode.Analyzers.LoggingUartAnalyzer showAnalyzer sysbus.uart Antmicro.Renode.Analyzers.LoggingUartAnalyzer
""".format(cpu_type, args.repl) """.format(cpu_type, args.repl)
rom_base = csr['memories']['rom']['base'] if 'rom' in csr['memories'] else None opensbi_base = csr['memories']['opensbi']['base'] if 'opensbi' in csr['memories'] else None
if rom_base is not None and args.bios_binary: if opensbi_base is not None and args.bios_binary:
# load LiteX BIOS to ROM # load LiteX BIOS to ROM
result += """ result += """
sysbus LoadBinary @{} {} sysbus LoadBinary @{} {}
cpu PC {} """.format(args.bios_binary, hex(opensbi_base))
""".format(args.bios_binary, rom_base, rom_base)
for cpu_id in range(0, number_of_cores):
result += f"cpu{cpu_id} PC {hex(opensbi_base)}\n"
if args.tftp_ip: if args.tftp_ip:
result += """ result += """
@ -792,16 +919,16 @@ connector Connect ethmac switch
connector Connect host.tap switch connector Connect host.tap switch
""".format(args.configure_network) """.format(args.configure_network)
elif flash_binaries: elif flash_binaries:
if 'flash_boot_address' not in csr['constants']: if 'spiflash' not in csr['memories']:
print('Warning! There is no flash memory to load binaries to') print('Warning! There is no flash memory to load binaries to')
else: else:
# load binaries to spiflash to boot from there # load binaries to spiflash to boot from there
for offset in flash_binaries: for offset in flash_binaries:
path = flash_binaries[offset] path = flash_binaries[offset]
flash_boot_address = int(csr['constants']['flash_boot_address'], 0) + offset flash_boot_address = int(csr['memories']['spiflash']['base']) + offset
firmware_data = open(path, 'rb').read() firmware_data = open(os.path.expanduser(path), 'rb').read()
crc32 = zlib.crc32(firmware_data) crc32 = zlib.crc32(firmware_data)
result += 'sysbus WriteDoubleWord {} {}\n'.format(hex(flash_boot_address), hex(len(firmware_data))) result += 'sysbus WriteDoubleWord {} {}\n'.format(hex(flash_boot_address), hex(len(firmware_data)))
@ -950,8 +1077,10 @@ def main():
etherbone_peripherals = check_etherbone_peripherals(args.etherbone_peripherals) etherbone_peripherals = check_etherbone_peripherals(args.etherbone_peripherals)
number_of_cores = get_cpu_count(csr)
if args.repl: if args.repl:
print_or_save(args.repl, generate_repl(csr, etherbone_peripherals, args.autoalign_memor_regions)) print_or_save(args.repl, generate_repl(csr, etherbone_peripherals, args.autoalign_memor_regions, number_of_cores))
if args.resc: if args.resc:
if not args.repl: if not args.repl:
@ -961,7 +1090,7 @@ def main():
flash_binaries = parse_flash_binaries(csr, args) flash_binaries = parse_flash_binaries(csr, args)
tftp_binaries = check_tftp_binaries(args) tftp_binaries = check_tftp_binaries(args)
print_or_save(args.resc, generate_resc(csr, args, print_or_save(args.resc, generate_resc(csr, number_of_cores, args,
flash_binaries, flash_binaries,
tftp_binaries)) tftp_binaries))