2012年07月13日

PIC32MX220F032B メモ

CPLD のカテゴリで延々と記事を書いているのだが、ちょっと脱線。

USB OTG コントローラ付きの PIC32MX PIC32MX220F032B が、秋月で 220円という低価格で売られるようになった。この値段だと、単なる USB コントローラより安い。AVR の代わりとして使う気はないのだが、なにかの専用ICとして仕立て使ってみたい気がしている。

    興味があるのは、とりあえず USB ホスト機能。USB BlueTooth ドングルは妙に安いようだから、これでシリアルやSPIに変換できたらと思っている。

というわけで、ちょっと下調べ。

28 pin DIP だが、自由に使えるピンは少ないようだ。

~MCLR (1) AVdd
PGED3 RA0 G1 AVss
PGEC3 RA1 G2 G1 RB15/SCK2 G1 SS1
PGED1 RB0 G4 G4 RB14/VBUSON/SCK1 G2 SDI1 SDO1 SDO2
PGEC1 RB1 G2 G3 RB13 G3 SDI2
SDA2/RB2 G3 Vusb3v3 G4 SS2
SCL2/RB3 G1 G2 RB11/D- PGEC2
Vss G4 RB10/D+ PGED2
CLKI Vcap
CLKO Vss
SOSCI RB4 G1 G4 RB9/SDA1 TDO
SOSCO RA4 G3 G2 PB8/SCL1 TCK
Vdd G1 PB7 TDI
TMS/USBID RB5 G2 Vbus

とりあえずピン配置をメモ。JTAG を使うと 11 pin しか自由に使えない。ただ、コンフィグで JTAG は Disable に出来る。そうすると 15 pin 使えるようになるらしい。

補足: デフォルトでは、セカンダリオシレータ(32.768 Hz 水晶) が ON になっているそうだ。さらに、USBID と VUSBON の設定も コンフィグにあり、これも ON になっているとポートとして使えない。また、PPS -- Periphral Pin Assign という機能があって、UART などのピンをいくつかのピンから選択できるのだが、この選択を 1 回しかできなくする設定も コンフィグにある。とにかくコンフィグはよくよく調べておかないとならない。

補足2: JTAG は コンフィグでは Enable にしておいて、ブートローダやプログラムの先頭で

CFGCON = 0; // 1xx/2xx 以外では DDPCON = 0;

とやって Disable にするものらしい。こうすると、ファームウェアを書き込むときにだけ JTAG を使うことができる。

    RA0 o AREF PGED3
    GND
    RB4 U1TX RB15 o D13 LED1
    RA4 U1RX RB13 o D12
    RB14 SCK1 RB5 o D11 TMS
    RA1 o D10 PGEC3
    RA10 x D9 LED2
    RB7 o D8 BUT/TDI

    RC7 x D7
    RC6 x D6
    A0 x RC0 RC5 x D5
    A1 x RC1 RC4 x D4
    A2 o RB0 RC3 x D3
    A3 o RB1 RC2 x D2
    A4 o RB2 RB9 o D1 U2TX/TDO
    A5 o RB3 RB8 o D0 U2RX/TCK

    ちょっと PIC32-PINGUINO-MX32 のピン配置を調べてみた。これは、同じ PIC32MX220F032 だが 44pin の D を使っている。B ではピンが足りなすぎて Arduino もどきにするには、厳しそうだ。

組み込みの機能のピンは、割り当てが最初から決まっているものと、割り当てを変えられるものがある。ここで載せたのは、最初から決まっているもので、興味があるもの。( ADC 関係は面倒なので載せていない。)
どうも I2C と SPI の SCK は配置を変えられないようだ。

    ピン A0 B3 B4 B15 B7
    出力 U1TX U2RTS SS1 OC1 C2OUT
    入力 INT4 T2CK IC4 SS1 REFCLK1

    ピン RA1 RB5 RB1 RB11 RB8
    出力 SDO1 SDO2 OC2
    入力 INT3 T3CK IC3 U1CTS U2RX SDI1

    ピン RB6 RA4 RB13 RB2
    出力 SDO1 SDO2 OC4 OC5 REFCLKO
    入力 INT2 T4CK IC1 IC5 U1RX U2CTS SDI2 OCFB

    ピン RB14 RB0 RB10 RB9
    出力 U1RTS U2TX SS2 OC3 C1OUT
    入力 INT1 T5CK IC2 SS2 OCFA

割当てを変えられるものは、4 つのグループに分けられていて、その中では自由に割り当てられるようだ。

あといくつか

    フラッシュの書き込みは、OpenOCD が使えるとのこと。
    ~MCLR (リセット) にはプルアップが必要。
    レジスタへの 書き込みは割り込み禁止で

デジキーでの値段とか

    QFN28 や SSOP28 パッケージもあり、デジキーでは 300円ぐらい。10 個買えば、秋月価格に近くなるみたいだ。

      QFN28:
      1 315.00000 315
      10 246.00000 2,460
      25 225.80000 5,64
      SSOP28
      1 300.00000 300
      10 236.30000 2,363
      25 216.12000 5,403

    ピンを増やしたいなら TQFP44 がある。QFN もあるけど。値段は 少し高くなる。

    44pin は、PIC32MX220F032D とか末尾が D。
    メモリが多いのは、型番が違う。

    PIC32MX220F032B/D Flash 32KB RAM 8KB
    PIC32MX230F064B/D Flash 64KB RAM 16KB
    PIC32MX250F128B/D Flash 128KB RAM 32KB

    SSOP28 128KB
    1 431.00000 431
    10 338.20000 3,382
    25 310.12000 7,753
    DIP28 128KB
    1 457.00000 457
    10 359.30000 3,593
    25 329.48000 8,237

    これでも安いといえば安いが、使いこなせなければ意味ないし気軽に買えるわけでもない。

USBコントローラ

    PIC32MXの USB インターフェイス について 秋月の URL にあるデータシートを見れば、説明が載っているわけだが、全部英語。で、レジスタの名前 例えば U1OTGIR で検索すると ... PIC24F の 日本語ドキュメント がヒットする。

    構成図を見ると、PIC32MX と PIC24F の USB モジュールは実に似ている。どうもほとんど同じようだ。概要を知りたい程度なら、PIC24Fの方で済みそう。

    PIC には、USB Framework というのがあるらしいが、32KB でも厳しいらしい。gcc のくせに最適化を使わせないというのは、どうかと思うし、このチップを使うなら、全部自前で用意した開発ツールでビルドできるようにしたい。

    ... というわけで、使いたくはあるが、なかなか手がつけられないのだった。

ブートローダ

    AVR の ATmega32u2 用に自前の USB ライブラリ で USBasp プロトコル ブートローダ を作っている。 PIC32MX も 3KB の ブートローダ 領域を持っている。移植できたりしないかな。ヒューズビット等の関係があるから、avrdude が使えるかどうか分からないが ...

    あと、AVR と同様に割り込みを一切使わずに ブートローダ を作れるなら、mips16e が使えるかも知れない。

    ブートローダは、後述している Pinguino プロジェクト のを使うのが吉らしい。自分で作る必要はなかったか。

     ・ Section 5 : Flash Programming -- Run-Time Self-Programming(RTSP) の仕様書 (pdf)
     ・ PIC32MX Flash Programming Specification -- JTAG , ICSP の仕様書 (pdf)

      JTAG を使った書き込みには、2 種類あるらしい。ひとつは、Programming Executive(PE) を RAM に送り込んで それを使って Flash を操作するやりかたで 多分これが標準的。もうひとつは、直接 RTSP 用レジスタを操作して書き込むやりかた?

参考になるサイト

     ・ 『マイコン風雲録: 秋月でPIC32MX 28pin DIP版出る

    ここが詳しい。Pinguino プロジェクト というのがキーワードっぽい。

    Pinguino プロジェクト

      ブートローダや gcc まで 含まれた開発ツール Pinguino X.3 を 配布している。

      gcc/binutils のパッチ や config方法 、ブートローダのソースコードだけ見たいのだが どこにあるんだろう?

      そもそも gcc のバージョンは? 4.6.2 と 4.5.2 があるようだが。 自製 AVR_Toolchain は、gcc-4.4.3 を使っているのだが、これで済ませられないかな?

      追記: svn から最新のものをダウンロードしてみた。(リビジョン 543)

        Configured with: ../configure --target=mips-elf --program-prefix=mips- \
        --enable-languages=c --prefix=/home/jpm/pcompiler --disable-nls \
        --disable-tui --disable-gdbtk --disable-shared --enable-static --disable-threads \
        --disable-bootstrap --with-dwarf2 --enable-multilib --enable-sim \
        --with-float=soft --without-headers --with-lib-path=: \
        --with-pkgversion='Pinguino C Compiler for PIC32 v4.5'
        Thread model: single
        gcc version 4.5.2 (Pinguino C Compiler for PIC32 v4.5)

      -v で確認するとこういうオプションで config されていることは分かった。

      MIPS は AVR のように 新しいチップに対応するために コンパイラを変更したりしない。たぶんパッチなしなのだろう。変更がないのなら、gcc-4.4.3 でも構わなさそう。ちょっとビルドしてみよう。

      ところで、バイナリのダウンロードは、
       http://code.google.com/p/pinguino32/downloads/list
      ここから出来るのだが、win32 版だけ小さい。これはたぶん g++ がない。svn の方にも win版だけ g++ がなかった。

    pic32prog: PIC32MX220をPickit2/Pickkit3で書き込む

      これは ... ソースコード を GPLv2 で配布しているではないか。MPSSE(Olimex ARM-USB-Tiny JTAG adapter) 用のコードもある。
      ちょっと見てみよう。FT232RL に対応できるかも知れないし、逆に 自製の JTAG ツールで対応できるかも知れない。(対応できた。追記参照)

    Olomex PIC32-PINGUINO-MX220



      Olimex のボード 。結構安い。9.95EUR。送料はいくらだろう?

      リンクも参考になりそう。

      arduio と ピン互換になるようにしていて、外側内側には ユニバーサル基板を使えるようにもう一列追加されている。

      追記: Mouser で PINGUINO を扱っている

      PIC32-PINGUINO-MX220 1:\1,572
      PINGUINO-MICRO (PIC32MX440F256H) 1:\1,660

      本末転倒なのかも知れないが、pickit3 や PIC32MX250F128B/PIC32MX220F220B あるいは UM232H なんかを一緒に買うと 7500 円は超えそうだし、Mouser で買うのも悪くなさそう。

      追記: デジキーでも PINGUINO を扱いはじめた。

      PIC32-PINGUINO-MX220 1:\1,418
      PINGUINO-MICRO (PIC32MX440F256H) 1:\1,418

      こっちの方が安い。

    エラッタ : レジスタに書き込んでるときに 割り込みが起きると 割り込みからの戻りで 再度書き込む。

    割り込みが起きた以上そうなるのは、MIPS の特性。割り込みが起きないようにしていないというのがバグ?

PIC32MX 用 gcc のビルド(1)

    以前、『AVR Toolchain (その2)』という記事で avr-gcc をビルドした。この バージョンの gcc を PIC32MX 向けにビルドしようと思う。

    binutils-2.20.1


    ./configure --prefix=/d/MIPS32_Toolchain --target=mips-elf --disable-shared \
    --disable-threads --enable-languages=c,c++ --disable-dssi \
    --disable-plugin


    --enable-languages など関係ないはずだが、prefix と target だけ変更してビルド。

    make ; make install

    gcc-4.4.3


    ./configure --target=avr --prefix=/c/AVR_Toolchain --enable-languages=c,c++ \
    --with-dwarf2 --disable-doc --disable-shared --disable-libada \
    --disable-libssp --disable-nls --enable-fixed-point \
    --with-build-time-tools=/c/AVR_Toolchain/avr/bin

    AVR ではこういう指定をした。

    ./configure --target=mips-elf --prefix=/d/MIPS32_Toolchain --enable-languages=c \
    --with-dwarf2 --disable-doc --disable-shared --disable-libada \
    --disable-libssp --disable-nls --disable-fixed-point \
    --with-build-time-tools=/d/MIPS32_Toolchain/mips-elf/bin \
    --disable-tui --disable-gdbtk --disable-threads --disable-bootstrap \
    --enable-multilib --enable-sim --with-float=soft --without-headers

    それを元に Pinguino で指定しているオプションを追加。C のみに限れば 比較的簡単だろう。

    オプションは、有効でないものもおそらく指定している。だが、確認するのが面倒なので気にしない。

    make は通った。

    mips-elf-gcc -Os -mips16

    も使えるようだ。


    ls /d/MIPS32_Toolchain/bin/
    libiconv-2.dll mips-elf-gcc-4.4.3.exe mips-elf-objdump.exe
    libintl-8.dll mips-elf-gcc.exe mips-elf-ranlib.exe
    mips-elf-addr2line.exe mips-elf-gccbug mips-elf-readelf.exe
    mips-elf-ar.exe mips-elf-gcov.exe mips-elf-size.exe
    mips-elf-as.exe mips-elf-ld.exe mips-elf-strings.exe
    mips-elf-c++filt.exe mips-elf-nm.exe mips-elf-strip.exe
    mips-elf-cpp.exe mips-elf-objcopy.exe

    ls x.3/win32/p32/bin/
    grep.exe mips-as.exe mips-ld.bfd.exe mips-strings.exe
    libiconv-2.dll mips-c++filt.exe mips-ld.exe mips-strip.exe
    libiconv2.dll mips-cpp.exe mips-nm.exe mphidflash.exe
    libintl-8.dll mips-elf-gcc-4.5.2.exe mips-objcopy.exe pcre3.dll
    libintl3.dll mips-elfedit.exe mips-objdump.exe regex2.dll
    make.exe mips-gcc.exe mips-ranlib.exe
    mips-addr2line.exe mips-gccbug mips-readelf.exe
    mips-ar.exe mips-gcov.exe mips-size.exe

    作ったものの実行ファイル一覧と Pinguino (win版)比較。

    dll は、AVR_Toolchain と同じものを入れている。

    Pinguino では、make と grep が添付されている。binutils は target=mips でビルドしているのか。gcc もリネームしている。

    さて、これだけで、PIC32MX220F032B で動くものが作れるかというと ダメ。足りないものがある。

    Pinguino をチェックすると

      p32/include/non-free/cp0defs.h
      p32/include/non-free/p32xxxx.h
      p32/include/non-free/regdef.h

      p32/include/non-free/proc/p32mx220f032b.h
      p32/include/non-free/proc/p32mx220f032d.h
      p32/include/non-free/proc/p32mx250f128b.h
      p32/include/non-free/proc/p32mx440f256h.h
      p32/include/non-free/proc/p32mx460f512l.h
      p32/include/non-free/proc/p32mx795f512l.h
      p32/include/non-free/proc/p32mxgeneric.h
      p32/include/non-free/proc/ppic32mx.h

      p32/obj/non-free/crt0.S

      p32/lkr/ISRwrapper.S
      p32/lkr/elf32pic32mx.x
      p32/lkr/procdefs.ld
      p32/lkr/PIC32_PINGUINO_220/ISRwrapper.S
      p32/lkr/PIC32_PINGUINO_220/elf32pic32mx.x
      p32/lkr/PIC32_PINGUINO_220/procdefs.ld

    多分これらが必要。include のものは、レジスタの定義が主。non-free となっているのは、MicroChip 提供のコード。crt0.S はスタートアップ。p32/lkr は、リンカスクリプトだと思うが、ISRwrapper.S が何なのかは未チェック。

    これらを使って main を call するところまで 行くのが最初のステップ。

    ライブラリは、AVR-libc 風のものを 以前に作ったのがあるから、これを使えるようにしたい。これが次のステップ。

      記事『USBBOOTプログラム用ライブラリ』にどういうものかについて記載している。usbboot-wk19.tar.gz がその最後の版で 未完成のまま放置中。

      記事を見直すと、-fno-pic ,-fno-pic -mno-abicalls , -fpic といったオプションのどれを使うべきかで悩んでいた。これもチェックしておかないと。

      PIC32MX は、シャドウ・レジスタを持っている。対応できると 割り込みで、レジスタのセーブ・リストアが要らなくなる。ちなみに AVR-libc 風ライブラリは、(適当なものだが)割り込みベクタもサポートしている。

      リンカスクリプトは、チップの種類毎に ユーザプログラム用、ブートローダ用の 2 つ。中を見てみると割り込みベクタまでいちいち定義している。AVR-libc だとベクタは アーキテクチャとして定義されている。のだが、ライブラリの中でサポートしていて、リンカスクリプトは、関知しない。MIPS は、割り込み要因毎のベクタは アーキテクチャとして定義されていない。にも関わらずリンカスクリプトに細かく定義されている。

      これは相当に気に入らない。どうせ自製のライブラリにするんだから、リンカスクリプトは 一種類で済ませたい。

      メモリマップを調べてみた。kseg0 だけ書くと

      Start Size
      Config 9FC00BF0 10
      Boot 9FC00000 BF0
      Flash 9D000000 16KB - 128KB
      RAM 80000000 4KB - 32KB

      1xx , 2xx は、こんな風になっていて、スタートアドレスは同じ。Flash と RAM のサイズが違うのみ。

      あと、PIC32MX は、M4K アーキテクチャで、割り込みベクタは、ハードウェアでサポートされているらしい。そうだとしても AVR-libc のように .text セクションの先頭に 置けばよいだけのはず。ただ、エントリが細分化されると レジスタのセーブ・リストアが面倒。シャドウレジスタを使いたくなる。使い方を調べないと...
       ・ Breaf Introduction for M4K Shadow Registers
      これか?

      リンカスクリプトを見てみたが 割り込みベクタは 1エントリにつき 32バイト(8命令分)使う。で、1xx/2xx は 32 エントリ -- 1KB も消費するわけだ。データシートの割り込みコントローラの説明を見たところ マルチベクターモードと シングルベクターモードがあることが分かった。シングルベクターモードでは、シャドウレジスタを使うか使わないかの設定もある。あと、INTSTAT レジスタに 割り込み番号が入っているが、CPU に通知した番号らしい。もし、シングルベクターモードでは 常に 0 になるなら使えない。( IFSx という ビットマップから番号を作れはするが、重くなる。)

    その次が、自製の USB デバイスライブラリの移植。

      こっちは、『Teensy(1.0)互換ボード』に記載した angel_loader-1.4c.zip が最新。API を共通にしたいところ。

      USB デバイスライブラリは、割り込みを使用していない。PIC32MX でも同じように出来ると良いのだが ...

    ここまで動かせたら、USB HOST のプログラミングに入れる。なかなかに道のりは遠い。

    ライタソフトも FT232R 対応のものを用意したい。これは別途検討するが、動かそうとする段階で欲しくなる。

      MachXO2コンフィグめも』の記事の rtavr_tools-0.10.tar.gz が公開している 自製 JTAG ツールの最新版なのだが、それから変更した版がある。

      自製 JTAG ツールだと、もともと FT232R 用で、JTAG レベルの手順が分れば、PIC32MX に対応できる。HEX ファイルの読み込みも 一応ある。

      pic32prog に FT232R 用のコードを追加する手もあるだろう。どっちが楽かというと 多分自製ツールでの対応。でも、元が MPSSE に対応しているなら、 FT232R のコード追加は、それほど難しくはない。ただし、ピンアサインの自由度がなくなるかも知れない。

ライタの検討(1)

     ・ PIC32MX Flash Programming Specification -- JTAG , ICSP の仕様書 (pdf)

    まずは、どういう手順で操作するのか知るためにこれを読んでみた。

    FLASH をプログラミングするには、2 つの手段がある。ひとつは、JTAG でもうひとつは、ICSP(2-Wire) 。ICSP は、PGECx/PGEDx という 2 つの 信号線と ~MCLR を使うもので、PIC32MX220F032B には、3 セットもある。そのうちのひとつは、D-/D+ に割り当てられている。USB を使うのが前提であれば、このピンは必ず空いている。相当便利なような気がしてきた。

    ICSP は、PGECx をクロックにして、PGEDx でデータを送受信する。これを JTAG の操作にマッピングしている。だから 下位レイヤを変更することで対応できる。それは良いのだが、JTAG だと 1 CLK で済むところを、標準的には 4 CLK かける 。そして、4 CLK の間で 出力と入力を切り替える。MPSSE だと、高度な I/O ができるので問題ないのだが、FT232R だと 出力を Hi-Z にするための付加回路が必要になりそう。チップの機能で 入出力を切り替えると とてつもなく遅くなる。

    あと、ICSP を使うためには、手続きがある。3 セットもあるわけだし、どれを使うか 手続きなしに決められるわけもない。一方 JTAG は手続きなしらしい。... ということは Disable にしてしまったら、どうなるのだろう? ひょっとしたら使えないのかも知れない。

      追記: だいぶ分かってきた。PGEx は、どれを使うかコンフィグで設定する。00 を設定してしまうと、使えなくなってしまう。JTAG も コンフィグで Disable できるが、プログラムでもできる。Enable にしておいて、プログラムの頭で Disable にする使い方が良い。(ブートローダを使っている場合は、既に Disableにされているから プログラムではなにもしなくて良い。)こうすることで、最悪 JTAG を使って復活できる。

      もうひとつの懸案は、クロック。AVR だとクロックの設定を失敗すると面倒なことになった。JTAG/ICSP では、クロックは関係無いようなことが書いてある。そうであれば、チップイレーズすることで、なんとでもなるのだろう。

    ... というわけで、ICSP を中心に検討した方が良さそうな感じ。

ライタの検討(2)

    ICSP を中心にするといっても、操作の基本は JTAG と変わらない。では、それはどういうものなのか?

     ・SetMode
     ・SendCommand
     ・XferData
     ・XferFastData
     ・XferInstruction

    これら 5 つのオペレーションを使うらしい。最後の XferInstruction は、基本操作ではなく、上の 4 つを使って実装する。で、名前から想像できるように 命令列を送り込んで実行させるもののようだ。

    どのような命令を送り込むのかについて、詳しく書いてある。また、PE -- Program Executive という プログラムを RAM に送りこんで 高度な操作をさせることも可能になっている。PE を使うやりかたは標準的で、pic32prog でも採用しているようだ。

    それは良いのだが ... こんな手続きだと 非常に面倒。自製の ツールに組み込むのは止めにして、pic32prog を改造する方針で検討する。

    pic32prog を使う利点はほかにもあった。
     ・ PIC32MX220F032B 製品ページ
    ここに、いろいろリンクがあるのだが、AN1388 -- PIC32 Bootloader の HID プロトコルに対応しているのだ。そうであれば、pic32prog に 機能をまとめてしまった方が便利だろう。

    さて、pic32prog だが、svn から チェックアウトしたものを
     ・ pic32prog-r51-20120714.tar.gz
    に置いた。

    これを見ると .. MPSSE を使うのに ftd2xx.dll を使っていない。なんと libusb を使って直接アクセスしている。これは GPL をクリアするためなのだろうか? ただ、遅いはずだし、ちょっと改造がやりにくい。といっても Synchronos BitBang Mode はモードが違うだけで、READ/WRITE を基本にするところは同じ。ICSP に対応するにしても MPSSE のコマンドが違うだけの話。なんとかなりそうな気はする。

    あと、対応している VID:PID が OLIMEX_ARM_USB_TINY/OLIMEX_ARM_USB_TINY_H だけになっている。ジェネリックな FTDI の ID に対応していない。

    行数は全部で、985 行。libftdi 相当が含まれていると思えば、かなり小さいともいえる。処理をちょっと見てみたのだが、

     ・ MPSSE としての機能しか使っていない。TCK/TMS/TDI/TDO のみの操作ということ。
     ・ mpsse_send / mpsse_recv が基本操作。MPSSE だと recv 不要の操作がある。ICSP の対応 や Synchrons BitBang では、必ず READ が入るから 少々対応が面倒。

割り込みアドレスの調査

    話は飛ぶのだが、ライブラリを作るうえで どんな割り込みアドレスがあるのか知っておかないといけない。ちょっと調べてみた。

    その前に メモリマップ

    kseg1 base Memory Map
    1xx/2xx 3xx/4xx 5xx/6xx/7xx
    Start Size Start Size Start Size
    Config BFC00BF0 10 BFC02FF0 10 BFC02FF0 10
    Boot BFC00000 BF0 BFC00000 2FF0 BFC00000 2FF0
    Flash BD000000 16KB - 128KB BD000000 BD000000
    RAM A0000000 4KB - 32KB A0000000 A0000000

    kseg1 内のメモリマップを作った。1xx/2xx 以外も調べてみたが、Boot 領域 のサイズが違う。Boot 領域の最後 16 バイトは、Config 領域で、AVR での ヒューズビット・ロックビットに相当する重要な情報が入っている。ここが 1xx/2xx でずれるというのは覚えておかないと。
    ただ、スタートアドレスはみな同じ。リンカースクリプトはかなり共通化できそう。

    さて、割り込みだが、

    0xBFC0_0000 Reset,Soft Reset,NMI
    0xBFC0_0200 EJTAG Debug (EJTAG ProbEn==1)
    0xBFC0_0380 all generic interrupt (BEV==1)
    0xBFC0_0480 EJTAG Debug (EJTAG ProbEn==0)

    0xBD00_0000 (EBase)
    0xBD00_0200 Single Vector Mode (BEV==0)
    0xBD00_0200 Multi Vector Mode (BEV==0)
    + vector_no * 32 * VS(VS==1)

    どうも ユーザコードにくる割り込みは、0xBD00_0200 から始まる割り込みベクタのみらしい。シングルベクタモードを使うと、たった一つ。

    あと、ブート領域になにかコードが入っていないと、ユーザコードは実行されない。それに 最初のエントリは、どこなのか? ブートローダによって決まるような?

