Skip to content

Commit 4a953b3

Browse files
authored
Error eagerly when attempting to annotate dynamic indices (#4896)
This is done by adding DynamicIndexBinding so that Chisel can more precisely track when a Data is part of a dynamic index.
1 parent 43bba60 commit 4a953b3

File tree

8 files changed

+109
-7
lines changed

8 files changed

+109
-7
lines changed

core/src/main/scala/chisel3/Aggregate.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -415,7 +415,7 @@ sealed class Vec[T <: Data] private[chisel3] (gen: => T, val length: Int)
415415
// TODO port technically isn't directly child of this data structure, but the result of some
416416
// muxes / demuxes. However, this does make access consistent with the top-level bindings.
417417
// Perhaps there's a cleaner way of accomplishing this...
418-
port.bind(ChildBinding(this), reconstructedResolvedDirection)
418+
port.bind(DynamicIndexBinding(this), reconstructedResolvedDirection)
419419

420420
val i = Vec.truncateIndex(p, length)(UnlocatableSourceInfo)
421421
port.setRef(Node(this), i)

core/src/main/scala/chisel3/Data.scala

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -510,6 +510,7 @@ abstract class Data extends HasId with NamedComponent with DataIntf {
510510
case ElementLitBinding(litArg) => "(unhandled literal)"
511511
case BundleLitBinding(litMap) => "(unhandled bundle literal)"
512512
case VecLitBinding(litMap) => "(unhandled vec literal)"
513+
case DynamicIndexBinding(vec) => _bindingToString(vec.topBinding)
513514
case _ => ""
514515
}
515516

@@ -687,9 +688,10 @@ abstract class Data extends HasId with NamedComponent with DataIntf {
687688
true
688689
case Some(ViewBinding(target, _)) => target.isVisibleFromModule
689690
case Some(AggregateViewBinding(mapping, _)) => mapping.values.forall(_.isVisibleFromModule)
690-
case Some(pb: SecretPortBinding) => true // Ignore secret to not require visibility
691-
case Some(_: UnconstrainedBinding) => true
692-
case _ => false
691+
case Some(DynamicIndexBinding(vec)) => vec.isVisibleFromModule // Use underlying Vec visibility for dynamic index
692+
case Some(pb: SecretPortBinding) => true // Ignore secret to not require visibility
693+
case Some(_: UnconstrainedBinding) => true
694+
case _ => false
693695
}
694696
}
695697
private[chisel3] def visibleFromBlock: Option[SourceInfo] = MonoConnect.checkBlockVisibility(this)

core/src/main/scala/chisel3/experimental/package.scala

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ package chisel3
44

55
import chisel3.reflect.DataMirror.internal.chiselTypeClone
66
import chisel3.experimental.SourceInfo
7+
import chisel3.internal.binding.DynamicIndexBinding
78

89
/** Package for experimental features, which may have their API changed, be removed, etc.
910
*
@@ -66,13 +67,18 @@ package object experimental {
6667
*/
6768
object requireIsAnnotatable {
6869
def apply(node: Data, msg: String = ""): Unit = {
70+
def prefix = if (msg.nonEmpty) s"$msg " else ""
6971
requireIsHardware(node, msg)
7072
if (node.isLit) {
71-
val prefix = if (msg.nonEmpty) s"$msg " else ""
7273
throw ExpectedAnnotatableException(
7374
s"$prefix'$node' must not be a literal."
7475
)
7576
}
77+
if (node.topBinding.isInstanceOf[DynamicIndexBinding]) {
78+
throw ExpectedAnnotatableException(
79+
s"$prefix'$node' must not be a dynamic index into a Vec. Try assigning it to a Wire."
80+
)
81+
}
7682
}
7783
}
7884

