DWM-DDSを使ってみる
by K.I
2008/08/09〜28
Index
- Verilogのお勉強。DesignWaveMagagine/Jan2005のSpartan3基板(DWM基板)でDDSを使ってみる。
- でも、2005年1月号を無くしちゃったので、2007年7月号のチュートリアルを参考にする。
- FPGA基板の結線もわかんないので、 これを参照しながらやってみる。
- この回路図だと基板のヘッダのピン番号が分からないので、会社でここだけコピーして来たけど、 これも分かりづらい。
- 電源以外はヘッダピンには、FPGAの端子番号を書いた方が便利じゃないのかな。
- 開発環境は、XilinxのISE10.1、Xilinx版のModelSim III 6.3c
- 昔のWebPackに比べて、結構変わってしまった印象がある。それに随分遅くなったような。。
- 書込みは、特殊電子回路(旧ナヒテック)の SmartJTAG、 MitouJTAG0.3.1を使用。
- 最初にDWM基板に最低限必要な配線を済ましておく。たくさん配線するのはめんどくさいので、
- 前に DWM基板を試した時の配線に、24MHzオシレータと3.5φステレオジャックを追加するだけにした。
- DWM基板上には、以前にコンフィグROMのxcf01sを追加してある。
- FPGA端子番号は、後でインプリメントする時に必要になる
基板端子番号 | FPGA端子 | 結線 |
CN3-A06 | | +3.3V |
CN3-A11 | | GND |
CN2-A01 | P38 | 24MHz オシレータへ |
| P47 | DWM基板上のLED_Green |
| P49 | DWM基板上のLED_Red |
| P50 | DWM基板上のタクトスイッチ |
CN3-A03 | P55 | R(ステレオジャック中央) |
CN3-A04 | P60 | L(ステレオジャック先端) |
CN3-A08 | P67 | sine[0] |
CN3-A09 | P71 | sine[1] |
CN3-A10 | P74 | sine[2] |
CN3-A12 | P79 | sine[3] |
CN3-A13 | P81 | sine[4] |
CN3-A14 | P86 | sine[5] |
CN3-A15 | P92 | sine[6] |
CN3-A16 | P96 | sine[7] |
- 24MHzオシレータにしたのは、単に手持ちがそれしか無かったから。
- ステレオジャックには、PWMで出す予定。共通部分は、GNDに接続。
- sine出力はデバッグ用に出しただけで、実際は配線してない。
1これは現在売られていないみたいなので。まぁ、書き込みは何使っても良い訳だし。。
[top]
- 基本的には、DWM/July2007の簡易信号発生器の製作のチュートリアルに従って行う。
- クロックモジュールとDDSは、XilinxのISEのCoreジェネレータで生成する。
- DAC(D/Aコンバータ)はアプリケーションノートをそのまま使わせてもらう。
- 24MHzをそのまま使っても良いと思うんだけど、チュートリアルでは5MHzを作ってるので、同じようにしてみる。
- 普通は適当に分周器をHDLで書くけど、チュートリアルではArchitectureWizardを使ってDCMを造ってるので、真似してみる。
- DLLで位相と周波数を設定出来るらしい。これで分周器作ってくれるなら、楽で良いなぁ。
- ProcessウィンドウのCreateNewSourceをダブルクリックして、IP(Coregen&ArchitectureWizard)を選択。
- NewSourceWizard - SelectIPウィンドウで、FPGA Features and Design→Clocking→Virtex-II Pro, Vertex-II, Spartan-3→Single DCM v9.1iを選択。
- Xilinx Clocking Wizard - General Setupウィンドウが開くので、
- RST,CLKFXをチェック、LOCKEDのチェックを外して、Nextを押す。
- 次は、そのままNext
- ClockBufferSettingsで、Use Global Buffers for all selected clock outputsを選択した状態
- 次にCLKFX出力するクロック設定
- Use outupt frequencyに、5MHzと入力する。
- CalculatedeでMultiply,Divide,Jittorが表示されるはずなんだけど、以下のメッセージが出る。
The output frequency is not within the valid range.
Please enter a new value for the output frequency.
- 良く見ると、DFS Mode Lowで、Foutは18〜210MHzになってる。
- ってことは、5MHzは作れない2のかぁ。。XC3S250Eなら作れるんだろうな。
- 仕方が無いので20MHzを作って、4分周して使うことにする。
- Multiply=20,Divide=24、Jittorがunit intervalで0.05、p-p nsで2.37?まぁ半端だからジッタはあるんだな。
- DDS用のクロックでジッタがあるのはマズイような気がするが、チュートリアルでもジッタあるみたいだからまぁ良いか。
- これで、Finish。dcm_block.xawってファイルが出来たらしい。
- 次に、Sourceウィンドウで dcm_block.xawを選択。
- ProcessウィンドウのView HDL Instantiation Templateをダブルクリックする。
dcm_block instance_name (
.CLKIN_IN(CLKIN_IN),
.RST_IN(RST_IN),
.CLKFX_OUT(CLKFX_OUT),
.CLKIN_IBUFG_OUT(CLKIN_IBUFG_OUT),
.CLK0_OUT(CLK0_OUT)
);
- このモジュール宣言を、貼り付けてやれば良い。
- CLK0 →入力と同相のクロック
- CLK90 →90°位相のクロック
- CLKDV →1/2クロック
- CLK2X →x2クロック
- CLKFX →DLLの出力クロック(設定した周波数)
- という意味のようだ。CLK0以外は、指定しないとモジュールで出力されない。
- CLKIN_IBUFGは、入力クロックを単にバッファしたものかな?
- どんな周波数でも作れる訳じゃないけど、あまり考えずに簡単に分周器が作れるのはやはり便利だ。
4分周回路
- ddsで直接5MHzを作れなかったので4分周回路を書く。
module dds_clock(
input clk0,
input rst,
output clk
);
reg[1:0] clkreg; // 分周用レジスタ
wire clkfx; // DCM→4分周回路への配線
ip_dcm instance_name ( // Coreジェネレータで作ったDCMモジュール
.CLKIN_IN(clk0), // 入力の24MHz
.RST_IN(rst), // リセット
.CLKFX_OUT(clkfx), // 出力の20MHz
.CLKIN_IBUFG_OUT(), // たぶん入力をバッファしたもの
.CLK0_OUT() // たぶん入力と同相のクロック
);
// 4分周回路 // clkfx(20MHz)/4 = clk(5MHz)
always @(posedge clkfx or posedge rst) begin
if (rst == 1)
clkreg <= 2'b00; // 非同期リセット
else
clkreg <= {clkreg[0],~clkreg[1]}; // ジョンソンカウンタ
end
assign clk = clkreg[1]; // 分周用レジスタの上位ビットを出力へ
endmodule
- 4分周回路は、この場合どんな風に記述しても良いんだけど、とりあえずジョンソンカウンタで記述した。
- つまり、上位ビットを反転して下位に戻してシフトする。
- ProcessウィンドウのCreateNewSourceをダブルクリックして、IP(Coregen&ArchitectureWizard)を選択。
- NewSourceWizard - SelectIPウィンドウで、Digital Signal Processing→Waveform Synthesis→DDS Compiler V1.1を選択。
- DDS Compiler v1.1ウィンドウが開くので、以下のように設定して、Next。
Output Selection Sine
Channels 1
DDS Clock Rate 5
Spurious Free Dynamic Range 45
Frequency Resolution 0.1
- Output Frequencies を以下のように設定して、Next。
Channel1 0.2
Phase Increment Programable
- Phase Offset Angles はそのままNext。
Channel1 0
Phase Offset None
- 次は以下のように設定してNext。(これ以外はそのまま)
Noise Shaping None
Memory type AUTO
Clear Options SCLR Pin
- 次は確認なので、Next。続いてFinishする。
- 生成に数分掛かるが、dds_core.xcoが生成される。
- 次に、Sourceウィンドウで dds_core.xcoを選択。
- Editメニュー→LanguageTemplates...→COREGEN→VELILOG Component Instantiationで、dds_coreをクリックすると、右側にモジュール宣言が表示される。
dds_core YourInstanceName (
.a(a), // Bus [4 : 0]
.clk(clk),
.sclr(sclr),
.we(we),
.data(data), // Bus [25 : 0]
.sine(sine)); // Bus [7 : 0]
- DWM/July2007の記事では、8bitのR-2Rラダ-DAを作ってるけど、配線が面倒なのでPWMで出すことにした。
- Xilinxのアプリケーションノート XAPP154の、ΔΣD/Aをそのまま使う。
- よく分からないけど、多分1次のΔΣ変換だと思う。
- bit長が選べるようになってるけど、今回は7ビットなのでそのまま。
`define MSBI 7 // Most significant Bit of DAC input
//This is a Delta-Sigma Digital to Analog Converter
module xapp154(DACout, DACin, Clk, Reset);
output DACout; // This is the average output that feeds low pass filter
reg DACout; // for optimum performance, ensure that this ff is in IOB
input [`MSBI:0] DACin; // DAC input (excess 2**MSBI)
input Clk;
input Reset;
reg [`MSBI+2:0] DeltaAdder; // Output of Delta adder
reg [`MSBI+2:0] SigmaAdder; // Output of Sigma adder
reg [`MSBI+2:0] SigmaLatch; // Latches output of Sigma adder
reg [`MSBI+2:0] DeltaB; // B input of Delta adder
always @(SigmaLatch) DeltaB = {SigmaLatch[`MSBI+2], SigmaLatch[`MSBI+2]} << (`MSBI+1);
always @(DACin or DeltaB) DeltaAdder = DACin + DeltaB;
always @(DeltaAdder or SigmaLatch) SigmaAdder = DeltaAdder + SigmaLatch;
always @(posedge Clk or posedge Reset)
begin
if(Reset)
begin
SigmaLatch <= 1'b1 << (`MSBI+1);
DACout <= 1'b0;
end
else
begin
SigmaLatch <= SigmaAdder;
DACout <= SigmaLatch[`MSBI+2];
end
end
endmodule
- ΔΣについては良く分からなんだけど、そのうち理解したいな〜。
2逆に210MHzが作れるっていうのは凄いな。
[top]
- DDSの仕組みは、基本的にはサイン波形をデータとして持つ波形ROMの内容を順番に出力するだけ。
- つまり速く出力すればするほど、高い周波数になる。
- これでも良いんだけど、この方式だと高い周波数を出すのは限界がある。
- そのため、出力する速さは固定だが、データを間引きして出力することで周波数を変える。
- 間引きすればするほど、高い周波数になる。つまり位相角の変化量を変えるということ。
- 今回作ったDDSの波形ROMの大きさは、2^26=67108864と非常に大きい。
- これを5MHzで全部出力すれば、5000000/67108864≒0.00745Hzってことになる。
- たぶん、CoreジェネレータでDDSを作った時、Frequency Resolutionを0.1Hzにしたので、こうなったんだろう。
- DDSのdata入力は、波形ROMの位相変化量Δθを表しているんだけど、
- 結局のところ、波形ROMを間引く数を指定していることが分かる。
- ということで、波形データを2個毎に出力すれば周波数は2倍、4個毎に出力すれば周波数は4倍と、dataの設定値と周波数は単純に比例するので、
- 今回作ったDDSの周波数fは、クロック周波数5MHz、dataが26bitで、以下の式で表わされる
- 音として出力したいので、とりあえず聞こえる周波数になるように分周と変化させるビットを調整した。
- data=7FFの時、153Hz
- data=7FFFの時、2441Hz
- DDSの周波数を設定する回路を付け加える。
- 5MHzを2^24分周して約3.3秒毎に、data[14:11]の4bit(16通り)に変化させる。
- つまりdataは、07FF〜7FFFに変化する。
- 図を描く程じゃないが、だいたい以下のような構成になっている。
`timescale 1ns / 1ps
module dds_main( // 外(FPGAの端子)に繋がる入出力定義
input rstn, // リセットスイッチ
input clk0, // 24MHzオシレータからのクロック
output ledr, // 赤LED
output ledg, // 緑LED
output out1, // 出力1(ステレオジャック中央)
output out2, // 出力2(ステレオジャック先端)
output [7:0] sine // サイン波形出力(8ビット)
);
reg [27:0] we_coun; // 分周用カウンタ
wire [25:0] data; // 周波数設定用の配線
wire rst; // リセット用の配線
dds_clock clock0 ( // 5MHzのクロックを生成する
.clk0(clk0), // Coreジェネレータで作ったDCMモジュールを使っている
.rst(rst),
.clk(clk)
);
ip_dds dds0 ( // Coreジェネレータで作ったDDSモジュール
.clk(clk),
.sclr(rst),
.we(we),
.data(data), // Bus [25 : 0] →周波数設定
.sine(sine) // Bus [7 : 0] →サイン波形出力
);
xapp154 dac0 ( // アプリケーションノート154のDACモジュール
.DACout(out2), // out2にPWM出力する
.DACin(sine), // 波形データ(PCM)
.Clk(clk),
.Reset(rst)
);
// 分周回路(単純なカウンタ)
always @(posedge clk or posedge rst) begin
if (rst == 1)
we_coun <= 0; // 非同期リセット
else
we_coun <= we_coun + 1'b1;
end
// 周波数設定(data = 00007FF - 0007FFF) (152Hz - 2441Hz)
assign data[10:0] = 11'b11111111111; // 下位11ビットは1固定
assign data[14:11] = we_coun[27:24]; // この4ビットだけ変化させる
assign data[25:15] = 11'b00000000000; // 上位11ビットは0固定
assign we = we_coun[23]; // 1の時、周波数が設定される
assign rst = ~rstn; // タクトスイッチは押した時0なので反転
assign ledr = rst; // LEDは使わないので、とりあえずrstを出力
assign ledg = ~rst; // LEDは使わないので、とりあえずrstの反転を出力
assign out1 = ~out2; // out1はout2の反転を出力する(BTL出力)
endmodule
- ここで、シンセサイズ(Synthesize)を掛けて、文法的に問題ないかどうかチェックする。
- エラーやワーニングは、下の方のタブからファイル名をクリックすれば、該当箇所をすぐに確認3できる。
3ところで、この黄色の三角はどうやって消せば良いんだろう?
[top]
- ちゃんと動くかどうか、シミュレーションしてみよう。
- テストベンチは、Sourcesウィンドウで右クリックして、
- New Source→Verilog Test Fixtureでファイル名を指定、
- Associate Sourceでメインプログラムを指定すれば、テストベンチの素が出来る。
`timescale 1ns / 1ps
module dds_test;
// Inputs
reg rstn;
reg clk0;
// Outputs
wire ledr;
wire ledg;
wire out1;
wire out2;
wire [7:0] sine;
// Instantiate the Unit Under Test (UUT)
dds_main uut (
.rstn(rstn),
.clk0(clk0),
.ledr(ledr),
.ledg(ledg),
.out1(out1),
.out2(out2),
.sine(sine)
); // ここから上は、自動的に作られたもの
parameter RATE = 100; // レートを100nsとする
always #(RATE/2) clk0 = > clk0; // クロックは、alwaysで繰り返し
initial begin
clk0 = 0; // クロックとリセットの初期値
rstn = 0;
#(RATE)
rstn=1; // リセット解除
#(RATE*5000) // 5000クロック分実行して
$break; // 止める
end
initial
$monitor ( $stime, // 値を表示する(ModelSimなら無くても良い)
" clk0=%b rstn=%b ledr=%b ledg=%b out1=%b out2=%b",
clk0, rstn, ledr, ledg, out1, out2 );
endmodule
- 入力は、クロックとリセットだけなので、
- 最初にリセットを掛けて、あとはクロックをしばらく流すだけで良い。
- 実際のシミュレーション時間は、$breakした時間じゃ止まらなくて、ISEのProcessメニューから、
- →Properties→Simulation Properties→Simulation Run Timeで設定した時間になるみたいだ。
- $finishにすれば止まるけど、ModelSim自体が終わろうとして煩わしいので、$breakを使う。
- Xilinx版のModelSimを使って、シミュレーションしてみる。
- ModelSimは、以前はWebPackをインストールすると一緒に入っていたが、今は別にダウンロードする必要があるようだ。
- デフォルトでは、ModelSimを使うようになっていないので、Sourcesウィンドウのデバイスを選択して、
- 右クリック→propertiesで Simulatorを、Modelsim-XE Verilogに変更する。
- ModelSimシミュレーション時にはSourceウィンドウの上を、Sorce for: Behavioral Simulation に切り替える必要がある。
- テストベンチを選択して、ModelSim Simulator→Simulate Behavioral Model をダブルクリックするとModelSimが起動する。
- sineデータは、そのままだと分かりにくいので、フォーマットをアナログに設定する。
- とりあえず、シミュレーションでは、ちゃんと動いているようだ。
[top]
- インプリメント、つまり配置・配線時に、ユーザが制約を設けるためのファイル。
- メインプログラムを選択しておいて、Processesウィンドウから、
- Floorplan IO - Pre-Synthesisをダブルクリック。以下のように設定する。
- I/O Nameなどは、既に設定されているので、基本的にはLocにFPGAのピン番号を指定するだけ。
I/O Name | I/O Direction | Loc | Bank | I/O Std. |
clk0 | Input | P38 | BANK4 | |
ledg | Output | P47 | BANK4 | |
ledr | Output | P49 | BANK4 | |
rstn | Input | P50 | BANK4 | |
out1 | Output | P55 | BANK3 | |
out2 | Output | P60 | BANK3 | |
sine[0] | Output | P67 | BANK2 | |
sine[1] | Output | P71 | BANK2 | |
sine[2] | Output | P74 | BANK2 | |
sine[3] | Output | P79 | BANK1 | |
sine[4] | Output | P81 | BANK1 | |
sine[5] | Output | P86 | BANK1 | |
sine[6] | Output | P92 | BANK0 | |
sine[7] | Output | P96 | BANK0 | |
NET "clk0" LOC = "P38" ;
NET "ledg" LOC = "P47" ;
NET "ledr" LOC = "P49" ;
NET "out1" LOC = "P55" ;
NET "out2" LOC = "P60" ;
NET "rstn" LOC = "P50" ;
NET "sine[0]" LOC = "P67" ;
NET "sine[1]" LOC = "P71" ;
NET "sine[2]" LOC = "P74" ;
NET "sine[3]" LOC = "P79" ;
NET "sine[4]" LOC = "P81" ;
NET "sine[5]" LOC = "P86" ;
NET "sine[6]" LOC = "P92" ;
NET "sine[7]" LOC = "P96" ;
- これはSourcesウィンドウに表示されるので、これを直接直しても良い。
- Sourcesウィンドウでメインプログラムを選択して、ProcessesウィンドウのGenerate Programmingを右クリック
- Properties→Startup Options→FPGA Start-Up Clockを
- FPGAのRAMに書き込む時は、JTAG Clockにする
- コンフィグROMに書込む時は、CCLKにする
- 普通は、ここでiMPACTを起動して、
- FPGAのRAMに書込む時は、コンフィグROMをBypassして、FPGAにbitファイルを書き込む
- コンフィグROMに書込む時は、iMPACTでbitファイル→MCSファイルに変換してから
- 改めてiMPACTで、FPGAをBypassして、コンフィグROMにMCSファイルを書き込む →だったと思うけど。
- 自分は、SmartJTAGに付いてた、 MitouJTAG0.3.1を使ってるので、bitファイルを直接書けるので楽してるけど。
- ちゃんとしたMitouJTAG欲しいんだけど、趣味で買うのはちょっと高いんだよなぁ。うーん。。。
[top]
- というか、鳴らしてみました。実験は、こんな感じで。
- ちなみにスピーカーは、ワンダーキットのボール紙のスピーカーボックスキット4。
- 鳴らしてみると、低い音から順に高い音に変化しているのが分かる。
- 音量はずいぶん小さい。やっぱりバッファを付けないと駄目だなぁ。
- 波形を見てみると、こんな感じになった。
- サイン波出力の最上位ビット、sine[7]と一緒に表示させたもの。
- パルス幅変調というより、パルス密度変調のようにもみえる。
- これじゃどんな波形か、全然わかんないので、簡単なCRフィルタを通してみる。
- 適当に、0.022μFをスピーカーの端子間に、あとは1kΩを通して出力をスピーカーに接続しただけ。
- フィルタを通すと、なんとなく正弦波が含まれているのが見えてくる→なんか不思議だ〜。
- しっかりとしたフィルタを通せば、もう少しキレイな波形になりそうだ。
4スピーカーの周りだけ黒く塗りました。
[top]
- 何に引っ掛かったかすぐに忘れるので、一応書いておく。
- 入力のsineは正しいし、シュミレーションも問題ないのに、DACの出力が全く出ない。
- DACの使用方法の間違いを疑っていたが、結局単純にピン番号を間違えていた。
- ヘッダのピン番号とFPGAのピン番号の対応が分かりづらいなぁと思っていたのに、案の定間違えた。。。
- まさかピンは間違えてないだろうと思っていたので、気がつくのに時間が掛かった。
- 最初分周を変えたら動かなくなった。
- いろいろ弄っても直らないので、元に戻しても駄目だった。
- 何故か、コンフィグROMの書込みに変えたら直った。
- 調べるとSmartJTAGで、FPGAの直接書き込みが失敗することがあるようだった。
- SmartJTAGがかなり高温になるのが何か関係あるかも。
[top]
[電子工作関連に戻る]