aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorjackkoenig2016-03-03 12:23:57 -0800
committerjackkoenig2016-03-03 12:23:57 -0800
commit4d77e3ac1020b404e5a0f5d68cd36fb3a07ef333 (patch)
tree5e78244b61d87d0256efe974a7a352285f745a65
parent0aa246385d1d2eabafce0e659d6438a38c3b6519 (diff)
Add some integration tests: successful compilation and execution
-rw-r--r--src/main/scala/firrtl/Driver.scala2
l---------src/test/resources/integration1
-rw-r--r--src/test/scala/firrtlTests/FirrtlSpec.scala124
-rw-r--r--src/test/scala/firrtlTests/IntegrationSpec.scala26
-rw-r--r--test/integration/GCDTester.fir135
-rw-r--r--test/integration/top.cpp88
6 files changed, 375 insertions, 1 deletions
diff --git a/src/main/scala/firrtl/Driver.scala b/src/main/scala/firrtl/Driver.scala
index f397a2ac..c93bb54e 100644
--- a/src/main/scala/firrtl/Driver.scala
+++ b/src/main/scala/firrtl/Driver.scala
@@ -47,7 +47,7 @@ Options:
"""
private val defaultOptions = Map[Symbol, Any]().withDefaultValue(false)
- private def compile(input: String, output: String, compiler: Compiler)
+ def compile(input: String, output: String, compiler: Compiler)
{
val parsedInput = Parser.parse(input, Source.fromFile(input).getLines)
val writerOutput = new PrintWriter(new File(output))
diff --git a/src/test/resources/integration b/src/test/resources/integration
new file mode 120000
index 00000000..5e63f954
--- /dev/null
+++ b/src/test/resources/integration
@@ -0,0 +1 @@
+../../../test/integration \ No newline at end of file
diff --git a/src/test/scala/firrtlTests/FirrtlSpec.scala b/src/test/scala/firrtlTests/FirrtlSpec.scala
new file mode 100644
index 00000000..438a5282
--- /dev/null
+++ b/src/test/scala/firrtlTests/FirrtlSpec.scala
@@ -0,0 +1,124 @@
+
+package firrtlTests
+
+import java.io._
+
+import scala.sys.process._
+import org.scalatest._
+import org.scalatest.prop._
+
+import firrtl._
+
+// This trait is borrowed from Chisel3, ideally this code should only exist in one location
+trait BackendCompilationUtilities {
+ /** Create a temporary directory with the prefix name. Exists here because it doesn't in Java 6.
+ */
+ def createTempDirectory(prefix: String): File = {
+ val temp = File.createTempFile(prefix, "")
+ if (!temp.delete()) {
+ throw new IOException(s"Unable to delete temp file '$temp'")
+ }
+ if (!temp.mkdir()) {
+ throw new IOException(s"Unable to create temp directory '$temp'")
+ }
+ temp
+ }
+
+ /** Copy the contents of a resource to a destination file.
+ */
+ def copyResourceToFile(name: String, file: File) {
+ val in = getClass().getResourceAsStream(name)
+ if (in == null) {
+ throw new FileNotFoundException(s"Resource '$name'")
+ }
+ val out = new FileOutputStream(file)
+ Iterator.continually(in.read).takeWhile(-1 !=).foreach(out.write)
+ out.close()
+ }
+
+
+ def makeHarness(template: String => String, post: String)(f: File): File = {
+ val prefix = f.toString.split("/").last
+ val vf = new File(f.toString + post)
+ val w = new FileWriter(vf)
+ w.write(template(prefix))
+ w.close()
+ vf
+ }
+
+ /** Generates a Verilator invocation to convert Verilog sources to C++
+ * simulation sources.
+ *
+ * The Verilator prefix will be V$dutFile, and running this will generate
+ * C++ sources and headers as well as a makefile to compile them.
+ *
+ * Verilator will automatically locate the top-level module as the one among
+ * all the files which are not included elsewhere. If multiple ones exist,
+ * the compilation will fail.
+ *
+ * @param dutFile name of the DUT .v without the .v extension
+ * @param dir output directory
+ * @param vSources list of additional Verilog sources to compile
+ * @param cppHarness C++ testharness to compile/link against
+ */
+ def verilogToCpp(
+ dutFile: String,
+ dir: File,
+ vSources: Seq[File],
+ cppHarness: File): ProcessBuilder =
+
+ Seq("verilator",
+ "--cc", s"$dutFile.v") ++
+ vSources.map(file => Seq("-v", file.toString)).flatten ++
+ Seq("--assert",
+ "--Wno-fatal",
+ "--trace",
+ "-O2",
+ "+define+TOP_TYPE=V" + dutFile,
+ "-CFLAGS", s"""-Wno-undefined-bool-conversion -O2 -DTOP_TYPE=V$dutFile -include V$dutFile.h""",
+ "-Mdir", dir.toString,
+ "--exe", cppHarness.toString)
+
+ def cppToExe(prefix: String, dir: File): ProcessBuilder =
+ Seq("make", "-C", dir.toString, "-j", "-f", s"V${prefix}.mk", s"V${prefix}")
+
+ def executeExpectingFailure(
+ prefix: String,
+ dir: File,
+ assertionMsg: String = "Assertion failed"): Boolean = {
+ var triggered = false
+ val e = Process(s"./V${prefix}", dir) !
+ ProcessLogger(line => {
+ triggered = triggered || line.contains(assertionMsg)
+ System.out.println(line)
+ })
+ triggered
+ }
+
+ def executeExpectingSuccess(prefix: String, dir: File): Boolean = {
+ !executeExpectingFailure(prefix, dir)
+ }
+}
+
+trait FirrtlRunners extends BackendCompilationUtilities {
+ lazy val cpp = new File(s"/integration/top.cpp")
+ def compileFirrtlTest(prefix: String, srcDir: String): File = {
+ val testDir = createTempDirectory(prefix)
+ copyResourceToFile(s"${srcDir}/${prefix}.fir", new File(testDir, s"${prefix}.fir"))
+
+ Driver.compile(s"${testDir}/${prefix}.fir", s"${testDir}/${prefix}.v", VerilogCompiler)
+ testDir
+ }
+ def runFirrtlTest(prefix: String, srcDir: String) {
+ val testDir = compileFirrtlTest(prefix, srcDir)
+ val harness = new File(testDir, s"top.cpp")
+ copyResourceToFile(cpp.toString, harness)
+
+ verilogToCpp(prefix, testDir, Seq(), harness).!
+ cppToExe(prefix, testDir).!
+ executeExpectingSuccess(prefix, testDir)
+ }
+}
+
+class FirrtlPropSpec extends PropSpec with PropertyChecks with FirrtlRunners
+
diff --git a/src/test/scala/firrtlTests/IntegrationSpec.scala b/src/test/scala/firrtlTests/IntegrationSpec.scala
new file mode 100644
index 00000000..eb5a7fa1
--- /dev/null
+++ b/src/test/scala/firrtlTests/IntegrationSpec.scala
@@ -0,0 +1,26 @@
+
+package firrtlTests
+
+import org.scalatest._
+import org.scalatest.prop._
+
+class IntegrationSpec extends FirrtlPropSpec {
+
+ case class Test(name: String, dir: String)
+
+ val runTests = Seq(Test("GCDTester", "/integration"))
+
+ runTests foreach { test =>
+ property(s"${test.name} should execute correctly") {
+ runFirrtlTest(test.name, test.dir)
+ }
+ }
+
+ val compileTests = Seq(Test("rocket", "/regress"), Test("rocket-firrtl", "/regress"))
+
+ compileTests foreach { test =>
+ property(s"${test.name} should compile to Verilog") {
+ compileFirrtlTest(test.name, test.dir)
+ }
+ }
+}
diff --git a/test/integration/GCDTester.fir b/test/integration/GCDTester.fir
new file mode 100644
index 00000000..335c573e
--- /dev/null
+++ b/test/integration/GCDTester.fir
@@ -0,0 +1,135 @@
+circuit GCDTester :
+ module DecoupledGCD :
+ input clk : Clock
+ input reset : UInt<1>
+ output io : { flip in : { flip ready : UInt<1>, valid : UInt<1>, bits : { a : UInt<32>, b : UInt<32>}}, out : { flip ready : UInt<1>, valid : UInt<1>, bits : UInt<32>}}
+ io is invalid
+ reg busy : UInt<1>, clk with :
+ reset => (reset, UInt<1>("h0"))
+ reg done : UInt<1>, clk with :
+ reset => (reset, UInt<1>("h0"))
+ reg x : UInt<32>, clk with :
+ reset => (UInt<1>("h0"), x)
+ reg y : UInt<32>, clk with :
+ reset => (UInt<1>("h0"), y)
+ node T_40 = eq(busy, UInt<1>("h0"))
+ io.in.ready <= T_40
+ io.out.valid <= done
+ node T_42 = eq(y, UInt<1>("h0"))
+ node T_43 = and(busy, T_42)
+ when T_43 :
+ done <= UInt<1>("h1")
+ skip
+ node T_45 = and(done, io.out.ready)
+ when T_45 :
+ busy <= UInt<1>("h0")
+ skip
+ node start = and(io.in.valid, io.in.ready)
+ when start :
+ busy <= UInt<1>("h1")
+ done <= UInt<1>("h0")
+ skip
+ node T_50 = gt(x, y)
+ when T_50 :
+ node T_51 = sub(x, y)
+ node T_52 = tail(T_51, 1)
+ x <= T_52
+ skip
+ node T_54 = eq(T_50, UInt<1>("h0"))
+ when T_54 :
+ node T_55 = sub(y, x)
+ node T_56 = tail(T_55, 1)
+ y <= T_56
+ skip
+ when start :
+ x <= io.in.bits.a
+ y <= io.in.bits.b
+ skip
+ io.out.bits <= x
+
+ module GCDTester :
+ input clk : Clock
+ input reset : UInt<1>
+ output io : { }
+ io is invalid
+ inst dut of DecoupledGCD
+ dut.io is invalid
+ dut.clk <= clk
+ dut.reset <= reset
+ reg count : UInt<4>, clk with :
+ reset => (reset, UInt<4>("h9"))
+ wire a : UInt<7>[10]
+ a[0] <= UInt<6>("h2e")
+ a[1] <= UInt<7>("h5f")
+ a[2] <= UInt<5>("h1a")
+ a[3] <= UInt<6>("h3d")
+ a[4] <= UInt<5>("h12")
+ a[5] <= UInt<6>("h33")
+ a[6] <= UInt<6>("h2d")
+ a[7] <= UInt<7>("h42")
+ a[8] <= UInt<7>("h47")
+ a[9] <= UInt<7>("h47")
+ wire b : UInt<7>[10]
+ b[0] <= UInt<6>("h27")
+ b[1] <= UInt<6>("h2c")
+ b[2] <= UInt<6>("h25")
+ b[3] <= UInt<7>("h60")
+ b[4] <= UInt<6>("h30")
+ b[5] <= UInt<6>("h27")
+ b[6] <= UInt<7>("h55")
+ b[7] <= UInt<7>("h54")
+ b[8] <= UInt<4>("h8")
+ b[9] <= UInt<7>("h50")
+ wire z : UInt<3>[10]
+ z[0] <= UInt<1>("h1")
+ z[1] <= UInt<1>("h1")
+ z[2] <= UInt<1>("h1")
+ z[3] <= UInt<1>("h1")
+ z[4] <= UInt<3>("h6")
+ z[5] <= UInt<2>("h3")
+ z[6] <= UInt<3>("h5")
+ z[7] <= UInt<3>("h6")
+ z[8] <= UInt<1>("h1")
+ z[9] <= UInt<1>("h1")
+ dut.io.out.ready <= UInt<1>("h0")
+ reg en : UInt<1>, clk with :
+ reset => (reset, UInt<1>("h1"))
+ dut.io.in.bits.a <= a[count]
+ dut.io.in.bits.b <= b[count]
+ dut.io.in.valid <= en
+ node T_80 = and(en, dut.io.in.ready)
+ when T_80 :
+ en <= UInt<1>("h0")
+ skip
+ node T_83 = eq(en, UInt<1>("h0"))
+ node T_84 = and(dut.io.out.valid, T_83)
+ when T_84 :
+ dut.io.out.ready <= UInt<1>("h1")
+ node T_87 = eq(dut.io.out.bits, z[count])
+ node T_89 = eq(reset, UInt<1>("h0"))
+ when T_89 :
+ node T_91 = eq(T_87, UInt<1>("h0"))
+ when T_91 :
+ node T_93 = eq(reset, UInt<1>("h0"))
+ when T_93 :
+ printf(clk, UInt<1>("h1"), "Assertion failed\n at GCDTester.scala:38 assert( dut.io.out.bits === z(count) )\n")
+ skip
+ stop(clk, UInt<1>("h1"), 1)
+ skip
+ skip
+ node T_95 = eq(count, UInt<1>("h0"))
+ when T_95 :
+ node T_97 = eq(reset, UInt<1>("h0"))
+ when T_97 :
+ stop(clk, UInt<1>("h1"), 0)
+ skip
+ skip
+ node T_99 = eq(T_95, UInt<1>("h0"))
+ when T_99 :
+ en <= UInt<1>("h1")
+ node T_102 = sub(count, UInt<1>("h1"))
+ node T_103 = tail(T_102, 1)
+ count <= T_103
+ skip
+ skip
+
diff --git a/test/integration/top.cpp b/test/integration/top.cpp
new file mode 100644
index 00000000..8bfe2a99
--- /dev/null
+++ b/test/integration/top.cpp
@@ -0,0 +1,88 @@
+#include <verilated.h>
+#include <iostream>
+
+#if VM_TRACE
+# include <verilated_vcd_c.h> // Trace file format header
+#endif
+
+using namespace std;
+
+//VGCDTester *top;
+TOP_TYPE *top;
+
+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
+}
+
+// TODO Provide command-line options like vcd filename, timeout count, etc.
+const long timeout = 100000000L;
+
+int main(int argc, char** argv) {
+ Verilated::commandArgs(argc, argv); // Remember args
+ top = new TOP_TYPE;
+
+#if VM_TRACE // If verilator was invoked with --trace
+ Verilated::traceEverOn(true); // Verilator must compute traced signals
+ VL_PRINTF("Enabling waves...\n");
+ VerilatedVcdC* tfp = new VerilatedVcdC;
+ top->trace (tfp, 99); // Trace 99 levels of hierarchy
+ tfp->open ("dump.vcd"); // Open the dump file
+#endif
+
+
+ top->reset = 1;
+
+ cout << "Starting simulation!\n";
+
+ while (!Verilated::gotFinish() && main_time < timeout) {
+ if (main_time > 10) {
+ top->reset = 0; // Deassert reset
+ }
+ if ((main_time % 10) == 1) {
+ top->clk = 1; // Toggle clock
+ }
+ if ((main_time % 10) == 6) {
+ top->clk = 0;
+ }
+ top->eval(); // Evaluate model
+#if VM_TRACE
+ if (tfp) tfp->dump (main_time); // Create waveform trace for this timestamp
+#endif
+ main_time++; // Time passes...
+ }
+
+ if (main_time >= timeout) {
+ cout << "Simulation terminated by timeout at time " << main_time <<
+ " (cycle " << main_time / 10 << ")"<< endl;
+ return -1;
+ } else {
+ cout << "Simulation completed at time " << main_time <<
+ " (cycle " << main_time / 10 << ")"<< endl;
+ }
+
+ // Run for 10 more clocks
+ vluint64_t end_time = main_time + 100;
+ while (main_time < end_time) {
+ if ((main_time % 10) == 1) {
+ top->clk = 1; // Toggle clock
+ }
+ if ((main_time % 10) == 6) {
+ top->clk = 0;
+ }
+ top->eval(); // Evaluate model
+#if VM_TRACE
+ if (tfp) tfp->dump (main_time); // Create waveform trace for this timestamp
+#endif
+ main_time++; // Time passes...
+ }
+
+#if VM_TRACE
+ if (tfp) tfp->close();
+#endif
+}
+