EOS S3基板の実装が完了したらしい
実装完了
QuickLogic EOS S3の実装が完了したとのこと。下記写真とX線検査が送られてきました。
まぁ、予想通り。短絡検査などの確認は全数検査が終わり、年明けに発送されるとのことでした。
なんでシルク黄色なんだろ…
楽しみ、と言いたいところですが、一通りの試験だけして、製品化せずに終息させてしまいそうです。お蔵入り。
もっと低価格で性能も上で、この状況でもデバイスを500個ほど取り寄せることができたので、そちらを採用しようかなと算段しています。
とはいえ、せっかく実装したのと、取引先の皆さんを待たせている都合上、出来るところまで試験しようと思います。
みなさまも、来年も頑張りましょうね
QuickLogic EOS S3基板を起こすハメになった話
ついに半導体枯渇が死活問題になってきた
ある程度の在庫を抱えてはいたのですが、Cyclone 10LP/MAX10が枯渇しそうです。
少なくともトレイに入った状態のintel FPGAはここ半年くらいお目にかかっていません。在庫しているボードものこりがわずかで、使い終わったらおしまい。商社によると年末に各社から不良在庫回避放出イベントが多少あるんじゃないか、と言われてましたがそうでもなさそう。
そんな経緯で、代替デバイスを検討すべく小規模FPGAに限って新興メーカ数社を中心に評価ボード、供給体制をチェックしていました。とはいえ弊社との取引先に無断でメインコアを変えるわけにも行かないので、対応を長いこと協議していました。
ADAS向けとシニアカーなどの福祉車両向けがメインです。車載グレードが必ずしも要求されない用途に限ります。
複数のデバイスを調査していますが、早い段階からチェックしていたQuickLogic EOS S3は、私(ハードウェア担当)が選択肢から外そうとしていました。詳細は後述しますが、いわゆるFPGAに求める機能があまりにも欠落しているかな、、、という判断でした。
ですが、取引先の大半のエンジニア(ソフトウェア担当)が違う観点でした。
下記は協議中に挙げられたメリットデメリットです。
基板の設計にあたって
チップの在庫確保もでき問題なさそうです。
問題はハードウェアチームです。基板製造コストがintelFPGAに比べかなり上がるのと、intelFPGAほどの機能が見込めないので妥協案を探っていきます。
- PLLがない問題
頼りにしていたSi5351Aがしばらく枯渇しています。L/Tみても絶望しかありません。
これについてはPLLで何がしたいか、という問題を検討しました。多くの場合数百MHzのクロックが欲しいのではなく、数十MHzのクロックとその90度位相が遅れたクロック(I-Q)が欲しいだけです。ですので、ロジック回路で解決できそうです。高速動作する74AVC74などに欲しい周波数の4倍を加え、I,Qを取得する算段にします。なお、FPGA側でそのくらいは作れそうに思えますが、ループエラーで論理合成は成功しないと思われます。そもそもFmaxが低いのでそれ以前の問題ですが。
- 基板製造価格
製造費用増は仕方がありません。事前検討の結果、EOS S3のBGA64ピンのうち62ピンはどうしても必要なことがわかっています。
パッドオンビアかつビルドアップを選択しますが、べリードビアを避ける、フルスタックではなく貫通viaにする(4-layer ,1−2−1 BHなし)ことによりなるべく費用を下げます*2
- SRAMが足りない問題
外付けでQSPI IoT SRAMを使用します。EOS S3のFmaxがかなり低いことも検討の結果わかっていますので、帯域を確保するため2個パラレルで使用します。この時必要になるGPIOは12pin(=6pin x 2個)です。パラレルSRAMを使用する場合より格段に少ないピン数です。
基板外観
こんな感じ。アートワークは2日ほどで終わりました。部品選定は選定中に商社から在庫が消えるという体験を何回もやっているので4日ほどかかっています。もうやだー。
裏面
何も部品は配置しません。*3シルクで電源端子の説明だけ書いておきます。
部品点数自体は少ないので、4層もあれば広々と配置できます。
見ての通り、Raspberry Pi ZeroのHATと同じフットプリントです。デバッグはすべてラズパイから行います。
部品も確保済み。ガーバーアウトしたので、あとは製造実装まちです。
QuickLogic EOS S3でCMOSカメラOV5642を駆動する
小規模FPGA+小規模マイコンならではの使い方としてイメージセンサの駆動は最適かもしれません。
マイコンだけだと、駆動ロジック自体は単純な割に処理速度がギリギリになってしまうからです。外部メモリーIFを持っているマイコンであれば駆動は簡単ですが、そのような品種は比較的高価で多ピンの場合が多いので、EOS S3はそれらに対するアドバンテージと言える、かも。知らんけど。
※ QuickLogic EOS S3評価ボードはスイッチサイエンスさんでも発売中。
www.switch-science.com
この記事は、イメージセンサの選定から、ロジックの検討、HDL記述、CPUからFPGAへのアクセス、とりだした画像データのMATLABによるデコードまで行います。
- イメージセンサ検討
- イメージセンサ初期化
- 画像データのストア方法検討
- FPGAロジック設計方針
- SRAMが足りない問題
- CPU側のプログラム
- ハードウェア
- プロジェクト
- 動作方法
- RGB565をプレビューしたい
- 動作風景
イメージセンサ検討
扱いやすく安価なイメージセンサがあります。
akizukidenshi.com
外部から24MHzでクロックを入れる必要があります。
MIPIインターフェースのみを持つカメラが多い昨今で、OmniVision OV5642はDVP(=8bitパラレルIF)を持ちVGAサイズ時60fpsまで駆動できるこのデバイスはかなり重宝します。EOS S3のようにカメラ用IFハードマクロを持たない古典的なCPLD/FPGAでも駆動できるからです。
イメージセンサ初期化
SCCB(I2C)インタフェースで設定をします。今回VGAサイズ、RGB565フォーマット出力に設定しています。この記事でSCCB設定方法について書くつもりはありません。OV5642はイメージセンサにしては結構「枯れた」デバイスですので、検索すると設定方法のブログや記事などが結構見つかります。実際に記述したコードはこちらをご覧ください。
画像データのストア方法検討
EOS S3のハードマクロSRAMは8kbyteあります。もちろんこれを画像データの蓄積に利用するのですが、VGAサイズでRGB565=16bit/ピクセルの場合、640x480x2[byte/pixel]=600kbyteとなるので、必要データ数の75分の1しかありません。対策は以下のいずれかを選ぶと思います。
1は真っ当な解決方法です。ほとんどの組み込みエンジニアは異論は無いでしょう。2もできるならしたいところです。やってみて出来そうだな、という手応えはありましたが、それ以外のこと(例えば画像処理など)を並行してさせると一気に破綻しそうなので、今回は採用しませんでした。3は一見すると意味不明ですが、事と次第においては最適解です。例えば画像処理において、取得できた画像のすべての要素がROI*1ということはまずあり得ません。画像中でROIとなるオブジェクトは多くて数点、画像中の占める割合で言うと25%~30%くらいでしょう。
加えて量産の場合、せっかく格安FPGAを採用したのにRAMを外付けするというと稟議で「PoCまではRAM付けていいけど、量産(前試作)までに外してね 」と返ってくることもあります。量産だと一個百円のデバイスでも苦い顔される。購買サイドで考えればわかんなくないけど。
というわけで、今回は3を試してみます。1でもQSPI IoT SRAMで実験したので、また後日ブログにしようかと思います。
FPGAロジック設計方針
eFPGAでOV5642からのピクセルクロック、VSYNC、HREF、8bit信号を取得するロジックを記述します。データはハードマクロ非同期SRAMに記録しAHB to WishboneでCPUメモリ空間からリードできるようにします。
問題はWishboneバスブリッジが最大10MHzであるということです。お世辞にも高速とは言えません。転送帯域を稼ぐため、8bitデータを4回分(=32bit)をFPGA側で受信後に整形し、RAMに記録する事にしました。下図に示します。
こうすると、ピクセルクロック(PCLK)=24MHzでデータが転送される場合、RAMへのアクセススピードは24/4=6MHz(<10MHz)となり帯域を十分確保できます。詳細は後述しますが、指定された分の記録が終わるとステートマシンは動作を止め、CPUへ完了した旨をステータスビットを1にして知らせます。CPUから解除信号を受信するとステートマシンはリセットされ動作を再開します。
ステートマシンを記述します。
localparam CRSET = 2'd0; // RESET localparam CB08F = 2'd1; // Camera buffer 8bit Full localparam CB16F = 2'd2; // Camera buffer 16bit full localparam CB24F = 2'd3; // Camera buffer 24bit full localparam RST32 = 32'hFFFF_FFFF; localparam RST11 = 11'h7ff; assign cam_data_valid = HREFI & VSYNCI ; always @( posedge PCLKI or posedge WBs_RST_i) begin if(WBs_RST_i) begin cam_reg1 <= RST32; cam_reg_out <= 32'h00; cam_status <= CRSET; cam_reg_rdy <= 1'b0; cam_ram_cnt <= RST11; cam_freerun <= RST32; end else begin // PCLK if(cam_data_valid & cam_go_flag)begin case(cam_status) CRSET: begin cam_reg1 <= {24'h00, CAM_DAT[7:0]}; cam_reg_rdy <= 1'b0; cam_status <= (cam_state_stop)? cam_status : CB08F; end CB08F: begin cam_reg1 <= {16'h00,cam_reg1[7:0],CAM_DAT[7:0]}; cam_freerun <= cam_freerun + 32'h01; cam_ram_cnt <= (cam_ram_cnt + 11'h01); cam_reg_rdy <= 1'b0; cam_status <= CB16F; end CB16F: begin cam_reg1 <= {8'h00,cam_reg1[15:0],CAM_DAT[7:0]}; cam_reg_rdy <= 1'b0; cam_status <= CB24F; end CB24F: begin cam_reg_out <= {cam_reg1[23:0],CAM_DAT[7:0]}; cam_reg1 <= 32'h0; cam_reg_rdy <= 1'b1; // 非同期SRAM書き込み信号 cam_status <= CRSET; end endcase end else if(~cam_go_flag)begin cam_reg1 <= RST32; cam_reg_out <= cam_reg_out; cam_status <= CRSET; cam_reg_rdy <= 1'b0; cam_ram_cnt <= RST11; cam_freerun <= RST32; end else begin //!cam_data_valid cam_reg_rdy <= 1'b0; cam_status <= cam_status; cam_reg_out <= cam_reg_out; end end // PCLK end
SRAMが足りない問題
この問いに一言で回答するなら「画像データの一部分だけをスキャンして残りは捨てる」です。前述したようにROIが判明している場合は他の要素が邪魔になるのでよくやる手法です。メモリが足りないという理由でやることはあまりしませんが…。
OV5642からは1秒あたり数フレームから数十フレーム相当のデータが絶えず流れてきます。今回は8kBしかRAMがないので、
- 1フレーム目は最初から8kB分を取得しCPUへ転送。フレーム中の残りのデータは捨てる
- 2フレーム目は8kB~16kBまでの8kB分を取得しCPUへ転送。フレーム中の残りのデータは捨てる
- 3フレーム目は16kB~24kBまでの8kB分を取得しCPUへ転送。フレーム中の残りの(以下略
.....
- 75フレームでVGAサイズ1枚分データがようやく取得完了
と、75回繰り返してデータを取得することになります。このやり方だと動く物体を撮像するのは少し無理があります。
今回はUSBシリアルで取得データをゆっくり転送する都合上、さらに時間を要するのでさらに遅くなります。しかたないね。
ROIを考慮する場合はデータは少なくて済むはずなのでここまで手間ではないと思います。
CPU側のプログラム
ハードマクロSRAMは32bit x 512ワードの非同期SRAMを4つ接続しています。4つのアドレスは決まっているため、C側では以下のように宣言しています。
volatile uint32_t **fb_ram0 = 0x40022000; volatile uint32_t **fb_ram1 = 0x40024000; volatile uint32_t **fb_ram2 = 0x40026000; volatile uint32_t **fb_ram3 = 0x40028000; volatile uint32_t **fb_status = 0x4002a000;
最後のfb_status
はステータスレジスタです。ステータスレジスタ読み書きによってFPGA側のモジュールの起動・停止などを行います。ステータスの詳細は下記の通りです。read only / write onlyはそれぞれCPU側(=Cソースコード)から見た時の状態です。
以上を踏まえVGAサイズのデータ1枚分を取得するコードを記述すると、以下のように記述できます。
uint32_t fboundary = 0; uint32_t a[512*4]; // 8kb分のデータ保管用 for(uint32_t ii = 1 ; ii < 76; ii++) { fboundary = ii;// *(volatile uint32_t *)fb_status = (0x00000 | fboundary); // リセット *(volatile uint32_t *)fb_status = (0x10000 | fboundary); // fboundary番目のデータリードスタート while( 0x08 != (*(volatile uint32_t *)fb_status & 0x08) ); //転送は完了したか *(volatile uint32_t *)fb_status = (0x00000 | fboundary); // ステートマシンリセット memcpy(&a[512*0], fb_ram0, (512 * sizeof(uint32_t))); // ram0 -> a memcpy(&a[512*1], fb_ram1, (512 * sizeof(uint32_t))); // ram1 -> a memcpy(&a[512*2], fb_ram2, (512 * sizeof(uint32_t))); // ram2 -> a memcpy(&a[512*3], fb_ram3, (512 * sizeof(uint32_t))); // ram3 -> a for(uint32_t i = 0 ; i < 512*4 ; i++) { dbg_hex32(a[i]); dbg_str("\n"); // シリアルデータ送信 } }
ハードウェア
QuickFeatherに直接はんだづけ。こんな雑な配線でもデータが化けないので感動します。ボードのリセットボタンとユーザーボタンがすり減って反応が鈍くなってきたので、そろそろ基板を起こそうかと考えています。
カメラデータ取得・通信用にUSBシリアルを接続しています。
結線図を書く気力がないので、カメラとQuickFeatherとの配線情報はpcfを参照ください。
プロジェクト
下記githubからcloneしてください
github.com
git clone -b v0.0.5 https://github.com/panda5mt/qf_wbfpga_pio/
動作方法
書き込み後、リセットするとスタートします。ボーレートはかなり早めの921600baud、8N1でデータがシリアルから取得できます。
ターミナルを使っているならば、パイプで一度テキストファイル等に保存した方が取り扱いが楽だと思われます。
下記例はpicture.txtにデータを保存しています。/dev/ttyUSB0
はUSBシリアルのポートです。各自の環境で変更してください。
例:
$ cu -l /dev/ttyUSB0 -s 921600 > picture.txt
RGB565をプレビューしたい
ビットマップのヘッダとかよくわからなかったのと、わかったところで真面目に実装する気力もないのでMATLABの力を借りました。先ほど取得できたpicture.txtを読み込みます。
数十行で書けてしまう。MATLABはいいぞ。
clc; format long; RGB_img = zeros(480,640,3,'uint8'); img = zeros(480,640,'uint32'); lower5 = hex2dec('1f') .* ones(480,640,'uint32'); % 0x1f 0x1f .... lower6 = hex2dec('3f') .* ones(480,640,'uint32'); % 0x3f 0x3f .... lower8 = hex2dec('ff') .* ones(480,640,'uint32'); % 0xff 0xff .... lower16 = 65535 .* ones(480,640,'uint32'); % 0xffff 0xffff .... fileID = fopen('picture.txt'); while (true) tline = fgetl(fileID); disp(tline); if contains(string(tline),'!st') disp("start signal received..."); break; end end for HGT = 1:480 for WID = 1:2:640 data = fgetl(fileID); if -1 ~= data img(HGT, WID) = hex2dec(data); img(HGT, WID+1) = bitshift(hex2dec(data),-16); end end end img = bitand(lower16, img); %img = RGB565 imgR = (255/63) .* bitand(lower5, bitshift(img,-11)); % Red component imgG = (255/127).* bitand(lower6, bitshift(img,-5)); % Green component imgB = (255/63) .* bitand(lower5, img); % Blue component RGB_img(:,:,1) = imgR; RGB_img(:,:,2) = imgG; RGB_img(:,:,3) = imgB; imshow(RGB_img);
動作風景
できました!
埼玉県新座市イメージキャラクタ 「ゾウキリン」 (C)2010 新座市
MATLABのプロジェクトはこちらです(上記「プロジェクト」に含まれています)
https://github.com/panda5mt/qf_wbfpga_pio/tree/v0.0.5/matlab
おしまい。
*1:Region of Interest:関心領域
QuickLogic EOS S3のハードマクロFIFOを使う
EOS S3はPSoC3/5LPと比較すると、アーキテクチャの差があるので単純比較できないものの、およそ3~4.5倍ほどの論理ゲート規模を有します。
それはそうと、ロジック規模だけで優劣をつけてしまうので有れば15年以上前のCPLDと同等になってしまいます。それ以上の価値としてEOS S3の特徴は、小規模組み込みデバイスとしては大きめのRAMが搭載されていてサブモジュールごとに割り振られている点です。Cortex-M4Fには512KB、eFPGAには8kBのRAMが割り振られています。
FPGA側から8KBのRAMはハードマクロRAMまたはFIFOとしてインスタンスできます。8kBはちょっと少ないんじゃないかという感じもしますが、EOS S3はセンサーマネジメントSoCなので、FPGA IPでなんでも作り込むような古典的思想ではなく、飽くまでセンサーIFのみを用意するにとどめ、取得したデータはDMAでCortex-M4F側のSRAMに転送する前提なのでこれはこれでいいのかなと。DMAのDestinationはCortex-M4FサブモジュールのSRAMだけでなくSPIマスタにも切り変え可能なため、IoT RAMのような高速大容量SPI RAMがDestination側で使えたらいいなと考えているところです。
下記の順番でこの記事は構成されています。
使用可能なハードマクロ
FPGAが利用できるハードマクロは
- RAM (8 x 8Kbits)
- FIFO (8 x 8Kbits)
- 乗算器 (32bit x 32bit x 2)
です。小規模FPGAらしく必要最小限でとどめていて好印象。
ハードマクロFIFOのインスタンス
Design example Using FIFOsにハードマクロFIFOのインスタンス事例と、VerilogのexampleコードYour-Installed_Path/quicklogic-arch-defs/tests/fifo_test
があります。Wishbone bus readyなコードになっているので、自作プロジェクト内で動作できるように若干の修正は行いますが、ほぼそのまま使おうとおもいます。CPU側のコードだけ追記するような感じです。
FIFOは8kbit、16kbitのブロックを使用することができ、それぞれのブロックを並列、直列接続することによってビット長を増やしたり、単純にFIFO深さをn倍にしたりすることもできます。
下記例は8kbitのFIFOを1ブロック分インスタンスしています。
(前略) FIFO_8K_BLK # (.data_depth_int(data_depth_int),.data_width_int(data_width_int),.reg_rd_int(reg_rd_int),.sync_fifo_int(sync_fifo_int) ) FIFO_INST ( .DIN(DIN), .PUSH(PUSH), .POP(POP), .Fifo_Push_Flush(Fifo_Push_Flush), .Fifo_Pop_Flush(Fifo_Pop_Flush), .Push_Clk(Push_Clk), .Pop_Clk(Pop_Clk),.PUSH_FLAG(PUSH_FLAG), .POP_FLAG(POP_FLAG), .Push_Clk_En(Push_Clk_En), .Pop_Clk_En(Pop_Clk_En), .Fifo_Dir(Fifo_Dir), .Async_Flush(Async_Flush), .Almost_Full(Almost_Full), .Almost_Empty(Almost_Empty), .DOUT(DOUT));
それぞれの信号の詳細は、FIFO Usage — QuickLogic-FPGA-Toolchainを参照ください。
Cortex-M4F側のプログラム
Exampleでは3つ、タイプの異なるFIFOが用意されています。
メモリマップのとおり、構造体を用意しました。
fpga/inc/fpga_fifoctrl.h
typedef struct fpga_fifoctrl { uint32_t device_id; // 0x00 uint32_t rev_num; // 0x04 uint32_t gpio_in; // 0x08 uint32_t gpio_out; // 0x0C uint32_t gpio_oe; // 0x10 uint32_t reserved1[60-1]; uint32_t fifo1_acc; // 0x100 uint32_t fifo1_flags; // 0x104 uint32_t reserved2[63-1]; uint32_t fifo2_acc; // 0x200 uint32_t fifo2_flags; // 0x204 uint32_t reserved3[127-1]; uint32_t fifo3_acc; // 0x400 uint32_t fifo3_flags; // 0x404 } fpga_fifoctrl_t; fpga_fifoctrl_t* fifoctrl_regs = (fpga_fifoctrl_t*)(FPGA_PERIPH_BASE);
このままだとちょっとアクセスが面倒な感じがするので、それぞれのFIFOやフラグにアクセスできるように関数を用意します。
// FIFO APIs uint32_t fpga_getflag(uint8_t ch); uint32_t fpga_getfifo(uint8_t ch); void fpga_setfifo(uint8_t ch, uint32_t value);
フラグ取得API,FIFO読み込みAPI,FIFO書き込みAPIです。
main関数で下記の手順を実行するテストコードを書きます。
- ライト動作
- 特定のChannelのFIFOに深さ512だけ書き込む
- リード動作
- Channel1~3まで上記ステップを繰り返し
コードです。
src/main.c
// (中略) // test each FIFOs(FIFO1~3) for (uint8_t ch=FIFO_CH1 ; ch<=FIFO_CH3 ; ch++) { dbg_str("\r\n\r\n------------------ CHANNEL "); dbg_int(ch); dbg_str(" ------------------"); for(uint32_t i=0 ; i<512 ; i++) { fpga_setfifo(ch,i); } for(uint32_t i=0 ; i<512 ; i++) { dbg_str("\r\nstatus = 0x"); dbg_hex32(fpga_getflag(ch)); dbg_str("....fifo = 0x"); dbg_hex32(fpga_getfifo(ch)); } // 最後のFIFOをReadした後(=FIFOはすべてEmpty)のFlagを確認する dbg_str("\r\nstatus = 0x"); dbg_hex32(fpga_getflag(ch)); dbg_str(".\r\n"); }
動作確認
QuickFeatherにUSB-UARTを接続していると動作確認ができます。
------------------ CHANNEL 1 ------------------ status = 0x00000f80....fifo = 0x00000000 status = 0x00000e8f....fifo = 0x00000001 status = 0x00000e0e....fifo = 0x00000002 status = 0x00000e0e....fifo = 0x00000003 status = 0x00000e0e....fifo = 0x00000004 (略) status = 0x00008102....fifo = 0x000001ff status = 0x00008001. ------------------ CHANNEL 2 ------------------ status = 0x00000e03....fifo = 0x00000000 status = 0x00000d02....fifo = 0x00000001 status = 0x00000d02....fifo = 0x00000002 status = 0x00000d02....fifo = 0x00000003 status = 0x00000d02....fifo = 0x00000004 (略) status = 0x00000302....fifo = 0x000001fc status = 0x00000202....fifo = 0x000001fd status = 0x00000202....fifo = 0x000001fe status = 0x00008102....fifo = 0x000001ff status = 0x00008001. ------------------ CHANNEL 3 ------------------ status = 0x00000f80....fifo = 0x00000000 status = 0x00000e8f....fifo = 0x00000001 status = 0x00000e0e....fifo = 0x00000002 status = 0x00000e0e....fifo = 0x00000003 (略) status = 0x00000302....fifo = 0x000001fb status = 0x00000302....fifo = 0x000001fc status = 0x00000202....fifo = 0x000001fd status = 0x00000202....fifo = 0x000001fe status = 0x00008102....fifo = 0x000001ff status = 0x00008001.
Channel1、FIFOリード動作の初めの部分
深さ512分だけFIFOを書き込んだ後からスタートしています。Status(Flag1)=0x0f80ですので、PUSH,POPフラグともにFullを示しています。
Almost Fullフラグも立っています。
Channel1、FIFOリード動作の終盤部分
Status(Flag1)=0x81xxとなっておりAlmost Emptyフラグが立っていることがわかります。
コード全体
QORC-SDKプロジェクト、
qorc-sdk/qf_apps
フォルダ内で、以下のプロジェクトをcloneしてください。
$ git clone -b v0.0.2 https://github.com/panda5mt/qf_wbfpga_pio.git
QuickLogic EOS S3をDockerで使う
前回の記事のキャッチアップです。Dockerが使えると環境整備の負担が少なくて良いので、作ってみました。
脱・開発環境汚染
現在、複数の大学と企業と連携したプロジェクトで開発をしているのですが、環境構築やってらんない、CI/CD考慮した環境にすべきとの指摘があり、移行可能なプロジェクトから順次Dockerを採用しています。そこで使用中のQORC-SDKもDockerに移行しました。その時のメモとなります。
QuickLogic公式にもql_symbiFlowのみのDockerfileがあるのですが、どうせなら開発環境全部用意して欲しいのだが、というところがこの記事の始まりです。
この記事はDockerの初歩的な知識があり、セットアップも終えている前提で始まります。
Dockerfile
Macbook Late 2020(intel)、WSL2上での動作を確認しています。M1 Macでも一応動作しそうですがかなり遅いです。CI/CD考慮しているのにM1に拘ることもないでしょう。x86_64使いましょ。
任意のディレクトリを作成しDockerfileを作成します。
$ mkdir docker-qorc $ cd docker-qorc $ touch Dockerfile
Dockerfileは以下の内容です。Docker imageはあまり軽くない(2.0GBほど)ですが、
成果物と開発環境の両方が欲しいので今のところはこの方針でいきます。
FROM debian:buster-slim RUN apt update && apt -y install --no-install-recommends sudo make libtbb2 wget coreutils udev curl time tar\ && apt -y install git gcc-arm-none-eabi\ && apt clean && rm -rf /var/lib/apt/lists/* RUN git clone -b v1.10.0 https://github.com/QuickLogic-Corp/qorc-sdk.git SHELL ["/bin/bash", "-c"] # 以下3行はtarからarm-none-eabiを導入する場合は不要。(armからのダウンロードがすごく遅いのでaptで導入してシンボリックリンクを貼っている) WORKDIR /qorc-sdk RUN mkdir -p arm_toolchain_install/gcc-arm-none-eabi-9-2020-q2-update/ RUN ln -s /usr/bin/ arm_toolchain_install/gcc-arm-none-eabi-9-2020-q2-update/ #RUN sed -i -e 's/v1.3.1/v1.3.2/g' envsetup.sh #RUN source envsetup.sh WORKDIR /root RUN echo -e '#!/bin/bash\n\ncd /qorc-sdk\nsource envsetup.sh\ncd -\nmake -C GCC_Project\nscp GCC_Project/output/bin/*.bin pi@raspberrypi.local:/home/pi\n' > qlogic_build.sh RUN sed -i 's/\r//' *.sh RUN chmod +x qlogic_build.sh WORKDIR /qorc-sdk/qf_apps # 以下はキャッシュされると最新版がgit cloneできないための対策 ARG CACHEBUST=1 RUN git clone https://github.com/panda5mt/qf_wbfpga_pio.git
Dockerイメージのビルド
保存したら、Dockerイメージをビルドします。イメージ名は適当にtest
にしました。Docker hubを使うのであればイメージ名は考慮した方がいいでしょう。この記事の趣旨ではないですがDockerイメージをVSCodeを使っているならばこの項目での作業は全く必要ありません。
$ docker build -t test --build-arg CACHEBUST=$(date +%s) .
所要時間はそんなにいらないはずです。5分ほどで仕上がるでしょう。
イメージを起動します。
$ docker run -it test
ログインできたら下記のようになると思います。
[root@123456789abc:/qorc-sdk/qf_apps#
QORCプロジェクトのビルド
例題としてqf_advancedfpga
をビルドします。Dockerイメージを作った直後の初回だけanacondaが走って環境を構築するので10分くらいかかるかも。次回以降は純粋にプロジェクトのビルドのみです。
$ cd qf_advancedfpga/
$ ~/qlogic_build.sh
エラーなく終わればOK*1。GCC_Project/output/qf_advancedfpga.bin
がビルドバイナリです。qf_apps
内の他のプロジェクトも同様の方法でビルドできます。
Dockerイメージ状態の保存
Dockerイメージ起動中に別ターミナルでコンテナIDの確認をします。
$ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 123456789abc test "bash" 11 minutes ago Up 11 minutes foobar
コンテナID=123456789abcをコミットします。
$ docker commit 123456789abc test
docker commit
はあまり使ったことはないですが...
成果物をgit pushした後はイメージごと捨ててしまうので。
個人、チームでやりやすい開発スタイルで使えばいいんじゃないかな。
おしまい。
小規模マイコン+FPGA、QuickLogic EOS S3の試食
小さめなマイコン+グルーロジックの代表格はPSoC5 LPなどが挙げられます。回路規模でその上になるとXilinxであればZynq、IntelであればCyclone V SoCのような比較的中規模デバイスでしょう。
www.quicklogic.com
昨年の11月に購入して積んでいました。
必要に駆られて使ってみることに。
Quicklogic EOS-S3はこの両者の中間のデバイスと言えるかもしれません。このデバイスは80MHzで動作するCortex-M4Fを軸に小規模DSPとeFPGAを搭載した安価なデバイスです。少量購入でも単価1000円くらい。FPGA側はCPU(バスマスタ)にぶら下がるメモリマップドスレーブとして開発できます。(Wishboneバス)
Vivado IPインテグレータやPlatform DesignerみたいなGUIはありません。GUIのアシストが必要になるレベルの大規模なものを作る前提ではないのでしょう。いずれにせよ、今回のようなデバイスは使い捨てができる回路規模であり、そういう前提で使うのが吉です。末長く使う物でもないので、これを大黒柱にした開発は避けるべきでしょう。
今回の記事は以下のような構成です。前半は開発環境の整備。中盤はプログラミングに必要なGPIOやFPGA内部とのアクセスに必要なメモリマップの情報です。また後半ではオリジナルコードを記述しながら、FPGAのプログラミング時に必要なピンアサイン情報の設定方法も取り上げます。
開発環境の整備
(※Dockerを使用する場合の記事を書きました。私のように開発環境が多く、使用後はイメージごと使い捨てるスタイルの方はこちらの方が便利だと思います)
Quicklogicの提供するFPGA開発環境(ql_symbiflow)が、x86_64 Linuxを前提にしています。他はarm-none-eabi-gccおよびPython3が動作すればアーキテクチャはなんでも動くのですが、ここでCPUのアーキテクチャが固定されてしまいます。SymbiFlow本体にはそのような制約はないようですし、そのうち改善されるといいですね。
今回はWSL1を使いました。WSL1/2はこのブログ執筆時点でUSBが動作しませんが、私の場合はWindows(WSL)はオンプレのコンパイラ専用機として使用するので問題ありません。開発は手元のmacでVS Code Remote Developmentで行い、EOS-S3への書き込みはラズパイから実行します。
WSL1はubuntu-20.04を使用しています。諸事情*1でWSL2からダウングレードしています。
QORC-SDK導入
以下のgithubからcloneしてきます。
github.com
$ git clone -b v1.10.0 https://github.com/QuickLogic-Corp/qorc-sdk.git $ cd qorc-sdk/
コンパイラ導入
諸々のコンパイラ環境を一気に導入します。
$ source envsetup.sh
これだけです。
ここでanacondaをいきなりインストールされて、環境構築が始まります。私の環境ではタイムアウトに起因するエラーが何回か出ました。根気よく何回かトライする必要があるかも。
インストール時のエラーについて
- apioに関するエラー:WSLはUSB(シリアル)が動かないのでエラーで構わない。
- fasmに関するエラー:論理合成後のバイナリストリームをC言語ヘッダファイルに変換する部分がエラーを起こしています。再インストールが必要かもしれません。
加えて、gcc-arm-none-eabiはQORC-SDKが提供するバージョンを使うのが良いと思われます。バージョンによってはサンプルプログラムをビルドした際にRAM領域がオーバーフローすることがあります。
githubのREADMEをみるといろいろPATHを通すように指示がありますが、私の場合はログイン後に毎回source envsetup.sh
を走らせています。ログインと同時にPATHを通されてしまうとQORC-SDKのconda環境が別に構築したpip環境や、既存のconda環境を破壊することがあるので嫌だっただけです…pyenvを真面目に設定すればいいのでしょうが。
サンプルプロジェクトのお試しビルド
前節までで導入した環境の動作確認のためサンプルプロジェクトをビルドしてみます。ARMコンパイラとFPGA論理合成ソフトウェアql_symbiflowの両方の動作確認ができるサンプルコードを選択します。 qf_apps/qf_helloworldhw
やqf_apps/qf_advancedfpga
といったサンプルプロジェクトが最適と思われます。
例:qf_helloworldhwのサンプルをビルド
cd qf_apps/qf_helloworldhw make clean -C GCC_Project make -C GCC_Project
clean後のビルドはCコンパイルの他、論理合成も走るので5分くらい気長に待ちます。
(中略) make[1]: Entering directory '/mnt/c/qorc-sdk/qf_apps/qf_helloworldhw/GCC_Project' Linking ... Convert ELF to BIN Create text symbol table. make[1]: Leaving directory '/mnt/c/qorc-sdk/qf_apps/qf_helloworldhw/GCC_Project' Copy output files ... make: Leaving directory '/mnt/c/qorc-sdk/qf_apps/qf_helloworldhw/GCC_Project'
こんな感じで終了していれば成功。
もし
ERROR: 'AL4S3B_FPGA_top_bit.h' not found.
のようなエラーが出ている場合、ql_symbiflowのインストールに失敗しています。前節で実行したsource envsetup.sh
の際のエラーを確認することをお勧めします。
おまけ:ラズパイでの書き込み
Raspbianでもubuntuでも同様の手法です。下記のREADMEの通りに導入します。
github.com
$ git clone --recursive https://github.com/QuickLogic-Corp/TinyFPGA-Programmer-Application.git $ pip3 install tinyfpgab
毎回コマンド打つのは面倒ですので、スクリプトを書きます。書き込みするbinファイルは大抵同じファイル名だと思いますので、下記のようにしています。ttyポート名は各自の環境に合わせてください。
#!/bin/bash #qfprog --port /dev/ttyACM0 --bootloader *.bin --mode fpga-m4 BINFILE=qf_advancedfpga.bin qfprog="python3 /(your-installed-directory)/TinyFPGA-Programmer-Application/tinyfpga-programmer-gui.py" $(qfprog) --port /dev/ttyACM0 --m4app $(BINFILE) --mode fpga-m4
上記テキストを保存し、実行権限を付与すればOK.
GPIOとピンアサイン
閑話休題。フレキシブルなマイコンのイメージが先行し、I2CやSPI、UARTなどの特定機能は任意のピンに割り当てられそうに思いますが、残念ながらEOS-S3はあまり自由度はありません。Cortex-M4FやSensor Manager側に接続されている機能はピンアサインがほぼ固定されています。FPGAからの入出力であれば電源、クロック、ADC入力を除くほぼ全てのピンに割り当て可能なはず(執筆時点で解決できていない部分があります。後述する「コード作成」セクションの「ピンへの反映」をご覧ください)です。
下図は、それぞれのパッケージにおける、個々のピンにアサイン可能な特定機能一覧の抜粋です。
FBIOというのはFPGA Fabric IOのことです。アサイン可能なピンの詳細はEOS-S3データシートのp.99~p.102をご覧ください。
どのピンに何を割り当てたかはテーブルでソースコードに記述します。
サンプルプロジェクトの場合、src/pincfg_table.c
に記述されています。
PadConfig pincfg_table[] = { { // setup UART TX .ucPin = PAD_44, .ucFunc = PAD44_FUNC_SEL_UART_TXD, .ucCtrl = PAD_CTRL_SRC_A0, .ucMode = PAD_MODE_OUTPUT_EN, .ucPull = PAD_NOPULL, .ucDrv = PAD_DRV_STRENGHT_4MA, .ucSpeed = PAD_SLEW_RATE_SLOW, .ucSmtTrg = PAD_SMT_TRIG_DIS, }, { // setup UART RX .ucPin = PAD_45, // Options: 14, 16, 25, or 45 .ucFunc = PAD45_FUNC_SEL_UART_RXD, .ucCtrl = PAD_CTRL_SRC_A0, .ucMode = PAD_MODE_INPUT_EN, .ucPull = PAD_NOPULL, }, { // Pad 17 -- clock out from FPGA .ucPin = PAD_17, .ucFunc = PAD17_FUNC_SEL_FBIO_17, .ucCtrl = PAD_CTRL_SRC_FPGA, .ucMode = PAD_MODE_OUTPUT_EN, .ucPull = PAD_NOPULL, .ucDrv = PAD_DRV_STRENGHT_4MA, .ucSpeed = PAD_SLEW_RATE_FAST, .ucSmtTrg = PAD_SMT_TRIG_EN, }, //(中略)..... }
ピンアサインはパッケージのピン番号ではなく信号名(PAD_17 etc..)で記述します。
なお、FPGAの入出力の場合、Verilog側で記述した信号名と対応させる必要があるため、さらに*.pcfファイルを記述する必要があります。詳細はコード作成のセクションで扱います。
メモリマップについて
EOS-S3は様々なサブシステムが接続されており、メモリマップを網羅するのは大変です。この記事では今回使う範囲のメモリマップに限定します。
メモリマップ詳細はTRMのp.38~p.43をご覧ください。
https://www.quicklogic.com/wp-content/uploads/2020/06/QL-S3-Technical-Reference-Manual.pdf
プログラミングでよく使うのはPeripheralの0x4000_0000~0x5fff_ffff番地でしょう。
上記アドレスのうち特にFPGAへインターフェースするメモリ領域は0x4002_0000~の128kB分です。
この番地はAHB Slave-Wishbone Masterブリッジが接続されている領域です。
詳細はTRMのp.275~ご覧ください。また、後述する、コードの作成時にも触れますので参考になれば。
コード作成
今回、FPGA領域に簡単なGPIOポートを作成します。
Cortex-M4F側から32bitのデータを受け取り、下位4bitだけをGPIOポートに反映するという簡単な物です。一連の動作確認には必要十分だと思います。
既存のサンプルコードに直接手を加えるのはあまり賢明ではないでしょう。サンプルを雛形として新規にプロジェクトを作成することにします。すでにQORC-SDK自体にその仕組みがありますので利用しましょう。
既存プロジェクトを雛形に新規プロジェクトを作成
qf_apps
ディレクトリに戻ります。
既存プロジェクトqf_mqttsn_ai_app
を雛形にして、新しいプロジェクトqf_wbfpga_pio
を作ります
$ python create_newapp.py --source qf_mqttsn_ai_app --dest qf_wbfpga_pio
CPUからFPGAへアクセスするには
メモリマップのセクションでも取り上げた通り、AHB Slave-Wishbone Masterブリッジが接続されています。FPGA側に作成したオリジナル回路とCPU間でデータの送受信をする際には、オリジナル回路にWishbone Slaveを接続する必要があります。
これもサンプルがありますので、それを基に作ってしまいます。FPGA側の参考になるサンプルプロジェクトはqf_apps/qf_advancedfpga
です。
このプロジェクト内のfpga/rtl/AL4S3B_FPGA_Rgisters.v
を抜粋してみます。
Cortex-M4F -> FPGA
Wishboneマスタによるデータライト部分です。Cortex-M4Fからはデータ代入動作でこの部分にライトされます。
assign FB_COLORS_REG_Wr_Dcd = (WBs_ADR_i == FPGA_COLORS_ADR) & WBs_CYC_i & WBs_STB_i & WBs_WE_i & (~WBs_ACK_o); // 中略 if (FB_COLORS_REG_Wr_Dcd) begin color0 <= WBs_BYTE_STB_i[0] ? WBs_DAT_i[3:0] : color0; color1 <= WBs_BYTE_STB_i[1] ? WBs_DAT_i[10:8] : color1; color2 <= WBs_BYTE_STB_i[2] ? WBs_DAT_i[18:16] : color2; color3 <= WBs_BYTE_STB_i[3] ? WBs_DAT_i[26:24] : color3; end
FPGA -> Cortex-M4F
Wishboneマスタによるデータリード部分です。今回該当部分は0xDEADBEEF
がリードできるようにします。
always @( * ) begin case(WBs_ADR_i[ADDRWIDTH-1:0]) FPGA_REG_ID_VALUE_ADR : WBs_DAT_o <= Device_ID_o; FPGA_REV_NUM_ADR : WBs_DAT_o <= Rev_Num; FPGA_SCRATCH_REG_ADR : WBs_DAT_o <= { 16'h0, Scratch_reg }; FPGA_COLORS_ADR : WBs_DAT_o <= 32'hDEADBEEF; //{ 5'b0, color3, 5'b0, color2, 5'b0, color1, 5'b0, color0}; FPGA_DURATION0_ADR : WBs_DAT_o <= { 20'b0, duration0}; FPGA_DURATION1_ADR : WBs_DAT_o <= { 20'b0, duration1}; FPGA_DURATION2_ADR : WBs_DAT_o <= { 20'b0, duration2}; FPGA_DURATION3_ADR : WBs_DAT_o <= { 20'b0, duration3}; default : WBs_DAT_o <= AL4S3B_DEF_REG_VALUE; endcase end
Wishboneのアドレスによって、データを振り分けています。たくさんレジスタがありますが、今回の試行ではcolor0
レジスタだけを利用し、GPIOに反映するロジックを記述しようと思います。
ヘッダに記述された構造体も見てみます。fpga/inc/fpga_ledctlr.h
にあります。
便宜上2つ用意されています。この部分はqf_advancedfpga
のサンプルの便宜上の都合ですので、1つにシュリンクしても良いでしょう。
// 構造体1つ目 typedef struct fpga_ledctlr_regs { uint32_t device_id; // 0x00 uint32_t rev_num; // 0x04 uint16_t scratch_reg; // 0x08 uint16_t reserved1; // 0x0A uint32_t reserved2; // 0x0C uint8_t color0; // 0x10 uint8_t color1; // 0x11 uint8_t color2; // 0x12 uint8_t color3; // 0x13 uint32_t reserved7[3]; // 0x14 uint32_t duration0; // 0x20 uint32_t duration1; // 0x24 uint32_t duration2; // 0x28 uint32_t duration3; // 0x2C } fpga_ledctlr_regs_t; //構造体2つ目 typedef struct fpga_ledctlr_regs2 { uint32_t device_id; // 0x00 uint32_t rev_num; // 0x04 uint16_t scratch_reg; // 0x08 uint16_t reserved1; // 0x0A uint32_t reserved2; // 0x0C uint32_t colors; // 0x10 uint32_t reserved7[3]; // 0x14 uint32_t duration0; // 0x20 uint32_t duration1; // 0x24 uint32_t duration2; // 0x28 uint32_t duration3; // 0x2C } fpga_ledctlr_regs2_t;
Cortex-M4FからFPGAへデータライトするには
#define FPGA_PERIPH_BASE (0x40020000) // 中略 fpga_ledctlr_regs_t* pledctlr_regs = (fpga_ledctlr_regs_t*)(FPGA_PERIPH_BASE); pledctlr_regs->color0 = 0xXX; // 任意の数
のように変数に代入するだけでOKです。
Cortex-M4FへFPGAからデータリードするには
#define FPGA_PERIPH_BASE (0x40020000) // 中略 int32_t read_data; fpga_ledctlr_regs2_t* pledctlr_regs2 = (fpga_ledctlr_regs2_t*)(FPGA_PERIPH_BASE); read_data = pledctlr_regs2->colors; // read_dataに格納
これも簡単です。インターフェース部分が理解できてきました。
この情報をもとに、以下の方針で修正していきます。
pledctlr_regs->color0 = 任意の数値; ↓ FPGAのcolor0レジスタに反映 ↓ color0の下位4bitを{p3_o,p2_o,p1_o,p0_o}に反映 ↓ GPIOに反映
まず、qf_advancedfpga/fpga/
の部分を作成中のプロジェクトqf_wbfpga_pio
にコピーし、makefileを修正します。
makefileの修正は本質ではないものの面倒で時間が取られる部分です。修正後のプロジェクトをgithubに上げております。詳細はこのセクションの最後をご覧ください。
RTL修正部分:詳細は割愛しますが、レジスタ
color0
を4bit幅に直し、出力ポートp0
,p1
,p2
,p3
を追加するコードを追記しました。color0
,p0
,p1
,p2
,p3
といったキーワードでfpga/
ディレクトリをgrepするとわかると思います。GPIOに反映するロジックは簡単です。Wishboneクロックに同期して出力を反映しているだけです。
fpga/rtl/LED_controller.v
reg [3:0] port_l; always @(posedge rst or posedge clk) if (rst)begin port_l <= 4'h0; end else begin port_l <= color0; end assign p0 = port_l[0]; assign p1 = port_l[1]; assign p2 = port_l[2]; assign p3 = port_l[3]; endmodule
ピンへの反映
p0~p3までの4つの出力をIOに反映します。
fpga/rtl/quickfeather.pcf
を記述します。
*.pcfは
set_io 信号名 「このパッケージでのピン番号」または「IOエイリアス(IO_10など)」
という文法で記述します。
使用しているボード(QuickFeather)に搭載されているのはQFN(PU64)パッケージです。
set_io p0_o 40 set_io p1_o 42 set_io p2_o 37 set_io p3_o 28
したがって、BGA(PD64)パッケージの場合は以下のように記述します。
set_io p0_o E7 set_io p1_o D7 set_io p2_o G8 set_io p3_o H5
WCLSP(WR42)の場合は割愛します。
冒頭で述べた懸念点です。どこのFBIOでも指定できるはず、と述べたのですが、以下のピン番号は属性がIOTYPE=SDIOMUX
であり、IOに指定するとエラーが発生し論理合成を終えることができませんでした。このピンを避けた方が良いと思われます。
QFN(PU64)パッケージ: 22,21,20,18,17,15,16,11,13,14,10,9,8,7 番ピン
詳細は、Symbiflow Installation Guide and TutorialのPcfの記述方法をご覧ください。
Cソースコードの記述
FreeRTOSが移植されているので、そのまま使いました。
必要な部分のみの抜粋です。
#define FPGA_PERIPH_BASE (0x40020000) fpga_ledctlr_regs_t* pledctlr_regs = (fpga_ledctlr_regs_t*)(FPGA_PERIPH_BASE); fpga_ledctlr_regs2_t* pledctlr_regs2 = (fpga_ledctlr_regs2_t*)(FPGA_PERIPH_BASE); void vTask1(void *pvParameters); void vTask2(void *pvParameters); int main(){ xTaskCreate(vTask1,"Task1", 100, NULL, 1, NULL); xTaskCreate(vTask2,"Task2", 100, NULL, 1, NULL); vTaskStartScheduler(); while(1); } void vTask1(void *pvParameters){ while(1){ pledctlr_regs->color0 = 0x05; vTaskDelay(500); pledctlr_regs->color0 = 0x0a; vTaskDelay(500); } } void vTask2(void *pvParameters){ while(1){ vTaskDelay(1000); dbg_str("\r\n\r\nRead data from FPGA=0x"); dbg_hex32(pledctlr_regs2->colors); // データリード } }
1つ目のスレッドvTask1でFPGAのメモリ空間の指定した領域へ500msec毎に0x05、0x0Aを記述しています。
2つ目のスレッドはUARTに1秒間隔でFPGAからリードしたデータを(=0xdeadbeefになるはずです)表示しています。UARTは115200baudで、QuickFeather基板の場合J3ピンヘッダの2(TX),3(RX)にUSB-シリアルを接続する必要があります。
動作風景
プロジェクト
qorc-sdk/qf_apps
フォルダで、以下のプロジェクトをcloneしてください。
github.com
$ git clone -b v0.0.1 https://github.com/panda5mt/qf_wbfpga_pio.git
デバイスのパッケージについて(QFNは売ってない?)
2021年8月現在、Mouserで評価ボード、チップ単体を入手できます。
www.mouser.jp
BGA(PD64)、WCLSP(WR42)、QFN(PU64)の3タイプのパッケージがありますが、QFNは残念ながら量産計画はなく、評価ボードのみの供給になるようです。
EOS-S3データシートのp.98。
NOTE: The QFN pinout information is provided as reference for QuickFeather board only. The QFN package is not in mass production.
このPD64パッケージの基板を製作したので、後日記事にできればと思います。
intelHLSプロトタイプの設計(Cyclone10GX基板の設計)
最近では、安全運転支援のカメラ開発も乗用車から船舶向け、最近では庫内の運搬用群ロボットまで引き合いをいただいております。
ありがとうございます。
開発スピード向上
上記のような簡易プロトタイプ用途としてCyclone10LPのボードを1年前に開発しました。基板単体を販売するほど販路やマーケが大きい会社ではないのでそのようなことはしていませんが、弊社の提供するサービスが動くハードウェアの一部として提供しており、すでにH社製ADASの補助、無人バイク制御などで活躍しています。
MATLAB/SimulinkをはじめとしたMBD開発に軸足を置いているため開発効率はそこそこ高い*1ものの、GPU開発に比べると遅いのも事実です。FPGAを利用した画像処理、深層学習、あるいは軽量NNの論文でも明らかなように1GFLOPSあたりの電力効率は2~10倍ほどアドバンテージはあり、単位処理能力あたりのデバイス単価は圧倒的に安いですが、開発速度がおそいと研究開発の分野では厳しいものがあります。
開発初期のプロト以降では開発コスト高めのFPGA内部はどうしても固定化されてしまう問題がありました。このためFPGA内部の開発も流動化できるような高位言語プロトタイプFPGAボードを設計しました。
高位言語を使おう
上記の問題はCPU/GPU/FPGAのアーキテクチャの差異によるものですし、intelが力を入れて取り組んでいる分野でもあるので、intel oneAPIが次第に解決してくれるだろうとぼんやり考えていたのですが、その橋渡しになるであろうintelHLSが、お世辞にも開発に使うには厳しすぎるだろというものでした。
例えば、VS2010が必要だったり、MAX10とかその規模でHLSいらないよね?みたいな状況でした。*2
この記事を書いている時点で、Quartus Prime Pro v21.1ではVS2017になっています。また、HLSが使用できるデバイスが、ALMアーキテクチャFPGAに限定されています。
やっとか。という思いもありますが…時間をかけてインテルのマーケが市場調査を行い、HLSを周回軌道に乗せたようにも見えます。
intel HLSについてはまた次の記事で扱います。
弊社の場合、HLSはHLAC特徴量の演算モジュール、FCNの畳み込み層の高速化モジュールの設計のみに使用します。
手っ取り早くAvalon-MM/STモジュールにすればこっちのもの。
もちろんいずれもHDLでの記述ができないものではありません。が、この分野は年々更新されるため新しい手法にも追いつきつつ検証、試行するにはHLSが不可欠となってきています。
デバイスの選定
Cyclone V EとCyclone10GXを天秤にかけました。
Cyclone10GXは(ほぼ)Arria 10と言えます。ウェハもおそらく同じものでしょう。フリーライセンスで使えるArria10。
フリーライセンスでDDR3/DDR4使う、高速トランシーバGXBを使うとなるとCyclone10GXが魅力的です。
また、車載に使う場合も考慮します。電力バカ食いされるとポンコツ欧州車でECUエラーになりやすいので、LEは控えめなデバイスを選定しました。(86kLE~105kLE)
対策すりゃいいんだけど、車載で電力消費が多いのはそもそも歓迎されません。車に収まるレベルの小さいバッテリーで動いてるわけだし。
その他のFA用途でも、消費電力は少ないに越したことはありません。
ちょうどCyclone10LPとロジック規模がクロスする、85kLE~105kLEのCyclone 10GXを採用しています。
Cyclone10GX基板、公式も含め評価ボードがすごく少ないんですよね。あと220kLEの最大規模のデバイスを採用している場合がほとんどで消費電力はそれなりに大きく、電源回路も電力消費も豪華。笑
基板設計も製造も今年は、(来年も期待はできませんが)設計サイド、量産サイドで考えると、気が遠くなるような芳しくない条件が重なります。
半導体の供給が遅れている
半導体がない、互換品ももちろん売り切れ、生産目処が立たないためベンダーは製品ラインナップから終息製品に突如移行。水晶発振器は旭化成の火災の影響で欲しいスペックのが突然のディスコン。今日あっても明日あるかわからない。
というわけで、設計始める前に100pcs~1000pcs単位で材料を集めました。ほんとこれからどうするんだろう。この設計スタイルはギャンブルでしかない。
今回、電圧も電流も異なる電源を複数必要としているため、本来であればそれぞれ最適な定格のものを選ぶのですが、大量に在庫しなくてはならない都合上、最大スペックに合わせています。電源ICもバリアントが増えると調達リスクが嵩みます。リスクとコストのギリギリを攻めています。
ロームさん、および代理店のご厚意により弊社でも実績のあるFPGA用超低リップルDCDCをサンプル提供いただきました。
また水晶発振器は調達の関係でルネサス、大真空、京セラとすべて国産品になってます。
ハーネスが資材供給の遅れから作れない
また、ハーネスの問題も無視できません。如何に深刻なのか、少し例をあげます。
以下は公然の秘密で、スズキ本社に問い合わせたところ「隠しているつもりはない、書いて構わない」とのことでしたので例を挙げます。
・ワゴンR
・SWIFT
これらは2021年春にフルモデルチェンジの新型が出る予定でした。ワゴンRはスライド式自動ドアがつきます。今までのワゴンRの立ち位置はハスラーが今後担います。(荷室が大きい、普通の4ドア)
SWIFTは5ナンバーを死守しつつの完全なモデルチェンジでした(除くスイスポ)。
SWIFTに関しては5年が経過しており、全車速対応の車線変更支援などADASを強化した完全モデルチェンジが出る予定でした。またADASのメインはデュアルカメラでしたが、こちらも挑戦的な変更がされるはずでした。我々アフターにも他に色々情報だけきておりますが、言って良いのはここまでとのことです。
これに関して、もうカタログまで印刷、プロトは倉庫にある状態です。取りやめたのは、
実は1番目は今年中に解決されそうです(また火災が起きたりしなければ)。B社も今年中にはどうにかしてくれそうとのこと。ですが、3番目が来年末にも解決が難しそうとのこと。
すくなくとも今年は専用ハーネス作ったり、設計するのは本当にやめた方がいい
あ、そうそう。ジムニーの納車が遅いのは仕様です。別に特段遅れてはいないそうですよ。うちもまだ来ないんだけど。。。
というわけで、LVDS、高速トランシーバともにUSB Type-Cコネクタを設け、そこから入出力しています。
このようにすれば、大量に家電量販店に出回っているUSB Type-C(USB3.0用)ケーブルを流用できます。残念ながらUSBとしてのプロトコルスタックは用意するつもりがないので互換性はありませんが、事故を避けるため、cc端子は5.1kΩでプルダウンして、5V,3AをUSB PDに則って取得しています。Type-C対応のモバイルバッテリーがあれば給電できます。15W有れば今回使用するCyclone10GXの電力を十分賄えます。
FAギョーカイだとよくやるパターンですね。USB3に対応しているType-Cコネクタを使用すると最大で、差動高速トランシーバTX2ch、RX2ch、差動1ch、GPIO2chを引き伸ばす事ができます。もちろん、そのようにすると代償としてコネクタの表裏が決まってしまうのでそこは対策する必要があるかもしれません(表裏逆の場合は給電止めるなど)。PCIe Gen2 x2を引き回すのに重宝しますね。
基板
仕事後にダラダラ設計やってたので1ヶ月くらいかかってますね。。
Raspberry Pi 3Aと同じフットプリントにしました。前回のCyclone10LPと同じく、Raspiからコンフィグできます。
部品面
ハンダ面
6層基板です。ドリル径0.3、L/S=5mil、内層クリアランスは10milとしました。2018年ごろから中国、台湾のPCB製造業社でも外層3mil/内層4milくらいまでは可能になってきました。以前は国外のPCB製造業社だと内層はとんでもなくL/Sを緩くしないといけないイメージでしたが、良くなっていますね。板厚は1.6mmくらいであれば、ドリル径0.3mmもあれば特注扱いにはならないと思います。
DDR3Lを搭載しました。データバスはインテルのガイドを参考に3.5mm以上ズレる場合だけ配線長を調整しています。
下記は90%程配線が終わった図。
ベタを貼ると内層は見えなくなってしまいますがこんな感じ。
Cyclone10GXの5つのバンク*3をフルに使い、4つの機能に分けています。
- GPIO
- Raspberry Piとの通信用です。これのために1.8V(FPGA)-3.3V(RasPi)間でレベル変換を使用しています。
- LVDS
- ディスプレーなど、低速ではないものの引き回す必要がある通信用
- DDR3L
- GXB
- 言わずもがな、高速差動信号用です。車載、FAのフィールドで、以下の使用を想定しています。USB Type-Cケーブルを使って引き回します。
- PCI Express Gen.2 (x2~)
- SFP+
次回は、基板完成して、到着しているといいな。
参考
外部メモリコントローラIPについて(intel)
https://www.intel.com/content/dam/www/programmable/us/en/pdfs/literature/ug/ug-20116.pdf
外部メモリのAW時の注意など(intel)
https://www.intel.com/content/dam/www/programmable/us/en/pdfs/literature/hb/external-memory/emi_debug_hw.pdf
電源やコンフィグピンの扱い(intel)
https://www.intel.com/content/dam/www/programmable/us/en/pdfs/literature/dp/cyclone-10/pcg-01022.pdf
降圧コンバータのPCBレイアウト手法(ROHM)
https://fscdn.rohm.com/jp/products/databook/applinote/ic/power/switching_regulator/converter_pcb_layout_appli-j.pdf
推奨されるUSB Type-C PCBレイアウトについて(Microchip)
https://microchipsupport.force.com/s/article/USB-Type-C-Layout-Recommendations