key word :
module , assign , always , function , 異なるビット間での接続
ごちゃごちゃとした説明はかったるいので、そこら辺りはどこかの書籍に任せるとして、実際の記述とその説明を交えることによって簡単に説明を。
・基本構成 module
module name(**,**,**); //モジュール名 input [X:0] **,**; //入力信号 input ***; //入力信号 output [X:0] ****; //出力信号 reg ***; //FF宣言 wire ***; //内部信号 回路の記述〜〜〜 end module |
まずverilog記述は左記の様にモジュール名を決める。 そして外部への入出力信号(in,out)のビット長を決定し、 回路間を繋ぐ信号線を wire で定義し、FF で保存される値に関しては reg で定義する。 これらの初期設定を終えたら、後は回路の記述を行い、 最後にendmoduleで閉じる。 この際注意すべきことは、ファイル名とモジュール名を同じにしておく方が良いということ ファイル名とモジュール名が異なっていると、呼び出す際にどちらか分からなくなってしまう。 あっ、ちなみに拡張子は *.v です。 |
あと、ここで説明することとしたら [X:0] の記述。
これは信号のビット長を定めるもので、8bitの信号を扱いたい場合は[7:0]って記述するのが一般的です。(7 downto 0)
一応[0:7]とか[8:1]って書いても大丈夫なんですけど、最下位bitが0bit目ってのが一番慣れ親しんでると思うんで、[7:0] で進めていきます。
7bit | 6bit | 5bit | 4bit | 3bit | 2bit | 1bit | 0bit |
・assign文 組み合わせ回路
module and_test(A,B,Z); input [2:0] A; input B; output Z; assign Z = A[0] & B; end module |
ANDやORの様な論理演算や算術演算など、一行程度に記述できる回路であればassignを用いることが多い。 assign文には、wire宣言したネット型を代入する。reg宣言した型ではエラーとなる。 一応、inputで宣言された型は、wire型で定義されているので、問題はない。 記述は左記の様に行う。 まず、module名であるが、ここは判りやすい名前でよいのだけれど、verilog の予約語と重なったりinput、outpuで宣言した名前と重なったりするとエラーとなるので注意。 ここの記述 "=" はブロッキング代入と言われ、Cの様な手続き型言語のように逐次実行される。
|
この記述の動作は
ZはAの0bit目とBのANDを取った値となる。
一応、bit数は同じじゃないとダメなんで注意。
・always文 順序回路
always @(posedge clk)begin //clkが立ち上がるたびに q <= d ; //qにdを代入 d <= q ; //dにqを代入 end always #10 //10経過するたびに clk = ~clk; //clkを反転 always @(a or b) //a、bに変化があるたびに if( a == 1 ) //aが1なら q <= a; //qにbを代入 |
always節は、括弧内の信号が指定された条件の変化があったときに内部に書かれた命令が実行される。 ※括弧内に入っている値を、センシビティ・リストと言う。 動作条件としてシステム周波数clkのposedgeを使うのが基本で、posedgeとは信号が0から1に変化した際という意味を持つ。 また、always節にて他の動作条件を記述する際は、orを使う。 FF(フリップフロップ)やラッチ信号は、レジスタ宣言(reg)しておき、always節内で記述する。 このalways節中の "<=" は通常の手続き型言語と異なりハードウェア記述言語の特徴の一つであり、「ノン・ブロッキング代入」と言われているものである。 この記述はシミュレーション実行時に並列に評価され、下記の例の場合、clkが変化するとdとqへの代入が同時に行われる。 この動作は実際のFFの動作に近いものである。 C言語と同じように、1行下までなら括弧などで括る必要は無いが、そうで無い場合は、begin 〜 end で括る必要がある。 FFとして使う記述では基本的に、このセンシビティ・リスト内にはクロックとリセット以外は記述しない。してはいけない。 任意の信号の立ち上がりや立下りを利用して回路を動作させたい場合は多々あるが、それを利用してしまうと非同期の回路になり 実際に回路化する際に問題が多くなる。しかし、任意の信号の立ち上がり等を利用したい場合は多々ある。それについては後ほど 説明を追加する。
|
一応、センシビティリストにposとか使わなければ(a or b)の様に普通の回路も表現可能。
順序回路を動作させる際、posedge(クロックの立ち上がり)を利用している。
回路全体の同期を取る際、データの遅延等を考えた際のことを考え
全ての動作をposedgeで作るのが望ましい。
また、内部の動作が複数行にわたる場合は、begin 〜 endで囲う必要がある。
if文も同様であり、case文の場合はcase( ) 〜 endcaseで囲う。
そして、予想していない入力に対応するためにも、if文ではelseをcase文ではdefaultを定義しておくのがベター。
・function文
function select_f ; input in1,in2,in3,in4; input [1:0] sel; case ( sel ) 3'h0 : select_f = in1 ; 3'h1 : select_f = in2 ; 3'h2 : select_f = in3 ; 3'h3 : select_f = in4 ; default : select_f = 1'bx ; endcase endfunction /////////////////////////////////////////// assign out1 = select_f(a,b,c,d,sel1); assign out2 = select_f(a,b,c,d,sel2); |
サブルーチンと言うかなんと言うか、与えられた値を処理して値を返す関数。 今回作った select_fという関数は、a,b,c,dという値をselによって選択し、out1に選択された値を入れるというもの。 select_f( )の括弧内の順番で、inputでしていされた型に値が入れられるので、順番に注意! あと、さり気なく case 文つかったけど、こちらの詳しい説明はまた後ほど。
|
alwaysに比べて識別子を加えたりと、若干記述量が増えるし、引数や戻り値が呼び出し側と一致しなくても文法エラーが出ないからちょっと曲者。
けれどもラッチを生成しないって事や組み合わせ回路か順序回路かの区別が明確になってるんで、個人の好みに併せて。
まぁ、慣れたら殆どはalwaysで書けるので、必要ないといえば必要ないのだけれど・・・
・異なるbit間での接続
//2bitの信号Aを4bitのNameへ入力する module名(〜〜〜〜,.Name( { 2'h0 , A } ) ); module名(〜〜〜〜,.Name( R[0] ) ); |
例えば上の様に、規格化されたモジュールの入力信号が4bitで、そこへ入力する信号Aが都合上2bitだった場合。 " { } " で括り、その内部では足したいbit数を記述して " , " で区切り入力したい信号を入れる。 ↓こんなイメージ
また、8bitの信号の0bit目を利用したい際には、1bitで宣言された型に直接入れれば一応は信号が通るが、R[0]と、しっかりと記述した方が良い。 Verilogの場合だと、bit数が異なっても通るが、VHDLだと通らないので、bit数をしっかりと合わせましょう。
|
一応、応用というかなんと言うか、テストベンチにてレジスタに値を入れる際、16進数の値をぽーんと入れてもいいのだけれど、分割すると判りやすくなるかもしれぬ。
regi1 "<= 8'h35;
regi1 "<= {1'b0,1'b0,1'b1,1'b1,1'b0,1'b1,1'b0,1'b1};
regi1 "<= {3'h1,3'h5,2'b01};
7bit | 6bit | 5bit | 4bit | 3bit | 2bit | 1bit | 0bit |
8'h35 | |||||||
4'h3 | 4'h5 | ||||||
1'h0 | 1'h0 | 1'h1 | 1'h1 | 1'h0 | 1'h1 | 1'h0 | 1'h1 |
3'h1 | 3'h5 | 2'b01 | |||||
とりあえずこんなもん。