追記、回路図を作ってみた



    PIC32MX2xx用基板』用の回路図の焼き直しなのだが、ユニバーサル基板とかで作るときの参考用として作った。



    いつも水晶の収まりが悪いわけだが、MA-506などはどうだろう?足を伸ばすことで、ブレッドボードなんかに挿せるような気がする。

    追記) ブレッドボードで 、ライタのテスト用配置を考えているのだが、なかなか厳しい。PIC32MX032B は、28 pin で 14 列を使う。予定している UM232H も 28 pin で これまた 14 列。そして、EIC-801 (BB-801) などは 30 列しかない。2 列しか 余裕がないのだ。で、この 2 列で入るものとして、リセットスイッチと DTC144 を考えている。

    o-------o
    | |
    | TACT |
    | -SW |
    --o---o---o o-------o
    |
    | o---o---o
    UM232H | |
    | x x | PIC23MX220F032B
    | |
    | o---o---o
    --o---o---o

    o o o DTC144
    IN OUT GND

    たぶん、こういう位置関係以外は無理。UM232H の重なっている部分は、AD6, AD7 で IN/OUT ともに接続する設定にすれば OK 。LED は、ストレートに 接続するしかない。そのためには、小基板を使ったりして 抵抗 + LED のパーツを作る必要がある。

    AE-UM232R だと 24 pin で 2 列増える。だが、DTC144 は重ねられないから 位置関係はあまり変わらない。

Arduino がライタになるらしい。

    ardupic32』というのを発見した。これは、JTAG で書き込む。

    遅いらしいのだが、ブートローダさえ書き込めさえすれば、あとは快適になるはず。幸いなことに、JTAG のピンは 5V トレラントになっているので、5V 版の Arduino が使える。

    同じようなものは、USB デバイスのコードが自由に書けるようになりさえすれば、作れるはずで いずれは検討したい。

追記 : BitBang で 書き込みが出来た。

    pic32prog を改造して、FT232R や FT232H で 書き込みできるようになった。(テストは、UM232H のみ)

    ものは、
    pic32prog-r62-ftdi-05.zip

    バイナリも同梱しているので、ftd2xx.dll を別途用意すれば使える。『pic32progの改造』の記事で説明はしているのだが、経緯込みで書いているので、イマイチわかりづらいと思う。いずれまとめの記事を起こしたいとは思う。

    もう少し詳しく書くと、Synchronous BitBang を使った CISP (外付けトランジスタが必要) と MPSSE での BitBang を使った CISP (結線のみ) の動作が確認できた。JTAG は、まだ動いていない。

関連記事
posted by すz at 20:51| Comment(0) | TrackBack(0) | PIC32MX

2012年07月07日

USBコントローラの設計(2)

前記事で、QFN32 の MachXO2-256 に入る USBコントローラを設計してみた。次は、これをどうやって使うのかソフト編に入りたい。... が結局どうなったのか混乱するので、仕様の整理から。(対応するのは、qfn32samples-12)

BUS版と SPI版

    HOST とのインターフェイスは 2 種類ある。LCD で良く使われる 8080 バス仕様と SPI 仕様。

    BUS 版。自作モジュールのピン配置を示す。

    // XO2-256 UDRV XO2-256
    // 27 DP 1 24 VCC
    // 28 DM 2 23 nBUSY 25
    // 29 nRD 3(TMS) 22 nCRC_ERROR 23
    // 30 nWR 4(TCK) 21 CLK_OUT 21
    // 32 DB0 5(TDI) 20 CLK_4X 20
    // 1 DB1 6(TDO) 19 nDATA_FULL 17
    // 4 DB2 7 18 nDATA_EMPTY 16
    // 5 DB3 8 17 N.C. (JTAGENB)
    // 8 DB4 9 16 nDATA_RDY 14
    // 9 DB5 10 15 nCS 13
    // 10 DB6 11 14 RS 12
    // GND 12 13 DB7 11

    (JTAGENBを除いて)1つのピンも余らない。コンフィグデータを書き込むときには、JTAGENB を H にして、JTAG ポートから書き込む。

    // _ ____________________________________ _____
    // RS _><____________________________________><_____ // // _____ _______ // nCS |________________________________| // // ______________ _____________ // nWR/nRD |_____________| // // WRITE: _________________ // DB[7:0] ----------------<_________________>--------
    // |---| CLK_4X (MAX)
    // |---------------------| CLK_4X x 2 (MIN)
    //
    // READ: _____________
    // DB[7:0] --------------<_____________>----------
    //
    // |-| gate level delay

    アクセスのタイミング。こうはしたが、RS は、nWR/nRD を L にしたときに確定していれば良い。nCS は、単なる出力コントロールなので、nRD と 同時に変化させても構わない。

    nWR は、普通 posedge で採取するものなのだが、内部クロックに同期して採取しているので無理。negedge から間をおいて採取になる。negedge から CLK_4X の 1T (Full-speed なら 20.8 ns) 以内に WRITE データを確定させないといけない。そこから 最小 1T 確定させておかなければならない。 Low-Speed なら 1T が 167 ns にもなる。注意が必要。

    RS は、A0 とも表記される レジスタ選択。L:データ/H:コントロール

    BUS 版を 一応は作ってみたが、実際に使うことは想定していない。

    実際に使うのは、SPI 版

    // XO2-256 UDRV SPI XO2-256
    // 27 DP 1 24 VCC
    // 28 DM 2 23 nBUSY 25
    // 29 nCS(2) 3(TMS) 22 nCRC_ERROR 23
    // 30 SCK(2) 4(TCK) 21 CLK_OUT 21
    // 32 MOSI(2) 5(TDI) 20 CLK_4X 20
    // 1 MISO(2) 6(TDO) 19 nDATA_FULL 17
    // 4 7 18 nDATA_EMPTY 16
    // 5 8 17 N.C. (JTAGENB)
    // 8 9 16 nDATA_RDY 14
    // 9 MISO 10 15 nCS 13
    // 10 MOSI 11 14 RS 12
    // GND 12 13 SCK 11

    ピン配置は、こうした。(2) と書いてあるのは、第二の選択。

    JTAG のポートを空けるならば、オリジナルのピン配置にする。ただ、SPI を通してコンフィグもやりたい(できる)なら、(2) の方が便利。

    SPI自体のビットレートには制限はあまりない。FIFO とのハンドシェークが制限になるだけ。SPI では、FIFO を CLK (12MHz) で駆動している。ハンドシェークに 4 CLK かけるとすると 24 MHz が上限。FIFO のクロックを CLK_4X にすれば、96 MHz までいく。(FPGA の SCK の上限はもっと高い)

共通のピンの説明

    DP/DM

      USB の D+/D- 。Low-Speed デバイスは、DMを 1.5 K でプルアップする。Full-Speed デバイスは DP の方。FPGA の ピン設定では、抵抗値が高く役に立たないので注意。

      HOST 向けでは、両方をプルダウンする。こちらは、FPGA の ピン設定で良い。現状では、Low-Speed/Full-Speed の両方には対応できない。

    CLK_4X/CLK_OUT

      Full-Speed では、48 MHz , Low-Speed では 6MHz を CLK_4X に入力する。CLK_OUT は、反転出力で水晶を直付け可能なように配慮。

    nDATA_RDY

      受信データがあるときに L になる。受信ハンドシェーク用で、割り込み線として使う。

    nBUSY/nCRC_ERROR/nDATA_FULL/nDATA_EMPTY

      RS=1 で READ すれば、同じ情報が読めるので必須ではない。特に nDATA_EMPTY 。nDATA_RDY と論理とタイミングが違うだけなので、信号すら不要。これらは、LED とかでのモニタ用として一応 残してある。

      信号自体は、後で説明。

define と parameter

    変更可能だと思っているものについて説明しておく。

    (define) USE_SPI

    SPI にするか BUS にするかの選択。( USE_BUS は常に define で構わない )

    (parameter) SUPPORT_CRC5S=1

    デバイスでは、CRC-5 を使う トークンパケットを出さないので、CRC-5 生成は不要。
    SUPPORT_CRC5S=0 とすると僅かに規模が減る。

    Full-Speed 用
    (parameter) FS=1
    (parameter) FIFO_DEPTH = 5

    Low-Speed 用
    (parameter) FS=0
    (parameter) FIFO_DEPTH = 4

    Low-Speed では、SOP のみの送信をサポートする。規模が増えるので、FIFO を減らすことで対処する。

    Full-Speed 用の FIFO は 32 バイト。最大パケットサイズ 32 を想定しているが、MCU 側の 応答時間+転送速度によっては、もっと大きなパケットにも対応可能。逆に 32バイトのパケット全部は FIFO に入らない。送信時にちょっと細工がいる。

データ送受信

    BUS 版では、

      RS=0 としてデータを書けば FIFO に入り、読めば FIFO のデータを読める。

      FIFO が溢れれば nDATA_FULL が L になり、FIFO をリセットするまで FULL 状態が維持される。
      データがないのに読めば、前のデータが読める。nDATA_RDY を見てのハンドシェークが必要。

    SPI では、

      (送受信モード = 0 のとき) RS=0 として データを書けば FIFO に入る。DATA_FULL の動作は同じ。
      (送受信モード = 1 のとき) RS=0 として データを読めば FIFO のデータを読める。nDATA_RDY との関係は同じ。

    送受信モード, FIFO リセット については、次で説明する。

コントロールレジスタ

    RS=0 にして書き込むことで 以下の状態を制御する。

      0x80 送受信モードを 0 (受信モード)
      0x82 送受信モードを 1 (送信モード)
      0x81 送信要求を 1 (送信モード+送信スタート)

    RS=0 にして読み込むと以下の状態が読める。

      bit7: CRC_ERROR
      bit6: DATA_FULL
      bit5: DATA_EMPTY
      bit4: DATA_RDY
      bit3: RECV_EN (受信中)
      bit2: SEND_EN (送信中)
      bit1: 送受信モード (0: 送信 / 1: 受信)
       bit0: 送信要求 (1: スタート / 0: 完了)

    送信では、送受信モード=0 にしてから、FIFO にデータを送る。ある程度送ったら 送信要求=1 。残りのデータを FIFO にデータを送る。
    FIFO のサイズを大きく超えるデータ量だと、この制御は難しい。規模に余裕があるので、DATA_EMPTY をフロー制御用の信号に置き換えるべき。


      assign I_DATA_EMPTY = (i_addr == o_addr);

      wire [DEPTH-2:0] data_len = (i_addr[DEPTH-1:1] - o_addr[DEPTH-1:1]);
      assign I_DATA_EMPTY = (data_len < 2**(DEPTH-2) );

      誤差 -1 だから 引き算の結果は、±1 。FIFO のデータ量が
      0 〜 14 なら 1
      (15 〜 16 なら どちらになるか分からない)
      17 〜 31 なら 0

      という制御なら入った。後日変更しよう。

    (まとめ終わり・本題が続く)

V-USB

    V-USB は、AVR のみで Low-Speed デバイスにするドライバで、商業利用でないなら、GPL v2 のもとに利用できる。(USB の ID を利用するのは、また別)

    AVR のみで動くのならば、必要な処理すべてが入っているわけだ。一部置き換えることで、作ったコントローラ用になるはず。

    一体どこをどう変えるのか? ... 目星は付けた。

    V-USB は、アセンブラと C に分かれている。インターフェイスを見たがどうも、アセンブラを置き換えるだけで済むようだ。C を変更することになったとしても変更は僅かになりそう。

    コンフィグは、実にややこしくなっている。ここは変更すべきところが出てくる。

    まずは、この方針で V-USB というものを理解する。

