みねっちょのマイコン関係ブログ

組込開発系フリーソフトやハードの情報発信ブログ

ARM Cortex-M0 コアを無償な Verilog シミュレータで動かす

サイト内 Google 検索:


最終更新: 2021-05-16
ARM 社の「DesingStart プログラム」としてクリック スルー ライセンス可能な、ARM Cortex-M0 CPU コア単体の Obfuscated (難読化) HDL を、無償な Verilog シミュレータ Icarus Verilog や Verilator で動かす手順を紹介します。
Windows 10 上 WSL の Ubuntu-20.04 で提供される標準パッケージを使用することで、C コンパイラを使った ROM コードの作成と、Verilog HDL シミュレーションとが無償で実行可能です。CPU の動作を理解する為に必要なレジスタや AHB バスの状態は、クロックサイクル毎にテキストダンプしています。また、GUI での波形確認も無償で行えます。



目次:

こんな方にお薦め:

  • ARM Cortex-M シリーズ用のブート プログラムの動作を確認したい
  • FPGA や SoC 用に設計した AHB ペリフェラルの動作を手軽に確認したい
    (但、サインオフ シミュレータとしては使えません)
  • AMBA3 AHB Lite を実際に動かして仕様を理解したい
  • CPU コアのシミュレーションのコツを覚えたい

[広告]


前提条件:

ARM DesignStart プログラム (CPU本体):

ARM 社は、Cortex-M3 コア ベースの SoC サブシステム SSE-050 と、Cortex-M0 コアを「DesignStart プログラム」として評価目的や個人利用向けにクリック スルー契約だけでダウンロード可能な形態で提供しています。その中には、FPGA や SoC 化用のペリフェラルVerilog HDL ソースコードだけでなく、CPU コア本体の Obfuscated (難読化) HDL も含まれています。この Verilog HDL は3大 EDA ベンダのツールを使ってシミュレーションする事を想定していますが、無償の Icarus Verilog や Verilator でもシミュレーション可能です。

「ARM DesignStart プログラム」のデリバラブル (Deliverable) パッケージは、次のサイトからダウンロード可能です。ライセンス契約をよく読み、違反の無い様に使用してください。尚、今回使用するのは「DesignStart Eval」の Cortex-M0 コアです。また、C コンパイラを含めたツールは全て Windows 10 WSL の Ubuntu-20.04 上に標準パッケージとして提供されている無償の物を使用しますので、「Cortex-M0 DesignStart Eval Package」以外の追加のツールのダウンロードやライセンスは不要です。 www.arm.com

テストベンチと簡易 DUT:

Icarus Verilog にも Verilator にも共通に使用可能なテストベンチの大まかな作成方針は、冒頭に紹介した【こちらの別記事】と変わりありません。但、下位階層のテストベンチは、テキスト ダンプをCPU コアのレジスタや AHB バスに変更しています。また、VCD ダンプを追加しています。(今回のテストベンチで、VCD ダンプが可能なのは Icarus Verilog だけです。Verilator で VCD ダンプを行うには、別の方法が必要となります。) 更に、【こちらの別記事】で作成した ROM コード用の、プログラム終了時にシミュレーションを自動で停止させる仕組みを追加しています。

DUT (Design Under Test) 用の HDL には、CPUコアのインスタンス配置と、簡易的なパイプライン ROM とパイプライン SRAM を記述してあります。この ROM と RAM は AHB にダイレクトに接続してあり、ウエイト無しでアクセスが可能です。また、SRAM へはバイト単位にライト可能です。

