diff options
| -rw-r--r-- | chiselFrontend/src/main/scala/chisel3/Aggregate.scala | 2 | ||||
| -rw-r--r-- | chiselFrontend/src/main/scala/chisel3/Bits.scala | 160 | ||||
| -rw-r--r-- | chiselFrontend/src/main/scala/chisel3/Num.scala | 129 | ||||
| -rw-r--r-- | chiselFrontend/src/main/scala/chisel3/package.scala | 3 | ||||
| -rw-r--r-- | src/main/scala/chisel3/stage/package.scala | 1 | ||||
| -rw-r--r-- | src/test/scala/chiselTests/FixedPointSpec.scala | 15 | ||||
| -rw-r--r-- | src/test/scala/chiselTests/IntervalSpec.scala | 59 | ||||
| -rw-r--r-- | src/test/scala/chiselTests/LiteralExtractorSpec.scala | 43 |
8 files changed, 315 insertions, 97 deletions
diff --git a/chiselFrontend/src/main/scala/chisel3/Aggregate.scala b/chiselFrontend/src/main/scala/chisel3/Aggregate.scala index 42b40ed9..8141fdba 100644 --- a/chiselFrontend/src/main/scala/chisel3/Aggregate.scala +++ b/chiselFrontend/src/main/scala/chisel3/Aggregate.scala @@ -43,7 +43,7 @@ sealed abstract class Aggregate extends Data { } } - override def litOption: Option[BigInt] = ??? // TODO implement me + override def litOption: Option[BigInt] = None // TODO implement me /** Returns a Seq of the immediate contents of this Aggregate, in order. */ diff --git a/chiselFrontend/src/main/scala/chisel3/Bits.scala b/chiselFrontend/src/main/scala/chisel3/Bits.scala index 28d1690d..633636fd 100644 --- a/chiselFrontend/src/main/scala/chisel3/Bits.scala +++ b/chiselFrontend/src/main/scala/chisel3/Bits.scala @@ -1179,6 +1179,43 @@ package experimental { import chisel3.internal.firrtl.BinaryPoint + /** Chisel types that have binary points support retrieving + * literal values as `Double` or `BigDecimal` + */ + trait HasBinaryPoint { self: Bits => + def binaryPoint: BinaryPoint + + /** Return the [[Double]] value of this instance if it is a Literal + * @note this method may return throw an exception if the literal value won't fit in a Double + */ + def litToDoubleOption: Option[Double] = { + litOption match { + case Some(bigInt: BigInt) => + Some(Num.toDouble(bigInt, binaryPoint)) + case _ => None + } + } + + /** Return the double value of this instance assuming it is a literal (convenience method) + */ + def litToDouble: Double = litToDoubleOption.get + + /** Return the [[BigDecimal]] value of this instance if it is a Literal + * @note this method may return throw an exception if the literal value won't fit in a BigDecimal + */ + def litToBigDecimalOption: Option[BigDecimal] = { + litOption match { + case Some(bigInt: BigInt) => + Some(Num.toBigDecimal(bigInt, binaryPoint)) + case _ => None + } + } + + /** Return the [[BigDecimal]] value of this instance assuming it is a literal (convenience method) + * @return + */ + def litToBigDecimal: BigDecimal = litToBigDecimalOption.get + } //scalastyle:off number.of.methods /** A sealed class representing a fixed point number that has a bit width and a binary point The width and binary point * may be inferred. @@ -1195,7 +1232,7 @@ package experimental { * @define constantWidth @note The width of the returned $coll is unchanged, i.e., `width of this`. */ sealed class FixedPoint private(width: Width, val binaryPoint: BinaryPoint) - extends Bits(width) with Num[FixedPoint] { + extends Bits(width) with Num[FixedPoint] with HasBinaryPoint { override def toString: String = { val bindingString = litToDoubleOption match { @@ -1218,16 +1255,6 @@ package experimental { case _ => this badConnect that } - /** Convert to a [[scala.Option]] of [[scala.Boolean]] */ - def litToDoubleOption: Option[Double] = litOption.map { intVal => - val multiplier = math.pow(2, binaryPoint.get) - intVal.toDouble / multiplier - } - - /** Convert to a [[scala.Option]] */ - def litToDouble: Double = litToDoubleOption.get - - /** Unary negation (expanding width) * * @return a hardware $coll equal to zero minus this $coll @@ -1519,7 +1546,7 @@ package experimental { * Factory and convenience methods for the FixedPoint class * IMPORTANT: The API provided here is experimental and may change in the future. */ - object FixedPoint { + object FixedPoint extends NumObject { import FixedPoint.Implicits._ @@ -1559,6 +1586,14 @@ package experimental { toBigInt(value, binaryPoint.get), width = width, binaryPoint = binaryPoint ) } + /** Create an FixedPoint literal with inferred width from BigDecimal. + * Use PrivateObject to force users to specify width and binaryPoint by name + */ + def fromBigDecimal(value: BigDecimal, width: Width, binaryPoint: BinaryPoint): FixedPoint = { + fromBigInt( + toBigInt(value, binaryPoint.get), width = width, binaryPoint = binaryPoint + ) + } /** Create an FixedPoint port with specified width and binary position. */ def apply(value: BigInt, width: Width, binaryPoint: BinaryPoint): FixedPoint = { @@ -1568,34 +1603,10 @@ package experimental { lit.bindLitArg(newLiteral) } - /** - * How to create a bigint from a double with a specific binaryPoint - * @param x a double value - * @param binaryPoint a binaryPoint that you would like to use - * @return - */ - def toBigInt(x: Double, binaryPoint: Int): BigInt = { - val multiplier = math.pow(2, binaryPoint) - val result = BigInt(math.round(x * multiplier)) - result - } - - /** - * converts a bigInt with the given binaryPoint into the double representation - * @param i a bigint - * @param binaryPoint the implied binaryPoint of @i - * @return - */ - def toDouble(i: BigInt, binaryPoint: Int): Double = { - val multiplier = math.pow(2, binaryPoint) - val result = i.toDouble / multiplier - result - } object Implicits { - // implicit class fromDoubleToLiteral(val double: Double) extends AnyVal { implicit class fromDoubleToLiteral(double: Double) { def F(binaryPoint: BinaryPoint): FixedPoint = { FixedPoint.fromDouble(double, Width(), binaryPoint) @@ -1605,6 +1616,16 @@ package experimental { FixedPoint.fromDouble(double, width, binaryPoint) } } + + implicit class fromBigDecimalToLiteral(bigDecimal: BigDecimal) { + def F(binaryPoint: BinaryPoint): FixedPoint = { + FixedPoint.fromBigDecimal(bigDecimal, Width(), binaryPoint) + } + + def F(width: Width, binaryPoint: BinaryPoint): FixedPoint = { + FixedPoint.fromBigDecimal(bigDecimal, width, binaryPoint) + } + } } } @@ -1627,7 +1648,7 @@ package experimental { * @param range a range specifies min, max and binary point */ sealed class Interval private[chisel3] (val range: chisel3.internal.firrtl.IntervalRange) - extends Bits(range.getWidth) with Num[Interval] { + extends Bits(range.getWidth) with Num[Interval] with HasBinaryPoint { override def toString: String = { val bindingString = litOption match { @@ -2071,7 +2092,7 @@ package experimental { * Factory and convenience methods for the Interval class * IMPORTANT: The API provided here is experimental and may change in the future. */ - object Interval { + object Interval extends NumObject { /** Create an Interval type with inferred width and binary point. */ def apply(): Interval = Interval(range"[?,?]") @@ -2155,78 +2176,31 @@ package experimental { case _ => } val lit = IntervalLit(value, width, binaryPoint) - val bound = firrtlir.Closed(Interval.toDouble(value, binaryPoint.asInstanceOf[KnownBinaryPoint].value)) + val bound = firrtlir.Closed(Interval.toBigDecimal(value, binaryPoint.asInstanceOf[KnownBinaryPoint].value)) val result = new Interval(IntervalRange(bound, bound, binaryPoint)) lit.bindLitArg(result) } protected[chisel3] def Lit(value: BigInt, range: IntervalRange): Interval = { val lit = IntervalLit(value, range.getWidth, range.binaryPoint) - val bigDecimal = BigDecimal(value) + val bigDecimal = BigDecimal(value) / (1 << lit.binaryPoint.get) val inRange = (range.lowerBound, range.upperBound) match { case (firrtlir.Closed(l), firrtlir.Closed(u)) => l <= bigDecimal && bigDecimal <= u - case (firrtlir.Closed(l), firrtlir.Open(u)) => l <= bigDecimal && bigDecimal <= u - case (firrtlir.Open(l), firrtlir.Closed(u)) => l <= bigDecimal && bigDecimal <= u - case (firrtlir.Open(l), firrtlir.Open(u)) => l <= bigDecimal && bigDecimal <= u + case (firrtlir.Closed(l), firrtlir.Open(u)) => l <= bigDecimal && bigDecimal < u + case (firrtlir.Open(l), firrtlir.Closed(u)) => l < bigDecimal && bigDecimal <= u + case (firrtlir.Open(l), firrtlir.Open(u)) => l < bigDecimal && bigDecimal < u } if(! inRange) { throw new ChiselException( - s"Error literal interval value $value is not contained in specified range $range" + s"Error literal interval value $bigDecimal is not contained in specified range $range" ) } val result = Interval(range) lit.bindLitArg(result) } - /** How to create a BigInt from a double with a specific binaryPoint - * - * @param x a double value - * @param binaryPoint a binaryPoint that you would like to use - * @return - */ - def toBigInt(x: Double, binaryPoint: BinaryPoint): BigInt = { - val intBinaryPoint = binaryPoint match { - case KnownBinaryPoint(n) => n - case b => - throw new ChiselException(s"Error converting Double $x to BigInt, binary point must be known, not $b") - } - val multiplier = BigInt(1) << intBinaryPoint - val result = BigInt(math.round(x * multiplier.doubleValue)) - result - - } - - /** - * How to create a BigInt from a BigDecimal with a specific binaryPoint - * - * @param b a BigDecimal value - * @param binaryPoint a binaryPoint that you would like to use - * @return - */ - def toBigInt(b: BigDecimal, binaryPoint: BinaryPoint): BigInt = { - val bp = binaryPoint match { - case KnownBinaryPoint(n) => n - case x => - throw new ChiselException(s"Error converting BigDecimal $b to BigInt, binary point must be known, not $x") - } - (b * math.pow(2.0, bp.toDouble)).toBigInt - } - - /** - * converts a bigInt with the given binaryPoint into the double representation - * - * @param i a BigInt - * @param binaryPoint the implied binaryPoint of @i - * @return - */ - def toDouble(i: BigInt, binaryPoint: Int): Double = { - val multiplier = BigInt(1) << binaryPoint - val result = i.toDouble / multiplier.doubleValue - result - } - /** - * This returns the smallest number that can legally fit in range, if possible + * This returns the smallest Interval literal that can legally fit in range, if possible * If the lower bound or binary point is not known then return None * * @param range use to figure low number @@ -2245,7 +2219,7 @@ package experimental { } /** - * This returns the largest number that can legally fit in range, if possible + * This returns the largest Interval literal that can legally fit in range, if possible * If the upper bound or binary point is not known then return None * * @param range use to figure low number diff --git a/chiselFrontend/src/main/scala/chisel3/Num.scala b/chiselFrontend/src/main/scala/chisel3/Num.scala index 8984697f..e4d61d94 100644 --- a/chiselFrontend/src/main/scala/chisel3/Num.scala +++ b/chiselFrontend/src/main/scala/chisel3/Num.scala @@ -2,6 +2,9 @@ package chisel3 +import chisel3.internal.ChiselException +import chisel3.internal.firrtl.{BinaryPoint, KnownBinaryPoint} + import scala.language.experimental.macros import chisel3.internal.sourceinfo.{SourceInfo, SourceInfoTransform} @@ -177,3 +180,129 @@ trait Num[T <: Data] { def do_max(that: T)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): T = Mux(this < that, that, this.asInstanceOf[T]) } + +object Num extends NumObject + +/** NumbObject has a lot of convenience methods for converting between + * BigInts and Double and BigDecimal + * For backwards compatibility this is used with FixedPoint and Interval objects + * but is better used with the Num Object + * + */ +trait NumObject { + val MaxBitsBigIntToBigDecimal = 108 + val MaxBitsBigIntToDouble = 53 + + /** + * How to create a bigint from a double with a specific binaryPoint + * @param x a double value + * @param binaryPoint a binaryPoint that you would like to use + * @return + */ + def toBigInt(x: Double, binaryPoint: Int): BigInt = { + val multiplier = math.pow(2, binaryPoint) + val result = BigInt(math.round(x * multiplier)) + result + } + + /** + * How to create a bigint from a big decimal with a specific binaryPoint + * @param x a BigDecimal value + * @param binaryPoint a binaryPoint that you would like to use + * @return + */ + def toBigInt(x: Double, binaryPoint: BinaryPoint): BigInt = { + binaryPoint match { + case KnownBinaryPoint(n) => toBigInt(x, n) + case x => + throw new ChiselException(s"Error converting Double $x to BigInt, binary point must be known, not $x") + } + } + + /** + * How to create a bigint from a big decimal with a specific binaryPoint (int) + * @param x a BigDecimal value + * @param binaryPoint a binaryPoint that you would like to use + * @return + */ + def toBigInt(x: BigDecimal, binaryPoint: Int): BigInt = { + val multiplier = math.pow(2, binaryPoint) + val result = (x * multiplier).rounded.toBigInt() + result + } + + /** + * How to create a bigint from a big decimal with a specific binaryPoint + * @param value a BigDecimal value + * @param binaryPoint a binaryPoint that you would like to use + * @return + */ + def toBigInt(value: BigDecimal, binaryPoint: BinaryPoint): BigInt = { + binaryPoint match { + case KnownBinaryPoint(n) => toBigInt(value, n) + case x => + throw new ChiselException(s"Error converting BigDecimal $value to BigInt, binary point must be known, not $x") + } + } + + /** + * converts a bigInt with the given binaryPoint into the double representation + * @param i a bigint + * @param binaryPoint the implied binaryPoint of @i + * @return + */ + def toDouble(i: BigInt, binaryPoint: Int): Double = { + if(i.bitLength >= 54) { + throw new ChiselException( + s"BigInt $i with bitlength ${i.bitLength} is too big, precision lost with > $MaxBitsBigIntToDouble bits" + ) + } + val multiplier = math.pow(2, binaryPoint) + val result = i.toDouble / multiplier + result + } + + /** + * converts a bigInt with the given binaryPoint into the double representation + * @param value a bigint + * @param binaryPoint the implied binaryPoint of @i + * @return + */ + def toDouble(value: BigInt, binaryPoint: BinaryPoint): Double = { + binaryPoint match { + case KnownBinaryPoint(n) => toDouble(value, n) + case x => + throw new ChiselException(s"Error converting BigDecimal $value to BigInt, binary point must be known, not $x") + } } + + /** + * converts a bigInt with the given binaryPoint into the BigDecimal representation + * @param value a bigint + * @param binaryPoint the implied binaryPoint of @i + * @return + */ + def toBigDecimal(value: BigInt, binaryPoint: Int): BigDecimal = { + if(value.bitLength > 108) { + throw new ChiselException( + s"BigInt $value with bitlength ${value.bitLength} is too big, precision lost with > $MaxBitsBigIntToBigDecimal bits" + ) + } + val multiplier = BigDecimal(1.0) / BigDecimal(math.pow(2, binaryPoint)) + val result = BigDecimal(value) * multiplier + result + } + + /** + * converts a bigInt with the given binaryPoint into the BigDecimal representation + * @param value a bigint + * @param binaryPoint the implied binaryPoint of @i + * @return + */ + def toBigDecimal(value: BigInt, binaryPoint: BinaryPoint): BigDecimal = { + binaryPoint match { + case KnownBinaryPoint(n) => toBigDecimal(value, n) + case x => + throw new ChiselException(s"Error converting BigDecimal $value to BigInt, binary point must be known, not $x") + } + } +}
\ No newline at end of file diff --git a/chiselFrontend/src/main/scala/chisel3/package.scala b/chiselFrontend/src/main/scala/chisel3/package.scala index 3af21d57..65bfdeb7 100644 --- a/chiselFrontend/src/main/scala/chisel3/package.scala +++ b/chiselFrontend/src/main/scala/chisel3/package.scala @@ -115,6 +115,9 @@ package object chisel3 { // scalastyle:ignore package.object.name implicit class fromDoubleToLiteral(double: Double) extends experimental.FixedPoint.Implicits.fromDoubleToLiteral(double) + implicit class fromBigDecimalToLiteral(bigDecimal: BigDecimal) + extends experimental.FixedPoint.Implicits.fromBigDecimalToLiteral(bigDecimal) + // Interval is experimental for now, but we alias the implicit conversion classes here // to minimize disruption with existing code. implicit class fromIntToLiteralInterval(int: Int) diff --git a/src/main/scala/chisel3/stage/package.scala b/src/main/scala/chisel3/stage/package.scala index 67d38ae7..57766be6 100644 --- a/src/main/scala/chisel3/stage/package.scala +++ b/src/main/scala/chisel3/stage/package.scala @@ -28,7 +28,6 @@ package object stage { private[chisel3] implicit object ChiselExecutionResultView extends OptionsView[ChiselExecutionResult] { - lazy val dummyWriteEmitted = new firrtl.stage.phases.WriteEmitted lazy val dummyConvert = new Convert lazy val dummyEmitter = new Emitter diff --git a/src/test/scala/chiselTests/FixedPointSpec.scala b/src/test/scala/chiselTests/FixedPointSpec.scala index a928f08e..e97c6be7 100644 --- a/src/test/scala/chiselTests/FixedPointSpec.scala +++ b/src/test/scala/chiselTests/FixedPointSpec.scala @@ -19,6 +19,21 @@ class FixedPointLiteralSpec extends FlatSpec with Matchers { initialDouble should be(finalDouble) } + + they should "have their literals support to double and to BigDecimal" in { + val d = -7.125 + val lit1 = d.F(3.BP) + lit1.litToDouble should be (d) + + val d2 = BigDecimal("1232123213131123.125") + val lit2 = d2.F(3.BP) + lit2.litToBigDecimal should be (d2) + + // Numbers that are too big will throw exception + intercept[ChiselException] { + lit2.litToDouble + } + } } //noinspection TypeAnnotation,EmptyParenMethodAccessedAsParameterless diff --git a/src/test/scala/chiselTests/IntervalSpec.scala b/src/test/scala/chiselTests/IntervalSpec.scala index 863771a3..ae7fdabf 100644 --- a/src/test/scala/chiselTests/IntervalSpec.scala +++ b/src/test/scala/chiselTests/IntervalSpec.scala @@ -456,12 +456,69 @@ class IntervalSpec extends FreeSpec with Matchers with ChiselRunners { () => new BasicTester { val x = 5.I(range"[0,4]") - } + } ).elaborate } } } + "Interval literals support to double and to BigDecimal" in { + val d = -7.125 + val lit1 = d.I(3.BP) + lit1.litToDouble should be (d) + + val d2 = BigDecimal("1232123213131123.125") + val lit2 = d2.I(3.BP) + lit2.litToBigDecimal should be (d2) + + // Numbers that are too big will throw exception + intercept[ChiselException] { + lit2.litToDouble + } + } + + "Interval literals creation handles edge cases" - { + "value at closed boundaries works" in { + val inputRange = range"[-6, 6].2" + val in1 = (-6.0).I(inputRange) + val in2 = 6.0.I(inputRange) + BigDecimal(in1.litValue()) / (1 << inputRange.binaryPoint.get) should be (-6) + BigDecimal(in2.litValue()) / (1 << inputRange.binaryPoint.get) should be (6) + intercept[ChiselException] { + (-6.25).I(inputRange) + } + intercept[ChiselException] { + (6.25).I(inputRange) + } + } + "value at open boundaries works" in { + val inputRange = range"(-6, 6).2" + val in1 = (-5.75).I(inputRange) + val in2 = 5.75.I(inputRange) + BigDecimal(in1.litValue()) / (1 << inputRange.binaryPoint.get) should be (-5.75) + BigDecimal(in2.litValue()) / (1 << inputRange.binaryPoint.get) should be (5.75) + intercept[ChiselException] { + (-6.0).I(inputRange) + } + intercept[ChiselException] { + (6.0).I(inputRange) + } + } + "values not precisely at open boundaries works but are converted to nearest match" in { + val inputRange = range"(-6, 6).2" + val in1 = (-5.95).I(inputRange) + val in2 = 5.95.I(inputRange) + BigDecimal(in1.litValue()) / (1 << inputRange.binaryPoint.get) should be (-5.75) + BigDecimal(in2.litValue()) / (1 << inputRange.binaryPoint.get) should be (5.75) + intercept[ChiselException] { + (-6.1).I(inputRange) + } + intercept[ChiselException] { + (6.1).I(inputRange) + } + } + } + "Let's take a look at the results of squeeze over small range" in { assertTesterPasses { new ClipSqueezeWrapDemo( diff --git a/src/test/scala/chiselTests/LiteralExtractorSpec.scala b/src/test/scala/chiselTests/LiteralExtractorSpec.scala index b56672af..145c1ef2 100644 --- a/src/test/scala/chiselTests/LiteralExtractorSpec.scala +++ b/src/test/scala/chiselTests/LiteralExtractorSpec.scala @@ -3,7 +3,7 @@ package chiselTests import chisel3._ -import chisel3.experimental.FixedPoint +import chisel3.experimental._ import chisel3.experimental.BundleLiterals._ import chisel3.testers.BasicTester @@ -55,6 +55,47 @@ class LiteralExtractorSpec extends ChiselFlatSpec { }} } + "doubles and big decimals" should "round trip properly" in { + intercept[ChiselException] { + Num.toBigDecimal(BigInt("1" * 109, 2), 0.BP) // this only works if number takes less than 109 bits + } + + intercept[ChiselException] { + Num.toDouble(BigInt("1" * 54, 2), 0.BP) // this only works if number takes less than 54 bits + } + + val bigInt108 = BigInt("1" * 108, 2) + val bigDecimal = Num.toBigDecimal(bigInt108, 2) + + val bigIntFromBigDecimal = Num.toBigInt(bigDecimal, 2) + + bigIntFromBigDecimal should be (bigInt108) + + val bigInt53 = BigInt("1" * 53, 2) + + val double = Num.toDouble(bigInt53, 2) + + val bigIntFromDouble = Num.toBigInt(double, 2) + + bigIntFromDouble should be (bigInt53) + } + + "encoding and decoding of Intervals" should "round trip" in { + val rangeMin = BigDecimal(-31.5) + val rangeMax = BigDecimal(32.5) + val range = range"($rangeMin, $rangeMax).2" + for(value <- (rangeMin - 4) to (rangeMax + 4) by 2.25) { + if (value < rangeMin || value > rangeMax) { + intercept[ChiselException] { + val literal = value.I(range) + } + } else { + val literal = value.I(range) + literal.isLit() should be(true) + literal.litValue().toDouble / 4.0 should be(value) + } + } + } "literals declared outside a builder context" should "compare with those inside builder context" in { class InsideBundle extends Bundle { |
