Skip to content

Commit

Permalink
New BitField field assignment algorithm.
Browse files Browse the repository at this point in the history
  • Loading branch information
mundya committed Apr 11, 2015
1 parent dce23a0 commit 22e551a
Show file tree
Hide file tree
Showing 2 changed files with 236 additions and 144 deletions.
194 changes: 129 additions & 65 deletions rig/bitfield.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ class BitField(object):
available fields).
"""

def __init__(self, length=32, _fields=None, _field_values=None):
def __init__(self, length=32, _fields=None, _field_values=None,
_parent=None):
"""Create a new BitField.
An instance, `b`, of :py:class:`.BitField` represents a fixed-length
Expand All @@ -64,8 +65,12 @@ def __init__(self, length=32, _fields=None, _field_values=None):
For internal use only. The shared, global field dictionary.
_field_values : dict
For internal use only. Mapping of field-identifier to value.
_parent : BitField
For internal use only. Parent BitField of a newly created BitField.
"""
self.length = length
self._parent = _parent
self._children = list()

# An OrderedDict if field definitions (globally shared by all
# derivatives of the same BitField) which maps human-friendly
Expand Down Expand Up @@ -199,6 +204,10 @@ def add_field(self, identifier, length=None, start_at=None,
relative_to_lsb=relative_to_lsb
)

# Add this as a new child of the parent
if self._parent is not None and self not in self._parent._children:
self._parent._children.append(self)

def __call__(self, **field_values):
"""Return a new BitField instance with fields assigned values as
specified in the keyword arguments.
Expand Down Expand Up @@ -249,7 +258,7 @@ def __call__(self, **field_values):
self.fields[identifier].max_value = max(
self.fields[identifier].max_value, value)

return BitField(self.length, self.fields, field_values)
return BitField(self.length, self.fields, field_values, self)

def __getattr__(self, identifier):
"""Get the value of a field.
Expand Down Expand Up @@ -389,15 +398,16 @@ def assign_fields(self):
Users should typically call this method after all field values have
been assigned, otherwise fields may be fixed at an inadequate size.
"""
# We must fix fields at every level of the heirarchy sepeartely
# We must fix fields at every level of the hierarchy separately
# (otherwise fields of children won't be allowed to overlap). Here we
# do a breadth-first iteration over the heirarchy, fixing the fields at
# each level.
# do a breadth-first iteration over the hierarchy to fix fields with
# given starting positions; then we do depth-first to fix other fields.
unsearched_heirarchy = [BitField(self.length, self.fields)]
while unsearched_heirarchy:
ks = unsearched_heirarchy.pop(0)
ks._assign_enabled_fields()
# Look for potential children in the herarchy
ks._assign_enabled_fields_with_fixed_start()

# Look for potential children in the hierarchy
for identifier, field in ks._potential_fields():
enabled_field_idents = set(i for (i, f) in
ks._enabled_fields())
Expand All @@ -413,6 +423,13 @@ def assign_fields(self):
if set_fields:
unsearched_heirarchy.append(ks(**set_fields))

# Depth-first search to fix the remaining fields
# Traverse up to the root and then recurse down
root = self
while root._parent is not None:
root = root._parent
root._recurse_assign_without_fixed_start()

def __eq__(self, other):
"""Test that this :py:class:`.BitField` is equivalent to another.
Expand Down Expand Up @@ -568,73 +585,120 @@ def _potential_fields(self):
else:
blocked.add(identifier)

def _assign_enabled_fields(self):
"""For internal use only. Assign a position & length to any enabled
fields which do not have one.
def _assign_enabled_fields_with_fixed_start(self):
"""For internal use only. Assign a length to any enabled fields which
do not have one.
"""
assigned_bits = 0
unassigned_fields_no_start = list()
unassigned_fields_with_start = list()
unassigned_fields = list()
for identifier, field in self._enabled_fields():
if field.length is not None and field.start_at is not None:
assigned_bits |= ((1 << field.length) - 1) << field.start_at
elif field.start_at is None:
unassigned_fields_no_start.append((identifier, field))
else:
unassigned_fields_with_start.append((identifier, field))
elif field.start_at is not None:
unassigned_fields.append((identifier, field))

