vhdl
VHDLを使ったデジタルハードウェア設計
サーチ…
前書き
このトピックでは、簡単なデジタル回路をVHDLで正しく設計する簡単な方法を提案します。この方法は、グラフィカルブロック図と覚えやすい原理に基づいています。
ハードウェアをまず考え、次にVHDLをコードする
VHDLを使用したデジタルハードウェア設計の初心者向けで、言語の合成セマンティクスの理解が限られています。
備考
VHDLを使用したデジタルハードウェア設計は、初心者の方でも簡単ですが、知るべき重要な事項がいくつかあります。デジタルハードウェアのVHDL記述を変換するツールはロジックシンセサイザです。ロジックシンセサイザで使用されるVHDL言語のセマンティクスは、言語リファレンスマニュアル(LRM)で説明されているシミュレーションセマンティクスとはかなり異なります。さらに悪いことに、それは標準化されておらず、合成ツールによって異なります。
提案された方法は、単純化のためにいくつかの重要な制限を導入する。
- レベルトリガラッチはありません。
- 回路は、単一クロックの立ち上がりエッジで同期しています。
- 非同期リセットまたはセットがありません。
- 解決された信号には複数のドライブはありません。
ブロック図の例は、一連の3のうちの最初のもので、デジタルハードウェアの基本を簡潔に示し、デジタル回路のブロックダイアグラムを設計するための短いリストのルールを提案しています。このルールは、想定どおりにシミュレートし合成するVHDLコードへの直接的な変換を保証するのに役立ちます。
コーディングの例では、ブロックダイアグラムからVHDLコードへの変換について説明し、簡単なデジタル回路で説明します。
最後に、 John Cooleyのデザインコンテストの例は、提案された方法をデジタル回路のより複雑な例に適用する方法を示しています。それはまた、導入された制限を詳述し、それらのいくつかを緩和する。
ブロック図
デジタルハードウェアは、次の2種類のハードウェアプリミティブから構築されます。
- 組み合わせゲート(インバータ、および/または、xor、1ビット全加算器、1ビットマルチプレクサ...)これらの論理ゲートは、入力に対して単純な論理演算を実行し、出力を生成します。入力の1つが変化するたびに、電気信号の伝搬が開始され、短時間の遅延後に出力が結果値に安定します。伝播遅延は、デジタル回路が動作できる速度、すなわちその最大クロック周波数に強く関係するため、重要です。
- メモリ要素(ラッチ、Dフリップフロップ、RAM ...)。組み合わせ論理ゲートとは対照的に、メモリ素子は、それらの入力のいずれかの変化に直ちに反応しない。それらは、データ入力、制御入力およびデータ出力を有する。彼らは、データ入力の変更にではなく、制御入力の特定の組み合わせに反応します。例えば、立ち上がりエッジでトリガされたDフリップフロップ(DFF)には、クロック入力とデータ入力があります。クロックの各立ち上がりエッジで、データ入力がサンプリングされ、データ出力にコピーされます。データ出力は、データ入力が変化してもクロックの次の立ち上がりエッジまで安定しています。
デジタルハードウェア回路は、組み合わせロジックとメモリ要素の組み合わせです。メモリ要素にはいくつかの役割があります。それらの1つは、同じデータを複数の連続した操作で再利用できるようにすることです。これを使用する回路は、しばしば順序回路と呼ばれる。下の図は、立ち上がりエッジトリガレジスタのおかげで、同じコンビナトリアル加算器を使用して整数値を累積する順序回路の例を示しています。これはブロック図の最初の例です。
パイプライニングは、メモリ要素の他の一般的な用途であり、多くのマイクロプロセッサアーキテクチャの基礎でもあります。複雑な処理をより簡単な操作で分割し、いくつかの連続した処理の実行を並列化することによって回路のクロック周波数を高めることを目的としています。
ブロック図は、デジタル回路のグラフィック表示です。コーディングする前に、正しい決定を下し、全体の構造をよく理解するのに役立ちます。これは、多くのソフトウェア設計方法で推奨される予備解析段階と同等です。経験豊富な設計者は、少なくとも単純な回路ではこの設計段階をスキップすることがよくあります。しかし、デジタルハードウェア設計の初心者で、デジタル回路をVHDLでコーディングする場合は、ブロックダイアグラムを描画するための10の簡単なルールを採用すれば、
- 大きな矩形で図面を囲んでください。これはあなたの回路の境界です。この境界を横切るものはすべて、入力ポートまたは出力ポートです。 VHDLエンティティはこの境界を記述する。
- 組み合わせロジック(例えば、丸いブロック)からエッジトリガレジスタ(例えば、四角ブロック)を明確に分離します。 VHDLでは、これらはプロセスに変換されますが、同期とコンビナトリアルの2つの非常に異なる種類があります。
- レベルトリガラッチを使用しないでください。立ち上がりエッジでトリガされたレジスタのみを使用してください。この制約はVHDLからのものではなく、モデルのラッチに完全に使用できます。初心者のための合理的なアドバイスです。ラッチはあまり必要とされず、少なくとも最初の設計では、それらの使用で避けなければならない多くの問題が生じます。
- すべての立ち上がりエッジ・トリガ・レジスタに同じ単一クロックを使用してください。ここでもまた、この制約は簡単のためです。 VHDLは、マルチクロックシステムをモデル化するのに完全に使用できません。クロック
clock
名前を付けます。それは外側から来て、すべての四角いブロックとそれだけの入力です。あなたが望むなら、時計を表現することさえなくても、それはすべての正方形のブロックについて同じであり、それを暗黙のままあなたの図に残すことができます。 - ブロック間の通信を、名前付きの矢印を使用して表します。ブロックの場合は矢印が、矢印は出力です。矢印のブロックには、矢印が入力されます。これらの矢印はすべて、VHDLエンティティのポートになります(VHDLエンティティの大きな矩形またはVHDLアーキテクチャの信号を通過する場合)。
- 矢印には単一の起源がありますが、いくつかの目的地を持つことができます。確かに、もし矢印がいくつかの起源を持っていれば、私たちはいくつかのドライバでVHDL信号を作ります。これは完全に不可能ではありませんが、短絡を避けるために特別な注意が必要です。私たちはこのようにこれを避けます。矢印に複数の目的地がある場合は、矢印を必要なだけ何度もフォークします。接続した交差点と交差していない交差点を区別するためにドットを使用します。
- 一部の矢印は、大きな四角形の外側から来ています。これらはエンティティの入力ポートです。入力矢印は、あなたのブロックの出力にすることもできません。これは、VHDL言語によって強制されます。エンティティの入力ポートは読み込み可能ですが、書き込むことはできません。これはやはり短絡を避けるためです。
- いくつかの矢は外に出る。これらは出力ポートです。 2008年以前のVHDLバージョンでは、エンティティの出力ポートは書き込むことができますが、読み込めません。したがって、出力矢印には、1つの原点と1つの単一の宛先が必要です。出力矢印上にフォークがない場合、出力矢印はあなたのブロックの1つの入力になることはできません。いくつかのブロックの入力としてアウトプット矢印を使用する場合は、新しい丸いブロックを挿入して2つの部分に分割します。1つの内部ブロックと、必要な数のフォークと、新しいブロックから来る出力矢印ブロックして外に出る。新しいブロックはVHDLの単純な連続割り当てになります。透明な名前変更の一種。 VHDL 2008の出力ポートも読み取ることができます。
- 外側から出入りしない矢印はすべて内部信号です。 VHDLアーキテクチャでそれらをすべて宣言します。
- 図の各サイクルは、少なくとも1つの正方形ブロックを含まなければならない。これはVHDLによるものではありません。これは、デジタルハードウェア設計の基本原則に基づいています。コンビナトリアルループは絶対に避けなければならない。非常にまれな場合を除き、有用な結果をもたらすものではありません。そして、丸いブロックのみを含むブロック図のサイクルは、組み合わせループである。
最後のルールを慎重にチェックすることを忘れないでください。他のルールと同じくらい重要ですが、検証するのが少し難しいかもしれません。
ラッチ、マルチクロック、複数のドライバを持つ信号のように、現在除外している機能が絶対に必要な場合を除き、10のルールに準拠した回路のブロック図を簡単に描画する必要があります。そうでない場合は、VHDLまたはロジックシンセサイザではなく、必要な回路に問題がある可能性があります。そして、おそらくあなたが望む回路はデジタルハードウェアではないということです。
順序回路の例に10のルールを適用すると、次のようなブロック図が得られます。
- 図の周りの大きな四角形は、VHDLエンティティの入力ポートと出力ポートを表す3つの矢印で交差しています。
- ブロックダイアグラムには、加算器と出力リネーミングブロックの2つのラウンド(コンビナトリアル)ブロックと、レジスタの1つのスクエア(同期)ブロックがあります。
- エッジトリガレジスタのみを使用します。
- 時計という名前の
clock
は1つしかなく、clock
立ち上がりエッジのみを使用します。 - ブロックダイアグラムには5つの矢印があり、1つにはフォークがあります。これらは2つの内部信号、2つの入力ポートと1つの出力ポートに対応しています。
- すべての矢印には、2つの送り先を持つ
Sum
という名前の矢印を除き、1つの起点と1つの目的地があります。 -
Data_in
とClock
矢印は2つの入力ポートです。彼らは私たち自身のブロックの出力ではありません。 -
Data_out
矢印は出力ポートです。 2008年以前のバージョンのVHDLと互換性を持たせるために、Sum
とData_out
間に追加の名前変更(丸め)ブロックを追加しました。したがって、Data_out
は正確に1つの送信元と1つの送信先があります。 -
Sum
とNext_sum
は、2つの内部信号です。 - グラフには正確に1つのサイクルがあり、それは1つの正方形ブロックを含む。
私たちのブロックダイアグラムは10のルールに従います。 コーディング例では、このタイプのブロックダイアグラムをVHDLでどのように変換するかを詳しく説明します。
コーディング
この例は、一連の3の2番目の例です。まだ実行していない場合は、 ブロックダイアグラムの例を最初にお読みください。
10種類のルール( ブロックダイアグラムの例を参照)に従ったブロックダイアグラムでは、VHDLコーディングが簡単になります。
- 周囲の大きな矩形がVHDLエンティティになり、
- 内部の矢印はVHDL信号になり、アーキテクチャで宣言され、
- すべての正方形ブロックがアーキテクチャ本体内の同期プロセスになり、
- すべてのラウンドブロックがアーキテクチャ本体のコンビナトリアルプロセスになります。
これを順序回路のブロック図で説明しましょう。
回路のVHDLモデルは、2つのコンパイル単位で構成されています。
- 回路の名前とそのインタフェース(ポート名、方向、タイプ)を記述するエンティティ。これは、ブロックダイアグラムの周囲の大きな矩形を直接翻訳したものです。データが整数で、
clock
がVHDLタイプのbit
(2つの値のみ'0'
と'1'
)を使用すると仮定すると、順序回路のエンティティは次のようになります。
entity sequential_circuit is
port(
Data_in: in integer;
Clock: in bit;
Data_out: out integer
);
end entity sequential_circuit;
- 回路の内部を記述するアーキテクチャ(それが何をするか)。これは、内部信号が宣言され、すべてのプロセスがインスタンス化される場所です。私たちの順序回路のアーキテクチャーの骨子は、
architecture ten_rules of sequential_circuit is
signal Sum, Next_sum: integer;
begin
<...processes...>
end architecture ten_rules;
アーキテクチャ本体に追加する3つのプロセス、1つの同期(正方形ブロック)と2つのコンビナトリアル(丸いブロック)があります。
同期プロセスは次のようになります。
process(clock)
begin
if rising_edge(clock) then
o1 <= i1;
...
ox <= ix;
end if;
end process;
ここで、 i1, i2,..., ix
は、図の対応する正方形ブロックに入るすべての矢印であり、 o1, ..., ox
は、図の対応する正方形ブロックを出力するすべての矢印です。もちろん、シグナルの名前を除いて、何も変更されません。何もない。一人のキャラクターでもない。
したがって、この例の同期プロセスは次のようになります。
process(clock)
begin
if rising_edge(clock) then
Sum <= Next_sum;
end if;
end process;
これは、非公式に翻訳することができます。 clock
が変化した場合にのみ、変化が立ち上がりエッジ( '0'
から'1'
)であれば、信号Next_sum
の値を信号Sum
に割り当てます。
コンビナトリアルプロセスは次のようになります。
process(i1, i2,... , ix)
variable v1: <type_of_v1>;
...
variable vy: <type_of_vy>;
begin
v1 := <default_value_for_v1>;
...
vy := <default_value_for_vy>;
o1 <= <default_value_for_o1>;
...
oz <= <default_value_for_oz>;
<statements>
end process;
ここで、 i1, i2,..., in
は、ダイアグラムの対応するラウンドブロックに入るすべての矢印です。 すべてとそれ以上。私たちはどんな矢印も忘れず、リストに何も追加しません。
v1, ..., vy
は、プロセスのコードを単純化するために必要な変数です。彼らは他の命令型プログラミング言語とまったく同じ役割を担っています。一時的な値を保持します。彼らは絶対にすべてが読み込まれる前に割り当てられる必要があります。これを保証しなければ、あるプロセス実行から次のプロセス実行までの変数の値を保持するメモリ要素の種類をモデル化するので、プロセスはもうコンビナトリアルにはなりません。これは、プロセスの開始時にvi := <default_value_for_vi>
ステートメントの理由です。 <default_value_for_vi>
は定数でなければならないことに注意してください。もしそうでなければ、それらが式であれば、誤って式内の変数を使用し、それを割り当てる前に変数を読み取る可能性があります。
o1, ..., om
は、ダイアグラムの対応する丸いブロックを出力するすべての矢印です。 すべてとそれ以上。これらは、プロセスの実行中に少なくとも1回は絶対に割り当てられなければなりません。 VHDL制御構造( if
...、 case
...)は出力信号の割り当てを非常に簡単に防ぐことができるので、処理の始めに無条件に定数値<default_value_for_oi>
を割り当てることを強くお勧めします。場合でも、この方法では、 if
文のマスク信号の割り当て、それはとにかく値を受信しています。
変数の名前(存在する場合)、入力の名前、出力の名前、 <default_value_for_..>
定数および<statements>
の値を除いて、このVHDLスケルトンには絶対に何も変更されません。単一のデフォルト値の割り当てを忘れないでください。合成によって不要なメモリ要素(可能性が高いラッチ)が推定され、その結果が最初に望むものでない場合は、
この例の順序回路では、コンビナトリアル加算器プロセスは次のようになります。
process(Sum, Data_in)
begin
Next_sum <= 0;
Next_sum <= Sum + Data_in;
end process;
場合:これは、非公式に翻訳することができるSum
又はData_in
(または両方)を知らせるために値0を割り当てる変更Next_sum
[値再度割り当てるSum + Data_in
。
最初の代入(定数の既定値が0
)の直後に、それを上書きする別の代入があるため、次のように簡略化できます。
process(Sum, Data_in)
begin
Next_sum <= Sum + Data_in;
end process;
2番目の組み合わせプロセスは、2008年以前のVHDLバージョンに準拠するために、複数の出力先を持つ出力矢印に追加したラウンドブロックに対応しています。
process(Sum)
begin
Data_out <= 0;
Data_out <= Sum;
end process;
他のコンビナトリアルプロセスと同じ理由で、次のように単純化することができます。
process(Sum)
begin
Data_out <= Sum;
end process;
順序回路の完全なコードは次のとおりです。
-- File sequential_circuit.vhd
entity sequential_circuit is
port(
Data_in: in integer;
Clock: in bit;
Data_out: out integer
);
end entity sequential_circuit;
architecture ten_rules of sequential_circuit is
signal Sum, Next_sum: integer;
begin
process(clock)
begin
if rising_edge(clock) then
Sum <= Next_sum;
end if;
end process;
process(Sum, Data_in)
begin
Next_sum <= Sum + Data_in;
end process;
process(Sum)
begin
Data_out <= Sum;
end process;
end architecture ten_rules;
注:3つのプロセスを任意の順序で書くことができます。シミュレーションや合成の最終結果には何も変わりません。これは、3つのプロセスが並行ステートメントであり、VHDLはそれらが本当に並行であるかのように処理するためです。
ジョン・クーリーのデザインコンテスト
この例は、SNUG'95(Synopsys Users Group会議)のJohn Cooleyのデザインコンテストから直接得られたものです。このコンテストは、VHDLとVerilogのデザイナーが同じデザイン問題に反対することを意図していました。ジョンが心に留めていたことはおそらく、どの言語が最も効率的であるかを判断することでした。その結果、9人のVerilogデザイナーのうち8人が設計コンテストを完了しましたが、5人のVHDLデザイナーの誰もできませんでした。うまくいけば、提案された方法を使って、もっと良い仕事をするでしょう。
仕様
私たちの目標は、単純な合成可能なVHDL(エンティティおよびアーキテクチャ)で、キャリー出力、ボロー出力、およびパリティ出力を備えた、同期式のアップ/ダウン3、ローディング可能なモジュラス512カウンタを設計することです。カウンタは9ビットの符号なしカウンタであるため、0〜511の範囲です。カウンタのインターフェイス仕様は次の表に示されています。
名 | ビット幅 | 方向 | 説明 |
---|---|---|---|
クロック | 1 | 入力 | マスタークロック;カウンタはCLOCKの立ち上がりエッジで同期化されます |
DI | 9 | 入力 | データ入力バス。 UPとDOWNが両方ともローのときにカウンタにDIがロードされます |
アップ | 1 | 入力 | アップカウント3カウントコマンド。 UPがハイでDOWNがローのとき、カウンタは3をインクリメントし、その最大値(511)をラップし、 |
ダウン | 1 | 入力 | ダウンカウント5コマンド。 DOWNがハイでUPがローのとき、カウンタは5ずつ減少し、最小値(0)をラップします。 |
CO | 1 | 出力 | 信号を出す。最大値(511)を超えてカウントアップするときにのみハイとなり、 |
BO | 1 | 出力 | 信号を借りる。高値は最小値(0)を下回ってカウントアップし、 |
行う | 9 | 出力 | 出力バス。カウンタの現在の値。 UPとDOWNが両方ともハイのとき、カウンタはその値を保持します |
PO | 1 | 出力 | パリティ出力信号。カウンタの現在の値が1の偶数を含むときにハイ |
最大値を超えてカウントアップする場合、または最小値より下にカウントダウンする場合、カウンタはラップアラウンドします。
カウンタ電流値 | 上下 | カウンター次の値 | 次のCO | 次のBO | 次のPO |
---|---|---|---|---|---|
バツ | 00 | DI | 0 | 0 | パリティ(DI) |
バツ | 11 | バツ | 0 | 0 | パリティ(x) |
0≤x≤508 | 10 | x + 3 | 0 | 0 | パリティ(x + 3) |
509 | 10 | 0 | 1 | 0 | 1 |
510 | 10 | 1 | 1 | 0 | 0 |
511 | 10 | 2 | 1 | 0 | 0 |
5≦x≦511 | 01 | x-5 | 0 | 0 | パリティ(x-5) |
4 | 01 | 511 | 0 | 1 | 0 |
3 | 01 | 510 | 0 | 1 | 1 |
2 | 01 | 509 | 0 | 1 | 1 |
1 | 01 | 508 | 0 | 1 | 0 |
0 | 01 | 507 | 0 | 1 | 1 |
ブロック図
これらの仕様に基づいて、ブロックダイアグラムの設計を開始できます。最初にインタフェースを表現しましょう:
私たちの回路は4つの入力(クロックを含む)と4つの出力を持っています。次のステップは、使用するレジスタとコンビナトリアルブロックの数と役割の決定です。この単純な例では、1つの組み合わせブロックをカウンタの次の値の計算、実行、および借り出しに割り当てます。別の組合せブロックは、パリティの次の値を計算するために使用される。カウンタの現在の値、キャリーアウトおよびボローアウトはレジスタに格納され、パリティの現在の値は別のレジスタに格納されます。結果は下の図のようになります。
ブロックダイアグラムが10種類のデザインルールに準拠していることを確認すると、すぐに完了します。
- 私たちの外部インターフェイスは、周囲の大きな矩形で適切に表現されます。
- 私たちの2つの組み合わせブロック(丸)と2つのレジスタ(四角)は明確に分離されています。
- 立ち上がりエッジでトリガされたレジスタのみを使用します。
- 私たちは1つのクロックしか使用しません。
- 4つの内部矢印(信号)、4つの入力矢印(入力ポート)、および4つの出力矢印(出力ポート)があります。
- 矢印のどれもいくつかの起源がありません。 3つの目的地があります(
clock
、ncnt
、do
)。 - 4つの入力矢印のいずれも内部ブロックの出力ではありません。
- 3つの出力矢印には、起点と起点がそれぞれ1つずつあります。しかし、
do
は2つの目的地があります:外部と私たちの組み合わせブロックの1つ。これはルール番号8に違反し、2008年以前のVHDLバージョンに準拠したい場合は、新しいコンビナトリアルブロックを挿入することで修正する必要があります。
- 私たちは、今まさに5内部信号(持って
cnt
、nco
、nbo
、ncnt
とnpo
)。 -
cnt
とncnt
によって形成されるダイアグラムには1サイクルしかありncnt
。サイクルには正方形のブロックがあります。
2008年以前のVHDLバージョンでのコーディング
ブロック図をVHDLで翻訳するのは簡単です。カウンタの現在の値の範囲はbit_vector
です。 bit_vector
、9ビットのbit_vector
信号を使用してそれを表します。唯一の微妙な点は、同じデータに対してビット単位(パリティの計算など)と算術演算を実行する必要があるからです。ライブラリieee
の標準的なnumeric_bit
パッケージは、これを解決します。これは、 bit_vector
と全く同じ宣言を持つunsigned
型を宣言し、算術演算子にunsigned
整数の混在を取るようにオーバーロードします。キャリーアウトと借り出しを計算するために、10ビットのunsigned
テンポラリ値を使用します。
ライブラリの宣言とエンティティ:
library ieee;
use ieee.numeric_bit.all;
entity cooley is
port(
clock: in bit;
up: in bit;
down: in bit;
di: in bit_vector(8 downto 0);
co: out bit;
bo: out bit;
po: out bit;
do: out bit_vector(8 downto 0)
);
end entity cooley;
アーキテクチャのスケルトンは次のとおりです。
architecture arc1 of cooley is
signal cnt: unsigned(8 downto 0);
signal ncnt: unsigned(8 downto 0);
signal nco: bit;
signal nbo: bit;
signal npo: bit;
begin
<...processes...>
end architecture arc1;
それぞれの5つのブロックはプロセスとしてモデル化されています。私たちの2つのレジスタに対応する同期プロセスは非常に簡単にコーディングできます。 コーディングの例で提案されたパターンを使用するだけです。例えば、パリティ・アウト・フラグを格納するレジスタは、次のようにコード化される。
poreg: process(clock)
begin
if rising_edge(clock) then
po <= npo;
end if;
end process poreg;
co
、 bo
、およびcnt
を格納するもう1つのレジスタ:
cobocntreg: process(clock)
begin
if rising_edge(clock) then
co <= nco;
bo <= nbo;
cnt <= ncnt;
end if;
end process cobocntreg;
名前の変更の組み合わせプロセスも非常に簡単です:
rename: process(cnt)
begin
do <= (others => '0');
do <= bit_vector(cnt);
end process rename;
パリティ計算では、変数と単純なループを使用できます。
parity: process(ncnt)
variable tmp: bit;
begin
tmp := '0';
npo <= '0';
for i in 0 to 8 loop
tmp := tmp xor ncnt(i);
end loop;
npo <= not tmp;
end process parity;
最後のコンビナトリアルプロセスはすべての中で最も複雑ですが、提案された翻訳方法を厳密に適用することで容易になります:
u3d5: process(up, down, di, cnt)
variable tmp: unsigned(9 downto 0);
begin
tmp := (others => '0');
nco <= '0';
nbo <= '0';
ncnt <= (others => '0');
if up = '0' and down = '0' then
ncnt <= unsigned(di);
elsif up = '1' and down = '1' then
ncnt <= cnt;
elsif up = '1' and down = '0' then
tmp := ('0' & cnt) + 3;
ncnt <= tmp(8 downto 0);
nco <= tmp(9);
elsif up = '0' and down = '1' then
tmp := ('0' & cnt) - 5;
ncnt <= tmp(8 downto 0);
nbo <= tmp(9);
end if;
end process u3d5;
2つの同期プロセスをマージすることもでき、組み合わせプロセスの1つを簡単な同時信号割り当てで簡素化することができます。ライブラリとパッケージの宣言を含む完全なコードと提案された単純化は次のとおりです。
library ieee;
use ieee.numeric_bit.all;
entity cooley is
port(
clock: in bit;
up: in bit;
down: in bit;
di: in bit_vector(8 downto 0);
co: out bit;
bo: out bit;
po: out bit;
do: out bit_vector(8 downto 0)
);
end entity cooley;
architecture arc2 of cooley is
signal cnt: unsigned(8 downto 0);
signal ncnt: unsigned(8 downto 0);
signal nco: bit;
signal nbo: bit;
signal npo: bit;
begin
reg: process(clock)
begin
if rising_edge(clock) then
co <= nco;
bo <= nbo;
po <= npo;
cnt <= ncnt;
end if;
end process reg;
do <= bit_vector(cnt);
parity: process(ncnt)
variable tmp: bit;
begin
tmp := '0';
npo <= '0';
for i in 0 to 8 loop
tmp := tmp xor ncnt(i);
end loop;
npo <= not tmp;
end process parity;
u3d5: process(up, down, di, cnt)
variable tmp: unsigned(9 downto 0);
begin
tmp := (others => '0');
nco <= '0';
nbo <= '0';
ncnt <= (others => '0');
if up = '0' and down = '0' then
ncnt <= unsigned(di);
elsif up = '1' and down = '1' then
ncnt <= cnt;
elsif up = '1' and down = '0' then
tmp := ('0' & cnt) + 3;
ncnt <= tmp(8 downto 0);
nco <= tmp(9);
elsif up = '0' and down = '1' then
tmp := ('0' & cnt) - 5;
ncnt <= tmp(8 downto 0);
nbo <= tmp(9);
end if;
end process u3d5;
end architecture arc2;
もう少し行く
提案された方法は単純で安全ですが、緩和できるいくつかの制約に依存しています。
ブロック図をスキップする
経験豊富なデザイナーは、シンプルなデザインのブロック図をスキップすることができます。しかし、彼らはまだハードウェアを最初に考えています。彼らは紙の上ではなく頭を描くが、何とか描き続けている。
非同期リセットを使用する
非同期リセット(またはセット)がデザインの品質を改善できる状況があります。提案された方法は、同期リセット(クロックの立ち上がりエッジで考慮されるリセット)のみをサポートしています。
process(clock)
begin
if rising_edge(clock) then
if reset = '1' then
o <= reset_value_for_o;
else
o <= i;
end if;
end if;
end process;
非同期リセットを使用したバージョンでは、感度リストにリセット信号を追加し、最も高い優先順位を与えることで、テンプレートを変更します。
process(clock, reset)
begin
if reset = '1' then
o <= reset_value_for_o;
elsif rising_edge(clock) then
o <= i;
end if;
end process;
いくつかの簡単なプロセスをマージする
私たちはこの例の最終版ですでにこれを使用しました。それらがすべて同じクロックを持つ場合、いくつかの同期プロセスをマージすることは簡単です。複数の組み合わせプロセスを1つにまとめることも簡単です。ブロックダイアグラムの単純な再編成に過ぎません。
いくつかの組み合わせプロセスを同期プロセスとマージすることもできます。しかし、これを行うには、ブロックダイアグラムに戻り、11番目のルールを追加する必要があります。
- いくつかの丸いブロックとそれらのまわりにエンクロージャーを描くことによって少なくとも1つの四角形ブロックをグループ化します。また、矢印を囲むこともできます。エンクロージャーの外を出入りしない場合は、矢印がエンクロージャーの境界を超えないようにしてください。これが完了したら、エンクロージャのすべての出力矢印を確認します。いずれかがエンクロージャーの丸いブロックから来た場合、またはエンクロージャーの入力でもある場合、これらのプロセスを同期プロセスでマージすることはできません。それ以外は可能です。
たとえば、カウンタの例では、次の図の赤いエンクロージャに2つのプロセスをグループ化できませんでした。
ncnt
はエンクロージャの出力であり、その起点はラウンド(コンビナトリアル)ブロックであるためです。しかしグループ化することができます:
内部信号npo
は役に立たなくなり、結果として生じるプロセスは次のようになります。
poreg: process(clock)
variable tmp: bit;
begin
if rising_edge(clock) then
tmp := '0';
for i in 0 to 8 loop
tmp := tmp xor ncnt(i);
end loop;
po <= not tmp;
end if;
end process poreg;
他の同期プロセスとマージすることもできます。
reg: process(clock)
variable tmp: bit;
begin
if rising_edge(clock) then
co <= nco;
bo <= nbo;
cnt <= ncnt;
tmp := '0';
for i in 0 to 8 loop
tmp := tmp xor ncnt(i);
end loop;
po <= not tmp;
end if;
end process reg;
グループ化することもできます:
はるかに単純なアーキテクチャに導く:
architecture arc5 of cooley is
signal cnt: unsigned(8 downto 0);
begin
process(clock)
variable ncnt: unsigned(9 downto 0);
variable tmp: bit;
begin
if rising_edge(clock) then
ncnt := '0' & cnt;
co <= '0';
bo <= '0';
if up = '0' and down = '0' then
ncnt := unsigned('0' & di);
elsif up = '1' and down = '0' then
ncnt := ncnt + 3;
co <= ncnt(9);
elsif up = '0' and down = '1' then
ncnt := ncnt - 5;
bo <= ncnt(9);
end if;
tmp := '0';
for i in 0 to 8 loop
tmp := tmp xor ncnt(i);
end loop;
po <= not tmp;
cnt <= ncnt(8 downto 0);
end if;
end process;
do <= bit_vector(cnt);
end architecture arc5;
2つのプロセスがあります( do
同時シグナル割り当ては同等のプロセスの略語です)。 1つのプロセスだけを持つソリューションは、練習問題として残されています。注意してください。興味深く微妙な疑問が生じます。
さらに進んでいく
レベルトリガラッチ、立ち下がりクロックエッジ、複数のクロック(およびクロックドメイン間の再同期化器)、同じ信号に対する複数のドライバなどは悪いことではありません。それらは時には便利です。しかし、それらを使用する方法と関連する落とし穴を避ける方法を学ぶことは、VHDLを使ったデジタルハードウェア設計のこの短い紹介をはるかに超えています。
VHDL 2008でのコーディング
VHDL 2008では、コードをさらに単純化するために使用できるいくつかの変更が導入されました。この例では、2つの修正が有効です。
- 出力ポートを読み取ることができます、我々はもはや
cnt
信号を必要としません、 - 単項
xor
演算子を使用してパリティを計算することができます。
VHDL 2008のコードは次のとおりです。
library ieee;
use ieee.numeric_bit.all;
entity cooley is
port(
clock: in bit;
up: in bit;
down: in bit;
di: in bit_vector(8 downto 0);
co: out bit;
bo: out bit;
po: out bit;
do: out bit_vector(8 downto 0)
);
end entity cooley;
architecture arc6 of cooley is
begin
process(clock)
variable ncnt: unsigned(9 downto 0);
begin
if rising_edge(clock) then
ncnt := unsigned('0' & do);
co <= '0';
bo <= '0';
if up = '0' and down = '0' then
ncnt := unsigned('0' & di);
elsif up = '1' and down = '0' then
ncnt := ncnt + 3;
co <= ncnt(9);
elsif up = '0' and down = '1' then
ncnt := ncnt - 5;
bo <= ncnt(9);
end if;
po <= not (xor ncnt(8 downto 0));
do <= bit_vector(ncnt(8 downto 0));
end if;
end process;
end architecture arc6;