From b0654edeb8d91cdb27c66e6485214d8f4875c156 Mon Sep 17 00:00:00 2001 From: Albert Magyar Date: Mon, 1 Feb 2021 14:06:39 -0800 Subject: [PATCH] Update reported width from div/rem to match FIRRTL results (#1748) * Update reported width from div/rem to match FIRRTL results * Add tests for width of % and / on UInt and SInt * Add loop-based test for known UInt/SInt op result widths Co-authored-by: Jack Koenig (cherry picked from commit 98ce9194e5d87fdd5be931b6cd516d180a6540cd) # Conflicts: # core/src/main/scala/chisel3/internal/firrtl/IR.scala --- .../src/main/scala/chisel3/Bits.scala | 6 +- .../scala/chisel3/internal/firrtl/IR.scala | 754 ++++++++++++++++++ src/test/scala/chiselTests/SIntOps.scala | 32 + src/test/scala/chiselTests/UIntOps.scala | 33 + src/test/scala/chiselTests/WidthSpec.scala | 59 ++ 5 files changed, 881 insertions(+), 3 deletions(-) create mode 100644 core/src/main/scala/chisel3/internal/firrtl/IR.scala diff --git a/chiselFrontend/src/main/scala/chisel3/Bits.scala b/chiselFrontend/src/main/scala/chisel3/Bits.scala index 11460418726..7fe627ad9b0 100644 --- a/chiselFrontend/src/main/scala/chisel3/Bits.scala +++ b/chiselFrontend/src/main/scala/chisel3/Bits.scala @@ -435,7 +435,7 @@ sealed class UInt private[chisel3] (width: Width) extends Bits(width) with Num[U override def do_/ (that: UInt)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): UInt = binop(sourceInfo, UInt(this.width), DivideOp, that) override def do_% (that: UInt)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): UInt = - binop(sourceInfo, UInt(this.width), RemOp, that) + binop(sourceInfo, UInt(this.width min that.width), RemOp, that) override def do_* (that: UInt)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): UInt = binop(sourceInfo, UInt(this.width + that.width), TimesOp, that) @@ -732,9 +732,9 @@ sealed class SInt private[chisel3] (width: Width) extends Bits(width) with Num[S override def do_* (that: SInt)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): SInt = binop(sourceInfo, SInt(this.width + that.width), TimesOp, that) override def do_/ (that: SInt)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): SInt = - binop(sourceInfo, SInt(this.width), DivideOp, that) + binop(sourceInfo, SInt(this.width + 1), DivideOp, that) override def do_% (that: SInt)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): SInt = - binop(sourceInfo, SInt(this.width), RemOp, that) + binop(sourceInfo, SInt(this.width min that.width), RemOp, that) /** Multiplication operator * diff --git a/core/src/main/scala/chisel3/internal/firrtl/IR.scala b/core/src/main/scala/chisel3/internal/firrtl/IR.scala new file mode 100644 index 00000000000..61f97ce643d --- /dev/null +++ b/core/src/main/scala/chisel3/internal/firrtl/IR.scala @@ -0,0 +1,754 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chisel3.internal.firrtl + +import firrtl.{ir => fir} + +import chisel3._ +import chisel3.internal._ +import chisel3.internal.sourceinfo.SourceInfo +import chisel3.experimental._ +import _root_.firrtl.{ir => firrtlir} +import _root_.firrtl.PrimOps + +import scala.collection.immutable.NumericRange +import scala.math.BigDecimal.RoundingMode + + +case class PrimOp(name: String) { + override def toString: String = name +} + +object PrimOp { + val AddOp = PrimOp("add") + val SubOp = PrimOp("sub") + val TailOp = PrimOp("tail") + val HeadOp = PrimOp("head") + val TimesOp = PrimOp("mul") + val DivideOp = PrimOp("div") + val RemOp = PrimOp("rem") + val ShiftLeftOp = PrimOp("shl") + val ShiftRightOp = PrimOp("shr") + val DynamicShiftLeftOp = PrimOp("dshl") + val DynamicShiftRightOp = PrimOp("dshr") + val BitAndOp = PrimOp("and") + val BitOrOp = PrimOp("or") + val BitXorOp = PrimOp("xor") + val BitNotOp = PrimOp("not") + val ConcatOp = PrimOp("cat") + val BitsExtractOp = PrimOp("bits") + val LessOp = PrimOp("lt") + val LessEqOp = PrimOp("leq") + val GreaterOp = PrimOp("gt") + val GreaterEqOp = PrimOp("geq") + val EqualOp = PrimOp("eq") + val PadOp = PrimOp("pad") + val NotEqualOp = PrimOp("neq") + val NegOp = PrimOp("neg") + val MultiplexOp = PrimOp("mux") + val AndReduceOp = PrimOp("andr") + val OrReduceOp = PrimOp("orr") + val XorReduceOp = PrimOp("xorr") + val ConvertOp = PrimOp("cvt") + val AsUIntOp = PrimOp("asUInt") + val AsSIntOp = PrimOp("asSInt") + val AsFixedPointOp = PrimOp("asFixedPoint") + val AsIntervalOp = PrimOp("asInterval") + val WrapOp = PrimOp("wrap") + val SqueezeOp = PrimOp("squz") + val ClipOp = PrimOp("clip") + val SetBinaryPoint = PrimOp("setp") + val IncreasePrecision = PrimOp("incp") + val DecreasePrecision = PrimOp("decp") + val AsClockOp = PrimOp("asClock") + val AsAsyncResetOp = PrimOp("asAsyncReset") +} + +abstract class Arg { + def fullName(ctx: Component): String = name + def name: String +} + +case class Node(id: HasId) extends Arg { + override def fullName(ctx: Component): String = id.getOptionRef match { + case Some(arg) => arg.fullName(ctx) + case None => id.instanceName + } + def name: String = id.getOptionRef match { + case Some(arg) => arg.name + case None => id.instanceName + } +} + +abstract class LitArg(val num: BigInt, widthArg: Width) extends Arg { + private[chisel3] def forcedWidth = widthArg.known + private[chisel3] def width: Width = if (forcedWidth) widthArg else Width(minWidth) + override def fullName(ctx: Component): String = name + // Ensure the node representing this LitArg has a ref to it and a literal binding. + def bindLitArg[T <: Element](elem: T): T = { + elem.bind(ElementLitBinding(this)) + elem.setRef(this) + elem + } + + protected def minWidth: Int + if (forcedWidth) { + require(widthArg.get >= minWidth, + s"The literal value ${num} was elaborated with a specified width of ${widthArg.get} bits, but at least ${minWidth} bits are required.") + } +} + +case class ILit(n: BigInt) extends Arg { + def name: String = n.toString +} + +case class ULit(n: BigInt, w: Width) extends LitArg(n, w) { + def name: String = "UInt" + width + "(\"h0" + num.toString(16) + "\")" + def minWidth: Int = 1 max n.bitLength + + require(n >= 0, s"UInt literal ${n} is negative") +} + +case class SLit(n: BigInt, w: Width) extends LitArg(n, w) { + def name: String = { + val unsigned = if (n < 0) (BigInt(1) << width.get) + n else n + s"asSInt(${ULit(unsigned, width).name})" + } + def minWidth: Int = 1 + n.bitLength +} + +case class FPLit(n: BigInt, w: Width, binaryPoint: BinaryPoint) extends LitArg(n, w) { + def name: String = { + val unsigned = if (n < 0) (BigInt(1) << width.get) + n else n + s"asFixedPoint(${ULit(unsigned, width).name}, ${binaryPoint.asInstanceOf[KnownBinaryPoint].value})" + } + def minWidth: Int = 1 + n.bitLength +} + +case class IntervalLit(n: BigInt, w: Width, binaryPoint: BinaryPoint) extends LitArg(n, w) { + def name: String = { + val unsigned = if (n < 0) (BigInt(1) << width.get) + n else n + s"asInterval(${ULit(unsigned, width).name}, ${n}, ${n}, ${binaryPoint.asInstanceOf[KnownBinaryPoint].value})" + } + val range: IntervalRange = { + new IntervalRange(IntervalRange.getBound(isClosed = true, BigDecimal(n)), + IntervalRange.getBound(isClosed = true, BigDecimal(n)), IntervalRange.getRangeWidth(binaryPoint)) + } + def minWidth: Int = 1 + n.bitLength +} + +case class Ref(name: String) extends Arg +case class ModuleIO(mod: BaseModule, name: String) extends Arg { + override def fullName(ctx: Component): String = + if (mod eq ctx.id) name else s"${mod.getRef.name}.$name" +} +case class Slot(imm: Node, name: String) extends Arg { + override def fullName(ctx: Component): String = + if (imm.fullName(ctx).isEmpty) name else s"${imm.fullName(ctx)}.${name}" +} +case class Index(imm: Arg, value: Arg) extends Arg { + def name: String = s"[$value]" + override def fullName(ctx: Component): String = s"${imm.fullName(ctx)}[${value.fullName(ctx)}]" +} + +object Width { + def apply(x: Int): Width = KnownWidth(x) + def apply(): Width = UnknownWidth() +} + +sealed abstract class Width { + type W = Int + def min(that: Width): Width = this.op(that, _ min _) + def max(that: Width): Width = this.op(that, _ max _) + def + (that: Width): Width = this.op(that, _ + _) + def + (that: Int): Width = this.op(this, (a, b) => a + that) + def shiftRight(that: Int): Width = this.op(this, (a, b) => 0 max (a - that)) + def dynamicShiftLeft(that: Width): Width = + this.op(that, (a, b) => a + (1 << b) - 1) + + def known: Boolean + def get: W + protected def op(that: Width, f: (W, W) => W): Width +} + +sealed case class UnknownWidth() extends Width { + def known: Boolean = false + def get: Int = None.get + def op(that: Width, f: (W, W) => W): Width = this + override def toString: String = "" +} + +sealed case class KnownWidth(value: Int) extends Width { + require(value >= 0) + def known: Boolean = true + def get: Int = value + def op(that: Width, f: (W, W) => W): Width = that match { + case KnownWidth(x) => KnownWidth(f(value, x)) + case _ => that + } + override def toString: String = s"<${value.toString}>" +} + +object BinaryPoint { + def apply(x: Int): BinaryPoint = KnownBinaryPoint(x) + def apply(): BinaryPoint = UnknownBinaryPoint +} + +sealed abstract class BinaryPoint { + type W = Int + def max(that: BinaryPoint): BinaryPoint = this.op(that, _ max _) + def + (that: BinaryPoint): BinaryPoint = this.op(that, _ + _) + def + (that: Int): BinaryPoint = this.op(this, (a, b) => a + that) + def shiftRight(that: Int): BinaryPoint = this.op(this, (a, b) => 0 max (a - that)) + def dynamicShiftLeft(that: BinaryPoint): BinaryPoint = + this.op(that, (a, b) => a + (1 << b) - 1) + + def known: Boolean + def get: W + protected def op(that: BinaryPoint, f: (W, W) => W): BinaryPoint +} + +case object UnknownBinaryPoint extends BinaryPoint { + def known: Boolean = false + def get: Int = None.get + def op(that: BinaryPoint, f: (W, W) => W): BinaryPoint = this + override def toString: String = "" +} + +sealed case class KnownBinaryPoint(value: Int) extends BinaryPoint { + def known: Boolean = true + def get: Int = value + def op(that: BinaryPoint, f: (W, W) => W): BinaryPoint = that match { + case KnownBinaryPoint(x) => KnownBinaryPoint(f(value, x)) + case _ => that + } + override def toString: String = s"<<${value.toString}>>" +} + + +sealed abstract class MemPortDirection(name: String) { + override def toString: String = name +} +object MemPortDirection { + object READ extends MemPortDirection("read") + object WRITE extends MemPortDirection("write") + object RDWR extends MemPortDirection("rdwr") + object INFER extends MemPortDirection("infer") +} + +sealed trait RangeType { + def getWidth: Width + + def * (that: IntervalRange): IntervalRange + def +& (that: IntervalRange): IntervalRange + def -& (that: IntervalRange): IntervalRange + def << (that: Int): IntervalRange + def >> (that: Int): IntervalRange + def << (that: KnownWidth): IntervalRange + def >> (that: KnownWidth): IntervalRange + def merge(that: IntervalRange): IntervalRange +} + +object IntervalRange { + /** Creates an IntervalRange, this is used primarily by the range interpolator macro + * @param lower lower bound + * @param upper upper bound + * @param firrtlBinaryPoint binary point firrtl style + * @return + */ + def apply(lower: firrtlir.Bound, upper: firrtlir.Bound, firrtlBinaryPoint: firrtlir.Width): IntervalRange = { + new IntervalRange(lower, upper, firrtlBinaryPoint) + } + + def apply(lower: firrtlir.Bound, upper: firrtlir.Bound, binaryPoint: BinaryPoint): IntervalRange = { + new IntervalRange(lower, upper, IntervalRange.getBinaryPoint(binaryPoint)) + } + + def apply(lower: firrtlir.Bound, upper: firrtlir.Bound, binaryPoint: Int): IntervalRange = { + IntervalRange(lower, upper, BinaryPoint(binaryPoint)) + } + + /** Returns an IntervalRange appropriate for a signed value of the given width + * @param binaryPoint number of bits of mantissa + * @return + */ + def apply(binaryPoint: BinaryPoint): IntervalRange = { + IntervalRange(firrtlir.UnknownBound, firrtlir.UnknownBound, binaryPoint) + } + + /** Returns an IntervalRange appropriate for a signed value of the given width + * @param width number of bits to have in the interval + * @param binaryPoint number of bits of mantissa + * @return + */ + def apply(width: Width, binaryPoint: BinaryPoint = 0.BP): IntervalRange = { + val range = width match { + case KnownWidth(w) => + val nearestPowerOf2 = BigInt("1" + ("0" * (w - 1)), 2) + IntervalRange( + firrtlir.Closed(BigDecimal(-nearestPowerOf2)), firrtlir.Closed(BigDecimal(nearestPowerOf2 - 1)), binaryPoint + ) + case _ => + IntervalRange(firrtlir.UnknownBound, firrtlir.UnknownBound, binaryPoint) + } + range + } + + def unapply(arg: IntervalRange): Option[(firrtlir.Bound, firrtlir.Bound, BinaryPoint)] = { + return Some((arg.lower, arg.upper, arg.binaryPoint)) + } + + def getBound(isClosed: Boolean, value: String): firrtlir.Bound = { + if(value == "?") { + firrtlir.UnknownBound + } + else if(isClosed) { + firrtlir.Closed(BigDecimal(value)) + } + else { + firrtlir.Open(BigDecimal(value)) + } + } + + def getBound(isClosed: Boolean, value: BigDecimal): firrtlir.Bound = { + if(isClosed) { + firrtlir.Closed(value) + } + else { + firrtlir.Open(value) + } + } + + def getBound(isClosed: Boolean, value: Int): firrtlir.Bound = { + getBound(isClosed, (BigDecimal(value))) + } + + def getBinaryPoint(s: String): firrtlir.Width = { + firrtlir.UnknownWidth + } + + def getBinaryPoint(n: Int): firrtlir.Width = { + if(n < 0) { + firrtlir.UnknownWidth + } + else { + firrtlir.IntWidth(n) + } + } + def getBinaryPoint(n: BinaryPoint): firrtlir.Width = { + n match { + case UnknownBinaryPoint => firrtlir.UnknownWidth + case KnownBinaryPoint(w) => firrtlir.IntWidth(w) + } + } + + def getRangeWidth(w: Width): firrtlir.Width = { + if(w.known) { + firrtlir.IntWidth(w.get) + } + else { + firrtlir.UnknownWidth + } + } + def getRangeWidth(binaryPoint: BinaryPoint): firrtlir.Width = { + if(binaryPoint.known) { + firrtlir.IntWidth(binaryPoint.get) + } + else { + firrtlir.UnknownWidth + } + } + + def Unknown: IntervalRange = range"[?,?].?" +} + + +sealed class IntervalRange( + val lowerBound: firrtlir.Bound, + val upperBound: firrtlir.Bound, + private[chisel3] val firrtlBinaryPoint: firrtlir.Width) + extends firrtlir.IntervalType(lowerBound, upperBound, firrtlBinaryPoint) + with RangeType { + + (lowerBound, upperBound) match { + case (firrtlir.Open(begin), firrtlir.Open(end)) => + if(begin >= end) throw new ChiselException(s"Invalid range with ${serialize}") + binaryPoint match { + case KnownBinaryPoint(bp) => + if(begin >= end - (BigDecimal(1) / BigDecimal(BigInt(1) << bp))) { + throw new ChiselException(s"Invalid range with ${serialize}") + } + case _ => + } + case (firrtlir.Open(begin), firrtlir.Closed(end)) => + if(begin >= end) throw new ChiselException(s"Invalid range with ${serialize}") + case (firrtlir.Closed(begin), firrtlir.Open(end)) => + if(begin >= end) throw new ChiselException(s"Invalid range with ${serialize}") + case (firrtlir.Closed(begin), firrtlir.Closed(end)) => + if(begin > end) throw new ChiselException(s"Invalid range with ${serialize}") + case _ => + } + + override def toString: String = { + val binaryPoint = firrtlBinaryPoint match { + case firrtlir.IntWidth(n) => s"$n" + case _ => "?" + } + val lowerBoundString = lowerBound match { + case firrtlir.Closed(l) => s"[$l" + case firrtlir.Open(l) => s"($l" + case firrtlir.UnknownBound => s"[?" + } + val upperBoundString = upperBound match { + case firrtlir.Closed(l) => s"$l]" + case firrtlir.Open(l) => s"$l)" + case firrtlir.UnknownBound => s"?]" + } + s"""range"$lowerBoundString,$upperBoundString.$binaryPoint"""" + } + + val increment: Option[BigDecimal] = firrtlBinaryPoint match { + case firrtlir.IntWidth(bp) => + Some(BigDecimal(math.pow(2, -bp.doubleValue))) + case _ => None + } + + /** If possible returns the lowest possible value for this Interval + * @return + */ + val getLowestPossibleValue: Option[BigDecimal] = { + increment match { + case Some(inc) => + lower match { + case firrtlir.Closed(n) => Some(n) + case firrtlir.Open(n) => Some(n + inc) + case _ => None + } + case _ => + None + } + } + + /** If possible returns the highest possible value for this Interval + * @return + */ + val getHighestPossibleValue: Option[BigDecimal] = { + increment match { + case Some(inc) => + upper match { + case firrtlir.Closed(n) => Some(n) + case firrtlir.Open(n) => Some(n - inc) + case _ => None + } + case _ => + None + } + } + + /** Return a Seq of the possible values for this range + * Mostly to be used for testing + * @return + */ + def getPossibleValues: NumericRange[BigDecimal] = { + (getLowestPossibleValue, getHighestPossibleValue, increment) match { + case (Some(low), Some(high), Some(inc)) => (low to high by inc) + case (_, _, None) => + throw new ChiselException(s"BinaryPoint unknown. Cannot get possible values from IntervalRange $toString") + case _ => + throw new ChiselException(s"Unknown Bound. Cannot get possible values from IntervalRange $toString") + + } + } + + override def getWidth: Width = { + width match { + case firrtlir.IntWidth(n) => KnownWidth(n.toInt) + case firrtlir.UnknownWidth => UnknownWidth() + } + } + + private def doFirrtlOp(op: firrtlir.PrimOp, that: IntervalRange): IntervalRange = { + PrimOps.set_primop_type( + firrtlir.DoPrim(op, + Seq(firrtlir.Reference("a", this), firrtlir.Reference("b", that)), Nil,firrtlir.UnknownType) + ).tpe match { + case i: firrtlir.IntervalType => IntervalRange(i.lower, i.upper, i.point) + case other => sys.error("BAD!") + } + } + + private def doFirrtlDynamicShift(that: UInt, isLeft: Boolean): IntervalRange = { + val uinttpe = that.widthOption match { + case None => firrtlir.UIntType(firrtlir.UnknownWidth) + case Some(w) => firrtlir.UIntType(firrtlir.IntWidth(w)) + } + val op = if(isLeft) PrimOps.Dshl else PrimOps.Dshr + PrimOps.set_primop_type( + firrtlir.DoPrim(op, + Seq(firrtlir.Reference("a", this), firrtlir.Reference("b", uinttpe)), Nil,firrtlir.UnknownType) + ).tpe match { + case i: firrtlir.IntervalType => IntervalRange(i.lower, i.upper, i.point) + case other => sys.error("BAD!") + } + } + + private def doFirrtlOp(op: firrtlir.PrimOp, that: Int): IntervalRange = { + PrimOps.set_primop_type( + firrtlir.DoPrim(op, + Seq(firrtlir.Reference("a", this)), Seq(BigInt(that)), firrtlir.UnknownType) + ).tpe match { + case i: firrtlir.IntervalType => IntervalRange(i.lower, i.upper, i.point) + case other => sys.error("BAD!") + } + } + + /** Multiply this by that, here we return a fully unknown range, + * firrtl's range inference can figure this out + * @param that + * @return + */ + override def *(that: IntervalRange): IntervalRange = { + doFirrtlOp(PrimOps.Mul, that) + } + + /** Add that to this, here we return a fully unknown range, + * firrtl's range inference can figure this out + * @param that + * @return + */ + override def +&(that: IntervalRange): IntervalRange = { + doFirrtlOp(PrimOps.Add, that) + } + + /** Subtract that from this, here we return a fully unknown range, + * firrtl's range inference can figure this out + * @param that + * @return + */ + override def -&(that: IntervalRange): IntervalRange = { + doFirrtlOp(PrimOps.Sub, that) + } + + private def adjustBoundValue(value: BigDecimal, binaryPointValue: Int): BigDecimal = { + if(binaryPointValue >= 0) { + val maskFactor = BigDecimal(1 << binaryPointValue) + val a = (value * maskFactor) + val b = a.setScale(0, RoundingMode.DOWN) + val c = b / maskFactor + c + } else { + value + } + } + + private def adjustBound(bound: firrtlir.Bound, binaryPoint: BinaryPoint): firrtlir.Bound = { + binaryPoint match { + case KnownBinaryPoint(binaryPointValue) => + bound match { + case firrtlir.Open(value) => firrtlir.Open(adjustBoundValue(value, binaryPointValue)) + case firrtlir.Closed(value) => firrtlir.Closed(adjustBoundValue(value, binaryPointValue)) + case _ => bound + } + case _ => firrtlir.UnknownBound + } + } + + /** Creates a new range with the increased precision + * + * @param newBinaryPoint + * @return + */ + def incPrecision(newBinaryPoint: BinaryPoint): IntervalRange = { + newBinaryPoint match { + case KnownBinaryPoint(that) => + doFirrtlOp(PrimOps.IncP, that) + case _ => + throwException(s"$this.incPrecision(newBinaryPoint = $newBinaryPoint) error, newBinaryPoint must be know") + } + } + + /** Creates a new range with the decreased precision + * + * @param newBinaryPoint + * @return + */ + def decPrecision(newBinaryPoint: BinaryPoint): IntervalRange = { + newBinaryPoint match { + case KnownBinaryPoint(that) => + doFirrtlOp(PrimOps.DecP, that) + case _ => + throwException(s"$this.decPrecision(newBinaryPoint = $newBinaryPoint) error, newBinaryPoint must be know") + } + } + + /** Creates a new range with the given binary point, adjusting precision + * on bounds as necessary + * + * @param newBinaryPoint + * @return + */ + def setPrecision(newBinaryPoint: BinaryPoint): IntervalRange = { + newBinaryPoint match { + case KnownBinaryPoint(that) => + doFirrtlOp(PrimOps.SetP, that) + case _ => + throwException(s"$this.setPrecision(newBinaryPoint = $newBinaryPoint) error, newBinaryPoint must be know") + } + } + + /** Shift this range left, i.e. shifts the min and max by the specified amount + * @param that + * @return + */ + override def <<(that: Int): IntervalRange = { + doFirrtlOp(PrimOps.Shl, that) + } + + /** Shift this range left, i.e. shifts the min and max by the known width + * @param that + * @return + */ + override def <<(that: KnownWidth): IntervalRange = { + <<(that.value) + } + + /** Shift this range left, i.e. shifts the min and max by value + * @param that + * @return + */ + def <<(that: UInt): IntervalRange = { + doFirrtlDynamicShift(that, isLeft = true) + } + + /** Shift this range right, i.e. shifts the min and max by the specified amount + * @param that + * @return + */ + override def >>(that: Int): IntervalRange = { + doFirrtlOp(PrimOps.Shr, that) + } + + /** Shift this range right, i.e. shifts the min and max by the known width + * @param that + * @return + */ + override def >>(that: KnownWidth): IntervalRange = { + >>(that.value) + } + + /** Shift this range right, i.e. shifts the min and max by value + * @param that + * @return + */ + def >>(that: UInt): IntervalRange = { + doFirrtlDynamicShift(that, isLeft = false) + } + + /** + * Squeeze returns the intersection of the ranges this interval and that Interval + * @param that + * @return + */ + def squeeze(that: IntervalRange): IntervalRange = { + doFirrtlOp(PrimOps.Squeeze, that) + } + + /** + * Wrap the value of this [[Interval]] into the range of a different Interval with a presumably smaller range. + * @param that + * @return + */ + def wrap(that: IntervalRange): IntervalRange = { + doFirrtlOp(PrimOps.Wrap, that) + } + + /** + * Clip the value of this [[Interval]] into the range of a different Interval with a presumably smaller range. + * @param that + * @return + */ + def clip(that: IntervalRange): IntervalRange = { + doFirrtlOp(PrimOps.Clip, that) + } + + /** merges the ranges of this and that, basically takes lowest low, highest high and biggest bp + * set unknown if any of this or that's value of above is unknown + * Like an union but will slurp up points in between the two ranges that were part of neither + * @param that + * @return + */ + override def merge(that: IntervalRange): IntervalRange = { + val lowest = (this.getLowestPossibleValue, that.getLowestPossibleValue) match { + case (Some(l1), Some(l2)) => + if(l1 < l2) { this.lower } else { that.lower } + case _ => + firrtlir.UnknownBound + } + val highest = (this.getHighestPossibleValue, that.getHighestPossibleValue) match { + case (Some(l1), Some(l2)) => + if(l1 >= l2) { this.lower } else { that.lower } + case _ => + firrtlir.UnknownBound + } + val newBinaryPoint = (this.firrtlBinaryPoint, that.firrtlBinaryPoint) match { + case (firrtlir.IntWidth(b1), firrtlir.IntWidth(b2)) => + if(b1 > b2) { firrtlir.IntWidth(b1)} else { firrtlir.IntWidth(b2) } + case _ => + firrtlir.UnknownWidth + } + IntervalRange(lowest, highest, newBinaryPoint) + } + + def binaryPoint: BinaryPoint = { + firrtlBinaryPoint match { + case firrtlir.IntWidth(n) => + assert(n < Int.MaxValue, s"binary point value $n is out of range") + KnownBinaryPoint(n.toInt) + case _ => UnknownBinaryPoint + } + } +} + +abstract class Command { + def sourceInfo: SourceInfo +} +abstract class Definition extends Command { + def id: HasId + def name: String = id.getRef.name +} +case class DefPrim[T <: Data](sourceInfo: SourceInfo, id: T, op: PrimOp, args: Arg*) extends Definition +case class DefInvalid(sourceInfo: SourceInfo, arg: Arg) extends Command +case class DefWire(sourceInfo: SourceInfo, id: Data) extends Definition +case class DefReg(sourceInfo: SourceInfo, id: Data, clock: Arg) extends Definition +case class DefRegInit(sourceInfo: SourceInfo, id: Data, clock: Arg, reset: Arg, init: Arg) extends Definition +case class DefMemory(sourceInfo: SourceInfo, id: HasId, t: Data, size: BigInt) extends Definition +case class DefSeqMemory(sourceInfo: SourceInfo, id: HasId, t: Data, size: BigInt, readUnderWrite: fir.ReadUnderWrite.Value) extends Definition +case class DefMemPort[T <: Data](sourceInfo: SourceInfo, id: T, source: Node, dir: MemPortDirection, index: Arg, clock: Arg) extends Definition +case class DefInstance(sourceInfo: SourceInfo, id: BaseModule, ports: Seq[Port]) extends Definition +case class WhenBegin(sourceInfo: SourceInfo, pred: Arg) extends Command +case class WhenEnd(sourceInfo: SourceInfo, firrtlDepth: Int, hasAlt: Boolean = false) extends Command +case class AltBegin(sourceInfo: SourceInfo) extends Command +case class OtherwiseEnd(sourceInfo: SourceInfo, firrtlDepth: Int) extends Command +case class Connect(sourceInfo: SourceInfo, loc: Node, exp: Arg) extends Command +case class BulkConnect(sourceInfo: SourceInfo, loc1: Node, loc2: Node) extends Command +case class Attach(sourceInfo: SourceInfo, locs: Seq[Node]) extends Command +case class ConnectInit(sourceInfo: SourceInfo, loc: Node, exp: Arg) extends Command +case class Stop(sourceInfo: SourceInfo, clock: Arg, ret: Int) extends Command +case class Port(id: Data, dir: SpecifiedDirection) +case class Printf(sourceInfo: SourceInfo, clock: Arg, pable: Printable) extends Command +object Formal extends Enumeration { + val Assert = Value("assert") + val Assume = Value("assume") + val Cover = Value("cover") +} +case class Verification(op: Formal.Value, sourceInfo: SourceInfo, clock: Arg, + predicate: Arg, message: String) extends Command +abstract class Component extends Arg { + def id: BaseModule + def name: String + def ports: Seq[Port] +} +case class DefModule(id: RawModule, name: String, ports: Seq[Port], commands: Seq[Command]) extends Component +case class DefBlackBox(id: BaseBlackBox, name: String, ports: Seq[Port], topDir: SpecifiedDirection, params: Map[String, Param]) extends Component + +case class Circuit(name: String, components: Seq[Component], annotations: Seq[ChiselAnnotation] = Seq.empty) diff --git a/src/test/scala/chiselTests/SIntOps.scala b/src/test/scala/chiselTests/SIntOps.scala index 9309c915e9e..0afb0addd23 100644 --- a/src/test/scala/chiselTests/SIntOps.scala +++ b/src/test/scala/chiselTests/SIntOps.scala @@ -113,4 +113,36 @@ class SIntOpsSpec extends ChiselPropSpec { assertTesterPasses(new SIntLitExtractTester) } + // We use WireDefault with 2 arguments because of + // https://www.chisel-lang.org/api/3.4.1/chisel3/WireDefault$.html + // Single Argument case 2 + property("modulo divide should give min width of arguments") { + assertKnownWidth(4) { + val x = WireDefault(SInt(8.W), DontCare) + val y = WireDefault(SInt(4.W), DontCare) + val op = x % y + WireDefault(chiselTypeOf(op), op) + } + assertKnownWidth(4) { + val x = WireDefault(SInt(4.W), DontCare) + val y = WireDefault(SInt(8.W), DontCare) + val op = x % y + WireDefault(chiselTypeOf(op), op) + } + } + + property("division should give the width of the numerator + 1") { + assertKnownWidth(9) { + val x = WireDefault(SInt(8.W), DontCare) + val y = WireDefault(SInt(4.W), DontCare) + val op = x / y + WireDefault(chiselTypeOf(op), op) + } + assertKnownWidth(5) { + val x = WireDefault(SInt(4.W), DontCare) + val y = WireDefault(SInt(8.W), DontCare) + val op = x / y + WireDefault(chiselTypeOf(op), op) + } + } } diff --git a/src/test/scala/chiselTests/UIntOps.scala b/src/test/scala/chiselTests/UIntOps.scala index e5ab706f443..1c376c8d237 100644 --- a/src/test/scala/chiselTests/UIntOps.scala +++ b/src/test/scala/chiselTests/UIntOps.scala @@ -149,5 +149,38 @@ class UIntOpsSpec extends ChiselPropSpec with Matchers { io.out := io.in.asBools()(2) }) } + + // We use WireDefault with 2 arguments because of + // https://www.chisel-lang.org/api/3.4.1/chisel3/WireDefault$.html + // Single Argument case 2 + property("modulo divide should give min width of arguments") { + assertKnownWidth(4) { + val x = WireDefault(UInt(8.W), DontCare) + val y = WireDefault(UInt(4.W), DontCare) + val op = x % y + WireDefault(chiselTypeOf(op), op) + } + assertKnownWidth(4) { + val x = WireDefault(UInt(4.W), DontCare) + val y = WireDefault(UInt(8.W), DontCare) + val op = x % y + WireDefault(chiselTypeOf(op), op) + } + } + + property("division should give the width of the numerator") { + assertKnownWidth(8) { + val x = WireDefault(UInt(8.W), DontCare) + val y = WireDefault(UInt(4.W), DontCare) + val op = x / y + WireDefault(chiselTypeOf(op), op) + } + assertKnownWidth(4) { + val x = WireDefault(UInt(4.W), DontCare) + val y = WireDefault(UInt(8.W), DontCare) + val op = x / y + WireDefault(chiselTypeOf(op), op) + } + } } diff --git a/src/test/scala/chiselTests/WidthSpec.scala b/src/test/scala/chiselTests/WidthSpec.scala index 0e76fdca272..84bff8efac6 100644 --- a/src/test/scala/chiselTests/WidthSpec.scala +++ b/src/test/scala/chiselTests/WidthSpec.scala @@ -186,3 +186,62 @@ class RegInitWidthSpec extends WireDefaultRegInitSpecImpl { def builder2[T <: Data](x: T, y: T): T = RegInit(x, y) } +class OpWidthSpec extends ChiselFlatSpec { + import firrtl._ + import firrtl.ir._ + + val maxWidth = 5 + val uIntOps: Seq[((UInt, UInt) => UInt, PrimOp)] = + Seq( + (_ +& _, PrimOps.Add), + (_ -& _, PrimOps.Sub), + (_ * _, PrimOps.Mul), + (_ / _, PrimOps.Div), + (_ % _, PrimOps.Rem), + (_ << _, PrimOps.Dshl), + (_ >> _, PrimOps.Dshr) + ) + + assertTesterPasses(new chisel3.testers.BasicTester { + for (i <- 0 to maxWidth) { + for (j <- 0 to maxWidth) { + for ((cOp, fOp) <- uIntOps) { + val args = Seq(i, j).map(w => Reference("", UIntType(IntWidth(w)))) + fOp.propagateType(DoPrim(fOp, args, Nil, UnknownType)) match { + case UIntType(IntWidth(w)) => + val x = 0.U(maxWidth.W).head(i) + val y = 0.U(maxWidth.W).head(j) + assert(w == cOp(x, y).getWidth) + } + } + } + } + stop() + }) + + val sIntOps: Seq[((SInt, SInt) => SInt, PrimOp)] = + Seq( + (_ +& _, PrimOps.Add), + (_ -& _, PrimOps.Sub), + (_ * _, PrimOps.Mul), + (_ / _, PrimOps.Div), + (_ % _, PrimOps.Rem) + ) + + assertTesterPasses(new chisel3.testers.BasicTester { + for (i <- 0 to maxWidth) { + for (j <- 0 to maxWidth) { + for ((cOp, fOp) <- sIntOps) { + val args = Seq(i, j).map(w => Reference("", SIntType(IntWidth(w)))) + fOp.propagateType(DoPrim(fOp, args, Nil, UnknownType)) match { + case SIntType(IntWidth(w)) => + val x = 0.U(maxWidth.W).head(i).asSInt + val y = 0.U(maxWidth.W).head(j).asSInt + assert(w == cOp(x, y).getWidth) + } + } + } + } + stop() + }) +}