« 2020年11月 | トップページ | 2021年1月 »

2020.12.31

2020年を振り返って

今年の1年間をふりかえってみます。

目標は、「Facebookをがんばる」「レスポンシブデザインをがんばる」とのことでしたが、ほとんどやってませんね🤣

Facebookは緊急事態宣言が出てから、自宅にこもらなければならず、せめてもの楽しみとして作った料理をひたすらアップしたけど、仕事の記事は全然書けませんでした。レスポンシブデザインもすっかりわすれてしまっていました。

何をやっていたのか1年を振り替えってみると、

1月

  • Trenzサイトの高機能化
  • Cosmo-Zにユーザ回路を追加する方法の文書化
  • DAC8ボードの設計
  • GOWINのTEC0117ボードの取り扱い開始

2月

  • Spartan-7ボードでMicroblazeやEtherを動かしたりなど
  • Cosmo-ZのCorruptedエラーの解決

3月

  • Trenz社製品のパラメトリックサーチを開発
  • MPSSE-JTAGの開発
  • Cosmo-Z 64ch版を開発

4月

  • MAX1000とCYC1000の取り扱い開始
  • Artix-7のPCI Express基板の設計開始
  • Cosmo-Z 64chのGTXリンクが切れる

5月

  • Unityでガーバデータを探検するプログラムを作成
  • C#でUSBとインタフェースしたり、ビットマップを描いたりする方法の勉強

6月

  • 何もしていない

7月

  • JTAGチャレンジ基板の販売開始
  • Cosmo-Z Miniのソース公開

8月

  • SmaryLynqの解析
  • DAC出力用LCフィルタの研究

9月

  • 万能フィルタ基板の開発
  • Spartan-7ボード用デバイスドライバで署名エラーが出る件
  • Artix-7のDDR3メモリの最高アクセス速度をAXIインタフェースで測る
  • Cosmo-K DVIの製造不良の原因を特定
  • Cosmo-K DVIのデザインの再設計開始
  • 大事にしていた信号発生器が壊れる

10月

  • 特電お客様サイトへの登録方法を改善
  • ZYNQでECCメモリを使う方法を解明
  • 受託開発の基板をずっと開発

11月

  • ジャンクション温度について解説
  • FPGAとDDR3メモリのインタフェースのタイミングについて解説
  • LDOにダイオードの並列は必要かを解説
  • 7シリーズのクロッキングリソースについての理解
  • Como-K DVIでHDMIの開発

12月

  • VHDLの高度な使い方について解説
  • DC-DC(オンボードスイッチング電源)のノイズ対策
  • FT2232HとSYNC FIFOをAXIに変換する
  • Spartan-7ボードのSPI ROMを交換する

 

あまり稼働率が高くなさそうです。特に、6月って何をしていたんだろう??

2018年に会社の規模を縮小して一人だけの会社にしてしまったので事務作業にとられる時間が半端じゃなく多くなっています。その分、事務員さんに気を使わなくていいのですが、やはり事務の人がいたほうがいいですね。

もし、事務をやってみたいという方がいらっしゃいましたらお声がけください。

 

来年もよろしくお願いします。

 

| | コメント (0)

2020.12.24

Spartan-7ボードのSPI ROMを取り替える

特電Spartan-7ボードにはWinbondのSPI ROMが乗っているので、昨日のブログに書いたような理由から、VivadoではROMへの書き込みができません。

特電Spartan-7ボードはUSB-JTAGの機能が搭載されていて弊社のアプリから高速にROMの書き換えができるのですが、お客様からVivadoから書き込みたいとの要望をいただいたので調査してみました。

下の写真はSpartan-7ボードとTrenz社のUSB-JTAGアダプタ「TE0790」を万能基板に乗せた写真です。この組み合わせで実験をします。

Sp7

この基板のSPI ROMを、手元にあったISSIのIS25LP128Fに乗せ換えます。

TE0790はDigilent互換なのでVivadoからそのまま認識されます。

Spi1_20201224195402

認識されたら正しいSPI ROMを選択します。

Spi2_20201224195501

チェックボックスを付けて・・

Spi4

無事に書き込みできました!

Spi6

このまま、手元にあったSpansionのS25FL128Lに乗せ換えてみると、

Spi3

「IS25LP128FではなくS25FL128Lが乗ってるぞ!」と怒られてしまいました。

ちゃんとIDCODEチェックはしているみたいですね。

 

Vivadoに対応したSPI ROMを使えば問題なく書き込みができることと起動することは確認しました。これまでに特電のSpartan-7ボードをお買い上げいただいた方で、ご希望の方には無償でROMの交換をいたします。メール等でご連絡の上、着払いでボードをお送りください。

また、このROMは8ピンSOICでそれほどはんだ付けは難しくないので、自社で交換することをご希望の方には交換用のSPI ROMを送らせていただきます。

 

| | コメント (0)

2020.12.23

Vivadoで未対応のSPI ROMに何とか対応したい

VivadoからJTAGを通じてSPI ROMに書き込む際に、どんなSPI ROMでも書き込めるわけではなく、非対応のSPI ROMというのも多くあります。その中でもとりわけWinbondのROMは非対応のようなのですが、何とかして書き込めないかもがいてみました。

まず、JTAGでFPGAとつないだら、デバイス名をクリックしてAdd configuration memory deviceを実行します。

Spiadd_1

ここにいろいろな候補が出てくるのですが、Winbond系はないのです。

Spiadd_2

とりあえず容量が同じ適当なデバイスを選んで書き込んでみようとしても・・

[Labtools 27-2251] Unable to read device properties. Please make sure that the proper configuration memory part is selected.

Spiadd_3

と言われて書き込むことができません。

XILINXのVivadoフォルダの中をいろいろ探してD:\Xilinx\Vivado\2019.2\data\xicomフォルダにあるxicom_cfgmem_part_table.csvというファイルに対応しているSPI ROMのリストがあったので、ここに1行書き加えてみました。

