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.
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],
}
Register Field Types
A register field may use:
bit, with width one;bit[N], with widthN;- a bit-backed enum with underlying representation
bitorbit[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-firstassign logical bit positions.little-endian/big-endiancontrol 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-clear | Writing one requests clear |
write-one-set | Writing one requests set |
write-one-toggle | Writing one requests toggle |
write-zero-clear | Writing zero requests clear |
write-zero-set | Writing zero requests set |
write-zero-toggle | Writing 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.
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()andWrite(); - 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
RawPtrandunsafe.
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/noshadowpolicy; - read from a
write-onlyfield; - write to a
read-onlyfield; - 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:
ClockSourceis a nominal open two-bit enum;InterruptFlagsis a nested four-bit register;Pendingretainswrite-one-clearsemantics;Readyis read-only;DeviceControlestablishes a register-level shadow-policy default;Resetoverrides that default withnoshadow;- the complete register width is exactly sixteen bits;
impladds behavior without changing the bit layout;- the detached
snapshothas 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.
bitandbit[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-firstis the default field-allocation rule.msb-firstexplicitly allocates declarations from the most-significant side.- Field allocation order and byte order are independent.
little-endianandbig-endiancontrol 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-writeby default. read-onlyandwrite-onlyare compiler-enforced permissions.- Specialized write semantics include write-one/zero clear, set and toggle forms.
read-clearandread-setdescribe effectful reads.- Specialized field semantics are part of the register declaration and cannot be erased by lowering.
shadowandnoshadowdeclare 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.
@addressmay bind a register to a statically resolvable canonical hardware endpoint.- Canonical named endpoints such as
Platform.GPIOAare 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.