diff options
| author | Chick Markley | 2019-10-18 19:44:08 -0700 |
|---|---|---|
| committer | Adam Izraelevitz | 2019-10-18 19:44:08 -0700 |
| commit | 7b93b0f8c48e39cc9730cf9f91340cf733dadafe (patch) | |
| tree | 3e9666c29d6c9901f221fed4728d05b9fd75067e /src/test | |
| parent | fafd984a923591841917cd4c3a1f4c823dc485b4 (diff) | |
Interval Data Type Support for Chisel (#1210)
Plan to be released with 3.3.
Breaks experimental Range API.
Adds new Interval type and associated support.
This commit adds the following:
- Renamed Range to IntervalRange to avoid name collision with scala Range
- Changed RangeTransform macro to Return an IntervalRange
- Improved error messages on missing comma or decimal
- Added notational support for binary point
- Some formatting cleanup also
- SIntFactory
- Change to use IntervalRange API
- UIntFactory
- UInt from range has custom width computation
- It does not need to deal with lowerbound extending bit requirements
- Code to handle special case of range"[0,0]" to have a width of 1
- IR.scala
- Removed Bound and other constraint code that was duplicating firrtl stuff
- Added new RangeType
- Added IntervalRange class and object
- RangeSpec
- modified just a bit to handle notational differences
- previous range interpolator returned tuple now returns IntervalRange
- Add IntervalType to emitter
- Added IntervalSpec with many tests
- Added ScalaIntervalSimulatorSpec which tests golden model for Interval
- Added ScalaIntervalSimulator which is a golden model for Interval
- This gold may not have been polished to a high sheen
- Add IntervalLit cases to Converter
- Add Interval PrimOps to IR
- asInterval, wrap, squz, clip, setp, decp, incp
- Add IntervalLit class to IR
- Add Interval to MonoConnect
- Add Interval Type to Bits (in experimental package)
- add conversions to Interval from other types
- Add Interval clone stuff to Data
- Add Literal creation helpers to chisel3 package
- these may move to experimental if I can figure that out
Diffstat (limited to 'src/test')
| -rw-r--r-- | src/test/scala/chiselTests/IntervalRangeSpec.scala | 221 | ||||
| -rw-r--r-- | src/test/scala/chiselTests/IntervalSpec.scala | 919 | ||||
| -rw-r--r-- | src/test/scala/chiselTests/RangeSpec.scala | 98 | ||||
| -rw-r--r-- | src/test/scala/chiselTests/ScalaIntervalSimulatorTest.scala | 96 | ||||
| -rw-r--r-- | src/test/scala/chiselTests/Util.scala | 52 |
5 files changed, 1291 insertions, 95 deletions
diff --git a/src/test/scala/chiselTests/IntervalRangeSpec.scala b/src/test/scala/chiselTests/IntervalRangeSpec.scala new file mode 100644 index 00000000..c152e72d --- /dev/null +++ b/src/test/scala/chiselTests/IntervalRangeSpec.scala @@ -0,0 +1,221 @@ +// See README.md for license details. + +package chiselTests + +import chisel3._ +import chisel3.experimental._ +import _root_.firrtl.{ir => firrtlir} +import chisel3.internal.firrtl.{BinaryPoint, IntervalRange, KnownBinaryPoint, UnknownBinaryPoint} +import org.scalatest.{FreeSpec, Matchers} + +//scalastyle:off method.name magic.number +class IntervalRangeSpec extends FreeSpec with Matchers { + + "IntervalRanges" - { + def C(b: BigDecimal): firrtlir.Bound = firrtlir.Closed(b) + + def O(b: BigDecimal): firrtlir.Bound = firrtlir.Open(b) + + def U(): firrtlir.Bound = firrtlir.UnknownBound + + def UBP(): BinaryPoint = UnknownBinaryPoint + + def checkRange(r: IntervalRange, l: firrtlir.Bound, u: firrtlir.Bound, b: BinaryPoint): Unit = { + r.lowerBound should be(l) + r.upperBound should be(u) + r.binaryPoint should be(b) + } + + def checkBinaryPoint(r: IntervalRange, b: BinaryPoint): Unit = { + r.binaryPoint should be(b) + } + + "IntervalRange describes the range of values of the Interval Type" - { + "Factory methods can create IntervalRanges" - { + "ranges can start or end open or closed, default binary point is none" in { + checkRange(range"[0,10]", C(0), C(10), 0.BP) + checkRange(range"[-1,10)", C(-1), O(10), 0.BP) + checkRange(range"(11,12]", O(11), C(12), 0.BP) + checkRange(range"(-21,-10)", O(-21), O(-10), 0.BP) + } + + "ranges can have unknown bounds" in { + checkRange(range"[?,10]", U(), C(10), 0.BP) + checkRange(range"(?,10]", U(), C(10), 0.BP) + checkRange(range"[-1,?]", C(-1), U(), 0.BP) + checkRange(range"[-1,?)", C(-1), U(), 0.BP) + checkRange(range"[?,?]", U(), U(), 0.BP) + checkRange(range"[?,?].?", U(), U(), UBP()) + } + + "binary points can be specified" in { + checkBinaryPoint(range"[?,10].0", 0.BP) + checkBinaryPoint(range"[?,10].2", 2.BP) + checkBinaryPoint(range"[?,10].?", UBP()) + } + "malformed ranges will throw ChiselException or are compile time errors" in { + // must be a cleverer way to show this + intercept[ChiselException] { + range"[19,5]" + } + assertDoesNotCompile(""" range"?,10] """) + assertDoesNotCompile(""" range"?,? """) + } + } + } + + "Ranges can be specified for UInt, SInt, and FixedPoint" - { + "invalid range specifiers should fail at compile time" in { + assertDoesNotCompile(""" range"" """) + assertDoesNotCompile(""" range"[]" """) + assertDoesNotCompile(""" range"0" """) + assertDoesNotCompile(""" range"[0]" """) + assertDoesNotCompile(""" range"[0, 1" """) + assertDoesNotCompile(""" range"0, 1]" """) + assertDoesNotCompile(""" range"[0, 1, 2]" """) + assertDoesNotCompile(""" range"[a]" """) + assertDoesNotCompile(""" range"[a, b]" """) + assertCompiles(""" range"[0, 1]" """) // syntax sanity check + } + + "range macros should allow open and closed bounds" in { + range"[-1, 1)" should be(range"[-1,1).0") + range"[-1, 1)" should be(IntervalRange(C(-1), O(1), 0.BP)) + range"[-1, 1]" should be(IntervalRange(C(-1), C(1), 0.BP)) + range"(-1, 1]" should be(IntervalRange(O(-1), C(1), 0.BP)) + range"(-1, 1)" should be(IntervalRange(O(-1), O(1), 0.BP)) + } + + "range specifiers should be whitespace tolerant" in { + range"[-1,1)" should be(IntervalRange(C(-1), O(1), 0.BP)) + range" [-1,1) " should be(IntervalRange(C(-1), O(1), 0.BP)) + range" [ -1 , 1 ) " should be(IntervalRange(C(-1), O(1), 0.BP)) + range" [ -1 , 1 ) " should be(IntervalRange(C(-1), O(1), 0.BP)) + } + + "range macros should work with interpolated variables" in { + val a = 10 + val b = -3 + + range"[$b, $a)" should be(IntervalRange(C(b), O(a), 0.BP)) + range"[${a + b}, $a)" should be(IntervalRange(C(a + b), O(a), 0.BP)) + range"[${-3 - 7}, ${-3 + a})" should be(IntervalRange(C(-10), O(-3 + a), 0.BP)) + + def number(n: Int): Int = n + + range"[${number(1)}, ${number(3)})" should be(IntervalRange(C(1), O(3), 0.BP)) + } + + "UInt should get the correct width from a range" in { + UInt(range"[0, 8)").getWidth should be(3) + UInt(range"[0, 8]").getWidth should be(4) + UInt(range"[0, 0]").getWidth should be(1) + } + + "SInt should get the correct width from a range" in { + SInt(range"[0, 8)").getWidth should be(4) + SInt(range"[0, 8]").getWidth should be(5) + SInt(range"[-4, 4)").getWidth should be(3) + SInt(range"[0, 0]").getWidth should be(1) + } + + "UInt should check that the range is valid" in { + an[ChiselException] should be thrownBy { + UInt(range"[1, 0]") + } + an[ChiselException] should be thrownBy { + UInt(range"[-1, 1]") + } + an[ChiselException] should be thrownBy { + UInt(range"(0,0]") + } + an[ChiselException] should be thrownBy { + UInt(range"[0,0)") + } + an[ChiselException] should be thrownBy { + UInt(range"(0,0)") + } + an[ChiselException] should be thrownBy { + UInt(range"(0,1)") + } + } + + "SInt should check that the range is valid" in { + an[ChiselException] should be thrownBy { + SInt(range"[1, 0]") + } + an[ChiselException] should be thrownBy { + SInt(range"(0,0]") + } + an[ChiselException] should be thrownBy { + SInt(range"[0,0)") + } + an[ChiselException] should be thrownBy { + SInt(range"(0,0)") + } + an[ChiselException] should be thrownBy { + SInt(range"(0,1)") + } + } + } + + "shift operations should work on ranges" - { + "<<, shiftLeft affects the bounds but not the binary point" in { + checkRange(range"[0,7].1", C(0), C(7), 1.BP) + checkRange(range"[0,7].1" << 1, C(0), C(14), 1.BP) + + checkRange(range"[2,7].2", C(2), C(7), 2.BP) + checkRange(range"[2,7].2" << 1, C(4), C(14), 2.BP) + } + + ">>, shiftRight affects the bounds but not the binary point" in { + checkRange(range"[0,7].0", C(0), C(7), 0.BP) + checkRange(range"[0,7].0" >> 1, C(0), C(3), 0.BP) + + checkRange(range"[0,7].1", C(0), C(7), 1.BP) + checkRange(range"[0,7].1" >> 1, C(0), C(3.5), 1.BP) + + checkRange(range"[2,7].2", C(2), C(7), 2.BP) + checkRange(range"[2,7].2" >> 1, C(1), C(3.5), 2.BP) + + checkRange(range"[2,7].2", C(2), C(7), 2.BP) + checkRange(range"[2,7].2" >> 2, C(0.5), C(1.75), 2.BP) + + // the 7(b111) >> 3 => 0.875(b0.111) but since + // binary point is two, lopping must occur so 0.875 becomes 0.75 + checkRange(range"[-8,7].2", C(-8), C(7), 2.BP) + checkRange(range"[-8,7].2" >> 3, C(-1), C(0.75), 2.BP) + + + checkRange(range"(0,7).0", O(0), O(7), 0.BP) + checkRange(range"(0,7).0" >> 1, O(0), O(3), 0.BP) + + checkRange(range"(0,7).1", O(0), O(7), 1.BP) + checkRange(range"(0,7).1" >> 1, O(0), O(3.5), 1.BP) + + checkRange(range"(2,7).2", O(2), O(7), 2.BP) + checkRange(range"(2,7).2" >> 1, O(1), O(3.5), 2.BP) + + checkRange(range"(2,7).2", O(2), O(7), 2.BP) + checkRange(range"(2,7).2" >> 2, O(0.5), O(1.75), 2.BP) + + // the 7(b111) >> 3 => 0.875(b0.111) but since + // binary point is two, lopping must occur so 0.875 becomes 0.75 + checkRange(range"(-8,7).2", O(-8), O(7), 2.BP) + checkRange(range"(-8,7).2" >> 3, O(-1), O(0.75), 2.BP) + } + + "set precision can change the bounds due to precision loss, direction of change is always to lower value" in { + intercept[ChiselException] { + checkRange(range"[-7.875,7.875].3".setPrecision(UnknownBinaryPoint), C(-7.875), C(7.875), 5.BP) + } + + checkRange(range"[-7.875,7.875].3", C(-7.875), C(7.875), 3.BP) + checkRange(range"[1.25,2].2".setPrecision(1.BP), C(1.0), C(2), 1.BP) + checkRange(range"[-7.875,7.875].3".setPrecision(5.BP), C(-7.875), C(7.875), 5.BP) + checkRange(range"[-7.875,7.875].3".setPrecision(1.BP), C(-8.0), C(7.5), 1.BP) + } + } + } + +} diff --git a/src/test/scala/chiselTests/IntervalSpec.scala b/src/test/scala/chiselTests/IntervalSpec.scala new file mode 100644 index 00000000..863771a3 --- /dev/null +++ b/src/test/scala/chiselTests/IntervalSpec.scala @@ -0,0 +1,919 @@ +// See LICENSE for license details. + +package chiselTests + +import scala.language.reflectiveCalls +import _root_.firrtl.ir.{Closed, Open} +import chisel3._ +import chisel3.internal.firrtl.{IntervalRange, KnownBinaryPoint} +import chisel3.internal.sourceinfo.{SourceInfo, UnlocatableSourceInfo} +import chisel3.stage.{ChiselGeneratorAnnotation, ChiselStage} +import chisel3.testers.BasicTester +import cookbook.CookbookTester +import firrtl.options.TargetDirAnnotation +import firrtl.passes.CheckTypes.InvalidConnect +import firrtl.passes.CheckWidths.{DisjointSqueeze, InvalidRange} +import firrtl.passes.{PassExceptions, WrapWithRemainder} +import firrtl.stage.{CompilerAnnotation, FirrtlCircuitAnnotation} +import firrtl.{FIRRTLException, HighFirrtlCompiler, LowFirrtlCompiler, MiddleFirrtlCompiler, MinimumVerilogCompiler, NoneCompiler, SystemVerilogCompiler, VerilogCompiler} +import org.scalatest.{FreeSpec, Matchers} + +//scalastyle:off magic.number +//noinspection TypeAnnotation + +object IntervalTestHelper { + + /** Compiles a Chisel Module to Verilog + * NOTE: This uses the "test_run_dir" as the default directory for generated code. + * @param compilerName the generator for the module + * @param gen the generator for the module + * @return the Verilog code as a string. + */ + //scalastyle:off cyclomatic.complexity + def makeFirrtl[T <: RawModule](compilerName: String)(gen: () => T): String = { + val c = compilerName match { + case "none" => new NoneCompiler() + case "high" => new HighFirrtlCompiler() + case "lo" => new LowFirrtlCompiler() + case "low" => new LowFirrtlCompiler() + case "middle" => new MiddleFirrtlCompiler() + case "verilog" => new VerilogCompiler() + case "mverilog" => new MinimumVerilogCompiler() + case "sverilog" => new SystemVerilogCompiler() + case _ => + throw new Exception( + s"Unknown compiler name '$compilerName'! (Did you misspell it?)" + ) + } + val compiler = CompilerAnnotation(c) + val annotations = Seq(new ChiselGeneratorAnnotation(gen), TargetDirAnnotation("test_run_dir/IntervalSpec"), compiler) + val processed = (new ChiselStage).run(annotations) + processed.collectFirst { case FirrtlCircuitAnnotation(source) => source } match { + case Some(circuit) => circuit.serialize + case _ => + throw new Exception( + s"makeFirrtl($compilerName) failed to generate firrtl circuit" + ) + } + } +} + +import chiselTests.IntervalTestHelper.makeFirrtl +import chisel3.experimental._ +import chisel3.experimental.Interval + +class IntervalTest1 extends Module { + val io = IO(new Bundle { + val in1 = Input(Interval(range"[0,4]")) + val in2 = Input(Interval(range"[0,4].3")) + val out = Output(Interval(range"[0,8].3")) + }) + + io.out := io.in1 + io.in2 +} + +class IntervalTester extends CookbookTester(10) { + + val dut = Module(new IntervalTest1) + + dut.io.in1 := BigInt(4).I + dut.io.in2 := 4.I + assert(dut.io.out === 8.I) + + val i = Interval(range"[0,10)") + stop() +} + +class IntervalTest2 extends Module { + val io = IO(new Bundle { + val p = Input(Bool()) + val in1 = Input(Interval(range"[0,4]")) + val in2 = Input(Interval(range"[0,6]")) + val out = Output(Interval()) + }) + + io.out := Mux(io.p, io.in1, io.in2) +} + +class IntervalTester2 extends CookbookTester(10) { + + val dut = Module(new IntervalTest2) + + dut.io.p := 1.U + dut.io.in1 := 4.I + dut.io.in2 := 5.I + assert(dut.io.out === 4.I) + + stop() +} + +class IntervalAddTester extends BasicTester { + + val in1 = Wire(Interval(range"[0,4]")) + val in2 = Wire(Interval(range"[0,4]")) + + in1 := 2.I + in2 := 2.I + + 5.U + + val result = in1 +& in2 + + assert(result === 4.I) + + stop() + +} + +class IntervalSetBinaryPointTester extends BasicTester { + implicit val sourceinfo: SourceInfo = UnlocatableSourceInfo + val in1 = Wire(Interval(range"[0,4].4")) + val in2 = in1.setPrecision(2) + + assert(in2.binaryPoint == KnownBinaryPoint(2)) + + in1 := 2.I + + val shiftedLeft = in1.increasePrecision(2) + + assert( + shiftedLeft.binaryPoint == KnownBinaryPoint(6), + s"Error: increasePrecision result ${shiftedLeft.range} expected bt = 2" + ) + + val shiftedRight = in1.decreasePrecision(2) + + assert( + shiftedRight.binaryPoint == KnownBinaryPoint(2), + s"Error: increasePrecision result ${shiftedRight.range} expected bt = 2" + ) + + stop() +} + +class MoreIntervalShiftTester extends BasicTester { + implicit val sourceinfo: SourceInfo = UnlocatableSourceInfo + + val in1 = Wire(Interval(range"[0,4].4")) + val in2 = in1.setPrecision(2) + + assert(in2.binaryPoint == KnownBinaryPoint(2)) + + val toShiftLeft = Wire(Interval(range"[0,4].4")) + val shiftedLeft = in1.increasePrecision(2) + + assert( + shiftedLeft.binaryPoint == KnownBinaryPoint(2), + s"Error: decreasePrecision result ${shiftedLeft.range} expected bt = 2" + ) + + val toShiftRight = Wire(Interval(range"[0,4].4")) + val shiftedRight = in1.decreasePrecision(2) + + assert( + shiftedRight.binaryPoint == KnownBinaryPoint(6), + s"Error: decreasePrecision result ${shiftedRight.range} expected bt = 2" + ) + + stop() +} + +/** + * This is a reality check not a test. Makes it easier to figure out + * what is going on in other places + * @param range a range for inputs + * @param targetRange a range for outputs + * @param startNum start here + * @param endNum end here + * @param incNum increment by this + */ +class ClipSqueezeWrapDemo(range: IntervalRange, + targetRange: IntervalRange, + startNum: Double, + endNum: Double, + incNum: Double) + extends BasicTester { + + val binaryPointAsInt = range.binaryPoint.asInstanceOf[KnownBinaryPoint].value +// val startValue = Interval.fromDouble(startNum, binaryPoint = binaryPointAsInt) +// val increment = Interval.fromDouble(incNum, binaryPoint = binaryPointAsInt) +// val endValue = Interval.fromDouble(endNum, binaryPoint = binaryPointAsInt) + val startValue = startNum.I(range.binaryPoint) + val increment = incNum.I(range.binaryPoint) + val endValue = endNum.I(range.binaryPoint) + + val counter = RegInit(Interval(range), startValue) + + counter := (counter + increment).squeeze(counter) + when(counter > endValue) { + stop() + } + + val clipped = counter.clip(0.U.asInterval(targetRange)) + val squeezed = counter.squeeze(0.U.asInterval(targetRange)) + val wrapped = counter.wrap(0.U.asInterval(targetRange)) + + when(counter === startValue) { + printf(s"Target range is $range\n") + printf("value clip squeeze wrap\n") + } + + printf( + " %d %d %d %d\n", + counter.asSInt(), + clipped.asSInt(), + squeezed.asSInt(), + wrapped.asSInt() + ) +} + +class SqueezeFunctionalityTester(range: IntervalRange, + startNum: BigDecimal, + endNum: BigDecimal, + increment: BigDecimal) + extends BasicTester { + + val counter = RegInit(0.U(10.W)) + counter := counter + 1.U + when(counter > 10.U) { + stop() + } + + val squeezeInterval = Wire(Interval(range)) + squeezeInterval := 0.I + + val squeezeTemplate = Wire(Interval(range)) + + val ss = WireInit(Interval(range), (-10).S.asInterval(range)) + + val toSqueeze = counter.asInterval(range) - ss + + squeezeTemplate := toSqueeze.squeeze(squeezeInterval) + + printf( + s"SqueezeTest %d %d.squeeze($range) => %d\n", + counter, + toSqueeze.asSInt(), + squeezeTemplate.asSInt() + ) +} + +/** + * Demonstrate a simple counter register with an Interval type + */ +class IntervalRegisterTester extends BasicTester { + + val range = range"[-2,5]" + val counter = RegInit(Interval(range), (-1).I) + counter := (counter + 1.I) + .squeeze(counter) // this works with other types, why not Interval + when(counter > 4.I) { + stop() + } +} + +//noinspection ScalaStyle +class IntervalWrapTester extends BasicTester { + + val t1 = Wire(Interval(range"[-2, 12]")) + t1 := (-2).I + val u1 = 0.U(3.W) + val r1 = RegInit(u1) + r1 := u1 + val t2 = t1.wrap(u1) + val t3 = t1.wrap(r1) + + assert( + t2.range.upper == Closed(7), + s"t1 upper ${t2.range.upper} expected ${Closed(7)}" + ) + assert( + t3.range.upper == Closed(7), + s"t1 upper ${t3.range.upper} expected ${Closed(7)}" + ) + + val in1 = WireInit(Interval(range"[0,9].6"), 0.I) + val in2 = WireInit(Interval(range"[1,6).4"), 2.I) + val in3 = in1.wrap(in2) + + assert( + in3.range.lower == Closed(1), + s"in3 lower ${in3.range.lower} expected ${Closed(1)}" + ) + assert( + in3.range.upper == Open(6), + s"in3 upper ${in3.range.upper} expected ${Open(6)}" + ) + assert( + in3.binaryPoint == KnownBinaryPoint(6), + s"in3 binaryPoint ${in3.binaryPoint} expected ${KnownBinaryPoint(2)}" + ) + + val enclosedRange = range"[-2, 5]" + val base = Wire(Interval(range"[-4, 6]")) + val enclosed = WireInit(Interval(enclosedRange), 0.I) + val enclosing = WireInit(Interval(range"[-6, 8]"), 0.I) + val overlapLeft = WireInit(Interval(range"[-10,-2]"), (-3).I) + val overlapRight = WireInit(Interval(range"[-1,10]"), 0.I) + + val w1 = base.wrap(enclosed) + val w2 = base.wrap(enclosing) + val w3 = base.wrap(overlapLeft) + val w4 = base.wrap(overlapRight) + val w7 = base.wrap(enclosedRange) + + base := 6.I + + assert(w1 === (-2).I) + assert(w2 === 6.I) + assert(w3 === (-3).I) + assert(w4 === 6.I) + assert(w7 === (-2).I) + + stop() +} + +class IntervalClipTester extends BasicTester { + + val enclosedRange = range"[-2, 5]" + val base = Wire(Interval(range"[-4, 6]")) + val enclosed = Wire(Interval(enclosedRange)) + val enclosing = Wire(Interval(range"[-6, 8]")) + val overlapLeft = Wire(Interval(range"[-10,-2]")) + val overlapRight = Wire(Interval(range"[-1,10]")) + val disjointLeft = Wire(Interval(range"[-14,-7]")) + val disjointRight = Wire(Interval(range"[7,11]")) + + enclosed := DontCare + enclosing := DontCare + overlapLeft := DontCare + overlapRight := DontCare + disjointLeft := DontCare + disjointRight := DontCare + + val enclosedResult = base.clip(enclosed) + val enclosingResult = base.clip(enclosing) + val overlapLeftResult = base.clip(overlapLeft) + val overlapRightResult = base.clip(overlapRight) + val disjointLeftResult = base.clip(disjointLeft) + val disjointRightResult = base.clip(disjointRight) + val enclosedViaRangeString = base.clip(enclosedRange) + + base := 6.I + + assert(enclosedResult === 5.I) + assert(enclosingResult === 6.I) + assert(overlapLeftResult === (-2).I) + assert(overlapRightResult === 6.I) + assert(disjointLeftResult === (-7).I) + assert(disjointRightResult === 7.I) + + assert(enclosedViaRangeString === 5.I) + + stop() +} + +class IntervalChainedAddTester extends BasicTester { + + val intervalResult = Wire(Interval()) + val uintResult = Wire(UInt()) + + intervalResult := 1.I + 1.I + 1.I + 1.I + 1.I + 1.I + 1.I + uintResult := 1.U +& 1.U +& 1.U +& 1.U +& 1.U +& 1.U +& 1.U + + assert(intervalResult === 7.I) + assert(uintResult === 7.U) + stop() +} + +class IntervalChainedMulTester extends BasicTester { + + val intervalResult = Wire(Interval()) + val uintResult = Wire(UInt()) + + intervalResult := 2.I * 2.I * 2.I * 2.I * 2.I * 2.I * 2.I + uintResult := 2.U * 2.U * 2.U * 2.U * 2.U * 2.U * 2.U + + assert(intervalResult === 128.I) + assert(uintResult === 128.U) + stop() +} + +class IntervalChainedSubTester extends BasicTester { + val intervalResult1 = Wire(Interval()) + val intervalResult2 = Wire(Interval()) + val uIntResult = Wire(UInt()) + val sIntResult = Wire(SInt()) + val fixedResult = Wire(FixedPoint()) + + intervalResult1 := 17.I - 2.I - 2.I - 2.I - 2.I - 2.I - 2.I // gives same result as -& operand version below + intervalResult2 := 17.I -& 2.I -& 2.I -& 2.I -& 2.I -& 2.I -& 2.I + uIntResult := 17.U -& 2.U -& 2.U -& 2.U -& 2.U -& 2.U -& 2.U + fixedResult := 17.0.F(0.BP) -& 2.0.F(0.BP) -& 2.0.F(0.BP) -& 2.0.F(0.BP) -& 2.0 + .F(0.BP) -& 2.0.F(0.BP) -& 2.0.F(0.BP) + sIntResult := 17.S -& 2.S -& 2.S -& 2.S -& 2.S -& 2.S -& 2.S + + assert(uIntResult === 5.U) + assert(sIntResult === 5.S) + assert(fixedResult.asUInt === 5.U) + assert(intervalResult1 === 5.I) + assert(intervalResult2 === 5.I) + + stop() +} + +//TODO: need tests for dynamic shifts on intervals +class IntervalSpec extends FreeSpec with Matchers with ChiselRunners { + + type TempFirrtlException = Exception + + "Test a simple interval add" in { + assertTesterPasses { new IntervalAddTester } + } + "Intervals can be created" in { + assertTesterPasses { new IntervalTester } + } + "Test a simple interval mux" in { + assertTesterPasses { new IntervalTester2 } + } + "Intervals can have binary points set" in { + assertTesterPasses { new IntervalSetBinaryPointTester } + } + "Interval literals that don't fit in explicit ranges are caught by chisel" - { + "case 1: does not fit in specified width" in { + intercept[ChiselException] { + ChiselGeneratorAnnotation( + () => + new BasicTester { + val x = 5.I(3.W, 0.BP) + } + ).elaborate + } + } + "case 2: doesn't fit in specified range" in { + intercept[ChiselException] { + ChiselGeneratorAnnotation( + () => + new BasicTester { + val x = 5.I(range"[0,4]") + } + ).elaborate + } + } + } + + "Let's take a look at the results of squeeze over small range" in { + assertTesterPasses { + new ClipSqueezeWrapDemo( + range = range"[-10,33].0", + targetRange = range"[-4,17].0", + startNum = -4.0, + endNum = 30.0, + incNum = 1.0 + ) + } + assertTesterPasses { + new ClipSqueezeWrapDemo( + range = range"[-2,5].1", + targetRange = range"[-1,3].1", + startNum = -2.0, + endNum = 5.0, + incNum = 0.5 + ) + } + } + "Intervals can be squeezed into another intervals range" in { + assertTesterPasses { + new SqueezeFunctionalityTester( + range"[-2,5]", + BigDecimal(-10), + BigDecimal(10), + BigDecimal(1.0) + ) + } + } + "Intervals can be wrapped with wrap operator" in { + assertTesterPasses { new IntervalWrapTester } + } + + "Interval compile pathologies: clip, wrap, and squeeze have different behavior" - { + "wrap target range is completely left of source" in { + intercept[TempFirrtlException] { + assertTesterPasses(new BasicTester { + val base = Wire(Interval(range"[-4, 6]")) + base := 6.I + val disjointLeft = WireInit(Interval(range"[-7,-5]"), (-6).I) + val w5 = base.wrap(disjointLeft) + stop() + }) + } + } + "wrap target range is completely right of source" in { + intercept[TempFirrtlException] { + assertTesterPasses(new BasicTester { + val base = Wire(Interval(range"[-4, 6]")) + base := 6.I + val disjointLeft = WireInit(Interval(range"[7,10]"), 8.I) + val w5 = base.wrap(disjointLeft) + stop() + }) + } + } + "clip target range is completely left of source" in { + assertTesterPasses(new BasicTester { + val base = Wire(Interval(range"[-4, 6]")) + base := 6.I + val disjointLeft = WireInit(Interval(range"[-7,-5]"), (-6).I) + val w5 = base.clip(disjointLeft) + chisel3.assert(w5 === (-5).I) + stop() + }) + } + "clip target range is completely right of source" in { + assertTesterPasses(new BasicTester { + val base = Wire(Interval(range"[-4, 6]")) + base := 6.I + val disjointLeft = WireInit(Interval(range"[7,10]"), 8.I) + val w5 = base.clip(disjointLeft) + chisel3.assert(w5.asSInt === 7.S) + stop() + }) + } + "squeeze target range is completely right of source" in { + intercept[TempFirrtlException] { + assertTesterPasses(new BasicTester { + val base = Wire(Interval(range"[-4, 6]")) + base := 6.I + val disjointLeft = WireInit(Interval(range"[7,10]"), 8.I) + val w5 = base.squeeze(disjointLeft) + chisel3.assert(w5.asSInt === 6.S) + stop() + }) + } + } + "squeeze target range is completely left of source" in { + intercept[TempFirrtlException] { + assertTesterPasses(new BasicTester { + val base = Wire(Interval(range"[-4, 6]")) + base := 6.I + val disjointLeft = WireInit(Interval(range"[-7, -5]"), 8.I) + val w5 = base.squeeze(disjointLeft) + stop() + }) + } + } + + def makeCircuit(operation: String, + sourceRange: IntervalRange, + targetRange: IntervalRange): () => RawModule = { () => + new Module { + val io = IO(new Bundle { val out = Output(Interval()) }) + val base = Wire(Interval(sourceRange)) + base := 6.I + + val disjointLeft = WireInit(Interval(targetRange), 8.I) + val w5 = operation match { + case "clip" => base.clip(disjointLeft) + case "wrap" => base.wrap(disjointLeft) + case "squeeze" => base.squeeze(disjointLeft) + } + io.out := w5 + } + } + + "disjoint ranges should error when used with clip, wrap and squeeze" - { + + def mustGetException(disjointLeft: Boolean, + operation: String): Boolean = { + val (rangeA, rangeB) = if (disjointLeft) { + (range"[-4, 6]", range"[7,10]") + } else { + (range"[7,10]", range"[-4, 6]") + } + try { + makeFirrtl("low")(makeCircuit(operation, rangeA, rangeB)) + false + } catch { + case _: InvalidConnect | _: PassExceptions | _: InvalidRange | _: WrapWithRemainder | _: DisjointSqueeze => + true + case _: Throwable => + false + } + } + + "Range A disjoint left, operation clip should generate useful error" in { + mustGetException(disjointLeft = true, "clip") should be(false) + } + "Range A largely out of bounds left, operation wrap should generate useful error" in { + mustGetException(disjointLeft = true, "wrap") should be(true) + } + "Range A disjoint left, operation squeeze should generate useful error" in { + mustGetException(disjointLeft = true, "squeeze") should be(true) + } + "Range A disjoint right, operation clip should generate useful error" in { + mustGetException(disjointLeft = false, "clip") should be(true) + } + "Range A disjoint right, operation wrap should generate useful error" in { + mustGetException(disjointLeft = false, "wrap") should be(true) + } + "Range A disjoint right, operation squeeze should generate useful error" in { + mustGetException(disjointLeft = false, "squeeze") should be(true) + } + } + + "Errors are sometimes inconsistent or incorrectly labelled as Firrtl Internal Error" - { + "squeeze disjoint is not internal error when defined in BasicTester" in { + intercept[DisjointSqueeze] { + makeFirrtl("low")( + () => + new BasicTester { + val base = Wire(Interval(range"[-4, 6]")) + val base2 = Wire(Interval(range"[-4, 6]")) + base := 6.I + base2 := 5.I + val disjointLeft = WireInit(Interval(range"[7,10]"), 8.I) + val w5 = base.squeeze(disjointLeft) + stop() + } + ) + } + } + "wrap disjoint is not internal error when defined in BasicTester" in { + intercept[DisjointSqueeze] { + makeFirrtl("low")( + () => + new BasicTester { + val base = Wire(Interval(range"[-4, 6]")) + val base2 = Wire(Interval(range"[-4, 6]")) + base := 6.I + base2 := 5.I + val disjointLeft = WireInit(Interval(range"[7,10]"), 8.I) + val w5 = base.squeeze(disjointLeft) + stop() + } + ) + } + } + "squeeze disjoint from Module gives exception" in { + intercept[DisjointSqueeze] { + makeFirrtl("lo")( + () => + new Module { + val io = IO(new Bundle { + val out = Output(Interval()) + }) + val base = Wire(Interval(range"[-4, 6]")) + base := 6.I + + val disjointLeft = WireInit(Interval(range"[7,10]"), 8.I) + val w5 = base.squeeze(disjointLeft) + io.out := w5 + } + ) + } + } + "clip disjoint from Module gives no error" in { + makeFirrtl("lo")( + () => + new Module { + val io = IO(new Bundle { + val out = Output(Interval()) + }) + val base = Wire(Interval(range"[-4, 6]")) + base := 6.I + + val disjointLeft = WireInit(Interval(range"[7,10]"), 8.I) + val w5 = base.clip(disjointLeft) + io.out := w5 + } + ) + } + "wrap disjoint from Module wrap with remainder" in { + intercept[WrapWithRemainder] { + makeFirrtl("lo")( + () => + new Module { + val io = IO(new Bundle { + val out = Output(Interval()) + }) + val base = Wire(Interval(range"[-4, 6]")) + base := 6.I + + val disjointLeft = WireInit(Interval(range"[7,10]"), 8.I) + val w5 = base.wrap(disjointLeft) + io.out := w5 + } + ) + } + } + } + + "assign literal out of range of interval" in { + intercept[firrtl.passes.CheckTypes.InvalidConnect] { + assertTesterPasses(new BasicTester { + val base = Wire(Interval(range"[-4, 6]")) + base := (-8).I + }) + } + } + } + + "Intervals should catch assignment of literals outside of range" - { + "when literal is too small" in { + intercept[InvalidConnect] { + makeFirrtl("lo")( + () => + new Module { + val io = IO(new Bundle { val out = Output(Interval()) }) + val base = Wire(Interval(range"[-4, 6]")) + base := (-7).I + io.out := base + } + ) + } + } + "when literal is too big" in { + intercept[InvalidConnect] { + makeFirrtl("low")( + () => + new Module { + val io = IO(new Bundle { val out = Output(Interval()) }) + val base = Wire(Interval(range"[-4, 6]")) + base := 9.I + io.out := base + } + ) + } + } + } + + "Intervals can be shifted left" in { + assertTesterPasses(new BasicTester { + val i1 = 3.0.I(range"[0,4]") + val shifted1 = i1 << 2 + val shiftUInt = WireInit(1.U(8.W)) + val shifted2 = i1 << shiftUInt + + chisel3.assert(shifted1 === 12.I, "shifted 1 should be 12, it wasn't") + chisel3.assert(shifted2 === 6.I, "shifted 2 should be 6 it wasn't") + stop() + }) + } + + "Intervals can be shifted right" in { + assertTesterPasses(new BasicTester { + val i1 = 12.0.I(range"[0,15]") + val shifted1 = i1 >> 2 + val shiftUInt = 1.U + val shifted2 = i1 >> shiftUInt + + chisel3.assert(shifted1 === 3.I) + chisel3.assert(shifted2 === 6.I) + stop() + }) + } + + "Intervals can be used to construct registers" in { + assertTesterPasses { new IntervalRegisterTester } + } + "Intervals can be clipped with clip (saturate) operator" in { + assertTesterPasses { new IntervalClipTester } + } + "Intervals adds same answer as UInt" in { + assertTesterPasses { new IntervalChainedAddTester } + } + "Intervals should produce canonically smaller ranges via inference" in { + val loFirrtl = makeFirrtl("low")( + () => + new Module { + val io = IO(new Bundle { + val in = Input(Interval(range"[0,1]")) + val out = Output(Interval()) + }) + + val intervalResult = Wire(Interval()) + + intervalResult := 1.I + 1.I + 1.I + 1.I + 1.I + 1.I + 1.I + io.out := intervalResult + } + ) + loFirrtl.contains("output io_out : SInt<4>") should be(true) + + } + "Intervals multiplication same answer as UInt" in { + assertTesterPasses { new IntervalChainedMulTester } + } + "Intervals subs same answer as UInt" in { + assertTesterPasses { new IntervalChainedSubTester } + } + "Test clip, wrap and a variety of ranges" - { + """range"[0.0,10.0].2" => range"[2,6].2"""" in { + assertTesterPasses(new BasicTester { + + val sourceRange = range"[0.0,10.0].2" + val targetRange = range"[2,6].2" + + val sourceSimulator = ScalaIntervalSimulator(sourceRange) + val targetSimulator = ScalaIntervalSimulator(targetRange) + + for (sourceValue <- sourceSimulator.allValues) { + val clippedValue = Wire(Interval(targetRange)) + clippedValue := sourceSimulator + .makeLit(sourceValue) + .clip(clippedValue) + + val goldClippedValue = + targetSimulator.makeLit(targetSimulator.clip(sourceValue)) + + // Useful for debugging + // printf(s"source value $sourceValue clipped gold value %d compare to clipped value %d\n", + // goldClippedValue.asSInt(), clippedValue.asSInt()) + + chisel3.assert(goldClippedValue === clippedValue) + + val wrappedValue = Wire(Interval(targetRange)) + wrappedValue := sourceSimulator + .makeLit(sourceValue) + .wrap(wrappedValue) + + val goldWrappedValue = + targetSimulator.makeLit(targetSimulator.wrap(sourceValue)) + + // Useful for debugging + // printf(s"source value $sourceValue wrapped gold value %d compare to wrapped value %d\n", + // goldWrappedValue.asSInt(), wrappedValue.asSInt()) + + chisel3.assert(goldWrappedValue === wrappedValue) + } + + stop() + }) + } + } + + "Test squeeze over a variety of ranges" - { + """range"[2,6].2""" in { + assertTesterPasses(new BasicTester { + + val sourceRange = range"[0.0,10.0].2" + val targetRange = range"[2,6].3" + + val sourceSimulator = ScalaIntervalSimulator(sourceRange) + val targetSimulator = ScalaIntervalSimulator(targetRange) + + for (sourceValue <- sourceSimulator.allValues) { + val squeezedValue = Wire(Interval(targetRange)) + squeezedValue := sourceSimulator + .makeLit(sourceValue) + .clip(squeezedValue) + + val goldSqueezedValue = + targetSimulator.makeLit(targetSimulator.clip(sourceValue)) + + // Useful for debugging + // printf(s"source value $sourceValue squeezed gold value %d compare to squeezed value %d\n", + // goldSqueezedValue.asSInt(), squeezedValue.asSInt()) + + chisel3.assert(goldSqueezedValue === squeezedValue) + } + + stop() + }) + } + } + + "test asInterval" - { + "use with UInt" in { + assertTesterPasses(new BasicTester { + val u1 = Wire(UInt(5.W)) + u1 := 7.U + val i1 = u1.asInterval(range"[0,15]") + val i2 = u1.asInterval(range"[0,15].2") + printf("i1 %d\n", i1.asUInt) + chisel3.assert(i1 === 7.I, "i1") + stop() + }) + } + "use with SInt" in { + assertTesterPasses(new BasicTester { + val s1 = Wire(SInt(5.W)) + s1 := 7.S + val s2 = Wire(SInt(5.W)) + s2 := 7.S + val i1 = s1.asInterval(range"[-16,15]") + val i2 = s1.asInterval(range"[-16,15].1") + printf("i1 %d\n", i1.asSInt) + printf("i2 %d\n", i2.asSInt) + chisel3.assert(i1 === 7.I, "i1 is wrong") + chisel3.assert(i2 === (3.5).I(binaryPoint = 1.BP), "i2 is wrong") + stop() + }) + } + "more SInt tests" in { + assertTesterPasses(new BasicTester { + chisel3.assert(7.S.asInterval(range"[-16,15].1") === 3.5.I(binaryPoint = 1.BP), "adding binary point") + stop() + }) + } + } +} diff --git a/src/test/scala/chiselTests/RangeSpec.scala b/src/test/scala/chiselTests/RangeSpec.scala index e2313f34..e85a477d 100644 --- a/src/test/scala/chiselTests/RangeSpec.scala +++ b/src/test/scala/chiselTests/RangeSpec.scala @@ -4,101 +4,9 @@ package chiselTests import chisel3._ import chisel3.experimental.ChiselRange - -import chisel3.internal.firrtl.{Open, Closed} -import org.scalatest.{Matchers, FreeSpec} +import chisel3.internal.firrtl._ +import firrtl.ir.{Closed, Open} +import org.scalatest.{FreeSpec, Matchers} class RangeSpec extends FreeSpec with Matchers { - "Ranges can be specified for UInt, SInt, and FixedPoint" - { - "invalid range specifiers should fail at compile time" in { - assertDoesNotCompile(""" range"" """) - assertDoesNotCompile(""" range"[]" """) - assertDoesNotCompile(""" range"0" """) - assertDoesNotCompile(""" range"[0]" """) - assertDoesNotCompile(""" range"[0, 1" """) - assertDoesNotCompile(""" range"0, 1]" """) - assertDoesNotCompile(""" range"[0, 1, 2]" """) - assertDoesNotCompile(""" range"[a]" """) - assertDoesNotCompile(""" range"[a, b]" """) - assertCompiles(""" range"[0, 1]" """) // syntax sanity check - } - - "range macros should allow open and closed bounds" in { - range"[-1, 1)" should be( (Closed(-1), Open(1)) ) - range"[-1, 1]" should be( (Closed(-1), Closed(1)) ) - range"(-1, 1]" should be( (Open(-1), Closed(1)) ) - range"(-1, 1)" should be( (Open(-1), Open(1)) ) - } - - "range specifiers should be whitespace tolerant" in { - range"[-1,1)" should be( (Closed(-1), Open(1)) ) - range" [-1,1) " should be( (Closed(-1), Open(1)) ) - range" [ -1 , 1 ) " should be( (Closed(-1), Open(1)) ) - range" [ -1 , 1 ) " should be( (Closed(-1), Open(1)) ) - } - - "range macros should work with interpolated variables" in { - val a = 10 - val b = -3 - - range"[$b, $a)" should be( (Closed(b), Open(a)) ) - range"[${a + b}, $a)" should be( (Closed(a + b), Open(a)) ) - range"[${-3 - 7}, ${-3 + a})" should be( (Closed(-10), Open(-3 + a)) ) - - def number(n: Int): Int = n - range"[${number(1)}, ${number(3)})" should be( (Closed(1), Open(3)) ) - } - - "UInt should get the correct width from a range" in { - UInt(range"[0, 8)").getWidth should be (3) - UInt(range"[0, 8]").getWidth should be (4) - UInt(range"[0, 0]").getWidth should be (1) - } - - "SInt should get the correct width from a range" in { - SInt(range"[0, 8)").getWidth should be (4) - SInt(range"[0, 8]").getWidth should be (5) - SInt(range"[-4, 4)").getWidth should be (3) - SInt(range"[0, 0]").getWidth should be (1) - } - - "UInt should check that the range is valid" in { - an [IllegalArgumentException] should be thrownBy { - UInt(range"[1, 0]") - } - an [IllegalArgumentException] should be thrownBy { - UInt(range"[-1, 1]") - } - an [IllegalArgumentException] should be thrownBy { - UInt(range"(0,0]") - } - an [IllegalArgumentException] should be thrownBy { - UInt(range"[0,0)") - } - an [IllegalArgumentException] should be thrownBy { - UInt(range"(0,0)") - } - an [IllegalArgumentException] should be thrownBy { - UInt(range"(0,1)") - } - } - - "SInt should check that the range is valid" in { - an [IllegalArgumentException] should be thrownBy { - SInt(range"[1, 0]") - } - an [IllegalArgumentException] should be thrownBy { - SInt(range"(0,0]") - } - an [IllegalArgumentException] should be thrownBy { - SInt(range"[0,0)") - } - an [IllegalArgumentException] should be thrownBy { - SInt(range"(0,0)") - } - an [IllegalArgumentException] should be thrownBy { - SInt(range"(0,1)") - } - } - } } diff --git a/src/test/scala/chiselTests/ScalaIntervalSimulatorTest.scala b/src/test/scala/chiselTests/ScalaIntervalSimulatorTest.scala new file mode 100644 index 00000000..0bf8741e --- /dev/null +++ b/src/test/scala/chiselTests/ScalaIntervalSimulatorTest.scala @@ -0,0 +1,96 @@ +// See README.md for license details. + +package chiselTests + +import chisel3._ +import chisel3.experimental._ +import org.scalatest.{FreeSpec, Matchers} + +class ScalaIntervalSimulatorSpec extends FreeSpec with Matchers { + "clip tests" - { + "Should work for closed ranges" in { + val sim = ScalaIntervalSimulator(range"[2,4]") + sim.clip(BigDecimal(1.0)) should be (2.0) + sim.clip(BigDecimal(2.0)) should be (2.0) + sim.clip(BigDecimal(3.0)) should be (3.0) + sim.clip(BigDecimal(4.0)) should be (4.0) + sim.clip(BigDecimal(5.0)) should be (4.0) + } + "Should work for closed ranges with binary point" in { + val sim = ScalaIntervalSimulator(range"[2,6].2") + sim.clip(BigDecimal(1.75)) should be (2.0) + sim.clip(BigDecimal(2.0)) should be (2.0) + sim.clip(BigDecimal(2.25)) should be (2.25) + sim.clip(BigDecimal(2.5)) should be (2.5) + sim.clip(BigDecimal(5.75)) should be (5.75) + sim.clip(BigDecimal(6.0)) should be (6.0) + sim.clip(BigDecimal(6.25)) should be (6.0) + sim.clip(BigDecimal(6.5)) should be (6.0) + sim.clip(BigDecimal(8.5)) should be (6.0) + } + "Should work for open ranges" in { + val sim = ScalaIntervalSimulator(range"(2,4)") + sim.clip(BigDecimal(1.0)) should be (3.0) + sim.clip(BigDecimal(2.0)) should be (3.0) + sim.clip(BigDecimal(3.0)) should be (3.0) + sim.clip(BigDecimal(4.0)) should be (3.0) + sim.clip(BigDecimal(5.0)) should be (3.0) + } + "Should work for open ranges with binary point" in { + val sim = ScalaIntervalSimulator(range"(2,6).2") + sim.clip(BigDecimal(1.75)) should be (2.25) + sim.clip(BigDecimal(2.0)) should be (2.25) + sim.clip(BigDecimal(2.25)) should be (2.25) + sim.clip(BigDecimal(2.5)) should be (2.5) + sim.clip(BigDecimal(5.75)) should be (5.75) + sim.clip(BigDecimal(6.0)) should be (5.75) + sim.clip(BigDecimal(6.25)) should be (5.75) + sim.clip(BigDecimal(6.5)) should be (5.75) + sim.clip(BigDecimal(8.5)) should be (5.75) + } + } + "wrap tests" - { + "Should work for closed ranges" in { + val sim = ScalaIntervalSimulator(range"[2,6]") + sim.wrap(BigDecimal(1.0)) should be (6.0) + sim.wrap(BigDecimal(2.0)) should be (2.0) + sim.wrap(BigDecimal(3.0)) should be (3.0) + sim.wrap(BigDecimal(4.0)) should be (4.0) + sim.wrap(BigDecimal(5.0)) should be (5.0) + sim.wrap(BigDecimal(6.0)) should be (6.0) + sim.wrap(BigDecimal(7.0)) should be (2.0) + } + "Should work for closed ranges with binary point" in { + val sim = ScalaIntervalSimulator(range"[2,6].2") + sim.wrap(BigDecimal(1.75)) should be (6.0) + sim.wrap(BigDecimal(2.0)) should be (2.0) + sim.wrap(BigDecimal(2.25)) should be (2.25) + sim.wrap(BigDecimal(2.5)) should be (2.5) + sim.wrap(BigDecimal(5.75)) should be (5.75) + sim.wrap(BigDecimal(6.0)) should be (6.0) + sim.wrap(BigDecimal(6.25)) should be (2.0) + sim.wrap(BigDecimal(6.5)) should be (2.25) + } + "Should work for open ranges" in { + val sim = ScalaIntervalSimulator(range"(2,6)") + sim.wrap(BigDecimal(1.0)) should be (4.0) + sim.wrap(BigDecimal(2.0)) should be (5.0) + sim.wrap(BigDecimal(3.0)) should be (3.0) + sim.wrap(BigDecimal(4.0)) should be (4.0) + sim.wrap(BigDecimal(5.0)) should be (5.0) + sim.wrap(BigDecimal(6.0)) should be (3.0) + sim.wrap(BigDecimal(7.0)) should be (4.0) + } + "Should work for open ranges with binary point" in { + val sim = ScalaIntervalSimulator(range"(2,6).2") + sim.wrap(BigDecimal(1.75)) should be (5.5) + sim.wrap(BigDecimal(2.0)) should be (5.75) + sim.wrap(BigDecimal(2.25)) should be (2.25) + sim.wrap(BigDecimal(2.5)) should be (2.5) + sim.wrap(BigDecimal(5.75)) should be (5.75) + sim.wrap(BigDecimal(6.0)) should be (2.25) + sim.wrap(BigDecimal(6.25)) should be (2.5) + sim.wrap(BigDecimal(7.0)) should be (3.25) + } + } +} diff --git a/src/test/scala/chiselTests/Util.scala b/src/test/scala/chiselTests/Util.scala index f71cd7f3..8c9bc4ea 100644 --- a/src/test/scala/chiselTests/Util.scala +++ b/src/test/scala/chiselTests/Util.scala @@ -5,6 +5,9 @@ package chiselTests import chisel3._ +import chisel3.experimental.Interval +import chisel3.internal.firrtl.{IntervalRange, KnownBinaryPoint, Width} +import _root_.firrtl.{ir => firrtlir} class PassthroughModuleIO extends Bundle { val in = Input(UInt(32.W)) @@ -20,4 +23,53 @@ class PassthroughModule extends Module with AbstractPassthroughModule class PassthroughMultiIOModule extends MultiIOModule with AbstractPassthroughModule class PassthroughRawModule extends RawModule with AbstractPassthroughModule +case class ScalaIntervalSimulator(intervalRange: IntervalRange) { + val binaryPoint: Int = intervalRange.binaryPoint.asInstanceOf[KnownBinaryPoint].value + val epsilon: Double = 1.0 / math.pow(2.0, binaryPoint.toDouble) + + val (lower, upper) = (intervalRange.lowerBound, intervalRange.upperBound) match { + + case (firrtlir.Closed(lower1), firrtlir.Closed(upper1)) => (lower1, upper1) + case (firrtlir.Closed(lower1), firrtlir.Open(upper1)) => (lower1, upper1 - epsilon) + case (firrtlir.Open(lower1), firrtlir.Closed(upper1)) => (lower1 + epsilon, upper1) + case (firrtlir.Open(lower1), firrtlir.Open(upper1)) => (lower1 + epsilon, upper1 - epsilon) + case _ => + throw new Exception(s"lower and upper bounds must be defined, range here is $intervalRange") + } + + def clip(value: BigDecimal): BigDecimal = { + + if (value < lower) { + lower + } + else if (value > upper) { + upper + } + else { + value + } + } + + def wrap(value: BigDecimal): BigDecimal = { + + if (value < lower) { + upper + (value - lower) + epsilon + } + else if (value > upper) { + ((value - upper) - epsilon) + lower + } + else { + value + } + } + + def allValues: Iterator[BigDecimal] = { + (lower to upper by epsilon).toIterator + } + + def makeLit(value: BigDecimal): Interval = { + Interval.fromDouble(value.toDouble, width = Width(), binaryPoint = binaryPoint.BP) + } +} + |