Spiadd_4
これでもう一回Vivadoを起動しなおしてみると、デバイスを追加することはできました。

Spiadd_5

しかし、実際に書き込んでみても、中身のアルゴリズムがコピー元のISSIやSpansionであるため、IDCODEが違うといって結局は書き込むことはできませんでした。

これだけ大量のデバイスのIDCODEがどこのファイルに書かれているかということですが、おそらくD:\Xilinx\Vivado\2019.2\data\xicomのspi.cfgだと思います。

このファイルには

S25FL032P_PROPERTY  = {
SUPPORTED = true;
X2_READ = true;
X4_READ = true;
USE_RES = true;
IDCODE = 15;
READ_STATUS_ENABLED = TRUE;
SECTOR_SIZE = 65536;
WRSR_MAXWAITTIME = 50000;
STATUS_REGISTER_LENGTH = 16;
PROGRAM_WAITTIME_INC = 1;
PROGRAM_WAITTIME = 1500;
PROGRAM_MAXWAITTIME = 3000;
BULKERASE_WAITTIME = 25000000;
BULKERASE_MAXWAITTIME = 192000000;
DEV_MANUFACTURER = "Spansion";
DEV_FAMILY = "S25FL";
DEV_RANDOM_CODE = "KTNLB";
FMY_RANDOM_CODE = "QJYPT";
SPARTAN3E_SUPPORTED = FALSE;
VIRTEX7_SUPPORTED = FALSE;
QVIRTEX7_SUPPORTED = FALSE;
DEVICE_SIZE = 33554432;
};
・・・

という定義が延々と続いていてそれっぽいのですが、Spartan-7に関する記述がないのと、spi.cfgにそれっぽく新規デバイスを追加しても書き込みはできないので、spi.cfgは使われていないのではないかと思っています。

ファイルの日付も微妙に古いので、spi.cfgに代わるファイルがどこかにあるのではないかと考えています。

ISEとiMPACTだとIDCODEチェックをスキップして書き込むことはできたのですが、XILINXのアンサーやフォーラムによれば「Winbondは対応していない、IDCODEチェックをスキップする方法は用意されていない」とはっきり書かれているので、やはり対応できないのが本日の結論です。

 

| | コメント (0)

2020.12.20

FTDIのSYNC FIFOからAXIを経由してDDR3アクセス

FTDIのSYNC FIFOをAXI化するコアをBRAMアクセスで徹底的にデバッグしてから、AXI-BRAMの代わりにInterconnectとMIGを入れてDDR3メモリにアクセスすることに成功しました。一発OKでした。AXIって便利。

Ft_axi_ddr3

テストプログラムで速度を測定。

Axi_ddr3_inout

DDR3メモリにアクセスしている間のFPGAのピンの動きをMITOUJTAGで見てみましょう。

Ft_ddr3_bscan

FTDIのUSBバスに書き込んでいる(赤)間はDDR3が読み出し(緑)になって、USBバスが読み出しの間はDDR3が書き込みになっているのがわかります。

 

 

| | コメント (0)

2020.12.19

FT2232HとSYNC FIFOのAXI化

いろいろ苦労して、ようやくFT2232HのSYNC FIFOをAXI化するコアができました。

出てきたAXIにBRAMをつないで読み書きのテストをします。

Ft_sfifo

OUT方向、IN方向ともに38MB/sくらいの速度が出ています。

Ft_sfifo2

AXIのクロックはUSBCLKOUTの60MHzのままなので、実用的にするにはAXI Interconnectを通さなきゃいけませんね。

FTDIのInTransferSizeは最大64kByteとのことなのですが、1Mバイトくらいに設定しても動いてしまうし、そのほうがスループットも高いので、いったいどうなんでしょう。

 

| | コメント (0)

2020.12.16

FT2232HのSYNC FIFOの通信速度を測ってみた

FPGAとFT2232Hを接続し、速度を測ることに成功しました。

Ft2232h_speedtest_20201217105601

OUT転送は38MB/sで、IN転送は44MB/sでした。

CypressのEZ-USB FX2とほぼ同じです。どちらもUSB2.0の規格の上限ぎりぎりといえます。

 

大きなサイズの転送をしようとして苦労したポイントを2つほど述べます。

まず、FT2232HのIN転送のサイズは65536バイトで区切られますが、DLLに与えるバッファの大きさは4194304バイト(4Mバイト)までのようです。

多くのデバイスドライバがこの程度のサイズに制限されているので驚くことはありませんが、カーネルモードとユーザモードの遷移には10usくらいの時間がかかるので、できるだけ大きなサイズのバッファで渡したほうがいいですね。32Mとか64Mの転送をするなら、ユーザプログラムの中で小分けにして転送します。

それからもう一つ、C#にはmemcpyがないので、自分で作るしかありません。

private void memcpy(UInt32[] target, UInt32[] source, uint offset, uint count)
{
// offsetもcountもバイト単位
for(uint i = 0; i < count / 4; i++)
{
target[i] = source[i + offset / 4];
}
}
private void memcpy(Byte[] target, UInt32[] source, uint count, uint offset = 0)
{
// countはバイト単位
for (uint i = 0; i < count / 4; i++)
{
UInt32 s = source[i + offset / 4];
target[i * 4 + 0] = unchecked((byte)((s >> 0) & 0xff));
target[i * 4 + 1] = unchecked((byte)((s >> 8) & 0xff));
target[i * 4 + 2] = unchecked((byte)((s >> 16) & 0xff));
target[i * 4 + 3] = unchecked((byte)((s >> 24) & 0xff));
}
}

C言語みたいに

int wbuf = new int[1048576];
・・・ unsigned char *p = (unsigned char*)&wbuf[256];

みたいなことができればバッファのコピーをしなくていいのですが、C#だとこういうことはできなさそうです。

