diff --git a/.gitignore b/.gitignore index 8d2662e..ac4da65 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,5 @@ .build Output Scripts/__pycache__ -docs/README.md \ No newline at end of file +docs/README.md +.worktrees \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..c9d64c5 --- /dev/null +++ b/README.md @@ -0,0 +1,288 @@ +# HALGEN-ARM + +> Hardware Abstraction Layer Generator for ARM Cortex-M microcontrollers + +HALGEN is a Swift code-generation tool that reads CMSIS-SVD (System View Description) XML files for ARM Cortex-M microcontrollers and produces type-safe Swift register-access code targeting Swift Embedded. The generated code uses `@inline(__always)` memory-mapped I/O with zero-overhead volatile access. + +**Currently supports**: Microchip ATSAMD21E18A (Cortex-M0+) + +## Quick Start + +```bash +# Build and run the generator +./generate.sh +``` + +This builds the `SwiftARMGeneratorCLI` Xcode target (Release, arm64) and runs it with defaults: +- Input: `./SVDs/` (finds all `.svd` files) +- Output: `./Output//` + +Output is a Swift Package Manager module per chip with generated peripheral code. + +## Architecture + +### Data Flow + +``` +SVDs/*.svd ──[SVDDecoder]──> SVDDevice (model) + │ +docs/.json ──[ChipDocumentationLoader]──> Supplemental docs +docs/general.json │ + ▼ +GeneratorRegistry ──[GenerationPipeline]──> [PeripheralGenerator] × N + │ + ▼ + GeneratedCodeFile[] + │ + ▼ + Output//Sources// +``` + +### Key Components + +| Component | File | Role | +|-----------|------|------| +| **CLI** | `CLI/main.swift` | Argument parsing, SVD discovery, decode→generate→export pipeline | +| **SVD Model** | `SVD/SVD.swift` | Pure Swift structs mirroring CMSIS-SVD schema (`SVDDevice`, `SVDPeripheral`, `SVDRegister`, `SVDField`, `SVDCluster`) | +| **SVD Parser** | `SVD/SVDDecoder.swift` | Custom XML parser (no external XML dependency for ARM path) | +| **Code Generator** | `Generators/SVDCodeGenerator.swift` | Top-level orchestrator: loads docs, runs pipeline, adds static support files, exports | +| **Pipeline** | `Generators/GenerationPipeline.swift` | Iterates all registered generators, matches to device via `supports(device:)` | +| **Registry** | `Generators/GeneratorRegistry.swift` | Static list of all active peripheral generators | +| **Peripheral Protocol** | `Generators/PeripheralGenerator.swift` | Protocol: `name`, `subdirectory`, `supports(device:)`, `generate(device:documentation:)` | +| **Register Generator** | `CodeGeneration/RegisterGenerator.swift` | Shared helper: generates Swift computed properties for registers (8/16/32-bit, volatile R/W, write-one-to-clear) | +| **Bitfield Generator** | `CodeGeneration/BitfieldGenerator.swift` | Shared helper: generates bitfield sub-accessors (Bool/UInt32/UInt8/UInt16, typed enums) | +| **Documentation Loader** | `Documentation/ChipDocumentationLoader.swift` | Loads `docs/.json` + `docs/general.json`, resolves scoped supplemental data for registers/bitfields/enum values. Tracks missing data for quality reporting. | +| **Boilerplate** | `Documentation/BoilerplateTemplate.swift` | Loads template `.swift` files from `docs/boilerplate/` with `{{placeholder}}` substitution | +| **Code Formatter** | `CodeGeneration/Linter/` | SwiftSyntax-based formatting of generated output | +| **Utils** | `CodeGeneration/Utils.swift` | Naming conventions (camelCase conversion, reserved word handling), documentation comment builders | + +### Peripheral Generators + +Each peripheral gets its own generator struct conforming to `PeripheralGenerator`: + +| Generator | File | Detects | Output | +|-----------|------|---------|--------| +| GPIO | `Generators/Peripherals/GPIO.swift` | `PORT` peripheral | Port groups (PORTA, PORTB), register properties, DigitalPin typealiases | +| SERCOM | `Generators/Peripherals/SERCOM.swift` | SERCOM instances | One file per instance (USART/I2C/SPI modules from cluster-based register layout) | +| GCLK | `Generators/Peripherals/GenericClockController.swift` | `GCLK` peripheral | Clock generator config, source/generator/channel ID enums | +| PM | `Generators/Peripherals/PowerManager.swift` | `PM` peripheral | Sleep modes, clock prescalers, peripheral clock mask registers | +| DMAC | `Generators/Peripherals/DMAC.swift` | `DMAC` peripheral | DMA controller registers, SRAM descriptor layout types | + +## Adding a New Peripheral Generator + +1. **Create the generator** in `SwiftARMGenerator/Generators/Peripherals/`. Implement the `PeripheralGenerator` protocol: + ```swift + struct MyNewGenerator: PeripheralGenerator { + let name: String = "MyNew" + let subdirectory: String = "module" + + func supports(device: SVDDevice) -> Bool { + device.peripherals.contains { $0.name == "MY_NEW" } + } + + func generate(device: SVDDevice, documentation: ChipDocumentationLoader) -> [GeneratedCodeFile] { + guard let peripheral = device.peripherals.first(where: { $0.name == "MY_NEW" }) else { + return [] + } + // Build code using shared helpers: + // - generateSVDRegister() for register properties + // - ChipDocumentationLoader.supplementalData(for:) for docs + // - generatedFileHeader() for file preamble + // Return [GeneratedCodeFile] + } + } + ``` + +2. **Register it** in `Generators/GeneratorRegistry.swift`: + ```swift + static let allGenerators: [PeripheralGenerator] = [ + GPIOGenerator(), + GenericClockControllerGenerator(), + PowerManagerGenerator(), + SERCOMGenerator(), + DMACGenerator(), + MyNewGenerator() // <-- add here + ] + ``` + +3. **Add supplemental documentation** (optional but recommended): + - Add register/bitfield overrides to `docs/.json` + - Add shared patterns to `docs/general.json` + - Keys support scoped resolution: `PERIPHERAL.register`, `register.bitfield`, `PERIPHERAL.register.bitfield` + +## Supplemental Documentation System + +Two JSON files feed the `ChipDocumentationLoader` to override raw SVD descriptions: + +### `docs/.json` — Chip-specific overrides +```json +{ + "chip": "ATSAMD21E18A", + "datasheet": "https://...", + "board": { "ramSize": 32768, "flashSize": 262144, "cpuFrequency": 48000000 }, + "registers": { + "GCLK.CLKCTRL": { + "variableName": "clockControl", + "documentation": ["Controls clock generator selection and output."], + "access": "read-write" + } + }, + "bitfields": { + "GCLK.CLKCTRL.GEN": { + "variableName": "generatorID", + "valueType": "UInt8", + "documentation": ["Selects which clock generator drives this channel."] + } + }, + "enumValues": { + "GCLK.CLKCTRL.GEN": { + "GCLKGEN0": { "caseName": "generator0", "documentation": ["Generator 0"] } + } + } +} +``` + +### `docs/general.json` — Cross-chip reusable patterns +```json +{ + "registers": [ + { "aliases": ["CTRLA"], "variableName": "controlA", "valueType": "UInt32", "access": "read-write" } + ], + "bitfields": [] +} +``` + +**Key resolution order**: Scoped keys are checked from most specific to least specific: +- `PERIPHERAL.register` → `register` → (chip docs then general docs aliases) +- `PERIPHERAL.register.bitfield` → `register.bitfield` → `bitfield` + +If no supplemental data is found, the system auto-generates a camelCase name from the SVD field name and logs it as "missing" in the generation report. + +## Code Generation Patterns + +### Register properties +Generated as computed static properties using `@inline(__always)`: +```swift +@inline(__always) +static var controlA: UInt32 { + get { + _volatileRegisterReadUInt32(baseAddress + 0x00) + } + set { + _volatileRegisterWriteUInt32(baseAddress + 0x00, newValue) + } +} +``` + +8-bit and 16-bit registers use aligned 32-bit reads with masking and shifting. + +### Bitfield accessors +Nested computed properties on the register struct (after the register property): +```swift +@inline(__always) +static var enable: Bool { + get { (controlA & (UInt32(1) << 0)) != 0 } + set { controlA = newValue ? (controlA | (UInt32(1) << 0)) : (controlA & ~(UInt32(1) << 0))) } +} +``` + +### Static support files +The generator copies `docs/corearm-package/Sources/CoreARM/` files into the output module: +- `Port.swift` — `AtomicPort` protocol, `Bit` types, `DigitalPin`, `PartialPort` +- `DigitalValue.swift` — `DigitalValue` (high/low abstraction) + +### Boilerplate templates +Files in `docs/boilerplate/` use `{{placeholder}}` syntax for substitution (e.g., DMAC enum cases). + +## Build & Run Details + +### Build (Xcode) +```bash +xcodebuild -project SwiftARMGenerator.xcodeproj \ + -scheme SwiftARMGeneratorCLI \ + -configuration Release \ + -destination "platform=macOS,arch=arm64" \ + -derivedDataPath .build +``` + +### Run directly +```bash +.build/Build/Products/Release/SwiftARMGeneratorCLI \ + --input SVDs/ATSAMD21E18A.svd \ + --output Output/ +``` + +### CLI Flags +| Flag | Description | +|------|-------------| +| `--input ` | SVD file or directory (default: `./SVDs/`) | +| `--output ` | Output directory (default: `./Output/`) | +| `--help`, `-h` | Show usage | + +## Dependencies + +Resolved via Swift Package Manager inside Xcode: +- **SwiftSyntax** + **SwiftParser** — code formatting/linting of generated Swift output + +## Key Conventions + +1. **Protocol-driven generation**: Each peripheral is a separate `PeripheralGenerator` struct. Adding one = new file + registry entry. +2. **Named documentation keys**: Register/bitfield overrides use dot-separated scoped keys. The loader resolves most-specific → least-specific. +3. **Volatile memory access**: Generated code calls helper functions (`_volatileRegisterReadUInt32`/`_volatileRegisterWriteUInt32`) that map to raw pointer access with volatile semantics. +4. **All generated accessors are `@inline(__always)` and `static`** — no struct instantiation needed. +5. **Legacy AVR code** exists in `GenerationApi.swift` (ATDF decoding, export) but is inactive for the ARM path. + +## Project State (May 2026) + +- **5 peripheral generators** complete: GPIO, SERCOM, GCLK, PM, DMAC +- **1 chip supported**: ATSAMD21E18A (SAMD21 series) +- **Active development**: DMAC just added, register access pattern recently refactored +- **No automated tests** — validation via SwiftSyntax linting of generated output + manual review + +## Directory Reference + +``` +SwiftARMGenerator/ +├── CLI/main.swift Entry point +├── SVD/ +│ ├── SVD.swift CMSIS-SVD data model +│ └── SVDDecoder.swift Custom XML → SVD parser +├── Generators/ +│ ├── SVDCodeGenerator.swift Orchestrator (docs + pipeline + export) +│ ├── GenerationPipeline.swift Runs all matching peripheral generators +│ ├── GeneratorRegistry.swift Static list of active generators +│ ├── PeripheralGenerator.swift Protocol definition +│ ├── GenerationApi.swift Legacy AVR code (inactive for ARM) +│ └── Peripherals/ +│ ├── GPIO.swift +│ ├── SERCOM.swift +│ ├── GenericClockController.swift +│ ├── PowerManager.swift +│ └── DMAC.swift +├── CodeGeneration/ +│ ├── RegisterGenerator.swift Register property codegen +│ ├── BitfieldGenerator.swift Bitfield accessor codegen +│ ├── SupplementalData.swift Data types for doc overrides +│ ├── Utils.swift Naming, documentation helpers +│ └── Linter/ SwiftSyntax formatting +├── Documentation/ +│ ├── ChipDocumentation.swift Codable models for JSON docs +│ ├── ChipDocumentationLoader.swift Loader + resolution + missing-data tracking +│ ├── GeneralDocumentation.swift Model for shared patterns +│ └── BoilerplateTemplate.swift .swift template loading + substitution +└── Extensions/ + └── Extensions.swift String helpers (hex, validation) + +docs/ +├── ATSAMD21E18A.json Chip-specific supplemental data +├── general.json Cross-chip reusable patterns +├── boilerplate/ .swift templates with {{substitution}} +└── corearm-package/ Static support files copied to output module + └── Sources/CoreARM/ + ├── CoreARM.swift Port, Bit, DigitalPin protocols + ├── DigitalValue.swift + └── Utilities.swift + +SVDs/ +└── ATSAMD21E18A.svd Input CMSIS-SVD XML +``` diff --git a/SwiftARMGenerator.xcodeproj/project.pbxproj b/SwiftARMGenerator.xcodeproj/project.pbxproj index f8387e5..d384349 100644 --- a/SwiftARMGenerator.xcodeproj/project.pbxproj +++ b/SwiftARMGenerator.xcodeproj/project.pbxproj @@ -16,11 +16,15 @@ EEA200000000000000000004 /* PeripheralGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEA100000000000000000004 /* PeripheralGenerator.swift */; }; EEA200000000000000000005 /* GPIO.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEA100000000000000000005 /* GPIO.swift */; }; EEA200000000000000000006 /* SERCOM.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEA100000000000000000006 /* SERCOM.swift */; }; + EEA200000000000000000009 /* GenericClockController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEA100000000000000000009 /* GenericClockController.swift */; }; + EEA20000000000000000000A /* PowerManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEA10000000000000000000A /* PowerManager.swift */; }; + EEA20000000000000000000B /* DMAC.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEA10000000000000000000B /* DMAC.swift */; }; EEA200000000000000000007 /* RegisterGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEA100000000000000000007 /* RegisterGenerator.swift */; }; EEA200000000000000000008 /* BitfieldGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEA100000000000000000008 /* BitfieldGenerator.swift */; }; 09BDF2C899F873D13C4A098E /* ChipDocumentation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71ADE5ED95CCFC3E5786867C /* ChipDocumentation.swift */; }; B9580F2C21ABD3F64DCAEFA6 /* GeneralDocumentation.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0D9CC48062F501F0F172000 /* GeneralDocumentation.swift */; }; D40F4C67532C14CF194495F3 /* ChipDocumentationLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9627A9CD5EBA9BBFC607292 /* ChipDocumentationLoader.swift */; }; + EEA20000000000000000000C /* BoilerplateTemplate.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEA10000000000000000000C /* BoilerplateTemplate.swift */; }; 664A3209249F4495D654F6BF /* SupplementalData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4612798196F827810A9E48C7 /* SupplementalData.swift */; }; 8E493230C04C212E71266B0B /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBD5722218460EE82C126620 /* Utils.swift */; }; /* End PBXBuildFile section */ @@ -35,11 +39,15 @@ EEA100000000000000000004 /* PeripheralGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftARMGenerator/Generators/PeripheralGenerator.swift; sourceTree = SOURCE_ROOT; }; EEA100000000000000000005 /* GPIO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftARMGenerator/Generators/Peripherals/GPIO.swift; sourceTree = SOURCE_ROOT; }; EEA100000000000000000006 /* SERCOM.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftARMGenerator/Generators/Peripherals/SERCOM.swift; sourceTree = SOURCE_ROOT; }; + EEA100000000000000000009 /* GenericClockController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftARMGenerator/Generators/Peripherals/GenericClockController.swift; sourceTree = SOURCE_ROOT; }; + EEA10000000000000000000A /* PowerManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftARMGenerator/Generators/Peripherals/PowerManager.swift; sourceTree = SOURCE_ROOT; }; + EEA10000000000000000000B /* DMAC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftARMGenerator/Generators/Peripherals/DMAC.swift; sourceTree = SOURCE_ROOT; }; EEA100000000000000000007 /* RegisterGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftARMGenerator/CodeGeneration/RegisterGenerator.swift; sourceTree = SOURCE_ROOT; }; EEA100000000000000000008 /* BitfieldGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftARMGenerator/CodeGeneration/BitfieldGenerator.swift; sourceTree = SOURCE_ROOT; }; 71ADE5ED95CCFC3E5786867C /* ChipDocumentation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftARMGenerator/Documentation/ChipDocumentation.swift; sourceTree = SOURCE_ROOT; }; F0D9CC48062F501F0F172000 /* GeneralDocumentation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftARMGenerator/Documentation/GeneralDocumentation.swift; sourceTree = SOURCE_ROOT; }; E9627A9CD5EBA9BBFC607292 /* ChipDocumentationLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftARMGenerator/Documentation/ChipDocumentationLoader.swift; sourceTree = SOURCE_ROOT; }; + EEA10000000000000000000C /* BoilerplateTemplate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftARMGenerator/Documentation/BoilerplateTemplate.swift; sourceTree = SOURCE_ROOT; }; 4612798196F827810A9E48C7 /* SupplementalData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftARMGenerator/CodeGeneration/SupplementalData.swift; sourceTree = SOURCE_ROOT; }; EBD5722218460EE82C126620 /* Utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftARMGenerator/CodeGeneration/Utils.swift; sourceTree = SOURCE_ROOT; }; EECLI0192F600000000000001 /* SwiftARMGeneratorCLI */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = SwiftARMGeneratorCLI; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -181,9 +189,13 @@ EEA200000000000000000008 /* BitfieldGenerator.swift in Sources */, EEA200000000000000000005 /* GPIO.swift in Sources */, EEA200000000000000000006 /* SERCOM.swift in Sources */, + EEA200000000000000000009 /* GenericClockController.swift in Sources */, + EEA20000000000000000000A /* PowerManager.swift in Sources */, + EEA20000000000000000000B /* DMAC.swift in Sources */, 09BDF2C899F873D13C4A098E /* ChipDocumentation.swift in Sources */, B9580F2C21ABD3F64DCAEFA6 /* GeneralDocumentation.swift in Sources */, D40F4C67532C14CF194495F3 /* ChipDocumentationLoader.swift in Sources */, + EEA20000000000000000000C /* BoilerplateTemplate.swift in Sources */, 664A3209249F4495D654F6BF /* SupplementalData.swift in Sources */, 8E493230C04C212E71266B0B /* Utils.swift in Sources */, ); diff --git a/SwiftARMGenerator/CodeGeneration/BitfieldGenerator.swift b/SwiftARMGenerator/CodeGeneration/BitfieldGenerator.swift index f809afd..b0d68fd 100644 --- a/SwiftARMGenerator/CodeGeneration/BitfieldGenerator.swift +++ b/SwiftARMGenerator/CodeGeneration/BitfieldGenerator.swift @@ -30,3 +30,175 @@ func svdBitfieldGetterExpression(parentExpression: String, bitOffset: Int, bitWi return "(\(parentExpression) >> \(bitOffset)) & \(hexLiteral(svdBitfieldMask(bitWidth: bitWidth)))" } + +func generateSVDBitfieldAccessors( + register: SVDRegister, + parentExpression: String, + indentation: Int, + writeBehavior: SVDRegisterWriteBehavior = .normal, + bitfieldData: (_ field: SVDField) -> SupplementalBitfieldData +) -> String { + guard register.fields.isEmpty == false else { + return "" + } + + let spaces = String(repeating: " ", count: indentation) + var code = "" + + for field in register.fields { + guard let bitOffset = field.bitOffset, + let bitWidth = field.bitWidth else { + continue + } + + let supplementalBitfieldData = bitfieldData(field) + let fieldName = supplementalBitfieldData.variableName.isEmpty + ? svdBitfieldVariableName(for: field) + : supplementalBitfieldData.variableName + + let fieldType = supplementalBitfieldData.valueType.isEmpty + ? svdBitfieldAccessorType(bitWidth: bitWidth) + : supplementalBitfieldData.valueType + let fieldDocumentation = svdBitfieldDocumentation( + field: field, + supplementalData: supplementalBitfieldData, + indentation: indentation + ) + let getterExpr = svdBitfieldGetterSource( + fieldType: fieldType, + defaultValue: supplementalBitfieldData.defaultValue, + parentExpression: parentExpression, + bitOffset: bitOffset, + bitWidth: bitWidth + ) + let setterExpr = supplementalBitfieldData.access == .read || svdAccessIsReadOnly(register.access) + ? nil + : svdBitfieldSetterSource( + fieldType: fieldType, + parentExpression: parentExpression, + bitOffset: bitOffset, + bitWidth: bitWidth, + writeBehavior: writeBehavior + ) + let getterBody = svdIndent(getterExpr, by: indentation + 8) + let setterBody = setterExpr.map { svdIndent($0, by: indentation + 8) } + + code += """ + \(fieldDocumentation) + \(spaces)@inline(\(supplementalBitfieldData.inline)) + \(spaces)static var \(fieldName): \(fieldType) { + \(spaces) get { + \(getterBody) + \(spaces) } + \(setterBody.map { """ + \(spaces) set { + \($0) + \(spaces) } + """ } ?? "") + \(spaces)} + + """ + } + + return code +} + +private func svdBitfieldDocumentation( + field: SVDField, + supplementalData: SupplementalBitfieldData, + indentation: Int +) -> String { + if supplementalData.isMissing == false, + supplementalData.documentation.isEmpty == false { + return svdIndent(makeDocumentationComment(body: supplementalData.documentation), by: indentation) + } + + return String(repeating: " ", count: indentation) + "/// \(field.description ?? field.name)" +} + +private func svdBitfieldVariableName(for field: SVDField) -> String { + swiftMemberIdentifier(from: field.name) +} + +private func svdBitfieldGetterSource( + fieldType: String, + defaultValue: String, + parentExpression: String, + bitOffset: Int, + bitWidth: Int +) -> String { + switch fieldType { + case "Bool": + return "(\(parentExpression) & (UInt32(1) << \(bitOffset))) != 0" + case "UInt32": + return "(\(parentExpression) >> \(bitOffset)) & \(hexLiteral(svdBitfieldMask(bitWidth: bitWidth)))" + case "UInt16": + return "UInt16((\(parentExpression) >> \(bitOffset)) & \(hexLiteral(svdBitfieldMask(bitWidth: bitWidth))))" + case "UInt8": + return "UInt8((\(parentExpression) >> \(bitOffset)) & \(hexLiteral(svdBitfieldMask(bitWidth: bitWidth))))" + default: + let mask = hexLiteral(svdBitfieldMask(bitWidth: bitWidth)) + guard defaultValue.isEmpty == false else { + return """ + let value = (\(parentExpression) >> \(bitOffset)) & \(mask) + return \(fieldType)(rawValue: value)! + """ + } + + return """ + let value = (\(parentExpression) >> \(bitOffset)) & \(mask) + return \(fieldType)(rawValue: value) ?? .\(defaultValue) + """ + } +} + +private func svdBitfieldSetterSource( + fieldType: String, + parentExpression: String, + bitOffset: Int, + bitWidth: Int, + writeBehavior: SVDRegisterWriteBehavior +) -> String { + if writeBehavior == .writeOneToClear { + return svdDirectBitfieldSetterSource( + fieldType: fieldType, + parentExpression: parentExpression, + bitOffset: bitOffset, + bitWidth: bitWidth + ) + } + + switch fieldType { + case "Bool": + return "\(parentExpression) = newValue ? (\(parentExpression) | (UInt32(1) << \(bitOffset))) : (\(parentExpression) & ~(UInt32(1) << \(bitOffset)))" + case "UInt32": + let mask = hexLiteral(svdBitfieldMask(bitWidth: bitWidth)) + return "\(parentExpression) = (\(parentExpression) & ~(UInt32(\(mask)) << \(bitOffset))) | ((newValue & \(mask)) << \(bitOffset))" + case "UInt8", "UInt16": + let mask = hexLiteral(svdBitfieldMask(bitWidth: bitWidth)) + return "\(parentExpression) = (\(parentExpression) & ~(UInt32(\(mask)) << \(bitOffset))) | ((UInt32(newValue) & \(mask)) << \(bitOffset))" + default: + let mask = hexLiteral(svdBitfieldMask(bitWidth: bitWidth)) + return "\(parentExpression) = (\(parentExpression) & ~(UInt32(\(mask)) << \(bitOffset))) | ((newValue.rawValue & \(mask)) << \(bitOffset))" + } +} + +private func svdDirectBitfieldSetterSource( + fieldType: String, + parentExpression: String, + bitOffset: Int, + bitWidth: Int +) -> String { + let mask = hexLiteral(svdBitfieldMask(bitWidth: bitWidth)) + + switch fieldType { + case "Bool": + return "if newValue { \(parentExpression) = UInt32(1) << \(bitOffset) }" + case "UInt32": + return "if newValue != 0 { \(parentExpression) = (newValue & \(mask)) << \(bitOffset) }" + case "UInt8", "UInt16": + return "if newValue != 0 { \(parentExpression) = (UInt32(newValue) & \(mask)) << \(bitOffset) }" + default: + return "if newValue.rawValue != 0 { \(parentExpression) = (newValue.rawValue & \(mask)) << \(bitOffset) }" + } +} diff --git a/SwiftARMGenerator/CodeGeneration/RegisterGenerator.swift b/SwiftARMGenerator/CodeGeneration/RegisterGenerator.swift index 2445cfb..1c481ae 100644 --- a/SwiftARMGenerator/CodeGeneration/RegisterGenerator.swift +++ b/SwiftARMGenerator/CodeGeneration/RegisterGenerator.swift @@ -12,6 +12,74 @@ enum SVDRegisterWriteBehavior { case writeOneToClear } +/// Generates a Swift property for an SVD register and all supported bitfield accessors. +/// +/// This mirrors the AVR generator shape: peripheral generators decide which registers +/// to include, while the shared register/bitfield generators do the repetitive source +/// construction from SVD plus supplemental documentation data. +func generateSVDRegister( + register: SVDRegister, + baseName: String, + indentation: Int, + writeBehavior: SVDRegisterWriteBehavior = .normal, + registerData: (_ register: SVDRegister) -> SupplementalRegisterData, + bitfieldData: (_ field: SVDField) -> SupplementalBitfieldData +) -> String { + guard let addressOffset = register.addressOffset else { + return "" + } + + let supplementalRegisterData = registerData(register) + let variableName = supplementalRegisterData.variableName.isEmpty + ? svdRegisterVariableName(for: register) + : supplementalRegisterData.variableName + + let access = supplementalRegisterData.access.isEmpty ? register.access ?? "read-write" : supplementalRegisterData.access + let registerSize = register.size ?? 32 + let addressExpression = svdRegisterAddressExpression(baseName: baseName, offset: addressOffset) + let spaces = String(repeating: " ", count: indentation) + let getter = svdIndent( + buildSVDRegisterGetter( + addressExpression: addressExpression, + offset: addressOffset, + registerSize: registerSize + ), + by: indentation + 4 + ) + let setter = svdAccessIsReadOnly(access) ? "" : svdIndent( + buildSVDRegisterSetter( + addressExpression: addressExpression, + offset: addressOffset, + registerSize: registerSize, + writeBehavior: writeBehavior + ), + by: indentation + 4 + ) + let registerDocumentation = svdRegisterDocumentation( + register: register, + supplementalData: supplementalRegisterData, + indentation: indentation + ) + let bitfields = generateSVDBitfieldAccessors( + register: register, + parentExpression: variableName, + indentation: indentation, + writeBehavior: writeBehavior, + bitfieldData: bitfieldData + ) + + return """ + \(registerDocumentation) + \(spaces)@inline(__always) + \(spaces)static var \(variableName): UInt32 { + \(getter) + \(setter) + \(spaces)} + + \(bitfields) + """ +} + func buildSVDRegisterGetter(addressExpression: String, offset: UInt64, registerSize: Int) -> String { switch registerSize { case 8: @@ -163,3 +231,40 @@ func svdIndent(_ text: String, by spaces: Int) -> String { } .joined(separator: "\n") } + +func svdAccessIsReadOnly(_ access: String?) -> Bool { + switch access?.lowercased() { + case "read-only", "r": + return true + default: + return false + } +} + +func svdRegisterDocumentation( + register: SVDRegister, + supplementalData: SupplementalRegisterData, + indentation: Int +) -> String { + if supplementalData.isMissing == false, + supplementalData.documentation.isEmpty == false { + return svdIndent(makeDocumentationComment(body: supplementalData.documentation), by: indentation) + "\n" + } + + let description = register.description ?? register.name + var lines = ["/// \(description) - \(register.name)"] + + for field in register.fields { + guard let fieldDescription = field.description else { + continue + } + + lines.append("/// \(field.name): \(fieldDescription)") + } + + return svdIndent(lines.joined(separator: "\n"), by: indentation) + "\n" +} + +func svdRegisterVariableName(for register: SVDRegister) -> String { + swiftMemberIdentifier(from: register.displayName ?? register.name) +} diff --git a/SwiftARMGenerator/CodeGeneration/SupplementalData.swift b/SwiftARMGenerator/CodeGeneration/SupplementalData.swift index fb05b5b..87f34ad 100644 --- a/SwiftARMGenerator/CodeGeneration/SupplementalData.swift +++ b/SwiftARMGenerator/CodeGeneration/SupplementalData.swift @@ -30,6 +30,12 @@ struct SupplementalBitfieldData { var inline: String = "__always" var splitTargetLSB: String? = nil var overrideGeneratedDocumentation: Bool = false + var isMissing: Bool = false +} + +struct SupplementalEnumValueData { + let caseName: String + let documentation: String } enum Access: String { diff --git a/SwiftARMGenerator/CodeGeneration/Utils.swift b/SwiftARMGenerator/CodeGeneration/Utils.swift index adcd277..558e3f4 100644 --- a/SwiftARMGenerator/CodeGeneration/Utils.swift +++ b/SwiftARMGenerator/CodeGeneration/Utils.swift @@ -154,6 +154,114 @@ func getVariableName(caption: String) -> String { return ([leadingToken] + remainingTokens).joined() } +func swiftMemberIdentifier(from rawValue: String) -> String { + let identifier = swiftTypeIdentifier(from: rawValue.trimmingCharacters(in: CharacterSet(charactersIn: "_"))) + let parts = identifier + .split(separator: "_") + .map(String.init) + .filter { $0.isEmpty == false } + + guard let first = parts.first else { + return "_" + } + + let memberName = ([swiftLowerCamelIdentifier(from: first)] + parts.dropFirst().map(swiftUpperCamelIdentifier(from:))).joined() + guard swiftReservedWords.contains(memberName) == false else { + return "\(memberName)Value" + } + + return memberName +} + +func swiftLowerCamelIdentifier(from rawValue: String) -> String { + switch rawValue { + case "RUNSTDBY": + return "runStdby" + default: + return rawValue.lowercased() + } +} + +func swiftUpperCamelIdentifier(from rawValue: String) -> String { + let cleaned = rawValue.trimmingCharacters(in: CharacterSet(charactersIn: "_")) + if cleaned == "ENABLE" { + return "Enable" + } + + guard cleaned.contains("_") else { + return cleaned + } + + return cleaned + .split(separator: "_") + .map { part in + let lowercased = part.lowercased() + guard let first = lowercased.first else { + return "" + } + + return String(first).uppercased() + lowercased.dropFirst() + } + .joined() +} + +let swiftReservedWords: Set = [ + "associatedtype", + "class", + "deinit", + "enum", + "extension", + "fileprivate", + "func", + "import", + "init", + "inout", + "internal", + "let", + "open", + "operator", + "private", + "precedencegroup", + "protocol", + "public", + "rethrows", + "static", + "struct", + "subscript", + "super", + "typealias", + "var", + "break", + "case", + "continue", + "default", + "defer", + "do", + "else", + "fallthrough", + "for", + "guard", + "if", + "in", + "repeat", + "return", + "switch", + "where", + "while", + "as", + "any", + "catch", + "false", + "is", + "nil", + "self", + "Self", + "throw", + "throws", + "true", + "try" +] + /// Adds Padding to strings for documentation. This is intended to be used for centering text in mono-spaced ASCII tables. /// - Parameter input: String of 7 characters or less. /// - Returns: A string of 7 characters, if the input string had more than 7 characters it should be unchanged. diff --git a/SwiftARMGenerator/Documentation/ChipDocumentation.swift b/SwiftARMGenerator/Documentation/ChipDocumentation.swift index aa8a584..d28ba78 100644 --- a/SwiftARMGenerator/Documentation/ChipDocumentation.swift +++ b/SwiftARMGenerator/Documentation/ChipDocumentation.swift @@ -13,6 +13,7 @@ struct ChipDocumentation: Codable { let board: Board? let registers: [String: Register] let bitfields: [String: Bitfield] + let enumValues: [String: [String: EnumValue]]? struct Board: Codable { let ramSize: Int? @@ -76,6 +77,18 @@ struct ChipDocumentation: Codable { ) } } + + struct EnumValue: Codable { + let caseName: String? + let documentation: [String]? + + func toSupplementalData() -> SupplementalEnumValueData { + SupplementalEnumValueData( + caseName: caseName ?? "", + documentation: formatDocumentation(documentation) + ) + } + } } func formatDocumentation(_ paragraphs: [String]?) -> String { diff --git a/SwiftARMGenerator/Documentation/ChipDocumentationLoader.swift b/SwiftARMGenerator/Documentation/ChipDocumentationLoader.swift index 6802cb0..14f8733 100644 --- a/SwiftARMGenerator/Documentation/ChipDocumentationLoader.swift +++ b/SwiftARMGenerator/Documentation/ChipDocumentationLoader.swift @@ -146,20 +146,59 @@ class ChipDocumentationLoader { } func supplementalData(for register: SVDRegister) -> SupplementalRegisterData { - if let cachedData = registerCache[register.name] { + supplementalData(for: register, candidateKeys: [register.name], cacheKey: register.name) + } + + func supplementalData(for register: SVDRegister, peripheral: SVDPeripheral) -> SupplementalRegisterData { + let candidates = scopedRegisterKeys(registerName: register.name, peripheral: peripheral) + return supplementalData( + for: register, + candidateKeys: candidates, + cacheKey: candidates.first ?? register.name + ) + } + + func supplementalData(for field: SVDField) -> SupplementalBitfieldData { + supplementalData(for: field, candidateKeys: [field.name], cacheKey: field.name) + } + + func supplementalData(for field: SVDField, register: SVDRegister, peripheral: SVDPeripheral) -> SupplementalBitfieldData { + let candidates = scopedBitfieldKeys(fieldName: field.name, registerName: register.name, peripheral: peripheral) + return supplementalData( + for: field, + candidateKeys: candidates, + cacheKey: candidates.first ?? field.name + ) + } + + func supplementalData(for value: SVDEnumeratedValue, field: SVDField, register: SVDRegister, peripheral: SVDPeripheral) -> SupplementalEnumValueData? { + let candidates = scopedEnumValueKeys(fieldName: field.name, registerName: register.name, peripheral: peripheral) + for key in candidates { + guard let enumValue = chipDocumentation?.enumValues?[key]?[value.name] else { + continue + } + + return enumValue.toSupplementalData() + } + + return nil + } + + private func supplementalData(for register: SVDRegister, candidateKeys: [String], cacheKey: String) -> SupplementalRegisterData { + if let cachedData = registerCache[cacheKey] { return cachedData } - let chipDocs = chipDocumentation?.registers[register.name] - let generalDocs = generalRegister(for: register.name) + let chipDocs = firstChipRegister(for: candidateKeys) + let generalDocs = firstGeneralRegister(for: candidateKeys) guard chipDocs != nil || generalDocs != nil else { let fallbackData = missingSupplementalData(for: register) - registerCache[register.name] = fallbackData + registerCache[cacheKey] = fallbackData return fallbackData } - let fallbackVariableName = getVariableName(caption: register.description ?? register.name) + let fallbackVariableName = swiftMemberIdentifier(from: register.displayName ?? register.name) let resolvedData = SupplementalRegisterData( variableName: preferredVariableName(chipDocs?.variableName, generalDocs?.variableName, fallback: fallbackVariableName), valueType: chipDocs?.valueType ?? generalDocs?.valueType ?? "", @@ -174,25 +213,25 @@ class ChipDocumentationLoader { overrideGeneratedDocumentation: chipDocs?.overrideGeneratedDocumentation ?? false ) - registerCache[register.name] = resolvedData + registerCache[cacheKey] = resolvedData return resolvedData } - func supplementalData(for field: SVDField) -> SupplementalBitfieldData { - if let cachedData = bitfieldCache[field.name] { + private func supplementalData(for field: SVDField, candidateKeys: [String], cacheKey: String) -> SupplementalBitfieldData { + if let cachedData = bitfieldCache[cacheKey] { return cachedData } - let chipDocs = chipDocumentation?.bitfields[field.name] - let generalDocs = generalBitfield(for: field.name) + let chipDocs = firstChipBitfield(for: candidateKeys) + let generalDocs = firstGeneralBitfield(for: candidateKeys) guard chipDocs != nil || generalDocs != nil else { let fallbackData = missingSupplementalData(for: field) - bitfieldCache[field.name] = fallbackData + bitfieldCache[cacheKey] = fallbackData return fallbackData } - let fallbackVariableName = getVariableName(caption: field.description ?? field.name) + let fallbackVariableName = swiftMemberIdentifier(from: field.name) let rawValueType = chipDocs?.valueType ?? generalDocs?.valueType ?? "" let inferredValueType = inferValueTypeIfNeeded(rawValueType, field: field) let resolvedData = SupplementalBitfieldData( @@ -206,7 +245,7 @@ class ChipDocumentationLoader { overrideGeneratedDocumentation: chipDocs?.overrideGeneratedDocumentation ?? false ) - bitfieldCache[field.name] = resolvedData + bitfieldCache[cacheKey] = resolvedData return resolvedData } @@ -222,12 +261,65 @@ class ChipDocumentationLoader { ) } - private func generalRegister(for alias: String) -> GeneralRegister? { - generalDocumentation?.registers.first { $0.aliases.contains(alias) } + private func firstChipRegister(for candidateKeys: [String]) -> ChipDocumentation.Register? { + candidateKeys.lazy.compactMap { self.chipDocumentation?.registers[$0] }.first + } + + private func firstChipBitfield(for candidateKeys: [String]) -> ChipDocumentation.Bitfield? { + candidateKeys.lazy.compactMap { self.chipDocumentation?.bitfields[$0] }.first + } + + private func firstGeneralRegister(for candidateKeys: [String]) -> GeneralRegister? { + generalDocumentation?.registers.first { register in + candidateKeys.contains { register.aliases.contains($0) } + } + } + + private func firstGeneralBitfield(for candidateKeys: [String]) -> GeneralBitfield? { + generalDocumentation?.bitfields.first { bitfield in + candidateKeys.contains { bitfield.aliases.contains($0) } + } } - private func generalBitfield(for alias: String) -> GeneralBitfield? { - generalDocumentation?.bitfields.first { $0.aliases.contains(alias) } + private func scopedRegisterKeys(registerName: String, peripheral: SVDPeripheral) -> [String] { + uniqueKeys([ + "\(peripheral.name).\(registerName)", + peripheral.groupName.map { "\($0).\(registerName)" }, + registerName + ]) + } + + private func scopedBitfieldKeys(fieldName: String, registerName: String, peripheral: SVDPeripheral) -> [String] { + uniqueKeys([ + "\(peripheral.name).\(registerName).\(fieldName)", + peripheral.groupName.map { "\($0).\(registerName).\(fieldName)" }, + "\(registerName).\(fieldName)", + fieldName + ]) + } + + private func scopedEnumValueKeys(fieldName: String, registerName: String, peripheral: SVDPeripheral) -> [String] { + uniqueKeys([ + "\(peripheral.name).\(registerName).\(fieldName)", + peripheral.groupName.map { "\($0).\(registerName).\(fieldName)" }, + "\(registerName).\(fieldName)", + fieldName + ]) + } + + private func uniqueKeys(_ rawKeys: [String?]) -> [String] { + var seen: Set = [] + var keys: [String] = [] + + for rawKey in rawKeys { + guard let key = rawKey, seen.insert(key).inserted else { + continue + } + + keys.append(key) + } + + return keys } private func preferredVariableName(_ primary: String?, _ secondary: String?, fallback: String) -> String { @@ -272,7 +364,7 @@ class ChipDocumentationLoader { } private func missingSupplementalData(for register: SVDRegister) -> SupplementalRegisterData { - let suggestedVariableName = getVariableName(caption: register.description ?? register.name) + let suggestedVariableName = swiftMemberIdentifier(from: register.displayName ?? register.name) let peripheralName = activePeripheralName() var missingRegisters = missingRegistersByPeripheral[peripheralName] ?? [:] @@ -298,7 +390,7 @@ class ChipDocumentationLoader { } private func missingSupplementalData(for field: SVDField) -> SupplementalBitfieldData { - let suggestedVariableName = getVariableName(caption: field.description ?? field.name) + let suggestedVariableName = swiftMemberIdentifier(from: field.name) let peripheralName = activePeripheralName() var missingBitfields = missingBitfieldsByPeripheral[peripheralName] ?? [:] @@ -318,7 +410,8 @@ class ChipDocumentationLoader { valueType: "", defaultValue: "", documentation: "", - access: accessFromSVD(field.access ?? "read-write") + access: accessFromSVD(field.access ?? "read-write"), + isMissing: true ) } diff --git a/SwiftARMGenerator/Generators/GeneratorRegistry.swift b/SwiftARMGenerator/Generators/GeneratorRegistry.swift index 8749977..e8c5c2a 100644 --- a/SwiftARMGenerator/Generators/GeneratorRegistry.swift +++ b/SwiftARMGenerator/Generators/GeneratorRegistry.swift @@ -8,6 +8,9 @@ struct GeneratorRegistry { static let allGenerators: [PeripheralGenerator] = [ GPIOGenerator(), - SERCOMGenerator() + GenericClockControllerGenerator(), + PowerManagerGenerator(), + SERCOMGenerator(), + DMACGenerator() ] } diff --git a/SwiftARMGenerator/Generators/Peripherals/DMAC.swift b/SwiftARMGenerator/Generators/Peripherals/DMAC.swift new file mode 100644 index 0000000..15e0157 --- /dev/null +++ b/SwiftARMGenerator/Generators/Peripherals/DMAC.swift @@ -0,0 +1,480 @@ +// +// DMAC.swift +// SwiftARMGeneratorCLI +// +// Created by HALGEN on 05/21/2026. +// + +import Foundation + +struct DMACGenerator: PeripheralGenerator { + let name = "DMAC" + let subdirectory = "module" + + func supports(device: SVDDevice) -> Bool { + dmacPeripheral(in: device) != nil + } + + func generate(device: SVDDevice) -> [GeneratedCodeFile] { + guard let peripheral = dmacPeripheral(in: device) else { + return [] + } + + return [buildDMACFile(device: device, peripheral: peripheral, documentation: nil, documentationDirectory: nil)] + } + + func generate(device: SVDDevice, documentation: ChipDocumentationLoader) -> [GeneratedCodeFile] { + guard let peripheral = dmacPeripheral(in: device) else { + return [] + } + + return [ + buildDMACFile( + device: device, + peripheral: peripheral, + documentation: documentation, + documentationDirectory: documentation.directory + ) + ] + } +} + +// MARK: - Peripheral Detection + +private func dmacPeripheral(in device: SVDDevice) -> SVDPeripheral? { + device.peripherals.first { $0.name == "DMAC" || $0.groupName == "DMAC" } +} + +// MARK: - File Builder + +private func buildDMACFile( + device: SVDDevice, + peripheral: SVDPeripheral, + documentation: ChipDocumentationLoader?, + documentationDirectory: URL? +) -> GeneratedCodeFile { + let fileName = "DMAC.swift" + let moduleName = device.generatedSwiftModuleName + let baseName = "DMAC_BASE" + let baseAddress = peripheral.baseAddress ?? 0 + let registers = peripheral.registers.map(dmacRegisterForGeneration) + let fieldUsage = dmacFieldUsage(in: registers, peripheral: peripheral, documentation: documentation) + let registerVariableNames = Set(registers.map { register in + dmacRegisterData(for: register, peripheral: peripheral, documentation: documentation).variableName + }) + + var code = generatedFileHeader( + fileName: fileName, + moduleName: moduleName, + description: "Generated by Swift ARM Generator from \(device.name)." + ) + + code += "@usableFromInline let \(baseName): UInt = \(hexLiteral(baseAddress, minimumDigits: 8)) // SAMD21 DMAC base address\n\n" + + code += """ + /// Direct Memory Access Controller. + /// + /// Provides the DMAC peripheral registers plus the SRAM descriptor layout used by + /// BASEADDR and WRBADDR. Enable the DMAC clocks in PowerManager before touching + /// these registers. + struct DMAC { + + """ + + code += generateDMACSupportingTypes(device: device, documentationDirectory: documentationDirectory) + + for register in registers { + code += dmacRegisterSectionHeader(for: register) + code += generateDMACRegister( + register: register, + baseName: baseName, + indentation: 4, + writeBehavior: dmacWriteBehavior(for: register), + registerData: { register in + dmacRegisterData(for: register, peripheral: peripheral, documentation: documentation) + }, + bitfieldData: { field in + dmacBitfieldData( + for: field, + register: register, + peripheral: peripheral, + fieldUsage: fieldUsage, + registerVariableNames: registerVariableNames, + documentation: documentation + ) + } + ) + } + + code += "}\n" + + return GeneratedCodeFile(fileName: fileName, content: code, subdirectory: "module") +} + +// MARK: - Supporting Types + +private func generateDMACSupportingTypes(device: SVDDevice, documentationDirectory: URL?) -> String { + guard let documentationDirectory else { + return "" + } + + return BoilerplateTemplate.render( + named: "DMACSupportingTypes.swift", + documentationDirectory: documentationDirectory, + substitutions: ["triggerCases": generateDMACTriggerSourceCases(device: device)] + ) +} + +private func generateDMACTriggerSourceCases(device: SVDDevice) -> String { + var code = "" + + for instance in dmacSERCOMInstances(in: device) { + let receiveValue = UInt32(1 + instance.index * 2) + let transmitValue = UInt32(2 + instance.index * 2) + let lowerName = instance.name.lowercased() + code += " case \(lowerName)Receive = \(receiveValue)\n" + code += " case \(lowerName)Transmit = \(transmitValue)\n" + } + + return code +} + +private struct DMACSERCOMInstance { + let name: String + let index: Int +} + +private func dmacSERCOMInstances(in device: SVDDevice) -> [DMACSERCOMInstance] { + device.peripherals + .compactMap { peripheral -> DMACSERCOMInstance? in + guard peripheral.name.hasPrefix("SERCOM"), + let index = Int(peripheral.name.dropFirst("SERCOM".count)) else { + return nil + } + + return DMACSERCOMInstance(name: peripheral.name, index: index) + } + .sorted { $0.index < $1.index } +} + +// MARK: - Register Generation + +private func generateDMACRegister( + register: SVDRegister, + baseName: String, + indentation: Int, + writeBehavior: SVDRegisterWriteBehavior = .normal, + registerData: (_ register: SVDRegister) -> SupplementalRegisterData, + bitfieldData: (_ field: SVDField) -> SupplementalBitfieldData +) -> String { + guard let addressOffset = register.addressOffset else { + return "" + } + + let supplementalRegisterData = registerData(register) + let variableName = supplementalRegisterData.variableName.isEmpty + ? svdRegisterVariableName(for: register) + : supplementalRegisterData.variableName + + let access = supplementalRegisterData.access.isEmpty ? register.access ?? "read-write" : supplementalRegisterData.access + let registerSize = register.size ?? 32 + let addressExpression = svdRegisterAddressExpression(baseName: baseName, offset: addressOffset) + let spaces = String(repeating: " ", count: indentation) + let getter = svdIndent( + dmacBuildRegisterGetter( + addressExpression: addressExpression, + offset: addressOffset, + registerSize: registerSize + ), + by: indentation + 4 + ) + let setter = svdAccessIsReadOnly(access) ? "" : svdIndent( + dmacBuildRegisterSetter( + addressExpression: addressExpression, + offset: addressOffset, + registerSize: registerSize, + writeBehavior: writeBehavior + ), + by: indentation + 4 + ) + let registerDocumentation = svdRegisterDocumentation( + register: register, + supplementalData: supplementalRegisterData, + indentation: indentation + ) + let bitfields = generateSVDBitfieldAccessors( + register: register, + parentExpression: variableName, + indentation: indentation, + writeBehavior: writeBehavior, + bitfieldData: bitfieldData + ) + + return """ + \(registerDocumentation) + \(spaces)@inline(__always) + \(spaces)static var \(variableName): UInt32 { + \(getter) + \(setter) + \(spaces)} + + \(bitfields) + """ +} + +private func dmacBuildRegisterGetter(addressExpression: String, offset: UInt64, registerSize: Int) -> String { + switch registerSize { + case 8: + return """ + get { + UInt32(UnsafeMutablePointer(bitPattern: \(addressExpression))!.pointee) + } + """ + case 16: + return """ + get { + UInt32(UnsafeMutablePointer(bitPattern: \(addressExpression))!.pointee) + } + """ + default: + return """ + get { + UnsafeMutablePointer(bitPattern: \(addressExpression))!.pointee + } + """ + } +} + +private func dmacBuildRegisterSetter( + addressExpression: String, + offset: UInt64, + registerSize: Int, + writeBehavior: SVDRegisterWriteBehavior +) -> String { + if writeBehavior == .writeOneToClear { + return dmacBuildDirectRegisterSetter( + addressExpression: addressExpression, + offset: offset, + registerSize: registerSize + ) + } + + switch registerSize { + case 8: + return """ + set { + UnsafeMutablePointer(bitPattern: \(addressExpression))!.pointee = UInt8(newValue & 0xFF) + } + """ + case 16: + return """ + set { + UnsafeMutablePointer(bitPattern: \(addressExpression))!.pointee = UInt16(newValue & 0xFFFF) + } + """ + default: + return """ + set { + UnsafeMutablePointer(bitPattern: \(addressExpression))!.pointee = newValue + } + """ + } +} + +private func dmacBuildDirectRegisterSetter(addressExpression: String, offset: UInt64, registerSize: Int) -> String { + switch registerSize { + case 8: + return """ + set { + UnsafeMutablePointer(bitPattern: \(addressExpression))!.pointee = UInt8(newValue & 0xFF) + } + """ + case 16: + return """ + set { + UnsafeMutablePointer(bitPattern: \(addressExpression))!.pointee = UInt16(newValue & 0xFFFF) + } + """ + default: + return """ + set { + UnsafeMutablePointer(bitPattern: \(addressExpression))!.pointee = newValue + } + """ + } +} + +// MARK: - Register Generation Data + +private func dmacRegisterForGeneration(_ register: SVDRegister) -> SVDRegister { + guard register.fields.count == 1, + let field = register.fields.first, + field.bitOffset == 0, + field.bitWidth == register.size else { + return register + } + + return SVDRegister( + name: register.name, + derivedFrom: register.derivedFrom, + displayName: register.displayName, + description: register.description, + alternateGroup: register.alternateGroup, + alternateRegister: register.alternateRegister, + addressOffset: register.addressOffset, + size: register.size, + access: register.access, + protection: register.protection, + resetValue: register.resetValue, + resetMask: register.resetMask, + dim: register.dim, + dimIncrement: register.dimIncrement, + dimIndex: register.dimIndex, + fields: [] + ) +} + +private func dmacRegisterSectionHeader(for register: SVDRegister) -> String { + let offset = register.addressOffset.map { hexLiteral($0) } ?? "unknown" + let size = register.size ?? 32 + let description = register.description ?? "Register" + + return " // MARK: - \(register.name) - \(description) (Offset \(offset), \(size)-bit)\n\n" +} + +private func dmacRegisterData( + for register: SVDRegister, + peripheral: SVDPeripheral, + documentation: ChipDocumentationLoader? +) -> SupplementalRegisterData { + if let supplementalData = documentation?.supplementalData(for: register, peripheral: peripheral), + supplementalData.isMissing == false { + return supplementalData + } + + return SupplementalRegisterData( + variableName: swiftMemberIdentifier(from: register.displayName ?? register.name), + valueType: "UInt32", + defaultValue: "", + documentation: "", + access: "", + isMissing: true + ) +} + +private func dmacBitfieldData( + for field: SVDField, + register: SVDRegister, + peripheral: SVDPeripheral, + fieldUsage: [String: Int], + registerVariableNames: Set, + documentation: ChipDocumentationLoader? +) -> SupplementalBitfieldData { + if let supplementalData = documentation?.supplementalData(for: field, register: register, peripheral: peripheral), + supplementalData.isMissing == false { + return supplementalData + } + + return SupplementalBitfieldData( + variableName: dmacBitfieldVariableName( + for: field, + register: register, + fieldUsage: fieldUsage, + registerVariableNames: registerVariableNames, + peripheral: peripheral, + documentation: documentation + ), + valueType: "", + defaultValue: "", + documentation: "", + access: dmacAccess(from: field.access ?? register.access ?? "read-write"), + isMissing: true + ) +} + +private func dmacWriteBehavior(for register: SVDRegister) -> SVDRegisterWriteBehavior { + switch register.name { + case "SWTRIGCTRL", "CHINTENCLR", "CHINTENSET", "CHINTFLAG": + return .writeOneToClear + default: + return .normal + } +} + +private func dmacAccess(from access: String) -> Access { + switch access.lowercased() { + case "read-only", "r": + return .read + case "write-only", "w": + return .write + default: + return .readWrite + } +} + +private func dmacBitfieldVariableName( + for field: SVDField, + register: SVDRegister, + fieldUsage: [String: Int], + registerVariableNames: Set, + peripheral: SVDPeripheral, + documentation: ChipDocumentationLoader? +) -> String { + let fieldName = dmacBitfieldBaseName( + for: field, + register: register, + peripheral: peripheral, + documentation: documentation + ) + let registerName = dmacRegisterData(for: register, peripheral: peripheral, documentation: documentation).variableName + let needsRegisterPrefix = (fieldUsage[fieldName] ?? 0) > 1 + || fieldName == registerName + || registerVariableNames.contains(fieldName) + + guard needsRegisterPrefix else { + return fieldName + } + + return "\(registerName)\(dmacUpperCamelIdentifier(from: fieldName))" +} + +private func dmacFieldUsage( + in registers: [SVDRegister], + peripheral: SVDPeripheral, + documentation: ChipDocumentationLoader? +) -> [String: Int] { + registers.reduce(into: [:]) { counts, register in + for field in register.fields { + let fieldName = dmacBitfieldBaseName( + for: field, + register: register, + peripheral: peripheral, + documentation: documentation + ) + counts[fieldName, default: 0] += 1 + } + } +} + +private func dmacBitfieldBaseName( + for field: SVDField, + register: SVDRegister, + peripheral: SVDPeripheral, + documentation: ChipDocumentationLoader? +) -> String { + if let supplementalData = documentation?.supplementalData(for: field, register: register, peripheral: peripheral), + supplementalData.isMissing == false, + supplementalData.variableName.isEmpty == false { + return supplementalData.variableName + } + + return swiftMemberIdentifier(from: field.name) +} + +private func dmacUpperCamelIdentifier(from memberName: String) -> String { + guard let first = memberName.first else { + return memberName + } + + return String(first).uppercased() + memberName.dropFirst() +} diff --git a/SwiftARMGenerator/Generators/Peripherals/GenericClockController.swift b/SwiftARMGenerator/Generators/Peripherals/GenericClockController.swift new file mode 100644 index 0000000..5f6336c --- /dev/null +++ b/SwiftARMGenerator/Generators/Peripherals/GenericClockController.swift @@ -0,0 +1,340 @@ +// +// GenericClockController.swift +// SwiftARMGeneratorCLI +// +// Created by HALGEN on 05/05/2026. +// + +import Foundation + +struct GenericClockControllerGenerator: PeripheralGenerator { + let name = "GenericClockController" + let subdirectory = "module" + + func supports(device: SVDDevice) -> Bool { + guard let peripheral = gclkPeripheral(in: device) else { + return false + } + + return gclkHasRequiredRegisters(peripheral) + } + + func generate(device: SVDDevice) -> [GeneratedCodeFile] { + guard let peripheral = gclkPeripheral(in: device), gclkHasRequiredRegisters(peripheral) else { + return [] + } + return [buildGCLKFile(device: device, peripheral: peripheral, documentation: nil)] + } + + func generate(device: SVDDevice, documentation: ChipDocumentationLoader) -> [GeneratedCodeFile] { + guard let peripheral = gclkPeripheral(in: device), gclkHasRequiredRegisters(peripheral) else { + return [] + } + return [buildGCLKFile(device: device, peripheral: peripheral, documentation: documentation)] + } +} + +// MARK: - Peripheral Detection + +private func gclkPeripheral(in device: SVDDevice) -> SVDPeripheral? { + device.peripherals.first { $0.name == "GCLK" || $0.groupName == "GCLK" } +} + +private let gclkRequiredRegisterNames: Set = ["CTRL", "STATUS", "CLKCTRL", "GENCTRL", "GENDIV"] + +private func gclkHasRequiredRegisters(_ peripheral: SVDPeripheral) -> Bool { + gclkRequiredRegisterNames.allSatisfy { name in + peripheral.registers.contains { $0.name == name } + } +} + +// MARK: - File Builder + +private func buildGCLKFile( + device: SVDDevice, + peripheral: SVDPeripheral, + documentation: ChipDocumentationLoader? +) -> GeneratedCodeFile { + let fileName = "GenericClockController.swift" + let moduleName = device.generatedSwiftModuleName + let baseName = "GCLK_BASE" + let baseAddress = peripheral.baseAddress ?? 0 + let registers = peripheral.registers + + var code = generatedFileHeader( + fileName: fileName, + moduleName: moduleName, + description: "Generated by Swift ARM Generator from \(device.name)." + ) + + code += "@usableFromInline let \(baseName): UInt = \(hexLiteral(baseAddress, minimumDigits: 8)) // SAMD21 datasheet §15.8\n\n" + + code += """ + /// Generic Clock Controller + /// + /// The Generic Clock Controller (GCLK) manages the distribution of clocks from various sources + /// to the peripherals on the SAMD21. It consists of Generic Clock Generators (0–8), each of + /// which can be independently configured with a clock source and division factor. Each generator's + /// output can then be routed to one or more peripheral clock inputs via the CLKCTRL register. + /// + /// Typical setup sequence: + /// 1. Configure a Generator: write GENDIV (division factor), then write GENCTRL (source + enable). + /// 2. Wait for SYNCBUSY to clear. + /// 3. Route the Generator to a peripheral: write CLKCTRL (generator + peripheral ID + enable). + /// + /// See SAMD21 datasheet Section 15. + struct GenericClockController { + + """ + + code += generateEnums(peripheral: peripheral, documentation: documentation) + + for register in registers where gclkRequiredRegisterNames.contains(register.name) { + code += gclkRegisterSectionHeader(for: register) + code += generateSVDRegister( + register: register, + baseName: baseName, + indentation: 4, + registerData: { register in + gclkRegisterData(for: register, peripheral: peripheral, documentation: documentation) + }, + bitfieldData: { field in + gclkBitfieldData(for: field, register: register, peripheral: peripheral, documentation: documentation) + } + ) + } + + code += "}\n" + + return GeneratedCodeFile(fileName: fileName, content: code, subdirectory: "module") +} + +// MARK: - Enum Generation + +private func generateEnums(peripheral: SVDPeripheral, documentation: ChipDocumentationLoader?) -> String { + var code = " // MARK: - Supporting Enums\n\n" + code += generateGeneratorEnum(peripheral: peripheral, documentation: documentation) + code += generateClockSourceEnum(peripheral: peripheral, documentation: documentation) + code += generatePeripheralClockIDEnum(peripheral: peripheral, documentation: documentation) + code += generateDivideSelectionEnum() + return code +} + +private func generateGeneratorEnum(peripheral: SVDPeripheral, documentation: ChipDocumentationLoader?) -> String { + var code = "" + code += " /// Generic Clock Generator selection.\n" + code += " /// The SAMD21 provides nine independent Generic Clock Generators (0 through 8).\n" + code += " /// Generator 0 is the main clock generator and has a 16-bit division register;\n" + code += " /// Generators 1–8 have 8-bit division registers.\n" + code += " enum Generator: UInt32 {\n" + + if let context = gclkEnumContext(fieldName: "GEN", registerName: "CLKCTRL", in: peripheral) { + for value in sortedEnumValues(context.field) { + guard let rawValue = value.value else { + continue + } + + let swiftName = gclkEnumCaseName(for: value, context: context, documentation: documentation) + code += " case \(swiftName) = \(rawValue)\n" + } + } + + code += " }\n\n" + return code +} + +private func generateClockSourceEnum(peripheral: SVDPeripheral, documentation: ChipDocumentationLoader?) -> String { + var code = "" + code += " /// Clock source selection for a Generic Clock Generator (GENCTRL.SRC).\n" + code += " /// See SAMD21 datasheet Section 15.8.4 and Table 15-1.\n" + code += " enum ClockSource: UInt32 {\n" + + if let context = gclkEnumContext(fieldName: "SRC", registerName: "GENCTRL", in: peripheral) { + for value in sortedEnumValues(context.field) { + guard let rawValue = value.value else { + continue + } + + let swiftName = gclkEnumCaseName(for: value, context: context, documentation: documentation) + let description = gclkEnumCaseDocumentation(for: value, context: context, documentation: documentation) + code += " /// \(description)\n" + code += " case \(swiftName) = \(hexLiteral(rawValue))\n" + } + } + + code += " }\n\n" + return code +} + +private func generatePeripheralClockIDEnum(peripheral: SVDPeripheral, documentation: ChipDocumentationLoader?) -> String { + var code = "" + code += " /// Peripheral clock input selection for CLKCTRL.ID.\n" + code += " /// Identifies which peripheral's generic clock input to configure.\n" + code += " /// See SAMD21 datasheet Section 15.8.3 and Table 15-2.\n" + code += " enum PeripheralClockID: UInt32 {\n" + + if let context = gclkEnumContext(fieldName: "ID", registerName: "CLKCTRL", in: peripheral) { + for value in sortedEnumValues(context.field) { + guard let rawValue = value.value else { + continue + } + + let swiftName = gclkEnumCaseName(for: value, context: context, documentation: documentation) + let description = gclkEnumCaseDocumentation(for: value, context: context, documentation: documentation) + code += " /// \(description)\n" + code += " case \(swiftName) = \(hexLiteral(rawValue))\n" + } + } + + code += " }\n\n" + return code +} + +private func generateDivideSelectionEnum() -> String { + return """ + /// Division mode for a Generic Clock Generator (GENCTRL.DIVSEL). + /// Controls how the GENDIV.DIV value is interpreted. + /// See SAMD21 datasheet Section 15.8.4. + enum DivideSelection: UInt32 { + /// Linear division: f_out = f_src / (DIV+1), or f_src if DIV = 0. + case linear = 0 + /// Power-of-two division: f_out = f_src / 2^(DIV+1). + case powerOfTwo = 1 + } + + + """ +} + +// MARK: - Register Generation Data + +private func gclkRegisterSectionHeader(for register: SVDRegister) -> String { + switch register.name { + case "CTRL": + return """ + // MARK: - CTRL – Control Register (Offset 0x00, 8-bit) + // + // CTRL, STATUS, and CLKCTRL share one 32-bit-aligned word at GCLK_BASE. + // Little-endian 32-bit layout at GCLK_BASE: + // Bits [7:0] = CTRL (offset 0x00, R/W 8-bit) + // Bits [15:8] = STATUS (offset 0x01, R 8-bit) + // Bits [31:16] = CLKCTRL (offset 0x02, R/W 16-bit) + // + // Each register property below performs a 32-bit read-modify-write so that + // it only affects its own field and leaves the others intact. + + + """ + case "GENCTRL": + return " // MARK: - GENCTRL – Generic Clock Generator Control (Offset 0x04, 32-bit)\n\n" + case "GENDIV": + return " // MARK: - GENDIV – Generic Clock Generator Division (Offset 0x08, 32-bit)\n\n" + default: + return "" + } +} + +private func gclkRegisterData( + for register: SVDRegister, + peripheral: SVDPeripheral, + documentation: ChipDocumentationLoader? +) -> SupplementalRegisterData { + if let documentation { + return documentation.supplementalData(for: register, peripheral: peripheral) + } + + return SupplementalRegisterData( + variableName: swiftMemberIdentifier(from: register.displayName ?? register.name), + valueType: "UInt32", + defaultValue: "", + documentation: "", + access: register.access ?? "read-write", + isMissing: true + ) +} + +private func gclkBitfieldData( + for field: SVDField, + register: SVDRegister, + peripheral: SVDPeripheral, + documentation: ChipDocumentationLoader? +) -> SupplementalBitfieldData { + if let documentation { + return documentation.supplementalData(for: field, register: register, peripheral: peripheral) + } + + return SupplementalBitfieldData( + variableName: swiftMemberIdentifier(from: field.name), + valueType: field.bitWidth == 1 ? "Bool" : "UInt32", + defaultValue: "", + documentation: "", + access: gclkAccess(from: field.access ?? "read-write"), + isMissing: true + ) +} + +private func gclkAccess(from access: String) -> Access { + switch access.lowercased() { + case "read-only", "r": + return .read + case "write-only", "w": + return .write + default: + return .readWrite + } +} + +// MARK: - SVD Helpers + +private struct GCLKEnumContext { + let peripheral: SVDPeripheral + let register: SVDRegister + let field: SVDField +} + +private func gclkEnumContext(fieldName: String, registerName: String, in peripheral: SVDPeripheral) -> GCLKEnumContext? { + guard let register = peripheral.registers.first(where: { $0.name == registerName }) else { + return nil + } + guard let field = register.fields.first(where: { $0.name == fieldName }) else { + return nil + } + + return GCLKEnumContext(peripheral: peripheral, register: register, field: field) +} + +private func sortedEnumValues(_ field: SVDField) -> [SVDEnumeratedValue] { + (field.enumeratedValues.first?.values ?? []).sorted { lhs, rhs in + (lhs.value ?? 0) < (rhs.value ?? 0) + } +} + +private func gclkEnumCaseName( + for value: SVDEnumeratedValue, + context: GCLKEnumContext, + documentation: ChipDocumentationLoader? +) -> String { + if let caseName = documentation? + .supplementalData(for: value, field: context.field, register: context.register, peripheral: context.peripheral)? + .caseName, + caseName.isEmpty == false { + return caseName + } + + return swiftMemberIdentifier(from: value.name) +} + +private func gclkEnumCaseDocumentation( + for value: SVDEnumeratedValue, + context: GCLKEnumContext, + documentation: ChipDocumentationLoader? +) -> String { + if let enumDocs = documentation? + .supplementalData(for: value, field: context.field, register: context.register, peripheral: context.peripheral)? + .documentation, + enumDocs.isEmpty == false { + return enumDocs + } + + return value.description ?? value.name +} diff --git a/SwiftARMGenerator/Generators/Peripherals/PowerManager.swift b/SwiftARMGenerator/Generators/Peripherals/PowerManager.swift new file mode 100644 index 0000000..800903b --- /dev/null +++ b/SwiftARMGenerator/Generators/Peripherals/PowerManager.swift @@ -0,0 +1,191 @@ +// +// PowerManager.swift +// SwiftARMGeneratorCLI +// +// Created by HALGEN on 05/11/2026. +// + +import Foundation + +struct PowerManagerGenerator: PeripheralGenerator { + let name = "PowerManager" + let subdirectory = "module" + + func supports(device: SVDDevice) -> Bool { + pmPeripheral(in: device) != nil + } + + func generate(device: SVDDevice) -> [GeneratedCodeFile] { + guard let peripheral = pmPeripheral(in: device) else { + return [] + } + + return [buildPowerManagerFile(device: device, peripheral: peripheral, documentation: nil)] + } + + func generate(device: SVDDevice, documentation: ChipDocumentationLoader) -> [GeneratedCodeFile] { + guard let peripheral = pmPeripheral(in: device) else { + return [] + } + + return [buildPowerManagerFile(device: device, peripheral: peripheral, documentation: documentation)] + } +} + +// MARK: - Peripheral Detection + +private func pmPeripheral(in device: SVDDevice) -> SVDPeripheral? { + device.peripherals.first { $0.name == "PM" || $0.groupName == "PM" } +} + +// MARK: - File Builder + +private func buildPowerManagerFile( + device: SVDDevice, + peripheral: SVDPeripheral, + documentation: ChipDocumentationLoader? +) -> GeneratedCodeFile { + let fileName = "PowerManager.swift" + let moduleName = device.generatedSwiftModuleName + let baseName = "PM_BASE" + let baseAddress = peripheral.baseAddress ?? 0 + + var code = generatedFileHeader( + fileName: fileName, + moduleName: moduleName, + description: "Generated by Swift ARM Generator from \(device.name)." + ) + + code += "@usableFromInline let \(baseName): UInt = \(hexLiteral(baseAddress, minimumDigits: 8)) // SAMD21 datasheet Section 16.8\n\n" + + code += """ + /// Power Manager + /// + /// Controls sleep mode, CPU/APB clock prescalers, peripheral clock masks, + /// clock-ready interrupts, and reset-cause flags. + /// + /// See SAMD21 datasheet Section 16. + struct PowerManager { + + """ + + code += generatePMEnums() + + for register in peripheral.registers { + code += pmRegisterSectionHeader(for: register) + code += generateSVDRegister( + register: register, + baseName: baseName, + indentation: 4, + writeBehavior: pmWriteBehavior(for: register), + registerData: { register in + pmRegisterData(for: register, peripheral: peripheral, documentation: documentation) + }, + bitfieldData: { field in + pmBitfieldData(for: field, register: register, peripheral: peripheral, documentation: documentation) + } + ) + } + + code += "}\n" + + return GeneratedCodeFile(fileName: fileName, content: code, subdirectory: "module") +} + +// MARK: - Enum Generation + +private func generatePMEnums() -> String { + """ + // MARK: - Supporting Enums + + /// Idle sleep mode depth (SLEEP.IDLE). + enum IdleMode: UInt32 { + case cpu = 0 + case ahb = 1 + case apb = 2 + } + + /// Clock prescaler division for the CPU, APBA, APBB, and APBC buses. + enum ClockDivider: UInt32 { + case div1 = 0 + case div2 = 1 + case div4 = 2 + case div8 = 3 + case div16 = 4 + case div32 = 5 + case div64 = 6 + case div128 = 7 + } + + + """ +} + +// MARK: - Register Generation Data + +private func pmRegisterSectionHeader(for register: SVDRegister) -> String { + let offset = register.addressOffset.map { hexLiteral($0) } ?? "unknown" + let size = register.size ?? 32 + let description = register.description ?? "Register" + + return " // MARK: - \(register.name) - \(description) (Offset \(offset), \(size)-bit)\n\n" +} + +private func pmRegisterData( + for register: SVDRegister, + peripheral: SVDPeripheral, + documentation: ChipDocumentationLoader? +) -> SupplementalRegisterData { + if let documentation { + return documentation.supplementalData(for: register, peripheral: peripheral) + } + + return SupplementalRegisterData( + variableName: swiftMemberIdentifier(from: register.displayName ?? register.name), + valueType: "UInt32", + defaultValue: "", + documentation: "", + access: register.access ?? "read-write", + isMissing: true + ) +} + +private func pmBitfieldData( + for field: SVDField, + register: SVDRegister, + peripheral: SVDPeripheral, + documentation: ChipDocumentationLoader? +) -> SupplementalBitfieldData { + if let documentation { + return documentation.supplementalData(for: field, register: register, peripheral: peripheral) + } + + return SupplementalBitfieldData( + variableName: swiftMemberIdentifier(from: field.name), + valueType: field.bitWidth == 1 ? "Bool" : "UInt32", + defaultValue: "", + documentation: "", + access: pmAccess(from: field.access ?? register.access ?? "read-write"), + isMissing: true + ) +} + +private func pmWriteBehavior(for register: SVDRegister) -> SVDRegisterWriteBehavior { + switch register.name { + case "INTENCLR", "INTENSET", "INTFLAG": + return .writeOneToClear + default: + return .normal + } +} + +private func pmAccess(from access: String) -> Access { + switch access.lowercased() { + case "read-only", "r": + return .read + case "write-only", "w": + return .write + default: + return .readWrite + } +} diff --git a/SwiftARMGenerator/Generators/Peripherals/SERCOM.swift b/SwiftARMGenerator/Generators/Peripherals/SERCOM.swift index 4350cf2..7a68b6c 100644 --- a/SwiftARMGenerator/Generators/Peripherals/SERCOM.swift +++ b/SwiftARMGenerator/Generators/Peripherals/SERCOM.swift @@ -547,111 +547,3 @@ private func bitfieldAccessorName( return "\(registerVariableName)\(swiftUpperCamelIdentifier(from: cleanedFieldName))" } - -private func swiftMemberIdentifier(from rawValue: String) -> String { - let identifier = swiftTypeIdentifier(from: cleanedSVDName(rawValue)) - let parts = identifier - .split(separator: "_") - .map(String.init) - .filter { $0.isEmpty == false } - - guard let first = parts.first else { - return "_" - } - - let memberName = ([swiftLowerCamelIdentifier(from: first)] + parts.dropFirst().map(swiftUpperCamelIdentifier(from:))).joined() - guard swiftReservedWords.contains(memberName) == false else { - return "\(memberName)Value" - } - - return memberName -} - -private func swiftLowerCamelIdentifier(from rawValue: String) -> String { - switch rawValue { - case "RUNSTDBY": - return "runStdby" - default: - return rawValue.lowercased() - } -} - -private func swiftUpperCamelIdentifier(from rawValue: String) -> String { - let cleaned = cleanedSVDName(rawValue) - if cleaned == "ENABLE" { - return "Enable" - } - - guard cleaned.contains("_") else { - return cleaned - } - - return cleaned - .split(separator: "_") - .map { part in - let lowercased = part.lowercased() - guard let first = lowercased.first else { - return "" - } - - return String(first).uppercased() + lowercased.dropFirst() - } - .joined() -} - -private let swiftReservedWords: Set = [ - "associatedtype", - "class", - "deinit", - "enum", - "extension", - "fileprivate", - "func", - "import", - "init", - "inout", - "internal", - "let", - "open", - "operator", - "private", - "precedencegroup", - "protocol", - "public", - "rethrows", - "static", - "struct", - "subscript", - "super", - "typealias", - "var", - "break", - "case", - "continue", - "default", - "defer", - "do", - "else", - "fallthrough", - "for", - "guard", - "if", - "in", - "repeat", - "return", - "switch", - "where", - "while", - "as", - "any", - "catch", - "false", - "is", - "nil", - "self", - "Self", - "throw", - "throws", - "true", - "try" -] diff --git a/docs/ATSAMD21E18A.json b/docs/ATSAMD21E18A.json index e0cef05..290dd07 100644 --- a/docs/ATSAMD21E18A.json +++ b/docs/ATSAMD21E18A.json @@ -11,7 +11,709 @@ "Port Data Direction register.", "Each bit controls whether the corresponding PORT pin is configured as an input (0) or output (1)." ] + }, + "GCLK.CTRL": { + "variableName": "controlRegister", + "valueType": "UInt32", + "access": "read-write" + }, + "GCLK.STATUS": { + "variableName": "statusRegister", + "valueType": "UInt32", + "access": "read-only" + }, + "GCLK.CLKCTRL": { + "variableName": "clockControlRegister", + "valueType": "UInt32", + "access": "read-write" + }, + "GCLK.GENCTRL": { + "variableName": "generatorControl", + "valueType": "UInt32", + "access": "read-write" + }, + "GCLK.GENDIV": { + "variableName": "generatorDivision", + "valueType": "UInt32", + "access": "read-write" + }, + "PM.CTRL": { + "variableName": "controlRegister", + "valueType": "UInt32", + "access": "read-write" + }, + "PM.SLEEP": { + "variableName": "sleepRegister", + "valueType": "UInt32", + "access": "read-write" + }, + "PM.CPUSEL": { + "variableName": "cpuClockSelectRegister", + "valueType": "UInt32", + "access": "read-write" + }, + "PM.APBASEL": { + "variableName": "apbaClockSelectRegister", + "valueType": "UInt32", + "access": "read-write" + }, + "PM.APBBSEL": { + "variableName": "apbbClockSelectRegister", + "valueType": "UInt32", + "access": "read-write" + }, + "PM.APBCSEL": { + "variableName": "apbcClockSelectRegister", + "valueType": "UInt32", + "access": "read-write" + }, + "PM.AHBMASK": { + "variableName": "ahbMaskRegister", + "valueType": "UInt32", + "access": "read-write" + }, + "PM.APBAMASK": { + "variableName": "apbaMaskRegister", + "valueType": "UInt32", + "access": "read-write" + }, + "PM.APBBMASK": { + "variableName": "apbbMaskRegister", + "valueType": "UInt32", + "access": "read-write" + }, + "PM.APBCMASK": { + "variableName": "apbcMaskRegister", + "valueType": "UInt32", + "access": "read-write" + }, + "PM.INTENCLR": { + "variableName": "interruptEnableClearRegister", + "valueType": "UInt32", + "access": "read-write" + }, + "PM.INTENSET": { + "variableName": "interruptEnableSetRegister", + "valueType": "UInt32", + "access": "read-write" + }, + "PM.INTFLAG": { + "variableName": "interruptFlagRegister", + "valueType": "UInt32", + "access": "read-write" + }, + "PM.RCAUSE": { + "variableName": "resetCauseRegister", + "valueType": "UInt32", + "access": "read-only" + }, + "DMAC.CTRL": { + "variableName": "control", + "valueType": "UInt32", + "access": "read-write" + }, + "DMAC.CRCCTRL": { + "variableName": "crcControl", + "valueType": "UInt32", + "access": "read-write" + }, + "DMAC.CRCDATAIN": { + "variableName": "crcDataInput", + "valueType": "UInt32", + "access": "read-write" + }, + "DMAC.CRCCHKSUM": { + "variableName": "crcChecksum", + "valueType": "UInt32", + "access": "read-write" + }, + "DMAC.CRCSTATUS": { + "variableName": "crcStatus", + "valueType": "UInt32", + "access": "read-write" + }, + "DMAC.DBGCTRL": { + "variableName": "debugControl", + "valueType": "UInt32", + "access": "read-write" + }, + "DMAC.QOSCTRL": { + "variableName": "qosControl", + "valueType": "UInt32", + "access": "read-write" + }, + "DMAC.SWTRIGCTRL": { + "variableName": "softwareTriggerControl", + "valueType": "UInt32", + "access": "read-write" + }, + "DMAC.PRICTRL0": { + "variableName": "priorityControl0", + "valueType": "UInt32", + "access": "read-write" + }, + "DMAC.INTPEND": { + "variableName": "interruptPending", + "valueType": "UInt32", + "access": "read-write" + }, + "DMAC.INTSTATUS": { + "variableName": "interruptStatus", + "valueType": "UInt32", + "access": "read-only" + }, + "DMAC.BUSYCH": { + "variableName": "busyChannels", + "valueType": "UInt32", + "access": "read-only" + }, + "DMAC.PENDCH": { + "variableName": "pendingChannels", + "valueType": "UInt32", + "access": "read-only" + }, + "DMAC.ACTIVE": { + "variableName": "active", + "valueType": "UInt32", + "access": "read-only" + }, + "DMAC.BASEADDR": { + "variableName": "descriptorBaseAddress", + "valueType": "UInt32", + "access": "read-write" + }, + "DMAC.WRBADDR": { + "variableName": "writeBackBaseAddress", + "valueType": "UInt32", + "access": "read-write" + }, + "DMAC.CHID": { + "variableName": "channelID", + "valueType": "UInt32", + "access": "read-write" + }, + "DMAC.CHCTRLA": { + "variableName": "channelControlA", + "valueType": "UInt32", + "access": "read-write" + }, + "DMAC.CHCTRLB": { + "variableName": "channelControlB", + "valueType": "UInt32", + "access": "read-write" + }, + "DMAC.CHINTENCLR": { + "variableName": "channelInterruptEnableClear", + "valueType": "UInt32", + "access": "read-write" + }, + "DMAC.CHINTENSET": { + "variableName": "channelInterruptEnableSet", + "valueType": "UInt32", + "access": "read-write" + }, + "DMAC.CHINTFLAG": { + "variableName": "channelInterruptFlag", + "valueType": "UInt32", + "access": "read-write" + }, + "DMAC.CHSTATUS": { + "variableName": "channelStatus", + "valueType": "UInt32", + "access": "read-only" + } + }, + "bitfields": { + "DMAC.CTRL.SWRST": { + "variableName": "softwareReset", + "valueType": "Bool", + "access": "read-write" + }, + "DMAC.CTRL.DMAENABLE": { + "variableName": "dmaEnable", + "valueType": "Bool", + "access": "read-write" + }, + "DMAC.CTRL.CRCENABLE": { + "variableName": "crcEnable", + "valueType": "Bool", + "access": "read-write" + }, + "DMAC.CTRL.LVLEN0": { + "variableName": "priorityLevel0Enable", + "valueType": "Bool", + "access": "read-write" + }, + "DMAC.CTRL.LVLEN1": { + "variableName": "priorityLevel1Enable", + "valueType": "Bool", + "access": "read-write" + }, + "DMAC.CTRL.LVLEN2": { + "variableName": "priorityLevel2Enable", + "valueType": "Bool", + "access": "read-write" + }, + "DMAC.CTRL.LVLEN3": { + "variableName": "priorityLevel3Enable", + "valueType": "Bool", + "access": "read-write" + }, + "DMAC.CHID.ID": { + "variableName": "selectedChannelID", + "valueType": "UInt32", + "access": "read-write" + }, + "DMAC.INTPEND.ID": { + "variableName": "pendingChannelID", + "valueType": "UInt32", + "access": "read-write" + }, + "DMAC.ACTIVE.ID": { + "variableName": "activeChannelID", + "valueType": "UInt32", + "access": "read-only" + }, + "DMAC.CHCTRLA.SWRST": { + "variableName": "channelSoftwareReset", + "valueType": "Bool", + "access": "read-write" + }, + "DMAC.CHCTRLA.ENABLE": { + "variableName": "channelEnable", + "valueType": "Bool", + "access": "read-write" + }, + "DMAC.CHCTRLB.LVL": { + "variableName": "channelArbitrationLevel", + "valueType": "PriorityLevel", + "defaultValue": "level0", + "access": "read-write" + }, + "DMAC.CHCTRLB.TRIGSRC": { + "variableName": "triggerSource", + "valueType": "TriggerSource", + "defaultValue": "disabled", + "access": "read-write" + }, + "DMAC.CHCTRLB.TRIGACT": { + "variableName": "triggerAction", + "valueType": "TriggerAction", + "defaultValue": "block", + "access": "read-write" + }, + "GCLK.CTRL.SWRST": { + "variableName": "softwareReset", + "valueType": "Bool", + "access": "read-write" + }, + "GCLK.STATUS.SYNCBUSY": { + "variableName": "synchronizationBusy", + "valueType": "Bool", + "access": "read-only" + }, + "GCLK.CLKCTRL.ID": { + "variableName": "peripheralClockID", + "valueType": "PeripheralClockID", + "defaultValue": "dfll48MReference", + "access": "read-write" + }, + "GCLK.CLKCTRL.GEN": { + "variableName": "clockGenerator", + "valueType": "Generator", + "defaultValue": "generator0", + "access": "read-write" + }, + "GCLK.CLKCTRL.CLKEN": { + "variableName": "clockEnable", + "valueType": "Bool", + "access": "read-write" + }, + "GCLK.CLKCTRL.WRTLOCK": { + "variableName": "writeLock", + "valueType": "Bool", + "access": "read-write" + }, + "GCLK.GENCTRL.ID": { + "variableName": "generatorID", + "valueType": "Generator", + "defaultValue": "generator0", + "access": "read-write" + }, + "GCLK.GENCTRL.SRC": { + "variableName": "clockSource", + "valueType": "ClockSource", + "defaultValue": "internalOscillator8M", + "access": "read-write" + }, + "GCLK.GENCTRL.GENEN": { + "variableName": "generatorEnable", + "valueType": "Bool", + "access": "read-write" + }, + "GCLK.GENCTRL.IDC": { + "variableName": "improveDutyCycle", + "valueType": "Bool", + "access": "read-write" + }, + "GCLK.GENCTRL.OOV": { + "variableName": "outputOffValue", + "valueType": "Bool", + "access": "read-write" + }, + "GCLK.GENCTRL.OE": { + "variableName": "outputEnable", + "valueType": "Bool", + "access": "read-write" + }, + "GCLK.GENCTRL.DIVSEL": { + "variableName": "divideSelection", + "valueType": "DivideSelection", + "defaultValue": "linear", + "access": "read-write" + }, + "GCLK.GENCTRL.RUNSTDBY": { + "variableName": "runInStandby", + "valueType": "Bool", + "access": "read-write" + }, + "GCLK.GENDIV.ID": { + "variableName": "divisionGeneratorID", + "valueType": "Generator", + "defaultValue": "generator0", + "access": "read-write" + }, + "GCLK.GENDIV.DIV": { + "variableName": "divisionFactor", + "valueType": "UInt32", + "access": "read-write" + }, + "PM.SLEEP.IDLE": { + "variableName": "idleMode", + "valueType": "IdleMode", + "defaultValue": "cpu", + "access": "read-write" + }, + "PM.CPUSEL.CPUDIV": { + "variableName": "cpuDivider", + "valueType": "ClockDivider", + "defaultValue": "div1", + "access": "read-write" + }, + "PM.APBASEL.APBADIV": { + "variableName": "apbaDivider", + "valueType": "ClockDivider", + "defaultValue": "div1", + "access": "read-write" + }, + "PM.APBBSEL.APBBDIV": { + "variableName": "apbbDivider", + "valueType": "ClockDivider", + "defaultValue": "div1", + "access": "read-write" + }, + "PM.APBCSEL.APBCDIV": { + "variableName": "apbcDivider", + "valueType": "ClockDivider", + "defaultValue": "div1", + "access": "read-write" + }, + "PM.AHBMASK.HPB0_": { + "variableName": "hpb0ClockEnable", + "valueType": "Bool", + "access": "read-write" + }, + "PM.AHBMASK.HPB1_": { + "variableName": "hpb1ClockEnable", + "valueType": "Bool", + "access": "read-write" + }, + "PM.AHBMASK.HPB2_": { + "variableName": "hpb2ClockEnable", + "valueType": "Bool", + "access": "read-write" + }, + "PM.AHBMASK.DSU_": { + "variableName": "dsuAHBClockEnable", + "valueType": "Bool", + "access": "read-write" + }, + "PM.AHBMASK.NVMCTRL_": { + "variableName": "nvmctrlAHBClockEnable", + "valueType": "Bool", + "access": "read-write" + }, + "PM.AHBMASK.DMAC_": { + "variableName": "dmacAHBClockEnable", + "valueType": "Bool", + "access": "read-write" + }, + "PM.AHBMASK.USB_": { + "variableName": "usbAHBClockEnable", + "valueType": "Bool", + "access": "read-write" + }, + "PM.APBAMASK.PAC0_": { + "variableName": "pac0ClockEnable", + "valueType": "Bool", + "access": "read-write" + }, + "PM.APBAMASK.PM_": { + "variableName": "pmClockEnable", + "valueType": "Bool", + "access": "read-write" + }, + "PM.APBAMASK.SYSCTRL_": { + "variableName": "sysctrlClockEnable", + "valueType": "Bool", + "access": "read-write" + }, + "PM.APBAMASK.GCLK_": { + "variableName": "gclkClockEnable", + "valueType": "Bool", + "access": "read-write" + }, + "PM.APBAMASK.WDT_": { + "variableName": "wdtClockEnable", + "valueType": "Bool", + "access": "read-write" + }, + "PM.APBAMASK.RTC_": { + "variableName": "rtcClockEnable", + "valueType": "Bool", + "access": "read-write" + }, + "PM.APBAMASK.EIC_": { + "variableName": "eicClockEnable", + "valueType": "Bool", + "access": "read-write" + }, + "PM.APBBMASK.PAC1_": { + "variableName": "pac1ClockEnable", + "valueType": "Bool", + "access": "read-write" + }, + "PM.APBBMASK.DSU_": { + "variableName": "dsuClockEnable", + "valueType": "Bool", + "access": "read-write" + }, + "PM.APBBMASK.NVMCTRL_": { + "variableName": "nvmctrlClockEnable", + "valueType": "Bool", + "access": "read-write" + }, + "PM.APBBMASK.PORT_": { + "variableName": "portClockEnable", + "valueType": "Bool", + "access": "read-write" + }, + "PM.APBBMASK.DMAC_": { + "variableName": "dmacClockEnable", + "valueType": "Bool", + "access": "read-write" + }, + "PM.APBBMASK.USB_": { + "variableName": "usbClockEnable", + "valueType": "Bool", + "access": "read-write" + }, + "PM.APBBMASK.HMATRIX_": { + "variableName": "hmatrixClockEnable", + "valueType": "Bool", + "access": "read-write" + }, + "PM.APBCMASK.PAC2_": { + "variableName": "pac2ClockEnable", + "valueType": "Bool", + "access": "read-write" + }, + "PM.APBCMASK.EVSYS_": { + "variableName": "evsysClockEnable", + "valueType": "Bool", + "access": "read-write" + }, + "PM.APBCMASK.SERCOM0_": { + "variableName": "sercom0ClockEnable", + "valueType": "Bool", + "access": "read-write" + }, + "PM.APBCMASK.SERCOM1_": { + "variableName": "sercom1ClockEnable", + "valueType": "Bool", + "access": "read-write" + }, + "PM.APBCMASK.SERCOM2_": { + "variableName": "sercom2ClockEnable", + "valueType": "Bool", + "access": "read-write" + }, + "PM.APBCMASK.SERCOM3_": { + "variableName": "sercom3ClockEnable", + "valueType": "Bool", + "access": "read-write" + }, + "PM.APBCMASK.TCC0_": { + "variableName": "tcc0ClockEnable", + "valueType": "Bool", + "access": "read-write" + }, + "PM.APBCMASK.TCC1_": { + "variableName": "tcc1ClockEnable", + "valueType": "Bool", + "access": "read-write" + }, + "PM.APBCMASK.TCC2_": { + "variableName": "tcc2ClockEnable", + "valueType": "Bool", + "access": "read-write" + }, + "PM.APBCMASK.TC3_": { + "variableName": "tc3ClockEnable", + "valueType": "Bool", + "access": "read-write" + }, + "PM.APBCMASK.TC4_": { + "variableName": "tc4ClockEnable", + "valueType": "Bool", + "access": "read-write" + }, + "PM.APBCMASK.TC5_": { + "variableName": "tc5ClockEnable", + "valueType": "Bool", + "access": "read-write" + }, + "PM.APBCMASK.ADC_": { + "variableName": "adcClockEnable", + "valueType": "Bool", + "access": "read-write" + }, + "PM.APBCMASK.AC_": { + "variableName": "acClockEnable", + "valueType": "Bool", + "access": "read-write" + }, + "PM.APBCMASK.DAC_": { + "variableName": "dacClockEnable", + "valueType": "Bool", + "access": "read-write" + }, + "PM.APBCMASK.PTC_": { + "variableName": "ptcClockEnable", + "valueType": "Bool", + "access": "read-write" + }, + "PM.APBCMASK.I2S_": { + "variableName": "i2sClockEnable", + "valueType": "Bool", + "access": "read-write" + }, + "PM.INTENCLR.CKRDY": { + "variableName": "clockReadyInterruptEnableClear", + "valueType": "Bool", + "access": "read-write" + }, + "PM.INTENSET.CKRDY": { + "variableName": "clockReadyInterruptEnableSet", + "valueType": "Bool", + "access": "read-write" + }, + "PM.INTFLAG.CKRDY": { + "variableName": "clockReady", + "valueType": "Bool", + "access": "read-write" + }, + "PM.RCAUSE.POR": { + "variableName": "wasPowerOnReset", + "valueType": "Bool", + "access": "read-only" + }, + "PM.RCAUSE.BOD12": { + "variableName": "wasBrownOut12Reset", + "valueType": "Bool", + "access": "read-only" + }, + "PM.RCAUSE.BOD33": { + "variableName": "wasBrownOut33Reset", + "valueType": "Bool", + "access": "read-only" + }, + "PM.RCAUSE.EXT": { + "variableName": "wasExternalReset", + "valueType": "Bool", + "access": "read-only" + }, + "PM.RCAUSE.WDT": { + "variableName": "wasWatchdogReset", + "valueType": "Bool", + "access": "read-only" + }, + "PM.RCAUSE.SYST": { + "variableName": "wasSystemReset", + "valueType": "Bool", + "access": "read-only" } }, - "bitfields": {} + "enumValues": { + "GCLK.CLKCTRL.GEN": { + "GCLK0": { "caseName": "generator0" }, + "GCLK1": { "caseName": "generator1" }, + "GCLK2": { "caseName": "generator2" }, + "GCLK3": { "caseName": "generator3" }, + "GCLK4": { "caseName": "generator4" }, + "GCLK5": { "caseName": "generator5" }, + "GCLK6": { "caseName": "generator6" }, + "GCLK7": { "caseName": "generator7" }, + "GCLK8": { "caseName": "generator8" } + }, + "GCLK.GENCTRL.SRC": { + "XOSC": { "caseName": "externalCrystalOscillator" }, + "GCLKIN": { "caseName": "generatorInputPad" }, + "GCLKGEN1": { "caseName": "genericClockGenerator1" }, + "OSCULP32K": { "caseName": "ultraLowPower32K" }, + "OSC32K": { "caseName": "internalOscillator32K" }, + "XOSC32K": { "caseName": "externalCrystalOscillator32K" }, + "OSC8M": { "caseName": "internalOscillator8M" }, + "DFLL48M": { "caseName": "dfll48M" }, + "DPLL96M": { "caseName": "fdpll96M" } + }, + "GCLK.CLKCTRL.ID": { + "DFLL48": { "caseName": "dfll48MReference" }, + "FDPLL": { "caseName": "fdpll96MReference" }, + "FDPLL32K": { "caseName": "fdpll96MLockTimer" }, + "WDT": { "caseName": "watchdogTimer" }, + "RTC": { "caseName": "realTimeCounter" }, + "EIC": { "caseName": "externalInterruptController" }, + "USB": { "caseName": "usb" }, + "EVSYS_0": { "caseName": "eventSystemChannel0" }, + "EVSYS_1": { "caseName": "eventSystemChannel1" }, + "EVSYS_2": { "caseName": "eventSystemChannel2" }, + "EVSYS_3": { "caseName": "eventSystemChannel3" }, + "EVSYS_4": { "caseName": "eventSystemChannel4" }, + "EVSYS_5": { "caseName": "eventSystemChannel5" }, + "EVSYS_6": { "caseName": "eventSystemChannel6" }, + "EVSYS_7": { "caseName": "eventSystemChannel7" }, + "EVSYS_8": { "caseName": "eventSystemChannel8" }, + "EVSYS_9": { "caseName": "eventSystemChannel9" }, + "EVSYS_10": { "caseName": "eventSystemChannel10" }, + "EVSYS_11": { "caseName": "eventSystemChannel11" }, + "SERCOMX_SLOW": { "caseName": "sercomSlow" }, + "SERCOM0_CORE": { "caseName": "sercom0Core" }, + "SERCOM1_CORE": { "caseName": "sercom1Core" }, + "SERCOM2_CORE": { "caseName": "sercom2Core" }, + "SERCOM3_CORE": { "caseName": "sercom3Core" }, + "SERCOM4_CORE": { "caseName": "sercom4Core" }, + "SERCOM5_CORE": { "caseName": "sercom5Core" }, + "TCC0_TCC1": { "caseName": "tcc0AndTcc1" }, + "TCC2_TC3": { "caseName": "tcc2AndTc3" }, + "TC4_TC5": { "caseName": "tc4AndTc5" }, + "TC6_TC7": { "caseName": "tc6AndTc7" }, + "ADC": { "caseName": "analogToDigitalConverter" }, + "AC_DIG": { "caseName": "analogComparatorDigital" }, + "AC_ANA": { "caseName": "analogComparatorAnalog" }, + "DAC": { "caseName": "digitalToAnalogConverter" }, + "I2S_0": { "caseName": "i2sSerialClock0" }, + "I2S_1": { "caseName": "i2sSerialClock1" } + } + } } diff --git a/docs/boilerplate/DMACSupportingTypes.swift b/docs/boilerplate/DMACSupportingTypes.swift new file mode 100644 index 0000000..a249a4d --- /dev/null +++ b/docs/boilerplate/DMACSupportingTypes.swift @@ -0,0 +1,116 @@ + // MARK: - Supporting Types + + /// DMAC trigger source values for CHCTRLB.TRIGSRC. + enum TriggerSource: UInt32 { + case disabled = 0 +{{triggerCases}} + } + + /// DMAC channel arbitration level. + enum PriorityLevel: UInt32 { + case level0 = 0 + case level1 = 1 + case level2 = 2 + case level3 = 3 + } + + /// DMAC trigger action for CHCTRLB.TRIGACT. + enum TriggerAction: UInt32 { + case block = 0 + case beat = 2 + case transaction = 3 + } + + /// Descriptor event output selection for BTCTRL.EVOSEL. + enum DescriptorEventOutput: UInt32 { + case disabled = 0 + case block = 1 + case beat = 3 + } + + /// Descriptor block action for BTCTRL.BLOCKACT. + enum DescriptorBlockAction: UInt32 { + case noAction = 0 + case interrupt = 1 + case suspend = 2 + case both = 3 + } + + /// DMAC beat size for descriptors. + enum BeatSize: UInt32 { + case byte = 0 + case halfWord = 1 + case word = 2 + } + + /// Descriptor step-size target for BTCTRL.STEPSEL. + enum StepSelection: UInt32 { + case destination = 0 + case source = 1 + } + + /// Descriptor address increment step size for BTCTRL.STEPSIZE. + enum StepSize: UInt32 { + case x1 = 0 + case x2 = 1 + case x4 = 2 + case x8 = 3 + case x16 = 4 + case x32 = 5 + case x64 = 6 + case x128 = 7 + } + + /// DMAC SRAM transfer descriptor. + /// + /// Descriptor memory must be aligned to at least 16 bytes before assigning its + /// address to descriptorBaseAddress or writeBackBaseAddress. + @_alignment(16) + struct Descriptor { + static let byteSize = 16 + static let requiredAlignment = 16 + + var blockTransferControl: UInt16 + var blockTransferCount: UInt16 + var sourceAddress: UInt32 + var destinationAddress: UInt32 + var nextDescriptorAddress: UInt32 + + init( + blockTransferControl: UInt16 = 0, + blockTransferCount: UInt16 = 0, + sourceAddress: UInt32 = 0, + destinationAddress: UInt32 = 0, + nextDescriptorAddress: UInt32 = 0 + ) { + self.blockTransferControl = blockTransferControl + self.blockTransferCount = blockTransferCount + self.sourceAddress = sourceAddress + self.destinationAddress = destinationAddress + self.nextDescriptorAddress = nextDescriptorAddress + } + + @inline(__always) + static func makeBlockTransferControl( + valid: Bool = false, + eventOutput: DescriptorEventOutput = .disabled, + blockAction: DescriptorBlockAction = .noAction, + beatSize: BeatSize = .byte, + sourceIncrement: Bool = false, + destinationIncrement: Bool = false, + stepSelection: StepSelection = .destination, + stepSize: StepSize = .x1 + ) -> UInt16 { + var value: UInt32 = 0 + if valid { value |= UInt32(1) << 0 } + value |= (eventOutput.rawValue & 0x03) << 1 + value |= (blockAction.rawValue & 0x03) << 3 + value |= (beatSize.rawValue & 0x03) << 8 + if sourceIncrement { value |= UInt32(1) << 10 } + if destinationIncrement { value |= UInt32(1) << 11 } + value |= (stepSelection.rawValue & 0x01) << 12 + value |= (stepSize.rawValue & 0x07) << 13 + return UInt16(value & 0xFFFF) + } + } +