Skip to content

Commit

Permalink
Add Disable, a new API for disabling simulation constructs
Browse files Browse the repository at this point in the history
  • Loading branch information
jackkoenig committed Aug 18, 2023
1 parent 468cc3a commit 9a213d1
Show file tree
Hide file tree
Showing 6 changed files with 245 additions and 1 deletion.
6 changes: 6 additions & 0 deletions core/src/main/scala/chisel3/Bits.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1016,6 +1016,12 @@ sealed trait Reset extends Element with ToBoolable {

/** @group SourceInfoTransformMacro */
def do_asAsyncReset(implicit sourceInfo: SourceInfo): AsyncReset

/** Casts this $coll to a [[Disable]] */
final def asDisable: Disable = macro SourceInfoWhiteboxTransform.noArg

/** @group SourceInfoTransformMacro */
def do_asDisable(implicit sourceInfo: SourceInfo): Disable = new Disable(this.asBool)
}

object Reset {
Expand Down
77 changes: 77 additions & 0 deletions core/src/main/scala/chisel3/Disable.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// SPDX-License-Identifier: Apache-2.0

package chisel3

import chisel3.internal._
import chisel3.experimental.{IntrinsicModule, OpaqueType, SourceInfo}
import chisel3.internal.sourceinfo.SourceInfoTransform

import scala.language.experimental.macros
import scala.collection.immutable.ListMap

/** API for handling disabling of simulation constructs
*
* Disables may be non-synthesizable so they can only be used for disabling simulation constructs
*
* The default disable is the "hasBeenReset" of the currently in scope reset.
* It can be set by the user via the [[withDisable]] API
*
* Users can access the current `Disable` with [[Module.disable]]
*/
// We could just an OpaqueType, but since OpaqueTypes have some API holes, this non-Data type
class Disable private[chisel3] (private[chisel3] val value: Bool) {

/** Logical not
*
* @return invert the logical value of this `Disable`
* @group Bitwise
*/
final def unary_! : Disable = macro SourceInfoTransform.noArg

/** @group SourceInfoTransformMacro */
def do_unary_!(implicit sourceInfo: SourceInfo): Disable = new Disable(!this.value)
}

object Disable {

sealed trait Option
case object None extends Option
case object NotHasBeenReset extends Option

private[chisel3] def withDisable[T](option: Option)(block: => T): T = {
// Save parentScope
val parentDisable = Builder.currentDisable

Builder.currentDisable = option

val res = block // execute block

// Return to old scope
Builder.currentDisable = parentDisable
res
}
}

/** Creates a new [[Disable]] scope */
object withDisable {

/** Creates a new Disable scope
*
* @param disable an Optional new implicit Disable, None means no disable
* @param block the block of code to run with new implicit Disable
* @return the result of the block
*/
def apply[T](disable: Disable.Option)(block: => T): T = Disable.withDisable(disable)(block)
}

/**
*/
// Note because this uses abstract reset, it cannot be instantiable until we have the ability to
// create both sync and sync reset instances from the same Definition
private[chisel3] class HasBeenResetIntrinsic(implicit sourceInfo: SourceInfo)
extends IntrinsicModule(f"circt_has_been_reset") {
// Compiler plugin does not run on core so we have to suggest names
val clock = IO(Input(Clock())).suggestName("clock")
val reset = IO(Input(Reset())).suggestName("reset")
val out = IO(Output(Bool())).suggestName("out")
}
31 changes: 31 additions & 0 deletions core/src/main/scala/chisel3/Module.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import chisel3.properties.Class
import chisel3.reflect.DataMirror
import _root_.firrtl.annotations.{IsModule, ModuleName, ModuleTarget}
import _root_.firrtl.AnnotationSeq
import chisel3.internal.plugin.autoNameRecursively

object Module extends SourceInfoDoc {

Expand All @@ -41,6 +42,8 @@ object Module extends SourceInfoDoc {
val parentWhenStack = Builder.whenStack

// Save then clear clock and reset to prevent leaking scope, must be set again in the Module
// Note that Disable is a function of whatever the current reset is, so it does not need a port
// and thus does not change when we cross module boundaries
val (saveClock, saveReset) = (Builder.currentClock, Builder.currentReset)
val savePrefix = Builder.getPrefix
Builder.clearPrefix()
Expand Down Expand Up @@ -107,6 +110,32 @@ object Module extends SourceInfoDoc {
/** Returns the implicit Reset, if it is defined */
def resetOption: Option[Reset] = Builder.currentReset

/** Returns the current implicit [[Disable]], if one is defined
*
* Note that [[Disable]] is a function of the implicit clock and reset
* so having no implicit clock or reset may imply no `Disable`.
*/
def disable(implicit sourceInfo: SourceInfo): Option[Disable] = {
Builder.currentDisable match {
case Disable.None => None
case Disable.NotHasBeenReset => hasBeenReset.map(x => autoNameRecursively("disable")(!x))
}
}

// Should this be public or should users just go through .disable?
// Note that having a reset but not clock means hasBeenReset is None, should we default to just !reset?
private def hasBeenReset(implicit sourceInfo: SourceInfo): Option[Disable] = {
// TODO memoize this
(Builder.currentClock, Builder.currentReset) match {
case (Some(clock), Some(reset)) =>
val hasBeenReset = Module(new HasBeenResetIntrinsic)
hasBeenReset.clock := clock
hasBeenReset.reset := reset
Some(new Disable(hasBeenReset.out))
case _ => None
}
}

/** Returns the current Module */
def currentModule: Option[BaseModule] = Builder.currentModule

Expand Down Expand Up @@ -178,6 +207,7 @@ abstract class Module extends RawModule {
// Implicit clock and reset pins
final val clock: Clock = IO(Input(Clock()))(UnlocatableSourceInfo).suggestName("clock")
final val reset: Reset = IO(Input(mkReset))(UnlocatableSourceInfo).suggestName("reset")
// TODO add a way to memoize hasBeenReset iff it is used

// TODO It's hard to remove these deprecated override methods because they're used by
// Chisel.QueueCompatibility which extends chisel3.Queue which extends chisel3.Module
Expand Down Expand Up @@ -213,6 +243,7 @@ abstract class Module extends RawModule {
// Setup ClockAndReset
Builder.currentClock = Some(clock)
Builder.currentReset = Some(reset)
// Note that we do no such setup for disable, it will default to hasBeenReset of the currentReset
Builder.clearPrefix()

private[chisel3] override def initializeInParent(): Unit = {
Expand Down
7 changes: 7 additions & 0 deletions core/src/main/scala/chisel3/internal/Builder.scala
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,7 @@ private[chisel3] class DynamicContext(
var whenStack: List[WhenContext] = Nil
var currentClock: Option[Clock] = None
var currentReset: Option[Reset] = None
var currentDisable: Disable.Option = Disable.NotHasBeenReset
val errors = new ErrorLog(warningFilters, sourceRoots, throwOnFirstError)
val namingStack = new NamingStack

Expand Down Expand Up @@ -742,6 +743,11 @@ private[chisel3] object Builder extends LazyLogging {
dynamicContext.currentReset = newReset
}

def currentDisable: Disable.Option = dynamicContext.currentDisable
def currentDisable_=(newDisable: Disable.Option): Unit = {
dynamicContext.currentDisable = newDisable
}

def inDefinition: Boolean = {
dynamicContextVar.value
.map(_.inDefinition)
Expand Down Expand Up @@ -790,6 +796,7 @@ private[chisel3] object Builder extends LazyLogging {
val name = fullName.stripPrefix("_")
nameRecursively(s"${prefix}_${name}", elt, namer)
}
case disable: Disable => nameRecursively(prefix, disable.value, namer)
case _ => // Do nothing
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,8 @@ class ChiselComponent(val global: Global, arguments: ChiselPluginArguments)
tq"chisel3.BaseType",
tq"chisel3.MemBase[_]",
tq"chisel3.VerificationStatement",
tq"chisel3.properties.DynamicObject"
tq"chisel3.properties.DynamicObject",
tq"chisel3.Disable"
)
private val shouldMatchModule: Type => Boolean = shouldMatchGen(tq"chisel3.experimental.BaseModule")
private val shouldMatchInstance: Type => Boolean = shouldMatchGen(tq"chisel3.experimental.hierarchy.Instance[_]")
Expand Down
122 changes: 122 additions & 0 deletions src/test/scala/chiselTests/DisableSpec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
// SPDX-License-Identifier: Apache-2.0

package chiselTests

import chisel3._
import chisel3.testers.BasicTester
import _root_.circt.stage.ChiselStage

import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers
import chisel3.experimental.prefix

class DisableSpec extends AnyFlatSpec with Matchers {

behavior.of("Disable")

it should "should be None by default in a RawModule" in {
ChiselStage.emitCHIRRTL(new RawModule {
Module.disable should be(None)
})
}

it should "default to hasBeenReset in a Module" in {
val chirrtl = ChiselStage.emitCHIRRTL(new Module {
override def desiredName = "Top"
val doDisable = Module.disable
})
chirrtl should include("intmodule HasBeenResetIntrinsic :")
chirrtl should include("input clock : Clock")
chirrtl should include("input reset : Reset")
chirrtl should include("output out : UInt<1>")
chirrtl should include("intrinsic = circt_has_been_reset")
chirrtl should include("module Top :")
chirrtl should include("inst HasBeenResetIntrinsic of HasBeenResetIntrinsic")
chirrtl should include("connect HasBeenResetIntrinsic.clock, clock")
chirrtl should include("connect HasBeenResetIntrinsic.reset, reset")
chirrtl should include("node doDisable = eq(HasBeenResetIntrinsic.out, UInt<1>(0h0))")
}

it should "be None when there is a clock but no reset" in {
ChiselStage.emitCHIRRTL(new RawModule {
val clk = IO(Input(Clock()))
withClock(clk) {
Module.disable should be(None)
}
})
}

it should "be None when there is a reset but no clock" in {
ChiselStage.emitCHIRRTL(new RawModule {
val rst = IO(Input(AsyncReset()))
withReset(rst) {
Module.disable should be(None)
}
})
}

it should "be defined when there is a clock and a reset" in {
ChiselStage.emitCHIRRTL(new RawModule {
val clk = IO(Input(Clock()))
val rst = IO(Input(AsyncReset()))
withClockAndReset(clk, rst) {
assert(Module.disable.isDefined)
}
})
}

it should "be possible to set it to None" in {
ChiselStage.emitCHIRRTL(new Module {
assert(Module.disable.isDefined)
withDisable(Disable.None) {
Module.disable should be(None)
}
assert(Module.disable.isDefined)
})
}

it should "setting should propagate across module boundaries" in {
ChiselStage.emitCHIRRTL(new Module {
assert(Module.disable.isDefined)
withDisable(Disable.None) {
Module.disable should be(None)
val inst = Module(new Module {
Module.disable should be(None)
})
}
assert(Module.disable.isDefined)
})
}

it should "be setable back to notHasBeenReset" in {
ChiselStage.emitCHIRRTL(new Module {
assert(Module.disable.isDefined)
withDisable(Disable.None) {
Module.disable should be(None)
val inst = Module(new Module {
Module.disable should be(None)
withDisable(Disable.NotHasBeenReset) {
assert(Module.disable.isDefined)
}
})
}
assert(Module.disable.isDefined)
})
}

it should "default the node name to disable" in {
val chirrtl = ChiselStage.emitCHIRRTL(new Module {
Module.disable // No name given
})
chirrtl should include("node disable = eq(HasBeenResetIntrinsic.out, UInt<1>(0h0))")
}

it should "be impacted by prefix" in {
val chirrtl = ChiselStage.emitCHIRRTL(new Module {
prefix("foo") {
Module.disable // No name given
}
})
chirrtl should include("node foo_disable = eq(HasBeenResetIntrinsic.out, UInt<1>(0h0))")
}
}

0 comments on commit 9a213d1

Please sign in to comment.