かずの不定期便ブログ

備忘録代わりに書きます

VexRiscvを用いたオリジナルSoCをverilatorでJTAGデバッグ

VexRiscvにはOpenOCDを用いて、RTL simulationでJTAGデバッグが比較的簡単に行う仕組みが用意されています。 その利用方法をここにとどめておきます。


VexRiscvの生成環境の構築

本筋ではありませんが、簡単に記載しておきます。 wslのubuntuにて構築しました。 まずは、以下のVexRiscvのgithubからリポジトリを持ってきます。

github.com

git clone https://github.com/SpinalHDL/VexRiscv.git

READMEのDependenciesに従い必要なツールのインストールを行います。 JAVA, SBT, Verilatorをインストールします。 Verilatorは現在ver5系が取得できますが、READMEに記載のv4.216でなくても良いですが、ver4系を取得するのが無難です。
自分が試した時はver5系だとvexriscvコアがうまく動作しませんでした。(そのうち改善されるかもですね)
ubuntu 20だとmakeすら不要でsudo apt install verilatorでよいと思われます。

VexRiscvコアの作成

先ほどgit cloneしたdirから一段下りて
cd VexRiscvします。

今回JTAGデバッグしたいので、
src/main/scala/vexriscv/demo/
配下にあるものでJTAG付きのものを利用します。今回は
VexRiscvAxi4WithIntegratedJtag.scala
を使います。

いよいよRTLを作成します。

sbt "runMain vexriscv.demo.VexRiscvAxi4WithIntegratedJtag" 

scala配下のdir区切りの/.へ置き換えたものを指定すれば良いようです。
1分もかからずRTLが生成されます。以下のファイルが生成されます。
VexRiscvAxi4.v
cpu0.yaml

2つめのcpu0.yamlはOpenOCDを実行する際に使います。

SoCの作成

さきほど作成したVexRiscvコアをインスタンスしてSoCを作成します。 boot時の命令を格納するaxi_ramモジュールとuartモジュールのみの最低限のSoCを作成しました。(必要に応じてバス変換とcrossbarスイッチも)
バス構成は、VexRiscvの命令バス、データバス2系統を1系統にまとめるのとアクセスアドレスに応じて、2系統のマスターポートへ振り分けるaxi_crossbarモジュールを置いています。

SoCブロック図

SoCについての詳細はここでは省略します。参考にSoCトップのRTLだけ載せておきます。
各子モジュールは記載しません。
またwire宣言も省略しています。
作成したSoCの雰囲気だけを感じていただければと。

module soc_slideshow (
  input  wire          io_asyncResetx,
  input  wire          io_mainClk,
  input  wire          io_jtag_tms,
  input  wire          io_jtag_tdi,
  output wire          io_jtag_tdo,
  input  wire          io_jtag_tck,
  output wire          io_uart_txd,
  input  wire          io_uart_rxd
);