V-USB のアセンブラコード・インターフェイス(1)

    udbdrv.c: usbPoll() をまず見よう。受信後どんなデータを見ているのかが分かる。

    uchar usbRxToken;
    uchar usbCurrentTok;
    volatile schar usbRxLen;
    uchar usbInputBufOffset;
    uchar usbRxBuf[2*USB_BUFSIZE];
    :
    len = usbRxLen - 3;
    if(len >= 0){
    usbProcessRx(usbRxBuf + USB_BUFSIZE + 1 - usbInputBufOffset, len);
    usbRxLen = 0;
    }

    usbProcessRx:
    if(usbRxToken < 0x10){
    usbFunctionWriteOut(data, len);
    return;
    }
    if(usbRxToken == (uchar)USBPID_SETUP){
    :
    if(type != USBRQ_TYPE_STANDARD){
    replyLen = usbFunctionSetup(data);
    }else{
    :

    大胆にロジックを削った。

    受信データがある場合、usbRxLen に PID , CRC-16 を含めたデータサイズが格納される。
    ただし、0 オフセットではなく、USB_BUFSIZE - usbInputBufOffset から格納。

    受信データとは何かというと DATA0/1 パケット。このパケットの前には、SETUP か OUT のトークンが来ている。それは、usbRxToken に格納され、後で処理の切り分けに使う。OUT でのエンドポイント番号は、usbCurrentTok に格納されるらしい。

    まずは、ここまで。

    どうも usbRxLen がカナメらしい。これをセットすることで上位レイヤーの受信処理が動き出す。

    自作 USBコントローラ(以下 udrv) で対応させる場合、ルールさえ守れば後は自由にして良さそうだ。

    まず、どの契機で動かすか ... Full-Speed では、次々にデータが来るから nDATA_RDY=L を割り込みの契機にする。一度フレームが来たら、1 フレーム分 全部処理し終わるまで 割り込みから抜けない。というもので良さそう。

      ただ、Low-Speed でそれでは面白くない。余裕があるからだ。受信完了を契機にしたいところ。nBUSY は、送信完了で H になるのだが、L になるようセットしておけば、受信完了でも H になる。

      Full-Speed では、FIFO が溢れる恐れがあるし、連続受信で、L になるようセットするのが間に合わないケースが出てくる。Low-Speed では問題ないようにできるはず。

      Full-Speed でも、FIFO が 半分を超えるか、受信完了で 割り込むという手がある。送信の場合は、FIFO が 半分を切れば追加のデータを送るのが良さそう。送信完了自体は検出する必要はない。そういう信号線(IRQ)が作れないか検討してみよう。

      DATA_EMPTY はなくしても良いし、nCRC_ERROR と nDATA_FULL はエラーが起きたという意味にして 1 つにまとめても良いし、なくしてさえ良い。nBUSY は動いているのを目視したいから残したいが、送信中+受信中に変えたい。

      //assign nBUSY = r_start;
      assign nBUSY = ~(RECV_EN | SEND_EN);
      assign nIRQ = ~r_irq;
      always @(posedge CLK)
      begin
      r_irq &= ~UNDER_HALF ^ r_start ^ i_recv_mode;
      end

      こんな風に変えるのなら可能だった。(UNDER_HALF は、変更後の I_DATA_EMPTY )

      送信モードでは、nIRQ の 最初の状態は H 。
      r_start == 0 すなわち 送信データ詰め込み中ならば、半分埋まったところで L になる。
      r_start = 1 すなわち 送信をスタートさせると nIRQ は反転。
      半分以上埋めているなら、半分を切れば L になる。(半分以下なら、r_start=1 で L )
      送信完了後は、受信モードになる。最初は 空 だから L のまま。
      ここで r_start = 1にすると H に反転。
      受信データが半分を超えるか、受信が完了すれば L になる。

      受信完了後、自動で r_start = 1にすることは可能だった。

      if (r_se0)
      begin
      `ifdef AUTO_RECV_AFTER_SEND
      if (~r_recv_mode) r_recv_mode <= 1;
      `endif
      r_start <= 1'b0;
      end
      else if (~r_recv_en1 & r_recv_en)
      r_start <= 1'b1;

      上記(2ヶ所ある)の最後の else が追加分。

      そこまでやると、H → L の変化だけが重要になる。送信完了後にやるべきことはないのだ。

        送信で重要なのは、途切れずにデータを送り込むこと。
        半分を切れば割り込みが起きるから、16 バイト+αを送り込む。送り込む速度が 12Mbps よりかなり速くないと 64 バイトのパケット送信は無理だが。送り込んでしまえば、あとは受信待ち。

        受信で重要なのは、FIFO を溢れさせないことと 確実な 受信完了の受け取り。両方 H → L 。

    さて、受信データは、usbRxBuf に貯めこんでいく。usbRxLen が 0 になっていたら、貯めるインデックスを 0 にする。負の値にならないようにするなら、最大パケット長に 128 以上は取れない。Full-speed でも 64 が精々。

    受信データの前に SETUP か OUT が来ている。これは、PID を usbRxToken に格納して、廃棄? -- データをそのまま残しても良いかも知れない。

V-USB のアセンブラコード・インターフェイス(2)

    1 パケットの受信完了で何をしているか ... アセンブラを眺めてみると

    受け取ったパケットのPID によって次の処理を行う。

      DATA0/1 の場合 : handleData
      ( usbDeviceAddr を見て チェック : ignorePacket )
      IN の場合 : handleIn
      OUT の場合 : handleSetupOrOut
      SETUP の場合 : handleSetupOrOut
      どれでもない場合 : ignorePacket

    handleSetupOrOut では、一旦リターンする。(思っていたのと違った)

    handleIn は、送るべきデータが用意されていなければ NAK を送ってリターン
    ある場合は、usbSendAndReti これが送信ルーチンだが、終了後 usbNewDeviceAddr を見て usbDeviceAddr を更新している。

    udrv の場合、usbSendAndReti に相当する部分は、かなり簡略化できる。データをそのまま送れば良いからだ。

    どうすれば良いか大分イメージできた。次は、送信データを受け取るところを見てみよう。あと、ハンドシェーク。ACK/NAK/STALL をどういう場合に送っているか(あるいは送らないか)もチェックしよう。

V-USB のアセンブラコード・インターフェイス(3)

    typedef struct usbTxStatus{
    volatile uchar len;
    uchar buffer[USB_BUFSIZE];
    }usbTxStatus_t;

    extern usbTxStatus_t usbTxStatus1, usbTxStatus3;
    #define usbTxLen1 usbTxStatus1.len
    #define usbTxBuf1 usbTxStatus1.buffer
    #define usbTxLen3 usbTxStatus3.len
    #define usbTxBuf3 usbTxStatus3.buffer

    volatile uchar usbTxLen = USBPID_NAK;
    uchar usbTxBuf[USB_BUFSIZE];

    usbBuildTxBlock:

    usbTxBuf[0] ^= USBPID_DATA0 ^ USBPID_DATA1; /* DATA toggling */
    len = usbDeviceRead(usbTxBuf + 1, wantLen);
    if(len <= 8){
    usbCrc16Append(&usbTxBuf[1], len);
    len += 4; /* length including sync byte */
    }else{
    len = USBPID_STALL;
    }
    usbTxLen = len;

    usbDeviceRead:
    if(usbMsgFlags & USB_FLG_USE_USER_RW){
    len = usbFunctionRead(data, len);
    }else{
    if(usbMsgFlags & USB_FLG_MSGPTR_IS_ROM){ /* ROM data */
    :
    }else{ /* RAM data */
    :
    usbSetInterrupt:
    usbGenericSetInterrupt(data, len, &usbTxStatus1);

    usbSetInterrupt3:
    usbGenericSetInterrupt(data, len, &usbTxStatus3);

    とりあえず、送信データは、3 種類ある。usbTxBuf と usbTxBuf1/usbTxBuf3。 これらのフォーマットはすべて同じで、PID と CRC-16 を含むデータ。usbTxLen には、NAK , STALL などの PID が入る場合がある。 そうでない場合は、SYNC の分を含む 1 多い値。

    IN がきた時 endpoint が 0 なら usbTxBuf を使う。USB_CFG_EP3_NUMBER に一致すれば usbTxBuf3 , それ以外なら usbTxBuf1を使う。

    udrv に対応させる場合、CRC-16 は余計なのだが、インターフェイスを変えない。内部で -2 すれば良いだけの話だからだ。オーバヘッドが大きければ、CRC-16 の計算を止める。

    こうしておくと udrv の送信で CRC-16 の計算をサポートしないという選択枝が出来る。

    ついでに気がついたのでメモ: データサイズが 8 バイト以内だという のがハードコーディングされている所がある。ここは、Full-Speed 対応で変更しないと。

パケットのフロー制御

    パケットが来たら ACK/NAK/STALL のどれかを送ったり、あるいは送らないという処理が必要。

    いったい V-USB ではどうしているのだろう。

    まずは、予備知識

      HOST | device | HOST
      ---------------+--------------------------+----------
      SETUP DATA0 | ACK/(none) |
      | |
      OUT DATA0/1 | ACK/NAK/STALL/(none) |
      | |
      IN | DATA0/1 or NAK/STALL | ACK/(none)

    代表的なのは、仕様上こういうことらしい。だが、実際のコードはもっと細い。どういう風になっているか まとめておかないと、コードが作れないのだ。

    SETUP/OUT の後 の DATA0/1
    NAK : (usbRxLun != 0) のとき 受信バッファが空いていないので受け取らない。
    ACK : 0 バイト受信のとき 、受信バッファの状態を変更せず ACK
    STALL : 該当ケースなし。
    (none) : 前が SETUP/OUT でないとき
    IN
    NAK : (usbRxLun != 0) のとき 受信バッファが空いていないので送らない。
    ( すぐに ACK が来るが受け取れない )
    送信するデータがないとき。
    上位から指定されたとき
    STALL : 上位から指定されたとき

    (その他)
    SETUP/OUT (none)
    受信バッファオーバーフロー (none)
    想定外のトークン / エンドポイント 範囲外 (none)

    どうも、これだけのようだ。CRC エラーはないが、たぶん想定外と同じ。

    受信バッファが空いていないので受け取らない。というのは、若干面倒。FIFO には受け取った上で NAK を送信すれば良いのだが、受信完了を待つのが ちょっと。

    IN での NAK は、自力では、思いつかなさそう。
    あと、SETUP の後、NAK は送らないのだが ... 送るケースがあるような ...これは、そういうことがないようにしている?

    usbCurrentTok にストアするタイミング

    SETUP/OUT (none)
    受信バッファオーバーフロー (none)
    想定外のトークン / エンドポイント 範囲外 (none)

    これらは、ACK/NAK を返さないだけでなく、usbCurrentTok を更新している。ちなみに DATA0/1 の後の (none) では、更新しない。わざわざそうしているから、理由があるのかも。

ドライバーの設計

    ようやく本題に入る。ドライバーをどうやって作るのか検討を始めよう。

    その前に 仕様が大分変わった。
     ・ udrv-13-spec.txt
    変更案をいろいろ書いたが、このスペックをもとにする。

    ISR(INT0_vect) {
    static uint8_t recv_offset = 0;
    static int8_t send_len = 0;
    static uint8_t *send_ptr;
    uint8_t i;

    まずは、C で全部記述することにする。AVR だとこんな感じで割り込みを定義できる。
    初期化で、negedge nIRQ で INT0割り込みが 起きるようにしておく。

    if (send_len > 0) {
    :
    return;
    }

    送信で、送りそこなった分があれば送るコードが必要かも知れない。割り込みは、FIFO が半分になったとき起きる。ただ、SPI が十分速くないと最大パケットサイズを大きくできない。遅いと 最大パケットサイズ を 32 にせざるを得ないが、そうなると、送信をキックするところで全部送れてしまう。たぶん必要ないので、これは後回しにしよう。

    次、受信だが、割り込みは、受信完了か FIFO が半分を超えたところで起きる。

    if (bit_is_clear(nDATA_RDY_PIN, nDATA_RDY_BIT)) {
    re_recv:
    do {
    if (recv_offset < USB_BUFSIZE) {
    usbRxBuf[recv_offset++] = spi_read();
    } else
    spi_read();
    } while (bit_is_clear(nDATA_RDY_PIN, nDATA_RDY_BIT));
    }
    i = ctrl_write(CTRL_READ_STATUS);
    if (i & ST_BUSY) return; // RECV in progress
    if (i & ST_DATA_RDY) goto re_recv;
    if (i & ST_CRC_ERROR) {
    recv_offset = 0;
    return;
    }
    if (i & ST_DATA_FULL) {
    recv_offset = 0;
    ctrl_write(CTRL_SEND_MODE);
    ctrl_write(CTRL_RECV_MODE);
    return;
    }

    nDATA_RDYが L で buffer に入る限りは、とにかく入れる。H になれば、FIFO が空ということ。ループを抜けてチェックにはいる。ctrl_write() でステータスが読めるとしておく。

    ST_BUSY なら、まだ受信が続いているということ。割り込みを一旦抜けて、次の割り込みを待つ。
    受信が終了したとしても、僅かな期間で 最後のバイトが受信されたかも知れない。ST_DATA_RDY ならば、もう一度 読み込む。

    エラーが起きたら、無視することになっている。ST_DATA_FULLだと、一旦 送信モードにしないとステータスが消えない。

    ここで バッファに受信データが入った。次はパケットの解析。

    usbRxToken = usbRxBuf[0];
    if ((usbRxToken == USBPID_SETUP) | (usbRxToken == USBPID_OUT)) {
    if ( recv_offset < 4 ) {
    if (usbRxLen) recv_offset = USB_BUFSIZE + 1;
    return; // RECV DATA0/1
    }
    if (recv_offset == USB_BUFSIZE + 1) { // buffer busy
    recv_offset = 0;
    goto send_nak;
    }
    if (recv_offset == USB_BUFSIZE ) { // buffer overrun
    recv_offset = 0;
    return;
    }
    // data top : USB_BUFSIZE - usbInputBufOffset
    usbInputBufOffset = USB_BUFSIZE - 3;
    usbRxLen = recv_offset - 3;
    recv_offset = 0;
    send_handshake(USBPID_ACK);
    return;

    SETUP か OUT の場合、次に DATA0/1 パケットが来る。続けて 読み込んで バッファにくっつけてしまう。ただし、usbRxLen が 0 でなければ、受け取った後、NAK を送る。recv_offset = USB_BUFSIZE + 1 として後で分かるようにしておく。DATA0/1 パケットを受け取った後、バッファが溢れていたら NAK は送らない。

    このコードで DATA0/1 トークンのチェックはしていない。後で入れておく必要がある。
    あと、RxLen が 0 でないときでも、DATA パケットが開始されるまでに SETUP/OUT を受け取らないと、DATA パケットを受け取ってしまう。ここは直すの面倒。

    最後に、usbRxLen をセットして、ACK を送り割り込みを抜ける。

    IN が来たら送信。usbRxLen が 0 以外なら NAK を送るのは、SETUP/OUT と同じ。アドレスが違うときも NAK 。
    次に エンドポイント番号によって、送信バッファを選択。send_len には、SYNC と CRC-16が含まれるので udrv では、-3 する。これで送信の準備は出来た。ちなみに、これ以外のパケットが来ても何もしない。

    } else if ( usbRxToken == USBPID_IN ) {
    if ( usbRxLen < 0 ) goto send_nak;
    i = (usbRxBuf[1] >> 1);
    if (i & (i != usbDeviceAddr)) goto send_nak;
    i = (usbRxBuf[1] << 3) & 0x08 | (usbRxBuf[2] >> 5) & 0x07;
    if (i == 0) {
    send_len = usbTxLen;
    send_ptr = usbTxBuf;
    usbTxLen = USBPID_NAK;
    } else if ( i == USB_CFG_EP3_NUMBER) {
    send_len = usbTxLen3;
    send_ptr = usbTxBuf3;
    usbTxLen3 = USBPID_NAK;
    } else {
    send_len = usbTxLen1;
    send_ptr = usbTxBuf1;
    usbTxLen1 = USBPID_NAK;
    }
    if (send_len > 3) send_len -= 3; // remove SYNC , CRC-16
    usbDeviceAddr = usbNewDeviceAddr;
    (送信処理)
    } else {
    recv_offset = 0;
    return;
    }

    いよいよ送信にはいる。まず、send_len が負の場合 ACK などのハンドシェークの PID を送る仕様。0 なら、送るものがないので NAK 。
    いよいよ送信だが、まずは、送信モードにする。最初は FIFO が空で nIRQ は H 。詰めていって 半分を超えると L になる。ここまで詰めたら送信スタート。半分に満たない場合は、スタートと共に nIRQ が L になる。

    割り込み要求ビットが立っているので、送信スタートの直後にクリア。次にまだまだ詰められるので 20 を上限に詰める。
    最後に 全部詰められたら、割り込み要求ビットをクリア。

    if (send_len < 0) {
    send_handshake(send_len);
    send_len = 0;
    return;
    }
    if (send_len == 0) goto send_nak;
    ctrl_write(CTRL_SEND_MODE);
    bit_clear(nCS_PORT, nCS_BIT);
    do {
    spi_write(*send_ptr++);
    while ( (--send_len) && bit_is_set(nIRQ_PIN, nIRQ_BIT) );
    while (spi_busy())
    ;
    bit_set(nCS_PORT, nCS_BIT);
    ctrl_write(CTRL_SEND_START);
    bit_set(EIFR, INTF0); // clear INT0 req
    re_send:
    if (send_len > 0) {
    bit_clear(nCS_PORT, nCS_BIT);
    i = 20;
    if (send_len < i) i = send_len + 1;
    while (--i) {
    spi_write(*send_ptr++);
    --send_len;
    }
    while (spi_busy())
    ;
    }
    bit_set(nCS_PORT, nCS_BIT);
    if (send_len == 0)
    bit_set(EIFR, INTF0); // clear INT0 req
    return;

    だいたいこんな感じだが、送信はちょっとグダグダ。SPI が遅ければどうせ全部詰めることになるのだ。nIRQ など見ないで 24 など 規定数詰めたらスタート 。残りを順次詰めて、最後に 割り込み要求ビットをクリアで良いと思う。

    まぁ AVR 以外も想定してロジックを考えてみたのだが、24MHz 以上とかの 高速なSPI とかじゃないと意味なさそう。... Low-Speed を忘れていたが、こちらは最大パケット長が 8 なので余裕。全部詰めてスタートで良い。

追記: バグがいくつか

    送信で、NAK などハンドシェークパケットを送る際に Length の方にセットする仕様なのだが、変更する必要があった。
    ハンドシェーク の PID は以下のとおり

    /* handshake */
    #define USBPID_ACK 0xd2 /* 1101 0010 */
    #define USBPID_NAK 0x5a /* 0101 1010 */
    #define USBPID_STALL 0x1e /* 0001 1110 */

    Low-Speed では、DATA パケットを送る場合 の Length の最大値は 12 なのだ。V-USB では、Length の bit4 が 1 だと ハンドシェークと判断する。この仕様だと 32 バイトのパケットサイズすら対応できない。

    PID の bit0 が 0 なのを利用して、0x80 | (PID >> 1) とすることにした。

    ただ、こうやって仕様変更すると usbdrv.c の方を結構修正しないといけない。10 ヶ所ぐらいある。あと usbTxLen & 0x10 などとして判断しているところもあってこれも 0x80 に変更しないといけない。

    次に受信のほう。パケットを受けられるだけ受けるというのは、どうも無理がありそうだ。

      SOF → SETUP → DATA0

    例えばこんな風にパケットが来たらくっつけてしまうやりかただと対応が面倒。

    どうも 受信中にパケットの切れ目を判断してパケット毎に処理しないとダメそうだ。判断は簡単だが、ループの内側に if 分が 2 つほど入ることになる。ちょっと面白くないが、とりあえずは、やむを得ないということにしよう。

    IN (device → host) のデータ再送未対応。

    V-USB も分かっていながら未対応にしている。作っているのは、サイズの制限など気にしてないので、対応させておきたい。

    バスリセット 未対応

    ホストがバスリセットすると、たぶんおかしくなる。

ACK や NAK を返すタイミングについて

    どうも気になって ググってみると ... ACK や NAK を返すタイミングは相当にタイトらしい。Low-Speed で 18 クロックだとか。( USB の仕様書を見てみたが、 Full-Speed も同じだった)

    いつまでも待っていたら HOST が次を始められない。それはそうだ。

    データがぶつかるのは、IN の後 デバイスから送られる DATA0/1 と NAK/STALL ハンドシェーク。HOST が DATA0/1 を送った後の デバイスから送られる ACK/NAK/STALL ハンドシェーク。

    ... となると いままで作ってきた udrv の仕様では全然ダメだ。全然間に合わない。変更するにも規模が足りない。

    DATA0/1 を受けとれない状態なら NAK か STALL。受け取った後 エラーが起きなければ ACK 。こういうものだけなら、規模がもうすこしあれば行けそうな気がする。だが、DATA を送信するのは一体どうするのだろう?最初からデータを入れとかないと無理なのでは?

      あと、Low-Speed 専用ならどうだろう? 1.5 MHz で 18 clk ということは、20 MHz で 240 clk 。受信完了し続いての動作だから、可能なやりかたはありそうだ。

      だが、12MHz で 18clk は、20 MHz で 30 clk 。SPI では 1バイトを送る時間もなさそう。これは諦める。対応する余裕はない。

      Full-Speed device に対応するには、エンドポイント毎の バッファを持ったモジュールを MCU の代わりに入れて、高レベルのコントローラにするしかないような気がする。いま作っているのは、単なる物理層だから無駄にはならない。だが、高レベルのコントローラまで設計するつもりは今はない。

      ただ、Low-Speed でも 受信完了してから 受信データを受け取り ACK を返すのでは遅すぎる。受信したら即受け取りでないと ダメかも知れない。

    逆に HOST 専用ならどうだろう? HOST が制御しているのだから、送信同士がぶつからないように制御はできる。USB の仕様書でも、送信同士がぶつかるケースについてだけ言及しているように見える。

    ところで、device 側から見ると、ACK が来なかったら、(たぶん)次の要求は再送。このあたり、V-USB ではどうなっているのだろう? 気になってきた。

      ; Comment about when to set usbTxLen to USBPID_NAK:
      ; We should set it back when we receive the ACK from the host. This would
      ; be simple to implement: One static variable which stores whether the last
      ; tx was for endpoint 0 or 1 and a compare in the receiver to distinguish the
      ; ACK. However, we set it back immediately when we send the package,
      ; assuming that no error occurs and the host sends an ACK. We save one byte
      ; RAM this way and avoid potential problems with endless retries. The rest of
      ; the driver assumes error-free transfers anyway.

    なんかわざわざコメントが書いてあった。やっぱり 送信後 バッファを捨ててしまうのはまずそうな ... たぶん ACK が来たときに usbTxLen を更新してバッファを捨てれば良い。

    さて、device では タイムアウト が必要ということではなさそうだ。デバイス依存になるかも知れないが、Full-Speed の HOST は可能かも知れない。

ホストの検討

    なかなか問題が多そうだが、デバイス側はどうすれば良いか分かってきた。では、ホストはどうしたら良いのだろう。これも問題がありそうだが、強引に検討だけはしておこう。

      実現できたとしても、実用上の利点というのは、ほとんどなさそう。デバイス側が USB の勉強になったように、これもまた勉強。

      作るためには、ホストの制御を基本から理解する必要がある。逆にこれが使えるぐらいの知識が出来たら、他の OTG 付き MCU で制御できるようになるはず。V-USB の HOST 版というものもたぶん作れるようになる。

      OTG 付きの MCU というと、秋月の PIC32MX220F032B がわずか 220 円で手に入るようになった。これを使いこなせば実にいろいろできそうな気がする。

    さて、HOST にしてなにを制御するのか? 汎用的なものは一切考えない。それは PC でやるべきことだ。一応電子工作として テーマを 2 つ

      (1) キーボード や マウスの制御

      これができるようになると、PS/2 変換器が作れそう。ただ、V-USB のような 直接制御が可能そうなので、実際には udrv で作る必要はないだろう。あと、HUB には対応するのは、面倒そうだが ... いったいどうするのか 後回しにするがいずれ検討したい。

      (2) BlueTooth ドングル

      USB BlueTooth ドングルは、妙に安いので これが作れると嬉しいような気がする。実際に作るなら、PIC32MX220F032B で変換器を作るのが最も安くあがりそうだが ... まずは udrv で検討する。

      はたして、可能なのかどうか? 最初の壁はパケットサイズ。MaxPS=64 みたいなので、結構厳しいものがある。

    目標はできたが、どのように設計するのかについて条件がある。まずはそこから。

      (1) V-USB のアセンブラコードを最小限度の変更で利用できるようなインターフェイスにする。

      Low-Speed の HOST なら ピンを直接制御しても 作れそう。ただ、送信・受信をするコードを 一から作れるような気はしないので、V-USB のコードを流用するにはどうしたら良いかについて、最初から考慮しておく。

      (2) PIC32MX220F032B が最終ターゲット

      AVR にも HOST 付きのものはあるが、どうも 最近は device only のものばかりになっている。XMEGA にも USB 付きが出てきているが device ばかり。

      まだ、AVR を中心に使おうとは思っているのだが、PIC32MX を専用IC にしてしまうような使い方なら 使ってみたいとは思っている。なにしろ値段が安い。220 円ならホストインターフェイスを買うより安くなってしまう。

      (3) MicroChip の USB Framework は使わない。

      これを使うと MicroChip 以外のものを扱うとき困ってしまう。API も合わせない。作りたいのは、上記の 2 つぐらいだし、理解するために試しに作ってみるだけなので、いきなり高い完成度のものは狙わない。

      もっと言うと、MicroChip が出している開発ツールを一切使わないで開発できるようにしたい。なかなか厳しそうではあるが、使いたい MIPS チップは他にもある。ちなみに、書き込みは、 OpenOCD が既に対応しているそうだ。問題なのはライブラリだが、(他のMIPSチップ向けだが)作りかけたものがある。

    こういう条件で作り出すのは良いのだが、さてどうしよう。

    最初に PIC32MXの USB インターフェイスがどういうものなのか、あたりを付けないといけないだろう。
     ・ 秋月の PIC32MX220F032B
    ここにデータシートがあるので 見てみる。で、レジスタの名前 例えば U1OTGIR で検索すると ... PIC24F の 日本語ドキュメント がヒットする。

    構成図を見ると、PIC32MX と PIC24F の USB モジュールは実に似ている。どうもほとんど同じようだ。なら、調査は、PIC24Fの方で済ますことにする。

    つらつらと眺めてみたが、HOST の場合、それほど高機能ではなさそう。エンドポイントも 1 つだけを使う。なら udrv と大差ないんじゃないかと言う気がしてきた。udrv 向けに普通に作れば対応可能だろう。

      あと気になったのは、U1SOF レジスタ。HOST のみの機能で、送信同士のぶつかりを防ぐためのタイムアウト値を格納する。最大値は 255 。なるほど、変更できるかどうか別にして、HOST では、タイムアウトが必ずあるものだと思った方が良さそうだ。逆に、デバイスで ACK 待ちのタイムアウト値はレジスタとしてはない。デバイスでは、タイムアウトという概念はなくても良いのかも知れない。

    次に、HOST がどうやって送信するのか? V-USB の device のコードは、見てきたわけだが、HOST から来る 指令に対応するだけのものなのだ。だからこの知識だけでは、HOST が どのような送信の仕方をするのかは全然分からない。

    ぐぐってみると PICFUN さんのところの、
     ・ http://www.picfun.com/usb03.html
    が引っかかった。随分前にも見たことがあるのだが、理解する必要もなかったので忘れていた。

       ・ 概念的には、SOF で始まるフレームがある。
       ・ フレームのなかには、SETUP - DATA0 - ACK とか IN - DATA0/1 - ACK とか一連の シーケンスからなる トランザクションがある。
       ・ バルク転送というのは、本来 フレーム内で必要なトランザクションを行ったあと、余った帯域で 行うもの。(優先度が低い)
       ・ インタラプト転送というのは、優先度が高いが、頻繁に行うものではない。
       ・ フレームについて概念的にと書いたのは、デバイスから見ればフレームがあってもなくても処理に大差ないため。今まで作ったもののレベルだと、リセットするのを防ぐとかその程度の意味しかないような感じ。

    今の理解はこんな感じ。分からないのは、

       ・ Low-Speed では、EOP のみを送るルールがある。
       ・ そうなるとフレームはなくても良さそうなもの。
       ・ SOF は送らなくとも良いのか or 送ってはダメなのか ?

ホストの設計

    随分前置きが長くなった。さぁ、設計をしてみよう。

    Low-Speed では、必ずしもフレームを構成しなくても良さそうなのだが、定期的に EOP を送らなければならない。そこから考えると、1ms 周期のタイマ割り込みを作りそこから、一連の 送受信処理につなげていく 構造が良さそうだ。

    Full-Speed では、最初に SOF を送る。Low-Speed では、SOF は送らない? 結局送信すべきものがないと EOP だけ送る。

    トランザクションの途中では、かならず受信待ちが入るわけだが、ここで割り込みを一旦抜ける。返ってこなかったら、次の 1ms のタイマ割り込みで タイムアウトの処理を行うことにする。これで良いのか? という気もするのだが、動作ポイントは、タイマ割り込みと 受信割り込みということで進めよう。

    次にバッファの受け渡し。受信待ちに入れば、タスクの方が動作可能になる。まず送信について考えてみると、そこで 次のデータを準備したりできるわけだ。だが、ACK が返らない限り バッファが開放できない。送信では(少なくとも)ダブルバッファが必要そうに思える。

    そもそも 1 フレームで 1 つしか送信できないとすると、MaxPS=64 でも 64KB/sec にしかならない。BlueTooth のことを考えると これじゃ帯域が低すぎるような気がする。ダブルバッファでは足りず FIFO にしないといけないかも知れないが、まずは ダブルバッファということにしておこう。

    では受信ではどうなのか? データを受け取り ACK を返せば受信成功だ。で、すぐに次の受信待ちになる。このときに、タスクで受信処理をできるかも知れない。やっぱりダブルバッファは必要そう。

    バッファの構造はどうしよう。

      V-USB では、受信バッファに 全部収めて、さらに usbRxLen / usbRxToken とかの情報を設定するルールだった。ここは、ダブルバッファにするが、同じようなものにすればよさそうだ。

      送信は、3 つのバッファがあって、IN を受信すると エンドポイントに対応したバッファを選んで送信する仕組みだった。送るデータは ACK/NAK などを送るか、バッファに格納されたデータを送るかの 2 種類。

      HOST の場合、ACK を 受信完了で送ることはあるが、NAK/STALL を送信することはない。そのかわり SETUP/OUT に続いて DATA0/1 を送ることになる。また、IN/OUT/SETUP のトークンを送るには、アドレスとエンドポイントの情報が必要。ついでに CRC-5 を生成しないといけなくなった。

    V-USB の送信コードを有効に使うならば、Length とバッファのアドレスを指定して送信する機能と ACK を送信する機能を使うことになる。どちらも 割り込みから抜けてしまうことになっているが、そこは 戻ってきてもらわないと困る。

    あと、SETUP/OUT + DATA で 送信バッファを 2 つ使うのではバッファの効率が悪い。1 つのバッファに詰めることにする。トークンより大きいデータが入っていたら 2 回に分けて送信するようなイメージ。call したい ところは 2 ヵ所だが、サブルーチンにする必要はないかもしれない。

    一方 ACK を返すのは、たったの一ヶ所。こちらは無条件ジャンプで事足りる。

    ここまでがドライバの仕事。要するにバッファの受け渡しで 事が進む。タスク側の処理がこれに加えて必要になる。あと、接続しただとか、切断しただとか状態管理も別途必要で、これについては後で検討しよう。まずは、中核となる部分だが、ここのイメージはできた。

状態監視とか

    接続後の動作しか検討していなかったのだが、気になってきた。今の udrv で 接続や切断、リセットに対応できるのだろうか?

    なにも接続されていない状態では、D+/D- ともに L で SE0 状態。接続されていれば idle 状態。

      この状態をホストがどのように検出できるか?

      受信をスタートさせると SE0 なのですぐに bit0: 送受信要求 が 0 になる。これは、nIRQ が L になることでも検出できる。続けて 2 回実行しても同じ結果なら、接続されていないことが分かる。

      受信をスタートさせても、nIRQ が H なら、接続はされていて idle 状態。

      これを定期的にポーリングしてやれば良いのだろう。

    逆に、ホストがリセットしたい場合、50ms 以上 SE0 状態にする。

      device 側が、これを検出する仕組みは、ホストと同じ。だが、ホスト側で 強制的に SE0 状態にする仕組みを作っていない。

      実をいうと -13 版では、コントロールレジスタに 0x84 を書き込むことで、ENABLE ピンを L にする 仕組みを入れた。このピンでプルアップすることで、device では、切断状態を作り出せる。

      ホストでは、0x88 を書き込むことで、D+ , D- ともに L 出力ということにしようかと思う。ただ、この程度の変更すら規模的に厳しい。ただ、既に SUPPORT_CRC5S=0 としていてそのおかげで入った。要するに ホストで使うトークンや SOF に対して CRC-5 は自動生成しない。生成済みのデータを送る必要があるが、僅か 11 bit 分の CRC 計算だし重くはないだろう。

関連記事
posted by すz at 15:51| Comment(0) | TrackBack(0) | CPLD

2012年06月28日

USBコントローラの設計

USBコントローラを設計してみたい。-- これは、FPGA を始めたときから思っていたこと。そろそろ、それが出来るレベルになったかも。少なくともどうやって設計すべきなのか見当も付かなかったのが、今は見当ぐらいは付くようになった。

最初は、規模が小さい MachXO2-256 で作れるものとして検討してみよう。もちろん、規模が小さいわけだから、一般的なコントローラの機能は作れない。では何を作るのか?

    レベル 0)
    パケットを送出し、受け取る。そういうものならまずは可能だろう。ホストから来た 0/1 のシーケンスをそのまま送るのは、難しくなさそうだ。

    受信を考えると、まず相手のクロックに同期させなければならない。4 倍クロックを使って、最初の 信号の立ち上がりに合わせようかと思う。ここのところは、少々難しそうだが、USART などと似た様なものだから、それほどでもないだろう。

      パケットの開始(SOP)は、送信側からみると、入力状態から、アイドル状態と同じ値を一旦出力することらしい。アイドル状態は、出力しない状態で Low-Speed では、D- がプルアップされているわけだから D- = H , D+ = L 。Full-Speed では、逆になる。

      続いて SYNC データを送出する。データは LSB first で 0x80 。これを NRZI (Non Return Zero Invert) エンコードすると H-L-H-L-H-L-H-H (Low-Speed での D+/Full-Speed D-)。 最初に L → H と変わるから ここを拾って 受信クロックを同期させるわけだ。 クロックが同期してなければ、1 つ遅延させる。 6 回チャンスがあるから、最後の H-H までに同期するはず。

      パケットの終了(EOP)は D- , D+ 共に L 出力 x 2クロックの後 、アイドル状態と同じ値を一旦出力して、入力状態。受信では、D- , D+ 共に Lを検出したら、1 クロック待って 受信を終了させれば良い。

    あと難しそうなのが、FIFO が必須ということ。途切れなく 送受信しなければならないので、データが来たからといって、すぐに 送信すれば良いものでもない。受信では、FULL にならないようにする配慮も必要。

    ちなみに、Full-Speed での最大パケットサイズは、512 Bytes だったと思う。MachXO2-256では、合計で 64B ぐらいしか取れそうにない。なかなか考えどころ。

    レベル1)
    USB では、NRZI (Non Return Zero Invert) エンコーディング というのを行なっている。レベル1では、エンコード/デコードする 。後で説明するが、NRZI を 論理レベルに変換すると、MCU 側の処理が楽になる。コントローラ側でも 送受信の単位が バイト単位になって FIFO まわりが少し楽になる。

    レベル2)
    パケットの形式を知った上で、いくつかの機能をサポート。パケット ID(PID) が正しいかどうかの検出や、CRC のチェック/生成など。

    このレベルより高度なものは規模的に無理。このレベルでも怪しい。一応目標にはするが、レベル1になるかも。

さて、こういうものを作るとして、MCU との 送受信インターフェイスはどうしよう。FIFO を使うから基本は、8bit パラレル。

ただ、パラレルでは、ピンを消費する上に AVR なんかだと SPI より遅くなる。やはり SPI かと考えている。まぁこれは、出来た後でも良い。

ところで、USB PHY のインターフェイスは、UTMI というものが定義されているらしい。SMSC では、これのピン数を減らした ULPI( UTMI + Low Pin Interface) の PHY を製品化している。これに合わせることは、考えていないが、出来た後、再度検討してみよう。まずは、上記のものを設計しなければ。

USBでの信号の扱い

    基本は、差動信号だが、上で出てきたように例外がある。(両方 L レベル)

    FPGA は一般に 差動出力も持っているのだが、これをサポートするため、シングルエンド x 2 で操作すれば良いだろう。受信側も 同じ事情。差動入力もできるが、シングルエンドも必要。FPGA は 1つのポートで 複数の入力の仕方ができるものなのだろうか? その上 出力にも切り替えられないといけないのだが ...

    多分無理だと考えて、差動入力をパラレルに接続することにしよう。ポートが勿体なければ、シングルエンドで 値を読むことにしても良い。

    さて、デバイスとして構成するなら 1.5K のプルアップが必要だ。ポートが余っていれば 2つのポートに抵抗を付けることで、設定可能にしても良いだろう。ホスト側は、15K のプルダウン。これは、FPGA のプルダウン機能でいけそう。

    こういうことなので、最大は D-,D+ にそれぞれ 3 つづつ 計 6 個のポートが必要。

LRZI について

    簡単に説明してしまうと 論理 0 出力は反転、論理 1 出力は 状態保持。
    そして、同じ状態が 6 クロック続くと 次に 0 を挿入する。-- 要するに論理 11111 は、0 または 1 の状態が 6 クロック続くので 0 が挿入される。

    Full-Speed は、物理的な差動出力が逆になるわけだが、受信側から見ると、同じ値になる。初期状態が違うだけで、反転-保持の関係は変わらないからだ。

以上の理解で、レベル1は設計できるはず。さあ設計してみよう。

クロックジェネレータ

    48 MHz を入力して、12 MHz を作る。Low-Speed の場合は、6MHz 入力で 1.5 MHz を作る。

    PLL のない MachXO2-256 で 48 MHz をどうするのか? という問題があるが、とりあえず考えない。あとクロックは、送信中、受信中のみ 生成することにしよう。

    module udrv_clkgen (
    input CLK_4X
    , input DP
    , input SYNC_REQ
    , output SYNC_ACK
    , output CLK
    );
    reg [1:0] clk_ph = 0;
    reg r_dp = 1'b0;
    reg r_done = 1'b0;

    assign CLK = clk_ph[1];
    assign SYNC_ACK = r_done;

    always @(posedge CLK_4X)
    begin
    r_dp <= DP;
    if (~SYNC_REQ | ~(r_dp ^ DP) | (clk_ph == 3) )
    clk_ph <= clk_ph + 1;
    if (~SYNC_REQ)
    r_done <= 1'b0;
    else if ((r_dp ^ DP) & (clk_ph == 3))
    r_done <= 1'b1;
    clk_ph <= clk_ph + 1;
    end
    endmodule

    // _______
    // CLK |_______|
    //
    // clk_ph 3 | 0 | 1 | 2 | 3 | 0
    // _____________
    // DP >< ><
    //
    // _________
    // SYNC_ACK __|
    //

    SYNC_REQ は、上位モジュールからの受信要求で、SYNC_ACK が 1 になると 受信準備が出来たということで、受信モジュールを enable するつもり。

    (clk_ph == 3) 以外で DP が変化すると 1/4 クロック遅らせる。OK になれば、SYNC_ACK が 1 になるので、(上位モジュールが) SYNC_REQ を 0 にする。

受信モジュール

    DP 以外に SE0 という入力信号を作ることにした。これは、DP-,DP+ を シングルエンドとして見て共に 0 のとき 1 にする。RECV_EN で受信開始だが、SYNC の途中なので、1 - 1 または 0 - 0 が来た次から 採取開始。データは、FIFO に入れることにする。FIFO が溢れても関知しない。

    module udrv_reciever (
    input CLK
    , input RECV_EN
    , input DP
    , input SE0
    , output [7:0] RECV_DATA
    , output RECV_DATA_RDY
    , input RECV_DATA_ACK
    );

    reg recv_stat = 0;
    reg r_dp = 0;
    reg [7:0] data_out;
    reg data_rdy = 0;
    reg [7:0] r_data;
    reg [2:0] data_count = 0;
    reg [2:0] one_count = 0;

    assign RECV_DATA[7:0] = r_data[7:0];
    assign RECV_DATA_RDY = data_rdy;

    always @(posedge CLK)
    begin
    r_dp <= DP;
    if (~RECV_EN | SE0)
    begin
    recv_stat <= 0;
    data_count <= 0;
    r_data <= 0;
    one_count <= 0;
    end
    else if (recv_stat == 0)
    begin
    if (~(r_dp ^ DP)) // end of SYNC
    recv_stat <= 1;
    end
    else
    begin
    if (~(r_dp ^ DP)) one_count <= one_count + 1;
    else one_count <= 0;

    if (one_count <= 5)
    begin
    r_data <= { ~(r_dp ^ DP), r_data[7:1] };
    data_count <= data_count + 1;

    if (data_count == 7)
    data_out <= { ~(r_dp ^ DP), r_data[7:1] };
    end
    end

    if ( RECV_EN & ~SE0 & (recv_stat == 1)
    & (one_count <= 5) & (data_count == 7) )
    data_rdy <= 1'b1;
    else if (RECV_DATA_ACK)
    data_rdy <= 1'b0;

    end
    endmodule

送信モジュール

    SEND_EN のとき送信開始。まず SYNC を送出し、次からデータを送る。データが途切れると送信終了。
    送信側は、シングルエンドとしてのポート状態を生成する。ちょっと変なのだが、(SEND_DONE != 0) のとき DP,DM を出力することを想定している。

    一応 受信モジュールとのペアで、パラレルの通信ができるようにした。送信が途切れると パケットを終了し、再開すると自動的に SYNC が付く。CRC などはないから、再送できないので実用的ではないが、ロジックのデバッグには役立ちそうだ。

    send_stat は、6 状態になった。シーケンスがあるので、そんなものだろう。

      send_stat = 0 : アイドル
      send_stat = 1 :
      send_stat = 2 : SYNC とデータ
      send_stat = 3 : SOP 1 (SE0)
      send_stat = 4 : SOP 2 (SE0)
      send_stat = 5 : SOP 3

    module sender (
    input CLK
    , input SEND_EN
    , output SEND_DONE
    , output DP
    , output DM
    , input [7:0] SEND_DATA
    , input SEND_DATA_RDY
    , output SEND_DATA_ACK
    );

    reg r_dp;
    reg r_dm;
    reg [2:0] send_stat = 0;
    reg r_out = 0;
    reg [6:0] data_in;
    reg data_ack = 0;
    reg [7:0] data_count = 0;
    reg [7:0] one_count = 0;

    assign DP = r_dp;
    assign DM = r_dm;
    assign SEND_DONE = (send_stat == 0);
    assign SEND_DATA_ACK = data_ack;

    always @(posedge CLK)
    begin
    if (~SEND_EN)
    begin
    send_stat <= 0;
    end
    if ( (send_stat == 0) & SEND_EN )
    begin
    send_stat <= 1;
    data_count <= 7;
    data_in[7:0] <= 7'b1000000;
    r_out <= 1'b0;
    end
    else if (send_stat == 1)
    send_stat <= 2;
    else if ( (send_stat == 2) & (one_count <= 5) & (data_count == 0) )
    if (SEND_DATA_RDY)
    begin
    data_count <= 7;
    data_in[6:0] <= SEND_DATA[7:1];
    r_out <= SEND_DATA[0];
    end
    else // exit sending
    send_stat <= 3;
    else if ( (send_stat == 2) & (one_count <= 5) )
    begin
    data_count <= data_count - 1;
    data_in[6:0] <= { 1'b0, data_in[6:1] };
    r_out <= data_in[0];
    end
    else if (send_stat == 3) send_stat <= 4;
    else if (send_stat == 4) send_stat <= 5;
    else if (send_stat == 5) send_stat <= 0;

    if ( (send_stat == 3) | (send_stat == 4) ) // SE0
    begin
    one_count <= 0;
    r_dp <= 1'b0;
    r_dm <= 1'b0;
    end
    else if (send_stat == 2)
    begin
    if ( (~r_out) | (one_count > 5) )
    begin
    one_count <= 0;
    r_dp <= ~r_dp;
    r_dm <= r_dp;
    end
    else one_count <= one_count + 1;
    end
    else // if ( (send_stat == 1) | (send_stat == 5) )
    begin
    one_count <= 0;
    r_dp <= (FS) ? 1'b1 : 1'b0;
    r_dm <= (FS) ? 1'b0 : 1'b1;
    end

    data_ack <= SEND_EN & SEND_DATA_RDY & ( (send_stat == 1)
    & (one_count <= 5) & (data_count == 0) );
    end
    endmodule

FIFOモジュール

    FIFOモジュールも自分で作る。Dual PORT の分散RAM ベース。32 バイトで 30 スライス 、64 バイトだと 50 スライス。たぶん 32 バイトしか選べない。

      32 バイト増えて 20 スライス増えている。2 バイトあたり 1 スライス だから 16bit x 1bit が 2 LUT の計算。Dual PORT ならこの値が適切。

      送受信は、同時には行わない。FIFO を 2 つ持つのは、もったいないので可能なら共用にしたい。

    module udrv_fifo # (
    parameter DEPTH = 5
    // parameter DEPTH = 6
    ) (
    input CLK
    , input RST
    , input [7:0] I_DATA
    , input I_DATA_RDY
    , output I_DATA_ACK
    , output I_DATA_FULL
    , output [7:0] O_DATA
    , output O_DATA_RDY
    , input O_DATA_ACK
    );
    reg [5:0] mem [0:2**DEPTH-1];

    reg [DEPTH-1:0] i_addr = 0;
    reg [DEPTH-1:0] o_addr = 0;
    wire [DEPTH-1:0] i_next = i_addr + 1;
    wire [DEPTH-1:0] o_next = o_addr + 1;
    reg i_ack = 0;
    reg o_rdy = 0;
    assign I_DATA_FULL = (i_addr != o_next);
    assign I_DATA_ACK = i_ack;
    assign O_DATA_RDY = o_rdy;

    always @(negedge CLK)
    begin
    if (RST)
    begin
    i_addr <= 0;
    o_addr <= 0;
    i_ack <= 1'b0;
    o_rdy <= 1'b0;
    end
    else if (I_DATA_RDY & ~i_ack)
    begin
    if (~I_DATA_FULL)
    begin
    i_addr <= i_next;
    mem[i_next] <= I_DATA;
    end
    i_ack <= 1'b1;
    end
    else
    i_ack <= 1'b0;

    if ( O_DATA_ACK & (i_addr != o_next))
    begin
    o_addr <= o_next;
    o_rdy <= 1'b1;
    end
    else if (i_addr != o_next)
    o_rdy <= 1'b1;
    else if ( O_DATA_ACK )
    o_rdy <= 1'b0;
    end
    assign O_DATA = mem[o_addr];

    endmodule

    以上が今回作ったもの。シミュレーションとかデバッグは未だ。あとインターフェイスに SPI を付けなければならない。そうしないとピンが多すぎて、QFN32 に収まらないのだ。

    で、専用の SPI モジュールを作ったのだが、問題がある。パケットより小さな FIFO で、Full-Speed に対応するには、12 M bps 前後で通信できないといけない。なかなかに厳しい条件だが、それは置いておく。問題は、SPI の SCK が CLK より早い場合にも対応しないといけないところ。FIFO との ハンドシェークが鍵なのだが、うまく行っていないかも。

    とりあえず QFN32 に収めるために、8bit データを inout に変更するのが良さそう。

    ところで、作ったは良いがどうやって使うのか? ... V-USB のコードを改造すればうまく制御できるのではないかと思っている。V-USB のなかで見たくもない部分は、通信のところで、アセンブラになっている。これをまるごと置き換えてしまえば 良さそうなのだ。ただ、Full-Speed に対応するには、48 MHz が必要になってしまう。Low-Speed で良いなら V-USB で良いし。結局のところ 規模の大きいFPGA のコアで使うぐらいか。

    なかなか使いドコロがないが、CRC のコードも作りたいし、それだけ追加してみて、シミュレータでチェックして、ひとまずの完成としよう。

    この後、送信をシミュレータでデバッグした。


    Full-Speed(FS) の例。FS では、D+(DP) がプルアップされているわけだから DP = H がアイドル。Hi-Z から DP = H なって、LHLHLHLL と SYNC コードを送出している。次に 0x33 を送るのだが、 ちゃんと LLHLLLHL となっている。

    終わりは、DP/DM 共に L を 2 クロックで、次に アイドルと同じ DP = H で最後に Hi-Z 。

    結構バグがあったが、なんとかデータを送ってくれるようになった。上で貼り付けたコードは、バグがあるままなので注意。正しいのは、最新のソースコードのみである。

      送信でインターフェイスを若干変更。ひとつは、送信STARTを指示するフラグ。FIFO にパケットを全部送ってから、送信というのが妥当だと思えたので。-- 以前に AT90USB162 のコードを作ったが 最大パケットサイズは、32 か 64 にしか出来なかった。それで十分でもあった。その程度ならば FIFOに全部入るのだ。

      もうひとつは、DP/DM を出力にするのを指示するフラグ。SEND 中というフラグで代用しようと思っていたのだが 1 クロックずれる。

    次は受信をデバッグしよう。送信データを受信側とつなぐのだが、CLK がずれるので、ちょっと工夫がいる。


    受信も一応動かせた。受信要求を出すと SYNC_REQ が 1 になって、CLK をずらす。確かに 180°ずれて SYNC が完了しているのだが、SYNC_REQ が ON/OFF を繰り返していて、ぎりぎりで 受信開始になっている。... まずそう。
    それは、おいおい直すとして ... 今回は、送信用、受信用 2 つのモジュールを接続したのだが、pull-up/pull-down が分からなかったので論理レベルで接続することにした。このため、LB_TEST という define を追加。


    0x33,0x34,0x35,0x36 を送信しているのだが、その 4つをちゃんと受け取って、受信終了。その後、RECV_REQ が ON/OFF を繰り返している。(受信前は、SYNC_REQ)。一応動きはしたが、やっぱりまずそうだ。

CRC について(基礎編)

    まずは基礎から。

    USBでは、CRC-5 と CRC-16 の2通りの CRC を使っている。CRC-5 は SYNC 込み 32bit のパケット専用で、それより大きいパケットでは CRC-16 を使う。CRC-16 と言っても、計算式はひと通りしかないわけではないらしい。USB での方法を知らないといけないのだ。

    (16bit) x^15 + x^14 + x^2 + 1
    (5bit) x^5 + x^2 + 1

    生成多項式にはこれを使う。初期値には all 1 を使い、出力は ビット反転した上で LSB と MSB も反転。

    コードで示した方が早そうだ。

    CRC-16:
    unsigned int r_crc = 0xffff;
    unsigned int In;
    for (i=0; i< XX; i++) {
    w = (r_crc ^ In) & 1;
    r_crc = (r_crc >> 1) & 0x7fff;
    if (w) {
    r_crc ^= (1<<0);
    r_crc ^= (1<<13);
    r_crc ^= (1<<15); // w
    }
    In = (In >> 1);
    printf("%04x r %04x\n", r_crc, ~r_crc & 0xffff);
    }
    CRC-5:
    unsigned int r_crc = 0x1f;
    unsigned int In;

    for (i=0; i< XX; i++) {
    w = (r_crc ^ In) & 1;
    r_crc = (r_crc >> 1) & 0xf;
    if (w) {
    r_crc ^= (1<<4);
    r_crc ^= (1<<2);
    }

    In = (In >> 1);
    printf("%02x r %02x\n", r_crc, ~r_crc & 0x1f);
    }

    出力は、~r_crc (bit反転したもの)なので注意。

    さて、受信で CRC値が含まれているが、これも含めて CRC 計算してしまうと ... 特定の値になる。CRC-5 では 5'b11001 , CRC-16 では 16'h4FFE 。(bit反転前の値(r_crc) なので注意)

CRC について(応用編)

    応用編というより実装編。

    module udrv_crc # (
    parameter WIDTH=16
    // parameter WIDTH=5
    , parameter SEQUENTIAL_MATCHING=0
    ) (
    input CLK
    , input [1:0] SEL
    , input I // LSB first
    , output O // LSB first
    , output MATCH
    , output [WIDTH-1:0] CRC
    );

    SEL で 動作を指定するのだが、基本的に 2 つのモードがある。ひとつは、01 で CRC 計算。もうひとつは、10 で、計算結果をビットストリームにして出力 -- 送信などで使う。
    00 は、なにもしない。-- USB では、0 が挿入される場合があるから、これが必要なのだ。あと、11 は、初期値にする 。
    パラレルの CRC は、シミュレータのチェック用で使わないのが基本。

    同時に CRC の一致検出 もつけた。やりかたには 2 通りある。ひとつは、計算結果を出力して、入力と比べるというやりかた(SEQUENTIAL_MATCHING=1)。もうひとつは、最後まで計算して特定の値になったかどうか チェックするやりかた(SEQUENTIAL_MATCHING=0)。計算結果を出力してしまうと、以降計算はできないので両立はできない。

    (SEQUENTIAL_MATCHING=0) は、受信での CRC-16 チェックに使う。USB ではパケットを受け取り終わってはじめて、2 バイト前が CRC だったと分かる。だから、これを使う以外にはない。

    (SEQUENTIAL_MATCHING=1) は、CRC-5 に使おうかと思っている。

    reg [WIDTH-1:0] r_crc = (-1);
    wire I2 = (r_crc[0] ^ I);
    assign CRC [WIDTH-1:0] = ~r_crc[WIDTH-1:0] ;
    assign O = ~r_crc[0];
    reg r_match = 0;
    assign MATCH = (SEQUENTIAL_MATCHING) ? r_match
    : (WIDTH == 5) ? ( ~r_crc == 5'b11001 )
    : (WIDTH == 16) ? ( ~r_crc == 16'h4FFE )
    : 0;

    always @(negedge CLK)
    begin
    if ( SEL == 2'b11 ) // RST
    begin
    r_crc <= (-1);
    r_match <= 1;
    end
    else if ( SEL == 2'b10 ) // Extract bitstream
    begin
    r_match <= (I ^ r_crc[0]) & r_match;
    r_crc <= { 1'b0, r_crc[WIDTH-1:1] };
    end
    else if ( SEL == 2'b01 ) // Calc.
    if (WIDTH == 16)
    r_crc <= { I2 , r_crc[15]
    , ( r_crc[14] ^ I2) , r_crc[13:2]
    , ( r_crc[ 1] ^ I2) } ;
    else if (WIDTH == 5)
    r_crc <= { I2, r_crc[4]
    , ( r_crc[ 3] ^ I2), r_crc[2:1] };
    end
    endmodule

    実装はこれ。短いとみると長いとみるか...

    これを 送受信に組み込むことは成功した。送信・受信でモジュールを兼用。-- わずか 3 スライスほどだが、節約できた。合計の規模は、110 になった。ただし、FIFO のサイズを 32 バイト にしたときの話。これで FIFO のサイズを 64 バイトにするのは無理になった。

    実は、ピンの数の方が切実。JTAGENB を使うようにすれば、21 pin 使えるのだが、既に 19 pin 使っている。受信では CRC エラーの出力でさらに 1pin 増えた。送信では、CRC を生成するかどうかの 設定を 2 種類入れたかったのだが、無理なので自動で 生成することにした。

      CRC-5 は、計算結果を入れるかどうかの判断をするタイミング(3バイト目 残り 5bit)で、次のデータがあるかどうか を見て なければ、入れる。そこできちんと終わらせないといけない。次のデータがあれば CRC-16 を入れることにする。( データを受け取り終わったら CRC-16 の 2 バイトが挿入される。)

    こういうロジックを入れて 110 スライスなのだ。CRC をサポートせず生のデータを送信・受信するなら 79 スライスになった。31 スライスも使っているが、CRC 自体は小さい。制御が大変なのだ。

    さて、入れたいメカニズムは入ったのだが ... 入出力をどうするのか まだ。
    SPI はひとつの案だが、詳細は詰めないと。それに今の 入出力 。これも一般的な仕様にする必要がある。

    ところで、48 MHz の件、 『クリスタルオシレータ(48MHz) SG-8002DC(3.3V)』 -- PLL なしだとこれを使うしかないようだ。

入出力のハンドシェーク

    これまで、全部が CLK に同期して動いていた。だが、別クロック系統のものと接続するには、具合が悪い。パラレルで MCU と接続する場合も今のままではダメだし、SPI と接続する場合も同様。

    まずは、パラレルのところの整備から始める。

    // ______________________ _______
    // DATA <______________________><_______ // // _______ _____ // RDY ________| |______________| // // ________ // ACK _____________| |________________ // // // CLK | | | | | //

    今考えているのを図にしてみた。
     1. ACK が 0 のとき DATA をセットできる。セットしたら RDY を 0 にする。
     2. DATA が確定したので、RDY を 1 にする。
     3. 受け取る方は、受信後、ACK を 1 にする。
    4. ACK が 1 になったら、RDY を 0 にする。
     5. 受け取る方は、RDY が 0 になったら ACK を 0 にする。
     最短 3 クロック。

    このルールにしたがうようにまず変更した。規模は 110 で変わらない。

    さて、BUS を使うなら、一般的なデバイス -- RD/WR で制御できるもの と同じようにしておきたい。ついでにコントロールレジスタも付けて ピンも減らしたい。

    RDについては、

    assign RECV_DATA_ACK = ~RD;

    で制御できる。データがあれば、ACK の前後で 安定している。なければ、前のデータが 読めるだけ。RD が H になった後 次のデータがセットされるので、読み込みサイクルが早すぎるとマズイ。なんなら FIFO のクロックを 4倍速にしたり マスター側に合わせて良い。それが出来るように ハンドシェークを見直したのだ。

    RECV_DATA_RDY は、BUS に接続するなら コントロールレジスタから読めると便利。だが、AVR などでポート制御するなら、ピンに出しておいた方が良い。

    assign DB[7:0] = (~RD & ~RS) ? RECV_DATA[7:0]
    : (~RD & RS) ? CTRL_REG[7:0]
    : 8'bz;
    assign SEND_DATA[7:0] = DB[7:0];

    出力制御は、こんな風にする。

    次に WRのほう。

    assign SEND_DATA_RDY = ~WR;

    WR を L にしたときデータが安定しているという条件なら、これでも良い。ACK は無視。勝手に L→ H→ L になる。不安なら、WR を遅延させるのも良いかも知れない。

    ここまで作ったところ 115 スライスになった。わずかな増加だが、これを元に SPI を付けるのだ。残り はわずか 13 スライス。

SPI インターフェイス

    これで、 SCK が CLK より早かろうが遅かろうが問題なくなった。いよいよ最後の機能として SPI を付ける。

    module udrv_spi (
    input MOSI
    , input SCK
    , input CS
    , output MISO

    , input [7:0] SPI_DATA_I
    , output SPI_DATA_I_ACK
    , input SPI_DATA_I_RDY

    , output [7:0] SPI_DATA
    , output SPI_DATA_RDY
    , input SPI_DATA_ACK
    );

    モジュール単体は、こう。SPI を パラレルに変換しているだけ。CLK もない。
    続いて本体

    reg [3:0] r_count = 0;
    reg [7:0] r_spi;
    reg [7:0] spi_out;
    reg r_mosi = 1'b0;

    reg o_data_ack = 1'b0;
    reg o_data_set = 1'b0;
    reg o_data_rdy = 1'b0;
    assign SPI_DATA_RDY = o_data_rdy;

    reg i_data_ack = 1'b0;
    assign SPI_DATA_I_ACK = i_data_ack;

    assign SPI_DATA = spi_out[7:0];
    assign MISO = r_spi[7];

    always @(posedge SCK)
    begin
    r_mosi <= MOSI;
    if (r_count == 1)
    i_data_ack <= 1'b1;
    else if (~SPI_DATA_I_RDY)
    i_data_ack <= 1'b0;
    end

    always @(CS , negedge SCK)
    begin
    if (CS) // LOAD
    begin
    r_count <= (-1);
    r_spi <= SPI_DATA_I;
    end
    else if (r_count == 7) // LOAD
    begin
    r_count <= 0;
    r_spi <= SPI_DATA_I;
    spi_out <= {r_spi[6:0], r_mosi };
    end
    else // SHIFT
    begin
    r_count <= r_count + 1;
    r_spi <= {r_spi[6:0], r_mosi };
    end

    o_data_ack <= SPI_DATA_ACK;
    o_data_set <= (~CS & (r_count == 7));
    if (o_data_set)
    o_data_rdy <= 1'b1;
    else if (~o_data_ack & SPI_DATA_ACK)
    o_data_rdy <= 1'b0;
    end
    endmodule

    悩ましいのは、動作のタイミングが足りないところ。

      通常、最後のタイミングは、negedge SCK で (r_count == 7) 。ここでデータ自体は受け取れる。o_data_set を 1 にすることも出来る。だが、o_data_rdy を 1 に出来ない。
      CS の変化も条件に入れてようやく 1 にできる。ただし、0 には出来ない。

      これでもシミュレータでは動く。だが、always @(CS , negedge SCK) という記述を期待通りにやってくれるかどうか? 不安だったりする。前に Xilinx で試したときは、ダメだったような ...

    それも問題だが、上位レイヤーでどう使うか?

    コントロールレジスタをサポートしたいのだが、どういうプロトコルにするか ... RS が邪魔なのだ。まぁ、余裕もなさそうだから、ピンから入力することにしよう。

    送信の場合、RS=0 にして、パケット分のデータを 送り、RS=1 にして、コントロールレジスタに書き込むことで、SEND_START を 1 にする 。FIFO が空になれば、一定時間後に 送信完了。

    受信が問題。通常は受信モード。FIFO が空でなければ、受信中。一定時間後に 受信は完了する。... なんだか苦しいが、ピンもロジックも足りない。内部ロジックには、送信中、受信中というのがあるので、コントロールレジスタをポーリングすれば、分かるようにしておこう。

    こんなところか。で、これを実際に作りこむと .. 124 スライスにまでなった。

      128 スライスなど小さいと思っていたのだが、udrv.v は、なんと 859 行もある。コメントはそれなりにはあるが、コード自体に冗長な部分はない。メモリを使わずにコードだけで埋めるのは結構しんどい。



    シミュレータで、送信側を確認。

      シミュレータでは、negedge CS , posedge CS が動作ポイントに入るのは確認できた。
      r_count は、4bit 。最初に negedge CS で動いたときに 7 にするのは まずい。F にしている。
      negedge CS で F それから 0 1 ... 7 でデータ出力。最後は、negedge SCK で データ出力できている。posedge CS でかろうじて o_data_rdy を 1 。これでデータは受け取ってもらえる。ACK は半端な状態だが、次に送信するときに、 続きから処理される。

      o_data_rdy を 1 クロック早めるのは、データを確実に渡せない気がする。ただ、FIFO 側で 遅延して受け取るような変更なら出来る。決めたルールに例外を持ち込むことになるが、このようなケースなら止むを得ないか。

      追記: 間違っていた。


      always @(CS , negedge SCK)
      begin
      if (CS) // LOAD
      begin
      r_count <= (-1);
      r_spi <= SPI_DATA_I;

      この部分なのだが、先頭の negedge CS で条件が成立していない。
      受信の動作確認で分かった。

      reg r_cs = 0;
      always @(CS or negedge SCK)
      begin
      r_cs <= CS;
      if (r_cs) // LOAD
      begin
      r_count <= 0;
      r_spi <= SPI_DATA_I;

      こう変えたら動いたのだが、どうも釈然としない。 negedge SCK だけの場合は、変更前の SCK が見えたのに、こう書くと変更後?



      受信しているところ。送信側も SPI だから両方ちゃんと動いている。

      FIFO に遅延読み込みのパラメータを付け o_data_rdy を 1 SCK クロック 前にした。

    入れたい機能は入れた。QFN32 の 128 スライスという条件をほぼフルに使った。もう満足だったりする。随分と勉強にはなったが、この後どうしよう?

    Full-Speed/Low-Speed の切り替えを付けたい。あと差動入力を使うオプション。だが、これ以上のロジック追加は無理だろう。今回は、MachXO2-256 でどこまで出来るか? というテーマなので、いずれ別の機会にしよう。

      思い出したので、メモ。
      FIFO は、Dual-Port の RAM を使っている。いま想定している使い方だとパケットを受け取り終わってから、READ することになりそう。送信の場合も同じ。なら Single-Port でも良さそうな気がする。制御ロジックが似た様なものなら 2 倍の容量が使える。FIFO まわりを差し替えるだけで済むなら検討してみたい。
       ... やってみたが、却って規模が増えた。.. 失敗。

      あと FIFO は、同期 FIFO を非同期で使えるようにしている。非同期 FIFO というものもある。それと差し替えるとどうなるか試してみたい。 (参考: 『FPGAの部屋:同期FIFOと非同期FIFO』)

    残っているものとしては、JTAG 通信と ADC 。DAC は一応 設計してみたのだから、ADC もその程度のものはやりたい。

検討もれ

    ひといきつきたいのだが、検討もれがあるかないかが気になっている。マズそうなものをリストアップしていこう。ただし、すぐには直さないかも。

    (EOP だけのパケット)

      Low-Speed では、これがあるそうだ。

      まずこれを送るインターフェイスがない。送信では、SYNC が自動で付加される。0 バイトを送出することもできない。最短は、SYNC - PID - EOP 。

      受信でも検出できない。だけでなく、マズイケースがありそうだ。受信中にまで行くと EOP でともかく状態を抜けるのだが、少々不安。あと、受信は完了したが、0 バイトというインターフェイスがいる。

      (タイムアウト)
      反応が遅すぎてタイムアウトになる可能性がある。 3ms バスが アイドル状態だとサスペンドだそうだ。
      心配だったのだが、AVR の USB だと ACK/NAK/STALL を返すのは、上位レイヤーだったことを思い出した。... たぶん大丈夫ではないかと思う。

      ただちょっと確認。1 ms というのは、1.5 MHz クロックだとして 1500 クロック。8 バイトのパケットは、SYNC , PID , CRC を含まないそうだから 1(SOP) + 14 x 8 + 3(EOP) の 96 クロックが 最短。Low-Speed だとしても 1ms に対して 十分に短い 感じ。反応が悪いと性能が出ない。

      ところで、FIFO は 32 バイトだが、PID + データ を収めないといけない。だが 32 バイトのパケットとは、データのサイズが 32 バイトのようだ。なら収まらない。これもなんとか対処する方法を考えないと。
      幸い、受信部分と FIFO の間には、1 バイトのバッファがある。最後のデータを待たせればなんとか。送信も SPI だとバッファがある。

追記:使い方編

    使い方を考えながら、インターフェイスを見なおした。

    まずは、BUS の信号

    // _ ____________________________________ _____
    // RS _><____________________________________><_____
    //
    // _____ _______
    // nCS |________________________________|
    //
    // ______________ _____________
    // nWR/nRD |_____________|
    //
    // _______________
    // DB[7:0] --------------<_______________>----------
    //

    LCD とかのタイミングを参考にするとこんな感じか。信号名も nWR とか nXX にしてみた。

    READ は、これで問題ない。WRITE はどうしたものか。どうも posedge nWR でデータ採取しているみたいに見えるのだが ... ここは negedge nWR から遅延させて採取にしたい。

    SPI は、普通のタイミング ... だが 、最後のデータは、CS を H にしたとき 、上位に 受け取られる。

    とりあえずは、これでいこうと思っている。

      ところで、FIFO 付きの BUS/SPI コードを作ったことになる。DAC で欲しかったものだ。DAC もこれをくっつけようと思う。

    さて、どう使うかまとめてみた。

    ステータスレジスタ

      bit7: CRC_ERROR
      bit6: DATA_FULL
      bit5: DATA_EMPTY
      bit4: DATA_RDY
      bit3: RECV_EN (受信中)
      bit2: SEND_EN (送信中)
      bit1: r_recv_mode (送受信モード ) (0: 送信 / 1: 受信)
       bit0: r_start (送受信スタート) (1: スタート / 0: 完了)

      内部の状態をステータスレジスタとして見せる。この内いくつかは ピンにも出力している。

      対応するピン出力( 負論理 )
      nCRC_ERROR
      nDATA_EMPTY
      nDATA_FULL
      nDATA_RDY
      nBUSY ( ~r_start )

    信号の説明

      r_recv_mode (送受信モード )

       状態は、0: 送信モード か 1: 受信モードのどちらかに大別される。
       どちらでもない状態は存在しない。

      r_start/BUSY (送受信スタート)

       1 にすると 送信モードでは送信要求、受信モードでは 受信要求。
       送信が完了すると、続いて受信スタートになる。(r_recv_mode = 1)
      受信が完了 すると 0 になる。

      CRC_ERROR :
       受信完了後有効で、1 になると CRC_ERROR が起きたことを示す。

      DATA_FULL :
       1 で、FIFO が溢れたことを示す。(データロスト)
       FIFO をリセットするまで有効

      DATA_EMPTY :
      1 で FIFO が空であることを示す。データが入れば 0 になる。

      DATA_RDY :
      1 で 受信データがあることを示す。
       ハンドシェーク用で、DATA_EMPTY とは変化のタイミングが異なる。

      ※) FIFO は、受信モード→送信モードに切り替えたときと、受信中になったタイミングでリセットされる。

      ※) ステータスレジスタの読み込み:
      (SPI) RS=1 で 0x00 を書き込む。
      (BUS) RS=1 で読み込む

    コントロールレジスタ

      bit7: 書き込み要求
      bit1: r_recv_mode (送受信モード ) (0: 送信 / 1: 受信)
       bit0: r_start (送受信スタート) (1: スタート / 0: 完了)

      動作をさせる信号は、コントロールレジスタとした。書き込み要求は、SPI のみで必須だが、インターフェイスを合わせるために、BUS でも必須ということにしておく。

      ※) コントロールレジスタの書き込み:
       1)送信 モード
      (SPI) RS=1 で 0x80 を書き込む。
      (BUS) RS=1 で 0x80 を書き込む。
      2)送信スタート
      (SPI) RS=1 で 0x81 を書き込む。
      (BUS) RS=1 で 0x81 を書き込む。
       3) 受信スタート
      (SPI) RS=1 で 0x83 を書き込む。
      (BUS) RS=1 で 0x83 を書き込む。

      サポートするのは上記の 3 つ。

    送信方法

       1) 送信 モードに する
       2) PID+データを書き込む。(合計 31 バイトまで)
       3) 送信 スタート にする
       4) (タイミングを見て) 追加のデータを書き込む。

      やはり、規模的に これが限界。最大パケットサイズを 32 バイトにすれば、追加のデータは、高々 2 バイト。すぐに送信が始まることは保証できるので、ステータスを見なくても タイミングは計れるはず。空になる前に送り込めるだろう。



      シミュレータで確認したが、こんな感じ。



      続いて送信されるわけだが、FIFO が空になると CRC を付けて 送信完了。CRC-5 を使う場合も自動で元データと CRC を入れ替える。

      ※ シミュレータ結果で i_recv_mode が 1 になっているが、過渡のものなので気にしないで欲しい。

    受信方法

       1) 受信 スタート にする



      データが来ると nDATA_RDY が 0 になるので データを受け取る。nBUSY が 0 になった後は、新たなデータは来ない。 nBUSY = 0 かつ nDATA_EMPTY = 0 を確認して終わる。

      終わるときに、nDATA_FULL = 0 か nCRC_ERROR = 0 なら、エラーにする。



      FIFO が溢れなければ良いので、高々 2 バイト余分に受け取ることは可能だろう。

        nDATA_RDY=0 で割り込みをかけるとする。ここから 12 MHz で 8 x 31 クロックもある。この間に SPI で 2 バイト 受け取れば溢れない。AVR なら問題なさそうに思える。

    以上だが、パケットの間隔というのが気になる。送信→受信、受信→受信で、データを受け取り損ねるとマズイ。特に受信→受信。このケースには、SETUP や OUT がある。

    まずいなら、仕様を変更しないとならない。... どうもその必要がありそうだ。

    ソースコードは、qfn32samples-11.zip の予定(準備中)。

