Skip to content

Commit a26f89d

Browse files
jackkoenigmergify[bot]
authored andcommitted
Add support for zero-width bit extraction (#3352)
(cherry picked from commit b910bf9)
1 parent 0a612c1 commit a26f89d

File tree

3 files changed

+81
-12
lines changed

3 files changed

+81
-12
lines changed

core/src/main/scala/chisel3/Bits.scala

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -185,25 +185,34 @@ sealed abstract class Bits(private[chisel3] val width: Width) extends Element wi
185185
*
186186
* @example
187187
* {{{
188-
* myBits = 0x5 = 0b101
189-
* myBits(1,0) => 0b01 // extracts the two least significant bits
188+
* val myBits = "0b101".U
189+
* myBits(1, 0) // "0b01".U // extracts the two least significant bits
190+
*
191+
* // Note that zero-width ranges are also legal
192+
* myBits(-1, 0) // 0.U(0.W) // zero-width UInt
190193
* }}}
191194
* @param x the high bit
192195
* @param y the low bit
193-
* @return a hardware component contain the requested bits
196+
* @return a hardware component containing the requested bits
194197
*/
195198
final def apply(x: Int, y: Int): UInt = macro SourceInfoTransform.xyArg
196199

197200
/** @group SourceInfoTransformMacro */
198201
final def do_apply(x: Int, y: Int)(implicit sourceInfo: SourceInfo): UInt = {
199-
if (x < y || y < 0) {
200-
Builder.error(s"Invalid bit range ($x,$y)")
202+
if ((x < y && !(x == -1 && y == 0)) || y < 0) {
203+
val zeroWidthSuggestion =
204+
if (x == y - 1) {
205+
s". If you are trying to extract zero-width range, right-shift by 'lo' before extracting."
206+
} else {
207+
""
208+
}
209+
Builder.error(s"Invalid bit range [hi=$x, lo=$y]$zeroWidthSuggestion")
201210
}
202-
val w = x - y + 1
211+
val resultWidth = x - y + 1
203212
// This preserves old behavior while a more more consistent API is under debate
204213
// See https://github.com/freechipsproject/chisel3/issues/867
205214
litOption.map { value =>
206-
((value >> y) & ((BigInt(1) << w) - 1)).asUInt(w.W)
215+
((value >> y) & ((BigInt(1) << resultWidth) - 1)).asUInt(resultWidth.W)
207216
}.getOrElse {
208217
requireIsHardware(this, "bits to be sliced")
209218

@@ -214,21 +223,28 @@ sealed abstract class Bits(private[chisel3] val width: Width) extends Element wi
214223
case _ =>
215224
}
216225

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

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

src/test/scala/chiselTests/SIntOps.scala

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,4 +171,31 @@ class SIntOpsSpec extends ChiselPropSpec with Utils {
171171
WireDefault(chiselTypeOf(op), op)
172172
}
173173
}
174+
175+
property("Zero-width bit extractions should be supported") {
176+
assertKnownWidth(0) {
177+
val x = WireDefault(SInt(8.W), DontCare)
178+
val op = x(-1, 0)
179+
WireDefault(chiselTypeOf(op), op)
180+
}
181+
assertKnownWidth(0) {
182+
val x = WireDefault(SInt(8.W), DontCare)
183+
val hi = 5
184+
val lo = 6
185+
val op = (x >> lo)(hi - lo, 0)
186+
WireDefault(chiselTypeOf(op), op)
187+
}
188+
}
189+
190+
property("Zero-width bit extractions from the middle of an SInt should give an actionable error") {
191+
val (log, x) = grabLog(intercept[Exception](ChiselStage.emitCHIRRTL(new RawModule {
192+
val x = WireDefault(SInt(8.W), DontCare)
193+
val op = x(5, 6)
194+
WireDefault(chiselTypeOf(op), op)
195+
})))
196+
log should include(
197+
"Invalid bit range [hi=5, lo=6]. If you are trying to extract zero-width range, right-shift by 'lo' before extracting."
198+
)
199+
}
200+
174201
}

src/test/scala/chiselTests/UIntOps.scala

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,32 @@ class UIntOpsSpec extends ChiselPropSpec with Matchers with Utils {
382382
}
383383
}
384384

385+
property("Zero-width bit extractions should be supported") {
386+
assertKnownWidth(0) {
387+
val x = WireDefault(UInt(8.W), DontCare)
388+
val op = x(-1, 0)
389+
WireDefault(chiselTypeOf(op), op)
390+
}
391+
assertKnownWidth(0) {
392+
val x = WireDefault(UInt(8.W), DontCare)
393+
val hi = 5
394+
val lo = 6
395+
val op = (x >> lo)(hi - lo, 0)
396+
WireDefault(chiselTypeOf(op), op)
397+
}
398+
}
399+
400+
property("Zero-width bit extractions from the middle of a UInt should give an actionable error") {
401+
val (log, x) = grabLog(intercept[Exception](ChiselStage.emitCHIRRTL(new RawModule {
402+
val x = WireDefault(UInt(8.W), DontCare)
403+
val op = x(5, 6)
404+
WireDefault(chiselTypeOf(op), op)
405+
})))
406+
log should include(
407+
"Invalid bit range [hi=5, lo=6]. If you are trying to extract zero-width range, right-shift by 'lo' before extracting."
408+
)
409+
}
410+
385411
property("emit warning if dynamic index is too wide or too narrow") {
386412
class TooWide extends Module {
387413
val in = IO(Input(UInt(2.W)))

0 commit comments

Comments
 (0)