From 436d23bf30e281baf11998fcbb85f112b3e27517 Mon Sep 17 00:00:00 2001 From: Benjamin Tu Date: Fri, 2 Aug 2019 23:04:38 -0700 Subject: [PATCH] [VTA] [Chisel] Added Chisel Module Unit Test Infrastructure (#3698) * added wholething * changed build and makefile --- hardware/chisel/Makefile | 4 + hardware/chisel/README.md | 30 ++++++ hardware/chisel/build.sbt | 10 +- .../src/test/scala/unittest/Launcher.scala | 54 +++++++++++ .../src/test/scala/unittest/MvmTest.scala | 91 +++++++++++++++++++ .../src/test/scala/unittest/TestRunner.scala | 91 +++++++++++++++++++ 6 files changed, 277 insertions(+), 3 deletions(-) create mode 100644 hardware/chisel/README.md create mode 100644 hardware/chisel/src/test/scala/unittest/Launcher.scala create mode 100644 hardware/chisel/src/test/scala/unittest/MvmTest.scala create mode 100644 hardware/chisel/src/test/scala/unittest/TestRunner.scala diff --git a/hardware/chisel/Makefile b/hardware/chisel/Makefile index 3c9b60148017..cf57c0e0fda3 100644 --- a/hardware/chisel/Makefile +++ b/hardware/chisel/Makefile @@ -44,6 +44,7 @@ vta_dir = $(abspath ../../) tvm_dir = $(abspath ../../../) verilator_build_dir = $(vta_dir)/$(BUILD_NAME)/verilator chisel_build_dir = $(vta_dir)/$(BUILD_NAME)/chisel +test_name = mvm verilator_opt = --cc verilator_opt += +define+RANDOMIZE_GARBAGE_ASSIGN @@ -111,6 +112,9 @@ verilog_test: $(chisel_build_dir)/$(TOP_TEST).$(CONFIG).v $(chisel_build_dir)/$(TOP_TEST).$(CONFIG).v: sbt 'runMain vta.$(config_test) --target-dir $(chisel_build_dir) --top-name $(TOP_TEST).$(CONFIG)' +test: + sbt 'test:runMain unittest.Launcher $(test_name)' + clean: -rm -rf target project/target project/project diff --git a/hardware/chisel/README.md b/hardware/chisel/README.md new file mode 100644 index 000000000000..24e3531537d6 --- /dev/null +++ b/hardware/chisel/README.md @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + +VTA in Chisel +=================================================== +For contributors who wants to test a chisel module: + + - You can add your test files in `src/test/scala/unitttest` + - Add your test name and tests to the `test` object in `src/test/scala/unitttest/Launcher.scala` + - Check out the provided sample test `mvm` which tests the MatrixVectorComputation module + in `src/main/scala/core/TensorGemm.scala` + +- Running unit tests: `make test test_name=your_own test_name` + + + diff --git a/hardware/chisel/build.sbt b/hardware/chisel/build.sbt index 3fa93d2c5cfc..45fbf00dd66c 100644 --- a/hardware/chisel/build.sbt +++ b/hardware/chisel/build.sbt @@ -60,9 +60,13 @@ resolvers ++= Seq( Resolver.sonatypeRepo("snapshots"), Resolver.sonatypeRepo("releases")) -libraryDependencies ++= Seq( - "edu.berkeley.cs" %% "chisel3" % "3.1.7", -) +val defaultVersions = Map( + "chisel3" -> "3.1.7", + "chisel-iotesters" -> "[1.2.5,1.3-SNAPSHOT[" + ) + +libraryDependencies ++= Seq("chisel3","chisel-iotesters").map { + dep: String => "edu.berkeley.cs" %% dep % sys.props.getOrElse(dep + "Version", defaultVersions(dep)) } scalacOptions ++= scalacOptionsVersion(scalaVersion.value) javacOptions ++= javacOptionsVersion(scalaVersion.value) diff --git a/hardware/chisel/src/test/scala/unittest/Launcher.scala b/hardware/chisel/src/test/scala/unittest/Launcher.scala new file mode 100644 index 000000000000..53b0c9ff0946 --- /dev/null +++ b/hardware/chisel/src/test/scala/unittest/Launcher.scala @@ -0,0 +1,54 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package unittest +// taken from https://github.com/freechipsproject/chisel-testers + +import chisel3._ +import chisel3.iotesters.{Driver, TesterOptionsManager} +import vta.core._ +import vta.util.config._ +import vta.shell._ + +class TestConfig extends Config(new CoreConfig ++ new PynqConfig) + +/* Launcher. + * + * The Launcher object includes a test list for the TestRunner to check. + * Users can utilize this Launcher to run custom tests. + * + * How to Use: + * When the user input: sbt 'test:runMain unittest.Launcher mvm' + * the TestRunner will look for 'mvm' in the map and executes the + * test that 'mvm' is mapped to + */ +object Launcher { + implicit val p: Parameters = new TestConfig + val tests = Map( + "mvm" -> { (manager: TesterOptionsManager) => + Driver.execute(() => new MatrixVectorMultiplication, manager) { + (c) => new TestMatrixVectorMultiplication(c) + } + } + ) + + def main(args: Array[String]): Unit = { + TestRunner(tests, args) + } +} diff --git a/hardware/chisel/src/test/scala/unittest/MvmTest.scala b/hardware/chisel/src/test/scala/unittest/MvmTest.scala new file mode 100644 index 000000000000..afa68eac8dae --- /dev/null +++ b/hardware/chisel/src/test/scala/unittest/MvmTest.scala @@ -0,0 +1,91 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package unittest + +import chisel3._ +import chisel3.util._ +import chisel3.iotesters.{ChiselFlatSpec, Driver, PeekPokeTester} +import scala.util.Random +import scala.math.pow +import vta.core._ + +class TestMatrixVectorMultiplication(c: MatrixVectorMultiplication) extends PeekPokeTester(c) { + + /* mvm_ref + * + * This is a software function that computes dot product with a programmable shift + * This is used as a reference for the hardware + */ + def mvm_ref(inp: Array[Int], wgt: Array[Array[Int]], shift: Int) : Array[Int] = { + val size = inp.length + val res = Array.fill(size) {0} + for (i <- 0 until size) { + var dot = 0 + for (j <- 0 until size) { + dot += wgt(i)(j) * inp(j) + } + res(i) = dot * pow(2, shift).toInt + } + return res + } + + val cycles = 5 + for (i <- 0 until cycles) { + val r = new Random + // generate random data based on config bits + val in_a = Array.fill(c.size) { r.nextInt(pow(2, c.inpBits).toInt) - pow(2, c.inpBits-1).toInt} + val in_b = Array.fill(c.size, c.size) { r.nextInt(pow(2, c.wgtBits).toInt) - pow(2, c.wgtBits-1).toInt} + val res = mvm_ref(in_a, in_b, 0) + val inpMask = (pow(2, c.inpBits) - 1).toLong + val wgtMask = (pow(2, c.wgtBits) - 1).toLong + val accMask = (pow(2, c.accBits) - 1).toLong + + for (i <- 0 until c.size) { + poke(c.io.inp.data.bits(0)(i), in_a(i) & inpMask) + poke(c.io.acc_i.data.bits(0)(i), 0) + for (j <- 0 until c.size) { + poke(c.io.wgt.data.bits(i)(j), in_b(i)(j) & wgtMask) + } + } + + poke(c.io.reset, 0) + + poke(c.io.inp.data.valid, 1) + poke(c.io.wgt.data.valid, 1) + poke(c.io.acc_i.data.valid, 1) + + step(1) + + poke(c.io.inp.data.valid, 0) + poke(c.io.wgt.data.valid, 0) + poke(c.io.acc_i.data.valid, 0) + + // wait for valid signal + while (peek(c.io.acc_o.data.valid) == BigInt(0)) { + step(1) // advance clock + } + if (peek(c.io.acc_o.data.valid) == BigInt(1)) { + for (i <- 0 until c.size) { + expect(c.io.acc_o.data.bits(0)(i), res(i) & accMask) + } + } + } + +} diff --git a/hardware/chisel/src/test/scala/unittest/TestRunner.scala b/hardware/chisel/src/test/scala/unittest/TestRunner.scala new file mode 100644 index 000000000000..39e270141bd7 --- /dev/null +++ b/hardware/chisel/src/test/scala/unittest/TestRunner.scala @@ -0,0 +1,91 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package unittest +// taken from https://github.com/freechipsproject/chisel-testers + +import scala.collection.mutable.ArrayBuffer +import chisel3.iotesters._ + +object TestRunner { + + def apply(testMap: Map[String, TesterOptionsManager => Boolean], args: Array[String]): Unit = { + var successful = 0 + val errors = new ArrayBuffer[String] + + val optionsManager = new TesterOptionsManager() + optionsManager.doNotExitOnHelp() + + optionsManager.parse(args) + + val programArgs = optionsManager.commonOptions.programArgs + + if(programArgs.isEmpty) { + println("Available tests") + for(x <- testMap.keys) { + println(x) + } + println("all") + System.exit(0) + } + + val testsToRun = if(programArgs.exists(x => x.toLowerCase() == "all")) { + testMap.keys + } + else { + programArgs + } + + for(testName <- testsToRun) { + testMap.get(testName) match { + case Some(test) => + println(s"Starting $testName") + try { + optionsManager.setTopName(testName) + optionsManager.setTargetDirName(s"test_run_dir/$testName") + if(test(optionsManager)) { + successful += 1 + } + else { + errors += s"$testName: test error occurred" + } + } + catch { + case exception: Exception => + exception.printStackTrace() + errors += s"$testName: exception ${exception.getMessage}" + case t : Throwable => + errors += s"$testName: throwable ${t.getMessage}" + } + case _ => + errors += s"Bad Test name: $testName" + } + + } + if(successful > 0) { + println(s"Tests passing: $successful") + } + if(errors.nonEmpty) { + println("=" * 80) + println(s"Errors: ${errors.length}: in the following tests") + println(errors.mkString("\n")) + println("=" * 80) + } + } +}