エラーのないFPGAのUARTコードを自作する
FPGAのフォトフレームプロジェクトは、いよいよ第一期完成フェーズにあたる第7ステップに進んだ。SDカードにあるビットマップ画像データを液晶モニターに表示する段階である。フォトフレームを作ると決めてブログで宣言したのが今年の2月末。ほぼ6ヶ月かけてここまできたことになる。
このあいだには、XilinxのFPGAチップと液晶モニターをプローブの誤接触で壊したり、HDL言語(VerilogHDL)の習得にてこずったり、今回も多くのドラマ(?)が生まれた。電子工作を始めて2年半、自分でも感心するほど多種多様な工作をしてきたが、ひとつのプロジェクトにこんなに長く(半年)かけたことはない。FPGAはやはりてごわい。
これまで何度も書いているように、当研究所では電子工作には必ず何らかの実用的(こじつけでも良い)な目的を決めてから、工作を始めることにしている。定年後の楽しみに始めたアマチュアの電子工作である。何をやろうと、どこでやめようと誰もとがめる人はいないのだが、これだけ目的にこだわるのは理由がある。
自分は飽きっぽい性格で、めげやすい。何の規制もせずにやりたいことだけやっていたら、いつまでたっても技術の向上は望めない。電子工作は幸い、初心者からプロまで、どんなレベルであろうと、それなりに夢中になり楽しめる世界だが、悔しいことに技術レベルが上がれば上がるほど楽しめる範囲が広くなっていくことは否定できない。
何でも良いから具体的な目標(仕様)を設定しておくと、何とかそれを実現しようと、一生懸命に工夫、努力をする。飽きっぽい割には、一旦こだわると意地になる習性がある。しかし、これによって技術レベルは確実に上がる。遊びとは言いながら少しでも技術レベルを上げて今まで出来なかったことを可能にし、世界を広げたい。
当研究所の所長は、この40年間、ほぼコンピューターのシステム開発を仕事にしていた(後半は企画と管理ばっかりだったが)。この世界ではコンピューターのハードウエアというものはいわば神が創ったものであり、これに注文をつけることはとんでもないことで、仕様どおり有難く使わせていただくものだった。
PCやマイクロコンピューターでも似たようなものである。しかしFPGAはこういう今までの常識をくつがえすディバイスである。作ろうと思えば、自らが思いのままプロセッサーを自作できるのだ。何とかこのディバイスを自由自在に操れるようにしておきたい。
フォトフレームプロジェクトは現物を作るだけでなくFPGAをマスターするということもプロジェクトの大きな目的にしている。そんなこともあってまた少し道草を食って、本来のフォトフレームとは関係ないFPGAのUARTにはまりこんでいた。
雑誌のサンプルUARTソースがうまく動いていない(8/16)
SPIのバグ取りに散々苦労して、わかって見ればその原因はUARTの方だったという話は前回にした。このFPGAのUARTのVerilogHDLソースコードは、雑誌のデザインウエーブマガジン(その後季刊のデジタルデザインテクノロジーに改称)2008年10月号P86のサンプルソースをそっくり利用させてもらっている。
雑誌のサンプルソースにバグはないはずなのだが、SRAMからのUART出力には明らかにエラーが出ている。エラーの頻度はそう高くないので、これまで気が付かなかった。これとは別のfpga4fun.comのUARTに較べれば、はるかに安定していると思っていた。
しかし、詳しく調べると、1KBに数文字の字化け(エラー)があるようだ。特にリセット直後の数文字はたいていおかしい。フォトフレームプロジェクトではそろそろデジタル液晶の準備にかからなければいけない(ケース工作はほぼ完成)のだが、ここはそれ、しつこい性格である。気になると、他の事に手が付かなくなる。この際、徹底的にデバッグすることにした。
折りしも、SPIのトラブルのとき勧められた、「HDLによる高性能デジタル回路設計」という本を入手し、FPGAを本格的に勉強しはじめたこともある。ロジアナを使って、雑誌サンプルソースの不具合の原因解明を始めた。
まず、犯行現場の現場検証が重要だ。字化けの事実を検証する。テスト用にボーレートカウンターの動きをFPGA出力ピンに出すコードをソースに追加して、これをロジアナで拾い、少しづつデータを送ってはロジアナでタイミングチャートを分析する。
その結果、どこで字化けが起きるかは、ほぼ確定できた。UARTのデータの送出順序はLSBファーストである。字化けは必ずMSBで起きている。データの最後の方を集中的に調べていくと、添付画像にあるように、8ビット目の最後がボーレートの半分で終わり、そのままストップビットになるところがいくつも見つかった。
UART受信はボーレート期間中の多数決論理でHかLを決めているので、ボーレートが少しずれると半分の幅しかないMSBで0のデータが1になる可能性がある。これが前回の記事で1や2が半角のアやイになる理由である。スタートビットはこのあと正しく出ているので、エラーはここだけに止まる。
字化けが起きる原因はつきとめられた。しかし何故、このUARTの最後のビットがボーレート通りの時間にならないかがわからない。ソースコードにはおかしなところはない。きちんとボーレートの時間通りウェイトが入っている(はずである)。
「HDLによる高性能デジタル回路設計」で勉強する。(8/18)
すんさんの掲示板で勧められ、絶版だと聞いていたが、秋葉原の書泉で探してみたら最後の一冊らしいカバーが少し汚れたものが見つかり早速手に入れた。
噂には聞いていたが、高度な内容である。非同期回路だけでなく、同期回路も安心ならないことを学ぶ。それ以外にも考慮しなければならないことが沢山ある。自分がいかに能天気にRTL設計をしていたかを思い知らされる。
雑誌UARTのソースコード(動作環境 Altera QuartusⅡ)は、この本を読み進むうち、かなりな問題があることがわかってきた。まず、送信要求のビットはキーボード押下で発生する非同期パルスだが、その配慮がなされていないこと、状態遷移のレジスターが多段(5ビット)で、単なるバイナリーコードであるうえに、これをインクリメントするステップと、状態遷移をするロジックが同一クロック時に動いており、これが正しく評価されない可能性があることなどである。
本に拠れば、非同期はもちろんであるが、同期回路といえども、ハザード(出力が不定になるところ)は、常に起きる可能性があり、その対処が必要としている。それから言えば、この雑誌のサンプルソースはあやしいところが沢山ある。
それにこのソースには不可解なところがある。UARTは非同期通信なので、同期シリアル(USART)と違ってデータはクロックのエッジではなく、HighかLowでデータを採取する。それなのにサンプリング周波数はボーレートの2倍にとってある。少なくとも送信の場合はオーバーサンプリングしても無意味である。
恐らく、受信側のモジュールとボーレートを共通にするためだと思うが、ソースコードを読めば読むほど、何かあやしくなってきた。具体的にどこでハザードが起き、それによってボーレートが最後だけ短くなるのかは、まだ経験が浅くてつきとめられない。しかし現実に字化けが起きていることは事実である。
こうなったら、自前でUARTのソースコードを書いてみたくなってきた。参考書で学んだテクニックを応用する絶好の機会だ。
ここまで勉強したら自分で作ってみよう(8/21)
擬似コーディングを念入りにやって、自前のUART送信コードを書き始めた。UARTは、クロックパルスを持たない非同期通信なので、スタートビットからの時間間隔(ボーレート)だけでデータをHかLに設定して送信する。雑誌の2倍サンプリングをやめて、ボーレートだけのステートマシンにする。
自慢は、ステートレジスターのグレイコード化(1ビットづつ変化する遷移ビット)と、同時に多数のビットを変えないロジック、ボーレートカウンターのような多数のビットを変更するときは、ステートをあけて結果を利用するなどのテクニックだ。半日ばかりで出来た。おお、前よりステートメント数も少なくなっている。これで動けばいうことなしだ。
期待に胸をふくらませて論理合成する。祈る気持ちでUART端末を立ち上げる。ARMからデータを送り、FPGA側のUARTのスペースキー(出力指示)を押した。やった、やったぞ、最初の文字から全く誤りなくテキスト文書が表示される。キーを何度も押す。すごい。全くエラーがない。このあいだのXbeeの電力ロガーのデータはテキストデータで定型なので、少しでもエラーがあると目立つのだが、どこまでも綺麗に出力される(前は、3回に一回はどこかしらデータの乱れがあった)。
いやあ満足、満足である。勢いに乗って消費リソースを調べてみた。今度のソースコード全体のスライスは366、前は382だった。差はわずかだが、このスライス量はすべてのルーチン(UART受信、SRAMアクセス、ビデオ信号、SPI、10進変換など)を含んでいる。送信UARTだけならスライス量は80程度と考えられ、それでこの差は大幅な削減だ。嬉しい。
調子に乗って、またソースコードを公開してしまうことにした。送信の部分だけだが、Latticeの環境では、間違いなく雑誌のコードより信頼性の高いVerilogHDLソースである。なおコメントに日本語が入っているが、これは公開のために追加したもので、Latticeのエディターでは日本語は化けてしまうので注意されたい。
//------------------------------------------------------------
// UART送信モジュール 8/25/2010 (C) LABO Gataro
//
// CLKはマスタークロック、BAUDは受信モジュールと共通のパラメーター
// (受信モジュールは、DWM2008年10月月号P86を利用している)
//
// メインで以下のステートメントが必要(38.4kbpsのとき)
//
// parameter CLK = 33000000; // FPGA master clock
// parameter BAUD = CLK/(38400*2); // 38.4kbps 8bit 1start/stop
// assign div = BAUD;
//
module UART_TX( so, pi, xmit, rdy, div, pCLK, pRST );
output so ; // transmitted out シリアル出力
input[7:0] pi; // data to transmit 送信データ(8ビット)
input xmit; //start transmit flag (req) 送信要求
output rdy; // rdy=0 : in send proc (busy) ビジーフラッグ
input[10:0] div; // baud rate count from main module ボーレート
input pCLK; // master clock
input pRST; // master reset(XP2 is positive reset)
reg [7:0] ps8;
reg so ;
reg [1:0] state;
reg [10:0] dc ;
reg rdy ;
reg [4:0] bitctr; // including start/stop bit
wire [10:0] div2;
assign div2 = {div[9:0],1'b0}; // multiply by 2 for TX
parameter IDLE = 2'b00; //state reg in gray code
parameter DSET = 2'b01;
parameter WAIT = 2'b11;
always @(posedge pCLK or posedge pRST ) begin
if(pRST) begin // initial process if reset
ps8 <= 8'h00; // clear output reg
state <= IDLE; // initial state
dc <= div2; // load baud rate couner
rdy <= 1'b1; // inform TX ready
so <= 1'b1; // UART is negative logic(active=0)
bitctr <= 4'h0; // confirm initial status
end
else begin
case(state)
IDLE: if(xmit == 1'b1) begin // if tx request
ps8 <= pi; // set 8bit tx data
state <= DSET; // move to next state
bitctr <= 4'h0; // initialize counter
rdy <= 1'b0; // set busy bit
end
DSET: begin
if(bitctr == 4'h0) so <= 1'b0; // start bit
else if(bitctr == 4'h9) so <= 1'b1; // stop bit
else begin
so <= ps8[0]; // put data on serial line
ps8[6:0] <= ps8[7:1]; // shift data reg
end
bitctr <= bitctr + 4'h1; // shift 1 bit
state <= WAIT; // go to wait state
end
WAIT: begin
dc <= dc - 11'h1; // decrement baud rate counter
if(dc == 11'h0) begin // refill baud rate value
dc <= div2;
if(bitctr > 4'h9) begin // goto IDLE after stop bit sent
state <= IDLE;
rdy <= 1'b1; // set ready bit
end else
state <= DSET; //repeat 10 times(start + 8bits + stop)
end
end
endcase
end // end of else after Init
end // end of always clause
endmodule // UART_TX
| 固定リンク
| コメント (2)
| トラックバック (0)
最近のコメント