FPGA開発日記

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

Chipyardで独自コアシミュレーション環境構築方法の調査 (バス幅拡張とアサーション修正)

テストパタンを流してとりあえず最初のフェッチが上手く行くかどうかを見ていたのだが、どうも以下のアサーションエラーで落ちてしまう。

Assertion failed: 'A' channel Get carries invalid source ID (connected at MSRHTile.scala:163:21)

Aチャネルのエラーなのでフェッチのタイミングに何かエラーがあるんだろうかと思いながらデバッグしても全く意味が分からず、そしてVerilatorのシミュレーションでは_の付いた信号はシミュレーション対象としてくれないので詳細が取れない。Verilatorのオプションで以下を追加してみる。

diff --git a/sims/verilator/Makefile b/sims/verilator/Makefile
index 1b9276ac..acb4116a 100644
--- a/sims/verilator/Makefile
+++ b/sims/verilator/Makefile
@@ -125,7 +125,12 @@ $(model_mk): $(sim_vsrcs) $(sim_common_files) $(EXTRA_SIM_REQS)
 $(model_mk_debug): $(sim_vsrcs) $(sim_common_files) $(EXTRA_SIM_REQS)
        rm -rf $(model_dir_debug)
        mkdir -p $(model_dir_debug)
-       $(VERILATOR) $(VERILATOR_OPTS) -o $(sim_debug) --trace -Mdir $(model_dir_debug) -CFLAGS "-include $(model_header_debug)"
+       $(VERILATOR) $(VERILATOR_OPTS) -o $(sim_debug) --trace \
+         --trace-params \
+         --trace-structs \
+         --trace-threads 4 \
+         --trace-underscore \
+       -Mdir $(model_dir_debug) -CFLAGS "-include $(model_header_debug)"
        touch $@

これで信号をひたすらトレースしていると、どうもDチャネルが邪魔をしているっぽいのが見て取れた。Dチャネルがバーストで複数サイクルを返しているので、それでAチャネルの次のリクエストと被っているらしい。

f:id:msyksphinz:20210125004037p:plain

バスのサイズ自体を256ビットで仮定していたがシステムバスが64ビットなので4回バーストで返ってきているらしい。バスのサイズを修正する。

