最終更新: 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-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 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 の値を表示します。 cat から EOF までを ubuntu にコピー&ペーストすると、frame.v というファイルが出来ます。 cat から EOF までを ubuntu にコピー&ペーストすると、cframe.cpp というファイルが出来ます。 2021-05-02: Closing Operation 追加 cat から EOF までを ubuntu にコピー&ペーストすると、bench.v というファイルが出来ます。 2021-04-16: $write 文内誤字修正 (HWDATA ⇒ HRDATA) cat から EOF までを ubuntu にコピー&ペーストすると、dut.v というファイルが出来ます。 以下に Icarus Verilog と Verilator のコンパイル用スクリプトを示します。 「Cortex-M0 DesignStart Eval Package」を展開したディレクトリを「M0_ROOT」という環境変数にセットしています。この部分は、御自身のディレクトリに書き換えてください。 また、テスト用の ROM コードファイルには、【こちらの記事】で作成した「main.v」というファイルを使用します。 尚、今回使うテストベンチとシェルスクリプトの書き方で VCD ダンプが可能なのは Icarus Verilog だけです。Verilator では PLI の組み込みが必要となります。 Icarus Verilog の場合のコンパイル用シェル スクリプトは次の通りです。 cat から EOF までを ubuntu にコピー&ペーストすると、ivlog.sh というファイルが出来ます。 前記のファイルの「M0_ROOT」の部分を書き換えが完了したら、ubuntu で次の通りコマンドを打つと、コンパイルが実行されます。「./a.out」でシミュレーションが実行され、ターミナル上にテキストダンプが出力されます。ファイルに保存したい場合には、「./a.out &> foo.log」等とリダイレクションをします。また、「u_dut.vcd」という名前の波形ファイルが出来ます。 Icarus Verilog の場合のコンパイル用シェル スクリプトは次の通りです。尚、今回のテストベンチでは、Verilator での VCD ダンプには対応していません。 cat から EOF までを ubuntu にコピー&ペーストすると、veri.sh というファイルが出来ます。 このファイルの「M0_ROOT」の部分の書き換えが完了したら、ubuntu で次の通りコマンドを打つと、コンパイルが実行されます。「./obj_dir/Vbench」でシミュレーションが実行され、ターミナル上にテキストダンプが出力されます。ファイルに保存したい場合には、「./obj_dir/Vbench &> foo.log」等とリダイレクションをします。 一番左にサイクルカウント数とリセット端子、次にプログラム カウンタ(PC) を表示しています。この値を、ROM コードのビルド時に作成した C 言語のソースコード入りディスアセンブル リストと見比べます。記事が長くなりましたので、別稿に改めたいと思います。
2021-04-17: HRDATA の列に HWDATA だったのを修正、下線付加
Icarus Verilog によるシミュレーションを実行すると、ディレクトリに「u_dut.vcd」と言う名前の波形ファイルが出来ます。
Windows 上で X Window サーバを立ち上げた後、ubuntu 上で次の通りコマンドを打つと、GTKwave が立ち上がります。X Window サーバの立ち上げ方は、【こちらの記事】を参考にしてください。
また、GTKwave の使い方は、本記事では省略させて頂きます。
こんな方にお薦め:
(但、サインオフ シミュレータとしては使えません)
前提条件:
(インストールと簡単な動作確認方法は【こちらの別記事】に記載しています。)
sudo apt install gtkwave
ARM DesignStart プログラム (CPU本体):
テストベンチと簡易 DUT:
上位階層のテストベンチ (Verilog HDL 版):
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" > 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
下位階層のテストベンチ (Verilog HDL のみ):
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: $display 文内誤字修正 (コメントの HRDARA ⇒ HRDATA, HWDARA ⇒ HWDATA)
2021-04-18: $display 文内 FAILD: code の部分を %8h ⇒ %08hCPU外側のROMとRAMだけの簡易DUT (Verilog HDL のみ):
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 の場合:
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
chmod +x vlog.sh
vlog.sh
./a.out
Verilator の場合:
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
chmod +x veri.sh
veri.sh
./obj_dir/Vbench
テキスト ダンプの追いかけ方:
2021-04-18: =SIM_STOP の配置を4バイト アラインに修正、下線付加GTKWave による波形の確認:
gtkwave u_dut.vcd