また、下位テストベンチ上で、NMI を30サイクル目でアサート、40サイクル目でデアサートしています。更に、下位テストベンチ上で、定義した `SIM_STOP の番地 (0x80000000) 番地にライト アクセスを行うとシミュレーションの実行が停止すると同時に、C 言語の main() 関数からの戻り値が格納されている R0 レジスタの値を テストベンチでダンプして PASS (戻り値 = 0) と FAIL (戻り値が 0 以外) を表示します。FAIL の時には R0 の値を表示します。

上位階層のテストベンチ (Verilog HDL 版):

cat から EOF までを ubuntu にコピー&ペーストすると、frame.v というファイルが出来ます。

cat << "EOF" > frame.v
`default_nettype none
`timescale 1ns/1ps

module frame;

  parameter     PERIOD = 1000;

  /* Testbench */
  bench u_bench();

  /* Clock and DumpTrigger */
  always begin
    u_bench.clk = 1;
    #(PERIOD/2-1);
    u_bench.dumptrig = 0;
    #1;
    u_bench.clk = 0;
    #(PERIOD/2-1);
    u_bench.dumptrig = 1;
    #1;
  end

endmodule

`default_nettype wire
EOF

上位階層のテストベンチ (C++ 版):

cat から EOF までを ubuntu にコピー&ペーストすると、cframe.cpp というファイルが出来ます。

cat << "EOF" > cframe.cpp
#pragma onece
#include <iostream>
#include "verilated.h"
#include "obj_dir/Vbench.h"

int main(int argc, char* argv[]) {

  /* Command Line Arguments */
  Verilated::commandArgs(argc, argv);

  /* Testbench */
  Vbench* u_bench = new Vbench();

  /* Clock and DumpTrigger */
  while (!Verilated::gotFinish()) {
    u_bench->bench__DOT__clk = 1;
    u_bench->eval();
    u_bench->bench__DOT__dumptrig = 0;
    u_bench->eval();
    u_bench->bench__DOT__clk = 0;
    u_bench->eval();
    u_bench->bench__DOT__dumptrig = 1;
    u_bench->eval();
  }

  /* Closing Operation */
  u_bench->final();
  delete u_bench;
  return 0;
}
EOF

2021-05-02: Closing Operation 追加

下位階層のテストベンチ (Verilog HDL のみ):

cat から EOF までを ubuntu にコピー&ペーストすると、bench.v というファイルが出来ます。

cat << "EOF" > bench.v
`default_nettype none
`timescale 1ns/1ps

