[lit] RTL simulation script with Verilator and Questa support (#334)

* [Tests] [Questa] If found, provide feature and path

* Abstraction script and moving existing test

* Default sim

* Passing default simulator through env var

* Adding default driver.sv

* Various bug fixes

* Argparse wasn't working properly

* Don't touch files in test/

* Creating a default for commercial simulation

* Making circt-rtl-sim a tool not a util script

* Removing old comment

* Adding 'rtl-sim' feature and renaming 'comsim' to 'ieee-sim'
This commit is contained in:
John Demme 2020-12-22 14:30:25 -08:00 committed by GitHub
parent 4377c178ca
commit c9ccc4de01
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 313 additions and 17 deletions

View File

@ -128,6 +128,27 @@ else()
endif()
endif()
# If Questa hasn't been explicitly disabled, find it.
option(QUESTA_DISABLE "Disable the Questa simulation tests.")
if (QUESTA_DISABLE)
message(STATUS "Disabling Questa tests.")
else()
if (EXISTS ${QUESTA_PATH})
message(STATUS "Setting Questa path to ${QUESTA_PATH}.")
else()
# Search for Questa's `vsim` command.
find_program(QUESTA_PATH "vsim")
if(EXISTS ${QUESTA_PATH})
# Then strip the filename.
get_filename_component(QUESTA_PATH ${QUESTA_PATH} DIRECTORY)
message(STATUS "Found Questa at ${QUESTA_PATH}.")
else()
set(QUESTA_PATH "")
message(STATUS "Did not find Questa.")
endif()
endif()
endif()
# If Yosys hasn't been explicitly disabled, find it.
option(YOSYS_DISABLE "Disable the yosys tests.")
if (YOSYS_DISABLE)

View File

@ -1,7 +1,9 @@
set(CIRCT_INTEGRATION_TEST_DEPENDS
FileCheck count not
circt-opt
circt-translate)
circt-translate
circt-rtl-sim
firtool)
# If ESI Cosim is available to build then enable its tests.
if (TARGET EsiCosimDpiServer)

View File

