FPGA開発日記

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

FIRRTLに入門する (9. Negative EdgeのAsynchronous Resetを追加してみる)

https://raw.githubusercontent.com/freechipsproject/firrtl/master/doc/images/firrtl_logo.svg?sanitize=true

FIRでAsync Resetの作り方が分かったので、練習問題としてNegative EdgeのAsynchronous Resetを作ってみようかと思う。

Async Resetは専用の型が定義されていたので、同様にNegative EdgeのAsync Resetの型を定義してみる。

  • firrtl/src/main/scala/firrtl/ir/IR.scala
case object AsyncResetNType extends GroundType {
  val width = IntWidth(1)
  def serialize: String = "AsyncResetN"
  def mapWidth(f: Width => Width): Type = this
  def foreachWidth(f: Width => Unit): Unit = Unit
}

AsyncResetTypeに対して、AsyncResetNTypeを定義した。これはAsyncReset同様に、AsyncResetNとして使用する。

circuit SimpleCircuit :
  module SimpleCircuit :
...
    input arstn : AsyncResetN

FIRRTL.g4も改造して、AsyncResetNを扱えるように変更した。

  • src/main/antlr4/FIRRTL.g4
diff --git a/src/main/antlr4/FIRRTL.g4 b/src/main/antlr4/FIRRTL.g4
index be15ab7c..269e101c 100644
--- a/src/main/antlr4/FIRRTL.g4
+++ b/src/main/antlr4/FIRRTL.g4
@@ -52,6 +52,7 @@ type
   | 'Fixed' ('<' intLit '>')? ('<' '<' intLit '>' '>')?
   | 'Clock'
   | 'AsyncReset'
+  | 'AsyncResetN'
   | 'Reset'
   | 'Analog' ('<' intLit '>')?
   | '{' field* '}'        // Bundle
@@ -251,6 +252,7 @@ primop
   | 'pad('
   | 'asUInt('
   | 'asAsyncReset('
+  | 'asAsyncResetN('
   | 'asSInt('
   | 'asClock('
   | 'shl('

色々と実装を追加しなければならないのだが、Verilogalwaysルーチンを生成する仕組みは、

  1. FFが定義されているとそれを3種類に分類する。
    • Syncronous Resetのタイプ
    • Asynchronous Reset(Posedge)のタイプ
    • Asynchronous Reset(Negedge)のタイプ
  2. always文で囲んだルーチンを生成し、そこに上記のFFごとに実装を貼り付けていく。
    • Reset信号による初期化ルーチンは、上記の分類作業中に最後にMuxで挿入してくと、Verilog生成時に先頭に生成されるようになる。

ではまずは分類するルーチンを改造していく。

  • firrtl/src/main/scala/firrtl/SystemVerilogEmitter.scala
    def regUpdate(r: Expression, clk: Expression, reset: Expression, init: Expression) = {
      def addUpdate(expr: Expression, tabs: String): Seq[Seq[Any]] = expr match {
...
      if (weq(init, r)) { // Synchronous Reset
        noResetAlwaysBlocks.getOrElseUpdate(clk, ArrayBuffer[Seq[Any]]()) ++= addUpdate(netlist(r), "")
      } else { // Asynchronous Reset
        assert(reset.tpe == AsyncResetType || reset.tpe == AsyncResetNType, "Error! Synchronous reset should have been removed!")
        val tv = init
        val fv = netlist(r)
        if (reset.tpe == AsyncResetType) {
          asyncResetAlwaysBlocks  += ((clk, reset, addUpdate(Mux(reset, tv, fv, mux_type_and_widths(tv, fv)), "")))
        } else {
          asyncResetNAlwaysBlocks += ((clk, reset, addUpdate(Mux(DoPrim(Not, Seq(reset), Nil, BoolType), tv, fv, mux_type_and_widths(tv, fv)), "")))
        }
      }

この実装を見つけだすまでに結構な時間がかかったのだが、要するにSynchronous Resetの時はnoResetAlwaysBlocksに代入し、Asynchronous Reset(Posedge)の場合はasyncResetAlwaysBlocksにFF動作を代入しておき、Asynchronous Reset(Negedge)の場合はasyncResetNAlwaysBlocksに代入するという仕組みである。

Negative EdgeのAsyncResetを作るためにかなり苦労したのだが、

if (!reset) begin

という文法を生成するためにどうやったかというと、Muxの条件の所を単純なreset信号ではなく、DoPrimを使って演算子を挿入している。

asyncResetNAlwaysBlocks += ((clk, reset, addUpdate(Mux(DoPrim(Not, Seq(reset), Nil, BoolType), tv, fv, mux_type_and_widths(tv, fv)), "")))

そして、最後にVerilogを生成するにあたりこの3種類で生成Verilogルーチンを切り分ける。

  • firrtl/src/main/scala/firrtl/SystemVerilogEmitter.scala
    def emit_streams(): Unit = {
      description match {
...
      for ((clk, content) <- noResetAlwaysBlocks if content.nonEmpty) {
        emit(Seq(tab, "always_ff @(posedge ", clk, ") begin"))
        for (line <- content) emit(Seq(tab, tab, line))
        emit(Seq(tab, "end"))
      }

      for ((clk, reset, content) <- asyncResetAlwaysBlocks if content.nonEmpty) {
        emit(Seq(tab, "always_ff @(posedge ", clk, ", posedge ", reset, ") begin"))
        for (line <- content) emit(Seq(tab, tab, line))
        emit(Seq(tab, "end"))
      }

      for ((clk, reset, content) <- asyncResetNAlwaysBlocks if content.nonEmpty) {
        emit(Seq(tab, "always_ff @(posedge ", clk, ", negedge ", reset, ") begin"))
        for (line <- content) emit(Seq(tab, tab, line))
        emit(Seq(tab, "end"))
      }

それ以外にも少しずつ改造しておりやっとこの形になっている。生成時のAssertionのコードとか改造したのだが、おおむね上記のコードで行ける。

テストコードを流してみる。

circuit SimpleCircuit :
  module SimpleCircuit :
    input clk : Clock
    input rst : UInt<1>
    input arst : AsyncReset
    input arstn : AsyncResetN
...
    reg myreg3 : UInt<32>, clk with : (reset => (arstn, UInt<32>("h0")))
    when en :
      myreg3 <= io.in
      skip
    when clr :
      myreg3 <= UInt<32>("h1")
      skip
    myreg3_out <= myreg3

これでどうなるか。

module SimpleCircuit(
  input logic         clk,
  input logic         rst,
  input logic         arst,
  input logic         arstn,
    ...
    
  always_ff @(posedge clk, negedge arstn) begin
    if (~ arstn) begin
      myreg3 <= 32'h0;
    end else if (clr) begin
      myreg3 <= 32'h1;
    end else if (en) begin
      myreg3 <= io_in;
    end
  end
endmodule

想定通りのコードが生成された。よさそうだ。