diff --git a/core/src/main/scala/chisel3/Data.scala b/core/src/main/scala/chisel3/Data.scala index eb8ff993a34..7ff792dd5cf 100644 --- a/core/src/main/scala/chisel3/Data.scala +++ b/core/src/main/scala/chisel3/Data.scala @@ -46,6 +46,18 @@ object SpecifiedDirection { case Input => Output } + /** Returns true if specified direction manifests as a flipped field + * + * @param dir provided specified direction + * @return + */ + private[chisel3] def isFlipped(dir: SpecifiedDirection): Boolean = dir match { + case Unspecified => false + case Flip => true + case Output => false + case Input => true + } + /** Returns the effective SpecifiedDirection of this node given the parent's effective SpecifiedDirection * and the user-specified SpecifiedDirection of this node. */ @@ -251,8 +263,17 @@ object chiselTypeOf { } } -/** - * Input, Output, and Flipped are used to define the directions of Module IOs. +/** Creates a field of a parent [[Aggregate]] which is + * - flipped relative to that parent + * - coerced all members of the field to be aligned + * + * E.g. The following will create a field `i` of `b` where all recursive sub-elements of `i` are aligned, and where `i` flipped relative to `b` + * + * ```scala + * val b = new Bundle { + * val i = Input(new Decoupled(UInt(32.W)) + * } + * ``` * * Note that they currently clone their source argument, including its bindings. * @@ -263,18 +284,71 @@ object Input { SpecifiedDirection.specifiedDirection(source)(_ => SpecifiedDirection.Input) } } + +/** Creates a field of a parent [[Aggregate]] which is + * - aligned relative to that parent + * - coerced all members of the field to be aligned + * + * E.g. The following will create a field `i` of `b` where all recursive sub-elements of `i` are aligned, and where `i` is also aligned relative to `b` + * + * ```scala + * val b = new Bundle { + * val i = Output(new Decoupled(UInt(32.W)) + * } + * ``` + * + * Note that they currently clone their source argument, including its bindings. + * + * Thus, an error will be thrown if these are used on bound Data + */ object Output { def apply[T <: Data](source: => T): T = { SpecifiedDirection.specifiedDirection(source)(_ => SpecifiedDirection.Output) } } +/** Creates a field of a parent [[Aggregate]] which is + * - flipped relative to that parent + * + * E.g. The following will create a field `i` of `b` where `i` is flipped relative to `b` + * + * ```scala + * val b = new Bundle { + * val i = Flipped(new Decoupled(UInt(32.W)) + * } + * ``` + * + * Note that they currently clone their source argument, including its bindings. + * + * Thus, an error will be thrown if these are used on bound Data + */ object Flipped { def apply[T <: Data](source: => T): T = { SpecifiedDirection.specifiedDirection(source)(x => SpecifiedDirection.flip(x.specifiedDirection)) } } +/** Creates a field of a parent [[Aggregate]] which is + * - aligned relative to that parent + * + * E.g. The following will create a field `i` of `b` where `i` is aligned (not flipped) relative to `b` + * + * ```scala + * val b = new Bundle { + * val i = Aligned(new Decoupled(UInt(32.W)) + * } + * ``` + * + * Note that they currently clone their source argument, including its bindings. + * + * Thus, an error will be thrown if these are used on bound Data + */ +object Aligned { + def apply[T <: Data](source: => T): T = { + SpecifiedDirection.specifiedDirection(source)(x => x.specifiedDirection) + } +} + /** This forms the root of the type system for wire data types. The data value * must be representable as some number (need not be known at Chisel compile * time) of bits, and must have methods to pack / unpack structured data to / diff --git a/core/src/main/scala/chisel3/IO.scala b/core/src/main/scala/chisel3/IO.scala index 64ee8c67d53..c8cfc12271a 100644 --- a/core/src/main/scala/chisel3/IO.scala +++ b/core/src/main/scala/chisel3/IO.scala @@ -9,11 +9,7 @@ object IO { /** Constructs a port for the current Module * - * This must wrap the datatype used to set the io field of any Module. - * i.e. All concrete modules must have defined io in this form: - * [lazy] val io[: io type] = IO(...[: io type]) - * - * Items in [] are optional. + * This must wrap the datatype used to create an io port of any Module. * * The granted iodef must be a chisel type and not be bound to hardware. * @@ -62,3 +58,57 @@ object IO { iodefClone } } + +object Incoming { + + /** Constructs an incoming port of the provided `iodef` chisel type for the current Module + * + * The following example creates a input (and mixed-alignment) aggregate port in the current module + * + * ```scala + * val i = Incoming(new Decoupled(UInt(32.W))) + * ``` + * In this example, the sub-elements of `i` are ports of the following direction: + * * `i.bits` is an input port + * * `i.valid` is an input port + * * `i.ready` is an output port + * + * Note that `Incoming(foo)` is equivalent to `IO(Flipped(foo))` + * + * This must wrap the datatype used to create an io port of any Module. + * + * The granted iodef must be a non-flipped chisel type and not be bound to hardware. + */ + def apply[T <: Data](iodef: => T)(implicit sourceInfo: SourceInfo): T = { + if (SpecifiedDirection.isFlipped(iodef.specifiedDirection)) + Builder.error("Incoming(..) cannot accept a chisel typed which is flipped") + IO(Flipped(iodef)) + } +} + +object Outgoing { + + /** Constructs an outgoing port of the provided `iodef` chisel type for the current Module + * + * The following example creates a output (and mixed-alignment) aggregate port in the current module + * + * ```scala + * val i = Outgoing(new Decoupled(UInt(32.W))) + * ``` + * In this example, the sub-elements of `i` are ports of the following direction: + * * `i.bits` is an output port + * * `i.valid` is an output port + * * `i.ready` is an input port + * + * Note that `Outgoing(foo)` is equivalent to `IO(foo)` + * + * This must wrap the datatype used to create an io port of any Module. + * + * The granted iodef must be a non-flipped chisel type and not be bound to hardware. + */ + def apply[T <: Data](iodef: => T)(implicit sourceInfo: SourceInfo): T = { + if (SpecifiedDirection.isFlipped(iodef.specifiedDirection)) + Builder.error("Outgoing(..) cannot accept a chisel typed which is flipped") + IO(iodef) + } +} diff --git a/src/test/scala/chiselTests/ConnectableSpec.scala b/src/test/scala/chiselTests/ConnectableSpec.scala index a204d03afd1..73c682d6be2 100644 --- a/src/test/scala/chiselTests/ConnectableSpec.scala +++ b/src/test/scala/chiselTests/ConnectableSpec.scala @@ -26,7 +26,7 @@ object ConnectableSpec { extends Module { val io = IO(new Bundle { val in = Flipped(inType) - val out = Flipped(Flipped(outType)) // no clonetype, no Aligned (yet) + val out = Aligned(outType) // no clonetype, no Aligned (yet) val monitor = monitorOp.map(mop => { Output(inType) }) @@ -44,22 +44,22 @@ object ConnectableSpec { def vec[T <: Data](tpe: T, n: Int = 3) = Vec(n, tpe) def alignedBundle[T <: Data](fieldType: T) = new Bundle { - val foo = Flipped(Flipped(fieldType)) - val bar = Flipped(Flipped(fieldType)) + val foo = Aligned(fieldType) + val bar = Aligned(fieldType) } def mixedBundle[T <: Data](fieldType: T) = new Bundle { - val foo = Flipped(Flipped(fieldType)) + val foo = Aligned(fieldType) val bar = Flipped(fieldType) } def alignedFooBundle[T <: Data](fieldType: T) = new Bundle { - val foo = Flipped(Flipped(fieldType)) + val foo = Aligned(fieldType) } def flippedBarBundle[T <: Data](fieldType: T) = new Bundle { val bar = Flipped(fieldType) } def opaqueType[T <: Data](fieldType: T) = new Record with OpaqueType { - lazy val elements = SeqMap("" -> Flipped(Flipped(fieldType))) + lazy val elements = SeqMap("" -> Aligned(fieldType)) } def allElementTypes(): Seq[() => Data] = Seq(() => UInt(3.W)) diff --git a/src/test/scala/chiselTests/IOCompatibility.scala b/src/test/scala/chiselTests/IOSpec.scala similarity index 56% rename from src/test/scala/chiselTests/IOCompatibility.scala rename to src/test/scala/chiselTests/IOSpec.scala index be44d7a88e2..567596e1c99 100644 --- a/src/test/scala/chiselTests/IOCompatibility.scala +++ b/src/test/scala/chiselTests/IOSpec.scala @@ -35,7 +35,7 @@ class IOCModuleWire extends Module { io.out := inc.out } -class IOCompatibilitySpec extends ChiselPropSpec with Matchers with Utils { +class IOSpec extends ChiselPropSpec with Matchers with Utils with MatchesAndOmits { property("IOCModuleVec should elaborate") { ChiselStage.emitCHIRRTL { new IOCModuleVec(2) } @@ -55,4 +55,27 @@ class IOCompatibilitySpec extends ChiselPropSpec with Matchers with Utils { ChiselStage.emitCHIRRTL(new IOUnwrapped) } } + + property("Incoming/Outgoing should generate an input/output without coercing alignment") { + class Test extends Module { + val in = Incoming(new util.DecoupledIO(UInt(32.W))) + val out = Outgoing(new util.DecoupledIO(UInt(32.W))) + out :<>= in + } + val result = ChiselStage.emitCHIRRTL { new Test } + matchesAndOmits(result)("input in", "output out", "flip ready")() + } + + property("Incoming/Outgoing should fail an input/output") { + class Test extends Module { + val in = Incoming(Flipped(new util.DecoupledIO(UInt(32.W)))) + val out = Outgoing(Flipped(new util.DecoupledIO(UInt(32.W)))) + in :<>= out + } + val (err, _) = grabLog { intercept[ChiselException] { ChiselStage.emitCHIRRTL { new Test } } } + matchesAndOmits(err)( + "Incoming(..) cannot accept a chisel typed which is flipped", + "Outgoing(..) cannot accept a chisel typed which is flipped" + )() + } }