-
Notifications
You must be signed in to change notification settings - Fork 43
/
Copy pathmemory.py
194 lines (166 loc) · 6.25 KB
/
memory.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
"""
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__)
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
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
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
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)
class Memory(bitfield.Bitfield):
"""
Memory on an FPGA.
"""
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())
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)
def read_raw(self, **kwargs):
"""
Placeholder for child classes.
:return: (rawdata, timestamp)
"""
raise RuntimeError('Must be implemented by subclass.')
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}
def write(self, **kwargs):
raise RuntimeError('Must be implemented by subclass.')
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