Introducing Swift MMIO

Hi Swift Community!

We're excited to introduce swift-mmio, our first library for the Embedded Swift ecosystem. swift-mmio is designed to provide safe and secure APIs for fundamental low-level operations required in embedded firmware development, such as reading and writing memory-mapped registers. Drawing inspiration from mmio libraries in languages like C++ and Rust, swift-mmio focuses on improving the correctness of code interacting with MMIO.

The swift-mmio package offers a set of macros to define register maps directly within Swift source code:

import MMIO

/// An example 32-bit wide register, encompassing three individual bit fields:
/// - "en" (Enable)
/// - "clken" (Clock Enable)
/// - "rst" (Reset)
@Register(bitWidth: 32)
struct CR1 {
  @ReadWrite(bits: 0..<1, as: Bool.self)
  var en: EN
  @ReadWrite(bits: 1..<2, as: Bool.self)
  var clken: CLKEN
  @ReadWrite(bits: 2..<3, as: Bool.self)
  var rst: RST
}

@RegisterBank
struct Control {
  @RegisterBank(offset: 0x0)
  var cr1: Register<CR1>
  @RegisterBank(offset: 0x4)
  var cr2: Register<CR2>
}

And, leveraging Swift's expressiveness, swift-mmio provides programmers with type safe API for register and bank operations:

var control = Control(unsafeAddress: 0x1000)

// Get a reference to the cr1 register in the control bank.
var cr1 = control.cr1

// Perform a read-modify-write of the cr1 register.
cr1.modify { cr1 in
  // Mutate `en` as a Bool.
  cr1.en = true

  // Mutate `clken` as a raw integer.
  cr1.raw.clken = 0

  // Mutate `rst` using C-style manual bit operations.
  cr1.raw.storage |= (1 & CR1.RST.bitMask) << CR1.RST.bitOffset
}

The swift-mmio API is still evolving, and we're eager to hear from the community. Your feedback and contributions will help shape the future of this library. Feedback is welcome here on the Swift Forums and as GitHub Issues (and PRs) on the repo linked above. Additional features and tasks will be added to the Issues list soon for early contributors to dig in immediately.

65 Likes

Congratulations on beginning this journey with the first library! :wink:

3 Likes

Well done, that's really super cool!!

2 Likes

I love to see swift improve for embedded. My last attempt dates 2 years now where I could not get over one big roadblock. Swift itself demands at least 2mb storage. This is to much for small embedded systems. Has this improved?

1 Like

This is amazing! I can't wait to adopt this in my (future) projects

2 Likes

Embedded builds can get down to under a few hundred bytes of code that is runnable on systems as flashed images. It is to the extent that the reserved stack or second stage boot loaders normally associated with those targets are considerably larger portions of the flashed image than the compiled code. Obviously that means there are a number of things missing that desktop swift has and we need to strike a balance depending on what level of systems are being targeted.

The MMIO package gives a method to build the libraries for interacting with the hardware that leverages one of the super-powers in this scenario: Swift's optimization around inlining that quite honestly is hard to do w/ languages like C/C++. From my experience using this library it optimizes even abstracted register access (to some sort of HAL living on-top of the mmio register interfaces) down to just a singular read/write instruction to a register.

8 Likes

Indeed, this is a core requirement of MMIO. All calls to read() and write() compile down to specific bit-width load/store instructions. The FileCheck based tests are used to verify this requirement.

Tangentially related, we may explore doublewide reads and writes for platforms which support them. On aarch64 this would leverage stp/ldp instructions.

4 Likes

See Embedded Swift for more info on the size reduction effort

3 Likes

I'm curious about the Debug build story, though - generally Swift doesn't inline things and doesn't do any meaningful code-size-reducing optimisations for debug builds. It is apparently possible to construct Swift code in a way that does permit debug builds to still boil down to simple machine instructions - I believe the Atomics library takes some pains to ensure this - but, at the very least that's hard and a lot of extra work.

Maybe this is a bit tangential to this library, since it applies to Embedded Swift generally, but I'm curious if you can elaborate on how debug builds work and if it is indeed an issue for MMIO?

This looks awesome. Looking forward to seeing much more use of Swift in this space.