RemoteVariable

RemoteVariable maps a Variable onto hardware-accessible memory. In most PyRogue applications, this means a register or bit field in an FPGA or in a device reachable through the Rogue memory path. It is the standard choice for register-style control and monitoring.

When To Use RemoteVariable

Use RemoteVariable when the tree value should correspond to a specific memory-mapped hardware location.

That usually means:

  • Configuration registers

  • Status registers

  • Counters, flags, masks, and control fields

  • Wide logical values assembled from more than one register location

Unlike LocalVariable, a RemoteVariable participates in the hardware transaction path. Reads and writes flow through the parent Device, the Block structure built during attachment, and the underlying memory interface.

What You Usually Set

Most RemoteVariable definitions combine the shared Variable parameters from Variable with a small set of hardware-mapping parameters:

  • offset for the memory location.

  • bitSize and bitOffset for the field layout.

  • base for the typed interpretation of the data.

  • verify when write-back verification policy matters.

  • overlapEn when multiple Variables share one register word.

  • numValues, valueBits, and valueStride for arrays or tables.

  • bulkOpEn when large Variables need different transaction behavior.

The first three parameters define where the value lives and how its bits should be interpreted. The remaining options mainly control transaction policy and layout edge cases, such as write verification, shared register words, packed arrays, and bulk-operation participation.

Memory Mapping Parameters

A RemoteVariable is defined by memory-mapping parameters such as:

  • offset

  • bitSize

  • bitOffset

  • Optional base/type Model and packing parameters

These define where the value exists in the hardware address space and how its bits should be interpreted. The base Model tells PyRogue whether the value should behave like an unsigned integer, signed integer, boolean, floating point quantity, byte array, and so on.

At the tree level, this means a RemoteVariable gives users a typed, formatted value-oriented interface while PyRogue handles the lower-level details of byte packing, block grouping, and memory transactions underneath.

offset, bitSize, and bitOffset can each be either a single value or lists. List form is used when one logical Variable is split across multiple register locations.

If the value uses a fixed-point interpretation such as base=pr.Fixed(...) or base=pr.UFixed(...), see Fixed-Point Models for the scaling and display model behind those types.

How RemoteVariable Fits The Tree

RemoteVariable is where the Variable and Device narratives meet:

  • Variable provides the user-facing typed value interface

  • Device provides the write/verify/read/check transaction model

  • Block groups one or more compatible RemoteVariables into efficient transaction units

So when a user calls get(read=True) or set(write=True), the actual hardware activity still runs through the parent Device and its Block operations.

Example Patterns

Most RemoteVariable definitions in real PyRogue device trees fall into a small set of patterns:

  • A single control or status field in one register word

  • An enumerated field where raw bit values map to named hardware modes

  • A logical value assembled from multiple register locations

  • A packed array exposed through numValues, valueBits, and valueStride

  • Multiple Variables intentionally sharing one register word through overlapEn=True

The following examples mirror those patterns.

Single Register Fields

This is the most common case: a normal register field with a fixed offset, bit position, and access mode.

import pyrogue as pr

class MyDevice(pr.Device):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)

        self.add(pr.RemoteVariable(
            name='ScratchPad',
            description='Register used to test reads and writes',
            offset=0x00,
            bitSize=32,
            bitOffset=0,
            mode='RW',
            base=pr.UInt,
            disp='{:#08x}',
        ))

        self.add(pr.RemoteVariable(
            name='RxReady',
            description='Polled status bit',
            offset=0x04,
            bitSize=1,
            bitOffset=0,
            mode='RO',
            base=pr.Bool,
            pollInterval=1,
        ))

Enumerated Fields

Enum mappings are also common. They let the Variable present hardware-meaningful state names while still using the underlying integer register field.

import pyrogue as pr

class MyDevice(pr.Device):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)

        self.add(pr.RemoteVariable(
            name='RxDataWidth',
            offset=0x10,
            bitOffset=11,
            bitSize=3,
            mode='RW',
            base=pr.UInt,
            enum={
                2: '16',
                3: '20',
                4: '32',
                5: '40',
                6: '64',
                7: '80',
            },
        ))

Split Variables Across Multiple Addresses

