diff options
| author | jackkoenig | 2016-03-03 12:23:57 -0800 |
|---|---|---|
| committer | jackkoenig | 2016-03-03 12:23:57 -0800 |
| commit | 4d77e3ac1020b404e5a0f5d68cd36fb3a07ef333 (patch) | |
| tree | 5e78244b61d87d0256efe974a7a352285f745a65 | |
| parent | 0aa246385d1d2eabafce0e659d6438a38c3b6519 (diff) | |
Add some integration tests: successful compilation and execution
| -rw-r--r-- | src/main/scala/firrtl/Driver.scala | 2 | ||||
| l--------- | src/test/resources/integration | 1 | ||||
| -rw-r--r-- | src/test/scala/firrtlTests/FirrtlSpec.scala | 124 | ||||
| -rw-r--r-- | src/test/scala/firrtlTests/IntegrationSpec.scala | 26 | ||||
| -rw-r--r-- | test/integration/GCDTester.fir | 135 | ||||
| -rw-r--r-- | test/integration/top.cpp | 88 |
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 +} + |
