Registers

A Sec register[N] declaration defines a nominal fixed-width bit-layout type whose total logical width is exactly N bits.

type Control register[16] {
    Enabled: bit,
    Mode: bit[3],
    _: bit[12],
}

Register types describe exact bit layout. They can be used for hardware register definitions, protocol headers, packed control words, wire values, file-format fields and other fixed-width representations.

Physical hardware access is a separate semantic layer. A register declaration does not by itself imply MMIO, a fixed address, volatility or any particular hardware transaction.

Core rule: register[N] defines the logical register value and its field-local semantics. Hardware-access rules separately define how a hardware-bound register may be physically observed or modified.

A General Value Type

Register types are ordinary nominal Sec value types with exact-width layout semantics.

type PacketHeader register[32] {
    Version: bit[4],
    Type: bit[4],
    Length: bit[24],
}

let header: PacketHeader

Nothing in this declaration says that header refers to hardware. It may be a normal value decoded from a packet, loaded from a file, received over a bus or produced by another function.

A register declaration does not inherently imply:

  • MMIO;
  • a physical address;
  • port I/O;
  • CSR or system-register access;
  • volatile access;
  • runtime mapping;
  • hardware-resource ownership;
  • a physical bus transaction width.

These facts belong to the hardware binding and platform model.

Declaration

The basic declaration form is:

type Name register[Width] {
    fields
}

Example:

type Status register[8] {
    Ready: bit,
    Error: bit,
    Code: bit[2],
    _: bit[4],
}

Width must be a positive compile-time integer.

Register fields are comma-separated, and a trailing comma is permitted.

Nominal Identity

Every declared register type has its own nominal identity.

type ControlA register[8] {
    Value: bit[8],
}

type ControlB register[8] {
    Value: bit[8],
}

ControlA and ControlB are different types even though they have identical widths and field layouts.

Register compatibility is not inferred structurally from physical representation.

Exact Width

The sum of every declared field width, including reserved fields, must equal the register width exactly.

type Control register[8] {
    Enabled: bit,
    Mode: bit[3],
    _: bit[4],
}

The layout consumes exactly eight bits:

1 + 3 + 4 = 8

Underfilling is invalid:

type Invalid register[8] {
    Enabled: bit,
    Mode: bit[3],
}

Overfilling is invalid:

type Invalid register[8] {
    Value: bit[9],
}
Rule: Every logical bit in a register belongs to exactly one declared named or reserved field.

Register Field Types

A register field may use:

  • bit, with width one;
  • bit[N], with width N;
  • a bit-backed enum with underlying representation bit or bit[N];
  • another register type whose width is known at compile time.
type Control register[16] {
    Enabled: bit,
    Mode: bit[3],
    Code: bit[12],
}

bit is not a general standalone scalar

bit and bit[N] are register-field and bit-backed-enum representations. They are not general standalone Sec variable types.

let raw: bit[32]    // invalid

Use an ordinary value type when a standalone raw value is required:

let raw: uint32

Reserved Fields

The identifier _ declares reserved or intentionally unnamed bits.

type Status register[8] {
    Ready: bit,
    _: bit[7],
}

Reserved fields occupy layout width but do not introduce a source-accessible member.

let value := status._    // invalid

Their existence still matters to layout and to any later hardware-access contract that describes how physical operations affect the complete register.

Bit Numbering

Bit zero is always the least-significant bit.

For an N-bit register:

bit 0       = least-significant bit
bit N - 1   = most-significant bit

Register layout is defined in logical bit-number terms. Diagrams drawn left-to-right are illustrative only.

Field Allocation Order

Default: lsb-first

Register fields are allocated lsb-first unless explicitly stated otherwise.

type Control register[16] {
    Enabled: bit,
    Mode: bit[3],
    _: bit[12],
}

The logical layout is:

Enabled = bit 0
Mode    = bits 3..1
_       = bits 15..4

