Brian Sune 3ba9b1f7bd Cyclone V Board handsoff script
Since turning from old build flow.
New Altera SoCFPGA requires converting handsoff
conversion via the python script. This is from
official provided, and now sync to U-Boot with
better location at tools/cv_xxxx. Meantime,
requirement.txt is also provided to further
explain the libraries require for these scripts.

Signed-off-by: Brian Sune <briansune@gmail.com>
Reviewed-by: Tien Fong Chee <tien.fong.chee@altera.com>
2025-12-01 14:01:12 +08:00

572 lines
20 KiB
Python
Executable File

# SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
"""
Pinmux header file generator
Process the hps.xml from Quartus and convert them to headers
usable by U-Boot.
Copyright (C) 2022 Intel Corporation <www.intel.com>
Author: Lee, Kah Jing <kah.jing.lee@intel.com>
"""
import os
import re
import streamer
import xmlgrok
import xml.dom.minidom
import collections
import io
from io import StringIO
class CompatStringIO(io.StringIO):
def write(self, s):
if hasattr(s, 'decode'):
# Use unicode for python2 to keep compatible
return int(super(CompatStringIO, self).write(s.decode('utf-8')))
else:
return super(CompatStringIO, self).write(s)
def getvalue(self):
return str(super(CompatStringIO, self).getvalue())
class HPSGrokker(object):
SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))
TEMPLATE_DIR = os.path.dirname(SCRIPT_DIR) + '/src'
MAKEFILE_FILENAME = "Makefile"
makefileTemplate = ""
RESET_CONFIG_H_FILENAME = "reset_config.h"
resetConfigHTemplate = ""
# If no device family is specified, assume Cyclone V.
derivedDeviceFamily = "cyclone5"
# Assume FPGA DMA 0-7 are not in use by default
# Note: there appears to be a weird mismatch between sopcinfo
# value vs hps.xml value of DMA_Enable of string_list hw.tcl
# type, where sopcinfo uses comma as separator e.g.
# "No,No,No,..." while hps.xml uses space as separator.
dmaEnable = "No No No No No No No No"
def __init__(self, inputDir, outputDir, hpsFileName='hps.xml'):
""" HPSGrokker initialization """
self.inputDir = inputDir
self.outputDir = outputDir
self.hpsInFileName = inputDir + os.sep + hpsFileName
self.dom = xml.dom.minidom.parse(self.hpsInFileName)
self.peripheralStream = None
self.pinmuxConfigBuffer = None
self.pinmuxHeaderBuffer = None
self.pinmuxHeaderFile = None
self.pinmuxArraySize = 0
self.config_hps_ = "CFG_HPS_"
self.clockStream = None
self.pinmux_regs = self.get_default_pinmux_regs()
self.pinmux_configs = self.get_default_pinmux_configs()
self.pinmux_config_h = None
self.createFilesFromHPS()
def get_default_pinmux_regs(self):
""" Set default pinmux values """
p = collections.OrderedDict()
p['EMACIO0'] = 0
p['EMACIO1'] = 0
p['EMACIO2'] = 0
p['EMACIO3'] = 0
p['EMACIO4'] = 0
p['EMACIO5'] = 0
p['EMACIO6'] = 0
p['EMACIO7'] = 0
p['EMACIO8'] = 0
p['EMACIO9'] = 0
p['EMACIO10'] = 0
p['EMACIO11'] = 0
p['EMACIO12'] = 0
p['EMACIO13'] = 0
p['EMACIO14'] = 0
p['EMACIO15'] = 0
p['EMACIO16'] = 0
p['EMACIO17'] = 0
p['EMACIO18'] = 0
p['EMACIO19'] = 0
p['FLASHIO0'] = 0
p['FLASHIO1'] = 0
p['FLASHIO2'] = 0
p['FLASHIO3'] = 0
p['FLASHIO4'] = 0
p['FLASHIO5'] = 0
p['FLASHIO6'] = 0
p['FLASHIO7'] = 0
p['FLASHIO8'] = 0
p['FLASHIO9'] = 0
p['FLASHIO10'] = 0
p['FLASHIO11'] = 0
p['GENERALIO0'] = 0
p['GENERALIO1'] = 0
p['GENERALIO2'] = 0
p['GENERALIO3'] = 0
p['GENERALIO4'] = 0
p['GENERALIO5'] = 0
p['GENERALIO6'] = 0
p['GENERALIO7'] = 0
p['GENERALIO8'] = 0
p['GENERALIO9'] = 0
p['GENERALIO10'] = 0
p['GENERALIO11'] = 0
p['GENERALIO12'] = 0
p['GENERALIO13'] = 0
p['GENERALIO14'] = 0
p['GENERALIO15'] = 0
p['GENERALIO16'] = 0
p['GENERALIO17'] = 0
p['GENERALIO18'] = 0
p['GENERALIO19'] = 0
p['GENERALIO20'] = 0
p['GENERALIO21'] = 0
p['GENERALIO22'] = 0
p['GENERALIO23'] = 0
p['GENERALIO24'] = 0
p['GENERALIO25'] = 0
p['GENERALIO26'] = 0
p['GENERALIO27'] = 0
p['GENERALIO28'] = 0
p['GENERALIO29'] = 0
p['GENERALIO30'] = 0
p['GENERALIO31'] = 0
p['MIXED1IO0'] = 0
p['MIXED1IO1'] = 0
p['MIXED1IO2'] = 0
p['MIXED1IO3'] = 0
p['MIXED1IO4'] = 0
p['MIXED1IO5'] = 0
p['MIXED1IO6'] = 0
p['MIXED1IO7'] = 0
p['MIXED1IO8'] = 0
p['MIXED1IO9'] = 0
p['MIXED1IO10'] = 0
p['MIXED1IO11'] = 0
p['MIXED1IO12'] = 0
p['MIXED1IO13'] = 0
p['MIXED1IO14'] = 0
p['MIXED1IO15'] = 0
p['MIXED1IO16'] = 0
p['MIXED1IO17'] = 0
p['MIXED1IO18'] = 0
p['MIXED1IO19'] = 0
p['MIXED1IO20'] = 0
p['MIXED1IO21'] = 0
p['MIXED2IO0'] = 0
p['MIXED2IO1'] = 0
p['MIXED2IO2'] = 0
p['MIXED2IO3'] = 0
p['MIXED2IO4'] = 0
p['MIXED2IO5'] = 0
p['MIXED2IO6'] = 0
p['MIXED2IO7'] = 0
p['GPLINMUX48'] = 0
p['GPLINMUX49'] = 0
p['GPLINMUX50'] = 0
p['GPLINMUX51'] = 0
p['GPLINMUX52'] = 0
p['GPLINMUX53'] = 0
p['GPLINMUX54'] = 0
p['GPLINMUX55'] = 0
p['GPLINMUX56'] = 0
p['GPLINMUX57'] = 0
p['GPLINMUX58'] = 0
p['GPLINMUX59'] = 0
p['GPLINMUX60'] = 0
p['GPLINMUX61'] = 0
p['GPLINMUX62'] = 0
p['GPLINMUX63'] = 0
p['GPLINMUX64'] = 0
p['GPLINMUX65'] = 0
p['GPLINMUX66'] = 0
p['GPLINMUX67'] = 0
p['GPLINMUX68'] = 0
p['GPLINMUX69'] = 0
p['GPLINMUX70'] = 0
p['GPLMUX0'] = 1
p['GPLMUX1'] = 1
p['GPLMUX2'] = 1
p['GPLMUX3'] = 1
p['GPLMUX4'] = 1
p['GPLMUX5'] = 1
p['GPLMUX6'] = 1
p['GPLMUX7'] = 1
p['GPLMUX8'] = 1
p['GPLMUX9'] = 1
p['GPLMUX10'] = 1
p['GPLMUX11'] = 1
p['GPLMUX12'] = 1
p['GPLMUX13'] = 1
p['GPLMUX14'] = 1
p['GPLMUX15'] = 1
p['GPLMUX16'] = 1
p['GPLMUX17'] = 1
p['GPLMUX18'] = 1
p['GPLMUX19'] = 1
p['GPLMUX20'] = 1
p['GPLMUX21'] = 1
p['GPLMUX22'] = 1
p['GPLMUX23'] = 1
p['GPLMUX24'] = 1
p['GPLMUX25'] = 1
p['GPLMUX26'] = 1
p['GPLMUX27'] = 1
p['GPLMUX28'] = 1
p['GPLMUX29'] = 1
p['GPLMUX30'] = 1
p['GPLMUX31'] = 1
p['GPLMUX32'] = 1
p['GPLMUX33'] = 1
p['GPLMUX34'] = 1
p['GPLMUX35'] = 1
p['GPLMUX36'] = 1
p['GPLMUX37'] = 1
p['GPLMUX38'] = 1
p['GPLMUX39'] = 1
p['GPLMUX40'] = 1
p['GPLMUX41'] = 1
p['GPLMUX42'] = 1
p['GPLMUX43'] = 1
p['GPLMUX44'] = 1
p['GPLMUX45'] = 1
p['GPLMUX46'] = 1
p['GPLMUX47'] = 1
p['GPLMUX48'] = 1
p['GPLMUX49'] = 1
p['GPLMUX50'] = 1
p['GPLMUX51'] = 1
p['GPLMUX52'] = 1
p['GPLMUX53'] = 1
p['GPLMUX54'] = 1
p['GPLMUX55'] = 1
p['GPLMUX56'] = 1
p['GPLMUX57'] = 1
p['GPLMUX58'] = 1
p['GPLMUX59'] = 1
p['GPLMUX60'] = 1
p['GPLMUX61'] = 1
p['GPLMUX62'] = 1
p['GPLMUX63'] = 1
p['GPLMUX64'] = 1
p['GPLMUX65'] = 1
p['GPLMUX66'] = 1
p['GPLMUX67'] = 1
p['GPLMUX68'] = 1
p['GPLMUX69'] = 1
p['GPLMUX70'] = 1
p['NANDUSEFPGA'] = 0
p['UART0USEFPGA'] = 0
p['RGMII1USEFPGA'] = 0
p['SPIS0USEFPGA'] = 0
p['CAN0USEFPGA'] = 0
p['I2C0USEFPGA'] = 0
p['SDMMCUSEFPGA'] = 0
p['QSPIUSEFPGA'] = 0
p['SPIS1USEFPGA'] = 0
p['RGMII0USEFPGA'] = 0
p['UART1USEFPGA'] = 0
p['CAN1USEFPGA'] = 0
p['USB1USEFPGA'] = 0
p['I2C3USEFPGA'] = 0
p['I2C2USEFPGA'] = 0
p['I2C1USEFPGA'] = 0
p['SPIM1USEFPGA'] = 0
p['USB0USEFPGA'] = 0
p['SPIM0USEFPGA'] = 0
return p
def get_default_pinmux_configs(self):
""" Get default pinmux values """
p = collections.OrderedDict()
p['rgmii0'] = { 'name': 'CFG_HPS_EMAC0', 'used': 0 }
p['rgmii1'] = { 'name': 'CFG_HPS_EMAC1', 'used': 0 }
p['usb0'] = { 'name': 'CFG_HPS_USB0', 'used': 0 }
p['usb1'] = { 'name': 'CFG_HPS_USB1', 'used': 0 }
p['nand'] = { 'name': 'CFG_HPS_NAND', 'used': 0 }
p['sdmmc'] = { 'name': 'CFG_HPS_SDMMC', 'used': 0 }
p['CFG_HPS_SDMMC_BUSWIDTH'] = { 'name': 'CFG_HPS_SDMMC_BUSWIDTH', 'used': 0 }
p['qspi'] = { 'name': 'CFG_HPS_QSPI', 'used': 0 }
p['CFG_HPS_QSPI_CS3'] = { 'name': 'CFG_HPS_QSPI_CS3', 'used': 0 }
p['CFG_HPS_QSPI_CS2'] = { 'name': 'CFG_HPS_QSPI_CS2', 'used': 0 }
p['CFG_HPS_QSPI_CS1'] = { 'name': 'CFG_HPS_QSPI_CS1', 'used': 0 }
p['CFG_HPS_QSPI_CS0'] = { 'name': 'CFG_HPS_QSPI_CS0', 'used': 0 }
p['uart0'] = { 'name': 'CFG_HPS_UART0', 'used': 0 }
p['CFG_HPS_UART0_TX'] = { 'name': 'CFG_HPS_UART0_TX', 'used': 0 }
p['CFG_HPS_UART0_CTS'] = { 'name': 'CFG_HPS_UART0_CTS', 'used': 0 }
p['CFG_HPS_UART0_RTS'] = { 'name': 'CFG_HPS_UART0_RTS', 'used': 0 }
p['CFG_HPS_UART0_RX'] = { 'name': 'CFG_HPS_UART0_RX', 'used': 0 }
p['uart1'] = { 'name': 'CFG_HPS_UART1', 'used': 0 }
p['CFG_HPS_UART1_TX'] = { 'name': 'CFG_HPS_UART1_TX', 'used': 0 }
p['CFG_HPS_UART1_CTS'] = { 'name': 'CFG_HPS_UART1_CTS', 'used': 0 }
p['CFG_HPS_UART1_RTS'] = { 'name': 'CFG_HPS_UART1_RTS', 'used': 0 }
p['CFG_HPS_UART1_RX'] = { 'name': 'CFG_HPS_UART1_RX', 'used': 0 }
p['trace'] = { 'name': 'CFG_HPS_TRACE', 'used': 0 }
p['i2c0'] = { 'name': 'CFG_HPS_I2C0', 'used': 0 }
p['i2c1'] = { 'name': 'CFG_HPS_I2C1', 'used': 0 }
p['i2c2'] = { 'name': 'CFG_HPS_I2C2', 'used': 0 }
p['i2c3'] = { 'name': 'CFG_HPS_I2C3', 'used': 0 }
p['spim0'] = { 'name': 'CFG_HPS_SPIM0', 'used': 0 }
p['spim1'] = { 'name': 'CFG_HPS_SPIM1', 'used': 0 }
p['spis0'] = { 'name': 'CFG_HPS_SPIS0', 'used': 0 }
p['spis1'] = { 'name': 'CFG_HPS_SPIS1', 'used': 0 }
p['can0'] = { 'name': 'CFG_HPS_CAN0', 'used': 0 }
p['can1'] = { 'name': 'CFG_HPS_CAN1', 'used': 0 }
p['can1'] = { 'name': 'CFG_HPS_CAN1', 'used': 0 }
p['can1'] = { 'name': 'CFG_HPS_CAN1', 'used': 0 }
p['can1'] = { 'name': 'CFG_HPS_CAN1', 'used': 0 }
p['can1'] = { 'name': 'CFG_HPS_CAN1', 'used': 0 }
return p
def updateTemplate(self, name, value):
""" Update Makefile & reset_config.h """
pattern = "${" + name + "}"
self.makefileTemplate = self.makefileTemplate.replace(pattern, value)
self.resetConfigHTemplate = self.resetConfigHTemplate.replace(pattern, value)
def romanToInteger(self, roman):
"""
Convert roman numerals to integer
Since we only support I,V,X, the
supported range is 1-39
"""
table = { 'I':1 , 'V':5, 'X':10 }
literals = list(roman)
value = 0
i = 0
while(i < (len(literals) - 1)):
current = table[literals[i]]
next = table[literals[i + 1]]
if (current < next):
value += (next - current)
i += 2
else:
value += current
i += 1
if (i < (len(literals))):
value += table[literals[i]]
return value
def getDeviceFamily(self):
""" Get device family """
return self.derivedDeviceFamily
def getDeviceFamilyName(self, deviceFamily):
""" Get device family name """
p = re.compile('^(\w+)\s+(\w+)$')
m = p.match(deviceFamily)
return m.group(1).lower() + str(self.romanToInteger(m.group(2)))
def handleHPSSystemNode(self, systemNode):
""" handleHPSPeripheralsNode(peripheralsNode)
peripheralsNode is a peripherals element node in hps.xml
peripheralsNode is a list of peripheralNodes
"""
configNode = xmlgrok.firstElementChild(systemNode)
while configNode != None:
name = configNode.getAttribute('name')
value = configNode.getAttribute('value')
self.updateTemplate(name, value)
if name == "DEVICE_FAMILY":
self.derivedDeviceFamily = self.getDeviceFamilyName(value)
if name == "DMA_Enable":
self.dmaEnable = value
configNode = xmlgrok.nextElementSibling(configNode)
def handleHPSPeripheralNode(self, peripheralNode):
""" This node of the hps.xml may contain a name, value pair
We need to:
emit a #define for the peripheral for is 'used' state
emit a #define for that pair, if it is marked 'used'
"""
peripheralNode = xmlgrok.firstElementChild(peripheralNode)
while peripheralNode != None:
if peripheralNode.hasAttribute('name') and peripheralNode.hasAttribute('used'):
newLine = "\n"
name = peripheralNode.getAttribute('name')
used = peripheralNode.getAttribute('used')
if used == 'true' or used == True:
used = 1
elif used == 'false' or used == False:
used = 0
configs = collections.OrderedDict()
configNode = xmlgrok.firstElementChild(peripheralNode)
while configNode != None:
config_define_name = configNode.getAttribute('name')
config_define_value = configNode.getAttribute('value')
configs[config_define_name] = config_define_value
configNode = xmlgrok.nextElementSibling(configNode)
if configNode == None:
newLine += newLine
self.pinmuxConfigBuffer.write("#define " + str(config_define_name) + ' ' + '(' + str(config_define_value) + ')' + newLine)
entry = self.pinmux_configs[name]
define_name = entry['name']
if (len(configs) > 0):
self.pinmux_configs[name] = { 'name': define_name, 'used': used, 'configs': configs }
else:
self.pinmux_configs[name] = { 'name': define_name, 'used': used }
# skip the parent peripheral node
# since only need to define child config node(s)
peripheralNode = xmlgrok.nextElementSibling(peripheralNode)
def handleHPSPinmuxNode(self, pinmuxNode):
""" For a pinmuxNode, we may emit a #define for the name, value pair
"""
if pinmuxNode.hasAttribute('name') and pinmuxNode.hasAttribute('value'):
self.pinmuxArraySize += 1
name = pinmuxNode.getAttribute('name')
value = pinmuxNode.getAttribute('value')
def handleHPSPinmuxesNode(self, pinmuxesNode):
""" PinmuxesNode is a list of pinmuxNodes
"""
self.pinmuxHeaderBuffer.write(str("const u8 sys_mgr_init_table[] = {\n"))
pinmuxNode = xmlgrok.firstElementChild(pinmuxesNode)
while pinmuxNode != None:
if pinmuxNode.hasAttribute('name') and pinmuxNode.hasAttribute('value'):
self.pinmuxArraySize += 1
name = pinmuxNode.getAttribute('name')
value = pinmuxNode.getAttribute('value')
self.pinmux_regs[name] = value
pinmuxNode = xmlgrok.nextElementSibling(pinmuxNode)
reg_count = 0
pinmux_regs_count = len(self.pinmux_regs)
for reg, value in self.pinmux_regs.items():
reg_count += 1
if reg_count < pinmux_regs_count:
self.pinmuxHeaderBuffer.write(str("\t" + str(value) + ', /* ' + reg + ' */\n' ))
else:
self.pinmuxHeaderBuffer.write(str("\t" + str(value) + ' /* ' + reg + ' */\n' ))
# Write the close of the pin MUX array in the header
self.pinmuxHeaderBuffer.write(str("};" ))
def handleHPSClockNode(self, clockNode):
""" A clockNode may emit a #define for the name, frequency pair
"""
if clockNode.hasAttribute('name') and clockNode.hasAttribute('frequency'):
name = clockNode.getAttribute('name')
frequency = clockNode.getAttribute('frequency')
self.clockStream.write("#define " + name + ' ' + '(' + frequency + ')' + '\n')
def handleHPSClocksNode(self, clocksNode):
""" A list of clockNodes is call clocksNode
"""
self.clockStream = streamer.Streamer(self.outputDir + os.sep + clocksNode.nodeName + '.h', 'w')
self.clockStream.open()
clockNode = xmlgrok.firstElementChild(clocksNode)
while clockNode != None:
self.handleHPSClockNode(clockNode)
clockNode = xmlgrok.nextElementSibling(clockNode)
self.clockStream.close()
def handleHpsFpgaInterfaces(self, node):
""" Update FPGA Interface registers """
node = xmlgrok.firstElementChild(node)
while node != None:
name = node.getAttribute('name')
used = node.getAttribute('used')
if used == 'true':
reset = 0
else:
reset = 1
if name == 'F2H_AXI_SLAVE':
self.updateTemplate("DERIVED_RESET_ASSERT_FPGA2HPS", str(reset))
elif name == 'H2F_AXI_MASTER':
self.updateTemplate("DERIVED_RESET_ASSERT_HPS2FPGA", str(reset))
elif name == 'LWH2F_AXI_MASTER':
self.updateTemplate("DERIVED_RESET_ASSERT_LWHPS2FPGA", str(reset))
node = xmlgrok.nextElementSibling(node)
def createFilesFromHPS(self):
""" Parse xml and create pinmux_config.h """
# Unfortunately we can't determine the file name before
# parsing the XML, so let's build up the source file
# content in string buffer
self.pinmuxHeaderBuffer = CompatStringIO()
self.pinmuxConfigBuffer = CompatStringIO()
# Get a list of all nodes with the hps element name
hpsNodeList = self.dom.getElementsByTagName('hps')
if len(hpsNodeList) > 1:
print ("*** WARNING:" + "Multiple hps Elements found in %s!" % self.hpsInFileName)
# For each of the hps element nodes, go through the child list
# Note that currently there is only one hps Element
# but this code will handle more than one hps node
# In the future, multiple hps nodes may need additional code
# to combine settings from the multiple hps Elements
for hpsNode in hpsNodeList:
# Currently, there are only 3 children of the hps Element:
# peripherals, pin_muxes, and clocks
# but this is left open-ended for future additions to the
# specification for the hps.xml
childNode = xmlgrok.firstElementChild(hpsNode)
while childNode != None:
if childNode.nodeName == 'pin_muxes':
self.handleHPSPinmuxesNode(childNode)
elif childNode.nodeName == 'system':
self.handleHPSSystemNode(childNode)
elif childNode.nodeName == 'fpga_interfaces':
self.handleHpsFpgaInterfaces(childNode)
elif childNode.nodeName == 'peripherals':
self.handleHPSPeripheralNode(childNode)
else:
print ("***Error:Found unexpected HPS child node:%s" % childNode.nodeName)
childNode = xmlgrok.nextElementSibling(childNode)
self.updateTemplate("DERIVED_DEVICE_FAMILY", self.derivedDeviceFamily)
# Now we write string buffers into files once we know the device family
self.pinmux_config_h = 'pinmux_config.h'
self.pinmux_config_src = 'pinmux_config_' + self.derivedDeviceFamily + '.c'
# Create pinmux_config .h
headerDefine = "__SOCFPGA_PINMUX_CONFIG_H__"
self.pinmuxHeaderFile = streamer.Streamer(self.outputDir + os.sep + self.pinmux_config_h, 'w')
self.pinmuxHeaderFile.open()
self.pinmuxHeaderFile.writeLicenseHeader()
self.pinmuxHeaderFile.write('/*\n * Altera SoCFPGA PinMux configuration\n */\n\n')
self.pinmuxHeaderFile.write("#ifndef " + headerDefine + "\n")
self.pinmuxHeaderFile.write("#define " + headerDefine + "\n\n")
self.pinmuxHeaderFile.write(self.pinmuxHeaderBuffer.getvalue())
self.pinmuxHeaderFile.write("\n#endif /* " + headerDefine + " */\n")
self.pinmuxHeaderFile.close()
# Free up string buffers
self.pinmuxHeaderBuffer.close()
self.pinmuxConfigBuffer.close()