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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
|
// SPDX-License-Identifier: Apache-2.0
package chiselTests.util.random
import chisel3._
import chisel3.stage.ChiselStage
import chisel3.util.{Cat, Counter, Enum}
import chisel3.util.random._
import chisel3.testers.{BasicTester, TesterDriver}
import chiselTests.{ChiselFlatSpec, Utils}
import math.pow
class FooLFSR(val reduction: LFSRReduce, seed: Option[BigInt]) extends PRNG(4, seed) with LFSR {
def delta(s: Seq[Bool]): Seq[Bool] = s
}
class LFSRMaxPeriod(gen: => UInt) extends BasicTester {
val rv = gen
val started = RegNext(true.B, false.B)
val seed = withReset(!started) { RegInit(rv) }
val (_, wrap) = Counter(started, math.pow(2.0, rv.getWidth).toInt - 1)
when(rv === seed && started) {
chisel3.assert(wrap)
stop()
}
val last = RegNext(rv)
chisel3.assert(rv =/= last, "LFSR last value (0b%b) was equal to current value (0b%b)", rv, last)
}
/**
* This test creates two 4 sided dice.
* Each cycle it adds them together and adds a count to the bin corresponding to that value
* The asserts check that the bins show the correct distribution.
*/
class LFSRDistribution(gen: => UInt, cycles: Int = 10000) extends BasicTester {
val rv = gen
val bins = Reg(Vec(8, UInt(32.W)))
// Use tap points on each LFSR so values are more independent
val die0 = Cat(Seq.tabulate(2) { i => rv(i) })
val die1 = Cat(Seq.tabulate(2) { i => rv(i + 2) })
val (trial, done) = Counter(true.B, cycles)
val rollValue = die0 +& die1 // Note +& is critical because sum will need an extra bit.
bins(rollValue) := bins(rollValue) + 1.U
when(done) {
printf(p"bins: $bins\n") // Note using the printable interpolator p"" to print out a Vec
// test that the distribution feels right.
assert(bins(1) > bins(0))
assert(bins(2) > bins(1))
assert(bins(3) > bins(2))
assert(bins(4) < bins(3))
assert(bins(5) < bins(4))
assert(bins(6) < bins(5))
assert(bins(7) === 0.U)
stop()
}
}
/** This tests that after reset an LFSR is not locked up. This manually sets the seed of the LFSR at run-time to the
* value that would cause it to lock up. It then asserts reset. The next cycle it checks that the value is NOT the
* locked up value.
* @param gen an LFSR to test
* @param lockUpValue the value that would lock up the LFSR
*/
class LFSRResetTester(gen: => LFSR, lockUpValue: BigInt) extends BasicTester {
val lfsr = Module(gen)
lfsr.io.seed.valid := false.B
lfsr.io.seed.bits := DontCare
lfsr.io.increment := true.B
val (count, done) = Counter(true.B, 5)
lfsr.io.seed.valid := count === 1.U
lfsr.io.seed.bits := lockUpValue.U(lfsr.width.W).asBools
lfsr.io.increment := true.B
when(count === 2.U) {
assert(lfsr.io.out.asUInt === lockUpValue.U, "LFSR is NOT locked up, but should be!")
}
lfsr.reset := count === 3.U
when(count === 4.U) {
assert(lfsr.io.out.asUInt =/= lockUpValue.U, "LFSR is locked up, but should not be after reset!")
}
when(done) {
stop()
}
}
class LFSRSpec extends ChiselFlatSpec with Utils {
def periodCheck(gen: (Int, Set[Int], LFSRReduce) => PRNG, reduction: LFSRReduce, range: Range): Unit = {
val testName = s"have a maximal period over a range of widths (${range.head} to ${range.last})" +
s" using ${reduction.getClass}"
it should testName in {
range.foreach { width =>
LFSR.tapsMaxPeriod(width).foreach { taps =>
info(s"""width $width okay using taps: ${taps.mkString(", ")}""")
assertTesterPasses(
new LFSRMaxPeriod(PRNG(gen(width, taps, reduction))),
annotations = TesterDriver.verilatorOnly
)
}
}
}
}
behavior.of("LFSR")
it should "throw an exception if initialized to a seed of zero for XOR configuration" in {
{
the[IllegalArgumentException] thrownBy extractCause[IllegalArgumentException] {
ChiselStage.elaborate(new FooLFSR(XOR, Some(0)))
}
}.getMessage should include("Seed cannot be zero")
}
it should "throw an exception if initialized to a seed of all ones for XNOR configuration" in {
{
the[IllegalArgumentException] thrownBy extractCause[IllegalArgumentException] {
ChiselStage.elaborate(new FooLFSR(XNOR, Some(15)))
}
}.getMessage should include("Seed cannot be all ones")
}
it should "reset correctly without a seed for XOR configuration" in {
assertTesterPasses(new LFSRResetTester(new FooLFSR(XOR, None), 0))
}
it should "reset correctly without a seed for XNOR configuration" in {
assertTesterPasses(new LFSRResetTester(new FooLFSR(XNOR, None), 15))
}
behavior.of("MaximalPeriodGaloisLFSR")
it should "throw an exception if no LFSR taps are known" in {
{
the[IllegalArgumentException] thrownBy extractCause[IllegalArgumentException] {
ChiselStage.elaborate(new MaxPeriodGaloisLFSR(787))
}
}.getMessage should include("No max period LFSR taps stored for requested width")
}
periodCheck((w: Int, t: Set[Int], r: LFSRReduce) => new GaloisLFSR(w, t, reduction = r), XOR, 2 to 16)
periodCheck((w: Int, t: Set[Int], r: LFSRReduce) => new GaloisLFSR(w, t, reduction = r), XNOR, 2 to 16)
ignore should "have a sane distribution for larger widths" in {
((17 to 32) ++ Seq(64, 128, 256, 512, 1024, 2048, 4096)).foreach { width =>
info(s"width $width okay!")
assertTesterPasses(new LFSRDistribution(LFSR(width), math.pow(2, 22).toInt))
}
}
behavior.of("MaximalPeriodFibonacciLFSR")
periodCheck((w: Int, t: Set[Int], r: LFSRReduce) => new FibonacciLFSR(w, t, reduction = r), XOR, 2 to 16)
periodCheck((w: Int, t: Set[Int], r: LFSRReduce) => new FibonacciLFSR(w, t, reduction = r), XNOR, 2 to 16)
behavior.of("LFSR maximal period taps")
it should "contain all the expected widths" in {
((2 to 786) ++ Seq(1024, 2048, 4096)).foreach(LFSR.tapsMaxPeriod.keys should contain(_))
}
}
|