Answering this with Brian's other post and comment on his own answer on this post in mind. This is going to be a long answer because it touches on a couple of warts in the Chisel API that are being improved but are certainly relevant in the current version (v3.4.3 as of 12 Aug 2021).
Brian's answer is correct that if you want to name the individual fields you need to use a Seq and not a Vec. The reason for this is that, from Chisel's perspective, an IO of type Vec is a single port with an aggregate type, whereas the Seq is just a sequence of unrelated ports. The Seq is a Scala construct (whereas Vec comes from Chisel), so Chisel itself doesn't know anything about the relationship between the ports in the Seq.
The problem then, is that you need a Vec to do dynamic indexing. You can use VecInit to create a dynamically indexable Wire from your Seq whenever you need to do dynamic indexing:
For example:
class MyModule(names: Seq[String]) extends RawModule {
val enq = names.map(n => IO(Flipped(Decoupled(UInt(8.W)))).suggestName(n))
val idx = IO(Input(UInt(log2Ceil(names.size).W)))
val deq = IO(Decoupled(UInt(8.W)))
// enqWire connects all fields of enq
val enqWire = VecInit(enq)
// Need to make sure backpressure is always driven
enqWire.foreach(_.ready := false.B)
deq <> enqWire(idx)
}
This will work so long as deq is itself a port. It will not work if deq were a Wire because <> is a commutative operator and is thus ambiguous when connecting 2 bidirectional wires. For a longer explanation, see this PR comment.
If deq needs to be a Wire for some reason, you could use a helper module that does have Vecs as ports:
For example:
class InnerHelper(n: Int) extends RawModule {
val enq = IO(Flipped(Vec(n, Decoupled(UInt(8.W)))))
val idx = IO(Input(UInt(log2Ceil(n).W)))
val jdx = IO(Input(UInt(log2Ceil(n).W)))
val deq = IO(Vec(n, Decoupled(UInt(8.W))))
// backpressure defaults
enq.foreach(_.ready := false.B)
deq.foreach { x =>
x.valid := false.B
x.bits := DontCare
}
deq(jdx) <> enq(idx)
}
class MyModule(names: Seq[String]) extends RawModule {
val enq = names.map(n => IO(Flipped(Decoupled(UInt(8.W)))).suggestName(n))
val idx = IO(Input(UInt(log2Ceil(names.size).W)))
val jdx = IO(Input(UInt(log2Ceil(names.size).W)))
val deq = names.map(n => IO(Decoupled(UInt(8.W))).suggestName(s"${n}_out"))
val helper = Module(new InnerHelper(names.size))
helper.enq <> enq
helper.idx := idx
helper.jdx := jdx
helper.deq <> deq
}
It's a bit of a pain, but it at least resolves the ambiguity. There are other utilities we could build--for example, instead of a custom InnerHelper for each case, we could make a utility method that creates a module so that the returned value of dynamically indexing a Seq is a port of a new submodule, but it's a bit tricky.
The good news is that a better way is coming--DataView in Chisel 3.5 should make it possible to view a Seq as a Vec (rather than having to use VecInit which creates a Wire) which makes it easier to avoid this Wire <> connect ambiguity issue. I also hope to either "fix" <> for Wires or perhaps provide a new operator that is not commutative :<>, but that is not yet being worked on.