1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
|
// SPDX-License-Identifier: Apache-2.0
package examples
import chiselTests.ChiselFlatSpec
import chisel3.testers.BasicTester
import chisel3._
import chisel3.util._
import VendingMachineUtils._
class VendingMachineIO(val 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 = log2Ceil(maxValue + 1).W
val value = RegInit(0.asUInt(width))
val incValue = WireDefault(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] = VecInit(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] = VecInit(expected.map(_.B))
val (idx, done) = Counter(true.B, testLength + 1)
when(done) { stop(); stop() } // Two stops for Verilator
dut.io.inputs := inputVec(idx).asBools
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)
}
}
}
|