CIRCTの続き。CIRCTにはMLIRのモジュール構造をVerilogに出力するためのモジュールが実装されている。
lib/EmitVerilog/EmitVerilog.cpp
LogicalResult circt::emitVerilog(ModuleOp module, llvm::raw_ostream &os) { VerilogEmitterState state(os); CircuitEmitter(state).emitMLIRModule(module); return failure(state.encounteredError); } void circt::registerVerilogEmitterTranslation() { static TranslateFromMLIRRegistration toVerilog("emit-verilog", emitVerilog); }
CircuitEmitter
から、emitMLIRModule()
が呼び出されている。これがmodule
を出力するためのメソッドだ。Circuit
というのがFIRの最も大きなモジュール単位で、これを出力するためのメソッドがemitCircuit()
となる。
lib/EmitVerilog/EmitVerilog.cpp
void CircuitEmitter::emitMLIRModule(ModuleOp module) { for (auto &op : *module.getBody()) { if (auto circuit = dyn_cast<CircuitOp>(op)) emitCircuit(circuit); else if (!isa<ModuleTerminatorOp>(op)) op.emitError("unknown operation"); } }
emitCircuit()
では、大きく分けて以下のステージで構成されている。
- ヘッダの出力する。いくつかの
define
文の定義など。 - モジュールの出力。これには
emitFModule()
を使用する。
for (auto &op : *circuit.getBody()) { if (auto module = dyn_cast<FModuleOp>(op)) { ModuleEmitter(state).emitFModule(module); continue; } // Ignore the done terminator at the end of the circuit. // Ignore 'ext modules'. if (isa<DoneOp>(op) || isa<FExtModuleOp>(op)) continue; ...
emitFModule
では、大きく分けて以下のステージで構成される。
- 入出力ポートの定義とファイルへの出力。
- 変数(ワイヤ・レジスタ)の宣言とファイルへの出力。これには
collectNamesEmitDecls()
が使用される。
void ModuleEmitter::collectNamesEmitDecls(Block &block) { // In the first pass, we fill in the symbol table, calculate the max width // of the declaration words and the max type width. size_t maxDeclNameWidth = 0, maxTypeWidth = 0; // Return the word (e.g. "wire") in Verilog to declare the specified thing. auto getVerilogDeclWord = [](Operation *op) -> StringRef { // Note: MemOp is handled as "wire" here because each of its subcomponents ...
assign
文などの出力。これにはemitOperation()
が使用される。
void ModuleEmitter::emitOperation(Operation *op) { // Expressions may either be ignored or emitted as an expression statements. if (isVerilogExpression(op)) { if (outOfLineExpressions.count(op)) emitStatementExpression(op); return; } ...
always
文などのステートメントの出力。これにはemitConditionStmtKind
使用される。
/// Emit a block of conditional statements that have the same ConditionStmtKind. static void emitConditionStmtKind(ArrayRef<ModuleEmitter::ConditionalStatement> elements, ModuleEmitter &emitter) { // Separate the top level blocks with a newline. emitter.os << '\n'; switch (elements[0].kind) { case ModuleEmitter::ConditionalStmtKind::Declaration: splitByPredicate(emitter, elements, emitCondActionByPPCond, [](const ModuleEmitter::ConditionalStatement &condStmt) { return condStmt.ppCond; }); ...
ここで何度も登場してくるCircuitOp
などのクラスだが、これはMLIRのtdファイルから出力されるものらしい。FIRRTLの定義ファイルは以下から参照することができる。
include/circt/Dialect/FIRRTL/FIRRTL.td
// Base class for the operation in this dialect. class FIRRTLOp<string mnemonic, list<OpTrait> traits = []> : Op<FIRRTLDialect, mnemonic, traits>; include "Types.td" include "OpStructure.td" include "OpDeclarations.td" include "OpExpressions.td" include "OpStatements.td" include "Passes.td"
例えば、CircuitOp
の定義はOpStructure.td
に定義されている。
def CircuitOp : FIRRTLOp<"circuit", [IsolatedFromAbove, SymbolTable, SingleBlockImplicitTerminator<"DoneOp">]> { let summary = "FIRRTL Circuit"; let description = [{ The "firrtl.circuit" operation represents an overall Verilog circuit, containing a list of modules. }]; let arguments = (ins StrAttr:$name); let results = (outs); let regions = (region SizedRegion<1>:$body); let skipDefaultBuilders = 1; let builders = [ OpBuilder<"OpBuilder &builder, OperationState &result, StringAttr name"> ]; ...