Code to Models

mmio.zig at tip
Login

File projects/bottom_up/tangled/src/mmio.zig from the latest check-in


//
// 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));
}