FPGA開発日記

カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages , English Version https://fpgadevdiary.hatenadiary.com/

CIRCTのcirct-translateの内部構造を解析する (EmitVerilogモジュールの解析)

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">
  ];
...