core/src/main/scala/chisel3/internal/Binding.scala

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,9 @@ private[chisel3] object binding {
3737
case ActualDirection.Input => Input
3838
case dir => throw new RuntimeException(s"Unexpected port element direction '$dir'")
3939
}
40-
case _ => Internal
40+
// Dynamic indices use the direction of the underlying Vec
41+
case DynamicIndexBinding(vec) => from(vec.topBinding, direction)
42+
case _ => Internal
4143
}
4244
}
4345
}
@@ -89,6 +91,15 @@ private[chisel3] object binding {
8991
case class RegBinding(enclosure: RawModule, parentBlock: Option[Block]) extends ConstrainedBinding with BlockBinding
9092
case class WireBinding(enclosure: RawModule, parentBlock: Option[Block]) extends ConstrainedBinding with BlockBinding
9193

94+
/** Special binding for Vec dynamic indexing */
95+
case class DynamicIndexBinding(vec: Vec[_]) extends ConstrainedBinding with BlockBinding {
96+
def parentBlock: Option[Block] = vec.topBinding match {
97+
case b: BlockBinding => b.parentBlock
98+
case _ => None
99+
}
100+
def enclosure: BaseModule = vec._parent.get
101+
}
102+
92103
case class ClassBinding(enclosure: Class) extends ConstrainedBinding with ReadOnlyBinding
93104

94105
case class ObjectFieldBinding(enclosure: BaseModule) extends ConstrainedBinding

core/src/main/scala/chisel3/internal/Builder.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -645,6 +645,7 @@ private[chisel3] object Builder extends LazyLogging {
645645
// And name of the field if we have one, we don't for dynamic indexing of Vecs
646646
getSubName(data).map(p + "_" + _).getOrElse(p)
647647
}
648+
case DynamicIndexBinding(vec) => recData(vec)
648649
case SampleElementBinding(parent) => recData(parent)
649650
case PortBinding(mod) if Builder.currentModule.contains(mod) => data.seedOpt
650651
case PortBinding(mod) => map2(mod.seedOpt, data.seedOpt)(_ + "_" + _)

core/src/main/scala/chisel3/internal/MonoConnect.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ private[chisel3] object MonoConnect {
9595
x.topBinding match {
9696
case mp: MemoryPortBinding =>
9797
None // TODO (albert-magyar): remove this "bridge" for odd enable logic of current CHIRRTL memories
98+
case DynamicIndexBinding(vec) => checkBlockVisibility(vec) // This goes with the MemoryPortBinding hack
9899
case bb: BlockBinding => bb.parentBlock.collect { case b: Block if !visible(b) => b.sourceInfo }
99100
case _ => None
100101
}

src/test/scala-2/chiselTests/DontTouchSpec.scala

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,40 @@ class DontTouchSpec extends AnyFlatSpec with Matchers with FileCheck {
116116
e.getMessage should include("must not be a literal")
117117
}
118118

119+
it should "give a decent error when used on dynamic indices" in {
120+
val e = the[chisel3.ExpectedAnnotatableException] thrownBy {
121+
ChiselStage.emitCHIRRTL(new Module {
122+
override def desiredName = "Top"
123+
val idx = Wire(UInt(2.W))
124+
val vec = Wire(Vec(4, UInt(8.W)))
125+
val x = vec(idx)
126+
dontTouch(x)
127+
})
128+
}
129+
e.getMessage should include(
130+
"Data marked dontTouch 'Top.vec[idx]: Wire[UInt<8>]' must not be a dynamic index into a Vec. Try assigning it to a Wire."
131+
)
132+
}
133+
134+
it should "give a decent error when used on subfields of dynamic indices" in {
135+
class MyBundle extends Bundle {
136+
val a = UInt(8.W)
137+
val b = UInt(8.W)
138+
}
139+
val e = the[chisel3.ExpectedAnnotatableException] thrownBy {
140+
ChiselStage.emitCHIRRTL(new Module {
141+
override def desiredName = "Top"
142+
val idx = Wire(UInt(2.W))
143+
val vec = Wire(Vec(4, new MyBundle))
144+
val x = vec(idx).a
145+
dontTouch(x)
146+
})
147+
}
148+
e.getMessage should include(
149+
"Data marked dontTouch 'Top.vec[idx].a: Wire[UInt<8>]' must not be a dynamic index into a Vec. Try assigning it to a Wire."
150+
)
151+
}
152+
119153
"fields" should "be marked don't touch by default" in {
120154
ChiselStage
121155
.emitCHIRRTL(new HasDeadCodeLeaves())

src/test/scala-2/chiselTests/Vec.scala

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import chisel3.util.{Counter, DecoupledIO}
77
import circt.stage.ChiselStage
88
import org.scalatest.matchers.should.Matchers
99
import org.scalatest.propspec.AnyPropSpec
10+
import chisel3.testing.scalatest.FileCheck
1011

1112
class IOTesterModFill(vecSize: Int) extends Module {
1213
// This should generate a BindingException when we attempt to wire up the Vec.fill elements
@@ -55,7 +56,7 @@ class ReduceTreeTester extends Module {
5556
dut.io := DontCare
5657
}
5758

58-
class VecSpec extends AnyPropSpec with Matchers with LogUtils {
59+
class VecSpec extends AnyPropSpec with Matchers with LogUtils with FileCheck {
5960

6061
private def uint(value: BigInt): String = uint(value, value.bitLength.max(1))
6162
private def uint(value: BigInt, width: Int): String =
@@ -475,4 +476,50 @@ class VecSpec extends AnyPropSpec with Matchers with LogUtils {
475476
chirrtl should include("connect _out_WIRE, UInt<1>(0h0)")
476477
chirrtl should include("connect out, vec[_out_WIRE]")
477478
}
479+
480+
property("It should be legal to dynamically index ports of child modules") {
481+
class Child extends RawModule {
482+
val in = IO(Input(Vec(4, UInt(8.W))))
483+
val out = IO(Output(Vec(4, UInt(8.W))))
484+
out := in
485+
}
486+
class Top extends RawModule {
487+
val in = IO(Input(Vec(4, UInt(8.W))))
488+
val idx = IO(Input(UInt(2.W)))
489+
val out = IO(Output(UInt(8.W)))
490+
val child = Module(new Child)
491+
child.in := in
492+
val index = idx + 1.U // roll-over
493+
child.in(index) := 0.U
494+
out := child.out(idx)
495+
}
496+
ChiselStage
497+
.emitCHIRRTL(new Top)
498+
.fileCheck()(
499+
"""|CHECK: connect child.in[index], UInt<1>(0h0)
500+
|CHECK: connect out, child.out[idx]
501+
|""".stripMargin
502+
)
503+
}
504+
505+
property("It should be legal to dynamically index the result of a SyncReadMem read") {
506+
class Top extends Module {
507+
val idx = IO(Input(UInt(2.W)))
508+
val jdx = IO(Input(UInt(1.W)))
509+
val en = IO(Input(Bool()))
510+
val out = IO(Output(UInt(8.W)))
511+
val mem = SyncReadMem(4, Vec(2, UInt(8.W)))
512+
// SyncReadMem read gets placed in a when block
513+
// There is sketchy logic to allow this, make sure it also works for dynamic indexing the mem-port
514+
val port = mem.read(idx, en)
515+
out := port(jdx)
516+
}
517+
ChiselStage
518+
.emitCHIRRTL(new Top)
519+
.fileCheck()(
520+
"""|CHECK: read mport port = mem[_port_WIRE], clock
521+
|CHECK: connect out, port[jdx]
522+
|""".stripMargin
523+
)
524+
}
478525
}

0 commit comments

Comments
 (0)