Files
BloomPatched/build/scripts/TargetDescriptionFiles/TargetDescriptionFile.php

458 lines
17 KiB
PHP
Raw Normal View History

2021-06-01 23:54:04 +01:00
<?php
namespace Bloom\BuildScripts\TargetDescriptionFiles;
use Exception;
use SimpleXMLElement;
2021-06-01 23:54:04 +01:00
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";
2021-06-01 23:54:04 +01:00
class TargetDescriptionFile
{
const ARCHITECTURE_AVR8 = 'AVR8';
public string $filePath;
public ?SimpleXMLElement $xml = null;
public ?string $targetName = null;
public ?string $targetArchitecture = null;
/** @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 = [];
2021-06-01 23:54:04 +01:00
/** @var Variant[] */
public array $variants = [];
/** @var Pinout[] */
public array $pinoutsMappedByName = [];
2021-06-01 23:54:04 +01:00
public function __construct(string $filePath)
{
$this->filePath = $filePath;
$this->init();
}
protected function init()
2021-06-01 23:54:04 +01:00
{
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)) {
2021-06-01 23:54:04 +01:00
$deviceAttributes = $device->attributes();
if (!empty($deviceAttributes['name'])) {
$this->targetName = $device['name'];
}
if (!empty($deviceAttributes['architecture'])) {
$this->targetArchitecture = stristr($device['architecture'], 'avr') !== false
? self::ARCHITECTURE_AVR8 : $device['architecture'];
2021-06-01 23:54:04 +01:00
}
}
$this->loadVariants();
$this->loadAddressSpaces();
$this->loadPropertyGroups();
$this->loadModules();
$this->loadPeripheralModules();
$this->loadPhysicalInterfaces();
$this->loadPinouts();
}
2022-07-09 14:09:05 +01:00
protected function stringToInt(string $value): ?int
{
2022-07-09 14:09:05 +01:00
return stristr($value, '0x') !== false
? (int) hexdec($value)
: (strlen($value) > 0 ? (int) $value : null);
}
2021-06-01 23:54:04 +01:00
private function loadVariants(): void
{
$variantElements = $this->xml->xpath('variants/variant');
2021-06-01 23:54:04 +01:00
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'])
2022-07-09 14:09:05 +01:00
? $this->stringToInt($addressSpaceAttrs['start']) : null;
$addressSpace->size = isset($addressSpaceAttrs['size'])
2022-07-09 14:09:05 +01:00
? $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'])
2022-07-09 14:09:05 +01:00
? $this->stringToInt($memorySegmentAttrs['start']) : null;
$memorySegment->type = isset($memorySegmentAttrs['type']) ? $memorySegmentAttrs['type'] : null;
$memorySegment->size = isset($memorySegmentAttrs['size'])
2022-07-09 14:09:05 +01:00
? $this->stringToInt($memorySegmentAttrs['size']) : null;
$memorySegment->pageSize = isset($memorySegmentAttrs['pagesize'])
2022-07-09 14:09:05 +01:00
? $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'])
2022-07-09 14:09:05 +01:00
? $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'])
2022-07-09 14:09:05 +01:00
? $this->stringToInt($registerAttrs['offset']) : null;
$register->size = isset($registerAttrs['size'])
2022-07-09 14:09:05 +01:00
? $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;
2022-07-09 14:09:05 +01:00
$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;
if (stristr($pinout->name, Pinout::TYPE_DIP) !== false) {
$pinout->type = Pinout::TYPE_DIP;
} else if (stristr($pinout->name, Pinout::TYPE_SOIC) !== false) {
$pinout->type = Pinout::TYPE_SOIC;
} else if (stristr($pinout->name, Pinout::TYPE_SSOP) !== false) {
$pinout->type = Pinout::TYPE_SSOP;
} else if (stristr($pinout->name, Pinout::TYPE_QFN) !== false) {
$pinout->type = Pinout::TYPE_QFN;
} else if (stristr($pinout->name, Pinout::TYPE_QFP) !== false) {
$pinout->type = Pinout::TYPE_QFP;
} else if (stristr($pinout->name, Pinout::TYPE_BGA) !== false) {
$pinout->type = Pinout::TYPE_BGA;
}
// 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;
2022-07-09 14:09:05 +01:00
$pin->position = isset($pinAttrs['position']) ? $this->stringToInt($pinAttrs['position']) : null;
$pinout->pins[] = $pin;
}
$this->pinoutsMappedByName[strtolower($pinout->name)] = $pinout;
}
}
2021-06-01 23:54:04 +01:00
public function validate(): array
{
$failures = [];
if (empty($this->targetName)) {
$failures[] = 'Target name not found';
}
if (empty($this->targetArchitecture)) {
$failures[] = 'Target architecture not found';
}
if (empty($this->variants)) {
2021-06-05 22:43:03 +01:00
$failures[] = 'Missing target variants';
2021-06-01 23:54:04 +01:00
}
2021-06-05 22:43:03 +01:00
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.';
}
2021-06-05 22:43:03 +01:00
}
2021-06-01 23:54:04 +01:00
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, [Pinout::TYPE_DIP, Pinout::TYPE_QFN, Pinout::TYPE_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, [Pinout::TYPE_SOIC, Pinout::TYPE_DIP, Pinout::TYPE_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, [Pinout::TYPE_QFN, Pinout::TYPE_QFP]) && count($pinout->pins) % 4 != 0) {
$failures[] = 'QFP/QFN pinout (' . $pinout->name . ') pin count is not a multiple of four.';
}
}
2021-06-01 23:54:04 +01:00
return $failures;
}
}