Model

Models define how values are encoded to bytes and decoded back to Python types. In practice, a Model is selected per Variable (usually with base=... on RemoteVariable), and Block transactions apply that Model during read/write.

Most users interact with Models indirectly through Variable base and related type configuration. Advanced users can use Model classes directly for custom encoding/decoding workflows.

Why Models Exist

Models separate three closely related concerns:

  1. Hardware representation such as bit width, endianness, and signedness.

  2. Python-facing type behavior such as int, bool, float, or str.

  3. Conversion logic used by Block staging and transaction code.

This keeps Variable definitions clear and lets the same conversion behavior be reused across many registers.

How Variables Select A Model

Most users select a Model when defining each RemoteVariable.

import pyrogue as pr

class MyRegs(pr.Device):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.add(pr.RemoteVariable(
            name='Status',
            offset=0x00,
            bitSize=32,
            mode='RO',
            base=pr.UInt,   # Model selection
        ))

In the current implementation, RemoteVariable usually receives a Model class such as pr.UInt or pr.Float rather than a pre-built instance. The Variable constructor then instantiates the Model using the Variable’s effective bit width.

That is why base=pr.UInt is the normal form. For custom or parameterized Models such as fixed-point encodings, the base you pass must still line up with the Variable’s bit layout.

What A Model Defines

A Model is more than a byte-conversion helper. It also defines metadata that shapes how the Variable behaves in Python and in the generated tree metadata.

Common Model responsibilities include:

  • pytype for the native Python-facing type.

  • defaultdisp for the default display format when the Variable does not override disp.

  • minValue() and maxValue() for default limit behavior.

  • toBytes() and fromBytes() for raw conversion.

  • fromString() for display-string parsing.

  • modelId for the lower-level Rogue memory-processing path.

This is why Models belong conceptually between Variable and Blocks: Variables present the value-oriented API, Models define the encoding, and Blocks move the bytes.

Built-In Model Families

Common built-in Models include:

  • Integer: UInt, UIntBE, UIntReversed, Int, IntBE

  • Boolean: Bool

  • Text: String

  • Floating point: Float, FloatBE, Double, DoubleBE

  • Fixed point: Fixed, UFixed

  • Custom Python conversion path: Models that use modelId = rim.PyFunc

Built-In Model Types

Model

Hardware Type

Python Type

Bit Size

Notes

UInt

unsigned integer

int

unconstrained

Little endian

UIntBE

unsigned integer

int

unconstrained

Big endian

UIntReversed

unsigned integer

int

unconstrained

Reversed bit order

Int

signed integer

int

unconstrained

Little endian

IntBE

signed integer

int

unconstrained

Big endian

Bool

bit

bool

1-bit

String

bytes

string

unconstrained

Float

32-bit float

float

32-bits

FloatBE

32-bit float

float

32-bits

Big endian

Double

64-bit float

float

64-bits

DoubleBE

64-bit float

float

64-bits

Big endian

Fixed

fixed point

float

unconstrained

Fixed-point conversion

UFixed

fixed point

float

unconstrained

Unsigned fixed-point conversion

For low-level Model class reference and constants, see Model.

Fixed-Point Models

Fixed and UFixed are important enough, and nuanced enough in practice, that they now have their own focused page:

That page explains the fixed-point mental model, bitSize and binPoint, the conversion formulas, numeric range, and a worked example.

Model Utility Helpers

The Python Model module also includes a few small helpers that are useful when you need to reason about raw layouts or write a custom Model:

  • wordCount() computes how many words are needed for a bit width.

  • byteCount() computes how many bytes are needed for a bit width.

  • reverseBits() reverses bit significance across a fixed width.

  • twosComplement() interprets a value with two’s-complement sign semantics.

These are especially useful in custom conversion code and in documentation examples where raw register layout matters.

Model Instances And Caching

One implementation detail is worth knowing when you use Model classes directly: the Model metaclass caches instances by constructor arguments.

In practice, repeated requests for the same Model and parameters, such as pr.UInt(16), return the same shared Model instance. Most users do not need to think about this because Variables create Models for them, but it explains why Models are treated more like reusable type descriptors than like per-Variable mutable state.

Model Conversion Flow In Block Access

When a Variable is read or written:

  1. Variable logic resolves its Model and layout metadata.

  2. Block conversion methods pack or unpack values for that Model.

  3. Block transaction methods move bytes to or from hardware.

Model selection answers “how bytes map to values”; Block transactions answer “when bytes move.”

For transaction flow details, see Blocks.

Custom Model Example

Use a custom Model when built-ins do not match an encoding format.

import pyrogue as pr
import rogue.interfaces.memory as rim

class BitReversedUInt(pr.Model):
    pytype = int
    defaultdisp = '{:#x}'
    modelId = rim.PyFunc

    def __init__(self, bitsize):
        super().__init__(bitsize)

    def toBytes(self, value):
        # Reverse bit order so MSB appears at bit position 0 in memory.
        raw_normal = int(value)
        raw_reversed = pr.reverseBits(raw_normal, self.bitSize)
        return raw_reversed.to_bytes(pr.byteCount(self.bitSize), 'little', signed=False)

    def fromBytes(self, ba):
        raw_reversed = int.from_bytes(ba, 'little', signed=False)
        return pr.reverseBits(raw_reversed, self.bitSize)

    def fromString(self, string):
        return int(string, 0)

    def minValue(self):
        return 0

    def maxValue(self):
        return (2 ** self.bitSize) - 1

class MyDevice(pr.Device):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.add(pr.RemoteVariable(
            name='AsicStatus',
            offset=0x1000,
            bitSize=16,
            bitOffset=0,
            mode='RW',
            base=BitReversedUInt,
        ))

This is the same overall pattern used by built-in Models: define the Python type behavior and the byte conversion behavior in one reusable object, then attach that Model to one or more Variables.

How BitReversedUInt Differs From Built-In UInt

This example shows a register layout where hardware bit order differs from normal integer interpretation:

  • Built-in UInt assumes standard bit significance ordering.

  • BitReversedUInt reverses bits on write and read so software uses normal integer semantics while memory uses reversed bit positions.

  • This is useful for custom register layouts where the hardware view is not a normal integer bit ordering.

What To Explore Next

API Reference