Explicit msb-first

type Header register[8] msb-first {
    Version: bit[4],
    IHL: bit[4],
}

The logical layout is:

Version = bits 7..4
IHL     = bits 3..0

msb-first changes declaration allocation direction. It does not change the rule that bit zero is the LSB.

Byte Order

Field allocation order and byte order are independent.

  • lsb-first / msb-first assign logical bit positions.
  • little-endian / big-endian control multi-byte encoding and decoding.
type DeviceWord register[32] little-endian {
    Value: bit[32],
}
type PacketHeader register[32] big-endian {
    Version: bit[4],
    Length: bit[28],
}

Both dimensions may be combined:

type Word register[16] lsb-first big-endian {
    Code: bit[4],
    Value: bit[12],
}

Code remains bits 3..0. The byte-order declaration controls how the complete sixteen-bit value is represented as ordered bytes.

When no byte order is written, the applicable contextual or native rule is used.

Bit-Backed Enum Fields

A bit-backed enum may be used directly as a register field when its width matches the field representation.

enum ClockSource bit[2] {
    Internal = 0b00
    External = 0b01
    Bypass = 0b10
}

type ClockConfig register[8] {
    Source: ClockSource,
    Enabled: bit,
    _: bit[5],
}

Source occupies two bits and retains nominal type ClockSource.

Bit-backed enums are open over their complete representable domain. Therefore 0b11 is still a valid ClockSource value, although it has no declared member name.

match config.Source {
    ClockSource.Internal => UseInternal()
    ClockSource.External => UseExternal()
    ClockSource.Bypass => UseBypass()
    _ => HandleReservedEncoding()
}

Nested Registers

One register type may be used as a field of another register type.

type Flags register[4] {
    Carry: bit,
    Zero: bit,
    Negative: bit,
    Overflow: bit,
}

type StatusWord register[16] {
    Flags: Flags,
    Mode: bit[4],
    _: bit[8],
}

The nested register contributes exactly its declared width and occupies one contiguous slice of the outer register.

The outer allocation rule determines where the slice is placed. Inside that slice, the nested register's own field-allocation rule applies.

The nested register's bit zero maps to the least-significant bit of its assigned slice.

Nested byte order

Nested register composition is logical bit composition, not byte serialization.

The outer register's byte-order rule governs encoding or decoding of the complete outer value. The nested register's byte-order declaration matters when that nested value is independently serialized or deserialized.

Sec does not insert an implicit byte swap merely because inner and outer declarations name different byte orders.

No recursive layout

Every register must have a finite exact compile-time-known width. Recursive register layouts that imply infinite storage are invalid.

Field Access Semantics

Register fields may carry compiler-known read, write and side-effect semantics.

These declarations describe the logical meaning of accessing the field. They are part of the register type and survive whether that type is later used as a detached value or through a hardware binding.

When the register is hardware-bound, the hardware-access model connects these logical field operations to valid physical operations. That later planning layer must preserve the semantics declared here.

Read / Write Permissions

Fields are read-write by default.

Control: bit

is permission-equivalent to:

Control: bit read-write

Read-only

Ready: bit read-only

Reading is permitted where the surrounding value or binding allows it. Writing the field is a compile-time error.

Write-only

Command: bit write-only

Writing is permitted where the surrounding value or binding allows it. Reading the field is a compile-time error.

Access permission and specialized side-effect behavior are separate semantic axes. Contradictory combinations are rejected.

Specialized Write Semantics

Sec defines a compiler-known family for common field-local write behavior:

Pending: bit write-one-clear,
Enable: bit write-one-set,
Invert: bit write-one-toggle,
Error: bit write-zero-clear,
Disable: bit write-zero-set,
Restore: bit write-zero-toggle,
Modifier Logical field operation
write-one-clearWriting one requests clear
write-one-setWriting one requests set
write-one-toggleWriting one requests toggle
write-zero-clearWriting zero requests clear
write-zero-setWriting zero requests set
write-zero-toggleWriting zero requests toggle

