From bf6c91f87d701c28dc2d366b3ed19d9c7b3682d5 Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Tue, 19 May 2026 11:44:47 -0700 Subject: [PATCH 1/6] Change FixedIOModule to use Lookupable instead of <: Data First try matching on LookupableImpl AI-assisted-by: Claude Code (Claude Sonnet 4.6) Signed-off-by: Jack Koenig --- .../hierarchy/core/Lookupable.scala | 307 +++++++++++++++--- src/main/scala-3/chisel3/FixedIOModule.scala | 61 ++++ 2 files changed, 329 insertions(+), 39 deletions(-) create mode 100644 src/main/scala-3/chisel3/FixedIOModule.scala diff --git a/core/src/main/scala/chisel3/experimental/hierarchy/core/Lookupable.scala b/core/src/main/scala/chisel3/experimental/hierarchy/core/Lookupable.scala index b5c9feccc63..8ae0f55d554 100644 --- a/core/src/main/scala/chisel3/experimental/hierarchy/core/Lookupable.scala +++ b/core/src/main/scala/chisel3/experimental/hierarchy/core/Lookupable.scala @@ -70,12 +70,21 @@ trait Lookupable[-B] { } // Simplify the implementation of Lookupable by only having 1 virtual method. // Only the Chisel core types need separate implementations for Instance and Definition. -private trait LookupableImpl[-B] extends Lookupable[B] { +// Made package-private (instead of private) so FixedIOBaseModule can pattern-match on it. +// Made invariant (instead of contravariant) to allow `out` to return B. +private[chisel3] trait LookupableImpl[B] extends Lookupable[B] { protected def impl[A](that: A => B, hierarchy: Hierarchy[A]): C override def instanceLookup[A](that: A => B, instance: Instance[A]): C = impl(that, instance) override def definitionLookup[A](that: A => B, definition: Definition[A]): C = impl(that, definition) + + // Extract all leaf Data values from a value of type B. + private[chisel3] def in(b: B): Seq[Data] + + // Reconstruct B from an iterator of IO-bound Data values (consuming elements in order). + // `original` provides the original value of type B, used to reconstruct non-Data parts. + private[chisel3] def out(original: B, iter: Iterator[Data]): B } object Lookupable { @@ -94,6 +103,8 @@ object Lookupable { type C = X type B = X override protected def impl[A](that: A => B, hierarchy: Hierarchy[A]): C = that(hierarchy.proto) + private[chisel3] def in(b: X): Seq[Data] = Seq.empty + private[chisel3] def out(original: X, iter: Iterator[Data]): X = original } /** Factory method for creating Lookupable for user-defined types @@ -103,11 +114,24 @@ object Lookupable { out: T1 => X )( implicit sourceInfo: SourceInfo - ): Simple[X] = new LookupableImpl[X] { - type C = X - override protected def impl[A](that: A => X, hierarchy: Hierarchy[A]): C = { - val t1res = implicitly[Lookupable[T1]].hierarchyLookup[A](a => in(that(a)), hierarchy).asInstanceOf[T1] - out(t1res) + ): Simple[X] = { + val inFn = in + val outFn = out + val t1Look = implicitly[Lookupable[T1]] + new LookupableImpl[X] { + type C = X + override protected def impl[A](that: A => X, hierarchy: Hierarchy[A]): C = { + val t1res = t1Look.hierarchyLookup[A](a => inFn(that(a)), hierarchy).asInstanceOf[T1] + outFn(t1res) + } + private[chisel3] def in(b: X): Seq[Data] = t1Look match { + case li: LookupableImpl[T1] => li.in(inFn(b)) + case _ => Seq.empty + } + private[chisel3] def out(original: X, iter: Iterator[Data]): X = t1Look match { + case li: LookupableImpl[T1] => outFn(li.out(inFn(original), iter)) + case _ => original + } } } @@ -118,12 +142,30 @@ object Lookupable { out: (T1, T2) => X )( implicit sourceInfo: SourceInfo - ): Simple[X] = new LookupableImpl[X] { - type C = X - override protected def impl[A](that: A => X, hierarchy: Hierarchy[A]): C = { - val t1res = implicitly[Lookupable[T1]].hierarchyLookup[A](a => in(that(a))._1, hierarchy).asInstanceOf[T1] - val t2res = implicitly[Lookupable[T2]].hierarchyLookup[A](a => in(that(a))._2, hierarchy).asInstanceOf[T2] - out(t1res, t2res) + ): Simple[X] = { + val inFn = in + val outFn = out + val t1Look = implicitly[Lookupable[T1]] + val t2Look = implicitly[Lookupable[T2]] + new LookupableImpl[X] { + type C = X + override protected def impl[A](that: A => X, hierarchy: Hierarchy[A]): C = { + val t1res = t1Look.hierarchyLookup[A](a => inFn(that(a))._1, hierarchy).asInstanceOf[T1] + val t2res = t2Look.hierarchyLookup[A](a => inFn(that(a))._2, hierarchy).asInstanceOf[T2] + outFn(t1res, t2res) + } + private[chisel3] def in(b: X): Seq[Data] = { + val (v1, v2) = inFn(b) + val d1 = t1Look match { case li: LookupableImpl[T1] => li.in(v1); case _ => Seq.empty } + val d2 = t2Look match { case li: LookupableImpl[T2] => li.in(v2); case _ => Seq.empty } + d1 ++ d2 + } + private[chisel3] def out(original: X, iter: Iterator[Data]): X = { + val (v1, v2) = inFn(original) + val r1 = t1Look match { case li: LookupableImpl[T1] => li.out(v1, iter); case _ => v1 } + val r2 = t2Look match { case li: LookupableImpl[T2] => li.out(v2, iter); case _ => v2 } + outFn(r1, r2) + } } } @@ -134,13 +176,34 @@ object Lookupable { out: (T1, T2, T3) => X )( implicit sourceInfo: SourceInfo - ): Simple[X] = new LookupableImpl[X] { - type C = X - override protected def impl[A](that: A => X, hierarchy: Hierarchy[A]): C = { - val t1res = implicitly[Lookupable[T1]].hierarchyLookup[A](a => in(that(a))._1, hierarchy).asInstanceOf[T1] - val t2res = implicitly[Lookupable[T2]].hierarchyLookup[A](a => in(that(a))._2, hierarchy).asInstanceOf[T2] - val t3res = implicitly[Lookupable[T3]].hierarchyLookup[A](a => in(that(a))._3, hierarchy).asInstanceOf[T3] - out(t1res, t2res, t3res) + ): Simple[X] = { + val inFn = in + val outFn = out + val t1Look = implicitly[Lookupable[T1]] + val t2Look = implicitly[Lookupable[T2]] + val t3Look = implicitly[Lookupable[T3]] + new LookupableImpl[X] { + type C = X + override protected def impl[A](that: A => X, hierarchy: Hierarchy[A]): C = { + val t1res = t1Look.hierarchyLookup[A](a => inFn(that(a))._1, hierarchy).asInstanceOf[T1] + val t2res = t2Look.hierarchyLookup[A](a => inFn(that(a))._2, hierarchy).asInstanceOf[T2] + val t3res = t3Look.hierarchyLookup[A](a => inFn(that(a))._3, hierarchy).asInstanceOf[T3] + outFn(t1res, t2res, t3res) + } + private[chisel3] def in(b: X): Seq[Data] = { + val (v1, v2, v3) = inFn(b) + val d1 = t1Look match { case li: LookupableImpl[T1] => li.in(v1); case _ => Seq.empty } + val d2 = t2Look match { case li: LookupableImpl[T2] => li.in(v2); case _ => Seq.empty } + val d3 = t3Look match { case li: LookupableImpl[T3] => li.in(v3); case _ => Seq.empty } + d1 ++ d2 ++ d3 + } + private[chisel3] def out(original: X, iter: Iterator[Data]): X = { + val (v1, v2, v3) = inFn(original) + val r1 = t1Look match { case li: LookupableImpl[T1] => li.out(v1, iter); case _ => v1 } + val r2 = t2Look match { case li: LookupableImpl[T2] => li.out(v2, iter); case _ => v2 } + val r3 = t3Look match { case li: LookupableImpl[T3] => li.out(v3, iter); case _ => v3 } + outFn(r1, r2, r3) + } } } @@ -151,14 +214,38 @@ object Lookupable { out: (T1, T2, T3, T4) => X )( implicit sourceInfo: SourceInfo - ): Simple[X] = new LookupableImpl[X] { - type C = X - override protected def impl[A](that: A => X, hierarchy: Hierarchy[A]): C = { - val t1res = implicitly[Lookupable[T1]].hierarchyLookup[A](a => in(that(a))._1, hierarchy).asInstanceOf[T1] - val t2res = implicitly[Lookupable[T2]].hierarchyLookup[A](a => in(that(a))._2, hierarchy).asInstanceOf[T2] - val t3res = implicitly[Lookupable[T3]].hierarchyLookup[A](a => in(that(a))._3, hierarchy).asInstanceOf[T3] - val t4res = implicitly[Lookupable[T4]].hierarchyLookup[A](a => in(that(a))._4, hierarchy).asInstanceOf[T4] - out(t1res, t2res, t3res, t4res) + ): Simple[X] = { + val inFn = in + val outFn = out + val t1Look = implicitly[Lookupable[T1]] + val t2Look = implicitly[Lookupable[T2]] + val t3Look = implicitly[Lookupable[T3]] + val t4Look = implicitly[Lookupable[T4]] + new LookupableImpl[X] { + type C = X + override protected def impl[A](that: A => X, hierarchy: Hierarchy[A]): C = { + val t1res = t1Look.hierarchyLookup[A](a => inFn(that(a))._1, hierarchy).asInstanceOf[T1] + val t2res = t2Look.hierarchyLookup[A](a => inFn(that(a))._2, hierarchy).asInstanceOf[T2] + val t3res = t3Look.hierarchyLookup[A](a => inFn(that(a))._3, hierarchy).asInstanceOf[T3] + val t4res = t4Look.hierarchyLookup[A](a => inFn(that(a))._4, hierarchy).asInstanceOf[T4] + outFn(t1res, t2res, t3res, t4res) + } + private[chisel3] def in(b: X): Seq[Data] = { + val (v1, v2, v3, v4) = inFn(b) + val d1 = t1Look match { case li: LookupableImpl[T1] => li.in(v1); case _ => Seq.empty } + val d2 = t2Look match { case li: LookupableImpl[T2] => li.in(v2); case _ => Seq.empty } + val d3 = t3Look match { case li: LookupableImpl[T3] => li.in(v3); case _ => Seq.empty } + val d4 = t4Look match { case li: LookupableImpl[T4] => li.in(v4); case _ => Seq.empty } + d1 ++ d2 ++ d3 ++ d4 + } + private[chisel3] def out(original: X, iter: Iterator[Data]): X = { + val (v1, v2, v3, v4) = inFn(original) + val r1 = t1Look match { case li: LookupableImpl[T1] => li.out(v1, iter); case _ => v1 } + val r2 = t2Look match { case li: LookupableImpl[T2] => li.out(v2, iter); case _ => v2 } + val r3 = t3Look match { case li: LookupableImpl[T3] => li.out(v3, iter); case _ => v3 } + val r4 = t4Look match { case li: LookupableImpl[T4] => li.out(v4, iter); case _ => v4 } + outFn(r1, r2, r3, r4) + } } } @@ -169,15 +256,42 @@ object Lookupable { out: (T1, T2, T3, T4, T5) => X )( implicit sourceInfo: SourceInfo - ): Simple[X] = new LookupableImpl[X] { - type C = X - override protected def impl[A](that: A => X, hierarchy: Hierarchy[A]): C = { - val t1res = implicitly[Lookupable[T1]].hierarchyLookup[A](a => in(that(a))._1, hierarchy).asInstanceOf[T1] - val t2res = implicitly[Lookupable[T2]].hierarchyLookup[A](a => in(that(a))._2, hierarchy).asInstanceOf[T2] - val t3res = implicitly[Lookupable[T3]].hierarchyLookup[A](a => in(that(a))._3, hierarchy).asInstanceOf[T3] - val t4res = implicitly[Lookupable[T4]].hierarchyLookup[A](a => in(that(a))._4, hierarchy).asInstanceOf[T4] - val t5res = implicitly[Lookupable[T5]].hierarchyLookup[A](a => in(that(a))._5, hierarchy).asInstanceOf[T5] - out(t1res, t2res, t3res, t4res, t5res) + ): Simple[X] = { + val inFn = in + val outFn = out + val t1Look = implicitly[Lookupable[T1]] + val t2Look = implicitly[Lookupable[T2]] + val t3Look = implicitly[Lookupable[T3]] + val t4Look = implicitly[Lookupable[T4]] + val t5Look = implicitly[Lookupable[T5]] + new LookupableImpl[X] { + type C = X + override protected def impl[A](that: A => X, hierarchy: Hierarchy[A]): C = { + val t1res = t1Look.hierarchyLookup[A](a => inFn(that(a))._1, hierarchy).asInstanceOf[T1] + val t2res = t2Look.hierarchyLookup[A](a => inFn(that(a))._2, hierarchy).asInstanceOf[T2] + val t3res = t3Look.hierarchyLookup[A](a => inFn(that(a))._3, hierarchy).asInstanceOf[T3] + val t4res = t4Look.hierarchyLookup[A](a => inFn(that(a))._4, hierarchy).asInstanceOf[T4] + val t5res = t5Look.hierarchyLookup[A](a => inFn(that(a))._5, hierarchy).asInstanceOf[T5] + outFn(t1res, t2res, t3res, t4res, t5res) + } + private[chisel3] def in(b: X): Seq[Data] = { + val (v1, v2, v3, v4, v5) = inFn(b) + val d1 = t1Look match { case li: LookupableImpl[T1] => li.in(v1); case _ => Seq.empty } + val d2 = t2Look match { case li: LookupableImpl[T2] => li.in(v2); case _ => Seq.empty } + val d3 = t3Look match { case li: LookupableImpl[T3] => li.in(v3); case _ => Seq.empty } + val d4 = t4Look match { case li: LookupableImpl[T4] => li.in(v4); case _ => Seq.empty } + val d5 = t5Look match { case li: LookupableImpl[T5] => li.in(v5); case _ => Seq.empty } + d1 ++ d2 ++ d3 ++ d4 ++ d5 + } + private[chisel3] def out(original: X, iter: Iterator[Data]): X = { + val (v1, v2, v3, v4, v5) = inFn(original) + val r1 = t1Look match { case li: LookupableImpl[T1] => li.out(v1, iter); case _ => v1 } + val r2 = t2Look match { case li: LookupableImpl[T2] => li.out(v2, iter); case _ => v2 } + val r3 = t3Look match { case li: LookupableImpl[T3] => li.out(v3, iter); case _ => v3 } + val r4 = t4Look match { case li: LookupableImpl[T4] => li.out(v4, iter); case _ => v4 } + val r5 = t5Look match { case li: LookupableImpl[T5] => li.out(v5, iter); case _ => v5 } + outFn(r1, r2, r3, r4, r5) + } } } @@ -445,9 +559,17 @@ object Lookupable { } implicit def lookupData[B <: Data](implicit sourceInfo: SourceInfo): Simple[B] = - new Lookupable[B] { + new LookupableImpl[B] { type C = B - def definitionLookup[A](that: A => B, definition: Definition[A]): C = { + + private[chisel3] def in(b: B): Seq[Data] = Seq(b) + private[chisel3] def out(original: B, iter: Iterator[Data]): B = iter.next().asInstanceOf[B] + + // impl is never called because instanceLookup and definitionLookup are both overridden below + protected def impl[A](that: A => B, hierarchy: Hierarchy[A]): C = + throw new InternalErrorException("lookupData.impl should never be called directly") + + override def definitionLookup[A](that: A => B, definition: Definition[A]): C = { val ret = that(definition.proto) if (isView(ret)) { ??? // TODO!!!!!! cloneViewToContext(ret, instance, ioMap, instance.getInnerDataContext) @@ -455,7 +577,7 @@ object Lookupable { doLookupData(ret, definition.cache, None, definition.getInnerDataContext) } } - def instanceLookup[A](that: A => B, instance: Instance[A]): C = { + override def instanceLookup[A](that: A => B, instance: Instance[A]): C = { val ret = that(instance.proto) // As Property ports are not yet Lookupable, they are skipped here. @@ -517,6 +639,8 @@ object Lookupable { override protected def impl[A](that: A => B, hierarchy: Hierarchy[A]): C = { cloneMemToContext(that(hierarchy.proto), hierarchy.getInnerDataContext.get) } + private[chisel3] def in(b: B): Seq[Data] = Seq.empty + private[chisel3] def out(original: B, iter: Iterator[Data]): B = original } // TODO, this, cloneMemToContext, and cloneDataToContext should be unified @@ -554,6 +678,8 @@ object Lookupable { override protected def impl[A](that: A => HasTarget, hierarchy: Hierarchy[A]): C = { cloneHasTargetToContext(that(hierarchy.proto), hierarchy.getInnerDataContext.get) } + private[chisel3] def in(b: HasTarget): Seq[Data] = Seq.empty + private[chisel3] def out(original: HasTarget, iter: Iterator[Data]): HasTarget = original } import scala.language.higherKinds // Required to avoid warning for lookupIterable type parameter @@ -566,6 +692,20 @@ object Lookupable { val ret = that(hierarchy.proto).asInstanceOf[Iterable[B]] ret.map { (x: B) => lookupable.hierarchyLookup[A](_ => x, hierarchy) }.asInstanceOf[C] } + private[chisel3] def in(fb: F[B]): Seq[Data] = + fb.asInstanceOf[Iterable[B]].flatMap { b => + lookupable match { + case li: LookupableImpl[B] => li.in(b) + case _ => Seq.empty + } + }.toSeq + private[chisel3] def out(original: F[B], iter: Iterator[Data]): F[B] = + original.asInstanceOf[Iterable[B]].map { b => + lookupable match { + case li: LookupableImpl[B] => li.out(b, iter) + case _ => b + } + }.asInstanceOf[F[B]] } implicit def lookupOption[B]( implicit sourceInfo: SourceInfo, @@ -576,6 +716,20 @@ object Lookupable { val ret = that(hierarchy.proto) ret.map { (x: B) => lookupable.hierarchyLookup[A](_ => x, hierarchy) } } + private[chisel3] def in(o: Option[B]): Seq[Data] = + o.toSeq.flatMap { b => + lookupable match { + case li: LookupableImpl[B] => li.in(b) + case _ => Seq.empty + } + } + private[chisel3] def out(original: Option[B], iter: Iterator[Data]): Option[B] = + original.map { b => + lookupable match { + case li: LookupableImpl[B] => li.out(b, iter) + case _ => b + } + } } implicit def lookupEither[L, R]( implicit sourceInfo: SourceInfo, @@ -589,6 +743,18 @@ object Lookupable { lookupableL.hierarchyLookup[A](_ => x, hierarchy) } } + private[chisel3] def in(e: Either[L, R]): Seq[Data] = e match { + case Left(l) => + lookupableL match { case li: LookupableImpl[L] => li.in(l); case _ => Seq.empty } + case Right(r) => + lookupableR match { case li: LookupableImpl[R] => li.in(r); case _ => Seq.empty } + } + private[chisel3] def out(original: Either[L, R], iter: Iterator[Data]): Either[L, R] = original match { + case Left(l) => + Left(lookupableL match { case li: LookupableImpl[L] => li.out(l, iter); case _ => l }) + case Right(r) => + Right(lookupableR match { case li: LookupableImpl[R] => li.out(r, iter); case _ => r }) + } } // TODO Once Lookupable return type change is removed, we can just call product factory above. @@ -603,6 +769,16 @@ object Lookupable { val t2res = lookupableT2.hierarchyLookup[A](that(_)._2, hierarchy) (t1res, t2res) } + private[chisel3] def in(t: (T1, T2)): Seq[Data] = { + val d1 = lookupableT1 match { case li: LookupableImpl[T1] => li.in(t._1); case _ => Seq.empty } + val d2 = lookupableT2 match { case li: LookupableImpl[T2] => li.in(t._2); case _ => Seq.empty } + d1 ++ d2 + } + private[chisel3] def out(original: (T1, T2), iter: Iterator[Data]): (T1, T2) = { + val r1 = lookupableT1 match { case li: LookupableImpl[T1] => li.out(original._1, iter); case _ => original._1 } + val r2 = lookupableT2 match { case li: LookupableImpl[T2] => li.out(original._2, iter); case _ => original._2 } + (r1, r2) + } } // TODO Once Lookupable return type change is removed, we can just call product factory above. @@ -619,6 +795,18 @@ object Lookupable { val t3res = lookupableT3.hierarchyLookup[A](that(_)._3, hierarchy) (t1res, t2res, t3res) } + private[chisel3] def in(t: (T1, T2, T3)): Seq[Data] = { + val d1 = lookupableT1 match { case li: LookupableImpl[T1] => li.in(t._1); case _ => Seq.empty } + val d2 = lookupableT2 match { case li: LookupableImpl[T2] => li.in(t._2); case _ => Seq.empty } + val d3 = lookupableT3 match { case li: LookupableImpl[T3] => li.in(t._3); case _ => Seq.empty } + d1 ++ d2 ++ d3 + } + private[chisel3] def out(original: (T1, T2, T3), iter: Iterator[Data]): (T1, T2, T3) = { + val r1 = lookupableT1 match { case li: LookupableImpl[T1] => li.out(original._1, iter); case _ => original._1 } + val r2 = lookupableT2 match { case li: LookupableImpl[T2] => li.out(original._2, iter); case _ => original._2 } + val r3 = lookupableT3 match { case li: LookupableImpl[T3] => li.out(original._3, iter); case _ => original._3 } + (r1, r2, r3) + } } // TODO Once Lookupable return type change is removed, we can just call product factory above. @@ -638,6 +826,24 @@ object Lookupable { val t4res = lookupableT4.hierarchyLookup[A](that(_)._4, hierarchy) (t1res, t2res, t3res, t4res) } + private[chisel3] def in(t: (T1, T2, T3, T4)): Seq[Data] = { + val d1 = lookupableT1 match { case li: LookupableImpl[T1] => li.in(t._1); case _ => Seq.empty } + val d2 = lookupableT2 match { case li: LookupableImpl[T2] => li.in(t._2); case _ => Seq.empty } + val d3 = lookupableT3 match { case li: LookupableImpl[T3] => li.in(t._3); case _ => Seq.empty } + val d4 = lookupableT4 match { case li: LookupableImpl[T4] => li.in(t._4); case _ => Seq.empty } + d1 ++ d2 ++ d3 ++ d4 + } + private[chisel3] def out(original: (T1, T2, T3, T4), iter: Iterator[Data]): (T1, T2, T3, T4) = { + val r1 = + lookupableT1 match { case li: LookupableImpl[T1] => li.out(original._1, iter); case _ => original._1 } + val r2 = + lookupableT2 match { case li: LookupableImpl[T2] => li.out(original._2, iter); case _ => original._2 } + val r3 = + lookupableT3 match { case li: LookupableImpl[T3] => li.out(original._3, iter); case _ => original._3 } + val r4 = + lookupableT4 match { case li: LookupableImpl[T4] => li.out(original._4, iter); case _ => original._4 } + (r1, r2, r3, r4) + } } // TODO Once Lookupable return type change is removed, we can just call product factory above. @@ -659,6 +865,27 @@ object Lookupable { val t5res = lookupableT5.hierarchyLookup[A](that(_)._5, hierarchy) (t1res, t2res, t3res, t4res, t5res) } + private[chisel3] def in(t: (T1, T2, T3, T4, T5)): Seq[Data] = { + val d1 = lookupableT1 match { case li: LookupableImpl[T1] => li.in(t._1); case _ => Seq.empty } + val d2 = lookupableT2 match { case li: LookupableImpl[T2] => li.in(t._2); case _ => Seq.empty } + val d3 = lookupableT3 match { case li: LookupableImpl[T3] => li.in(t._3); case _ => Seq.empty } + val d4 = lookupableT4 match { case li: LookupableImpl[T4] => li.in(t._4); case _ => Seq.empty } + val d5 = lookupableT5 match { case li: LookupableImpl[T5] => li.in(t._5); case _ => Seq.empty } + d1 ++ d2 ++ d3 ++ d4 ++ d5 + } + private[chisel3] def out(original: (T1, T2, T3, T4, T5), iter: Iterator[Data]): (T1, T2, T3, T4, T5) = { + val r1 = + lookupableT1 match { case li: LookupableImpl[T1] => li.out(original._1, iter); case _ => original._1 } + val r2 = + lookupableT2 match { case li: LookupableImpl[T2] => li.out(original._2, iter); case _ => original._2 } + val r3 = + lookupableT3 match { case li: LookupableImpl[T3] => li.out(original._3, iter); case _ => original._3 } + val r4 = + lookupableT4 match { case li: LookupableImpl[T4] => li.out(original._4, iter); case _ => original._4 } + val r5 = + lookupableT5 match { case li: LookupableImpl[T5] => li.out(original._5, iter); case _ => original._5 } + (r1, r2, r3, r4, r5) + } } @deprecated( @@ -677,6 +904,8 @@ object Lookupable { } new Instance(Clone(underlying)) } + private[chisel3] def in(b: B): Seq[Data] = Seq.empty + private[chisel3] def out(original: B, iter: Iterator[Data]): B = original } implicit def lookupIsLookupable[B <: IsLookupable](implicit sourceInfo: SourceInfo): Simple[B] = isLookupable[B] diff --git a/src/main/scala-3/chisel3/FixedIOModule.scala b/src/main/scala-3/chisel3/FixedIOModule.scala new file mode 100644 index 00000000000..224fe2ec62f --- /dev/null +++ b/src/main/scala-3/chisel3/FixedIOModule.scala @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chisel3 + +import chisel3.experimental.{BaseModule, ExtModule, Param} +import chisel3.experimental.hierarchy.core.{Lookupable, LookupableImpl} + +/** A module or external module whose IO is generated from a specific generator. + * This module may have no additional IO created other than what is specified + * by its `ioGenerator` abstract member. + */ +sealed trait FixedIOBaseModule[A](using lookupable: Lookupable[A]) extends BaseModule { + + /** A generator of IO */ + protected def ioGenerator: A + + final val io: A = (lookupable: @unchecked) match { + case impl: LookupableImpl[A] => + val dataElems = impl.in(ioGenerator) + val names = LazyList.from(0).map(i => ('a' + i).toChar.toString) + val ports = dataElems.zip(names).map { case (d, name) => + val p = IO(d) + p.suggestName(name) + p + } + impl.out(ioGenerator, ports.iterator) + case _ => + throw new Exception( + "FixedIOBaseModule requires a LookupableImpl-based Lookupable. " + + "Custom Lookupable extensions (deprecated since Chisel 7.0.0) are not supported." + ) + } + endIOCreation() + +} + +/** A Chisel module whose IO is determined by an IO generator. This module + * cannot have additional IO created by modules that extend it. + * + * @param ioGenerator + */ +class FixedIORawModule[A](final val ioGenerator: A)(using Lookupable[A]) extends RawModule with FixedIOBaseModule[A] + +/** A Chisel module whose IO (in addition to [[clock]] and [[reset]]) is determined + * by an IO generator. This module cannot have additional IO created by modules that + * extend it. + * + * @param ioGenerator + */ +class FixedIOModule[A](final val ioGenerator: A)(using Lookupable[A]) extends Module with FixedIOBaseModule[A] + +/** A Chisel blackbox whose IO is determined by an IO generator. This module + * cannot have additional IO created by modules that extend it. + * + * @param ioGenerator + * @param params + */ +class FixedIOExtModule[A](final val ioGenerator: A, params: Map[String, Param] = Map.empty[String, Param])( + using Lookupable[A] +) extends ExtModule(params) + with FixedIOBaseModule[A] From 46f3c1d1a474b533ee4fbcb104b89e3a17624108 Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Tue, 19 May 2026 11:57:47 -0700 Subject: [PATCH 2/6] Use private virtual methods instead of matching AI-assisted-by: Claude Code (Claude Sonnet 4.6) Signed-off-by: Jack Koenig --- .../hierarchy/core/Lookupable.scala | 275 +++++++----------- src/main/scala-3/chisel3/FixedIOModule.scala | 26 +- 2 files changed, 116 insertions(+), 185 deletions(-) diff --git a/core/src/main/scala/chisel3/experimental/hierarchy/core/Lookupable.scala b/core/src/main/scala/chisel3/experimental/hierarchy/core/Lookupable.scala index 8ae0f55d554..0726f7f21a7 100644 --- a/core/src/main/scala/chisel3/experimental/hierarchy/core/Lookupable.scala +++ b/core/src/main/scala/chisel3/experimental/hierarchy/core/Lookupable.scala @@ -6,6 +6,7 @@ import chisel3.experimental.{BaseModule, SourceInfo} import chisel3.experimental.hierarchy.{InstanceClone, InstantiableClone, ModuleClone} import scala.annotation.implicitNotFound +import scala.annotation.unchecked.uncheckedVariance import scala.collection.mutable.HashMap import chisel3._ import chisel3.experimental.dataview.{isView, reify, reifyIdentityView} @@ -59,6 +60,18 @@ trait Lookupable[-B] { protected def getProto[A](h: Hierarchy[A]): A = h.proto protected def getUnderlying[A](h: Hierarchy[A]): Underlying[A] = h.underlying + // Extract all leaf Data values from a value of type B for use by FixedIOBaseModule. + // Default returns empty; LookupableImpl subclasses override with the real extraction. + private[chisel3] def in(b: B): Seq[Data] = Seq.empty + + // Reconstruct B by consuming IO-bound Data from the iterator, using `original` for non-Data parts. + // Default passes `original` through unchanged. + // Uses @uncheckedVariance to permit B in covariant return position within contravariant Lookupable[-B]. + private[chisel3] def out( + original: B @uncheckedVariance, + iter: Iterator[Data] + ): B @uncheckedVariance = original + // Single method that may eventually replace instanceLookup and definitionLookup. private[chisel3] def hierarchyLookup[A](that: A => B, hierarchy: Hierarchy[A]): C = { hierarchy match { @@ -70,8 +83,8 @@ trait Lookupable[-B] { } // Simplify the implementation of Lookupable by only having 1 virtual method. // Only the Chisel core types need separate implementations for Instance and Definition. -// Made package-private (instead of private) so FixedIOBaseModule can pattern-match on it. -// Made invariant (instead of contravariant) to allow `out` to return B. +// Made package-private (instead of private) and invariant (instead of contravariant [-B]) so +// subclasses can implement the typed `out` method returning B. private[chisel3] trait LookupableImpl[B] extends Lookupable[B] { protected def impl[A](that: A => B, hierarchy: Hierarchy[A]): C @@ -79,12 +92,9 @@ private[chisel3] trait LookupableImpl[B] extends Lookupable[B] { override def instanceLookup[A](that: A => B, instance: Instance[A]): C = impl(that, instance) override def definitionLookup[A](that: A => B, definition: Definition[A]): C = impl(that, definition) - // Extract all leaf Data values from a value of type B. - private[chisel3] def in(b: B): Seq[Data] - - // Reconstruct B from an iterator of IO-bound Data values (consuming elements in order). - // `original` provides the original value of type B, used to reconstruct non-Data parts. - private[chisel3] def out(original: B, iter: Iterator[Data]): B + // Re-abstract so every LookupableImpl subclass must provide concrete implementations. + override private[chisel3] def in(b: B): Seq[Data] + override private[chisel3] def out(original: B, iter: Iterator[Data]): B } object Lookupable { @@ -103,8 +113,8 @@ object Lookupable { type C = X type B = X override protected def impl[A](that: A => B, hierarchy: Hierarchy[A]): C = that(hierarchy.proto) - private[chisel3] def in(b: X): Seq[Data] = Seq.empty - private[chisel3] def out(original: X, iter: Iterator[Data]): X = original + override private[chisel3] def in(b: X): Seq[Data] = Seq.empty + override private[chisel3] def out(original: X, iter: Iterator[Data]): X = original } /** Factory method for creating Lookupable for user-defined types @@ -124,14 +134,9 @@ object Lookupable { val t1res = t1Look.hierarchyLookup[A](a => inFn(that(a)), hierarchy).asInstanceOf[T1] outFn(t1res) } - private[chisel3] def in(b: X): Seq[Data] = t1Look match { - case li: LookupableImpl[T1] => li.in(inFn(b)) - case _ => Seq.empty - } - private[chisel3] def out(original: X, iter: Iterator[Data]): X = t1Look match { - case li: LookupableImpl[T1] => outFn(li.out(inFn(original), iter)) - case _ => original - } + override private[chisel3] def in(b: X): Seq[Data] = t1Look.in(inFn(b)) + override private[chisel3] def out(original: X, iter: Iterator[Data]): X = + outFn(t1Look.out(inFn(original), iter).asInstanceOf[T1]) } } @@ -154,17 +159,13 @@ object Lookupable { val t2res = t2Look.hierarchyLookup[A](a => inFn(that(a))._2, hierarchy).asInstanceOf[T2] outFn(t1res, t2res) } - private[chisel3] def in(b: X): Seq[Data] = { + override private[chisel3] def in(b: X): Seq[Data] = { val (v1, v2) = inFn(b) - val d1 = t1Look match { case li: LookupableImpl[T1] => li.in(v1); case _ => Seq.empty } - val d2 = t2Look match { case li: LookupableImpl[T2] => li.in(v2); case _ => Seq.empty } - d1 ++ d2 + t1Look.in(v1) ++ t2Look.in(v2) } - private[chisel3] def out(original: X, iter: Iterator[Data]): X = { + override private[chisel3] def out(original: X, iter: Iterator[Data]): X = { val (v1, v2) = inFn(original) - val r1 = t1Look match { case li: LookupableImpl[T1] => li.out(v1, iter); case _ => v1 } - val r2 = t2Look match { case li: LookupableImpl[T2] => li.out(v2, iter); case _ => v2 } - outFn(r1, r2) + outFn(t1Look.out(v1, iter).asInstanceOf[T1], t2Look.out(v2, iter).asInstanceOf[T2]) } } } @@ -190,19 +191,17 @@ object Lookupable { val t3res = t3Look.hierarchyLookup[A](a => inFn(that(a))._3, hierarchy).asInstanceOf[T3] outFn(t1res, t2res, t3res) } - private[chisel3] def in(b: X): Seq[Data] = { + override private[chisel3] def in(b: X): Seq[Data] = { val (v1, v2, v3) = inFn(b) - val d1 = t1Look match { case li: LookupableImpl[T1] => li.in(v1); case _ => Seq.empty } - val d2 = t2Look match { case li: LookupableImpl[T2] => li.in(v2); case _ => Seq.empty } - val d3 = t3Look match { case li: LookupableImpl[T3] => li.in(v3); case _ => Seq.empty } - d1 ++ d2 ++ d3 + t1Look.in(v1) ++ t2Look.in(v2) ++ t3Look.in(v3) } - private[chisel3] def out(original: X, iter: Iterator[Data]): X = { + override private[chisel3] def out(original: X, iter: Iterator[Data]): X = { val (v1, v2, v3) = inFn(original) - val r1 = t1Look match { case li: LookupableImpl[T1] => li.out(v1, iter); case _ => v1 } - val r2 = t2Look match { case li: LookupableImpl[T2] => li.out(v2, iter); case _ => v2 } - val r3 = t3Look match { case li: LookupableImpl[T3] => li.out(v3, iter); case _ => v3 } - outFn(r1, r2, r3) + outFn( + t1Look.out(v1, iter).asInstanceOf[T1], + t2Look.out(v2, iter).asInstanceOf[T2], + t3Look.out(v3, iter).asInstanceOf[T3] + ) } } } @@ -230,21 +229,18 @@ object Lookupable { val t4res = t4Look.hierarchyLookup[A](a => inFn(that(a))._4, hierarchy).asInstanceOf[T4] outFn(t1res, t2res, t3res, t4res) } - private[chisel3] def in(b: X): Seq[Data] = { + override private[chisel3] def in(b: X): Seq[Data] = { val (v1, v2, v3, v4) = inFn(b) - val d1 = t1Look match { case li: LookupableImpl[T1] => li.in(v1); case _ => Seq.empty } - val d2 = t2Look match { case li: LookupableImpl[T2] => li.in(v2); case _ => Seq.empty } - val d3 = t3Look match { case li: LookupableImpl[T3] => li.in(v3); case _ => Seq.empty } - val d4 = t4Look match { case li: LookupableImpl[T4] => li.in(v4); case _ => Seq.empty } - d1 ++ d2 ++ d3 ++ d4 + t1Look.in(v1) ++ t2Look.in(v2) ++ t3Look.in(v3) ++ t4Look.in(v4) } - private[chisel3] def out(original: X, iter: Iterator[Data]): X = { + override private[chisel3] def out(original: X, iter: Iterator[Data]): X = { val (v1, v2, v3, v4) = inFn(original) - val r1 = t1Look match { case li: LookupableImpl[T1] => li.out(v1, iter); case _ => v1 } - val r2 = t2Look match { case li: LookupableImpl[T2] => li.out(v2, iter); case _ => v2 } - val r3 = t3Look match { case li: LookupableImpl[T3] => li.out(v3, iter); case _ => v3 } - val r4 = t4Look match { case li: LookupableImpl[T4] => li.out(v4, iter); case _ => v4 } - outFn(r1, r2, r3, r4) + outFn( + t1Look.out(v1, iter).asInstanceOf[T1], + t2Look.out(v2, iter).asInstanceOf[T2], + t3Look.out(v3, iter).asInstanceOf[T3], + t4Look.out(v4, iter).asInstanceOf[T4] + ) } } } @@ -274,23 +270,19 @@ object Lookupable { val t5res = t5Look.hierarchyLookup[A](a => inFn(that(a))._5, hierarchy).asInstanceOf[T5] outFn(t1res, t2res, t3res, t4res, t5res) } - private[chisel3] def in(b: X): Seq[Data] = { + override private[chisel3] def in(b: X): Seq[Data] = { val (v1, v2, v3, v4, v5) = inFn(b) - val d1 = t1Look match { case li: LookupableImpl[T1] => li.in(v1); case _ => Seq.empty } - val d2 = t2Look match { case li: LookupableImpl[T2] => li.in(v2); case _ => Seq.empty } - val d3 = t3Look match { case li: LookupableImpl[T3] => li.in(v3); case _ => Seq.empty } - val d4 = t4Look match { case li: LookupableImpl[T4] => li.in(v4); case _ => Seq.empty } - val d5 = t5Look match { case li: LookupableImpl[T5] => li.in(v5); case _ => Seq.empty } - d1 ++ d2 ++ d3 ++ d4 ++ d5 + t1Look.in(v1) ++ t2Look.in(v2) ++ t3Look.in(v3) ++ t4Look.in(v4) ++ t5Look.in(v5) } - private[chisel3] def out(original: X, iter: Iterator[Data]): X = { + override private[chisel3] def out(original: X, iter: Iterator[Data]): X = { val (v1, v2, v3, v4, v5) = inFn(original) - val r1 = t1Look match { case li: LookupableImpl[T1] => li.out(v1, iter); case _ => v1 } - val r2 = t2Look match { case li: LookupableImpl[T2] => li.out(v2, iter); case _ => v2 } - val r3 = t3Look match { case li: LookupableImpl[T3] => li.out(v3, iter); case _ => v3 } - val r4 = t4Look match { case li: LookupableImpl[T4] => li.out(v4, iter); case _ => v4 } - val r5 = t5Look match { case li: LookupableImpl[T5] => li.out(v5, iter); case _ => v5 } - outFn(r1, r2, r3, r4, r5) + outFn( + t1Look.out(v1, iter).asInstanceOf[T1], + t2Look.out(v2, iter).asInstanceOf[T2], + t3Look.out(v3, iter).asInstanceOf[T3], + t4Look.out(v4, iter).asInstanceOf[T4], + t5Look.out(v5, iter).asInstanceOf[T5] + ) } } } @@ -562,8 +554,8 @@ object Lookupable { new LookupableImpl[B] { type C = B - private[chisel3] def in(b: B): Seq[Data] = Seq(b) - private[chisel3] def out(original: B, iter: Iterator[Data]): B = iter.next().asInstanceOf[B] + override private[chisel3] def in(b: B): Seq[Data] = Seq(b) + override private[chisel3] def out(original: B, iter: Iterator[Data]): B = iter.next().asInstanceOf[B] // impl is never called because instanceLookup and definitionLookup are both overridden below protected def impl[A](that: A => B, hierarchy: Hierarchy[A]): C = @@ -639,8 +631,8 @@ object Lookupable { override protected def impl[A](that: A => B, hierarchy: Hierarchy[A]): C = { cloneMemToContext(that(hierarchy.proto), hierarchy.getInnerDataContext.get) } - private[chisel3] def in(b: B): Seq[Data] = Seq.empty - private[chisel3] def out(original: B, iter: Iterator[Data]): B = original + override private[chisel3] def in(b: B): Seq[Data] = Seq.empty + override private[chisel3] def out(original: B, iter: Iterator[Data]): B = original } // TODO, this, cloneMemToContext, and cloneDataToContext should be unified @@ -678,8 +670,8 @@ object Lookupable { override protected def impl[A](that: A => HasTarget, hierarchy: Hierarchy[A]): C = { cloneHasTargetToContext(that(hierarchy.proto), hierarchy.getInnerDataContext.get) } - private[chisel3] def in(b: HasTarget): Seq[Data] = Seq.empty - private[chisel3] def out(original: HasTarget, iter: Iterator[Data]): HasTarget = original + override private[chisel3] def in(b: HasTarget): Seq[Data] = Seq.empty + override private[chisel3] def out(original: HasTarget, iter: Iterator[Data]): HasTarget = original } import scala.language.higherKinds // Required to avoid warning for lookupIterable type parameter @@ -692,20 +684,10 @@ object Lookupable { val ret = that(hierarchy.proto).asInstanceOf[Iterable[B]] ret.map { (x: B) => lookupable.hierarchyLookup[A](_ => x, hierarchy) }.asInstanceOf[C] } - private[chisel3] def in(fb: F[B]): Seq[Data] = - fb.asInstanceOf[Iterable[B]].flatMap { b => - lookupable match { - case li: LookupableImpl[B] => li.in(b) - case _ => Seq.empty - } - }.toSeq - private[chisel3] def out(original: F[B], iter: Iterator[Data]): F[B] = - original.asInstanceOf[Iterable[B]].map { b => - lookupable match { - case li: LookupableImpl[B] => li.out(b, iter) - case _ => b - } - }.asInstanceOf[F[B]] + override private[chisel3] def in(fb: F[B]): Seq[Data] = + fb.asInstanceOf[Iterable[B]].flatMap(b => lookupable.in(b)).toSeq + override private[chisel3] def out(original: F[B], iter: Iterator[Data]): F[B] = + original.asInstanceOf[Iterable[B]].map(b => lookupable.out(b, iter).asInstanceOf[B]).asInstanceOf[F[B]] } implicit def lookupOption[B]( implicit sourceInfo: SourceInfo, @@ -716,20 +698,10 @@ object Lookupable { val ret = that(hierarchy.proto) ret.map { (x: B) => lookupable.hierarchyLookup[A](_ => x, hierarchy) } } - private[chisel3] def in(o: Option[B]): Seq[Data] = - o.toSeq.flatMap { b => - lookupable match { - case li: LookupableImpl[B] => li.in(b) - case _ => Seq.empty - } - } - private[chisel3] def out(original: Option[B], iter: Iterator[Data]): Option[B] = - original.map { b => - lookupable match { - case li: LookupableImpl[B] => li.out(b, iter) - case _ => b - } - } + override private[chisel3] def in(o: Option[B]): Seq[Data] = + o.toSeq.flatMap(b => lookupable.in(b)) + override private[chisel3] def out(original: Option[B], iter: Iterator[Data]): Option[B] = + original.map(b => lookupable.out(b, iter).asInstanceOf[B]) } implicit def lookupEither[L, R]( implicit sourceInfo: SourceInfo, @@ -743,17 +715,13 @@ object Lookupable { lookupableL.hierarchyLookup[A](_ => x, hierarchy) } } - private[chisel3] def in(e: Either[L, R]): Seq[Data] = e match { - case Left(l) => - lookupableL match { case li: LookupableImpl[L] => li.in(l); case _ => Seq.empty } - case Right(r) => - lookupableR match { case li: LookupableImpl[R] => li.in(r); case _ => Seq.empty } + override private[chisel3] def in(e: Either[L, R]): Seq[Data] = e match { + case Left(l) => lookupableL.in(l) + case Right(r) => lookupableR.in(r) } - private[chisel3] def out(original: Either[L, R], iter: Iterator[Data]): Either[L, R] = original match { - case Left(l) => - Left(lookupableL match { case li: LookupableImpl[L] => li.out(l, iter); case _ => l }) - case Right(r) => - Right(lookupableR match { case li: LookupableImpl[R] => li.out(r, iter); case _ => r }) + override private[chisel3] def out(original: Either[L, R], iter: Iterator[Data]): Either[L, R] = original match { + case Left(l) => Left(lookupableL.out(l, iter).asInstanceOf[L]) + case Right(r) => Right(lookupableR.out(r, iter).asInstanceOf[R]) } } @@ -769,16 +737,10 @@ object Lookupable { val t2res = lookupableT2.hierarchyLookup[A](that(_)._2, hierarchy) (t1res, t2res) } - private[chisel3] def in(t: (T1, T2)): Seq[Data] = { - val d1 = lookupableT1 match { case li: LookupableImpl[T1] => li.in(t._1); case _ => Seq.empty } - val d2 = lookupableT2 match { case li: LookupableImpl[T2] => li.in(t._2); case _ => Seq.empty } - d1 ++ d2 - } - private[chisel3] def out(original: (T1, T2), iter: Iterator[Data]): (T1, T2) = { - val r1 = lookupableT1 match { case li: LookupableImpl[T1] => li.out(original._1, iter); case _ => original._1 } - val r2 = lookupableT2 match { case li: LookupableImpl[T2] => li.out(original._2, iter); case _ => original._2 } - (r1, r2) - } + override private[chisel3] def in(t: (T1, T2)): Seq[Data] = + lookupableT1.in(t._1) ++ lookupableT2.in(t._2) + override private[chisel3] def out(original: (T1, T2), iter: Iterator[Data]): (T1, T2) = + (lookupableT1.out(original._1, iter).asInstanceOf[T1], lookupableT2.out(original._2, iter).asInstanceOf[T2]) } // TODO Once Lookupable return type change is removed, we can just call product factory above. @@ -795,18 +757,13 @@ object Lookupable { val t3res = lookupableT3.hierarchyLookup[A](that(_)._3, hierarchy) (t1res, t2res, t3res) } - private[chisel3] def in(t: (T1, T2, T3)): Seq[Data] = { - val d1 = lookupableT1 match { case li: LookupableImpl[T1] => li.in(t._1); case _ => Seq.empty } - val d2 = lookupableT2 match { case li: LookupableImpl[T2] => li.in(t._2); case _ => Seq.empty } - val d3 = lookupableT3 match { case li: LookupableImpl[T3] => li.in(t._3); case _ => Seq.empty } - d1 ++ d2 ++ d3 - } - private[chisel3] def out(original: (T1, T2, T3), iter: Iterator[Data]): (T1, T2, T3) = { - val r1 = lookupableT1 match { case li: LookupableImpl[T1] => li.out(original._1, iter); case _ => original._1 } - val r2 = lookupableT2 match { case li: LookupableImpl[T2] => li.out(original._2, iter); case _ => original._2 } - val r3 = lookupableT3 match { case li: LookupableImpl[T3] => li.out(original._3, iter); case _ => original._3 } - (r1, r2, r3) - } + override private[chisel3] def in(t: (T1, T2, T3)): Seq[Data] = + lookupableT1.in(t._1) ++ lookupableT2.in(t._2) ++ lookupableT3.in(t._3) + override private[chisel3] def out(original: (T1, T2, T3), iter: Iterator[Data]): (T1, T2, T3) = ( + lookupableT1.out(original._1, iter).asInstanceOf[T1], + lookupableT2.out(original._2, iter).asInstanceOf[T2], + lookupableT3.out(original._3, iter).asInstanceOf[T3] + ) } // TODO Once Lookupable return type change is removed, we can just call product factory above. @@ -826,24 +783,14 @@ object Lookupable { val t4res = lookupableT4.hierarchyLookup[A](that(_)._4, hierarchy) (t1res, t2res, t3res, t4res) } - private[chisel3] def in(t: (T1, T2, T3, T4)): Seq[Data] = { - val d1 = lookupableT1 match { case li: LookupableImpl[T1] => li.in(t._1); case _ => Seq.empty } - val d2 = lookupableT2 match { case li: LookupableImpl[T2] => li.in(t._2); case _ => Seq.empty } - val d3 = lookupableT3 match { case li: LookupableImpl[T3] => li.in(t._3); case _ => Seq.empty } - val d4 = lookupableT4 match { case li: LookupableImpl[T4] => li.in(t._4); case _ => Seq.empty } - d1 ++ d2 ++ d3 ++ d4 - } - private[chisel3] def out(original: (T1, T2, T3, T4), iter: Iterator[Data]): (T1, T2, T3, T4) = { - val r1 = - lookupableT1 match { case li: LookupableImpl[T1] => li.out(original._1, iter); case _ => original._1 } - val r2 = - lookupableT2 match { case li: LookupableImpl[T2] => li.out(original._2, iter); case _ => original._2 } - val r3 = - lookupableT3 match { case li: LookupableImpl[T3] => li.out(original._3, iter); case _ => original._3 } - val r4 = - lookupableT4 match { case li: LookupableImpl[T4] => li.out(original._4, iter); case _ => original._4 } - (r1, r2, r3, r4) - } + override private[chisel3] def in(t: (T1, T2, T3, T4)): Seq[Data] = + lookupableT1.in(t._1) ++ lookupableT2.in(t._2) ++ lookupableT3.in(t._3) ++ lookupableT4.in(t._4) + override private[chisel3] def out(original: (T1, T2, T3, T4), iter: Iterator[Data]): (T1, T2, T3, T4) = ( + lookupableT1.out(original._1, iter).asInstanceOf[T1], + lookupableT2.out(original._2, iter).asInstanceOf[T2], + lookupableT3.out(original._3, iter).asInstanceOf[T3], + lookupableT4.out(original._4, iter).asInstanceOf[T4] + ) } // TODO Once Lookupable return type change is removed, we can just call product factory above. @@ -865,27 +812,17 @@ object Lookupable { val t5res = lookupableT5.hierarchyLookup[A](that(_)._5, hierarchy) (t1res, t2res, t3res, t4res, t5res) } - private[chisel3] def in(t: (T1, T2, T3, T4, T5)): Seq[Data] = { - val d1 = lookupableT1 match { case li: LookupableImpl[T1] => li.in(t._1); case _ => Seq.empty } - val d2 = lookupableT2 match { case li: LookupableImpl[T2] => li.in(t._2); case _ => Seq.empty } - val d3 = lookupableT3 match { case li: LookupableImpl[T3] => li.in(t._3); case _ => Seq.empty } - val d4 = lookupableT4 match { case li: LookupableImpl[T4] => li.in(t._4); case _ => Seq.empty } - val d5 = lookupableT5 match { case li: LookupableImpl[T5] => li.in(t._5); case _ => Seq.empty } - d1 ++ d2 ++ d3 ++ d4 ++ d5 - } - private[chisel3] def out(original: (T1, T2, T3, T4, T5), iter: Iterator[Data]): (T1, T2, T3, T4, T5) = { - val r1 = - lookupableT1 match { case li: LookupableImpl[T1] => li.out(original._1, iter); case _ => original._1 } - val r2 = - lookupableT2 match { case li: LookupableImpl[T2] => li.out(original._2, iter); case _ => original._2 } - val r3 = - lookupableT3 match { case li: LookupableImpl[T3] => li.out(original._3, iter); case _ => original._3 } - val r4 = - lookupableT4 match { case li: LookupableImpl[T4] => li.out(original._4, iter); case _ => original._4 } - val r5 = - lookupableT5 match { case li: LookupableImpl[T5] => li.out(original._5, iter); case _ => original._5 } - (r1, r2, r3, r4, r5) - } + override private[chisel3] def in(t: (T1, T2, T3, T4, T5)): Seq[Data] = + lookupableT1.in(t._1) ++ lookupableT2.in(t._2) ++ lookupableT3.in(t._3) ++ lookupableT4.in(t._4) ++ lookupableT5 + .in(t._5) + override private[chisel3] def out(original: (T1, T2, T3, T4, T5), iter: Iterator[Data]): (T1, T2, T3, T4, T5) = + ( + lookupableT1.out(original._1, iter).asInstanceOf[T1], + lookupableT2.out(original._2, iter).asInstanceOf[T2], + lookupableT3.out(original._3, iter).asInstanceOf[T3], + lookupableT4.out(original._4, iter).asInstanceOf[T4], + lookupableT5.out(original._5, iter).asInstanceOf[T5] + ) } @deprecated( @@ -904,8 +841,8 @@ object Lookupable { } new Instance(Clone(underlying)) } - private[chisel3] def in(b: B): Seq[Data] = Seq.empty - private[chisel3] def out(original: B, iter: Iterator[Data]): B = original + override private[chisel3] def in(b: B): Seq[Data] = Seq.empty + override private[chisel3] def out(original: B, iter: Iterator[Data]): B = original } implicit def lookupIsLookupable[B <: IsLookupable](implicit sourceInfo: SourceInfo): Simple[B] = isLookupable[B] diff --git a/src/main/scala-3/chisel3/FixedIOModule.scala b/src/main/scala-3/chisel3/FixedIOModule.scala index 224fe2ec62f..692d3097b90 100644 --- a/src/main/scala-3/chisel3/FixedIOModule.scala +++ b/src/main/scala-3/chisel3/FixedIOModule.scala @@ -3,7 +3,7 @@ package chisel3 import chisel3.experimental.{BaseModule, ExtModule, Param} -import chisel3.experimental.hierarchy.core.{Lookupable, LookupableImpl} +import chisel3.experimental.hierarchy.core.Lookupable /** A module or external module whose IO is generated from a specific generator. * This module may have no additional IO created other than what is specified @@ -14,21 +14,15 @@ sealed trait FixedIOBaseModule[A](using lookupable: Lookupable[A]) extends BaseM /** A generator of IO */ protected def ioGenerator: A - final val io: A = (lookupable: @unchecked) match { - case impl: LookupableImpl[A] => - val dataElems = impl.in(ioGenerator) - val names = LazyList.from(0).map(i => ('a' + i).toChar.toString) - val ports = dataElems.zip(names).map { case (d, name) => - val p = IO(d) - p.suggestName(name) - p - } - impl.out(ioGenerator, ports.iterator) - case _ => - throw new Exception( - "FixedIOBaseModule requires a LookupableImpl-based Lookupable. " + - "Custom Lookupable extensions (deprecated since Chisel 7.0.0) are not supported." - ) + final val io: A = { + val dataElems = lookupable.in(ioGenerator) + val names = LazyList.from(0).map(i => ('a' + i).toChar.toString) + val ports = dataElems.zip(names).map { case (d, name) => + val p = IO(d) + p.suggestName(name) + p + } + lookupable.out(ioGenerator, ports.iterator) } endIOCreation() From 1f721d851f178ec82a2fa44446f402e602d2a5ea Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Tue, 19 May 2026 14:44:56 -0700 Subject: [PATCH 3/6] Manually implement @public for FixedIOBaseModule AI-assisted-by: Claude Code (Claude Sonnet 4.6) Signed-off-by: Jack Koenig --- src/main/scala-3/chisel3/FixedIOModule.scala | 4 +- src/main/scala/chisel3/FixedIOModule.scala | 57 +++++++++++++++++--- 2 files changed, 53 insertions(+), 8 deletions(-) diff --git a/src/main/scala-3/chisel3/FixedIOModule.scala b/src/main/scala-3/chisel3/FixedIOModule.scala index 692d3097b90..eb659c9f5c6 100644 --- a/src/main/scala-3/chisel3/FixedIOModule.scala +++ b/src/main/scala-3/chisel3/FixedIOModule.scala @@ -2,6 +2,7 @@ package chisel3 +import chisel3.experimental.hierarchy.{instantiable, public} import chisel3.experimental.{BaseModule, ExtModule, Param} import chisel3.experimental.hierarchy.core.Lookupable @@ -9,12 +10,13 @@ import chisel3.experimental.hierarchy.core.Lookupable * This module may have no additional IO created other than what is specified * by its `ioGenerator` abstract member. */ +@instantiable sealed trait FixedIOBaseModule[A](using lookupable: Lookupable[A]) extends BaseModule { /** A generator of IO */ protected def ioGenerator: A - final val io: A = { + @public final val io: A = { val dataElems = lookupable.in(ioGenerator) val names = LazyList.from(0).map(i => ('a' + i).toChar.toString) val ports = dataElems.zip(names).map { case (d, name) => diff --git a/src/main/scala/chisel3/FixedIOModule.scala b/src/main/scala/chisel3/FixedIOModule.scala index 83ca2da1c16..b736693b83c 100644 --- a/src/main/scala/chisel3/FixedIOModule.scala +++ b/src/main/scala/chisel3/FixedIOModule.scala @@ -3,6 +3,7 @@ package chisel3 import chisel3.experimental.hierarchy.{instantiable, public} +import chisel3.experimental.hierarchy.core.{Definition, Instance, Lookupable} import chisel3.experimental.{BaseModule, UnlocatableSourceInfo} /** A module or external module whose IO is generated from a specific generator. @@ -10,23 +11,62 @@ import chisel3.experimental.{BaseModule, UnlocatableSourceInfo} * by its `ioGenerator` abstract member. */ @instantiable -sealed trait FixedIOBaseModule[A <: Data] extends BaseModule { +sealed trait FixedIOBaseModule[A] extends BaseModule { /** A generator of IO */ protected def ioGenerator: A - @public - final val io = FlatIO(ioGenerator)(UnlocatableSourceInfo) + // Concrete subclasses implement this via an implicit constructor parameter. + protected implicit def lookupable: Lookupable[A] + + // No @public, lookups implemented manually in companion object. + final val io: A = { + implicit val l: Lookupable[A] = lookupable + val dataElems = l.in(ioGenerator) + val names = LazyList.from(0).map(i => ('a' + i).toChar.toString) + val ports = dataElems.zip(names).map { case (d, name) => + val p = FlatIO(d)(UnlocatableSourceInfo) + p.suggestName(name) + p + } + l.out(ioGenerator, ports.iterator) + } endIOCreation() } +object FixedIOBaseModule { + + // @public val io: A on the trait would generate these, but the @instantiable macro produces an + // external implicit class with no access to the trait's `lookupable` member. We replicate the + // generated lookup by passing proto.lookupable explicitly instead. + implicit class DefinitionOps[A](___module: Definition[FixedIOBaseModule[A]]) { + def io: A = { + implicit val _mg: chisel3.internal.MacroGenerated = new chisel3.internal.MacroGenerated {} + implicit val _l: Lookupable.Aux[A, A] = + ___module.proto.lookupable.asInstanceOf[Lookupable.Aux[A, A]] + ___module._lookup(_.io) + } + } + + implicit class InstanceOps[A](___module: Instance[FixedIOBaseModule[A]]) { + def io: A = { + implicit val _mg: chisel3.internal.MacroGenerated = new chisel3.internal.MacroGenerated {} + implicit val _l: Lookupable.Aux[A, A] = + ___module.proto.lookupable.asInstanceOf[Lookupable.Aux[A, A]] + ___module._lookup(_.io) + } + } +} + /** A Chisel module whose IO is determined by an IO generator. This module * cannot have additional IO created by modules that extend it. * * @param ioGenerator */ -class FixedIORawModule[A <: Data](final val ioGenerator: A) extends RawModule with FixedIOBaseModule[A] +class FixedIORawModule[A](final val ioGenerator: A)(implicit val lookupable: Lookupable[A]) + extends RawModule + with FixedIOBaseModule[A] /** A Chisel module whose IO (in addition to [[clock]] and [[reset]]) is determined * by an IO generator. This module cannot have additional IO created by modules that @@ -34,7 +74,9 @@ class FixedIORawModule[A <: Data](final val ioGenerator: A) extends RawModule wi * * @param ioGenerator */ -class FixedIOModule[A <: Data](final val ioGenerator: A) extends Module with FixedIOBaseModule[A] +class FixedIOModule[A](final val ioGenerator: A)(implicit val lookupable: Lookupable[A]) + extends Module + with FixedIOBaseModule[A] /** A Chisel blackbox whose IO is determined by an IO generator. This module * cannot have additional IO created by modules that extend it. @@ -42,6 +84,7 @@ class FixedIOModule[A <: Data](final val ioGenerator: A) extends Module with Fix * @param ioGenerator * @param params */ -class FixedIOExtModule[A <: Data](final val ioGenerator: A, params: Map[String, Param] = Map.empty[String, Param]) - extends ExtModule(params) +class FixedIOExtModule[A](final val ioGenerator: A, params: Map[String, Param] = Map.empty[String, Param])( + implicit val lookupable: Lookupable[A] +) extends ExtModule(params) with FixedIOBaseModule[A] From 7cd46700172e06801463fc497b42661a09132752 Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Thu, 28 May 2026 08:42:04 -0700 Subject: [PATCH 4/6] Add DataProduct to FixedIOModule as well --- src/main/scala/chisel3/FixedIOModule.scala | 37 +++++++++++++++------- 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/src/main/scala/chisel3/FixedIOModule.scala b/src/main/scala/chisel3/FixedIOModule.scala index b736693b83c..f0401ebe01d 100644 --- a/src/main/scala/chisel3/FixedIOModule.scala +++ b/src/main/scala/chisel3/FixedIOModule.scala @@ -5,6 +5,7 @@ package chisel3 import chisel3.experimental.hierarchy.{instantiable, public} import chisel3.experimental.hierarchy.core.{Definition, Instance, Lookupable} import chisel3.experimental.{BaseModule, UnlocatableSourceInfo} +import chisel3.experimental.dataview.DataProduct /** A module or external module whose IO is generated from a specific generator. * This module may have no additional IO created other than what is specified @@ -19,17 +20,24 @@ sealed trait FixedIOBaseModule[A] extends BaseModule { // Concrete subclasses implement this via an implicit constructor parameter. protected implicit def lookupable: Lookupable[A] + protected implicit def dataProduct: DataProduct[A] + // No @public, lookups implemented manually in companion object. final val io: A = { - implicit val l: Lookupable[A] = lookupable - val dataElems = l.in(ioGenerator) - val names = LazyList.from(0).map(i => ('a' + i).toChar.toString) - val ports = dataElems.zip(names).map { case (d, name) => + val a = ioGenerator + val dataElems = lookupable.in(a) + // TODO add check for name collisions + val nameLookup = dataProduct.dataIterator(a, "").toMap + val io = dataElems.map { d => val p = FlatIO(d)(UnlocatableSourceInfo) + val name = { + val n = nameLookup(d) + if (n == "") "io" else n + } p.suggestName(name) p } - l.out(ioGenerator, ports.iterator) + lookupable.out(ioGenerator, io.iterator) } endIOCreation() @@ -43,7 +51,7 @@ object FixedIOBaseModule { implicit class DefinitionOps[A](___module: Definition[FixedIOBaseModule[A]]) { def io: A = { implicit val _mg: chisel3.internal.MacroGenerated = new chisel3.internal.MacroGenerated {} - implicit val _l: Lookupable.Aux[A, A] = + implicit val _l: Lookupable.Aux[A, A] = ___module.proto.lookupable.asInstanceOf[Lookupable.Aux[A, A]] ___module._lookup(_.io) } @@ -52,7 +60,7 @@ object FixedIOBaseModule { implicit class InstanceOps[A](___module: Instance[FixedIOBaseModule[A]]) { def io: A = { implicit val _mg: chisel3.internal.MacroGenerated = new chisel3.internal.MacroGenerated {} - implicit val _l: Lookupable.Aux[A, A] = + implicit val _l: Lookupable.Aux[A, A] = ___module.proto.lookupable.asInstanceOf[Lookupable.Aux[A, A]] ___module._lookup(_.io) } @@ -64,8 +72,10 @@ object FixedIOBaseModule { * * @param ioGenerator */ -class FixedIORawModule[A](final val ioGenerator: A)(implicit val lookupable: Lookupable[A]) - extends RawModule +class FixedIORawModule[A](final val ioGenerator: A)( + implicit val lookupable: Lookupable[A], + val dataProduct: DataProduct[A] +) extends RawModule with FixedIOBaseModule[A] /** A Chisel module whose IO (in addition to [[clock]] and [[reset]]) is determined @@ -74,8 +84,10 @@ class FixedIORawModule[A](final val ioGenerator: A)(implicit val lookupable: Loo * * @param ioGenerator */ -class FixedIOModule[A](final val ioGenerator: A)(implicit val lookupable: Lookupable[A]) - extends Module +class FixedIOModule[A](final val ioGenerator: A)( + implicit val lookupable: Lookupable[A], + val dataProduct: DataProduct[A] +) extends Module with FixedIOBaseModule[A] /** A Chisel blackbox whose IO is determined by an IO generator. This module @@ -85,6 +97,7 @@ class FixedIOModule[A](final val ioGenerator: A)(implicit val lookupable: Lookup * @param params */ class FixedIOExtModule[A](final val ioGenerator: A, params: Map[String, Param] = Map.empty[String, Param])( - implicit val lookupable: Lookupable[A] + implicit val lookupable: Lookupable[A], + val dataProduct: DataProduct[A] ) extends ExtModule(params) with FixedIOBaseModule[A] From 85b29086d7da2c356e04b1a08d886ece63749b45 Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Thu, 28 May 2026 09:48:32 -0700 Subject: [PATCH 5/6] Remove <: Data from ModuleChoice AI-assisted-by: Claude Code (Claude Sonnet 4.6) Signed-off-by: Jack Koenig --- .../chisel3/choice/ModuleChoiceIntf.scala | 4 ++-- src/main/scala-3/chisel3/FixedIOModule.scala | 2 ++ .../chisel3/choice/ModuleChoiceIntf.scala | 4 ++-- src/main/scala/chisel3/FixedIOModule.scala | 2 ++ src/main/scala/chisel3/choice/ModuleChoice.scala | 16 +++++++++++----- 5 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/main/scala-2/chisel3/choice/ModuleChoiceIntf.scala b/src/main/scala-2/chisel3/choice/ModuleChoiceIntf.scala index ed63c7c2da9..5136e244896 100644 --- a/src/main/scala-2/chisel3/choice/ModuleChoiceIntf.scala +++ b/src/main/scala-2/chisel3/choice/ModuleChoiceIntf.scala @@ -29,13 +29,13 @@ private[chisel3] trait ModuleChoiceObjIntf extends SourceInfoDoc { self: ModuleC * * @throws java.lang.IllegalArgumentException if the cases do not belong to the same option. */ - def apply[T <: Data]( + def apply[T]( default: => FixedIOBaseModule[T] )(choices: => Seq[(Case, () => FixedIOBaseModule[T])]): T = macro InstChoiceTransform.apply[T] /** @group SourceInfoTransformMacro */ - def do_apply[T <: Data]( + def do_apply[T]( default: => FixedIOBaseModule[T], choices: Seq[(Case, () => FixedIOBaseModule[T])] )( diff --git a/src/main/scala-3/chisel3/FixedIOModule.scala b/src/main/scala-3/chisel3/FixedIOModule.scala index eb659c9f5c6..e7cc88a6151 100644 --- a/src/main/scala-3/chisel3/FixedIOModule.scala +++ b/src/main/scala-3/chisel3/FixedIOModule.scala @@ -16,6 +16,8 @@ sealed trait FixedIOBaseModule[A](using lookupable: Lookupable[A]) extends BaseM /** A generator of IO */ protected def ioGenerator: A + private[chisel3] def _lookupable: Lookupable[A] = lookupable + @public final val io: A = { val dataElems = lookupable.in(ioGenerator) val names = LazyList.from(0).map(i => ('a' + i).toChar.toString) diff --git a/src/main/scala-3/chisel3/choice/ModuleChoiceIntf.scala b/src/main/scala-3/chisel3/choice/ModuleChoiceIntf.scala index 079307e62bc..f7fd85d51d3 100644 --- a/src/main/scala-3/chisel3/choice/ModuleChoiceIntf.scala +++ b/src/main/scala-3/chisel3/choice/ModuleChoiceIntf.scala @@ -2,7 +2,7 @@ package chisel3.choice -import chisel3.{Data, FixedIOBaseModule} +import chisel3.FixedIOBaseModule import chisel3.experimental.SourceInfo private[chisel3] trait ModuleChoiceObjIntf { self: ModuleChoice.type => @@ -26,7 +26,7 @@ private[chisel3] trait ModuleChoiceObjIntf { self: ModuleChoice.type => * * @throws java.lang.IllegalArgumentException if the cases do not belong to the same option. */ - def apply[T <: Data]( + def apply[T]( default: => FixedIOBaseModule[T] )( choices: => Seq[(Case, () => FixedIOBaseModule[T])] diff --git a/src/main/scala/chisel3/FixedIOModule.scala b/src/main/scala/chisel3/FixedIOModule.scala index f0401ebe01d..cc2d4de877d 100644 --- a/src/main/scala/chisel3/FixedIOModule.scala +++ b/src/main/scala/chisel3/FixedIOModule.scala @@ -22,6 +22,8 @@ sealed trait FixedIOBaseModule[A] extends BaseModule { protected implicit def dataProduct: DataProduct[A] + private[chisel3] def _lookupable: Lookupable[A] = lookupable + // No @public, lookups implemented manually in companion object. final val io: A = { val a = ioGenerator diff --git a/src/main/scala/chisel3/choice/ModuleChoice.scala b/src/main/scala/chisel3/choice/ModuleChoice.scala index ae8b83e703a..d74b64b64ee 100644 --- a/src/main/scala/chisel3/choice/ModuleChoice.scala +++ b/src/main/scala/chisel3/choice/ModuleChoice.scala @@ -6,6 +6,7 @@ import scala.collection.immutable.ListMap import chisel3.{Data, FixedIOBaseModule, Module, SourceInfoDoc} import chisel3.experimental.{BaseModule, SourceInfo} +import chisel3.experimental.hierarchy.core.Lookupable import chisel3.internal.{groupByIntoSeq, Builder} import chisel3.internal.binding.InstanceChoiceBinding import chisel3.internal.Builder.pushCommand @@ -13,17 +14,21 @@ import chisel3.internal.firrtl.ir.DefInstanceChoice object ModuleChoice extends ModuleChoiceObjIntf { - protected def _applyImpl[T <: Data]( + protected def _applyImpl[T]( default: => FixedIOBaseModule[T], choices: Seq[(Case, () => FixedIOBaseModule[T])] )( implicit sourceInfo: SourceInfo ): T = { val instDefaultModule = Module.evaluate(default) + val lk: Lookupable[T] = instDefaultModule._lookupable + + val defaultLeaves = lk.in(instDefaultModule.io) val choiceModules = choices.map { case (choice, module) => val instModule = Module.evaluate(module()) - if (!instModule.io.typeEquivalent(instDefaultModule.io)) { + val choiceLeaves = lk.in(instModule.io) + if (!defaultLeaves.zip(choiceLeaves).forall { case (a, b) => b.typeEquivalent(a) }) { Builder.error("Error: choice module IO bundles are not type equivalent") } Builder.options += choice @@ -46,13 +51,14 @@ object ModuleChoice extends ModuleChoiceObjIntf { } val group = groupedChoices.head.name - val binding = instDefaultModule.io.cloneTypeFull - binding.bind(InstanceChoiceBinding(Builder.forcedUserModule, Builder.currentBlock)) + val bindingLeaves = defaultLeaves.map(_.cloneTypeFull) + bindingLeaves.foreach(_.bind(InstanceChoiceBinding(Builder.forcedUserModule, Builder.currentBlock))) + val binding = lk.out(instDefaultModule.io, bindingLeaves.iterator) pushCommand( DefInstanceChoice( sourceInfo, - binding, + bindingLeaves.head, instDefaultModule, group, choiceModules.map { case (choice, instModule) => From 2295e383a72fc367d351786e38a022d6e9c8fe73 Mon Sep 17 00:00:00 2001 From: Schuyler Eldridge Date: Fri, 29 May 2026 18:27:17 -0400 Subject: [PATCH 6/6] fixup! Remove <: Data from ModuleChoice --- .../scala/chiselTests/ModuleChoiceSpec.scala | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/test/scala/chiselTests/ModuleChoiceSpec.scala b/src/test/scala/chiselTests/ModuleChoiceSpec.scala index 97f82fa646c..e2785febb97 100644 --- a/src/test/scala/chiselTests/ModuleChoiceSpec.scala +++ b/src/test/scala/chiselTests/ModuleChoiceSpec.scala @@ -168,4 +168,26 @@ class ModuleChoiceSpec extends AnyFlatSpec with Matchers with FileCheck { |CHECK: connect inst.in, io.in""".stripMargin ) } + + it should "generate correct FIRRTL when using tuple ports" in { + + class Bar extends FixedIORawModule[(UInt, UInt)]((UInt(1.W), UInt(2.W))) + + class Foo extends Module { + val a = IO(Output(UInt(1.W))) + val b = IO(Output(UInt(2.W))) + private val bar = ModuleChoice(new Bar)(Seq(Platform.FPGA -> new Bar)) + a :<= bar._1 + b :<= bar._2 + } + + ChiselStage.emitCHIRRTL(new Foo) + .fileCheck()( + """|CHECK: instchoice bar of Bar + |CHECK: connect a, bar._1 + |CHECK-NEXT: connect b, bar._2 + |""" + ) + + } }