Source code for pyrogue._Model

#-----------------------------------------------------------------------------
# Company    : SLAC National Accelerator Laboratory
#-----------------------------------------------------------------------------
#  Description:
#       PyRogue base module - Model Class
#-----------------------------------------------------------------------------
# This file is part of the rogue software platform. It is subject to
# the license terms in the LICENSE.txt file found in the top-level directory
# of this distribution and at:
#    https://confluence.slac.stanford.edu/display/ppareg/LICENSE.html.
# No part of the rogue software platform, including this file, may be
# copied, modified, propagated, or distributed except according to the terms
# contained in the LICENSE.txt file.
#-----------------------------------------------------------------------------

import rogue.interfaces.memory as rim
import numpy as np
import struct

def wordCount(bits, wordSize):
    """


    Parameters
    ----------
    bits :

    wordSize :


    Returns
    -------

    """
    ret = bits // wordSize
    if (bits % wordSize != 0 or bits == 0):
        ret += 1
    return ret


def byteCount(bits):
    """


    Parameters
    ----------
    bits :


    Returns
    -------

    """
    return wordCount(bits, 8)


def reverseBits(value, bitSize):
    """


    Parameters
    ----------
    value :

    bitSize :


    Returns
    -------

    """
    result = 0
    for i in range(bitSize):
        result <<= 1
        result |= value & 1
        value >>= 1
    return result


def twosComplement(value, bitSize):
    """
    compute the 2's complement of int value

    Parameters
    ----------
    value :

    bitSize :


    Returns
    -------

    """
    if (value & (1 << (bitSize - 1))) != 0: # if sign bit is set e.g., 8bit: 128-255
        value = value - (1 << bitSize)      # compute negative value
    return value                            # return positive value as is


class ModelMeta(type):
    """ """
    def __init__(cls, *args, **kwargs):
        super().__init__(*args, **kwargs)
        cls.subclasses = {}

    def __call__(cls, *args, **kwargs):
        key = cls.__name__ + str(args) + str(kwargs)

        if key not in cls.subclasses:
            #print(f'Key: {key}')
            inst = super().__call__(*args, **kwargs)
            cls.subclasses[key] = inst
        return cls.subclasses[key]