やはりバッファをコピーしなければならないのでしょうか。intとuintの変換でさえ怒り出すコンパイラを相手にするのは疲れます。型に厳しすぎるのがC#の生産性を下げていると思うのですが、どうでしょうか。

 

| | コメント (1)

2020.12.15

FT2232H操作ステートマシンを作る

FT2232HをFPGAとつないでSYNC FIFOモードでインタフェースできるような回路を作っています。

ポイントとなる点はいくつかあって、まず、ソフトウェアでSYNC FIFOモードを設定してあげないとUSBCLKが出力されないので、USBCLKをFPGAのクロックに使っている場合は注意が必要です。

それから、SIWUは平常時はHにしておかないとFT2232HのTXEやWRがうまく動作しません。

こういった点に注意しながらステートマシンを設計していきます。

Ft2232h_statemachine

基本的にこのICはUSB-UARTなので、8bit単位でデータが送られてきます。FT2232HとFPGA間の配線は起動時にどういう状態になっているかは保障できないので、開始時点でFIFOにどんなデータがたまっているかは予測が付きません。そのため、FT2232Hから送られてくるデータの先頭にはゴミが入ることを前提として設計していきます。

PC上のユーザプログラムは、FPGAに接続されたDDR3メモリや内部レジスタにアクセスするわけなので、アドレス、長さ、IN/OUTの区別などが必要ですが、FT2232Hは8bitのデータを1バイトずつ読み書きするだけなので、自分で上位のプロトコルを考えなければなりません。

特定のキーワード(ここでは0x12345678)を受けたらWRITE開始、0x12345679を受けたらREAD開始としましょう。FPGAの出口はAXI4にするので、32bit単位でデータをグループ化する必要もあります。キーワードの次には、アドレス(32bit)と長さ(32bit)を受け取るというステートマシンにしました。

ADDRやLength、データを送受信するステートでは0~3まで数えるカウンタを用意して、4発目で次に進めるようにしています。

SIWUは最後のデータを送り出した後でトグルしないといけないようです。そのため、2クロックほどの待ち時間を入れています。

32バイトの書き込みを行う波形を示します。↓

Write_32bytes

32バイトならFT2232Hからデータシートどおりの波形が送られてきますが、512バイト以上になると少し変わってきます。

Write_512

510バイト目の書き込みの際にTXE#が突然立ち上がります。これはデータを受け取れないことを意味します。そのため(1)の部分で出している0xFEは受け取られていません。WR#は下げっぱなしでもいいのですが気持ち悪いのでHに戻すことにします。

(2)の部分で再びTXE#が下がるのでWR#をLに下げ、2つのデータが受け取られます。データが受け取られたと思ったら次のクロックではWR#は素早くHに戻しておきます。

おそらく、FT2232Hは510バイトをひとまとまりにしてデータを送っているのでしょう。FT2232Hが管理するフィールドが2バイトあるようです。

FPGAの設計としては、受信したTXE#と自分が出したWR#のOR(つまり負論理のAND)で1バイトの送信として扱い、受信したRXF#と自分が出したRD#のOR(つまり負論理のAND)で1バイトの受信として扱う必要があります。

大きなサイズの転送をするようになるとこれらのハンドシェイクが意外と大変なので、ステートマシンとしては150行程度になりました。長いのでブログには書けません。

 

| | コメント (0)

2020.12.14

FT2232とFPGAの接続

新しく作った基板でFTDI社のUSBチップFT2232HとFPGAを接続しているのですが、予想外に苦労しました。

同じ轍を踏まないように、ハマったポイントを整理しておきます。

FT2232Hは、デフォルトではUSBシリアルとして動くようですが、ASYNC FIFOやSYNC FIFO、MPSSEなど様々なモードで動作させることができます。最高速度で動かすにはSYNC FIFOにする必要があるのですが、CLKOUTという端子からクロックが出てこず、SYNC FIFOになっていないような様子でした。

どうやって切り替えているのかというと、EEPROMの内容とソフトウェアで行うようです。

具体的には、C#でなら、

FTDI ft = new FTDI();
FTDI.FT_STATUS stat = ft.OpenByIndex(0);
byte mask = 0xff;
stat = ft.SetBitMode(mask, 0);
stat = ft.SetBitMode(mask, FTDI.FT_BIT_MODES.FT_BIT_MODE_SYNC_FIFO);

とすることで、切り替えができます。

これを行うにはEEPROMにあらかじめ245タイプのFIFOである旨を書いておかなければなりません。EEPROMがない状態でいくらこのコードを実行しても、正しくは動作しません。

Ftprog_245

しかしながら、作った基板ではFT_Progでプログラミングしようとしても”Programming Failed Has the device been removed?”というエラーが出てしまうのでした。

Ft_error

原因は、使用しているEEPROMの構成でした。

いつものように基板を小さく作らなければならないので、Microchip社の93AA56AT-I/OTというSOT-23の6ピンのROMを使ったのですが、この93AA56ATは8bit構成なのです。

93aa56

FT2232Hのデータシートをよく見たら、16bit構成のROMを使えと書いてあります。

Eeprom

93AA56も8ピンのICならORGという端子があって8bitと16bitを切り替えらるのですが、6ピンの93AA56AT-I/OTにはORG端子がないので、製造の段階で8bitに固定されてしまっているようでした。

Org

万事休すかと思ったのですが、93AA56ATという型番は8bit、93AA56BTという型番は16bit、93AA56CTという型番は8bit/16bit切り替えであることに気づき、93AA56BTを使えばよいことに気が付きました。

93AA56BTまたは8pinの93AA56CTが必要なのですが、深夜で秋葉も開いていないので、手近にあった他社のUSB-JTAGをバラして93AA56BTを取り出して本基板に実装することでFT_Progで書き込むことに成功しました。

これでFT2232HのPortAが245タイプのFIFOに設定できたので、冒頭のプログラムを実行してみたところ、ASYNC FIFOからSYNC FIFOに切り替えるとCLKOUTが出てくることが確認できました。

