543 lines
20 KiB
PHP
543 lines
20 KiB
PHP
<?php
|
|
namespace Bloom\BuildScripts\TargetDescriptionFiles;
|
|
|
|
use Exception;
|
|
use SimpleXMLElement;
|
|
|
|
require_once __DIR__ . "/TargetFamily.php";
|
|
require_once __DIR__ . "/Variant.php";
|
|
require_once __DIR__ . "/AddressSpace.php";
|
|
require_once __DIR__ . "/PropertyGroup.php";
|
|
require_once __DIR__ . "/Module.php";
|
|
require_once __DIR__ . "/PhysicalInterface.php";
|
|
require_once __DIR__ . "/Signal.php";
|
|
require_once __DIR__ . "/Pinout.php";
|
|
|
|
class TargetDescriptionFile
|
|
{
|
|
const ARCHITECTURE_AVR8 = 'AVR8';
|
|
|
|
public string $filePath;
|
|
public ?SimpleXMLElement $xml = null;
|
|
|
|
public ?string $targetName = null;
|
|
public ?TargetFamily $targetFamily = null;
|
|
public ?string $configurationValue = null;
|
|
public ?string $targetArchitecture = null;
|
|
|
|
/** @var string[] */
|
|
public array $deviceAttributesByName = [];
|
|
|
|
/** @var AddressSpace[] */
|
|
protected array $addressSpacesById = [];
|
|
|
|
/** @var PropertyGroup[] */
|
|
protected array $propertyGroupsByName = [];
|
|
|
|
/** @var Module[] */
|
|
protected array $modulesByName = [];
|
|
|
|
/** @var Module[] */
|
|
protected array $peripheralModulesByName = [];
|
|
|
|
/** @var PhysicalInterface[] */
|
|
protected array $physicalInterfacesByName = [];
|
|
|
|
/** @var Variant[] */
|
|
public array $variants = [];
|
|
|
|
/** @var Pinout[] */
|
|
public array $pinoutsMappedByName = [];
|
|
|
|
public function __construct(string $filePath)
|
|
{
|
|
$this->filePath = $filePath;
|
|
$this->init();
|
|
}
|
|
|
|
protected function init()
|
|
{
|
|
if (!file_exists($this->filePath)) {
|
|
throw new Exception("Invalid TDF file path - file does not exist.");
|
|
}
|
|
|
|
$xml = simplexml_load_file($this->filePath);
|
|
if ($xml === false) {
|
|
throw new Exception("Failed to parse TDF XML.");
|
|
}
|
|
|
|
$this->xml = $xml;
|
|
|
|
$device = $this->xml->device;
|
|
if (!empty($device)) {
|
|
$this->deviceAttributesByName = current((array)$device->attributes());
|
|
|
|
if (!empty($this->deviceAttributesByName['name'])) {
|
|
$this->targetName = $device['name'];
|
|
$this->configurationValue = strtolower($device['name']);
|
|
}
|
|
|
|
if (!empty($this->deviceAttributesByName['family'])) {
|
|
$this->targetFamily = TargetFamily::tryFrom($device['family']);
|
|
}
|
|
|
|
if (!empty($this->deviceAttributesByName['architecture'])) {
|
|
$this->targetArchitecture = stristr($device['architecture'], 'avr') !== false
|
|
? self::ARCHITECTURE_AVR8 : $device['architecture'];
|
|
}
|
|
}
|
|
|
|
$this->loadVariants();
|
|
$this->loadAddressSpaces();
|
|
$this->loadPropertyGroups();
|
|
$this->loadModules();
|
|
$this->loadPeripheralModules();
|
|
$this->loadPhysicalInterfaces();
|
|
$this->loadPinouts();
|
|
}
|
|
|
|
protected function stringToInt(?string $value): ?int
|
|
{
|
|
if (is_null($value)) {
|
|
return null;
|
|
}
|
|
|
|
return stristr($value, '0x') !== false
|
|
? (int) hexdec($value)
|
|
: (strlen($value) > 0 ? (int) $value : null);
|
|
}
|
|
|
|
protected function getPeripheralModuleRegisterGroupOffset(
|
|
string $moduleName,
|
|
string $instanceName,
|
|
string $registerGroupName
|
|
): ?int {
|
|
if (isset($this->peripheralModulesByName[$moduleName])) {
|
|
$module = $this->peripheralModulesByName[$moduleName];
|
|
|
|
if (isset($module->instancesMappedByName[$instanceName])) {
|
|
$instance = $module->instancesMappedByName[$instanceName];
|
|
|
|
if (isset($instance->registerGroupsMappedByName[$registerGroupName])) {
|
|
return $instance->registerGroupsMappedByName[$registerGroupName]->offset;
|
|
}
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
protected function getPropertyValue(string $propertyGroupName, string $propertyName): ?string
|
|
{
|
|
if (isset($this->propertyGroupsByName[$propertyGroupName])) {
|
|
$propertyGroup = $this->propertyGroupsByName[$propertyGroupName];
|
|
|
|
if (isset($propertyGroup->propertiesMappedByName[$propertyName])) {
|
|
return $propertyGroup->propertiesMappedByName[$propertyName]->value;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
protected function getMemorySegment(
|
|
string $addressSpaceId,
|
|
string $memorySegmentType,
|
|
?string $memorySegmentName = null
|
|
): ?MemorySegment {
|
|
if (isset($this->addressSpacesById[$addressSpaceId])) {
|
|
$addressSpace = $this->addressSpacesById[$addressSpaceId];
|
|
|
|
if (isset($addressSpace->memorySegmentsByTypeAndName[$memorySegmentType])) {
|
|
$memorySegmentsByName = $addressSpace->memorySegmentsByTypeAndName[$memorySegmentType];
|
|
|
|
return !is_null($memorySegmentName)
|
|
? $memorySegmentsByName[$memorySegmentName] ?? null
|
|
: reset($memorySegmentsByName);
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
protected function getMemorySegmentSize(
|
|
string $addressSpaceId,
|
|
string $memorySegmentType,
|
|
?string $memorySegmentName = null
|
|
): ?int {
|
|
$memorySegment = $this->getMemorySegment($addressSpaceId, $memorySegmentType, $memorySegmentName);
|
|
return $memorySegment instanceof MemorySegment ? $this->stringToInt($memorySegment->size) : null;
|
|
}
|
|
|
|
protected function getModuleRegister(
|
|
string $moduleName,
|
|
string $registerGroupName,
|
|
string $registerName
|
|
): ?Register {
|
|
if (isset($this->modulesByName[$moduleName])) {
|
|
$module = $this->modulesByName[$moduleName];
|
|
|
|
if (isset($module->registerGroupsMappedByName[$registerGroupName])) {
|
|
return $module->registerGroupsMappedByName[$registerGroupName]->registersMappedByName[$registerName]
|
|
?? null;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private function loadVariants(): void
|
|
{
|
|
$variantElements = $this->xml->xpath('variants/variant');
|
|
foreach ($variantElements as $variantElement) {
|
|
$variantAttributes = $variantElement->attributes();
|
|
$variant = new Variant();
|
|
|
|
if (!empty($variantAttributes['ordercode'])) {
|
|
$variant->name = $variantAttributes['ordercode'];
|
|
}
|
|
|
|
if (!empty($variantAttributes['name'])) {
|
|
$variant->name = $variantAttributes['name'];
|
|
}
|
|
|
|
if (!empty($variantAttributes['package'])) {
|
|
$variant->package = $variantAttributes['package'];
|
|
}
|
|
|
|
if (!empty($variantAttributes['pinout'])) {
|
|
$variant->pinout = $variantAttributes['pinout'];
|
|
}
|
|
|
|
$this->variants[] = $variant;
|
|
}
|
|
}
|
|
|
|
private function loadAddressSpaces(): void
|
|
{
|
|
$addressSpaceElements = $this->xml->xpath('device/address-spaces/address-space');
|
|
foreach ($addressSpaceElements as $addressSpaceElement) {
|
|
$addressSpaceAttrs = $addressSpaceElement->attributes();
|
|
$addressSpace = new AddressSpace();
|
|
|
|
$addressSpace->id = isset($addressSpaceElement['id']) ? $addressSpaceElement['id'] : null;
|
|
if (is_null($addressSpace->id)) {
|
|
// All address spaces must have an ID - don't bother if one isn't found
|
|
continue;
|
|
}
|
|
|
|
$addressSpace->name = isset($addressSpaceAttrs['name']) ? $addressSpaceAttrs['name'] : null;
|
|
$addressSpace->startAddress = isset($addressSpaceAttrs['start'])
|
|
? $this->stringToInt($addressSpaceAttrs['start']) : null;
|
|
$addressSpace->size = isset($addressSpaceAttrs['size'])
|
|
? $this->stringToInt($addressSpaceAttrs['size']) : null;
|
|
|
|
$memorySegmentElements = $addressSpaceElement->xpath('memory-segment');
|
|
foreach ($memorySegmentElements as $memorySegmentElement) {
|
|
$memorySegmentAttrs = $memorySegmentElement->attributes();
|
|
$memorySegment = new MemorySegment();
|
|
|
|
$memorySegment->name = isset($memorySegmentAttrs['name']) ? $memorySegmentAttrs['name'] : null;
|
|
$memorySegment->startAddress = isset($memorySegmentAttrs['start'])
|
|
? $this->stringToInt($memorySegmentAttrs['start']) : null;
|
|
$memorySegment->type = isset($memorySegmentAttrs['type']) ? $memorySegmentAttrs['type'] : null;
|
|
$memorySegment->size = isset($memorySegmentAttrs['size'])
|
|
? $this->stringToInt($memorySegmentAttrs['size']) : null;
|
|
$memorySegment->pageSize = isset($memorySegmentAttrs['pagesize'])
|
|
? $this->stringToInt($memorySegmentAttrs['pagesize']) : null;
|
|
|
|
$addressSpace->memorySegmentsByTypeAndName[strtolower($memorySegment->type)]
|
|
[strtolower($memorySegment->name)] = $memorySegment;
|
|
}
|
|
|
|
$this->addressSpacesById[strtolower($addressSpace->id)] = $addressSpace;
|
|
}
|
|
}
|
|
|
|
private function loadPropertyGroups(): void
|
|
{
|
|
$propertyGroupElements = $this->xml->xpath('device/property-groups/property-group');
|
|
foreach ($propertyGroupElements as $propertyGroupElement) {
|
|
$propertyGroupAttrs = $propertyGroupElement->attributes();
|
|
$propertyGroup = new PropertyGroup();
|
|
|
|
$propertyGroup->name = isset($propertyGroupAttrs['name']) ? $propertyGroupAttrs['name'] : null;
|
|
if (empty($propertyGroup->name)) {
|
|
continue;
|
|
}
|
|
|
|
$propertyElements = $propertyGroupElement->xpath('property');
|
|
foreach ($propertyElements as $propertyElement) {
|
|
$propertyAttrs = $propertyElement->attributes();
|
|
$property = new Property();
|
|
|
|
$property->name = isset($propertyAttrs['name']) ? $propertyAttrs['name'] : null;
|
|
if (empty($propertyGroup->name)) {
|
|
continue;
|
|
}
|
|
|
|
$property->value = isset($propertyAttrs['value']) ? $propertyAttrs['value'] : null;
|
|
|
|
$propertyGroup->propertiesMappedByName[strtolower($property->name)] = $property;
|
|
}
|
|
|
|
$this->propertyGroupsByName[strtolower($propertyGroup->name)] = $propertyGroup;
|
|
}
|
|
}
|
|
|
|
private function generateRegisterGroupFromElement(SimpleXMLElement $registerGroupElement): ?RegisterGroup
|
|
{
|
|
$registerGroupAttrs = $registerGroupElement->attributes();
|
|
$registerGroup = new RegisterGroup();
|
|
|
|
$registerGroup->name = isset($registerGroupAttrs['name']) ? $registerGroupAttrs['name'] : null;
|
|
if (empty($registerGroup->name)) {
|
|
return null;
|
|
}
|
|
|
|
$registerGroup->offset = isset($registerGroupAttrs['offset'])
|
|
? $this->stringToInt($registerGroupAttrs['offset']) : null;
|
|
|
|
$registerElements = $registerGroupElement->xpath('register');
|
|
foreach ($registerElements as $registerElement) {
|
|
$registerAttrs = $registerElement->attributes();
|
|
$register = new Register();
|
|
|
|
$register->name = isset($registerAttrs['name']) ? $registerAttrs['name'] : null;
|
|
if (empty($register->name)) {
|
|
continue;
|
|
}
|
|
|
|
$register->offset = isset($registerAttrs['offset'])
|
|
? $this->stringToInt($registerAttrs['offset']) : null;
|
|
$register->size = isset($registerAttrs['size'])
|
|
? $this->stringToInt($registerAttrs['size']) : null;
|
|
|
|
$bitFieldElements = $registerElement->xpath('bitfield');
|
|
foreach ($bitFieldElements as $bitFieldElement) {
|
|
$bitFieldAttrs = $bitFieldElement->attributes();
|
|
$bitField = new BitField();
|
|
|
|
$bitField->name = isset($bitFieldAttrs['name']) ? $bitFieldAttrs['name'] : null;
|
|
if (empty($bitField->name)) {
|
|
continue;
|
|
}
|
|
|
|
$register->bitFieldsByName[strtolower($bitField->name)] = $bitField;
|
|
}
|
|
|
|
$registerGroup->registersMappedByName[strtolower($register->name)] = $register;
|
|
}
|
|
|
|
return $registerGroup;
|
|
}
|
|
|
|
private function generateModuleFromElement(SimpleXMLElement $moduleElement): ?Module
|
|
{
|
|
$moduleAttrs = $moduleElement->attributes();
|
|
$module = new Module();
|
|
|
|
$module->name = isset($moduleAttrs['name']) ? $moduleAttrs['name'] : null;
|
|
if (empty($module->name)) {
|
|
return null;
|
|
}
|
|
|
|
$registerGroupElements = $moduleElement->xpath('register-group');
|
|
foreach ($registerGroupElements as $registerGroupElement) {
|
|
$registerGroup = $this->generateRegisterGroupFromElement($registerGroupElement);
|
|
|
|
if ($registerGroup instanceof RegisterGroup) {
|
|
$module->registerGroupsMappedByName[strtolower($registerGroup->name)] = $registerGroup;
|
|
}
|
|
}
|
|
|
|
$instanceElements = $moduleElement->xpath('instance');
|
|
foreach ($instanceElements as $instanceElement) {
|
|
$instanceAttrs = $instanceElement->attributes();
|
|
$moduleInstance = new ModuleInstance();
|
|
|
|
$moduleInstance->name = isset($instanceAttrs['name']) ? $instanceAttrs['name'] : null;
|
|
if (empty($moduleInstance->name)) {
|
|
continue;
|
|
}
|
|
|
|
$registerGroupElements = $instanceElement->xpath('register-group');
|
|
foreach ($registerGroupElements as $registerGroupElement) {
|
|
$registerGroup = $this->generateRegisterGroupFromElement($registerGroupElement);
|
|
|
|
if ($registerGroup instanceof RegisterGroup) {
|
|
$moduleInstance->registerGroupsMappedByName[strtolower($registerGroup->name)] = $registerGroup;
|
|
}
|
|
}
|
|
|
|
$signalElements = $instanceElement->xpath('signals/signal');
|
|
foreach ($signalElements as $signalElement) {
|
|
$signalAttrs = $signalElement->attributes();
|
|
$signal = new Signal();
|
|
|
|
$signal->padName = isset($signalAttrs['pad']) ? $signalAttrs['pad'] : null;
|
|
$signal->function = isset($signalAttrs['function']) ? $signalAttrs['function'] : null;
|
|
$signal->index = isset($signalAttrs['index']) ? $this->stringToInt($signalAttrs['index']) : null;
|
|
|
|
$moduleInstance->signals[] = $signal;
|
|
}
|
|
|
|
$module->instancesMappedByName[strtolower($moduleInstance->name)] = $moduleInstance;
|
|
}
|
|
|
|
return $module;
|
|
}
|
|
|
|
private function loadModules(): void
|
|
{
|
|
$moduleElements = $this->xml->xpath('modules/module');
|
|
foreach ($moduleElements as $moduleElement) {
|
|
$module = $this->generateModuleFromElement($moduleElement);
|
|
|
|
if ($module instanceof Module) {
|
|
$this->modulesByName[strtolower($module->name)] = $module;
|
|
}
|
|
}
|
|
}
|
|
|
|
private function loadPeripheralModules(): void
|
|
{
|
|
$moduleElements = $this->xml->xpath('device/peripherals/module');
|
|
foreach ($moduleElements as $moduleElement) {
|
|
$module = $this->generateModuleFromElement($moduleElement);
|
|
|
|
if ($module instanceof Module) {
|
|
$this->peripheralModulesByName[strtolower($module->name)] = $module;
|
|
}
|
|
}
|
|
}
|
|
|
|
private function loadPhysicalInterfaces(): void
|
|
{
|
|
$interfaceElements = $this->xml->xpath('device/interfaces/interface');
|
|
foreach ($interfaceElements as $interfaceElement) {
|
|
$interfaceAttrs = $interfaceElement->attributes();
|
|
$physicalInterface = new PhysicalInterface();
|
|
|
|
$physicalInterface->name = isset($interfaceAttrs['name']) ? $interfaceAttrs['name'] : null;
|
|
if (empty($physicalInterface->name)) {
|
|
continue;
|
|
}
|
|
|
|
$physicalInterface->type = isset($interfaceAttrs['type']) ? $interfaceAttrs['type'] : null;
|
|
|
|
$this->physicalInterfacesByName[strtolower($physicalInterface->name)] = $physicalInterface;
|
|
}
|
|
}
|
|
|
|
private function loadPinouts(): void
|
|
{
|
|
$pinoutElements = $this->xml->xpath('pinouts/pinout');
|
|
foreach ($pinoutElements as $pinoutElement) {
|
|
$pinoutAttrs = $pinoutElement->attributes();
|
|
$pinout = new Pinout();
|
|
|
|
$pinout->name = isset($pinoutAttrs['name']) ? $pinoutAttrs['name'] : null;
|
|
$pinout->function = isset($pinoutAttrs['function']) ? $pinoutAttrs['function'] : null;
|
|
$pinout->type = PinoutType::tryFrom($pinout->name);
|
|
|
|
// Attempt to extract the number of expected pins for this pinout, from the pinout name
|
|
$expectedPinCount = filter_var($pinout->name, FILTER_SANITIZE_NUMBER_INT);
|
|
if (is_numeric($expectedPinCount)) {
|
|
$pinout->expectedPinCount = (int) $expectedPinCount;
|
|
}
|
|
|
|
$pinElements = $pinoutElement->xpath('pin');
|
|
foreach ($pinElements as $pinElement) {
|
|
$pinAttrs = $pinElement->attributes();
|
|
$pin = new Pin();
|
|
|
|
$pin->pad = isset($pinAttrs['pad']) ? $pinAttrs['pad'] : null;
|
|
$pin->position = isset($pinAttrs['position']) ? $this->stringToInt($pinAttrs['position']) : null;
|
|
|
|
$pinout->pins[] = $pin;
|
|
}
|
|
|
|
$this->pinoutsMappedByName[strtolower($pinout->name)] = $pinout;
|
|
}
|
|
}
|
|
|
|
public function validate(): array
|
|
{
|
|
$failures = [];
|
|
|
|
if (empty($this->targetName)) {
|
|
$failures[] = 'Target name not found';
|
|
}
|
|
|
|
if (str_contains($this->targetName, ' ')) {
|
|
$failures[] = 'Target name cannot contain whitespaces';
|
|
}
|
|
|
|
if (empty($this->targetFamily)) {
|
|
$failures[] = 'Missing/invalid target family';
|
|
}
|
|
|
|
if (empty($this->targetArchitecture)) {
|
|
$failures[] = 'Target architecture not found';
|
|
}
|
|
|
|
if (empty($this->variants)) {
|
|
$failures[] = 'Missing target variants';
|
|
}
|
|
|
|
foreach ($this->variants as $variant) {
|
|
$variantValidationFailures = $variant->validate();
|
|
|
|
if (!empty($variantValidationFailures)) {
|
|
$failures[] = 'Variant validation failures: ' . implode(", ", $variantValidationFailures);
|
|
}
|
|
|
|
if (empty($this->pinoutsMappedByName[strtolower($variant->pinout)])) {
|
|
$failures[] = 'Pinout ("' . $variant->pinout . '") for variant "' . $variant->name . '" not found.';
|
|
}
|
|
}
|
|
|
|
if (empty($this->pinoutsMappedByName)) {
|
|
$failures[] = 'No pinouts found';
|
|
}
|
|
|
|
foreach ($this->pinoutsMappedByName as $pinout) {
|
|
if (!is_null($pinout->expectedPinCount)) {
|
|
if ($pinout->expectedPinCount != count($pinout->pins)) {
|
|
$failures[] = 'Pin count (' . count($pinout->pins) . ') for pinout "' . $pinout->name
|
|
. '" does not match expected pin count (' . $pinout->expectedPinCount . ')';
|
|
}
|
|
|
|
} else {
|
|
$failures[] = 'Could not deduce expected pin count for pinout "' . $pinout->name . '"';
|
|
}
|
|
|
|
if (in_array($pinout->type, [PinoutType::DIP, PinoutType::QFN, PinoutType::SOIC])) {
|
|
foreach ($pinout->pins as $index => $pin) {
|
|
if (is_null($pin->position)) {
|
|
$failures[] = 'Missing/invalid pin position for pin (index: ' . $index . ').';
|
|
}
|
|
|
|
if (is_null($pin->pad)) {
|
|
$failures[] = 'Missing/invalid pin pad for pin (index: ' . $index . ').';
|
|
}
|
|
}
|
|
}
|
|
|
|
if (
|
|
in_array($pinout->type, [PinoutType::SOIC, PinoutType::DIP, PinoutType::SSOP])
|
|
&& count($pinout->pins) % 2 != 0
|
|
) {
|
|
$failures[] = 'DIP/SOIC/SSOP pinout (' . $pinout->name . ') pin count is not a multiple of two.';
|
|
}
|
|
|
|
if (in_array($pinout->type, [PinoutType::QFN, PinoutType::QFP]) && count($pinout->pins) % 4 != 0) {
|
|
$failures[] = 'QFP/QFN pinout (' . $pinout->name . ') pin count is not a multiple of four.';
|
|
}
|
|
}
|
|
|
|
return $failures;
|
|
}
|
|
}
|