summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--chiselFrontend/src/main/scala/chisel3/core/Bits.scala17
-rw-r--r--chiselFrontend/src/main/scala/chisel3/internal/firrtl/IR.scala48
-rw-r--r--coreMacros/src/main/scala/chisel3/internal/RangeTransform.scala102
-rw-r--r--src/main/scala/chisel3/package.scala14
-rw-r--r--src/test/scala/chiselTests/RangeSpec.scala104
5 files changed, 284 insertions, 1 deletions
diff --git a/chiselFrontend/src/main/scala/chisel3/core/Bits.scala b/chiselFrontend/src/main/scala/chisel3/core/Bits.scala
index 4a09c70e..82b60a4c 100644
--- a/chiselFrontend/src/main/scala/chisel3/core/Bits.scala
+++ b/chiselFrontend/src/main/scala/chisel3/core/Bits.scala
@@ -563,6 +563,14 @@ private[core] sealed trait UIntFactory {
result.binding = LitBinding()
result
}
+ /** Create a UInt with the specified range */
+ def apply(range: Range): UInt = {
+ width(range.getWidth)
+ }
+ /** Create a UInt with the specified range */
+ def apply(range: (NumericBound[Int], NumericBound[Int])): UInt = {
+ apply(KnownUIntRange(range._1, range._2))
+ }
/** Create a UInt with a specified width - compatibility with Chisel2. */
// NOTE: This resolves UInt(width = 32)
@@ -728,6 +736,15 @@ object SInt {
/** Create an SInt literal with specified width. */
def apply(value: BigInt, width: Width): SInt = Lit(value, width)
+ /** Create a SInt with the specified range */
+ def apply(range: Range): SInt = {
+ width(range.getWidth)
+ }
+ /** Create a SInt with the specified range */
+ def apply(range: (NumericBound[Int], NumericBound[Int])): SInt = {
+ apply(KnownSIntRange(range._1, range._2))
+ }
+
def Lit(value: BigInt): SInt = Lit(value, Width())
def Lit(value: BigInt, width: Int): SInt = Lit(value, Width(width))
/** Create an SInt literal with specified width. */
diff --git a/chiselFrontend/src/main/scala/chisel3/internal/firrtl/IR.scala b/chiselFrontend/src/main/scala/chisel3/internal/firrtl/IR.scala
index 17b869f2..262b939f 100644
--- a/chiselFrontend/src/main/scala/chisel3/internal/firrtl/IR.scala
+++ b/chiselFrontend/src/main/scala/chisel3/internal/firrtl/IR.scala
@@ -109,6 +109,54 @@ case class Index(imm: Arg, value: Arg) extends Arg {
override def fullName(ctx: Component): String = s"${imm.fullName(ctx)}[${value.fullName(ctx)}]"
}
+sealed trait Bound
+sealed trait NumericBound[T] extends Bound {
+ val value: T
+}
+sealed case class Open[T](value: T) extends NumericBound[T]
+sealed case class Closed[T](value: T) extends NumericBound[T]
+
+sealed trait Range {
+ val min: Bound
+ val max: Bound
+ def getWidth: Width
+}
+
+sealed trait KnownIntRange extends Range {
+ val min: NumericBound[Int]
+ val max: NumericBound[Int]
+
+ require( (min, max) match {
+ case (Open(low_val), Open(high_val)) => low_val < high_val - 1
+ case (Closed(low_val), Open(high_val)) => low_val < high_val
+ case (Open(low_val), Closed(high_val)) => low_val < high_val
+ case (Closed(low_val), Closed(high_val)) => low_val <= high_val
+ })
+}
+
+sealed case class KnownUIntRange(min: NumericBound[Int], max: NumericBound[Int]) extends KnownIntRange {
+ require (min.value >= 0)
+
+ def getWidth: Width = max match {
+ case Open(v) => Width(BigInt(v - 1).bitLength.max(1))
+ case Closed(v) => Width(BigInt(v).bitLength.max(1))
+ }
+}
+
+sealed case class KnownSIntRange(min: NumericBound[Int], max: NumericBound[Int]) extends KnownIntRange {
+
+ val maxWidth = max match {
+ case Open(v) => Width(BigInt(v - 1).bitLength + 1)
+ case Closed(v) => Width(BigInt(v).bitLength + 1)
+ }
+ val minWidth = min match {
+ case Open(v) => Width(BigInt(v + 1).bitLength + 1)
+ case Closed(v) => Width(BigInt(v).bitLength + 1)
+ }
+ def getWidth: Width = maxWidth.max(minWidth)
+
+}
+
object Width {
def apply(x: Int): Width = KnownWidth(x)
def apply(): Width = UnknownWidth()
diff --git a/coreMacros/src/main/scala/chisel3/internal/RangeTransform.scala b/coreMacros/src/main/scala/chisel3/internal/RangeTransform.scala
new file mode 100644
index 00000000..f431341d
--- /dev/null
+++ b/coreMacros/src/main/scala/chisel3/internal/RangeTransform.scala
@@ -0,0 +1,102 @@
+// See LICENSE for license details.
+
+// Macro transforms that statically (at compile time) parse range specifiers and emit the raw
+// (non-human-friendly) range constructor calls.
+
+package chisel3.internal
+
+import scala.language.experimental.macros
+import scala.reflect.macros.blackbox.Context
+import scala.reflect.macros.whitebox
+
+class RangeTransform(val c: Context) {
+ import c.universe._
+ def apply(args: c.Tree*): c.Tree = {
+ val stringTrees = c.prefix.tree match {
+ case q"$_(scala.StringContext.apply(..$strings))" => strings
+ case _ => c.abort(c.enclosingPosition, s"Range macro unable to parse StringContext, got: ${showCode(c.prefix.tree)}")
+ }
+ val strings = stringTrees.map { tree => tree match {
+ case Literal(Constant(string: String)) => string
+ case _ => c.abort(c.enclosingPosition, s"Range macro unable to parse StringContext element, got: ${showRaw(tree)}")
+ } }
+
+ var nextStringIndex: Int = 1
+ var nextArgIndex: Int = 0
+ var currString: String = strings(0)
+
+ /** Mutably gets the next numeric value in the range specifier.
+ */
+ def getNextValue(): c.Tree = {
+ currString = currString.dropWhile(_ == ' ') // allow whitespace
+ if (currString.isEmpty) {
+ if (nextArgIndex >= args.length) {
+ c.abort(c.enclosingPosition, s"Incomplete range specifier")
+ }
+ val nextArg = args(nextArgIndex)
+ nextArgIndex += 1
+
+ if (nextStringIndex >= strings.length) {
+ c.abort(c.enclosingPosition, s"Incomplete range specifier")
+ }
+ currString = strings(nextStringIndex)
+ nextStringIndex += 1
+
+ nextArg
+ } else {
+ val nextStringVal = currString.takeWhile(!Set('[', '(', ' ', ',', ')', ']').contains(_))
+ currString = currString.substring(nextStringVal.length)
+ if (currString.isEmpty) {
+ c.abort(c.enclosingPosition, s"Incomplete range specifier")
+ }
+ c.parse(nextStringVal)
+ }
+ }
+
+ // Currently, not allowed to have the end stops (inclusive / exclusive) be interpolated.
+ currString = currString.dropWhile(_ == ' ')
+ val startInclusive = currString(0) match {
+ case '[' => true
+ case '(' => false
+ case other => c.abort(c.enclosingPosition, s"Unknown start inclusive/exclusive specifier, got: '$other'")
+ }
+ currString = currString.substring(1) // eat the inclusive/exclusive specifier
+ val minArg = getNextValue()
+ currString = currString.dropWhile(_ == ' ')
+ if (currString(0) != ',') {
+ c.abort(c.enclosingPosition, s"Incomplete range specifier, expected ','")
+ }
+ currString = currString.substring(1) // eat the comma
+ val maxArg = getNextValue()
+ currString = currString.dropWhile(_ == ' ')
+ val endInclusive = currString(0) match {
+ case ']' => true
+ case ')' => false
+ case other => c.abort(c.enclosingPosition, s"Unknown end inclusive/exclusive specifier, got: '$other'")
+ }
+ currString = currString.substring(1) // eat the inclusive/exclusive specifier
+ currString = currString.dropWhile(_ == ' ')
+
+ if (nextArgIndex < args.length) {
+ val unused = args.mkString("")
+ c.abort(c.enclosingPosition, s"Unused interpolated values in range specifier: '$unused'")
+ }
+ if (!currString.isEmpty || nextStringIndex < strings.length) {
+ val unused = currString + strings.slice(nextStringIndex, strings.length).mkString(", ")
+ c.abort(c.enclosingPosition, s"Unused characters in range specifier: '$unused'")
+ }
+
+ val startBound = if (startInclusive) {
+ q"_root_.chisel3.internal.firrtl.Closed($minArg)"
+ } else {
+ q"_root_.chisel3.internal.firrtl.Open($minArg)"
+ }
+ val endBound = if (endInclusive) {
+ q"_root_.chisel3.internal.firrtl.Closed($maxArg)"
+ } else {
+ q"_root_.chisel3.internal.firrtl.Open($maxArg)"
+ }
+
+ q"($startBound, $endBound)"
+ }
+}
diff --git a/src/main/scala/chisel3/package.scala b/src/main/scala/chisel3/package.scala
index 3cdda971..29aa6528 100644
--- a/src/main/scala/chisel3/package.scala
+++ b/src/main/scala/chisel3/package.scala
@@ -3,7 +3,7 @@
package object chisel3 { // scalastyle:ignore package.object.name
import scala.language.experimental.macros
- import internal.firrtl.Width
+ import internal.firrtl.{Width, NumericBound}
import internal.sourceinfo.{SourceInfo, SourceInfoTransform}
import util.BitPat
@@ -202,5 +202,17 @@ package object chisel3 { // scalastyle:ignore package.object.name
implicit def fromBigIntToIntParam(x: BigInt): IntParam = IntParam(x)
implicit def fromDoubleToDoubleParam(x: Double): DoubleParam = DoubleParam(x)
implicit def fromStringToStringParam(x: String): StringParam = StringParam(x)
+
+ implicit class ChiselRange(val sc: StringContext) extends AnyVal {
+ /** Specifies a range using mathematical range notation. Variables can be interpolated using
+ * standard string interpolation syntax.
+ * @example {{{
+ * UInt(range"[0, 2)")
+ * UInt(range"[0, $myInt)")
+ * UInt(range"[0, ${myInt + 2})")
+ * }}}
+ */
+ def range(args: Any*): (NumericBound[Int], NumericBound[Int]) = macro chisel3.internal.RangeTransform.apply
+ }
}
}
diff --git a/src/test/scala/chiselTests/RangeSpec.scala b/src/test/scala/chiselTests/RangeSpec.scala
new file mode 100644
index 00000000..e2313f34
--- /dev/null
+++ b/src/test/scala/chiselTests/RangeSpec.scala
@@ -0,0 +1,104 @@
+// See LICENSE for license details.
+
+package chiselTests
+
+import chisel3._
+import chisel3.experimental.ChiselRange
+
+import chisel3.internal.firrtl.{Open, Closed}
+import org.scalatest.{Matchers, FreeSpec}
+
+class RangeSpec extends FreeSpec with Matchers {
+ "Ranges can be specified for UInt, SInt, and FixedPoint" - {
+ "invalid range specifiers should fail at compile time" in {
+ assertDoesNotCompile(""" range"" """)
+ assertDoesNotCompile(""" range"[]" """)
+ assertDoesNotCompile(""" range"0" """)
+ assertDoesNotCompile(""" range"[0]" """)
+ assertDoesNotCompile(""" range"[0, 1" """)
+ assertDoesNotCompile(""" range"0, 1]" """)
+ assertDoesNotCompile(""" range"[0, 1, 2]" """)
+ assertDoesNotCompile(""" range"[a]" """)
+ assertDoesNotCompile(""" range"[a, b]" """)
+ assertCompiles(""" range"[0, 1]" """) // syntax sanity check
+ }
+
+ "range macros should allow open and closed bounds" in {
+ range"[-1, 1)" should be( (Closed(-1), Open(1)) )
+ range"[-1, 1]" should be( (Closed(-1), Closed(1)) )
+ range"(-1, 1]" should be( (Open(-1), Closed(1)) )
+ range"(-1, 1)" should be( (Open(-1), Open(1)) )
+ }
+
+ "range specifiers should be whitespace tolerant" in {
+ range"[-1,1)" should be( (Closed(-1), Open(1)) )
+ range" [-1,1) " should be( (Closed(-1), Open(1)) )
+ range" [ -1 , 1 ) " should be( (Closed(-1), Open(1)) )
+ range" [ -1 , 1 ) " should be( (Closed(-1), Open(1)) )
+ }
+
+ "range macros should work with interpolated variables" in {
+ val a = 10
+ val b = -3
+
+ range"[$b, $a)" should be( (Closed(b), Open(a)) )
+ range"[${a + b}, $a)" should be( (Closed(a + b), Open(a)) )
+ range"[${-3 - 7}, ${-3 + a})" should be( (Closed(-10), Open(-3 + a)) )
+
+ def number(n: Int): Int = n
+ range"[${number(1)}, ${number(3)})" should be( (Closed(1), Open(3)) )
+ }
+
+ "UInt should get the correct width from a range" in {
+ UInt(range"[0, 8)").getWidth should be (3)
+ UInt(range"[0, 8]").getWidth should be (4)
+ UInt(range"[0, 0]").getWidth should be (1)
+ }
+
+ "SInt should get the correct width from a range" in {
+ SInt(range"[0, 8)").getWidth should be (4)
+ SInt(range"[0, 8]").getWidth should be (5)
+ SInt(range"[-4, 4)").getWidth should be (3)
+ SInt(range"[0, 0]").getWidth should be (1)
+ }
+
+ "UInt should check that the range is valid" in {
+ an [IllegalArgumentException] should be thrownBy {
+ UInt(range"[1, 0]")
+ }
+ an [IllegalArgumentException] should be thrownBy {
+ UInt(range"[-1, 1]")
+ }
+ an [IllegalArgumentException] should be thrownBy {
+ UInt(range"(0,0]")
+ }
+ an [IllegalArgumentException] should be thrownBy {
+ UInt(range"[0,0)")
+ }
+ an [IllegalArgumentException] should be thrownBy {
+ UInt(range"(0,0)")
+ }
+ an [IllegalArgumentException] should be thrownBy {
+ UInt(range"(0,1)")
+ }
+ }
+
+ "SInt should check that the range is valid" in {
+ an [IllegalArgumentException] should be thrownBy {
+ SInt(range"[1, 0]")
+ }
+ an [IllegalArgumentException] should be thrownBy {
+ SInt(range"(0,0]")
+ }
+ an [IllegalArgumentException] should be thrownBy {
+ SInt(range"[0,0)")
+ }
+ an [IllegalArgumentException] should be thrownBy {
+ SInt(range"(0,0)")
+ }
+ an [IllegalArgumentException] should be thrownBy {
+ SInt(range"(0,1)")
+ }
+ }
+ }
+}