スレッド表示 | 新しいものから | 前のトピック | 次のトピック | 下へ |
投稿者 | スレッド |
---|---|
webadm | 投稿日時: 2008-2-4 3:44 |
Webmaster 登録日: 2004-11-7 居住地: 投稿: 3107 |
Re: Poorman's Signature Analyzerの仕様検討開始 ○Scan & Test Oscillator
Scan & Test OscillatorはCLK信号を入力としてDisplay Controllerで必要な表示用カウンタ信号DCOUNTとData LatchでLED DATA表示用クロック信号DCLOCK及びセルフテスト用TEST CLOCK, TEST START, TEST STOP, TEST DATAを出力する。 CLK信号を入力とするバイナリカウンターの最下位4ビットからDCOUNT信号が生成される。更に上位の1ビットからDCLOCK信号が、更に上位の1ビットからTEST CLOCK信号が生成され、残りの上位ビットはTEST START, TEST STOP信号パターン生成用ROMのアドレスとして使用される。 |
webadm | 投稿日時: 2008-2-6 13:10 |
Webmaster 登録日: 2004-11-7 居住地: 投稿: 3107 |
設計開始 だいたい仕様が固まったので回路設計に入る。
オリジナルのHP5004Aシグネチャアナライザは古い時代のものなので、基本的に汎用ICのみで回路が構成されている。そのまま回路図入力すればいいじゃないかという考えもあるが、汎用ICというのは結構くせ者で非同期回路の固まりみたいなもの。等価なRTLライブラリがあればいいけれども無いと自分で作らないといけない。なまじか詳細な動作を理解していない汎用ICと等価なロジックを書くのはやっかいだ。だとすると最初からフルにHDLで書いたほうが良い。 汎用ICでまったく同じ回路をこしらえればという案は却下。ロジック部分は特別なICは使われておらずたぶん今でも売っているものが大半だが、十分な解像度でスキャンされた図面が無いので、一部配線がかすれて見えないところもあるし、それもいちいち確認しながら回路図を起こすというのもめんどうくさくリスクも多い。いざ配線して動かなかったらどうするのかと。 その点はFPGAはデザインの悪いところを見つけて直せば配線はそのままで済む。 今回の仕様は、機能仕様だけにとどまらず実現可能性もある程度めどを付けた形でタイミングチャートまで書いてチェックしている。普通は機能仕様だけで、それ以降の作業は設計と称しているが、そうしたケースでは設計の最初の作業は詳細なブロックにブレークダウンし、ブロック間の信号インタフェースを決めて、タイミングチャートを描いてとか詳細仕様を決めることから始まる。いきなりHDLをしこしこ書いたりはしない。機能仕様を見ると即座に詳細仕様が頭に展開されるような天才なら別かもしれないが。はたしてそれが正しいのかどうか他の誰も知りようが無い。普通は第三者が詳細仕様をチェックして大きな間違いや問題点を取り除いてリスクを低減するレビューを行う。 そういった段階的にリスクを取り除く作業を積み重ねることでミスがあっても些細なレベルであれば大事には至らないで済むことになる。また詳細な仕様が誰でも読めるようにアウトプットされているので、テストベンチとかを第三者がそれをベースに作るという分担作業が出来る。HDLを書いた本人がテストベンチを作るとなると、速いこと完成させたいというアクセルとバグを残してはいけないというブレーキの両方を踏むことになり、精神的によろしくない。この役割は別々の人に負ってもらうのが互いの能力を最大限に発揮する意味で良い。しかし問題は二人必要になるという点で、設計コストは二倍になる。しばしばコスト削減で一人が両方を兼ねるとバグが取りきれずに後で関係各位に大損害を与える結果に成りかねないリスクを有することを承知しなければならない。 今回の仕様ではオリジナルのHP5004Aが非同期回路と同期回路の混成になっており、FPGAで再現するにはあまりよろしくない。本当は完全な同期回路設計にすればいいのだろうけど、そうすると非常に高速の基準クロックが必要になる。それですべての入力信号を一度サンプルして内部ではすべて基準クロックに同期して変化する信号のみを扱うのがベストである。 まあ趣味なのでダメだったら直せばよいということで、最初は自分の理解できる範囲で回路を組んでみることにする。実はちゃんと同期回路設計する知識と経験が無いだけなのだが。 今回の回路規模は細かなブロックひとつひとつは小規模で、通勤電車の椅子に座っている間や、外で食事をしている間とかに考えてメモ帳に書き写していける程度簡単である。それでも信号を沢山扱うブロックはタイミングチャートを書くのが面倒くさい。それでも書いて見て問題点が見つかったり、疑問が出てきたりして理解を深めることが出来た。良く理解していないで作るのがもっともおろかである。 さていよいよRTLを書くことになるが、しばらくやっていないので大分おっくうになってしまっている。この種のスキルは使っていないと確実に衰える。 C言語で書いたものをHDLに変換してくれるというのも最近ではあるらしいが、確かにC言語は(かなり記憶が怪しくなってきてはいるが)まだおっくうではないので、それもありかもしれない。でもだいぶ寄り道をしそうな気がしないでもない。今回は止めておこう。 オーソドックスにがりがり書くしかないか。 |
webadm | 投稿日時: 2008-2-7 10:21 |
Webmaster 登録日: 2004-11-7 居住地: 投稿: 3107 |
RTLのフレームワークを入力 とりあえずQuartusを立ち上げて、新しいVerilogデザインプロジェクトを作成し、トップと各内部ブロックに対応するモジュールソースをフレームワークだけ入力。
satop.v: トップモジュール module satop(sa_clock, sa_start, sa_stop, sa_data, clock_pol, start_pol, stop_pol, clk, reset, hold, test_clock, test_start, test_stop, test_data, led_seg, led_com, led_gate, led_unstable, led_data); // Input Port(s) input sa_clock, sa_start, sa_stop, sa_data; input clock_pol, start_pol, stop_pol; input clk, reset, hold; // Output Port(s) output test_clock, test_start, test_stop, test_data; output [6:0] led_seg; output [3:0] led_com; output led_gate, led_unstable, led_data; wire clock, start, stop, data, clear, shift, dclock; wire [15:0] prn; wire [3:0] dcount; // Parameter Declaration(s) // Additional Module Item(s) edgesel edgesel(sa_clock, sa_start, sa_stop, clock_pol, start_pol, stop_pol, reset, clock, start, stop); sm sm(clock, start, stop, hold, reset, clear, shift, led_gate); prg prg(clear, shift, data, reset, prn); dc dc(prn, dcount, led_seg, led_com, led_unstable); dl dl(sa_data, clock, reset, dclock, data, led_data); stosc stosc(clk, dcount, dclock, test_clock, test_start, test_stop, test_data); endmodule edgesel.v:エッジセレクタモジュール module edgesel(sa_clock, sa_start, sa_stop, clock_pol, start_pol, stop_pol, reset, clock, start, stop); // Input Port(s) input sa_clock, sa_start, sa_stop; input clock_pol, start_pol, stop_pol; input reset; // Output Port(s) output clock; output reg start, stop; // Parameter Declaration(s) // Additional Module Item(s) endmodule sm.v:ステートマシンモジュール module sm(clock, start, stop, hold, reset, clear, shift, led_gate); input clock, start, stop, hold, reset; output clear, shift, led_gate; endmodule prg.v:疑似乱数生成モジュール module prg(clear, shift, data, reset, prn); input clear, shift, data, reset; output [15:0] prn; endmodule dc.v:表示コントローラーモジュール module dc(prn, dcount, led_seg, led_com, led_unstable); input [15:0] prn; input [3:0] dcount; output [6:0] led_seg; output [3:0] led_com; output led_unstable; endmodule dl.v:データラッチモジュール module dl(sa_data, clock, reset, dclock, data, led_data); input sa_data, clock, reset, dclock; output data, led_data; endmodule stosc.v:スキャン&テストオシレーターモジュール module stosc(clk, dcount, dclock, test_clock, test_start, test_stop, test_data); input clk; output [3:0] dcount; output dclock, test_clock, test_start, test_stop, test_data; endmodule 中身はなく単にモジュールインタフェースだけのスケルトンである。がっかりするかもしれないが、これをすることで仕様上の抜けとかが見つかったりする。実際にうっかりトップモジュールの図でSA_DATAが抜け落ちていたのを発見した。 この抜け殻のようなデザインファイルをコンパイルすると信号をドライブしてないぞとか警告は出るが記述に誤りが無ければコンパイルは正常に終了してRTL Viewerで全体ブロックの鳥瞰図を見ることが出来る。 中身はからっぽだけどももう出来たも同然のような気分が味わえる。あとはモジュールひとつひとつ中身を実装していけばよい。 さてこれからが楽しみである。 |
webadm | 投稿日時: 2008-2-7 23:16 |
Webmaster 登録日: 2004-11-7 居住地: 投稿: 3107 |
RTLの中身の実装方法 RTLのコーディングは1年以上やっていないのでVerilogの構文もすっかり忘れてしまった。
それでも心配はない、Quartusに限らず大抵のFPGA用統合化開発環境にはランゲージテンプレートが用意されている。頻繁に使用される構文やロジック回路のプリミティブな記述のフレームワークが多数用意されていて、それを選択してソースファイルに挿入してあとは自分で肉付けすればいい。 Quartusの場合はソースファイル上で右クリックメニューから"Insert Template"を選択すればダイアログが表示され、用意されているテンプレートの一覧と、選択したテンプレートの内容が表示される。OKを押すと選択されたテンプレートがソースコードのカーソルの位置に挿入される。 Verilog,VHDL他Quartusで用いることのできる各種HDLにスクリプト言語のテンプレートが用意されている。 HDLの場合は良く使われる機能モジュールのテンプレート ・ROM/RAM ・シフトレジスタ ・ステートマシン ・演算器 デザインファイル内で頻繁に使われるHDL文法 ・単体スケルトン ・宣言文 ・生成文 ・代入文 ・順序回路 ・演算子 ベンダー非依存なロジックプリミティブ ・各種レジスタ ・ラッチ ・トライステート付き(レジスタ、ラッチ、入出力) Altera固有の合成属性付きプリミティブ ・バッファ ・レジスタ及びラッチ が選択できるようになっている。慣れてしまえば、テンプレートを使わずに手で入力できるようになるが、それまでは備忘録代わりになる。 新しくFPGAやCPLDで作る回路の仕様や実現可能性を検討する際に、これらのテンプレートで利用可能なプリミティブにどんなものがあるか知っていれば、目的の動作の回路がそれだけを使用して記述出来るかどうかが判断できる。どんなものがあるかは自分の目で見て憶えておく必要がある。ここに無いものは自分で考えるか既にあるプリミティブを組み合わせて実現できるか検討する必要がある。ロジック回路を一通り勉強すれば何が出来て何ができないかは判断できるはず。 今回の仕様検討時にもありきたりのロジック回路のプリミティブだけで構成したので非同期信号を内部で扱う点以外は問題無いはずである。 あとFPGAベンダー独自のプリミティブ記述を一端デザインソース中で使ってしまうと、他のベンダーのツールではコンパイルエラーとなったり、該当するライブラリが見つからないとPlace & Route時に怒られることになる。その場合はそのターゲットのFPGAベンダーが等価なプリミティブを提供していればそれに合わせてソースを書き換える必要がある。ベンダー独自プリミティブを使用するときはそれ以外に実現手段が無いときに限る。 |
webadm | 投稿日時: 2008-2-7 23:44 |
Webmaster 登録日: 2004-11-7 居住地: 投稿: 3107 |
エッジセレクタの実装 最初に簡単なエッジセレクタを実装してみる。
テンプレートから拝借した排他的論理和演算子と継続的代入と非同期リセット付きレジスタのみで記述できた。 edgesel.v:エッジセレクタモジュール(実装済み) module edgesel(sa_clock, sa_start, sa_stop, clock_pol, start_pol, stop_pol, reset, clock, start, stop); // Input Port(s) input sa_clock, sa_start, sa_stop; input clock_pol, start_pol, stop_pol; input reset; // Output Port(s) output clock; output reg start, stop; // Parameter Declaration(s) // Additional Module Item(s) assign clock = sa_clock ^ clock_pol; always @ (negedge reset or posedge clock) begin // Reset whenever the reset signal goes low, regardless of the clock if (!reset) begin start <= 1'b0; stop <= 1'b0; end // If not resetting, update the register output on the clock's rising edge else begin start <= sa_start ^ start_pol; stop <= sa_stop ^ stop_pol; end end endmodule コンパイルするとエラーは出ない。出力信号がまだ他のどこにもつながっていないので最適化によってPlace & Root時に回路は無くなってしまうがネットリストの段階では存在するのでRTL Viewerでどんな構成になったか見ることはできる。 見ると仕様で考えていたブロック図と同じだ。実装の便宜上RESET信号は負論理としている。 |
webadm | 投稿日時: 2008-2-8 8:31 |
Webmaster 登録日: 2004-11-7 居住地: 投稿: 3107 |
ステートマシンの実装 次ぎはちょっと面倒なステートマシンの実装。
ステートマシンは組み合わせ回路と状態値レジスタから構成されるので、HDLではそれぞれ同時並列動作する回路を記述することになる。訓練しないと人間は同時に進行する2つ以上の物事を扱いきれない。大概考え違いや見落としが生じるのは複雑なステートマシンで生じる。ステートマシンでハードウェアで実現できるとわかっていてもハードウェア設計者は大げさなCPUを載せてソフト屋にまかせたほうが楽だというのを昔から知っている。速度は遅くなるがCPUは同時に実行できる命令は1つだけなのでソフトの方が凡人でも扱える。 シグネチャアナライザも扱う信号の変化がオーディオ帯ぐらいにゆっくりならばPICマイコンとかでソフトでやったほうが簡単なのだが、あいにく1MHz〜25MHzの信号を扱うのでそういうわけにはいかない。 ここでもQuartusのテンプレートにあるState Machinesの中から4-State Mealy state machineを拝借している。Mealy state machineとは出力信号が状態値と入力信号から生成されるタイプのものである。状態に変化が無くても入力信号の変化によって出力信号を変化させなければならない回路向き。これとは別にMoore State machineというのがあり、こちらは出力が状態値によってのみ決定されるタイプ。 sm.v:ステートマシン(実装済み) module sm(clock, start, stop, hold, reset, clear, shift, led_gate); input clock, start, stop, hold, reset; output reg clear, shift, led_gate; // Declare state register reg [1:0]state; // Declare states parameter S00 = 0, S01 = 1, S10 = 2, S11 = 3; // Determine the next state synchronously, based on the // current state and the input always @ (posedge clock or negedge reset) begin if (!reset) state <= S00; else case (state) S00: if (!start) begin state <= S01; end else begin state <= S00; end S01: if (start) begin if (!stop) begin state <= S10; end else begin state <= S11; end end else begin state <= S01; end S10: if (stop && !hold) begin state <= S00; end else begin state <= S10; end S11: if (!stop) begin state <= S10; end else begin state <= S11; end endcase end // Determine the output based only on the current state // and the input (do not wait for a clock edge). always @ (state or stop or clock or reset) begin case (state) S00: begin clear = reset; shift = 0; led_gate = 0; end S01: begin clear = reset; shift = 0; led_gate = 0; end S10: begin clear = (!stop & clock) & reset; shift = clock; led_gate = 1; end S11: begin clear = reset; shift = clock; led_gate = 1; end endcase end endmodule これも便宜上CLEAR信号は負論理とした。 初めて出力ピンにまでネットリストがつながった(LED_GATE)のでその部分についてのみPlace & RouteされてLEが割り当てられた。 RTL Viewerでステートマシンのネットリストを見ると。 ステートマシンの部分が自動的に下層ブロックになっている。これを表示させると。 テンプレートを使って記述するとちゃんとステートマシンとして認識してこのように状態遷移図を描いてくれる。賢い。 ところでステートマシンがこのようにブラックボックスになっているのが気になる人はTechnology Viewerで実際にどんなふうにPlace & Rootされているか見ることができる。 外部にLED_GATEしかつながっていないので、まだその先がつながっていないCLEARやSHIFTに関する回路は最適化によって消えてしまっている。 見ると複数のLEで構成されているが、各LEの内部構成を表示すると驚愕の事実が発覚。なんとレジスタが4つ使用されている。それというのも大抵の論理合成ツールはステートマシンを高速かつロジックハザード発生が最小限となるワンホットステートマシンというのをデフォルトで生成する。これはレジスタ1ビットが取り得る一つの状態毎に割り当てられている。なのでそれぞれのレジスタの出力がそのまま状態値のデコード出力でもあり、周辺の組み合わせ回路でわざわざ状態値をデコードする必要が無く遅延も少なくなるというメリットがある。しかし2ビットでもできるのが倍の4ビットになるので状態数が多いステートマシンでは合成指示を与えてワンホットを使わないようにするということもある。 |
webadm | 投稿日時: 2008-2-8 10:06 |
Webmaster 登録日: 2004-11-7 居住地: 投稿: 3107 |
疑似乱数生成器の実装 次ぎにややこしい疑似乱数生成器。
これもシフトレジスタの記述はテンプレートから拝借。フィードバック回路とパリティジェネレータ回路にリセットロジックを肉付けし、出力段に出力レジスタを設けてお終い。 prg.v:疑似乱数生成モジュール(実装済み) module prg(clear, shift, data, reset, prn); input clear, shift, data, reset; output reg [15:0] prn; // Declare the shift register reg [15:0] sr; // Shift everything over, load the incoming bit always @ (posedge shift or negedge clear or negedge reset) begin if (!clear | !reset) begin sr <= 0; end else begin sr[15:1] <= sr[14:0]; sr[0] <= data ^ sr[6] ^ sr[11] ^ sr[12] ^ sr[15]; end end always @ (negedge clear or negedge reset) begin if (!reset) begin prn <= 0; end else begin prn <= sr; end end endmodule 最初に入力した時に出力レジスタをリセットする回路を忘れてしまっていた。RTL Viewerで確認したらシフトレジスタをRESETがアサートされた時にリセットしないと次ぎの計測結果が正しくなくなる仕様上の不備を発見。さっそく仕様にフィードバックしてRESETでどちらのレジスタもリセットされるようにした。自分で決めた仕様の誤りはなかなか自分では気づかない。いわば盲点である。RTL Viewerはその点機械だから正直にそのまま示してくれる。人間は機械のように常に厳しく目を光らせるということはできない。 これも仕様レベルとほぼ一致する。 |
webadm | 投稿日時: 2008-2-8 11:30 |
Webmaster 登録日: 2004-11-7 居住地: 投稿: 3107 |
表示コントローラーの実装 次ぎは少し面倒くさい表示コントローラー。
最後に生成された疑似乱数を4桁の7セグメントLEDにダイナミック表示するのとあわせて疑似乱数が変動している場合にUNSTABLE LEDを点灯させる。 これは以前にやった1万年カレンダー時計から拝借。基本的にデコーダー回路をVerilogのfunctionで記述している。直接手続き構文中でデコードすると桁毎にデコード回路が生成されてその出力を最後にセレクトするというとんでも無い回路になったのでこのようにした記憶がある。 あとは前回の疑似乱数を保持するレジスタと、UNSTABLE出力を保持するレジスタを追加。 dc.v:表示コントローラー(実装済み) module dc(prn, dcount, led_seg, led_com, led_unstable); input [15:0] prn; input [3:0] dcount; output [6:0] led_seg; output [3:0] led_com; output reg led_unstable; wire strobe_prn; wire strobe_unstable; reg [15:0] prev_prn; assign strobe_prn = (dcount[1:0] == 2'b00); assign strobe_unstable = (dcount[1:0] == 2'b10); assign led_seg = decoder(prnsel(prn, dcount[3:2])); assign led_com = two2four(dcount[3:2]); always @ (posedge strobe_prn) begin prev_prn <= prn; end always @ (posedge strobe_unstable) begin led_unstable <= (prev_prn != prn); end function [6:0] decoder(input [3:0] seg); case(seg) 0: decoder = 7'b1000000; // 0 1: decoder = 7'b1111001; // 1 2: decoder = 7'b0100100; // 2 3: decoder = 7'b0110000; // 3 4: decoder = 7'b0011001; // 4 5: decoder = 7'b0010010; // 5 6: decoder = 7'b0000010; // 6 7: decoder = 7'b1111000; // 7 8: decoder = 7'b0000000; // 8 9: decoder = 7'b0010000; // 9 10: decoder = 7'b0001000; // A 11: decoder = 7'b1000110; // C 12: decoder = 7'b0001110; // F 13: decoder = 7'b0001001; // H 14: decoder = 7'b0001100; // P 15: decoder = 7'b1000001; // U endcase endfunction function [3:0] prnsel(input [15:0] prn, input [1:0] sel); case(sel) 0: prnsel = prn[15:12]; 1: prnsel = prn[11:8]; 2: prnsel = prn[7:4]; 3: prnsel = prn[3:0]; endcase endfunction function [3:0] two2four(input [1:0] sel); case(sel) 0: two2four = 4'b0001; 1: two2four = 4'b0010; 2: two2four = 4'b0100; 3: two2four = 4'b1000; endcase endfunction endmodule 以前の1万年カレンダー時計の記述を見た時点で、制御すべきLEDのセグメント数が7つあるのに、信号が6本しか用意してなかったことに気づく。大失敗。何故6本になったかというと、確か7セグメントLEDには小数点のセグメントがあって、これは要らないからということでひとつ減らして6にしたというバカな間違いを犯していたためである。仕様を直さないと。 合成されたネットリストをRTL Viewerで見ると。 レジスタ周辺は予想通り。表示デコーダーが予想していたのとちょっと見た目が違うけど機能的には等価。 |
webadm | 投稿日時: 2008-2-8 11:54 |
Webmaster 登録日: 2004-11-7 居住地: 投稿: 3107 |
データラッチの実装 次ぎは簡単なデータラッチモジュール。
唯一ややこしいのがデータが変化していることを表示するためのLED_DATA信号の生成パス。 SRFFを仕様では想定していたが、ベンダー固有のプリミティブはなるべく使いたくないのでベンダー非依存な記述を行った。RESETが最も優先順位が高く、次いでデータの変化、最後に更新クロックとしてみた。 dl.v:データラッチモジュール(実装済み) module dl(sa_data, clock, reset, dclock, data, led_data); input sa_data, clock, reset, dclock; output reg data, led_data; wire data_changed = (sa_data != data); always @ (negedge reset or posedge clock) begin // Reset whenever the reset signal goes low, regardless of the clock if (!reset) begin data <= 1'b0; end // If not resetting, update the register output on the clock's rising edge else begin data <= sa_data; end end always @(posedge data_changed or posedge dclock or negedge reset) begin // The reset signal overrides the hold signal; reset the value to 0 if (!reset) begin led_data <= 1'b0; end else if (data_changed) begin led_data <= 1'b1; end // Otherwise, change the variable only when updates are enabled else if (dclock) begin led_data <= 1'b0; end end endmodule RTL Viewerで見るとSRFFは生成されずにプリセット付きのDFFとなっていた。なるほど賢い。これでまったく問題ない。 |
webadm | 投稿日時: 2008-2-8 12:34 |
Webmaster 登録日: 2004-11-7 居住地: 投稿: 3107 |
スキャン&テストオシレータの実装 最後がローカルクロック信号生成回路。
外部から基準クロックを供給してもらってそれをバイナリカウンタで分周し、必要な周期のクロックや制御信号をタップ出力する。 一部はテスト信号生成用ROMのアドレスやテストクロックやテストデータとして使用。今回はとりあずSTART/STOPのパルスが出るだけとした。 stosc.v:スキャン&テストオシレータモジュール(実装済み) module stosc(clk, dcount, dclock, test_clock, test_start, test_stop, test_data); input clk; output [3:0] dcount; output dclock, test_clock, test_start, test_stop, test_data; reg [9:0] count; assign dcount = count[3:0]; assign dclock = count[4]; assign test_clock = count[1]; assign test_data = count[2]; assign {test_stop,test_start}=testrom(count[9:5]); always @ (posedge clk) begin count <= count + 1; end function [1:0] testrom(input [4:0] addr); case(addr) 5'b00000: testrom = 2'b11; default: testrom = 2'b00; endcase endfunction endmodule RTL Viewerでみるとやはり予想通り。 これで全部のモジュールを実装したのですべての入力信号と出力信号がつながったことになる。当初から予想していた通り、以前にやった1万年カレンダーに比べればはるかに小規模な回路なので使用するLE数も91/240と少ない。これなら古いFLEX8000とかにも収まりそうである。MAXIIとかではもったいないかもしれない。 |
(1) 2 3 4 » |
スレッド表示 | 新しいものから | 前のトピック | 次のトピック | トップ |
投稿するにはまず登録を | |