Async2sync

それからFPGAに簡単なステートマシンを入れて、#RXFが立ち下がったら#OEと#RDを下げて4バイトのデータを受信するということに成功しました。(データは00 01 02 03)

Fiford

FPGAの中から外に出る、あるいは外から中に入るには数nsの遅延があるはずなので、次のクロック的に間に合うかという心配はあったのですが、特に調整しなくても問題なくインタフェースできました。

 

| | コメント (0)

2020.12.13

DC-DCからのノイズ対策

引き続きDC-DCがOPアンプに及ぼすノイズの原因究明を行っていますが、作ってしまった基板ではノイズに敏感なラインの横にDGNDが走っていて、これはもう変えることができません。それでも何とかノイズを減らせないかと考えています。

OPアンプはボルテージフォロアなのですが、いろいろ抵抗を付けたり外したりしているうちに、OPアンプの+入力に3kΩくらいの抵抗を付けるとノイズが激減することがわかりました。

また、各OPアンプに共通しているラインをCRでGNDに落とすことも少し効果がありました。

 

Noise4

OPアンプの+入力に抵抗を入れることがなぜノイズ対策になるのか、謎です。

電磁界を拾ってしまうループアンテナのような構造をこの抵抗が切っているのではないかと推測されます。

 

| | コメント (0)

2020.12.12

Cosmo-K DVIにヒートシンクを取り付け

Kintex-7のHDMI 2入力、2出力ボード「Cosmo-K DVI」をデモ機として発送するため、専用のヒートシンクを取り付けました。アルミの削りだしで作られていて、FPGAとDDR3メモリにぴったりフィットするようにできています。

Kdvi_hs1

このボード、ヒートシンクを付けてもすごく熱くなるのですが、DDR3メモリの速度を下げることで70℃未満にできることが確認できました。DDR3メモリの速度を下げると、MIGから出てくるui_clkの速度も下がってしまいます。もしui_clkですべてのAXI関係を動かしているとVideo IPに入るところのAXI Streamの速度も下がってしまいます。

HDMI 1080pの出力は148.5MHzなので、この速度で24bit RGBを出さなければなりませんから、DDR3メモリがいくら早くても、AXI Streamの部分が遅いと追いつかないんですね。このことに気が付かずに速度を出したら「出力がロックしない」という現象に悩まされていました。

Video_in

AXI4-Steram to Video OutコアにはPixel Per Clockという設定があって、ここを増やせばクロック速度は遅くてもデータバスの幅を増やして対応できそうですが、そこまでやる気力はもうありません。

Ppclk

それから、この基板(Cosmo-K DVI)は2018年に作った特定のロットでは周波数特性が悪くて、HDMI 1080pの同時が入力できない、と思っていたのですが、どうやらその考えは間違っていたようです。

HDMI入力1と2を入れ替えたら、いままで1080pが受信できないチャネルで受信できるようになり、受信できたチャネルで受信できなくなったのです、

Kdvi_hs3

原因としては、1つのI/O bankに2つのHDMI入力がつながっているのですが、1つのbankにMMCMは1つしかないから、2個目のHDMI入力は隣のbankのMMCMを借りてくることになります。隣のMMCMに行くにはクロックが長距離を走ることになり、その分の遅延が多くなるため1080pでの受信が不安定になるのだろうというわけです。

Kdvi_hs2

昔、4年前の初期ロットのころはVivado 2016だったので、今とは配線が何か違っていてたまたま1080p 2chで動いていたのかもしれません。

| | コメント (0)

2020.12.11

DC-DCのノイズはどこから乗って来るのか

私はOPアンプの用の正負電源にRKZ-0505DというDC-DCをよく使っているのですが、DC-DCの出すノイズにはいつも悩まされています。

RKZ-0505Dは5V入力から±5Vを作ってくれる絶縁型のDC-DCで、とても便利なのですが、このDC-DCを使った回路は大きなノイズに悩まされることがあります。ただし、必ずノイズが乗るわけではなく、ノイズが乗らない場合もあります。

今、下の図のような回路を作っています。

もちろん、DC-DCの出力は大きなコンデンサやコイルでがっちりとフィルタしています。

Noise1

↑のようなOPアンプのただのバッファ回路なのですが、OPアンプがたくさんあるので距離が長くなってしまうことと、電源容量が不足するのでDC-DCを複数使っているところがポイントです。

各OPアンプには共通のオフセット電圧を加えることができるようになっています。ところが、このオフセット電圧を与える信号線がディジタルGNDと並行しているためか、ノイズの影響を受けてしまって、出力に10mV程度のスパイク状のノイズ(数十ns)が出てしまうのでした。

 

このノイズに数か月悩んできましたが、別のDC-DCを使って±5Vのラインをビニル電線で遠方から引いてきた場合、ノイズが激減するということが確認されました。

Noise2

しかし、近傍の+5V電源から取った場合、再びノイズが乗ってしまいます。

この結果からわかることは、RKZ-0505Dが悪いのではなく、一般的にDC-DCのノイズというのはDC-DCの電源入力側からもまき散らされるということです。DC-DCの出力をLCでフィルタしてもダメだということです。

Noise3

また、DC-DCの入力に流れる電流が直接ノイズとして乗るのではなく、スパイク状の大きな電流が流れることによって磁界が発生して、その磁界を拾ってしまうことでアナログ信号にノイズが乗るのだということまで推測できました。

ただ、ビニル電線を基板に近づけると再びノイズが乗るようになるので、どういう経路なのかよくわかりませんが、電磁誘導なんだろうなということはわかります。

回路的にはDC-DCの入力側には大きなノイズが乗るようなので、これは他の電源と分離して交わらないようにして、ボードの根本まで戻すべきなのでしょう。

DC-DCを使った回路でノイズが乗る場合もあるし、乗らない場合もあるのですが、こういったレイアウトが大きく関係しているのだろうと思われます。

