From be239b9c70f73b71233263b89e4214e43001a6d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aleksandar=20Kondi=C4=87?= Date: Thu, 17 Aug 2023 20:39:01 +0200 Subject: [PATCH] Clean up API and add tests that use chiseltest.iotesters * Update fixedpoint library --- fixedpoint | 2 +- .../numbers/chisel_concrete/DspReal.scala | 82 +-- .../numbers/chisel_concrete/RealTrig.scala | 32 +- .../chisel_types/DspRealTypeClass.scala | 14 +- src/test/scala/dsptools/DspContextSpec.scala | 126 +++++ .../dsptools/DspTesterUtilitiesSpec.scala | 69 +++ .../dsptools/ShiftRegisterDelaySpec.scala | 151 ++++++ src/test/scala/dsptools/numbers/AbsSpec.scala | 91 ++++ .../scala/dsptools/numbers/BaseNSpec.scala | 31 ++ .../dsptools/numbers/BlackBoxFloatSpec.scala | 306 +++++++++++ .../dsptools/numbers/FixedPointSpec.scala | 225 ++++++++ .../numbers/FixedPrecisionChangerSpec.scala | 83 +++ src/test/scala/dsptools/numbers/LnSpec.scala | 31 ++ .../scala/dsptools/numbers/NumbersSpec.scala | 491 ++++++++++++++++++ .../numbers/ParameterizedOpSpec.scala | 156 ++++++ .../dsptools/numbers/TypeclassSpec.scala | 186 +++++++ src/test/scala/examples/DspComplexSpec.scala | 75 +++ .../examples/ParameterizedAdderSpec.scala | 6 +- .../ParameterizedSaturatingAdderSpec.scala | 89 ++++ src/test/scala/examples/RealAdderSpec.scala | 47 ++ .../examples/SimpleCaseClassBundleSpec.scala | 48 ++ .../SimpleComplexMultiplierSpec.scala | 60 +++ .../scala/examples/SimpleDspModuleSpec.scala | 80 +++ .../StreamingAutocorrelatorSpec.scala | 34 ++ .../examples/TransposedStreamFIRSpec.scala | 65 +++ 25 files changed, 2513 insertions(+), 67 deletions(-) create mode 100644 src/test/scala/dsptools/DspContextSpec.scala create mode 100644 src/test/scala/dsptools/DspTesterUtilitiesSpec.scala create mode 100644 src/test/scala/dsptools/ShiftRegisterDelaySpec.scala create mode 100644 src/test/scala/dsptools/numbers/AbsSpec.scala create mode 100644 src/test/scala/dsptools/numbers/BaseNSpec.scala create mode 100644 src/test/scala/dsptools/numbers/BlackBoxFloatSpec.scala create mode 100644 src/test/scala/dsptools/numbers/FixedPointSpec.scala create mode 100644 src/test/scala/dsptools/numbers/FixedPrecisionChangerSpec.scala create mode 100644 src/test/scala/dsptools/numbers/LnSpec.scala create mode 100644 src/test/scala/dsptools/numbers/NumbersSpec.scala create mode 100644 src/test/scala/dsptools/numbers/ParameterizedOpSpec.scala create mode 100644 src/test/scala/dsptools/numbers/TypeclassSpec.scala create mode 100644 src/test/scala/examples/DspComplexSpec.scala create mode 100644 src/test/scala/examples/ParameterizedSaturatingAdderSpec.scala create mode 100644 src/test/scala/examples/RealAdderSpec.scala create mode 100644 src/test/scala/examples/SimpleCaseClassBundleSpec.scala create mode 100644 src/test/scala/examples/SimpleComplexMultiplierSpec.scala create mode 100644 src/test/scala/examples/SimpleDspModuleSpec.scala create mode 100644 src/test/scala/examples/StreamingAutocorrelatorSpec.scala create mode 100644 src/test/scala/examples/TransposedStreamFIRSpec.scala diff --git a/fixedpoint b/fixedpoint index 3d5ae17e..35dda166 160000 --- a/fixedpoint +++ b/fixedpoint @@ -1 +1 @@ -Subproject commit 3d5ae17eb6dd90353b3d825761605df355f73560 +Subproject commit 35dda166f58f021cc32d00a2e76a5a33691c2b20 diff --git a/src/main/scala/dsptools/numbers/chisel_concrete/DspReal.scala b/src/main/scala/dsptools/numbers/chisel_concrete/DspReal.scala index bca48a2c..57f3e067 100644 --- a/src/main/scala/dsptools/numbers/chisel_concrete/DspReal.scala +++ b/src/main/scala/dsptools/numbers/chisel_concrete/DspReal.scala @@ -77,19 +77,19 @@ class DspReal() extends Bundle { twoOperandBool(arg1, Module(new BBFNotEquals())) } - def ln(dummy: Int = 0): DspReal = { + def ln: DspReal = { oneOperandOperator(Module(new BBFLn())) } - def log10(dummy: Int = 0): DspReal = { + def log10: DspReal = { oneOperandOperator(Module(new BBFLog10())) } - def exp(dummy: Int = 0): DspReal = { + def exp: DspReal = { oneOperandOperator(Module(new BBFExp())) } - def sqrt(dummy: Int = 0): DspReal = { + def sqrt: DspReal = { oneOperandOperator(Module(new BBFSqrt())) } @@ -97,21 +97,21 @@ class DspReal() extends Bundle { twoOperandOperator(arg1, Module(new BBFPow())) } - def floor(dummy: Int = 0): DspReal = { + def floor: DspReal = { oneOperandOperator(Module(new BBFFloor())) } - def ceil(dummy: Int = 0): DspReal = { + def ceil: DspReal = { oneOperandOperator(Module(new BBFCeil())) } - def round(): DspReal = (this + DspReal(0.5)).floor() + def round: DspReal = (this + DspReal(0.5)).floor - def truncate(): DspReal = { - Mux(this < DspReal(0.0), this.ceil(), this.floor()) + def truncate: DspReal = { + Mux(this < DspReal(0.0), this.ceil, this.floor) } - def abs(): DspReal = Mux(this < DspReal(0.0), DspReal(0.0) - this, this) + def abs: DspReal = Mux(this < DspReal(0.0), DspReal(0.0) - this, this) // Assumes you're using chisel testers private def backendIsVerilator: Boolean = true @@ -131,7 +131,7 @@ class DspReal() extends Bundle { // Swept in increments of 0.0001pi, and got ~11 decimal digits of accuracy // Can add more half angle recursion for more precision //scalastyle:off magic.number - def sin(dummy: Int = 0): DspReal = { + def sin: DspReal = { if (backendIsVerilator) { // Taylor series; Works best close to 0 (-pi/2, pi/2) -- so normalize! def sinPiOver2(in: DspReal): DspReal = { @@ -143,7 +143,7 @@ class DspReal() extends Bundle { val terms = TrigUtility.sinCoeff(nmax).zip(xpow).map { case ((c, scale), x) => DspReal(c) * x / DspReal(scale) } terms.reduceRight(_ + _) * in } - val num2Pi = (this / twoPi).truncate() + val num2Pi = (this / twoPi).truncate // Repeats every 2*pi, so normalize to -pi, pi val normalized2Pi = this - num2Pi * twoPi val temp1 = Mux(normalized2Pi > pi, normalized2Pi - twoPi, normalized2Pi) @@ -182,19 +182,19 @@ class DspReal() extends Bundle { } } - def cos(dummy: Int = 0): DspReal = { - if (backendIsVerilator) (this + halfPi).sin() + def cos: DspReal = { + if (backendIsVerilator) (this + halfPi).sin else oneOperandOperator(Module(new BBFCos())) } // Swept input at 0.0001pi increments. For tan < 1e9, ~8 decimal digit precision (fractional) // WARNING: tan blows up (more likely to be wrong when abs is close to pi/2) - def tan(dummy: Int = 0): DspReal = { + def tan: DspReal = { if (backendIsVerilator) { def tanPiOver2(in: DspReal): DspReal = { - in.sin() / in.cos() + in.sin / in.cos } - val numPi = (this / pi).truncate() + val numPi = (this / pi).truncate // Repeats every pi, so normalize to -pi/2, pi/2 // tan(x + pi) = tan(x) val normalizedPi = this - numPi * pi @@ -208,7 +208,7 @@ class DspReal() extends Bundle { // Correct to 9 decimal digits sweeping by 0.0001pi // See http://myweb.lmu.edu/hmedina/papers/reprintmonthly156-161-medina.pdf - def atan(dummy: Int = 0): DspReal = { + def atan: DspReal = { if (backendIsVerilator) { def arctanPiOver2(in: DspReal): DspReal = { val m = TrigUtility.atanM @@ -223,7 +223,7 @@ class DspReal() extends Bundle { } val isNeg = this.signBit // arctan(-x) = -arctan(x) - val inTemp = this.abs() + val inTemp = this.abs // arctan(x) = pi/2 - arctan(1/x) for x > 0 // Approximation accuracy in [0, 1] val outTemp = Mux(inTemp > one, halfPi - arctanPiOver2(one / inTemp), arctanPiOver2(inTemp)) @@ -234,18 +234,18 @@ class DspReal() extends Bundle { // See https://en.wikipedia.org/wiki/Inverse_trigonometric_functions // Must be -1 <= x <= 1 - def asin(dummy: Int = 0): DspReal = { + def asin: DspReal = { if (backendIsVerilator) { val sqrtIn = one - (this * this) - val atanIn = this / (one + sqrtIn.sqrt()) - DspReal(2) * atanIn.atan() + val atanIn = this / (one + sqrtIn.sqrt) + DspReal(2) * atanIn.atan } else oneOperandOperator(Module(new BBFASin())) } // Must be -1 <= x <= 1 - def acos(dummy: Int = 0): DspReal = { + def acos: DspReal = { if (backendIsVerilator) { - halfPi - this.asin() + halfPi - this.asin } else oneOperandOperator(Module(new BBFACos())) } @@ -256,7 +256,7 @@ class DspReal() extends Bundle { val x = arg1 val y = this val atanArg = y / x - val atanRes = atanArg.atan() + val atanRes = atanArg.atan val muxIn: Iterable[(Bool, DspReal)] = Iterable( (x > zero) -> atanRes, (x.signBit && !y.signBit) -> (atanRes + pi), @@ -270,49 +270,49 @@ class DspReal() extends Bundle { } def hypot(arg1: DspReal): DspReal = { - if (backendIsVerilator) (this * this + arg1 * arg1).sqrt() + if (backendIsVerilator) (this * this + arg1 * arg1).sqrt else twoOperandOperator(arg1, Module(new BBFHypot())) } // See https://en.wikipedia.org/wiki/Hyperbolic_function - def sinh(dummy: Int = 0): DspReal = { - if (backendIsVerilator) (this.exp() - (zero - this).exp()) / DspReal(2) + def sinh: DspReal = { + if (backendIsVerilator) (this.exp - (zero - this).exp) / DspReal(2) else oneOperandOperator(Module(new BBFSinh())) } - def cosh(dummy: Int = 0): DspReal = { - if (backendIsVerilator) (this.exp() + (zero - this).exp()) / DspReal(2) + def cosh: DspReal = { + if (backendIsVerilator) (this.exp + (zero - this).exp) / DspReal(2) else oneOperandOperator(Module(new BBFCosh())) } - def tanh(dummy: Int = 0): DspReal = { - if (backendIsVerilator) (this.exp() - (zero - this).exp()) / (this.exp() + (zero - this).exp()) + def tanh: DspReal = { + if (backendIsVerilator) (this.exp - (zero - this).exp) / (this.exp + (zero - this).exp) else oneOperandOperator(Module(new BBFTanh())) } // Requires Breeze for testing: - def asinh(dummy: Int = 0): DspReal = { - if (backendIsVerilator) ((this * this + one).sqrt() + this).ln() + def asinh: DspReal = { + if (backendIsVerilator) ((this * this + one).sqrt + this).ln else oneOperandOperator(Module(new BBFASinh())) } // x >= 1 - def acosh(dummy: Int = 0): DspReal = { - if (backendIsVerilator) ((this * this - one).sqrt() + this).ln() + def acosh: DspReal = { + if (backendIsVerilator) ((this * this - one).sqrt + this).ln else oneOperandOperator(Module(new BBFACosh())) } // |x| < 1 - def atanh(dummy: Int = 0): DspReal = { - if (backendIsVerilator) ((one + this) / (one - this)).ln() / DspReal(2) + def atanh: DspReal = { + if (backendIsVerilator) ((one + this) / (one - this)).ln / DspReal(2) else oneOperandOperator(Module(new BBFATanh())) } // Not used directly -- there's an equivalent in the type classes (was causing some confusion) /* - def intPart(dummy: Int = 0): DspReal = { + def intPart: DspReal = { oneOperandOperator(Module(new BBFIntPart())) } */ @@ -320,7 +320,7 @@ class DspReal() extends Bundle { /** Returns this Real's value rounded to a signed integer. * Behavior on overflow (possible with large exponent terms) is undefined. */ - def toSInt(dummy: Int = 0): SInt = { + def toSInt: SInt = { println(Console.YELLOW + "WARNING: Real -> SInt === THIS DESIGN IS NOT SYNTHESIZABLE!" + Console.RESET) val blackbox = Module(new BBFToInt) blackbox.io.in := node @@ -330,7 +330,7 @@ class DspReal() extends Bundle { /** Returns this Real's value as its bit representation in DspReal.underlyingWidth-bit floating point. */ - def toDoubleBits(dummy: Int = 0): UInt = { + def toDoubleBits: UInt = { node } } diff --git a/src/main/scala/dsptools/numbers/chisel_concrete/RealTrig.scala b/src/main/scala/dsptools/numbers/chisel_concrete/RealTrig.scala index 76f2d745..aa177b9d 100644 --- a/src/main/scala/dsptools/numbers/chisel_concrete/RealTrig.scala +++ b/src/main/scala/dsptools/numbers/chisel_concrete/RealTrig.scala @@ -4,23 +4,23 @@ package dsptools.numbers // Make using these ops more like using math.opName object RealTrig { - def ln(x: DspReal) = x.ln() - def log10(x: DspReal) = x.log10() - def exp(x: DspReal) = x.exp() - def sqrt(x: DspReal) = x.sqrt() + def ln(x: DspReal) = x.ln + def log10(x: DspReal) = x.log10 + def exp(x: DspReal) = x.exp + def sqrt(x: DspReal) = x.sqrt def pow(x: DspReal, n: DspReal) = x.pow(n) - def sin(x: DspReal) = x.sin() - def cos(x: DspReal) = x.cos() - def tan(x: DspReal) = x.tan() - def atan(x: DspReal) = x.atan() - def asin(x: DspReal) = x.asin() - def acos(x: DspReal) = x.acos() + def sin(x: DspReal) = x.sin + def cos(x: DspReal) = x.cos + def tan(x: DspReal) = x.tan + def atan(x: DspReal) = x.atan + def asin(x: DspReal) = x.asin + def acos(x: DspReal) = x.acos def atan2(y: DspReal, x: DspReal) = y.atan2(x) def hypot(x: DspReal, y: DspReal) = x.hypot(y) - def sinh(x: DspReal) = x.sinh() - def cosh(x: DspReal) = x.cosh() - def tanh(x: DspReal) = x.tanh() - def asinh(x: DspReal) = x.asinh() - def acosh(x: DspReal) = x.acosh() - def atanh(x: DspReal) = x.tanh() + def sinh(x: DspReal) = x.sinh + def cosh(x: DspReal) = x.cosh + def tanh(x: DspReal) = x.tanh + def asinh(x: DspReal) = x.asinh + def acosh(x: DspReal) = x.acosh + def atanh(x: DspReal) = x.tanh } diff --git a/src/main/scala/dsptools/numbers/chisel_types/DspRealTypeClass.scala b/src/main/scala/dsptools/numbers/chisel_types/DspRealTypeClass.scala index 6740b78f..05485044 100644 --- a/src/main/scala/dsptools/numbers/chisel_types/DspRealTypeClass.scala +++ b/src/main/scala/dsptools/numbers/chisel_types/DspRealTypeClass.scala @@ -45,7 +45,7 @@ trait DspRealSigned extends Any with Signed[DspReal] with DspRealRing with hasCo def signum(a: DspReal): ComparisonBundle = { ComparisonHelper(a === DspReal(0.0), a < DspReal(0.0)) } - def abs(a: DspReal): DspReal = a.abs() + def abs(a: DspReal): DspReal = a.abs def context_abs(a: DspReal): DspReal = { Mux( isSignNonNegative(ShiftRegister(a, context.numAddPipes)), @@ -61,16 +61,16 @@ trait DspRealSigned extends Any with Signed[DspReal] with DspRealRing with hasCo trait DspRealIsReal extends Any with IsReal[DspReal] with DspRealOrder with DspRealSigned with hasContext { def ceil(a: DspReal): DspReal = { - a.ceil() + a.ceil } def context_ceil(a: DspReal): DspReal = { - ShiftRegister(a, context.numAddPipes).ceil() + ShiftRegister(a, context.numAddPipes).ceil } - def floor(a: DspReal): DspReal = a.floor() + def floor(a: DspReal): DspReal = a.floor def isWhole(a: DspReal): Bool = a === round(a) // Round *half up* -- Different from System Verilog definition! (where half is rounded away from zero) // according to 5.7.2 (http://www.ece.uah.edu/~gaede/cpe526/2012%20System%20Verilog%20Language%20Reference%20Manual.pdf) - def round(a: DspReal): DspReal = a.round() + def round(a: DspReal): DspReal = a.round def truncate(a: DspReal): DspReal = { Mux( ShiftRegister(a, context.numAddPipes) < DspReal(0.0), @@ -136,7 +136,7 @@ trait DspRealReal def signBit(a: DspReal): Bool = isSignNegative(a) override def fromInt(n: Int): DspReal = super[ConvertableToDspReal].fromInt(n) override def fromBigInt(n: BigInt): DspReal = super[ConvertableToDspReal].fromBigInt(n) - def intPart(a: DspReal): SInt = truncate(a).toSInt() + def intPart(a: DspReal): SInt = truncate(a).toSInt // WARNING: Beware of overflow(!) def asFixed(a: DspReal, proto: FixedPoint): FixedPoint = { require(proto.binaryPoint.known, "Binary point must be known for DspReal -> FixedPoint") @@ -145,7 +145,7 @@ trait DspRealReal val out = Wire(proto.cloneType) out := DspContext.withTrimType(NoTrim) { // round is round half up - round(a * DspReal((1 << bp).toDouble)).toSInt().asFixed.div2(bp) + round(a * DspReal((1 << bp).toDouble)).toSInt.asFixed.div2(bp) } out } diff --git a/src/test/scala/dsptools/DspContextSpec.scala b/src/test/scala/dsptools/DspContextSpec.scala new file mode 100644 index 00000000..cb18be68 --- /dev/null +++ b/src/test/scala/dsptools/DspContextSpec.scala @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: Apache-2.0 + +package dsptools + +import chisel3._ +import dsptools.numbers._ +import chiseltest._ +import chiseltest.iotesters._ +import dsptools.misc.PeekPokeDspExtensions +import org.scalatest.freespec.AnyFreeSpec +import org.scalatest.matchers.should.Matchers + +import scala.collection.parallel.CollectionConverters.RangeIsParallelizable + +class DspContextSpec extends AnyFreeSpec with ChiselScalatestTester with Matchers { + "Context handling should be unobtrusive and convenient" - { + "There should be a default available at all times" in { + DspContext.current.binaryPoint should be(DspContext.defaultBinaryPoint) + } + + "it can be very to override for simple alterations" in { + DspContext.current.binaryPoint should be(DspContext.defaultBinaryPoint) + + DspContext.withBinaryPoint(-22) { + DspContext.current.binaryPoint.get should be(-22) + } + + DspContext.current.binaryPoint should be(DspContext.defaultBinaryPoint) + } + + "it should be easy to override when using multiples" in { + DspContext.current.binaryPoint should be(DspContext.defaultBinaryPoint) + DspContext.current.overflowType should be(DspContext.defaultOverflowType) + + DspContext.alter(DspContext.current.copy(binaryPoint = Some(77), overflowType = Saturate)) { + DspContext.current.binaryPoint.get should be(77) + DspContext.current.overflowType should be(Saturate) + } + + DspContext.current.binaryPoint should be(DspContext.defaultBinaryPoint) + DspContext.current.overflowType should be(DspContext.defaultOverflowType) + } + + "it should work multi-threaded and return values of block" ignore { + DspContext.current.numBits should be(DspContext.defaultNumBits) + + val points = (1 to 100).par.map { n => + DspContext.withNumBits(n) { + DspContext.current.numBits.get should be(n) + n * n + } + } + + val zipped = points.zipWithIndex + zipped.foreach { + case (p: Int, i: Int) => p should be(math.pow(i + 1, 2)) + } + + DspContext.current.numBits should be(DspContext.defaultNumBits) + } + } + + "Test proper nesting of DspContext over module instantiation" in { + test(new ContextNestingTop(UInt(4.W), UInt(5.W))) + .runPeekPoke(new ContextNestingTester(_)) + } +} + +class ContextNestingTester(c: ContextNestingTop[UInt]) extends PeekPokeTester(c) with PeekPokeDspExtensions { + poke(c.io.in1, 15.0) + poke(c.io.in2, 2.0) + + expect(c.io.mod1Default, 1.0) + expect(c.io.mod1Wrap, 1.0) + expect(c.io.mod1Grow, 17.0) + expect(c.io.mod2Default, 17.0) + expect(c.io.mod2Wrap, 1.0) + expect(c.io.mod2Grow, 17.0) +} + +class ContextNestingBottom[T <: Data: Ring](gen1: T, gen2: T) extends Module { + val io = IO(new Bundle { + val in1 = Input(gen1) + val in2 = Input(gen1) + val outDefault = Output(gen2) + val outWrap = Output(gen2) + val outGrow = Output(gen2) + }) + + DspContext.withOverflowType(Wrap) { + io.outWrap := io.in1.context_+(io.in2) + } + DspContext.withOverflowType(Grow) { + io.outGrow := io.in1.context_+(io.in2) + } + + io.outDefault := io.in1.context_+(io.in2) +} + +class ContextNestingTop[T <: Data: Ring](gen1: T, gen2: T) extends Module { + val io = IO(new Bundle { + val in1 = Input(gen1) + val in2 = Input(gen1) + val mod1Default = Output(gen2) + val mod1Wrap = Output(gen2) + val mod1Grow = Output(gen2) + val mod2Default = Output(gen2) + val mod2Wrap = Output(gen2) + val mod2Grow = Output(gen2) + }) + + private val mod1 = DspContext.withOverflowType(Wrap) { Module(new ContextNestingBottom(gen1, gen2)) } + private val mod2 = DspContext.withOverflowType(Grow) { Module(new ContextNestingBottom(gen1, gen2)) } + + mod1.io.in1 := io.in1 + mod1.io.in2 := io.in2 + mod2.io.in1 := io.in1 + mod2.io.in2 := io.in2 + + io.mod1Default := mod1.io.outDefault + io.mod1Wrap := mod1.io.outWrap + io.mod1Grow := mod1.io.outGrow + io.mod2Default := mod2.io.outDefault + io.mod2Wrap := mod2.io.outWrap + io.mod2Grow := mod2.io.outGrow +} diff --git a/src/test/scala/dsptools/DspTesterUtilitiesSpec.scala b/src/test/scala/dsptools/DspTesterUtilitiesSpec.scala new file mode 100644 index 00000000..9607e5b3 --- /dev/null +++ b/src/test/scala/dsptools/DspTesterUtilitiesSpec.scala @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: Apache-2.0 + +package dsptools + +import dsptools.misc.DspTesterUtilities.signedToBigIntUnsigned +import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.matchers.should.Matchers + +import scala.math.{abs, pow} + +class DspTesterSpec {} + +class DspTesterUtilitiesSpec extends AnyFlatSpec with Matchers { + + behavior.of("Tester Converters") + + it should "convert positive and negative doubles to their BigInt, fixed point equivalents" in { + + def check_conversion(value: Double, totalWidth: Int, fractionalWidth: Int, verbose: Boolean = false): Unit = { + if (verbose) { println(s"value = $value\ntotal width = $totalWidth\nfractional width = $fractionalWidth") } + var bi = signedToBigIntUnsigned(value, totalWidth, fractionalWidth) + if (verbose) { println(s"result = $bi") } + // check sign, flip if necessary + if (totalWidth > 0 && bi.testBit(totalWidth - 1)) { + bi = -1 * ((bi ^ ((BigInt(1) << totalWidth) - 1)) + 1) + } + val bid = bi.toDouble / (BigInt(1) << fractionalWidth).toDouble + if (verbose) { println(s"back to double = $bid") } + val comp = scala.math.abs(bid - value) + if (verbose) { println(s"comp = $comp") } + val ref = scala.math.pow(2, -fractionalWidth) + if (verbose) { println(s"ref = $ref") } + require(abs(bid - value) < pow(2, -fractionalWidth)) + } + + // integers + var width = 14 + for (i <- -pow(2, width - 1).toInt until pow(2, width - 1).toInt) { + check_conversion(i, width, 0) + } + + // big integers + width = 40 + for (i <- -pow(2, width - 1).toInt to pow(2, width - 1).toInt by pow(2, 20).toInt) { + check_conversion(i, width, 0) + } + + // total > fractional + width = 19 + var fract = 8 + for (i <- BigDecimal(-pow(2, width - fract - 1)) to pow(2, width - fract - 1) - 1 by 1.0 / fract * 0.9) { + check_conversion(i.toDouble, width, fract) + } + + // total < fractional + width = 11 + fract = 17 + for (i <- BigDecimal(-pow(2, width - fract - 1)) to pow(2, width - fract - 1) - 1 by 1.0 / fract * 0.9) { + check_conversion(i.toDouble, width, fract) + } + + } + + it should "fail to convert doubles to BigInts when not enough space is supplied" in { + intercept[IllegalArgumentException] { signedToBigIntUnsigned(2.0, 4, 2) } + intercept[IllegalArgumentException] { signedToBigIntUnsigned(-2.25, 4, 2) } + } + +} diff --git a/src/test/scala/dsptools/ShiftRegisterDelaySpec.scala b/src/test/scala/dsptools/ShiftRegisterDelaySpec.scala new file mode 100644 index 00000000..13ce1dc2 --- /dev/null +++ b/src/test/scala/dsptools/ShiftRegisterDelaySpec.scala @@ -0,0 +1,151 @@ +// SPDX-License-Identifier: Apache-2.0 + +package dsptools + +import chisel3.{fromDoubleToLiteral => _, fromIntToBinaryPoint => _, _} +import chiseltest._ +import chiseltest.iotesters._ +import dsptools.misc.PeekPokeDspExtensions +import dsptools.numbers._ +import fixedpoint._ +import org.scalatest.freespec.AnyFreeSpec + +import scala.collection.mutable + +//TODO: DspReal truncate, ceil +//TODO: FixedPoint ceil +//TODO: For truncate and ceil, compare delay between Fixed and Real + +//scalastyle:off magic.number regex + +class AbsCircuitWithDelays[T <: Data: Signed](gen: T, val delays: Int) extends Module { + val io = IO(new Bundle { + val in = Input(gen) + val outContextAbs = Output(gen) + }) + + DspContext.withNumAddPipes(delays) { + val con = io.in.context_abs + printf("io.in %d con %d\n", io.in.asUInt, con.asUInt) + io.outContextAbs := con + } +} + +class CeilTruncateCircuitWithDelays(val delays: Int) extends Module { + val io = IO(new Bundle { + val inFixed = Input(FixedPoint(12.W, 4.BP)) + val inReal = Input(DspReal()) + val outFixedCeil = Output(FixedPoint(12.W, 4.BP)) + val outRealCeil = Output(DspReal()) + val outFixedTruncate = Output(FixedPoint(12.W, 4.BP)) + val outRealTruncate = Output(DspReal()) + }) + + DspContext.withNumAddPipes(delays) { + io.outFixedCeil := io.inFixed.ceil + io.outRealCeil := io.inReal.context_ceil + io.outFixedTruncate := io.inFixed.truncate + io.outRealTruncate := io.inReal.truncate + } +} +class CircuitWithDelaysTester[T <: Data: Signed](c: AbsCircuitWithDelays[T]) + extends PeekPokeTester(c) + with PeekPokeDspExtensions { + private val delaySize = c.delays + + def oneTest(): Unit = { + def values: Seq[Double] = (BigDecimal(-delaySize) to delaySize.toDouble by 1.0).map(_.toDouble) + val inQueue = new mutable.Queue[Double] ++ values + val outQueue = new mutable.Queue[Double] ++ Seq.fill(delaySize - 1)(0.0) ++ values.map(_.abs) + + while (inQueue.nonEmpty) { + val inValue = inQueue.dequeue() + poke(c.io.in, inValue) + step(1) + val expectedValue = outQueue.dequeue() + expect(c.io.outContextAbs, expectedValue) + } + while (outQueue.nonEmpty) { + val expectedValue = outQueue.dequeue() + step(1) + expect(c.io.outContextAbs, expectedValue) + } + } + + reset() + poke(c.io.in, 0.0) + step(10) + oneTest() +} + +class CeilTruncateTester(c: CeilTruncateCircuitWithDelays) extends PeekPokeTester(c) with PeekPokeDspExtensions { + private val delaySize = c.delays + + def oneTest( + inFixedIo: FixedPoint, + outFixedIo: FixedPoint, + inRealIo: DspReal, + outRealIo: DspReal, + delaySize: Int + ): Unit = { + def values: Seq[Double] = (BigDecimal(-delaySize) to delaySize.toDouble by 1.0).map(_.toDouble) + val inQueue = new mutable.Queue[Double] ++ values + val outQueue = new mutable.Queue[Double] ++ Seq.fill(delaySize)(0.0) ++ values.map(_.ceil) + + while (inQueue.nonEmpty) { + val inValue = inQueue.dequeue() + poke(inFixedIo, inValue) + poke(inRealIo, inValue) + val expectedValue = outQueue.dequeue() + expect(outFixedIo, expectedValue) + expect(outRealIo, expectedValue) + step(1) + } + while (outQueue.nonEmpty) { + val expectedValue = outQueue.dequeue() + expect(outFixedIo, expectedValue) + expect(outRealIo, expectedValue) + step(1) + } + } + + poke(c.io.inFixed, 0.0) + poke(c.io.inReal, 0.0) + reset() + step(10) + oneTest(c.io.inFixed, c.io.outFixedCeil, c.io.inReal, c.io.outRealCeil, delaySize) +} + +class ShiftRegisterDelaySpec extends AnyFreeSpec with ChiselScalatestTester { + // Fails because FixedPoint's own ceil method is being used instead + "ceil delay should be consistent between dsp real and fixed point" ignore { + test(new CeilTruncateCircuitWithDelays(2)) + .withAnnotations(Seq(VerilatorBackendAnnotation)) + .runPeekPoke(new CeilTruncateTester(_)) + } + + "abs delays should be consistent across both sides of underlying mux" - { + + def sGen: SInt = SInt(16.W) + def fGen: FixedPoint = FixedPoint(16.W, 8.BP) + def rGen: DspReal = DspReal() + + "when used with SInt" in { + test(new AbsCircuitWithDelays(sGen, 3)) + .withAnnotations(Seq(VerilatorBackendAnnotation)) + .runPeekPoke(new CircuitWithDelaysTester(_)) + } + + "when used with FixedPoint" in { + test(new AbsCircuitWithDelays(fGen, 3)) + .withAnnotations(Seq(VerilatorBackendAnnotation)) + .runPeekPoke(new CircuitWithDelaysTester(_)) + } + + "when used with DspReal" in { + test(new AbsCircuitWithDelays(rGen, 8)) + .withAnnotations(Seq(VerilatorBackendAnnotation)) + .runPeekPoke(new CircuitWithDelaysTester(_)) + } + } +} diff --git a/src/test/scala/dsptools/numbers/AbsSpec.scala b/src/test/scala/dsptools/numbers/AbsSpec.scala new file mode 100644 index 00000000..1910a64e --- /dev/null +++ b/src/test/scala/dsptools/numbers/AbsSpec.scala @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: Apache-2.0 + +package dsptools.numbers + +import chisel3.{fromDoubleToLiteral => _, fromIntToBinaryPoint => _, _} +import fixedpoint._ +import chisel3.experimental.{FixedPoint => _, _} +import dsptools.{DspContext, Grow, Wrap} +import org.scalatest.freespec.AnyFreeSpec +import chiseltest._ +import chiseltest.iotesters._ +import dsptools.misc.PeekPokeDspExtensions + +class AbsSpec extends AnyFreeSpec with ChiselScalatestTester { + "absolute value should work for all types" - { + "abs should be obvious when not at extreme negative" - { + "but returns a negative number for extreme value at max negative for SInt and FixedPoint" - { + "with interpreter" in { + test(new DoesAbs(UInt(4.W), SInt(4.W), FixedPoint(5.W, 2.BP))) + .runPeekPoke(new DoesAbsTester(_)) + } + "and with verilator" in { + test(new DoesAbs(UInt(4.W), SInt(4.W), FixedPoint(5.W, 2.BP))) + .withAnnotations(Seq(VerilatorBackendAnnotation)) + .runPeekPoke(new DoesAbsTester(_)) + } + } + } + } + +} + +class DoesAbsTester(c: DoesAbs[UInt, SInt, FixedPoint]) extends PeekPokeTester(c) with PeekPokeDspExtensions { + for (i <- BigDecimal(0.0) to 15.0 by 1.0) { + poke(c.io.uIn, i) + expect(c.io.uAbsGrow, i) + expect(c.io.uAbsWrap, i) + step(1) + } + for (i <- BigDecimal(-7.0) to 7.0 by 1.0) { + poke(c.io.sIn, i) + expect(c.io.sAbsGrow, i.abs) + expect(c.io.sAbsWrap, i.abs) + step(1) + } + poke(c.io.sIn, -8.0) + expect(c.io.sAbsGrow, 8.0) + expect(c.io.sAbsWrap, -8.0) + + val increment = 0.25 + + for (i <- BigDecimal(-3.75) to 3.75 by increment) { + poke(c.io.fIn, i) + expect(c.io.fAbsGrow, i.abs) + expect(c.io.fAbsWrap, i.abs) + step(1) + } + poke(c.io.fIn, -4.0) + expect(c.io.fAbsGrow, 4.0) + expect(c.io.fAbsWrap, -4.0) +} + +class DoesAbs[TU <: Data: Signed: Ring, TS <: Data: Signed: Ring, TF <: Data: Signed: Ring]( + uGen: TU, + sGen: TS, + fGen: TF) + extends Module { + val io = IO(new Bundle { + val uIn = Input(uGen) + val sIn = Input(sGen) + val fIn = Input(fGen) + + val uAbsGrow = Output(uGen) + val uAbsWrap = Output(uGen) + + val sAbsGrow = Output(SInt(5.W)) + val sAbsWrap = Output(SInt(4.W)) + + val fAbsGrow = Output(FixedPoint(6.W, 2.BP)) + val fAbsWrap = Output(FixedPoint(5.W, 2.BP)) + }) + + io.uAbsGrow := DspContext.withOverflowType(Grow) { io.uIn.context_abs } + io.uAbsWrap := DspContext.withOverflowType(Wrap) { io.uIn.context_abs } + + io.sAbsGrow := DspContext.withOverflowType(Grow) { io.sIn.context_abs } + io.sAbsWrap := DspContext.withOverflowType(Wrap) { io.sIn.context_abs } + + io.fAbsGrow := DspContext.withOverflowType(Grow) { io.fIn.context_abs } + io.fAbsWrap := DspContext.withOverflowType(Wrap) { io.fIn.context_abs } +} diff --git a/src/test/scala/dsptools/numbers/BaseNSpec.scala b/src/test/scala/dsptools/numbers/BaseNSpec.scala new file mode 100644 index 00000000..e2aacced --- /dev/null +++ b/src/test/scala/dsptools/numbers/BaseNSpec.scala @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: Apache-2.0 + +package dsptools.numbers + +import dsptools.numbers.representations.BaseN +import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.matchers.should.Matchers + +class BaseNSpec extends AnyFlatSpec with Matchers { + behavior.of("BaseN") + it should "properly convert a decimal into BaseN" in { + // n in decimal, rad = radix, res = expected representation in base rad + case class BaseNTest(n: Int, rad: Int, res: Seq[Int]) + + // Most significant digit first (matched against WolframAlpha) + val tests = Seq( + BaseNTest(27, 4, Seq(1, 2, 3)), + BaseNTest(17, 3, Seq(1, 2, 2)), + BaseNTest(37, 5, Seq(1, 2, 2)) + ) + tests.foreach { + case BaseNTest(n, rad, res) => + require(BaseN.toDigitSeqMSDFirst(n, rad) == res, s"Base $rad conversion should work!") + val paddedBaseN = BaseN.toDigitSeqMSDFirst(n, rad, 500) + require( + paddedBaseN == (Seq.fill(paddedBaseN.length - res.length)(0) ++ res), + s"Padded base $rad conversion should work!" + ) + } + } +} diff --git a/src/test/scala/dsptools/numbers/BlackBoxFloatSpec.scala b/src/test/scala/dsptools/numbers/BlackBoxFloatSpec.scala new file mode 100644 index 00000000..7a88caef --- /dev/null +++ b/src/test/scala/dsptools/numbers/BlackBoxFloatSpec.scala @@ -0,0 +1,306 @@ +// SPDX-License-Identifier: Apache-2.0 + +package dsptools.numbers + +import chisel3._ +import chiseltest._ +import chiseltest.iotesters._ +import chisel3.testers.BasicTester +import chisel3.util._ +import dsptools.misc.PeekPokeDspExtensions +import org.scalatest.flatspec.AnyFlatSpec + +//scalastyle:off magic.number regex + +class BlackBoxFloatTester extends BasicTester { + val (cnt, _) = Counter(true.B, 10) + val accum = RegInit(DspReal(0)) + + private val addOut = accum + DspReal(1.0) + private val mulOut = addOut * DspReal(2.0) + + accum := addOut + + printf( + "cnt: %x accum: %x add: %x mult: %x\n", + cnt, + accum.toDoubleBits, + addOut.toDoubleBits, + mulOut.toDoubleBits + ) + + when(cnt === 0.U) { + assert(addOut === DspReal(1)) + assert(mulOut === DspReal(2)) + }.elsewhen(cnt === 1.U) { + assert(addOut === DspReal(2)) + assert(mulOut === DspReal(4)) + }.elsewhen(cnt === 2.U) { + assert(addOut === DspReal(3)) + assert(mulOut === DspReal(6)) + }.elsewhen(cnt === 3.U) { + assert(addOut === DspReal(4)) + assert(mulOut === DspReal(8)) + } + + when(cnt >= 3.U) { + // for unknown reasons, stop needs to be invoked multiple times + stop() + } +} + +class BlackBoxFloatAdder extends Module { + val io = IO(new Bundle { + val a = Input(DspReal()) + val b = Input(DspReal()) + val c = Output(DspReal()) + val d = Output(DspReal()) + val e = Output(DspReal()) + }) + + io.c := io.a + io.b + io.d := io.a + io.a + io.e := io.b + io.b +} + +class BlackBoxFloatAdderTester(c: BlackBoxFloatAdder) extends PeekPokeTester(c) with PeekPokeDspExtensions { + poke(c.io.a, 2.1) + poke(c.io.b, 3.0) + + expect(c.io.c, 5.1, "reals should add") + expect(c.io.d, 4.2, "reals should add") + expect(c.io.e, 6.0, "reals should add") +} + +object FloatOpCodes { + val Add = 0 + val Subtract = 1 + val Multiply = 2 + val Divide = 3 + val Ln = 4 + val Log10 = 5 + val Exp = 6 + val Sqrt = 7 + val Pow = 8 + val Floor = 9 + val Ceil = 10 + val Sin = 11 + val Cos = 12 + val Tan = 13 + val ASin = 14 + val ACos = 15 + val ATan = 16 + val ATan2 = 17 + val Hypot = 18 + val Sinh = 19 + val Cosh = 20 + val Tanh = 21 + val ASinh = 22 + val ACosh = 23 + val ATanh = 24 + val GreaterThan = 25 + val GreaterThanOrEqual = 26 + val LessThan = 27 + val LessThanOrEqual = 28 +} + +class FloatOps extends Module { + val io = IO(new Bundle { + val in1 = Input(DspReal()) + val in2 = Input(DspReal()) + val opsel = Input(UInt(64.W)) + val out = Output(DspReal()) + val boolOut = Output(Bool()) + }) + + io.boolOut := false.B + io.out := DspReal(0) +} + +class FloatOpsWithTrig extends FloatOps { + import FloatOpCodes._ + + switch(io.opsel) { + is(Add.U) { io.out := io.in1 + io.in2 } + is(Subtract.U) { io.out := io.in1 - io.in2 } + is(Multiply.U) { io.out := io.in1 * io.in2 } + is(Divide.U) { io.out := io.in1 / io.in2 } + is(Ln.U) { io.out := io.in1.ln } + is(Log10.U) { io.out := io.in1.log10 } + is(Exp.U) { io.out := io.in1.exp } + is(Sqrt.U) { io.out := io.in1.sqrt } + is(Pow.U) { io.out := io.in1.pow(io.in2) } + is(FloatOpCodes.Floor.U) { io.out := io.in1.floor } + is(Ceil.U) { io.out := io.in1.ceil } + + is(GreaterThan.U) { io.boolOut := io.in1 > io.in2 } + is(GreaterThanOrEqual.U) { io.boolOut := io.in1 >= io.in2 } + + is(Sin.U) { io.out := io.in1.sin } + is(Cos.U) { io.out := io.in1.cos } + is(Tan.U) { io.out := io.in1.tan } + is(ASin.U) { io.out := io.in1.asin } + is(ACos.U) { io.out := io.in1.acos } + is(ATan.U) { io.out := io.in1.atan } + is(ATan2.U) { io.out := io.in1.atan2(io.in2) } + is(Hypot.U) { io.out := io.in1.hypot(io.in2) } + is(Sinh.U) { io.out := io.in1.sinh } + is(Cosh.U) { io.out := io.in1.cosh } + is(Tanh.U) { io.out := io.in1.tanh } + is(ASinh.U) { io.out := io.in1.asinh } + is(ACosh.U) { io.out := io.in1.acosh } + is(ATanh.U) { io.out := io.in1.atanh } + + } +} + +class FloatOpsWithoutTrig extends FloatOps { + import FloatOpCodes._ + switch(io.opsel) { + is(Add.U) { io.out := io.in1 + io.in2 } + is(Subtract.U) { io.out := io.in1 - io.in2 } + is(Multiply.U) { io.out := io.in1 * io.in2 } + is(Divide.U) { io.out := io.in1 / io.in2 } + is(Ln.U) { io.out := io.in1.ln } + is(Log10.U) { io.out := io.in1.log10 } + is(Exp.U) { io.out := io.in1.exp } + is(Sqrt.U) { io.out := io.in1.sqrt } + is(Pow.U) { io.out := io.in1.pow(io.in2) } + is(FloatOpCodes.Floor.U) { io.out := io.in1.floor } + is(Ceil.U) { io.out := io.in1.ceil } + is(GreaterThan.U) { io.boolOut := io.in1 > io.in2 } + is(GreaterThanOrEqual.U) { io.boolOut := io.in1 >= io.in2 } + } +} + +class FloatOpTester[T <: FloatOps](c: T, testTrigFuncs: Boolean = true) + extends PeekPokeTester(c) + with PeekPokeDspExtensions { + import FloatOpCodes._ + val a = 3.4 + val b = 7.1 + // scala doesn't have inverse hyperbolic functions, hardcode them + val asinh_a = 1.9378792776645006 + val acosh_a = 1.8945590126722978042798892652 + poke(c.io.in1, a) + poke(c.io.in2, b) + + poke(c.io.opsel, Add) + expect(c.io.out, a + b, "reals should add") + poke(c.io.opsel, Subtract) + expect(c.io.out, a - b, "reals should subtract") + poke(c.io.opsel, Multiply) + expect(c.io.out, a * b, "reals should multiply") + poke(c.io.opsel, Divide) + expect(c.io.out, a / b, "reals should divide") + poke(c.io.opsel, Ln) + expect(c.io.out, math.log(a), "log should work on reals") + poke(c.io.opsel, Log10) + expect(c.io.out, math.log10(a), "log10 should work on reals") + poke(c.io.opsel, Exp) + expect(c.io.out, math.exp(a), "exp should work on reals") + poke(c.io.opsel, Sqrt) + expect(c.io.out, math.sqrt(a), "sqrt should work on reals") + poke(c.io.opsel, Pow) + expect(c.io.out, math.pow(a, b), "reals should pow") + poke(c.io.opsel, FloatOpCodes.Floor) + expect(c.io.out, math.floor(a), "floor should work on reals") + poke(c.io.opsel, Ceil) + expect(c.io.out, math.ceil(a), "ceil should work on reals") + + if (testTrigFuncs) { + poke(c.io.opsel, Sin) + expect(c.io.out, math.sin(a), "sin should work on reals") + poke(c.io.opsel, Cos) + expect(c.io.out, math.cos(a), "cos should work on reals") + poke(c.io.opsel, Tan) + expect(c.io.out, math.tan(a), "tan should work on reals") + + val arcArg = 0.5 + poke(c.io.in1, arcArg) + poke(c.io.opsel, ASin) + expect(c.io.out, math.asin(arcArg), "asin should work on reals") + poke(c.io.opsel, ACos) + expect(c.io.out, math.acos(arcArg), "acos should work on reals") + + poke(c.io.in1, a) + poke(c.io.opsel, ATan) + expect(c.io.out, math.atan(a), "atan should work on reals") + poke(c.io.opsel, ATan2) + expect(c.io.out, math.atan2(a, b), "atan2 should work on reals") + poke(c.io.opsel, Hypot) + expect(c.io.out, math.hypot(a, b), "hypot should work on reals") + poke(c.io.opsel, Sinh) + expect(c.io.out, math.sinh(a), "sinh should work on reals") + poke(c.io.opsel, Cosh) + expect(c.io.out, math.cosh(a), "cosh should work on reals") + poke(c.io.opsel, Tanh) + expect(c.io.out, math.tanh(a), "tanh should work on reals") + poke(c.io.opsel, ASinh) + expect(c.io.out, asinh_a, "asinh should work on reals") + poke(c.io.opsel, ACosh) + expect(c.io.out, acosh_a, "acosh should work on reals") + poke(c.io.opsel, ATanh) + // not defined + // dspExpect(c.io.out, math.atanh(a), "atanh should work on reals") + } + + for { + x <- (BigDecimal(-1.0) to 1.0 by 1.0).map(_.toDouble) + y <- (BigDecimal(-1.0) to 1.0 by 1.0).map(_.toDouble) + } { + poke(c.io.in1, x) + poke(c.io.in2, y) + poke(c.io.opsel, GreaterThan) + expect(c.io.boolOut, x > y, s"$x > $y should be ${x > y}") + step(1) + } + +} + +class BlackBoxFloatSpec extends AnyFlatSpec with ChiselScalatestTester { + "A BlackBoxed FP block" should "work" in { + test(new BlackBoxFloatTester) + .withAnnotations(Seq(VerilatorBackendAnnotation)) + .runUntilStop() + } + + "basic addition" should "work with reals through black boxes" in { + test(new BlackBoxFloatAdder) + .withAnnotations(Seq(VerilatorBackendAnnotation)) + .runPeekPoke(new BlackBoxFloatAdderTester(_)) + } + + "float ops" should "work with trig functions" in { + test(new FloatOpsWithTrig) + .withAnnotations(Seq(VerilatorBackendAnnotation)) + .runPeekPoke(new FloatOpTester(_)) + } + + "float ops" should "work without trig functions" in { + test(new FloatOpsWithoutTrig) + .withAnnotations(Seq(VerilatorBackendAnnotation)) + .runPeekPoke(new FloatOpTester(_, testTrigFuncs = false)) + } + + "greater than" should "work with negatives" in { + test(new NegCircuit) + .withAnnotations(Seq(VerilatorBackendAnnotation)) + .runPeekPoke(new NegCircuitTester(_)) + } +} + +class NegCircuit extends Module { + val io = IO(new Bundle { + val in1 = Input(DspReal()) + val in2 = Input(DspReal()) + val out = Output(Bool()) + }) + io.out := io.in1 > io.in2 +} + +class NegCircuitTester(c: NegCircuit) extends PeekPokeTester(c) with PeekPokeDspExtensions { + poke(c.io.in1, -1.0) + poke(c.io.in2, -2.0) + expect(c.io.out, true) +} diff --git a/src/test/scala/dsptools/numbers/FixedPointSpec.scala b/src/test/scala/dsptools/numbers/FixedPointSpec.scala new file mode 100644 index 00000000..a6049ba9 --- /dev/null +++ b/src/test/scala/dsptools/numbers/FixedPointSpec.scala @@ -0,0 +1,225 @@ +// SPDX-License-Identifier: Apache-2.0 + +package dsptools.numbers + +//scalastyle:off magic.number + +import chisel3.testers.BasicTester +import chisel3.{fromDoubleToLiteral => _, fromIntToBinaryPoint => _, _} +import fixedpoint._ +import dsptools.numbers.implicits._ +import org.scalatest.freespec.AnyFreeSpec +import chiseltest._ +import chiseltest.iotesters._ +import dsptools.misc.PeekPokeDspExtensions +import org.scalatest.propspec.AnyPropSpec + +class FixedRing1(val width: Int, val binaryPoint: Int) extends Module { + val io = IO(new Bundle { + val in = Input(FixedPoint(width.W, binaryPoint.BP)) + val floor = Output(FixedPoint(width.W, binaryPoint.BP)) + val ceil = Output(FixedPoint(width.W, binaryPoint.BP)) + val isWhole = Output(Bool()) + val round = Output(FixedPoint(width.W, binaryPoint.BP)) + val real = Output(DspReal()) + }) + + io.floor := io.in.floor + io.ceil := io.in.ceil + io.isWhole := io.in.isWhole + io.round := io.in.round + io.real := DspReal(0) +} + +class FixedRing1Tester(c: FixedRing1) extends PeekPokeTester(c) with PeekPokeDspExtensions { + val increment: Double = if (c.binaryPoint == 0) 1.0 else 1.0 / (1 << c.binaryPoint) + for (i <- (BigDecimal(-2.0) to 3.0 by increment).map(_.toDouble)) { + poke(c.io.in, i) + + expect(c.io.floor, breeze.numerics.floor(i), s"floor of $i should be ${breeze.numerics.floor(i)}") + expect(c.io.ceil, breeze.numerics.ceil(i), s"ceil of $i should be ${breeze.numerics.ceil(i)}") + expect(c.io.isWhole, breeze.numerics.floor(i) == i, s"isWhole of $i should be ${breeze.numerics.floor(i) == i}") + expect(c.io.round, breeze.numerics.round(i).toDouble, s"round of $i should be ${breeze.numerics.round(i)}") + step(1) + } +} + +/** + * Shift the inValue right and left, statically and dynamically. + * @note shiftRight has a constraint that shift amount must be less than width of inValue + * @param width width of shift target + * @param binaryPoint the binary point of the shift target + * @param fixedShiftSize how much to shift the target + */ +class FixedPointShifter(val width: Int, val binaryPoint: Int, val fixedShiftSize: Int) extends Module { + val dynamicShifterWidth = 3 + + val io = IO(new Bundle { + val inValue = Input(FixedPoint(width.W, binaryPoint.BP)) + val dynamicShiftValue = Input(UInt(dynamicShifterWidth.W)) + val shiftRightResult: Option[FixedPoint] = if (fixedShiftSize < width) { + Some(Output(FixedPoint((width - fixedShiftSize).W, binaryPoint.BP))) + } else { + None + } + val shiftLeftResult = Output(FixedPoint((width + fixedShiftSize).W, binaryPoint.BP)) + val dynamicShiftRightResult = Output(FixedPoint(width.W, binaryPoint.BP)) + val dynamicShiftLeftResult = Output(FixedPoint((width + (1 << dynamicShifterWidth) - 1).W, binaryPoint.BP)) + }) + + io.shiftLeftResult := io.inValue << fixedShiftSize + io.shiftRightResult.foreach { out => + out := io.inValue >> fixedShiftSize + } + io.dynamicShiftLeftResult := io.inValue << io.dynamicShiftValue + io.dynamicShiftRightResult := io.inValue >> io.dynamicShiftValue +} + +class FixedPointShiftTester(c: FixedPointShifter) extends PeekPokeTester(c) with PeekPokeDspExtensions { + val increment: Double = 1.0 / (1 << c.binaryPoint) + + def expectedValue(value: Double, left: Boolean, shift: Int): Double = { + val factor = 1 << c.binaryPoint + val x = value * factor + val y = x.toInt + val z = if (left) y << shift else y >> shift + val w = z.toDouble / factor + w + } + + def truncate(value: Double): Double = { + val factor = 1 << c.binaryPoint + val x = value * factor + val y = x.toInt.toDouble + val z = y / factor + z + } + + def extremaOfSIntOfWidth(width: Int): (BigInt, BigInt) = { + val nearestPowerOf2 = BigInt("1" + ("0" * (width - 1)), 2) + (-nearestPowerOf2, nearestPowerOf2 - 1) + } + + poke(c.io.dynamicShiftValue, 0) + + val (minSIntValue, maxSIntValue) = extremaOfSIntOfWidth(c.width) + + val minValue = minSIntValue.toDouble * increment + val maxValue = maxSIntValue.toDouble * increment + + for (value <- (BigDecimal(minValue) to maxValue by increment).map(_.toDouble)) { + poke(c.io.inValue, value) + expect( + c.io.shiftLeftResult, + expectedValue(value, left = true, c.fixedShiftSize), + s"shift left ${c.fixedShiftSize} of $value should be ${expectedValue(value, left = true, c.fixedShiftSize)}" + ) + c.io.shiftRightResult.foreach { sro => + expect( + sro, + expectedValue(value, left = false, c.fixedShiftSize), + s"shift right ${c.fixedShiftSize} of $value should be ${expectedValue(value, left = false, c.fixedShiftSize)}" + ) + } + + step(1) + + for (dynamicShiftValue <- 0 until c.width) { + poke(c.io.dynamicShiftValue, dynamicShiftValue) + step(1) + expect( + c.io.dynamicShiftLeftResult, + expectedValue(value, left = true, dynamicShiftValue), + s"dynamic shift left $dynamicShiftValue of $value should " + + s"be ${expectedValue(value, left = true, dynamicShiftValue)}" + ) + expect( + c.io.dynamicShiftRightResult, + expectedValue(value, left = false, dynamicShiftValue), + s"dynamic shift right $dynamicShiftValue of $value should" + + s"be ${expectedValue(value, left = false, dynamicShiftValue)}" + ) + } + } + +} + +class BrokenShifter(n: Int) extends Module { + val io = IO(new Bundle { + val i = Input(FixedPoint(8.W, 4.BP)) + val o = Output(FixedPoint(8.W, 4.BP)) + val si = Input(SInt(8.W)) + val so = Output(SInt(8.W)) + }) + io.o := io.i >> n + io.so := io.si >> n +} + +class BrokenShifterTester(c: BrokenShifter) extends PeekPokeTester(c) with PeekPokeDspExtensions { + poke(c.io.i, 1.5) + peek(c.io.o) + poke(c.io.si, 6) + peek(c.io.so) +} + +class FixedPointSpec extends AnyFreeSpec with ChiselScalatestTester { + "FixedPoint numbers should work properly for the following mathematical type functions" - { + // for (backendName <- Seq("verilator")) { + for (backendName <- Seq("default", "verilator")) { + val annos = if (backendName == "verilator") Seq(VerilatorBackendAnnotation) else Seq() + + s"The ring family run with the $backendName simulator" - { + for (binaryPoint <- 0 to 4 by 2) { + s"should work, with binaryPoint $binaryPoint" in { + test(new FixedRing1(16, binaryPoint = binaryPoint)) + .withAnnotations(annos) + .runPeekPoke(new FixedRing1Tester(_)) + } + } + } + + s"The shift family when run with the $backendName simulator" - { + val defaultWidth = 8 + for { + binaryPoint <- + Set(0, 1, 1) ++ + (defaultWidth - 1 to defaultWidth + 1) ++ + (defaultWidth * 2 - 1 to defaultWidth * 2 + 1) + fixedShiftSize <- Set(0, 1, 2) ++ (defaultWidth - 1 to defaultWidth + 1) + } { + s"should work with binary point $binaryPoint, with shift $fixedShiftSize " in { + test(new FixedPointShifter(width = 8, binaryPoint = binaryPoint, fixedShiftSize = fixedShiftSize)) + .withAnnotations(annos) + .runPeekPoke(new FixedPointShiftTester(_)) + } + } + } + + //TODO: This error does not seem to be caught at this time. Firrtl issue #450 + s"shifting by too big a number causes error with $backendName" ignore { + for (shiftSize <- 8 to 10) { + test(new BrokenShifter(n = shiftSize)) + .withAnnotations(annos) + .runPeekPoke(new BrokenShifterTester(_)) + } + } + + } + } +} + +class FixedPointChiselSpec extends AnyPropSpec with ChiselScalatestTester { + property("asReal shold work") { + test(new BasicTester { + val x = FixedPoint.fromDouble(13.5, 16.W, 4.BP) + + val y = x.asReal + + chisel3.assert(y === DspReal(13.5)) + + stop() + }) + .withAnnotations(Seq(VerilatorBackendAnnotation)) + .runUntilStop() + } +} diff --git a/src/test/scala/dsptools/numbers/FixedPrecisionChangerSpec.scala b/src/test/scala/dsptools/numbers/FixedPrecisionChangerSpec.scala new file mode 100644 index 00000000..567b3da6 --- /dev/null +++ b/src/test/scala/dsptools/numbers/FixedPrecisionChangerSpec.scala @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: Apache-2.0 + +package dsptools.numbers + +import chisel3.{fromDoubleToLiteral => _, fromIntToBinaryPoint => _, _} +import fixedpoint._ +import chiseltest._ +import chiseltest.iotesters._ +import dsptools.misc.PeekPokeDspExtensions +import org.scalatest.freespec.AnyFreeSpec +import org.scalatest.matchers.should.Matchers + +//scalastyle:off magic.number + +class FixedPrecisionChanger(inWidth: Int, inBinaryPoint: Int, outWidth: Int, outBinaryPoint: Int) extends Module { + val io = IO(new Bundle { + val in = Input(FixedPoint(inWidth.W, inBinaryPoint.BP)) + val out = Output(FixedPoint(outWidth.W, outBinaryPoint.BP)) + }) + + val reg = Reg(FixedPoint()) + reg := io.in + io.out := reg +} + +class FixedPointTruncatorTester(c: FixedPrecisionChanger, inValue: Double, outValue: Double) + extends PeekPokeTester(c) + with PeekPokeDspExtensions { + poke(c.io.in, inValue) + step(1) + expect(c.io.out, outValue, s"got ${peek(c.io.out)} should have $outValue") +} + +class RemoveMantissa(inWidth: Int, inBinaryPoint: Int, outWidth: Int, outBinaryPoint: Int) extends Module { + val io = IO(new Bundle { + val in = Input(FixedPoint(inWidth.W, inBinaryPoint.BP)) + val out = Output(FixedPoint(outWidth.W, 0.BP)) + }) + + val reg = Reg(FixedPoint()) + reg := io.in + io.out := reg //.setBinaryPoint(0) +} + +class RemoveMantissaTester(c: RemoveMantissa, inValue: Double, outValue: Double) + extends PeekPokeTester(c) + with PeekPokeDspExtensions { + poke(c.io.in, inValue) + step(1) + expect(c.io.out, outValue, s"got ${peek(c.io.out)} should have $outValue") +} + +class FixedPrecisionChangerSpec extends AnyFreeSpec with ChiselScalatestTester { + "assignment of numbers with differing binary points seems to work as I would expect" - { + "here we assign to a F8.1 from a F8.3" in { + test(new FixedPrecisionChanger(8, 3, 8, 1)) + .runPeekPoke(new FixedPointTruncatorTester(_, 6.875, 6.5)) + } + "here we assign to a F8.1 from a F8.1" - { + "conversion to fixed point with less precision than poked value rounds up to 7, IS THIS RIGHT?" in { + test(new FixedPrecisionChanger(8, 1, 8, 1)) + .runPeekPoke(new FixedPointTruncatorTester(_, 6.875, 7.0)) + } + } + "here we assign to a F10.6 from a F10.3" in { + test(new FixedPrecisionChanger(10, 3, 10, 6)) + .runPeekPoke(new FixedPointTruncatorTester(_, 6.875, 6.875)) + } + "let's try 1/3 just for fun with a big mantissa" - { + "oops, this works because I built in a fudge factor for double comparison, how should this be done" in { + test(new FixedPrecisionChanger(64, 58, 64, 16)) + .runPeekPoke(new FixedPointTruncatorTester(_, 1.0 / 3.0, 0.3333282470703125)) + } + } + } + + "removing mantissa can be done" - { + "by using the setBinaryPoint Method" in { + test(new RemoveMantissa(12, 4, 8, 0)) + .runPeekPoke(new RemoveMantissaTester(_, 3.75, 3.0)) + } + } +} diff --git a/src/test/scala/dsptools/numbers/LnSpec.scala b/src/test/scala/dsptools/numbers/LnSpec.scala new file mode 100644 index 00000000..ce957ff1 --- /dev/null +++ b/src/test/scala/dsptools/numbers/LnSpec.scala @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: Apache-2.0 + +package dsptools.numbers + +import chisel3._ +import chiseltest._ +import chiseltest.iotesters._ +import dsptools.misc.PeekPokeDspExtensions +import org.scalatest.freespec.AnyFreeSpec + +class LnModule extends Module { + val io = IO(new Bundle { + val num = Input(DspReal()) + val ln = Output(DspReal()) + }) + + io.ln := io.num.ln +} + +class LnTester(c: LnModule) extends PeekPokeTester(c) with PeekPokeDspExtensions { + poke(c.io.num, 11.0) + private val x = peek(c.io.ln) + println(s"poked 1.0 got $x expected ${math.log(11.0)}") + +} +class LnSpec extends AnyFreeSpec with ChiselScalatestTester { + "ln should work" in { + test(new LnModule) + .runPeekPoke(new LnTester(_)) + } +} diff --git a/src/test/scala/dsptools/numbers/NumbersSpec.scala b/src/test/scala/dsptools/numbers/NumbersSpec.scala new file mode 100644 index 00000000..82125a60 --- /dev/null +++ b/src/test/scala/dsptools/numbers/NumbersSpec.scala @@ -0,0 +1,491 @@ +// SPDX-License-Identifier: Apache-2.0 + +package dsptools.numbers + +import chisel3.{fromDoubleToLiteral => _, fromIntToBinaryPoint => _, _} +import fixedpoint._ +import chiseltest._ +import chiseltest.experimental.sanitizeFileName +import chiseltest.iotesters._ +import dsptools._ +import dsptools.misc.PeekPokeDspExtensions +import org.scalatest.freespec.AnyFreeSpec +import org.scalatest.matchers.should.Matchers + +/** + * This will attempt to follow the dsptools.numbers.README.md file as close as possible. + */ +//scalastyle:off magic.number +class NumbersSpec extends AnyFreeSpec with ChiselScalatestTester with Matchers { + def f(w: Int, b: Int): FixedPoint = FixedPoint(w.W, b.BP) + def u(w: Int): UInt = UInt(w.W) + def s(w: Int): SInt = SInt(w.W) + + "DspContext behavior control" - { + "overflow type controls how a * b, a.trimBinary(n) and a.div2(n) should round results" - { + "Overflow type tests for UInt" in { + test(new OverflowTypeCircuit(u(4), u(5), u(5))) + .runPeekPoke( + new OverflowTypeCircuitTester( + _, + // in1, in2, addWrap, addGrow, subWrap, subGrow + (15, 1, 0, 16, 14, 0), + (14, 2, 0, 16, 12, 0), + (1, 2, 3, 3, 15, 0) + ) + ) + + test(new OverflowTypeCircuit(u(4), u(5), u(5))) + .withAnnotations(Seq(VerilatorBackendAnnotation)) + .runPeekPoke( + new OverflowTypeCircuitTester( + _, + // in1, in2, addWrap, addGrow, subWrap, subGrow + (15, 1, 0, 16, 14, 0), + (14, 2, 0, 16, 12, 0), + (1, 2, 3, 3, 15, 0) + ) + ) + } + + "UInt subtract with overflow type Grow not supported" ignore { + val expectedMessage = "OverflowType Grow is not supported for UInt subtraction" + val exception = intercept[Exception] { + test(new BadUIntSubtractWithGrow2(u(4))) + .runPeekPoke(new NumbersEmptyTester(_)) + } + exception match { + case e: DspException => e.getMessage should be(expectedMessage) + case _: Exception => exception.getCause should be(DspException(expectedMessage)) + } + } + + "UInt subtract with overflow type Grow not supported cannot be detected without evidence that io is ring" in { + test(new ShouldBeBadUIntSubtractWithGrow) + .runPeekPoke(new NumbersEmptyTester(_)) + } + + "Overflow type tests for SInt" in { + test(new OverflowTypeCircuit(s(4), s(5), s(5))) + .runPeekPoke( + new OverflowTypeCircuitTester( + _, + // in1, in2, addWrap, addGrow, subWrap, subGrow + (7, 2, -7, 9, 5, 5), + (-8, 2, -6, -6, 6, -10), + (-8, -2, 6, -10, -6, -6) + ) + ) + + test(new OverflowTypeCircuit(s(4), s(5), s(5))) + .withAnnotations(Seq(VerilatorBackendAnnotation)) + .runPeekPoke( + new OverflowTypeCircuitTester( + _, + // in1, in2, addWrap, addGrow, subWrap, subGrow + (7, 2, -7, 9, 5, 5), + (-8, 2, -6, -6, 6, -10), + (-8, -2, 6, -10, -6, -6) + ) + ) + } + + "Overflow type tests for FixedPoint" in { + test(new OverflowTypeCircuit(f(4, 2), f(5, 2), f(8, 3))) + .runPeekPoke( + new OverflowTypeCircuitTester( + _, + // in1, in2, addWrap, addGrow, subWrap, subGrow + (1.75, 0.5, -1.75, 2.25, 1.25, 1.25), + (-1.75, 0.5, -1.25, -1.25, 1.75, -2.25), + (-1.75, -0.5, 1.75, -2.25, -1.25, -1.25) + ) + ) + + test(new OverflowTypeCircuit(f(4, 2), f(5, 2), f(8, 3))) + .withAnnotations(Seq(VerilatorBackendAnnotation)) + .runPeekPoke( + new OverflowTypeCircuitTester( + _, + // in1, in2, addWrap, addGrow, subWrap, subGrow + (1.75, 0.5, -1.75, 2.25, 1.25, 1.25), + (-1.75, 0.5, -1.25, -1.25, 1.75, -2.25), + (-1.75, -0.5, 1.75, -2.25, -1.25, -1.25) + ) + ) + } + } + + "trim type controls how a * b, a.trimBinary(n) and a.div2(n) should round results" - { + "Trim type tests for multiplication" in { + test(new TrimTypeMultiplyCircuit(f(6, 2), f(8, 4), f(12, 5))) + .runPeekPoke( + new TrimTypeMultiplyCircuitTester( + _, + // a, b, mulF, mulC, mulRTZ, mulRTI, mulRHD, mulRHUp, mulRHTZ, mulRHTI, mulRHTE, mulRHTO, mulNoTrim + (1.5, 1.25, 1.75, 2.0, 1.75, 2.0, 1.75, 2.0, 1.75, 2.0, 2.0, 1.75, 1.875), + (1.25, 1.25, 1.5, 1.75, 1.5, 1.75, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5625), + (0.75, 1.5, 1.0, 1.25, 1.0, 1.25, 1.0, 1.25, 1.0, 1.25, 1.0, 1.25, 1.125), + (1.25, 0.75, 0.75, 1.0, 0.75, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.9375), + (-1.5, 1.25, -2.0, -1.75, -1.75, -2.0, -2.0, -1.75, -1.75, -2.0, -2.0, -1.75, -1.875), + (-1.25, 1.25, -1.75, -1.5, -1.5, -1.75, -1.5, -1.5, -1.5, -1.5, -1.5, -1.5, -1.5625), + (-0.75, 1.5, -1.25, -1.0, -1.0, -1.25, -1.25, -1.0, -1.0, -1.25, -1.0, -1.25, -1.125), + (-1.25, 0.75, -1.0, -0.75, -0.75, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -0.9375) + ) + ) + } + + "Trim type tests for division" in { + test(new TrimTypeDiv2Circuit(f(6, 2), f(8, 4), f(12, 5))) + .runPeekPoke( + new TrimTypeDiv2CircuitTester( + _, + // a, divF, divC, divRTZ, divRTI, divRHD, divRHUp, divRHTZ, divRHTI, divRHTE, divRHTO, divNoTrim + (1.5, 0.25, 0.5, 0.25, 0.5, 0.25, 0.5, 0.25, 0.5, 0.5, 0.25, 0.375), + (1.25, 0.25, 0.5, 0.25, 0.5, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.3125), + (0.75, 0.0, 0.25, 0.0, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.1875), + (0.5, 0.0, 0.25, 0.0, 0.25, 0.0, 0.25, 0.0, 0.25, 0.0, 0.25, 0.125), + (-1.5, -0.5, -0.25, -0.25, -0.5, -0.5, -0.25, -0.25, -0.5, -0.5, -0.25, -0.375), + (-1.25, -0.5, -0.25, -0.25, -0.5, -0.25, -0.25, -0.25, -0.25, -0.25, -0.25, -0.3125), + (-0.75, -0.25, -0.0, -0.0, -0.25, -0.25, -0.25, -0.25, -0.25, -0.25, -0.25, -0.1875), + (-0.5, -0.25, -0.0, -0.0, -0.25, -0.25, -0.0, -0.0, -0.25, -0.0, -0.25, -0.125) + ) + ) + } + } + } + + "Test for BinaryRepresentation section of Numbers Spec" in { + def f(w: Int, b: Int): FixedPoint = FixedPoint(w.W, b.BP) + test(new BinaryRepr(u(8), s(8), f(10, 2))) + .withAnnotations(Seq(VerilatorBackendAnnotation)) + .runPeekPoke(new BinaryReprTester(_)) + } +} + +class TrimTypeMultiplyCircuit[T <: Data: Ring](gen1: T, gen2: T, gen3: T) extends Module { + val io = IO(new Bundle { + val a = Input(gen1) + val b = Input(gen1) + val multiplyFloor = Output(gen2) + val multiplyCeiling = Output(gen2) + val multiplyRoundTowardsZero = Output(gen2) + val multiplyRoundTowardsInfinity = Output(gen2) + val multiplyRoundHalfDown = Output(gen2) + val multiplyRoundHalfUp = Output(gen2) + val multiplyRoundHalfTowardsZero = Output(gen2) + val multiplyRound = Output(gen2) + val multiplyConvergent = Output(gen2) + val multiplyRoundHalfToOdd = Output(gen2) + val multiplyNoTrim = Output(gen3) + }) + + DspContext.withBinaryPointGrowth(0) { + val regMultiplyFloor = RegNext(DspContext.withTrimType(Floor) { + io.a.context_*(io.b) + }) + val regMultiplyCeiling = RegNext(DspContext.withTrimType(Ceiling) { + io.a.context_*(io.b) + }) + val regMultiplyRoundTowardsZero = RegNext(DspContext.withTrimType(RoundTowardsZero) { + io.a.context_*(io.b) + }) + val regMultiplyRoundTowardsInfinity = RegNext(DspContext.withTrimType(RoundTowardsInfinity) { + io.a.context_*(io.b) + }) + val regMultiplyRoundHalfDown = RegNext(DspContext.withTrimType(RoundHalfDown) { + io.a.context_*(io.b) + }) + val regMultiplyRoundHalfUp = RegNext(DspContext.withTrimType(RoundHalfUp) { + io.a.context_*(io.b) + }) + val regMultiplyRoundHalfTowardsZero = RegNext(DspContext.withTrimType(RoundHalfTowardsZero) { + io.a.context_*(io.b) + }) + val regMultiplyRound = RegNext(DspContext.withTrimType(Round) { + io.a.context_*(io.b) + }) + val regMultiplyConvergent = RegNext(DspContext.withTrimType(Convergent) { + io.a.context_*(io.b) + }) + val regMultiplyRoundHalfToOdd = RegNext(DspContext.withTrimType(RoundHalfToOdd) { + io.a.context_*(io.b) + }) + val regMultiplyNoTrim = RegNext(DspContext.withTrimType(NoTrim) { + io.a.context_*(io.b) + }) + + io.multiplyFloor := regMultiplyFloor + io.multiplyCeiling := regMultiplyCeiling + io.multiplyRoundTowardsZero := regMultiplyRoundTowardsZero + io.multiplyRoundTowardsInfinity := regMultiplyRoundTowardsInfinity + io.multiplyRoundHalfDown := regMultiplyRoundHalfDown + io.multiplyRoundHalfUp := regMultiplyRoundHalfUp + io.multiplyRoundHalfTowardsZero := regMultiplyRoundHalfTowardsZero + io.multiplyRound := regMultiplyRound + io.multiplyConvergent := regMultiplyConvergent + io.multiplyRoundHalfToOdd := regMultiplyRoundHalfToOdd + io.multiplyNoTrim := regMultiplyNoTrim + } +} + +class TrimTypeDiv2Circuit[T <: Data: Ring: BinaryRepresentation](gen1: T, gen2: T, gen3: T) extends Module { + val io = IO(new Bundle { + val a = Input(gen1) + val div2Floor = Output(gen2) + val div2Ceiling = Output(gen2) + val div2RoundTowardsZero = Output(gen2) + val div2RoundTowardsInfinity = Output(gen2) + val div2RoundHalfDown = Output(gen2) + val div2RoundHalfUp = Output(gen2) + val div2RoundHalfTowardsZero = Output(gen2) + val div2Round = Output(gen2) + val div2Convergent = Output(gen2) + val div2RoundHalfToOdd = Output(gen2) + val div2NoTrim = Output(gen3) + }) + + val d = 2 + DspContext.withBinaryPointGrowth(0) { + val regDiv2Floor = RegNext(DspContext.withTrimType(RoundDown) { + io.a.div2(d) + }) + val regDiv2Ceiling = RegNext(DspContext.withTrimType(RoundUp) { + io.a.div2(d) + }) + val regDiv2RoundTowardsZero = RegNext(DspContext.withTrimType(RoundTowardsZero) { + io.a.div2(d) + }) + val regDiv2RoundTowardsInfinity = RegNext(DspContext.withTrimType(RoundTowardsInfinity) { + io.a.div2(d) + }) + val regDiv2RoundHalfDown = RegNext(DspContext.withTrimType(RoundHalfDown) { + io.a.div2(d) + }) + val regDiv2RoundHalfUp = RegNext(DspContext.withTrimType(RoundHalfUp) { + io.a.div2(d) + }) + val regDiv2RoundHalfTowardsZero = RegNext(DspContext.withTrimType(RoundHalfTowardsZero) { + io.a.div2(d) + }) + val regDiv2Round = RegNext(DspContext.withTrimType(RoundHalfTowardsInfinity) { + io.a.div2(d) + }) + val regDiv2Convergent = RegNext(DspContext.withTrimType(RoundHalfToEven) { + io.a.div2(d) + }) + val regDiv2RoundHalfToOdd = RegNext(DspContext.withTrimType(RoundHalfToOdd) { + io.a.div2(d) + }) + val regDiv2NoTrim = RegNext(DspContext.withTrimType(NoTrim) { + io.a.div2(d) + }) + + io.div2Floor := regDiv2Floor + io.div2Ceiling := regDiv2Ceiling + io.div2RoundTowardsZero := regDiv2RoundTowardsZero + io.div2RoundTowardsInfinity := regDiv2RoundTowardsInfinity + io.div2RoundHalfDown := regDiv2RoundHalfDown + io.div2RoundHalfTowardsZero := regDiv2RoundHalfTowardsZero + io.div2Round := regDiv2Round + io.div2Convergent := regDiv2Convergent + io.div2RoundHalfUp := regDiv2RoundHalfUp + io.div2RoundHalfToOdd := regDiv2RoundHalfToOdd + io.div2NoTrim := regDiv2NoTrim + } +} + +class TrimTypeMultiplyCircuitTester[T <: Data: Ring]( + c: TrimTypeMultiplyCircuit[T], + testVectors: (Double, Double, Double, Double, Double, Double, Double, Double, Double, Double, Double, Double, + Double)*) + extends PeekPokeTester(c) + with PeekPokeDspExtensions { + for ((a, b, m1, m2, m3, m4, m5, m6, m7, m8, m9, m10, m11) <- testVectors) { + poke(c.io.a, a) + poke(c.io.b, b) + + step(1) + + expect(c.io.multiplyFloor, m1) + expect(c.io.multiplyCeiling, m2) + expect(c.io.multiplyRoundTowardsZero, m3) + expect(c.io.multiplyRoundTowardsInfinity, m4) + expect(c.io.multiplyRoundHalfDown, m5) + expect(c.io.multiplyRoundHalfUp, m6) + expect(c.io.multiplyRoundHalfTowardsZero, m7) + expect(c.io.multiplyRound, m8) + expect(c.io.multiplyConvergent, m9) + expect(c.io.multiplyRoundHalfToOdd, m10) + expect(c.io.multiplyNoTrim, m11) + } +} + +class TrimTypeDiv2CircuitTester[T <: Data: Ring: BinaryRepresentation]( + c: TrimTypeDiv2Circuit[T], + testVectors: (Double, Double, Double, Double, Double, Double, Double, Double, Double, Double, Double, Double)*) + extends PeekPokeTester(c) + with PeekPokeDspExtensions { + for ((a, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11) <- testVectors) { + poke(c.io.a, a) + + step(1) + + expect(c.io.div2Floor, d1) + expect(c.io.div2Ceiling, d2) + expect(c.io.div2RoundTowardsZero, d3) + expect(c.io.div2RoundTowardsInfinity, d4) + expect(c.io.div2RoundHalfDown, d5) + expect(c.io.div2RoundHalfUp, d6) + expect(c.io.div2RoundHalfTowardsZero, d7) + expect(c.io.div2Round, d8) + expect(c.io.div2Convergent, d9) + expect(c.io.div2RoundHalfToOdd, d10) + expect(c.io.div2NoTrim, d11) + } +} + +class OverflowTypeCircuitTester[T <: Data: Ring, U <: Data: Ring]( + c: OverflowTypeCircuit[T, U], + testVectors: (Double, Double, Double, Double, Double, Double)*) + extends PeekPokeTester(c) + with PeekPokeDspExtensions { + for ((a, b, e1, e2, e3, e4) <- testVectors) { + poke(c.io.a, a) + poke(c.io.b, b) + + step(1) + + expect(c.io.addWrap, e1) + expect(c.io.addGrow, e2) + expect(c.io.subWrap, e3) + expect(c.io.subGrow, e4) + } +} + +class OverflowTypeCircuit[T <: Data: Ring, U <: Data: Ring](gen1: T, gen2: T, gen3: U) extends Module { + val io = IO(new Bundle { + val a = Input(gen1) + val b = Input(gen1) + val addWrap = Output(gen2) + val addGrow = Output(gen2) + val subWrap = Output(gen3) + val subGrow = Output(gen3) + }) + + val regAddWrap = RegNext(DspContext.withOverflowType(Wrap) { io.a.context_+(io.b) }) + val regAddGrow = RegNext(DspContext.withOverflowType(Grow) { io.a.context_+(io.b) }) + + val regSubWrap = RegNext(DspContext.withOverflowType(Wrap) { io.a.context_-(io.b) }) + val regSubGrow = RegNext( + if (io.a.isInstanceOf[UInt]) 0.U else DspContext.withOverflowType(Grow) { io.a.context_-(io.b) } + ) + // val regSubGrow = RegNext(DspContext.withOverflowType(Grow) { io.a context_- io.b }) + + io.addWrap := regAddWrap + io.addGrow := regAddGrow + io.subWrap := regSubWrap + io.subGrow := regSubGrow +} + +class NumbersEmptyTester[T <: Module](c: T) extends PeekPokeTester(c) with PeekPokeDspExtensions + +class ShouldBeBadUIntSubtractWithGrow extends Module { + val io = IO(new Bundle { + val a = Input(UInt(4.W)) + val b = Input(UInt(4.W)) + val o = Output(UInt(4.W)) + }) + val r = RegNext(DspContext.withOverflowType(Grow) { io.a - io.b }) + io.o := r +} + +class BadUIntSubtractWithGrow2[T <: Data: Ring](gen: T) extends Module { + val io = IO(new Bundle { + val a = Input(gen) + val b = Input(gen) + val o = Output(gen) + }) + val r = RegNext(DspContext.withOverflowType(Grow) { io.a.context_-(io.b) }) + io.o := r +} + +class BinaryReprTester(c: BinaryRepr[UInt, SInt, FixedPoint]) extends PeekPokeTester(c) with PeekPokeDspExtensions { + poke(c.io.uIn, 0) + expect(c.io.uOut, 0.0) + + poke(c.io.sIn, 0) + expect(c.io.sOut, 0.0) + + poke(c.io.fIn, 0.0) + expect(c.io.fOut, 0.0) + + step(1) + + poke(c.io.sIn, 1) + expect(c.io.sOut, 0.0) + + poke(c.io.fIn, 1.0) + expect(c.io.fOut, 0.0) + + step(1) + + poke(c.io.sIn, -1) + expect(c.io.sOut, 1.0) + + poke(c.io.fIn, -1.0) + expect(c.io.fOut, 1.0) + + step(1) + + poke(c.io.uIn, 3) + expect(c.io.uDiv2, 0.0) + + poke(c.io.sIn, 3) + expect(c.io.sDiv2, 0.0) + + poke(c.io.fIn, 3.5) + expect(c.io.fOut, 0.0) + + step(1) + poke(c.io.uIn, 48) + expect(c.io.uDiv2, 12.0) + + poke(c.io.sIn, 32) + expect(c.io.sDiv2, 8.0) + + poke(c.io.fIn, 14.0) + expect(c.io.fDiv2, 3.5) +} + +class BinaryRepr[TU <: Data: RealBits, TS <: Data: RealBits, TF <: Data: RealBits](uGen: TU, sGen: TS, fGen: TF) + extends Module { + val io = IO(new Bundle { + val uIn = Input(uGen) + val sIn = Input(sGen) + val fIn = Input(fGen) + val uOut = Output(UInt(1.W)) + val sOut = Output(UInt(1.W)) + val fOut = Output(UInt(1.W)) + + val uDiv2 = Output(uGen) + val sDiv2 = Output(sGen) + val fDiv2 = Output(fGen) + + val uMul2 = Output(UInt((uGen.getWidth * 2).W)) + val sMul2 = Output(SInt((sGen.getWidth * 2).W)) + val fMul2 = Output(FixedPoint(20.W, 2.BP)) + }) + + io.uOut := io.uIn.signBit + io.sOut := io.sIn.signBit + io.fOut := io.fIn.signBit + + io.uDiv2 := io.uIn.div2(2) + io.sDiv2 := io.sIn.div2(2) + io.fDiv2 := io.fIn.div2(2) + + io.uMul2 := io.uIn.mul2(2) + io.sMul2 := io.sIn.mul2(2) + io.fMul2 := io.fIn.mul2(2) +} diff --git a/src/test/scala/dsptools/numbers/ParameterizedOpSpec.scala b/src/test/scala/dsptools/numbers/ParameterizedOpSpec.scala new file mode 100644 index 00000000..ac50a987 --- /dev/null +++ b/src/test/scala/dsptools/numbers/ParameterizedOpSpec.scala @@ -0,0 +1,156 @@ +// SPDX-License-Identifier: Apache-2.0 + +package dsptools.numbers + +import breeze.math.Complex +import chisel3.{fromDoubleToLiteral => _, fromIntToBinaryPoint => _, _} +import fixedpoint._ +import chiseltest._ +import chiseltest.iotesters._ +import org.scalatest.freespec.AnyFreeSpec +import dsptools._ +import dsptools.misc.PeekPokeDspExtensions + +//scalastyle:off magic.number + +class ParameterizedNumberOperation[T <: Data: Ring]( + inputGenerator: () => T, + outputGenerator: () => T, + val op: String = "+") + extends Module { + val io = IO(new Bundle { + val a1: T = Input(inputGenerator().cloneType) + val a2: T = Input(inputGenerator().cloneType) + val c: T = Output(outputGenerator().cloneType) + }) + + val register1 = Reg(outputGenerator().cloneType) + + register1 := { + op match { + case "+" => io.a1 + io.a2 + case "-" => io.a1 - io.a2 + case "*" => DspContext.withTrimType(NoTrim) { io.a1 * io.a2 } + // case "/" => io.a1 / io.a2 + case _ => throw new Exception(s"Bad operator $op passed to ParameterizedNumberOperation") + } + } + + io.c := register1 +} + +class ParameterizedOpTester[T <: Data: Ring](c: ParameterizedNumberOperation[T]) + extends PeekPokeTester(c) + with PeekPokeDspExtensions { + for { + i <- BigDecimal(0.0) to 1.0 by 0.25 + j <- BigDecimal(0.0) to 4.0 by 0.5 + } { + val expected = c.op match { + case "+" => i + j + case "-" => i - j + case "*" => i * j + case _ => i + j + } + + poke(c.io.a1, i) + poke(c.io.a2, j) + step(1) + + val result = peek(c.io.c) + + expect(c.io.c, expected, s"$i ${c.op} $j => $result, should have been $expected") + } +} + +class ParameterizedOpSpec extends AnyFreeSpec with ChiselScalatestTester { + """ + ParameterizedNumericOperation will + """ - { + def realGenerator(): DspReal = new DspReal + def fixedInGenerator(): FixedPoint = FixedPoint(16.W, 8.BP) + def fixedOutGenerator(): FixedPoint = FixedPoint(48.W, 8.BP) + + "process Real numbers with the basic mathematical operations" - { + Seq("+", "-", "*").foreach { operation => + s"operation $operation should work for all inputs" in { + test(new ParameterizedNumberOperation(realGenerator, realGenerator, operation)) + .withAnnotations(Seq(VerilatorBackendAnnotation)) + .runPeekPoke(new ParameterizedOpTester(_)) + } + } + } + "process Fixed point numbers with the basic mathematical operations" - { + Seq("+", "-", "*").foreach { operation => + s"operation $operation should work for all inputs" in { + test(new ParameterizedNumberOperation(fixedInGenerator, fixedOutGenerator, operation)) + .runPeekPoke(new ParameterizedOpTester(_)) + } + } + } + } +} + +class ComplexOpTester[T <: DspComplex[_]](c: ParameterizedNumberOperation[T]) + extends PeekPokeTester(c) + with PeekPokeDspExtensions { + for { + i <- (BigDecimal(-1.0) to 1.0 by 0.25).map(_.toDouble) + j <- (BigDecimal(-4.0) to 4.0 by 0.5).map(_.toDouble) + } { + val c1 = Complex(i, j) + val c2 = Complex(j, i) + + val expected = c.op match { + case "+" => c1 + c2 + case "-" => c1 - c2 + case "*" => c1 * c2 + case _ => c1 + c2 + } + + poke(c.io.a1, c1) + poke(c.io.a2, c2) + step(1) + + val result = peek(c.io.c) + + expect(c.io.c, expected, s"$i ${c.op} $j => $result, should have been $expected") + } +} + +class ComplexOpSpec extends AnyFreeSpec with ChiselScalatestTester { + """ + ParameterizedNumericOperation will + """ - { + def complexFixedGenerator(): DspComplex[FixedPoint] = { + DspComplex(FixedPoint(16.W, 2.BP), FixedPoint(16.W, 2.BP)) + } + + def complexFixedOutputGenerator(): DspComplex[FixedPoint] = { + DspComplex(FixedPoint(48.W, 4.BP), FixedPoint(48.W, 4.BP)) + } + + def complexRealGenerator(): DspComplex[DspReal] = { + DspComplex(DspReal(1.0), DspReal(1.0)) + } + + "process DspComplex[Real] numbers with the basic mathematical operations" - { + Seq("+", "-", "*").foreach { operation => + s"operation $operation should work for all inputs" in { + test(new ParameterizedNumberOperation(complexRealGenerator, complexRealGenerator, operation)) + .withAnnotations(Seq(VerilatorBackendAnnotation)) + .runPeekPoke(new ComplexOpTester(_)) + } + } + } + + "process DspComplex[FixedPoint] numbers with the basic mathematical operations" - { + Seq("+", "-", "*").foreach { operation => + s"operation $operation should work for all inputs" in { + test(new ParameterizedNumberOperation(complexFixedGenerator, complexFixedOutputGenerator, operation)) + .runPeekPoke(new ComplexOpTester(_)) + } + } + } + } +} diff --git a/src/test/scala/dsptools/numbers/TypeclassSpec.scala b/src/test/scala/dsptools/numbers/TypeclassSpec.scala new file mode 100644 index 00000000..79d10033 --- /dev/null +++ b/src/test/scala/dsptools/numbers/TypeclassSpec.scala @@ -0,0 +1,186 @@ +// SPDX-License-Identifier: Apache-2.0 + +package dsptools.numbers + +import chisel3.{fromDoubleToLiteral => _, fromIntToBinaryPoint => _, _} +import fixedpoint._ +import chiseltest._ +import chiseltest.iotesters._ +import dsptools.misc.PeekPokeDspExtensions +import org.scalatest.freespec.AnyFreeSpec +import org.scalatest.matchers.should.Matchers + +/* + * These tests mostly exist to ensure that expressions of the form + * Typeclass[T].function() + * compile. Issue #17 talks about how this wasn't working for some + * typeclasses + */ + +class FuncModule[T <: Data](gen: T, func: T => T) extends Module { + val io = IO(new Bundle { + val in = Input(gen.cloneType) + val out = Output(gen.cloneType) + }) + io.out := func(io.in) +} + +object RingFunc { + def apply[T <: Data: Ring](in: T): T = { + val zero = Ring[T].zero + val one = Ring[T].one + Ring[T].times(one, Ring[T].plus(in, zero)) + } +} +class RingModule[T <: Data: Ring](gen: T) extends FuncModule(gen, { in: T => RingFunc(in) }) + +object EqFunc { + def apply[T <: Data: Eq: Ring](in: T): T = shadow.Mux(Eq[T].eqv(in, Ring[T].zero), Ring[T].one, in) +} +class EqModule[T <: Data: Eq: Ring](gen: T) extends FuncModule(gen, { in: T => EqFunc(in) }) + +object IntegerFunc { + def apply[T <: Data: Integer](in: T): T = + Integer[T].round(in) + IsIntegral[T].mod(in, in) +} +class IntegerModule[T <: Data: Integer](gen: T) extends FuncModule(gen, { in: T => IntegerFunc(in) }) + +object OrderFunc { + def apply[T <: Data: Order](in: T): T = + Order[T].min(in, in) +} +class OrderModule[T <: Data: Order](gen: T) extends FuncModule(gen, { in: T => OrderFunc(in) }) + +object PartialOrderFunc { + def apply[T <: Data: PartialOrder](in: T): T = + shadow.Mux(PartialOrder[T].partialCompare(in, in).bits.eq, in, in) +} +class PartialOrderModule[T <: Data: PartialOrder: Ring](gen: T) + extends FuncModule(gen, { in: T => PartialOrderFunc(in) }) + +class SignedModule[T <: Data: Signed](gen: T) + extends FuncModule( + gen, + { in: T => shadow.Mux(Signed[T].sign(in).neg, Signed[T].abs(in), shadow.Mux(Signed[T].sign(in).zero, in, in)) } + ) + +class BinaryRepresentationModule[T <: Data: BinaryRepresentation](gen: T) + extends FuncModule( + gen, + { in: T => (((in << 2) >> 1) << 3.U) >> 2.U } + ) + +trait FuncTester[T <: Data, V] { + def dut: FuncModule[T] + def testInputs: Seq[V] + def testOutputs: Seq[V] + + def myPoke(port: T, value: V): Unit + def myExpect(port: T, value: V): Unit + + testInputs.zip(testOutputs).foreach { + case (in, out) => + myPoke(dut.io.in, in) + myExpect(dut.io.out, out) + } +} + +class SIntFuncTester[T <: FuncModule[SInt]](dut: T, val testInputs: Seq[Int], val testOutputs: Seq[Int]) + extends PeekPokeTester(dut) + with PeekPokeDspExtensions + with FuncTester[SInt, Int] { + def myPoke(port: SInt, value: Int) = poke(port, value) + def myExpect(port: SInt, value: Int) = expect(port, value) +} + +class FixedPointFuncTester[T <: FuncModule[FixedPoint]]( + dut: T, + val testInputs: Seq[Double], + val testOutputs: Seq[Double]) + extends PeekPokeTester(dut) + with PeekPokeDspExtensions + with FuncTester[FixedPoint, Double] { + def myPoke(port: FixedPoint, value: Double) = poke(port, value) + def myExpect(port: FixedPoint, value: Double) = expect(port, value) +} + +class DspRealFuncTester[T <: FuncModule[DspReal]](dut: T, val testInputs: Seq[Double], val testOutputs: Seq[Double]) + extends PeekPokeTester(dut) + with PeekPokeDspExtensions + with FuncTester[DspReal, Double] { + def myPoke(port: DspReal, value: Double) = poke(port, value) + def myExpect(port: DspReal, value: Double) = expect(port, value) +} + +class TypeclassSpec extends AnyFreeSpec with ChiselScalatestTester { + "Ring[T].func() should work" in { + test(new RingModule(SInt(10.W))) + .runPeekPoke(new SIntFuncTester(_, Seq(2, -3), Seq(2, -3))) + + test(new RingModule(FixedPoint(10.W, 4.BP))) + .runPeekPoke(new FixedPointFuncTester(_, Seq(2, -3), Seq(2, -3))) + + test(new RingModule(DspReal())) + .withAnnotations(Seq(VerilatorBackendAnnotation)) + .runPeekPoke(new DspRealFuncTester(_, Seq(2, -3), Seq(2, -3))) + } + "Eq[T].func() should work" in { + test(new EqModule(SInt(10.W))) + .runPeekPoke(new SIntFuncTester(_, Seq(2, 0), Seq(2, 1))) + + test(new EqModule(FixedPoint(10.W, 4.BP))) + .runPeekPoke(new FixedPointFuncTester(_, Seq(2, 0), Seq(2, 1))) + + test(new EqModule(DspReal())) + .withAnnotations(Seq(VerilatorBackendAnnotation)) + .runPeekPoke(new DspRealFuncTester(_, Seq(2, 0), Seq(2, 1))) + } + "Integer[T].func() should work" in { + test(new IntegerModule(SInt(10.W))) + .runPeekPoke(new SIntFuncTester(_, Seq(2, -3), Seq(2, -3))) + } + "Order[T].func() should work" in { + test(new OrderModule(SInt(10.W))) + .runPeekPoke(new SIntFuncTester(_, Seq(2, -3), Seq(2, -3))) + + test(new OrderModule(FixedPoint(10.W, 4.BP))) + .runPeekPoke(new FixedPointFuncTester(_, Seq(2, -3), Seq(2, -3))) + + test(new OrderModule(DspReal())) + .withAnnotations(Seq(VerilatorBackendAnnotation)) + .runPeekPoke(new DspRealFuncTester(_, Seq(2, -3), Seq(2, -3))) + } + "PartialOrder[T].func() should work" in { + test(new PartialOrderModule(SInt(10.W))) + .runPeekPoke(new SIntFuncTester(_, Seq(2, -3), Seq(2, -3))) + + test(new PartialOrderModule(FixedPoint(10.W, 4.BP))) + .runPeekPoke(new FixedPointFuncTester(_, Seq(2, -3), Seq(2, -3))) + + test(new PartialOrderModule(DspReal())) + .withAnnotations(Seq(VerilatorBackendAnnotation)) + .runPeekPoke(new DspRealFuncTester(_, Seq(2, -3), Seq(2, -3))) + } + "Signed[T].func() should work" in { + test(new SignedModule(SInt(10.W))) + .runPeekPoke(new SIntFuncTester(_, Seq(2, -3), Seq(2, 3))) + + test(new SignedModule(FixedPoint(10.W, 4.BP))) + .runPeekPoke(new FixedPointFuncTester(_, Seq(2, -3), Seq(2, 3))) + + test(new SignedModule(DspReal())) + .withAnnotations(Seq(VerilatorBackendAnnotation)) + .runPeekPoke(new DspRealFuncTester(_, Seq(2, -3), Seq(2, 3))) + } + "BinaryRepresentation[T].func() should work" in { + test(new BinaryRepresentationModule(SInt(10.W))) + .runPeekPoke(new SIntFuncTester(_, Seq(2, 3), Seq(8, 12))) + + test(new BinaryRepresentationModule(FixedPoint(10.W, 4.BP))) + .runPeekPoke(new FixedPointFuncTester(_, Seq(2, 3), Seq(8, 12))) + + test(new BinaryRepresentationModule(DspReal())) + .withAnnotations(Seq(VerilatorBackendAnnotation)) + .runPeekPoke(new DspRealFuncTester(_, Seq(2, 3), Seq(8, 12))) + } +} diff --git a/src/test/scala/examples/DspComplexSpec.scala b/src/test/scala/examples/DspComplexSpec.scala new file mode 100644 index 00000000..e1dcebc3 --- /dev/null +++ b/src/test/scala/examples/DspComplexSpec.scala @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: Apache-2.0 + +package examples + +import chisel3._ +import chisel3.testers.BasicTester +import chiseltest._ +import dsptools.numbers.DspComplex +import org.scalatest.propspec.AnyPropSpec + +//scalastyle:off magic.number +class DspComplexExamples extends Module { + val io = IO(new Bundle { + val in = Input(DspComplex(SInt(5.W), SInt(5.W))) + val outJ = Output(DspComplex(SInt(5.W), SInt(5.W))) + val inByJ = Output(DspComplex(SInt(5.W), SInt(5.W))) + val inByJShortcut = Output(DspComplex(SInt(5.W), SInt(5.W))) + }) + + io.outJ := DspComplex.j[SInt] + io.inByJ := io.in * DspComplex.j[SInt] + io.inByJShortcut := io.in.mulj() +} + +class DspComplexExamplesTester extends BasicTester { + val dut = Module(new DspComplexExamples) + + dut.io.in.real := 7.S + dut.io.in.imag := (-4).S + + printf(s"inByJ.real: %d\n", dut.io.inByJ.real) + printf(s"inByJ.imag: %d\n", dut.io.inByJ.imag) + + printf(s"inByJShortcut.real: %d\n", dut.io.inByJShortcut.real) + printf(s"inByJShortcut.imag: %d\n", dut.io.inByJShortcut.imag) + + assert(dut.io.outJ.real === 0.S) + assert(dut.io.outJ.imag === 1.S) + + assert(dut.io.inByJ.real === 4.S) + assert(dut.io.inByJ.imag === 7.S) + + assert(dut.io.inByJShortcut.real === 4.S) + assert(dut.io.inByJShortcut.imag === 7.S) + + stop() +} + +class SIntTester extends BasicTester { + val x = 10.S + + val xcopy = Wire(x.cloneType) + xcopy := x + + assert(x === xcopy) + + val y = DspComplex((-4).S, (-1).S) + + assert(y.real === (-4).S) + assert(y.imag === (-1).S) + + stop() +} + +class DspComplexSpec extends AnyPropSpec with ChiselScalatestTester { + property("using j with complex numbers should work") { + test(new DspComplexExamplesTester) + .runUntilStop() + } + + property("assigning Wire(SInt) should work") { + test(new SIntTester) + .runUntilStop() + } +} diff --git a/src/test/scala/examples/ParameterizedAdderSpec.scala b/src/test/scala/examples/ParameterizedAdderSpec.scala index bca1d720..3edc0e51 100644 --- a/src/test/scala/examples/ParameterizedAdderSpec.scala +++ b/src/test/scala/examples/ParameterizedAdderSpec.scala @@ -44,10 +44,12 @@ class ParameterizedAdderSpec extends AnyFlatSpec with ChiselScalatestTester { behavior.of("parameterized adder circuit on blackbox real") - ignore should "allow registers to be declared that infer widths" in { + it should "allow registers to be declared that infer widths" in { def getReal: DspReal = new DspReal - test(new ParameterizedAdder(() => getReal)).runPeekPoke(new ParameterizedAdderTester(_)) + test(new ParameterizedAdder(() => getReal)) + .withAnnotations(Seq(VerilatorBackendAnnotation)) + .runPeekPoke(new ParameterizedAdderTester(_)) } behavior.of("parameterized adder circuit on fixed point") diff --git a/src/test/scala/examples/ParameterizedSaturatingAdderSpec.scala b/src/test/scala/examples/ParameterizedSaturatingAdderSpec.scala new file mode 100644 index 00000000..1534e00e --- /dev/null +++ b/src/test/scala/examples/ParameterizedSaturatingAdderSpec.scala @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: Apache-2.0 + +package examples + +import chisel3._ +import chiseltest._ +import chiseltest.iotesters._ +import dsptools.misc.PeekPokeDspExtensions +import dsptools.{DspContext, Saturate} +import dsptools.numbers._ +import org.scalatest.flatspec.AnyFlatSpec + +class ParameterizedSaturatingAdder[T <: Data: Integer](gen: => T) extends Module { + val io = IO(new Bundle { + val a1: T = Input(gen.cloneType) + val a2: T = Input(gen.cloneType) + val normalSum = Output(gen.cloneType) + val saturatedSum = Output(gen.cloneType) + }) + + val register1 = Reg(gen.cloneType) + val register2 = Reg(gen.cloneType) + + println(s"ParameterizedSaturatingAdder ${DspContext.current}") + register1 := io.a1 + io.a2 + + DspContext.withOverflowType(Saturate) { + register2 := io.a1 + io.a2 + } + io.normalSum := register1 + io.saturatedSum := register2 +} + +class ParameterizedSaturatingAdderTester[T <: Data: Integer](c: ParameterizedSaturatingAdder[T], width: Int) + extends PeekPokeTester(c) + with PeekPokeDspExtensions { + + val min = -(1 << (width - 1)) + val max = (1 << (width - 1)) - 1 + println("Min = " + min.toString) + println("Max = " + max.toString) + def overflowint(x: Int): Int = { + if (x > max) overflowint(min + x - max - 1) + else if (x < min) overflowint(max + x - min + 1) + else x + } + def saturateint(x: Int): Int = { + if (x > max) max + else if (x < min) min + else x + } + for { + i <- min to max + j <- min to max + } { + println(s"I=$i and J=$j") + poke(c.io.a1, i) + poke(c.io.a2, j) + step(1) + + val resultNormal = peek(c.io.normalSum) + val resultSaturated = peek(c.io.saturatedSum) + + expect( + c.io.normalSum, + overflowint(i + j), + s"parameterized normal adder $i + $j => $resultNormal should have been ${overflowint(i + j)}" + ) + expect( + c.io.saturatedSum, + saturateint(i + j), + s"parameterized saturating adder $i + $j => $resultSaturated should have been ${saturateint(i + j)}" + ) + } +} + +class ParameterizedSaturatingAdderSpec extends AnyFlatSpec with ChiselScalatestTester { + behavior.of("parameterized saturating adder circuit on SInt") + + it should "allow registers to be declared that infer widths" ignore { + + val width = 3 + def getSInt: SInt = SInt(width.W) + + test(new ParameterizedSaturatingAdder(getSInt)) + .runPeekPoke(new ParameterizedSaturatingAdderTester(_, width)) + } + +} diff --git a/src/test/scala/examples/RealAdderSpec.scala b/src/test/scala/examples/RealAdderSpec.scala new file mode 100644 index 00000000..398c5704 --- /dev/null +++ b/src/test/scala/examples/RealAdderSpec.scala @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: Apache-2.0 + +package examples + +import chisel3._ +import chiseltest._ +import chiseltest.iotesters._ +import dsptools.misc.PeekPokeDspExtensions +import dsptools.numbers.DspReal +import org.scalatest.flatspec.AnyFlatSpec + +class RealAdder extends Module { + val io = IO(new Bundle { + val a1 = Input(new DspReal) + val a2 = Input(new DspReal) + val c = Output(new DspReal) + }) + + val register1 = Reg(new DspReal) + + register1 := io.a1 + io.a2 + + io.c := register1 +} + +class RealAdderTester(c: RealAdder) extends PeekPokeTester(c) with PeekPokeDspExtensions { + for { + i <- (BigDecimal(0.0) to 1.0 by 0.25).map(_.toDouble) + j <- (BigDecimal(0.0) to 4.0 by 0.5).map(_.toDouble) + } { + poke(c.io.a1, i) + poke(c.io.a2, j) + step(1) + + expect(c.io.c, i + j) + } +} + +class RealAdderSpec extends AnyFlatSpec with ChiselScalatestTester { + behavior.of("adder circuit on blackbox real") + + it should "allow registers to be declared that infer widths" in { + test(new RealAdder) + .withAnnotations(Seq(VerilatorBackendAnnotation)) + .runPeekPoke(new RealAdderTester(_)) + } +} diff --git a/src/test/scala/examples/SimpleCaseClassBundleSpec.scala b/src/test/scala/examples/SimpleCaseClassBundleSpec.scala new file mode 100644 index 00000000..d1394782 --- /dev/null +++ b/src/test/scala/examples/SimpleCaseClassBundleSpec.scala @@ -0,0 +1,48 @@ +//// SPDX-License-Identifier: Apache-2.0 +// +package examples + +import chisel3._ +import chiseltest._ +import chiseltest.iotesters._ +import org.scalatest.flatspec.AnyFlatSpec + +//scalastyle:off magic.number + +//case class CaseClassBundle(a: SInt) extends Bundle +//case class CaseClassBundle(underlying: SInt) extends Bundle { +// val underlying = gen.cloneType +// override def cloneType: this.type = new CaseClassBundle(underlying.cloneType).asInstanceOf[this.type] +//} +class CaseClassBundle(gen: SInt) extends Bundle { + val underlying = gen +} + +class SimpleCaseClassModule(gen: SInt) extends Module { + val io = IO(new Bundle { + val in = Input(new CaseClassBundle(gen)) + val out = Output(new CaseClassBundle(gen)) + }) + + val register1 = Reg(io.out.cloneType) + + register1 := io.in + + io.out := register1 +} + +class SimpleCaseClassBundleTester(c: SimpleCaseClassModule) extends PeekPokeTester(c) { + + poke(c.io.in.underlying, 7) + step(1) + expect(c.io.out.underlying, 7) +} + +class SimpleCaseClassBundleSpec extends AnyFlatSpec with ChiselScalatestTester { + behavior.of("SimpleCaseClassBundle") + + it should "push number through with one step delay" in { + test(new SimpleCaseClassModule(SInt(5.W))) + .runPeekPoke(new SimpleCaseClassBundleTester(_)) + } +} diff --git a/src/test/scala/examples/SimpleComplexMultiplierSpec.scala b/src/test/scala/examples/SimpleComplexMultiplierSpec.scala new file mode 100644 index 00000000..adc03eb1 --- /dev/null +++ b/src/test/scala/examples/SimpleComplexMultiplierSpec.scala @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: Apache-2.0 + +package examples + +import chisel3.{fromDoubleToLiteral => _, fromIntToBinaryPoint => _, _} +import fixedpoint._ +import chiseltest._ +import chiseltest.iotesters._ +import chisel3.{Bundle, Module} +import dsptools.misc.PeekPokeDspExtensions +import dsptools.numbers._ +import org.scalatest.flatspec.AnyFlatSpec + +//scalastyle:off magic.number +class SimpleComplexMultiplier extends Module { + val io = IO(new Bundle { + val a1 = Input(DspComplex(FixedPoint(6.W, 4.BP), FixedPoint(6.W, 4.BP))) + val a2 = Input(DspComplex(FixedPoint(8.W, 1.BP), FixedPoint(8.W, 1.BP))) + val c = Output(DspComplex(FixedPoint(14.W, 5.BP), FixedPoint(14.W, 5.BP))) + }) + // spatialAssert(Seq(io.a1), Seq(io.c), 5) + // spatialAssert(Seq(io.a2), Seq(io.c), "group1") + + val register1 = Reg(io.c.cloneType) + + // val registerReal = Reg(io.a1.real) + // val registerimag = Reg(io.a1.imag) + + register1 := io.a1 * io.a2 + + io.c := register1 +} + +class SimpleComplexMultiplierTester(c: SimpleComplexMultiplier) extends PeekPokeTester(c) with PeekPokeDspExtensions { + for { + i <- (BigDecimal(0.0) to 1.0 by 0.25).map(_.toDouble) + j <- (BigDecimal(0.0) to 4.0 by 0.5).map(_.toDouble) + } { + val expected = i * j + + poke(c.io.a1.real, i) + poke(c.io.a1.imag, 0.0) + poke(c.io.a2.real, j) + poke(c.io.a2.imag, 0.0) + step(1) + + expect(c.io.c.real, i * j) + + println(s"SimpleComplexMultiplier: $i * $j should make $expected got ${peek(c.io.c.real)}") + } +} + +class SimpleComplexMultiplierSpec extends AnyFlatSpec with ChiselScalatestTester { + behavior.of("SimpleComplexMultiplier") + + it should "multiply complex numbers excellently" in { + test(new SimpleComplexMultiplier) + .runPeekPoke(new SimpleComplexMultiplierTester(_)) + } +} diff --git a/src/test/scala/examples/SimpleDspModuleSpec.scala b/src/test/scala/examples/SimpleDspModuleSpec.scala new file mode 100644 index 00000000..96b13de7 --- /dev/null +++ b/src/test/scala/examples/SimpleDspModuleSpec.scala @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: Apache-2.0 + +package examples + +// Allows you to use Chisel Module, Bundle, etc. +import chisel3.{fromDoubleToLiteral => _, fromIntToBinaryPoint => _, _} +import dsptools.misc.PeekPokeDspExtensions +// Allows you to use FixedPoint +import fixedpoint._ +// If you want to take advantage of type classes >> Data:RealBits (i.e. pass in FixedPoint or DspReal) +import dsptools.numbers._ +// Enables you to set DspContext's for things like overflow behavior, rounding modes, etc. +import dsptools.DspContext +// Use chiseltest +import chiseltest._ +// Use chiseltest's iotesters inferface +import chiseltest.iotesters._ +// Scala unit testing style +import org.scalatest.flatspec.AnyFlatSpec + +// IO Bundle. This also creates x, y, z inputs/outputs (direction must be specified at some IO hierarchy level) +// of the type you specify via gen (must be Data:RealBits = UInt, SInt, FixedPoint, DspReal) +class SimpleDspIo[T <: Data: RealBits](gen: T) extends Bundle { + val x = Input(gen.cloneType) + val y = Input(gen.cloneType) + val z = Output(gen.cloneType) +} + +// Parameterized Chisel Module; takes in type parameters as explained above +class SimpleDspModule[T <: Data: RealBits](gen: T, val addPipes: Int) extends Module { + // This is how you declare an IO with parameters + val io = IO(new SimpleDspIo(gen)) + // Output will be current x + y addPipes clk cycles later + // Note that this relies on the fact that type classes have a special + that + // add addPipes # of ShiftRegister after the sum. If you don't wrap the sum in + // DspContext.withNumAddPipes(addPipes), the default # of addPipes is used. + DspContext.withNumAddPipes(addPipes) { + io.z := io.x.context_+(io.y) + } +} + +// You create a tester that must extend PeekPokeDspExtensions to support Dsp type peeks/pokes (with doubles, complex, etc.) +class SimpleDspModuleTester[T <: Data: RealBits](c: SimpleDspModule[T]) + extends PeekPokeTester(c) + with PeekPokeDspExtensions { + val x = Seq(-1.1, -0.4, 0.4, 1.1) + val z = x.map(2 * _) + for (i <- 0 until (x.length + c.addPipes)) { + val in = x(i % x.length) + // Feed in to the x, y inputs + poke(c.io.x, in) + poke(c.io.y, in) + + if (i >= c.addPipes) { + // Expect that the z output matches the expected value @ z(i - c.addPipes) to some tolerance + // as described below + expect(c.io.z, z(i - c.addPipes)) + } + // Step the clock by 1 period + step(1) + } +} + +// Scala style testing +class SimpleDspModuleSpec extends AnyFlatSpec with ChiselScalatestTester { + + behavior.of("simple dsp module") + + it should "properly add fixed point types" ignore { + // Run the dsp tester by following this style: You need to pass in the module to test. Note that here, you're + // testing the module with inputs/outputs of FixedPoint type (Q15.12) and 3 registers (for retiming) at the output. + // You could alternatively use DspReal() + // Scala keeps track of which tests pass/fail. + // Supposedly, Chisel3 testing infrastructure might be overhauled to reduce the amount of boilerplate, + // but this is currently the endorsed way to do things. + test(new SimpleDspModule(FixedPoint(16.W, 12.BP), addPipes = 3)) + .runPeekPoke(new SimpleDspModuleTester(_)) + } + +} diff --git a/src/test/scala/examples/StreamingAutocorrelatorSpec.scala b/src/test/scala/examples/StreamingAutocorrelatorSpec.scala new file mode 100644 index 00000000..166e75cc --- /dev/null +++ b/src/test/scala/examples/StreamingAutocorrelatorSpec.scala @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: Apache-2.0 + +package examples + +//scalastyle:off magic.number + +import chisel3._ +import chiseltest._ +import chiseltest.iotesters._ +import dsptools.misc.PeekPokeDspExtensions +import dsptools.numbers.implicits._ +import org.scalatest.flatspec.AnyFlatSpec + +class StreamingAutocorrelatorTester(c: StreamingAutocorrelator[SInt]) + extends PeekPokeTester(c) + with PeekPokeDspExtensions { + + for (num <- -5 to 5) { + poke(c.io.input, BigInt(num)) + step(1) + println(peek(c.io.output).toString()) + } +} + +class StreamingAutocorrelatorSpec extends AnyFlatSpec with ChiselScalatestTester { + "StreamingAutocorrelatorFIR" should "compute a running average like thing" in { + val taps = Seq.tabulate(3) { x => x.S } + //implicit val DefaultDspContext = DspContext() + //implicit val evidence = (context :DspContext) => new SIntRing()(context) + + test(new StreamingAutocorrelator(SInt(10.W), SInt(20.W), 2, 3)) + .runPeekPoke(new StreamingAutocorrelatorTester(_)) + } +} diff --git a/src/test/scala/examples/TransposedStreamFIRSpec.scala b/src/test/scala/examples/TransposedStreamFIRSpec.scala new file mode 100644 index 00000000..f48c4482 --- /dev/null +++ b/src/test/scala/examples/TransposedStreamFIRSpec.scala @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: Apache-2.0 + +package examples + +//scalastyle:off magic.number + +import chisel3._ +import chiseltest._ +import chiseltest.iotesters._ +import dsptools.numbers.implicits._ +import org.scalatest.flatspec.AnyFlatSpec +import dsptools.examples.{ConstantTapTransposedStreamingFIR, TransposedStreamingFIR} + +class ConstantTapTransposedStreamingTester(c: ConstantTapTransposedStreamingFIR[SInt, Int]) extends PeekPokeTester(c) { + val smallest = -5 + val biggest = 5 + println(s"Taps are ${c.taps.toString}") + + def checkAnswer(n: Int): Int = { + // assumes inputs increase by 1 each time + c.taps.zipWithIndex.foldLeft(0) { + case (s, (tap, idx)) => + s + tap * (if (n - idx >= smallest) n - idx else 0) + } + } + // initialize old state to 0 + poke(c.io.input.valid, 1) + poke(c.io.input.bits, BigInt(0)) + step(c.taps.length) + + for (num <- smallest to biggest) { + poke(c.io.input.bits, BigInt(-7)) + poke(c.io.input.valid, 0) + for (i <- 0 until 10) { + step(1) + expect(c.io.output.valid, 0, "Output should not be valid if input is invalid") + } + poke(c.io.input.valid, 1) + poke(c.io.input.bits, BigInt(num)) + step(1) + println(peek(c.io.output.bits).toString()) + println(s"Answer should be ${checkAnswer(num)}") + expect(c.io.output.bits, checkAnswer(num), "Output did should match expected data") + expect(c.io.output.valid, 1, "Output should be valid if input is valid") + } + +} + +class TransposedStreamingTester(c: TransposedStreamingFIR[SInt]) extends PeekPokeTester(c) { + + for (num <- -5 to 5) { + poke(c.io.input, BigInt(num)) + step(1) + println(peek(c.io.output).toString()) + } +} + +class TransposedStreamFIRSpec extends AnyFlatSpec with ChiselScalatestTester { + "ConstantTapTransposedStreamingFIR" should "compute a running average like thing" in { + val taps = 0 until 3 + + test(new ConstantTapTransposedStreamingFIR(SInt(10.W), SInt(16.W), taps)) + .runPeekPoke(new ConstantTapTransposedStreamingTester(_)) + } +}