#どんな記事?
自分がみた感じVerilogの文法説明書みたいなページはたくさんあるのですが、軽い感じで読めるサイトが少ないなと思って書いてみました。
本ページでは、Verilogの文法を必要最低限理解するために必要なことを説明していきます。筆者もVerilogを始めたてなので、細かい言葉遣いに間違いがあるかもしれません...
最終的にプロセッサー(もどき)をサンプルとして扱いたいと思っています。
一回で書くのは大変なので、何回かに分けて投稿したいと思います。
今回は、変数の代入なりモジュール宣言なり、if分岐なりを説明します。
#wire変数とreg変数
Verilogの変数には、wire(ワイヤ)とreg(レジスタ)の2つの型みたいなものがあります。C言語などからきた人にはここがとてもわかりにくいと思います(体験談)。
##wire変数
wire変数は、モジュール(部品)同士をつなぐ配線をあらわします。C言語の変数と違い、値を保持する入れ物ではなく 値が流れている線 のイメージです。
たとえば、32bitのwire変数は以下のように宣言します。
wire [31:0] hoge;
ハードウェア記述なので何bitなのかを宣言しなければなりません。wire変数は、常に値の更新が起こりうる変数です。
##reg変数
reg変数は、C言語などの変数と同様、値を保持しておく入れ物です。
たとえば、32bitのreg変数は以下のように宣言します。
reg [31:0] hoge;
reg変数はwire変数とは違って、値が流れているわけではないので値を代入するタイミングを指定する必要があります。
#assign文とalways文
ものすごく大雑把なことを言うと、ワイヤ変数への代入はassign文、レジスタ変数への代入はalways文中でおこなうと考えてください(実際には、レジスタ変数はalways文以外での書き換えられます)。
##assign文
ワイヤ同士をつなげたい場合assign文を使用します。
たとえば、32bitの符号付きワイヤaとbの和をワイヤcに流したい場合、以下のように書きます。
wire signed [31:0] a, b, c;
assign c = a + b;
Verilogでは、なにも宣言しないと符号なしの変数になるので注意してください。
また以下のように、wire変数の初期化の時点でワイヤをつなげることもできます。
wire [31:0] a, b;
wire [31:0] c = a + b;
##always文
reg変数はwire変数とは違って、値が流れているわけではないので値を代入するタイミングを指定する必要があります。このタイミングの指定をハードウェアではクロックで行います。そして、タイミングの指定方法がalways文です。always文は、あるタイミングで起こしたい処理を書くことができます。
たとえば、クロックの立ち上がり時に行いたい処理は以下のブロックのなかに書きます(以下、クロックはCLKというreg変数で表します)。
reg [31:0] a;
always @(posedge CLK) begin
a <= 0; //CLKの立ち上がり時に、aに0を代入
end
ここで、ブロックの指定にはC言語の{
, }
の代わりにbegin
, end
を使用します。また、posedge
以外にnegedge
も使用できます。
reg変数への代入は、initial文、task、function中でも可能ですが、これはあとで触れます。
#ブロッキング代入とノンブロッキング代入
Verilogの代入には2種類の代入法があります。違いとしては、同時に代入を行うのか、順番に行うのかということです。
なんで2種類必要かというと、ハードウェアなのでC言語みたいに コードを上から読んでいくわけではない です。なので、あるタイミングで、この代入とこの代入を同時に行いたい、という場合があります。
##ブロッキング代入
ブロッキング代入は、順番に行う代入です。=
を使用します。
たとえば、レジスタaとbの和をcに代入するときは以下のようになります。
reg [31:0] a, b, c;
c = a+ b;
##ノンブロッキング代入
ノンブロッキング代入は、同時に行う代入です。
たとえば、レジスタaとbの値を入れ替えたいときは以下のようになります。
reg [31:0] a, b;
a <= b;
b <= a;
C言語では必要なスワップ用の一時変数が不要になっています。
はじめのうちは、reg変数への代入はノンブロッキング代入しておけばなんとかなります(たぶん)。
#モジュールの作成
モジュールとは、回路を構成している部品だと思っていただければ大丈夫です。
ここでは指定されたアドレスの値を返すメモリを考えてみましょう。コードはこんな感じです。
modeule MEM(addr, data) ;
input [9:0] addr;
output [31:0] data;
reg [31:0] mem[1023:0]; //4 Kbyte memory
assign data = mem[addr];
endmodule
最初にinput
とoutput
で入力と出力を指定します。基本的に入出力はワイヤで行います。なお、mem[1023:0]
は配列を表します。
メモリはレジスタとして値を保持していて、それをワイヤに流すという構図になっています。出力のdataはワイヤなのでassign
を使用していることに注意してください。
#function文
function文はC言語の関数に相当します。ただし、関数名と返り値の名前が一致していなくてはなりません。
以下に、入力の値に1足した数を返すfunctionのコードを示します。
wire [31:0] a,result;
function [31:0] calctest;
input [31:0] inp;
calctest = inp + 1;
endfunction
//呼び出すとき
assign result = calctest(a);
#if文とswitch文と三項演算子
##if文とswitch文
if文やswitch文はfunctionおよびalways文中でしか使用できません。以下にサンプルコードを上げておきます。
function calcif;
input [31:0] inp;
if(inp == 1) begin
calcif = 0;
end else if (inp == 2) begin
calcif = 1;
end else begin
calcif = 2;
end
endfunction
function calccase;
input [31:0] inp;
case(inp)
32'd1: calccase = 0;
32'd2: calccase = 1;
defaule: calccase = 2;
endcase
endfunction
ここで、32'd
は32bitの10進数表記を表します。
##三項演算子
三項演算子はどこでも使用できます。(条件式) ? (真のときの値) : (偽のときの値)
と書きます。boolが真のときのみインクリメントする処理は以下のようになります。
wire bool; //指定しないと1bit
wire a = (bool) ? a+1 : a;