追記: ソースコードのブラッシュアップとバグ修正

    仕様を変更するにも、まずは規模を減らさないといけない。無駄なところがないかチェックしてみた。

    そうしたら、... 送信での CRC 生成がエンバグしていた。
    後で use_i16 , use_i5 という レジスタを作ったのだが、全く不要なばかりか、バグの原因になっていた。

    次、CRC のモジュールは 送信・受信で共有している。特に CRC-5 は、配線の切り替えの方が重いような気がしたので、2 つに分けてみた .... これは 1 スライスの増加に終わった。残念。

    次、受信での CRC の制御。こっちもバグっていた。まず、CRC-5 か CRC-16 のどちらの結果を取るか示す MATCH_SEL というレジスタがあるのだが、完了でリセットされる。これではまずいので 状態を残すように修正。

    次、MATCH_SEL 自体を良くみたら 受信 4 バイト以上という条件だったので、data_bytes の bit を 1 つ増やして data_bytes[2] に変更。

    次、FIFO リセットの条件を 受信モード・送信モードを切り替えたときに変更。受信→受信に対応するベースにする。FIFO_FULL は、受信モードでずっと残るが、その方が都合が良いようにも思える。その上で、受信モードは常に受信待ちということにした。r_start は、1つめを受信したときに 0 になる。完了が必要ならば、r_startを再度 1 にする。

    さて、 送信→受信のパターン。DATA0/DATA1 を送ったあとの ACK が相当するようだ。タイミングによって ACK を取りこぼしてしまう。やはり送信後すかさず受信モードにすることにした。r_start は、0 になるので、次の受信の完了は検出できない。あと、送信時の FIFO_FULL も検出できない。

    ここまでやった。そして ... なんとか 128 スライスに収まった。