These modifiers describe one logical field write. They do not describe a multi-register device protocol.

A compiler lowering may not erase these semantics and replace them blindly with ordinary read-modify-write behavior.

Specialized Read Semantics

Read-side effects may also be declared:

Event: bit read-clear,
Flag: bit read-set,

read-clear is the canonical spelling for a read whose defined side effect clears the field.

read-set is the corresponding form whose defined side effect sets the field.

These reads are effectful. Compiler analyses and lowering may not treat them as ordinary pure field observations when doing so would remove, duplicate, speculate or reorder the defined effect.

Shadow Policy Modifiers

shadow and noshadow declare hardware-access shadow policy.

The global default is:

noshadow

A field may override the policy:

type Configuration register[8] {
    Mode: DeviceMode shadow,
    Ready: bit read-only,
    _: bit[5],
}

A register may establish a local default:

type Configuration register[16] shadow {
    Enabled: bit,
    Mode: DeviceMode,
    CurrentInput: bit read-only noshadow,
    _: bit[12],
}

The inheritance rule is:

explicit field shadow/noshadow
otherwise register-level shadow/noshadow
otherwise noshadow

Shadow policy does not change field layout or read/write permission.

The detailed meaning of shadow state, observed knowledge, pending write intent and hardware commit belongs to the later Hardware Register Access chapter.

Access Semantics Compose Through Nested Registers

Nested registers preserve the semantics of their contained fields.

type InterruptFlags register[4] {
    Pending: bit write-one-clear,
    Enabled: bit,
    Ready: bit read-only,
    _: bit,
}

type DeviceStatus register[16] {
    IRQ: InterruptFlags,
    _: bit[12],
}

DeviceStatus.IRQ.Pending retains write-one-clear semantics.

DeviceStatus.IRQ.Ready remains read-only.

An operation on an entire nested field is valid only when the compiler can preserve every contained access and side-effect requirement.

Register Values

A register type is a fixed-width nominal value type.

type Packet register[32] {
    Value: bit[32],
}

let packet: Packet

A detached register value carries no inherent hardware identity or physical address.

It can be stored, passed, returned, inspected and transformed according to the ordinary value, ownership and access rules that apply to the register type.

Field permissions remain part of the nominal register semantics even for detached values. A write-only field does not become source-readable merely because the value is detached.

Conversions

An integer value may be converted explicitly to a register type when it fits the register width.

type Packet register[32] {
    Value: bit[32],
}

let raw: uint32 := ReadWord()
let packet := Packet(raw)

Every uint32 value fits a 32-bit register, so no runtime width check is needed here.

When the integer source may exceed the register width, conversion is checked:

type Packet12 register[12] {
    Value: bit[12],
}

let raw: uint16 := ReadWord()
let packet := try Packet12(raw)

A compile-time-known out-of-range value is rejected during compilation.

Integer conversion interprets an already formed numeric value or bit pattern. It is not byte decoding and does not perform implicit endian conversion.

Byte-oriented encoding and decoding are separate operations and obey the register's byte-order contract.

Field Access

Named fields use ordinary member syntax:

let enabled := control.Enabled
control.Mode = value

A reserved _ field cannot be named or accessed.

Field access preserves the nominal type of the field. A bit-backed enum field remains its enum type, and a nested register field remains its nested register type.

Whether field access is purely local or corresponds to hardware observation depends on the receiver's binding. The register declaration itself does not decide that.

Register Types and Impl

Register types are nominal Sec types and may have ordinary implementations.

type Status register[8] {
    Ready: bit read-only,
    Error: bit,
    _: bit[6],
}

impl Status {
    fn IsHealthy() bool {
        return self.Ready && !self.Error
    }
}

Methods, properties and interface implementations follow the normal impl rules.

