Verilogを使ってみる
by K.I
2007/02/04〜
Index
- VHDLは使ったことあったけど(書いたことがあるぐらいだけど)、Verilogは使ったことがなかった。
- KさんがVerilogを使うと言うので、自分も勉強がてら試しにちょっと使ってみようと思う。
- とりあえず、備忘録ということで。
[top]
- XilinxのSpartan3 StarterKITを使う。これはちょっと弄ったこともあるけど、まともな回路は作ったことない。
- とりあえず、シリアルインターフェースでレジスタの書換えが出来るようなものを作ってみる。(作ってみたい)
- まずは、StarterKITの使えそうな機能を、抜粋しておく。(UserGuideの内容そのままだが、見るの面倒なので。。)
- スターターキットのRS232ポートは、以下のようなピン接続になってる。
RS232 port | DB9 | Spartan3 |
| | |
| | |
RXD | Pin3 | T13 |
| | |
TXD | Pin2 | R13 |
| | |
- 予備のRXD/TXDが、N10/T14に接続されているので、その気になればRS232Cを2本とれる。
Switch | SW7 | SW6 | SW5 | SW4 | SW3 | SW2 | SW1 | SW0 |
| | | | | | | | |
| | | | | | | | |
FPGA Pin | K13 | K14 | J13 | J14 | H13 | H14 | G12 | F12 |
| | | | | | | | |
- プッシュボタンは、リセットとかプログラムのスタートとかに使える。
PushButton | BTN3 (UserReset) | BTN2 | BTN1 | BTN0 |
| | | | |
| | | | |
FPGA Pin | L14 | L13 | M14 | M13 |
| | | | |
LED | LED7 | LED6 | LED5 | LED4 | LED3 | LED2 | LED1 | LED0 |
| | | | | | | | |
| | | | | | | | |
FPGA Pin | P11 | P12 | N12 | P13 | N14 | L12 | P14 | K12 |
| | | | | | | | |
- で、肝心のクロックは、基板の裏に50MHzの発振器が実装されてる。
Oscillator Source | FPGA Pin |
| |
| |
50MHz(IC4) | T9 |
| |
Socket(IC8) | D9 |
| |
- DCMを使って分周出来るみたいだけど、、DCMってなんだ?
- UNISIMライブラリに含まれているみたいだ。でも、VerilogではUNISIMライブラリってどうやって使うんだろ?
- DCMって絶対使わなきゃダメなのか? それとも、外部の回路との間でクロックのやり取りがあるときに使うものなのかな。
[top]
- Xilinxの統合環境ISEの簡単な使い方について
- まず、File→NewProjectで、新規プロジェクトを作成。とりあえず ledとする。
- Device Family → Spartan3
- Device → xc3s200
- Package → ft256
- Speed Grade → -4 (これは分からんので、適当)
- 今回は、Verilogを使うように設定する。
- Top-Level Module Type → HDL
- Synthesys Tool → XST(VHDL/Verilog)
- Simulator → Modelsim
- Generated Simulation Language → Verilog
- 次にVerilog Module を作成。 とりあえず seri.vhdにする。
- 回路モジュールの名前(とりあえず seri)と、入出力の設定。
- 最初は、単純にRXDをTXDに繋いで見る。
- 単純に繋ぐだけなら、assignでブロッキング代入すれば良い。(だと思う)
- HDLで記述された入出力ピンを、FPGAの実際のピンの割り当てるためのファイル。
- Sources in Projectのウィンドウを右クリックして、New Source
- Implementation Constraints File で、UCFファイルを作成。とりあえず、veri.ucfとする。
- これで、UCFファイルがプロジェクトの追加される。
- UCFファイルをダブルクリックしても空っぽだ。
- Assign Package Pins をダブルクリックすると、RXD,TXDが表示されるので、以下のようにピンを設定。
I/O Name | I/O Direction | Loc | Bank | I/O Std. |
| | | | |
| | | | |
RXD | Input | T13 | BANK4 | |
| | | | |
TXD | Output | R13 | BANK4 | |
| | | | |
- HDL記述をコンパイルして、FPGA内部のRAMに書き込むためのデータファイル。
- Generate Programming Fileのプロパティから、StartUp Optionsタブ→FPGA Start-Up Clockで、CCLKをチェックする。
- コンフィグレーションPROMにデータを書き込む場合は、CCLKにする必要1がある。
- Readback OptionタブのCreate ReadBack Data Filesをチェック、するとCreate Mask Fileが有効になるので、チェックしておく。
- mskファイルは、bitファイルをFPGAに書き込む場合に必要になる。→PROMに書き込む場合は不要かも。
- 上記の設定後に、Generate Programming Fileをダブルクリックすると、bitファイルが生成される。
- FPGA書き込み用ツールのiMPACTは、bitファイルを直接コンフィグレーションPROMに書き込むことが出来ない。
- iMPACTを使って、bitファイル→MCSファイルに変換する必要がある。2
- WebPACKは、まぁまぁ使いやすいツールなんだけど、iMPACTは全く使い辛い3ツールなので、ここで引っ掛かる人は多いと思う。
- やり方はいろいろあるが、どれも手順が分かり辛い。とりあえずWizardを使った方法を詳しく書いておく。
- まず、WebPACKのGenerate PROM, ACE, or JTAG Fileをダブルクリックして、iMPACTを起動する。
- Editメニュー→Launch Wizard...を選択。→戻るボタンが有効なら、とにかく一番最初に戻っておく。
- What do you want todo first? で、Prepare Configuration Filesを選択。
- I want to create a : で、PROM File を選択。
- I want to target a: で、Generic Parallel PROM を選択。PROM File FormatはMCSを選択。PROM File Nameを指定する。
- Auto Select PROMに、チェック。
- You have entered following information は内容を確認するだけ、そのまま次へ。
- Add File...ボタンを押して、bitファイルを指定する。
- Would you like to add another design file to Data Stream: 0? というDialogには、いいえと答える。
- 完了ボタンを押して、Do you want to generate file now? というDialogに、はいと答える。
- PROM File Generation Succeeded と表示されれば、MCSファイルが作成されている。
- しかしながら、UCFファイルを書き換えたりしてbinを作り直すと、うまくMCSファイルが作れないことがある。
- でも何回かやりなおしていると、出来たりする。よく分からん。。
- bitファイルの作成までは、簡単なんだけどね。。。
- 書き込みする前に、JTAGケーブルをスターターキットに接続して、電源のACアダプタを挿しておく。
- まず、WebPACKのConfigure Device(iMPACT)をダブルクリックして、iMPACTを起動する。
- Editメニュー→Launch Wizard...を選択。→戻るボタンが有効なら、とにかく一番最初に戻っておく。
- What do you want todo first? で、Configure Devicesを選択。
- I want to configure device via: で、Boundary-Scan Modeを選択。
- Automatically connect to cable and identify Boundary-Scan chain を選択。
- すると、JTAGのバウンダリスキャンで、2つのデバイスが見つかったというDialogが出るのでOKを押す。
- 最初は、xc3s200が選択されるので、Bypassボタンを押してスルーする。
- 次に、xcf02sが選択されるので、mcsファイルを指定する。
- これは、検出されたデバイスをダブルクリックして指定しても同じ。
- xcf02sを、右クリック、programを選択。OKボタンを押すと、コンフィグレーションPROMへ書き込まれる。
- コンフィグレーションPROMに書き込んだデータは、FPGAの起動時に自動的にRAMに読み込まれる。
- すぐ反映させるには、スターターキットのPROGボタンを押せば良い。
- RXDをTXDに繋いだだけの回路だが、ハイパーターミナルで、ちゃんとキー入力がエコーされることを確認出来た。
- ちょっと確認する場合は、PushスイッチとLEDの方が分かりやすいかも。
I/O Name | I/O Direction | Loc | Bank | I/O Std. |
| | | | |
| | | | |
SW | Input | M13 | BANK3 | |
| | | | |
LED | Output | K12 | BANK3 | |
| | | | |
- User Constraintsの、Assign Package Pinsで、書き換えられるんだけど、なんかうまく行かない。
- Edit Constraints (Text)から、開いてみると2重に定義されてしまってる。
- こちらを直すと、ちゃんと動作した。
1JTAGを使ってコンフィグレーションPROMに書き込む場合でも、CCLKを選択する。
2何でこんな面倒な仕様なんだろう。。自動的に変換して書けるようにもすれば良いのに。。。
3一度使い方が分かったはずだったんだけど、久しぶりに弄ったら嵌ってしまった。なんて分かり難いツールなんだ。。。
[top]
- ここまで来て、いきなり回路を考えるのは厳しそうって思えてきた。
- まずは単純な回路で一通り試してからの方が良さそうだ。
- 簡単な3ビットUpカウンタを書いてみる。→シュミレーションやりやすいように短く
- 非同期リセット、プリセット付き
`timescale 1ns / 1ps
module counter3(d, q, ld, rst, clk);
input [2:0] d;
output [2:0] q;
input ld;
input rst;
input clk;
reg [2:0] q; // alwaysから変化させるので、regとしても指定
always @(posedge clk or posedge rst) begin
// 非同期リセットなので、センシビリティリストにrstを入れる
if (rst == 1'b1) begin
q <= 3'b000; // 非同期リセット
end else if (ld == 1'b1) begin
q <= d; // 同期プリセット
end else begin
q <= q + 1'b1; // Upカウント
end
end
endmodule
- 書いてみると、VHDLとそんなに違いはないみたい。
- Verilogではalwaysが、VHDLのprocess文に近いようだ。
- alwaysやinitialから変化させる場合は、regにする必要があるらしい。
- regが記憶素子、wireが接続素子ということだが、いまいち使い分けが分かり難いんだけど。
- xc3s200-4ft256を右クリックして、NewSourceで Verilog Test Fixture を選択する。
- すると、テストベンチのスケルトンのようなものが出来る。
- ディレイを使って、入力信号を記述する。
`timescale 1ns / 1ps
module test_v;
// Inputs
reg [2:0] d;
reg ld;
reg rst;
reg clk;
// Outputs
wire [2:0] q;
// Instantiate the Unit Under Test (UUT)
counter3 uut (
.d(d),
.q(q),
.ld(ld),
.rst(rst),
.clk(clk)
);
parameter CYCLE = 100; // 1サイクルを100と定義する
always #(CYCLE/2) clk = ~clk; // クロック生成
initial begin
#0; // 最初は全部クリア
d = 3'b000;
ld = 0;
rst = 0;
clk = 0;
// Wait 100 ns for global reset to finish
#(CYCLE);
rst = 1; // リセット
#(CYCLE);
rst = 0;
#(CYCLE*10);
d = 3'b011; // プリセット値
ld = 1; // データロード
#(CYCLE);
d = 3'b000;
ld = 0;
#(CYCLE*10);
end
endmodule
という感じで、カウンタのテストベンチを書いてみた。
- ModelSim Simulatorの、Simulate Behavioral ModelをダブルクリックするとModelSimが起動する。
- シュミレーション時間を、1usにして4回RUNしてみる。
- ちゃんと動いてるみたいだな〜。まぁこれなら、Verilogも使えそうだ。
- 波形のRADIXの設定等は Fileメニューから Save(例えば wave.do)して、必要な時にコマンドラインから do wave.doとすれば良い。
[top]
- UARTの受信だけ考えてみる。とりあえずの仕様としては、
- データ長は8ビットで、LSBファースト
- パリティなし、1ストップビット
- ボーレートは、9600bps固定
- スターターキット基板上の50MHzを分周して、9600bpsのボーレート用クロックを作る。
- 受信用に9600bpsの4倍のクロックを作るとすると、50000000/9600/4 = 約1302分周か。
- リプルカウンタで分周器を作ってサンプリング回路のクロックにしたいところだけど、同期回路ではクロックが複数になってしまうのは気持ちが悪い。
- 分周器は同期回路では複雑になってしまって嫌なんだけど、とりあえず制御信号として作ってみよう。
`timescale 1ns / 1ps
module clkgen(rxck, txck, rst, clk);
output rxck;
output txck;
input rst;
input clk;
reg rxck; // alwaysから変化させるので、regとしても指定
reg txck;
reg [10:0] rxcount;
reg [1:0] txcount;
always @(posedge clk or posedge rst) begin
// 非同期リセットなので、センシビリティリストにrstを入れる
if (rst == 1'b1) begin
rxcount <= 0; // 非同期リセット
rxck <= 0;
end else if (count == 1301) begin // 1302分周
rxcount <= 0;
rxck <= 1;
end else begin
rxcount <= count + 1; // Upカウント
rxck <= 0;
end
end
always @(posedge clk or posedge rst) begin
// 非同期リセットなので、センシビリティリストにrstを入れる
if (rst == 1'b1) begin
txcount <= 0; // 非同期リセット
txck <= 0;
end else if (count == 3) begin
txck <= 1; // 4分周
end else if (rxck == 1'b1) begin
txcount <= txcount + 1; // Upカウント
txck <= 0;
end
end
endmodule
- デューティとかは敢えて考えず、制御用に1クロック幅の信号を作る。
- txckはボーレート、rxckはボーレートの4倍のタイミングのクロック制御信号。
- 分周器で作ったボーレートの4倍のタイミング(rxck)で、RXDからのデータをシフト。
- 外からの信号ということで、シフトレジスタで受けて、ついでに簡単なノイズ除去を行う。
`timescale 1ns / 1ps
module ncan(rxd, buf, rst, rxck, clk);
input rxd;
output [7:0] buf;
input rst;
input rxck;
input clk;
reg [7:0] buf;
wire cont;
always @(posedge clk or posedge rst) begin
if (rst == 1'b1) begin
buf <= 8'b00000000; // 非同期リセット
end else if (rxck == 1'b1) begin
buf <= buf >> 1; // シフト
buf[7] <= rxd;
buf[4] <= ncout;
end
end
function nc;
input [2:0] in;
if (in == 3'b000)
nc = 0; // 3回連続0で0出力
else if (in == 3'b111)
nc = 1; // 3回連続1で1出力
endfunction
assign ncout = nc(buf[7:5]); // これが簡易雑音除去回路ってことで
endmodule
- これは無駄にシフトレジスタで受けてるんだけど、自分としてはデータが流れてる感じが好きなので。
- 実際は、ノイズ除去もなしで単純にFFで受けるぐらいでも良いと思う。
- シリアルデータは、最初にスタートビット、データ8ビット、最後にストップビットが来るので、10状態のカウンタを作れば良い。
- スタートビットを検出したら、ステート用のカウンタをリセット。
`timescale 1ns / 1ps
module state(buf, rst, state, rxck, rxck4, clk);
input [7:0] buf;
input rst;
output[3:0] state;
input rxck;
output rxck4;
input clk;
reg [3:0] state;
reg [1:0] rxcount4;
assign start = (buf[3:0] == 4'b0011)?1:0; // スタートビット検出
always @(posedge clk or posedge rst) begin
// 非同期リセットなので、センシビリティリストにrstを入れる
if (rst == 1'b1) begin
rxcount4 <= 0; // 非同期リセット
end else if (rxck == 1 && start == 1) begin
rxcount4 <= 0; // startで同期リセット
end else if (rxck == 1) begin
rxcount4 <= txcount4 + 1; // Upカウント
end
end
assign rxck4 = (rcount4 == 2'b11)?1:0; // rxck4のタイミングは調整必要かも
always @(posedge clk or posedge rst) begin
// 非同期リセットなので、センシビリティリストにrstを入れる
if (rst == 1'b1) begin
state <= 0; // 非同期リセット
start <= 0;
end else if (rxck4 == 1'b1 && (start == 1'b1 || state == 4'b1001)) begin
state <= 0; // スタートビット検出、状態9で同期リセット
start <= 0;
end else if (rxck4 == 1'b1) begin
state <= state + 1; // Upカウント
end
end
endmodule
- ボーレートのタイミングは、rxck4じゃなくてtxckでも良いかもしれないが、
- このステートマシンのカウンタは、バイナリカウンタ構成だが、これだと同期カウンタになっちゃうので、出来上がりは結構複雑になる。
- これはテキトウに作ってるので、こんな風にしたけど、実際はあまりお勧め出来ない。
- 10状態であれば、5ビットのジョンソンカウンタでも良いんじゃないかと思う。
- その方が単純なカウンタになるし、デコードが簡単だし変なハザードも出ない。
- ステートマシンが出来れば、あとは単純にパラレル変換するだけだ。
- ボーレートのタイミング(rxck4)でデータをシフトする。
`timescale 1ns / 1ps
module seripara(out, rst, state, rxck4, clk);
output out;
input rst;
input[3:0] state;
input rxck4;
input clk;
reg [7:0] rxbuf;
reg [7:0] out;
always @(posedge clk or posedge rst) begin
// 非同期リセットなので、センシビリティリストにrstを入れる
if (rst == 1'b1) begin
rxbuf <= 0; // 非同期リセット
out <= 0;
end else if (state == 0) begin
; // 状態0はなにもしない
end else if (rxck4 == 1'b1 && state == 9) begin
out <= rxbuf; // 状態9で出力
end else if (rxck4 == 1'b1) begin
rxbuf <= rxbuf>>1; // それ以外の状態でシフト
rxbuf[7] <= buf[4]; // シリバラ変換(取り込み位置も調整要か?)
end
end
endmodule
- シリパラ変換のデータ取り込みは、スタートビット検出位置からズラしてるつもり。。
[top]
いろいろ参考にさせて頂いたサイト(感謝)
- 上で検討したのと全然別なんだけど、 こちら にUARTサンプルコードがあったので、使わせてもらった。
- クロックと同期していた方が良かったので、ちょっとそれだけ変えてある。
- 仕様が不明なため、シュミレーションで動作確認した。
- txclkは送信ボーレートと同じ、rxclkは受信ボーレートx16
- 送信は、tx_dataに送信データセット⇒ld_tx_data=0→1→0⇒tx_empty=1で送信完了
- 受信は、rx_empty=0で受信完了⇒uld_rx_data=0→1→0⇒rx_dataに受信データがセットされる
- 受信完了後、unloadしないとデータが読めないので注意
- tx_enable,rx_enableは常にEnableでも良い様だ
- このコードは、clkに同期するように、オリジナルを書き換えてある
- rx_clkはtx_clk*16である必要がある。rx_clk,tx_clkは、1クロック幅のenable信号
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Engineer: K.I
// Module Name: uart
// Description: UARTモジュール
// http://www.asic-world.com/examples/verilog/uart.htmlのUARTサンプルコード
// 仕様が不明なため、シュミレーションで動作確認した。
// txclkは送信ボーレートと同じ、rxclkは受信ボーレートx16
// 送信は、tx_dataに送信データセット⇒ld_tx_data=0→1→0⇒tx_empty=1で送信完了
// 受信は、rx_empty=0で受信完了⇒uld_rx_data=0→1→0⇒rx_dataに受信データがセットされる
// 受信完了後、unloadしないとデータが読めないので注意
// tx_enable,rx_enableは常にEnableでも良い様だ
// このコードは、clkに同期するように、オリジナルを書き換えてある
// rx_clkはtx_clk*16である必要がある。rx_clk,tx_clkは、1クロック幅のenable信号
//////////////////////////////////////////////////////////////////////////////////
//-----------------------------------------------------
// Design Name : uart
// File Name : uart.v
// Function : Simple UART
// Coder : Deepak Kumar Tala
// Modify : Kohji Itoh
//-----------------------------------------------------
module uart (
reset ,
clk ,
txclk ,
ld_tx_data ,
tx_data ,
tx_enable ,
tx_out ,
tx_empty ,
rxclk ,
uld_rx_data ,
rx_data ,
rx_enable ,
rx_in ,
rx_empty
);
// Port declarations
input reset ;
input clk ;
input txclk ;
input ld_tx_data ;
input [7:0] tx_data ;
input tx_enable ;
output tx_out ;
output tx_empty ;
input rxclk ;
input uld_rx_data ;
output [7:0] rx_data ;
input rx_enable ;
input rx_in ;
output rx_empty ;
// Internal Variables
reg [7:0] tx_reg ;
reg tx_empty ;
reg tx_over_run ;
reg [3:0] tx_cnt ;
reg tx_out ;
reg [7:0] rx_reg ;
reg [7:0] rx_data ;
reg [3:0] rx_sample_cnt ;
reg [3:0] rx_cnt ;
reg rx_frame_err ;
reg rx_over_run ;
reg rx_empty ;
reg rx_d1 ;
reg rx_d2 ;
reg rx_busy ;
// UART RX Logic
always @ (posedge clk or posedge reset) begin
if (reset) begin
rx_reg <= 0;
rx_data <= 0;
rx_sample_cnt <= 0;
rx_cnt <= 0;
rx_frame_err <= 0;
rx_over_run <= 0;
rx_empty <= 1;
rx_d1 <= 1;
rx_d2 <= 1;
rx_busy <= 0;
end
else if (uld_rx_data) begin
// Uload the rx data
rx_data <= rx_reg;
rx_empty <= 1;
end
else if (rxclk) begin
// Synchronize the asynch signal
rx_d1 <= rx_in;
rx_d2 <= rx_d1;
// Receive data only when rx is enabled
if (rx_enable) begin
// Check if just received start of frame
if (!rx_busy && !rx_d2) begin
rx_busy <= 1;
rx_sample_cnt <= 1;
rx_cnt <= 0;
end
// Start of frame detected, Proceed with rest of data
if (rx_busy) begin
rx_sample_cnt <= rx_sample_cnt + 1;
// Logic to sample at middle of data
if (rx_sample_cnt == 7) begin
if ((rx_d2 == 1) && (rx_cnt == 0)) begin
rx_busy <= 0;
end else begin
rx_cnt <= rx_cnt + 1;
// Start storing the rx data
if (rx_cnt > 0 && rx_cnt < 9) begin
rx_reg[rx_cnt - 1] <= rx_d2;
end
if (rx_cnt == 9) begin
rx_busy <= 0;
// Check if End of frame received correctly
if (rx_d2 == 0) begin
rx_frame_err <= 1;
end else begin
rx_empty <= 0;
rx_frame_err <= 0;
// Check if last rx data was not unloaded,
rx_over_run <= (rx_empty) ? 0 : 1;
end
end
end
end
end
end
else begin // !rx_enable
rx_busy <= 0;
end
end
end
// UART TX Logic
always @ (posedge clk or posedge reset) begin
if (reset) begin
tx_reg <= 0;
tx_empty <= 1;
tx_over_run <= 0;
tx_out <= 1;
tx_cnt <= 0;
end
else if (txclk) begin
if (tx_enable && !tx_empty) begin
tx_cnt <= tx_cnt + 1;
if (tx_cnt == 0) begin
tx_out <= 0;
end
if (tx_cnt > 0 && tx_cnt < 9) begin
tx_out <= tx_reg[tx_cnt -1];
end
if (tx_cnt == 9) begin
tx_out <= 1;
tx_cnt <= 0;
tx_empty <= 1;
end
end
if (!tx_enable) begin
tx_cnt <= 0;
end
end
else if (ld_tx_data) begin
if (!tx_empty) begin
tx_over_run <= 0;
end
else begin
tx_reg <= tx_data;
tx_empty <= 0;
end
end
end
endmodule
[top]
- クロックに同期していない信号をそのまま同期カウンタに入れたりすると、
- 各ビットに入力されるタイミングはバラバラなので、滅茶苦茶な結果になる。
- これは基本的にはFFで受けて、クロックで同期してから入力すれば良いが、
- 非同期の入力は不安定なので、FFの出力も不安定4になってしまう場合がある
- このような不安定状態を、メタステーブルと言うらしい。
- メタステーブルは再現性が無く、検証も難しいというか無理。
- メタステーブルの対策としては、例えば2段のFFで受けて、クロックで同期してやる。
- FF間の配線は短くなるように制約をつける方が良いらしい5
- それで同期しても、多ビット入力の場合は、当然タイミングのズレはあるので、
- ズレ量よりも後に、データを取り込むようにしないと問題になるので要注意
- 自分は、同期リセットはリセットが掛からないことが心配なので、主に非同期リセットを使うんだけど、
- こちらで紹介されているように、解除は同期式でやった方が良いらしい
4不安定状態が長時間続いたり、発振状態になるかもしれない。
5不安定状態の影響を最小に、短時間で収めたいということだと思う。
[top]
- alwaysの構文が複雑で、FFかLatchか分からないと言っているらしい。
- 自分の場合は、if - else if - else if という構文を書いているつもりで、
- うっかり、if - if - else if と記述してしまって、暫く気がつかなかった。。。
- エラーメッセージが具体的じゃなくて分かりずらい。ピン番号とか出してくれれば良いんだけど。。
[top]
[電子工作関連に戻る]