Skip to content

Merge STM32 peripheral register maps into variants - #17

Merged
salkinium merged 5 commits into
mainfrom
feature/peripheral-variants
Sep 17, 2026
Merged

salkinium merged 5 commits into
mainfrom
feature/peripheral-variants

Conversation

@salkinium

@salkinium salkinium commented Sep 17, 2026 •

Copy link
Copy Markdown
Member

Merges the register maps of all 22001 peripheral instances of all 287 STM32
devices into as few variants as possible, to answer how many different silicon
implementations of a peripheral exist, and publishes them on the homepage with
the Peripheral Variant Explorer.

Each variant is one implementation, described by a core that every instance
has and the optional features that only some have. A feature is every
element that appears in exactly the same instances, since those are enabled and
disabled together, like the clock enable bits of a GPIO port that only the
larger packages bond out.

What makes two register maps different

Neither names nor addresses alone can decide this:

  • ST renames registers without changing the layout, most visibly between the
    STM32H7x and STM32H7[RS]x. Comparing names alone calls these different.
  • ST reuses layouts for different registers. The STM32F1 GPIO_CRL and the
    GPIO_MODER of every other device both start with two-bit fields at the same
    positions, so comparing addresses alone merges the two GPIO implementations.

So both are compared, and not symmetrically: a name at two locations is a
conflict, a location with two names is a rename. Since unrelated elements
never share a name, two more rules catch what names cannot: differently laid out
bit fields that share bits conflict, and a rename only counts inside the
same register
. Without those, the STM32L1, STM32L0, STM32G0 and STM32H503
comparators merge into a single variant whose CSR is a pile of unrelated bit
fields.

Three modes

located by answers
binary register address can one driver poke both at fixed addresses?
source register name does code written against a CMSIS header work?
similar register name, ignoring documentation-only differences are these essentially the same?

Neither is strictly coarser: source compatibility merges the two GPIO
implementations, but splits COMP, where several devices have a differently
laid out CSR. The similar mode tolerates e.g. USART_BRR described either
as one bit field or as DIV_MANTISSA with DIV_FRACTION.

1808 distinct register maps become 387 binary, 319 source and 282 similar
variants.
GPIO comes out as exactly two implementations plus LPGPIO1, which
reuses GPIO_TypeDef with a one-bit MODER.

Feature names

Features are named by the first source that works: a lookup table downloaded
from the gist, a local model served by LM Studio, or a deterministic heuristic.
CI and the homepage only ever use the table, so no build depends on a model. The
published table names 2623 of 2624 features.

Also in here

  • header2svd: the value macros of the non-contiguous CRYP_CR_ALGOMODE
    replaced the bit field itself and were spread over three alternate registers.
    Fixed, which changes CRYP_CR on 27 devices and FLASH_OPTR_nRST_MODE on the
    STM32WB1x, and nothing else of the 1.7 million bit fields.
  • Register access from the __IO/__I qualifiers, now shown by both
    explorers. Sparse by nature: 136633 members are __IO, 918 are __I/__IM,
    and __O is never used, so write-only registers cannot be told apart.
  • The SVD reader and writer keep the structure type as groupName, plus
    descriptions, alternates, dimensions and enumerated values.
  • CI generates the variants and attaches variants-archive.zip to releases.

Release fixes from v0.0.4

Also carries two unrelated CI fixes for failures of the v0.0.4 release:

  • Publish to PyPI was rejected with Can't have direct dependency: modm-pinout @ git+..., since PyPI does not accept direct URL dependencies.
    It is no longer declared in the docs extra, and the docs workflow installs
    it separately.
  • Build docs failed when two deployments raced each other pushing to
    modm-ext/data.modm.io. They are serialized with a concurrency group, and the
    generated content is rebased onto the remote before pushing, since it replaces
    the repository wholesale.

Known limits

  • TIM splits mostly on the option registers that route device signals, so those
    are left out of the comparison entirely.
  • LPTIM counts as two variants because CMP was renamed to CCR1 along with
    its only field, so nothing indicates that it is the same register.
  • Gemma reads TIM COM as "communication" in 2 of 2624 feature names.

The reader dropped everything except names, offsets and bit ranges, and
the writer had no place for the CMSIS structure type of a peripheral,
which is the only reliable way to tell which instances are the same kind
of hardware. Write it as the standard groupName element together with the
register access, and read back the descriptions, alternates, array
dimensions, enumerated values and interrupts.
…access

The values of a bit field were only removed if the bit field was
contiguous, so the value macros of CRYP_CR_ALGOMODE (bits 5:3 and 19)
replaced the bit field itself and were spread over three alternate
registers. Match them against the name of the whole bit field instead,
which changes CRYP_CR on 27 devices and FLASH_OPTR_nRST_MODE on the
STM32WB1x, and nothing else of the 1.7 million bit fields.

Members declared __I or __IM expand to volatile const and are read-only,
everything else is read-write. The __O qualifier expands to the same
volatile as __IO, so write-only registers cannot be told apart, but no ST
header uses it: 136633 members are __IO, 711 __I and 207 __IM.
Every STM32 device has its own register map, but the peripherals inside
them are reused across the whole device range. This merges the register
maps of all 22001 peripheral instances of all 287 devices into as few
variants as possible, where each variant is one silicon implementation
described by a core that every instance has and the optional features that
only some have. A feature is every element that appears in exactly the
same instances, since those are enabled and disabled together.

Neither names nor addresses alone can decide whether two register maps are
the same silicon, so both are compared and not symmetrically: a name at
two locations is a conflict, a location with two names is a rename. Since
unrelated elements never share a name, differently laid out bit fields
that share bits are a conflict too, and a rename only counts inside the
same register. Otherwise the STM32L1, STM32L0, STM32G0 and STM32H503
comparators all merge into one variant of unrelated bit fields.

The elements are located by register address in binary mode and by
register name in source mode, which is what code written against a CMSIS
header needs, while similar mode also tolerates differences that only
exist in the documentation, like USART_BRR against DIV_MANTISSA and
DIV_FRACTION. The timer option registers are left out entirely, since they
route device signals and would split the timers per family.

1808 distinct register maps become 387 binary, 319 source and 282 similar
variants. Their features are named by a lookup table, a local model served
by LM Studio, or a heuristic, whichever is available first.
Generates the variant JSON files into the documentation folder and adds
the Peripheral Variant Explorer to the homepage, which is fetched from the
same gist as the SVD Explorer together with the table of generated feature
names, so that neither the page nor the names need to be committed here.

The variants are also generated in CI and attached to releases as
variants-archive.zip.
PyPI rejects packages with direct URL dependencies, so modm-pinout cannot
be declared in the docs extra. The docs workflow installs it explicitly.

Two docs deployments running concurrently race each other when pushing to
the data.modm.io repository. Serialize them with a concurrency group and
rebase the generated content onto the remote before pushing.
@salkinium
salkinium merged commit 0bb6018 into main Sep 17, 2026
10 checks passed
@salkinium
salkinium deleted the feature/peripheral-variants branch September 17, 2026 20:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant