diff options
13 files changed, 404 insertions, 21 deletions
diff --git a/chiselFrontend/src/main/scala/chisel3/Bits.scala b/chiselFrontend/src/main/scala/chisel3/Bits.scala index f50512f0..5a6db7c1 100644 --- a/chiselFrontend/src/main/scala/chisel3/Bits.scala +++ b/chiselFrontend/src/main/scala/chisel3/Bits.scala @@ -1183,7 +1183,106 @@ trait SIntFactory { object SInt extends SIntFactory -sealed trait Reset extends Element with ToBoolable +sealed trait Reset extends Element with ToBoolable { + /** Casts this $coll to an [[AsyncReset]] */ + final def asAsyncReset(): AsyncReset = macro SourceInfoWhiteboxTransform.noArg + + /** @group SourceInfoTransformMacro */ + def do_asAsyncReset(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): AsyncReset +} + +object Reset { + def apply(): Reset = new ResetType +} + +/** "Abstract" Reset Type inferred in FIRRTL to either [[AsyncReset]] or [[Bool]] + * + * @note This shares a common interface with [[AsyncReset]] and [[Bool]] but is not their actual + * super type due to Bool inheriting from abstract class UInt + */ +final class ResetType(private[chisel3] val width: Width = Width(1)) extends Element with Reset { + override def toString: String = s"Reset$bindingToString" + + def cloneType: this.type = Reset().asInstanceOf[this.type] + + private[chisel3] def typeEquivalent(that: Data): Boolean = + this.getClass == that.getClass + + override def connect(that: Data)(implicit sourceInfo: SourceInfo, connectCompileOptions: CompileOptions): Unit = that match { + case _: Reset => super.connect(that)(sourceInfo, connectCompileOptions) + case _ => super.badConnect(that)(sourceInfo) + } + + override def litOption = None + + /** Not really supported */ + def toPrintable: Printable = PString("Reset") + + override def do_asUInt(implicit sourceInfo: SourceInfo, connectCompileOptions: CompileOptions): UInt = pushOp(DefPrim(sourceInfo, UInt(this.width), AsUIntOp, ref)) + + private[chisel3] override def connectFromBits(that: Bits)(implicit sourceInfo: SourceInfo, + compileOptions: CompileOptions): Unit = { + this := that + } + + /** @group SourceInfoTransformMacro */ + def do_asAsyncReset(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): AsyncReset = + pushOp(DefPrim(sourceInfo, AsyncReset(), AsAsyncResetOp, ref)) + + /** @group SourceInfoTransformMacro */ + def do_asBool(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Bool = + pushOp(DefPrim(sourceInfo, Bool(), AsUIntOp, ref)) + + /** @group SourceInfoTransformMacro */ + def do_toBool(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Bool = do_asBool +} + +object AsyncReset { + def apply(): AsyncReset = new AsyncReset +} + +/** Data type representing asynchronous reset signals + * + * These signals are similar to [[Clock]]s in that they must be glitch-free for proper circuit + * operation. [[Reg]]s defined with the implicit reset being an [[AsyncReset]] will be + * asychronously reset registers. + */ +sealed class AsyncReset(private[chisel3] val width: Width = Width(1)) extends Element with Reset { + override def toString: String = s"AsyncReset$bindingToString" + + def cloneType: this.type = AsyncReset().asInstanceOf[this.type] + + private[chisel3] def typeEquivalent(that: Data): Boolean = + this.getClass == that.getClass + + override def connect(that: Data)(implicit sourceInfo: SourceInfo, connectCompileOptions: CompileOptions): Unit = that match { + case _: AsyncReset => super.connect(that)(sourceInfo, connectCompileOptions) + case _ => super.badConnect(that)(sourceInfo) + } + + override def litOption = None + + /** Not really supported */ + def toPrintable: Printable = PString("AsyncReset") + + override def do_asUInt(implicit sourceInfo: SourceInfo, connectCompileOptions: CompileOptions): UInt = pushOp(DefPrim(sourceInfo, UInt(this.width), AsUIntOp, ref)) + + // TODO Is this right? + private[chisel3] override def connectFromBits(that: Bits)(implicit sourceInfo: SourceInfo, + compileOptions: CompileOptions): Unit = { + this := that.asBool.asAsyncReset + } + + /** @group SourceInfoTransformMacro */ + def do_asAsyncReset(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): AsyncReset = this + + /** @group SourceInfoTransformMacro */ + def do_asBool(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Bool = + pushOp(DefPrim(sourceInfo, Bool(), AsUIntOp, ref)) + + /** @group SourceInfoTransformMacro */ + def do_toBool(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Bool = do_asBool +} // REVIEW TODO: Why does this extend UInt and not Bits? Does defining airth // operations on a Bool make sense? @@ -1286,6 +1385,10 @@ sealed class Bool() extends UInt(1.W) with Reset { /** @group SourceInfoTransformMacro */ def do_asClock(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Clock = pushOp(DefPrim(sourceInfo, Clock(), AsClockOp, ref)) + + /** @group SourceInfoTransformMacro */ + def do_asAsyncReset(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): AsyncReset = + pushOp(DefPrim(sourceInfo, AsyncReset(), AsAsyncResetOp, ref)) } trait BoolFactory { diff --git a/chiselFrontend/src/main/scala/chisel3/CompileOptions.scala b/chiselFrontend/src/main/scala/chisel3/CompileOptions.scala index 33a41b72..ed410c6e 100644 --- a/chiselFrontend/src/main/scala/chisel3/CompileOptions.scala +++ b/chiselFrontend/src/main/scala/chisel3/CompileOptions.scala @@ -20,6 +20,8 @@ trait CompileOptions { val checkSynthesizable: Boolean // Require explicit assignment of DontCare to generate "x is invalid" val explicitInvalidate: Boolean + // Should the reset type of Module be a Bool or a Reset + val inferModuleReset: Boolean } object CompileOptions { @@ -48,7 +50,9 @@ object ExplicitCompileOptions { // Check that referenced Data have actually been declared. val checkSynthesizable: Boolean, // Require an explicit DontCare assignment to generate a firrtl DefInvalid - val explicitInvalidate: Boolean + val explicitInvalidate: Boolean, + // Should the reset type of Module be a Bool or a Reset + val inferModuleReset: Boolean ) extends CompileOptions // Collection of "not strict" connection compile options. @@ -59,7 +63,8 @@ object ExplicitCompileOptions { dontTryConnectionsSwapped = false, dontAssumeDirectionality = false, checkSynthesizable = false, - explicitInvalidate = false + explicitInvalidate = false, + inferModuleReset = false ) // Collection of "strict" connection compile options, preferred for new code. @@ -69,6 +74,7 @@ object ExplicitCompileOptions { dontTryConnectionsSwapped = true, dontAssumeDirectionality = true, checkSynthesizable = true, - explicitInvalidate = true + explicitInvalidate = true, + inferModuleReset = true ) } diff --git a/chiselFrontend/src/main/scala/chisel3/RawModule.scala b/chiselFrontend/src/main/scala/chisel3/RawModule.scala index 6d316074..8f201ce6 100644 --- a/chiselFrontend/src/main/scala/chisel3/RawModule.scala +++ b/chiselFrontend/src/main/scala/chisel3/RawModule.scala @@ -143,7 +143,11 @@ abstract class MultiIOModule(implicit moduleCompileOptions: CompileOptions) extends RawModule { // Implicit clock and reset pins val clock: Clock = IO(Input(Clock())) - val reset: Reset = IO(Input(Bool())) + val reset: Reset = { + // Top module and compatibility mode use Bool for reset + val inferReset = _parent.isDefined && moduleCompileOptions.inferModuleReset + IO(Input(if (inferReset) Reset() else Bool())) + } // Setup ClockAndReset Builder.currentClock = Some(clock) @@ -219,14 +223,7 @@ abstract class LegacyModule(implicit moduleCompileOptions: CompileOptions) pushCommand(DefInvalid(sourceInfo, io.ref)) } - override_clock match { - case Some(override_clock) => clock := override_clock - case _ => clock := Builder.forcedClock - } - - override_reset match { - case Some(override_reset) => reset := override_reset - case _ => reset := Builder.forcedReset - } + clock := override_clock.getOrElse(Builder.forcedClock) + reset := override_reset.getOrElse(Builder.forcedReset) } } diff --git a/chiselFrontend/src/main/scala/chisel3/Reg.scala b/chiselFrontend/src/main/scala/chisel3/Reg.scala index 8d3a915d..51c59bdb 100644 --- a/chiselFrontend/src/main/scala/chisel3/Reg.scala +++ b/chiselFrontend/src/main/scala/chisel3/Reg.scala @@ -152,12 +152,12 @@ object RegInit { requireIsChiselType(t, "reg type") } val reg = t.cloneTypeFull - val clock = Builder.forcedClock.ref - val reset = Builder.forcedReset.ref + val clock = Builder.forcedClock + val reset = Builder.forcedReset reg.bind(RegBinding(Builder.forcedUserModule)) requireIsHardware(init, "reg initializer") - pushCommand(DefRegInit(sourceInfo, reg, clock, reset, init.ref)) + pushCommand(DefRegInit(sourceInfo, reg, clock.ref, reset.ref, init.ref)) reg } diff --git a/chiselFrontend/src/main/scala/chisel3/internal/MonoConnect.scala b/chiselFrontend/src/main/scala/chisel3/internal/MonoConnect.scala index e07f980d..ace7be20 100644 --- a/chiselFrontend/src/main/scala/chisel3/internal/MonoConnect.scala +++ b/chiselFrontend/src/main/scala/chisel3/internal/MonoConnect.scala @@ -87,6 +87,10 @@ private[chisel3] object MonoConnect { elemConnect(sourceInfo, connectCompileOptions, sink_e, source_e, context_mod) case (sink_e: Clock, source_e: Clock) => elemConnect(sourceInfo, connectCompileOptions, sink_e, source_e, context_mod) + case (sink_e: AsyncReset, source_e: AsyncReset) => + elemConnect(sourceInfo, connectCompileOptions, sink_e, source_e, context_mod) + case (sink_e: ResetType, source_e: Reset) => + elemConnect(sourceInfo, connectCompileOptions, sink_e, source_e, context_mod) case (sink_e: EnumType, source_e: UnsafeEnum) => elemConnect(sourceInfo, connectCompileOptions, sink_e, source_e, context_mod) case (sink_e: EnumType, source_e: EnumType) if sink_e.typeEquivalent(source_e) => diff --git a/chiselFrontend/src/main/scala/chisel3/internal/firrtl/Converter.scala b/chiselFrontend/src/main/scala/chisel3/internal/firrtl/Converter.scala index cdc55b59..5309609b 100644 --- a/chiselFrontend/src/main/scala/chisel3/internal/firrtl/Converter.scala +++ b/chiselFrontend/src/main/scala/chisel3/internal/firrtl/Converter.scala @@ -214,6 +214,8 @@ private[chisel3] object Converter { def extractType(data: Data, clearDir: Boolean = false): fir.Type = data match { // scalastyle:ignore cyclomatic.complexity line.size.limit case _: Clock => fir.ClockType + case _: AsyncReset => fir.AsyncResetType + case _: ResetType => fir.ResetType case d: EnumType => fir.UIntType(convert(d.width)) case d: UInt => fir.UIntType(convert(d.width)) case d: SInt => fir.SIntType(convert(d.width)) diff --git a/chiselFrontend/src/main/scala/chisel3/internal/firrtl/IR.scala b/chiselFrontend/src/main/scala/chisel3/internal/firrtl/IR.scala index 2cb4d092..e4b660dd 100644 --- a/chiselFrontend/src/main/scala/chisel3/internal/firrtl/IR.scala +++ b/chiselFrontend/src/main/scala/chisel3/internal/firrtl/IR.scala @@ -47,6 +47,7 @@ object PrimOp { val AsFixedPointOp = PrimOp("asFixedPoint") val SetBinaryPoint = PrimOp("bpset") val AsClockOp = PrimOp("asClock") + val AsAsyncResetOp = PrimOp("asAsyncReset") } abstract class Arg { diff --git a/src/main/scala/chisel3/internal/firrtl/Emitter.scala b/src/main/scala/chisel3/internal/firrtl/Emitter.scala index 2f1b75b0..3d10670e 100644 --- a/src/main/scala/chisel3/internal/firrtl/Emitter.scala +++ b/src/main/scala/chisel3/internal/firrtl/Emitter.scala @@ -27,7 +27,9 @@ private class Emitter(circuit: Circuit) { private def emitType(d: Data, clearDir: Boolean = false): String = d match { // scalastyle:ignore cyclomatic.complexity line.size.limit case d: Clock => "Clock" - case d: EnumType => s"UInt${d.width}" + case _: AsyncReset => "AsyncReset" + case _: ResetType => "Reset" + case d: chisel3.core.EnumType => s"UInt${d.width}" case d: UInt => s"UInt${d.width}" case d: SInt => s"SInt${d.width}" case d: FixedPoint => s"Fixed${d.width}${d.binaryPoint}" diff --git a/src/test/scala/chiselTests/AsyncResetSpec.scala b/src/test/scala/chiselTests/AsyncResetSpec.scala new file mode 100644 index 00000000..78a29e99 --- /dev/null +++ b/src/test/scala/chiselTests/AsyncResetSpec.scala @@ -0,0 +1,172 @@ +// See LICENSE for license details. + +package chiselTests + +import chisel3._ +import chisel3.util.{Counter, Queue} +import chisel3.testers.BasicTester +import firrtl.checks.CheckResets.NonLiteralAsyncResetValueException + +class AsyncResetTester extends BasicTester { + val (_, cDiv) = Counter(true.B, 4) + // First rising edge when count === 3 + val slowClk = cDiv.asClock + + val (count, done) = Counter(true.B, 16) + + val asyncResetNext = RegInit(false.B) + asyncResetNext := count === 4.U + val asyncReset = asyncResetNext.asAsyncReset + + val reg = withClockAndReset(slowClk, asyncReset) { + RegInit(123.U(8.W)) + } + reg := 5.U // Normal connection + + when (count === 3.U) { + assert(reg === 5.U) + } + when (count >= 5.U && count < 7.U) { + assert(reg === 123.U) + } .elsewhen (count >= 7.U) { + assert(reg === 5.U) + } + + when (done) { + stop() + } +} + +class AsyncResetAggregateTester extends BasicTester { + class MyBundle extends Bundle { + val x = UInt(8.W) + val y = UInt(8.W) + } + val (_, cDiv) = Counter(true.B, 4) + // First rising edge when count === 3 + val slowClk = cDiv.asClock + + val (count, done) = Counter(true.B, 16) + + val asyncResetNext = RegInit(false.B) + asyncResetNext := count === 4.U + val asyncReset = asyncResetNext.asAsyncReset + + val reg = withClockAndReset(slowClk, asyncReset) { + val init = Wire(Vec(2, new MyBundle)) + init(0).x := 0.U + init(0).y := 0.U + init(1).x := 0.U + init(1).y := 0.U + RegInit(init) + } + reg(0).x := 5.U // Normal connections + reg(0).y := 6.U + reg(1).x := 7.U + reg(1).y := 8.U + + when (count === 3.U) { + assert(reg(0).x === 5.U) + assert(reg(0).y === 6.U) + assert(reg(1).x === 7.U) + assert(reg(1).y === 8.U) + } + when (count >= 5.U && count < 7.U) { + assert(reg(0).x === 0.U) + assert(reg(0).y === 0.U) + assert(reg(1).x === 0.U) + assert(reg(1).y === 0.U) + } .elsewhen (count >= 7.U) { + assert(reg(0).x === 5.U) + assert(reg(0).y === 6.U) + assert(reg(1).x === 7.U) + assert(reg(1).y === 8.U) + } + + when (done) { + stop() + } +} + +class AsyncResetQueueTester extends BasicTester { + val (_, cDiv) = Counter(true.B, 4) + val slowClk = cDiv.asClock + + val (count, done) = Counter(true.B, 16) + + val asyncResetNext = RegNext(false.B, false.B) + val asyncReset = asyncResetNext.asAsyncReset + + val queue = withClockAndReset (slowClk, asyncReset) { + Module(new Queue(UInt(8.W), 4)) + } + queue.io.enq.valid := true.B + queue.io.enq.bits := count + + queue.io.deq.ready := false.B + + val doCheck = RegNext(false.B, false.B) + when (queue.io.count === 3.U) { + asyncResetNext := true.B + doCheck := true.B + } + when (doCheck) { + assert(queue.io.count === 0.U) + } + + when (done) { + stop() + } +} + +class AsyncResetSpec extends ChiselFlatSpec { + + behavior of "AsyncReset" + + it should "be allowed with literal reset values" in { + elaborate(new BasicTester { + withReset(reset.asAsyncReset)(RegInit(123.U)) + }) + } + + it should "NOT be allowed with non-literal reset values" in { + a [NonLiteralAsyncResetValueException] shouldBe thrownBy { + compile(new BasicTester { + val x = WireInit(123.U + 456.U) + withReset(reset.asAsyncReset)(RegInit(x)) + }) + } + } + + it should "NOT be allowed to connect directly to a Bool" in { + a [ChiselException] shouldBe thrownBy { + elaborate(new BasicTester { + val bool = Wire(Bool()) + val areset = reset.asAsyncReset + bool := areset + }) + } + } + + it should "simulate correctly" in { + assertTesterPasses(new AsyncResetTester) + } + + it should "simulate correctly with aggregates" in { + assertTesterPasses(new AsyncResetAggregateTester) + } + + it should "allow casting to and from Bool" in { + elaborate(new BasicTester { + val r: Reset = reset + val a: AsyncReset = WireInit(r.asAsyncReset) + val b: Bool = a.asBool + val c: AsyncReset = b.asAsyncReset + }) + } + + it should "allow changing the reset type of whole modules like Queue" in { + assertTesterPasses(new AsyncResetQueueTester) + } + +} diff --git a/src/test/scala/chiselTests/CloneModuleSpec.scala b/src/test/scala/chiselTests/CloneModuleSpec.scala index 59ba2eb5..ca8bd007 100644 --- a/src/test/scala/chiselTests/CloneModuleSpec.scala +++ b/src/test/scala/chiselTests/CloneModuleSpec.scala @@ -9,7 +9,7 @@ import chisel3.testers.BasicTester class MultiIOQueue[T <: Data](gen: T, val entries: Int) extends MultiIOModule { val clk = IO(Input(Clock())) - val rst = IO(Input(Bool())) + val rst = IO(Input(Reset())) val enq = IO(Flipped(EnqIO(gen))) val deq = IO(Flipped(DeqIO(gen))) val count = IO(Output(UInt(log2Ceil(entries + 1).W))) @@ -28,7 +28,7 @@ class QueueClone(multiIO: Boolean = false) extends Module { q1.rst := reset q1.enq <> io.enq q2_io("clk").asInstanceOf[Clock] := clock - q2_io("rst").asInstanceOf[Bool] := reset + q2_io("rst").asInstanceOf[Reset] := reset q2_io("enq").asInstanceOf[q1.enq.type] <> q1.deq io.deq <> q2_io("deq").asInstanceOf[q1.deq.type] io.count := q1.count + q2_io("count").asInstanceOf[q1.count.type] diff --git a/src/test/scala/chiselTests/CompatibilityInteroperabilitySpec.scala b/src/test/scala/chiselTests/CompatibilityInteroperabilitySpec.scala index 861b3fdd..4ca7dcda 100644 --- a/src/test/scala/chiselTests/CompatibilityInteroperabilitySpec.scala +++ b/src/test/scala/chiselTests/CompatibilityInteroperabilitySpec.scala @@ -243,5 +243,51 @@ class CompatibiltyInteroperabilitySpec extends ChiselFlatSpec { } } } + + "Compatibility Modules" should "have Bool as their reset type" in { + compile { + import Chisel._ + class Intf extends Bundle { + val in = Bool(INPUT) + val en = Bool(INPUT) + val out = Bool(OUTPUT) + } + class Child extends Module { + val io = new Intf + io.out := Mux(io.en, io.in, reset) + } + new Module { + val io = new Intf + val child = Module(new Child) + io <> child.io + } + } + } + + "Compatibility Modules" should "be instantiable inside chisel3 Modules" in { + compile { + object Compat { + import Chisel._ + class Intf extends Bundle { + val in = Input(UInt(8.W)) + val out = Output(UInt(8.W)) + } + class OldMod extends Module { + val io = IO(new Intf) + io.out := Reg(next = io.in) + } + } + import chisel3._ + import Compat._ + new Module { + val io = IO(new Intf) + io <> Module(new Module { + val io = IO(new Intf) + val inst = Module(new OldMod) + io <> inst.io + }).io + } + } + } } diff --git a/src/test/scala/chiselTests/ResetSpec.scala b/src/test/scala/chiselTests/ResetSpec.scala new file mode 100644 index 00000000..297c5516 --- /dev/null +++ b/src/test/scala/chiselTests/ResetSpec.scala @@ -0,0 +1,50 @@ +// See LICENSE for license details. + +package chiselTests + +import chisel3._ +import chisel3.experimental.{IO, RawModule} +import chisel3.util.{Counter, Queue} +import chisel3.testers.BasicTester + +class ResetAgnosticModule extends RawModule { + val clk = IO(Input(Clock())) + val rst = IO(Input(Reset())) + val out = IO(Output(UInt(8.W))) + + val reg = withClockAndReset(clk, rst)(RegInit(0.U(8.W))) + reg := reg + 1.U + out := reg +} + + +class ResetSpec extends ChiselFlatSpec { + + behavior of "Reset" + + it should "allow writing modules that are reset agnostic" in { + val sync = compile(new Module { + val io = IO(new Bundle { + val out = Output(UInt(8.W)) + }) + val inst = Module(new ResetAgnosticModule) + inst.clk := clock + inst.rst := reset + assert(inst.rst.isInstanceOf[chisel3.ResetType]) + io.out := inst.out + }) + sync should include ("always @(posedge clk)") + + val async = compile(new Module { + val io = IO(new Bundle { + val out = Output(UInt(8.W)) + }) + val inst = Module(new ResetAgnosticModule) + inst.clk := clock + inst.rst := reset.asTypeOf(AsyncReset()) + assert(inst.rst.isInstanceOf[chisel3.ResetType]) + io.out := inst.out + }) + async should include ("always @(posedge clk or posedge rst)") + } +} diff --git a/src/test/scala/examples/SimpleVendingMachine.scala b/src/test/scala/examples/SimpleVendingMachine.scala index 4bb6027a..2021ece8 100644 --- a/src/test/scala/examples/SimpleVendingMachine.scala +++ b/src/test/scala/examples/SimpleVendingMachine.scala @@ -52,7 +52,7 @@ class VerilogVendingMachine extends BlackBox { // Because this is a blackbox, we must explicity add clock and reset val io = IO(new SimpleVendingMachineIO { val clock = Input(Clock()) - val reset = Input(Bool()) + val reset = Input(Reset()) }) } |
