#-----------------------------------------------------------------------------
# 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)