// SPDX-License-Identifier: Apache-2.0 package chisel3.aop.injecting import chisel3.{withClockAndReset, Module, ModuleAspect, RawModule} import chisel3.aop._ import chisel3.internal.{Builder, DynamicContext} import chisel3.internal.firrtl.DefModule import chisel3.stage.{ChiselOptions, DesignAnnotation} import firrtl.annotations.ModuleTarget import firrtl.stage.RunFirrtlTransformAnnotation import firrtl.options.Viewer.view import firrtl.{ir, _} import scala.collection.mutable /** Aspect to inject Chisel code into a module of type M * * @param selectRoots Given top-level module, pick the instances of a module to apply the aspect (root module) * @param injection Function to generate Chisel hardware that will be injected to the end of module m * Signals in m can be referenced and assigned to as if inside m (yes, it is a bit magical) * @tparam T Type of top-level module * @tparam M Type of root module (join point) */ case class InjectingAspect[T <: RawModule, M <: RawModule]( selectRoots: T => Iterable[M], injection: M => Unit) extends InjectorAspect[T, M]( selectRoots, injection ) /** Extend to inject Chisel code into a module of type M * * @param selectRoots Given top-level module, pick the instances of a module to apply the aspect (root module) * @param injection Function to generate Chisel hardware that will be injected to the end of module m * Signals in m can be referenced and assigned to as if inside m (yes, it is a bit magical) * @tparam T Type of top-level module * @tparam M Type of root module (join point) */ abstract class InjectorAspect[T <: RawModule, M <: RawModule]( selectRoots: T => Iterable[M], injection: M => Unit) extends Aspect[T] { final def toAnnotation(top: T): AnnotationSeq = { val moduleNames = Select.allDefinitionsOf[chisel3.experimental.BaseModule](top.toDefinition).map { i => i.toTarget.module }.toSeq toAnnotation(selectRoots(top), top.name, moduleNames) } /** Returns annotations which contain all injection logic * * @param modules The modules to inject into * @param circuit Top level circuit * @param moduleNames The names of all existing modules in the original circuit, to avoid name collisions * @return */ final def toAnnotation(modules: Iterable[M], circuit: String, moduleNames: Seq[String]): AnnotationSeq = { RunFirrtlTransformAnnotation(new InjectingTransform) +: modules.map { module => val chiselOptions = view[ChiselOptions](annotationsInAspect) val dynamicContext = new DynamicContext( annotationsInAspect, chiselOptions.throwOnFirstError, chiselOptions.warnReflectiveNaming, chiselOptions.warningsAsErrors ) // Add existing module names into the namespace. If injection logic instantiates new modules // which would share the same name, they will get uniquified accordingly moduleNames.foreach { n => dynamicContext.globalNamespace.name(n) } val (chiselIR, _) = Builder.build( Module(new ModuleAspect(module) { module match { case x: Module => withClockAndReset(x.clock, x.reset) { injection(module) } case x: RawModule => injection(module) } }), dynamicContext ) val comps = chiselIR.components.map { case x: DefModule if x.name == module.name => x.copy(id = module) case other => other } val annotations = chiselIR.annotations.map(_.toFirrtl).filterNot { a => a.isInstanceOf[DesignAnnotation[_]] } /** Statements to be injected via aspect. */ val stmts = mutable.ArrayBuffer[ir.Statement]() /** Modules to be injected via aspect. */ val modules = Aspect.getFirrtl(chiselIR.copy(components = comps)).modules.flatMap { // for "container" modules, inject their statements case m: firrtl.ir.Module if m.name == module.name => stmts += m.body Nil // for modules to be injected case other: firrtl.ir.DefModule => Seq(other) } InjectStatement(ModuleTarget(circuit, module.name), ir.Block(stmts.toSeq), modules, annotations) }.toSeq } }