diff --git a/include/circt/Dialect/FIRRTL/FIRRTLUtils.h b/include/circt/Dialect/FIRRTL/FIRRTLUtils.h index 3eea97b0d64c..9bc3b8b42741 100644 --- a/include/circt/Dialect/FIRRTL/FIRRTLUtils.h +++ b/include/circt/Dialect/FIRRTL/FIRRTLUtils.h @@ -74,6 +74,22 @@ class TieOffCache { SmallDenseMap cache; }; +// Instance choice option case macro name utilities. +class InstanceChoiceMacroTable { +public: + InstanceChoiceMacroTable(Operation *op); + + // Get the macro for an option case. Return null if it doesn't exist. + FlatSymbolRefAttr getMacro(StringAttr optionName, StringAttr caseName) const; + + // Get all option/case pairs in the IR occurrence order. + auto getKeys() const { return cache.keys(); } + +private: + // Option/Case -> Macro Symbol + llvm::MapVector, FlatSymbolRefAttr> cache; +}; + //===----------------------------------------------------------------------===// // Template utilities //===----------------------------------------------------------------------===// diff --git a/integration_test/Dialect/FIRRTL/instance-choice.fir b/integration_test/Dialect/FIRRTL/instance-choice.fir new file mode 100644 index 000000000000..1d71c784c3e0 --- /dev/null +++ b/integration_test/Dialect/FIRRTL/instance-choice.fir @@ -0,0 +1,70 @@ +; REQUIRES: verilator +; This test verifies that the instance choice header inclusion mechanism works correctly +; with probes to access internal signals: + +; RUN: rm -rf %t && mkdir -p %t +; RUN: firtool %s --split-verilog -o=%t +; Check it errors when including both headers. +; RUN: not verilator %t/targets_top_Platform_ASIC.svh %t/targets_top_Platform_FPGA.svh %t/ref_top.sv %t/top.sv %t/ASICTarget.sv %t/FPGATarget.sv %t/DefaultTarget.sv --lint-only --top-module top 2>&1 | FileCheck %s --check-prefix=ERROR +; RUN: verilator %driver %t/targets_top_Platform_ASIC.svh %t/ref_top.sv %t/top.sv %t/ASICTarget.sv %t/FPGATarget.sv %t/DefaultTarget.sv --cc --sv --exe --build -o %t.asic.exe --top-module top +; RUN: %t.asic.exe --cycles 1 2>&1 | FileCheck %s --check-prefix=ASIC +; RUN: verilator %driver %t/targets_top_Platform_FPGA.svh %t/ref_top.sv %t/top.sv %t/ASICTarget.sv %t/FPGATarget.sv %t/DefaultTarget.sv --cc --sv --exe --build -o %t.fpga.exe --top-module top +; RUN: %t.fpga.exe --cycles 1 2>&1 | FileCheck %s --check-prefix=FPGA +; RUN: verilator %driver %t/ref_top.sv %t/top.sv %t/ASICTarget.sv %t/FPGATarget.sv %t/DefaultTarget.sv --cc --sv --exe --build -o %t.default.exe --top-module top +; RUN: %t.default.exe --cycles 1 2>&1 | FileCheck %s --check-prefix=DEFAULT +; +; ERROR: must__not__be__set +; +; ASIC: result: 10 +; ASIC: internal: 15 +; FPGA: result: 20 +; FPGA: internal: 25 +; DEFAULT: result: 0 +; DEFAULT: internal: 5 + +FIRRTL version 5.1.0 +circuit top: + option Platform: + FPGA + ASIC + + module DefaultTarget: + output result: UInt<32> + output internal_probe: Probe> + + wire internal: UInt<32> + connect internal, UInt<32>(5) + connect result, UInt<32>(0) + define internal_probe = probe(internal) + + module ASICTarget: + output result: UInt<32> + output internal_probe: Probe> + + wire internal: UInt<32> + connect internal, UInt<32>(15) + connect result, UInt<32>(10) + define internal_probe = probe(internal) + + module FPGATarget: + output result: UInt<32> + output internal_probe: Probe> + + wire internal: UInt<32> + connect internal, UInt<32>(25) + connect result, UInt<32>(20) + define internal_probe = probe(internal) + + public module top: + input clk: Clock + input rst: UInt<1> + + instchoice proc of DefaultTarget, Platform: + ASIC => ASICTarget + FPGA => FPGATarget + + wire probed_value: UInt<32> + connect probed_value, read(proc.internal_probe) + + printf(clk, UInt<1>(1), "result: %d\n", proc.result) + printf(clk, UInt<1>(1), "internal: %d\n", probed_value) diff --git a/lib/Conversion/FIRRTLToHW/LowerToHW.cpp b/lib/Conversion/FIRRTLToHW/LowerToHW.cpp index bcebc2d8e858..fb19bb891ffc 100644 --- a/lib/Conversion/FIRRTLToHW/LowerToHW.cpp +++ b/lib/Conversion/FIRRTLToHW/LowerToHW.cpp @@ -251,10 +251,12 @@ struct CircuitLoweringState { CircuitLoweringState(CircuitOp circuitOp, bool enableAnnotationWarning, firrtl::VerificationFlavor verificationFlavor, - InstanceGraph &instanceGraph, NLATable *nlaTable) + InstanceGraph &instanceGraph, NLATable *nlaTable, + const InstanceChoiceMacroTable ¯oTable) : circuitOp(circuitOp), instanceGraph(instanceGraph), enableAnnotationWarning(enableAnnotationWarning), - verificationFlavor(verificationFlavor), nlaTable(nlaTable) { + verificationFlavor(verificationFlavor), nlaTable(nlaTable), + macroTable(macroTable) { auto *context = circuitOp.getContext(); // Get the testbench output directory. @@ -465,6 +467,32 @@ struct CircuitLoweringState { macroDeclNames.insert(name); } + /// Information about an instance choice for a specific option case. + struct LoweredInstanceChoice { + StringAttr parentModule; + // The instance macro (macro name) for this instance choice + FlatSymbolRefAttr instanceMacro; + hw::InstanceOp hwInstance; + }; + + using OptionAndCase = std::pair; + + // Map from moduleName to (optionName, caseName) to list of instance choices. + DenseMap>> + instanceChoicesByModuleAndCase; + std::mutex instanceChoicesMutex; + + void addInstanceChoiceForCase(StringAttr optionName, StringAttr caseName, + StringAttr parentModule, + FlatSymbolRefAttr instanceMacro, + hw::InstanceOp hwInstance) { + OptionAndCase innerKey{optionName, caseName}; + std::unique_lock lock(instanceChoicesMutex); + instanceChoicesByModuleAndCase[parentModule][innerKey].push_back( + {parentModule, instanceMacro, hwInstance}); + } + /// The list of fragments on which the modules rely. Must be set outside the /// parallelized module lowering since module type reads access it. DenseMap> fragments; @@ -559,6 +587,9 @@ struct CircuitLoweringState { // emit.files for additional sources for verbatim extmodules llvm::StringMap emitFilesByFileName; llvm::sys::SmartMutex emitFilesMutex; + + // Instance choice macro table for looking up option case macros + const InstanceChoiceMacroTable ¯oTable; }; void CircuitLoweringState::processRemainingAnnotations( @@ -624,6 +655,14 @@ struct FIRRTLModuleLowering private: void lowerFileHeader(CircuitOp op, CircuitLoweringState &loweringState); + void emitInstanceChoiceIncludes(mlir::ModuleOp circuit, + CircuitLoweringState &loweringState); + static void emitInstanceChoiceIncludeFile( + OpBuilder &builder, ModuleOp circuit, StringAttr publicModuleName, + StringAttr optionName, StringAttr caseName, + ArrayRef instances, + Namespace &circuitNamespace, const InstanceChoiceMacroTable ¯oTable); + LogicalResult lowerPorts(ArrayRef firrtlPorts, SmallVectorImpl &ports, Operation *moduleOp, StringRef moduleName, @@ -694,7 +733,8 @@ void FIRRTLModuleLowering::runOnOperation() { // if lowering failed. CircuitLoweringState state(circuit, enableAnnotationWarning, verificationFlavor, getAnalysis(), - &getAnalysis()); + &getAnalysis(), + getAnalysis()); SmallVector opsToProcess; @@ -866,6 +906,11 @@ void FIRRTLModuleLowering::runOnOperation() { // Emit all the macros and preprocessor gunk at the start of the file. lowerFileHeader(circuit, state); + // Emit global include files for instance choice options. + // Make sure to call after `lowerFileHeader` so that symbols generated for + // instance choices don't conflict with the macros defined in the header. + emitInstanceChoiceIncludes(getOperation(), state); + // Now that the modules are moved over, remove the Circuit. circuit.erase(); } @@ -1015,6 +1060,144 @@ endpackage } } +/// Helper function to emit a single instance choice include file for a given +/// (option, case) combination. +void FIRRTLModuleLowering::emitInstanceChoiceIncludeFile( + OpBuilder &builder, mlir::ModuleOp circuit, StringAttr publicModuleName, + StringAttr optionName, StringAttr caseName, + ArrayRef instances, + Namespace &circuitNamespace, const InstanceChoiceMacroTable ¯oTable) { + // If no instances, don't emit anything. + if (instances.empty()) + return; + + // Filename format: targets__