Cプログラムによるハードウェアの設計
- C_model の記述方法(オリジナル)
- C_model の例
- C_model を verilog HDL に変換した例
- アルゴリズムレベル(ビヘイビアレベル)→サイクルレベル(RTLレベル)への変換方法
1.
C_modelの記述方法(オリジナル) --- YasC(ヤッシー)と命名しました。
YasC とは Yet advanced system C の略です。
拡張していない普通のCでシステムが設計できるという意味です。
Cによる設計は、SystemC
がもっとも多いく、またこれ以外にも提案され
ているが、この設計手法はそのどれよりも簡単にシステム記述ができ、トップダウン設計
に向いている。
【C_model による設計のメリット】
(1) verilog に比べて格段に可視性がよく、極めて論理性の高い仕様書になる。
(2) verilog に比べてCは飛躍的に記述能力が高く、少ないコード数で記述できるため、
システム検証に優れている。verilog に変換したら、ソースコード量は3〜4倍増えた。
(3) システム記述が容易で、未設計や外部回路、外部システムが容易に記述でき、出力を
音や画やファイルへの変換も容易にできる。
(4) Cは構造型プログラミング指向言語のため、フローチャートや状態遷移図が不要であ
る。
(5) システムのデバッグにCコンパイラー付属の強力なデバッガーが利用できる。
(6) シミュレーションが verilog よりも 100倍以上高速。
【C_model の設計ルール(オリジナル) 】
C_model 作成に用いた設計ルールと、今後の方向をまとめる。
この手法はオリジナルなので、他の C → HDL 変換ツール用の記述方法とは異なる。
--------------------------------------------------------------------------
・プログラミング言語
Cによる構造型プログラミング指向で記述する。
C++はオブジェクト指向型プログラミング言語で、ボトムアップ設計向きなので使用
しない。
--------------------------------------------------------------------------
・変数(接続端子、レジスター)の定義(宣言部の記述ルール)
(1) 基本的には、グローバル変数として定義する。
理由:
・デバッガーでデバッグする時、グローバル変数ならすべての変数がそのまま参照でき
るためデバッグ効率が高い。ローカル変数は、参照するのにいちいち関数名から指定する
必要がある。
・関数間の信号のやりとりに信号名リスト(仮引数)が不要。
・複数の信号の出力にポインターを使用せずにダイレクトに信号名を使用することがで
きる。
・関数間(モジュール間)で信号名の重複が許されないが、トップダウン設計なら信号
名の重複はない。
(2) 接続端子は通常の変数として、レジスターは *** と ***_i の2つの変数で定義する。
(*** は D-FF の出力で、***_i は D-FF のD入力を表す。)
(3) ビット幅はコメントで記述するか、または typedef でキャストを定義する。
(今回は DWORD, WORD, BYTE, BIT のキャストを使用したが、BIT2, BIT4 とかの記述
方法がある。)
(4) 変数名は verilog 変換時に同一の名称とする。従って、verilog の予約語を使用せ
ず、長さも注意する。
(5) 宣言文で、変数の意味は必ずコメントで記述する。できるだけ verilog 変換時にそ
のままコピーできるような内容にする。
--------------------------------------------------------------------------
・同時変化の記述、記述順序の注意
通常、Cでは同時変化(non_blocking_assignment --- verilog では <= で代入)を記
述できない。
また、接続においても、verilog では組み合わせ回路の記述順序は関係ないが、Cでは
上から順番に実行するため、記述に注意が必要である。
(1) 基本はできるだけレジスターで終端する。組み合わせ回路がある場合は、できるだけ
レジスターの入力側に置く。
(2) モジュール内では、レジスターへの入力は ***_i に入力し、
non_blocking_assignment(); で一括して *** に代入する。(同時変化の記述方法)
(3) 組み合わせ回路をレジスターの後ろに置いた場合は、その後ろに記述した文やモジュ
ールでしか参照できない。
もし、前で参照せねばならない場合は、そのモジュール内ではコメント化して
assignment(); に記述する。
その場合、どのモジュールの記述かを明記し、モジュール内でも assignment(); に移
動した旨を記述しておく。
--------------------------------------------------------------------------
・非同期セット/リセットの処理
(1) レジスターを有する各モジュール内でセット/リセットするようにする。
(2) 非同期リセットは *** = 0; とレジスターの出力に直接値を代入する。
rst_n はLリセット信号として、次のように記述する。
if (!rst_n) {
*** = 0;
}
***_i = .....
(3) non_blocking_assignment(); では、リセット時以外に ***_i から *** に代入する。
if (rst_n) {
*** = ***_i;
}
--------------------------------------------------------------------------
・クロック同期の記述、関数の記述
(1) クロック同期は、全体をループし(例えば main_loop())1回のループを1クロック
とする。
複数系統のクロックがある場合は、例えば dck とそれを1/2分周した mck がある場
合、dck でループし、1回毎に mck を 0,1 と反転させ、mck 動作のレジスターは、
non_blocking_assignment() で例えば mck==0 時のみレジスターの出力に代入する。
void non_blocking_assignment() {
if (rst_n) {
reg_a = reg_a_i; // reg_a は dck で動作
if (mck==0) {
reg_b = reg_b_i; // reg_b は mck で動作
}
}
}
ゲーティド・クロックを含む場合の記述
void non_blocking_assignment() {
if (rst_n) {
reg_a = reg_a_i;
if (clk_halt == 0)
reg_c = reg_c_i; // reg_c はゲーティド・クロック動作
}
}
}
(2) main_loop() 内は、次の順でループさせ、入出力がある場合はループ内に関数を記述
する。
n_dck = 0; // dck カウント数の初期化
while (1) { // ループ
if (n_dck==0) rst_n = 0; // 非同期リセット信号
else rst_n = 1;
assignment(); // モジュール内で記述できない組み合わせ回路の記述
モジュール名();
・
・
・
モジュール名();
non_blocking_assignment(); // レジスターの同時変化の記述
n_dck++; // スタートからのクロック数のカウント
}
(3) 原則として1つのモジュールを1つの関数とする。関数名は verilog のモジュール
名と同一にする。
(4) 関数の入出力は仮引数を持たず、グローバル変数の代入で行う。
記述量を減らし、変更を容易にし、ミスを減らすためである。
関数の return 値は、原則 void であるが、エラー終了、強制終了させる場合は、エラ
ーフラグを返す。
ただし verilog の function に相当するものはこの限りでない。
--------------------------------------------------------------------------
・ソースコードの記述場所
Cでは、事象が発生するところにコードを記述するのが原則である。
verilog では、必ずハードウェア単位で記述する。
従って、Cから verilog に変換するときに、事象単位からハードウェア単位にまとめ
直す。
--------------------------------------------------------------------------
・本記述方法の特徴と注意点
(1) レジスターは明示的に宣言しており、Cソースコードを見ればどれがレジスターか
すぐわかるため、Cソースコードを仕様書として使用しやすい。
(2) レジスターに関しては、順序を気にすることなく記述できる。
(3) 組み合わせ回路に関しては、後で実行される場所でしか参照することはできない。
もし、そのモジュールの前方で参照したい場合は、アサイン部(assignment()関数)
に記述する。(assignment()関数はモジュールよりも先に実行される。)
(4) 非同期セット/リセット、マルチ・クロック、ゲーティド・クロックは一括代入部に
記述することによって、容易に記述できる。
(5) 変数はグローバル変数宣言しているので、デバッグやシステム検討が容易である。
【C_model の記述方法(まとめ)】
・宣言部 -------- グローバル変数の宣言
・アサイン部 ---- ssignment() 関数
レジスターの出力の組み合わせ回路をあらかじめ定義
(モジュール部から異動)
・モジュール部 -- 回路動作を記述(複数個のモジュールを表す関数)
・一括代入部 ---- non_blocking_assignment() 関数
レジスターの同時変化を記述
非同期セット/リセット、マルチ・クロック、ゲーティド・クロック
対応
・ループ部 ------ クロックに同期して繰り返す。
・メイン部 ------ 初期設定、ファイルのオープン/クローズ等。
2.C_model の例 プログラム名: TestFir.c
本例はオーバーサンプリングFIRフィルターです。
ファイルから入力データをテキストデータとして読み込んで、オーバーサンプリング・フィルターを通した結果をファイルにテキストデータとして出力しています。
本例にはアサイン部は存在しません。
(注)単に例として作成したものですので、適切な性能はとれていません。
3.C_model を verilog HDL に変換した例 verilog リスト名: TestFir.v
TestFir.c のモジュール部 fir() を verilog に変換した例です。
- module fir( clk, rst_n, data1, data2, state_cnt );
-
input clk;
-
input rst_n;
// Lリセット信号
-
input [15:0] data1; // 入力データ
-
output[15:0] data2; // 出力データ
-
output[2:0] state_cnt; // ステートカウンター
-
reg [26:0] mult; // 乗算器出力
-
// regster
-
reg [2:0] state_cnt; // ステートカウンター
-
reg [15:0] s_reg[0:9]; // データ用シフトレジスター
-
reg [29:0] acc; // アキュムレータ
-
integer i;
-
- /*===================================================================*/
-
always@(state_cnt or s_reg)
- case (state_cnt)
- 3'd0: mult = 20 * (s_reg[0] + s_reg[9]);
- 3'd2: mult = -42 * (s_reg[1] + s_reg[8]);
- 3'd3: mult = 60 * (s_reg[2] + s_reg[7]);
- 3'd4: mult = -106 * (s_reg[3] + s_reg[6]);
- 3'd5: mult = 321 * (s_reg[4] + s_reg[5]);
- 3'd6: mult = 506 * s_reg[5];
- default: mult = 0;
- endcase
-
-
always@(posedge clk or negedge rst_n)
- if (!rst_n)
- acc <= 30'd0;
- else if ((state_cnt==3'd1)||(state_cnt==3'd6))
- acc <= mult + 256;
- else
- acc <= acc + mult;
-
- assign data2 = acc >> 9;
-
-
always@(posedge clk or negedge rst_n)
- if (!rst_n)
- for (i=0; i<10; i=i+1)
s_reg[i] <= 16'd0;
- else if (state_cnt==3'd5) begin
- s_reg[0] <= data1;
- for (i=1; i<10; i=i+1)
s_reg[i] <= s_reg[i-1];
- end
-
-
always@(posedge clk or negedge rst_n)
- if (!rst_n)
- state_cnt = 3'd0;
- else if (state_cnt == 3'd6)
- state_cnt <= 3'd1;
- else
- state_cnt <= state_cnt + 1;
-
-
endmodule
|
例の説明
TestFir.c
は1〜23行目が宣言部、25〜55行目がモジュール部、
57〜65行目が一括代入部、67〜86行目がループ部、88〜103行目が
メイン部となっており、アサイン部は存在しません。「C_model の記述方法」
を参照しながら見てください。
TestFir.v はそのモジュール部をハンドで verilog にコンパイルしたものです。
4.
アルゴリズムレベル(ビヘイビアレベル)→サイクルレベル(RTLレベル)への変換方法
・for ループの展開
繰り返し処理されたアルゴリズム記述は次のようにしてサイクルレベルの C_model に展開します。
1重ループ
for (i=0; i<n; i++) {
f();
}
|
|
↓↓ |
|
展開例(1)
i_cnt_i = 0; でスタート
...
if (i_cnt != n) {
f();
i_cnt_i = i_cnt + 1;
}
else (終了); --- n番目
|
展開例(2)
i_cnt_i = n - 1; でスタート
...
f();
if (i_cnt == 0) (終了);
else i_cnt_i = i_cnt - 1;
このように判定を処理の後にすると1サイクル少なく
てすむ。ただし必ず1回は f() を実行する。
|
---------------------------------------------------------------------------------------------------
2重ループ
for (i=0; i<n; i++) {
f();
for (j=0; j<m; j++) {
g();
}
}
|
|
↓↓ |
|
展開例(1)
i_cnt_i = 0;
j_cnt_i = 0; でスタート
...
if (j_cnt == 0) {
f();
}
if ((i_cnt != n)||(j_cnt != 0))
g();
if (j_cnt != m-1) {
j_cnt_i = j_cnt + 1;
}
else {
j_cnt_i = 0;
i_cnt_i = i_cnt + 1;
}
}
else (終了);
|
展開例(2)
i_cnt_i = n - 1;
j_cnt_i = m - 1; でスタート
...
if (j_cnt == m-1) {
f();
}
g();
if ((i_cnt == 0)||(j_cnt == 0)) {
(終了);
}
else {
if (j_cnt == 0) {
j_cnt_i = m - 1;
i_cnt_i = i_cnt - 1;
}
else {
j_cnt_i = j_cnt - 1;
}
}
|
 |
半導体回路・電子回路・電子機器設計のご用命は 有限会社安田電子設計事務所へ!! |
 |
 |
Home
技術紹介
サービス案内
会社概要
お問い合わせ
|
 |
 |
 |
 |
 |
Copyright(C) 2010 Yasuda Electronics Design Office All Rights Reserved. |
 |