まだ続く

    もう FIX したいのだが、まだ手直しするところが ...

    どれぐらいのクロックで動くのかの確認をするついでに、DCMA モジュールというのを入れてみた。これは、クロックセレクタで、ここを通すとグローバルクロックにつなげられる。で、やってみると DCMA に入力できない。... ピン配置を変えると OK みたいなので、ピン配置を見なおした。

    ついでに RS ... データとコントロールレジスタの選択だが、一般に RS=0 が コントロールレジスタみたいなので論理を逆転。

    あと見直していたら、CRC-5 を 2 個使うようになっていた。これで問題なさそうなので 2 個使う USE_DUAL_CRC5
    を標準にした。

    ついでに SUPPORT_SEND_EOP というのを作りかけていたのだが、ヤメ。コード削除。

    で、動作周波数の確認。... ボトルネックを見ていたら、r_recv_mode1 が引っかかった。1 クロック遅延させるつもりが、半クロックになっていたので変更。さらに FIFO のクロックを CLK_4X にしていたのだが、SPI では必要ないので通常の CLK に。

    最終的に CLK_4X は、67.7 MHz になった。本来 48 MHz だが、これぐらい余裕があれば安心。 規模は 127 スライスでぎりぎり OK。

    あと、CLK_OUT を付けた。CLK_4X の反転出力。水晶があれば、発振させられるかも。ソースコードは、-11 を上書き。

ソフト側の構想

    V-USB を改造すればいけるんじゃないかと上で書いたが、少し確認してみよう。本格的にやる場合は別記事にするが、とりあえず、あたりを付けよう。

    extern usbRxBuf, usbDeviceAddr, usbNewDeviceAddr, usbInputBufOffset
    extern usbCurrentTok, usbRxLen, usbRxToken, usbTxLen
    extern usbTxBuf, usbTxStatus1, usbTxStatus3
    extern usbSofCount

    ; extern unsigned usbCrc16(unsigned char *argPtr, unsigned char argLen);
    ; extern unsigned usbCrc16Append(unsigned char *data, unsigned char len);
    ; extern unsigned usbMeasurePacketLength(void);

    アセンブラで extern を grep すれば、こういうのが見つかる。
    下の 3 つは、アセンブラから呼び出すサブルーチンで無視して良い。CRC などは機能にあるし、usbMeasurePacketLength というのも NRZI 関係だろう。

    さて、上は全部変数。この変数を制御しているのが、usbdrv.c 。同じインターフェイスにすれば、わずかな変更でいけそうだ。たぶんアセンブラは INT0 割り込みで動作する。udrv では、なにか受信すると DATA_RDY が L になる。この変化を INT0(か 1) で拾えば同じような処理ができるはず。

    受信は割り込みベース、送信はポーリングでも良いかも。12 MHz でデータが来るが、16 バイト受信ぐらいまでに応答するとすれば、10 us 以内。20 MHz なら 200 クロックもある。これなら受信ルーチンも C で良いかも知れない。

    memo : 受信側

    uchar usbRxBuf[2*USB_BUFSIZE];
    uchar usbInputBufOffset;
    volatile schar usbRxLen;
    uchar usbCurrentTok;
    uchar usbRxToken;

    このあたりが受信で重要そう。

    len = usbRxLen - 3;
    if(len >= 0){
    usbProcessRx(usbRxBuf + USB_BUFSIZE + 1 - usbInputBufOffset, len);

    こういうコードがある。usbRxLen には、先頭の PID と 最後の CRC-16 が含まれるから -3 する。
    PID の場所は、usbRxBuf + USB_BUFSIZE - usbInputBufOffset ということらしい。

    CRC-16があるということは、DATA0/1 パケット。その前に SETUP か OUT が来ているはずで、それが、その前に入っているのかも。この udrv でも同じ仕様が都合が良さそう。

    usbRxToken は、OUT or SETUP (の PID)が入っている。usbCurrentTok は、OUT だった場合に エンドポイント番号が格納される。

    あと、受信では 1 フレームの送受信を一気にやってしまうようだ。IN が来たら予め用意されたデータを送る。OUT or SETUP では、ACK などの応答を返してしまう。

なんと、まだ続く

    V-USB のコードを眺めていたら CRC-16 は、DATA0/1 パケットに使うことが分かった。サイズは関係ない -- 誤解してた。あと、CRC-5 の送信側のコードがない。.... デバイスは 指示パケットを送信しないのであった。

    仕様を誤解していたのだから、まずは修正。CRC-5 の送信側のフラグは、crc5_extract というものなのだが、ややこしいので、整理したら規模が少し減った。さらにデバイス専用にも出来るようにして、少し規模を削れるように parameter を導入したところ、論理が変わらないのに規模が減った。今は 123 スライス。デバイス専用だと 121 スライス。

    ついでなので、0 バイト送信で SOP のみを送る機能は、Low-speed のみなので FS パラメータ(1 で Full-speed) に連動するように変更。さらに DP , SE0 状態を negedge CLK でサンプリングするよう変更。こうしておかないと 受信毎にクロックを 180°ずらすことになる。ソースコードは、-12 版準備中。

    最終的な仕様を記録しないとわけが分からなくなりそうだ。新記事を起こそう。

関連記事

     ・ 『QFN32の FPGA
     ・ 『TTL ALU 74281
     ・ 『FPGA時計の設計
     ・ 『MCPU -- A Minimal 8 Bit CPU
     ・ 『DACを設計してみよう
     ・ 『USBコントローラの設計

    最新ソースコード (2012/07/1)
     ・ qfn32samples-10.zip

      qfn32samples-10.zip で一応の完成とした。最終的な規模を記録しておこう。GSR は、1 つしかないリソースで、SPI で使った非同期リセットで 使われたらしい。

      BUS SPI
      Number of registers: 117 126
      PFU registers: 115 123
      PIO registers: 2 4
      Number of SLICEs: 116 122 /128
      SLICEs(logic/ROM): 32 32 /32
      SLICEs(logic/ROM/RAM): 84 90 /96
      As RAM: 12 12 /96
      As Logic/ROM: 72 78 /96
      Number of logic LUT4s: 200 214
      Number of distributed RAM: 12 12
      Total number of LUT4s: 224 238
      Number of PIO sites used: 20 14 /22
      Number of GSRs: 0 1 /1

    最新ソースコード (2012/07/05)
     ・ qfn32samples-11.zip

    最新ソースコード (2012/07/06)
     ・ qfn32samples-12.zip

      今度こそ.. ちなみに、実際に動かそうと思えるレベルかどうかが、『一応の完成』の基準。(動かせばバグは出るだろう)
      規模を記録しておく。僅かながら変更する余裕ができた。SUPPORT_CRC5S=0 とすると、送信時 CRC-5 は生成しない(device では不要) が、-2 スライスになる。

      BUS SPI (CRC 分)
      Number of registers: 123 148 45
      PFU registers: 121 145 45
      PIO registers: 2 3
      Number of SLICEs: 112 123 /128 27
      SLICEs(logic/ROM): 32 32 /32
      SLICEs(logic/ROM/RAM): 80 91 /96 33
      As RAM: 12 12 /96
      As Logic/ROM: 68 79 /96
      Number of logic LUT4s: 197 218 64
      Number of distributed RAM: 12 12
      Total number of LUT4s: 221 242 64
      Number of PIO sites used: 21 14 /22
      Number of GSRs: 0 1 /1


参考文献(データシート等)
 ・ 『MachXO2 ファミリデータシート(日本語 pdf)
 ・ 『MachXO2 テクニカルノート(日本語)
 ・ 『Lattice Diamond 1.4マニュアル
posted by すz at 00:24| Comment(0) | TrackBack(0) | CPLD

2012年06月21日

DACを設計してみよう

QFN32 パッケージの MachXO2-256 という FPGA でなにが出来るのか、検討しているのだが。... DAC が作れそうな気がする。FPGA は大概、コア用の電源と IO 用の電源に分かれている。この QFN32 も 例外ではないどころか、VCCIO が 4 つもある。ちゃんと電源を設計すれば、ノイズ的に有利かも知れない。ただ、オーディオDAC に匹敵するようなものには、できないだろう。どこまでのものが作れるかも追求しない。256 に入れられて 検証できるものを目指す。

AVR など PWM を持った MCU は 多いわけだが、ΣΔ変調の DAC を持っているものは、殆ど無い。同じ bit幅でも、ΣΔ変調だと ノイズを減らせるそうだ。ちょっと試してみたい。

ΣΔ変調の原理

    1 bit の ON/OFF だけで DAC を作ると 256 クロック使って ようやく 8bit 幅になる。このことは、PWM と変わらない。ノイズを減らせるのは、どうしてかというと PWM では 0 が続いたあと 1 にするだけのところを、1 を分散させて ノイズの周波数を 上げるためらしい。

    どうやって そういう 0/1 を作るのだろう?

    なかなか理解できなかったのだが、3bit の DAC を使って 8bit 出力することを考えてみたら、分かったような気がする。

    例えば、8'b01011010 を 3bit の DAC で出力するには ... 上位 3bit の 3'b010 をまず出力して 時々 3'b011 も出力することで 中間値を表現する。

    3'b010 を DACで出力すれば 5'b11010 が出力されなかった分として残る。次も 3'b010 なら 5'b11010 + 5'b11010 が残るのだが ... 桁上がりして、6'b110100 になる。だから 3'b011 を DAC で出力して、5'b10100を出力されなかった分として 覚え直す。

    要するに、ずっと 5'b11010 を加算していって、桁上がりしたときに 3'b011 を DACに出力すれば良いのだ。

    では、0/1 だけで表現するには ... 8'b01011010 を ずっと 加算していって、桁上がりしたときに 1 を出力すれば良い。256 回加算すれば、16'b 01011010 00000000 になるから、確かに 8'b01011010 個の 1 を出力したことになる。

    フィルタがどうとか に囚われてよく分からなかったのだが、どうも原理は簡単な話のようだ。

作ってみよう

    module dac # (
    parameter WIDTH = 8
    , parameter WITH_LATCH = 0
    ) (
    input CLK
    ,input STB // active high
    ,input [WIDTH-1:0] I_DATA
    ,output A_OUT
    );

    reg r_stb = 1'b0;
    reg [WIDTH-1:0] r_data = 0;
    reg [WIDTH-1:0] fraction = 0;
    reg [WIDTH-1:0] count = 0;
    reg r_out = 1'b0;

    always @(posedge CLK)
    begin
    r_stb <= STB;
    if (&count | (STB & ~r_stb))
    begin
    count <= 0;
    fraction <= I_DATA;
    r_out <= 0;
    if (WITH_LATCH)
    r_data <= I_DATA;
    end
    else
    begin
    { r_out, fraction } <= { 1'b0 ,fraction }
    + { 1'b0, (WITH_LATCH ? r_data : I_DATA) };
    count <= count + 1;
    end
    end

    assign A_OUT = r_out;

    endmodule

    取り敢えず書いてみたのがこれ。ラッチあり/なし を選べたり ビット幅を指定できるようにしてみた。そうそう、新しい呪文を覚えた。&count は、count の各ビットを AND する意味で all 1 のとき 1 になる。

    書いてみて思ったのだが、毎回 Nbit の加算をしている。これはクロックを上げられないのではないか?

    例えば 16 bit の出力 を 50kHz でするとする。65536 倍のクロック が必要で 計算すると 3.2768 GHz で駆動することに。とりあえず 3bit の DAC を使うとすれば ... 409.6 MHz 。これでも無理な話だが、周波数が減る上に 加算する bit 数も減る。

    さて、この 3bit の DAC もまた、ΣΔで作ってやれば、どうなるのだろう? 同じ 1bit のΣΔでも最大周波数が上がるのではないだろうか?

N bit ΣΔ

    module dac5 # (
    parameter AWIDTH = 5
    , parameter WIDTH = 16
    ) (
    input CLK
    ,input STB // active high
    ,input [WIDTH-1:0] I_DATA
    ,output [AWIDTH-1:0] A_OUT
    );

    reg r_stb = 1'b0;
    reg [WIDTH-1:0] r_data = 0;
    reg [WIDTH-AWIDTH-1:0] fraction = 0;
    reg [WIDTH-AWIDTH-1:0] count = 0;
    reg [AWIDTH-1:0] r_out = 0;

    wire [WIDTH-AWIDTH-1:0] WK = 0;
    wire [AWIDTH-1:0] A_ZERO = 0;
    wire [AWIDTH-1:0] A_WK = ~A_ZERO -1;
    wire [WIDTH-1:0] I_MAX = { A_WK , ~WK };
    wire [WIDTH-1:0] I_DATA2 = (&I_DATA[WIDTH-1:WIDTH-AWIDTH])
    ? I_DATA : I_MAX;

    always @(posedge CLK)
    begin
    r_stb <= STB;
    if (&count | (STB & ~r_stb))
    begin
    count <= 0;
    r_data <= I_DATA2;
    fraction <= I_DATA2[WIDTH-AWIDTH-1:0];
    r_out <= I_DATA2[WIDTH-1:WIDTH-AWIDTH];
    end
    else
    begin
    { r_out, fraction } <= { A_ZERO , fraction } + r_data;
    count <= count + 1;
    end
    end

    assign A_OUT = r_out;

    endmodule

    例えば 3bit DAC では、3'b111 より大きな値は、出力できないので 上限値を設けてやる。後は最初に説明したとおり。

    すなおに、N bit の R-2R ラダー DAC を使っても良いのだが、これと 1bit ΣΔを組み合わせてみたい。

    module dacx # (
    parameter WIDTH = 8
    ) (
    input CLK
    ,input STB // active high
    ,input [WIDTH-1:0] I_DATA
    ,output A_OUT
    );

    reg [2:0] hi_clk;
    wire [2:0] hi_out;
    wire lo_stb = CLK;

    always @(negedge CLK)
    begin
    hi_clk <= hi_clk + 1;
    end

    dac #( .WIDTH(3) , .WITH_LATCH(0) ) dac_lo ( .CLK(CLK), .STB(lo_stb)
    , .I_DATA(hi_out), .A_OUT(A_OUT) );
    dac5 #( .AWIDTH(3) , .WIDTH(WIDTH-3) ) dac_hi ( .CLK(hi_clk[2])
    , .STB(STB), .I_DATA(I_DATA) , .A_OUT(hi_out) );

    endmodule

    こうかな?

    ところで、ラッチなしにして、2 ** N 回クロックを待たないで、適時値を変更しても、それなりに追従する。dac5 の方もラッチなしに対応しておいた方が応用が効きそうだ。

    あとは、フィルタがどうとか ... だが、クロックはたっぷりある。簡単なものなら加算だけで作れそうな気がする。これはまた別途考えてみたい。

    それとは別に、データをどうやって受け取るかという問題もある。パラレルは無理だしなにより面倒。やっぱり SPI ? 実は、JTAG 経由の通信というのも検討中。

R-2R ラダー DAC

    簡単に説明すると

    A_OUT_2 --- R ---+----- Analog
    |
    2R
    |
    A_OUT_1 --- R ---+
    |
    2R
    |
    A_OUT_0 --- R ---+
    |
    2R
    |
    GND

    こんな回路。抵抗が 2 種類しかいらないが、値が厳密。あと電流は取れないから、オペアンプを使ってボルテージ・フォロワを組む。

      FPGA 出力には、(数十Ωの)抵抗分がある。これにより R と 2R の比が崩れるので注意。また、出力電流を 4段階ぐらいで調整できる(たぶん 抵抗分が変わる)ので、チューニングに使えるかも。

    秋月で 1608 をリール売りしているから、1K/2K , 1.2K/2.4K , 1.5K/3K とかの組みを選ぶと良さそう。2500 個もあるから、選別しほうだい。

    選別には、SMDテスターが便利そう。3000 カウントだから 3K は避けたほうが良いか。

動作周波数について

    2 つの モジュールにすることで、周波数を上げられないか? やってみたのだが、4bit でも 16bit でも 150MHz ぐらいで、あまり変わらない。2 つの モジュールにしても、やっぱり あまり変わらない。要するに CLK 自体が ボトルネック。150MHz ということは、12bit で 36.6 kHz が上限。3bit の R-2R DAC + 10 bit ΣΔあたりが妥当?

    ところで、100 MHz で 駆動したとして 例えば 010101 というパターンが出てきたとき素直に駆動して良いものなのだろうか? 1bit ならともかく、多ビットだと 周波数を落としたくなる。

    PWM だと 周期が低くなりすぎるし ... PWM + ΣΔ が良いのだろうか? PWM でも Phase Correct MODE というのが AVR にある。UP していって TOP まで行ったら 逆に DOWN していく。こうすると スイッチング周波数は、普通の PWM の 1/2 。例えば 4bit Phase Correct MODE PWM なら CLK が 100 MHz でも スイッチング周波数は、3 MHz ぐらいまで落とせる。ちょっとこれを検討してみたい。2 つの モジュール は、既に出来ているから、片方を PWM にするだけ -- 難しいことはない。

    3bit の R-2R DAC で PWM を使い 、さらに上位を ΣΔにするのは、ちょっと難しい。これも 挑戦してみよう。

    PWM を使うのとは別に 010101 というパターンを 例えば 000111 というのに変換してしまうのはどうだろう?

    考えたのは、次のやりかた。

      今出力しているのが 1 だとする。次に出力するのが、01 だと分かっているなら 10 にしてしまうのだ。(0 についても同様)

      1 01 → 1 10
      0 10 → 0 01

      要するに 単独の 1/0 を出ないようにしてしまうわけだから 絶対に 2bit は続くわけだ。3 bit に拡張するなら、上記に加えて

      1 001 → 1 100
      0 110 → 0 011

      とする。これで 3 つ以上連続することが保証される。

      ただ、この処理には問題がある。例えば 2 を出力する場合 、わざわざ離れた位置に 1 を配置するわけだが、これを くっつけてしまう。連続した数を数えておいて 、くっつける処理をキャンセルする必要がある。

      なにやら面倒な話になってきた。やはり PWM の方が良さそうな。

