Skip to content
2 changes: 2 additions & 0 deletions core/src/main/scala/chisel3/Bits.scala
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ sealed abstract class Bits(private[chisel3] val width: Width) extends Element wi
requireIsHardware(this, "bits to be indexed")

widthOption match {
case Some(w) if w == 0 => Builder.error(s"Cannot extract from zero-width")
case Some(w) if x >= w => Builder.error(s"High index $x is out of range [0, ${w - 1}]")
case _ =>
}
Expand Down Expand Up @@ -190,6 +191,7 @@ sealed abstract class Bits(private[chisel3] val width: Width) extends Element wi
requireIsHardware(this, "bits to be sliced")

widthOption match {
case Some(w) if w == 0 => Builder.error(s"Cannot extract from zero-width")
case Some(w) if y >= w => Builder.error(s"High and low indices $x and $y are both out of range [0, ${w - 1}]")
case Some(w) if x >= w => Builder.error(s"High index $x is out of range [0, ${w - 1}]")
case _ =>
Expand Down
4 changes: 2 additions & 2 deletions core/src/main/scala/chisel3/internal/firrtl/IR.scala
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ case class ILit(n: BigInt) extends Arg {

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)
def minWidth: Int = (if (w.known) 0 else 1).max(n.bitLength)

def cloneWithWidth(newWidth: Width): this.type = {
ULit(n, newWidth).asInstanceOf[this.type]
Expand All @@ -149,7 +149,7 @@ case class SLit(n: BigInt, w: Width) extends LitArg(n, w) {
val unsigned = if (n < 0) (BigInt(1) << width.get) + n else n
s"asSInt(${ULit(unsigned, width).name})"
}
def minWidth: Int = 1 + n.bitLength
def minWidth: Int = (if (w.known) 0 else 1) + n.bitLength

def cloneWithWidth(newWidth: Width): this.type = {
SLit(n, newWidth).asInstanceOf[this.type]
Expand Down
10 changes: 6 additions & 4 deletions src/test/scala/chiselTests/ChiselSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,11 @@ trait ChiselRunners extends Assertions with BackendCompilationUtilities {
val x = gen
assert(x.getWidth === expected)
// Sanity check that firrtl doesn't change the width
x := 0.U.asTypeOf(chiselTypeOf(x))
x := 0.U(0.W).asTypeOf(chiselTypeOf(x))
val (_, done) = chisel3.util.Counter(true.B, 2)
val ones = if (expected == 0) 0.U(0.W) else -1.S(expected.W).asUInt
when(done) {
chisel3.assert(~(x.asUInt) === -1.S(expected.W).asUInt)
chisel3.assert(~(x.asUInt) === ones)
stop()
}
})
Expand All @@ -79,10 +80,11 @@ trait ChiselRunners extends Assertions with BackendCompilationUtilities {
assertTesterPasses(new BasicTester {
val x = gen
assert(!x.isWidthKnown, s"Asserting that width should be inferred yet width is known to Chisel!")
x := 0.U.asTypeOf(chiselTypeOf(x))
x := 0.U(0.W).asTypeOf(chiselTypeOf(x))
val (_, done) = chisel3.util.Counter(true.B, 2)
val ones = if (expected == 0) 0.U(0.W) else -1.S(expected.W).asUInt
when(done) {
chisel3.assert(~(x.asUInt) === -1.S(expected.W).asUInt)
chisel3.assert(~(x.asUInt) === ones)
stop()
}
})
Expand Down
14 changes: 8 additions & 6 deletions src/test/scala/chiselTests/OneHotMuxSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,8 @@ class OneHotMuxSpec extends AnyFreeSpec with Matchers with ChiselRunners {
"UIntToOH with output width greater than 2^(input width)" in {
assertTesterPasses(new UIntToOHTester)
}
"UIntToOH should not accept width of zero (until zero-width wires are fixed" in {
intercept[IllegalArgumentException] {
assertTesterPasses(new BasicTester {
val out = UIntToOH(0.U, 0)
})
}
"UIntToOH should accept width of zero" in {
assertTesterPasses(new ZeroWidthOHTester)
}

}
Expand Down Expand Up @@ -316,3 +312,9 @@ class UIntToOHTester extends BasicTester {

stop()
}

class ZeroWidthOHTester extends BasicTester {
val out = UIntToOH(0.U, 0)
assert(out === 0.U(0.W))
stop()
}
23 changes: 23 additions & 0 deletions src/test/scala/chiselTests/SIntOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,19 @@ class SIntLitExtractTester extends BasicTester {
stop()
}

class SIntLitZeroWidthTester extends BasicTester {
assert(-0.S(0.W) === 0.S)
assert(~0.S(0.W) === 0.S)
assert(0.S(0.W) + 0.S(0.W) === 0.S)
assert(5.S * 0.S(0.W) === 0.S)
assert(0.S(0.W) / 5.S === 0.S)
assert(0.S(0.W).head(0) === 0.U)
assert(0.S(0.W).tail(0) === 0.U)
assert(0.S(0.W).pad(1) === 0.S)
assert(-0.S(0.W)(0, 0) === "b0".U)
stop()
}

class SIntOpsSpec extends ChiselPropSpec with Utils {

property("SIntOps should elaborate") {
Expand All @@ -116,6 +129,10 @@ class SIntOpsSpec extends ChiselPropSpec with Utils {
assertTesterPasses(new SIntLitExtractTester)
}

property("Basic arithmetic and bit operations with zero-width literals should return correct result (0)") {
assertTesterPasses(new SIntLitZeroWidthTester)
}

// We use WireDefault with 2 arguments because of
// https://www.chisel-lang.org/api/3.4.1/chisel3/WireDefault$.html
// Single Argument case 2
Expand Down Expand Up @@ -147,5 +164,11 @@ class SIntOpsSpec extends ChiselPropSpec with Utils {
val op = x / y
WireDefault(chiselTypeOf(op), op)
}
assertKnownWidth(1) {
val x = WireDefault(SInt(0.W), DontCare)
val y = WireDefault(SInt(8.W), DontCare)
val op = x / y
WireDefault(chiselTypeOf(op), op)
}
}
}
99 changes: 99 additions & 0 deletions src/test/scala/chiselTests/UIntOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,14 @@ class BadBoolConversion extends Module {
io.b := io.u.asBool
}

class ZeroWidthBoolConversion extends Module {
val io = IO(new Bundle {
val u = Input(UInt(0.W))
val b = Output(Bool())
})
io.b := io.u.asBool
}

class NegativeShift(t: => Bits) extends Module {
val io = IO(new Bundle {})
Reg(t) >> -1
Expand Down Expand Up @@ -185,6 +193,19 @@ class UIntLitExtractTester extends BasicTester {
stop()
}

class UIntLitZeroWidthTester extends BasicTester {
assert(-0.U(0.W) === 0.U)
assert(~0.U(0.W) === 0.U)
assert(0.U(0.W) + 0.U(0.W) === 0.U)
assert(5.U * 0.U(0.W) === 0.U)
assert(0.U(0.W) / 5.U === 0.U)
assert(0.U(0.W).head(0) === 0.U)
assert(0.U(0.W).tail(0) === 0.U)
assert(0.U(0.W).pad(2) === 0.U)
assert("b0".U(0.W)(0, 0) === "b0".U)
stop()
}

class UIntOpsSpec extends ChiselPropSpec with Matchers with Utils {
// Disable shrinking on error.
implicit val noShrinkListVal = Shrink[List[Int]](_ => Stream.empty)
Expand All @@ -194,6 +215,10 @@ class UIntOpsSpec extends ChiselPropSpec with Matchers with Utils {
ChiselStage.elaborate(new GoodBoolConversion)
}

property("Bools cannot be created from 0 bit UInts") {
a[Exception] should be thrownBy extractCause[Exception] { ChiselStage.elaborate(new ZeroWidthBoolConversion) }
}

property("Bools cannot be created from >1 bit UInts") {
a[Exception] should be thrownBy extractCause[Exception] { ChiselStage.elaborate(new BadBoolConversion) }
}
Expand All @@ -216,6 +241,24 @@ class UIntOpsSpec extends ChiselPropSpec with Matchers with Utils {
}
}

property("Out-of-bounds extraction from known-zero-width UInts") {
a[ChiselException] should be thrownBy extractCause[ChiselException] {
ChiselStage.elaborate(new RawModule {
val u = IO(Input(UInt(0.W)))
u(0, 0)
})
}
}

property("Out-of-bounds single-bit extraction from known-zero-width UInts") {
a[ChiselException] should be thrownBy extractCause[ChiselException] {
ChiselStage.elaborate(new RawModule {
val u = IO(Input(UInt(0.W)))
u(0)
})
}
}

property("UIntOps should elaborate") {
ChiselStage.elaborate { new UIntOps }
}
Expand Down Expand Up @@ -244,6 +287,10 @@ class UIntOpsSpec extends ChiselPropSpec with Matchers with Utils {
assertTesterPasses(new UIntLitExtractTester)
}

property("Basic arithmetic and bit operations with zero-width literals should return correct result (0)") {
assertTesterPasses(new UIntLitZeroWidthTester)
}

property("asBools should support chained apply") {
ChiselStage.elaborate(new Module {
val io = IO(new Bundle {
Expand All @@ -270,6 +317,18 @@ class UIntOpsSpec extends ChiselPropSpec with Matchers with Utils {
val op = x % y
WireDefault(chiselTypeOf(op), op)
}
assertKnownWidth(0) {
val x = WireDefault(UInt(0.W), DontCare)
val y = WireDefault(UInt(8.W), DontCare)
val op = x % y
WireDefault(chiselTypeOf(op), op)
}
assertKnownWidth(0) {
val x = WireDefault(UInt(8.W), DontCare)
val y = WireDefault(UInt(0.W), DontCare)
val op = x % y
WireDefault(chiselTypeOf(op), op)
}
}

property("division should give the width of the numerator") {
Expand All @@ -285,5 +344,45 @@ class UIntOpsSpec extends ChiselPropSpec with Matchers with Utils {
val op = x / y
WireDefault(chiselTypeOf(op), op)
}
assertKnownWidth(0) {
val x = WireDefault(UInt(0.W), DontCare)
val y = WireDefault(UInt(8.W), DontCare)
val op = x / y
WireDefault(chiselTypeOf(op), op)
}
}

property("head and tail should be zero-width on zero-width wires") {
assertKnownWidth(0) {
val x = WireDefault(UInt(0.W), DontCare)
val op = x.tail(0)
WireDefault(chiselTypeOf(op), op)
}
assertKnownWidth(0) {
val x = WireDefault(UInt(0.W), DontCare)
val op = x.head(0)
WireDefault(chiselTypeOf(op), op)
}
}

property("basic arithmetic operations be supported on zero-width wires") {
assertKnownWidth(0) {
val x = WireDefault(UInt(0.W), DontCare)
val y = WireDefault(UInt(0.W), DontCare)
val op = x + y
WireDefault(chiselTypeOf(op), op)
}
assertKnownWidth(0) {
val x = WireDefault(UInt(0.W), DontCare)
val y = WireDefault(UInt(0.W), DontCare)
val op = x - y
WireDefault(chiselTypeOf(op), op)
}
assertKnownWidth(0) {
val x = WireDefault(UInt(0.W), DontCare)
val y = WireDefault(UInt(0.W), DontCare)
val op = x * y
WireDefault(chiselTypeOf(op), op)
}
}
}
59 changes: 58 additions & 1 deletion src/test/scala/chiselTests/WidthSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,30 @@ object SimpleBundle {
}
}

class ZeroWidthBundle extends Bundle {
val x = UInt(0.W)
val y = UInt()
}
object ZeroWidthBundle {
def intoWire(): ZeroWidthBundle = {
val w = Wire(new ZeroWidthBundle)
w.x := 0.U(0.W)
w.y := 0.U(0.W)
w
}
}

class WidthSpec extends ChiselFlatSpec {
"Literals without specified widths" should "get the minimum legal width" in {
"hdeadbeef".U.getWidth should be(32)
"h_dead_beef".U.getWidth should be(32)
"h0a".U.getWidth should be(4)
"h1a".U.getWidth should be(5)
"h0".U.getWidth should be(1)
"h0".U.getWidth should be(1) // no literal is zero-width unless explicitly requested.
1.U.getWidth should be(1)
1.S.getWidth should be(2)
0.U.getWidth should be(1)
0.S.getWidth should be(1)
}
}

Expand All @@ -50,6 +65,21 @@ abstract class WireRegWidthSpecImpl extends ChiselFlatSpec {
}
}

it should "set the width to zero if the template type is set to zero-width" in {
assertKnownWidth(0) {
builder(UInt(0.W))
}
assertKnownWidth(0) {
val w = builder(new ZeroWidthBundle)
w := ZeroWidthBundle.intoWire()
w.x
}
assertKnownWidth(0) {
val x = builder(Vec(1, UInt(0.W)))
x(0)
}
}

it should "infer the width if the template type has no width" in {
assertInferredWidth(4) {
val w = builder(UInt())
Expand All @@ -67,6 +97,24 @@ abstract class WireRegWidthSpecImpl extends ChiselFlatSpec {
w(0)
}
}

it should "infer width as zero if the template type has no width and is initialized to zero-width literal" in {
assertInferredWidth(0) {
val w = builder(UInt())
w := 0.U(0.W)
w
}
assertInferredWidth(0) {
val w = builder(new ZeroWidthBundle)
w := ZeroWidthBundle.intoWire()
w.y
}
assertInferredWidth(0) {
val w = builder(Vec(1, UInt()))
w(0) := 0.U(0.W)
w(0)
}
}
}

class WireWidthSpec extends WireRegWidthSpecImpl {
Expand All @@ -89,6 +137,9 @@ abstract class WireDefaultRegInitSpecImpl extends ChiselFlatSpec {
assertKnownWidth(4) {
builder1(3.U(4.W))
}
assertKnownWidth(0) {
builder1(0.U(0.W))
}
}

it should "NOT set width if passed a literal without a forced width" in {
Expand All @@ -97,6 +148,12 @@ abstract class WireDefaultRegInitSpecImpl extends ChiselFlatSpec {
w := 3.U(4.W)
w
}

assertInferredWidth(1) {
val w = builder1(0.U)
w := 0.U(0.W)
w
}
}

it should "NOT set width if passed a non-literal" in {
Expand Down