Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
139 changes: 139 additions & 0 deletions core/src/main/scala/chisel3/FixedIO.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
// SPDX-License-Identifier: Apache-2.0

package chisel3

import chisel3.experimental.SourceInfo

/** Internal helper for creating ports from values whose shape is described by a
* [[chisel3.experimental.dataview.DataProduct]].
*
* For a single [[Data]] this defers to [[FlatIO]] so existing
* `FixedIOBaseModule` behavior is preserved exactly. For composite shapes
* (tuples, sequences) it recursively creates one port per contained [[Data]],
* naming each port based on its structural path (e.g. `_1`, `_2`, `_0`).
*
* The original generator value cannot be reused as the bound IO because
* [[IO]]/[[FlatIO]] may clone the provided [[Data]] (per
* [[chisel3.Data.mustClone]]). Instead, this helper rebuilds the structure of
* `A` with the cloned, port-bound values substituted in, returning a value of
* the same shape whose leaves are the live ports.
*/
private[chisel3] object FixedIO {

/** Bind `value`'s contained [[Data]] as ports, returning a value of the same
* shape whose leaves are the bound ports.
*
* The chisel naming plugin would normally inject a `withName("io")` around
* the val RHS for `final val io = ...`, but only when the val's type is
* known to be a [[Data]] (or container thereof). Because `A` is an
* unbounded type parameter, the plugin does not insert that wrapper, so we
* must call [[chisel3.withName]] explicitly here.
*
* @param value the generator value to walk
* @param path the structural path used to name multi-IO leaves (the
* top-level call typically passes `"io"`)
*/
def bindPorts(value: Any, path: String)(implicit si: SourceInfo): Any =
chisel3.withName(path)(bindPortsImpl(value, path))

private def bindPortsImpl(value: Any, path: String)(implicit si: SourceInfo): Any = value match {
case d: Data =>
// Top-level invocation with `path == "io"` and a single Data preserves
// existing FixedIOBaseModule behavior exactly (uses FlatIO so Bundles
// become flat top-level ports).
if (path == "io") FlatIO(d)
else {
val port = IO(d)
port.suggestName(path)
port
}
case t: Tuple1[_] =>
Tuple1(bindPorts(t._1, appendPath(path, "_1")))
case t: Tuple2[_, _] =>
(bindPorts(t._1, appendPath(path, "_1")), bindPorts(t._2, appendPath(path, "_2")))
case t: Tuple3[_, _, _] =>
(
bindPorts(t._1, appendPath(path, "_1")),
bindPorts(t._2, appendPath(path, "_2")),
bindPorts(t._3, appendPath(path, "_3"))
)
case t: Tuple4[_, _, _, _] =>
(
bindPorts(t._1, appendPath(path, "_1")),
bindPorts(t._2, appendPath(path, "_2")),
bindPorts(t._3, appendPath(path, "_3")),
bindPorts(t._4, appendPath(path, "_4"))
)
case t: Tuple5[_, _, _, _, _] =>
(
bindPorts(t._1, appendPath(path, "_1")),
bindPorts(t._2, appendPath(path, "_2")),
bindPorts(t._3, appendPath(path, "_3")),
bindPorts(t._4, appendPath(path, "_4")),
bindPorts(t._5, appendPath(path, "_5"))
)
case t: Tuple6[_, _, _, _, _, _] =>
(
bindPorts(t._1, appendPath(path, "_1")),
bindPorts(t._2, appendPath(path, "_2")),
bindPorts(t._3, appendPath(path, "_3")),
bindPorts(t._4, appendPath(path, "_4")),
bindPorts(t._5, appendPath(path, "_5")),
bindPorts(t._6, appendPath(path, "_6"))
)
case t: Tuple7[_, _, _, _, _, _, _] =>
(
bindPorts(t._1, appendPath(path, "_1")),
bindPorts(t._2, appendPath(path, "_2")),
bindPorts(t._3, appendPath(path, "_3")),
bindPorts(t._4, appendPath(path, "_4")),
bindPorts(t._5, appendPath(path, "_5")),
bindPorts(t._6, appendPath(path, "_6")),
bindPorts(t._7, appendPath(path, "_7"))
)
case t: Tuple8[_, _, _, _, _, _, _, _] =>
(
bindPorts(t._1, appendPath(path, "_1")),
bindPorts(t._2, appendPath(path, "_2")),
bindPorts(t._3, appendPath(path, "_3")),
bindPorts(t._4, appendPath(path, "_4")),
bindPorts(t._5, appendPath(path, "_5")),
bindPorts(t._6, appendPath(path, "_6")),
bindPorts(t._7, appendPath(path, "_7")),
bindPorts(t._8, appendPath(path, "_8"))
)
case t: Tuple9[_, _, _, _, _, _, _, _, _] =>
(
bindPorts(t._1, appendPath(path, "_1")),
bindPorts(t._2, appendPath(path, "_2")),
bindPorts(t._3, appendPath(path, "_3")),
bindPorts(t._4, appendPath(path, "_4")),
bindPorts(t._5, appendPath(path, "_5")),
bindPorts(t._6, appendPath(path, "_6")),
bindPorts(t._7, appendPath(path, "_7")),
bindPorts(t._8, appendPath(path, "_8")),
bindPorts(t._9, appendPath(path, "_9"))
)
case t: Tuple10[_, _, _, _, _, _, _, _, _, _] =>
(
bindPorts(t._1, appendPath(path, "_1")),
bindPorts(t._2, appendPath(path, "_2")),
bindPorts(t._3, appendPath(path, "_3")),
bindPorts(t._4, appendPath(path, "_4")),
bindPorts(t._5, appendPath(path, "_5")),
bindPorts(t._6, appendPath(path, "_6")),
bindPorts(t._7, appendPath(path, "_7")),
bindPorts(t._8, appendPath(path, "_8")),
bindPorts(t._9, appendPath(path, "_9")),
bindPorts(t._10, appendPath(path, "_10"))
)
case s: Seq[_] =>
s.zipWithIndex.map { case (elt, idx) => bindPorts(elt, appendPath(path, idx.toString)) }
case other =>
// Anything else (e.g. types with DataProduct.empty) has no Data to bind.
other
}

private def appendPath(path: String, suffix: String): String =
if (path.isEmpty) suffix else s"${path}_${suffix.stripPrefix("_")}"
}
105 changes: 95 additions & 10 deletions src/main/scala-2/chisel3/FixedIOModule.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,31 @@

