diff options
| -rw-r--r-- | src/main/scala/chisel3/util/MixedVec.scala | 129 | ||||
| -rw-r--r-- | src/main/scala/chisel3/util/util.scala | 1 | ||||
| -rw-r--r-- | src/test/scala/chiselTests/MixedVecSpec.scala | 253 |
3 files changed, 382 insertions, 1 deletions
diff --git a/src/main/scala/chisel3/util/MixedVec.scala b/src/main/scala/chisel3/util/MixedVec.scala new file mode 100644 index 00000000..cd3a1115 --- /dev/null +++ b/src/main/scala/chisel3/util/MixedVec.scala @@ -0,0 +1,129 @@ +// See LICENSE for license details. + +package chisel3.util + +import chisel3._ +import chisel3.core.{Data, requireIsChiselType, requireIsHardware} +import chisel3.internal.naming.chiselName + +import scala.collection.immutable.ListMap + +object MixedVecWireInit { + /** + * Construct a new wire with the given bound values. + * This is analogous to [[chisel3.core.VecInit]]. + * @param vals Values to create a MixedVec with and assign + * @return MixedVec with given values assigned + */ + def apply[T <: Data](vals: Seq[T]): MixedVec[T] = { + // Create a wire of this type. + val hetVecWire = Wire(MixedVec(vals.map(_.cloneTypeFull))) + // Assign the given vals to this new wire. + for ((a, b) <- hetVecWire.zip(vals)) { + a := b + } + hetVecWire + } +} + +object MixedVec { + /** + * Create a MixedVec from that holds the given types. + * @param eltsIn Element types. Must be Chisel types. + * @return MixedVec with the given types. + */ + def apply[T <: Data](eltsIn: Seq[T]): MixedVec[T] = new MixedVec(eltsIn) + + /** + * Create a MixedVec from the type of the given Vec. + * For example, given a Vec(2, UInt(8.W)), this creates MixedVec(Seq.fill(2){UInt(8.W)}). + * @param vec Vec to use as template + * @return MixedVec analogous to the given Vec. + */ + def apply[T <: Data](vec: Vec[T]): MixedVec[T] = { + MixedVec(Seq.fill(vec.length)(vec.sample_element)) + } +} + +/** + * A hardware array of elements that can hold values of different types/widths, + * unlike Vec which can only hold elements of the same type/width. + * + * @param eltsIn Element types. Must be Chisel types. + * + * @example {{{ + * val v = Wire(MixedVec(Seq(UInt(8.W), UInt(16.W), UInt(32.W)))) + * v(0) := 100.U(8.W) + * v(1) := 10000.U(16.W) + * v(2) := 101.U(32.W) + * }}} + */ +@chiselName +final class MixedVec[T <: Data](private val eltsIn: Seq[T]) extends Record with collection.IndexedSeq[T] { + // We want to create MixedVec only with Chisel types. + if (compileOptions.declaredTypeMustBeUnbound) { + eltsIn.foreach(e => requireIsChiselType(e)) + } + + // Clone the inputs so that we have our own references. + private val elts: IndexedSeq[T] = eltsIn.map(_.cloneTypeFull).toIndexedSeq + + /** + * Statically (elaboration-time) retrieve the element at the given index. + * @param index Index with which to retrieve. + * @return Retrieved index. + */ + def apply(index: Int): T = elts(index) + + /** + * Dynamically (via a mux) retrieve the element at the given index. + * + * @param index Index with which to retrieve. + * @return Retrieved index. + */ + def apply(index: UInt): T = { + requireIsHardware(index, "index must be hardware") + + if (length < 1) { + throw new IndexOutOfBoundsException("Collection is empty") + } + + MuxLookup(index, elts.head, elts.zipWithIndex.map { case (el, el_index) => el_index.U -> el }) + } + + /** Strong bulk connect, assigning elements in this MixedVec from elements in a Seq. + * + * @note the lengths of this and that must match + */ + def :=(that: Seq[T]): Unit = { + require(this.length == that.length) + for ((a, b) <- this zip that) + a := b + } + + /** Strong bulk connect, assigning elements in this MixedVec from elements in a Vec. + * + * @note the lengths of this and that must match + */ + def :=(that: Vec[T]): Unit = { + require(this.length == that.length) + for ((a, b) <- this zip that) + a := b + } + + /** + * Get the length of this MixedVec. + * @return Number of elements in this MixedVec. + */ + def length: Int = elts.length + + override val elements = ListMap(elts.zipWithIndex.map { case (element, index) => (index.toString, element) }: _*) + + // Need to re-clone again since we could have been bound since object creation. + override def cloneType: this.type = MixedVec(elts.map(_.cloneTypeFull)).asInstanceOf[this.type] + + // IndexedSeq has its own hashCode/equals that we must not use + override def hashCode: Int = super[Record].hashCode + + override def equals(that: Any): Boolean = super[Record].equals(that) +} diff --git a/src/main/scala/chisel3/util/util.scala b/src/main/scala/chisel3/util/util.scala index 987678e3..164abdf8 100644 --- a/src/main/scala/chisel3/util/util.scala +++ b/src/main/scala/chisel3/util/util.scala @@ -11,5 +11,4 @@ package object util { type ValidIO[+T <: Data] = chisel3.util.Valid[T] val ValidIO = chisel3.util.Valid val DecoupledIO = chisel3.util.Decoupled - } diff --git a/src/test/scala/chiselTests/MixedVecSpec.scala b/src/test/scala/chiselTests/MixedVecSpec.scala new file mode 100644 index 00000000..ca4fdf20 --- /dev/null +++ b/src/test/scala/chiselTests/MixedVecSpec.scala @@ -0,0 +1,253 @@ +// See LICENSE for license details. + +package chiselTests + +import chisel3._ +import chisel3.core.Binding +import chisel3.testers.BasicTester +import chisel3.util._ +import org.scalacheck.Shrink + +class MixedVecAssignTester(w: Int, values: List[Int]) extends BasicTester { + val v = MixedVecWireInit(values.map(v => v.U(w.W))) + for ((a, b) <- v.zip(values)) { + assert(a === b.asUInt) + } + stop() +} + +class MixedVecRegTester(w: Int, values: List[Int]) extends BasicTester { + val valuesInit = MixedVecWireInit(values.map(v => v.U(w.W))) + val reg = Reg(MixedVec(chiselTypeOf(valuesInit))) + + val doneReg = RegInit(false.B) + doneReg := true.B + + when(!doneReg) { + // First cycle: write to reg + reg := valuesInit + }.otherwise { + // Second cycle: read back from reg + for ((a, b) <- reg.zip(values)) { + assert(a === b.asUInt) + } + stop() + } +} + +class MixedVecIOPassthroughModule[T <: Data](hvec: MixedVec[T]) extends Module { + val io = IO(new Bundle { + val in = Input(hvec) + val out = Output(hvec) + }) + io.out := io.in +} + +class MixedVecIOTester(boundVals: Seq[Data]) extends BasicTester { + val v = MixedVecWireInit(boundVals) + val dut = Module(new MixedVecIOPassthroughModule(MixedVec(chiselTypeOf(v)))) + dut.io.in := v + for ((a, b) <- dut.io.out.zip(boundVals)) { + assert(a.asUInt === b.asUInt) + } + stop() +} + +class MixedVecZeroEntryTester extends BasicTester { + def zeroEntryMixedVec: MixedVec[Data] = MixedVec(Seq.empty) + + require(zeroEntryMixedVec.getWidth == 0) + + val bundleWithZeroEntryVec = new Bundle { + val foo = Bool() + val bar = zeroEntryMixedVec + } + require(0.U.asTypeOf(bundleWithZeroEntryVec).getWidth == 1) + require(bundleWithZeroEntryVec.asUInt.getWidth == 1) + + val m = Module(new Module { + val io = IO(Output(bundleWithZeroEntryVec)) + io.foo := false.B + }) + WireInit(m.io.bar) + + stop() +} + +class MixedVecDynamicIndexTester(n: Int) extends BasicTester { + val wire = Wire(MixedVec(Seq.fill(n) { UInt() })) + + val (cycle, done) = Counter(true.B, n) + + for (i <- 0 until n) { + when(cycle === i.U) { + wire(i) := i.U + } .otherwise { + wire(i) := DontCare + } + } + + assert(wire(cycle) === cycle) + + when (done) { stop() } +} + +class MixedVecFromVecTester extends BasicTester { + val wire = Wire(MixedVec(Vec(3, UInt(8.W)))) + wire := MixedVecWireInit(Seq(20.U, 40.U, 80.U)) + + assert(wire(0) === 20.U) + assert(wire(1) === 40.U) + assert(wire(2) === 80.U) + + stop() +} + +class MixedVecConnectWithVecTester extends BasicTester { + val mixedVecType = MixedVec(Vec(3, UInt(8.W))) + + val m = Module(new MixedVecIOPassthroughModule(mixedVecType)) + m.io.in := VecInit(Seq(20.U, 40.U, 80.U)) + val wire = m.io.out + + assert(wire(0) === 20.U) + assert(wire(1) === 40.U) + assert(wire(2) === 80.U) + + stop() +} + +class MixedVecConnectWithSeqTester extends BasicTester { + val mixedVecType = MixedVec(Vec(3, UInt(8.W))) + + val m = Module(new MixedVecIOPassthroughModule(mixedVecType)) + m.io.in := Seq(20.U, 40.U, 80.U) + val wire = m.io.out + + assert(wire(0) === 20.U) + assert(wire(1) === 40.U) + assert(wire(2) === 80.U) + + stop() +} + +class MixedVecOneBitTester extends BasicTester { + val flag = RegInit(false.B) + + val oneBit = Reg(MixedVec(Seq(UInt(1.W)))) + when (!flag) { + oneBit(0) := 1.U(1.W) + flag := true.B + } .otherwise { + assert(oneBit(0) === 1.U) + assert(oneBit.asUInt === 1.U) + stop() + } +} + +class MixedVecSpec extends ChiselPropSpec { + // Disable shrinking on error. + // Not sure why this needs to be here, but the test behaves very weirdly without it (e.g. empty Lists, etc). + implicit val noShrinkListVal = Shrink[List[Int]](_ => Stream.empty) + implicit val noShrinkInt = Shrink[Int](_ => Stream.empty) + + property("MixedVecs should be assignable") { + forAll(safeUIntN(8)) { case (w: Int, v: List[Int]) => + assertTesterPasses { + new MixedVecAssignTester(w, v) + } + } + } + + property("MixedVecs should be usable as the type for Reg()") { + forAll(safeUIntN(8)) { case (w: Int, v: List[Int]) => + assertTesterPasses { + new MixedVecRegTester(w, v) + } + } + } + + property("MixedVecs should be passed through IO") { + forAll(safeUIntN(8)) { case (w: Int, v: List[Int]) => + assertTesterPasses { + new MixedVecIOTester(v.map(i => i.U(w.W))) + } + } + } + + property("MixedVecs should work with mixed types") { + assertTesterPasses { + new MixedVecIOTester(Seq(true.B, 168.U(8.W), 888.U(10.W), -3.S)) + } + } + + property("MixedVecs should not be able to take hardware types") { + a [Binding.ExpectedChiselTypeException] should be thrownBy { + elaborate(new Module { + val io = IO(new Bundle {}) + val hw = Wire(MixedVec(Seq(UInt(8.W), Bool()))) + val illegal = MixedVec(hw) + }) + } + a [Binding.ExpectedChiselTypeException] should be thrownBy { + elaborate(new Module { + val io = IO(new Bundle {}) + val hw = Reg(MixedVec(Seq(UInt(8.W), Bool()))) + val illegal = MixedVec(hw) + }) + } + a [Binding.ExpectedChiselTypeException] should be thrownBy { + elaborate(new Module { + val io = IO(new Bundle { + val v = Input(MixedVec(Seq(UInt(8.W), Bool()))) + }) + val illegal = MixedVec(io.v) + }) + } + } + + property("MixedVecs with zero entries should compile and have zero width") { + assertTesterPasses { new MixedVecZeroEntryTester } + } + + property("MixedVecs should be dynamically indexable") { + assertTesterPasses{ new MixedVecDynamicIndexTester(4) } + } + + property("MixedVecs should be creatable from Vecs") { + assertTesterPasses{ new MixedVecFromVecTester } + } + + property("It should be possible to bulk connect a MixedVec and a Vec") { + assertTesterPasses{ new MixedVecConnectWithVecTester } + } + + property("It should be possible to bulk connect a MixedVec and a Seq") { + assertTesterPasses{ new MixedVecConnectWithSeqTester } + } + + property("MixedVecs of a single 1 bit element should compile and work") { + assertTesterPasses { new MixedVecOneBitTester } + } + + property("Connecting a MixedVec and something of different size should report a ChiselException") { + an [IllegalArgumentException] should be thrownBy { + elaborate(new Module { + val io = IO(new Bundle { + val out = Output(MixedVec(Seq(UInt(8.W), Bool()))) + }) + val seq = Seq.fill(5)(0.U) + io.out := seq + }) + } + an [IllegalArgumentException] should be thrownBy { + elaborate(new Module { + val io = IO(new Bundle { + val out = Output(MixedVec(Seq(UInt(8.W), Bool()))) + }) + val seq = VecInit(Seq(100.U(8.W))) + io.out := seq + }) + } + } +} |