An implementation cannot add bits, alter offsets or change the declared register width.

The Hardware Access Boundary

The register declaration describes a logical register value and field-local semantics. Hardware access adds a second contract that describes how physical hardware may actually realize those logical operations.

The central hardware-access principle is:

logical register operation
        ↓
semantics-preserving hardware operation plan
        ↓
physical endpoint operation or operation sequence

A hardware field does not imply a physical operation merely from its width, register width, address spelling or field name.

For example, reading a one-bit field does not imply that the hardware can perform a one-bit physical read.

The compiler connects the logical register operation to hardware only through resolved platform/device facts.

Boundary: This chapter defines the register side of that contract. Physical endpoints, operation planning, aliases, access widths, coherence, ordering, completion, mappings and explicit hardware transactions are covered separately.

Static @address Binding

A register type may be connected to a statically resolvable hardware endpoint using @address(...).

The preferred form uses a canonical named endpoint:

@address(Platform.GPIOA)
let mut gpio: GpioRegisters

A project-defined canonical endpoint may be used in the same way:

@address(Project.FpgaControl)
let mut control: FpgaControlRegisters

The endpoint name is not treated as an ordinary integer constant. It resolves to canonical hardware information required to validate the binding and later register operations.

The @address form takes one endpoint expression. Width, alignment, permissions, aliases, access classes and other hardware facts come from the canonical endpoint/storage contract rather than from additional positional arguments.

The full validation and hardware-access semantics of @address belong to the later Hardware Register Access chapter.

Hardware Topics Covered Later

Hardware register access is central to using register types against real devices, but it is large enough to deserve its own chapter.

The later Hardware Register Access chapter covers:

  • physical endpoint kinds;
  • hardware resource identity;
  • endpoint identity and aliases;
  • safe implicit field observation;
  • explicit register Read() and Write();
  • shadow state and pending write intent;
  • physical read and write widths;
  • alignment requirements;
  • read and write footprints;
  • collateral side effects;
  • coherent multi-step accesses;
  • operation planning and plan selection;
  • atomicity and failure atomicity;
  • ordering and completion;
  • runtime-resolved hardware mappings;
  • access context and authority;
  • hardware-access fault behavior;
  • the boundary between compiler-owned hardware realization and ordinary device protocol code;
  • raw volatile access through RawPtr and unsafe.

Keeping those concerns separate prevents the basic register type from being mistaken for an MMIO-only construct while still preserving the full compiler-known hardware model.

Signal Polarity Is Outside Register Semantics

Register declarations preserve field values and declared access semantics. The compiler does not infer domain meaning from a field or signal name.

It does not automatically infer or invert:

  • active-high versus active-low;
  • asserted versus deasserted;
  • enabled versus disabled;
  • reset polarity;
  • chip-select polarity;
  • interrupt polarity.

Driver and application code owns that interpretation.

if status.InterruptPin == 0 {
    HandleActiveLowInterrupt()
}

The compiler may know the field width and access semantics without deciding what logical state the physical bit represents in the device domain.

Diagnostics

Important register diagnostics include:

  • non-constant or non-positive register width;
  • field widths whose sum does not equal the declared width;
  • invalid field type;
  • recursive or infinitely sized nested register layout;
  • contradictory read/write semantics;
  • contradictory shadow / noshadow policy;
  • read from a write-only field;
  • write to a read-only field;
  • operation that cannot preserve nested or specialized field semantics;
  • integer-to-register conversion whose out-of-range value is known at compile time;
  • runtime integer-to-register conversion used without required checked handling.

Hardware-bound register diagnostics add further information from the physical endpoint contract. Those diagnostics are described with Hardware Register Access.

Tooling should be able to expose resolved register facts such as:

  • register width;
  • field width;
  • logical bit positions;
  • field allocation order;
  • byte-order contract;
  • field nominal type;
  • read/write permission;
  • special read/write semantics;
  • effective shadow policy.