module bench;

  `define       SIM_STOP 32'h80000000
  `define       TARG u_dut.u_CORTEXM0INTEGRATION
  parameter     ENDCOUNT = 200, RSTCOUNT = 10;
  parameter     NMISTART = 30, NMIEND   = 40;

  reg           clk = 1, dumptrig = 0, nrst = 1;
  reg           nmi = 0, result_en = 0;
  time          count = 0;

  /* Reset and Finish */
  always @(posedge clk) begin
    if (count == ENDCOUNT) $finish;
    else if (count == 0) nrst <= 0;
    else if (count == RSTCOUNT) nrst <= 1;
    else if (count == NMISTART) nmi <= 1;
    else if (count == NMIEND)   nmi <= 0;
    count <= count + 1;
  end

  /* Auto Simulation Stop */
  always @(posedge clk) begin
    if (`TARG.HTRANS[1] == 1 && `TARG.HADDR == `SIM_STOP) result_en <= 1;
    if (result_en) begin
      $display;              $display("*******************************");
      if (`TARG.HWDATA == 0) $display("***         SUCCESS         ***");
      else                   $display("*** FAILED: code 0x%08h ***", `TARG.HWDATA);
                             $display("*******************************");
      $finish;
    end
  end


  /* Design Under Test */
  dut u_dut (.clk(clk), .nrst(nrst), .nmi(nmi));

  /* Memory Load */
  initial begin
    $readmemh("main.v", u_dut.irom);
  end

  /* Text Dump */
  initial begin
    $display("------------------------------------------------------------------------------------------------------------------------");
    $display("        <==PC==> <==SP==> <=xPSR=> <==LR==> :              AHB-Lite                :         General Registers          ");
    $display("------------------------------------------------------------------------------------------------------------------------");
    $display("                                                               H        H                                               ");
    $display("                                                H   H      H   R        W      H                                        ");
    $display("   C                         x                H T   A      W   D        D      R H                                      ");
    $display("   O  n             M        P                P R   D      R   A        A      E R      R        R        R        R    ");
    $display("   U  R    P        S        S        L       R A   D      I   T        T      A E      0        0        0        0    ");
    $display("   N  S    C        P        R        R       O N   R      T   A        A      D S      3        2        1        0    ");
    $display("   T  T <======> <======> <======> <======> : T S <======> E <======> <======> Y P : <======> <======> <======> <======>");
    $display("------------------------------------------------------------------------------------------------------------------------");
  end
  always @(posedge dumptrig) begin
    $write("%4d: %1b", count, nrst);
    $write(" %8h %8h %8h %8h", `TARG.cm0_pc, `TARG.cm0_msp, `TARG.cm0_xpsr, `TARG.cm0_r14);
    $write(" : %1h %1h %8h %1h", `TARG.HPROT, `TARG.HTRANS, `TARG.HADDR, `TARG.HWRITE);
    $write(" %8h %8h %1h %1h", `TARG.HRDATA, `TARG.HWDATA, `TARG.HREADY, `TARG.HRESP);
    $write(" : %8h %8h %8h %8h", `TARG.cm0_r03, `TARG.cm0_r02, `TARG.cm0_r01 , `TARG.cm0_r00);
    $display;
  end

  /* VCD Dump */
  `ifdef DUMP_VCD
    initial begin
      $dumpfile("u_dut.vcd");
      $dumplimit(10000000); // 10M bytes
      $dumpvars(1, `TARG);
    end
  `endif

endmodule

`default_nettype wire
EOF

2021-04-16: $write 文内誤字修正 (HWDATA ⇒ HRDATA)
2021-04-16: $display 文内誤字修正 (コメントの HRDARA ⇒ HRDATA, HWDARA ⇒ HWDATA)
2021-04-18: $display 文内 FAILD: code の部分を %8h ⇒ %08h

CPU外側のROMとRAMだけの簡易DUT (Verilog HDL のみ):

cat から EOF までを ubuntu にコピー&ペーストすると、dut.v というファイルが出来ます。

cat << "EOF" > dut.v
`default_nettype none

module dut
  (input clk, input nrst, input nmi);

  wire  [31:0]  haddr, hrdata, hwdata;
  wire  [2:0]   hsize;
  wire  [1:0]   htrans;
  wire          hwrite;

  /****************/
  /* Pipeline ROM */
  /****************/
  parameter             irom_msb = 11;
  reg   [31:0]          irom[0:2**(irom_msb-1)-1];
  reg   [irom_msb:2]    irom_addr;

  wire irom_sel = (haddr[31:28] == 4'h0) & htrans[1] & ~hwrite; // 0x0xxxxxxx
  wire  [31:0]  irom_out = irom[irom_addr[irom_msb:2]][31:0];

  always @(posedge clk) begin
    if (irom_sel) irom_addr[irom_msb:2] <= haddr[irom_msb:2];
  end

  /*****************/
  /* Pipeline SRAM */
  /*****************/
  parameter             psram_msb = 11;
  reg   [31:0]          psram[0:2**(psram_msb-1)-1];
  reg   [psram_msb:2]   psram_addr;
  reg   [3:0]           psram_wr, byte_en;
  reg                   psram_rd;

  wire psram_sel = (haddr[31:28] == 4'h2) & htrans[1]; // 0x2xxxxxxx
  wire  [31:0]  psram_out = psram[psram_addr[psram_msb:2]][31:0];

  /** Byte enable decoder **/
  always @* begin
    casez ({hsize[2:0], haddr[1:0]})
      {3'b000, 2'b00} : byte_en[3:0] = 4'b0001; // 1 byte,  (4n +0)
      {3'b000, 2'b01} : byte_en[3:0] = 4'b0010; // 1 byte,  (4n +1)
      {3'b000, 2'b10} : byte_en[3:0] = 4'b0100; // 1 byte,  (4n +2)
      {3'b000, 2'b11} : byte_en[3:0] = 4'b1000; // 1 byte,  (4n +3)
      {3'b001, 2'b0?} : byte_en[3:0] = 4'b0011; // 2 bytes, (2n +0)
      {3'b001, 2'b1?} : byte_en[3:0] = 4'b1100; // 2 bytes, (2n +1)
      default         : byte_en[3:0] = 4'b1111; // 4, 8, 16 bytes
    endcase
  end

  always @(posedge clk) begin
    if (psram_sel) psram_addr[psram_msb:2] <= haddr[psram_msb:2];
    psram_rd <= psram_sel & ~hwrite;
    psram_wr[3:0] <= {4{psram_sel & hwrite}} & byte_en[3:0];
    if (psram_wr[3]) psram[psram_addr[psram_msb:2]][31:24] <= hwdata[31:24];
    if (psram_wr[2]) psram[psram_addr[psram_msb:2]][23:16] <= hwdata[23:16];
    if (psram_wr[1]) psram[psram_addr[psram_msb:2]][15: 8] <= hwdata[15: 8];
    if (psram_wr[0]) psram[psram_addr[psram_msb:2]][ 7: 0] <= hwdata[ 7: 0];
  end

  /**************/
  /* HRDATA Mux */
  /**************/
  assign hrdata[31:0] = psram_rd?psram_out[31:0]:irom_out[31:0];

  /**********************/
  /* Cortex-M0 CPU Core */
  /**********************/

  /* verilator lint_off PINCONNECTEMPTY */
  /* verilator lint_off PINMISSING */
  CORTEXM0INTEGRATION u_CORTEXM0INTEGRATION (
    /** Clocks **/
    .HCLK(clk), .FCLK(clk), .SCLK(clk), .DCLK(1'b0),
    /** Resets **/
    .PORESETn(nrst), .HRESETn(nrst), .DBGRESETn(nrst),

    /** AMBA3 AHB-Lite **/
    .HADDR(haddr[31:0]),
    .HWRITE(hwrite),
    .HSIZE(hsize[2:0]),
    .HBURST(/* empty */),
    .HPROT(/* empty */),
    .HTRANS(htrans[1:0]),
    .HRDATA(hrdata[31:0]),
    .HWDATA(hwdata[31:0]),
    .HREADY(1'b1),
    .HRESP(1'b0),
    .HMASTER(/* empty */),
    .HMASTLOCK(/* empty */),

    /** Interrupts **/
    .NMI(nmi), .IRQ(32'b0),

    /** Others **/
    .SWCLKTCK(1'b0), .SWDITMS(1'b0), .nTRST(1'b0), .TDI(1'b0),
    .DBGRESTART(1'b0), .EDBGRQ(1'b0), .SE(1'b0), .RSTBYPASS(1'b0),
    .RXEV(1'b0), .STCALIB(26'b0), .STCLKEN(1'b0),
    .IRQLATENCY(8'b0), .ECOREVNUM(28'b0),
    .SLEEPHOLDREQn(1'b1), .WICENREQ(1'b0), .CDBGPWRUPACK(1'b0)
  );
  /* verilator lint_on PINCONNECTEMPTY */
  /* verilator lint_on PINMISSING */

endmodule
EOF

Verilog HDLのコンパイルと実行:

以下に Icarus Verilog と Verilator のコンパイルスクリプトを示します。

「Cortex-M0 DesignStart Eval Package」を展開したディレクトリを「M0_ROOT」という環境変数にセットしています。この部分は、御自身のディレクトリに書き換えてください。

また、テスト用の ROM コードファイルには、【こちらの記事】で作成した「main.v」というファイルを使用します。

尚、今回使うテストベンチとシェルスクリプトの書き方で VCD ダンプが可能なのは Icarus Verilog だけです。Verilator では PLI の組み込みが必要となります。

Icarus Verilog の場合:

Icarus Verilog の場合のコンパイル用シェル スクリプトは次の通りです。

cat から EOF までを ubuntu にコピー&ペーストすると、ivlog.sh というファイルが出来ます。

cat << "EOF" > ivlog.sh
#!/bin/dash -x

export M0_ROOT=/home/minettyo/Cortex-M0/AT510-MN-80001-r2p0-00rel0

if [ -e a.out ]; then
  /bin/rm a.out
fi

iverilog -Wall -Winfloop -Wno-timescale -g2005  \
-s frame frame.v bench.v dut.v -DDUMP_VCD       \
-y ${M0_ROOT}/cores/cortexm0_designstart_r2p0/logical/cortexm0_integration/verilog
EOF

前記のファイルの「M0_ROOT」の部分を書き換えが完了したら、ubuntu で次の通りコマンドを打つと、コンパイルが実行されます。「./a.out」でシミュレーションが実行され、ターミナル上にテキストダンプが出力されます。ファイルに保存したい場合には、「./a.out &> foo.log」等とリダイレクションをします。また、「u_dut.vcd」という名前の波形ファイルが出来ます。

chmod +x vlog.sh
vlog.sh
./a.out

Verilator の場合:

Icarus Verilog の場合のコンパイル用シェル スクリプトは次の通りです。尚、今回のテストベンチでは、Verilator での VCD ダンプには対応していません。

cat から EOF までを ubuntu にコピー&ペーストすると、veri.sh というファイルが出来ます。

cat << "EOF" > veri.sh
#!/bin/dash -x

export M0_ROOT=/home/minettyo/Cortex-M0/AT510-MN-80001-r2p0-00rel0

if [ -e obj_dir/Vbench ]; then
  /bin/rm obj_dir/Vbench
fi

verilator -Wall -Wno-SYNCASYNCNET       \
-Wno-WIDTH -Wno-UNUSED -Wno-UNOPTFLAT   \
--cc bench.v dut.v                      \
--exe cframe.cpp                        \
-y ${M0_ROOT}/cores/cortexm0_designstart_r2p0/logical/cortexm0_integration/verilog

cd obj_dir
/bin/cp ../main.v .

export CXX='g++'
export CXXFLAGS='-Og -g -gdwarf-2 -fPIC'
export LINK='g++'
export LDFLAGS=''
make -e -f Vbench.mk

cd ..

time ./obj_dir/Vbench

EOF

このファイルの「M0_ROOT」の部分の書き換えが完了したら、ubuntu で次の通りコマンドを打つと、コンパイルが実行されます。「./obj_dir/Vbench」でシミュレーションが実行され、ターミナル上にテキストダンプが出力されます。ファイルに保存したい場合には、「./obj_dir/Vbench &> foo.log」等とリダイレクションをします。

chmod +x veri.sh
veri.sh
./obj_dir/Vbench

テキスト ダンプの追いかけ方:

一番左にサイクルカウント数とリセット端子、次にプログラム カウンタ(PC) を表示しています。この値を、ROM コードのビルド時に作成した C 言語のソースコード入りディスアセンブル リストと見比べます。記事が長くなりましたので、別稿に改めたいと思います。 f:id:minettyo:20210417005803p:plain 2021-04-17: HRDATA の列に HWDATA だったのを修正、下線付加

f:id:minettyo:20210418085841p:plain 2021-04-18: =SIM_STOP の配置を4バイト アラインに修正、下線付加

GTKWave による波形の確認:

Icarus Verilog によるシミュレーションを実行すると、ディレクトリに「u_dut.vcd」と言う名前の波形ファイルが出来ます。 Windows 上で X Window サーバを立ち上げた後、ubuntu 上で次の通りコマンドを打つと、GTKwave が立ち上がります。X Window サーバの立ち上げ方は、【こちらの記事】を参考にしてください。 また、GTKwave の使い方は、本記事では省略させて頂きます。

gtkwave u_dut.vcd