FPGA開発日記

FPGAというより、コンピュータアーキテクチャかもね! カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages

SiFiveのFreedom E300 Platformを単体でカスタマイズできるようにする (4. TileLinkの解析とコンフィグレーション設定)

RISC-Vの実装であるRocket-Chipは、RISC-Vの最新使用に追従しているため最初に見るべきデザインとしてはよくできているが、

  • Chiselで記述されており(初心者には)可読性が低い。
  • 外部のSoCプラットフォームについて情報がない

ことから、なかなかオリジナルのSoCをくみ上げるためのデザインとしてポーティングしにくい。

とはいえ、TileLinkは単なるバスのはずだし、クロックとリセットさえ突っ込めばとりあえずフェッチリクエストが上がってくるはずだ。 そのあたりを解析して、Rocket-Chipを利用したオリジナルSoCプラットフォーム構築のための情報収集を行っていこう。

f:id:msyksphinz:20180211025506p:plain
図. とりあえず実現したいRISC-V SoCブロック。まずはQSPIを取り除いて、だれでもアクセスできるTestRAMを接続したい。

関連記事

TileLink のコンフィグレーション設定

やっと気が付いたのだが、Freedom SoC (しいてはRocket-Core)を生成するときにメモリマップが表示されるのだが、これをよく見てみると ターゲットとしている0x2000_0000 にはReadの許可しか入っていなかった。

Generated Address Map
               0 -     1000 ARWX  debug-controller@0
           10000 -    12000  R XC rom@10000
         2000000 -  2010000 ARW   clint@2000000
         c000000 - 10000000 ARW   interrupt-controller@c000000
        10000000 - 10001000 ARW   aon@10000000
        10012000 - 10013000 ARW   gpio@10012000
        10013000 - 10014000 ARW   serial@10013000
        10015000 - 10016000 ARW   pwm@10015000
        10016000 - 10017000 ARW   i2c@10016000
        10023000 - 10024000 ARW   serial@10023000
        10024000 - 10025000 ARW   spi@10024000
        10025000 - 10026000 ARW   pwm@10025000
        10034000 - 10035000 ARW   spi@10034000
        10035000 - 10036000 ARW   pwm@10035000
        20000000 - 20002000  R    ram@20000000   <-- ここにRWしたいのだが、Readのフラグしか立っていない。
        80000000 - 80004000 ARWX  dtim@80000000

これをどうやって立てるのかずっと調査していたのだが、どうやら各モジュールのインタフェースとなっているTileLinkのコンフィグレーションで、Read/Write/Executableの権限を制御できるようになっているらしい。

こんなの、資料が無いと分からんわ...

diff --git a/src/main/scala/devices/tilelink/SimpleRAM.scala b/src/main/scala/devices/tilelink/SimpleRAM.scala
index d7c5d74..5b7552e 100644
--- a/src/main/scala/devices/tilelink/SimpleRAM.scala
+++ b/src/main/scala/devices/tilelink/SimpleRAM.scala
@@ -32,6 +32,8 @@ class TLSimpleRAM(c: SimpleRAMParams)(implicit p: Parameters) extends LazyModule
       regionType         = RegionType.UNCACHED,
       executable         = true,
       supportsGet        = TransferSizes(1, beatBytes),
+      supportsPutPartial = TransferSizes(1, beatBytes),
+      supportsPutFull    = TransferSizes(1, beatBytes),
       fifoId             = Some(0))), // requests are handled in order
     beatBytes = beatBytes)))

@@ -71,4 +73,3 @@ class TLSimpleRAM(c: SimpleRAMParams)(implicit p: Parameters) extends LazyModule
     in.e.ready := Bool(true)
   }
 }

というわけで、TileLink側でアクセス許可を渡してやると、プログラムからRead/Writeできるようになった。

生成されるメモリマップ表は以下のように変わり、0x2000_0000 - 0x20002000にRead/Write/Executableが与えられるようになった (CはCachableか?Uncachedの構成にしてほしいのだが...)

Generated Address Map
               0 -     1000 ARWX  debug-controller@0
           10000 -    12000  R XC rom@10000
         2000000 -  2010000 ARW   clint@2000000
         c000000 - 10000000 ARW   interrupt-controller@c000000
        10000000 - 10001000 ARW   aon@10000000
        10012000 - 10013000 ARW   gpio@10012000
        10013000 - 10014000 ARW   serial@10013000
        10015000 - 10016000 ARW   pwm@10015000
        10016000 - 10017000 ARW   i2c@10016000
        10023000 - 10024000 ARW   serial@10023000
        10024000 - 10025000 ARW   spi@10024000
        10025000 - 10026000 ARW   pwm@10025000
        10034000 - 10035000 ARW   spi@10034000
        10035000 - 10036000 ARW   pwm@10035000
        20000000 - 20002000 ARWXC ram@20000000
        80000000 - 80004000 ARWX  dtim@80000000

しかし、まだ疑問点がある。1回目のWriteだけで止まってしまったぞ? TileLinkはAcknowledgeを出す必要があるのか?解析は続く...

    li  a0, 0x20000000
    lw  t0, 0x20(a0)
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    li   t0, 0xdeadbeef
    sw  t0, 0x00(a0)
    sw  t0, 0x04(a0)
    sw  t0, 0x08(a0)
f:id:msyksphinz:20180214010449p:plain
図. SimpleRAMモジュールに対してWriteコマンドが発行され、データ0xdeadbeefが伝えられた波形。

2018/02/15追記。結局、TileLinkのアービトレーションが必要らしい。 in.d.bits および in.d.bits.opcode を制御するような論理を追加した。これらは、SRAM.scalaを参考にした。 この辺、資料がちゃんと用意されていないと、分かりようがないよ...

     // Flow control
-    when (in.d.fire())         { d_full := Bool(false) }
-    when (in.a.fire() && read) { d_full := Bool(true)  }
+    when (in.d.fire()) { d_full := Bool(false) }
+    when (in.a.fire()) { d_full := Bool(true)  }
     in.d.valid := d_full
     in.a.ready := in.d.ready || !d_full

+    in.d.bits := edge.AccessAck(d_source, d_size, !d_legal)
+    // avoid data-bus Mux
+    in.d.bits.data := d_data
+    in.d.bits.opcode := Mux(d_read, TLMessages.AccessAckData, TLMessages.AccessAck)
+
+    val read = in.a.bits.opcode === TLMessages.Get
+    val rdata = Wire(Vec(beatBytes, Bits(width = 8)))
+    val wdata = Vec.tabulate(beatBytes) { i => in.a.bits.data(8*(i+1)-1, 8*i) }

Dチャネルに正しくAcknowledge を挟むことによりTileLinkが動作するようになった。ただし、まだ不明な点がある。

  • d.bits.error が立ち上がっているがこれは問題無いのか。
  • d.bits.source はそもそもどういう意味があるのか。
f:id:msyksphinz:20180215001207p:plain
図. TileLink において連続Read/Writeを実行した様子。上側がA-Channel, 下側がD-Channel