[docs]class Model(object, metaclass=ModelMeta): """ Class which describes how a data type is represented and accessed using the Rogue Variables and Blocks Parameters ---------- bitSize : int Number of bits being represented binPoint : int Huh? Returns ------- Attributes ---------- name: str String representation of the Model type fstring: str Not sure what this is, Where is it used? encoding: str Encoding type for converting between string and byte arrays. i.e. UTF-8 pytype: int Python type class. defaultdisp: str Default display formatting string. May be overriden by the Variable disp parameter. signed: bool Flag indicating if value is signed. Default=False endianness: str Endianness indicator. 'little' or 'big'. Default='little' bitReverse: bool Bit reversal flag. modelId: int Block processing ID. See :ref:`interfaces_memory_constants_ptype` isBigEndian: bool True if endianness = 'big' ndType: np.dtype numpy type value (bool, int32, int64, uint32, uin64, float32, float64) """ fstring = None encoding = None pytype = None defaultdisp = '{}' signed = False endianness = 'little' bitReverse = False modelId = rim.PyFunc def __init__(self, bitSize, binPoint=0): self.binPoint = binPoint self.bitSize = bitSize self.name = self.__class__.__name__ self.ndType = None @property def isBigEndian(self): """ """ return self.endianness == 'big'
[docs] def toBytes(self, value): """ Convert the python value to byte array. Parameters ---------- value : obj Python value to convert Returns ------- """ return None
# Called by raw read/write and when bitsize > 64
[docs] def fromBytes(self, ba): """ Convert the python value to byte array. Parameters ---------- ba : bytearray Byte array to extract value from Returns ------- """ return None
[docs] def fromString(self, string): """ Convert the string to a python value. Parameters ---------- string : str String representation of the value Returns ------- """ return None
[docs] def minValue(self): """Return the minimum value for the Model type""" return None
[docs] def maxValue(self): """Return the maximum value for the Model type""" return None
[docs]class UInt(Model): """Model class for unsigned integers""" pytype = int defaultdisp = '{:#x}' modelId = rim.UInt def __init__(self, bitSize): super().__init__(bitSize) self.name = f'{self.__class__.__name__}{self.bitSize}' self.ndType = np.dtype(np.uint32) if bitSize <= 32 else np.dtype(np.uint64) # Called by raw read/write and when bitsize > 64
[docs] def toBytes(self, value): """ Parameters ---------- value : Returns ------- """ return value.to_bytes(byteCount(self.bitSize), self.endianness, signed=self.signed)
# Called by raw read/write and when bitsize > 64
[docs] def fromBytes(self, ba): """ Parameters ---------- ba : Returns ------- """ return int.from_bytes(ba, self.endianness, signed=self.signed)
[docs] def fromString(self, string): """ Parameters ---------- string : Returns ------- """ return int(string, 0)
def minValue(self): """ """ return 0 def maxValue(self): """ """ return (2**self.bitSize)-1
[docs]class UIntReversed(UInt): """Model class for unsigned integers, stored in reverse bit order""" modelId = rim.PyFunc # Not yet supported bitReverse = True def __init__(self, bitSize): super().__init__(bitSize) self.ndType = None
[docs] def toBytes(self, value): """ Parameters ---------- value : Returns ------- """ valueReverse = reverseBits(value, self.bitSize) return valueReverse.to_bytes(byteCount(self.bitSize), self.endianness, signed=self.signed)
[docs] def fromBytes(self, ba): """ Parameters ---------- ba : Returns ------- """ valueReverse = int.from_bytes(ba, self.endianness, signed=self.signed) return reverseBits(valueReverse, self.bitSize)
[docs]class Int(UInt): """Model class for integers""" # Override these and inherit everything else from UInt defaultdisp = '{:d}' signed = True modelId = rim.Int def __init__(self, bitSize): super().__init__(bitSize) self.ndType = np.dtype(np.int32) if bitSize <= 32 else np.dtype(np.int64) # Called by raw read/write and when bitsize > 64
[docs] def toBytes(self, value): """ Parameters ---------- value : Returns ------- """ if (value < 0) and (self.bitSize < (byteCount(self.bitSize) * 8)): newValue = value & (2**(self.bitSize)-1) # Strip upper bits ba = newValue.to_bytes(byteCount(self.bitSize), self.endianness, signed=False) else: ba = value.to_bytes(byteCount(self.bitSize), self.endianness, signed=True) return ba
# Called by raw read/write and when bitsize > 64
[docs] def fromBytes(self,ba): """ Parameters ---------- ba : Returns ------- """ if (self.bitSize < (byteCount(self.bitSize)*8)): value = int.from_bytes(ba, self.endianness, signed=False) if value >= 2**(self.bitSize-1): value -= 2**self.bitSize else: value = int.from_bytes(ba, self.endianness, signed=True) return
[docs] def fromString(self, string): """ Parameters ---------- string : Returns ------- """ i = int(string, 0) # perform twos complement if necessary if i>0 and ((i >> self.bitSize) & 0x1 == 1): i = i - (1 << self.bitSize) return i
def minValue(self): """ """ return -1 * (2**(self.bitSize-1)) def maxValue(self): """ """ return (2**(self.bitSize-1))-1
[docs]class UIntBE(UInt): """Model class for big endian unsigned integers""" endianness = 'big'
[docs]class IntBE(Int): """Model class for big endian integers""" endianness = 'big'
[docs]class Bool(Model): """Model class for booleans""" pytype = bool defaultdisp = {False: 'False', True: 'True'} modelId = rim.Bool def __init__(self, bitSize): assert bitSize == 1, f"The bitSize param of Model {self.__class__.__name__} must be 1" super().__init__(bitSize) self.ndType = np.dtype(bool)
[docs] def toBytes(self, value): """ Parameters ---------- value : Returns ------- """ return value.to_bytes(byteCount(self.bitSize), self.endianness, signed=self.signed)
[docs] def fromBytes(self, ba): """ Parameters ---------- ba : Returns ------- """ return bool(int.from_bytes(ba, self.endianness, signed=self.signed))
[docs] def fromString(self, string): """ Parameters ---------- string : Returns ------- """ return str.lower(string) == "true"
def minValue(self): """ """ return 0 def maxValue(self): """ """ return 1
[docs]class String(Model): """Model class for strings""" encoding = 'utf-8' defaultdisp = '{}' pytype = str modelId = rim.String def __init__(self, bitSize): super().__init__(bitSize) self.name = f'{self.__class__.__name__}({self.bitSize//8})'
[docs] def toBytes(self, value): """ Parameters ---------- value : Returns ------- """ ba = bytearray(value, self.encoding) ba.extend(bytearray(1)) return ba
[docs] def fromBytes(self, ba): """ Parameters ---------- ba : Returns ------- """ s = ba.rstrip(bytearray(1)) return s.decode(self.encoding)
[docs] def fromString(self, string): """ Parameters ---------- string : Returns ------- """ return string
[docs]class Float(Model): """Model class for 32-bit floats""" defaultdisp = '{:f}' pytype = float fstring = 'f' modelId = rim.Float def __init__(self, bitSize): assert bitSize == 32, f"The bitSize param of Model {self.__class__.__name__} must be 32" super().__init__(bitSize) self.name = f'{self.__class__.__name__}{self.bitSize}' self.ndType = np.dtype(np.float32)
[docs] def toBytes(self, value): """ Parameters ---------- value : Returns ------- """ return bytearray(struct.pack(self.fstring, value))
[docs] def fromBytes(self, ba): """ Parameters ---------- ba : Returns ------- """ return struct.unpack(self.fstring, ba)[0]
[docs] def fromString(self, string): """ Parameters ---------- string : Returns ------- """ return float(string)
def minValue(self): """ """ return -3.4e38 def maxValue(self): """ """ return 3.4e38
[docs]class Double(Float): """Model class for 64-bit floats""" fstring = 'd' modelId = rim.Double def __init__(self, bitSize): assert bitSize == 64, f"The bitSize param of Model {self.__class__.__name__} must be 64" Model.__init__(self,bitSize) self.name = f'{self.__class__.__name__}{self.bitSize}' self.ndType = np.dtype(np.float64) def minValue(self): """ """ return -1.80e308 def maxValue(self): """ """ return 1.80e308
[docs]class FloatBE(Float): """Model class for 32-bit floats stored as big endian""" endianness = 'big' fstring = '!f'
[docs]class DoubleBE(Double): """Model class for 64-bit floats stored as big endian""" endianness = 'big' fstring = '!d'
[docs]class Fixed(Model): """ Model class for fixed point signed integers Parameters ---------- bitSize : int Specifies the total number of bits, including the binary point bit width. binPoint : int Specifies the bit location of the binary point, where bit zero is the least significant bit. """ pytype = float signed = True modelId = rim.Fixed def __init__(self, bitSize, binPoint): super().__init__(bitSize,binPoint) self.name = f'Fixed_{self.bitSize}_{self.binPoint}' self.ndType = np.dtype(np.float64)
class UFixed(Model): """ Model class for fixed point unsigned integers Parameters ---------- bitSize : int Specifies the total number of bits, including the binary point bit width. binPoint : int Specifies the bit location of the binary point, where bit zero is the least significant bit. """ pytype = float signed = False modelId = rim.Fixed def __init__(self, bitSize, binPoint): super().__init__(bitSize,binPoint) self.name = f'UFixed_{self.bitSize}_{self.binPoint}' self.ndType = np.dtype(np.float64)