2

I'm writing a code, which handles some hardware accessible via certain communication interface. I'd like to define certain sets of registers as properties of the object describing the whole device. Lets assume, that I have functions hw_read(address, size) and hw_write(address, size, array_of_values), which allow to access the hardware.

Let's assume, that I have a block of registers with length 2, starting from address 10, and want to assign it to a property "reg1". I can do it in a following way:

class my_hardware(object):
  def __init__(self):
    # Perform initialization
    return
  @property
  def reg1(self):
      return hw_read(10,2)
  @reg1.setter
  def reg1(self,value):
      hw_write(10,2,value)

The above implementation is not very convenient, as I have to provide address and size twice. It is error prone, especially if I have to define bigger number of registers. Is it possible to implement it in such a way, that I could easily define set a few registers like below:

dev1=my_hardware()
dev1.define_register("reg1",10,2)
dev1.define_register("reg2",12,6)

And then access them via:

dev1.reg1=(0x1234,0x3211)
print dev1.reg2

The second approach has also significant advantage, that list of registers (their names, addresses and sizes) may be read from an external text file (e.g. used also for VHDL synthesis).

Update - possible solution?

After studying soe posts related to dynamical adding of properties, I have achieved the required functionality with the code shown below:

def hw_write (first,size,val):
    print "write:"+str(first)+", "+str(size)+" val="+str(val)

def hw_read (first,size):
    print "read:"+str(first)+", "+str(size)
    return (0x1234,)*size

class my_hardware(object):
    def __init__(self):
        return
    def add_reg(self,name,first,size):
        setattr(my_hardware,name,property(lambda self : hw_read(first,size), lambda self, x: hw_write(first,size,x)))

Below are sample results with dummy hw_read and hw_write functions:

>>> a=my_hardware()
>>> a.add_reg("reg1",10,2)
>>> a.add_reg("reg2",20,3)
>>> a.reg1
read:10, 2
(4660, 4660)
>>> a.reg2
read:20, 3
(4660, 4660, 4660)
>>> a.reg1=(10, 11)
write:10, 2 val=(10, 11)
>>> a.reg2=(10, 11, 12)
write:20, 3 val=(10, 11, 12)

I'll appreciate any suggestions whether the above solution is reasonable.

One problem (or feature?) which I can see, is that registers are defined "per class", not "per instance". It may be helpful, if we have a few devices with the same register sets, but may be misleading, if we want to use "my_hardware" class to access different devices with different register sets connected to the same bus.

update 2

I have found a solution, which allows to define derived classes describing particular devices, and define registers per class:

def hw_write (first,size,val):
    print "write:"+str(first)+", "+str(size)+"val="+str(val)

def hw_read (first,size):
    print "read:"+str(first)+", "+str(size)
    return (0x1234,)*size

class my_hardware(object):
    def __init__(self, base_address):
        self.ba = base_address
        return
    @classmethod
    def add_reg(myclass,name,first,size):
        setattr(myclass,name,property(lambda self : hw_read(self.ba+first,size), lambda self, x: hw_write(self.ba+first,size,x)))

Below is a sample session demonstrating correct operation:

>>> class dev1(my_hardware): 
...    pass
... 
>>> class dev2(my_hardware): 
...    pass 
... 
>>> dev1.add_reg("reg1",10,2)
>>> dev2.add_reg("reg2",15,3)
>>> a=dev1(100)
>>> b=dev2(200)
>>> a.reg1
read:110, 2
(4660, 4660)
>>> a.reg2
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'dev1' object has no attribute 'reg2'
>>> b.reg1
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'dev2' object has no attribute 'reg1'
>>> b.reg2
read:215, 3
(4660, 4660, 4660)

As you can see, register reg1 is defined in devices of class "dev1", while register reg2 is defined in devices of class dev2. Of course in this case we also need a base address to be passed to each device, and therefore I had to add "base_address" to the constructor.

Thanks, Wojtek

wzab
  • 676
  • 6
  • 17

1 Answers1

1

Update (Slight changes to match functionality of your second solution): You could implement a small Domain-Specific Language (http://en.wikipedia.org/wiki/Domain-specific_language) using the Descriptor Protocol (https://docs.python.org/3.4/howto/descriptor.html). As an example, given a descriptor:

class RegisterDescriptor(object):
    """
    A descriptor that models reading and writing to hardware registers.
    """

    def __init__(self, offset, size):
        self.offset = offset
        self.size = size

    def __get__(self, obj, type=None):
        if obj is None:
            return self
        return hw_read(obj.base_address + self.offset, self.size)

    def __set__(self, obj, value):
        if obj is None:
            raise AttributeError("Cannot set attribute")
        hw_write(obj.base_address + self.offset, self.size, value)

With this descriptor, you can now write a class and denote your registers semantically. Again, for example:

class AddressableHardware(object):
    """
    Base class for addressable hardware components.

    Attributes:
    base_address -- the base address of the device.
    """

    def __init__(self, base_address):
        self.base_address = base_address

class MyHardware(AddressableHardware):
    """
    An example hardware device.
    """

    reg1 = RegisterDescriptor(2, 4)
    reg2 = RegisterDescriptor(6, 1)

    def __init__(self, base_address):
        AddressableHardware.__init__(self, base_address)

mh = MyHardware(0x2E)
print(mh.reg1)
mh.reg2 = b'\x00'

Update: This approach is different from your second solution as I've taken a more declarative approach, where each type of device would have its own class and associated methods (maybe some helper methods to hide low-level access to the device) and deal in more Pythonic datatypes. Using the descriptor solution above, you would end up with something like:

class HardwareDeviceA(AddressableHardware):
    reg1 = RegisterDescriptor(10, 2)
    reg2 = RegisterDescriptor(20, 3)

    def __init__(self, base_address):
        AddressableHardware.__init__(self, base_address)

class HardwareDeviceB(AddressableHardware):
    reg1 = RegisterDescriptor(10, 4)

    def __init__(self, base_address):
        AddressableHardware.__init__(self, base_address)

This allows you to have multiple instances of device type A and B connected to the machine at different base addresses, and you don't need to setup their registers each time.

Brett Lempereur
  • 805
  • 5
  • 11
  • Brett, could you explain to me if your solution is equivalent to my, described in the second update, or maybe it is somehow better? I do understand, that in your solution it is possible to generate field names dynamically, using setattr. – wzab Sep 17 '14 at 21:19
  • Oops, I copied in the wrong version of the solution to my interpreter. Either way, I think the declarative approach is clearer as it gives you guarantees when you're writing your class about which registers will be available, and you should be putting device-related functionality as instance methods of the class. – Brett Lempereur Sep 18 '14 at 10:28