Merge pull request #993 from antmicro/add_renode_scripts
Add Renode platform/script and Zephyr DT overlay generators
This commit is contained in:
commit
d0b2eec962
|
@ -0,0 +1,885 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Copyright (c) 2019-2021 Antmicro <www.antmicro.com>
|
||||
|
||||
Renode platform definition (repl) and script (resc) generator for LiteX SoC.
|
||||
|
||||
This script parses LiteX 'csr.json' file and generates scripts for Renode
|
||||
necessary to emulate the given configuration of the LiteX SoC.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import pprint
|
||||
import zlib
|
||||
import argparse
|
||||
|
||||
|
||||
# those memory regions are handled in a special way
|
||||
# and should not be generated automatically
|
||||
non_generated_mem_regions = ['ethmac', 'csr']
|
||||
|
||||
|
||||
def get_descriptor(csr, name, size=None):
|
||||
res = { 'base': csr['csr_bases'][name], 'constants': {} }
|
||||
|
||||
for c in csr['constants']:
|
||||
if c.startswith('{}_'.format(name)):
|
||||
res['constants'][c[len(name) + 1:]] = csr['constants'][c]
|
||||
|
||||
if size:
|
||||
res['size'] = size
|
||||
|
||||
return res
|
||||
|
||||
|
||||
def generate_sysbus_registration(descriptor,
|
||||
skip_braces=False, region=None, skip_size=False):
|
||||
""" Generates system bus registration information
|
||||
consisting of a base address and an optional shadow
|
||||
address.
|
||||
|
||||
Args:
|
||||
descriptor (dict): dictionary containing 'address',
|
||||
'shadowed_address' (might be None) and
|
||||
optionally 'size' fields
|
||||
skip_braces (bool): determines if the registration info should
|
||||
be put in braces
|
||||
region (str or None): name of the region, if None the default
|
||||
one is assumed
|
||||
skip_size (bool): if set to true do not set size
|
||||
|
||||
Returns:
|
||||
string: registration information
|
||||
"""
|
||||
|
||||
def generate_registration_entry(address, size=None, name=None):
|
||||
if name:
|
||||
if not size:
|
||||
raise Exception('Size must be provided when registering non-default region')
|
||||
return 'sysbus new Bus.BusMultiRegistration {{ address: {}; size: {}; region: "{}" }}'.format(hex(address), hex(size), name)
|
||||
if size:
|
||||
return "sysbus <{}, +{}>".format(hex(address), hex(size))
|
||||
return "sysbus {}".format(hex(address))
|
||||
|
||||
address = descriptor['base']
|
||||
size = descriptor['size'] if 'size' in descriptor and not skip_size else None
|
||||
|
||||
if 'shadowed_address' in descriptor:
|
||||
result = "{}; {}".format(
|
||||
generate_registration_entry(address, size, region),
|
||||
generate_registration_entry(descriptor['shadowed_address'], size, region))
|
||||
else:
|
||||
result = generate_registration_entry(address, size, region)
|
||||
|
||||
if not skip_braces:
|
||||
result = "{{ {} }}".format(result)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def generate_ethmac(csr, name, **kwargs):
|
||||
""" Generates definition of 'ethmac' peripheral.
|
||||
|
||||
Args:
|
||||
csr (dict): LiteX configuration
|
||||
name (string): name of the peripheral
|
||||
kwargs (dict): additional parameters, including 'buffer'
|
||||
|
||||
Returns:
|
||||
string: repl definition of the peripheral
|
||||
"""
|
||||
buf = csr['memories']['ethmac']
|
||||
phy = get_descriptor(csr, 'ethphy', 0x800)
|
||||
peripheral = get_descriptor(csr, name)
|
||||
|
||||
result = """
|
||||
ethmac: Network.LiteX_Ethernet{} @ {{
|
||||
{};
|
||||
{};
|
||||
{}
|
||||
}}
|
||||
""".format('_CSR32' if csr['constants']['config_csr_data_width'] == 32 else '',
|
||||
generate_sysbus_registration(peripheral,
|
||||
skip_braces=True),
|
||||
generate_sysbus_registration(buf,
|
||||
skip_braces=True, region='buffer'),
|
||||
generate_sysbus_registration(phy,
|
||||
skip_braces=True, region='phy'))
|
||||
|
||||
interrupt_name = '{}_interrupt'.format(name)
|
||||
if interrupt_name in csr['constants']:
|
||||
result += ' -> cpu@{}\n'.format(
|
||||
csr['constants'][interrupt_name])
|
||||
|
||||
result += """
|
||||
|
||||
ethphy: Network.EthernetPhysicalLayer @ ethmac 0
|
||||
VendorSpecific1: 0x4400 // MDIO status: 100Mbps + link up
|
||||
"""
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def generate_memory_region(region_descriptor):
|
||||
""" Generates definition of memory region.
|
||||
|
||||
Args:
|
||||
region_descriptor (dict): memory region description
|
||||
|
||||
Returns:
|
||||
string: repl definition of the memory region
|
||||
"""
|
||||
|
||||
result = ""
|
||||
|
||||
if 'original_address' in region_descriptor:
|
||||
result += """
|
||||
This memory region's base address has been
|
||||
realigned to allow to simulate it -
|
||||
Renode currently supports memory regions
|
||||
with base address aligned to 0x1000.
|
||||
|
||||
The original base address of this memory region
|
||||
was {}.
|
||||
""".format(hex(region_descriptor['original_address']))
|
||||
|
||||
if 'original_size' in region_descriptor:
|
||||
result += """
|
||||
This memory region's size has been
|
||||
extended to allow to simulate it -
|
||||
Renode currently supports memory regions
|
||||
of size being a multiple of 0x1000.
|
||||
|
||||
The original size of this memory region
|
||||
was {} bytes.
|
||||
""".format(hex(region_descriptor['original_size']))
|
||||
|
||||
if result != "":
|
||||
result = """
|
||||
/* WARNING:
|
||||
{}
|
||||
*/""".format(result)
|
||||
|
||||
result += """
|
||||
{}: Memory.MappedMemory @ {}
|
||||
size: {}
|
||||
""".format(region_descriptor['name'],
|
||||
generate_sysbus_registration(region_descriptor, skip_size=True),
|
||||
hex(region_descriptor['size']))
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def generate_silencer(csr, name, **kwargs):
|
||||
""" Silences access to a memory region.
|
||||
|
||||
Args:
|
||||
csr (dict): LiteX configuration
|
||||
name (string): name of the peripheral
|
||||
kwargs (dict): additional parameters, not used
|
||||
|
||||
Returns:
|
||||
string: repl definition of the silencer
|
||||
"""
|
||||
return """
|
||||
sysbus:
|
||||
init add:
|
||||
SilenceRange <{} 0x200> # {}
|
||||
""".format(csr['csr_bases'][name], name)
|
||||
|
||||
|
||||
def get_cpu_type(csr):
|
||||
kind = None
|
||||
variant = None
|
||||
|
||||
config_cpu_type = next((k for k in csr['constants'].keys() if k.startswith('config_cpu_type_')), None)
|
||||
if config_cpu_type:
|
||||
kind = config_cpu_type[len('config_cpu_type_'):]
|
||||
|
||||
config_cpu_variant = next((k for k in csr['constants'].keys() if k.startswith('config_cpu_variant_')), None)
|
||||
if config_cpu_variant:
|
||||
variant = config_cpu_variant[len('config_cpu_variant_'):]
|
||||
|
||||
return (kind, variant)
|
||||
|
||||
|
||||
def generate_cpu(csr, time_provider):
|
||||
""" Generates definition of a CPU.
|
||||
|
||||
Returns:
|
||||
string: repl definition of the CPU
|
||||
"""
|
||||
kind, variant = get_cpu_type(csr)
|
||||
|
||||
if kind == 'vexriscv' or kind == 'vexriscv_smp':
|
||||
result = """
|
||||
cpu: CPU.VexRiscv @ sysbus
|
||||
"""
|
||||
if variant == 'linux':
|
||||
result += """
|
||||
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)
|
||||
|
||||
if kind == 'vexriscv_smp':
|
||||
result += """
|
||||
builtInIrqController: false
|
||||
"""
|
||||
|
||||
return result
|
||||
elif kind == 'picorv32':
|
||||
return """
|
||||
cpu: CPU.PicoRV32 @ sysbus
|
||||
cpuType: "rv32imc"
|
||||
"""
|
||||
elif kind == 'ibex':
|
||||
return """
|
||||
cpu: CPU.RiscV32 @ sysbus
|
||||
cpuType: "rv32imc"
|
||||
timeProvider: empty
|
||||
interruptMode: InterruptMode.Vectored
|
||||
"""
|
||||
else:
|
||||
raise Exception('Unsupported cpu type: {}'.format(kind))
|
||||
|
||||
|
||||
def generate_peripheral(csr, name, **kwargs):
|
||||
""" Generates definition of a peripheral.
|
||||
|
||||
Args:
|
||||
csr (dict): LiteX configuration
|
||||
name (string): name of the peripheral
|
||||
kwargs (dict): additional parameterss, including
|
||||
'model' and 'properties'
|
||||
|
||||
Returns:
|
||||
string: repl definition of the peripheral
|
||||
"""
|
||||
|
||||
peripheral = get_descriptor(csr, name)
|
||||
|
||||
model = kwargs['model']
|
||||
if csr['constants']['config_csr_data_width'] == 32 and 'model_CSR32' in kwargs:
|
||||
model = kwargs['model_CSR32']
|
||||
|
||||
result = '\n{}: {} @ {}\n'.format(
|
||||
kwargs['name'] if 'name' in kwargs else name,
|
||||
model,
|
||||
generate_sysbus_registration(peripheral))
|
||||
|
||||
for constant, val in peripheral['constants'].items():
|
||||
if 'ignored_constants' not in kwargs or constant not in kwargs['ignored_constants']:
|
||||
if constant == 'interrupt':
|
||||
result += ' -> cpu@{}\n'.format(val)
|
||||
else:
|
||||
result += ' {}: {}\n'.format(constant, val)
|
||||
|
||||
if 'properties' in kwargs:
|
||||
for prop, val in kwargs['properties'].items():
|
||||
result += ' {}: {}\n'.format(prop, val(csr))
|
||||
|
||||
if 'interrupts' in kwargs:
|
||||
for prop, val in kwargs['interrupts'].items():
|
||||
result += ' {} -> {}\n'.format(prop, val())
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def generate_spiflash(csr, name, **kwargs):
|
||||
""" Generates definition of an SPI controller with attached flash memory.
|
||||
|
||||
Args:
|
||||
csr (dict): LiteX configuration
|
||||
name (string): name of the peripheral
|
||||
kwargs (dict): additional parameterss, including
|
||||
'model' and 'properties'
|
||||
|
||||
Returns:
|
||||
string: repl definition of the peripheral
|
||||
"""
|
||||
|
||||
peripheral = get_descriptor(csr, name)
|
||||
|
||||
result = """
|
||||
spi_flash: SPI.LiteX_SPI_Flash @ {{
|
||||
{}
|
||||
}}
|
||||
|
||||
mt25q: SPI.Micron_MT25Q @ spi_flash
|
||||
underlyingMemory: spiflash
|
||||
""".format(
|
||||
generate_sysbus_registration(peripheral, skip_braces=True))
|
||||
return result
|
||||
|
||||
|
||||
def generate_cas(csr, name, **kwargs):
|
||||
result = generate_peripheral(csr, name, model='GPIOPort.LiteX_ControlAndStatus', ignored_constants=['leds_count', 'switches_count', 'buttons_count'])
|
||||
|
||||
peripheral = get_descriptor(csr, name)
|
||||
|
||||
leds_count = int(peripheral['constants']['leds_count'])
|
||||
switches_count = int(peripheral['constants']['switches_count'])
|
||||
buttons_count = int(peripheral['constants']['buttons_count'])
|
||||
|
||||
for i in range(leds_count):
|
||||
result += """
|
||||
{} -> led{}@0
|
||||
""".format(i, i)
|
||||
|
||||
for i in range(leds_count):
|
||||
result += """
|
||||
led{}: Miscellaneous.LED @ cas {}
|
||||
""".format(i, i)
|
||||
|
||||
for i in range(switches_count):
|
||||
result += """
|
||||
switch{}: Miscellaneous.Button @ cas {}
|
||||
-> cas@{}
|
||||
""".format(i, i + 32, i + 32)
|
||||
|
||||
for i in range(buttons_count):
|
||||
result += """
|
||||
button{}: Miscellaneous.Button @ cas {}
|
||||
-> cas@{}
|
||||
""".format(i, i + 64, i + 64)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def generate_mmc(csr, name, **kwargs):
|
||||
""" Generates definition of 'mmc' peripheral.
|
||||
|
||||
Args:
|
||||
csr (dict): LiteX configuration
|
||||
name (string): name of the peripheral
|
||||
kwargs (dict): additional parameters, including 'core', 'reader' and 'writer'
|
||||
|
||||
Returns:
|
||||
string: repl definition of the peripheral
|
||||
"""
|
||||
|
||||
# FIXME: Get litex to generate CSR region size into output information
|
||||
# currently only a base address is present
|
||||
peripheral = get_descriptor(csr, name)
|
||||
core = get_descriptor(csr, 'sdcore', 0x100)
|
||||
reader = get_descriptor(csr, 'sdblock2mem', 0x100)
|
||||
writer = get_descriptor(csr, 'sdmem2block', 0x100)
|
||||
|
||||
result = """
|
||||
mmc_controller: SD.LiteSDCard{} @ {{
|
||||
{}; // phy
|
||||
{};
|
||||
{};
|
||||
{}
|
||||
}}
|
||||
""".format('_CSR32' if csr['constants']['config_csr_data_width'] == 32 else '',
|
||||
generate_sysbus_registration(peripheral,
|
||||
skip_braces=True),
|
||||
generate_sysbus_registration(core,
|
||||
skip_braces=True, region='core'),
|
||||
generate_sysbus_registration(reader,
|
||||
skip_braces=True, region='reader'),
|
||||
generate_sysbus_registration(writer,
|
||||
skip_braces=True, region='writer'))
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def generate_clint(clint, frequency):
|
||||
# TODO: this is configuration for VexRiscv - add support for other CPU types
|
||||
result = """
|
||||
clint: IRQControllers.CoreLevelInterruptor @ {}
|
||||
frequency: {}
|
||||
[0, 1] -> cpu@[101, 100]
|
||||
""".format(generate_sysbus_registration(clint,
|
||||
skip_braces=True,
|
||||
skip_size=True),
|
||||
frequency)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def generate_plic(plic):
|
||||
# TODO: this is configuration for VexRiscv - add support for other CPU types
|
||||
result = """
|
||||
plic: IRQControllers.PlatformLevelInterruptController @ {}
|
||||
[0-3] -> cpu@[8-11]
|
||||
numberOfSources: 31
|
||||
numberOfTargets: 2
|
||||
prioritiesEnabled: false
|
||||
""".format(generate_sysbus_registration(plic,
|
||||
skip_braces=True,
|
||||
skip_size=True))
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def get_clock_frequency(csr):
|
||||
"""
|
||||
Args:
|
||||
csr (dict): LiteX configuration
|
||||
|
||||
Returns:
|
||||
int: system clock frequency
|
||||
"""
|
||||
# in different LiteX versions this property
|
||||
# has different names
|
||||
return csr['constants']['config_clock_frequency' if 'config_clock_frequency' in csr['constants'] else 'system_clock_frequency']
|
||||
|
||||
|
||||
peripherals_handlers = {
|
||||
'uart': {
|
||||
'handler': generate_peripheral,
|
||||
'model': 'UART.LiteX_UART',
|
||||
'ignored_constants': ['polling']
|
||||
},
|
||||
'timer0': {
|
||||
'handler': generate_peripheral,
|
||||
'model': 'Timers.LiteX_Timer',
|
||||
'model_CSR32': 'Timers.LiteX_Timer_CSR32',
|
||||
'properties': {
|
||||
'frequency':
|
||||
lambda c: get_clock_frequency(c)
|
||||
}
|
||||
},
|
||||
'ethmac': {
|
||||
'handler': generate_ethmac,
|
||||
},
|
||||
'cas': {
|
||||
'handler': generate_cas,
|
||||
},
|
||||
'cpu': {
|
||||
'name': 'cpu_timer',
|
||||
'handler': generate_peripheral,
|
||||
'model': 'Timers.LiteX_CPUTimer',
|
||||
'properties': {
|
||||
'frequency':
|
||||
lambda c: get_clock_frequency(c)
|
||||
},
|
||||
'interrupts': {
|
||||
# IRQ #100 in Renode's VexRiscv model is mapped to Machine Timer Interrupt
|
||||
'IRQ': lambda: 'cpu@100'
|
||||
}
|
||||
},
|
||||
'ddrphy': {
|
||||
'handler': generate_silencer
|
||||
},
|
||||
'sdram': {
|
||||
'handler': generate_silencer
|
||||
},
|
||||
'spiflash': {
|
||||
'handler': generate_spiflash
|
||||
},
|
||||
'spi': {
|
||||
'handler': generate_peripheral,
|
||||
'model': 'SPI.LiteX_SPI',
|
||||
'ignored_constants': ['interrupt'] # model in Renode currently doesn't support interrupts
|
||||
},
|
||||
'ctrl': {
|
||||
'handler': generate_peripheral,
|
||||
'model': 'Miscellaneous.LiteX_SoC_Controller',
|
||||
'model_CSR32': 'Miscellaneous.LiteX_SoC_Controller_CSR32'
|
||||
},
|
||||
'i2c0': {
|
||||
'handler': generate_peripheral,
|
||||
'model': 'I2C.LiteX_I2C'
|
||||
},
|
||||
'sdphy': {
|
||||
'handler': generate_mmc,
|
||||
},
|
||||
'spisdcard': {
|
||||
'handler': generate_peripheral,
|
||||
'model': 'SPI.LiteX_SPI',
|
||||
'ignored_constants': ['interrupt'] # model in Renode currently doesn't support interrupts
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def genereate_etherbone_bridge(name, address, port):
|
||||
# FIXME: for now the width is fixed to 0x800
|
||||
return """
|
||||
{}: EtherboneBridge @ sysbus <{}, +0x800>
|
||||
port: {}
|
||||
""".format(name, hex(address), port)
|
||||
|
||||
|
||||
def generate_repl(csr, etherbone_peripherals, autoalign):
|
||||
""" Generates platform definition.
|
||||
|
||||
Args:
|
||||
csr (dict): LiteX configuration
|
||||
|
||||
etherbone_peripherals (dict): collection of peripherals
|
||||
that should not be simulated directly in Renode,
|
||||
but connected to it over an etherbone bridge on
|
||||
a provided port number
|
||||
|
||||
autoalign (list): list of memory regions names that
|
||||
should be automatically re-aligned
|
||||
|
||||
Returns:
|
||||
string: platform defition containing all supported
|
||||
peripherals and memory regions
|
||||
"""
|
||||
result = ""
|
||||
|
||||
|
||||
# RISC-V CPU in Renode requires memory region size
|
||||
# to be a multiple of 4KB - this is a known limitation
|
||||
# (not a bug) and there are no plans to handle smaller
|
||||
# memory regions for now
|
||||
memories = []
|
||||
for m in csr['memories']:
|
||||
x = dict(csr['memories'][m])
|
||||
x['name'] = m
|
||||
memories.append(x)
|
||||
|
||||
for mem_region in filter_memory_regions(memories, alignment=0x1000, autoalign=autoalign):
|
||||
result += generate_memory_region(mem_region)
|
||||
|
||||
time_provider = None
|
||||
if 'clint' in csr['memories']:
|
||||
result += generate_clint(csr['memories']['clint'], csr['constants']['config_clock_frequency'])
|
||||
time_provider = 'clint'
|
||||
|
||||
if 'plic' in csr['memories']:
|
||||
result += generate_plic(csr['memories']['plic'])
|
||||
|
||||
if not time_provider and 'cpu' in csr['csr_bases']:
|
||||
time_provider = 'cpu_timer'
|
||||
|
||||
result += generate_cpu(csr, time_provider)
|
||||
|
||||
for name, address in csr['csr_bases'].items():
|
||||
if name not in peripherals_handlers:
|
||||
print('Skipping unsupported peripheral `{}` at {}'
|
||||
.format(name, hex(address)))
|
||||
continue
|
||||
|
||||
if name in etherbone_peripherals:
|
||||
# generate an etherbone bridge for the peripheral
|
||||
port = etherbone_peripherals[name]
|
||||
result += genereate_etherbone_bridge(name, address, port)
|
||||
pass
|
||||
else:
|
||||
# generate an actual model of the peripheral
|
||||
h = peripherals_handlers[name]
|
||||
result += h['handler'](csr, name, **h)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def filter_memory_regions(raw_regions, alignment=None, autoalign=[]):
|
||||
""" Filters memory regions skipping those of linker type
|
||||
and those from `non_generated_mem_regions` list
|
||||
and verifying if they have proper size and do not overlap.
|
||||
|
||||
Args:
|
||||
raw_regions (list): list of memory regions parsed from
|
||||
the configuration file
|
||||
alignment (int or None): memory size boundary
|
||||
|
||||
autoalign (list): list of memory regions names that
|
||||
should be automatically re-aligned
|
||||
Returns:
|
||||
list: reduced, sorted list of memory regions to be generated
|
||||
in a repl file
|
||||
"""
|
||||
previous_region = None
|
||||
|
||||
raw_regions.sort(key=lambda x: x['base'])
|
||||
for r in raw_regions:
|
||||
if 'linker' in r['type']:
|
||||
print('Skipping linker region: {}'.format(r['name']))
|
||||
continue
|
||||
|
||||
if 'io' in r['type']:
|
||||
print('Skipping io region: {}'.format(r['name']))
|
||||
continue
|
||||
|
||||
if r['name'] in non_generated_mem_regions:
|
||||
print('Skipping pre-defined memory region: {}'.format(r['name']))
|
||||
continue
|
||||
|
||||
if alignment is not None:
|
||||
size_mismatch = r['size'] % alignment
|
||||
address_mismatch = r['base'] % alignment
|
||||
|
||||
if address_mismatch != 0:
|
||||
if r['name'] in autoalign:
|
||||
r['original_address'] = r['base']
|
||||
r['base'] -= address_mismatch
|
||||
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:
|
||||
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)))
|
||||
sys.exit(1)
|
||||
|
||||
if size_mismatch != 0:
|
||||
if r['name'] in autoalign:
|
||||
r['original_size'] = r['size']
|
||||
r['size'] += alignment - size_mismatch
|
||||
print('Extending `{}` memory region size from {} to {} due to limitations in Renode'.format(r['name'], hex(r['original_size']), hex(r['size'])))
|
||||
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)))
|
||||
sys.exit(1)
|
||||
|
||||
if previous_region is not None and (previous_region['base'] + previous_region['size']) > (r['base'] + r['size']):
|
||||
print("Error: detected overlaping memory regions: `{}` and `{}`".format(r['name'], previous_region['name']))
|
||||
sys.exit(1)
|
||||
|
||||
previous_region = r
|
||||
yield r
|
||||
|
||||
|
||||
def generate_resc(csr, args, flash_binaries={}, tftp_binaries={}):
|
||||
""" Generates platform definition.
|
||||
|
||||
Args:
|
||||
csr (dict): LiteX configuration
|
||||
args (object): configuration
|
||||
flash_binaries (dict): dictionary with paths and offsets of files
|
||||
to load into flash
|
||||
tftp_binaries (dict): dictionary with paths and names of files
|
||||
to serve with the built-in TFTP server
|
||||
|
||||
Returns:
|
||||
string: platform defition containing all supported peripherals
|
||||
and memory regions
|
||||
"""
|
||||
cpu_type, _ = get_cpu_type(csr)
|
||||
|
||||
result = """
|
||||
using sysbus
|
||||
mach create "litex-{}"
|
||||
machine LoadPlatformDescription @{}
|
||||
machine StartGdbServer 10001
|
||||
showAnalyzer sysbus.uart
|
||||
showAnalyzer sysbus.uart Antmicro.Renode.Analyzers.LoggingUartAnalyzer
|
||||
""".format(cpu_type, args.repl)
|
||||
|
||||
rom_base = csr['memories']['rom']['base'] if 'rom' in csr['memories'] else None
|
||||
if rom_base is not None and args.bios_binary:
|
||||
# load LiteX BIOS to ROM
|
||||
result += """
|
||||
sysbus LoadBinary @{} {}
|
||||
cpu PC {}
|
||||
""".format(args.bios_binary, rom_base, rom_base)
|
||||
|
||||
|
||||
if args.tftp_ip:
|
||||
result += """
|
||||
|
||||
emulation CreateNetworkServer "server" "{}"
|
||||
server StartTFTP {}
|
||||
""".format(args.tftp_ip, args.tftp_port)
|
||||
|
||||
for name, path in tftp_binaries.items():
|
||||
result += """
|
||||
server.tftp ServeFile @{} "{}" """.format(path, name)
|
||||
|
||||
result += """
|
||||
|
||||
emulation CreateSwitch "switch"
|
||||
connector Connect ethmac switch
|
||||
connector Connect server switch
|
||||
"""
|
||||
|
||||
elif args.configure_network:
|
||||
# configure network to allow netboot
|
||||
result += """
|
||||
emulation CreateSwitch "switch"
|
||||
emulation CreateTap "{}" "tap"
|
||||
connector Connect ethmac switch
|
||||
connector Connect host.tap switch
|
||||
""".format(args.configure_network)
|
||||
elif flash_binaries:
|
||||
if 'flash_boot_address' not in csr['constants']:
|
||||
print('Warning! There is no flash memory to load binaries to')
|
||||
else:
|
||||
# load binaries to spiflash to boot from there
|
||||
|
||||
for offset in flash_binaries:
|
||||
path = flash_binaries[offset]
|
||||
flash_boot_address = int(csr['constants']['flash_boot_address'], 0) + offset
|
||||
|
||||
firmware_data = open(path, 'rb').read()
|
||||
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 + 4), hex(crc32))
|
||||
result += 'sysbus LoadBinary @{} {}\n'.format(path, hex(flash_boot_address + 8))
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def print_or_save(filepath, lines):
|
||||
""" Prints given string on standard output or to the file.
|
||||
|
||||
Args:
|
||||
filepath (string): path to the file lines should be written to
|
||||
or '-' to write to a standard output
|
||||
lines (string): content to be printed/written
|
||||
"""
|
||||
if filepath == '-':
|
||||
print(lines)
|
||||
else:
|
||||
with open(filepath, 'w') as f:
|
||||
f.write(lines)
|
||||
|
||||
|
||||
def parse_flash_binaries(csr, args):
|
||||
flash_binaries = {}
|
||||
|
||||
if args.firmware_binary:
|
||||
flash_binaries[0] = args.firmware_binary
|
||||
|
||||
if args.flash_binaries_args:
|
||||
for entry in args.flash_binaries_args:
|
||||
path, separator, offset_or_label = entry.rpartition(':')
|
||||
if separator == '':
|
||||
print("Flash binary '{}' is in a wrong format. It should be 'path:offset'".format(entry))
|
||||
sys.exit(1)
|
||||
|
||||
# offset can be either a number or one of the constants from the configuration
|
||||
try:
|
||||
# try a number first...
|
||||
offset = int(offset_or_label, 0)
|
||||
except ValueError:
|
||||
# ... if it didn't work, check constants
|
||||
if offset_or_label in csr['constants']:
|
||||
offset = int(csr['constants'][offset_or_label], 0)
|
||||
else:
|
||||
print("Offset is in a wrong format. It should be either a number or one of the constants from the configuration file:")
|
||||
print("\n".join("\t{}".format(c) for c in csr['constants'].keys()))
|
||||
sys.exit(1)
|
||||
|
||||
flash_binaries[offset] = path
|
||||
|
||||
return flash_binaries
|
||||
|
||||
|
||||
def check_tftp_binaries(args):
|
||||
"""
|
||||
Expected format is:
|
||||
* path_to_the_binary
|
||||
* path_to_the_binary:alternative_name
|
||||
"""
|
||||
|
||||
if args.tftp_ip is None and len(args.tftp_binaries_args) > 0:
|
||||
print('The TFPT server IP address must be provided')
|
||||
sys.exit(1)
|
||||
|
||||
tftp_binaries = {}
|
||||
|
||||
for entry in args.tftp_binaries_args:
|
||||
path, separator, name = entry.rpartition(':')
|
||||
if separator == '':
|
||||
# this means that no alternative name is provided, so we use the original one
|
||||
name = os.path.basename(entry)
|
||||
path = entry
|
||||
|
||||
if name in tftp_binaries:
|
||||
print('File with name {} specified more than one - please check your configuration.'.format(name))
|
||||
sys.exit(1)
|
||||
|
||||
tftp_binaries[name] = path
|
||||
|
||||
return tftp_binaries
|
||||
|
||||
|
||||
def check_etherbone_peripherals(peripherals):
|
||||
result = {}
|
||||
for p in peripherals:
|
||||
|
||||
name, separator, port = p.rpartition(':')
|
||||
if separator == '':
|
||||
print("Etherbone peripheral `{}` is in a wrong format. It should be in 'name:port'".format(p))
|
||||
sys.exit(1)
|
||||
|
||||
if name not in peripherals_handlers:
|
||||
print("Unsupported peripheral '{}'. Available ones:\n".format(name))
|
||||
print("\n".join("\t{}".format(c) for c in peripherals_handlers.keys()))
|
||||
sys.exit(1)
|
||||
|
||||
if name == 'cpu':
|
||||
print("CPU must be simulated in Renode")
|
||||
sys.exit(1)
|
||||
|
||||
result[name] = port
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('conf_file',
|
||||
help='JSON configuration generated by LiteX')
|
||||
parser.add_argument('--resc', action='store',
|
||||
help='Output script file')
|
||||
parser.add_argument('--repl', action='store',
|
||||
help='Output platform definition file')
|
||||
parser.add_argument('--configure-network', action='store',
|
||||
help='Generate virtual network and connect it to host')
|
||||
parser.add_argument('--bios-binary', action='store',
|
||||
help='Path to the BIOS binary')
|
||||
parser.add_argument('--firmware-binary', action='store',
|
||||
help='Path to the binary to load into boot flash')
|
||||
parser.add_argument('--flash-binary', action='append', dest='flash_binaries_args',
|
||||
help='Path and an address of the binary to load into boot flash')
|
||||
parser.add_argument('--etherbone', action='append', dest='etherbone_peripherals',
|
||||
default=[],
|
||||
help='Peripheral to connect over etherbone bridge')
|
||||
parser.add_argument('--auto-align', action='append', dest='autoalign_memor_regions',
|
||||
default=[],
|
||||
help='List of memory regions to align automatically (necessary due to limitations in Renode)')
|
||||
parser.add_argument('--tftp-binary', action='append', dest='tftp_binaries_args', default=[],
|
||||
help='Path and an optional alternative name of the binary to serve by the TFTP server')
|
||||
parser.add_argument('--tftp-server-ip', action='store', dest='tftp_ip',
|
||||
help='The IP address of the TFTP server')
|
||||
parser.add_argument('--tftp-server-port', action='store', default=69, type=int, dest='tftp_port',
|
||||
help='The port number of the TFTP server')
|
||||
args = parser.parse_args()
|
||||
|
||||
return args
|
||||
|
||||
|
||||
def main():
|
||||
args = parse_args()
|
||||
|
||||
with open(args.conf_file) as f:
|
||||
csr = json.load(f)
|
||||
|
||||
etherbone_peripherals = check_etherbone_peripherals(args.etherbone_peripherals)
|
||||
|
||||
if args.repl:
|
||||
print_or_save(args.repl, generate_repl(csr, etherbone_peripherals, args.autoalign_memor_regions))
|
||||
|
||||
if args.resc:
|
||||
if not args.repl:
|
||||
print("REPL is needed when generating RESC file")
|
||||
sys.exit(1)
|
||||
else:
|
||||
flash_binaries = parse_flash_binaries(csr, args)
|
||||
tftp_binaries = check_tftp_binaries(args)
|
||||
|
||||
print_or_save(args.resc, generate_resc(csr, args,
|
||||
flash_binaries,
|
||||
tftp_binaries))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -0,0 +1,182 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
# Copyright (c) 2019-2021 Antmicro <www.antmicro.com>
|
||||
# Copyright (c) 2021 Henk Vergonet <henk.vergonet@gmail.com>
|
||||
#
|
||||
# Zephyr DTS & config overlay generator for LiteX SoC.
|
||||
#
|
||||
# This script parses LiteX 'csr.json' file and generates DTS and config
|
||||
# files overlay for Zephyr.
|
||||
|
||||
# Changelog:
|
||||
# - 2021-07-05 Henk Vergonet <henk.vergonet@gmail.com>
|
||||
# removed dependency on intermediate interpretation layers
|
||||
# switch to JSON csr
|
||||
# fix uart size parameter
|
||||
# - 2021-07-15 Henk Vergonet <henk.vergonet@gmail.com>
|
||||
# added identifier_mem handler as dna0
|
||||
# added spiflash as spi0
|
||||
#
|
||||
|
||||
import argparse
|
||||
import json
|
||||
|
||||
|
||||
# DTS formatting
|
||||
def dts_open(name, parm): return "&{} {{\n".format(parm.get('alias', name))
|
||||
def dts_close(): return "};\n"
|
||||
def dts_intr(name, csr): return " interrupts = <{} 0>;\n".format(
|
||||
hex(csr['constants'][name + '_interrupt']))
|
||||
def dts_reg(regs): return " reg = <{}>;\n".format(regs)
|
||||
|
||||
|
||||
# DTS handlers
|
||||
def disabled_handler(name, parm, csr):
|
||||
return " status = \"disabled\";\n"
|
||||
|
||||
|
||||
def ram_handler(name, parm, csr):
|
||||
return dts_reg(" ".join([
|
||||
hex(csr['memories'][name]['base']),
|
||||
hex(csr['memories'][name]['size'])]))
|
||||
|
||||
|
||||
def ethmac_handler(name, parm, csr):
|
||||
dtsi = dts_reg(" ".join([
|
||||
hex(csr['csr_bases'][name]),
|
||||
hex(parm['size']),
|
||||
hex(csr['memories'][name]['base']),
|
||||
hex(csr['memories'][name]['size'])]))
|
||||
dtsi += dts_intr(name, csr)
|
||||
return dtsi
|
||||
|
||||
|
||||
def i2c_handler(name, parm, csr):
|
||||
dtsi = dts_reg(" ".join([
|
||||
hex(csr['csr_bases'][name]),
|
||||
hex(parm['size']),
|
||||
hex(csr['csr_bases'][name] + parm['size']),
|
||||
hex(parm['size'])]))
|
||||
dtsi += dts_intr(name, csr)
|
||||
return dtsi
|
||||
|
||||
|
||||
def peripheral_handler(name, parm, csr):
|
||||
dtsi = dts_reg(" ".join([
|
||||
hex(csr['csr_bases'][name]),
|
||||
hex(parm['size'])]))
|
||||
try:
|
||||
dtsi += dts_intr(name, csr)
|
||||
except KeyError as e:
|
||||
print(' dtsi key', e, 'not found, no interrupt override')
|
||||
return dtsi
|
||||
|
||||
|
||||
overlay_handlers = {
|
||||
'uart': {
|
||||
'handler': peripheral_handler,
|
||||
'alias': 'uart0',
|
||||
'size': 0x20,
|
||||
'config_entry': 'UART_LITEUART'
|
||||
},
|
||||
'timer0': {
|
||||
'handler': peripheral_handler,
|
||||
'size': 0x40,
|
||||
'config_entry': 'LITEX_TIMER'
|
||||
},
|
||||
'ethmac': {
|
||||
'handler': ethmac_handler,
|
||||
'alias': 'eth0',
|
||||
'size': 0x80,
|
||||
'config_entry': 'ETH_LITEETH'
|
||||
},
|
||||
'spiflash': {
|
||||
'handler': peripheral_handler,
|
||||
'alias': 'spi0',
|
||||
'size': 12,
|
||||
'config_entry': 'SPI_LITESPI'
|
||||
},
|
||||
'i2c0' : {
|
||||
'handler': i2c_handler,
|
||||
'size': 0x4,
|
||||
'config_entry': 'I2C_LITEX'
|
||||
},
|
||||
'main_ram': {
|
||||
'handler': ram_handler,
|
||||
'alias': 'ram0',
|
||||
},
|
||||
'identifier_mem': {
|
||||
'handler': peripheral_handler,
|
||||
'alias': 'dna0',
|
||||
'size': 0x100,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def generate_dts_config(csr):
|
||||
dts = cnf = ''
|
||||
|
||||
for name, parm in overlay_handlers.items():
|
||||
print('Generating overlay for:',name)
|
||||
enable = 'y'
|
||||
dtsi = dts_open(name, parm)
|
||||
|
||||
try:
|
||||
dtsi += parm['handler'](name, parm, csr)
|
||||
except KeyError as e:
|
||||
print(' dtsi key', e, 'not found, disable', name)
|
||||
enable = 'n'
|
||||
dtsi += disabled_handler(name, parm, csr)
|
||||
|
||||
dtsi += dts_close()
|
||||
dts += dtsi
|
||||
if 'config_entry' in parm:
|
||||
cnf += ' -DCONFIG_' + parm['config_entry'] + '=' + enable
|
||||
|
||||
for name, value in csr['csr_bases'].items():
|
||||
if name not in overlay_handlers.keys():
|
||||
print('No overlay handler for:', name, 'at', hex(value))
|
||||
|
||||
return dts, cnf
|
||||
|
||||
|
||||
# helpers
|
||||
def print_or_save(filepath, lines):
|
||||
""" Prints given string on standard output or to the file.
|
||||
|
||||
Args:
|
||||
filepath (string): path to the file lines should be written to
|
||||
or '-' to write to a standard output
|
||||
lines (string): content to be printed/written
|
||||
"""
|
||||
if filepath == '-':
|
||||
print(lines)
|
||||
else:
|
||||
with open(filepath, 'w') as f:
|
||||
f.write(lines)
|
||||
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('conf_file',
|
||||
help='JSON configuration generated by LiteX')
|
||||
parser.add_argument('--dts', action='store', required=True,
|
||||
help='Output DTS overlay file')
|
||||
parser.add_argument('--config', action='store', required=True,
|
||||
help='Output config overlay file')
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def main():
|
||||
args = parse_args()
|
||||
|
||||
with open(args.conf_file) as f:
|
||||
csr = json.load(f)
|
||||
dts, config = generate_dts_config(csr)
|
||||
|
||||
print_or_save(args.dts, dts)
|
||||
print_or_save(args.config, config)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
Loading…
Reference in New Issue