/* verilator lint_off  PINMISSING */
vexriscv_wrap vexriscv_wrap
  (
  .M0_AXI_ARADDR(M0_AXI_ARADDR),
  .M0_AXI_ARBURST(M0_AXI_ARBURST),
  .M0_AXI_ARCACHE(M0_AXI_ARCACHE),
  .M0_AXI_ARID(M0_AXI_ARID),
  .M0_AXI_ARLEN(M0_AXI_ARLEN),
  .M0_AXI_ARLOCK(M0_AXI_ARLOCK),
  .M0_AXI_ARPROT(M0_AXI_ARPROT),
  .M0_AXI_ARQOS(M0_AXI_ARQOS),
  .M0_AXI_ARREADY(M0_AXI_ARREADY),
  .M0_AXI_ARSIZE(M0_AXI_ARSIZE),
  .M0_AXI_ARVALID(M0_AXI_ARVALID),
  .M0_AXI_RDATA(M0_AXI_RDATA),
  .M0_AXI_RID(M0_AXI_RID),
  .M0_AXI_RLAST(M0_AXI_RLAST),
  .M0_AXI_RREADY(M0_AXI_RREADY),
  .M0_AXI_RRESP(M0_AXI_RRESP),
  .M0_AXI_RUSER(1'b0),
  .M0_AXI_RVALID(M0_AXI_RVALID),
  .M1_AXI_ARADDR(M1_AXI_ARADDR),
  .M1_AXI_ARBURST(M1_AXI_ARBURST),
  .M1_AXI_ARCACHE(M1_AXI_ARCACHE),
  .M1_AXI_ARID(M1_AXI_ARID),
  .M1_AXI_ARLEN(M1_AXI_ARLEN),
  .M1_AXI_ARLOCK(M1_AXI_ARLOCK),
  .M1_AXI_ARPROT(M1_AXI_ARPROT),
  .M1_AXI_ARQOS(M1_AXI_ARQOS),
  .M1_AXI_ARREADY(M1_AXI_ARREADY),
  .M1_AXI_ARSIZE(M1_AXI_ARSIZE),
  .M1_AXI_ARVALID(M1_AXI_ARVALID),
  .M1_AXI_AWADDR(M1_AXI_AWADDR),
  .M1_AXI_AWBURST(M1_AXI_AWBURST),
  .M1_AXI_AWCACHE(M1_AXI_AWCACHE),
  .M1_AXI_AWID(M1_AXI_AWID),
  .M1_AXI_AWLEN(M1_AXI_AWLEN),
  .M1_AXI_AWLOCK(M1_AXI_AWLOCK),
  .M1_AXI_AWPROT(M1_AXI_AWPROT),
  .M1_AXI_AWQOS(M1_AXI_AWQOS),
  .M1_AXI_AWREADY(M1_AXI_AWREADY),
  .M1_AXI_AWSIZE(M1_AXI_AWSIZE),
  .M1_AXI_AWVALID(M1_AXI_AWVALID),
  .M1_AXI_BID(M1_AXI_BID),
  .M1_AXI_BREADY(M1_AXI_BREADY),
  .M1_AXI_BRESP(M1_AXI_BRESP),
  .M1_AXI_BUSER(1'b0),
  .M1_AXI_BVALID(M1_AXI_BVALID),
  .M1_AXI_RDATA(M1_AXI_RDATA),
  .M1_AXI_RID(M1_AXI_RID),
  .M1_AXI_RLAST(M1_AXI_RLAST),
  .M1_AXI_RREADY(M1_AXI_RREADY),
  .M1_AXI_RRESP(M1_AXI_RRESP),
  .M1_AXI_RUSER(1'b0),
  .M1_AXI_RVALID(M1_AXI_RVALID),
  .M1_AXI_WDATA(M1_AXI_WDATA),
  .M1_AXI_WLAST(M1_AXI_WLAST),
  .M1_AXI_WREADY(M1_AXI_WREADY),
  .M1_AXI_WSTRB(M1_AXI_WSTRB),
  .M1_AXI_WVALID(M1_AXI_WVALID),

  .debug_resetOut(debug_resetOut),
  .clk(io_mainClk),
  .externalInterrupt(1'b0),
  .jtag_tck(io_jtag_tck),
  .jtag_tdi(io_jtag_tdi),
  .jtag_tdo(io_jtag_tdo),
  .jtag_tms(io_jtag_tms),
  .resetx(io_asyncResetx),
  .softwareInterrupt(1'b0),
  .timerInterrupt(1'b0)
);        
/* verilator lint_on  PINMISSING */

axi_crossbar_wrap_2x2 # (
  .S_ID_WIDTH(1)
  ) uaxi_crossbar_wrap_2x2 (
    .clk(io_mainClk),
    .rst((~io_asyncResetx) | debug_resetOut),

    /*
     * AXI slave interface
     */
     // S0 read only (IRAM)
    .s00_axi_awid(1'b0),
    .s00_axi_awaddr(32'h0),
    .s00_axi_awlen(8'h00),
    .s00_axi_awsize(3'b000),
    .s00_axi_awburst(2'b00),
    .s00_axi_awlock(1'b0),
    .s00_axi_awcache(4'h0),
    .s00_axi_awprot(3'b000),
    .s00_axi_awqos(4'h0),
    .s00_axi_awuser(1'b0),
    .s00_axi_awvalid(1'b0),
    .s00_axi_awready(/* open */),
    .s00_axi_wdata(32'h0),
    .s00_axi_wstrb(4'b0),
    .s00_axi_wlast(1'b0),
    .s00_axi_wuser(1'b0),
    .s00_axi_wvalid(1'b0),
    .s00_axi_wready(/* open*/),
    .s00_axi_bid(/* open */),
    .s00_axi_bresp(/* open */),
    .s00_axi_buser(/* open */),
    .s00_axi_bvalid(/* open */),
    .s00_axi_bready(1'b1),
    .s00_axi_arid(M0_AXI_ARID),
    .s00_axi_araddr(M0_AXI_ARADDR),
    .s00_axi_arlen(M0_AXI_ARLEN),
    .s00_axi_arsize(M0_AXI_ARSIZE),
    .s00_axi_arburst(M0_AXI_ARBURST),
    .s00_axi_arlock(M0_AXI_ARLOCK),
    .s00_axi_arcache(M0_AXI_ARCACHE),
    .s00_axi_arprot(M0_AXI_ARPROT),
    .s00_axi_arqos(M0_AXI_ARQOS),
    .s00_axi_aruser(M0_AXI_ARUSER),
    .s00_axi_arvalid(M0_AXI_ARVALID),
    .s00_axi_arready(M0_AXI_ARREADY),
    .s00_axi_rid(M0_AXI_RID),
    .s00_axi_rdata(M0_AXI_RDATA),
    .s00_axi_rresp(M0_AXI_RRESP),
    .s00_axi_rlast(M0_AXI_RLAST),
    .s00_axi_ruser(M0_AXI_RUSER),
    .s00_axi_rvalid(M0_AXI_RVALID),
    .s00_axi_rready(M0_AXI_RREADY),

    .s01_axi_awid(M1_AXI_AWID),
    .s01_axi_awaddr(M1_AXI_AWADDR),
    .s01_axi_awlen(M1_AXI_AWLEN),
    .s01_axi_awsize(M1_AXI_AWSIZE),
    .s01_axi_awburst(M1_AXI_AWBURST),
    .s01_axi_awlock(M1_AXI_AWLOCK),
    .s01_axi_awcache(M1_AXI_AWCACHE),
    .s01_axi_awprot(M1_AXI_AWPROT),
    .s01_axi_awqos(M1_AXI_AWQOS),
    .s01_axi_awuser(M1_AXI_AWUSER),
    .s01_axi_awvalid(M1_AXI_AWVALID),
    .s01_axi_awready(M1_AXI_AWREADY),
    .s01_axi_wdata(M1_AXI_WDATA),
    .s01_axi_wstrb(M1_AXI_WSTRB),
    .s01_axi_wlast(M1_AXI_WLAST),
    .s01_axi_wuser(M1_AXI_WUSER),
    .s01_axi_wvalid(M1_AXI_WVALID),
    .s01_axi_wready(M1_AXI_WREADY),
    .s01_axi_bid(M1_AXI_BID),
    .s01_axi_bresp(M1_AXI_BRESP),
    .s01_axi_buser(M1_AXI_BUSER),
    .s01_axi_bvalid(M1_AXI_BVALID),
    .s01_axi_bready(M1_AXI_BREADY),

    .s01_axi_arid(M1_AXI_ARID),
    .s01_axi_araddr(M1_AXI_ARADDR),
    .s01_axi_arlen(M1_AXI_ARLEN),
    .s01_axi_arsize(M1_AXI_ARSIZE),
    .s01_axi_arburst(M1_AXI_ARBURST),
    .s01_axi_arlock(M1_AXI_ARLOCK),
    .s01_axi_arcache(M1_AXI_ARCACHE),
    .s01_axi_arprot(M1_AXI_ARPROT),
    .s01_axi_arqos(M1_AXI_ARQOS),
    .s01_axi_aruser(M1_AXI_ARUSER),
    .s01_axi_arvalid(M1_AXI_ARVALID),
    .s01_axi_arready(M1_AXI_ARREADY),
    .s01_axi_rid(M1_AXI_RID),
    .s01_axi_rdata(M1_AXI_RDATA),
    .s01_axi_rresp(M1_AXI_RRESP),
    .s01_axi_rlast(M1_AXI_RLAST),
    .s01_axi_ruser(M1_AXI_RUSER),
    .s01_axi_rvalid(M1_AXI_RVALID),
    .s01_axi_rready(M1_AXI_RREADY),

    /*
     * AXI master interface
     */
    .m00_axi_awid(crs_m00_axi_awid),
    .m00_axi_awaddr(crs_m00_axi_awaddr),
    .m00_axi_awlen(crs_m00_axi_awlen),
    .m00_axi_awsize(crs_m00_axi_awsize),
    .m00_axi_awburst(crs_m00_axi_awburst),
    .m00_axi_awlock(crs_m00_axi_awlock),
    .m00_axi_awcache(crs_m00_axi_awcache),
    .m00_axi_awprot(crs_m00_axi_awprot),
    .m00_axi_awqos(crs_m00_axi_awqos),
    .m00_axi_awregion(crs_m00_axi_awregion),
    .m00_axi_awuser(crs_m00_axi_awuser),
    .m00_axi_awvalid(crs_m00_axi_awvalid),
    .m00_axi_awready(crs_m00_axi_awready),
    .m00_axi_wdata(crs_m00_axi_wdata),
    .m00_axi_wstrb(crs_m00_axi_wstrb),
    .m00_axi_wlast(crs_m00_axi_wlast),
    .m00_axi_wuser(crs_m00_axi_wuser),
    .m00_axi_wvalid(crs_m00_axi_wvalid),
    .m00_axi_wready(crs_m00_axi_wready),
    .m00_axi_bid(crs_m00_axi_bid),
    .m00_axi_bresp(crs_m00_axi_bresp),
    .m00_axi_buser(crs_m00_axi_buser),
    .m00_axi_bvalid(crs_m00_axi_bvalid),
    .m00_axi_bready(crs_m00_axi_bready),
    .m00_axi_arid(crs_m00_axi_arid),
    .m00_axi_araddr(crs_m00_axi_araddr),
    .m00_axi_arlen(crs_m00_axi_arlen),
    .m00_axi_arsize(crs_m00_axi_arsize),
    .m00_axi_arburst(crs_m00_axi_arburst),
    .m00_axi_arlock(crs_m00_axi_arlock),
    .m00_axi_arcache(crs_m00_axi_arcache),
    .m00_axi_arprot(crs_m00_axi_arprot),
    .m00_axi_arqos(crs_m00_axi_arqos),
    .m00_axi_arregion(crs_m00_axi_arregion),
    .m00_axi_aruser(crs_m00_axi_aruser),
    .m00_axi_arvalid(crs_m00_axi_arvalid),
    .m00_axi_arready(crs_m00_axi_arready),
    .m00_axi_rid(crs_m00_axi_rid),
    .m00_axi_rdata(crs_m00_axi_rdata),
    .m00_axi_rresp(crs_m00_axi_rresp),
    .m00_axi_rlast(crs_m00_axi_rlast),
    .m00_axi_ruser(crs_m00_axi_ruser),
    .m00_axi_rvalid(crs_m00_axi_rvalid),
    .m00_axi_rready(crs_m00_axi_rready),

    .m01_axi_awid(crs_m01_axi_awid),
    .m01_axi_awaddr(crs_m01_axi_awaddr),
    .m01_axi_awlen(crs_m01_axi_awlen),
    .m01_axi_awsize(crs_m01_axi_awsize),
    .m01_axi_awburst(crs_m01_axi_awburst),
    .m01_axi_awlock(crs_m01_axi_awlock),
    .m01_axi_awcache(crs_m01_axi_awcache),
    .m01_axi_awprot(crs_m01_axi_awprot),
    .m01_axi_awqos(crs_m01_axi_awqos),
    .m01_axi_awregion(crs_m01_axi_awregion),
    .m01_axi_awuser(crs_m01_axi_awuser),
    .m01_axi_awvalid(crs_m01_axi_awvalid),
    .m01_axi_awready(crs_m01_axi_awready),
    .m01_axi_wdata(crs_m01_axi_wdata),
    .m01_axi_wstrb(crs_m01_axi_wstrb),
    .m01_axi_wlast(crs_m01_axi_wlast),
    .m01_axi_wuser(crs_m01_axi_wuser),
    .m01_axi_wvalid(crs_m01_axi_wvalid),
    .m01_axi_wready(crs_m01_axi_wready),
    .m01_axi_bid(crs_m01_axi_bid),
    .m01_axi_bresp(crs_m01_axi_bresp),
    .m01_axi_buser(crs_m01_axi_buser),
    .m01_axi_bvalid(crs_m01_axi_bvalid),
    .m01_axi_bready(crs_m01_axi_bready),
    .m01_axi_arid(crs_m01_axi_arid),
    .m01_axi_araddr(crs_m01_axi_araddr),
    .m01_axi_arlen(crs_m01_axi_arlen),
    .m01_axi_arsize(crs_m01_axi_arsize),
    .m01_axi_arburst(crs_m01_axi_arburst),
    .m01_axi_arlock(crs_m01_axi_arlock),
    .m01_axi_arcache(crs_m01_axi_arcache),
    .m01_axi_arprot(crs_m01_axi_arprot),
    .m01_axi_arqos(crs_m01_axi_arqos),
    .m01_axi_arregion(crs_m01_axi_arregion),
    .m01_axi_aruser(crs_m01_axi_aruser),
    .m01_axi_arvalid(crs_m01_axi_arvalid),
    .m01_axi_arready(crs_m01_axi_arready),
    .m01_axi_rid(crs_m01_axi_rid),
    .m01_axi_rdata(crs_m01_axi_rdata),
    .m01_axi_rresp(crs_m01_axi_rresp),
    .m01_axi_rlast(crs_m01_axi_rlast),
    .m01_axi_ruser(crs_m01_axi_ruser),
    .m01_axi_rvalid(crs_m01_axi_rvalid),
    .m01_axi_rready(crs_m01_axi_rready)

);

axi_ram  #(
  .ADDR_WIDTH(14),
  .ID_WIDTH(2)
  ) uaxi_ram (
  .clk(io_mainClk),
  .rst(~io_asyncResetx),

  .s_axi_awid(crs_m00_axi_awid),
  .s_axi_awaddr(crs_m00_axi_awaddr[13:0]),
  .s_axi_awlen(crs_m00_axi_awlen),
  .s_axi_awsize(crs_m00_axi_awsize),
  .s_axi_awburst(crs_m00_axi_awburst),
  .s_axi_awlock(crs_m00_axi_awlock),
  .s_axi_awcache(crs_m00_axi_awcache),
  .s_axi_awprot(crs_m00_axi_awprot),
  .s_axi_awvalid(crs_m00_axi_awvalid),
  .s_axi_awready(crs_m00_axi_awready),
  .s_axi_wdata(crs_m00_axi_wdata),
  .s_axi_wstrb(crs_m00_axi_wstrb),
  .s_axi_wlast(crs_m00_axi_wlast),
  .s_axi_wvalid(crs_m00_axi_wvalid),
  .s_axi_wready(crs_m00_axi_wready),
  .s_axi_bid(crs_m00_axi_bid),
  .s_axi_bresp(crs_m00_axi_bresp),
  .s_axi_bvalid(crs_m00_axi_bvalid),
  .s_axi_bready(crs_m00_axi_bready),
  .s_axi_arid(crs_m00_axi_arid),
  .s_axi_araddr(crs_m00_axi_araddr[13:0]),
  .s_axi_arlen(crs_m00_axi_arlen),
  .s_axi_arsize(crs_m00_axi_arsize),
  .s_axi_arburst(crs_m00_axi_arburst),
  .s_axi_arlock(crs_m00_axi_arlock),
  .s_axi_arcache(crs_m00_axi_arcache),
  .s_axi_arprot(crs_m00_axi_arprot),
  .s_axi_arvalid(crs_m00_axi_arvalid),
  .s_axi_arready(crs_m00_axi_arready),
  .s_axi_rid(crs_m00_axi_rid),
  .s_axi_rdata(crs_m00_axi_rdata),
  .s_axi_rresp(crs_m00_axi_rresp),
  .s_axi_rlast(crs_m00_axi_rlast),
  .s_axi_rvalid(crs_m00_axi_rvalid),
  .s_axi_rready(crs_m00_axi_rready)
);

axi2apb #(
  .AXI4_ID_WIDTH(2),
  .AXI4_USER_WIDTH(1)
) uaxi2apb (
  .ACLK(io_mainClk),
  .ARESETn(io_asyncResetx),

  .AWID_i(crs_m01_axi_awid),
  .AWADDR_i(crs_m01_axi_awaddr),
  .AWLEN_i(crs_m01_axi_awlen),
  .AWSIZE_i(crs_m01_axi_awsize),
  .AWBURST_i(crs_m01_axi_awburst),
  .AWLOCK_i(crs_m01_axi_awlock),
  .AWCACHE_i(crs_m01_axi_awcache),
  .AWPROT_i(crs_m01_axi_awprot),
  .AWREGION_i(crs_m01_axi_awregion),
  .AWUSER_i(crs_m01_axi_awuser),
  .AWQOS_i(crs_m01_axi_awqos),
  .AWVALID_i(crs_m01_axi_awvalid),
  .AWREADY_o(crs_m01_axi_awready),

  .WDATA_i(crs_m01_axi_wdata),
  .WSTRB_i(crs_m01_axi_wstrb),
  .WLAST_i(crs_m01_axi_wlast),
  .WUSER_i(crs_m01_axi_wuser),
  .WVALID_i(crs_m01_axi_wvalid),
  .WREADY_o(crs_m01_axi_wready),

  .BID_o(crs_m01_axi_bid),
  .BRESP_o(crs_m01_axi_bresp),
  .BVALID_o(crs_m01_axi_bvalid),
  .BUSER_o(crs_m01_axi_buser),
  .BREADY_i(crs_m01_axi_bready),

  .ARID_i(crs_m01_axi_arid),
  .ARADDR_i(crs_m01_axi_araddr),
  .ARLEN_i(crs_m01_axi_arlen),
  .ARSIZE_i(crs_m01_axi_arsize),
  .ARBURST_i(crs_m01_axi_arburst),
  .ARLOCK_i(crs_m01_axi_arlock),
  .ARCACHE_i(crs_m01_axi_arcache),
  .ARPROT_i(crs_m01_axi_arprot),
  .ARREGION_i(crs_m01_axi_arregion),
  .ARUSER_i(crs_m01_axi_aruser),
  .ARQOS_i(crs_m01_axi_arqos),
  .ARVALID_i(crs_m01_axi_arvalid),
  .ARREADY_o(crs_m01_axi_arready),

  .RID_o(crs_m01_axi_rid),
  .RDATA_o(crs_m01_axi_rdata),
  .RRESP_o(crs_m01_axi_rresp),
  .RLAST_o(crs_m01_axi_rlast),
  .RUSER_o(crs_m01_axi_ruser),
  .RVALID_o(crs_m01_axi_rvalid),
  .RREADY_i(crs_m01_axi_rready),

  .PENABLE(apbrg_penable),
  .PWRITE(apbrg_pwrite),
  .PADDR(apbrg_paddr),
  .PSEL(apbrg_psel),
  .PWDATA(apbrg_pwdata),
  .PRDATA(apbrg_prdata),
  .PREADY(apbrg_pready),
  .PSLVERR(apbrg_pslverr)
  );

simpleuart_apb_wrap u_simpleuart_apb_wrap (
  .PADDR(apbrg_paddr[4:0]),
  .PSEL(apbrg_psel),
  .PENABLE(apbrg_penable),
  .PREADY(apbrg_pready),
  .PWRITE(apbrg_pwrite),
  .PWDATA(apbrg_pwdata),
  .PRDATA(apbrg_prdata),
  .PSLVERR(apbrg_pslverr),
  .io_uart_txd(io_uart_txd),
  .io_uart_rxd(io_uart_rxd),
  .pclk(io_mainClk),
  .resetn(io_asyncResetx)
);

endmodule

Verilatorによるsimulation

simulation環境作成に当たり、Briey soc環境を参考にしました。
なので、準備としてBriery socのSIMが流れるように環境を整えます。
READMEのBriey SoCに記載があるように、以下の様に色々なモジュールをインストールします。
一見論理SIMに関係ある?というようなものもインストールしますが、本環境を使うと画像データをリアルタイムで表示出来たりしますので便利です。

sudo apt-get install build-essential xorg-dev libudev-dev libgl1-mesa-dev libglu1-mesa-dev libasound2-dev libpulse-dev libopenal-dev libogg-dev libvorbis-dev libaudiofile-dev libpng12-dev libfreetype6-dev libusb-dev libdbus-1-dev zlib1g-dev libdirectfb-dev libsdl2-dev

sbt "runMain vexriscv.demo.Briey"でBriey socのRTLを生成すれば、

cd src/test/cpp/briey
make clean run TRACE=yes

で波形ダンプ付きでSIMが流れます。

SIM TOP階層(C++階層)の作成

本dirにあるmakefileを見るとSoCのRTL以外にmain.cppがverilatorに呼ばれているのが分かります。
該当行は以下です。

verilator -cc  ../../../../Briey.v -CFLAGS -std=c++11  ${ADDCFLAGS} --gdbbt ${VERILATOR_ARGS} -Wno-WIDTH -Wno-UNOPTFLAT --x-assign unique --exe main.cpp

main.cppはテスト階層になります。
このmain.cppを参考にしてオリジナルSoC用のmain.cppを作成します。  先頭でincludeされているVBriey.hは、verilog階層のtopモジュール名がBrieryであることに由来しています。
オリジナルSOCのtopモジュール名の先頭にVを付けたファイル名の*.hをインクルードするようにします。
最低限SIMするだけなら、デザイン由来のヘッダファイルはこれだけをインクルードすればSIM可能なようですが、 main.cppは他にもヘッダファイルをインクルードしていて、JTAGを動かすためのものやverilatorが持っているヘッダファイルをインクルードしています。
 verilogデザイン由来のものは、階層名をファイル名の一部にした*.hのファイル名になるので複雑です。インクルードするファイル名を間違えないようにするため、あらかじめ作成しておくと良いです。
verilator -cc verilogファイル名 ...コンパイルが出来るので、生成されたディレクトobj_dir/の中を見ると良いです。 -f でverilogのファイルリスト指定も出来ます。verilog simになれた方はこちらの形式の方がしっくりくるかと思います。
verilator -cc -f filelist

一部憶測も含んでしまいますが、main.cppを解説します。

#include "VBriey_VexRiscv.h"
VexRiscvコア階層のヘッダファイルです。コアは差し替えてモジュール名が変わっているのと、また階層も異なっているので、ファイル名は変わっているはずです。
obj_dir/配下を見ると、Vxxx_VexRiscvAxi4.hがあるはずです。このファイルをインクルードするように変更すれば良いです。
 本ヘッダファイルを見るとDESIGN SPECIFIC STATEに続くstruct {}のところに端子名によく似た名前があります。verilatorがこのような名前に変換しているのだろうと思われます。
 また、このVexRiscvコアのヘッダファイルをインクルードしている理由は、main.cppclass VexRiscvTracerというclass定義がありますが、 このclassの中でVexRiscvコアを呼び出し、命令トレースログなどをファイルへ出力する仕組みを実現しているようです。
 ここで呼び出されているVexRiscvコアのモジュール名も変更します。

VBriey_VexRiscv *cpu;  
VexRiscvTracer(VBriey_VexRiscv *cpu){

の2行をご自分のSoC環境に合わせて書き換えます。

main.cppのヘッダファイルのincludeしている行へ戻ると、以降class定義がされています。 不要なものは、削除して構いません。
必要なclassを、以下に列挙します。

  • 先に記載したclass VexRiscvTracer
  • class BrieyWorkspace

の2個を残せばよいです。

簡単にそれぞれのclassを説明すると

  • class SdramConfigから続くのはsdram関連のclassです。特に利用していなければ削除してよいです。
  • class Displayはsimulation中,リアルタイムで画像データを表示する仕組みです。使わないのであれば、削除します(残しておいても悪さはしません)
  • class VgaはSoCとの結線記述があるので削除します。

class BrieyWorkspace

class BrieyWorkspaceは重要です。clockの供給、リセット解除を行います。 public Workspace<VBriey>{
赤字のVBrieyはTOPモジュール名に"V"を頭に付けたものです。適宜変更します。

ClockDomainに続く記述は、クロック名の定義になります。
*axiClk : クロック名
new ClockDomain()でクロックの作成
()内で、左から順に、モジュールの端子との結線、リセット要因(NULL定義)、周期(ns)、発信開始時刻(ns)
class ClockDomain../common/framework.hの中で定義されています。 ここで指定するクロックがUARTのクロックに利用しているのであれば正しい周期を設定してください。
シリアルコンソール出力させるために必要です。
(試していませんが)verilatorはサイクルシミュレータなので、2系統以上のクロックがある場合は、整数倍でないと動かないかもしれません。

AsyncResetに続く記述はリセットです。先のクロックの要領で結線及びリセット解除タイミングを定義します。
このリセットは正論理です。私のデザインは負論理リセットだったので、負論理用のリセットclassを作成しました。
framework.hの中で定義されているclass AsyncResetを参考に作成します。

class AsyncResetx : public TimeProcess{
public:
        CData* resetx;
        uint32_t state;
        uint64_t duration;
        AsyncResetx(CData *resetx, uint64_t duration){
                this->resetx = resetx;
                *resetx = 1;
                state = 0;
                this->duration = duration;
                schedule(0);
        }

        virtual void tick(){
                switch(state){
                case 0:
                        *resetx = 0;
                        state = 1;
                        schedule(duration);
                        break;
                case 1:
                        *resetx = 1;
                        state = 2;
                        break;
                }
        }

};

もっとうまい作り方がある気がしますが、力技で作ってしまいました。

Jtag, UartRx は端子名を変更していなければそのまま使えます。
timeProcesses.push_back(xxx)は、おまじないでそのまま使います。
SdramConfig 以下は不要なので削除します。

こうしてできたmain.cppをsimlution実行dirへ置きます。 また、../common/*のファイルをsim実行dirの./commonへコピーします。中身は以下になります。
framework.h jtag.h uart.h framework.hclass AsyncResetxの定義が追加されたものになります。

出来上がったmain.cppを以下に貼り付けました。
あくまで参考ですので、作成したSoCに合わせて変更する必要があります。

verilator sim sample main · GitHub

RAMデータ(命令コード)の読み込み

RAMモデルには$readmemhによる初期化が仕込んであるのですが、verilatorのsim開始時にうまく読み込めませんでした。
なので、$readmemhを実行するモジュールを作成しました。

module raminit;
initial begin
    // IRAM初期化
    $readmemh("iram_init_file.mem", soc_slideshow.uaxi_ram.mem);
end
endmodule

本モジュールはインスタンスしないで、読み込むのみです。その場合にTOPモジュールが複数あるとメッセージが出てverilatorが動かないので、それを避けるため verilatorのオプションで-Wno-MULTITOPを付加しています。

makefileの代わりにsim実行スクリプトを作成します

makefileを改造してもよいのですが、あまりmakefileに詳しくない為、simを実行するシェルスクリプトを作りました。

set ADDCFLAGS = ""
verilator  -cc -f ../soc_slideshow.f ../tb/raminit_for_verilator.v -CFLAGS -std=c++11 $ADDCFLAGS -CFLAGS -pthread  -CFLAGS -lSDL2 -LDFLAGS -lSDL2 -CFLAGS "-O3" -CFLAGS -DVGA -CFLAGS -DTRACE_START=0 --gdbbt $WNO --x-assign unique --exe main.cpp
make -j -C obj_dir/ -f Vsoc_slideshow.mk Vsoc_slideshow
obj_dir/Vsoc_slideshow
  • soc_slideshow.f はSoCのファイルリスト
  • raminit_for_verilator.vは先に記載したRAMの初期化モジュール

fstファイル(波形ファイル)を出力する場合は
set ADDCFLAGS = "-CFLAGS -DTRACE --trace-fst " とすればよいです。

出来上がったcshellスクリプトを以下に貼り付けます。
verilator 実行cshellスクリプト · GitHub

ダウンロード後chmod +x run_verilator.cshで実行権を付加後、

./run_verilator.csh
もしくは
./run_verilator.csh  wave   #波形付き

でSIMが流れます。

JTAGデバッグ

OpenOCDの準備

riscv用のopenocdを準備する。

github.com

適当なdirを準備し
上記サイトのREADMEに従いインストールする。

sudo apt-get install libtool automake libusb-1.0.0-dev texinfo libusb-dev libyaml-dev pkg-config
git clone https://github.com/SpinalHDL/openocd_riscv.git
cd openocd_riscv
./bootstrap
./configure --enable-ftdi --enable-dummy
make

OpenOCDの実行

SIMを動かしている状態で以下を起動します。

src/openocd -f tcl/interface/jtag_tcp.cfg -c 'set BRIEY_CPU0_YAML ../VexRiscv/cpu0.yaml' -f tcl/target/briey.cfg

../VexRiscv/cpu0.yaml はvexriscvコアのRTLを作成した際に生成されたファイルになります。
OpenOCDのログメッセージは以下

Open On-Chip Debugger 0.11.0+dev-04033-g058dfa50d (2024-04-24-00:03)
Licensed under GNU GPL v2
For bug reports, read
        http://openocd.org/doc/doxygen/bugs.html
DEPRECATED! use 'adapter driver' not 'interface'
Info : only one transport option; autoselect 'jtag'
../VexRiscv/cpu0.yaml
DEPRECATED! use 'adapter speed' not 'adapter_khz'
DEPRECATED! use 'adapter srst delay' not 'adapter_nsrst_delay'
Info : set servers polling period to 50ms
Info : clock speed 4000 kHz
Info : JTAG tap: fpga_spinal.bridge tap/device found: 0x10001fff (mfg: 0x7ff (), part: 0x0001, ver: 0x1)
[fpga_spinal.cpu0] Target successfully examined.
Info : starting gdb server for fpga_spinal.cpu0 on 3333
Info : Listening on port 3333 for gdb connections
requesting target halt and executing a soft reset
Info : Listening on port 6666 for tcl connections
Info : Listening on port 4444 for telnet connections

openocdを実行するとSIMを動かしているターミナルには以下の様なメッセージが出ます。

CONNECTION RESET
CONNECTED

GDBの実行

riscvのツールチェーンに含まれているgdbを利用します。
elfファイルを準備して

riscv64-unknown-elf-gdb ./slideshow.elf
GNU gdb (GDB) 14.0.50.20230701-git
Copyright (C) 2023 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later http://gnu.org/licenses/gpl.html
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "--host=x86_64-pc-linux-gnu --target=riscv64-unknown-elf".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
https://www.gnu.org/software/gdb/bugs/.
Find the GDB manual and other documentation resources online at:
    http://www.gnu.org/software/gdb/documentation/.

For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./slideshow.elf...
(gdb)  target remote localhost:3333
Remote debugging using localhost:3333
crtStart () at ../src/crt.S:8
8         j crtInit
(gdb) monitor reset halt
JTAG tap: fpga_spinal.bridge tap/device found: 0x10001fff (mfg: 0x7ff (), part: 0x0001, ver: 0x1)
(gdb) load
Loading section .vector, size 0x144 lma 0x40000000
Loading section .memory, size 0x170 lma 0x40000144
Loading section .text.printchar, size 0x2c lma 0x400002b4
Loading section .text.prints, size 0xd8 lma 0x400002e0
Loading section .text.printi, size 0xfc lma 0x400003b8
Loading section .text.print, size 0x208 lma 0x400004b4
Loading section .text.printf, size 0x44 lma 0x400006bc
Loading section .text.uart_init, size 0x4c lma 0x40000700
Loading section .text.simpleuart_outbyte, size 0x4c lma 0x4000074c
Loading section .text.main, size 0x9c lma 0x40000798
Loading section .text.irqCallback, size 0x1c lma 0x40000834
Loading section .rodata, size 0x3d lma 0x40000850
Start address 0x40000000, load size 2189
Transfer rate: 2 KB/sec, 182 bytes/write.
(gdb) c
Continuing.

elfをloadして、continueすると、プログラムが最初から始まります。
SIMを実行している端末では

CONNECTION RESET
CONNECTED
SYSCLKFREQ=83333000
uart setting done
LED chika chika

UARTのログ(赤字)が出て、実行されているのが分かります。

VS codeからGDBの実行

debug対象の*.elfがあるdirでVScodeを立ち上げます。
code .を実行
拡張機能Cortex-Debugをインストールします。(riscvだけどmemory windowが使えたりと便利なので)

Cortex-Debug拡張機能

左ペインの虫さんマークをクリックし、"launch.jsonファイルを作成します"ボタンを押します。
デバッガ―の選択窓が出るのでCortex Debugを選択します。
殆んど何も書かれていないひな形ファイルが開くので,以下の様にlaunch.jsonを作成もしくは追記します。

{
    // IntelliSense を使用して利用可能な属性を学べます。
    // 既存の属性の説明をホバーして表示します。
    // 詳細情報は次を確認してください: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Riscv (Cortex Debug)",
            "cwd": "${workspaceFolder}",
            "executable": "${workspaceFolder}/slideshow.elf",
            "request": "launch",
            "type": "cortex-debug",
            "runToEntryPoint": "main",
            "servertype": "external",
            "gdbPath" : "/mnt/ext_storage/riscv/tools/rv64gc/bin/riscv64-unknown-elf-gdb",
            "gdbTarget": "localhost:3333",
        },
    ]
}

gdbPath行はgdbのパスを書いてください。
executable行は読み込むelfファイルを指定します。
ctrl+sで保存し、 ▶マークがある"Riscv (Cortex Debug)"をクリックします。
gdbが起動されてOpenOCDと繋がります。
デバッガの制御ボタンが出てきます。
main()関数のすぐのところで停止します。

 ソースコードの行番号あたりをクリックするとブレークポインタの設定が出来、プログラムの実行が停止します。 左ペインにはレジスタの状態が表示されています。
 ソースコードの変数のところをホバーすると現在の値がバルーンで出たり、右クリック→"ウオッチに追加"で左ペインに変数を置いとけます。
などなどデバッグが可能です。
再生ボタン(|▶)を押すと、プログラムが再開されます。

VScodeデバッグ

以上になります。

論理SIMでjtagが使えるのは新鮮です。
論理SIMでは波形が出せるので、波形debugが最強ですが、波形を出せないような長時間SIMの場合とか役に立つ場面があるかもしれません。

参考にしたサイト

GitHub - SpinalHDL/VexRiscv: A FPGA friendly 32 bit RISC-V CPU implementation
GitHub - SpinalHDL/openocd_riscv: Spen's Official OpenOCD Mirror