| """ | |
| /***************************************************************************** | |
| Licensed to Accellera Systems Initiative Inc. (Accellera) under one or | |
| more contributor license agreements. See the NOTICE file distributed | |
| with this work for additional information regarding copyright ownership. | |
| Accellera licenses this file to you under the Apache License, Version 2.0 | |
| (the "License"); you may not use this file except in compliance with the | |
| License. You may obtain a copy of the License at | |
| http://www.apache.org/licenses/LICENSE-2.0 | |
| Unless required by applicable law or agreed to in writing, software | |
| distributed under the License is distributed on an "AS IS" BASIS, | |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or | |
| implied. See the License for the specific language governing | |
| permissions and limitations under the License. | |
| *****************************************************************************/ | |
| Python Script to test Endianness Conversion Functions for OSCI TLM-2 | |
| There is a simple testbench programme in C++ which runs a single | |
| transaction through a single conversion function, to a simple target | |
| memory and back. This script will run the testbench many times and test for | |
| - incomplete execution, seg-faults, etc | |
| - distributability of conversion function: each transaction should have | |
| the same effect on initiator/target memory as a set of smaller transactions | |
| that sum to it | |
| - equivalence: all conversion functions should have the same functional | |
| effect as each other | |
| The approach is to provide the initial state of the initiator and | |
| target memory (as strings) and to capture their final states, so that | |
| only the effect of the transaction on the data buffers is measured. | |
| Script works out for itself which conversion functions are legal for a | |
| given transaction and applies all of them. Note that where data is | |
| wider than bus, only one conversion function is legal and testing is | |
| somewhat limited. | |
| Testing space (select a transaction at random from the space): | |
| - with and without byte-enables (generated at random for each data word | |
| and can be all-zero) | |
| - data widths of (1,2,4,8,16) | |
| - bus widths of (1,2,4,8,16), lower priority for those smaller than data | |
| width | |
| - transaction lengths of (1..32) x data width, higher probability for | |
| lower values | |
| - base address (0..1023) at bus_width steps | |
| - offset address (0..bus width) with a higher priority for 0 | |
| - address in initiator buffer uniform random | |
| - read or write | |
| - byte-enable length may be smaller than transasction length | |
| - may be a streaming burst | |
| Transaction breakdown | |
| - individual words (always) | |
| - one random breakdown with each segment containing between 1 and N-1 | |
| words, where N is the length in words | |
| - one breakdown with two segments interleaved, using (additional) byte | |
| enables to suppress the part where the other segment is active | |
| Data buffer definition: starts at 0, randomly filled | |
| with lower case letters and numbers. Size 2 kB. Addresses are limited to | |
| 1 kB. | |
| """ | |
| import random | |
| import string | |
| class transaction: | |
| """ contains read_not_write, address, length, byte_enable, | |
| bus_width, data_width, data_pointer, stream_width """ | |
| def __init__(self, **a): self.__dict__ = a | |
| def __str__(self): | |
| if self.read_not_write: a = "R: " | |
| else: a = "W: " | |
| a += "addr = %d, len = %d, bus = %d, word = %d, data = %d" % \ | |
| (self.address, self.length, self.bus_width, self.data_width, \ | |
| self.data_pointer) | |
| if self.byte_enable: a += ", be = " + self.byte_enable | |
| else: a += ", be = x" | |
| a += ", sw = %d" % (self.stream_width) | |
| return a | |
| def txn_generator(nr): | |
| pr_read = 0.5 | |
| pr_byte_enable = 0.5 | |
| pr_enabled = 0.5 | |
| bus_widths = [1, 2, 4, 8, 16] | |
| data_widths = [1, 2, 4, 8, 16] + [1, 2, 4, 8] + [1, 2, 4] + [1, 2] | |
| lengths = list(range(1,33)) + list(range(1,17)) + list(range(1,9)) + list(range(1,5)) + list(range(1,3)) | |
| pr_short_be = 0.2 | |
| pr_stream = 0.1 | |
| nr_generated = 0 | |
| while nr_generated < nr: | |
| # create a random transaction | |
| bus_width = random.choice(bus_widths) | |
| while True: | |
| data_width = random.choice(data_widths) | |
| if data_width <= bus_width: break | |
| if random.random() < 0.25: break | |
| length = random.choice(lengths) | |
| addr_base = random.choice(list(range(0,1024,bus_width))) | |
| addr_offset = random.choice(list(range(bus_width))+[0]*int(bus_width//2)) | |
| txn = transaction( | |
| bus_width = bus_width, | |
| data_width = data_width, | |
| read_not_write = random.random() < pr_read, | |
| length = length * data_width, | |
| address = addr_base + addr_offset, | |
| byte_enable = False, | |
| stream_width = length * data_width, | |
| data_pointer = random.randint(0,1023) | |
| ) | |
| if random.random() < pr_byte_enable: | |
| belen = length | |
| if random.random() < pr_short_be: | |
| belen = min(random.choice(lengths), length) | |
| bep = ["0" * data_width, "1" * data_width] | |
| txn.byte_enable = "".join([random.choice(bep) for x in range(belen)]) | |
| if random.random() < pr_stream and length > 1: | |
| strlen = length | |
| while True: | |
| strlen -= 1 | |
| if strlen == 1 or \ | |
| (random.random() < 0.5 and (length/strlen)*strlen == length): | |
| break | |
| txn.stream_width = strlen * data_width | |
| nr_generated += 1 | |
| yield txn | |
| # test code for transaction generator | |
| if False: | |
| for t in txn_generator(20): | |
| print(t) | |
| raise Exception | |
| # end test code | |
| class memory_state_cl: | |
| buffer_size = 2048 | |
| repeats = 10 * buffer_size // 36 | |
| population = (string.ascii_lowercase + string.digits) * int(repeats) | |
| def __init__(self): | |
| self.initiator = "".join( | |
| random.sample(memory_state_cl.population, memory_state_cl.buffer_size)) | |
| self.target = "".join( | |
| random.sample(memory_state_cl.population, memory_state_cl.buffer_size)) | |
| def copy(self): | |
| r = memory_state_cl() | |
| r.initiator = self.initiator | |
| r.target = self.target | |
| return r | |
| def __eq__(self, golden): | |
| return self.initiator==golden.initiator and self.target==golden.target | |
| def __ne__(self, golden): | |
| return self.initiator!=golden.initiator or self.target!=golden.target | |
| def __str__(self): | |
| return "initiator = " + self.initiator + "\n" + "target = " + self.target | |
| # all fragmentation generators | |
| def __FRAG__null(txn): | |
| yield txn | |
| def __FRAG__word(txn): | |
| curr_address = txn.address | |
| reset_address = curr_address + txn.stream_width | |
| if txn.byte_enable: | |
| full_byte_enable = txn.byte_enable * (1+txn.length/len(txn.byte_enable)) | |
| be_pos = 0 | |
| d_pos = txn.data_pointer | |
| end = txn.length + d_pos | |
| while d_pos < end: | |
| new_txn = transaction( | |
| bus_width = txn.bus_width, | |
| data_width = txn.data_width, | |
| read_not_write = txn.read_not_write, | |
| length = txn.data_width, | |
| address = curr_address, | |
| byte_enable = False, | |
| stream_width = txn.data_width, | |
| data_pointer = d_pos | |
| ) | |
| curr_address += txn.data_width | |
| if curr_address == reset_address: curr_address = txn.address | |
| d_pos += txn.data_width | |
| if txn.byte_enable: | |
| new_txn.byte_enable = full_byte_enable[be_pos:be_pos+txn.data_width] | |
| be_pos += txn.data_width | |
| yield new_txn | |
| def __FRAG__stream(txn): | |
| if txn.byte_enable: | |
| full_byte_enable = txn.byte_enable * (1+txn.length/len(txn.byte_enable)) | |
| be_pos = 0 | |
| bytes_done = 0 | |
| while bytes_done < txn.length: | |
| new_txn = transaction( | |
| bus_width = txn.bus_width, | |
| data_width = txn.data_width, | |
| read_not_write = txn.read_not_write, | |
| length = txn.stream_width, | |
| address = txn.address, | |
| byte_enable = False, | |
| stream_width = txn.stream_width, | |
| data_pointer = bytes_done + txn.data_pointer | |
| ) | |
| if txn.byte_enable: | |
| new_txn.byte_enable = full_byte_enable[be_pos:be_pos+txn.stream_width] | |
| be_pos += txn.stream_width | |
| yield new_txn | |
| bytes_done += txn.stream_width | |
| def __FRAG__random(stream_txn): | |
| for txn in __FRAG__stream(stream_txn): | |
| # txn has full byte enables and no stream feature guaranteed | |
| pr_nofrag = 0.5 | |
| end_address = txn.address + txn.length | |
| curr_address = txn.address | |
| be_pos = 0 | |
| d_pos = txn.data_pointer | |
| while curr_address < end_address: | |
| new_txn = transaction( | |
| bus_width = txn.bus_width, | |
| data_width = txn.data_width, | |
| read_not_write = txn.read_not_write, | |
| length = txn.data_width, | |
| address = curr_address, | |
| byte_enable = txn.byte_enable, | |
| stream_width = txn.data_width, | |
| data_pointer = d_pos | |
| ) | |
| curr_address += txn.data_width | |
| d_pos += txn.data_width | |
| if txn.byte_enable: | |
| new_txn.byte_enable = txn.byte_enable[be_pos:be_pos+txn.data_width] | |
| be_pos += txn.data_width | |
| while random.random() < pr_nofrag and curr_address < end_address: | |
| new_txn.length += txn.data_width | |
| new_txn.stream_width += txn.data_width | |
| curr_address += txn.data_width | |
| d_pos += txn.data_width | |
| if txn.byte_enable: | |
| new_txn.byte_enable += txn.byte_enable[be_pos:be_pos+txn.data_width] | |
| be_pos += txn.data_width | |
| yield new_txn | |
| def __FRAG__randinterleave(stream_txn): | |
| for txn in __FRAG__stream(stream_txn): | |
| # txn has full byte enables and no stream feature guaranteed | |
| pr_frag = 0.5 | |
| txns = [ transaction( | |
| bus_width = txn.bus_width, | |
| data_width = txn.data_width, | |
| read_not_write = txn.read_not_write, | |
| length = txn.length, | |
| address = txn.address, | |
| byte_enable = "", | |
| stream_width = txn.length, | |
| data_pointer = txn.data_pointer | |
| ), transaction( | |
| bus_width = txn.bus_width, | |
| data_width = txn.data_width, | |
| read_not_write = txn.read_not_write, | |
| length = txn.length, | |
| address = txn.address, | |
| byte_enable = "", | |
| stream_width = txn.length, | |
| data_pointer = txn.data_pointer | |
| ) ] | |
| curr = 0 | |
| be_pos = 0 | |
| on = "1" * txn.data_width | |
| off = "0" * txn.data_width | |
| while be_pos < txn.length: | |
| if txn.byte_enable: bew = txn.byte_enable[be_pos:be_pos+txn.data_width] | |
| else: bew = on | |
| txns[curr].byte_enable += bew | |
| txns[1-curr].byte_enable += off | |
| be_pos += txn.data_width | |
| if random.random() < pr_frag: curr = 1-curr | |
| yield txns[0] | |
| yield txns[1] | |
| fragmenters = [globals()[n] for n in globals().keys() if n[:8]=="__FRAG__"] | |
| # test code for fragmenters | |
| if False: | |
| for t in txn_generator(1): | |
| print(t) | |
| print() | |
| for u in fragmenters[4](t): | |
| print(u) | |
| raise Exception | |
| # end test code | |
| # conversion functions are determined by an index (shared with C++) and | |
| # a function that tests if they can be applied to a transaction | |
| def __CHCK__generic(txn): | |
| __CHCK__generic.nr = 0 | |
| return True | |
| def __CHCK__word(txn): | |
| __CHCK__word.nr = 1 | |
| if txn.data_width > txn.bus_width: return False | |
| if txn.stream_width < txn.length: return False | |
| if txn.byte_enable and len(txn.byte_enable) < txn.length: return False | |
| return True | |
| def __CHCK__aligned(txn): | |
| __CHCK__aligned.nr = 2 | |
| if txn.data_width > txn.bus_width: return False | |
| if txn.stream_width < txn.length: return False | |
| if txn.byte_enable and len(txn.byte_enable) < txn.length: return False | |
| base_addr = txn.address / txn.bus_width | |
| if base_addr * txn.bus_width != txn.address: return False | |
| nr_bus_words = txn.length / txn.bus_width | |
| if nr_bus_words * txn.bus_width != txn.length: return False | |
| return True | |
| def __CHCK__single(txn): | |
| __CHCK__single.nr = 3 | |
| if txn.length != txn.data_width: return False | |
| base_addr = txn.address / txn.bus_width | |
| end_base_addr = (txn.address + txn.length - 1) / txn.bus_width | |
| if base_addr != end_base_addr: return False | |
| return True | |
| def __CHCK__local_single(txn): | |
| __CHCK__local_single.nr = 4 | |
| if txn.length != txn.data_width: return False | |
| return True | |
| all_converters = [globals()[n] for n in globals().keys() if n[:8]=="__CHCK__"] | |
| for x in all_converters: x.usage = 0 | |
| class TesterFailure(Exception): pass | |
| class SystemCFailure(Exception): pass | |
| class ConverterDifference(Exception): pass | |
| class FragmenterDifference(Exception): pass | |
| from subprocess import Popen, PIPE | |
| # test a single fragment in multiple ways | |
| def test_a_fragment(f, ms): | |
| # f is the (fragment of a) transaction | |
| # ms is the memory state to use at start of test | |
| # run the same fragment through all applicable conversion functions | |
| # and check they all do the same thing | |
| # use the same sub-process for all of them | |
| # build complete stdin | |
| convs = [c for c in all_converters if c(f)] | |
| if len(convs) == 0: raise TesterFailure(f.str()) | |
| txtin = "\n".join( | |
| [("%s\n%s\nconverter = %d\n" % (f, ms, c.nr)) for c in convs]) | |
| # run and get stdout | |
| txtout = "no output" | |
| try: | |
| sp = Popen("../build-unix/test_endian_conv.exe", stdin=PIPE, stdout=PIPE) | |
| txtout = sp.communicate(txtin)[0] | |
| tmp = txtout.splitlines() | |
| initiators = [l.split()[-1] for l in tmp if l[:14] == " initiator = "] | |
| targets = [l.split()[-1] for l in tmp if l[:11] == " target = "] | |
| except: | |
| raise SystemCFailure("\n" + txtin + txtout) | |
| if sp.returncode != 0: raise SystemCFailure("\n" + txtin + txtout) | |
| if len(initiators) != len(convs): raise SystemCFailure("\n" + txtin + txtout) | |
| if len(targets) != len(convs): raise SystemCFailure("\n" + txtin + txtout) | |
| for c in convs: c.usage += 1 | |
| ms_out = memory_state_cl() | |
| ms_out.initiator = initiators[0] | |
| ms_out.target = targets[0] | |
| for i in range(1,len(convs)): | |
| if initiators[i]!=ms_out.initiator or targets[i]!=ms_out.target: | |
| raise ConverterDifference(""" | |
| %s | |
| start memory: | |
| %s | |
| converter = %d | |
| golden memory: | |
| %s | |
| actual memory: | |
| %s""" % (f, ms, i, golden_ms, ms_out)) | |
| return ms_out | |
| # main loop | |
| from sys import argv | |
| print("Testing Endianness Conversion Functions") | |
| print("March 2008") | |
| print("OSCI TLM-2") | |
| try: nr_txns_to_test = int(argv[1]) | |
| except: | |
| print("No command line input for number of tests, using default") | |
| nr_txns_to_test = 1000 | |
| print("Number to test:", nr_txns_to_test) | |
| # generate and test a number of transactions | |
| for txn in txn_generator(nr_txns_to_test): | |
| # each transaction has a random initial memory state | |
| initial_memory = memory_state_cl() | |
| # iterate over all defined fragmentation functions | |
| first_time = True | |
| for fragmenter in fragmenters: | |
| # all versions of the transaction start in the same place | |
| memory_state = initial_memory.copy() | |
| # now iterate over the fragments of the transaction, accumulating | |
| # the memory state | |
| for partial_txn in fragmenter(txn): | |
| memory_state = test_a_fragment(partial_txn, memory_state) | |
| if first_time: | |
| golden_memory_state = memory_state.copy() | |
| first_time = False | |
| else: | |
| if memory_state != golden_memory_state: raise FragmenterDifference(""" | |
| fragmenter: %s | |
| transaction: | |
| %s | |
| start memory: | |
| %s | |
| golden memory: | |
| %s | |
| actual memory: | |
| %s""" % (fragmenter, txn, initial_memory, golden_memory_state, memory_state)) | |
| print("."), | |
| print() | |
| print("Conversion functions usage frequency:") | |
| for c in all_converters: | |
| print(c.nr, c.__name__, c.usage) | |