mirror of
				https://github.com/YosysHQ/yosys
				synced 2025-10-31 11:42:30 +00:00 
			
		
		
		
	fix a few bugs in the functional backend and refactor the testing
This commit is contained in:
		
							parent
							
								
									674e6d201d
								
							
						
					
					
						commit
						6922633b0b
					
				
					 6 changed files with 366 additions and 270 deletions
				
			
		|  | @ -226,26 +226,36 @@ public: | ||||||
| 			T b = extend(inputs.at(ID(B)), b_width, width, is_signed); | 			T b = extend(inputs.at(ID(B)), b_width, width, is_signed); | ||||||
| 			if(is_signed) { | 			if(is_signed) { | ||||||
| 				if(cellType == ID($div)) { | 				if(cellType == ID($div)) { | ||||||
|  | 					// divide absolute values, then flip the sign if input signs differ
 | ||||||
|  | 					// but extend the width first, to handle the case (most negative value) / (-1)
 | ||||||
| 					T abs_y = factory.unsigned_div(abs(a, width), abs(b, width), width); | 					T abs_y = factory.unsigned_div(abs(a, width), abs(b, width), width); | ||||||
| 					T out_sign = factory.not_equal(sign(a, width), sign(b, width), 1); | 					T out_sign = factory.not_equal(sign(a, width), sign(b, width), 1); | ||||||
| 					return neg_if(extend(abs_y, width, y_width, true), y_width, out_sign); | 					return neg_if(extend(abs_y, width, y_width, false), y_width, out_sign); | ||||||
| 				} else if(cellType == ID($mod)) { | 				} else if(cellType == ID($mod)) { | ||||||
|  | 					// similar to division but output sign == divisor sign
 | ||||||
| 					T abs_y = factory.unsigned_mod(abs(a, width), abs(b, width), width); | 					T abs_y = factory.unsigned_mod(abs(a, width), abs(b, width), width); | ||||||
| 					return neg_if(extend(abs_y, width, y_width, true), y_width, sign(a, width)); | 					return neg_if(extend(abs_y, width, y_width, false), y_width, sign(a, width)); | ||||||
| 				} else if(cellType == ID($divfloor)) { | 				} else if(cellType == ID($divfloor)) { | ||||||
|  | 					// if b is negative, flip both signs so that b is positive
 | ||||||
| 					T b_sign = sign(b, width); | 					T b_sign = sign(b, width); | ||||||
| 					T a1 = neg_if(a, width, b_sign); | 					T a1 = neg_if(a, width, b_sign); | ||||||
| 					T b1 = neg_if(b, width, b_sign); | 					T b1 = neg_if(b, width, b_sign); | ||||||
| 					T a1_sign = sign(a1, width); | 					// if a is now negative, calculate ~((~a) / b) = -((-a - 1) / b + 1)
 | ||||||
|  | 					// which equals the negative of (-a) / b with rounding up rather than down
 | ||||||
|  | 					// note that to handle the case where a = most negative value properly,
 | ||||||
|  | 					// we have to calculate a1_sign from the original values rather than using sign(a1, width)
 | ||||||
|  | 					T a1_sign = factory.bitwise_and(factory.not_equal(sign(a, width), sign(b, width), 1), reduce_or(a, width), 1); | ||||||
| 					T a2 = factory.mux(a1, factory.bitwise_not(a1, width), a1_sign, width); | 					T a2 = factory.mux(a1, factory.bitwise_not(a1, width), a1_sign, width); | ||||||
| 					T y1 = factory.unsigned_div(a2, b1, width); | 					T y1 = factory.unsigned_div(a2, b1, width); | ||||||
| 					T y2 = factory.mux(y1, factory.bitwise_not(y1, width), a1_sign, width); | 					T y2 = extend(y1, width, y_width, false); | ||||||
| 					return extend(y2, width, y_width, true); | 					return factory.mux(y2, factory.bitwise_not(y2, y_width), a1_sign, y_width); | ||||||
| 				} else if(cellType == ID($modfloor)) { | 				} else if(cellType == ID($modfloor)) { | ||||||
|  | 					// calculate |a| % |b| and then subtract from |b| if input signs differ and the remainder is non-zero
 | ||||||
| 					T abs_b = abs(b, width); | 					T abs_b = abs(b, width); | ||||||
| 					T abs_y = factory.unsigned_mod(abs(a, width), abs_b, width); | 					T abs_y = factory.unsigned_mod(abs(a, width), abs_b, width); | ||||||
| 					T flip_y = factory.bitwise_and(factory.bitwise_xor(sign(a, width), sign(b, width), 1), factory.reduce_or(abs_y, width), 1); | 					T flip_y = factory.bitwise_and(factory.bitwise_xor(sign(a, width), sign(b, width), 1), factory.reduce_or(abs_y, width), 1); | ||||||
| 					T y_flipped = factory.mux(abs_y, factory.sub(abs_b, abs_y, width), flip_y, width); | 					T y_flipped = factory.mux(abs_y, factory.sub(abs_b, abs_y, width), flip_y, width); | ||||||
|  | 					// since y_flipped is strictly less than |b|, the top bit is always 0 and we can just sign extend the flipped result
 | ||||||
| 					T y = neg_if(y_flipped, width, sign(b, b_width)); | 					T y = neg_if(y_flipped, width, sign(b, b_width)); | ||||||
| 					return extend(y, width, y_width, true); | 					return extend(y, width, y_width, true); | ||||||
| 				} else | 				} else | ||||||
|  | @ -261,22 +271,8 @@ public: | ||||||
| 		} else if (cellType == ID($lut)) { | 		} else if (cellType == ID($lut)) { | ||||||
| 			int width = parameters.at(ID(WIDTH)).as_int(); | 			int width = parameters.at(ID(WIDTH)).as_int(); | ||||||
| 			Const lut_table = parameters.at(ID(LUT)); | 			Const lut_table = parameters.at(ID(LUT)); | ||||||
| 			T a = inputs.at(ID(A)); | 			lut_table.extu(1 << width); | ||||||
| 			// Output initialization
 | 			return handle_bmux(factory.constant(lut_table), inputs.at(ID(A)), 1 << width, 0, 1, width, width); | ||||||
| 			T y = factory.constant(Const(0, 1)); |  | ||||||
| 			// Iterate over each possible input combination
 |  | ||||||
| 			for (int i = 0; i < (1 << width); ++i) { |  | ||||||
| 				// Create a constant representing the value of i
 |  | ||||||
| 				T i_val = factory.constant(Const(i, width)); |  | ||||||
| 				// Check if the input matches this value
 |  | ||||||
| 				T match = factory.equal(a, i_val, width); |  | ||||||
| 				// Get the corresponding LUT value
 |  | ||||||
| 				bool lut_val = lut_table.bits[i] == State::S1; |  | ||||||
| 				T lut_output = factory.constant(Const(lut_val, 1)); |  | ||||||
| 				// Use a multiplexer to select the correct output based on the match
 |  | ||||||
| 				y = factory.mux(y, lut_output, match, 1); |  | ||||||
| 			} |  | ||||||
| 			return y; |  | ||||||
| 		} else if (cellType == ID($bwmux)) { | 		} else if (cellType == ID($bwmux)) { | ||||||
| 			int width = parameters.at(ID(WIDTH)).as_int(); | 			int width = parameters.at(ID(WIDTH)).as_int(); | ||||||
| 			T a = inputs.at(ID(A)); | 			T a = inputs.at(ID(A)); | ||||||
|  | @ -526,7 +522,7 @@ void FunctionalIR::topological_sort() { | ||||||
|     if(scc) log_error("combinational loops, aborting\n"); |     if(scc) log_error("combinational loops, aborting\n"); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| IdString merge_name(IdString a, IdString b) { | static IdString merge_name(IdString a, IdString b) { | ||||||
| 	if(a[0] == '$' && b[0] == '\\') | 	if(a[0] == '$' && b[0] == '\\') | ||||||
| 		return b; | 		return b; | ||||||
| 	else | 	else | ||||||
|  |  | ||||||
|  | @ -1,14 +1,30 @@ | ||||||
| import pytest | import pytest | ||||||
| from rtlil_cells import generate_test_cases | from rtlil_cells import generate_test_cases | ||||||
|  | import random | ||||||
|  | 
 | ||||||
|  | random_seed = random.getrandbits(32) | ||||||
| 
 | 
 | ||||||
| def pytest_addoption(parser): | def pytest_addoption(parser): | ||||||
|     parser.addoption( |     parser.addoption("--per-cell", type=int, default=None, help="run only N tests per cell") | ||||||
|         "--per-cell", type=int, default=None, help="run only N tests per cell" |     parser.addoption("--steps", type=int, default=1000, help="run each test for N steps") | ||||||
|     ) |     parser.addoption("--seed", type=int, default=random_seed, help="seed for random number generation, use random seed if unspecified") | ||||||
|  | 
 | ||||||
|  | def pytest_collection_finish(session): | ||||||
|  |     print('random seed: {}'.format(session.config.getoption("seed"))) | ||||||
|  | 
 | ||||||
|  | @pytest.fixture | ||||||
|  | def num_steps(request): | ||||||
|  |     return request.config.getoption("steps") | ||||||
|  | 
 | ||||||
|  | @pytest.fixture | ||||||
|  | def rnd(request): | ||||||
|  |     seed1 = request.config.getoption("seed") | ||||||
|  |     return lambda seed2: random.Random('{}-{}'.format(seed1, seed2)) | ||||||
| 
 | 
 | ||||||
| def pytest_generate_tests(metafunc): | def pytest_generate_tests(metafunc): | ||||||
|     if "cell" in metafunc.fixturenames: |     if "cell" in metafunc.fixturenames: | ||||||
|         print(dir(metafunc.config)) |  | ||||||
|         per_cell = metafunc.config.getoption("per_cell", default=None) |         per_cell = metafunc.config.getoption("per_cell", default=None) | ||||||
|         names, cases = generate_test_cases(per_cell) |         seed1 = metafunc.config.getoption("seed") | ||||||
|  |         rnd = lambda seed2: random.Random('{}-{}'.format(seed1, seed2)) | ||||||
|  |         names, cases = generate_test_cases(per_cell, rnd) | ||||||
|         metafunc.parametrize("cell,parameters", cases, ids=names) |         metafunc.parametrize("cell,parameters", cases, ids=names) | ||||||
|  | @ -1,30 +1,6 @@ | ||||||
| from itertools import chain | from itertools import chain | ||||||
| import random | import random | ||||||
| 
 | 
 | ||||||
| widths = [ |  | ||||||
|     (16, 32, 48, True), |  | ||||||
|     (16, 32, 48, False), |  | ||||||
|     (32, 16, 48, True), |  | ||||||
|     (32, 16, 48, False), |  | ||||||
|     (32, 32, 16, True), |  | ||||||
|     (32, 32, 16, False) |  | ||||||
| ] |  | ||||||
| 
 |  | ||||||
| shift_widths = [ |  | ||||||
|     (32, 6, 32, True, False), |  | ||||||
|     (32, 6, 32, False, False), |  | ||||||
|     (32, 6, 64, True, False), |  | ||||||
|     (32, 6, 64, False, False), |  | ||||||
|     (32, 32, 16, True, False), |  | ||||||
|     (32, 32, 16, False, False), |  | ||||||
|     (32, 6, 32, True, True), |  | ||||||
|     (32, 6, 32, False, True), |  | ||||||
|     (32, 6, 64, True, True), |  | ||||||
|     (32, 6, 64, False, True), |  | ||||||
|     (32, 32, 16, True, True), |  | ||||||
|     (32, 32, 16, False, True), |  | ||||||
| ] |  | ||||||
| 
 |  | ||||||
| def write_rtlil_cell(f, cell_type, inputs, outputs, parameters): | def write_rtlil_cell(f, cell_type, inputs, outputs, parameters): | ||||||
|     f.write('autoidx 1\n') |     f.write('autoidx 1\n') | ||||||
|     f.write('module \\gold\n') |     f.write('module \\gold\n') | ||||||
|  | @ -37,207 +13,260 @@ def write_rtlil_cell(f, cell_type, inputs, outputs, parameters): | ||||||
|         idx += 1 |         idx += 1 | ||||||
|     f.write(f'\tcell ${cell_type} \\UUT\n') |     f.write(f'\tcell ${cell_type} \\UUT\n') | ||||||
|     for (name, value) in parameters.items(): |     for (name, value) in parameters.items(): | ||||||
|  |         if value >= 2**32: | ||||||
|  |             f.write(f'\t\tparameter \\{name} {value.bit_length()}\'{value:b}\n') | ||||||
|  |         else: | ||||||
|             f.write(f'\t\tparameter \\{name} {value}\n') |             f.write(f'\t\tparameter \\{name} {value}\n') | ||||||
|     for name in chain(inputs.keys(), outputs.keys()): |     for name in chain(inputs.keys(), outputs.keys()): | ||||||
|         f.write(f'\t\tconnect \\{name} \\{name}\n') |         f.write(f'\t\tconnect \\{name} \\{name}\n') | ||||||
|     f.write(f'\tend\nend\n') |     f.write(f'\tend\nend\n') | ||||||
| 
 | 
 | ||||||
| class BaseCell: | class BaseCell: | ||||||
|     def __init__(self, name): |     def __init__(self, name, parameters, inputs, outputs, test_values): | ||||||
|         self.name = name |         self.name = name | ||||||
|  |         self.parameters = parameters | ||||||
|  |         self.inputs = inputs | ||||||
|  |         self.outputs = outputs | ||||||
|  |         self.test_values = test_values | ||||||
|  |     def get_port_width(self, port, parameters): | ||||||
|  |         def parse_specifier(spec): | ||||||
|  |             if isinstance(spec, int): | ||||||
|  |                 return spec | ||||||
|  |             if isinstance(spec, str): | ||||||
|  |                 return parameters[spec] | ||||||
|  |             if callable(spec): | ||||||
|  |                 return spec(parameters) | ||||||
|  |             assert False, "expected int, str or lambda" | ||||||
|  |         if port in self.inputs: | ||||||
|  |             return parse_specifier(self.inputs[port]) | ||||||
|  |         elif port in self.outputs: | ||||||
|  |             return parse_specifier(self.outputs[port]) | ||||||
|  |         else: | ||||||
|  |             assert False, "expected input or output" | ||||||
|  |     def generate_tests(self, rnd): | ||||||
|  |         def print_parameter(v): | ||||||
|  |             if isinstance(v, bool): | ||||||
|  |                 return "S" if v else "U" | ||||||
|  |             else: | ||||||
|  |                 return str(v) | ||||||
|  |         for values in self.test_values: | ||||||
|  |             if isinstance(values, int): | ||||||
|  |                 values = [values] | ||||||
|  |             name = '-'.join([print_parameter(v) for v in values]) | ||||||
|  |             parameters = {parameter: int(values[i]) for i, parameter in enumerate(self.parameters)} | ||||||
|  |             if self.is_test_valid(values): | ||||||
|  |                 yield (name, parameters) | ||||||
|  |     def write_rtlil_file(self, path, parameters): | ||||||
|  |         inputs = {port: self.get_port_width(port, parameters) for port in self.inputs} | ||||||
|  |         outputs = {port: self.get_port_width(port, parameters) for port in self.outputs} | ||||||
|  |         with open(path, 'w') as f: | ||||||
|  |             write_rtlil_cell(f, self.name, inputs, outputs, parameters) | ||||||
|  |     def is_test_valid(self, values): | ||||||
|  |         return True | ||||||
| 
 | 
 | ||||||
| class UnaryCell(BaseCell): | class UnaryCell(BaseCell): | ||||||
|     def __init__(self, name): |     def __init__(self, name, values): | ||||||
|         super().__init__(name) |         super().__init__(name, ['A_WIDTH', 'Y_WIDTH', 'A_SIGNED'], {'A': 'A_WIDTH'}, {'Y': 'Y_WIDTH'}, values) | ||||||
|     def generate_tests(self): |  | ||||||
|         for (a_width, _, y_width, signed) in widths: |  | ||||||
|             yield (f'{a_width}-{y_width}-{'S' if signed else 'U'}', |  | ||||||
|                    {'A_WIDTH' : a_width, |  | ||||||
|                     'A_SIGNED' : int(signed), |  | ||||||
|                     'Y_WIDTH' : y_width}) |  | ||||||
|     def write_rtlil_file(self, f, parameters): |  | ||||||
|         write_rtlil_cell(f, self.name, {'A': parameters['A_WIDTH']}, {'Y': parameters['Y_WIDTH']}, parameters) |  | ||||||
| 
 | 
 | ||||||
| class BinaryCell(BaseCell): | class BinaryCell(BaseCell): | ||||||
|     def __init__(self, name): |     def __init__(self, name, values): | ||||||
|         super().__init__(name) |         super().__init__(name, ['A_WIDTH', 'B_WIDTH', 'Y_WIDTH', 'A_SIGNED', 'B_SIGNED'], {'A': 'A_WIDTH', 'B': 'B_WIDTH'}, {'Y': 'Y_WIDTH'}, values) | ||||||
|     def generate_tests(self): |  | ||||||
|         for (a_width, b_width, y_width, signed) in widths: |  | ||||||
|             yield (f'{a_width}-{b_width}-{y_width}-{'S' if signed else 'U'}', |  | ||||||
|                    {'A_WIDTH' : a_width, |  | ||||||
|                     'A_SIGNED' : int(signed), |  | ||||||
|                     'B_WIDTH' : b_width, |  | ||||||
|                     'B_SIGNED' : int(signed), |  | ||||||
|                     'Y_WIDTH' : y_width}) |  | ||||||
|     def write_rtlil_file(self, f, parameters): |  | ||||||
|         write_rtlil_cell(f, self.name, {'A': parameters['A_WIDTH'], 'B': parameters['B_WIDTH']}, {'Y': parameters['Y_WIDTH']}, parameters) |  | ||||||
| 
 | 
 | ||||||
| class ShiftCell(BaseCell): | class ShiftCell(BaseCell): | ||||||
|     def __init__(self, name): |     def __init__(self, name, values): | ||||||
|         super().__init__(name) |         super().__init__(name,  ['A_WIDTH', 'B_WIDTH', 'Y_WIDTH', 'A_SIGNED', 'B_SIGNED'], {'A': 'A_WIDTH', 'B': 'B_WIDTH'}, {'Y': 'Y_WIDTH'}, values) | ||||||
|     def generate_tests(self): |     def is_test_valid(self, values): | ||||||
|         for (a_width, b_width, y_width, a_signed, b_signed) in shift_widths: |         (a_width, b_width, y_width, a_signed, b_signed) = values | ||||||
|             if not self.name in ('shift', 'shiftx') and b_signed: continue |         if not self.name in ('shift', 'shiftx') and b_signed: return False | ||||||
|             if self.name == 'shiftx' and a_signed: continue |         if self.name == 'shiftx' and a_signed: return False | ||||||
|             yield (f'{a_width}-{b_width}-{y_width}-{'S' if a_signed else 'U'}{'S' if b_signed else 'U'}', |         return True | ||||||
|                    {'A_WIDTH' : a_width, |  | ||||||
|                     'A_SIGNED' : int(a_signed), |  | ||||||
|                     'B_WIDTH' : b_width, |  | ||||||
|                     'B_SIGNED' : int(b_signed), |  | ||||||
|                     'Y_WIDTH' : y_width}) |  | ||||||
|     def write_rtlil_file(self, f, parameters): |  | ||||||
|         write_rtlil_cell(f, self.name, {'A': parameters['A_WIDTH'], 'B': parameters['B_WIDTH']}, {'Y': parameters['Y_WIDTH']}, parameters) |  | ||||||
| 
 | 
 | ||||||
| class MuxCell(BaseCell): | class MuxCell(BaseCell): | ||||||
|     def __init__(self, name): |     def __init__(self, name, values): | ||||||
|         super().__init__(name) |         super().__init__(name, ['WIDTH'], {'A': 'WIDTH', 'B': 'WIDTH', 'S': 1}, {'Y': 'WIDTH'}, values) | ||||||
|     def generate_tests(self): |  | ||||||
|         for width in [10, 20, 40]: |  | ||||||
|             yield (f'{width}', {'WIDTH' : width}) |  | ||||||
|     def write_rtlil_file(self, f, parameters): |  | ||||||
|         write_rtlil_cell(f, self.name, {'A': parameters['WIDTH'], 'B': parameters['WIDTH'], 'S': 1}, {'Y': parameters['WIDTH']}, parameters) |  | ||||||
| 
 | 
 | ||||||
| class BWCell(BaseCell): | class BWCell(BaseCell): | ||||||
|     def __init__(self, name): |     def __init__(self, name, values): | ||||||
|         super().__init__(name) |         inputs = {'A': 'WIDTH', 'B': 'WIDTH'} | ||||||
|     def generate_tests(self): |         if name == "bwmux": inputs['S'] = 'WIDTH' | ||||||
|         for width in [10, 20, 40]: |         super().__init__(name, ['WIDTH'], inputs, {'Y': 'WIDTH'}, values) | ||||||
|             yield (f'{width}', {'WIDTH' : width}) |  | ||||||
|     def write_rtlil_file(self, f, parameters): |  | ||||||
|         inputs = {'A': parameters['WIDTH'], 'B': parameters['WIDTH']} |  | ||||||
|         if self.name == "bwmux": inputs['S'] = parameters['WIDTH'] |  | ||||||
|         write_rtlil_cell(f, self.name, inputs, {'Y': parameters['WIDTH']}, parameters) |  | ||||||
| 
 | 
 | ||||||
| class PMuxCell(BaseCell): | class PMuxCell(BaseCell): | ||||||
|     def __init__(self, name): |     def __init__(self, name, values): | ||||||
|         super().__init__(name) |         b_width = lambda par: par['WIDTH'] * par['S_WIDTH'] | ||||||
|     def generate_tests(self): |         super().__init__(name, ['WIDTH', 'S_WIDTH'], {'A': 'WIDTH', 'B': b_width, 'S': 'S_WIDTH'}, {'Y': 'WIDTH'}, values) | ||||||
|         for (width, s_width) in [(10, 1), (10, 4), (20, 4)]: |  | ||||||
|             yield (f'{width}-{s_width}', |  | ||||||
|                    {'WIDTH' : width, |  | ||||||
|                     'S_WIDTH' : s_width}) |  | ||||||
|     def write_rtlil_file(self, f, parameters): |  | ||||||
|         s_width = parameters['S_WIDTH'] |  | ||||||
|         b_width = parameters['WIDTH'] * s_width |  | ||||||
|         write_rtlil_cell(f, self.name, {'A': parameters['WIDTH'], 'B': b_width, 'S': s_width}, {'Y': parameters['WIDTH']}, parameters) |  | ||||||
| 
 | 
 | ||||||
| class BMuxCell(BaseCell): | class BMuxCell(BaseCell): | ||||||
|     def __init__(self, name): |     def __init__(self, name, values): | ||||||
|         super().__init__(name) |         a_width = lambda par: par['WIDTH'] << par['S_WIDTH'] | ||||||
|     def generate_tests(self): |         super().__init__(name, ['WIDTH', 'S_WIDTH'], {'A': a_width, 'S': 'S_WIDTH'}, {'Y': 'WIDTH'}, values) | ||||||
|         for (width, s_width) in [(10, 1), (10, 2), (10, 4)]: |  | ||||||
|             yield (f'{width}-{s_width}', {'WIDTH' : width, 'S_WIDTH' : s_width}) |  | ||||||
|     def write_rtlil_file(self, f, parameters): |  | ||||||
|         write_rtlil_cell(f, self.name, {'A': parameters['WIDTH'] << parameters['S_WIDTH'], 'S': parameters['S_WIDTH']}, {'Y': parameters['WIDTH']}, parameters) |  | ||||||
| 
 | 
 | ||||||
| class DemuxCell(BaseCell): | class DemuxCell(BaseCell): | ||||||
|     def __init__(self, name): |     def __init__(self, name, values): | ||||||
|         super().__init__(name) |         y_width = lambda par: par['WIDTH'] << par['S_WIDTH'] | ||||||
|     def generate_tests(self): |         super().__init__(name, ['WIDTH', 'S_WIDTH'], {'A': 'WIDTH', 'S': 'S_WIDTH'}, {'Y': y_width}, values) | ||||||
|         for (width, s_width) in [(10, 1), (32, 2), (16, 4)]: |  | ||||||
|             yield (f'{width}-{s_width}', {'WIDTH' : width, 'S_WIDTH' : s_width}) |  | ||||||
|     def write_rtlil_file(self, f, parameters): |  | ||||||
|         write_rtlil_cell(f, self.name, {'A': parameters['WIDTH'], 'S': parameters['S_WIDTH']}, {'Y': parameters['WIDTH'] << parameters['S_WIDTH']}, parameters) |  | ||||||
| 
 |  | ||||||
| def seeded_randint(seed, a, b): |  | ||||||
|     r = random.getstate() |  | ||||||
|     random.seed(seed) |  | ||||||
|     n = random.randint(a, b) |  | ||||||
|     random.setstate(r) |  | ||||||
|     return n |  | ||||||
| 
 | 
 | ||||||
| class LUTCell(BaseCell): | class LUTCell(BaseCell): | ||||||
|     def __init__(self, name): |     def __init__(self, name, values): | ||||||
|         super().__init__(name) |         super().__init__(name, ['WIDTH', 'LUT'], {'A': 'WIDTH'}, {'Y': 1}, values) | ||||||
|     def generate_tests(self): |     def generate_tests(self, rnd): | ||||||
|         for width in [4, 6, 8]: |         for width in self.test_values: | ||||||
|             lut = seeded_randint(width, 0, 2**width - 1) |             lut = rnd(f'lut-{width}').getrandbits(2**width) | ||||||
|             yield (f'{width}', {'WIDTH' : width, 'LUT' : lut}) |             yield (f'{width}', {'WIDTH' : width, 'LUT' : lut}) | ||||||
|     def write_rtlil_file(self, f, parameters): |  | ||||||
|         write_rtlil_cell(f, self.name, {'A': parameters['WIDTH']}, {'Y': 1}, parameters) |  | ||||||
| 
 | 
 | ||||||
| class ConcatCell(BaseCell): | class ConcatCell(BaseCell): | ||||||
|     def __init__(self, name): |     def __init__(self, name, values): | ||||||
|         super().__init__(name) |         y_width = lambda par: par['A_WIDTH'] + par['B_WIDTH'] | ||||||
|     def generate_tests(self): |         super().__init__(name, ['A_WIDTH', 'B_WIDTH'], {'A': 'A_WIDTH', 'B': 'B_WIDTH'}, {'Y': y_width}, values) | ||||||
|         for (a_width, b_width) in [(16, 16), (8, 14), (20, 10)]: |  | ||||||
|             yield (f'{a_width}-{b_width}', {'A_WIDTH' : a_width, 'B_WIDTH' : b_width}) |  | ||||||
|     def write_rtlil_file(self, f, parameters): |  | ||||||
|         write_rtlil_cell(f, self.name, {'A': parameters['A_WIDTH'], 'B' : parameters['B_WIDTH']}, {'Y': parameters['A_WIDTH'] + parameters['B_WIDTH']}, parameters) |  | ||||||
| 
 | 
 | ||||||
| class SliceCell(BaseCell): | class SliceCell(BaseCell): | ||||||
|     def __init__(self, name): |     def __init__(self, name, values): | ||||||
|         super().__init__(name) |         super().__init__(name, ['A_WIDTH', 'OFFSET', 'Y_WIDTH'], {'A': 'A_WIDTH'}, {'Y': 'Y_WIDTH'}, values) | ||||||
|     def generate_tests(self): |  | ||||||
|         for (a_width, offset, y_width) in [(32, 10, 15), (8, 0, 4), (10, 0, 10)]: |  | ||||||
|             yield (f'{a_width}-{offset}-{y_width}', {'A_WIDTH' : a_width, 'OFFSET' : offset, 'Y_WIDTH': y_width}) |  | ||||||
|     def write_rtlil_file(self, f, parameters): |  | ||||||
|         write_rtlil_cell(f, self.name, {'A': parameters['A_WIDTH']}, {'Y': parameters['Y_WIDTH']}, parameters) |  | ||||||
| 
 | 
 | ||||||
| class FailCell(BaseCell): | class FailCell(BaseCell): | ||||||
|     def __init__(self, name): |     def __init__(self, name): | ||||||
|         super().__init__(name) |         super().__init__(name, [], {}, {}) | ||||||
|     def generate_tests(self): |     def generate_tests(self, rnd): | ||||||
|         yield ('', {}) |         yield ('', {}) | ||||||
|     def write_rtlil_file(self, f, parameters): |     def write_rtlil_file(self, path, parameters): | ||||||
|         raise Exception(f'\'{self.name}\' cell unimplemented in test generator') |         raise Exception(f'\'{self.name}\' cell unimplemented in test generator') | ||||||
| 
 | 
 | ||||||
|  | class FFCell(BaseCell): | ||||||
|  |     def __init__(self, name, values): | ||||||
|  |         super().__init__(name, ['WIDTH'], ['D'], ['Q'], values) | ||||||
|  |     def write_rtlil_file(self, path, parameters): | ||||||
|  |         from test_functional import yosys_synth | ||||||
|  |         verilog_file = path.parent / 'verilog.v' | ||||||
|  |         with open(verilog_file, 'w') as f: | ||||||
|  |             f.write(""" | ||||||
|  | module gold( | ||||||
|  |     input wire clk, | ||||||
|  |     input wire [{0}:0] D, | ||||||
|  |     output reg [{0}:0] Q | ||||||
|  | ); | ||||||
|  |     always @(posedge clk) | ||||||
|  |         Q <= D; | ||||||
|  | endmodule""".format(parameters['WIDTH'] - 1)) | ||||||
|  |         yosys_synth(verilog_file, path) | ||||||
|  | 
 | ||||||
|  | class MemCell(BaseCell): | ||||||
|  |     def __init__(self, name, values): | ||||||
|  |         super().__init__(name, ['DATA_WIDTH', 'ADDR_WIDTH'], {'WA': 'ADDR_WIDTH', 'RA': 'ADDR_WIDTH', 'WD': 'DATA_WIDTH'}, {'RD': 'DATA_WIDTH'}, values) | ||||||
|  |     def write_rtlil_file(self, path, parameters): | ||||||
|  |         from test_functional import yosys_synth | ||||||
|  |         verilog_file = path.parent / 'verilog.v' | ||||||
|  |         with open(verilog_file, 'w') as f: | ||||||
|  |             f.write(""" | ||||||
|  | module gold( | ||||||
|  |     input wire clk, | ||||||
|  |     input wire [{1}:0] WA, | ||||||
|  |     input wire [{0}:0] WD, | ||||||
|  |     output reg [{0}:0] RD | ||||||
|  | ); | ||||||
|  |     reg [{0}:0] mem[0:{1}]; | ||||||
|  |     always @(*) | ||||||
|  |         RD = mem[RA]; | ||||||
|  |     always @(posedge clk) | ||||||
|  |         mem[WA] <= WD; | ||||||
|  | endmodule""".format(parameters['DATA_WIDTH'] - 1, parameters['ADDR_WIDTH'] - 1)) | ||||||
|  |         yosys_synth(verilog_file, path) | ||||||
|  | 
 | ||||||
|  | binary_widths = [ | ||||||
|  |     # try to cover extending A operand, extending B operand, extending/truncating result | ||||||
|  |     (16, 32, 48, True, True), | ||||||
|  |     (16, 32, 48, False, False), | ||||||
|  |     (32, 16, 48, True, True), | ||||||
|  |     (32, 16, 48, False, False), | ||||||
|  |     (32, 32, 16, True, True), | ||||||
|  |     (32, 32, 16, False, False), | ||||||
|  |     # have at least one test that checks small inputs, which will exercise the cornercases more | ||||||
|  |     (4, 4, 8, True, True), | ||||||
|  |     (4, 4, 8, False, False) | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | unary_widths = [ | ||||||
|  |     (6, 12, True), | ||||||
|  |     (6, 12, False), | ||||||
|  |     (32, 16, True), | ||||||
|  |     (32, 16, False) | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | # note that meaningless combinations of signednesses are eliminated, | ||||||
|  | # like e.g. most shift operations don't take signed shift amounts | ||||||
|  | shift_widths = [ | ||||||
|  |     # one set of tests that definitely checks all possible shift amounts | ||||||
|  |     # with a bigger result width to make sure it's not truncated | ||||||
|  |     (32, 6, 64, True, False), | ||||||
|  |     (32, 6, 64, False, False), | ||||||
|  |     (32, 6, 64, True, True), | ||||||
|  |     (32, 6, 64, False, True), | ||||||
|  |     # one set that checks very oversized shifts | ||||||
|  |     (32, 32, 64, True, False), | ||||||
|  |     (32, 32, 64, False, False), | ||||||
|  |     (32, 32, 64, True, True), | ||||||
|  |     (32, 32, 64, False, True), | ||||||
|  |     # at least one test where the result is going to be truncated | ||||||
|  |     (32, 6, 16, False, False) | ||||||
|  | ] | ||||||
|  | 
 | ||||||
| rtlil_cells = [ | rtlil_cells = [ | ||||||
|     UnaryCell("not"), |     UnaryCell("not", unary_widths), | ||||||
|     UnaryCell("pos"), |     UnaryCell("pos", unary_widths), | ||||||
|     UnaryCell("neg"), |     UnaryCell("neg", unary_widths), | ||||||
|     BinaryCell("and"), |     BinaryCell("and", binary_widths), | ||||||
|     BinaryCell("or"), |     BinaryCell("or", binary_widths), | ||||||
|     BinaryCell("xor"), |     BinaryCell("xor", binary_widths), | ||||||
|     BinaryCell("xnor"), |     BinaryCell("xnor", binary_widths), | ||||||
|     UnaryCell("reduce_and"), |     UnaryCell("reduce_and", unary_widths), | ||||||
|     UnaryCell("reduce_or"), |     UnaryCell("reduce_or", unary_widths), | ||||||
|     UnaryCell("reduce_xor"), |     UnaryCell("reduce_xor", unary_widths), | ||||||
|     UnaryCell("reduce_xnor"), |     UnaryCell("reduce_xnor", unary_widths), | ||||||
|     UnaryCell("reduce_bool"), |     UnaryCell("reduce_bool", unary_widths), | ||||||
|     ShiftCell("shl"), |     ShiftCell("shl", shift_widths), | ||||||
|     ShiftCell("shr"), |     ShiftCell("shr", shift_widths), | ||||||
|     ShiftCell("sshl"), |     ShiftCell("sshl", shift_widths), | ||||||
|     ShiftCell("sshr"), |     ShiftCell("sshr", shift_widths), | ||||||
|     ShiftCell("shift"), |     ShiftCell("shift", shift_widths), | ||||||
|     ShiftCell("shiftx"), |     ShiftCell("shiftx", shift_widths), | ||||||
| #    ("fa", ["A", "B", "C", "X", "Y"]), | #    ("fa", ["A", "B", "C", "X", "Y"]), | ||||||
| #    ("lcu", ["P", "G", "CI", "CO"]), | #    ("lcu", ["P", "G", "CI", "CO"]), | ||||||
| #    ("alu", ["A", "B", "CI", "BI", "X", "Y", "CO"]), | #    ("alu", ["A", "B", "CI", "BI", "X", "Y", "CO"]), | ||||||
|     BinaryCell("lt"), |     BinaryCell("lt", binary_widths), | ||||||
|     BinaryCell("le"), |     BinaryCell("le", binary_widths), | ||||||
|     BinaryCell("eq"), |     BinaryCell("eq", binary_widths), | ||||||
|     BinaryCell("ne"), |     BinaryCell("ne", binary_widths), | ||||||
|     BinaryCell("eqx"), |     BinaryCell("eqx", binary_widths), | ||||||
|     BinaryCell("nex"), |     BinaryCell("nex", binary_widths), | ||||||
|     BinaryCell("ge"), |     BinaryCell("ge", binary_widths), | ||||||
|     BinaryCell("gt"), |     BinaryCell("gt", binary_widths), | ||||||
|     BinaryCell("add"), |     BinaryCell("add", binary_widths), | ||||||
|     BinaryCell("sub"), |     BinaryCell("sub", binary_widths), | ||||||
|     BinaryCell("mul"), |     BinaryCell("mul", binary_widths), | ||||||
| #    BinaryCell("macc"), | #    BinaryCell("macc"), | ||||||
|     BinaryCell("div"), |     BinaryCell("div", binary_widths), | ||||||
|     BinaryCell("mod"), |     BinaryCell("mod", binary_widths), | ||||||
|     BinaryCell("divfloor"), |     BinaryCell("divfloor", binary_widths), | ||||||
|     BinaryCell("modfloor"), |     BinaryCell("modfloor", binary_widths), | ||||||
|     BinaryCell("pow"), |     BinaryCell("pow", binary_widths), | ||||||
|     UnaryCell("logic_not"), |     UnaryCell("logic_not", unary_widths), | ||||||
|     BinaryCell("logic_and"), |     BinaryCell("logic_and", binary_widths), | ||||||
|     BinaryCell("logic_or"), |     BinaryCell("logic_or", binary_widths), | ||||||
|     SliceCell("slice"), |     SliceCell("slice", [(32, 10, 15), (8, 0, 4), (10, 0, 10)]), | ||||||
|     ConcatCell("concat"), |     ConcatCell("concat", [(16, 16), (8, 14), (20, 10)]), | ||||||
|     MuxCell("mux"), |     MuxCell("mux", [10, 16, 40]), | ||||||
|     BMuxCell("bmux"), |     BMuxCell("bmux", [(10, 1), (10, 2), (10, 4)]), | ||||||
|     PMuxCell("pmux"), |     PMuxCell("pmux", [(10, 1), (10, 4), (20, 4)]), | ||||||
|     DemuxCell("demux"), |     DemuxCell("demux", [(10, 1), (32, 2), (16, 4)]), | ||||||
|     LUTCell("lut"), |     LUTCell("lut", [4, 6, 8]), | ||||||
| #    ("sop", ["A", "Y"]), | #    ("sop", ["A", "Y"]), | ||||||
| #    ("tribuf", ["A", "EN", "Y"]), | #    ("tribuf", ["A", "EN", "Y"]), | ||||||
| #    ("specify2", ["EN", "SRC", "DST"]), | #    ("specify2", ["EN", "SRC", "DST"]), | ||||||
| #    ("specify3", ["EN", "SRC", "DST", "DAT"]), | #    ("specify3", ["EN", "SRC", "DST", "DAT"]), | ||||||
| #    ("specrule", ["EN_SRC", "EN_DST", "SRC", "DST"]), | #    ("specrule", ["EN_SRC", "EN_DST", "SRC", "DST"]), | ||||||
|     BWCell("bweqx"), |     BWCell("bweqx", [10, 16, 40]), | ||||||
|     BWCell("bwmux"), |     BWCell("bwmux", [10, 16, 40]), | ||||||
|  |     FFCell("ff", [10, 20, 40]), | ||||||
|  |     MemCell("mem", [(32, 4)]) | ||||||
| #    ("assert", ["A", "EN"]), | #    ("assert", ["A", "EN"]), | ||||||
| #    ("assume", ["A", "EN"]), | #    ("assume", ["A", "EN"]), | ||||||
| #    ("live", ["A", "EN"]), | #    ("live", ["A", "EN"]), | ||||||
|  | @ -260,12 +289,12 @@ rtlil_cells = [ | ||||||
| #    ("scopeinfo", []), | #    ("scopeinfo", []), | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
| def generate_test_cases(per_cell): | def generate_test_cases(per_cell, rnd): | ||||||
|     tests = [] |     tests = [] | ||||||
|     names = [] |     names = [] | ||||||
|     for cell in rtlil_cells: |     for cell in rtlil_cells: | ||||||
|         seen_names = set() |         seen_names = set() | ||||||
|         for (name, parameters) in cell.generate_tests(): |         for (name, parameters) in cell.generate_tests(rnd): | ||||||
|             if not name in seen_names: |             if not name in seen_names: | ||||||
|                 seen_names.add(name) |                 seen_names.add(name) | ||||||
|                 tests.append((cell, parameters)) |                 tests.append((cell, parameters)) | ||||||
|  |  | ||||||
|  | @ -1,6 +1,5 @@ | ||||||
| import sys | import sys | ||||||
| import argparse | import argparse | ||||||
| import random |  | ||||||
| import os | import os | ||||||
| import smtio | import smtio | ||||||
| import re | import re | ||||||
|  | @ -40,9 +39,10 @@ class SExprParser: | ||||||
|         rv, self.stack[0] = self.stack[0], [] |         rv, self.stack[0] = self.stack[0], [] | ||||||
|         return rv |         return rv | ||||||
| 
 | 
 | ||||||
| def simulate_smt_with_smtio(smt_file_path, vcd_path, smt_io): | def simulate_smt_with_smtio(smt_file_path, vcd_path, smt_io, num_steps, rnd): | ||||||
|     inputs = {} |     inputs = {} | ||||||
|     outputs = {} |     outputs = {} | ||||||
|  |     states = {} | ||||||
| 
 | 
 | ||||||
|     def handle_datatype(lst): |     def handle_datatype(lst): | ||||||
|         print(lst) |         print(lst) | ||||||
|  | @ -60,6 +60,14 @@ def simulate_smt_with_smtio(smt_file_path, vcd_path, smt_io): | ||||||
|                 bitvec_size = declaration[1][2] |                 bitvec_size = declaration[1][2] | ||||||
|                 assert output_name.startswith("gold_Outputs_") |                 assert output_name.startswith("gold_Outputs_") | ||||||
|                 outputs[output_name[len("gold_Outputs_"):]] = int(bitvec_size) |                 outputs[output_name[len("gold_Outputs_"):]] = int(bitvec_size) | ||||||
|  |         elif datatype_name.endswith("_State"): | ||||||
|  |             for declaration in declarations: | ||||||
|  |                 state_name = declaration[0] | ||||||
|  |                 assert state_name.startswith("gold_State_") | ||||||
|  |                 if declaration[1][0] == "_": | ||||||
|  |                     states[state_name[len("gold_State_"):]] = int(declaration[1][2]) | ||||||
|  |                 else: | ||||||
|  |                     states[state_name[len("gold_State_"):]] = (declaration[1][1][2], declaration[1][2][2]) | ||||||
| 
 | 
 | ||||||
|     parser = SExprParser() |     parser = SExprParser() | ||||||
|     with open(smt_file_path, 'r') as smt_file: |     with open(smt_file_path, 'r') as smt_file: | ||||||
|  | @ -73,25 +81,44 @@ def simulate_smt_with_smtio(smt_file_path, vcd_path, smt_io): | ||||||
|     parser.finish() |     parser.finish() | ||||||
|     assert smt_io.check_sat() == 'sat' |     assert smt_io.check_sat() == 'sat' | ||||||
| 
 | 
 | ||||||
|  |     def initial_state(states): | ||||||
|  |         mk_state_parts = [] | ||||||
|  |         rv = [] | ||||||
|  |         for name, width in states.items(): | ||||||
|  |             if isinstance(width, int): | ||||||
|  |                 binary_string = format(0, '0{}b'.format(width)) | ||||||
|  |                 mk_state_parts.append(f"#b{binary_string}") | ||||||
|  |             else: | ||||||
|  |                 binary_string = format(0, '0{}b'.format(width[1])) | ||||||
|  |                 rv.append(f"(declare-const test_state_initial_mem_{name} (Array (_ BitVec {width[0]}) (_ BitVec {width[1]})))") | ||||||
|  |                 rv.append(f"(assert (forall ((i (_ BitVec {width[0]}))) (= (select test_state_initial_mem_{name} i) #b{binary_string})))") | ||||||
|  |                 mk_state_parts.append(f"test_state_initial_mem_{name}") | ||||||
|  |         if len(states) == 0: | ||||||
|  |             mk_state_call = "gold_State" | ||||||
|  |         else: | ||||||
|  |             mk_state_call = "(gold_State {})".format(" ".join(mk_state_parts)) | ||||||
|  |         rv.append(f"(define-const test_state_step_n0 gold_State {mk_state_call})\n") | ||||||
|  |         return rv | ||||||
|  | 
 | ||||||
|     def set_step(inputs, step): |     def set_step(inputs, step): | ||||||
|         # This function assumes 'inputs' is a dictionary like {"A": 5, "B": 4} |         # This function assumes 'inputs' is a dictionary like {"A": 5, "B": 4} | ||||||
|         # and 'input_values' is a dictionary like {"A": 5, "B": 13} specifying the concrete values for each input. |         # and 'input_values' is a dictionary like {"A": 5, "B": 13} specifying the concrete values for each input. | ||||||
|          |          | ||||||
|         mk_inputs_parts = [] |         mk_inputs_parts = [] | ||||||
|         for input_name, width in inputs.items(): |         for input_name, width in inputs.items(): | ||||||
|             value = random.getrandbits(width)  # Generate a random number up to the maximum value for the bit size |             value = rnd.getrandbits(width)  # Generate a random number up to the maximum value for the bit size | ||||||
|             binary_string = format(value, '0{}b'.format(width))  # Convert value to binary with leading zeros |             binary_string = format(value, '0{}b'.format(width))  # Convert value to binary with leading zeros | ||||||
|             mk_inputs_parts.append(f"#b{binary_string}") |             mk_inputs_parts.append(f"#b{binary_string}") | ||||||
| 
 | 
 | ||||||
|         mk_inputs_call = "gold_Inputs " + " ".join(mk_inputs_parts) |         mk_inputs_call = "gold_Inputs " + " ".join(mk_inputs_parts) | ||||||
|         define_inputs = f"(define-const test_inputs_step_n{step} gold_Inputs ({mk_inputs_call}))\n" |         return [ | ||||||
|  |             f"(define-const test_inputs_step_n{step} gold_Inputs ({mk_inputs_call}))\n", | ||||||
|  |             f"(define-const test_results_step_n{step} (Pair gold_Outputs gold_State) (gold test_inputs_step_n{step} test_state_step_n{step}))\n", | ||||||
|  |             f"(define-const test_outputs_step_n{step} gold_Outputs (first test_results_step_n{step}))\n", | ||||||
|  |             f"(define-const test_state_step_n{step+1} gold_State (second test_results_step_n{step}))\n", | ||||||
|  |         ] | ||||||
| 
 | 
 | ||||||
|         define_outputs = f"(define-const test_outputs_step_n{step} gold_Outputs (first (gold test_inputs_step_n{step} gold_State)))\n" |     smt_commands = initial_state(states) | ||||||
|         smt_commands = [define_inputs, define_outputs] |  | ||||||
|         return smt_commands |  | ||||||
| 
 |  | ||||||
|     num_steps = 1000 |  | ||||||
|     smt_commands = [] |  | ||||||
|     for step in range(num_steps): |     for step in range(num_steps): | ||||||
|         for step_command in set_step(inputs, step): |         for step_command in set_step(inputs, step): | ||||||
|             smt_commands.append(step_command) |             smt_commands.append(step_command) | ||||||
|  | @ -168,13 +195,13 @@ def simulate_smt_with_smtio(smt_file_path, vcd_path, smt_io): | ||||||
| 
 | 
 | ||||||
|     write_vcd(vcd_path, signals) |     write_vcd(vcd_path, signals) | ||||||
| 
 | 
 | ||||||
| def simulate_smt(smt_file_path, vcd_path): | def simulate_smt(smt_file_path, vcd_path, num_steps, rnd): | ||||||
|     so = smtio.SmtOpts() |     so = smtio.SmtOpts() | ||||||
|     so.solver = "z3" |     so.solver = "z3" | ||||||
|     so.logic = "BV" |     so.logic = "ABV" | ||||||
|     so.debug_print = True |     so.debug_print = True | ||||||
|     smt_io = smtio.SmtIo(opts=so) |     smt_io = smtio.SmtIo(opts=so) | ||||||
|     try: |     try: | ||||||
|         simulate_smt_with_smtio(smt_file_path, vcd_path, smt_io) |         simulate_smt_with_smtio(smt_file_path, vcd_path, smt_io, num_steps, rnd) | ||||||
|     finally: |     finally: | ||||||
|         smt_io.p_close() |         smt_io.p_close() | ||||||
|  | @ -6,11 +6,14 @@ from pathlib import Path | ||||||
| 
 | 
 | ||||||
| base_path = Path(__file__).resolve().parent.parent.parent | base_path = Path(__file__).resolve().parent.parent.parent | ||||||
| 
 | 
 | ||||||
|  | # quote a string or pathlib path so that it can be used by bash or yosys | ||||||
|  | # TODO: is this really appropriate for yosys? | ||||||
| def quote(path): | def quote(path): | ||||||
|     return shlex.quote(str(path)) |     return shlex.quote(str(path)) | ||||||
| 
 | 
 | ||||||
|  | # run a shell command and require the return code to be 0 | ||||||
| def run(cmd, **kwargs): | def run(cmd, **kwargs): | ||||||
|     print(' '.join([shlex.quote(str(x)) for x in cmd])) |     print(' '.join([quote(x) for x in cmd])) | ||||||
|     status = subprocess.run(cmd, **kwargs) |     status = subprocess.run(cmd, **kwargs) | ||||||
|     assert status.returncode == 0, f"{cmd[0]} failed" |     assert status.returncode == 0, f"{cmd[0]} failed" | ||||||
| 
 | 
 | ||||||
|  | @ -20,7 +23,24 @@ def yosys(script): | ||||||
| def compile_cpp(in_path, out_path, args): | def compile_cpp(in_path, out_path, args): | ||||||
|     run(['g++', '-g', '-std=c++17'] + args + [str(in_path), '-o', str(out_path)]) |     run(['g++', '-g', '-std=c++17'] + args + [str(in_path), '-o', str(out_path)]) | ||||||
| 
 | 
 | ||||||
| def test_cxx(cell, parameters, tmp_path): | def yosys_synth(verilog_file, rtlil_file): | ||||||
|  |     yosys(f"read_verilog {quote(verilog_file)} ; prep ; clk2fflogic ; write_rtlil {quote(rtlil_file)}") | ||||||
|  | 
 | ||||||
|  | # simulate an rtlil file with yosys, comparing with a given vcd file, and writing out the yosys simulation results into a second vcd file | ||||||
|  | def yosys_sim(rtlil_file, vcd_reference_file, vcd_out_file): | ||||||
|  |     try: | ||||||
|  |         yosys(f"read_rtlil {quote(rtlil_file)}; sim -r {quote(vcd_reference_file)} -scope gold -vcd {quote(vcd_out_file)} -timescale 1us -sim-gold") | ||||||
|  |     except: | ||||||
|  |         # if yosys sim fails it's probably because of a simulation mismatch | ||||||
|  |         # since yosys sim aborts on simulation mismatch to generate vcd output | ||||||
|  |         # we have to re-run with a different set of flags | ||||||
|  |         # on this run we ignore output and return code, we just want a best-effort attempt to get a vcd | ||||||
|  |         subprocess.run([base_path / 'yosys', '-Q', '-p', | ||||||
|  |             f'read_rtlil {quote(rtlil_file)}; sim -vcd {quote(vcd_out_file)} -a -r {quote(vcd_reference_file)} -scope gold -timescale 1us'], | ||||||
|  |             capture_output=True, check=False) | ||||||
|  |         raise | ||||||
|  | 
 | ||||||
|  | def test_cxx(cell, parameters, tmp_path, num_steps, rnd): | ||||||
|     rtlil_file = tmp_path / 'rtlil.il' |     rtlil_file = tmp_path / 'rtlil.il' | ||||||
|     vcdharness_cc_file = base_path / 'tests/functional/vcd_harness.cc' |     vcdharness_cc_file = base_path / 'tests/functional/vcd_harness.cc' | ||||||
|     cc_file = tmp_path / 'my_module_functional_cxx.cc' |     cc_file = tmp_path / 'my_module_functional_cxx.cc' | ||||||
|  | @ -28,20 +48,14 @@ def test_cxx(cell, parameters, tmp_path): | ||||||
|     vcd_functional_file = tmp_path / 'functional.vcd' |     vcd_functional_file = tmp_path / 'functional.vcd' | ||||||
|     vcd_yosys_sim_file = tmp_path / 'yosys.vcd' |     vcd_yosys_sim_file = tmp_path / 'yosys.vcd' | ||||||
| 
 | 
 | ||||||
|     with open(rtlil_file, 'w') as f: |     cell.write_rtlil_file(rtlil_file, parameters) | ||||||
|         cell.write_rtlil_file(f, parameters) |  | ||||||
|     yosys(f"read_rtlil {quote(rtlil_file)} ; write_functional_cxx {quote(cc_file)}") |     yosys(f"read_rtlil {quote(rtlil_file)} ; write_functional_cxx {quote(cc_file)}") | ||||||
|     compile_cpp(vcdharness_cc_file, vcdharness_exe_file, ['-I', tmp_path, '-I', str(base_path / 'backends/functional/cxx_runtime')]) |     compile_cpp(vcdharness_cc_file, vcdharness_exe_file, ['-I', tmp_path, '-I', str(base_path / 'backends/functional/cxx_runtime')]) | ||||||
|     run([str(vcdharness_exe_file.resolve()), str(vcd_functional_file)]) |     seed = str(rnd(cell.name + "-cxx").getrandbits(32)) | ||||||
|     try: |     run([str(vcdharness_exe_file.resolve()), str(vcd_functional_file), str(num_steps), str(seed)]) | ||||||
|         yosys(f"read_rtlil {quote(rtlil_file)}; sim -r {quote(vcd_functional_file)} -scope gold -vcd {quote(vcd_yosys_sim_file)} -timescale 1us -sim-gold") |     yosys_sim(rtlil_file, vcd_functional_file, vcd_yosys_sim_file) | ||||||
|     except: |  | ||||||
|         subprocess.run([base_path / 'yosys', '-Q', '-p', |  | ||||||
|             f'read_rtlil {quote(rtlil_file)}; sim -vcd {quote(vcd_yosys_sim_file)} -r {quote(vcd_functional_file)} -scope gold -timescale 1us'], |  | ||||||
|             capture_output=True, check=False) |  | ||||||
|         raise |  | ||||||
| 
 | 
 | ||||||
| def test_smt(cell, parameters, tmp_path): | def test_smt(cell, parameters, tmp_path, num_steps, rnd): | ||||||
|     import smt_vcd |     import smt_vcd | ||||||
| 
 | 
 | ||||||
|     rtlil_file = tmp_path / 'rtlil.il' |     rtlil_file = tmp_path / 'rtlil.il' | ||||||
|  | @ -49,15 +63,8 @@ def test_smt(cell, parameters, tmp_path): | ||||||
|     vcd_functional_file = tmp_path / 'functional.vcd' |     vcd_functional_file = tmp_path / 'functional.vcd' | ||||||
|     vcd_yosys_sim_file = tmp_path / 'yosys.vcd' |     vcd_yosys_sim_file = tmp_path / 'yosys.vcd' | ||||||
| 
 | 
 | ||||||
|     with open(rtlil_file, 'w') as f: |     cell.write_rtlil_file(rtlil_file, parameters) | ||||||
|         cell.write_rtlil_file(f, parameters) |  | ||||||
|     yosys(f"read_rtlil {quote(rtlil_file)} ; write_functional_smt2 {quote(smt_file)}") |     yosys(f"read_rtlil {quote(rtlil_file)} ; write_functional_smt2 {quote(smt_file)}") | ||||||
|     run(['z3', smt_file]) |     run(['z3', smt_file]) # check if output is valid smtlib before continuing | ||||||
|     smt_vcd.simulate_smt(smt_file, vcd_functional_file) |     smt_vcd.simulate_smt(smt_file, vcd_functional_file, num_steps, rnd(cell.name + "-smt")) | ||||||
|     try: |     yosys_sim(rtlil_file, vcd_functional_file, vcd_yosys_sim_file) | ||||||
|         yosys(f"read_rtlil {quote(rtlil_file)}; sim -r {quote(vcd_functional_file)} -scope gold -vcd {quote(vcd_yosys_sim_file)} -timescale 1us -sim-gold") |  | ||||||
|     except: |  | ||||||
|         subprocess.run([base_path / 'yosys', '-Q', '-p', |  | ||||||
|             f'read_rtlil {quote(rtlil_file)}; sim -vcd {quote(vcd_yosys_sim_file)} -r {quote(vcd_functional_file)} -scope gold -timescale 1us'], |  | ||||||
|             capture_output=True, check=False) |  | ||||||
|         raise |  | ||||||
|  | @ -2,15 +2,46 @@ | ||||||
| #include <iostream> | #include <iostream> | ||||||
| #include <fstream> | #include <fstream> | ||||||
| #include <random> | #include <random> | ||||||
|  | #include <ctype.h> | ||||||
|  | #include <vector> | ||||||
| 
 | 
 | ||||||
| #include "my_module_functional_cxx.cc" | #include "my_module_functional_cxx.cc" | ||||||
| 
 | 
 | ||||||
|  | std::string vcd_name_mangle(std::string name) { | ||||||
|  |   std::string ret = name; | ||||||
|  |   bool escape = ret.empty() || !isalpha(ret[0]) && ret[0] != '_'; | ||||||
|  |   for(size_t i = 0; i < ret.size(); i++) { | ||||||
|  |     if(isspace(ret[i])) ret[i] = '_'; | ||||||
|  |     if(!isalnum(ret[i]) && ret[i] != '_' && ret[i] != '$') | ||||||
|  |       escape = true; | ||||||
|  |   } | ||||||
|  |   if(escape) | ||||||
|  |     return "\\" + ret; | ||||||
|  |   else | ||||||
|  |     return ret; | ||||||
|  | } | ||||||
|  | std::unordered_map<std::string, std::string> codes;  | ||||||
|  | 
 | ||||||
| struct DumpHeader { | struct DumpHeader { | ||||||
|   std::ofstream &ofs; |   std::ofstream &ofs; | ||||||
|  |   std::string code = "!"; | ||||||
|   DumpHeader(std::ofstream &ofs) : ofs(ofs) {} |   DumpHeader(std::ofstream &ofs) : ofs(ofs) {} | ||||||
|  |   void increment_code() { | ||||||
|  |     for(size_t i = 0; i < code.size(); i++) | ||||||
|  |       if(code[i]++ == '~') | ||||||
|  |         code[i] = '!'; | ||||||
|  |       else | ||||||
|  |         return; | ||||||
|  |     code.push_back('!'); | ||||||
|  |   } | ||||||
|   template <size_t n> |   template <size_t n> | ||||||
|   void operator()(const char *name, Signal<n> value) { |   void operator()(const char *name, Signal<n> value) { | ||||||
|     ofs << "$var wire " << n << " " << name[0] << " " << name << " $end\n"; |     ofs << "$var wire " << n << " " << code << " " << vcd_name_mangle(name) << " $end\n"; | ||||||
|  |     codes[name] = code; | ||||||
|  |     increment_code(); | ||||||
|  |   } | ||||||
|  |   template <size_t n, size_t m> | ||||||
|  |   void operator()(const char *name, Memory<n, m> value) { | ||||||
|   } |   } | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | @ -22,14 +53,17 @@ struct Dump { | ||||||
|     // Bit
 |     // Bit
 | ||||||
|     if (n == 1) { |     if (n == 1) { | ||||||
|       ofs << (value[0] ? '1' : '0'); |       ofs << (value[0] ? '1' : '0'); | ||||||
|       ofs << name[0] << "\n"; |       ofs << codes[name] << "\n"; | ||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
|     // vector (multi-bit) signals
 |     // vector (multi-bit) signals
 | ||||||
|     ofs << "b"; |     ofs << "b"; | ||||||
|     for (size_t i = n; i-- > 0;) |     for (size_t i = n; i-- > 0;) | ||||||
|       ofs << (value[i] ? '1' : '0'); |       ofs << (value[i] ? '1' : '0'); | ||||||
|     ofs << " " << name[0] << "\n"; |     ofs << " " << codes[name] << "\n"; | ||||||
|  |   } | ||||||
|  |   template <size_t n, size_t m> | ||||||
|  |   void operator()(const char *name, Memory<n, m> value) { | ||||||
|   } |   } | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | @ -61,14 +95,15 @@ struct Randomize { | ||||||
| 
 | 
 | ||||||
| int main(int argc, char **argv) | int main(int argc, char **argv) | ||||||
| { | { | ||||||
|   if (argc != 2) { |   if (argc != 4) { | ||||||
|     std::cerr << "Usage: " << argv[0] << " <functional_vcd_filename>\n"; |     std::cerr << "Usage: " << argv[0] << " <functional_vcd_filename> <steps> <seed>\n"; | ||||||
|     return 1; |     return 1; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   const std::string functional_vcd_filename = argv[1]; |   const std::string functional_vcd_filename = argv[1]; | ||||||
|  |   const int steps = atoi(argv[2]); | ||||||
|  |   const uint32_t seed = atoi(argv[3]); | ||||||
| 
 | 
 | ||||||
|   constexpr int steps = 1000; |  | ||||||
|   constexpr int number_timescale = 1; |   constexpr int number_timescale = 1; | ||||||
|   const std::string units_timescale = "us"; |   const std::string units_timescale = "us"; | ||||||
|   gold::Inputs inputs; |   gold::Inputs inputs; | ||||||
|  | @ -87,27 +122,12 @@ int main(int argc, char **argv) | ||||||
|     state.visit(d); |     state.visit(d); | ||||||
|   } |   } | ||||||
|   vcd_file << "$enddefinitions $end\n$dumpvars\n"; |   vcd_file << "$enddefinitions $end\n$dumpvars\n"; | ||||||
|   vcd_file << "#0\n"; |   std::mt19937 gen(seed); | ||||||
|   // Set all signals to false
 | 
 | ||||||
|   inputs.visit(Reset()); |   inputs.visit(Reset()); | ||||||
| 
 | 
 | ||||||
|   gold::eval(inputs, outputs, state, next_state); |  | ||||||
|   { |  | ||||||
|     Dump d(vcd_file); |  | ||||||
|     inputs.visit(d); |  | ||||||
|     outputs.visit(d); |  | ||||||
|     state.visit(d); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   // Initialize random number generator once
 |  | ||||||
|   std::random_device rd; |  | ||||||
|   std::mt19937 gen(rd()); |  | ||||||
| 
 |  | ||||||
|   for (int step = 0; step < steps; ++step) { |   for (int step = 0; step < steps; ++step) { | ||||||
|     // Functional backend cxx
 |     vcd_file << "#" << step << "\n"; | ||||||
|     vcd_file << "#" << (step + 1) << "\n"; |  | ||||||
|     inputs.visit(Randomize(gen)); |  | ||||||
| 
 |  | ||||||
|     gold::eval(inputs, outputs, state, next_state); |     gold::eval(inputs, outputs, state, next_state); | ||||||
|     { |     { | ||||||
|       Dump d(vcd_file); |       Dump d(vcd_file); | ||||||
|  | @ -117,6 +137,7 @@ int main(int argc, char **argv) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     state = next_state; |     state = next_state; | ||||||
|  |     inputs.visit(Randomize(gen)); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   vcd_file.close(); |   vcd_file.close(); | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue