"""
The base class for all things memory. More or less everything on the
FPGA is accessed by reading and writing memory addresses on the EPB/OPB
busses. Normally via KATCP.
"""
import logging
import bitfield
import struct
LOGGER = logging.getLogger(__name__)
[docs]def bin2fp(raw_word, bitwidth, bin_pt, signed):
"""
Convert a raw number based on supplied characteristics.
:param raw_word: the number to convert
:param bitwidth: its width in bits
:param bin_pt: the location of the binary point
:param signed: whether it is signed or not
:return: the formatted number, long, float or int
"""
word_masked = raw_word & ((2**bitwidth)-1)
if signed and (word_masked >= 2**(bitwidth-1)):
word_masked -= 2**bitwidth
if bin_pt == 0:
if bitwidth <= 63:
return int(word_masked)
else:
return long(word_masked)
else:
quotient = word_masked / (2**bin_pt)
rem = word_masked - (quotient * (2**bin_pt))
return quotient + (float(rem) / (2**bin_pt))
raise RuntimeError
[docs]def fp2fixed(num, bitwidth, bin_pt, signed):
"""
Convert a floating point number to its fixed point equivalent.
:param num:
:param bitwidth:
:param bin_pt:
:param signed:
"""
_format = '%s%i.%i' % ('fix' if signed else 'ufix', bitwidth, bin_pt)
if bin_pt > bitwidth:
raise ValueError('Cannot have bin_pt > bitwidth')
if bin_pt < 0:
raise ValueError('bin_pt < 0 makes no sense')
if (not signed) and (num < 0):
raise ValueError('Cannot represent negative number (%f) in %s' % (
num, _format))
if num == 0:
return 0
scaled = num * (2**bin_pt)
scaled = round(scaled)
if signed:
_nbits = bitwidth - 1
limits = [-1 * (2**_nbits), (2**_nbits) - 1]
else:
limits = [0, (2**bitwidth) - 1]
scaled = min(limits[1], max(limits[0], scaled))
unscaled = scaled / ((2**bin_pt) * 1.0)
return unscaled
[docs]def cast_fixed(fpnum, bitwidth, bin_pt):
"""
Represent a fixed point number as an unsigned number, like the Xilinx
reinterpret block.
:param fpnum:
:param bitwidth:
:param bin_pt:
"""
if fpnum == 0:
return 0
val = int(fpnum * (2**bin_pt))
if fpnum < 0:
val += 2**bitwidth
return val
[docs]def fp2fixed_int(num, bitwidth, bin_pt, signed):
"""
Compatability function, rather use the other functions explicitly.
"""
val = fp2fixed(num, bitwidth, bin_pt, signed)
return cast_fixed(val, bitwidth, bin_pt)
[docs]class Memory(bitfield.Bitfield):
"""
Memory on an FPGA.
"""
[docs] def __init__(self, name, width_bits, address, length_bytes):
"""
A chunk of memory on a device.
:param name: a name for this memory
:param width_bits: the width, in BITS, PER WORD
:param address: the start address in device memory
:param length_bytes: length, in BYTES
e.g. a Register has width_bits=32, length_bytes=4
e.g.2. a Snapblock could have width_bits=128, length_bytes=32768
"""
bitfield.Bitfield.__init__(self, name=name, width_bits=width_bits)
self.address = address
self.length_bytes = length_bytes
self.block_info = {}
LOGGER.debug('New Memory %s' % str(self))
def __str__(self):
return '%s%s: %ibits * %i, fields[%s]' % (
self.name, '' if self.address == -1 else '@0x%08x' % self.address,
self.width_bits, self.length_in_words(), self.fields_string_get())
[docs] def length_in_words(self):
"""
:return: the memory block's length, in Words
"""
return self.length_bytes / (self.width_bits / 8)
# def __setattr__(self, name, value):
# try:
# if name in self._fields.keys():
# self.write(**{name: value})
# except AttributeError:
# pass
# object.__setattr__(self, name, value)
[docs] def read_raw(self, **kwargs):
"""
Placeholder for child classes.
:return: (rawdata, timestamp)
"""
raise RuntimeError('Must be implemented by subclass.')
[docs] def read(self, **kwargs):
"""
Read raw binary data and convert it using the bitfield
description for this memory.
:return: (data dictionary, read time)
"""
# read the data raw, passing necessary arguments through
rawdata, rawtime = self.read_raw(**kwargs)
# and convert using our bitstruct
return {'data': self._process_data(rawdata), 'timestamp': rawtime}
[docs] def write(self, **kwargs):
raise RuntimeError('Must be implemented by subclass.')
[docs] def write_raw(self, uintvalue):
raise RuntimeError('Must be implemented by subclass.')
def _process_data(self, rawdata):
"""
Process raw data according to this memory's bitfield setup.
Does not use construct, just struct and iterate through.
Faster than construct. Who knew?
"""
if not(isinstance(rawdata, str) or isinstance(rawdata, buffer)):
raise TypeError('self.read_raw returning incorrect datatype. '
'Must be str or buffer.')
fbytes = struct.unpack('%iB' % self.length_bytes, rawdata)
width_bytes = self.width_bits / 8
memory_words = []
for wordctr in range(0, len(fbytes) / width_bytes):
startindex = wordctr * width_bytes
wordl = 0
for bytectr in range(0, width_bytes):
byte = fbytes[startindex + width_bytes - (bytectr + 1)]
wordl |= byte << (bytectr * 8)
# print('\t%d: bytel: 0x%02X, wordl: 0x%X' % (
# bytectr, byte, wordl))
memory_words.append(wordl)
# now we have all the words as longs, so carry on
processed = {}
for field in self._fields.itervalues():
processed[field.name] = []
for ctr, word in enumerate(memory_words):
for field in self._fields.itervalues():
word_shift = word >> field.offset
word_done = bin2fp(word_shift, field.width_bits,
field.binary_pt, field.numtype == 1)
processed[field.name].append(word_done)
return processed