シミュレーション専用モデルを書く
(1)学習の概要と目標
設計した回路の外側につながる部分を自分でつくることが出来ます。これをシミュレーションモデルといいますが、意外と簡単です。「ないものは自分でつくる」という精神が大事です。
- システムレベルの検証に利用できるシミュレーションモデルの作成例と利用例を学ぶ
以下の項目を理解し説明できる
- シミュレーションモデルとは
- ROM シミュレーションモデル
- RAM シミュレーションモデル
サイコロカウンタのシミュレーションを行ない動作確認する
(2)シミュレーションモデルとは
回路記述を検証するための周辺回路の動作記述を「シミュレーションモデル」といいます。回路記述を検証するには通常、その回路に対する入力信号を含んだテストベンチを用意します。
もふねこ:
RTLの動作確認をするとき、CPUやメモリの「ダミー(シミュレーションモデル)」を自分でサクッと作れるようになると、デバッグがめちゃくちゃ楽になるよ🐾!
論理合成できない書き方(#遅延とか)もシミュレーションなら使い放題だから、色々工夫してみてね!
一般に設計対象の回路は CPU やメモリなどと共に製品を構成しています。これらの周辺回路を含んだ状態でシミュレーションを行えば簡単に検証ができます。つまり個々の入力信号を用意しなくても、CPU のプログラムを用意すればシステム全体の動作を確認できるのです。
- 検証のための周辺回路の記述
- CPU などの複雑な機能を持ったものは ASIC ベンダなどから購入
- ROM・RAM などの RTL モデルは自作で十分
テストベンチ内に「回路記述」「CPU」「ROM」「RAM」を配置し、バスで接続して検証する
rom_data.hex] RAM[RAM モデル] BUS[システムバス] CPU <--> BUS RTL <--> BUS ROM --> BUS RAM <--> BUS end style CPU fill:#f3e5f5,stroke:#9c27b0; style RTL fill:#e8f5e9,stroke:#388e3c,stroke-width:2px; style ROM fill:#e3f2fd,stroke:#1976d2; style RAM fill:#e3f2fd,stroke:#1976d2;
CPU などの複雑な機能を持ったものは ASIC ベンダや AP ベンダに供給してもらいます。遅延を考慮しない RTL シミュレーションに限れば、ROM や RAM などは自作で十分でしょう。
これらのシミュレーションモデルは論理合成することはないので、論理合成にあわせた記述をする必要はありません。HDL の記述能力を最大限生かして記述することが出来ます。
(3)ROMシミュレーションモデル
簡単な ROM シミュレーションモデルを紹介します。15 ビットの ADDR と 1 ビットの出力制御信号(OEB)が入力され、8 ビットの DATA が出力されます。
ROM モデルの仕様
- 入力:
ADDR[14:0]、OEB - 出力:
DATA[7:0]
※ROM のデータはあらかじめ別ファイル("rom_data.hex")に用意しておき、シミュレーション開始時に $readmemh で読み込みます。
// ROMシミュレーションモデル
module ROM ( ADDR, DATA, OEB );
input [14:0] ADDR;
input OEB;
output [7:0] DATA;
reg [7:0] MEM [0:32767]; // レジスタ配列
// 読み出し遅延
parameter RDELAY = 1500;
assign #RDELAY DATA =
(OEB==0) ? MEM[ADDR] : 8'hZZ;
// シミュレーション開始時にファイル読み込み
initial $readmemh( "rom_data.hex", MEM );
endmodule
ROM のデータはレジスタ配列 MEM に格納します。このデータはあらかじめ別ファイルに用意しておき、シミュレーションがはじまったときに読み込みます。シミュレーション開始後に initial 文が実行され、データの読み込みにはシステムタスクの $readmemh を使います。
データの読み出しは assign 文で行います。出力制御信号 OEB が 0 のときにアドレスに対応したメモリの内容が出力され、1 のときにはハイインピーダンス(8'hZZ)が出力されるように記述します。
RDELAY は読み出し遅延時間で、あらかじめ parameter で設定しておきます。RTL のシミュレーションであればこの遅延はなくてもかまいません。
アドレス・DATA の幅は使用するシステムに合わせて変えます。あまり大きすぎるとシミュレーション速度が低下するので注意しましょう。
(4)RAMシミュレーションモデル
簡単な RAM シミュレーションモデルを紹介します。15 ビットの ADDR と 1 ビットの制御信号 3 本(CEB、WEB、OEB)が入力され、8 ビットの DATA は入出力として宣言されています。
RAM モデルの仕様
- 入力:
ADDR[14:0]、CEB、WEB、OEB - 入出力(双方向):
DATA[7:0]
※$readmemh, $writememh でメモリ内容のファイルアクセスが可能です。より正確な遅延付加には specify ブロックを使用します。
// RAMシミュレーションモデル
module RAM ( ADDR, DATA, CEB, WEB, OEB );
input [14:0] ADDR;
input CEB, WEB, OEB;
inout [7:0] DATA; // 双方向ポート
reg [7:0] MEM [0:32767];
wire WRITE, READ; // 内部制御信号
// 書き込み, 読み出し遅延
parameter WDELAY = 1000, RDELAY = 1500;
always @( WRITE or DATA ) begin
#WDELAY
if ( WRITE==1'b1 )
MEM[ADDR] <= DATA;
end
assign READ = (OEB==1'b0) & (CEB==1'b0);
assign WRITE = (WEB==1'b0) & (CEB==1'b0);
assign #RDELAY DATA = READ ? MEM[ADDR] : 8'hZZ;
endmodule
入出力ポートはキーワード inout の後にビット幅、ポート名を書いて宣言します。RAM のデータはレジスタ配列 MEM に格納します。データの読み出しと書き込みの切り替えは 3 本の制御信号を使って行います。READ が 1 のときにデータ読み出し、WRITE が 1 のときにデータ書き込みになります。
データの読み出しは ROM と同様に、assign 文を使って記述します。データの書き込みは always 文を使用します。RAM の場合は値を記憶しなければならないので、フリップフロップかラッチの記述になります。RAM の動作はラッチに近いのでラッチと同じ書き方です。
この RAM の記述で注意しなければならないのは、入力と出力が衝突しないようにすることです。データポートは双方向です。データを読み出すとき以外はデータ端子をハイインピーダンスにしています。
書き込み遅延を WDELAY、読み出し遅延を RDELAY と定義して記述しています。これらの遅延は ROM の場合と同様に RTL のシミュレーションにはなくてもかまいません(より正確な遅延付加には specify ブロックを使います)。
このようなモデルをつかってシミュレーションを行うと、システムタスクの $readmemh や $writememh を使用してメモリの内容を自由に読み書きできます。これらの機能はデバッグする上で非常に便利に使用できます。
(5)シミュレーションモデルの応用例
シミュレーションモデルの簡単な応用例を紹介します。サイコロカウンタの記述を作成し、シミュレーションする場合を考えてみます。
- 入力信号:
CLK,RES,EN - 出力信号:
LAMP[6:0](サイコロの目一個一個に対応) EN=1の時、サイコロ回転(目が変わる)EN=0の時、サイコロ停止RES=1の時、リセット(1の目を表示)
テストベンチを作成する場合、各入出力信号を表示させて動作のチェックを行うのが一般的です。しかし出力信号 LAMP の値を 1、0 で表示させてもサイコロのどの目が出ているのかすぐにはわかりません。
// 生の LAMP 出力のシミュレーション結果(判読が難しい)
.
.
33500 CLK=0 RES=0 EN=1 LAMP=1110111
34000 CLK=1 RES=0 EN=1 LAMP=0001000
34500 CLK=0 RES=0 EN=1 LAMP=0001000
35000 CLK=1 RES=0 EN=1 LAMP=1000001
35500 CLK=0 RES=0 EN=1 LAMP=1000001
36000 CLK=1 RES=0 EN=1 LAMP=0011100
36500 CLK=0 RES=0 EN=1 LAMP=0011100
37000 CLK=1 RES=0 EN=1 LAMP=1010101
37500 CLK=0 RES=0 EN=1 LAMP=1010101
38000 CLK=1 RES=0 EN=1 LAMP=1011101
38500 CLK=0 RES=0 EN=1 LAMP=1011101
39000 CLK=1 RES=0 EN=1 LAMP=1110111
39500 CLK=0 RES=0 EN=1 LAMP=1110111
40000 CLK=1 RES=0 EN=1 LAMP=0001000
40500 CLK=0 RES=0 EN=1 LAMP=0001000
41000 CLK=1 RES=0 EN=1 LAMP=1000001
41250 CLK=1 RES=0 EN=0 LAMP=1000001
41500 CLK=0 RES=0 EN=0 LAMP=1000001
42000 CLK=1 RES=0 EN=0 LAMP=1000001
42500 CLK=0 RES=0 EN=0 LAMP=1000001
43000 CLK=1 RES=0 EN=0 LAMP=1000001
.
.
そこで出力 LAMP をエンコードして 1~6 を出力するシミュレーションモデル DISP_ENC を作成します。このシミュレーションモデルとサイコロカウンタの記述をテストベンチの中で接続します。そしてこのシミュレーションモデルの出力 DISP をみればサイコロの表示を一目で確認できます。
数値化] SAI -->|LAMP 7bit| DISP2[DISP_ENC2
アスキーアート表示] EN --> DISP2 end style SAI fill:#e8f5e9,stroke:#388e3c; style DISP1 fill:#fff3e0,stroke:#f57c00; style DISP2 fill:#fff3e0,stroke:#f57c00;
// DISP 出力を追加したシミュレーション結果(判読しやすい)
.
.
33500 CLK=0 RES=0 EN=1 LAMP=1110111 DISP=6
34000 CLK=1 RES=0 EN=1 LAMP=0001000 DISP=1
34500 CLK=0 RES=0 EN=1 LAMP=0001000 DISP=1
35000 CLK=1 RES=0 EN=1 LAMP=1000001 DISP=2
35500 CLK=0 RES=0 EN=1 LAMP=1000001 DISP=2
36000 CLK=1 RES=0 EN=1 LAMP=0011100 DISP=3
36500 CLK=0 RES=0 EN=1 LAMP=0011100 DISP=3
37000 CLK=1 RES=0 EN=1 LAMP=1010101 DISP=4
37500 CLK=0 RES=0 EN=1 LAMP=1010101 DISP=4
38000 CLK=1 RES=0 EN=1 LAMP=1011101 DISP=5
38500 CLK=0 RES=0 EN=1 LAMP=1011101 DISP=5
39000 CLK=1 RES=0 EN=1 LAMP=1110111 DISP=6
39500 CLK=0 RES=0 EN=1 LAMP=1110111 DISP=6
40000 CLK=1 RES=0 EN=1 LAMP=0001000 DISP=1
40500 CLK=0 RES=0 EN=1 LAMP=0001000 DISP=1
41000 CLK=1 RES=0 EN=1 LAMP=1000001 DISP=2
41250 CLK=1 RES=0 EN=0 LAMP=1000001 DISP=2
41500 CLK=0 RES=0 EN=0 LAMP=1000001 DISP=2
42000 CLK=1 RES=0 EN=0 LAMP=1000001 DISP=2
42500 CLK=0 RES=0 EN=0 LAMP=1000001 DISP=2
43000 CLK=1 RES=0 EN=0 LAMP=1000001 DISP=2
.
.
これは非常に簡単な例ですが、目で見て確認する場合には、大きな効果が期待できます。
サイコロカウンタであれば上記のようなシミュレーションで十分検証できますが、さらに一工夫して $display を使ったシミュレーションモデル DISP_ENC2 を記述すると、テキストベースの表示でもサイコロの絵を出すことが出来ます。制御信号 EN が 0 に変化したときだけサイコロの絵をだすようにすればシミュレーション結果も見やすくなります。
// ASCII アートによるサイコロ表示(EN=0 になった瞬間のみ表示)
.
.
35500 CLK=0 RES=0 EN=1 LAMP=1000001
36000 CLK=1 RES=0 EN=1 LAMP=0011100
36500 CLK=0 RES=0 EN=1 LAMP=0011100
37000 CLK=1 RES=0 EN=1 LAMP=1010101
37500 CLK=0 RES=0 EN=1 LAMP=1010101
38000 CLK=1 RES=0 EN=1 LAMP=1011101
38500 CLK=0 RES=0 EN=1 LAMP=1011101
39000 CLK=1 RES=0 EN=1 LAMP=1110111
39500 CLK=0 RES=0 EN=1 LAMP=1110111
40000 CLK=1 RES=0 EN=1 LAMP=0001000
40500 CLK=0 RES=0 EN=1 LAMP=0001000
41000 CLK=1 RES=0 EN=1 LAMP=1000001
+-------+
| @ |
| |
| @ |
+-------+
41250 CLK=1 RES=0 EN=0 LAMP=1000001
41500 CLK=0 RES=0 EN=0 LAMP=1000001
42000 CLK=1 RES=0 EN=0 LAMP=1000001
42500 CLK=0 RES=0 EN=0 LAMP=1000001
43000 CLK=1 RES=0 EN=0 LAMP=1000001
このように VerilogHDL の記述能力を活用するといろいろなことが出来ます。ぜひチャレンジしてみてください。ただしやりすぎにならないように注意しましょう。
(6)修了判定
ワンポイント・アドバイスのサイコロカウンタのシミュレーションを実行してください。サイコロカウンタ、テストベンチ、シミュレーションモデルの内容を良く確認してください。
| 表示(目) | LAMP [6:0] | 表示(目) | LAMP [6:0] |
|---|---|---|---|
| 1 | 0001000 |
4 | 1010101 |
| 2 | 1000001 |
5 | 1011101 |
| 3 | 0011100 |
6 | 1110111 |
ファイル構成:saikoro2.v(本体)、saikoro2_test.v(テストベンチ)、disp_enc.v・disp_enc2.v(シミュレーションモデル)
1. サイコロカウンタ本体(saikoro2.v)
module SAIKORO2( CLK, RES, EN, LAMP );
input CLK, RES, EN;
output [6:0] LAMP;
reg [6:0] LAMP;
reg [6:0] LAMP_NEXT;
always @( posedge CLK or posedge RES ) begin
if ( RES==1'b1 )
LAMP <= 7'b0001000;
else
LAMP <= LAMP_NEXT;
end
always @( EN or LAMP ) begin
if ( EN==1'b1 )
case ( LAMP )
7'b0001000 : LAMP_NEXT <= 7'b1000001; // 1 -> 2
7'b1000001 : LAMP_NEXT <= 7'b0011100; // 2 -> 3
7'b0011100 : LAMP_NEXT <= 7'b1010101; // 3 -> 4
7'b1010101 : LAMP_NEXT <= 7'b1011101; // 4 -> 5
7'b1011101 : LAMP_NEXT <= 7'b1110111; // 5 -> 6
7'b1110111 : LAMP_NEXT <= 7'b0001000; // 6 -> 1
default : LAMP_NEXT <= 7'bX;
endcase
else
LAMP_NEXT <= LAMP;
end
endmodule
2. シミュレーションモデル1:数値エンコーダ(disp_enc.v)
module DISP_ENC( LAMP, DISP );
input [6:0] LAMP;
output [2:0] DISP;
reg [2:0] DISP;
always @( LAMP ) begin
case ( LAMP )
7'b0001000 : DISP <= 3'd1;
7'b1000001 : DISP <= 3'd2;
7'b0011100 : DISP <= 3'd3;
7'b1010101 : DISP <= 3'd4;
7'b1011101 : DISP <= 3'd5;
7'b1110111 : DISP <= 3'd6;
default : DISP <= 3'dX;
endcase
end
endmodule
3. シミュレーションモデル2:アスキーアート表示(disp_enc2.v)
module DISP_ENC2( LAMP, EN );
input EN;
input [6:0] LAMP;
// ENが0に立ち下がった瞬間(停止したとき)にサイコロを描画
always @(negedge EN) begin
case (LAMP)
7'b0001000 : begin
$display(" +-------+ ");
$display(" | | ");
$display(" | @ | ");
$display(" | | ");
$display(" +-------+ "); end
7'b1000001 : begin
$display(" +-------+ ");
$display(" | @ | ");
$display(" | | ");
$display(" | @ | ");
$display(" +-------+ "); end
7'b0011100 : begin
$display(" +-------+ ");
$display(" | @ | ");
$display(" | @ | ");
$display(" | @ | ");
$display(" +-------+ "); end
7'b1010101 : begin
$display(" +-------+ ");
$display(" | @ @ | ");
$display(" | | ");
$display(" | @ @ | ");
$display(" +-------+ "); end
7'b1011101 : begin
$display(" +-------+ ");
$display(" | @ @ | ");
$display(" | @ | ");
$display(" | @ @ | ");
$display(" +-------+ "); end
7'b1110111 : begin
$display(" +-------+ ");
$display(" | @ @ | ");
$display(" | @ @ | ");
$display(" | @ @ | ");
$display(" +-------+ "); end
default : begin
$display(" +-------+ ");
$display(" | ? ? | ");
$display(" | ? ? ? | ");
$display(" | ? ? | ");
$display(" +-------+ "); end
endcase
end
endmodule
4. テストベンチ(saikoro2_test.v)
module SAIKORO2_TEST;
reg CLK, RES, EN;
wire [6:0] LAMP;
wire [2:0] DISP;
parameter CYCLE = 1000;
// 本体とシミュレーションモデルのインスタンス化
SAIKORO2 SAIKORO (.CLK(CLK), .RES(RES), .EN(EN), .LAMP(LAMP));
DISP_ENC ENCODER (.LAMP(LAMP), .DISP(DISP));
DISP_ENC2 ENCODER2 (.LAMP(LAMP), .EN(EN));
always begin
CLK = 1'b1;
#(CYCLE/2) CLK = 1'b0;
#(CYCLE/2);
end
initial begin
RES = 1'b0;
EN = 1'b0;
#(CYCLE/4);
RES = 1'b1;
#CYCLE;
RES = 1'b0;
#(CYCLE * 8);
EN = 1'b1;
#(CYCLE * 11);
EN = 1'b0;
#(CYCLE * 7);
EN = 1'b1;
#(CYCLE * 14);
EN = 1'b0;
#(CYCLE * 7);
EN = 1'b1;
#(CYCLE * 19);
EN = 1'b0;
#(CYCLE * 7);
$finish;
end
initial begin
$monitor( $time, " CLK=%b RES=%b EN=%b LAMP=%b DISP=%d", CLK, RES, EN, LAMP, DISP );
end
endmodule
5. シミュレーション実行結果
テキストベースの表示でもサイコロの絵を出すことが出来ます。テストベンチによる詳細なシミュレーション出力は以下のようになります(図 14 ~ 18 の完全な文字起こし)。
📌 まとめ
- シミュレーションモデルは、設計対象の回路をテストするための「周辺回路の動作記述」である
- ROM や RAM のシミュレーションモデルを作成することで、システム全体の動作確認が容易になる
- ファイル読み書き(
$readmemh等)を活用することで、デバッグ効率が大幅に向上する $display等を用いた視覚的な出力(アスキーアートや変換された値)を用意することで、シミュレーション結果の判読性が飛躍的に高まる