package chisel3

import chisel3.experimental.hierarchy.{instantiable, public}
import chisel3.experimental.dataview.DataProduct
import chisel3.experimental.hierarchy.{Definition, Instance, IsInstantiable, Lookupable}
import chisel3.experimental.{BaseModule, UnlocatableSourceInfo}
import chisel3.internal.MacroGenerated

/** 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.
*
* `A` may be any type that has a [[chisel3.experimental.dataview.DataProduct]]
* implementation, including a single [[Data]], a tuple of `Data`-containing
* types, or a `Seq` of `Data`-containing types. Each contained [[Data]] is
* turned into a port; for the single-`Data` case [[FlatIO]] is used so that
* existing modules are unaffected.
*/
@instantiable
sealed trait FixedIOBaseModule[A <: Data] extends BaseModule {
sealed trait FixedIOBaseModule[A] extends BaseModule with IsInstantiable {

/** A generator of IO */
protected def ioGenerator: A

@public
final val io = FlatIO(ioGenerator)(UnlocatableSourceInfo)
endIOCreation()
/** Evidence that `A` contains [[Data]] elements that can be turned into ports. */
protected implicit def ioDataProduct: DataProduct[A]

/** The IO of this module, of shape `A` whose contained [[Data]] are ports. */
def io: A

}

Expand All @@ -26,22 +35,98 @@ sealed trait FixedIOBaseModule[A <: Data] extends BaseModule {
*
* @param ioGenerator
*/
class FixedIORawModule[A <: Data](final val ioGenerator: A) extends RawModule with FixedIOBaseModule[A]
class FixedIORawModule[A](final val ioGenerator: A)(implicit val ioDataProduct: DataProduct[A])
extends RawModule
with FixedIOBaseModule[A] {
final val io: A = FixedIO.bindPorts(ioGenerator, "io")(UnlocatableSourceInfo).asInstanceOf[A]
endIOCreation()
}

object FixedIORawModule {
// Manually-written equivalent of what `@public` on `io` would produce, but
// with a `Lookupable[A]` evidence param so we can support arbitrary
// `DataProduct[A]`-shaped IOs in addition to the historical `A <: Data` case.
implicit class FixedIORawModuleDefinitionExtensions[A](
private val ___module: Definition[FixedIORawModule[A]]
) extends AnyVal {
def io(implicit lookup: Lookupable[A]): lookup.C = {
implicit val mg: MacroGenerated = new MacroGenerated {}
___module._lookup(_.io)
}
}
implicit class FixedIORawModuleInstanceExtensions[A](
private val ___module: Instance[FixedIORawModule[A]]
) extends AnyVal {
def io(implicit lookup: Lookupable[A]): lookup.C = {
implicit val mg: MacroGenerated = new MacroGenerated {}
___module._lookup(_.io)
}
}
}

/** 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 <: Data](final val ioGenerator: A) extends Module with FixedIOBaseModule[A]
class FixedIOModule[A](final val ioGenerator: A)(implicit val ioDataProduct: DataProduct[A])
extends Module
with FixedIOBaseModule[A] {
final val io: A = FixedIO.bindPorts(ioGenerator, "io")(UnlocatableSourceInfo).asInstanceOf[A]
endIOCreation()
}

object FixedIOModule {
implicit class FixedIOModuleDefinitionExtensions[A](
private val ___module: Definition[FixedIOModule[A]]
) extends AnyVal {
def io(implicit lookup: Lookupable[A]): lookup.C = {
implicit val mg: MacroGenerated = new MacroGenerated {}
___module._lookup(_.io)
}
}
implicit class FixedIOModuleInstanceExtensions[A](
private val ___module: Instance[FixedIOModule[A]]
) extends AnyVal {
def io(implicit lookup: Lookupable[A]): lookup.C = {
implicit val mg: MacroGenerated = new MacroGenerated {}
___module._lookup(_.io)
}
}
}

/** 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 <: Data](final val ioGenerator: A, params: Map[String, Param] = Map.empty[String, Param])
class FixedIOExtModule[A](
final val ioGenerator: A,
params: Map[String, Param] = Map.empty[String, Param]
)(implicit val ioDataProduct: DataProduct[A])
extends ExtModule(params)
with FixedIOBaseModule[A]
with FixedIOBaseModule[A] {
final val io: A = FixedIO.bindPorts(ioGenerator, "io")(UnlocatableSourceInfo).asInstanceOf[A]
endIOCreation()
}

object FixedIOExtModule {
implicit class FixedIOExtModuleDefinitionExtensions[A](
private val ___module: Definition[FixedIOExtModule[A]]
) extends AnyVal {
def io(implicit lookup: Lookupable[A]): lookup.C = {
implicit val mg: MacroGenerated = new MacroGenerated {}
___module._lookup(_.io)
}
}
implicit class FixedIOExtModuleInstanceExtensions[A](
private val ___module: Instance[FixedIOExtModule[A]]
) extends AnyVal {
def io(implicit lookup: Lookupable[A]): lookup.C = {
implicit val mg: MacroGenerated = new MacroGenerated {}
___module._lookup(_.io)
}
}
}
47 changes: 40 additions & 7 deletions src/main/scala-3/chisel3/FixedIOModule.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,29 @@
package chisel3

import chisel3.experimental.{BaseModule, ExtModule, Param}
import chisel3.experimental.dataview.DataProduct
import chisel3.experimental.hierarchy.{instantiable, public}

/** 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.
*
* `A` may be any type that has a [[chisel3.experimental.dataview.DataProduct]]
* implementation, including a single [[Data]], a tuple of `Data`-containing
* types, or a `Seq` of `Data`-containing types. Each contained [[Data]] is
* turned into a port; for the single-`Data` case [[FlatIO]] is used so that
* existing modules are unaffected.
*/
sealed trait FixedIOBaseModule[A <: Data] extends BaseModule {
sealed trait FixedIOBaseModule[A] extends BaseModule {

/** A generator of IO */
protected def ioGenerator: A

final val io = FlatIO(ioGenerator)
endIOCreation()
/** Evidence that `A` contains [[Data]] elements that can be turned into ports. */
protected implicit def ioDataProduct: DataProduct[A]

/** The IO of this module, of shape `A` whose contained [[Data]] are ports. */
def io: A

}

Expand All @@ -23,22 +34,44 @@ sealed trait FixedIOBaseModule[A <: Data] extends BaseModule {
*
* @param ioGenerator
*/
class FixedIORawModule[A <: Data](final val ioGenerator: A) extends RawModule with FixedIOBaseModule[A]
@instantiable
class FixedIORawModule[A](final val ioGenerator: A)(using val ioDataProduct: DataProduct[A])
extends RawModule
with FixedIOBaseModule[A] {
@public
final val io: A = FixedIO.bindPorts(ioGenerator, "io").asInstanceOf[A]
endIOCreation()
}

/** 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 <: Data](final val ioGenerator: A) extends Module with FixedIOBaseModule[A]
@instantiable
class FixedIOModule[A](final val ioGenerator: A)(using val ioDataProduct: DataProduct[A])
extends Module
with FixedIOBaseModule[A] {
@public
final val io: A = FixedIO.bindPorts(ioGenerator, "io").asInstanceOf[A]
endIOCreation()
}

/** 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 <: Data](final val ioGenerator: A, params: Map[String, Param] = Map.empty[String, Param])
@instantiable
class FixedIOExtModule[A](
final val ioGenerator: A,
params: Map[String, Param] = Map.empty[String, Param]
)(using val ioDataProduct: DataProduct[A])
extends ExtModule(params)
with FixedIOBaseModule[A]
with FixedIOBaseModule[A] {
@public
final val io: A = FixedIO.bindPorts(ioGenerator, "io").asInstanceOf[A]
endIOCreation()
}
Loading
Loading