Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@
.build
Output
Scripts/__pycache__
docs/README.md
docs/README.md
.worktrees
288 changes: 288 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -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/<ChipName>/`

Output is a Swift Package Manager module per chip with generated peripheral code.

## Architecture

### Data Flow

```
SVDs/*.svd ──[SVDDecoder]──> SVDDevice (model)
docs/<chip>.json ──[ChipDocumentationLoader]──> Supplemental docs
docs/general.json │
GeneratorRegistry ──[GenerationPipeline]──> [PeripheralGenerator] × N
GeneratedCodeFile[]
Output/<Chip>/Sources/<Module>/
```

### 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/<chip>.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/<chip>.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/<chip>.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<N>` 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 <path>` | SVD file or directory (default: `./SVDs/`) |
| `--output <path>` | 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
```
12 changes: 12 additions & 0 deletions SwiftARMGenerator.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand All @@ -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; };
Expand Down Expand Up @@ -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 */,
);
Expand Down
Loading
Loading