summaryrefslogtreecommitdiff
path: root/src/test/scala/examples
diff options
context:
space:
mode:
authorjackkoenig2016-10-26 16:40:36 -0700
committerJack Koenig2017-02-02 22:53:03 -0800
commitdd51b917566e6b30c3f123ca22a0393e73c2afe8 (patch)
treef81fce104ccc3405c7924c76618483750a4350bb /src/test/scala/examples
parentb0a328492383108509c322189ed2803f671d7a59 (diff)
Revamp VendingMachine.scala as cookbook example
* Move to cookbook * Change FSM implementation to use switch & is * Add non-FSM implementation * Add execution-driven test
Diffstat (limited to 'src/test/scala/examples')
-rw-r--r--src/test/scala/examples/ImplicitStateVendingMachine.scala31
-rw-r--r--src/test/scala/examples/SimpleVendingMachine.scala95
-rw-r--r--src/test/scala/examples/VendingMachineGenerator.scala127
-rw-r--r--src/test/scala/examples/VendingMachineUtils.scala39
4 files changed, 292 insertions, 0 deletions
diff --git a/src/test/scala/examples/ImplicitStateVendingMachine.scala b/src/test/scala/examples/ImplicitStateVendingMachine.scala
new file mode 100644
index 00000000..ae1e28dd
--- /dev/null
+++ b/src/test/scala/examples/ImplicitStateVendingMachine.scala
@@ -0,0 +1,31 @@
+// See LICENSE for license details.
+
+package examples
+
+import chiselTests.ChiselFlatSpec
+import chisel3._
+
+// Vending machine implemented with an implicit state machine
+class ImplicitStateVendingMachine extends SimpleVendingMachine {
+ // We let the value of nickel be 1 and dime be 2 for efficiency reasons
+ val value = Reg(init = 0.asUInt(3.W))
+ val incValue = Wire(init = 0.asUInt(3.W))
+ val doDispense = value >= 4.U // 4 * nickel as 1 == $0.20
+
+ when (doDispense) {
+ value := 0.U // No change given
+ } .otherwise {
+ value := value + incValue
+ }
+
+ when (io.nickel) { incValue := 1.U }
+ when (io.dime) { incValue := 2.U }
+
+ io.dispense := doDispense
+}
+
+class ImplicitStateVendingMachineSpec extends ChiselFlatSpec {
+ "An vending machine implemented with implicit state" should "work" in {
+ assertTesterPasses { new SimpleVendingMachineTester(new ImplicitStateVendingMachine) }
+ }
+}
diff --git a/src/test/scala/examples/SimpleVendingMachine.scala b/src/test/scala/examples/SimpleVendingMachine.scala
new file mode 100644
index 00000000..d935b611
--- /dev/null
+++ b/src/test/scala/examples/SimpleVendingMachine.scala
@@ -0,0 +1,95 @@
+// See LICENSE for license details.
+
+package examples
+
+import chiselTests.ChiselFlatSpec
+import chisel3.testers.BasicTester
+import chisel3._
+import chisel3.util._
+
+class SimpleVendingMachineIO extends Bundle {
+ val nickel = Input(Bool())
+ val dime = Input(Bool())
+ val dispense = Output(Bool())
+}
+
+// Superclass for vending machines with very simple IO
+abstract class SimpleVendingMachine extends Module {
+ val io = IO(new SimpleVendingMachineIO)
+ assert(!(io.nickel && io.dime), "Only one of nickel or dime can be input at a time!")
+}
+
+// Vending machine implemented with a Finite State Machine
+class FSMVendingMachine extends SimpleVendingMachine {
+ val sIdle :: s5 :: s10 :: s15 :: sOk :: Nil = Enum(5)
+ val state = Reg(init = sIdle)
+
+ switch (state) {
+ is (sIdle) {
+ when (io.nickel) { state := s5 }
+ when (io.dime) { state := s10 }
+ }
+ is (s5) {
+ when (io.nickel) { state := s10 }
+ when (io.dime) { state := s15 }
+ }
+ is (s10) {
+ when (io.nickel) { state := s15 }
+ when (io.dime) { state := sOk }
+ }
+ is (s15) {
+ when (io.nickel) { state := sOk }
+ when (io.dime) { state := sOk }
+ }
+ is (sOk) {
+ state := sIdle
+ }
+ }
+ io.dispense := (state === sOk)
+}
+
+class VerilogVendingMachine extends BlackBox {
+ // Because this is a blackbox, we must explicity add clock and reset
+ val io = IO(new SimpleVendingMachineIO {
+ val clock = Input(Clock())
+ val reset = Input(Bool())
+ })
+}
+
+// Shim because Blackbox io is slightly different than normal Chisel Modules
+class VerilogVendingMachineWrapper extends SimpleVendingMachine {
+ val impl = Module(new VerilogVendingMachine)
+ impl.io.clock := clock
+ impl.io.reset := reset
+ impl.io.nickel := io.nickel
+ impl.io.dime := io.dime
+ io.dispense := impl.io.dispense
+}
+
+// Accept a reference to a SimpleVendingMachine so it can be constructed inside
+// the tester (in a call to Module.apply as required by Chisel
+class SimpleVendingMachineTester(mod: => SimpleVendingMachine) extends BasicTester {
+
+ val dut = Module(mod)
+
+ val (cycle, done) = Counter(true.B, 10)
+ when (done) { stop(); stop() } // Stop twice because of Verilator
+
+ val nickelInputs = Vec(true.B, true.B, true.B, true.B, true.B, false.B, false.B, false.B, true.B, false.B)
+ val dimeInputs = Vec(false.B, false.B, false.B, false.B, false.B, true.B, true.B, false.B, false.B, true.B)
+ val expected = Vec(false.B, false.B, false.B, false.B, true.B , false.B, false.B, true.B, false.B, false.B)
+
+ dut.io.nickel := nickelInputs(cycle)
+ dut.io.dime := dimeInputs(cycle)
+ assert(dut.io.dispense === expected(cycle))
+}
+
+class SimpleVendingMachineSpec extends ChiselFlatSpec {
+ "An FSM implementation of a vending machine" should "work" in {
+ assertTesterPasses { new SimpleVendingMachineTester(new FSMVendingMachine) }
+ }
+ "An Verilog implementation of a vending machine" should "work" in {
+ assertTesterPasses(new SimpleVendingMachineTester(new VerilogVendingMachineWrapper),
+ List("/VerilogVendingMachine.v"))
+ }
+}
diff --git a/src/test/scala/examples/VendingMachineGenerator.scala b/src/test/scala/examples/VendingMachineGenerator.scala
new file mode 100644
index 00000000..fc44a47e
--- /dev/null
+++ b/src/test/scala/examples/VendingMachineGenerator.scala
@@ -0,0 +1,127 @@
+// See LICENSE for license details.
+
+package examples
+
+import chiselTests.ChiselFlatSpec
+import chisel3.testers.BasicTester
+import chisel3._
+import chisel3.util._
+
+import VendingMachineUtils._
+
+class VendingMachineIO(legalCoins: Seq[Coin]) extends Bundle {
+ require(legalCoins.size >= 1, "The vending machine must accept at least 1 coin!")
+ // Order of coins by value
+ val coins: Seq[Coin] = legalCoins sortBy (_.value)
+ // Map of coin names to their relative position in value (ie. index in inputs)
+ val indexMap: Map[String, Int] = coins.map(_.name).zipWithIndex.toMap
+
+ require(coins map (_.value % coins.head.value == 0) reduce (_ && _),
+ "All coins must be a multiple of the lowest value coin!")
+
+ val inputs = Input(Vec(legalCoins.size, Bool()))
+ val dispense = Output(Bool())
+
+ def apply(coin: String): Unit = {
+ val idx = indexMap(coin)
+ inputs(idx) := true.B
+ }
+}
+
+// Superclass for parameterized vending machines
+abstract class ParameterizedVendingMachine(legalCoins: Seq[Coin], val sodaCost: Int) extends Module {
+ val io = IO(new VendingMachineIO(legalCoins))
+ // Enforce one hot
+ if (io.inputs.size > 1) {
+ for (input <- io.inputs) {
+ when (input) {
+ assert(io.inputs.filterNot(_ == input).map(!_).reduce(_ && _),
+ "Only 1 coin can be input in a given cycle!")
+ }
+ }
+ }
+}
+
+class VendingMachineGenerator(
+ legalCoins: Seq[Coin],
+ sodaCost: Int) extends ParameterizedVendingMachine(legalCoins, sodaCost) {
+ require(sodaCost > 0, "Sodas must actually cost something!")
+
+ // All coin values are normalized to a multiple of the minimum coin value
+ val minCoin = io.coins.head.value
+ val maxCoin = io.coins.last.value
+ val maxValue = (sodaCost + maxCoin - minCoin) / minCoin // normalize to minimum value
+
+ val width = log2Up(maxValue + 1).W
+ val value = Reg(init = 0.asUInt(width))
+ val incValue = Wire(init = 0.asUInt(width))
+ val doDispense = value >= (sodaCost / minCoin).U
+
+ when (doDispense) {
+ value := 0.U // No change given
+ } .otherwise {
+ value := value + incValue
+ }
+
+ for ((coin, index) <- io.coins.zipWithIndex) {
+ when (io.inputs(index)) { incValue := (coin.value / minCoin).U }
+ }
+ io.dispense := doDispense
+}
+
+class ParameterizedVendingMachineTester(
+ mod: => ParameterizedVendingMachine,
+ testLength: Int) extends BasicTester {
+ require(testLength > 0, "Test length must be positive!")
+
+ // Construct the module
+ val dut = Module(mod)
+ val coins = dut.io.coins
+
+ // Inputs and expected results
+ // Do random testing
+ private val _rand = scala.util.Random
+ val inputs: Seq[Option[Coin]] = Seq.fill(testLength)(coins.lift(_rand.nextInt(coins.size + 1)))
+ val expected: Seq[Boolean] = getExpectedResults(inputs, dut.sodaCost)
+
+ val inputVec: Vec[UInt] = Vec(inputs map {
+ case Some(coin) => (1 << dut.io.indexMap(coin.name)).asUInt(coins.size.W)
+ case None => 0.asUInt(coins.size.W)
+ })
+ val expectedVec: Vec[Bool] = Vec(expected map (_.B))
+
+ val (idx, done) = Counter(true.B, testLength + 1)
+ when (done) { stop(); stop() } // Two stops for Verilator
+
+ dut.io.inputs := inputVec(idx).toBools
+ assert(dut.io.dispense === expectedVec(idx))
+}
+
+class VendingMachineGeneratorSpec extends ChiselFlatSpec {
+ behavior of "The vending machine generator"
+
+ it should "generate a vending machine that accepts only nickels and dimes and costs $0.20" in {
+ val coins = Seq(Nickel, Dime)
+ assertTesterPasses {
+ new ParameterizedVendingMachineTester(new VendingMachineGenerator(coins, 20), 100)
+ }
+ }
+ it should "generate a vending machine that only accepts one kind of coin" in {
+ val coins = Seq(Nickel)
+ assertTesterPasses {
+ new ParameterizedVendingMachineTester(new VendingMachineGenerator(coins, 30), 100)
+ }
+ }
+ it should "generate a more realistic vending machine that costs $1.50" in {
+ val coins = Seq(Penny, Nickel, Dime, Quarter)
+ assertTesterPasses {
+ new ParameterizedVendingMachineTester(new VendingMachineGenerator(coins, 150), 100)
+ }
+ }
+ it should "generate a Harry Potter themed vending machine" in {
+ val coins = Seq(Knut, Sickle) // Galleons are worth too much
+ assertTesterPasses {
+ new ParameterizedVendingMachineTester(new VendingMachineGenerator(coins, Galleon.value), 100)
+ }
+ }
+}
diff --git a/src/test/scala/examples/VendingMachineUtils.scala b/src/test/scala/examples/VendingMachineUtils.scala
new file mode 100644
index 00000000..e229dab5
--- /dev/null
+++ b/src/test/scala/examples/VendingMachineUtils.scala
@@ -0,0 +1,39 @@
+// See LICENSE for license details.
+
+package examples
+
+import scala.collection.mutable
+
+/* Useful utilities for testing vending machines */
+object VendingMachineUtils {
+ abstract class Coin(val name: String, val value: Int)
+ // US Coins
+ case object Penny extends Coin("penny", 1)
+ case object Nickel extends Coin("nickel", 5)
+ case object Dime extends Coin("dime", 10)
+ case object Quarter extends Coin("quarter", 25)
+
+ // Harry Potter Coins
+ case object Knut extends Coin("knut", Penny.value * 2) // Assuming 1 Knut is worth $0.02
+ case object Sickle extends Coin("sickle", Knut.value * 29)
+ case object Galleon extends Coin("galleon", Sickle.value * 17)
+
+ def getExpectedResults(inputs: Seq[Option[Coin]], sodaCost: Int): Seq[Boolean] = {
+ var value = 0
+ val outputs = mutable.ArrayBuffer.empty[Boolean]
+ for (input <- inputs) {
+ val incValue = input match {
+ case Some(coin) => coin.value
+ case None => 0
+ }
+ if (value >= sodaCost) {
+ outputs.append(true)
+ value = 0
+ } else {
+ outputs.append(false)
+ value += incValue
+ }
+ }
+ outputs
+ }
+}