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['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; } }