Complete Example

module device

enum ClockSource bit[2] {
    Internal = 0b00
    External = 0b01
    Bypass = 0b10
}

type InterruptFlags register[4] {
    Pending: bit write-one-clear,
    Enabled: bit,
    Ready: bit read-only,
    _: bit,
}

type DeviceControl register[16] shadow {
    Clock: ClockSource,
    IRQ: InterruptFlags,
    Reset: bit write-one-set noshadow,
    _: bit[9],
}

impl DeviceControl {
    fn UsesExternalClock() bool {
        return self.Clock == ClockSource.External
    }
}

let snapshot: DeviceControl

match snapshot.Clock {
    ClockSource.Internal => HandleInternalClock()
    ClockSource.External => HandleExternalClock()
    ClockSource.Bypass => HandleBypassClock()
    _ => HandleReservedClockEncoding()
}

This example demonstrates the register type itself:

  • ClockSource is a nominal open two-bit enum;
  • InterruptFlags is a nested four-bit register;
  • Pending retains write-one-clear semantics;
  • Ready is read-only;
  • DeviceControl establishes a register-level shadow-policy default;
  • Reset overrides that default with noshadow;
  • the complete register width is exactly sixteen bits;
  • impl adds behavior without changing the bit layout;
  • the detached snapshot has ordinary value semantics.

The same DeviceControl type may later be used through a hardware binding:

@address(Platform.DeviceControl)
let mut control: DeviceControl

That binding adds hardware-access semantics without changing DeviceControl's declared width, field offsets or nominal type identity.

Summary

  • register[N] defines a nominal fixed-width bit-layout type.
  • Register types are general language types and are not inherently MMIO types.
  • A register declaration does not itself imply a physical address, volatility or hardware transaction.
  • The complete field width must equal the declared register width exactly.
  • bit and bit[N] are register-field and bit-enum representations, not general standalone scalar types.
  • Reserved _ fields occupy bits but are not source-accessible.
  • Bit zero is always the least-significant bit.
  • lsb-first is the default field-allocation rule.
  • msb-first explicitly allocates declarations from the most-significant side.
  • Field allocation order and byte order are independent.
  • little-endian and big-endian control byte encoding, not logical bit numbering.
  • Bit-backed enum fields retain nominal enum typing and accept every representable underlying bit pattern.
  • Nested registers occupy contiguous fixed-width slices and preserve their own inner field semantics.
  • Recursive or infinitely sized register layouts are invalid.
  • Register fields are read-write by default.
  • read-only and write-only are compiler-enforced permissions.
  • Specialized write semantics include write-one/zero clear, set and toggle forms.
  • read-clear and read-set describe effectful reads.
  • Specialized field semantics are part of the register declaration and cannot be erased by lowering.
  • shadow and noshadow declare hardware shadow policy without changing field layout or permission.
  • Nested registers preserve contained access and side-effect semantics.
  • A register may exist as an ordinary detached Sec value.
  • Integer-to-register conversion is explicit and checked when width fit cannot be proven.
  • Integer conversion does not perform implicit endian conversion.
  • Named register fields use ordinary member syntax and preserve their declared nominal types.
  • Register types may have ordinary methods, properties and interface implementations through impl.
  • The hardware-access layer connects logical register operations to physical endpoints through semantics-preserving operation plans.
  • A field width does not imply a physical hardware access width.
  • @address may bind a register to a statically resolvable canonical hardware endpoint.
  • Canonical named endpoints such as Platform.GPIOA are the preferred static binding form.
  • Hardware endpoint metadata is not reduced to an ordinary integer before the compiler consumes its semantic contract.
  • Physical endpoints, aliases, access widths, Read()/Write(), shadow state, operation planning, mappings, ordering and completion are covered in the dedicated Hardware Register Access chapter.
  • The compiler does not infer active-high/active-low or other device-domain signal meaning from register field names.