| | コメント (0)

2020.12.09

VHDLの高度な使い方(5) ~typeとsubtypeとpackage~

私は多チャネルの計測ボードなどを良く作るのですが、C言語でいうところのtypedefのように新しい型を作りたいときがあります。

例えば、ADCのデータが20ビットで、それが8チャネルあるような場合、

signal adcdata0 : std_logic_vector(19 downto 0);
signal adcdata1 : std_logic_vector(19 downto 0);
・・・

みたいなことを延々とやっていると見通しが悪くなるので、束ねたくなります。

そんなときに便利なのがtypeとsubtypeです。どっちがどっちだかいつもわからなくなるので、このブログにまとめておきます。

subtype adcdata_t is std_logic_vector(19 downto 0);
type adcdata_array_t is array (0 to 7) of adcdata_t;

subtypeでstd_logic_vectorの任意の長さの型を作り、typeでその型の配列を作ります。

実際に使うときには、

signal adcdata0 adcdata_t;

として1つずつ宣言したり、

signal adcdataadcdata_array_t;
・・・
adcdata0 <= adcdata(0);

のようにインデックスでアクセスできたりします。

typeの他の使い方としては、ステートマシンのステートやメモリとかがありますが、ここでは割愛します。

subtypeはstd_logic_vectorの範囲を限定した新しい型を作るのに、typeは配列の作成に、と覚えたいのですがややこしいですね。subtypeは限定でtypeは拡張と覚えればいいのでしょうか。本当に、いつもどっちがどっちだかわからなくなります。

 

では、このadcdata_tという新しい型をport に使うにはどうすればいいでしょう。architecture文の中なのでentityやcomponentのport宣言には使えません。そこで、パッケージ化します。

具体的には

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
package typedef is
constant ADC_NUM : integer := 8;
subtype adcdata_t is std_logic_vector(19 downto 0);
type adcdata_array_t is array (0 to 7) of adcdata_t;
end typedef;
package body typedef is
end typedef;

という新しいVHDLファイルを作っておいて、Vivadoのプロジェクトにソースとして登録します。ここではファイル名はtypedef.vhdとします。

この型を使いたいソースファイルで、

use work.typedef.all;

を付ければOKです。use文はentity文よりも前にあるので、このtypeやsubtypeが使えるというわけです。

