from timeit import dummy_src_name
Python library that provides classes for representing, defining and accessing registers in the embedded devices.
To add this library to your project do
uv add rgscore- To provide python API to define registers and serialize it into persistent json store/file.
- To provide access from Python to serialized form of register definitions (aka. addrMap json).
- Together with other libraries, such as i2c-api and i2c-drivers, to provide read and write access from Python to registers in live devices.
- Provide intuitive interactive API for interfacing with live devices.
To fulfill above goals this library defines following classes
Register- defines/holds name, address, description, in-memory bitarray storage, reset value and a model of fields (list ofFieldDefobjects).FieldDef- holds register field definition which includes field name, bit-range in the larger register bitarray, numeric type, r/w flag, description and optional enum values that maps numeric value held in a register field to symbolic names.EnumValue- symbolic name corresponding to a numeric value in the field. Also includes description.RLink- abstract class that connects registers to physical hardware. Requires implementation that drives low level communication to live device.RLinkI2C- implementation ofRLinksuitable for linking classes in this library to physical devices over I2C bus.AddrMap- grouping of registers ensuring their uniqueness within addrMap. Primary class for (de)serialization to/from json file.
Registers are grouped into ordered collections known as addrMap's, represented by AddrMap class. When registers are
added to the addrMap this ensures that they are unique (both, with regard to register names and addresses) within a
given addrMap. Registers can be added to the addrMap at the time of construction or later by
calling AddrMap::add(...) method.
from rgscore import Register, AddrMap, FieldDef, EnumValue
addrMap = AddrMap(registers=[
Register(
bit_len=8,
name="Foo",
address=0x01,
desc="Register Foo",
model=[FieldDef.value_of("a@[3:0]U4.1#ro")]
)
])
addrMap.add(Register(
bit_len=8,
name="Bar",
address=0x02,
model=[
FieldDef.value_of("b@[3:0]U4.0#ro").with_description("field b").with_enum(
[EnumValue("START", 1, "Starting..."), EnumValue("STOP", 0, "Stoping...")]
)
],
))AddrMap ensures that registers do not have overlapping names or addresses.
Class Register holds
name- optional human-readable name.address- optional integer address within larger address space.desc- optional description.reset- reset value (default is 0x0)bit_len- bit width.address_bus_width_bytes- width of the address bus (default is 1).model- optional model of register fields, i.e. an array ofFieldDefinstances.link_provider- optional prover ofRLink, object that links this register to the physical hardware.
If model is None, then model with just one field spanning the whole of
the register width is autogenerated with numerical type of unsigned int.
When adding it to the AddrMap it must have an address. If name is not
present, then adding it to the AddrMap will autogenerate name in the
format R${hex_address}.
Class FieldDef can be created using string description by calling
FieldDef.value_of(...). Where description is
<field_name>@<bit_range><num_type>
or
<field_name>@<bit_range><num_type>#<rw_flag>
where <bit_range> is [<end_offset>,<start_offset>]
<num_type> is fixed point number definition in
simplified Q_(number_format), i.e. U or S (for unsigned or
signed) followed by bit-width, then dot, and then fraction.
Flag <rw_flag> is optional and can be "rw" (default) or "ro".
Description can be added by calling with_description(...) and enum values with_enum(...)
Instance of AddrMap can writen down into json file and read back like so
from pathlib import Path
from rgscore import AddrMap
addrMap = AddrMap(...)
# write into a file
addrMapFilePath = Path.home() / "addrMap.json"
addrMap.save_to_json_file(addrMapFilePath)
# load from a file
addrMap = AddrMap.new_instance_from_file(addrMapFilePath.read_text())AddrMap class can be instantiated with the URL that points to addrMap json file.
from rgscore import AddrMap
addrMap = AddrMap.new_instance_from_url("https://.../addrMap.json")By default, this will cache downloaded json file at $HOME/.rgscore/cache/
directory, so that subsequent calls to this function will load addrMap from
cache. Cache can be refreshed if force_reload=True argument is set.
To change cache location you can set RGSCORE_CACHE_DIR environment variable
to another folder.
To access physical hardware you need to create instance of RLink that
links AddrMap (and therefore registers) to live chip over some physical bus.
Below is an example of doing that over I2C bus. This requires you to bring in
two more libraries, i2c-api
and i2c-drivers.
from i2c_drivers import I2CMasterI2CDriver
from rgscore import AddrMap, RLinkI2C
addrMap = AddrMap.new_instance_from_file("addrMap-BMP581.json", link=RLinkI2C(I2CMasterI2CDriver(), 0x47))
odr_config = addrMap.get_register_by_name("ODR_CONFIG")
odr_config.read() # read from the chip at I2C address 0x47
print("pwr_mode", odr_config.get_field_value("pwr_mode"))
# set new pwr_mode and write this register to the chip
odr_config.set_field_value("pwr_mode", 1)
odr_config.write()API described above is more applicable to developers writing applications on top of this library. For electrical
engineers using this library there are two other API end points, dut (aka. Device Under Test) and regList. The first
one dut is for accessing live registers values, and second one regLsit holds minimal necessary subset of register
and fields definitions.
Consider an example of accessing device BMP581. We assume that it is present on the I2C bus at address 0x47. For this chip we do have address map json file at https://raw.githubusercontent.com/Brainless-Software/rgscore/refs/heads/master/devices/AddrMap_BMP581.json
from i2c_drivers import I2CMasterI2CDriver
from rgscore import AddrMap, RLinkI2C, read, write
addrMap = AddrMap.new_instance_from_url(
"https://raw.githubusercontent.com/Brainless-Software/rgscore/refs/heads/master/devices/AddrMap_BMP581.json",
link=RLinkI2C(I2CMasterI2CDriver(), 0x47)
)
dut = addrMap.dut
regList = addrMap.regListFor dut (instance of DeviceUnderTest class) following rules apply:
dut.$register_namereturns a register (instance ofHardwareRegister)dut.$register_name.$field_namereturns value of a field in this register.dut.$register_name.$field_name = $number | EnumValueassigns value to this field.read(dut.$register_name)reads register value from the connected hardware device.write(dut.$register_name)writes register value to the connected hardware device.
For regList (instance of RegList class) following rules apply:
regList.$register_namereturnsFieldModelAccessorclass which in turn allows one to access fields.regList.$register_name.$field_namereturn object to access associated enum values if any.regList.$register_name()returns list of field names.regList.$register_name.$field_name()returns list of enum values if any.regList.$register_name.$field_name($number)returns associated enum value of$numbermatches any enum or just that number if no enums defined otherwiseNone.
The json file loaded above has entries for the following registers
from rgscore import AddrMap, Register, EnumValue, FieldDef
Register(
bit_len=8,
address=0x7E,
name="CMD",
reset=0,
model=[
FieldDef.value_of("cmd@[7:0]U8.0#rw").with_enum([
EnumValue("nvm_write_en", 0xA0), EnumValue("soft_reset", 0xB6)
])
]
)We can then issue soft reset like so
dut.CMD.cmd = regList.CMD.cmd.soft_reset # set `cmd` field to 182 (soft_reset enum value)
write(dut.CMD) # write this register to the device.
time.sleep(0.002) # reset takes 2 millisecondsNote that you can also set CMD.cmd by simply supplying a number
dut.CMD.cmd = 0xB6Accessing regList.CMD.cmd returns object with all enum values. But calling regList.CMD.cmd() returns
list of all enums for this field.
print(regList.CMD.cmd())Prints
[nvm_write_en(160.0), soft_reset(182.0)]And calling register returns list of field names
print(regList.CMD())Prints
['cmd']