Some hardware maps place one logical field across multiple words. In that case, pass lists for offset, bitOffset, and bitSize.

In this example, one logical 7-bit field is split across two 32-bit words in the memory map:

  • offset=0x34 contributes bit 15 (1 bit)

  • offset=0x38 contributes bits 5:0 (6 bits)

PyRogue combines those segments in list order into one Variable value.

import pyrogue as pr

class GtRegs(pr.Device):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)

        self.add(pr.RemoteVariable(
            name='RXDFELPMRESET_TIME',
            offset=[0x34, 0x38],
            bitOffset=[15, 0],
            bitSize=[1, 6],
            mode='RW',
            base=pr.UInt,
        ))

Packed Arrays

When hardware exposes a register table or memory-backed array, use numValues, valueBits, and valueStride. This pattern is common for tables, coefficient memories, and page/register dumps.

The array below exposes 256 32-bit elements at one base offset.

import pyrogue as pr

class TableRegs(pr.Device):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)

        self.add(pr.RemoteVariable(
            name='DataBlock',
            offset=0x0000,
            bitSize=32 * 0x100,
            bitOffset=0,
            numValues=0x100,
            valueBits=32,
            valueStride=32,
            updateNotify=True,
            bulkOpEn=True,
            overlapEn=True,
            verify=True,
            hidden=True,
            base=pr.UInt,
            mode='RW',
            groups=['NoStream', 'NoState', 'NoConfig'],
        ))

For very large arrays, projects often disable per-write verification or bulk operations to control traffic and latency.

self.add(pr.RemoteVariable(
    name='PixelData',
    offset=0x4000,
    bitSize=32 * num_cols,
    bitOffset=0,
    numValues=num_cols,
    valueBits=32,
    valueStride=32,
    bulkOpEn=False,
    verify=False,
    hidden=True,
    base=pr.UInt,
    mode='RW',
    groups=['NoStream', 'NoState', 'NoConfig'],
))

Overlapping Variables In One Register

Sometimes one register word is exposed both as a full-word access point and as smaller subfields. In that case, each Variable sharing the same address range must enable overlapEn=True.

import pyrogue as pr

class SharedWordRegs(pr.Device):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)

        self.add(pr.RemoteVariable(
            name='ConnectionConfig',
            offset=0x4014,
            base=pr.UIntBE,
            mode='WO',
            overlapEn=True,
        ))

        self.add(pr.RemoteVariable(
            name='ConnectionSpeed',
            offset=0x4014,
            bitSize=16,
            bitOffset=16,
            mode='RO',
            overlapEn=True,
            enum={
                0x28: 'CXP_1',
                0x30: 'CXP_2',
                0x38: 'CXP_3',
                0x40: 'CXP_5',
                0x48: 'CXP_6',
                0x50: 'CXP_10',
                0x58: 'CXP_12',
            },
            base=pr.UIntBE,
        ))

Wide Values Across Many Registers

List form also works well for read-only counters or masks split across many consecutive words.

For ES_QUALIFIER, the logical value is assembled from five 16-bit segments:

  • offset=0xB0 -> bits 15:0

  • offset=0xB4 -> bits 31:16

  • offset=0xB8 -> bits 47:32

  • offset=0xBC -> bits 63:48

  • offset=0xC0 -> bits 79:64

For RX_PRBS_ERR_CNT, the logical value is assembled from two 16-bit segments:

  • offset=0x978 -> bits 15:0

  • offset=0x97C -> bits 31:16

import pyrogue as pr

class EyeScanRegs(pr.Device):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)

        self.add(pr.RemoteVariable(
            name='ES_QUALIFIER',
            offset=[0xB0, 0xB4, 0xB8, 0xBC, 0xC0],
            bitOffset=[0, 0, 0, 0, 0],
            bitSize=[16, 16, 16, 16, 16],
            mode='RO',
            base=pr.UInt,
        ))

        self.add(pr.RemoteVariable(
            name='RX_PRBS_ERR_CNT',
            offset=[0x978, 0x97C],
            bitOffset=[0, 0],
            bitSize=[16, 16],
            mode='RO',
            base=pr.UInt,
        ))

What To Explore Next

API Reference

See RemoteVariable for generated API details.