@ -6,7 +6,7 @@
import Cosim_DpiPkg::*;
module main(
module top(
`ifdef VERILATOR
input logic clk,
input logic rstn

View File

@ -45,7 +45,7 @@ class CosimTestRunner:
self.runs = list()
self.sources = list()
self.cppSources = list()
self.top = "main"
self.top = "top"
def parse(self):
"""Parse a test file. Look for comments we recognize anywhere in the
@ -77,20 +77,19 @@ class CosimTestRunner:
# Assemble a list of sources (RTL and C++), applying the defaults and
# path rules.
cfg = self.test.config
if len(self.sources) == 0:
sources = [self.file]
else:
sources = [(src if os.path.isabs(src) else os.path.join(
self.srcdir, src)) for src in self.sources]
if len(self.cppSources) == 0:
cppSources = [os.path.join(
os.path.dirname(__file__), "..", "driver.cpp")]
cppSources = [os.path.join(cfg.circt_tools_dir, "driver.cpp")]
else:
cppSources = [(src if os.path.isabs(src) else os.path.join(
self.srcdir, src)) for src in self.cppSources]
# Include the cosim DPI SystemVerilog files.
cfg = self.test.config
cosimInclude = os.path.join(
cfg.circt_include_dir, "circt", "Dialect", "ESI", "cosim")
sources.insert(0, os.path.join(cosimInclude, "Cosim_DpiPkg.sv"))
@ -167,7 +166,7 @@ class CosimTestRunner:
simEnv = os.environ.copy()
simEnv["COSIM_PORT"] = str(port)
simProc = subprocess.Popen(
[f"{self.execdir}/obj_dir/V{self.top}", "--cycles", "-1"],
[f"{self.execdir}/obj_dir/V{self.top}"],
stdout=simStdout, stderr=simStderr, cwd=self.execdir,
env=simEnv)
# Wait a set amount of time for the simulation to start accepting

View File

@ -1,12 +1,11 @@
// REQUIRES: verilator
// RUN: circt-translate %s -emit-verilog -verify-diagnostics > %t1.sv
// RUN: verilator --cc --top-module main -sv %t1.sv --build --exe %S/../driver.cpp
// RUN: ./obj_dir/Vmain --cycles 8 2>&1 | FileCheck %s
// RUN: circt-rtl-sim.py %t1.sv --cycles 8 2>&1 | FileCheck %s
module {
// The RTL dialect doesn't have any sequential constructs yet. So don't do
// much.
rtl.module @main(%clk: i1, %rstn: i1) {
rtl.module @top(%clk: i1, %rstn: i1) {
%c1 = rtl.instance "aaa" @AAA () : () -> (i1)
%c1Shl = rtl.instance "shl" @shl (%c1) : (i1) -> (i1)
sv.alwaysat_posedge %clk {
@ -34,4 +33,8 @@ module {
// CHECK-NEXT: tick
// CHECK-NEXT: tick
// CHECK-NEXT: tick
// CHECK-NEXT: [driver] Ending simulation at tick #17
// CHECK-NEXT: tick
// CHECK-NEXT: tick
// CHECK-NEXT: tick
// CHECK-NEXT: tick
// CHECK-NEXT: [driver] Ending simulation at tick #25

View File

@ -1,5 +1,6 @@
// REQUIRES: verilator
// RUN: circt-translate %s -emit-verilog -verify-diagnostics > %t1.sv && verilator --lint-only --top-module AB %t1.sv
// RUN: circt-translate %s -emit-verilog -verify-diagnostics > %t1.sv
// RUN: verilator --lint-only --top-module AB %t1.sv
module {
rtl.module @B(%a: i1 { rtl.inout }) -> (i1 {rtl.name = "b"}, i1 {rtl.name = "c"}) {

View File

@ -0,0 +1,15 @@
// REQUIRES: ieee-sim
// RUN: circt-rtl-sim.py --sim %ieee-sim --cycles 2 %s
module top(
input clk,
input rstn
);
always@(posedge clk)
if (rstn)
$display("tock");
// CHECK: tock
// CHECK-NEXT: tock
endmodule

View File

@ -0,0 +1,15 @@
// REQUIRES: verilator
// RUN: circt-rtl-sim.py --cycles 2 %s | FileCheck %s
module top(
input clk,
input rstn
);
always@(posedge clk)
if (rstn)
$display("tock");
// CHECK: tock
// CHECK-NEXT: tock
endmodule

View File

@ -64,7 +64,8 @@ tool_dirs = [config.circt_tools_dir,
tools = [
'circt-opt',
'circt-translate',
'firtool'
'firtool',
'circt-rtl-sim.py'
]
# Enable yosys if it has been detected.
@ -78,6 +79,24 @@ if config.verilator_path != "":
tool_dirs.append(os.path.dirname(config.verilator_path))
tools.append('verilator')
config.available_features.add('verilator')
config.available_features.add('rtl-sim')
llvm_config.with_environment(
'VERILATOR_PATH', config.verilator_path)
# Enable Questa if it has been detected.
if config.questa_path != "":
config.available_features.add('questa')
config.available_features.add('ieee-sim')
config.available_features.add('rtl-sim')
llvm_config.with_environment(
'LM_LICENSE_FILE', os.environ['LM_LICENSE_FILE'])
# When we add support for other simulators, we'll have to figure out which
# one should be the default and modify this appropriately.
config.substitutions.append(
('%questa', os.path.join(config.questa_path, "vsim")))
config.substitutions.append(
('%ieee-sim', os.path.join(config.questa_path, "vsim")))
# Enable ESI cosim tests if they have been built.
if config.esi_cosim_path != "":

View File

@ -40,6 +40,7 @@ config.verilator_path = "@VERILATOR_PATH@"
config.esi_cosim_path = "@ESI_COSIM_PATH@"
config.timeout = "@CIRCT_INTEGRATION_TIMEOUT@"
config.yosys_path = "@YOSYS_PATH@"
config.questa_path = "@QUESTA_PATH@"
# Support substitution of the tools_dir with user parameters. This is
# used when we can't determine the tool dir at configuration time.

View File

@ -1,5 +1,6 @@
add_subdirectory(circt-opt)
add_subdirectory(circt-rtl-sim)
add_subdirectory(circt-translate)
add_subdirectory(handshake-runner)
add_subdirectory(firtool)

View File

@ -0,0 +1,16 @@
# ===- CMakeLists.txt - Simulation driver cmake ---------------*- cmake -*-===//
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#
# ===-----------------------------------------------------------------------===//
#
#
# ===-----------------------------------------------------------------------===//
set(SOURCES circt-rtl-sim.py driver.cpp driver.sv)
foreach(file IN ITEMS ${SOURCES})
configure_file(${file} ${CIRCT_TOOLS_DIR}/${file})
endforeach()
add_custom_target(circt-rtl-sim SOURCES ${SOURCES})

View File

@ -0,0 +1,143 @@
#!/usr/bin/env python3
# ===- circt-rtl-sim.py - CIRCT simulation driver -------------*- python -*-===//
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#
# ===-----------------------------------------------------------------------===//
#
# Script to drive CIRCT simulation tests.
#
# ===-----------------------------------------------------------------------===//
import argparse
import os
import subprocess
import sys
ThisFileDir = os.path.dirname(__file__)
class Questa:
"""Run and compile funcs for Questasim."""
DefaultDriver = "driver.sv"
def __init__(self, path, args):
if os.path.exists(path) and os.path.isfile(path):
self.path = os.path.dirname(path)
else:
self.path = path
self.args = args
def compile(self, sources):
vlog = os.path.join(self.path, "vlog")
return subprocess.run([vlog, "-sv"] + sources)
def run(self, cycles, simargs):
if self.args.no_default_driver:
top = self.args.top
else:
top = "driver"
vsim = os.path.join(self.path, "vsim")
# Note: vsim exit codes say nothing about the test run's pass/fail even if
# $fatal is encountered in the simulation.
cmd = [vsim, top, "-batch", "-do", "run -all"]
if cycles >= 0:
cmd.append(f"+cycles={cycles}")
return subprocess.run(cmd + simargs.split())
class Verilator:
"""Run and compile funcs for Verilator."""
DefaultDriver = "driver.cpp"
def __init__(self, args):
if "VERILATOR_PATH" in os.environ:
self.verilator = os.environ["VERILATOR_PATH"]
else:
self.verilator = args.sim
self.top = args.top
def compile(self, sources):
return subprocess.run([self.verilator, "--cc", "--top-module", self.top,
"-sv", "--build", "--exe"] + sources)
def run(self, cycles, args):
exe = os.path.join("obj_dir", "V" + self.top)
cmd = [exe]
if cycles >= 0:
cmd.append(f"--cycles")
cmd.append(str(cycles))
cmd += args.split()
print(f"Running: {cmd}")
sys.stdout.flush()
return subprocess.run(cmd)
def __main__(args):
defaultSim = ""
if "DEFAULT_SIM" in os.environ:
defaultSim = os.environ["DEFAULT_SIM"]
argparser = argparse.ArgumentParser(
description="RTL simulation runner for CIRCT")
argparser.add_argument("--sim", type=str, default="verilator",
help="Name of the RTL simulator (if in PATH) to use" +
" or path to an executable.")
argparser.add_argument("--no-compile", dest="no_compile", action='store_true',
help="Don't compile the simulation.")
argparser.add_argument("--no-run", dest="no_run", action='store_true',
help="Don't run the simulation.")
argparser.add_argument("--top", type=str, default="top",
help="Name of top module to run.")
argparser.add_argument("--simargs", type=str, default="",
help="Simulation arguments string.")
argparser.add_argument("--no-default-driver", dest="no_default_driver", action='store_true',
help="Do not use the standard top module/drivers.")
argparser.add_argument("--cycles", type=int, default=-1,
help="Number of cycles to run the simulator. " +
" -1 means don't stop.")
argparser.add_argument("sources", nargs="+",
help="The list of source files to be included.")
if len(args) <= 1:
argparser.print_help()
return
args = argparser.parse_args(args[1:])
# Break up simulator string
simParts = os.path.split(args.sim)
simName = simParts[1]
if simName in ["questa", "vsim", "vlog", "vopt"]:
sim = Questa(simParts[0], args)
elif simName == "verilator":
sim = Verilator(args)
else:
print(f"Could not determine simulator from '{args.sim}'",
file=sys.stderr)
return 1
if not args.no_default_driver:
args.sources.append(os.path.join(ThisFileDir, sim.DefaultDriver))
if not args.no_compile:
rc = sim.compile(args.sources)
if rc.returncode != 0:
return rc
if not args.no_run:
rc = sim.run(args.cycles, args.simargs)
return rc.returncode
return 0
if __name__ == '__main__':
sys.exit(__main__(sys.argv))

View File

@ -5,7 +5,7 @@
//
//===----------------------------------------------------------------------===//
#include "Vmain.h"
#include "Vtop.h"
#ifdef DEBUG
#include "verilated_vcd_c.h"
@ -30,11 +30,13 @@ int main(int argc, char **argv) {
Verilated::commandArgs(argc, argv);
size_t numCyclesToRun = 0;
bool runForever = true;
// Search the command line args for those we are sensitive to.
for (int i = 1; i < argc; ++i) {
if (std::string(argv[i]) == "--cycles") {
if (i + 1 < argc) {
numCyclesToRun = std::strtoull(argv[++i], nullptr, 10);
runForever = false;
} else {
std::cerr << "--cycles must be followed by number of cycles."
<< std::endl;
@ -44,7 +46,7 @@ int main(int argc, char **argv) {
}
// Construct the simulated module's C++ model.
auto &dut = *new Vmain();
auto &dut = *new Vtop();
#ifdef DEBUG
VerilatedVcdC *tfp = new VerilatedVcdC;
Verilated::traceEverOn(true);
@ -59,7 +61,7 @@ int main(int argc, char **argv) {
dut.clk = 0;
// Run for a few cycles with reset held.
for (timeStamp = 0; timeStamp < 10 && !Verilated::gotFinish(); timeStamp++) {
for (timeStamp = 0; timeStamp < 8 && !Verilated::gotFinish(); timeStamp++) {
dut.eval();
dut.clk = !dut.clk;
#ifdef DEBUG
@ -71,7 +73,8 @@ int main(int argc, char **argv) {
dut.rstn = 1;
// Run for the specified number of cycles out of reset.
for (; timeStamp <= (numCyclesToRun * 2) && !Verilated::gotFinish() &&
vluint64_t endTime = timeStamp + (numCyclesToRun * 2);
for (; (runForever || timeStamp <= endTime) && !Verilated::gotFinish() &&
!stopSimulation;
timeStamp++) {
dut.eval();

View File

@ -0,0 +1,57 @@
//===- driver.sv - Standard SystemVerilog testbench driver ----------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// Contains the top module driver for our standard simulation tests. It makes
// the following assumptions/demands:
//
// - The testbench module is called 'top'.
// - It exposes a pin named 'clk' (for the clock).
// - It exposes a pin named 'rstn' (for the reset signal).
//
//===----------------------------------------------------------------------===//
module driver();
logic clk = 0;
logic rstn = 0;
top top (
.clk(clk),
.rstn(rstn)
);
always begin
// A clock period is #4.
clk = ~clk;
#2;
end
initial begin
int cycles;
$display("[driver] Starting simulation");
rstn = 0;
// Hold in reset for 4 cycles.
@(posedge clk);
@(posedge clk);
@(posedge clk);
@(posedge clk);
rstn = 1;
if ($value$plusargs ("cycles=%d", cycles)) begin
int i;
for (i = 0; i < cycles; i++) begin
@(posedge clk);
end
$display("[driver] Ending simulation at tick #%0d", $time);
$finish();
end
end
endmodule