Skip to content

Add support for zero-width bit extraction (backport #3352) #3353

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: 3.6.x
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 32 additions & 10 deletions core/src/main/scala/chisel3/Bits.scala
Original file line number Diff line number Diff line change
Expand Up @@ -181,25 +181,40 @@ sealed abstract class Bits(private[chisel3] val width: Width) extends Element wi
*
* @example
* {{{
* myBits = 0x5 = 0b101
* myBits(1,0) => 0b01 // extracts the two least significant bits
* val myBits = "0b101".U
* myBits(1, 0) // "0b01".U // extracts the two least significant bits
*
* // Note that zero-width ranges are also legal
* myBits(-1, 0) // 0.U(0.W) // zero-width UInt
* }}}
* @param x the high bit
* @param y the low bit
* @return a hardware component contain the requested bits
* @return a hardware component containing the requested bits
*/
final def apply(x: Int, y: Int): UInt = macro SourceInfoTransform.xyArg

/** @group SourceInfoTransformMacro */
<<<<<<< HEAD
final def do_apply(x: Int, y: Int)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): UInt = {
if (x < y || y < 0) {
Builder.error(s"Invalid bit range ($x,$y)")
=======
final def do_apply(x: Int, y: Int)(implicit sourceInfo: SourceInfo): UInt = {
if ((x < y && !(x == -1 && y == 0)) || y < 0) {
val zeroWidthSuggestion =
if (x == y - 1) {
s". If you are trying to extract zero-width range, right-shift by 'lo' before extracting."
} else {
""
}
Builder.error(s"Invalid bit range [hi=$x, lo=$y]$zeroWidthSuggestion")
>>>>>>> b910bf946 (Add support for zero-width bit extraction (#3352))
}
val w = x - y + 1
val resultWidth = x - y + 1
// This preserves old behavior while a more more consistent API is under debate
// See https://github.com/freechipsproject/chisel3/issues/867
litOption.map { value =>
((value >> y) & ((BigInt(1) << w) - 1)).asUInt(w.W)
((value >> y) & ((BigInt(1) << resultWidth) - 1)).asUInt(resultWidth.W)
}.getOrElse {
requireIsHardware(this, "bits to be sliced")

Expand All @@ -210,21 +225,28 @@ sealed abstract class Bits(private[chisel3] val width: Width) extends Element wi
case _ =>
}

pushOp(DefPrim(sourceInfo, UInt(Width(w)), BitsExtractOp, this.ref, ILit(x), ILit(y)))
// FIRRTL does not yet support empty extraction so we must return the zero-width wire here:
if (resultWidth == 0) {
0.U(0.W)
} else {
pushOp(DefPrim(sourceInfo, UInt(Width(resultWidth)), BitsExtractOp, this.ref, ILit(x), ILit(y)))
}
}
}

// REVIEW TODO: again, is this necessary? Or just have this and use implicits?
/** Returns a subset of bits on this $coll from `hi` to `lo` (inclusive), statically addressed.
*
* @example
* {{{
* myBits = 0x5 = 0b101
* myBits(1,0) => 0b01 // extracts the two least significant bits
* val myBits = "0b101".U
* myBits(1, 0) // "0b01".U // extracts the two least significant bits
*
* // Note that zero-width ranges are also legal
* myBits(-1, 0) // 0.U(0.W) // zero-width UInt
* }}}
* @param x the high bit
* @param y the low bit
* @return a hardware component contain the requested bits
* @return a hardware component containing the requested bits
*/
final def apply(x: BigInt, y: BigInt): UInt = macro SourceInfoTransform.xyArg

Expand Down
27 changes: 27 additions & 0 deletions src/test/scala/chiselTests/SIntOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -171,4 +171,31 @@ class SIntOpsSpec extends ChiselPropSpec with Utils {
WireDefault(chiselTypeOf(op), op)
}
}

property("Zero-width bit extractions should be supported") {
assertKnownWidth(0) {
val x = WireDefault(SInt(8.W), DontCare)
val op = x(-1, 0)
WireDefault(chiselTypeOf(op), op)
}
assertKnownWidth(0) {
val x = WireDefault(SInt(8.W), DontCare)
val hi = 5
val lo = 6
val op = (x >> lo)(hi - lo, 0)
WireDefault(chiselTypeOf(op), op)
}
}

property("Zero-width bit extractions from the middle of an SInt should give an actionable error") {
val (log, x) = grabLog(intercept[Exception](ChiselStage.emitCHIRRTL(new RawModule {
val x = WireDefault(SInt(8.W), DontCare)
val op = x(5, 6)
WireDefault(chiselTypeOf(op), op)
})))
log should include(
"Invalid bit range [hi=5, lo=6]. If you are trying to extract zero-width range, right-shift by 'lo' before extracting."
)
}

}
26 changes: 26 additions & 0 deletions src/test/scala/chiselTests/UIntOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,32 @@ class UIntOpsSpec extends ChiselPropSpec with Matchers with Utils {
}
}

property("Zero-width bit extractions should be supported") {
assertKnownWidth(0) {
val x = WireDefault(UInt(8.W), DontCare)
val op = x(-1, 0)
WireDefault(chiselTypeOf(op), op)
}
assertKnownWidth(0) {
val x = WireDefault(UInt(8.W), DontCare)
val hi = 5
val lo = 6
val op = (x >> lo)(hi - lo, 0)
WireDefault(chiselTypeOf(op), op)
}
}

property("Zero-width bit extractions from the middle of a UInt should give an actionable error") {
val (log, x) = grabLog(intercept[Exception](ChiselStage.emitCHIRRTL(new RawModule {
val x = WireDefault(UInt(8.W), DontCare)
val op = x(5, 6)
WireDefault(chiselTypeOf(op), op)
})))
log should include(
"Invalid bit range [hi=5, lo=6]. If you are trying to extract zero-width range, right-shift by 'lo' before extracting."
)
}

property("emit warning if dynamic index is too wide or too narrow") {
class TooWide extends Module {
val in = IO(Input(UInt(2.W)))
Expand Down