diff --git a/src/main/scala/ConfigMixins.scala b/src/main/scala/ConfigMixins.scala
index 2c61a82..7e882e7 100644
--- a/src/main/scala/ConfigMixins.scala
+++ b/src/main/scala/ConfigMixins.scala
@@ -56,7 +56,7 @@ class WithNMSRHCores(n: Int) extends Config(
  * Setup default MSRH parameters.
  */
 class WithNormalMSRHSys extends Config((site, here, up) => {
-  case SystemBusKey => up(SystemBusKey, site).copy(beatBytes = 8)
+  case SystemBusKey => up(SystemBusKey, site).copy(beatBytes = 32)
   case XLen => 64
   case MaxHartIdBits => log2Up(site(MSRHTilesKey).size)
 })

この上で、MSRHのラッパーを修正する。

diff --git a/src/main/scala/MSRHTile.scala b/src/main/scala/MSRHTile.scala
index 24bf9cf..756c7b1 100644
--- a/src/main/scala/MSRHTile.scala
+++ b/src/main/scala/MSRHTile.scala
@@ -248,19 +247,33 @@ class MSRHTileModuleImp(outer: MSRHTile) extends BaseTileModuleImp(outer){
     outer.traceSourceNode.bundle map (t => t.valid := false.B)
   // }

+  val beatBytes = p(SystemBusKey).beatBytes
+
   // connect the axi interface
   outer.memTLNode.out foreach { case (out, edgeOut) =>
     out.a.valid        := core.io.o_ic_req_valid
-    out.a.bits.opcode  := core.io.o_ic_req_cmd
+    out.a.bits.opcode  := tl_opcode
     out.a.bits.param   := 0.U
-    out.a.bits.size    := 0.U
+    out.a.bits.size    := log2Ceil(beatBytes).U
     out.a.bits.source  := core.io.o_ic_req_tag
     out.a.bits.address := core.io.o_ic_req_addr
     // out.a.bits.user    := 0.U
     // out.a.bits.echo    := 0.U
-    out.a.bits.mask    := 0.U
+    out.a.bits.mask    := Fill(beatBytes * 8, 1.U(1.W))
     out.a.bits.data    := 0.U
     out.a.bits.corrupt := 0.U
     core.io.i_ic_req_ready     := out.a.ready
+
+    core.io.i_ic_resp_valid := out.d.valid
+    core.io.i_ic_resp_tag := out.d.bits.source
+    core.io.i_ic_resp_data := out.d.bits.data
+    out.d.ready := core.io.o_ic_resp_ready
   }
 }

これらの修正で、一応フェッチがエラー無く動くようになった。これでやっとフェッチユニットのデバッグに移ることができる。

f:id:msyksphinz:20210125004051p:plain
f:id:msyksphinz:20210125004059p:plain

Chipyardで独自コアシミュレーション環境構築方法の調査 (Verilatorでシミュレーション実行)

Chipyard環境上で独自CPUコアのシミュレーション環境を構築しようとしているが、いくつか環境の変更を行っている。

  • src/main/scala/MSRHCoreBlackbox.scala
class MSRHCoreBlackbox(
  xLen: Int
)
  extends BlackBox
  with HasBlackBoxResource
{
  val io = IO(new Bundle {
    val i_clk = Input(Clock())
/* ... */
    val i_ic_resp_tag   = Input(UInt(4.W))
    val i_ic_resp_data  = Input(UInt(512.W))
    val o_ic_resq_ready = Input(Bool())
  })

  // pre-process the verilog to remove "includes" and combine into one file
  val make = "make -C generators/msrh/src/main/resources/vsrc default "
  val proc = make
  require (proc.! == 0, "Failed to run preprocessing step")

  // add wrapper/blackbox after it is pre-processed
  addResource("/vsrc/MSRHCoreBlackbox.preprocessed.sv")
}

このブラックボックスの構成はArianeのものから取ってきた。ブラックボックスの定義を行っているのだが、最後にコマンドを実行しているようだ。Makeコマンドを実行してMSRHのVerilog構成のすべてをMSRHCoreBlackbox.preprocessed.svに集約するようになっている。generators/msrh/src/main/resources/vsrc/MakefileはすべてのVerilogファイルをcatして1つのファイルに集約する。

  • generators/msrh/src/main/resources/vsrc/Makefile
SRC_VLOG  =
SRC_VLOG += ./msrh/src/riscv64_pkg.sv
SRC_VLOG += ./msrh/src/mrh_pkg.sv
SRC_VLOG += ./msrh/src/l2_if.sv
SRC_VLOG += ./msrh/src/bit_oh_or.sv
SRC_VLOG += ./msrh/src/data_array.sv
SRC_VLOG += ./msrh/src/icache.sv
SRC_VLOG += ./msrh/src/tag_array.sv
SRC_VLOG += ./msrh/src/frontend.sv
SRC_VLOG += ./msrh/src/tlb.sv
SRC_VLOG += ./msrh/src/mrh_tile.sv
SRC_VLOG += ./msrh/src/mrh_tile_wrapper.sv

default:
        cat $(SRC_VLOG) > MSRHCoreBlackbox.preprocessed.sv

これで再度Verilogの生成に挑戦する。

$ make CONFIG=MSRHConfig debug -j4 2>&1 | tee msrhconfig.log

Verilatorでコンパイルが動き出すところまで来たぞ。地味にエラーが出るので直していきたいと思う。

%Error: /home/msyksphinz/work/riscv/chipyard/chipyard_msh/sims/verilator/generated-src/chipyard.TestHarness.MSRHConfig/chipyard.TestHarness.MSRHConfig.top.v:235874:6: Pin is not an in/out/inout/param/interface: 'o_ic_req_valid'
235874 |     .o_ic_req_valid(core_o_ic_req_valid),
       |      ^~~~~~~~~~~~~~
%Error: Exiting due to 3 error(s)

この辺だが、毎回Verilogの文法エラーを直して再度ChiselのWrapperを再処理するのに時間がかかりすぎて面倒なので、いくつかの要点だけはコマンドを取り出してすぐに確認できるようにしている。

  • 全てのSystemVerilog実装を1つのファイルに要約したMSRHCoreBlackbox.preprocessed.svの再生成
# at generators/msrh/src/main/resources/vsrc
make && mv MSRHCoreBlackbox.preprocessed.sv ../../../../../../sims/verilator/generated-src/chipyard.TestHarness.MSRHConfig/MSRHCoreBlackbox.preprocessed.sv
verilator --cc --exe -O3 -CFLAGS " -O1 -std=c++11 -I/home/msyksphinz/riscv64//include -I/home/msyksphinz/work/riscv/chipyard/chipyard_msh/tools/DRAMSim2 -D__STDC_FORMAT_MACROS  -DTEST_HARNESS=VTestHarness -DVERILATOR" -CFLAGS "-I/home/msyksphinz/work/riscv/chipyard/chipyard_msh/sims/verilator/generated-src/chipyard.TestHarness.MSRHConfig -include /home/msyksphinz/work/riscv/chipyard/chipyard_msh/sims/verilator/generated-src/chipyard.TestHarness.MSRHConfig/chipyard.TestHarness.MSRHConfig.plusArgs -include /home/msyksphinz/work/riscv/chipyard/chipyard_msh/sims/verilator/generated-src/chipyard.TestHarness.MSRHConfig/verilator.h" -LDFLAGS " -L/home/msyksphinz/work/riscv/chipyard/chipyard_msh/sims/verilator -lpthread" /home/msyksphinz/riscv64//lib/libfesvr.a /home/msyksphinz/work/riscv/chipyard/chipyard_msh/tools/DRAMSim2/libdramsim.a --timescale 1ns/1ps --top-module TestHarness -Wno-fatal --assert --output-split 10000 --output-split-cfuncs 100 --max-num-width 1048576 -f /home/msyksphinz/work/riscv/chipyard/chipyard_msh/sims/verilator/generated-src/chipyard.TestHarness.MSRHConfig/sim_files.common.f /home/msyksphinz/work/riscv/chipyard/chipyard_msh/sims/verilator/generated-src/chipyard.TestHarness.MSRHConfig/chipyard.TestHarness.MSRHConfig.top.v /home/msyksphinz/work/riscv/chipyard/chipyard_msh/sims/verilator/generated-src/chipyard.TestHarness.MSRHConfig/chipyard.TestHarness.MSRHConfig.harness.v /home/msyksphinz/work/riscv/chipyard/chipyard_msh/sims/verilator/generated-src/chipyard.TestHarness.MSRHConfig/chipyard.TestHarness.MSRHConfig.top.mems.v /home/msyksphinz/work/riscv/chipyard/chipyard_msh/sims/verilator/generated-src/chipyard.TestHarness.MSRHConfig/chipyard.TestHarness.MSRHConfig.harness.mems.v +define+PRINTF_COND=\$c\(\"verbose\",\"\&\&\"\,\"done_reset\"\) +define+STOP_COND=\$c\(\"done_reset\"\) -o /home/msyksphinz/work/riscv/chipyard/chipyard_msh/sims/verilator/simulator-chipyard-MSRHConfig-debug --trace -Mdir /home/msyksphinz/work/riscv/chipyard/chipyard_msh/sims/verilator/generated-src/chipyard.TestHarness.MSRHConfig/chipyard.TestHarness.MSRHConfig.debug -CFLAGS "-include /home/msyksphinz/work/riscv/chipyard/chipyard_msh/sims/verilator/generated-src/chipyard.TestHarness.MSRHConfig/chipyard.TestHarness.MSRHConfig.debug/VTestHarness.h"

色々と試行錯誤して、一応Verilatorでシミュレーションが動くようになってきた。ただしまだTileLinkのスペックに則っていない部分があるので、アサーションで落ちてしまう。この辺りを修正していこう。

./simulator-chipyard-MSRHConfig-debug --vcd dhrystone.msrh.vcd --verbose $RISCV/riscv64-unknown-elf/share/riscv-tests/benchmarks/dhrystone.riscv 2>&1 | tee dhrystone.msrh.log

f:id:msyksphinz:20210123174030p:plain

Chipyardで独自コアシミュレーション環境構築方法の調査 (ヘテロ構成についての調査)

Chipyardではいくつかのコンフィグレーションが用意されており、欲しい構成に応じてかなり柔軟にVerilogを生成することができる。どのようなコンフィグレーションが生成できるかについては、generators/chipyard/src/main/scala/config/に定義が置かれている。

cd generators/chipyard/src/main/scala/config
grep class -R | grep "extends Config"
ArianeConfigs.scala:11:class ArianeConfig extends Config(
ArianeConfigs.scala:28:class dmiArianeConfig extends Config(
TutorialConfigs.scala:20:class TutorialStarterConfig extends Config(
TutorialConfigs.scala:60:class TutorialMMIOConfig extends Config(
TutorialConfigs.scala:88:class TutorialSha3Config extends Config(
TutorialConfigs.scala:114:class TutorialSha3BlackBoxConfig extends Config(
TracegenConfigs.scala:6:class TraceGenConfig extends Config(
TracegenConfigs.scala:14:class NonBlockingTraceGenConfig extends Config(
TracegenConfigs.scala:22:class BoomTraceGenConfig extends Config(
TracegenConfigs.scala:31:class NonBlockingTraceGenL2Config extends Config(
TracegenConfigs.scala:40:class NonBlockingTraceGenL2RingConfig extends Config(
HeteroConfigs.scala:9:class LargeBoomAndRocketConfig extends Config(
HeteroConfigs.scala:31:class HwachaLargeBoomAndHwachaRocketConfig extends Config(
HeteroConfigs.scala:54:class DualLargeBoomAndRocketConfig extends Config(
HeteroConfigs.scala:77:class LargeBoomAndHwachaRocketConfig extends Config(
HeteroConfigs.scala:103:class LargeBoomAndRV32RocketConfig extends Config(
HeteroConfigs.scala:127:class DualLargeBoomAndDualRocketConfig extends Config(
HeteroConfigs.scala:149:class LargeBoomAndRocketWithControlCoreConfig extends Config(
RocketConfigs.scala:9:class RocketConfig extends Config(
RocketConfigs.scala:27:class HwachaRocketConfig extends Config(
RocketConfigs.scala:47:class GemminiRocketConfig extends Config(
RocketConfigs.scala:67:class RoccRocketConfig extends Config(
RocketConfigs.scala:87:class jtagRocketConfig extends Config(
RocketConfigs.scala:108:class dmiRocketConfig extends Config(
RocketConfigs.scala:127:class GCDTLRocketConfig extends Config(
RocketConfigs.scala:148:class GCDAXI4BlackBoxRocketConfig extends Config(
RocketConfigs.scala:168:class LargeSPIFlashROMRocketConfig extends Config(
RocketConfigs.scala:188:class SmallSPIFlashRocketConfig extends Config(
RocketConfigs.scala:208:class SimAXIRocketConfig extends Config(
RocketConfigs.scala:226:class SimBlockDeviceRocketConfig extends Config(
RocketConfigs.scala:246:class BlockDeviceModelRocketConfig extends Config(
RocketConfigs.scala:267:class GPIORocketConfig extends Config(
RocketConfigs.scala:288:class QuadRocketConfig extends Config(
RocketConfigs.scala:306:class RV32RocketConfig extends Config(
RocketConfigs.scala:324:class GB1MemoryRocketConfig extends Config(
RocketConfigs.scala:344:class Sha3RocketConfig extends Config(
RocketConfigs.scala:365:class InitZeroRocketConfig extends Config(
RocketConfigs.scala:385:class LoopbackNICRocketConfig extends Config(
RocketConfigs.scala:406:class L1ScratchpadSmallRocketConfig extends Config(
RocketConfigs.scala:427:class MbusScratchpadRocketConfig extends Config(
RocketConfigs.scala:448:class RingSystemBusRocketConfig extends Config(
RocketConfigs.scala:468:class StreamingPassthroughRocketConfig extends Config(
RocketConfigs.scala:488:class StreamingFIRRocketConfig extends Config (
RocketConfigs.scala:508:class SmallNVDLARocketConfig extends Config(
RocketConfigs.scala:527:class LargeNVDLARocketConfig extends Config(
BoomConfigs.scala:9:class SmallBoomConfig extends Config(
BoomConfigs.scala:28:class MediumBoomConfig extends Config(
BoomConfigs.scala:47:class LargeBoomConfig extends Config(
BoomConfigs.scala:66:class MegaBoomConfig extends Config(
BoomConfigs.scala:85:class GigaBoomConfig extends Config(
BoomConfigs.scala:104:class TeraBoomConfig extends Config(
BoomConfigs.scala:124:class StrangeBoomConfig extends Config(
BoomConfigs.scala:144:class DualSmallBoomConfig extends Config(
BoomConfigs.scala:163:class SmallRV32BoomConfig extends Config(
BoomConfigs.scala:184:class HwachaLargeBoomConfig extends Config(
BoomConfigs.scala:204:class LoopbackNICLargeBoomConfig extends Config(
BoomConfigs.scala:225:class DromajoBoomConfig extends Config(

ここで気になっているのは、BOOMとRocketのヘテロ環境が生成できるという構成であるLargeBoomAndRocketConfigとか、LargeBoomAndRocketWithControlCoreConfigだ。

これ以外にも、なんとNVDLAを埋め込んだ構成も生成することができる。これは面白い。

class SmallNVDLARocketConfig extends Config(
  new chipyard.iobinders.WithUARTAdapter ++
  new chipyard.iobinders.WithTieOffInterrupts ++
  new chipyard.iobinders.WithBlackBoxSimMem ++
  new chipyard.iobinders.WithTiedOffDebug ++
  new chipyard.iobinders.WithSimSerial ++
  new testchipip.WithTSI ++
  new chipyard.config.WithBootROM ++
  new chipyard.config.WithUART ++
  new chipyard.config.WithL2TLBs(1024) ++
  new nvidia.blocks.dla.WithNVDLA("small") ++ // add a small NVDLA
  new freechips.rocketchip.subsystem.WithNoMMIOPort ++
  new freechips.rocketchip.subsystem.WithNoSlavePort ++
  new freechips.rocketchip.subsystem.WithInclusiveCache ++
  new freechips.rocketchip.subsystem.WithNExtTopInterrupts(0) ++
  new freechips.rocketchip.subsystem.WithNBigCores(1) ++
  new freechips.rocketchip.subsystem.WithCoherentBusTopology ++
  new freechips.rocketchip.system.BaseConfig)

class LargeNVDLARocketConfig extends Config(
  new chipyard.iobinders.WithUARTAdapter ++
  new chipyard.iobinders.WithTieOffInterrupts ++
  new chipyard.iobinders.WithBlackBoxSimMem ++
  new chipyard.iobinders.WithTiedOffDebug ++
  new chipyard.iobinders.WithSimSerial ++
  new testchipip.WithTSI ++
  new chipyard.config.WithBootROM ++
  new chipyard.config.WithUART ++
  new chipyard.config.WithL2TLBs(1024) ++
  new nvidia.blocks.dla.WithNVDLA("large", true) ++ // add a large NVDLA with synth. rams
  new freechips.rocketchip.subsystem.WithNoMMIOPort ++
  new freechips.rocketchip.subsystem.WithNoSlavePort ++
  new freechips.rocketchip.subsystem.WithInclusiveCache ++
  new freechips.rocketchip.subsystem.WithNExtTopInterrupts(0) ++
  new freechips.rocketchip.subsystem.WithNBigCores(1) ++
  new freechips.rocketchip.subsystem.WithCoherentBusTopology ++
  new freechips.rocketchip.system.BaseConfig)

NVDLAの構成についてはwithNVDLAで指定するらしい。

  • generators/nvdla/src/main/scala/ConfigFragments.scala
/**
 * Config fragment to add a NVDLA to the SoC.
 * Supports "small" and "large" configs only.
 * Can enable synth. RAMs instead of default FPGA RAMs.
 */
class WithNVDLA(config: String, synthRAMs: Boolean = false) extends Config((site, here, up) => {
  case NVDLAKey => Some(NVDLAParams(config = config, raddress = 0x10040000L, synthRAMs = synthRAMs))
  case NVDLAFrontBusExtraBuffers => 0
})

以下のコマンドでVerilogを生成できる。

$ make CONFIG=SmallNVDLARocketConfig debug -j4
$ make CONFIG=LargeNVDLARocketConfig debug -j4

以下のコマンドでシミュレーションを実行してみた。

./simulator-chipyard-SmallNVDLARocketConfig-debug \
    --vcd dhrystone.nvdla_rocket.vcd \
    --verbose $RISCV/riscv64-unknown-elf/share/riscv-tests/benchmarks/dhrystone.riscv 2>&1 | tee dhrystone.vdla_rocket.log

一応NVDLAが組み込まれてシミュレーションが動いた。ただしNVDLAを触るテストパタンは作れていないので、インスタンス化されているのを確認しただけだ。

f:id:msyksphinz:20210122011506p:plain

Chipyardで独自コアシミュレーション環境構築方法の調査 (3. 独自コアの入出力ピンサポート)

RISC-Vコアのシミュレーション環境Chipyardでは、現在Rocket-Chip、BOOM、Arianeがサポートされているが、独自CPUコアをサポートするにあたり入出力ポートを独自CPU向けに改造する必要がある。

まず、ここではまず独自CPU側では以下のインタフェースを使っているものとする(まだ実装中)。

module mrh_tile_wrapper
(
    input logic i_clk,
    input logic i_reset_n,

    // L2 request from ICache
    output logic                                o_ic_req_vaild,
    output mrh_pkg::mem_cmd_t                   o_ic_req_cmd,
    output logic [riscv_pkg::PADDR_W-1:0]       o_ic_req_addr,
    output logic [mrh_pkg::L2_CMD_TAG_W-1:0]    o_ic_req_tag,
    output logic [mrh_pkg::ICACHE_DATA_W-1:0]   o_ic_req_data,
    output logic [mrh_pkg::ICACHE_DATA_W/8-1:0] o_ic_req_byte_en,
    input logic                                 i_ic_req_ready,

    input logic                              i_ic_resp_valid,
    input logic [mrh_pkg::L2_CMD_TAG_W-1:0]  i_ic_resp_tag,
    input logic [mrh_pkg::ICACHE_DATA_W-1:0] i_ic_resp_data,
    output logic                             o_ic_resq_ready
);

これをChisel側のMSRHTile側のブラックボックスでも同じようにポートを宣言する。

  • generators/msrh/src/main/scala/MSRHCoreBlackbox.scala
class MSRHCoreBlackbox(
  xLen: Int
)
  extends BlackBox
  with HasBlackBoxResource
{
  val io = IO(new Bundle {
    val i_clk = Input(Clock())
    val i_reset_n = Input(Bool())

    val o_ic_req_valid   = Output(Bool())
    val o_ic_req_cmd     = Output(UInt(4.W))
    val o_ic_req_addr    = Output(UInt(39.W))
    val o_ic_req_tag     = Output(UInt(4.W))
    val o_ic_req_data    = Output(UInt(512.W))
    val o_ic_req_byte_en = Output(UInt((512/8).W))
    val i_ic_req_ready   = Input(Bool())

    val i_ic_resp_valid = Input(Bool())
    val i_ic_resp_tag   = Input(UInt(4.W))
    val i_ic_resp_data  = Input(UInt(512.W))
    val o_ic_resq_ready = Input(Bool())
  })

同じように名前を統一することでVerilogコンパイル時にポートを認識できるようにする。

MSRHTile側はTileLinkのポートを持っているので、独自ポートをTileLinkに接続する。

  • generators/msrh/src/main/scala/MSRHTile.scala
class MSRHTile(
  val MSRHParams: MSRHTileParams,
  crossing: ClockCrossingType,
  lookup: LookupByHartIdImpl,
  q: Parameters,
  logicalTreeNode: LogicalTreeNode)
  extends BaseTile(MSRHParams, crossing, lookup, q)
  with SinksExternalInterrupts
  with SourcesExternalNotifications
{
...
  val memTLNode = TLClientNode(Seq(TLClientPortParameters(
    cacheClientParameters)))
...
  // connect the MSRH core
  val core = Module(new MSRHCoreBlackbox(
    // general core params
    xLen = p(XLen),
  ))
...
    
  // connect the axi interface
  outer.memTLNode.out foreach { case (out, edgeOut) =>
    out.a.valid        := core.io.o_ic_req_valid
    out.a.bits.opcode  := core.io.o_ic_req_cmd
    out.a.bits.param   := 0.U
    out.a.bits.size    := 0.U
    out.a.bits.source  := core.io.o_ic_req_tag
    out.a.bits.address := core.io.o_ic_req_addr
    // out.a.bits.user    := 0.U
    // out.a.bits.echo    := 0.U
    out.a.bits.mask    := 0.U
    out.a.bits.data    := 0.U
    out.a.bits.corrupt := 0.U
    core.io.i_ic_req_ready     := out.a.ready
  }

これでVerilogを合成すると以下のようになった。

module MSRHTile( // @[:chipyard.TestHarness.MSRHConfig.fir@195189.2]
  input         clock, // @[:chipyard.TestHarness.MSRHConfig.fir@195190.4]
  input         reset, // @[:chipyard.TestHarness.MSRHConfig.fir@195191.4]
  input         auto_int_in_xing_in_2_sync_0, // @[:chipyard.TestHarness.MSRHConfig.fir@195192.4]
  input         auto_int_in_xing_in_1_sync_0, // @[:chipyard.TestHarness.MSRHConfig.fir@195192.4]
  input         auto_tl_master_xing_out_a_ready, // @[:chipyard.TestHarness.MSRHConfig.fir@195192.4]
...
  MSRHCoreBlackbox core ( // @[MSRHTile.scala 220:20:chipyard.TestHarness.MSRHConfig.fir@195383.4]
    .i_clk(core_i_clk),
    .i_reset_n(core_i_reset_n),
    .o_ic_req_valid(core_o_ic_req_valid),
    .o_ic_req_cmd(core_o_ic_req_cmd),
    .o_ic_req_addr(core_o_ic_req_addr),
    .o_ic_req_tag(core_o_ic_req_tag),
    .o_ic_req_data(core_o_ic_req_data),
    .o_ic_req_byte_en(core_o_ic_req_byte_en),
    .i_ic_req_ready(core_i_ic_req_ready),
    .i_ic_resp_valid(core_i_ic_resp_valid),
    .i_ic_resp_tag(core_i_ic_resp_tag),
    .i_ic_resp_data(core_i_ic_resp_data),
    .o_ic_resq_ready(core_o_ic_resq_ready)
  );
...

Chipyardで独自コアシミュレーション環境構築方法の調査 (2)

ArianeCoreの生成方法が分かったので、次は独自コアの構成方法について調査してみよう。ここでは、独自のRISC-Vコア MSRH を接続する方法について考えてみる。

この調査の目的は、Chipyardの環境を使えば、CPUコアを設計してインタフェースを統一すれば簡単にシミュレーション環境とFPGA環境を構築することができるのではないかという目論見だ。自作CPUは楽しいが、テスト環境やメモリサブシステムを構築するのは実に面倒くさい。そこでChipyardに外部インタフェースとメモリアクセスの部分はすべてお任せしてしまい、まずはCPUコアの設計設計のみに集中できる環境が構築できるのではないかという考えがある。

このためには、まずRocketTile, BoomTile, ArianeTileに続いて独自CPUコア用のTileを構築する。これはMSRHTileとする。まずはこれを構築していこう。

$ tree msrh
msrh
|-- build.sbt
`-- src
    `-- main
        `-- scala
            |-- ConfigMixins.scala
            |-- MSRHCoreBlackbox.scala
            `-- MSRHTile.scala

3 directories, 4 files

MRSHTileは基本的にArianeTileから持ってきている。ArianeTileから名前を変えてMSRHTileとしている。MSRHCoreBlackBoxも同様。ConfigMixins.scalaもArianeの構成を同様にMSRHに変更している。

そしてVerilog生成用のConfigはArianeConfig.scalaと並列に作成する。

  • generators/chipyard/src/main/scala/config/MSRHConfigs.scala
class MSRHConfig extends Config(
  new chipyard.iobinders.WithUARTAdapter ++                      // display UART with a SimUARTAdapter
  new chipyard.iobinders.WithTieOffInterrupts ++                 // tie off top-level interrupts
  new chipyard.iobinders.WithSimAXIMem ++                        // drive the master AXI4 memory with a SimAXIMem
...
  new freechips.rocketchip.subsystem.WithNExtTopInterrupts(0) ++ // no external interrupts
  new msrh.WithNMSRHCores(1) ++                              // single MSRH core
  new freechips.rocketchip.subsystem.WithCoherentBusTopology ++  // hierarchical buses including mbus+l2
  new freechips.rocketchip.system.BaseConfig)                    // "base" rocketchip system

msrhWithNMSRHCores()は以下のような構成となっている。

  • generators/msrh/src/main/scala/ConfigMixins.scala
class WithNMSRHCores(n: Int) extends Config(
  new WithNormalMSRHSys ++
  new Config((site, here, up) => {
    case MSRHTilesKey => {
      List.tabulate(n)(i => MSRHTileParams(hartId = i))
    }
  })
)

そして、Tileの生成の部分に、Arianeの次に独自CPUコアの構成を追加する。

  • generators/chipyard/src/main/scala/Subsystem.scala
diff --git a/generators/chipyard/src/main/scala/Subsystem.scala b/generators/chipyard/src/main/scala/Subsystem.scala
index 99c3147..b08b68f 100644
--- a/generators/chipyard/src/main/scala/Subsystem.scala
+++ b/generators/chipyard/src/main/scala/Subsystem.scala
@@ -23,6 +23,7 @@ import freechips.rocketchip.amba.axi4._

 import boom.common.{BoomTile, BoomTilesKey, BoomCrossingKey, BoomTileParams}
 import ariane.{ArianeTile, ArianeTilesKey, ArianeCrossingKey, ArianeTileParams}
+import msrh.{MSRHTile, MSRHTilesKey, MSRHCrossingKey, MSRHTileParams}

 import testchipip.{DromajoHelper}

@@ -37,13 +38,15 @@ trait HasChipyardTiles extends HasTiles
   protected val rocketTileParams = p(RocketTilesKey)
   protected val boomTileParams = p(BoomTilesKey)
   protected val arianeTileParams = p(ArianeTilesKey)
+  protected val msrhTileParams = p(MSRHTilesKey)

   // crossing can either be per tile or global (aka only 1 crossing specified)
   private val rocketCrossings = perTileOrGlobalSetting(p(RocketCrossingKey), rocketTileParams.size)
   private val boomCrossings = perTileOrGlobalSetting(p(BoomCrossingKey), boomTileParams.size)
   private val arianeCrossings = perTileOrGlobalSetting(p(ArianeCrossingKey), arianeTileParams.size)
+  private val msrhCrossings = perTileOrGlobalSetting(p(MSRHCrossingKey), msrhTileParams.size)

-  val allTilesInfo = (rocketTileParams ++ boomTileParams ++ arianeTileParams) zip (rocketCrossings ++ boomCrossings ++ arianeCrossings)
+  val allTilesInfo = (rocketTileParams ++ boomTileParams ++ arianeTileParams ++ msrhTileParams) zip (rocketCrossings ++ boomCrossings ++ arianeCrossings ++ msrhCrossings)

   // Make a tile and wire its nodes into the system,
   // according to the specified type of clock crossing.
@@ -65,6 +68,9 @@ trait HasChipyardTiles extends HasTiles
         case a: ArianeTileParams => {
           LazyModule(new ArianeTile(a, crossing, PriorityMuxHartIdFromSeq(arianeTileParams), logicalTreeNode))
         }
+        case m: MSRHTileParams => {
+          LazyModule(new MSRHTile(m, crossing, PriorityMuxHartIdFromSeq(msrhTileParams), logicalTreeNode))
+        }
       }
       connectMasterPortsToSBus(tile, crossing)
       connectSlavePortsToCBus(tile, crossing)

ここまで追加して、Arianeと全く同じ構成で名前だけ変えたChipyardのVerilog構成が完成する。一応Verilogは生成されている。ただしBlackBoxは生成していないのでVerilatorが実行された時点でコンパイルは失敗する。まあとりあえずここまででいいや。インタフェースを統一して、接続確認をしていこうと思う。

Chipyardで独自コアシミュレーション環境構築方法の調査

Chipyardの環境ではScalaで実装しているRocket-ChipやBOOMコアもインスタンス化することができるが、それ以外にSystemVerilogで実装されているArianeもシミュレーションを実行することができる。

つまり、SystemVerilogで実装した独自CPUコアに対してもChipyardの環境を構築することができるということだ。この方法について調査しようと思う。

ArianeのChipyard用Wrapper

ArianeはSystemVerilogで記述されているため、これをそのままChiselベースのChipyardに載せることは出来ない。このため、Chiselで書いたSystemVerilog用のWrapperを用いることになる。

f:id:msyksphinz:20210119010740p:plain:w300

ここで大事なのはArinaneCoreBlackBoxで、ここにはSystemVerilogのデザインをラップするための機能が入っている。

  • src/main/scala/ArianeCoreBlackbox.scala
class ArianeCoreBlackbox(
  traceportEnabled: Boolean,
  traceportSz: Int,
  xLen: Int,
  rasEntries: Int,
  btbEntries: Int,
  bhtEntries: Int,
  execRegAvail: Int = 5,
...
      extends BlackBox(
    Map(
      "TRACEPORT_SZ" -> IntParam(traceportSz),
      "XLEN" -> IntParam(xLen),
      "RAS_ENTRIES" -> IntParam(rasEntries),
      "BTB_ENTRIES" -> IntParam(btbEntries),
      "BHT_ENTRIES" -> IntParam(bhtEntries),
      "EXEC_REG_CNT" -> IntParam(exeRegCnt),
      "CACHE_REG_CNT" -> IntParam(cacheRegCnt),

このデザインを見て初めて知ったのだが、Chiselの変数をパラメータとして伝搬させることもできるのか。知らなかった。

I/Oの接続は、I/Oポートを宣言してこれをTileで接続するようになっていた。

  • src/main/scala/ArianeCoreBlackbox.scala
  with HasBlackBoxResource
{
  val io = IO(new Bundle {
    val clk_i = Input(Clock())
    val rst_ni = Input(Bool())
    val boot_addr_i = Input(UInt(64.W))
    val hart_id_i = Input(UInt(64.W))
    val irq_i = Input(UInt(2.W))
    val ipi_i = Input(Bool())
    val time_irq_i = Input(Bool())
    val debug_req_i = Input(Bool())
    val trace_o = Output(UInt(traceportSz.W))

    val axi_resp_i_aw_ready      = Input(Bool())
    val axi_req_o_aw_valid       = Output(Bool())
    val axi_req_o_aw_bits_id     = Output(UInt(axiIdWidth.W))
...

最終的なI/Oの接続はTile上で行われている。これはArianeのI/OがAXIのようなのでこれをそのままつなげたようだ。

  • src/main/scala/ArianeTile.scala
  // connect the axi interface
  outer.memAXI4Node.out foreach { case (out, edgeOut) =>
    core.io.axi_resp_i_aw_ready    := out.aw.ready
    out.aw.valid                   := core.io.axi_req_o_aw_valid
    out.aw.bits.id                 := core.io.axi_req_o_aw_bits_id
    out.aw.bits.addr               := core.io.axi_req_o_aw_bits_addr
    out.aw.bits.len                := core.io.axi_req_o_aw_bits_len
    out.aw.bits.size               := core.io.axi_req_o_aw_bits_size

このように、ポートの規格を統一すれば独自コアでもChipyardの環境を構築することができる。今度はこれに挑戦してみよう。

ChipyardでRISC-VコアArianeを試す

Chipyardには、RISC-VのインオーダCPUであるArianeを試すための環境が用意されている。もともとSubrepoとしてArianeが配置されていたのは知っていたので何らかのビルド・実行できる環境があるとは思っていたが、どうやら本当に実行できるようだ。

ArianeはSystemVerilogで記述されているのだが、ChiselにはもともとSystemVerilogをWrappingする機能があるので簡単に実現できるだろうとは思っていた。

make CONFIG=ArianeConfig debug -j4

このArianeConfigというのは、ChiselのConfigを用いて記述されている。Rocket-ChipのDefaultConfigみたいなものだ。

  • generators/chipyard/src/main/scala/config/ArianeConfigs.scala
class ArianeConfig extends Config(
  new chipyard.iobinders.WithUARTAdapter ++                      // display UART with a SimUARTAdapter
  new chipyard.iobinders.WithTieOffInterrupts ++                 // tie off top-level interrupts
  new chipyard.iobinders.WithSimAXIMem ++                        // drive the master AXI4 memory with a SimAXIMem
  new chipyard.iobinders.WithTiedOffDebug ++                     // tie off debug (since we are using SimSerial for testing)
  new chipyard.iobinders.WithSimSerial ++                        // drive TSI with SimSerial for testing
  new testchipip.WithTSI ++                                      // use testchipip serial offchip link
  new chipyard.config.WithBootROM ++                             // use default bootrom
  new chipyard.config.WithUART ++                                // add a UART
  new freechips.rocketchip.subsystem.WithNoMMIOPort ++           // no top-level MMIO master port (overrides default set in rocketchip)
  new freechips.rocketchip.subsystem.WithNoSlavePort ++          // no top-level MMIO slave port (overrides default set in rocketchip)
  new freechips.rocketchip.subsystem.WithInclusiveCache ++       // use Sifive L2 cache
  new freechips.rocketchip.subsystem.WithNExtTopInterrupts(0) ++ // no external interrupts
  new ariane.WithNArianeCores(1) ++                              // single Ariane core
  new freechips.rocketchip.subsystem.WithCoherentBusTopology ++  // hierarchical buses including mbus+l2
  new freechips.rocketchip.system.BaseConfig)                    // "base" rocketchip system

なるほど、Rocket-Chipと基本的にバス周りを同じように固めているのか。ariane.WithNArianeCores(1)Arianeのコンフィグレーションを示している。

  • generators/chipyard/src/main/scala/config/ArianeConfigs.scala
/**
 * Create multiple copies of a Ariane tile (and thus a core).
 * Override with the default mixins to control all params of the tiles.
 *
 * @param n amount of tiles to duplicate
 */
class WithNArianeCores(n: Int) extends Config(
  new WithNormalArianeSys ++
  new Config((site, here, up) => {
    case ArianeTilesKey => {
      List.tabulate(n)(i => ArianeTileParams(hartId = i))
    }
  })
)

ArianeTileは以下で定義されていた。

  • generators/ariane/src/main/scala/ArianeTile.scala
class ArianeTile(
  val arianeParams: ArianeTileParams,
  crossing: ClockCrossingType,
  lookup: LookupByHartIdImpl,
  q: Parameters,
  logicalTreeNode: LogicalTreeNode)
  extends BaseTile(arianeParams, crossing, lookup, q)
  with SinksExternalInterrupts
  with SourcesExternalNotifications
{
...

Arianeコアのインスタンスは以下で行われている。

  • generators/ariane/src/main/scala/ArianeTile.scala
  // connect the ariane core
  val core = Module(new ArianeCoreBlackbox(
    // traceport params
    traceportEnabled = outer.arianeParams.trace,
...

実行結果は以下のようになった。

./simulator-chipyard-ArianeConfig-debug --verbose $RISCV/riscv64-unknown-elf/share/riscv-tests/benchmarks/dhrystone.riscv 2>&1 | tee dhrystone.ariane.log
using random seed 1610846131
This emulator compiled with JTAG Remote Bitbang client. To enable, use +jtag_rbb_enable=1.
Listening on port 56520
Microseconds for one run through Dhrystone: 4049
Dhrystones per Second:                      246
mcycle = 2024987
minstret = 196530

ただし、まあけた外れに遅いな...なんでこんなに遅いんだろ?

SmallBoomConfig 211123
MediumBoomConfig 116078
LargeBoomConfig 88391
MegaBoomConfig 74496
GigaBoomConfig 68039
ArianeConfig 2024987
f:id:msyksphinz:20210117112410p:plain