NGなクロック分周 [FPGA]
分周クロックの使い方について少し。
つらつらとFPGA関係で調べ物をしていたら気になる記事をみました。
元のクロックを50分周して低速なクロックを作成、低速なクロックでロジックを動作させたい場合についてです。
[悪い例]
reg [7:0] clk_cnt;
wire low_clk;
always @ ( posedge CLK or negedge RESETn ) begin
if( !RESETn )
clk_cnt <= 8'd0;
else if( clk_cnt >= 8'd49 )
clk_cnt <= 8'd0;
else
clk_cnt <= clk_cnt + 8'd1;
end
assign low_clk = ( clk_cnt < 25 );
always @ ( posedge low_clk or negedge RESETn ) begin
if( !RESETn )
//初期値
else
//ロジック
end
この例では、分周クロックはwire宣言にして、"clk_cnt < 25"をアサインしています。
これを実機で動かした場合、設計者の意図したとおりには動作しません。
改善例を以下に示します。
[改善例]
reg [7:0] clk_cnt;
reg low_clk;
always @ ( posedge CLK or negedge RESETn ) begin
if( !RESETn )
clk_cnt <= 8'd0;
else if( clk_cnt >= 8'd49 )
clk_cnt <= 8'd0;
else
clk_cnt <= clk_cnt + 8'd1;
end
always @ ( posedge CLK or negedge RESETn ) begin
if( !RESETn )
low_clk <= 1'b0;
else
low_clk <= ( clk_cnt < 25 );
end
always @ ( posedge low_clk or negedge RESETn ) begin
if( !RESETn )
//初期値
else
//ロジック
end
単にreg宣言に変更し、元クロックで叩いただけですね。
なにが問題なのか分かりますか?
clk_cnt は8bitのレジスタですが、それぞれのレジスタは別々のロジックセルに配置されます(1個のロジックセルには1個のFFしかないので当然ですね)。
別の場所に配置されるということは、CLK信号の遅延差、出力が次の比較回路に到着するまでの遅延差が違う、ということです。
clk_cntはが23から24に変化するときを例にしましょう。
5'b10111 → 5'b11000
という感じで1CLK毎に遷移していきますね。でもこれはあくまでCLKごとの変化です。
もっと細かい時間で見た場合にはbitごとにバラバラに変化するので
5'b10111 → 5'b11101 → 5'b11001 → 5'b11000
なんて変わっているのかもしれません。
low_clkの作成条件では25という数字が出ていますが、これはbit列でいうと 5'b11001 です。
先の例では23から24への変化ですから low_clk は変化してはいけないのですが、途中で5'b11101なんて値が一瞬出てしまうわけで、これって29ですからね、low_clkも変化しようというものです。
くれぐれもご注意を。
コードと生成される構造(3) [FPGA]
これは実現不可能な場合の例です。
LEのレジスタには、1本のクロック信号しか入れることが出来ません。それなのに、複数のクロックで動作するコードを書いた場合にはどうなるでしょうか。
試しにこんなコードを書いてコンパイルしてみました。
2つのクロック信号を入れて、どちらかの立ち上がりで出力が変化することを想定します。
[ソースコード]
always @ ( posedge CLK1 or posedge CLK2 or negedge RESETn ) begin
if( !RESETn )
Out <= 1'b0;
else
Out <= In1 & In2;
end
[コンパイル結果の構造]
Out = In2 & In1 & RESETn
ただの論理素子の出力になってしまい、クロック信号は無視されました。
LE構造にアサイン出来なかった為にコンパイラが別の解釈をした結果ですから、期待通りの動作はしないのですが、シミュレーションのやり方によっては正常動作に見えてしまうのが問題です。
ちなみに、コンパイル時にはワーニングのメッセージが出ていました。
Critical Warning (10237): Verilog HDL warning at logic_test.v(17): can't infer register for assignment in edge-triggered always construct because the clock isn't obvious. Generated combinational logic instead
意訳すると「クロック信号がよくわからないので組み合わせ回路にしてみました」と言ったところでしょうか。
コードと生成される構造(2) [FPGA]
LEの出力は論理素子の出力(非同期信号)か、レジスタの出力(同期信号)かを選べますが。レジスタ出力をANDやORなどの論理に入れたい場合があります。
この場合には、レジスタ出力をするLEのほかに、レジスタ出力を受け論理素子の出力を出すLEが別に必要になります。
[ソースコード]
always @ ( posedge CLK or negedge RESETn ) begin
if( !RESETn )
REG_Out <= 1'b0;
else
REG_Out <= In1 & In2;
end
assign OUT_pin = REG_Out & In3;
[コンパイル結果の構造]
// LE1の構造 - REG_Out に相当
always @ ( posedge CLK or negedge RESETn ) begin
if( !RESETn )
LE1_Out <= 1'b0;
else
LE1_Out <= In1 & In2;
end
// LE2の構造
assign LE2_Out = LE1_Out & In3;
// 出力ピンへのアサイン
assign OUT_pin = LE2_Out;
LEを2つ消費しますので、実は以下の形でも消費する量は変らなかったりします。
[ソースコード]
always @ ( posedge CLK or negedge RESETn ) begin
if( !RESETn )
REG_OutA <= 1'b0;
else
REG_OutA <= In3;
end
always @ ( posedge CLK or negedge RESETn ) begin
if( !RESETn )
REG_OutB <= 1'b0;
else
REG_OutB <= In1 & In2 & REG_OutA;
end
assign OUT_pin = REG_OutB;
[コンパイル結果の構造]
// LE1の構造
always @ ( posedge CLK or negedge RESETn ) begin
if( !RESETn )
LE1_Out <= 1'b0;
else
LE1_Out <= In3;
end
// LE2の構造
always @ ( posedge CLK or negedge RESETn ) begin
if( !RESETn )
LE2_Out <= 1'b0;
else
LE2_Out <= In1 & In2 & LE2_Out;
end
// 出力ピンへのアサイン
assign OUT_pin = LE2_Out;
コードと生成される構造(1) [FPGA]
前回はFPGAの中の構造について簡単に触れました。では、この構造に合致しないコードを書いた場合、どんな形で実現されるのでしょうか。
多くの場合は複数のLEを使うことで実現されますが、中には実現不可能な場合もあります。実現不可能な場合、コンパイラがエラーを出してくれればいいのですが、勝手に解釈した構造に変更してしまう場合もありますので注意してください。
いくつかの事例を示しながら説明します。
1.入力データが多すぎる場合
LEの入力が4つしかないのに、それ以上の信号を使った時。これは、アドレスのデコードや、データ中の特定のパターンを検出する場合によく使用されます。
この場合には、それぞれのLEの入力が4以下になるように、複数のLEに分割することで実現されます。
16bitのデータをデコードするのであれば、4つのLEに分割すれば4入力に収まりますね。そして4つのLEの出力をまた別のLEに入力し、再度のデコードを行います。
つまり16bitのデコードにはLEを5つ消費することになります。
16bitの値が0x1234と等しいかを調べる場合には以下のようになります。
[ソースコード]
assign OUT_pin = (A[15:0] == 16'h1234);
[コンパイル結果の構造]
// LE1の構造
assign LE1_Out = (A[15:12] == 4'h1);
// LE2の構造
assign LE2_Out = (A[11:8] == 4'h2);
// LE3の構造
assign LE3_Out = (A[7:4] == 4'h3);
// LE4の構造
assign LE4_Out = (A[3:0] == 4'h4);
// LE5の構造
assign LE5_Out = LE1_Out & LE2_Out & LE3_Out & LE4_Out;
// 出力ピンへのアサイン
assign OUT_pin = LE5_Out;
FPGAの構造 [FPGA]
FPGAは書き換えでロジックを変更できますから、たまにぐにゃぐにゃ変化するイメージを持っている方がいますね。でも、実は変更可能な場所ってそんなにはないんですよ。
どのシリーズでもいいのですが、デバイスのデータシートを見てください。データシートはメーカーのサイトからダウンロードできます。
Alteraの場合は[製品情報]の[資料]から適当なデバイスのシリーズ名を選択するとデータシートをpdfで見ることができます。ここで説明に使用するのはデータシートの中で「アーキテクチャ」となっている箇所です。
シリーズによって実装されているアーキテクチャに違いはありますが、AlteraのFPGAはLE(ロジックエレメント)、IOE(IOエレメント)、RAM、PLL、マルチプライヤ等で構成されています。
このうち、レジスタや、組み合わせ回路に直接関わってくるのはLE(ロジックエレメント)とIOE(IOエレメント)です。IOEは入出力ピンとセットの構成になっていますので、ほとんどのロジックはLEで実装することになります。
LEの構造は
入力信号 → 組み合わせ回路 → レジスタ → 出力信号
の順番に構成されています。
ここでの入力信号、出力信号とはLEの入出力であって、FPGAの内部信号のことです。
正確なところはデータシートの図を見るのが一番ですが、Verilogの構文に例えると以下のような感じでしょうか。
module LE(
CLK, RESETn, LE_in1, LE_in2, LE_in3, LE_out
);
input CLK;
input RESETn;
input LE_in1, LE_in2, LE_in3;
output LE_out;
parameter LUT_data = 8'b01101011;
parameter SyncLogic = 1;
wire lut_out;
reg Q;
assign lut_out = LUT_data[ { LE_in1, LE_in2, LE_in3 } ];
always @ ( posedge CLK or negedge RESETn ) begin
if( !RESETn )
Q <= 1'b0;
else
Q <= lut_out;
end
assign LE_out = ( SyncLogic ) ? Q: lut_out;
endmodule
FPGAの書き換え可能な論理はparameter宣言にした"LUT_data"と"SyncLogic"の値だけです。
それ以外の構造はまったく変更することが出来ません。
入力信号のLE_inの数は、デバイスのシリーズによって異なりますが、この例では3つにしています。
LE_in入力を元に、LUT_data内のデータを参照することで、組み合わせロジックと同じ結果を作り出し、SyncLogicの値により、同期出力、非同期出力を切り替えています。
今回はリセット時の"Q"の値はゼロとしました、アーキテクチャによってはプリセット(プリセット時に"1"を出力する)機能を持っている場合もありますが、リセット機能のみでプリセット機能を持っていないアーキテクチャも存在します。
プリセット機能を持っていないデバイスにリセット時"1"の論理を組んだ場合には、コンパイラがリセット"0"に論理を組み替えてしまいます(反転した信号として実装)。動作しないわけではありませんが、Quartus上でシミュレーションをすると反転信号しか見つからないので、多少違和感が残るかも知れません。
このLEの構造を見てどう思われますか? 様々な論理を実装可能なFPGAですが、実際に書き換えるのはLEの中ではわずかにparameter宣言のみ、LEの外では各LEをどう繋ぐか、LE以外のメモリや乗算器、PLLに接続されるか、といったところでしかありません。
Verilogは言語自体は自由度の高い書き方が可能ですが、好き勝手に書いてしまうと意図しないロジックが作られてしまう理由がここにあります。ソースコードを書く前に、少しでいいですから構造を意識してみて下さいね。
グローバルクロック [FPGA]
全体遅延が最少となるように引かれた専用線と思ってください。
ロジック規模が大きくなると、クロック信号も複数扱う場合があります。使用するFPGAによってグローバルクロックの数が決まっていますので、ご注意ください。グローバルクロックの数はデータシートに記載されています。
グローバルクロックはコンパイラの判断で自動的に割り振ってくれますので、クロック信号を一つだけ使用している場合には問題ありません。
複数のクロック信号を使用した場合、特に、グローバルクロックの数以上にクロック信号(クロックではなくても非同期信号を扱う場合には、使用しているロジックの数に応じて、グローバルクロックがアサインされることがあります)を使った場合には、グローバルクロックへアサインされているか、通常信号の扱いになっているのか、注意が必要です。
アサインの結果はコンパイルリポートでご確認くださいね。
よくある分周クロックと同期回路(2) [FPGA]
とてもわかりやすいのは、「分周しない」方法です。分周せず、同じクロックを使うのであれば、タイミング条件などほとんど気にせずに作れます。
では分周せずに動作クロックの周波数を落とすにはどうすればいいでしょうか。
私がよくやるのはイネーブル信号による制御です。
分周後のクロックに相当するイネーブル信号を生成して、そのイネーブル信号によって動作させます。前の記事の回路をイネーブル制御に書き換えるとこんな感じです。
module SerialOut(
RESETn, CLK, DATA, WE,
OUT
);
input RESETn; // system reset
input CLK; // system clock
input [7:0] DATA; // 8bit data
input WE; // Write enable
output OUT; // serial out
reg [3:0] ClockDivide; // clock divide
wire SerialEna; // serial clock timing
reg [9:0] ShiftReg; // p/s conv register
always @ ( posedge CLK or negedge RESETn ) begin
if( !RESETn )
ClockDivide <= 4'h0;
else
ClockDivide <= ClockDivide + 1'b1;
end
assign SerialEna = ( ClockDivide == 4'h8 );
always @ ( posedge CLK or negedge RESETn ) begin
if( !RESETn )
ShiftReg <= 9'h1FF;
else if( WE )
ShiftReg <= { DATA, 2'b01 };
else if( !SerialEna )
ShiftReg <= ShiftReg;
else
ShiftReg <= { 1'b1, ShiftReg[9:1] };
end
assign OUT = ShiftReg[0];
endmodule
always文は両方共CLKで動作させ、分周クロックの立ち上がりに相当するイネーブル信号としてSerialEnaを生成しました。
シリアル出力はSerialEnaのタイミングでシフトし、出力していますが、WEでの取り込みタイミングはSerialEnaより上位に置いていますので、シリアル出力の周波数によらず、CLK同期で取得可能です。
シリアル変換用のShiftRegを1bit増やしているのは、ShiftReg取り込みタイミングがシリアル出力の周波数にはマッチしないため、同期用に1bit、無効な信号を出力しているためです。
よくある分周クロックと同期回路 [FPGA]
分周したクロック信号を使って内部回路のロジックを動作させている場合、FPGAの構造によっては実機でうまく動作しない場合があります。
悪い例を一つ。
例えば以下の回路の場合にはどんな問題があるでしょうか。
入力CLKを分周してシリアル出力のクロックを生成。シリアルクロックでデータの取得と出力を行います。ちなみに、シリアル出力の初めが"0"で最後が"1"なのはRS232Cを意識しています。
module SerialOut(
RESETn, CLK, DATA, WE,
OUT
);
input RESETn; // system reset
input CLK; // system clock
input [7:0] DATA; // 8bit data
input WE; // Write enable
output OUT; // serial out
reg [3:0] ClockDivide; // clock divide
wire SerialCLK; // serial clock
reg [8:0] ShiftReg; // p/s conv register
always @ ( posedge CLK or negedge RESETn ) begin
if( !RESETn )
ClockDivide <= 4'h0;
else
ClockDivide <= ClockDivide + 1'b1;
end
assign SerialCLK = ClockDivide[3];
always @ ( posedge SerialCLK or negedge RESETn ) begin
if( !RESETn )
ShiftReg <= 9'h1FF;
else if( WE )
ShiftReg <= { DATA, 1'b0 };
else
ShiftReg <= { 1'b1, ShiftReg[8:1] };
end
assign OUT = ShiftReg[0];
endmodule
レジスタ出力をクロック信号"SerialCLK"として使用しています。この場合には元のCLKよりに対しSerialCLKは遅れが発生します。遅れる時間は数ns程度ですが、FPGAの構造によっては、グローバルクロックに割り当て出来ず、9bitのShiftRegの動作タイミングのバラつきが大きくなることがあります。また、CLKとは別のクロック系統として扱われるため、Quartusのタイミング制約もCLKとは別の制約が必要となります。
この場合、入力信号のDATA, WE が上位モジュールから CLK 同期で入力された場合でも、ShiftReg間のタイミング調整をQuartus任せに出来ないということです。
ほかにも、DATA, WE の取り込みタイミングが分周後のクロックに依存するため、入力信号の幅など、タイミング条件が難しくなります。
ここではシンプルな構造とすることで条件を見やすくしたつもりですが、回路の規模が増えてくると、問題点が分かり難くなり、つい安直な考えをしてしまいがちです。
入力クロックが16MHzの場合に、1MHzで動作させたい。そんなとき、分周すれば簡単に1MHzが作れるから、分周したクロックで動かせばいい、と思ってしまいます。
ちょっと待ってください。全てのロジックが1MHzでもいいのでしょうか、その回路の入力信号は16MHzのクロックに依存していませんか? 出力信号は? 使用するデバイスはレジスタ出力をグローバルクロックとして扱えますか?
クロックの系統等タイミングに関わる条件は、シミュレーションでは動作しても実機では動作しない、ということが起こり易いのでご注意ください。
異なるクロック間での信号の乗せ変え [FPGA]
多くのICでは、入力ピンのセットアップタイム、ホールドタイムが規定されています。
クロックの変化点の前後、セットアップタイム、ホールドタイムで指定された時間は入力信号を変化させてはいけないという制限です。
FPGAの内部回路でも同様の制限があります。同期回路の入力信号にはクロックの前後に制限が設けられています。しかし、1種類のクロックを使用している限りでは、タイミング調整はコンパイラが行ってくれますので、あまり気にしなくても回路が組めてしまいます。
2つ以上のクロックを使用する場合、その間でのタイミング制限にはコンパイラは感知しません。
というよりも複数のクロックでは必ずセットアップタイム、ホールドタイムが違反する状態が存在するため、制限のしようがないのです。
ですから、設計者がタイミング違反を前提として回路を組んでやる必要があります。
タイミング違反のとき具体的にはどんなことが起こるのか、以下のモジュールを例に説明しましょう。
module DiffClkCount(
RESETn, CLK_A, CLK_B, COUNT
);
input RESETn; // system reset
input CLK_A; // system clock A
input CLK_B; // system clock B
output [3:0] COUNT;
reg [3:0] COUNT_A; // count register A
reg [3:0] COUNT_B; // count register B
always @ ( posedge CLK_A or negedge RESETn ) begin
if( !RESETn )
COUNT_A <= 4'h0;
else
COUNT_A <= COUNT_A + 1'b1;
end
always @ ( posedge CLK_B or negedge RESETn ) begin
if( !RESETn )
COUNT_B <= 4'h0;
else
COUNT_B <= COUNT_A + 1'b1;
end
assign COUNT = COUNT_B;
endmodule
COUNT_Aは順次カウントアップし、COUNT_BにはCOUNT_Aの値を代入しています。CLK_AとCLK_Bに同じクロックを入れた場合には、COUNT_Bも同じようにカウントアップするはずです。
しかし、現実には、COUNT_Aは順番にカウントアップしているのに、COUNT_Bの値が崩れる場合があります。これは常にそうなるのではありません。例えば、FPGA内に上記の回路しか入らない場合だと、正常に動作するでしょう。FPGA内のロジックの配置状況、特定のタイミングで起こる現象ですので、気付かずに設計を進めてしまうと「たまに動作がおかしい」「今までは動作していたのに、別の機能を追加したら動かなくなった」という現象が発生することになります。
セットアップタイム、ホールドタイムで違反が発生した場合、レジスタの入力は変化前、変化後、どちらの信号を取り込むのか不定になります。また、FPGAの場合にはレジスタの配置がコンパイル毎に変更されますので、レジスタ間の信号遅延が変化してしまいます。
今回のように4bitという複数のレジスタでクロックの載せ変えを行う場合には、ビット毎に出力されるタイミング、取り込むタイミングが違います。そのため、同じタイミング違反、同じ不定であっても、あるビットは変化前を取り込み、あるビットは変化後を取り込んで出力に反映させるという結果になるのです。
未使用ピンの扱い [Quartus]
まぁ、前置きはともかく、実機でピンが余った場合の対処についてです。
1つは、未使用ピンも全てソースコードに記述し、ピンアサインを行う方法があります。
手間は掛かりますが、何本のピンが余っているかソースコードから確認出来ますので、信号の追加や、テスト信号の出力がやりやすいという利点があります。
ただし、使っていない入力、変化しない出力としてワーニングの扱いになりますのでご注意ください。
もう1つは、ソースコードでは記述せず、Quartus上で未使用ピンの処理設定を行う方法です。
元々、未使用のピンの扱いはQuartus上に設定項目があり、変更しなければデフォルトの設定でコンパイルが行われます。
この設定は"Assignments"メニューから"Setting"を選択して行います。
開いた"Setting"ダイアログで"Device"を選択し、"Device&PinOptions"ボタンを押します。
ボタンを押すともう1つのダイアログが開きますので、"Unused Pins"タブを開きましょう。このタブでは1つの項目しか設定がありませんが、"Reserve all unused pins"で未使用ピンの設定を変更できます。個人的によく使うのは"As output driving ground"つまり 0固定出力、そして"As input tri-stated with weak pull-up resistor"これは内部のプルアップを有効にした上で入力ピンにする、という設定です。
この設定は全ての未使用ピンの設定です。
一部のピンの設定を変えたい場合、例えば、今回のデザインでは使用しないが、基板上では他のデバイスに接続されている時。ピン毎に入出力の設定を変える場合には、使用している信号ピンと同じように"Pin Planner"を使用することで個別の設定が可能です。
"Pin Planner"を起動し、空白の NodeName に名前を入力します。ここでの名前はデザインで使用している信号名と被らないように注意してください。
信号名を入れ終わったら、"Direction"には入出力の方向を、"Location"にはピン番号を設定します。
そして最後に"Reserved"の欄をダブルクリックして処理方法を選択します。選択する内容は一括設定の内容と同じ項目から選びます。