SPI 通信の例

    秋月で売っている DAC MCP4922 や ADC の MCP3002 は、SPI で通信する。プロトコルを調べてみた。

    DAC :
     ・ 1 データ 送受信の度に CS を 上げ下げする必要がある。
     ・ 1 データは、MSB first , 16 bit 単位

    bit15 ~ A/B チャネル選択
    bit14:12 config
    bit11:0 データ[11:0]

    ADC:
     ・ 1 データ 送受信の度に CS を 上げ下げする必要がある。
     ・ 1 データは、MSB first , 24 bit 単位

    bit23 START (1)
    bit22:20 config (チャネル含む)
    (ADC → Master)
    bit19 NULL (0)
    bit18:9 データ[9:0]
    bit8:0 データ[1:9]

     ・ START を 遅らせて 16 bit 単位 にすることも可能

    bit15 (0)
    bit14 START (1)
    bit13:11 config
    (ADC → Master)
    bit10 NULL (0)
    bit9:0 データ[9:0]

    CS を 上げ下げする必要があるのは、FPGA 側としては、都合が良いかも。SPI というからには、MSB first にした方が自然。バイト単位になるような配慮も必要。変換タイミングは、正確にしたいだろうから 別途ということになりそう。(AVR だと PWM を使う)

    もっと大きな問題があった。クロックをどうしよう。変換タイミングを完全に正確にするには、送信側から CLK をもらうか、DAC 側のタイミングに 合わせてもらうか ...

    送信側から CLK をもらうにしても 50 MHz 〜 100 MHz というのは厳しい。規模が大きい FPGA だと PLL があるが、MachXO2-256 にはない。

    今回のは、DAC 側のタイミングに 合わせてもらうことにしよう。それにしても 問題が。AVR を使うならなんとかなるのだろうが、PC と通信する場合は、どうしよう。

    普通にシリアルで CTS/RTS 制御で良いのかな? 115Kbps だと 11.5 Kbytes/sec になってしまって 性能が足りない。1M bps とか できたら 3M bps とか欲しいところだが ... 途切れなく送り込めるのかどうかが問題。

    DAC を設計するなら MachXO2-1200 ぐらいは 欲しいところで、PC とのインターフェイスに FT2232H を使いたいということになりそうだ。 そういうものとして、『MachXO2 Breakout ボード』があるのだが ... 今回は 256 を使いこなすのがテーマなのでパス。とにかく AVR で使えるものだけを考えよう。

    ここまでの検討で
     ・ SPI を使う。
     ・ データリクエストを DAC 側が出す。
     ・ 16bit 単位
     ・ DAC データは、12bit-14bit の範囲
    ということに決めた。

    プロトコルは、別途。

ソースコード

関連記事
posted by すz at 22:55| Comment(0) | TrackBack(0) | CPLD

2012年06月17日

MCPU -- A Minimal 8 Bit CPU

MCPU -- A Minimal 8 Bit CPU in a 32 Macrocell CPLD

    というのを思い出した。これは、CPLD で実装できる 極端にシンプルな 8bit CPU 。

    実際に CPLD に実装しても メモリを別に用意しなくてはならない 。FPGA だとメモリがあるのが普通なので、全部を実装可能だ。でも、どうせならもっと規模が大きい CPU を実装したい ... ということで今までは興味がなかった。

    128スライスしかない MachXO2-256 ならどうだろう? メモリを載せて実装できるのだろうか? 試してみたくなった。

    まずは、資料の入手から。"MCPU - A Minimal 8 Bit CPU in a 32 Macrocell CPLD" でググると PDF ファイルがヒットする。まずは、これを入手。

  • http://opencores.org/project,mcpu

    どうも プロジェクトホームページは、ここらしい。アクセスできる人は、ここから入手するのが良さそうだ。

MCPU の概要

    ドキュメントを読むと 命令は なんと 4 つしかない。

    そんなものでプログラムを組めるのだろうか? という疑問にマクロを示すことで答えている。

      .. 確かにプログラムを書けそうな気はする。

    ソースコードは、VHDL版、Verilog 版が、ドキュメントの最後に添付されている。

      私は、VHDL版だけ見て、自己流の Verilog 版を作ることにした。... というより Verilog 版があるのを知らないで作ってしまった。まぁ、後でいじるつもりだし著作権的には自己流の方が都合が良いかも知れない。

      -- 著作権は、アイディア(ロジック)を保護するものではなく、表現を保護するもの。言語が違うから、表現も違う。オリジナルは尊重すべきだが、どうもこういうことらしい。

      verilog 版をよく見たらバグがあるようだ。要注意。

最初のインプリメント

    さて ... 規模をまずは見たい。メモリを付けて合成してみよう。

    メモリ空間は、なんと 6bit 64バイト分しかない。初期値付きの RAM として合成してみたら 59/128 スライス しか消費しなかった。

      module sram # (
      parameter SIZE = 64
      ) (
      input CLK,
      input WEB,
      input [5:0] ADDRB,
      input [7:0] DIB,
      output [7:0] DOB
      );
      reg [7:0] mem [0:SIZE-1];

      reg [7:0] r_addr;
      always @(negedge CLK)
      begin
      if (WEB) mem[ADDRB] <= DIB;
      r_addr <= ADDRB;
      end
      assign DOB = mem[r_addr];

      initial
      begin
      $readmemh("rom_data.mem" , mem, 0 , SIZE-1);
      end
      endmodule


    つけた RAMは、こういうもの。negedge で駆動。アドレスラッチ付き。

構想

    まずアドレス空間が狭すぎる。ハーバードアーキテクチャにしよう。これで RAM 専用空間に 64 バイト割り当てられる。

    で、何をしたいかと言うと I2C やらタイマがある EFB を割り当てたい ... ただ EFB の空間は 256 バイトもある。

