fault.verilator_target
module
Source code
import fault
from .array import Array
from pathlib import Path
import subprocess
import magma as m
import fault.actions as actions
from fault.actions import Poke, Eval
from fault.verilog_target import VerilogTarget, verilog_name
import fault.value_utils as value_utils
import fault.verilator_utils as verilator_utils
from fault.select_path import SelectPath
from fault.wrapper import PortWrapper, InstanceWrapper
import math
from hwtypes import BitVector, SIntVector
import subprocess
from fault.random import random_bv
import fault.utils as utils
import platform
import os
src_tpl = """\
{includes}
// Based on https://www.veripool.org/projects/verilator/wiki/Manual-verilator#CONNECTING-TO-C
vluint64_t main_time = 0; // Current simulation time
// This is a 64-bit integer to reduce wrap over issues and
// allow modulus. You can also use a double, if you wish.
double sc_time_stamp () {{ // Called by $time in Verilog
return main_time; // converts to double, to match
// what SystemC does
}}
#if VM_TRACE
VerilatedVcdC* tracer;
#endif
void my_assert(
unsigned int got,
unsigned int expected,
int i,
const char* port) {{
if (got != expected) {{
std::cerr << std::endl; // end the current line
std::cerr << \"Got : 0x\" << std::hex << got << std::endl;
std::cerr << \"Expected : 0x\" << std::hex << expected << std::endl;
std::cerr << \"i : \" << std::dec << i << std::endl;
std::cerr << \"Port : \" << port << std::endl;
#if VM_TRACE
tracer->close();
#endif
exit(1);
}}
}}
int main(int argc, char **argv) {{
Verilated::commandArgs(argc, argv);
V{circuit_name}* top = new V{circuit_name};
#if VM_TRACE
Verilated::traceEverOn(true);
tracer = new VerilatedVcdC;
top->trace(tracer, 99);
mkdir("logs", S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
tracer->open("logs/{circuit_name}.vcd");
#endif
{main_body}
#if VM_TRACE
tracer->close();
#endif
}}
""" # nopep8
class VerilatorTarget(VerilogTarget):
def __init__(self, circuit, directory="build/",
flags=[], skip_compile=False, include_verilog_libraries=[],
include_directories=[], magma_output="coreir-verilog",
circuit_name=None, magma_opts={}):
"""
Params:
`include_verilog_libraries`: a list of verilog libraries to include
with the -v flag. From the verilator docs:
-v <filename> Verilog library
`include_directories`: a list of directories to include using the
-I flag. From the the verilator docs:
-I<dir> Directory to search for includes
"""
super().__init__(circuit, circuit_name, directory, skip_compile,
include_verilog_libraries, magma_output, magma_opts)
self.flags = flags
self.include_directories = include_directories
# Compile the design using `verilator`
driver_file = self.directory / Path(f"{self.circuit_name}_driver.cpp")
verilator_cmd = verilator_utils.verilator_cmd(
self.circuit_name, self.verilog_file.name,
self.include_verilog_libraries, self.include_directories,
driver_file.name, self.flags)
if self.run_from_directory(verilator_cmd):
raise Exception(f"Running verilator cmd {verilator_cmd} failed")
self.debug_includes = set()
verilator_version = subprocess.check_output("verilator --version",
shell=True)
# Need to check version since they changed how internal signal access
# works
self.verilator_version = float(verilator_version.split()[1])
def make_poke(self, i, action):
if self.verilator_version > 3.874:
prefix = f"{self.circuit_name}"
else:
prefix = f"v"
if isinstance(action.port, fault.WrappedVerilogInternalPort):
path = action.port.path.replace(".", "->")
name = f"{prefix}->{path}"
elif isinstance(action.port, SelectPath):
name = ""
if len(action.port) > 2:
# TODO: Find the version that they changed this, 3.874 is known
# to use top->v instead of top->{circuit_name}
name += f"{prefix}->"
name += action.port.verilator_path
if len(action.port) > 2:
self.debug_includes.add(f"{action.port[0].circuit.name}")
for item in action.port[1:-1]:
circuit = type(item.instance)
circuit_name = circuit.verilog_name
# Verilator specializes each parametrization into a separate
# mdoule, this is an attempt to reverse engineer the naming
# scheme
if circuit_name == "coreir_reg":
circuit_name += "_"
circuit_name += f"_I{circuit.coreir_configargs['init']}"
circuit_name += f"_W{circuit.coreir_genargs['width']}"
elif circuit_name == "coreir_reg_arst":
circuit_name += "_"
circuit_name += f"_I{circuit.coreir_configargs['init']}"
if circuit.coreir_genargs['width'] != 1:
circuit_name += f"_W{circuit.coreir_genargs['width']}"
self.debug_includes.add(f"{circuit_name}")
else:
name = verilog_name(action.port.name)
# Special case poking internal registers
is_reg_poke = isinstance(action.port, SelectPath) and \
isinstance(action.port[-1], fault.WrappedVerilogInternalPort) \
and action.port[-1].path == "outReg"
max_bits = 64 if platform.architecture()[0] == "64bit" else 32
if isinstance(action.value, BitVector) and \
action.value.num_bits > max_bits:
asserts = []
for i in range(math.ceil(action.value.num_bits / max_bits)):
value = action.value[i * max_bits:min(
(i + 1) * max_bits, action.value.num_bits)]
asserts += [f"top->{name}[{i}] = {value};"]
if is_reg_poke:
raise NotImplementedError()
return asserts
else:
value = action.value
if 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()
result = [f"top->{name} = {value};"]
# Hack to support verilator's semantics, need to set the register
# mux values for expected behavior
if is_reg_poke:
action.port[-1].path = "out"
result += self.make_poke(i, action)
action.port[-1].path = "in"
result += self.make_poke(i, action)
if "enable_mux" in action.port[-3].instance_map:
mux_inst = action.port[-3].instance_map["enable_mux"]
action.port[-2] = InstanceWrapper(mux_inst, action.port[-3])
action.port[-1] = type(mux_inst).I0
result += self.make_poke(i, action)
return result
def make_print(self, i, action):
name = verilog_name(action.port.name)
return [f'printf("{action.port.debug_name} = '
f'{action.format_str}\\n", top->{name});']
def make_expect(self, i, action):
# For verilator, if an expect is "AnyValue" we don't need to
# perform the expect.
if value_utils.is_any(action.value):
return []
if self.verilator_version > 3.874:
prefix = f"{self.circuit_name}"
else:
prefix = f"v"
if isinstance(action.port, fault.WrappedVerilogInternalPort):
path = action.port.path.replace(".", "->")
name = f"{prefix}->{path}"
debug_name = name
elif isinstance(action.port, SelectPath):
name = action.port.verilator_path
if len(action.port) > 2:
name = f"{prefix}->" + name
if self.verilator_version >= 3.856:
if len(action.port) > 2:
self.debug_includes.add(f"{action.port[0].circuit.name}")
for item in action.port[1:-1]:
circuit_name = type(item.instance).name
self.debug_includes.add(f"{circuit_name}")
debug_name = action.port[-1].debug_name
else:
name = verilog_name(action.port.name)
debug_name = action.port.debug_name
value = action.value
if isinstance(value, actions.Peek):
if isinstance(value.port, fault.WrappedVerilogInternalPort):
path = action.port.path.replace(".", "->")
value = f"top->{prefix}->{path}"
else:
value = f"top->{verilog_name(value.port.name)}"
elif isinstance(value, PortWrapper):
if self.verilator_version >= 3.856:
if len(action.port) > 2:
self.debug_includes.add(f"{action.port[0].circuit.name}")
for item in value.select_path[1:-1]:
circuit_name = type(item.instance).name
self.debug_includes.add(f"{circuit_name}")
value = f"top->{prefix}->" + value.select_path.verilator_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()
return [f"my_assert(top->{name}, {value}, "
f"{i}, \"{debug_name}\");"]
def make_eval(self, i, action):
return ["top->eval();", "main_time++;", "#if VM_TRACE",
"tracer->dump(main_time);", "#endif"]
def make_step(self, i, action):
name = verilog_name(action.clock.name)
code = []
for step in range(action.steps):
code.append("top->eval();")
code.append("main_time++;")
code.append("#if VM_TRACE")
code.append("tracer->dump(main_time);")
code.append("#endif")
code.append(f"top->{name} ^= 1;")
return code
def generate_code(self, actions, verilator_includes, num_tests, circuit):
if verilator_includes:
# Include the top circuit by default
verilator_includes.insert(
0, f'{self.circuit_name}')
includes = [
f'"V{self.circuit_name}.h"',
] + [f'"V{self.circuit_name}_{include}.h"' for include in
verilator_includes] + [
'"verilated.h"',
'<iostream>',
'<verilated_vcd_c.h>',
'<sys/types.h>',
'<sys/stat.h>',
]
main_body = ""
for i, action in enumerate(actions):
code = self.generate_action_code(i, action)
for line in code:
main_body += f" {line}\n"
for i in range(num_tests):
main_body += self.add_assumptions(circuit, actions, i)
code = self.make_eval(i, Eval())
for line in code:
main_body += f" {line}\n"
main_body += self.add_guarantees(circuit, actions, i)
includes += [f'"V{self.circuit_name}_{include}.h"' for include in
self.debug_includes]
includes_src = "\n".join(["#include " + i for i in includes])
src = src_tpl.format(
includes=includes_src,
main_body=main_body,
circuit_name=self.circuit_name,
)
return src
def run_from_directory(self, cmd):
return subprocess.call(cmd, cwd=self.directory, shell=True)
def run(self, actions, verilator_includes=[], num_tests=0,
_circuit=None):
driver_file = self.directory / Path(f"{self.circuit_name}_driver.cpp")
# Write the verilator driver to file.
src = self.generate_code(actions, verilator_includes, num_tests,
_circuit)
with open(driver_file, "w") as f:
f.write(src)
# Run a series of commands: run the Makefile output by verilator, and
# finally run the executable created by verilator.
verilator_make_cmd = verilator_utils.verilator_make_cmd(
self.circuit_name)
assert not self.run_from_directory(verilator_make_cmd)
assert not self.run_from_directory(f"./obj_dir/V{self.circuit_name}")
def add_assumptions(self, circuit, actions, i):
main_body = ""
for port in circuit.interface.ports.values():
if port.isoutput():
for assumption in self.assumptions:
# TODO: Chained assumptions?
assume_port = assumption.port
if isinstance(assume_port, SelectPath):
assume_port = assume_port[-1]
if assume_port is port:
pred = assumption.value
while True:
randval = random_bv(len(assume_port))
if pred(randval):
break
code = self.make_poke(
len(actions) + i, Poke(port, randval))
for line in code:
main_body += f" {line}\n"
break
return main_body
def add_guarantees(self, circuit, actions, i):
main_body = ""
for name, port in circuit.interface.ports.items():
if port.isinput():
for guarantee in self.guarantees:
guarantee_port = guarantee.port
if isinstance(guarantee_port, SelectPath):
guarantee_port = guarantee_port[-1]
if guarantee_port is port:
# TODO: Support functions too
code = utils.get_short_lambda_body_text(guarantee.value)
# TODO: More robust symbol replacer on AST
for port in circuit.interface.ports:
code = code.replace("and", "&&")
code = code.replace(port, f"top->{port}")
main_body += f"""\
if (!({code})) {{
std::cerr << std::endl; // end the current line
std::cerr << \"Got : 0x\" << std::hex << top->{name} << std::endl;
std::cerr << \"Expected : {code}" << std::endl;
std::cerr << \"i : {i}\" << std::endl;
std::cerr << \"Port : {name}\" << std::endl;
#if VM_TRACE
tracer->close();
#endif
exit(1);
}}
"""
return main_body}
Classes
class VerilatorTarget (ancestors: VerilogTarget, Target, abc.ABC)
-
Inherited from:
VerilogTarget
Provides reuseable target logic for compiling circuits into verilog files.
Source code
class VerilatorTarget(VerilogTarget): def __init__(self, circuit, directory="build/", flags=[], skip_compile=False, include_verilog_libraries=[], include_directories=[], magma_output="coreir-verilog", circuit_name=None, magma_opts={}): """ Params: `include_verilog_libraries`: a list of verilog libraries to include with the -v flag. From the verilator docs: -v <filename> Verilog library `include_directories`: a list of directories to include using the -I flag. From the the verilator docs: -I<dir> Directory to search for includes """ super().__init__(circuit, circuit_name, directory, skip_compile, include_verilog_libraries, magma_output, magma_opts) self.flags = flags self.include_directories = include_directories # Compile the design using `verilator` driver_file = self.directory / Path(f"{self.circuit_name}_driver.cpp") verilator_cmd = verilator_utils.verilator_cmd( self.circuit_name, self.verilog_file.name, self.include_verilog_libraries, self.include_directories, driver_file.name, self.flags) if self.run_from_directory(verilator_cmd): raise Exception(f"Running verilator cmd {verilator_cmd} failed") self.debug_includes = set() verilator_version = subprocess.check_output("verilator --version", shell=True) # Need to check version since they changed how internal signal access # works self.verilator_version = float(verilator_version.split()[1]) def make_poke(self, i, action): if self.verilator_version > 3.874: prefix = f"{self.circuit_name}" else: prefix = f"v" if isinstance(action.port, fault.WrappedVerilogInternalPort): path = action.port.path.replace(".", "->") name = f"{prefix}->{path}" elif isinstance(action.port, SelectPath): name = "" if len(action.port) > 2: # TODO: Find the version that they changed this, 3.874 is known # to use top->v instead of top->{circuit_name} name += f"{prefix}->" name += action.port.verilator_path if len(action.port) > 2: self.debug_includes.add(f"{action.port[0].circuit.name}") for item in action.port[1:-1]: circuit = type(item.instance) circuit_name = circuit.verilog_name # Verilator specializes each parametrization into a separate # mdoule, this is an attempt to reverse engineer the naming # scheme if circuit_name == "coreir_reg": circuit_name += "_" circuit_name += f"_I{circuit.coreir_configargs['init']}" circuit_name += f"_W{circuit.coreir_genargs['width']}" elif circuit_name == "coreir_reg_arst": circuit_name += "_" circuit_name += f"_I{circuit.coreir_configargs['init']}" if circuit.coreir_genargs['width'] != 1: circuit_name += f"_W{circuit.coreir_genargs['width']}" self.debug_includes.add(f"{circuit_name}") else: name = verilog_name(action.port.name) # Special case poking internal registers is_reg_poke = isinstance(action.port, SelectPath) and \ isinstance(action.port[-1], fault.WrappedVerilogInternalPort) \ and action.port[-1].path == "outReg" max_bits = 64 if platform.architecture()[0] == "64bit" else 32 if isinstance(action.value, BitVector) and \ action.value.num_bits > max_bits: asserts = [] for i in range(math.ceil(action.value.num_bits / max_bits)): value = action.value[i * max_bits:min( (i + 1) * max_bits, action.value.num_bits)] asserts += [f"top->{name}[{i}] = {value};"] if is_reg_poke: raise NotImplementedError() return asserts else: value = action.value if 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() result = [f"top->{name} = {value};"] # Hack to support verilator's semantics, need to set the register # mux values for expected behavior if is_reg_poke: action.port[-1].path = "out" result += self.make_poke(i, action) action.port[-1].path = "in" result += self.make_poke(i, action) if "enable_mux" in action.port[-3].instance_map: mux_inst = action.port[-3].instance_map["enable_mux"] action.port[-2] = InstanceWrapper(mux_inst, action.port[-3]) action.port[-1] = type(mux_inst).I0 result += self.make_poke(i, action) return result def make_print(self, i, action): name = verilog_name(action.port.name) return [f'printf("{action.port.debug_name} = ' f'{action.format_str}\\n", top->{name});'] def make_expect(self, i, action): # For verilator, if an expect is "AnyValue" we don't need to # perform the expect. if value_utils.is_any(action.value): return [] if self.verilator_version > 3.874: prefix = f"{self.circuit_name}" else: prefix = f"v" if isinstance(action.port, fault.WrappedVerilogInternalPort): path = action.port.path.replace(".", "->") name = f"{prefix}->{path}" debug_name = name elif isinstance(action.port, SelectPath): name = action.port.verilator_path if len(action.port) > 2: name = f"{prefix}->" + name if self.verilator_version >= 3.856: if len(action.port) > 2: self.debug_includes.add(f"{action.port[0].circuit.name}") for item in action.port[1:-1]: circuit_name = type(item.instance).name self.debug_includes.add(f"{circuit_name}") debug_name = action.port[-1].debug_name else: name = verilog_name(action.port.name) debug_name = action.port.debug_name value = action.value if isinstance(value, actions.Peek): if isinstance(value.port, fault.WrappedVerilogInternalPort): path = action.port.path.replace(".", "->") value = f"top->{prefix}->{path}" else: value = f"top->{verilog_name(value.port.name)}" elif isinstance(value, PortWrapper): if self.verilator_version >= 3.856: if len(action.port) > 2: self.debug_includes.add(f"{action.port[0].circuit.name}") for item in value.select_path[1:-1]: circuit_name = type(item.instance).name self.debug_includes.add(f"{circuit_name}") value = f"top->{prefix}->" + value.select_path.verilator_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() return [f"my_assert(top->{name}, {value}, " f"{i}, \"{debug_name}\");"] def make_eval(self, i, action): return ["top->eval();", "main_time++;", "#if VM_TRACE", "tracer->dump(main_time);", "#endif"] def make_step(self, i, action): name = verilog_name(action.clock.name) code = [] for step in range(action.steps): code.append("top->eval();") code.append("main_time++;") code.append("#if VM_TRACE") code.append("tracer->dump(main_time);") code.append("#endif") code.append(f"top->{name} ^= 1;") return code def generate_code(self, actions, verilator_includes, num_tests, circuit): if verilator_includes: # Include the top circuit by default verilator_includes.insert( 0, f'{self.circuit_name}') includes = [ f'"V{self.circuit_name}.h"', ] + [f'"V{self.circuit_name}_{include}.h"' for include in verilator_includes] + [ '"verilated.h"', '<iostream>', '<verilated_vcd_c.h>', '<sys/types.h>', '<sys/stat.h>', ] main_body = "" for i, action in enumerate(actions): code = self.generate_action_code(i, action) for line in code: main_body += f" {line}\n" for i in range(num_tests): main_body += self.add_assumptions(circuit, actions, i) code = self.make_eval(i, Eval()) for line in code: main_body += f" {line}\n" main_body += self.add_guarantees(circuit, actions, i) includes += [f'"V{self.circuit_name}_{include}.h"' for include in self.debug_includes] includes_src = "\n".join(["#include " + i for i in includes]) src = src_tpl.format( includes=includes_src, main_body=main_body, circuit_name=self.circuit_name, ) return src def run_from_directory(self, cmd): return subprocess.call(cmd, cwd=self.directory, shell=True) def run(self, actions, verilator_includes=[], num_tests=0, _circuit=None): driver_file = self.directory / Path(f"{self.circuit_name}_driver.cpp") # Write the verilator driver to file. src = self.generate_code(actions, verilator_includes, num_tests, _circuit) with open(driver_file, "w") as f: f.write(src) # Run a series of commands: run the Makefile output by verilator, and # finally run the executable created by verilator. verilator_make_cmd = verilator_utils.verilator_make_cmd( self.circuit_name) assert not self.run_from_directory(verilator_make_cmd) assert not self.run_from_directory(f"./obj_dir/V{self.circuit_name}") def add_assumptions(self, circuit, actions, i): main_body = "" for port in circuit.interface.ports.values(): if port.isoutput(): for assumption in self.assumptions: # TODO: Chained assumptions? assume_port = assumption.port if isinstance(assume_port, SelectPath): assume_port = assume_port[-1] if assume_port is port: pred = assumption.value while True: randval = random_bv(len(assume_port)) if pred(randval): break code = self.make_poke( len(actions) + i, Poke(port, randval)) for line in code: main_body += f" {line}\n" break return main_body def add_guarantees(self, circuit, actions, i): main_body = "" for name, port in circuit.interface.ports.items(): if port.isinput(): for guarantee in self.guarantees: guarantee_port = guarantee.port if isinstance(guarantee_port, SelectPath): guarantee_port = guarantee_port[-1] if guarantee_port is port: # TODO: Support functions too code = utils.get_short_lambda_body_text(guarantee.value) # TODO: More robust symbol replacer on AST for port in circuit.interface.ports: code = code.replace("and", "&&") code = code.replace(port, f"top->{port}") main_body += f"""\ if (!({code})) {{ std::cerr << std::endl; // end the current line std::cerr << \"Got : 0x\" << std::hex << top->{name} << std::endl; std::cerr << \"Expected : {code}" << std::endl; std::cerr << \"i : {i}\" << std::endl; std::cerr << \"Port : {name}\" << std::endl; #if VM_TRACE tracer->close(); #endif exit(1); }} """ return main_body}
Methods
def __init__(self, circuit, directory='build/', flags=[], skip_compile=False, include_verilog_libraries=[], include_directories=[], magma_output='coreir-verilog', circuit_name=None, magma_opts={})
-
Params
-
include_verilog_libraries
: a list of verilog libraries to include with the -v flag. From the verilator docs: -vVerilog library include_directories
: a list of directories to include using the -I flag. From the the verilator docs: -IDirectory to search for includes
Source code
def __init__(self, circuit, directory="build/", flags=[], skip_compile=False, include_verilog_libraries=[], include_directories=[], magma_output="coreir-verilog", circuit_name=None, magma_opts={}): """ Params: `include_verilog_libraries`: a list of verilog libraries to include with the -v flag. From the verilator docs: -v <filename> Verilog library `include_directories`: a list of directories to include using the -I flag. From the the verilator docs: -I<dir> Directory to search for includes """ super().__init__(circuit, circuit_name, directory, skip_compile, include_verilog_libraries, magma_output, magma_opts) self.flags = flags self.include_directories = include_directories # Compile the design using `verilator` driver_file = self.directory / Path(f"{self.circuit_name}_driver.cpp") verilator_cmd = verilator_utils.verilator_cmd( self.circuit_name, self.verilog_file.name, self.include_verilog_libraries, self.include_directories, driver_file.name, self.flags) if self.run_from_directory(verilator_cmd): raise Exception(f"Running verilator cmd {verilator_cmd} failed") self.debug_includes = set() verilator_version = subprocess.check_output("verilator --version", shell=True) # Need to check version since they changed how internal signal access # works self.verilator_version = float(verilator_version.split()[1])}
def add_assumptions(self, circuit, actions, i)
-
Source code
def add_assumptions(self, circuit, actions, i): main_body = "" for port in circuit.interface.ports.values(): if port.isoutput(): for assumption in self.assumptions: # TODO: Chained assumptions? assume_port = assumption.port if isinstance(assume_port, SelectPath): assume_port = assume_port[-1] if assume_port is port: pred = assumption.value while True: randval = random_bv(len(assume_port)) if pred(randval): break code = self.make_poke( len(actions) + i, Poke(port, randval)) for line in code: main_body += f" {line}\n" break return main_body}
def add_guarantees(self, circuit, actions, i)
-
Source code
def add_guarantees(self, circuit, actions, i): main_body = "" for name, port in circuit.interface.ports.items(): if port.isinput(): for guarantee in self.guarantees: guarantee_port = guarantee.port if isinstance(guarantee_port, SelectPath): guarantee_port = guarantee_port[-1] if guarantee_port is port: # TODO: Support functions too code = utils.get_short_lambda_body_text(guarantee.value) # TODO: More robust symbol replacer on AST for port in circuit.interface.ports: code = code.replace("and", "&&") code = code.replace(port, f"top->{port}") main_body += f"""\ if (!({code})) {{ std::cerr << std::endl; // end the current line std::cerr << \"Got : 0x\" << std::hex << top->{name} << std::endl; std::cerr << \"Expected : {code}" << std::endl; std::cerr << \"i : {i}\" << std::endl; std::cerr << \"Port : {name}\" << std::endl; #if VM_TRACE tracer->close(); #endif exit(1); }} """ return main_body}
def generate_code(self, actions, verilator_includes, num_tests, circuit)
-
Source code
def generate_code(self, actions, verilator_includes, num_tests, circuit): if verilator_includes: # Include the top circuit by default verilator_includes.insert( 0, f'{self.circuit_name}') includes = [ f'"V{self.circuit_name}.h"', ] + [f'"V{self.circuit_name}_{include}.h"' for include in verilator_includes] + [ '"verilated.h"', '<iostream>', '<verilated_vcd_c.h>', '<sys/types.h>', '<sys/stat.h>', ] main_body = "" for i, action in enumerate(actions): code = self.generate_action_code(i, action) for line in code: main_body += f" {line}\n" for i in range(num_tests): main_body += self.add_assumptions(circuit, actions, i) code = self.make_eval(i, Eval()) for line in code: main_body += f" {line}\n" main_body += self.add_guarantees(circuit, actions, i) includes += [f'"V{self.circuit_name}_{include}.h"' for include in self.debug_includes] includes_src = "\n".join(["#include " + i for i in includes]) src = src_tpl.format( includes=includes_src, main_body=main_body, circuit_name=self.circuit_name, ) return src}
def make_eval(self, i, action)
-
Source code
def make_eval(self, i, action): return ["top->eval();", "main_time++;", "#if VM_TRACE", "tracer->dump(main_time);", "#endif"]}
def make_expect(self, i, action)
-
Source code
def make_expect(self, i, action): # For verilator, if an expect is "AnyValue" we don't need to # perform the expect. if value_utils.is_any(action.value): return [] if self.verilator_version > 3.874: prefix = f"{self.circuit_name}" else: prefix = f"v" if isinstance(action.port, fault.WrappedVerilogInternalPort): path = action.port.path.replace(".", "->") name = f"{prefix}->{path}" debug_name = name elif isinstance(action.port, SelectPath): name = action.port.verilator_path if len(action.port) > 2: name = f"{prefix}->" + name if self.verilator_version >= 3.856: if len(action.port) > 2: self.debug_includes.add(f"{action.port[0].circuit.name}") for item in action.port[1:-1]: circuit_name = type(item.instance).name self.debug_includes.add(f"{circuit_name}") debug_name = action.port[-1].debug_name else: name = verilog_name(action.port.name) debug_name = action.port.debug_name value = action.value if isinstance(value, actions.Peek): if isinstance(value.port, fault.WrappedVerilogInternalPort): path = action.port.path.replace(".", "->") value = f"top->{prefix}->{path}" else: value = f"top->{verilog_name(value.port.name)}" elif isinstance(value, PortWrapper): if self.verilator_version >= 3.856: if len(action.port) > 2: self.debug_includes.add(f"{action.port[0].circuit.name}") for item in value.select_path[1:-1]: circuit_name = type(item.instance).name self.debug_includes.add(f"{circuit_name}") value = f"top->{prefix}->" + value.select_path.verilator_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() return [f"my_assert(top->{name}, {value}, " f"{i}, \"{debug_name}\");"]}
def make_poke(self, i, action)
-
Source code
def make_poke(self, i, action): if self.verilator_version > 3.874: prefix = f"{self.circuit_name}" else: prefix = f"v" if isinstance(action.port, fault.WrappedVerilogInternalPort): path = action.port.path.replace(".", "->") name = f"{prefix}->{path}" elif isinstance(action.port, SelectPath): name = "" if len(action.port) > 2: # TODO: Find the version that they changed this, 3.874 is known # to use top->v instead of top->{circuit_name} name += f"{prefix}->" name += action.port.verilator_path if len(action.port) > 2: self.debug_includes.add(f"{action.port[0].circuit.name}") for item in action.port[1:-1]: circuit = type(item.instance) circuit_name = circuit.verilog_name # Verilator specializes each parametrization into a separate # mdoule, this is an attempt to reverse engineer the naming # scheme if circuit_name == "coreir_reg": circuit_name += "_" circuit_name += f"_I{circuit.coreir_configargs['init']}" circuit_name += f"_W{circuit.coreir_genargs['width']}" elif circuit_name == "coreir_reg_arst": circuit_name += "_" circuit_name += f"_I{circuit.coreir_configargs['init']}" if circuit.coreir_genargs['width'] != 1: circuit_name += f"_W{circuit.coreir_genargs['width']}" self.debug_includes.add(f"{circuit_name}") else: name = verilog_name(action.port.name) # Special case poking internal registers is_reg_poke = isinstance(action.port, SelectPath) and \ isinstance(action.port[-1], fault.WrappedVerilogInternalPort) \ and action.port[-1].path == "outReg" max_bits = 64 if platform.architecture()[0] == "64bit" else 32 if isinstance(action.value, BitVector) and \ action.value.num_bits > max_bits: asserts = [] for i in range(math.ceil(action.value.num_bits / max_bits)): value = action.value[i * max_bits:min( (i + 1) * max_bits, action.value.num_bits)] asserts += [f"top->{name}[{i}] = {value};"] if is_reg_poke: raise NotImplementedError() return asserts else: value = action.value if 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() result = [f"top->{name} = {value};"] # Hack to support verilator's semantics, need to set the register # mux values for expected behavior if is_reg_poke: action.port[-1].path = "out" result += self.make_poke(i, action) action.port[-1].path = "in" result += self.make_poke(i, action) if "enable_mux" in action.port[-3].instance_map: mux_inst = action.port[-3].instance_map["enable_mux"] action.port[-2] = InstanceWrapper(mux_inst, action.port[-3]) action.port[-1] = type(mux_inst).I0 result += self.make_poke(i, action) return result}
def make_print(self, i, action)
-
Source code
def make_print(self, i, action): name = verilog_name(action.port.name) return [f'printf("{action.port.debug_name} = ' f'{action.format_str}\\n", top->{name});']}
def make_step(self, i, action)
-
Source code
def make_step(self, i, action): name = verilog_name(action.clock.name) code = [] for step in range(action.steps): code.append("top->eval();") code.append("main_time++;") code.append("#if VM_TRACE") code.append("tracer->dump(main_time);") code.append("#endif") code.append(f"top->{name} ^= 1;") return code}
def run(self, actions, verilator_includes=[], num_tests=0)
-
Source code
def run(self, actions, verilator_includes=[], num_tests=0, _circuit=None): driver_file = self.directory / Path(f"{self.circuit_name}_driver.cpp") # Write the verilator driver to file. src = self.generate_code(actions, verilator_includes, num_tests, _circuit) with open(driver_file, "w") as f: f.write(src) # Run a series of commands: run the Makefile output by verilator, and # finally run the executable created by verilator. verilator_make_cmd = verilator_utils.verilator_make_cmd( self.circuit_name) assert not self.run_from_directory(verilator_make_cmd) assert not self.run_from_directory(f"./obj_dir/V{self.circuit_name}")}
def run_from_directory(self, cmd)
-
Source code
def run_from_directory(self, cmd): return subprocess.call(cmd, cwd=self.directory, shell=True)}