I need a bitfield definition that's compatible with ctypes Structures, so that I can use it as a pointer to a memory-mapped set of hardware registers, i.e.
class RegFile(Structure):
_fields_ = [
('ticks', c_uint32),
('id', id_bitfield),
]
Ideally they'd be very Pythonic things, and behave mostly like dicts. I've managed to work most of the gory details out to where I now have a factory function that makes bitfield classes similarly to making namedtuples. Here's an example of it ripping apart the fields of a standard float.
def make_register(name, fields, basetype=c_uint32):
# Define the underlying bitfield type
bitfield = type(name + '_bitfield', (LittleEndianStructure, ), {})
bitfield._pack_ = 1
bitfield._fields_ = fields
# Set up the union
d = {
'_fields_' : [('base', basetype), ('_b', bitfield)],
'_anonymous_' : ('_b',),
'__iter__' : _register_iter,
'keys' : _register_keys,
'items' : _register_items,
'update' : _register_update
}
return type(name, (Union, ), d)
ieee754_fields = [
('mantissa', c_uint, 23),
('exponent', c_uint, 8),
('sign', c_uint, 1)
]
IEEE754 = make_register('IEEE754', ieee754_fields, c_float)
x = IEEE754()
This works decently, but isn't a particularly Pythonic feeling syntax. Ideally I'd have some answer that allows me to define the bitfield class as:
class IEEE754(Register):
"""Individual bitfields of a standard IEEE-754 floating point number."""
_fields_ = ieee754_fields
_basetype_ = c_float
But I haven't been able to get that Register class to be a thing. It seemed like Register should inherit from Union and have some metaclass magic applied, but that way metaclass conflicts against Union lie. Any thoughts? Or should I just stick with what I've got?
You can achieve what you describe by creating a Register metaclass that extends type(ctypes.Union)
as below:
class Register(type(ctypes.Union)):
def __new__(mcs, name, bases, dict):
#make the bitfield class
class regBitfield(ctypes.LittleEndianStructure):
_fields_ = dict['_fields_']
#set up the Union
class regUnion(ctypes.Union):
_fields_ = [('base', dict['_basetype_']), ('_b', regBitfield)]
_anonymous_ = ('_b', )
__iter__ = _register_iter
keys = _register_keys
items = _register_items
update = _register_update
#return a subclass of the temporary regUnion class
return type(ctypes.Union).__new__(mcs, name, (regUnion,), dict)
class IEEE754: #for Python3 use class IEEE754(metaclass=Register):
__metaclass__ = Register #omit this line Python3
_fields_ = [('mantissa', ctypes.c_uint, 23),
('exponent', ctypes.c_uint, 8),
('sign', ctypes.c_uint, 1)
]
_basetype_ = ctypes.c_float
This works in almost the same way as your make_register
function, but utilizes the existing machinery of the class system and the special rules for extending ctypes.Union
and ctypes.Structure
. Specifically, any subclass of a subclass of ctypes.Union
or ctypes.Structure
will extend the _fields_
list, rather than replace it. Knowing this, we can create temporary classes within the __new__
of our Register
metaclass that the ultimate class extends. With the above:
>>> myFloat = IEEE754(42.0)
>>> print(myFloat.base)
42.0
>>> print(myFloat.mantissa)
2621440L
>>> fltReg._fields_
[('mantissa', <class 'ctypes.c_ulong'>, 23), ('exponent', <class 'ctypes.c_ulong'>, 8), ('sign', <class 'ctypes.c_ulong'>, 1)]
The Register.__new__
method might break if the type being created has multiple bases.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With