EFB の調査

    MachXO2 和訳テクニカルノート にある
     
  • TN1205 ユーザフラッシュメモリ(UFM)と組み込み機能ブロック(EFB)の使用ガイド
    を見てみよう。

    まずは、アドレスマップ

     プライマリI2C   0x40 〜 0x49
     セカンダリI2C  0x4A 〜 0x53
     SPI       0x54 〜 0x5D
     タイマ/カウンタ  0x5E 〜 0x6F

    一応 64 バイト以内に収まっている。0x70 〜 0x7f の 16 バイトも空いている。あと、使わない機能のレジスタは RAM としても使えるはず。

    独自の I/O ポートを付けたいなら どこかを削らなければならない。これは、後で考えよう。

    次に EFB との接続インターフェイス。

    EFB は、WISHBONE インターフェイス。この説明は、TN1205 に載っている。

  • wb_clk_i posedge で動作
  • wb_rst_i active high の同期動作
  • wb_adr_i {2'b01, adreg}
  • wb_dat_i EFB の入力
  • wb_dat_o EFB の出力
  • wb_we_i 1 で Write だが レベルセンシティブ
  • wb_stb_i 1 で EFB を選択 ( adreg[5:4] != 2'b11 )
  • wb_cyc_i 1 でバス有効?
  • wb_ack_o

    こんな感じだが、ライトもリードも 最低3サイクル必要と書いてある。

    WISHBONE 用のクロックをまず生成し、それを3分周してマスタクロックにすることにしよう。

MCPU の拡張 (1) ハーバードアーキテクチャ

    これは、実はすごく簡単。(元が簡単だし)

    メモリから READ しているもの のいくつかを プログラムメモリに変えれば良い。
     
  • adreg にロードしている所
     
  • 上位 2bit だけをロードしている所

MCPU の拡張 (2) アドレス空間の拡張

    ハーバードアーキテクチャ に変更しても プログラムメモリは、64B しかない。MachXO2-256 は、128B までいけるはずなのだ。もう少し増やせないか?

    空間を増やすためには、プログラムメモリのビット幅を増やせば良い。分散メモリの場合、使わない bit は 最適化で消えるから、気前よく 12 bit にしてしまおう。

    で、アドレス空間自体は、7bit にする。これで規模がどうなるか。

    Number of Inst. 64(org) 64 96 128
    Design Summary
    Number of registers: 32 34
    Number of SLICEs: 51 92 107 124
    SLICEs(logic/ROM): 32 32
    SLICEs(logic/ROM/RAM): 19 60 75 92
    As RAM: 6 24
    As Logic/ROM: 13 36 51 68
    Number of logic LUT4s: 77 126 156 188
    Number of distributed RAM: 6 24
    Number of ripple logic: 5 5
    Total number of LUT4s: 99 184 214 246

    分散メモリは、ROM に格納するデータによって規模が変わる。64B 分しか使わないなら 128B 全部使うのと比べ 32 スライスも減った。

    同じ 64B でも ハーバードアーキテクチャ版から随分増えた。これは、メモリ(RAM) を 16B から 64B に増やしたのが主な理由。-- メモリが 16B しか使えないという制限で良いなら、変に拡張しないほうが良い。

      ちなみに、オリジナルは 57 スライスで、ハーバードアーキテクチャ版は規模が減っている。

      メモリは、RAM 64B だったのが、ROM 64B + RAM 16B になったわけだ。2 種類に増えた上、トータルの容量も増えた。さらに EFB まで付けて 減ったわけだ。

      オリジナルは、ハーバードアーキテクチャ版を元に書きなおした。分散メモリの RAM実装が 重いということになるのだろう。

アドレスマップ と I/O ポート

    何種類か設計したわけだが、アドレスマップを整理して I/O ポートをどうしたら良いのか考えてみよう。


    オリジナル EFB版 MCPU EFB + アドレス拡張
    0x00 - 0x2F : プログラム EFB EFB
    0x30 : 0xFF (allone) 0xFF (allone) 0xFF (allone)
    0x31 : 0x00 (zero) 0x00 (zero) 0x00 (zero)
    0x32 : 0x01 (one) 0x01 (one) 0x01 (one)

    0x34 - 0x3F : RAM RAM
    0x40 - 0x7F : --- ---  拡張RAM

    プログラムROM : ---- 64B 64B - 128B

    マクロを見ると 0xFF,0x00,0x01 がメモリ上に必要なのだ。そして書き換えられるのは都合が悪い。アドレスは、EFB 版とアドレスを合わせることにすると 0x30 〜の 3 バイトが良さそうだ。

    実際に付けるかどうかは、別にして ... 出力ポートは、0x30 - 0x32 の 3バイトを割り当てることにする。入力ポートは 0x33 の 1 バイトのみ ということにしたらどうだろう。

ソースコード

  • qfn32samples-04.zip

      バグなど
    • mcpu.v , mcpu_efb.v , mcpu2_efb.v の sram モジュールで WEB の論理が逆になっていた。


追記: シミュレータ(iVerilog) でデバッグ

    iVerilog を使ってデバッグした。いろいろ修正したが、どうやら動かせるようになったようだ。

    動かせたのは、EFB を組み込んだりした拡張版。オリジナルに一番近いのは、まだ。

    テストコードは、PDF に載っていたサンプルコード。どうも 最大公約数をもとめるものらしい。あと、アセンブラもどきを perl スクリプトで組んだりもしている。

    作った MCPU のバージョンは4つある。

      (1) mcpu.v オリジナルをもとに作ったもの
      (2) mcpu_efb.v ハーバードアーキテクチャ版 にした上で EFB を組み込んだもの。
      (3) mcpu1_efb.v 拡張しやすいように、rewrite したもの。
      (4) mcpu2_efb.v アドレス空間を拡張したもの。

    (1) だけ動いていない。

    アセンブラもどきは、後方参照した場合、アドレスが分からないというレベルのもので、出力を参考にしてコードを転記している。

    規模は、(3) が、53スライス、(4) が 103 スライス。EFB を組み込んだ上で、adreg, O_DATA などをピンに出力している。(4) は、LDI 命令を追加しての規模で、なしにすると 92。なにか使い物になりそうな気もしてきたのだが ...

    よくよく考えると bit 操作すらできない代物だった。(3) は随分余裕があるようなので、これを元に ALU を付けたCPUを設計したほう方が実用的だろう。

    あと RAM のアクセスの仕方に難がある。分散メモリ専用ならこれでも良いのだが、今はブロックRAM を使えない。一方 ROM の方は問題ないようだ。

    (3) について、ちょっとメモっておこう。


    ADD 命令 + STA 命令
    _______ _______ _______
    CLK ______| |_______| |_______| |______|

    code_data 01AAAAAA | 01BBBBBB |

    inst 000 | 101 | 000 | 110
    ______________
    WE _______________________________________| |__

    @
    (RAM_USE_CLK)
    ______________
    WE_mem ______________________________| |__
    @ @ @ @

    分かりにくいと思うが、2 クロックで 1 命令実行 。posedge で CPU は動作している。最初の状態は、000 で アドレスだけは確定している。

    code_data は ROM 出力で negedge で変化する。アドレスは、ラッチを持たず ROM データをそのまま使っている。

    で、ROM データは、毎クロック読み出しているが、inst==0 の時だけの方が良いかも知れない。

    問題の RAM は、WE の立ち上がりで書き込みとしていた。一般的なコードにするとすれば ...

    posedge で動作させることにして、WE = 1 で書き込み。ただし WE は半クロック前に確定させないといけない。

    実際にやってみたら動いたのだが、規模は +7 スライスの増加。

     ・ qfn32samples-05.zip

新たな CPU へ

    mcpu2 では、命令用メモリを 12bit にした。そのうち 下位 7bit はアドレス。命令用に 5bit 使えるわけだが今は 2bit しか使っていない。ここを再構成し、ALU を入れることによって 別の CPU にしたい。

    といっても基本は変えない。演算する、ストアする、条件ジャンプするという枠組みは出来ているのだ。枠組みを壊さなければ、書きなおすところが圧倒的に減る。

    改造の要点

      10 : STA -- 変更しない。
      00 : NOR -- 演算命令 8 つ に変更

      使う ALU は、自作AVRコア用 ALU 。ちょうど 8bit だし、都合が良い。
      00XXX -- 3bit 使えるが まるまる ALU の S に使う。

      01 : ADD -- 即値演算命令 4 つに変更

      即値演算にも ALU を使う。ただし、論理演算のみ。

      01XX -- 即値は、2bitしか使えないのだ。命令の割り当ては、演算命令との兼ね合いで決める。

       即値は、01 S[1] S[0] 8bit即値 (S[2] = 1) とすることにしよう。
       演算は、00 S[1] S[0] S[2] 7bitアドレス

      11 : JCC -- 条件を拡張+α

      条件ジャンプ命令は、3bit 使える。C or Z で 1bit ,条件を逆にするのに 1bit 。あと 1bit は、C の値としておこう。 C をロードする命令がないのだ。

    動くかどうか分からないのだが、ROM 64 words , RAM 16 bytes なら 97 スライスに収まった。これならいけそう。

    命令表

      (inst == 100)
      00 00 0 ADD
      00 01 0 ADC (with carry)
      00 10 0 SUB
      00 11 0 SBC (with carry)
      00 00 1 AND
      00 01 1 XOR
      00 10 1 OR
      00 11 1 LD

      (inst == 101)
      01 00 ANDI
      01 01 XORI
      01 10 ORI
      01 11 LDI

      (inst == 110)
      10 XX X ST

      (inst == 111)
      11 00 C JCS jump if carry set
      11 01 C JCC jump if carry clear
      11 10 C JNE jump if zero-flag set
      11 11 C JEQ jump if zero-flag clear

      carry,zero-flag が変化するのは、ADD などの 算術演算命令のみ。

    全部で 17 命令。... だがコード上は 4 種類のまま。ALU が何をしているか関知してないから。

    シフト命令は .. ないなぁ。
    これを付けるとすれば ... 001 〜 011 の inst を使うことになる。

      (inst == 100)
      10 00 STA
      (inst == 001)
      10 01 拡張命令 1
      (inst == 010)
      10 10 拡張命令 2
      (inst == 011)
      10 11 拡張命令 3

    どんな命令にするか別にして、こういう割り当てにする。

    だいたい作った上で動作周波数を見てみた。4 グレードで 20 MHz 〜 25 MHz ぐらい。クロックは、EFB を操作する都合で 3 倍クロックを入力している。命令実行は 2 クロックに 1 回なので 1/6 ... 4 MIPS ぐらい?

    多分なにか間違っている。制約の設定が足りないのだろう。だが、EFB のクロック関係は適当すぎたかも。

    EFB を外して CLK で駆動したら 22.9 MHz になった。これでも 違うはずだが、この 2 倍は無理だろう。
    改造前の mcpu2 だと 45.5MHz と出た。ALU 周りがボトルネックと見做されたわけだ。だが、1.5 クロックかけて演算している。これを 0.5 クロックと見られたら 低い結果になりそう。

      どうも、MULTICYCLE制約というので設定するらしいので試してみた。

      FREQUENCY PORT "CLK" 36.000000 MHz ;
      MULTICYCLE TO CELL "alu_impl_I/alu_impl/alu_lo/akku_i0" 1.500000 X ;
      MULTICYCLE TO CELL "alu_impl_I/alu_impl/alu_lo/akku_i1" 1.500000 X ;
      MULTICYCLE TO CELL "alu_impl_I/alu_impl/alu_lo/akku_i2" 1.500000 X ;
      MULTICYCLE TO CELL "alu_impl_I/alu_impl/alu_lo/akku_i3" 1.500000 X ;
      MULTICYCLE TO CELL "alu_impl_I/alu_impl/alu_hi/akku_i4" 1.500000 X ;
      MULTICYCLE TO CELL "alu_impl_I/alu_impl/alu_hi/akku_i5" 1.500000 X ;
      MULTICYCLE TO CELL "alu_impl_I/alu_impl/alu_hi/akku_i6" 1.500000 X ;
      MULTICYCLE TO CELL "alu_impl_I/alu_impl/alu_hi/akku_i7" 1.500000 X ;
      MULTICYCLE TO CELL "C_68" 1.500000 X ;
      MULTICYCLE TO CELL "Z_69" 1.500000 X ;

      ... と 22.9 MHz が 57.0 MHz まで上がった。 いくらなんでも 上がりすぎのような ...

      FREQUENCY PORT "CLK" 36.000000 MHz ;
      DEFINE CELL GROUP "akku" "alu_impl_I/alu_impl/alu_hi/akku_i7"
      "alu_impl_I/alu_impl/alu_lo/akku_i1"
      "alu_impl_I/alu_impl/alu_lo/akku_i2"
      "alu_impl_I/alu_impl/alu_lo/akku_i3"
      "alu_impl_I/alu_impl/alu_lo/akku_i0"
      "alu_impl_I/alu_impl/alu_hi/akku_i6"
      "alu_impl_I/alu_impl/alu_hi/akku_i4"
      "alu_impl_I/alu_impl/alu_hi/akku_i5"
      "Z_69"
      "C_68" ;
      DEFINE CELL GROUP "code" "rom_impl/r_addr__i5"
      "rom_impl/r_addr__i4"
      "rom_impl/r_addr__i3"
      "rom_impl/r_addr__i2"
      "rom_impl/r_addr__i6"
      "rom_impl/r_addr__i1" ;
      MULTICYCLE FROM GROUP "code" TO GROUP "akku" 1.500000 X ;

      よく分かってないのだが、GROUP というのを定義して、その間を設定するやり方もある。指定したいのは、こっちのような気がする。基本的に rom の アドレスが変わることによって 結果が出るのだ。これだと 37.6 MHz ...

      mcpu2 だと 45.5MHz が 63.0 MHz に。

追記

    mcp3 の 命令の仕様を決めて、簡易アセンブラを 作った。ちょっと改良して後方参照も 見るようにした。やはり、ないとデバッグが不便。あと 出力ポート追加。

      MCPU3 命令表(全 21 命令)

      (inst == 100)
      00 00 0 ADD
      00 01 0 ADC (with carry)
      00 10 0 SUB
      00 11 0 SBC (with carry)
      00 00 1 AND
      00 01 1 XOR
      00 10 1 OR
      00 11 1 LD

      (inst == 101)
      01 00 ANDI
      01 01 XORI
      01 10 ORI
      01 11 LDI

      (inst == 110)
      10 00 X ST
      (inst == 001)
      10 01 ASR
      (inst == 010)
      10 10 ROL
      (inst == 011)
      10 11 ROR
      (inst == 111)
      11 00 C JCS jump if carry set
      11 01 C JCC jump if carry clear
      11 10 C JNE jump if zero-flag set
      11 11 C JEQ jump if zero-flag clear

      (MACRO)
      SEC set carry JCS *+1 (C=1)
      CLC clear carry JCC *+1 (C=0)
      JMP addr JCC adr (C=0), JCC adr (C=0)
       ・carry が変化するのは、ADD などの 算術演算命令
        と JCC 等のブランチ命令, シフト命令
       ・zero-flag は、論理演算やロード LD,LDI でも変化

    これで コードは作れるようになった。最初に作ったのは、MCPU のサンプルの GCD の移植。単純変換したら、1命令しか減らなかった。

    さて、次は EFB にアクセスしてみたい ... のだが、ちょっと出来が悪かったので再考。

      ・ まず 3 倍速というのがダメ。CPU の CLK が 2:1 になり ボトルネックになる。2 倍速にする。
      ・ 2 倍速 を検討したのだが、CPU は、ちょうど 2 クロック毎の処理になっている。
      ・ ROM を非同期アクセスに変更すると具合が良いことが分かった。

      READ サイクル
      _____ _____
      CLK | |_____| |_____|

      inst | 000 | 100 |
      ____________
      wb_cyc ______| |_____
      @ @
      ADDR DATA @
      ラッチ 実行
      WRITE サイクル

      _____ _____
      CLK | |_____| |_____|

      inst | 000 | 110 |
      _______________________
      wb_we | |_
      ____________
      wb_cyc ______| |_____
      @
      ADDR
      DATA

      READ/WRITE サイクルを図示するとこんな感じ。
      READ では、wb_cyc=1 と同時に アドレスが決まってないといけない。アドレスは、命令に含まれ、CLK 立ち上がりで読み込み開始。次の CLK_2X で データを読み込めるが、ここでラッチしないといけない。

      WRITE は、命令を読みこめばただちに分かる。DATA も用意しなければならないが、akku(アキュームレータ)を WRITE するだけなので、命令と同じタイミングで読める。

    あと、制約を書くのに C_68 とか変なサフィックスになるので、reg [1:0] flags に変更。

    だいぶ仕上がって来た。いままで作ってきたサンプルは、MachXO2-256 をターゲットにしている。当然 『MachXO2 breakout ボード』でも 動くはず。EFB アクセスはこれで確認してみたい。



      シミュレーション結果。赤い不定値部分は、RAM の範囲外ということ。EFB を読み込んでいるわけだ。(演算命令なので、値は使わないが)。 タイミングは上に書いた通りになっている。アクセスする時だけ wb_cyc をアサートしているので、wb_we のタイミングも想定通り。... なのだが、仕様を勘違いしているかも。

      ところで、EFB を使ったときの 最大周波数が低く出てしまう。赤い不定値部分のとなり -- RAM への書き込みをしているが、WE_mem が 1 になって、値が書き込んだときの値に変わっている。どうも ここがボトルネックと解析されているのだが、この値はだれも使わない。from WE_mem を 6X と設定したら CLK_2X が 61.5MHz になった。(命令実行は 1/4 なので 15 MIPS といったところ -- たいしたことはない)。

    あと作っておきたいのは、JTAG 経由の通信 。

関連記事
posted by すz at 10:34| Comment(0) | TrackBack(0) | CPLD

2012年06月13日

FPGA時計の設計

QFN32の FPGA』 を使ったなにかを設計してみたい。この際 AVR 使った方が有利で楽なものでも構わないが、実際に動かせるもの。ALU では、それを使ったものの敷居が高い。

で、時計はどうだろう? プロセッサなしで動かすには一体どうするのか? 実は意外にも簡単なことかも知れない。

構想1

    まず出力。今は 4桁の 7セグが入手できるからそれを使う。時計の : がないが、とりあえずは気にしない。気にする場合は、(配線が面倒になるのと引換に) 2桁の 7セグ x2 で間に LED を 2個入れられるようにして置けば良い。

    この FPGA だが ... DRIVE 能力の設定がある。LVCMOS33 だと デフォルト 8mA 、最大では 24mA の設定ができる。ただ、24 mA 流せてもコモンは 7 倍になるから 足りない。ドライバは必須。カソードコモンだと NPN トランジスタ が使えて楽そうな気もするのだが、7セグ側を H にして点灯させないといけない。そうなると白色 LED を使うのは厳しくなる。

    一応アノードコモンを使い 7セグ側を L で点灯ということにしておきたい。コモンのドライブも L 。こういったものは、出来た後見直すことにしよう。

    ピン数の合計 7 + 4 + 1(2) = 12(13)

      使える I/O は 21 しかない。ちゃんと入るものなのかどうか、常に気にするようにしよう。

    次にクロック。ダイナミック点灯や ボタンでのオペレーションを考えて 128 Hz を基本としておこう。 8MHz なら 128 で割り切れる。32768 Hz を原発信にしたい気もする。これは、あくまで最初の想定。出来上がってしまえば、対応できる範囲というのも分かってくるだろう。

    さて、これはどうやって入力するか -- 。取り敢えずは、外部のオシレータを想定すれば良い。ただ、水晶発振回路を内蔵したい。ピン数は、2 と見積もっておく。

    ピン数の合計 14(15)

    入力。時刻の設定は出来ないと困るだろう。最も簡単なのは、SEL と UP の 2 つ。DOWN も付けると、よくある時計のオペレーションになるはず。一応 3 つと見積もる。

    ピン数の合計 17(18) --- 残り 4(3)

    あと最大 4 つしか余らない。まぁ機能など付ける余裕もなさそう。JTAG はできれば空けときたいので、4 つ余らせることにしよう。

構想2 カウンタと表示

    ここからは、内部をどうするか ...

    まず時計というからにはカウンタが必要だ。それをどういう風に設計するか?

    前提としてダイナミック点灯するわけだ。ならば、出力は 4bitの BCD 1つで良い。これを 7seg エンコーダを通して 表示させるわけだ。

    考えたのだが、0〜59 や 0〜24 の 2 桁カウンタのモジュールをまず作ろうと思う。最大値は モジュールのパラメータで指定。あと 出力は 4bit で HI/LO を切り替える SEL 入力を持つことにする。

    入力は、128Hz のクロック と カウンタ動作の指定 UP(/DOWN)。あと上位に対する UP(/DOWN)。まずは、DOWN は後回し。回路が入るようなら検討。

    7seg エンコーダは、分散メモリを使ったテーブル ... にしたかったのだが Lattice の場合、妙な Warning が出るので assign と ? 演算子だけで何とかしようかと思う。(テーブルと同じ効果になるはず)。

構想3 カウンタと表示(続き)

    上のカウンタモジュールを 3 つ使い 時分秒に割り当てる。出力も 3 つ。とりあえず、時分 と 分秒 を表示する 2 つのモードを作ろう。

    セレクタでのこの 3 つの出力を切り替えられるようにして、モジュール内の HI/LO を切り替え を合わせてダイナミック点灯させる。

      ダイナミック点灯の 2bitカウンタがあるとする。下位は HI/LO に割り当てる。上位だけ考えれば良いのだ。

      0 1
      disp_mode 0 HH MM
      disp_mode 1 MM SS

      こうなるようにすれば良い。

    さて、これだけか? というと違う。時刻の設定である。時刻の設定ではセレクトされたものがブリンクする。設定モードについて考えておこう。

    モード自体の定義は、次のようにしよう。

      set_mode 0 : 通常表示 (HH:MM)
      set_mode 1 : 時設定 (HH:MM) で HH ブリンク
      set_mode 2 : 分設定 (HH:MM) で MM ブリンク
      set_mode 3 : 秒設定 (MM:SS) で SS ブリンク

      blink off blink on
      0 1 0 1
      set_mode 0 : HH MM HH MM
      set_mode 1 : HH MM -- MM
      set_mode 2 : HH MM HH --
      set_mode 3 : MM SS MM --

      ( -- : blank )

    だいぶややこしくなってきた。が、所詮これだけ。全部で 16 通りしかない。あと blink 信号は、クロックから 1Hz を取り出せば良い。

構想4 ボタン

    次は、どうやってボタンから入力をするかに飛ぼう。

    例えば、SELボタンを押したときは、set_mode をインクリメントするだけで良い。だが、どうやって押したと認識するのか? スイッチの入力をそのまま入れたのでは、128 Hz で set_mode が回転するだけだ。また、チャタリング対策というものも必要になる。

      (128 Hz で次の処理を行う)
      N 回 同じ入力が続いたら ボタンが押された、離されたと認識する。
      押された、離された という状態を作り、離された→押された と変化したとき ボタンON。
      使う側が、ボタンONを認識して リセット を送ってきたら (非同期に)ボタンOFF。

    こういう処理をするボタンモジュールを作ろうと考えている。

    N 回 同じ入力というのは、チャタリング対策。これを を認識するのに N -1 bit の レジスタが必要。N=4 にしたいなら、 128 Hzは、周波数が高すぎるかも知れない。これは、後で要調整。
 
実装編 カウンタ

    以上で重要な部分は説明した。後は実際のコードを示していこう。

    module clock_counter # (
    // parameter MAX = 60
    parameter MAX = 24
    ) (
    input CLK
    , input I_UP
    , input SEL // select output
    , output [3:0] O
    , output O_UP
    );

    reg [3:0] lower;
    reg [3:0] upper;
    reg r_ovr;

    assign O[3:0] = SEL ? lower[3:0] : upper[3:0];
    assign O_UP = r_ovr;

    always @(negedge CLK)
    begin
    if (I_UP)
    if ((upper >= (MAX-1)/10) & (lower >= (MAX-1)%10))
    begin
    r_ovr <= 1'b1;
    upper <= 0;
    lower <= 0;
    end
    else if (lower >= 9)
    begin
    r_ovr <= 1'b0;
    upper <= upper + 1;
    lower <= 0;
    end
    else
    begin
    r_ovr <= 1'b0;
    lower <= lower + 1;
    end
    else
    r_ovr <= 1'b0;
    end
    endmodule

    説明したとおりのもの。規模は1つ 10スライスのようだ。3つ使うから、これだけで 30 スライス。

実装編 ボタン

    module button
    (
    input CLK_128HZ
    , input I
    , output O
    , input R
    );

    reg [3:0] i_stat;
    reg prev_stat;
    reg r_out;

    always @(R, negedge CLK_128HZ)
    begin
    i_stat <= { i_stat[2:0] , I } ;
    if ( { i_stat[2:0] , I } == 4'b0000 )
    prev_stat <= 1'b0;
    else if ( { i_stat[2:0] , I } == 4'b1111 )
    prev_stat <= 1'b1;
    if (R) r_out <= 1'b0;
    else if (~prev_stat & ( { i_stat[2:0] , I } == 4'b1111 ))
    r_out <= 1'b1;
    end
    assign O = r_out;
    endmodule

    これもまた説明どおり。これは、4スライス。最低 2 つ使う。

一応完成

  • qfn32samples-03.zip

    Diamond プロジェクト込みのソースを置いておく。iVerilog シミュレータも一応通している。

    // XO2-256 modified ROM/RAM XO2-256
    // 27 B 1 24 VCC
    // 28 G 2 23 DIG1 25
    // 29 3 22 DIG2 23
    // 30 4 21 DIG3 21
    // 32 5 20 DIG4 20
    // 1 6 19 CLK_1HZ 17
    // 4 C 7 18 BTN_DN 16
    // 5 D 8 17 N.C.
    // 8 E 9 16 BTN_UP 14
    // 9 A 10 15 BTN_SEL 13
    // 10 F 11 14 CLK 12
    // GND 12 13 CLK_OUT 11

    ピン配置はとりあえずこうした。DIGは、LVCMOS33 の 24mA に設定。ACTIVE_HIGH に変更して、3mA 程度まで電流を減らせば直接ドライブできるかも。

    機能としては、
  • down ボタンを付けた
  • 7セグ制御の正論理・負論理を切り替えられるようにした。
  • クロックは、128HZ の倍数なら OK なようにした。

    一応説明で言及したものは、入れたことになる。-- これで規模は 86/128 スライス。
    機能追加はなかなか厳しそうだが、アラームクロックにするのは ... 可能かも。time_couner に比較用のレジスタを作って ... まぁ今後の課題にしておこう。

    追記: アラーム機能を付けてみた。

    .. といっても ポートを増やしたくなかったので、秒を示す LED を 1分間 高速点滅させるだけ。機能もちょっと見なおしている。


      set_mode 0 : 通常表示 (HH:MM)
      set_mode 1 : 時設定 (HH:MM) で HH ブリンク (アラーム)
      set_mode 2 : 分設定 (HH:MM) で MM ブリンク (アラーム)
      set_mode 1 : 時設定 (HH:MM) で HH ブリンク
      set_mode 2 : 分設定 (HH:MM) で MM ブリンク
      set_mode 3 : 秒表示 で SS ブリンク

      blink off blink on
      0 1 0 1
      set_mode 0 : HH MM HH MM
      set_mode 1 : HH MM -- MM
      set_mode 2 : HH MM HH --
      set_mode 3 : SS -- (秒表示のみ)

      ( -- : blank )

     ・ qfn32samples-04.zip (ソースコード)

関連記事
posted by すz at 20:03| Comment(0) | TrackBack(0) | CPLD

2012年06月10日

TTL ALU 74181

前の記事『QFN32の FPGA』から派生。

    FPGA MachXO2 の QFN32 パッケージが出るので、DIP にする基板を作ってみたい -- というところから、74HC181 (SN74LS181) の代替ができるものを .. となった。

    この IC は ALU(Arithmetic Logic Unit) で、部品を組み合わせて CPU を作ったりするのに欠かせない。有名な『CPU の創りかた』 でも 74HC181 を使っているとのこと。

    ただ入手がとても難しい。では、DIP にするついでにピン互換機能を持たせられるようにすれば、どうだろう。... 調べてみると、電子工作レベルで、ピン互換にできそうなものはあまりない。

    ならば、やってみようというのが動機。

手始めに コードを作ってみよう。

データシートを見てみる。

    www.alldatasheet.jp で検索してみる。
     
  • *4HC181
     
  • *4LS181

    とりあえず、TI の SN74LS181NE4 を参考書として使うことにする。

    で、見てみたのだが、難解。キャリー入力(Cn) が L と H で計算式が違う。これは面倒だなと思ったのだが、どうも ACtive-Low の表が、そういうことらしく、ACtive-High なら見れなくはない。

    ただ、キャリー出力がどうなるのかサッパリ分からない。結局頼りになるのは、回路図。回路図を読みこなすならば、再現してしまった方が 簡単のような気がしてきた。

      例えば、S == 3 では、キャリーありで F = 0 / キャリーなしで F = -1 と なっているのだが ..

      8bit に拡張した場合のことを考えると、0 を出力したときにキャリー出力が 1 (負論理なので L) にならないと変だ。だが、合っているのか? こういうことを、いちいち 回路図を眺めて確認するのは面倒すぎる。

回路図を元にコードを作る。

    回路図を見ると A/B を入力にして 2 つの出力にしている部分(左)と それを元に F などの出力を生成している部分(右)に分かれるようだ。

    左の出力を C/D という中間出力にして論理式にしてみよう。

    wire [3:0] C;
    wire [3:0] D;

    assign C[0] = ~( A[0] & ~B[0] & S[2] | A[0] & B[0] & S[3] );
    assign C[1] = ~( A[1] & ~B[1] & S[2] | A[1] & B[1] & S[3] );
    assign C[2] = ~( A[2] & ~B[2] & S[2] | A[2] & B[2] & S[3] );
    assign C[3] = ~( A[3] & ~B[3] & S[2] | A[3] & B[3] & S[3] );

    assign D[0] = ~( A[0] | B[0] & S[0] | ~B[0] & S[1] );
    assign D[1] = ~( A[1] | B[1] & S[0] | ~B[1] & S[1] );
    assign D[2] = ~( A[2] | B[2] & S[0] | ~B[2] & S[1] );
    assign D[3] = ~( A[3] | B[3] & S[0] | ~B[3] & S[1] );

    上の部分を C , 下の部分を D とするとこうなった。なんか随分すっきりしている。調子に乗って次。

    assign F[0] = ~( ~M & C_IN ) ^ D[0] ^ C[0];
    assign F[1] = ~( ~M & ( D[0] | C_IN & C[0] )) ^ D[1] ^ C[1];
    assign F[2] = ~( ~M & ( D[1] | D[0] & C[1] | C_IN & C[0] & C[1]))
    ^ D[2] ^ C[2];
    assign F[3] = ~( ~M & ( D[2] | D[1] & C[2] | D[0] & C[1] & C[2]
    | C_IN & C[0] & C[1] & C[2])) ^ D[3] ^ C[3];

    wire Yinv = D[3] | C[3] & ( D[2] | C[2] & ( D[1] | C[1] & D[0]));
    wire C_OUT = Yinv | C[3] & C[2] & C[1] & C[0] & C_IN;
    wire EQ = F[0] & F[1] & F[2] & F[3];

    wire X = ~( C[3] & C[2] & C[1] & C[0] );
    wire G = ~ Yinv;
    wire P = X;

    なにかバグがあるかもしれないが、こんな感じ。

    これを合成してみると ...

    Number of SLICEs: 9 out of 128 (7%)
    SLICEs(logic/ROM): 9 out of 32 (28%)
    SLICEs(logic/ROM/RAM): 0 out of 96 (0%)
    As RAM: 0 out of 96 (0%)
    As Logic/ROM: 0 out of 96 (0%)
    Total number of LUT4s: 17
    Number of PIO sites used: 20 out of 22 (91%)

    なんと 9/128 スライス。楽勝ではないか。P/G は生成していないが、C_OUT(Cn+4) の計算で使っているし、生成しても 僅かの差のはず。

    ただ、17 LUT で作れてしまうものなのだろうか? C/D 生成は、4 入力1出力 が 8 個だから 8LUT で確かにできる。残り 9 個だが .. 配線のために消費しなければ、出来そうな 気もする。

    訂正: F3 の括弧の位置が違った。直したら さらに規模が減った。

    ソースコード (Diamond プロジェクト込み)

     
  • qfn32samples-02.zip (2012/06/12 最新版に更新)
     
  • qfn32samples.zip (ここで説明したもの 旧版)

    ちなみに以前作った 74281 (レジスタ付き ALU = アキュームレータ) P/G なし (上記ソースコードに添付)

    Number of registers: 4
    Number of SLICEs: 39 out of 128 (30%)
    SLICEs(logic/ROM): 32 out of 32 (100%)
    SLICEs(logic/ROM/RAM): 7 out of 96 (7%)
    As RAM: 0 out of 96 (0%)
    As Logic/ROM: 7 out of 96 (7%)
    Total number of LUT4s: 78
    Number of PIO sites used: 20 out of 22 (91%)

    こちらは、高機能だし、最適化された回路図なしだから効率が悪いのかも。それにしても楽勝なのは変わりない。確か 64 マクロセルの XC2C64A(CoolRunner II) にようやく入れられたような 規模だったはずなんだが ...

      追記: これなのだが、後述の 74181 拡張をしてそれをベースにしたら 規模が多少小さくなった。そうであれば自作AVRコアにも適用したくなるし、いずれは 32bit CPU も作りたいので 整理して使いやすくしておこうかと思う。

      自作AVRコアにも適用しても確かに規模は減る。ただ、規模が減れば速くなるものかどうか? あと厳密に機能を作らないといけないのでデバッグが面倒。

検証するには

    規模のチェックは、これぐらいで良いだろう。次はどうやって検証するか。

    C のコードに変換するのは、簡単だ。で、実際にいくつかのパターンを入力して OK そうか見る。大丈夫そうになったら、真理値表を作る。

    確か誰かが全入力の真理値表を作っていたような ... 探してみよう。答え合わせができれば、完了。

    追記: 実際に C 化してみた。

    #define _C(v) (((v)&1)?'1':'0')

    #define wire
    #define assign

    void alu181() {
        (上記の Verilog コード)
    printf("M %c ", _C(M) );
    printf("S = %c%c%c%c ", _C(S[3]), _C(S[2]), _C(S[1]), _C(S[0]) );
    printf("C_IN %c ", _C(C_IN) );
    printf("A = %c%c%c%c ", _C(A[3]), _C(A[2]), _C(A[1]), _C(A[0]) );
    printf("B = %c%c%c%c ", _C(B[3]), _C(B[2]), _C(B[1]), _C(B[0]) );
    printf("F = %c%c%c%c ", _C(F[3]), _C(F[2]), _C(F[1]), _C(F[0]) );
    printf("C_OUT %c", _C(C_OUT) );
    printf("\n");
    }

    こんな風にすると、Verilog のコードを触らなくて済む。

      M 1 S = 0000 C_IN 1 A = 0011 B = 0111 F = 1100 C_OUT 1
      M 0 S = 0000 C_IN 1 A = 0011 B = 0111 F = 0011 C_OUT 1
      M 0 S = 0000 C_IN 0 A = 0011 B = 0111 F = 0100 C_OUT 1

      M 1 S = 0001 C_IN 1 A = 0011 B = 0111 F = 1000 C_OUT 1
      M 0 S = 0001 C_IN 1 A = 0011 B = 0111 F = 0111 C_OUT 1
      M 0 S = 0001 C_IN 0 A = 0011 B = 0111 F = 1000 C_OUT 1

      M 1 S = 0011 C_IN 1 A = 0011 B = 0111 F = 0000 C_OUT 1
      M 0 S = 0011 C_IN 1 A = 0011 B = 0111 F = 1111 C_OUT 1
      M 0 S = 0011 C_IN 0 A = 0011 B = 0111 F = 0000 C_OUT 0

      M 1 S = 0110 C_IN 1 A = 0011 B = 0111 F = 0100 C_OUT 1
      M 0 S = 0110 C_IN 1 A = 0011 B = 0111 F = 1011 C_OUT 1
      M 0 S = 0110 C_IN 0 A = 0011 B = 0111 F = 1100 C_OUT 1

    幾つか試したが、合っているようだ。 (最初は合わなかったので、括弧の間違いが見つかったわけだが)

所感

    実を言うと、MachXO2-1200 とか使っていても もっと規模が欲しい。256 ぐらいで何が出来るのか? と思っていたりしたのだが ... 制限内で何かを作るのも面白いかも知れない。CPLD なんかよりずっと回路が入りそう。それでも複雑なものは作れないから、単機能のものをじっくりやることになる。お手軽に使えるように環境を整えて、いろいろ設計してみようかと思う。

74181 の拡張

    74181 は、ものすごく高機能な印象があったのだが、74281 の ALU や 74381 をこれを元に実装しようとすると機能が足りないことが分かった。

    281 の ALU や 381 では、" B - A " があるのだが、181 では無理なのだ。さらに 281 の ALU では、" ~B - 1" なんてものまである。

    で、どうしようか 作ったのを眺めて考えた。181 では、前段の C,D を作るところだけで、機能セレクトを使っている。ようく見ると A と B は対称ではないが、機能セレクトを追加すれば、対称にできる。

    assign C[0] = ~((~A[0] & S[6] | A[0] & S[7])&(~B[0] & S[2] | B[0] & S[3]));
    assign C[1] = ~((~A[1] & S[6] | A[1] & S[7])&(~B[1] & S[2] | B[1] & S[3]));
    assign C[2] = ~((~A[2] & S[6] | A[2] & S[7])&(~B[2] & S[2] | B[2] & S[3]));
    assign C[3] = ~((~A[3] & S[6] | A[3] & S[7])&(~B[3] & S[2] | B[3] & S[3]));

    assign D[0] = ~( A[0] & S[4] | ~A[0] & S[5] | B[0] & S[0] | ~B[0] & S[1] );
    assign D[1] = ~( A[1] & S[4] | ~A[1] & S[5] | B[1] & S[0] | ~B[1] & S[1] );
    assign D[2] = ~( A[2] & S[4] | ~A[2] & S[5] | B[2] & S[0] | ~B[2] & S[1] );
    assign D[3] = ~( A[3] & S[4] | ~A[3] & S[5] | B[3] & S[0] | ~B[3] & S[1] );

    こうするのだ。S が 4bit から 8bit になってしまうわけだが、使い方は次のようにする。

    S[7:4] S[3:0]
    1 0 0 1 FS[3:0] --- (1)
    FS[3:0] 1 0 0 1 --- (2)

    0 1 1 0 FS[3:0] --- (3)
    FS[3:0] 0 1 1 0 --- (4)

    新設の上位 4bit を 1001 にすると ... 今までと互換(1)。 で、上位ビットと下位ビットを入れ替えると ... A と B が入れ替わる(2)。これは計算式を眺めれば分かるはずだ。

    さらに (1) の 1001 を 0110 にすれば A がビット反転する(~A)。 A と B を入れ替えた上で B をビット反転するには、(4) のようにする。

    これでようやく 281 の ALU や 381 の機能を作れるようになった。

メモ: (ピン)互換モジュールのピンアサイン案

    XO2-256 74181 74181 XO2-256
    27 B0 1 24 VCC
    28 A0 2 23 A1 25
    29 S3 3 22 B1 23
    30 S2 4 21 A2 21
    32 S1 5 20 B2 20
    1 S0 6 19 A3 17
    4 ~Cn 7 18 B3 16
    5 M 8 * 17 N.C. 26
    8 F0 9 16 ~Cn+4 14
    9 F1 10 * 15 SWAP 13
    10 F2 11 14 EQ 12
    GND 12 13 F3 11

    前の記事にも書いたが、実際は有効なピンが足りず互換にできない。P/G は使わないだろうということで、17 は N.C. 扱い。(N.C. とは書いたが無接続にすること)

    15 は、入力 A と B を入れ替える機能にしようかと思う。どうせ回路は余っているのだ。9 スライスが 13 スライスになったが さほどのことはない。

    あ、あたかもモジュールが存在するような書き方をしたが、部品も入手できておらずあくまで予定。

    181 の 機能

    S3 S2 S1 S0 M = 1 C_IN = 1 C_IN = 0
    logic (no carry) (with carry)

    0 0 0 0 ~A A A + 1
    0 0 0 1 ~(A | B)
    0 0 1 0
    0 0 1 1 0 -1 0
    0 1 0 0 ~(A & B)
    0 1 0 1 ~B
    0 1 1 0 A ^ B A - B - 1 A - B
    0 1 1 1
    1 0 0 0 ~A
    1 0 0 1 ~(A ^ B) A + B A + B + 1
    1 0 1 0 B
    1 0 1 1 A & B
    1 1 0 0 1 A + A A + A + 1
    1 1 0 1
    1 1 1 0 A | B
    1 1 1 1 A A - 1 A

    使う可能性があるものをピックアップしてみた。結構スカスカ。SWAP を追加しても M = 1 は結果が同じ。役に立ちそうなのは、B - A , B - 1 , B + 1 ぐらい。

オーバーフラグ(OVR)の 追加

    AVRコアだと H フラグ, V フラグというのがある。H フラグは、4bit のキャリーフラグで 181 ベースでの ALU なら 出力するだけで済む。(自作のコアでは、Verilog の演算をしていたので、別途生成しないといけなかった)

    さて、V フラグは どういうものか ... 現状のものは、次のような式を使っている。

    assign V_out = ~M &
    ( ~(S[1]) & (F[7] & ~A[7] & ~B[7] | ~F[7] & A[7] & B[7])
    | (S[1]) & (F[7] & ~A[7] & B[7] | ~F[7] & A[7] & ~B[7]) )
    ;

    M=1 が論理演算で、~(S[1]) は、算術加算。 で S[1] が算術減算。... 要するに A/B/F の最上位ビットを 比べているわけだが、加算/減算で論理が違う。

    一方 74LS382 (381 のモディファイ版) には、OVR という出力があって、こんな計算はしていない。よくわからなかったのだが、どうも C_OUT(n) ^ C_OUT(n-1) という計算をしているようだ。

    C_OUT(n) は、既にあるわけだが、181 は内部で C_OUT(n-1) も作っているはず。そうであれば、比較的簡単に OVR を作れるのではないか?

    wire C_OUT1 = ~( D[0] | ~C_IN & C[0]);
    wire C_OUT2 = ~( D[1] | C[1] & D[0] | ~C_IN & C[0] & C[1]);
    wire C_OUT3 = ~( D[2] | C[2] & ( D[1] | C[1] & D[0])
    | ~C_IN & C[0] & C[1] & C[2]);

    assign P = ~( C[3] & C[2] & C[1] & C[0] );
    assign G = ~(D[3] | C[3] & ( D[2] | C[2] & ( D[1] | C[1] & D[0])));
    assign C_OUT = ~(~G | ~C_IN & C[0] & C[1] & C[2] & C[3]);

    assign F[0] = ~( ~M & ~C_IN ) ^ D[0] ^ C[0];
    assign F[1] = ~( ~M & ~C_OUT1 ) ^ D[1] ^ C[1];
    assign F[2] = ~( ~M & ~C_OUT2 ) ^ D[2] ^ C[2];
    assign F[3] = ~( ~M & ~C_OUT3 ) ^ D[3] ^ C[3];

    assign OVR = ~M & (C_OUT3 ^ C_OUT);
    assign EQ = F[0] & F[1] & F[2] & F[3];

    どうもこうらしい。これで、たいぶすっきりした。

    で、この修正を入れたら、15 スライスにまで増えた。( ただし 使わなければ 元と同じ )

8bit への拡張

    この alu を 2 つ使って 8bit 版を作ってみている。C_OUT と 上位 4bit の C_IN をつないだもの。

    35 スライス / 68 LUT になった。だいぶでかくなった。

    P/G の生成は、

    assign P = PL | PH;
    assign G = GH & (PH & GL );

    こんな風にやるものらしい。PH , GH を出力すれば良いものではないようだ。8bit 版単体で使うなら関係ないのだが、いずれは、32bit CPU を作りたいので対応しておいた。また、8bit 版 になるとピンが増えるので、想定しているモジュールでは合成できない。

    最初の目的は、 自作AVRコアに適用すること。まだ機能が厳密に合っているのかどうか分からないが .. 42 スライス(83 LUT) が、31 スライス(62 LUT) にまで減った。問題は、規模よりは 速度のボトルネック。もとのやつより速いと良いのだが...

  • ここまでのソースコード (Diamond プロジェクト込み) → qfn32samples-04.zip

     2012/06/17 更新: iVerilog 用テストベンチを追加し、自作AVR コアに適用できるようデバッグ。

     
  • rtavr では、C_OUT/HALF フラグの意味が 減算では反転している。( 1 で ボロー )
     
  • 論理演算では、C_OUT/HALF フラグ は無視してよい。( 結果が違っても良い) -- 実際違うので テストで比較に入れない。
     
  • EQ の意味を取り違えていた。
      ( F == 1111 で EQ = 1 だから Zero Flag とは関係なし。)
     
  • (alu4.v 自体は無変更)

関連記事
posted by すz at 13:27| Comment(0) | TrackBack(0) | CPLD

2012年06月06日

QFN32の FPGA

ふと、MachXO2 でググってみたら、あらたに 32pin QFN が出るらしい。



デジキーでも LCMXO2-256HC のエントリーが出来ている。製品ページを見ても、なにやら情報が出ている。

Mouser では、1個買いの単価が出ている

    330円 (4 グレード)
    371円 (5 グレード)
    418円 (6 グレード)
    まだ在庫なし。工場リードタイム すら出ていない。

      (2012/7/3) 6 グレードだけだが、リードタイムが出ていた。9 週間。価格も安くなっていた。
      244円 (4 グレード)
      279円 (5 グレード)
      314円 (6 グレード)
      256 なんだし、これぐらいでないと。お、4 グレード は、25個時 単価 214円 か。これぐらいだと、AVR とかのサポート用に使える。


  • I/O 数は 22 -- これはどういう計算なんだろう?

    BSDLファイル は既にある。VCCIO が 4 バンクあって 電源だけで 10 pin 。残りが I/O という計算らしい。

    JTAG を専用にすると .. 18 I/O 。それに、PROGRAMN/DONE/JTAGENB の扱いをどうするか。

WLCSP25 は、1200 なのに、QFN32 は、256 のみ。1200 だったら大喜びしてたのに... 残念。

... とここまで書いて、以前の自分の記事に QFN32 のことが載っていることに気がついた。 忘れていたのか。

まぁ、そのことは置いておいて、今は、実際に 電子工作で扱える小ピンの FPGA が出ることはすなおに嬉しい。



これは、ストロベリーリナックスの CP2103 USBシリアルボードだが、こんな風に変換基板を作ることができれば、ナローDIP FPGA ボードになる。ただし、作ることができるとは限らない。CP2103 は、QFN28 なので QFN32 だとより条件が厳しい。1個分のホールを潰せば、ナローDIP化はできそうだが、ランドが少ない。果たして手ハンダが可能なのか? リフローもやってみたいが、それしか方法がないのもどうかという気がする。

    QFNパッケージのはんだ付け

    この記事には、『位置合わせさえ確実であれば無洗浄タイプのフラックスを使ってはんだ付けが可能です。』と記されている。楽々だったとも。

    ランドは、標準より少し 広げているようだ。パッケージの端から 0.5mm ぐらい?

それは後にして、そもそも この LCMXO2-256HC は何に使えるのだろう?

  • 4LUT 数 256
  • ブロック RAM なし
  • I2C, SPI, タイアカウンタ ハードマクロあり
  • PLL なし
  • 内蔵 クロック あり

まぁ ALU ぐらいは出来るだろう。ならば 74HC181 ピン互換が可能になるような、ピン配置にして 24pin の DIP モジュールにするのはどうか? 出来れば、データシートすら、なかなか見つからない 74LS281 互換にもしてみたい。

『CPUの創りかた』で 74HC181 を使っているらしいのだが、入手困難らしい。価値のある使い方がひとつできることになる。ランドも大きくすることが出来て、手ハンダも可能になるはず。

    電源を 3.3V 専用にして、2 pin 分にし、残りの 22 I/O を出すことは可能。

    ただ、Config で JTAG を Disable にすると、JTAGENB が JTAG を強制的に enable するための専用ピンになる。-- 要するに I/O が足りず完全互換にするのは無理。

    出力のうち めったに使われない P または G に JTAGENB を割り当てることでごまかそう。

    あとの問題は、電圧範囲で 3.3V 専用になることぐらい。



    こちらは、aitendo の CP2103モジュールだが、ちょうど 24pin 。こんな風に部品を載せる余裕が随分ある。

そうそう 低容量(128B)の ROM/RAM にはなる。

    8KB ROM と 2KB RAM は、

    A7 (1) (24) VCC
    A6 (2) (23) A8
    A5 (3) (22) A9
    A4 (4) (21) A12 /WE
    A3 (5) (20) CS /OE
    A2 (6) (19) A10
    A1 (7) (18) A11 /CE
    A0 (8) (17) D7
    D0 (9) (16) D6
    D1 (10) (15) D5
    D2 (11) (14) D4
    GND (12) (13) D3

    こんな ピン割り当て。(9) または (11) に JTAGENB を割り当てるので、互換にするのは無理。

    D2 を A8/A9/A10 のどれか に持っていくしかないかな。

    低容量(128B)でも、CPU を創るなら役にはたつ。マイクロプログラムなら 容量よりビット幅だし。

    訂正: 256B が行けるかと思ったのだが、実際にやってみると 128B までしか作れなかった。

    1LUT は、16bit (2B) の容量のはずで、256 LUT あれば 512B 分。ロジックに使う分があるから 256B の計算だったのだが ...

あとは ... 折角 I2C やら SPI やら付いているのだから、なにか デバイスを作ってみるとか。CPUは、入らないから、ステートマシンで作ることになる。内蔵クロックは、最大 133 MHz なので、超高速 PWM ってのも可能。

差動入力もある。コンパレータがわりに使えるかも。

ところで、こいつの電圧範囲はどうなるのだろう? 上限が 3.3V なのは良いとして下限は?
VCCIO は、3.3V/2.5V/1.8V/1.2V は行けそうなのだが、Config で決めた電圧でないといけないのだろうか? それとも、半端な電圧でも良いのだろうか?

ちなみに、ライタは、FT232RL が使えるものを 作った。デバイスを追加しないといけないし、実際には FT232RL での動作を確認していないが、たぶん大丈夫。論理合成ツールは、Diamond 1.4.2 以降で対応済みだそうだ。

追記: 74281 (P/G なし)を合成してみる。

    ピン配置
    XO2-256 74281 74281 XO2-256
    27 A1 1 24 VCC
    28 A2 2 23 A0 25
    29 RS1 3 22 CP 23
    30 RS0 4 21 SIO0 21
    32 RC 5 20 AS0 20
    1 SIO3 6 19 AS1 17
    4 A3 7 18 AS2 16
    5 Cn 8 17 M 14
    8 ~G 9 16 F0 13
    9 Cn+4 10 15 F1 12
    26 ~P 11 14 F2 11
    GND 12 13 F3 10

    以前 74281 の コードを XC2C64A 向けに作ってあったので、試してみることにした。

    実は基板は、eagle で設計済みで、モジュールへのピンアサインも決めてしまっている。

    実際にピンを割り当てるときに、まず JTAG_PORT を DISABLE にしないといけなかった。次に JED ファイル生成でエラーになった。どうやら MUX_CONFIGURATION_PORTS を ENABLE にしないといけないらしい。

    Number of registers: 4
    Number of SLICEs: 24 out of 128 (19%)

    Number of logic LUT4s: 42
    Number of ripple logic: 3 (6 LUT4s)
    Total number of LUT4s: 48


    CPLDだと厳しかったが、小規模でも FPGA だけあって 楽勝 のようだ。74181 は作ったことがないが、これも P,G なしにするし 楽勝だろう。

    基板は、もうすこし練りたい。例えば TDO は出力 だが、JTAG を Disable にし忘れると 回路を壊してしまう恐れが ... 抵抗ぐらいは入れておきたい。他にもオプションとして仕込んでおきたいものもある。

    思ったより回路が入るようだし、サンプルをいくつか用意出来るといいなと思っている。

    ALU と ROM/RAM は作るとして、後は?

    4桁7セグを使った時計とか? 電流は多少流せるようだし、いけるかも。あと、周波数カウンタなんかも出来そう。

基板の設計(1)



    まだ部品も手に入らないわけだが、基板を設計してみた。

  • QFN は、デザイン優先で(というかワザを覚えたので)ナナメにマウント。
     ただ、ちゃんとしたパッドの作り方がわからないので、そこは適当。
     シルクもナナメを使ってみたり。

  • コネクタ部分は、0.8φで小さめの径。秋月の細ピンヘッダを想定。

  • 今回は、裏に部品を付けない。いずれ リフローやってみたい。

      ホットプレートを使うらしいが、小型の電気鍋 (鍋 MG-500 とか 鍋 KG-152 とかでググると多数みつかる) ではダメなんだろうか? 保管場所を取るのはちょっと避けたい。

  • I2C は、プルアップ抵抗を付けられるように。XO2 は、I2C からコンフィグできたりするので実験用。 (普通は)付けない。
  • TDO は、ミスで出力がぶつかるのを危惧して抵抗付き。LED を付けるのにも便利なように配慮。
  • セラミック(or 水晶)発振子を付けられるようにしてみた。
     もうひとつ端子間に抵抗を入れられるようにしてある。
     この 2 つ -- 1M Ωを入れてしまうと、使い方に制限が出るわけだが、私が作るものは、配慮する。

    いつもの gerbtool で画像はつくれなかった。ナナメの部品には対応できていないようだ。



      原因だけは、分かった。ナナメの部品を使うと G36 というコードが使われるようになる。これは、多角形の塗りつぶしを指定するもの。

      そんなアルゴリズムは持っていないので、おかしくなっていたのだった。とりあえず G36 で アパーチャ を小さな矩形にしたところ上のようになった。対応するのは面倒なので、放置になりそう。

  • xo2qfn-05.zip

    eagle の ソース。部品が手に入るまで寝かせておく。(最新版に更新)

      更新: JTAGENB の位置が違うパターンを追加。

  • ここまで作って 74HC181 に合わせられないことが分かった。P/G のピンアサインを確認したつもりが ... 違った。どうしたものか。...



    結局、74HC181 の互換にできるように変更。281 互換にできたとしても、設計できる人しか有用である可能性がない。設計できるなら、ピン互換であることは重要でないだろうし、そもそも こういうものは使わないだろう。

    JTAGENB は、G である 17pin に移動。

    ところで、74HC181の コードを作るのは (P/G なしでも) 結構面倒だと分かった。そもそも Cn+4 の論理が分からない。回路図があるんだから、追っていけば分かるのだろうけど。

    追記: 多角形のフィルに対応。ちょっとインチキで形に条件が付くのだが、多分大丈夫。



    ナローDIP 版も設計してみた。1pin 分潰すだけではダメで 2pin 分になった。でも DIP28 だしソケットは使える。ブレットボードなんかでは、N.C. があるのは逆に便利かも。信号の並びは、ワイド版と同じだが ... おまけの部品は付けられず。まだまだ FPGA が手に入るのは先だろうから、これをベースに練っていこう。



    追記: 練ってみたら、こんなものに ... 。ワイドとナロー両方いけるうえに、1 列だけのユニバーサルエリア付き。切ればナローにもなる。なにか不恰好だが、発注するならこれか。

  • gerbtool-0.8.zip

    多角形のフィルに対応したので、gerbtool 単体もアップデート。

74HC181 とかの コード

  • qfn32samples.zip

    書いてみただけで、ピンアサインも前の案ベースになっていたりするのだが、74281, 74HC181, ROM, RAM のコードをまとめた。

    74HC181 は、とりあえず、回路図のロジックをそのまま組んでみた。

    追記 : ALU については、別記事にした。→ 『TTL ALU 74181』 。
    結構わかってきたので、拡張したりしてみている。

  • qfn32samples-03.zip とりあえず更新版 (2012/06/12)

    ALU だけではなく、いろいろ作ってみたいと思っている。ちなみに、I2C や SPI のモジュールは、バスから使う仕様なので、マイクロコントローラが必須に思えてきた。なので、256 では無理。何故 256 にこんなものが付いているのか? .. とも思ったのだが、コンフィグに使えるから意味はあるのだろう。

    次は、7seg 使った時計 .. かな? 時計自体は楽勝で入るのだが ... 時刻設定のインターフェイスが必要。ここが難しそう。チャタリング対策も要りそうだし。

    追記: 『FPGA時計の設計』設計はできてしまった。思いの外簡単だった。規模もまだ余裕がある。

  • qfn32samples-04.zip 更新版 (2012/06/17)

    なんと 無理だと思っていた CPU の実装が出来た。EFB にもアクセス可能。ただしメモリは、プログラム 128B(最大) + RAM 16B 。これでまともなコードが書けるかというと 無理そう。もうひとひねり要りそうだ。

    あと、FPGA時計にアラーム機能を追加したり、ALU の検証をしたり。

    後はなにを設計しよう --- 周波数カウンタ? 全然違うものとしては、DC/DC コンバータ? 高速 PWM も可能だし、コンパレータも 差動入力の流用で可能。面白いかも。

      そう言えば 『簡易シグマデルタ ADC』なんてものが、リファレンス・デザインにある。規模は 54 LUT だそうだから、載らないことはなさそう。温度計とか作れないかな? 一体 どれぐらいの周波数(sps)で AD変換可能なのだろう? 手軽に買える範囲だと PIC32MX が 1.1Msps だそうだ。それ以上になると AD9283BRS-80 の 80Msps 。10 Msps 程度がない。6bit でも良いから 簡単にできるのなら、(各種)液晶を RGB モニタに出来るかも。 まぁこの FPGA で作る必要はないのだが、何種類か ADC のモジュールを持っていると応用範囲が広がりそうな気がする。

      ... 高性能なのは、やっぱり無茶か。温度計とかが精々?

      コンパレータとして使うには、LVDS25 などを設定する。設定できるピンには制限がある。xxxT2_0 , xxxC2_0 とか T/C が含まれるピンのペア。T が非反転入力で このピンにアサインする。C は反転入力で指定しなくとも自動で選ばれる。QFN32 では、4/5 , 11/12, 13/14, 21/20, 28/27 の 5 ペアのみ。

      コンパレータとして使う場合、入力電圧の範囲が広いかどうか? ドキュメントには、LVDS を使ったときは、わずかに 低くなると書いてある。 VCCIO - 0.5V まで?

      MachXO2 のデータシートには、 (VCCIO=3.3Vのとき)入力コモンモード電圧 は、0.05V 〜 2.6V と書いてあった。(2.5V のときは 0.05V 〜 2.0V ) あと差動入力閾値 は、± 100mV だそうだ。

      入力電圧の範囲が問題になるのは、非反転入力に ダイレクトに ADC入力 を接続する場合。オペアンプと同じような話で、抵抗を直列に入れて (ADC入力 + 生成電圧) を 仮想GND と比較する場合は関係なくなる。

      ところで ADC が出来たとして データを 10進数に変換するのは、どうしよう? MachXO2-256 の規模では、無理なんじゃないかと思えてきた。可能にする方法として思いつくのは、内部形式を 出力形式に合わせることぐらい。そんなことが出来るものなのかどうか?

追記(2012/6/21)

  • qfn32samples-06.zip 更新版 (2012/06/21)

    ADC は置いておいて ... DAC が出来ないか検討。なんかいけそうなので、コード追加。

    あと MCPU 。オリジナルベースは捨てて、rewrite 版のみ残した。何種類もあってもメンテできないし。

    ところで、Mouser で、1個買いの単価が出ているが、6 グレードが発注可能になった。リードタイムが9週間になっているが、ボチボチか。基板を発注する時期が近づいている。

    アナログ回路を組むなら、VCCIO のいくつかを別電源にしたいところ。なのだが ... ナロー型では、無理だった。ワイド型なら とも思ったのだが、なかなか難しい。VCCIO1 と VCCIO3 の 4pin 分をパターンカットで分離可能にするのが精一杯。

追記(2012/6/23)

    基板変更。VCC, VCCIO 0/2, VCCIO 1/3 をパターンカットで分離可能にした。VCCIO 1, VCCIO 3 だけもさらに分離可能。追加したコンデンサが 4 つになったが、全部裏側。従来どおり 追加コンデンサなしでも使えるのだが、位置の関係で、パターンをいくつかショートさせた方が良い ... という変なものに。まぁ 追加コンデンサ付けるし。

    電源は、VIA を寄せて 2mm ピッチのコネクタにしてみた。

    これをIteadStudio (新サイト)に、ついに発注。発注したのは 2 つで 最初に作ったワイド版と ナロー版。IteadStudio の DRC でチェックしたら、0.3mm のドリルが DRC でエラーになった。0.4mm に変更。

    送料は、$5.59 。

    6/30 : 発送の連絡

    実は、ナロー版について、『問題が出るかもしれない、そのときは相談させてくれ』という内容のメールをもらった。が、なにごともなく製造できたようだ。

    7月9日 20:27 国際交換支店に到着

    来ても眺めるしかないが、いよいよか。
    実を言うと Muser で 6 グレードのやつを 6/24 に発注した。8/27 に発送できる見込みだそうだ。

    7月13日 基板受け取り。

    来た。書くのを忘れていたのだが、両方 1mm 厚にした。大きい方は 10 枚ぴったりで、テープで巻いてあるだけだった。小さい方は 16 枚でパックされていた。

    一見しただけだが、とても作れそうに見えない。まぁチップが来たら頑張ってはみるが。

    8 月 1日 Shipment Notification がキタ。予定よりずいぶんはやい。



追記: 2012/8/26 ようやく組立て



    はんだ付けは、位置さえ決まれば、簡単だった。足がないためか .. ハンダがとなりとくっつかず切れが良い。吸い取り線を使うまでもなかった。

      (注意)簡単なのは、底面と側面のパッドがつながっているタイプなので QFN ならなんでも簡単というわけではない。あと PIC32MX にも QFN があって興味が出てきたのだが、こちらのピッチは、0.65mm 。基板を起こすときに注意しないと。

    今は、とりあえず組み立てただけ。PIC32MX が終わったら、こちらに戻ってくる予定。

    LC など部品についてすっかり忘れていた。

      R2/R3/R5 -- I2C のプルアップや、セラミック発振子を使うときピン間に入れる 1MΩ用。これは今後も使わないようなオプションなので付けない。

      右下のソケットと R1/C5/C6 は、水晶振子を付けるためのもの 1MΩ/22pF で一応付けてみた。

      忘れていたのが、ジャンパなのだが、左下 と 右(ナナメ)の VIA は、電源の引き回しがながいのを嫌う場合のショートカット用。裏面にパスコンを付けるなら不要だし、そもそも不要かも知れない。様子見するので、ショートカットしない。

      あと、上の2x3の VIA は、I/O電圧を 3.3V 以外にする場合に使う。ウラのパターンをカットして使うが、3.3V 以外は考えていないので変更なし。

      左の 300mil 幅のやつは、オプションがほとんどない。(あっても裏面を使う) 。付けたのは、コンデンサ 3 つのみ。

    作ったは良いがどう書き込むかについて。

      JTAGツールとSYNCBB』にある、rtavr_tools-0.10 を使って 書き込む予定だが、MachXO2-1200 しか対応していない。ちょっと修正が必要。

      あと、『MachXO2 1200ZE Breakout ボード』 も 少しの改造でライタにできる。Diamond から直接書き込めるからお薦め。JTAGENB を有効にする オプションで なにもしない回路をコンフィグし、JTAGENB を L にするスライドスイッチを付けるだけ。JTAG 用のコネクタがあるので、そこから 出力。JTAG をチェーンする方法もあるが、パターンカットとか配線とかが面倒。

eagle の ソース

関連記事
posted by すz at 22:11| Comment(0) | TrackBack(0) | MachXO2

2012年05月26日

dealextremeの電子パーツ

dealextreme で扱っている 電子パーツが増えてきている。(コンパチ品ばかりだが) Arduino関係 まで扱うようになっている。

お買い得なものや 日本で手に入りにくいものもあるので、ピックアップしてみよう。

お買い得なもの

日本で手に入りにくいもの

これぐらいにしておこう。電子パーツと言いながら、コネクタとか スイッチばかりになってしまった。こういうのは、実際安いし種類も豊富にある。探してみると掘り出しものが見つかるかも知れない。

追記: ミニミニバナナ

EAGLEの45°回転

    ナビゲーションスイッチ MT-008A は、(反時計回りに)45 °回転して マウントする。このライブラリを作ろうとしていたのだが、どうやって 45 °回転した PAD を作ろうか悩んでいた。-- i アイコンでの 設定で、回転角があるのだが、ふと 45 と入れてみたら見事に回転した。(使っているのは、EAGLE-5.7)

    なんだ、EAGLE でも 45 °回転した部品は作れるのか ... と嬉々としてライブラリを作ってみた。座標を計算するのが面倒なのだが、なんとか 出来上がった。 出来上がってから、部品も回転できるのでは?... と思いついてやってみたら、できた。 45 °どころか任意の回転ができるようだ。

    90 °単位しかダメだとばかり思っていた。

    ところで、MT-008A は、ユニバーサル基板にマウント可能かも。作ったライブラリをじっくりみると ... 位置決め用の でっぱり? が、2.54mm ピッチ。三連の真ん中さえ絶縁して引き出せれば なんとかなりそうだ。
posted by すz at 12:29| Comment(0) | TrackBack(0) | 日記

2012年05月24日

JTAGツールとSYNCBB

前記事で MPSSE についてちょっと調べてみたわけだが ... 自分のツールで対応してみたいというのが理由だった。だが、結局は 機能が増えるわけではなく、性能が改善されるだけだ。それはそれで興味深くはあるのだが ... 後回しでも良いだろう。

MPSSE はちょっと置いておいて ... 他のことを考えてみよう。

一般に JTAG Cable は、けっこう高価だ。自作なんかも 行われているわけで、JTAGkey clone が人気がある。ただ、これにしても 部品も揃えないといけないし、それなりにはコストがかかる。なにより作るのが面倒で、お試しでちょっと使うには敷居が高い。

であれば...

  • AE-UM232R を使って、配線だけで 広範囲な電圧(1.8V-5V)に対応。ただし遅い
     -- VCCIO を ターゲットから貰えば良い。配線だけで済むが、接続の手順というものがあるかも。
  • UM232H を使って、3.3V 固定なものの 配線だけで高速に 使える。
     -- UM232H は、3.3V 専用。またシリアルの設定のままだと基本 MPSSE は使えない

こういう風になれば、少しは敷居が下がるのではないだろうか?

    SYNCBB でも FT2232H で 3.87 Mbps までは出た。MPSSE の 1/5 ぐらいの性能だが、常に最高性能を出せるとは限らないわけで、使い物にはなるはず。FT232R だと 随分(ひとけた)落ちると思うが、お試し用と割り切れば悪くないはず。

でも、なかなかこうならないのは、ツールの対応が進まないからだと思う。それについて考察してみる。

OpenOCD

    まずはこれ。FT232R のドライバは標準ではない。誰かパッチを作っていないか調べてみると ...

    なんとあった。
    FT232R based JTAG with OpenOCD patch

    これである。ただし、

    I do not have FT232R. I just feel this might be interesting.

    なんてことが書いてあって、ちょっと心もとない。コード自体はしっかりしているようなので、まぁこれベースで 改造していけば、使えそうな感じはする。ちなみに、OpenOCD-0.5.0 に対してパッチを当ててみたら 2 ヶ所だけ reject された。手で直すのは簡単ではあった。

    ちょっと見た範囲では、ピンアサインがいまいち

    * Bit 7 (0x80): unused.
    * Bit 6 (0x40): /SYSRST output.
    * Bit 5 (0x20): unused.
    * Bit 4 (0x10): /TRST output.
    * Bit 3 (0x08): TMS output. (CTS)
    * Bit 2 (0x04): TDO input. (RTS)
    * Bit 1 (0x02): TDI output. (RXD)
    * Bit 0 (0x01): TCK output. (TXD)

    Bit2 が RTS だから もともと出力。これに TDO を割り当ててしまうと 出力がぶつかり、配線だけで 使うわけにいかなくなる。ピンアサインについては、後で検討してみよう。

UrJTAG

    OpenOCD ほどではないが、次ぐらい? に有名なツール。これのパッチ があるのかどうか 調べてみたが見つからなかった。

    対応するのにどれぐらい大変なのか? ちょっと見てみた。

    まず、UrJTAG は、FT245 ベースの USB-Blaster と FT2232(MPSSE) ベースの各種 Cable に対応している。ドライバは二段構成で ftd2xx , libftdi に対応した 下位ドライバ2 種類と、上位ドライバの構造をしている。

    で、FT2232 ドライバは 2000行ほどあるのだが、各種 Cable に対応している部分が 半分を占める。Cable を1つだけにすると 1000 行弱で、それをベースに すれば、それほどは難しくないかも知れない。USB-Blaster のコードもあり 500行ぐらいとさらに小さい。でも FT2232 ドライバベースの方が良さそうな気がする。

    ただ、ちょっと問題がある。下位ドライバで FT_SetBitMode して SYNCBB(Synchronous BitBang) モードにしたいわけだが、そのときに Direction の パラメータが必要なのだ。MPSSE だと read/write で Direction を変更できるから良いのだが ... SYNCBB だと なにか API を追加しないといけなさそう。

cblsrv-0.1_ft2232

  • Amontec JTAGkey-Tiny (FT2232) を Xilinx iMPACTから使う

    URL はここ。どれだけ知られているのか分からないのだが、JTAGkey clone を iMPACT から直接使えるようにするもの。ただ、Linux では使えないし、MinGW の環境でビルドできなくなっている。そのままでは、(私が)いじるのには向いていない。

    ところで cblsrv は、ネットワーク経由で iMPACT から司令を受けて JTAG の処理をする サーバ。で、司令がどういうものか ... ちょっと見てみたところ、どうも JTAG の STATE 設定 と TDI へ bit ストリームを送り込む (+ TDO から受け取ったストリームを 返す) 構造のようだ。

    ちょうど JTAG のツールを作っているところだから、上位レイヤーと自作のコードを くっつけた方が (私には)扱いやすそうな感じ。いっそのこと 上位レイヤーのロジックをコピーして re-write しても良いかも知れない。

というわけだ。このなかでは パッチが既にある OpenOCD の改造が 一番敷居が低い。ピンアサインを決めるだけなのかも知れない。( 作った人は FT232R は持っていないものの、ピンアサインは MPSSE に準じているところから見て FT2232X でデバッグ済みのような気がする。)

なら、まずはピンアサイン。avrdude-serjtag で基本のアサインは 2 つある。やはり TXD/RXD は空けておきたいから
 
TMS = D7/RI
TCK = D5/DSR
TDI = D6/DCD
TDO = D3/CTS

これが基本か。 あとは 2 pin だが、

/SYSRST = D4/DTR
/TRST = D2/RTS

これで良いのじゃないか?

あと、JTAGkey clone では、SYSRST は双方向 が基本みたいだが、このピンアサインだと 出力専用 にせざるをえない。(これで良いのだろうか?)

cblsrv の調査

    どんなコマンドがあるか調べてみた。

    MSG_COMMANDS:
    CMD_DONE
    CMD_SET_CABLE_OPTION
    CMD_GET_CABLE_OPTION
    CMD_IS_CONNECTED
    CMD_READ
    CMD_WRITE
    CMD_SET_PIN
    CMD_PULSE_PIN
    CMD_START_OPERATION (open みたいなもの)
    CMD_NAVIGATE_TAP (from to の STATE遷移)
    CMD_WAIT_TIME (sleep)
    CMD_WAIT_TCK (N 個のクロック送出)
    MSG_GET_INFO:
    MSG_SET_CABLE_MODE:

    MSG_CLOSE_CABLE:
    MSG_CHECK_SERVER:
    MSG_0x06:

    これで全部。MSG_XXX で、ひとつのコネクション。違う MSG を送るたびに一旦コネクションを切る。MSG_COMMANDS だけは、他のと違って CMD_XX がひとつのコネクションのなかで連続して発行される。

    まず重要なのが、 CMD_NAVIGATE_TAP -- 簡単なものだとしても JTAG のレイヤーを 持っていないといけない。

    CMD_WRITE は、TDI ストリームを送り込む司令で、CMD_READ は、最後の CMD_WRITE に対する TDO ストリーム を返す。常に read しておかないといけないので、ちょっと嫌らしい。

    MSG_GET_INFO/MSG_SET_CABLE_MODE は対になっていて、ケーブル名 や ケーブルタイプ , port , speed を GET/SET する。

    CMD_SET_CABLE_OPTION/CMD_GET_CABLE_OPTION は、オプション名 に値(文字列) を読み書きするもの。これも 汎用的すぎてちょっと嫌なかんじ。

    cblsrv 自体は、C++ だが、クラスを使ってなくて、C に変更するのは容易。これを書き換えていくというのは、ひとつの手なんだが、書き方が自分の趣味に合わない。MSG_XX や CMD_XX に対して関数が割り当てられていて、case 文 と処理するコードが離れている。関数作る分だけ冗長だし、流れを読みにくい。

    やっぱり、re-write したいような ...

自作のツール

  • rtavr_tools-0.10.tar.gz

    一応 作っているものの最新版。ただし 前記事の MPSSE 対応とか この記事に関係するところをいじっている最中。あと、ちゃんと書いておくと もともと汎用的なものは目指していない。自分が使う範囲で便利なものを目標としている。
posted by すz at 20:06| Comment(0) | TrackBack(0) | artemis