# Sort the unassigned fields in order of fixed start position
unassigned_fields = \
sorted(unassigned_fields_with_start, key=lambda f: f[1].start_at)
unassigned_fields.extend(unassigned_fields_no_start)
unassigned_fields.sort(key=lambda f: f[1].start_at)

for identifier, field in unassigned_fields:
length = field.length
if length is None:
# Assign lengths based on values
length = int(log(field.max_value, 2)) + 1

start_at = field.start_at
if start_at is None:
# Force a failure if no better space is found
start_at = self.length

# Try every position until a space is found
if field.relative_to_lsb:
bits = range(0, self.length - length)
else:
bits = range(self.length - length, 0, -1)

for bit in bits:
field_bits = ((1 << length) - 1) << bit
if not (assigned_bits & field_bits):
start_at = bit
assigned_bits |= field_bits
break
assigned_bits = self._assign_enabled_field(
assigned_bits, identifier, field)

def _recurse_assign_without_fixed_start(self, assigned_bits=0,
excluded_fields=set()):
# Call this for the children first
excludes = set(i for (i, f) in self._enabled_fields())
new_assigned_bits = assigned_bits
for child in self._children:
# Children can assign bits independently
new_assigned_bits |= child._recurse_assign_without_fixed_start(
assigned_bits, excludes)

# Now assign locally
assigned_bits = self._assign_enabled_fields_without_fixed_start(
new_assigned_bits, excluded_fields)

# Check that everything HAS been assigned
for i, f in self._enabled_fields():
if i not in excluded_fields:
assert f.length is not None and f.start_at is not None

return assigned_bits

def _assign_enabled_fields_without_fixed_start(self, assigned_bits,
exclude_fields=set()):
"""For internal use only. Assign a length and position to any enabled
fields which do not have one.
Parameters
----------
exclude_fields : set
Exclude fields which will be assigned later.
"""
unassigned_fields = list()
for identifier, field in self._enabled_fields():
if field.length is None or field.start_at is None:
if identifier not in exclude_fields:
unassigned_fields.append((identifier, field))
else:
# A start position has been forced, ensure that it can be
# fulfilled
if not field.relative_to_lsb:
# If relative to MSB then swap direction
start_at = self.length - start_at - length

field_bits = ((1 << length) - 1) << start_at

if assigned_bits & field_bits:
raise ValueError(
"{}-bit field '{}' with fixed position "
"does not fit in bit field.".format(
field.length, identifier))

# Mark these bits as assigned
assigned_bits |= field_bits

# Check that the calculated field is within the bit field
if start_at + length <= self.length:
field.length = length
field.start_at = start_at
assigned_bits |= ((1 << field.length) - 1) << field.start_at

for identifier, field in unassigned_fields:
assert identifier not in exclude_fields
assigned_bits |= self._assign_enabled_field(
assigned_bits, identifier, field)

return assigned_bits

def _assign_enabled_field(self, assigned_bits, identifier, field):
length = field.length
if length is None:
# Assign lengths based on values
length = int(log(field.max_value, 2)) + 1

start_at = field.start_at
if start_at is None:
# Force a failure if no better space is found
start_at = self.length

# Try every position until a space is found
if field.relative_to_lsb:
bits = range(0, self.length - length)
else:
bits = range(self.length - length, 0, -1)

for bit in bits:
field_bits = ((1 << length) - 1) << bit
if not (assigned_bits & field_bits):
start_at = bit
assigned_bits |= field_bits
break
else:
# A start position has been forced, ensure that it can be fulfilled
if not field.relative_to_lsb:
# If relative to MSB then swap direction
start_at = self.length - start_at - length

field_bits = ((1 << length) - 1) << start_at

if assigned_bits & field_bits:
raise ValueError(
"{}-bit field '{}' "
"does not fit in bit field.".format(
field.length, identifier))
"{}-bit field '{}' with fixed position does not fit in "
"bit field.".format(
field.length, identifier)
)

# Mark these bits as assigned
assigned_bits |= field_bits

# Check that the calculated field is within the bit field
if start_at + length <= self.length:
field.length = length
field.start_at = start_at
else:
raise ValueError(
"{}-bit field '{}' does not fit in bit field.".format(
field.length, identifier)
)

return assigned_bits

0 comments on commit 22e551a

Please sign in to comment.