entity meastop is
Port (
clk : in std_logic;
reset : in std_logic;
adcdata : in adcdata_array_t;
run : in std_logic;
threshold : in adcdata_t;
・・・

と書きます。

これでモジュール化やパラメタライズがずっと楽になります!

 

| | コメント (0)

2020.12.08

VHDLの高度な使い方(4) ~entityのインスタンス化~

VHDLで書いていて面倒くさいなと思うのは、サブモジュールで作ったポートの定義をもう一度メインモジュールでcomponentとして定義しなければならないことです。

つまり、coreという名前のサブモジュールを作ったとしましょう。

entity core is port (
clk : in std_logic;
sw_i : in std_logic;
led_o : out std_logic_vector(7 downto 0)
);
end core;

これをメインから呼び出して使うときに、

component core port (
clk : in std_logic;
sw_i : in std_logic;
led_o : out std_logic_vector(7 downto 0)
);
end component;

という宣言を一回書かせてから、

inst_sub : core port map (
clk => clk,
sw_i => sw_i,
led_o => led_o
);

と、port mapするわけです。

サブモジュールに新しい信号が追加されたら、component文の宣言中のポートも増やさなければなりません。

これが面倒くさいと感じる人は多いと思います。

そこで、VHDL-93で拡張されたentityのインスタンス化という手法があります。

inst_sub : entity work.core port map (
clk => clk,
sw_i => sw_i,
led_o => led_o
);

というふうに、インスタンスを作成するときに、"entity"を付けるだけでcomponent宣言が省略できるのです。 work.は、現在のプロジェクトのという意味なのでwork.も必要です。

component宣言を省略できるというメリットはあるのですが、いくつかデメリットもあります。

例えば、この方法では下位のモジュールが先に作られていないと上位のモジュールが書けません。だから、先に下位のモジュールを作ってコンパイルしておかなければならないというデメリットはあります。

従来通りのcomponentのインスタンス化かがよいか、entityのインスタンス化がよいかは、それぞれのケースや好みに応じて使い分けてください。

| | コメント (0)

2020.12.07

新規作成基板でDDR3メモリが動かず大ピンチ!

新しい基板を作ったのですが、最初の起動はいつも緊張します。

設計時にどれだけ念入りにチェックしていても、思いもよらないようなことが起きたりするものです。

もし基板の設計が間違っていたりしたら、100万円くらいの費用と1か月間があっという間に飛んでしまいます。納期も差し迫った案件で作ったこの基板ですが、なんと、DDR3メモリが起動しない!?そんな緊張が走りました。

Ddr3_20201211162401

普通ならDDR3が起動してui_clkが動き出すものですが、うんともすんとも言いません。ILAを使ってデバッグしようにもクロックが止まっていてはどうにもなりません。

基板が間違っていたのか、中のロジックが間違っているのかわからない。そんな初期状況で、MITOUJTAG(みとうジェイタグ)を使ってみました。

MITOUJTAGは、JTAGを使ってFPGAと基板のデバッグができるツールです。FPGAのデバッグならILAがあるじゃないかと思うかもしれませんが、ILAと違ってFPGAの中のロジックが動いていなくても使えるし、基板検査用のバウンダリスキャンツールとは違って、テストベクタは一切不要です。

まさにオシロスコープのような感覚で、測定器として見たい場所が見えるのですます。

 

最初に見たのが、FPGAの端子の状態。DDR3を動かしたらリフレッシュとかでチャカチャカと激しく動くはずなのに、DDR3_CK以外が全く動いていません。

Fpga_not_run

DDR3メモリの波形を見てみると、リセットを解除しても全く動いていないのがわかります。

Ddr_not_run

ui_clkとmmcm_initもinit_calib_doneもバウンダリスキャンで見てみると、すべてLのままでした。

キャリブレーションが完了しないということなのでDDR3メモリの配線が間違っていたかな・・と絶望的な気持ちにもなったのですが、ひとつおかしなことに気が付きました。mmcm_initもLなのです。このmmcmはMIGの中でCKp/CKnとIOクロックを作っているものなので、DDR3メモリの配線が間違っていても初期化はされるはずです。

mmcmが完了しないということは、リセットがかかりっぱなしと考えてよいでしょう。

そう思ってBlock Designの配線をチェックしたら、なんと、lockedがaresetnにつながっていて、sys_rstがProcessor System Resetにつながっていたのです。

Wrong_reset

ここが間違っていました。

正しくはsys_rstに初段のmmcmのlockedを入れて、aresetにはProcessor System Resetのinterconnect_aresetnを入れます。mmcmはロックするとL→Hとなるので、これを負論理のリセットとして使うのが常套手段です。

Correct_reset

再度論理合成したら、ばっちり動きました。

Ddr_ok

Fpga_run

いやぁ安心しました。

MITOUJTAGを使わなかったらデバッグに何日かかったでしょう。MIGのリセットがかかりっぱなしになっていることに30分くらいで気が付けてよかったです。

「基板が動かないかもしれない・・」と不安になる時間は1分でも短くしたいですね。

 

| | コメント (0)

2020.12.06

VHDLの高度な使い方(3) ~TEXTIOとREADで処理系を作る~

TEXTIOをREADできるようになると、VHDLでコンパイルをせずにテストベンチの実行パターンを変えることができるようになります。

TEXTIOのREADで使える関数は以下のようなものがあります。

procedure readline(file f : text; l : out line);
procedure read(l : inout line; value : out bit; good : out boolean);
procedure read(l : inout line; value : out bit);
procedure read(l : inout line; value : out bit_vector; good : out boolean);
procedure read(l : inout line; value : out bit_vector);
procedure read(l : inout line; value : out boolean; good : out boolean);
procedure read(l : inout line; value : out boolean);
procedure read(l : inout line; value : out character; good : out boolean);
procedure read(l : inout line; value : out character);
procedure read(l : inout line; value : out integer; good : out boolean);
procedure read(l : inout line; value : out integer);
procedure read(l : inout line; value : out real; good : out boolean);
procedure read(l : inout line; value : out real);
procedure read(l : inout line; value : out string; good : out boolean);
procedure read(l : inout line; value : out string);
procedure read(l : inout line; value : out time; good : out boolean);
procedure read(l : inout line; value : out time);

第三引数のgood : out booleanが付いているのは「成功するとtrueを返す」というバージョンの関数です。

これ以外に、8進数と16進数用に

procedure oread (l : inout line; value : out std_ulogic_vector);
procedure oread (l : inout line; value : out std_ulogic_vector; good : out boolean);
procedure hread (l : inout line; value : out std_ulogic_vector);
procedure hread (l : inout line; value : out std_ulogic_vector; good : out boolean);

があります。

これらの関数の使い方は、まずfile_openでファイルを開きreadlineで1行読み込みます。

MAIN_PROCESS : process
file F_SETTING : text;
begin
・・・
file_open(F_SETTING, "setting.txt", READ_MODE);

読み込んだ行は line型 という特別な変数に格納されるので、それを1文字あるいは1区切りずつread関数で読んでいくというやり方になります。

ファイルの終わりに達したかどうかは

while (not endfile(F_SETTING) ) loop
readline(F_SETTING, li);
read(li, param1);
・・処理・・
end loop;

のように、endfile関数を使って判定します。

読み込んだ字句はread関数で変数(variable)に格納されます。変数をif文やcase文などで判定したり、<=でsignalに出力することができます。

whileループを抜けるには、exit;を使うこともできます。使い方はif(条件) then exit; end if;です。

VHDLは型に厳密で、どのread関数が呼ばれるかは引数の型によって決まります。中でも難しいのはstring型です。string型として認識させるためには長さをあらかじめ固定して宣言しておかなければなりません。

variable str1 : STRINGS(1 to 16);

のように、です。固定長文字列になってしまいます。何もかもが面倒くさいですが、一度ライブラリを自分で作ってしまえば、Verilogに負けないくらい快適なシミュレーション環境になります。

テキストのコマンドファイルを読み込んで、動作を変えるコードの例を示します。長いコードの一部分の要点のみを端折って記述しているのでこのままでは動かないと思います。

procedure load_settings(
file F_SETTING : text;
signal clk : out std_logic; -- 操作したい信号
signal cmd : out std_logic_vector(31 downto 0) -- 操作したい信号
) is
variable li : line;
variable token : STRING(1 to 16); -- 長さを指定しないとSTRINGが作れない
variable cmd : STRING(1 to 16); -- 長さを指定しないとSTRINGが作れない
variable ParseStat : ParseStat_t; -- 語句解析のステート
variable good : boolean;
 ・・・
begin
good := true;
while good loop
read_str(li, token, good);
if(token(1) = '#') then -- コメント読み飛ばし
exit;
end if;
if(good /= true) then -- 行の最後に達していたら抜ける
exit;
end if;
case ParseStat is
when IDLE =>
if (strcmp(token,"END")) then -- ENDが来たら設定終了 
ExitSettings := true;
exit;
end if;
・・・
if (strcmp(token,"ADCVAL")) then -- ADCVALが来たら次の字句をintegerとして読み込み
read(li, ADCVAL, good);
ParseStat := READ_VAL;
end if;
・・・
end case;
cmd(15 downto 0) <= ADCVAL;
clock(clk);
end if;

残る問題は、文字列の比較をどうするかです。

if ( cmd = "READ" ) then

のようにすると、文字数と内容の両方が一致しなければならないので、うまく動きません。VHDLは文字列の一部にNULを入れても可変長文字列にならないので、そこで文字列は終了とならないからです。

ということは、strcmp関数を自分で作らなければなりません。

以下に私のつくったなんちゃってstrcmp関数を示します。AがNUL終端の可変長、Bが固定長を想定しているので万能ではありません。

function strcmp (A : STRING ; B : STRING) return boolean is
begin
for i in A'range loop
if(A(i) = NUL) and ((i < B'low) or (i > B'high)) then -- 同時に終了
return true;
end if;
if((i < B'low) or (i > B'high)) then -- Bが先に終了
return false;
end if;
if (A(i) /= B(i)) then
return false;
end if;
end loop;
return true;
end function;

for文を回すのがA'low~A'highではなく、A'rangeにしているのは、1 to 16でも16 downto 1でもどちらでも対応できるようにするためです。

 

さて、TEXTIOの使い方とそれを使いやすくするためのPROCEDURE文の使い方について3回にわたり解説してきましたが、いかがでしたでしょうか。次はシミュレーションではなく、VHDLで回路設計を便利にするテクニックについて解説していきたいと思います。

 

| | コメント (0)

2020.12.05

VHDLの高度な使い方(2) ~PROCEDURE~

VHDLでテストベンチを書いていると、テスト対象のモジュールに様々な条件を与えて試してみたいというときがあります。

ベタに書いていくと大変で読みづらくなるので、そういうときはPROCEDURE文を使いましょう。

PROCECUREを使うと、C言語でいうところの関数呼び出しのようなものを作ることができて、引数に応じて様々に動作を変えることができます。また、引数を出力にすると、関数呼び出しをするたびに指定した信号が動きます。

例えば、

process(・・・)
begin
SPI_CS(0, SPI_CLK, SPI_CS);
SPI_WRITE( 0x80 , SPI_CLK, SPI_MOSI, SPI_MISO);
SPI_WRITE( 0x12 , SPI_CLK, SPI_MOSI, SPI_MISO);
SPI_CS(0, SPI_CLK, SPI_CS);
wait for;

のように書いて、テストベンチの見通しがよくなるようにします。SPI程度ならベタに書いていってもいいと思うかもしれませんが、PCI ExpressやSATAレベルの複雑さになってくると、関数呼び出し的な階層化を行わないときつくなってきます。

 

まずはPROCEDUREを使う簡単な例として、与えられた数の2倍と4倍を返すモジュールを考えます。

procedure mult (val : in integer; mul2 : out integer; mul4 : out integer) is
begin
mul2 := val * 2;
mul4 := val * 4;
end procedure;

説明するまでもなく簡単なのですが、FUNCTION文とちがって複数の戻り値をとることができます。また、関数全体の戻り値というのはありません。

次に、ちょっと高度な例として、呼び出されるたびに簡単な疑似乱数を生成するモジュールを考えます。

procedure lfsr (val : inout std_logic_vector(31 downto 0)) is
begin
val(31 downto 1) := val(30 downto 0);
val(0) := val(31) xor val(29) xor val(22) xor val(5) xor val(3) xor val(0);
end procedure;

これを使うには、process文の中でローカル変数を作っておいて、

process
variable seed : std_logic_vector(31 downto 0);
begin
while (TRUE) loop;
・・・
lfsr(seed);
・・・
end loop;
end process;

のようにして呼び出します。当然ながらLOOPが繰り返されるたびに新しい乱数が変数seedの中に入ります。

このPROCEDUREでは方向をinoutで宣言しているので、呼び出すたびに参照と書き換えが行われます。

  

次はより実践的な例です。実際の信号(といってもシミュレーションのテストベンチで生成する信号)を操作するモジュール、例えば、クロックの生成するモジュールを作ることにしましょう。

作り方は以下のとおりです。

procedure clock(signal clk : out std_logic) is
begin
clk <= '0';
wait for PERIOD / 2;
clk <= '1';
wait for PERIOD / 2;
end procedure;

ポイントは、操作したい信号はsignal 信号名 : out 型とすることです。信号を操作するにはsignalです。何もしないとvariableになってしまします。また、操作したい信号はoutにします。

最後は少し複雑な例です。TEXTIOで読み込んだlineからSTRINGを読み出すという関数です。STRINGはスペースやカンマ、タブなどで区切られたひとくくりとします。

procedure read_str(l     : inout line;
VALUE : out string;
good : out boolean) is
variable c : CHARACTER;
variable readOk : BOOLEAN;
variable i : INTEGER;
constant NBSP : CHARACTER := CHARACTER'val(160);
begin
VALUE := (VALUE'range => 'U');
Skip_whitespace (L);
good := FALSE;
if VALUE'length > 0 then
read (l, c, readOk);
i := value'low;
while i >= VALUE'low loop
if readOk = false then
VALUE(i) := NUL;
return;
end if;
if (c = ' ') or (c = HT) or (C = ',') or (c = NBSP) then -- HTはTABのこと
VALUE(i) := NUL;
return;
end if;
VALUE(i) := c;
i := i + 1;
good := TRUE;
read(l, c, readOk);
end loop;
end if;
end procedure;
procedure skip_whitespace (
L : inout LINE) is
variable readOk : BOOLEAN;
variable c : CHARACTER;
constant NBSP : CHARACTER := CHARACTER'val(160);
begin
while L /= null and L.all'length /= 0 loop
if (L.all(1) = ' ' or L.all(1) = NBSP or L.all(1) = ',' or L.all(1) = HT) then
read (l, c, readOk);
else
exit;
end if;
end loop;
end procedure;

この関数を使うと、TEXTIOでファイルから

CMD CH 1 READ 12 WRITE 24

みたいな行を読み出したとき、スペースで区切られた部分を1つずつ取り出してくれます。

明日はこの関数を使ってTEXTIOからコマンドを取り出すやりかたを考えてみましょう。

(続く)

| | コメント (0)

2020.12.04

VHDLの高度な使い方(1) ~TEXTIOとSTRING~

VHDLっていうと、IF文やCASE文を使っていろいろな条件を書いて回路を作っていくイメージですが、あまり知られていない文法がたくさんあります。

genericとfunctionを組み合わせて強力なパラメタライズをしたりとか、シミュレーション中でPROCEDUREを使って関数呼び出しをしたりとか、実はいろいろなことができるのです。そんなVHDLの高度な使い方を解説していきたいと思います。

今日は、シミュレーションを劇的に高度化するTEXTIOについて説明したいと思います。

TEXTIOを使うとVHDLのシミュレーション中にファイルの入出力ができるようになります。検証結果をファイルに書き込んだり、波形データをファイルから読み込んだりすることができるようになります。

波形データやパラメータをファイルから読み込むようにするとシミュレーションを再スタート(relaunch)しなくても、時刻を0に戻してから再Runすればよくなるので、シミュレーション時間の短縮にもつながります。また、Vivadoの画面上で波形を確認するのではなく、結果がファイルに書き込まれるので見るのも楽になります。

Vivadoではシミュレーションを行うためのソースとテストベンチを合わせて見えないところでEXEファイルを作り、そのEXEファイルを実行することシミュレーションを行っているようなので、検証の条件をファイルから読み込むようにすればEXEの再生成をしなくて済むようになります。

なので、TEXTIOを使ってファイル読み書きができるようになりましょう!

まず、TEXTIOを使うには、VHDLの先頭で

use ieee.std_logic_textio.all;
use std.textio.all;

を宣言しておきます。

ファイルを使うには変数や信号を宣言するところで、

file WRITE_FILE : text open write_mode is "result.txt";

のように宣言と同時にOpenしてもいいですし、宣言では

file F_SETTING : text;

と変数だけ作っておいて、PROCESS文の中で

file_open(F_SETTING, "setting.txt", READ_MODE);

として動的に開いてもよいでしょう。閉じるときはfile_close(F_SETTING);

file_close(F_SETTINGS);

で閉じます。シミュレーションが異常終了してファイルが閉じていないとWindowsの他のプログラムから書き換えできなくなるので、後者のやり方がおすすめです。

 

さて、ファイルを書き込むほうを見ていきましょう。といっても、TEXTIOで使える関数は、

procedure writeline(file f : text; l : inout line);
procedure write(l : inout line; value : in bit; justified : in side := RIGHT; field : in width := 0);
procedure write(l : inout line; value : in bit_vector; justified : in side := RIGHT; field : in width := 0);
procedure write(l : inout line; value : in boolean; justified : in side := RIGHT; field : in width := 0);
procedure write(l : inout line; value : in character; justified : in side := RIGHT; field : in width := 0);
procedure write(l : inout line; value : in integer; justified : in side := RIGHT; field : in width := 0);
procedure write(l : inout line; value : in real; justified : in side := RIGHT; field : in width := 0; digits : in natural := 0);
procedure write(l : inout line; value : in string; justified : in side := RIGHT; field : in width := 0);
procedure write(l : inout line; value : in time; justified : in side := RIGHT; field : in width := 0; unit : in time := ns);

くらいです。

あと、hwriteとowriteというのがあります。これは16進数や8進数で出力してくれるというもので、hwrite(lo, target_signal)のようにして使います。

驚くほど貧弱ですが、規格に腹を立てずに使いこなしてください。

VHDLは処理系としては貧弱ですが一丁前に関数のオーバーライドやデフォルトパラメータが使えます。基本的にはwrite関数でlineを作って、そのlineをwritelineで出力するというしくみです。第三引数以下は省略してかまいません。

write関数は第二引数に取った変数や信号の型に応じていろいろなタイプのwriteが用意されています。

第一引数のlというのは「アクセス」という特殊な型でmallocやdeleteをした動的なメモリ領域なのですが、深入りはせずに、使い方だけ示します。

process (・・・)
variable lo : line;
variable test : integer := 123;
begin
  write(lo , VAL1);
writeline(WRITE_FILE, test);
・・・

です。

writeでloに行を詰め込んでいって、writelineで吐き出す。それと同時にloはクリアされるというしくみです。

std_logic_vectorもstd_logicもintegerもいい感じに表示してくれますが、real型は +3.0000000e5みたいな形式となるので使いにくいですね。

時刻を出力する際には、

write(lo, integer(current_time / 1 ns));

というふうに、1ns単位に割り算してからinteger型にキャストするといいでしょう。

さて、シミュレーションの結果ファイルに文字列を書き込みたい場合もあるでしょう。文字列を扱うのはちょっと大変です。

シミュレータに文字列型として認識させてやらねばならいません。そのためには、文字列の即値を作るSTRING'()というのを使って、

write(lo, STRING'("SUCCESS"));

のようにしたり。もしくは、

write(lo, "SUCCESS" &LF);

のように、CHARACTER型やSTRING型のと&で結合させてSTRING型にします。

ここで出てきたLFというのは改行コードです。VHDLでは""の中に\nと書いても改行にはなりません。よく使う特殊記号はLFのほかに、TABを示すHT(コード08)や、NBSP(コード160)があります。

  

文字列と数値などの相互変換はとても大変ですが、一応用意されています。数値(integer)を文字列に変換するにはinteger'image(value)というのを使います。例えば、Iという変数を書き出したい場合には、

write(lo, "CH=(" & integer'image(I) & ") . ");

のようにします。

とにかく用意されている機能が貧弱なのですが、TEXTIOで頑張ることで

# DAQTEST  SIMULATION RESULT
#TIME[us] ADC1[mV]
0 0*
10 1000 
20 1200
・・・
RESULT(0) SUCCESS , PERIOD=310[ns]

のような人間に読みやすいレポートを作成することができるようになります。

(続く)

| | コメント (0)

« 2020年11月 | トップページ | 2021年1月 »