diff --git a/intelhex/__init__.py b/intelhex/__init__.py index a631b0a..0ae989e 100644 --- a/intelhex/__init__.py +++ b/intelhex/__init__.py @@ -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) @@ -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 diff --git a/intelhex/test.py b/intelhex/test.py index e016e26..cc8a396 100755 --- a/intelhex/test.py +++ b/intelhex/test.py @@ -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): @@ -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):