Blocks
A Block is the transaction unit used by PyRogue memory access. Variables map bit fields into Block byte ranges, and hardware reads/writes execute at Block level.
Why Blocks Exist
Blocks separate two concerns:
Value staging and conversion (pack/unpack Variable values into bytes)
Transport sequencing (initiate/check read/write/verify operations)
This separation lets Variable APIs stay high-level while transaction handling stays efficient and ordered.
How Variables Connect To Blocks
When Devices attach to a Root, compatible RemoteVariable instances are
grouped into Blocks. Grouping follows address/size compatibility and memory
path constraints (for example minimum access width).
For RemoteVariable:
Each Variable defines offset/bit mapping metadata
During Device attach/build, Device logic groups compatible remote Variables into Blocks
Each Variable gets
_blockpointing to the Block that services itTransaction methods on Device/Root call Block transactions, not per-Variable raw bus operations
For LocalVariable:
Each Variable uses a local software Block (
LocalBlock) for in-memory set/get behavior
Implications of Block grouping:
Several Variables can share one Block transaction
Partial updates can target changed sub-ranges
Grouped Block operations reduce transaction overhead
How Device Builds Blocks
The grouping is performed by Device during attach time,
inside Device._buildBlocks(). The process is worth understanding because it
determines both transaction grouping and the default ordering later used by
bulk block operations.
At a high level, the build process is:
Walk the Device’s child Nodes.
Add each
LocalVariable’s software Block directly to the Device.Collect hardware-backed
RemoteVariableinstances whose offsets are defined.Align each RemoteVariable to the Device’s minimum access size.
Sort the RemoteVariables by
(offset, varBytes).Group overlapping compatible RemoteVariables into shared Blocks.
Reuse a pre-created custom Block when a Variable falls inside it.
Create any remaining new Blocks, bind their Variables, and attach them to the Device.
Some details matter:
LocalVariableuses a one-to-one software Block. It does not participate in the RemoteVariable grouping algorithm.RemoteVariable grouping begins from sorted address order, so the auto-built hardware Blocks are normally address-oriented.
If two RemoteVariables overlap the same byte region after alignment, they are grouped into the same Block and their internal offsets are shifted relative to that Block base.
A custom Block added ahead of time with
addCustomBlock(...)takes precedence for Variables that fall inside its address range.After a Block is chosen or created, the Device sets each Variable’s
_blockreference to that Block and enables the Block according to the Device’s current enable state.
This is why the later bulk methods in Device Block Operations do not decide grouping on the fly. By the time reads, writes, verifies, and checks run, the Device has already built the Block structure they will traverse.
Access Path (RemoteVariable)
Typical read/write path:
User/API calls
setorgeton a VariableDevice/Root APIs initiate Block transactions
Block reads/writes memory through the Device’s memory interface
Completion/check updates Variable state and notifications
In bulk operations, many Variables can share one Block transaction, improving access efficiency versus isolated per-Variable transfers.
Block APIs And Transaction Flow
Conversion vs Transaction
The Block API has two layers:
Conversion layer:
set*/get*methods convert between native types and staged Block bytes.Transaction layer:
write/read/startTransaction/checkTransactionmoves staged bytes to and from hardware.
In the C++ Variable API, a typed set call performs both steps:
Conversion into staged bytes via a bound
BlockmethodBlock::write()(write + verify/check sequence)
A typed get call similarly performs:
Block::read()Conversion from staged bytes via a bound
Blockmethod
Typical write path:
Variable
setupdates staged Block bytes using Model conversionWrite (and optional verify) transactions are initiated
Completion is checked
Typical read path:
Read transactions are initiated
Completion is checked
Bytes are decoded back into Variable values
In PyRogue terminology, waiting for operation responses is called check.
Block Helper Functions
PyRogue exposes helper functions used by Variable/Device/Root flow:
startTransaction()checkTransaction()writeBlocks()verifyBlocks()readBlocks()checkBlocks()writeAndVerifyBlocks()readAndCheckBlocks()
Most users call these indirectly through Device/Root methods. Direct
use is mainly for custom transaction sequencing.
# Bulk read all Blocks attached to a Device
myDevice.readBlocks(recurse=True)
myDevice.checkBlocks(recurse=True)
# Write only the Block backing one Variable
myDevice.writeBlocks(variable=myDevice.MyReg, checkEach=True)
Logging
Each hardware-backed Block creates its own Rogue C++ logger when Variables are bound into it.
Logger pattern:
pyrogue.memory.block.<path>Example:
pyrogue.memory.block.Root.MyDevice.MyRegister
This logger is useful for low-level register-access debugging because it emits messages during:
Variable-to-Block binding
Transaction start/check flow
Retry handling
Verify/readback behavior
Configuration example:
import rogue
rogue.Logging.setFilter('pyrogue.memory.block', rogue.Logging.Debug)
The logger name is derived from the first Variable path assigned to the Block,
so filtering by the pyrogue.memory.block prefix is usually the practical
choice.
Packing Rules And Variable Layout
The internal setBytes/getBytes helpers are used by all typed methods
and apply Variable layout metadata:
Bit offsets and bit sizes (including disjoint fields)
List semantics (
numValues,valueStride)Fast contiguous byte-copy optimization when possible
Byte reversal and bit-order constraints
Because every typed method funnels through these helpers, custom subclasses can extend behavior while preserving the same packing model.
Models In Block Conversion
Blocks use Model definitions to translate between Python-facing value types
and hardware bit/byte representation.
Canonical Model documentation is in Model.
Model-Driven Block Method Dispatch
Variable instances bind to typed Block conversion methods based on
Model and size constraints.
Model |
C++ path |
Python path |
Notes |
|---|---|---|---|
|
|
|
Raw byte semantics. |
|
|
|
Fallback for very wide values. |
|
|
|
Fallback for very wide values. |
|
|
|
Typically 1-bit value semantics. |
|
|
|
Byte payload interpreted as text. |
|
|
|
32-bit float conversion. |
|
|
|
64-bit float conversion. |
|
|
|
Uses binary-point metadata. |
|
(Python-focused) |
|
Delegates conversion to Model hooks. |
Built-in Model Families
The following built-in Models are commonly used with Blocks:
Canonical Model coverage is in Model.
Model |
Hardware Type |
Python Type |
Bit Size |
Notes |
|---|---|---|---|---|
unsigned integer |
int |
unconstrained |
Little endian |
|
unsigned integer |
int |
unconstrained |
Big endian |
|
unsigned integer |
int |
unconstrained |
Reversed bit order |
|
signed integer |
int |
unconstrained |
Little endian |
|
signed integer |
int |
unconstrained |
Big endian |
|
bit |
bool |
1-bit |
||
bytes |
string |
unconstrained |
||
32-bit float |
float |
32-bits |
||
32-bit float |
float |
32-bits |
Big endian |
|
64-bit float |
float |
64-bits |
||
64-bit float |
float |
64-bits |
Big endian |
|
fixed point |
float |
unconstrained |
Fixed-point conversion |
|
fixed point |
float |
unconstrained |
Unsigned fixed-point conversion |
Most Model conversions run in low-level C++ Block paths for performance. An important exception is very wide integer handling, where Python Model logic is used when values exceed native conversion widths.
Implementation Boundary (Python and C++)
The Block API called from PyRogue maps to the rogue.interfaces.memory
runtime layer.
In practice:
Python code invokes methods on
pyrogue.Device/pyrogue.RemoteVariableThese route into Block/Variable objects exposed by
rogue.interfaces.memoryUnderlying C++ Block/Variable code handles transaction staging, read/write/verify behavior, stale tracking, packing/unpacking, and update notification triggers
Hub Interaction
Blocks are transaction sources; Hubs are transaction routers.
During a transaction, Hub logic:
Offsets addresses by local Hub/Device base
Forwards transactions to downstream memory slaves
Splits transactions into sub-transactions when request size exceeds downstream max-access capability
This is why Variable-to-Block transactions continue to work cleanly across multi-level Device trees with address translation.
Advanced Patterns
Custom Models (Complete Example)
Custom Models are a good fit when built-in Model classes do not match the desired encoding/decoding behavior.
The example below defines a complete MyUInt custom Model and then uses it
in a RemoteVariable.
import pyrogue as pr
import rogue.interfaces.memory as rim
class MyUInt(pr.Model):
ptype = int
defaultdisp = '{:#x}'
modelId = rim.PyFunc
def __init__(self, bitsize):
super().__init__(bitsize)
def toBytes(self, value):
return int(value).to_bytes(pr.byteCount(self.bitSize), 'little', signed=False)
def fromBytes(self, ba):
return int.from_bytes(ba, 'little', signed=False)
def fromString(self, string):
return int(string, 0)
def minValue(self):
return 0
def maxValue(self):
return (1 << self.bitSize) - 1
class MyDevice(pr.Device):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.add(pr.RemoteVariable(
name='MyRegister',
description='Register with custom Model',
offset=0x1000,
bitSize=32,
bitOffset=0,
base=MyUInt,
mode='RW',
))
RemoteVariable(base=MyUInt, ...) binds this Model to Block conversion for
that Variable.
Pre-Allocating Blocks
When you need a specific transaction grouping, pre-create a Block and then add Variables that overlap that address range.
import pyrogue as pr
import rogue.interfaces.memory as rim
class GroupedDevice(pr.Device):
def __init__(self, **kwargs):
super().__init__(**kwargs)
# Pre-allocate a 128-byte Block at offset 0x1000.
self.addCustomBlock(rim.Block(0x1000, 128))
self.add(pr.RemoteVariable(
name='MyRegister',
offset=0x1000,
bitSize=32,
bitOffset=0,
base=pr.UInt,
mode='RW',
))
This can improve throughput for use cases that benefit from larger grouped transactions.
What To Explore Next
Model API and utility helpers: Model
Root bulk write/read/check sequencing: Root
C++ Block reference: Block
Python LocalBlock reference: LocalBlock