Skip to content

Add option to IntelHex.segments to split segments on alignment boundaries #21

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 41 additions & 4 deletions intelhex/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -873,24 +873,31 @@ def merge(self, other, overlap='error'):
elif overlap == 'replace':
self.start_addr = other.start_addr

def segments(self):
def segments(self, alignment=None):
"""Return a list of ordered tuple objects, representing contiguous occupied data addresses.
Each tuple has a length of two and follows the semantics of the range and xrange objects.
The second entry of the tuple is always an integer greater than the first entry.
The second entry of the tuple is always an integer greater than the first entry. If
integer is passed as alignment, the contiguous segments are further split along
boundaries of integer multiples of the alignment.

@param alignment integer boundary on which to split segments
"""
# get normal segments
addresses = self.addresses()
if not addresses:
return []
elif len(addresses) == 1:
return([(addresses[0], addresses[0]+1)])
if not alignment:
alignment = addresses[-1] + 1
adjacent_differences = [(b - a) for (a, b) in zip(addresses[:-1], addresses[1:])]
breaks = [i for (i, x) in enumerate(adjacent_differences) if x > 1]
endings = [addresses[b] for b in breaks]
endings.append(addresses[-1])
beginings = [addresses[b+1] for b in breaks]
beginings.insert(0, addresses[0])
return [(a, b+1) for (a, b) in zip(beginings, endings)]
return [(a, b+1) for (x, y) in zip(beginings, endings) for (a, b) in _align_segment(x, y, alignment)]

def get_memory_size(self):
"""Returns the approximate memory footprint for data."""
n = sys.getsizeof(self)
Expand Down Expand Up @@ -1014,6 +1021,36 @@ def tobinarray(self, start=None, end=None, size=None):
#/class IntelHex16bit


