//
// DO NOT EDIT THIS FILE!
// THIS FILE IS AUTOMATICALLY EXTRACTED FROM A LITERATE PROGRAM SOURCE FILE.
//
//
// The MIT License
//
// This software is copyrighted 2021 - 2025 by G. Andrew Mangogna.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//! The `mmio` module contains a set of declarations and functions to aid in
//! accessing memory mapped I/O registers in a fashion that is less dependent
//! upon bit masks and bit offsets. Zig provides `extern struct` and `packed
//! struct` constructs that can be used to define the layout of peripheral
//! device registers in memory and to define the layout of control fields
//! within a register.
//!
//! This implementation is oriented to the ARM Cortex-M view of device
//! registers. Although there is nothing Cortex specific, 32-bit registers are
//! the prime focus. Most (but not all) peripheral registers for Cortex SOC's are 32-bit
//! quantities.
//!
//! There are many different techniques that hardware designers use to design
//! peripheral control interfaces. Most common is to layout the registers of
//! the device in an proximate group in memory and to layout the controls
//! fields within a register with bit fields that have specific peripheral
//! control or status meanings. Much of this design borrows heavily from the
//! ideas described
//! [here](https://www.scattered-thoughts.net/writing/mmio-in-zig/). This
//! design extends those ideas to handle other register field arrangements.
//!
//! This module contains three primary type functions that are used to
//! define the layout of memory mapped peripherals:
//!
//! 1. The `Register` function defines types for register containing
//! named bit fields.
//! 2. The `IndexedRegister` function defines types that contain a sequence
//! of bit fields in the register and allows for indexed access to the
//! register fields.
//! 3. The `BitRegister` function defines register types where all the fields
//! are a single bit. This is a degenerate case of `IndexedRegister` and
//! allows the register to be treated as an array of bits.
const std = @import("std");
const meta = std.meta;
const math = std.math;
const testing = std.testing;
/// Following ARM terminology, `words` are 32 bits. This type clarifies that
/// the underlying integer type is being used as a peripheral register.
pub const WordRegister = u32;
/// Following ARM terminology, `half words` are 16 bits. This type clarifies
/// that the underlying integer type is being used as a peripheral register.
pub const HalfWordRegister = u16;
/// Following ARM terminology, `bytes` are 8 bits. This type clarifies that the
/// underlying integer type is being used as a peripheral register.
pub const ByteRegister = u8;
/// Some peripheral registers are read only or write only. The
/// `NoAccessRegisterType` serves to provide a format for those registers that
/// have no meaningful fields for the direction of access.
pub const NoAccessRegisterType = packed struct(WordRegister) {
_reserved: WordRegister,
};
/// An unsigned integer type that can hold a bit offset of a field in a WordRegister.
pub const BitFieldOffset = math.Log2Int(WordRegister);
/// An unsigned integer type that can hold the length in bits of a field in a WordRegister.
pub const BitFieldLength = math.Log2Int(WordRegister); // no 32-bit bitfields
/// The `DefineBitField` type function returns a type suitable to hold the
/// definition of a bit field that may be resolved at run time to manipulate
/// the field contents. Usually, field access is provided by `comptime` known
/// bit fields. However, cases do arise when it is useful to store bit field
/// information and use it at runtime to obtain the same types of field reads and
/// updates available from the functions that use compile time bit field descriptions.
fn DefineBitField(
comptime T: type,
) type {
return struct {
offset: BitFieldOffset,
length: BitFieldLength,
const FieldId = meta.FieldEnum(T);
const Self = @This();
pub fn init(
comptime field_id: FieldId,
) Self {
const offset = @bitOffsetOf(T, @tagName(field_id));
const length = @bitSizeOf(meta.FieldType(T, field_id));
std.debug.assert(offset + length < 32);
return .{
.offset = offset,
.length = length,
};
}
fn fieldMask(
self: Self,
) WordRegister {
return ((@as(WordRegister, 1) << self.length) - 1) << self.offset;
}
pub fn insertField(
self: Self,
reg_value: WordRegister,
field_value: WordRegister,
) WordRegister {
const mask = self.fieldMask();
const shifted_value = field_value << self.offset;
const masked_value = shifted_value & mask;
return (reg_value & ~mask) | masked_value;
}
pub fn extractField(
self: Self,
reg_value: WordRegister,
) WordRegister {
const mask = self.fieldMask();
return (reg_value & mask) >> self.offset;
}
};
}
/// The `Register` type function defines a data type and a set of methods used
/// to access the fields of a memory mapped peripheral device register. The
/// `Read` argument is a data type that is assumed to be a `packed struct`
/// definition for the fields of the register when it is read from memory. The
/// `Write` argument is a data type that is assumed to be a `packed struct`
/// definition for the fields of the register when it is written to memory.
/// The differences between these types can be used to control read only or
/// write only fields within the register. The returned `type` has associated
/// methods that access the fields of the register according to the `Read` and
/// `Write` type definitions. Register fields are identified by enumeration
/// literals whose names match those of the field declarations of the `Read`
/// and `Write` types.
pub fn Register(
comptime Read: type,
comptime Write: type,
) type {
return packed struct {
/// The raw memory associated with the register.
reg_value: WordRegister,
const Self = @This();
/// It is convenient to hold the argument types internally.
pub const ReadType = Read;
pub const WriteType = Write;
/// An enumerated type with a tag for each field in the `ReadType`.
pub const ReadField = meta.FieldEnum(Read);
/// An enumerated type with a tag for each field in the `WriteType`.
pub const WriteField = meta.FieldEnum(Write);
/// Define a bit field type for run-time specified bit fields.
pub const BitField = DefineBitField(ReadType);
/// The `readDirect` function reads the memory location of the register
/// and returns is value as an uninterpreted integer number. This is a
/// so called, _raw read_, of the register.
pub fn readDirect(
self: *volatile Self,
) WordRegister {
return self.reg_value;
}
/// The `writeDirect` function writes the memory location of the
/// register and with an uninterpreted integer number. This is a so
/// called, _raw write_, to the register.
pub fn writeDirect(
self: *volatile Self,
value: WordRegister,
) void {
self.reg_value = value;
}
/// The `read` function reads the value of the peripheral register
/// returning that value typed according to the `ReadType` of the
/// register.
pub fn read(
self: *volatile Self,
) ReadType {
return @bitCast(self.readDirect());
}
/// The `write` function writes the value of the peripheral register
/// with a value typed according to the `WriteType` of the register.
pub fn write(
self: *volatile Self,
value: WriteType,
) void {
self.writeDirect(@bitCast(value));
}
/// The `readField` function reads the given register and returns the
/// field given by `field_id`.
pub fn readField(
self: *volatile Self,
/// An enumeration literal identifying the register field to return.
comptime field_id: ReadField,
) meta.FieldType(ReadType, field_id) {
const reg_value = self.read();
return @field(reg_value, @tagName(field_id));
}
/// The `updateField` function performs a read - modify - write
/// sequence to change the field given by, `field_id`, to a new value
/// given by, `value`. All other register fields are unaffected.
pub fn updateField(
self: *volatile Self,
comptime field_id: WriteField,
value: meta.FieldType(WriteType, field_id),
) void {
// New value is initialized with current register contents.
var new_value: WriteType = @bitCast(self.read());
@field(new_value, @tagName(field_id)) = value;
self.write(new_value);
}
/// The `writeField` function performs a write to set the field given
/// by, `field_id` to the value given by, `value`. **All other fields
/// are written as zero.**
pub fn writeField(
self: *volatile Self,
comptime field_id: WriteField,
value: meta.FieldType(WriteType, field_id),
) void {
// New value is initialized to zero.
var new_value: WriteType = @bitCast(@as(WordRegister, 0));
@field(new_value, @tagName(field_id)) = value;
self.write(new_value);
}
/// The `updateFields` function performs a read - modify - write
/// sequence to change the values of all the fields given by the
/// `field_values` argument. The `field_values` is assumed to be an
/// anonymous struct literal giving the field names and values which
/// are to be updated. This function provides a more convenient
/// interface to update multiple fields in a single operation.
pub fn updateFields(
self: *volatile Self,
field_values: anytype,
) void {
// New value starts with current register contents.
var new_value: WriteType = @bitCast(self.read());
inline for (comptime meta.fieldNames(@TypeOf(field_values))) |field_name| {
@field(new_value, field_name) = @field(field_values, field_name);
}
self.write(new_value);
}
/// The `writeFields` function performs a register write to change the
/// values of all the fields given by the `field_values` argument. The
/// `field_values` is assumed to be an anonymous struct literal giving
/// the field names and values which are to be updated. This function
/// provides a more convenient interface to write multiple fields in a
/// single operation. **All fields not included in `field_values` are
/// written as zero.**
pub fn writeFields(
self: *volatile Self,
field_values: anytype,
) void {
// New value is initialized to zero.
var new_value: WriteType = @bitCast(@as(WordRegister, 0));
inline for (comptime meta.fieldNames(@TypeOf(field_values))) |field_name| {
@field(new_value, field_name) = @field(field_values, field_name);
}
self.write(new_value);
}
/// The `setBitField` function performs a read - modify - write
/// sequence to write one to the register field given by `field_id`.
/// The given `field_id` field must be a `u1` type field. All other
/// fields in the register are unmodified.
pub fn setBitField(
self: *volatile Self,
comptime field_id: WriteField,
) void {
if (comptime meta.FieldType(WriteType, field_id) != u1) {
@compileError("field, " ++ @tagName(field_id) ++ ", is not a single bit field");
}
self.updateField(field_id, 1);
}
/// The `insertFieldValue` function performs a read - modify - write operation
/// to update the value of the register field given by `bitfield` to the
/// value given by `field_value`.
pub fn insertFieldValue(
self: *volatile Self,
bitfield: BitField,
field_value: WordRegister,
) void {
var new_value = self.readDirect();
new_value = bitfield.insertField(new_value, field_value);
self.writeDirect(new_value);
}
/// The `extractFieldValue` function returns the value in a register that is
/// described by the `bitfield` argument.
pub fn extractFieldValue(
self: *volatile Self,
bitfield: BitField,
) WordRegister {
const current_value = self.readDirect();
return bitfield.extractField(current_value);
}
/// The `testBitField` function returns `true` if the register field
/// given by, `field_id`, is one and `false` otherwise. The given
/// `field_id` field must be a `u1` type field.
pub fn testBitField(
self: *volatile Self,
comptime field_id: ReadField,
) bool {
if (comptime meta.FieldType(ReadType, field_id) != u1) {
@compileError("field, " ++ @tagName(field_id) ++ ", is not a single bit field");
}
return self.readField(field_id) == 1;
}
/// The `clearBitField` function performs a read - modify - write
/// sequence to write zero to the register field given by `field_id`.
/// The given `field_id` field must be a `u1` type field. All other
/// fields in the register are unmodified.
pub fn clearBitField(
self: *volatile Self,
comptime field_id: WriteField,
) void {
if (comptime meta.FieldType(WriteType, field_id) != u1) {
@compileError("field, " ++ @tagName(field_id) ++ ", is not a single bit field");
}
self.updateField(field_id, 0);
}
/// The `toggleBitField` function performs a read - modify - write
/// sequence to write the bit complement of the current field value to
/// the register field given by `field_id`. The given `field_id` field
/// must be a `u1` type field. All other fields in the register are
/// unmodified.
pub fn toggleBitField(
self: *volatile Self,
comptime field_id: WriteField,
) void {
if (comptime meta.FieldType(WriteType, field_id) != u1) {
@compileError("field, " ++ @tagName(field_id) ++ ", is not a single bit field");
}
const current = self.readField(field_id);
self.updateField(field_id, ~current);
}
/// The `writeBitField` function performs a write operation to set the
/// register field given by `field_id` to one. The given `field_id`
/// field must be a `u1` type field. **All other fields in the register
/// are written as zero.**
/// See also the `BitRegister` type function for defining registers
/// consisting entirely of single bit fields.
pub fn writeBitField(
self: *volatile Self,
comptime field_id: WriteField,
) void {
if (comptime meta.FieldType(WriteType, field_id) != u1) {
@compileError("field, " ++ @tagName(field_id) ++ ", is not a single bit field");
}
var value: WriteType = @bitCast(@as(WordRegister, 0));
@field(value, @tagName(field_id)) = 1;
self.write(value);
}
/// The `writeFieldValue` function performs a write operation to set the
/// register field given by `bitfield` to one.
/// **All other fields in the register are written as zero.**
pub fn writeFieldValue(
self: *volatile Self,
bitfield: BitField,
field_value: WordRegister,
) void {
var new_value: WordRegister = 0;
new_value = bitfield.insertField(new_value, field_value);
self.writeDirect(new_value);
}
};
}
/// The `IndexedRegister` type function creates a `packed struct` type along with
/// a set of methods that allow accessing fields within a register through
/// an index value. Each indexed register is composed of an integral number
/// of elements. The `Element` argument gives the type of the bit groups
/// in the register. The minimum number of bits in an `Element` is one
/// and a `WordRegister` must hold an integral number of `Element` type
/// values.
pub fn IndexedRegister(
/// The type of each element in the register. Note that the
/// read type and write type are the same.
comptime Element: type,
) type {
if (@bitSizeOf(Element) == 0)
@compileError("Indexed register elements must have non-zero size");
// Insist that the packing is ``close'', i.e. the number of bits in each element
// is a factor of the number of bits in a `WordRegister`. This restriction could
// probably be relaxed if the count of elements is also carried around.
if (@bitSizeOf(WordRegister) % @bitSizeOf(Element) != 0)
@compileError("Indexed register element size must be a factor of the word size");
return packed struct {
const Self = @This();
const BitMask = DefineBitField(Element);
const ElementAsInt = meta.Int(.unsigned, @bitSizeOf(Element));
const ElementIndex = math.Log2Int(WordRegister);
const element_scale: ElementIndex = @bitSizeOf(Element);
const element_mask = (@as(WordRegister, 1) << @bitSizeOf(Element)) - 1;
reg_value: WordRegister,
/// The `readDirect` function reads the memory location of the register
/// and returns is value as an uninterpreted integer number. This is a
/// so called, _raw read_, of the register.
pub fn readDirect(
self: *volatile Self,
) WordRegister {
return self.reg_value;
}
/// The `writeDirect` function writes the memory location of the
/// register and with an uninterpreted integer number. This is a so
/// called, _raw write_, to the register.
pub fn writeDirect(
self: *volatile Self,
value: WordRegister,
) void {
self.reg_value = value;
}
/// The `readIndexedElement` function returns the value of the register
/// element that is present at the `element_index` offset into the register.
pub fn readIndexedElement(
self: *volatile Self,
element_index: ElementIndex,
) Element {
return extractElement(self.ReadDirect(), element_index);
}
/// The `updateIndexedElement` function performs a read - modify - write
/// operation to set the value of the register element at `element_index`
/// to the value given by the `value` argument.
pub fn updateIndexedElement(
self: *volatile Self,
element_index: ElementIndex,
value: Element,
) void {
const new_value = insertElement(self.readDirect(), element_index, value);
self.writeDirect(new_value);
}
/// The `writeIndexedElement` function sets the value of the register element
/// at `element_index` to be the value given by the `value` argument.
/// ** All other bits in the register are written as zero**.
pub fn setIndexedElement(
self: *volatile Self,
element_index: ElementIndex,
value: Element,
) void {
const new_value = insertElement(0, element_index, value);
self.writeDirect(new_value);
}
fn insertElement(
base: WordRegister,
element_index: ElementIndex,
value: Element,
) WordRegister {
const element_offset = element_index * element_scale;
const value_as_bits: ElementAsInt = @bitCast(value);
return (base & ~(element_mask << element_offset)) | // clear field to zero
((value_as_bits & element_mask) << element_offset); // or in new value
}
fn extractElement(
base: WordRegister,
element_index: ElementIndex,
) Element {
const element_offset = element_index * element_scale;
return @bitCast((base >> element_offset) & element_mask);
}
};
}
/// The `BitRegister` type function creates a `packed struct` type and a set of
/// methods to access a register consisting entirely of single bit fields.
pub fn BitRegister() type {
return packed struct {
const Self = @This();
const BitIndex = math.Log2Int(WordRegister);
reg_value: WordRegister,
pub fn readDirect(
self: *volatile Self,
) WordRegister {
return self.reg_value;
}
pub fn writeDirect(
self: *volatile Self,
value: WordRegister,
) void {
self.reg_value = value;
}
pub fn readBitField(
self: *volatile Self,
bit_index: BitIndex,
) u1 {
const bit_mask = bitMask(bit_index);
const value = self.readDirect();
return if ((value & bit_mask) == 0) 0 else 1;
}
pub fn setBitField(
self: *volatile Self,
bit_index: BitIndex,
) void {
const bit_mask = bitMask(bit_index);
var new_value = self.readDirect();
new_value |= bit_mask;
self.writeDirect(new_value);
}
pub fn clearBitField(
self: *volatile Self,
bit_index: BitIndex,
) void {
const bit_mask = bitMask(bit_index);
var new_value = self.readDirect();
new_value &= ~bit_mask;
self.writeDirect(new_value);
}
pub fn toggleBitField(
self: *volatile Self,
bit_index: BitIndex,
) void {
const bit_mask = bitMask(bit_index);
var new_value = self.readDirect();
new_value ^= bit_mask;
self.writeDirect(new_value);
}
// single bit set, remaining bits 0
pub fn writeBitField(
self: *volatile Self,
bit_index: BitIndex,
) void {
const bit_mask = bitMask(bit_index);
self.writeDirect(bit_mask);
}
fn bitMask(
bit_index: BitIndex,
) WordRegister {
return @as(WordRegister, 1) << bit_index;
}
};
}
test "mmio register functions" {
const DrRegisterRead = packed struct(WordRegister) {
data: u8,
fedata: u1,
pedata: u1,
bedata: u1,
oedata: u1,
_reserved: u20,
};
const DrRegisterWrite = packed struct(WordRegister) {
data: u8,
_reserved: u24,
};
const UartDr = Register(DrRegisterRead, DrRegisterWrite);
const LcrhRegister = packed struct(WordRegister) {
brk: u1,
pen: u1,
eps: u1,
stp: u1,
fen: u1,
wlen: enum(u2) {
char_is_5_bits = 0,
char_is_6_bits = 1,
char_is_7_bits = 2,
char_is_8_bits = 3,
},
sps: u1,
_reserved: u24,
};
const UartLcrh = Register(LcrhRegister, LcrhRegister);
const UartPeripheral = extern struct {
dr: UartDr,
lcrh: UartLcrh,
};
const uart_reg_count = 0x48 / @sizeOf(WordRegister);
var fake_peripheral: [uart_reg_count]WordRegister = [_]WordRegister{0} ** uart_reg_count;
fake_peripheral[1] = 3 << @bitOffsetOf(LcrhRegister, "wlen");
const uart_0 = @as(*volatile UartPeripheral, @ptrCast(&fake_peripheral));
const lcrh = uart_0.lcrh.readDirect();
try testing.expectEqual(@as(WordRegister, 3 << 5), lcrh);
uart_0.lcrh.writeDirect(42);
try testing.expectEqual(@as(WordRegister, 42), uart_0.lcrh.readDirect());
uart_0.lcrh.writeDirect(0); // clear things out
var lcrh_value = uart_0.lcrh.read();
try testing.expectEqual(@as(u2, 0), @intFromEnum(lcrh_value.wlen));
lcrh_value.wlen = .char_is_8_bits;
uart_0.lcrh.write(lcrh_value);
try testing.expectEqual(@as(u2, 3), @intFromEnum(lcrh_value.wlen));
uart_0.lcrh.updateFields(.{
.wlen = .char_is_6_bits,
});
const wlen_value = uart_0.lcrh.readField(.wlen);
try testing.expectEqual(.char_is_6_bits, wlen_value);
uart_0.lcrh.updateFields(.{
.brk = 1,
.fen = 1,
});
try testing.expectEqual(@as(u1, 1), uart_0.lcrh.readField(.brk));
try testing.expectEqual(@as(u1, 1), uart_0.lcrh.readField(.fen));
uart_0.lcrh.setBitField(.eps);
try testing.expectEqual(@as(u1, 1), uart_0.lcrh.readField(.eps));
uart_0.lcrh.clearBitField(.eps);
try testing.expectEqual(@as(u1, 0), uart_0.lcrh.readField(.eps));
uart_0.lcrh.toggleBitField(.eps);
try testing.expectEqual(@as(u1, 1), uart_0.lcrh.readField(.eps));
// start with all zero bits
uart_0.lcrh.writeDirect(0);
uart_0.lcrh.writeBitField(.eps);
try testing.expectEqual(@as(u1, 1), uart_0.lcrh.readField(.eps));
uart_0.lcrh.writeDirect(0);
uart_0.lcrh.writeBitField(.eps);
try testing.expectEqual(@as(u1, 1), uart_0.lcrh.readField(.eps));
uart_0.lcrh.writeBitField(.pen);
try testing.expectEqual(@as(u1, 1), uart_0.lcrh.readField(.pen));
uart_0.lcrh.writeBitField(.stp);
try testing.expectEqual(@as(u1, 1), uart_0.lcrh.readField(.stp));
}