diff --git a/.travis.yml b/.travis.yml index 117deb20..77f039b4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,6 +6,7 @@ addons: # python managed by conda until 3.7 available # python: # - '3.6' + install: # install conda for py 3.7 - wget http://repo.continuum.io/miniconda/Miniconda-latest-Linux-x86_64.sh -O miniconda.sh diff --git a/fault/system_verilog_target.py b/fault/system_verilog_target.py index d56a427f..0ae7bac9 100644 --- a/fault/system_verilog_target.py +++ b/fault/system_verilog_target.py @@ -12,6 +12,7 @@ src_tpl = """\ module {circuit_name}_tb; +integer __error_occurred = 0; {declarations} {circuit_name} dut ( @@ -20,25 +21,24 @@ initial begin {initial_body} - #20 $finish; + #20 begin + if (__error_occurred) + $stop; + else + $finish; + end; end endmodule """ -ncsim_cmd_string = """\ -database -open -vcd vcddb -into verilog.vcd -default -timescale ps -probe -create -all -vcd -depth all -run 10000ns -quit -""" - class SystemVerilogTarget(VerilogTarget): def __init__(self, circuit, circuit_name=None, directory="build/", skip_compile=False, magma_output="coreir-verilog", magma_opts={}, include_verilog_libraries=[], simulator=None, - timescale="1ns/1ns", clock_step_delay=5): + timescale="1ns/1ns", clock_step_delay=5, num_cycles=10000, + dump_vcd=True, no_warning=False): """ circuit: a magma circuit @@ -67,11 +67,15 @@ def __init__(self, circuit, circuit_name=None, directory="build/", if simulator is None: raise ValueError("Must specify simulator when using system-verilog" " target") - if simulator not in ["vcs", "ncsim"]: + if simulator not in {"vcs", "ncsim", "iverilog"}: raise ValueError(f"Unsupported simulator {simulator}") self.simulator = simulator self.timescale = timescale self.clock_step_delay = clock_step_delay + self.num_cycles = num_cycles + self.dump_vcd = dump_vcd + self.no_warning = no_warning + self.declarations = [] def make_name(self, port): if isinstance(port, SelectPath): @@ -86,15 +90,33 @@ def make_name(self, port): name = verilog_name(port.name) return name - def make_poke(self, i, action): - name = self.make_name(action.port) - # For now we assume that verilog can handle big ints - value = action.value - if isinstance(action.port, m.SIntType) and value < 0: + def process_value(self, port, value): + if isinstance(port, m.SIntType) and value < 0: # Handle sign extension for verilator since it expects and # unsigned c type - port_len = len(action.port) + port_len = len(port) value = BitVector(value, port_len).as_uint() + elif value is fault.UnknownValue: + value = "'X" + elif isinstance(value, actions.Peek): + if isinstance(value.port, fault.WrappedVerilogInternalPort): + value = f"dut.{value.port.path}" + else: + value = f"{value.port.name}" + elif isinstance(value, PortWrapper): + value = f"dut.{value.select_path.system_verilog_path}" + elif isinstance(value, actions.FileRead): + new_value = f"{value.file.name_without_ext}_in" + if value.file.chunk_size == 1: + # Assume that the user didn't want an array 1 byte, so unpack + new_value += "[0]" + value = new_value + return value + + def make_poke(self, i, action): + name = self.make_name(action.port) + # For now we assume that verilog can handle big ints + value = self.process_value(action.port, action.value) return [f"{name} = {value};", f"#{self.clock_step_delay}"] def make_print(self, i, action): @@ -104,8 +126,9 @@ def make_print(self, i, action): return [f'$write("{action.format_str}"{ports});'] def make_loop(self, i, action): + self.declarations.append(f"integer {action.loop_var};") code = [] - code.append(f"for (int {action.loop_var} = 0;" + code.append(f"for ({action.loop_var} = 0;" f" {action.loop_var} < {action.n_iter};" f" {action.loop_var}++) begin") @@ -118,16 +141,36 @@ def make_loop(self, i, action): return code def make_file_open(self, i, action): - raise NotImplementedError() + if action.file.mode not in {"r", "w"}: + raise NotImplementedError(action.file.mode) + name = action.file.name_without_ext + self.declarations.append( + f"reg [7:0] {name}_in[0:{action.file.chunk_size - 1}];") + self.declarations.append(f"integer {name}_file;") + code = f"""\ +{name}_file = $fopen(\"{action.file.name}\", \"{action.file.mode}\"); +if (!{name}_file) $error("Could not open file {action.file.name}: %0d", {name}_file); +""" # noqa + return code.splitlines() def make_file_close(self, i, action): - raise NotImplementedError() + return [f"$fclose({action.file.name_without_ext}_file);"] def make_file_read(self, i, action): - raise NotImplementedError() + decl = f"integer __i;" + if decl not in self.declarations: + self.declarations.append(decl) + code = f"""\ +for (__i = 0; __i < {action.file.chunk_size}; __i++) begin + {action.file.name_without_ext}_in[__i] = $fgetc({action.file.name_without_ext}_file); +end +""" # noqa + return code.splitlines() def make_file_write(self, i, action): - raise NotImplementedError() + value = self.make_name(action.value) + return [f"$fwrite({action.file.name_without_ext}_file, \"%c\", " + f"{value});"] def make_expect(self, i, action): if value_utils.is_any(action.value): @@ -139,23 +182,14 @@ def make_expect(self, i, action): debug_name = name else: debug_name = action.port.name - value = action.value - if isinstance(value, actions.Peek): - if isinstance(value.port, fault.WrappedVerilogInternalPort): - value = f"dut.{value.port.path}" - else: - value = f"{value.port.name}" - elif isinstance(value, PortWrapper): - value = f"dut.{value.select_path.system_verilog_path}" - elif isinstance(action.port, m.SIntType) and value < 0: - # Handle sign extension for verilator since it expects and - # unsigned c type - port_len = len(action.port) - value = BitVector(value, port_len).as_uint() + value = self.process_value(action.port, action.value) - return [f"if ({name} != {value}) $error(\"Failed on action={i}" - f" checking port {debug_name}. Expected %x, got %x\"" - f", {value}, {name});"] + return f""" +if ({name} != {value}) begin + $error(\"Failed on action={i} checking port {debug_name}. Expected %x, got %x\" , {value}, {name}); + __error_occurred |= 1; +end; +""".splitlines() # noqa def make_eval(self, i, action): # Eval implicit in SV simulations @@ -168,32 +202,27 @@ def make_step(self, i, action): code.append(f"#5 {name} ^= 1;") return code - @staticmethod - def generate_recursive_port_code(name, type_): - declarations = "" + def generate_recursive_port_code(self, name, type_): port_list = [] if isinstance(type_, m.ArrayKind): for j in range(type_.N): - result = SystemVerilogTarget.generate_port_code( + result = self.generate_port_code( name + "_" + str(j), type_.T ) - declarations += result[0] - port_list.extend(result[1]) + port_list.extend(result) elif isinstance(type_, m.TupleKind): for k, t in zip(type_.Ks, type_.Ts): - result = SystemVerilogTarget.generate_port_code( + result = self.generate_port_code( name + "_" + str(k), t ) - declarations += result[0] - port_list.extend(result[1]) - return declarations, port_list + port_list.extend(result) + return port_list - @staticmethod - def generate_port_code(name, type_): + def generate_port_code(self, name, type_): is_array_of_bits = isinstance(type_, m.ArrayKind) and \ not isinstance(type_.T, m.BitKind) if is_array_of_bits or isinstance(type_, m.TupleKind): - return SystemVerilogTarget.generate_recursive_port_code(name, type_) + return self.generate_recursive_port_code(name, type_) else: width_str = "" if isinstance(type_, m.ArrayKind) and \ @@ -205,16 +234,15 @@ def generate_port_code(name, type_): t = "reg" else: raise NotImplementedError() - return f" {t} {width_str}{name};\n", [f".{name}({name})"] + self.declarations.append(f" {t} {width_str}{name};\n") + return [f".{name}({name})"] def generate_code(self, actions): initial_body = "" - declarations = "" port_list = [] for name, type_ in self.circuit.IO.ports.items(): - result = SystemVerilogTarget.generate_port_code(name, type_) - declarations += result[0] - port_list.extend(result[1]) + result = self.generate_port_code(name, type_) + port_list.extend(result) for i, action in enumerate(actions): code = self.generate_action_code(i, action) @@ -222,7 +250,7 @@ def generate_code(self, actions): initial_body += f" {line}\n" src = src_tpl.format( - declarations=declarations, + declarations="\n".join(self.declarations), initial_body=initial_body, port_list=",\n ".join(port_list), circuit_name=self.circuit_name, @@ -241,18 +269,39 @@ def run(self, actions): self.include_verilog_libraries) cmd_file = Path(f"{self.circuit_name}_cmd.tcl") if self.simulator == "ncsim": + if self.dump_vcd: + vcd_command = """ +database -open -vcd vcddb -into verilog.vcd -default -timescale ps +probe -create -all -vcd -depth all""" + else: + vcd_command = "" + ncsim_cmd_string = f"""\ +{vcd_command} +run {self.num_cycles}ns +quit""" + if self.no_warning: + warning = "-neverwarn" + else: + warning = "" with open(self.directory / cmd_file, "w") as f: f.write(ncsim_cmd_string) cmd = f"""\ -irun -top {self.circuit_name}_tb -timescale {self.timescale} -access +rwc -notimingchecks -input {cmd_file} {test_bench_file} {self.verilog_file} {verilog_libraries} +irun -top {self.circuit_name}_tb -timescale {self.timescale} -access +rwc -notimingchecks {warning} -input {cmd_file} {test_bench_file} {self.verilog_file} {verilog_libraries} """ # nopep8 - else: + elif self.simulator == "vcs": cmd = f"""\ vcs -sverilog -full64 +v2k -timescale={self.timescale} -LDFLAGS -Wl,--no-as-needed {test_bench_file} {self.verilog_file} {verilog_libraries} """ # nopep8 + elif self.simulator == "iverilog": + cmd = f"iverilog -o {self.circuit_name}_tb {test_bench_file} {self.verilog_file}" # noqa + else: + raise NotImplementedError(self.simulator) print(f"Running command: {cmd}") assert not subprocess.call(cmd, cwd=self.directory, shell=True) if self.simulator == "vcs": print(f"Running command: {cmd}") assert not subprocess.call("./simv", cwd=self.directory, shell=True) + elif self.simulator == "iverilog": + assert not subprocess.call(f"vvp -N {self.circuit_name}_tb", + cwd=self.directory, shell=True) diff --git a/tests/common.py b/tests/common.py index c29149c4..4a6c7605 100644 --- a/tests/common.py +++ b/tests/common.py @@ -70,3 +70,11 @@ def definition(io): # [io.a + io.b, io.a - io.b, io.a * io.b, io.a / io.b], opcode) # use arbitrary fourth op [io.a + io.b, io.a - io.b, io.a * io.b, io.b - io.a], opcode) + + +class AndCircuit(m.Circuit): + IO = ["I0", m.In(m.Bit), "I1", m.In(m.Bit), "O", m.Out(m.Bit)] + + @classmethod + def definition(io): + io.O <= io.I0 & io.I1 diff --git a/tests/test_setattr_interface.py b/tests/test_setattr_interface.py index 1daf2347..f64a24b5 100644 --- a/tests/test_setattr_interface.py +++ b/tests/test_setattr_interface.py @@ -1,8 +1,10 @@ import common import tempfile +import fault from fault import Tester import shutil import random +import pytest def pytest_generate_tests(metafunc): @@ -12,6 +14,8 @@ def pytest_generate_tests(metafunc): targets.append(("system-verilog", "ncsim")) if shutil.which("vcs"): targets.append(("system-verilog", "vcs")) + if shutil.which("iverilog"): + targets.append(("system-verilog", "iverilog")) metafunc.parametrize("target,simulator", targets) @@ -113,3 +117,23 @@ def test_setattr_tuple(target, simulator): tester.circuit.O.a.expect(5) tester.circuit.O.b.expect(11) run_test(target, simulator, tester) + + +def test_setattr_x(target, simulator): + if target == "verilator": + pytest.skip("X not support with Verilator") + circ = common.AndCircuit + tester = Tester(circ) + tester.circuit.I0 = 0 + tester.circuit.I1 = 1 + tester.eval() + tester.circuit.O.expect(0) + tester.circuit.I0 = fault.UnknownValue + tester.circuit.I1 = 1 + tester.eval() + tester.circuit.O.expect(0) + tester.circuit.I0 = fault.UnknownValue + tester.circuit.I1 = fault.UnknownValue + tester.eval() + tester.circuit.O.expect(fault.UnknownValue) + run_test(target, simulator, tester) diff --git a/tests/test_tester.py b/tests/test_tester.py index 8d3ed320..fb090483 100644 --- a/tests/test_tester.py +++ b/tests/test_tester.py @@ -18,6 +18,9 @@ def pytest_generate_tests(metafunc): if shutil.which("vcs"): targets.append( ("system-verilog", "vcs")) + if shutil.which("iverilog"): + targets.append( + ("system-verilog", "iverilog")) metafunc.parametrize("target,simulator", targets) @@ -295,14 +298,12 @@ def test_tester_loop(target, simulator): def test_tester_file_io(target, simulator): - if target == "system-verilog": - import pytest - pytest.skip("File IO not yet implemented for system-verilog target") circ = common.TestByteCircuit tester = fault.Tester(circ) tester.zero_inputs() file_in = tester.file_open("test_file_in.raw", "r") - file_out = tester.file_open("test_file_out.raw", "w") + out_file = "test_file_out.raw" + file_out = tester.file_open(out_file, "w") loop = tester.loop(8) value = loop.file_read(file_in) loop.poke(circ.I, value) @@ -312,6 +313,8 @@ def test_tester_file_io(target, simulator): tester.file_close(file_in) tester.file_close(file_out) with tempfile.TemporaryDirectory() as _dir: + if os.path.exists(_dir + "/" + out_file): + os.remove(_dir + "/" + out_file) with open(_dir + "/test_file_in.raw", "wb") as file: file.write(bytes([i for i in range(8)])) if target == "verilator": @@ -319,4 +322,9 @@ def test_tester_file_io(target, simulator): else: tester.compile_and_run(target, directory=_dir, simulator=simulator) with open(_dir + "/test_file_out.raw", "rb") as file: - assert file.read(8) == bytes([i for i in range(8)]) + expected = bytes([i for i in range(8)]) + if simulator == "iverilog": + # iverilog doesn't support writing a NULL byte out using %c, so + # this first value is skipped + expected = expected[1:] + assert file.read(8) == expected