def _align_segment(start, end, alignment):
"""Split segment into sub-segments on alignment boundaries. If the segment
spans an integer multiple of alignment it is split into two sub-segments,
each of which is further sub-divided if it spans additional bounadries.

@param start start address of the segment
@param end end address of the segment (inclusive)
@param alignment integer alignment boundary

@return generator of ordered tuple sub-segments representing the
start and end addresses (inclusive) of each sub-segment,
in order of increasing start address; no sub-segment will
span an integery multiple of `alignment`.
"""
if not (float(start).is_integer() and float(end).is_integer() and float(alignment).is_integer()):
raise ValueError("_align_segment: all parameters must be int")
if alignment <= 0:
raise ValueError("_align_segment: alignment must be positive")
if end < start:
raise ValueError("_align_segment: segment must be monotonic")
while True:
stop = (start//alignment + 1) * alignment - 1
if stop >= end:
yield (start, end)
return
else:
yield (start, stop)
start = stop+1


def hex2bin(fin, fout, start=None, end=None, size=None, pad=None):
"""Hex-to-Bin convertor engine.
@return 0 if all OK
Expand Down
151 changes: 150 additions & 1 deletion intelhex/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -809,7 +809,27 @@ def test_segments(self):
self.assertEqual(max(sg[0]), 0x102)
self.assertEqual(min(sg[1]), 0x200)
self.assertEqual(max(sg[1]), 0x203)
pass

def test_segments_alignment(self):
# test that address segments are correctly summarized and aligned
ih = IntelHex()
self.assertEqual([], ih.segments(alignment=4))
# 5 input segments; 1 and 2 come out unchanged, 3, 4, and 5 come
# out split into three sub-segments each (11 total output segments)
# (using alignment=4)
ih.puts(0x000, asbytes('0123')) # aligned segment
ih.puts(0x105, asbytes( '56' )) # aligned sub-segment
ih.puts(0x200, asbytes('0123456789ab')) # aligned multi-span
ih.puts(0x300, asbytes('0123456789' )) # left-justified multi-span
ih.puts(0x402, asbytes( '23456789ab')) # right-justified multi-span
expected = [
(0x000, 0x004), # from aligned segment
(0x105, 0x107), # from aligned sub-segment
(0x200, 0x204), (0x204, 0x208), (0x208, 0x20c), # from aligned multi-span
(0x300, 0x304), (0x304, 0x308), (0x308, 0x30a), # from left-justified multi-span
(0x402, 0x404), (0x404, 0x408), (0x408, 0x40c), # from right-justified multi-span
]
self.assertEqual(expected, ih.segments(alignment=4))

class TestIntelHexLoadBin(TestIntelHexBase):

Expand Down Expand Up @@ -1565,6 +1585,135 @@ def test_start_linear_address(self):
intelhex.Record.start_linear_address(0x12345678))


class Test_AlignSegment(TestIntelHexBase):

def test_singlespan(self):
# any segment between (0,3) inclusive should be returned un-changed for align=4
# 0 --> 0
# 0 1 --> 0 1
# 0 1 2 --> 0 1 2
# 0 1 2 3 --> 0 1 2 3
# 1 --> 1
# 1 2 --> 1 2
# 1 2 3 --> 1 2 3
# 2 --> 2
# 2 3 --> 2 3
# 3 --> 3
for a in range(0,4):
for b in range(a,4):
self.assertEqual([(a,b)], list(intelhex._align_segment(a, b, 4)))

def test_offset_singlespan(self):
# any segment between (8,11) inclusive should be returned un-changed for align=4
# 8 --> 8
# 8 9 --> 8 9
# 8 9 a --> 8 9 a
# 8 9 a b --> 8 9 a b
# 9 --> 9
# 9 a --> 9 a
# 9 a b --> 9 a b
# a --> a
# a b --> a b
# b --> b
for a in range(8,12):
for b in range(a,12):
self.assertEqual([(a,b)], list(intelhex._align_segment(a, b, 4)))

def test_aligned_multispan(self):
# with align=4, segments (0,11) and (8,19) should each be split into three
# equal-length sub-segments
# 0 1 2 3 4 5 6 7 8 9 a b --> 0 1 2 3 , 4 5 6 7 , 8 9 a b
# 8 9 a b c d e f g h i j --> 8 9 a b , c d e f , g h i j
for a in [ 0, 8 ]:
aligned = [ (a, a+4-1), (a+4, a+8-1), (a+8, a+11) ]
self.assertEqual(aligned, list(intelhex._align_segment(a, a+11, 4)))

def test_leftjustified_multispan(self):
# with align=4, if start address is aligned but end address is not, we expect
# to see n equal-length sub-segments followed by one shorter segment at the end
# 0 1 2 3 4 5 6 7 8 9 a --> 0 1 2 3 , 4 5 6 7 , 8 9 a
# 0 1 2 3 4 5 6 7 8 9 --> 0 1 2 3 , 4 5 6 7 , 8 9
# 0 1 2 3 4 5 6 7 8 --> 0 1 2 3 , 4 5 6 7 , 8
# 8 9 a b c d e f g h i --> 8 9 a b , c d e f , g h i
# 8 9 a b c d e f g h --> 8 9 a b , c d e f , g h
# 8 9 a b c d e f g --> 8 9 a b , c d e f , g
for a in [ 0, 8 ]:
aligned = [ (a, a+4-1), (a+4, a+8-1), (a+8, a+10) ]
self.assertEqual(aligned, list(intelhex._align_segment(a, a+10, 4)))
aligned = [ (a, a+4-1), (a+4, a+8-1), (a+8, a+ 9) ]
self.assertEqual(aligned, list(intelhex._align_segment(a, a+ 9, 4)))
aligned = [ (a, a+4-1), (a+4, a+8-1), (a+8, a+ 8) ]
self.assertEqual(aligned, list(intelhex._align_segment(a, a+ 8, 4)))

def test_rightjustified_multispan(self):
# with align=4, if end address is aligned but start address is not, we expect
# to see one shorter segment followed by n equal-length sub-segments
# 1 2 3 4 5 6 7 8 9 a b --> 1 2 3 , 4 5 6 7 , 8 9 a b
# 2 3 4 5 6 7 8 9 a b --> 2 3 , 4 5 6 7 , 8 9 a b
# 3 4 5 6 7 8 9 a b --> 3 , 4 5 6 7 , 8 9 a b
# 9 a b c d e f g h i j --> 9 a b , c d e f , g h i j
# a b c d e f g h i j --> a b , c d e f , g h i j
# b c d e f g h i j --> b , c d e f , g h i j
for a in [ 0, 8 ]:
aligned = [ (a+1, a+4-1), (a+4, a+8-1), (a+8, a+11) ]
self.assertEqual(aligned, list(intelhex._align_segment(a+1, a+11, 4)))
aligned = [ (a+2, a+4-1), (a+4, a+8-1), (a+8, a+11) ]
self.assertEqual(aligned, list(intelhex._align_segment(a+2, a+11, 4)))
aligned = [ (a+3, a+4-1), (a+4, a+8-1), (a+8, a+11) ]
self.assertEqual(aligned, list(intelhex._align_segment(a+3, a+11, 4)))

def test_centered_multispan(self):
# with align=4, if neither end address nor start address is aligned,
# we expect to see one shorter sub-segment at start and one at end,
# separated by n equal-length sub-segments.
# 1 2 3 4 5 6 7 8 9 a --> 1 2 3 , 4 5 6 7 , 8 9 a
# 2 3 4 5 6 7 8 9 --> 2 3 , 4 5 6 7 , 8 9
# 3 4 5 6 7 8 --> 3 , 4 5 6 7 , 8
# 9 a b c d e f g h i --> 9 a b , c d e f , g h i
# a b c d e f g h --> a b , c d e f , g h
# b c d e f g --> b , c d e f , g
for a in [ 0, 8 ]:
aligned = [ (a+1, a+4-1), (a+4, a+8-1), (a+8, a+10) ]
self.assertEqual(aligned, list(intelhex._align_segment(a+1, a+10, 4)))
aligned = [ (a+2, a+4-1), (a+4, a+8-1), (a+8, a+ 9) ]
self.assertEqual(aligned, list(intelhex._align_segment(a+2, a+ 9, 4)))
aligned = [ (a+3, a+4-1), (a+4, a+8-1), (a+8, a+ 8) ]
self.assertEqual(aligned, list(intelhex._align_segment(a+3, a+ 8, 4)))

def test_integer_parameters_only(self):
# all parameters must evaluate to integers
with self.assertRaises(ValueError):
list(intelhex._align_segment(0, 3, 4.5))
with self.assertRaises(ValueError):
list(intelhex._align_segment(0, 3.5, 4))
with self.assertRaises(ValueError):
list(intelhex._align_segment(0.5, 3, 4))
with self.assertRaises(ValueError):
list(intelhex._align_segment(0, 3, 'h'))
with self.assertRaises(ValueError):
list(intelhex._align_segment(0, 'b', 4))
with self.assertRaises(ValueError):
list(intelhex._align_segment('a', 3, 4))

def test_nonpositive_alignment(self):
# alignment must be positive (>= 1)
with self.assertRaises(ValueError):
list(intelhex._align_segment(0, 3, 0))
with self.assertRaises(ValueError):
list(intelhex._align_segment(0, 3, -4))

def test_negative_segment(self):
# segments must be monotonically increasing
with self.assertRaises(ValueError):
list(intelhex._align_segment(3, 0, 4))

def test_many_splits(self):
# this will produce 65536 sub-segments; previous recursive
# implementation would fall over at 1000
for subsegment in intelhex._align_segment(0, 2**18, 4):
pass


class Test_GetFileAndAddrRange(TestIntelHexBase):

def test_simple(self):
Expand Down