2011年05月21日

AVR互換コア(SMP化)

AVR互換コア rtavr は、いろんなパーツを外したり付けたりすることができるようになっている。その中には、スタックポインタ(SP)/フラグ(SREG) や ROM/RAM まで含まれる。

で、思いついてしまったのだが、ROM/RAM を共有する DUAL_CORE が簡単にできる。- ROM/RAM を取り外して、外部で 構成するだけだ。単なる DUAL_CORE ではなく、SMP (Symmetric Multiple Processor) だ 。

    これは、是非ともトライしたい。SMP を自分でデザインすることになるとは、予想もしなかった要素だ。ますます、面白くなってきてしまった。ちょっと検討してみよう。

    ( 『bitstreamベースのAVRコア(案)』なんて記事を書いたがどうでも良くなった。準備だけは出来たから、いずれやることにして、凍結しよう。)

SMP でも いわゆる Processor 固有領域というものが必要だ。スタックがあるから 下位 256B は違う領域を指さないと困る。
そうなると下位 128B までにある グローバル変数も プロセッサが違うと 違うデータになる。

共有するのは上位アドレスの領域。

SRAM は 2KB もあるから、両者でマッピングを変える。

    マッピングについては、後で詳細を詰めるとして ... RAM をもっと付けたくなるかも知れない。

    いままで、RAM には、2KB の空間しか渡していなかったが、8KB の空間を渡すように変更しよう。(ただし、8KB のとき 使えるのは、8KB - 64 バイト)。

    サイズの設定は、ROM と同じように RAM_SIZE=2048 といったパラメータにする。

ROM には、プログラムから読み込む機能があるが、2つのポートを それぞれの AVR に割り振ると それができなくなる。プログラムメモリを節約できるかわりに、初期値付きデータが利用できなくなったりして 使い勝手が低下する。それに ISP でも 2 つ目のポートを使っている。

    やはり、gcc の普通のプログラムが動かないのは論外だし、ISP も使いたい。

    プログラム と データを同時にアクセスしないよう AVR のパイプライン制御に手を加えると、CPU あたり 1 port で済ませることが可能になる。(従来のAVR は LPM命令でそうしていた)

    ... このコードを実装してみた。変更する所を最小にするために、見かけは Dual Port だが、競合しないことが保証されているので、1 port しか使わないようにしたわけだ。

    define は、ROM_PSEUDO_DP 。

    これをベースに、擬似 4 port の ROM module を作ってやれば良い。 ISP から見るとインターフェイスが 2 つになる。が、実体は 1 つなので、どちらを使っても良い。

あと、プロセッサ間の通信。メモリを共有しているから、基本的にデータの転送は不要だ。ロックの命令は作るつもりはない。同期制御には、イベントの通知だけを使う。-- これは USART を相互に接続することで、対応しておこう。(... 最初の考え)

    一旦シリアルにして、またパラレルに戻すのは無駄が多い。インターフェイスは、USART だが実際はパラレル ... FT232R と FT245R の関係 のようなモジュールを作っておきたい。

    それに、FIFO 化もしたい。USART は 既に 1 つの FIFO に対応しているから、少し仕様を追加するだけで、使い勝手良く実装できそうだ。

    ... これについては、『AVR互換コア(FIFOモジュール)』の記事参照。

IOR は独立に持つ。SP/SREG が含まれているし、通信するにもデバイスを独立に制御できなくてはならない。

  • プログラム空間が 1 つであれば、プロセッサ ID が必要だ。attiny40 ならば、そういうデータを置く領域が 64bit の空間にある。-- rtavr はどうしよう。

    現状 NVMCSR を使って ROM のプロテクトをいれている。DUAL CORE なら 相手の RAM のプロテクトを入れたい。どうせ仕様変更が必要なら、そこに プロセッサ ID を割りこませよう。

  • IOR のデバイスの構成は、ifdef になっている。現状だと CPU0 にだけ 特定のデバイスを付ける... といったことができない。同じデバイスを両方とも持ち、空間や割り込み番号、ポートへのマッピングは同一になってしまう。

    これは... 一体どうしたら良いのだろう?

    rtavr は、IOR を外すことさえできる。SMP 専用の IOR を作り、外部で接続することは可能だ。だが、デバイスの実体が 1 つだとすれば、調停機構が必要になる。IOR 側だけでなく、コア側にも リクエストと ACK/NAK の概念を入れないといけない。あと、割り込みもどういう風に制御するのか設計が必要。

      ROM の調停機構が入ったから、これをヒントに軽く検討してみよう。

      まず、IOR へのアクセスには、IN/OUT CBI/SBI と 条件スキップの SBIC/SBIS 命令、さらに X/Y/Z レジスタ間接の LD/ST (+LDD/STD) 命令がある。

      レジスタ間接は、アドレスが確定するのが遅いから、調停したくない。-- この条件から IOR のDual Port 化は必要ということになる。ー アクセスはできるが、競合した結果は保証しないという風にまず持っていくわけだ。その後で実際には競合しないように制御を追加すれば良い。

      さて、IN/OUT CBI/SBI と SBIC/SBIS は、アドレスが 6bit しかなく、INST にアドレスが入っているから、S1 で速やかに判断できる。S2 で 実際に使う 1 クロック前に 判断できるので、競合したときは、パイプラインストールさせれば良い。

      ここでひねりを入れる。SBIC/SBIS の後続命令が CBI/SBI , IN/OUT なら 続いて IOR を専有させる.. と見事にロックができる。( ただし、2 つの命令の間で割り込みが起きないようにしておかないといけない。)

      ロックする対象のレジスタは、なんでも良いが ソフトで使える GPIOR (0/1/2) というのも用意する。( 普通の AVR にはあるが、モデルとなった attiny40 にはないので追加する )

      (追記) ロックする対象のレジスタは、0 - 0x1f の範囲。 CBI/SBI と SBIC/SBIS はその範囲しかアクセスできない。
      ... そういえば RAMWIN の RAMDR が 0x1f , RAMAR が 0x20 でなぜ半端なとこに置くのか不思議だったが、こういう理由があったのか。

    ... どうも作ることは、可能そうだ。ならば是非トライしたい。

RAM空間の定義

    まず、いままで RAM をどう使っているか整理しておく。

    RAM物理アドレス 論理空間
    (RAM PHYS) (VIRTUAL ADDR)
    +----------+
    | |
    0x0040-0x0800 | A | 0x0040-0x07ff
    | | (0x0840 - 0x0fff)
    +----------+
    0x0000-0x003f | B | 0x0800-0x083f
    +----------+

    アドレスに IOR 空間の分を加算せず サイクリックに使っている。B の部分は、0x800-0x840 で見えるわけだ。ちなみに、デコードをさぼっているので、0x0840 - 0x0fff も A の部分が アクセスできてしまう。

    考慮すべき要素:

    • LDS/STS 命令は、RAM の下位 128B しかアクセスできない。空間で言うと 0x0040 - 0x00bf 。

    • AVR_Toolchain の設定は、attiny40 と同じにしているので、RAM は 256B しかない扱い。初期化も 256B までしかしないし、スタックポインタも 0x013F に設定される。

    • スタックのアドレスは、再定義することが可能だ。また、malloc のプールの範囲も再定義が可能。

    これを DUAL_CORE 化でどうするか ...

    RAMの下位アドレスは、プロセッサ固有空間にしなくてはならない。malloc のプールなどがあるから、256B と限らず もっと大きくしたい。かといって 共有空間では同じアドレスでアクセスしたい。

      ロックがないから、共有のプール領域の管理が難しい。それぞれのプロセッサが管理する領域を持つとすれば ... 相手が割り付けた領域を 使うことはできるが、相手しか free できない。

      まぁ、AVR で malloc を使ったことすらないのだが... 。

      (追記) ロックは可能になったから、共有のプール領域の管理が普通の SMP マシンのように出来る。プロセッサ固有空間のサイズを RAM 容量に合わせて変更できるようにしてみたが、使わなさそうだ。

      ちなみに、ロックは割り込み処理との排他にも使える atomic 操作。シングルプロセッサでも有効に使える要素がある。

RAM マッピング

     - CPU 1 では、RAM の 下位 1KB に対して アドレス の bit 9 を反転する..というのはどうだろう?

    CPU0 CPU1 WP(self)
    +----------+ +----------+ WP(other)
    0x0800-0x083f | PSB(1) | | PSB(0) | x o
    +----------+ +----------+
    0x0400-0x07ff | COMMON | | COMMON | x x
    +----------+ +----------+
    0x0240-0x03ff | PSA(1) | | PSA(0) | o x
    +----------+ +----------+
    0x0200-0x023f | PSB(0) | | PSB(1) | o x
    +----------+ +----------+
    0x0040-0x0200 | PSA(0) | | PSA(1) | x o
    +----------+ +----------+

    PSA(0) プロセッサ固有A CPU 0
    PSB(0) プロセッサ固有B CPU 0
    PSA(1) プロセッサ固有A CPU 1
    PSB(1) プロセッサ固有B CPU 1


    共有領域は、RAM_SIZE - 1KB 。

    プロセッサ固有A アドレスに + 0x200 すると 相手の データにアクセスできる。だが、プロセッサ固有B は、一種の HIGHMEM でややこしい。

    自分用のメモリだが、RAM のライトプロテクトをかけると、相手ではなく自分が書けなくなる。相手からは自由に書けるのだが、8KB のときだけ相手は見えない。

    この制御は、外部でも出来るが、CPUID を rtavr の中に持たせるので、rtavr の中でも出来る。-- rtavr で制御することにしよう。

    ちなみに CPUID は、rtavr のパラメータにする。

ロックの実装

    まず、-- 条件スキップの SBIC/SBIS 命令に続く IN/OUT , CBI/SBI の間では割り込みを入れないというところ -- こんなので良いのか検討してみよう。


    // 0x81: CPU0, 0x82 : CPU1
    if (bit_is_clear(GPIOR0, 7)) GPIOR0 = 0x81;
    if (bit_is_set(GPIOR0, 0)) { // ロック成功
    :
        GPIOR0 = 0x0; // アンロック
    }

    こういう使い方はできる。だが、ロック変数を最大で 3 つまでしか持てない。

    そうなると、二重ロックを使わざるを得ない。

    #define bit_set(port,bit) { port |= (1<<(bit));}
    #define bit_clear(port,bit) { port &= ~(1<<(bit));}

    if (bit_is_clear(GPIOR1, 0)) {
    // 0x81: CPU0, 0x82 : CPU1
    if (bit_is_clear(GPIOR0, 7)) GPIOR0 = 0x81;
    if (bit_is_set(GPIOR0, 0)) { // LVL1 ロック成功
    if (bit_is_clear(GPIOR1, 0)) { // LVL2 ロック成功
    bit_set(GPIOR1, 0);
        GPIOR0 = 0x0; // アンロック LVL1
    :
    :

    bit_clear(GPIOR1, 0); // アンロック LVL2
    }
    }
    }

    LVL1 ロックは、LVL2 ロック変数を変更する権利だと考えれば、別に GPIOR1 にロック変数を置く必要はないのかも知れない。

    まぁとにかく、複数命令間でアトミックでないと困るのは、 GPIOR0 = 0x81;

    割り込みを入れないとか、2 連続でアクセス権利を獲得できるのは、

      条件スキップの SBIC/SBIS 命令に続く IN/OUT , CBI/SBI で 同じアドレス。

    という条件が良いかも知れない。

    (追記) RAMWIN のことを忘れていた。
    RAMWIN は、IOR 経由で RAM にアクセスできる機能。最初の実装は、RAM の dual port を使っていたので、SMP では使えないものと思い込んでいた。

    だが、これを使えば、RAM の任意のバイトに対し LVL1 ロックが可能ではないか。


    unsigned char *lock;
    unsigned char lock_val = (NVMCSR & 1) ? 0x82 : 0x81;
    // 0x81: CPU0, 0x82 : CPU1
    cli();
    RAMARH = (uint16_t)lock >> 8;
    RAMAR = (uint16_t)lock & 0xff;
    if (bit_is_clear(RAMDR, 7)) RAMDR = lock_val;
    if (bit_is_set(RAMDR, 0)) { // ロック成功
    sei();
    :
    cli();
    RAMARH = (uint16_t)lock >> 8;
    RAMAR = (uint16_t)lock & 0xff;
        RAMDR = 0x0; // アンロック
    sei();
    } else { // ロック失敗
    sei();
    }

    こんな使い方ができる。lock 変数は、共有領域でないといけない。

    こういうことならば、RAMWIN の機能をちゃんと実装しよう。

対応状況

  • ROM_PSEUDO_DP

    これは、ROM 領域を LD/ST(+LDD/STD) でアクセスする場合に、命令の読み込みと競合しないようにする。ROM とのインターフェイスは、相変わらず DUAL PORTのままだが、ROM 内で SINGLE PORT 化可能になった。

  • SUPPORT_LOCKING

    『条件スキップの SBIC/SBIS 命令 の後続命令 が、同じアドレスに対する IN/OUT , CBI/SBI ならば、その間に割り込みを入れない』

    .. いまのところこれだけの機能。これは、比較的簡単に入った。

    だが、妙なことに規模があまり増えない。... 調べてみると SBIC/SBIS 命令の S0 でのプリデコード PD_SBIX が規模を減らす効果があった。さらに調べると、NEXTPC_TUNE でオプション化されていた PD_EXT (命令群のプリデコード)も、同じく規模が減る。この 2 つを 標準に変更。

    さて、今の SUPPORT_LOCKING は、まだまだ布石。IOR の調停機構にお伺いを立てて ACK を受け取らない限り パイプラインをストールさせる機能も入れる。... だが、それをする前に、IOR の Dual Port 化を完了させなければならない。

  • IOR_HAVE_GPIOR

    0 - 0xf の 16 個のアドレス範囲に対して GPIOR をサポート。

    .. FF で作るより、分散メモリで作った方が 規模が減る。なにしろ 1:16 だ。1 つの GPIOR を作るコストで 16 個分用意できる。また、この変更に伴い、CPI0/CPI1 のアドレスを移動。

    実装は、レジスタの下に置くという風にした。読み出しで、同じアドレスに 他のレジスタがなければ GPIOR を読み込む。書き込みの方は、レジスタがあっても書きこんで構わない。

    ただ、デコードに対するオプションが、LARGE_MAPPER , MUX_TRI , MUX_OR , +デフォルト とあって実装の組み合わせが増える。MUX_TRI , MUX_ORは、『レジスタが上にない』という意味の 信号を定義しないといけなくて特に面倒。なので、MUX_TRI , MUX_ORを切り捨ててコード削除。今後 MUX_TRI は使えなくなる。 MUX_ORは、使えるが IOR のサブモジュールに対する指定に限定。

  • IOR_HAVE_RAMWIN, IOR_HAVE_RAMWIN_HA

    これを Single Port でちゃんと実装することにした。 IOR_HAVE_RAMWIN_HA は、RAM の上位アドレス(HA) を指定する機能で、アクセスしやすいように 仕様変更した。

    実装する際にアドレス空間について見直した。もともとは、単に RAM の物理アドレスを差すだけだったのだが、CPU1 からアクセスする場合、アドレス変換が入らないと使いにくい。また、RAM のライトプロテクト機能も効かせたい。

    ... というわけで、アドレス変換は少々面倒なことになった。ちなみに対応コードは rtavr.v 。

    だいぶ進んだが、IOR Dual Port 化への道は遠い。

    メモリなどと違って、リソースの種類が沢山あるから、恐ろしく面倒なのだ。

    まずやらなければならないことは、CPU 固有のリソースと装置全体のリソースに分けること。

  • rtavr_ior_ps.v

    CPU 固有のリソースは、SREG/SP , INT0(+INT1) , NVM , RAMWIN 。それに加えて TMR0 / CPI0 ということにした。CPI0 は、Dual Port 化 IOR 内で クロス接続の予定。

    まずは、rtavr_ior.v を rtavr_ior_ps.v にコピーして、上記のものだけにする作業をした。その際に デコードに対するオプションを、LARGE_MAPPER 相当のみに機能削減。

    あと、INT0(+INT1)の入力信号の定義と、『アドレスに対応するレジスタが存在しない』という意味の 信号を定義。

    この作業は、削除が主なので比較的楽なほう。

  • rtavr_ior_ms.v

    次は、rtavr_ior.v を rtavr_ior_ms.v にコピーして、CPU 固有のリソースを削る。残ったものは、PORTA/B/C , USART, SPI , TWI(未実装), CPI1 。

    CPI1 は、外部との接続用で CPI0 と違う扱い。

    こちらもアドレスデコードは、LARGE_MAPPER 相当のみに機能削減。... これは、やりすぎかも。デフォルトだったデコード方式は、サブモジュールで 1 つに一旦まとめるというもの。Dual Port 化したとき 2つのデコード方式で意味が変わる。

      LARGE_MAPPER 方式は、READ に対しては、レジスタ1つ1つを独立にアクセスできる。コードの修正も楽な方。そのかわり、実際の配線量が多い。

      WRITE に関しても似た様なもの.. のはず。ただ、すべての レジスタに対して 入力データのセレクタが付くことになる。

      この配線量が非常に多そうな気がする。SUPPORT_LOCKING では、アクセスしようとするアドレスも仕様にいれているので、IOR 調停で プロセッサ固有のレジスタ以外は、同時には 更新できないという風にすると セレクタを 1 つにできて 配線量を減らせるはず。

        これは間違いだった。LARGE_MAPPER であっても、WRITE の場合はサブモジュール単位で 1 データ。レジスタ単位で WRITE データを別にはできない。1 サブモジュールに対して同時に WRITE すると、2 つのレジスタに同じデータ(CPU0側)が書きこまれる。... どうも凝ったことしないでも良さそうだ。

        GPIOR も事情は同じ。WRITE の場合 1 つのデータしか書けない。で、アドレスデコードを適当にすると、ひどいことになる。

      サブモジュール方式だと、同一モジュールに対して 同時にはアクセスできない。アドレスが 1 つしかないから、どちらかのアクセスを選ばないといけない。同時にアクセスされた場合の動作は未定義。-- 実装上は CPU0 優先にする。

      ... こんな風になる。サブモジュール方式の方が良さそうなのだが、どうせだいぶ作り直さないといけないので、一旦削除で良いだろう。


  • 割り込みのルーティング(ToDo)

    装置で共有するデバイスの割り込みを どちらの CPU に回すかの 機構がいる。

      両方ともに同じ信号を送り、ACK を 単に OR することで、どちらかが ACK を返せば割り込みがクリアされるようにした。基本どちらかだけが、割り込みを処理するようにしないといけない。両方のCPU で割り込みを許可すると、同時に両方で割り込みが入ったり、片側だけに割り込みが入ったりする。好ましくない挙動だが、とりあえずはこれでも良いだろう。

      本来なら、割り込みを許可している CPU を 1 つ選ぶ 。両方許可していたら SLEEP している方を優先。
      .. といった制御が良さそうだ。-- これはいずれ。

  • IOR アクセス調停機構(Todo)

    Dual Port 化の実装方法の違いで、調停のしかたも随分違うようだ。

  • まとめ

    こんな風に IOR Dual Port 化では、コードが大きく変わる。一旦 IOR の ソースを分岐させるが、最終的には、Dual Port 化 IOR を Single Port でも使うようにするつもり。

    Version 0.9 として リリースする予定だったが、大更新中なので、なかなか難しい。コードが落ち着かないので スナップショットも作る気になれない。ソースコードの提示は形が定まった後にする。

SMP化の効果

    正直なところ、SMP化してどれだけの効果があるものなのかよく分からない。

    AVR互換コア(FIFOモジュール)』の記事で書いた CPI を使って rtavr 同士をつなげば十分のような気がする。

      一方SMP化自体には意味がある。第一に rtavr を特徴づけることができる。SMP の 8bit 組み込み向け CPU なぞ世の中にほとんどないはずだ。作る立場として、意義を見いだせることは重要なのだ。

      SMP としての特徴は盛り込むつもりだから、教育用としても意味があるはずだ、SMP 制御の基礎を簡単なコードで動かしてみることができる。

    CPI だけを使った場合と比べて、どういう効果があるかちょっと考えてみる。

    • 規模

      CPI 自体の規模は非常に小さい。CPI だけを使うと ほぼ 2 倍の規模になると見積もれる。

      一方 SMP 化で共用するのは、ROM/RAM/装置IOR(PORT/SPI/USART)

      ROM/RAM はブロックRAM で単に使用個数の問題。SMP だと いわゆる分割損の分節約できるかも知れない。ただ、すなおに Dual Port の機能を使っていたのが、(CPU から見て)Single Port 化 することになり規模的にはセレクタの分が少々増える。

      IOR には、プロセッサ固有のものがある (SREG/SP/TMR0/CPI0)。これらは、単純に 2 倍になるから CPI だけの場合と比べて違いはない。

      装置IOR(PORT/SPI/USART)だけは、1 つしか持たない。これらは SMP の方が有利そうに思えるが、調停機構や、それぞれの CPU からのアクセスができるようにする配線で相殺されそうなかんじ。

      まぁ SMP だからといって有利にも不利にもならないように思える。

    • 扱いやすさ

      rtavr 同士で通信するなら、整合性がとれたプログラムが 2 つ必要だ。SMP なら 1 つなのだ。プログラミングには、少々コツがあるが、SMP の方が扱いやすい。

      構成についても同じ。SMP でなければ、2 種類を構成し、プログラムも別々に定義しないといけない。ー 実をいうと 今の rtavr は、2 種類を Implement することを想定しておらず、それ自体を行うためにいろいろ面倒なことをしないといけない。

    • 性能

      そもそも 2 個付けるのは、性能のためである。で、SMP はどう有利なのだろう?

      SMP では、CPI を相互に接続するから、不利な点はない。初期化以降 CPI だけを使って通信するなら、CPI だけのときと等価の動作も可能だ。

      ある装置から収集したデータを加工して もうひとつの CPU に送り込み、そこでさらに加工して出力する場合を考えてみる。

      こういう単純な通信しかしないのであれば、CPI を使うだけで事足りる。FIFO に送り込んだりするなら、ポインタの管理が不要な分高速にすらできる。

      性能的に有利になるとすれば、バッファが 16段の FIFO で足りない場合か、マルチストリームが必要な場合。
      ... といっても 細工すれば、バッファは増やせる。64段程度ぐらいまでなら対応できそうだ。マルチストリームも CPI を 増設する方法がある。2 個 3 個ぐらいならなんとかなる。規模には限界があるが、そもそもプログラムの規模が増えると rtavr に入らない。

      ランダムアクセスが必要な場合は、SMP が圧倒的に楽だ。CPI だとプロトコルを組んでいろいろ検討しないといけない。... といっても ランダムアクセスといえるだけのデータ量はそもそも扱えない。

      やはり、SMP だからといって性能は特別有利とは思えない。-- SMPの特徴は柔軟性だと言えそうだ。プログラムを変えることでいろんなやり方ができる。


対応状況(その2)


    // Target Device: xc3s200a-4ft256 , top-level rtavr
    // 従来 ior 新 ior
    // Number of Slice Flip Flops: 390 390
    // Number of 4 input LUTs: 1176 1081
    // Number of occupied Slices: 652 654
    // Number used as a route-thru: 33 33
    // Number used for Dual Port RAMs: 16 16
    // Number used as Shift registers: - -
    // Number of bonded IOBs: 12 12
    // IOB Flip Flops 1 1
    // Number of BUFGMUXs 3 3
    // Number of RAMB16BWEs - -
    // Number of DCMs - -
    // Number of BSCANs - -
    // Post-PAR Static Timing Report
    // Maximum frequency(MHz): 43.894 44.715

    とりあえずは、rtavr_ior.v のかわりに rtavr_ior_ms.v/rtavr_ior_ps.v を使って Implement できるようになった。50A 用の Config ではわずかに +2 スライスの増加で済んでいる。


    // SHRINKED SMALL-SP SMALL-SMP
    // Number of Slice Flip Flops: 390 421 732 (+ 342)
    // Number of 4 input LUTs: 1081 1198 2268 (+1070)
    // Number of occupied Slices: 654 727 1341 (+ 614)
    // Number used as a route-thru: 33 33 61
    // Number used for Dual Port RAMs: 16 16 32
    // Number used as 16x1 RAM: - 8 8
    // Number used as Shift registers: - - 16
    // Number of bonded IOBs: 12 12 12
    // IOB Flip Flops - - -
    // Number of BUFGMUXs 1 1 1
    // Number of RAMB16BWEs 3 3 3
    // Number of DCMs - - -
    // Number of BSCANs - - -
    // Post-PAR Static Timing Report
    // Maximum frequency(MHz): 44.715 45.319 46.134
    //
    // SHRINKED : soc/configs/50A/rtavr_defs.v
    // SMALL-SP : smp/configs/small-sp/rtavr_defs.v
    // SHRINKED + ROM_PSEUDO_DP + SUPPORT_LOCKING + IOR_HAVE_GPIOR
    // + IOR_HAVE_RAMWIN + IOR_HAVE_RAMWIN_HA + NVM_RAM_PROTECT
    // SMALL-SMP : smp/configs/small-sp/rtavr_defs.v

    SMP が Implement できるようになった。SHRINKED は、上記 新IOR に ROM を共通化した rtavr_rom_p4.v を使ったもの。これに、SMP で使う機能を加えたのが SMALL-SP 。SMALL-SMP はそれを SMP 化したもので、top-level として rtavr_smp.v を追加(他のソースコードは共通)。CPU 間通信用の CPI0 も入っている。

      現状、多数の Warning が出ている。直していくと多少規模が増えるかも。

    これに、IOR の調停機構を入れて、ひとまず完成のつもり。

    ところで、SMP のテストは、悩ましい。タイミングで動作が変わるからシミュレータとのトレース比較ができないのだ。ただ、動作が変わらないコードを書いて、それぞれの CPU の トレースを比較することはできそう。

愕然となった事実

    いろいろ見直してたら、いろんな所で RESET == 0 のとき、リセット動作をしているのに気がついた。... ずっと RESET は正論理 -- 1 でリセットのつもりだったのだ。で、現状は、正論理と負論理が混在。

    良く動いていたものだ。もともと正論理のつもりなので、それに合わせて修正。テストベンチも変更。

IOR の調停(の準備)

    GPIOR は、SMP で CBI/SBI が自由にできるように導入したのだが、分散メモリだと 調停するのが面倒なことになる。結局、構成を自由に変えられることにして FF で作り直した。これで、アドレス単位で調停すれば良いことになる。

      GPIOR0 - GPIOR2 の 3 つを構成すると 654 スライスが 681 スライスになった。SP(Single Processor)でこれだから、SMP だと もっと増えそうだ。

    あと、アドレス単位での調停をしたいのは PORT と RAMWIN の RAMDR 。

      PORT には、複数の装置が割り当てられているので、調停なしだと、ソフトで排他制御してアクセスすることになる。ビット出力するにもロックしてからとなると、非効率どころではない。

      RAMDR は、排他制御をできるようにするためのカナメだから、調停は必須。

    PORT も インターフェイスを変更して DDR と PORT を同時に書けるようにした。

    (調停機能の定義)以上のレジスタは、アドレスが同じなら 同時にアクセスできないようにする。

      RAMDR を除く CPU 固有のレジスタは、調停が必要ない。それ以外のレジスタも調停しないことにする。 ひとつのサブモジュールに対して同時に更新すると誤動作するので、ソフトでの排他制御が必要になる。サプモジュール毎にアクセスする CPU を決める使い方ならソフトでの排他制御は必要ない。

愕然となった事実(2)

    RAMWIN は、0x1f の RAMDR に対して SBI/CBI することができる。要するに RAM に read-modfy-write の機能が必要だということだ。

    実際にテストしてみて初めて気がついた。幸い ROM/RAM を 1 クロックの 前半 READ / 後半 WRITE と 2 回アクセスできる仕組みを以前使っていたので対応できた。ただ、IOR は非同期 READ を前提にしていてラッチしている。RAM の READ は間に合わないので、RAMWINからの READ は、ラッチを通さずセレクタで切り替えることにした。

    だが、ちゃんと対応しても動かない。変だと思って調べたら 直前の SBIS/SBIC 命令でおかしなことをしていた。... 原因はデコードのバグ。

    ... RESET もそうだが、今に至ってこのレベルのバグがみつかるとは!

    ちょっと不安になってきた。

rtavr-0.9.0

  • rtavr-0.9.0.tar.gz

    なんだかんだで、まだまだという気はするが、ついに 0.9.0 にした。

    ファイルを整理して、中途半端ながら ドキュメントも付けた。

    avr8l_trace の機能を追加。(ドキュメントはできていない)

      GPIOR0/GPIOR1 を付け、 RAMWIN の仕様を現状に合わせた。

      さらに、SBI/CBI 命令の仕様を変更。

      -exbi を付けると、CBI/SBI で変更するビットの変更前の値を T レジスタに入れる。

      ... こうすると TEST & SET の機能になる。GPIOR0/GPIOR1 などを 1bit 単位で ロック変数にできるのだ。

    もちろん rtavr での機能も実装。(SUPPORT_EXBI) 。tests/tbi_001 でコードを入れて確認済み。


    000004c2 <t_test_and_set>:
    261: 2788 eor r24, r24
    262: 9a1f sbi 0x03, 7 ; 3
    263: f980 bld r24, 0
    264: fb81 bst r24, 1
    265: 9508 ret

    テストコードは、こんな風にした。sbi で GPIOR bit7 に 1 を入れる。old-value が T に入るから 戻り値の bit0 に移す。バグったときトレースの差分が沢山出ないように T を bst でクリア。

    で、結果が 0 なら old-value が 0 だったということだからロック成功。1 なら失敗。

    あと RAMWIN のテストコードも紹介しておくと..

    000004ae <t_lock>:
    257: e744 ldi r20, 0x74 ; 116
    258: e050 ldi r21, 0x00 ; 0
    259: bb5e out 0x1e, r21 ; 30
    25a: bd40 out 0x20, r20 ; 32
    25b: 9bff sbis 0x1f, 7 ; 31
    25c: bb8f out 0x1f, r24 ; 31
    25d: b38f in r24, 0x1f ; 31
    25e: 9508 ret

    まず、アドレスを 0x0074(char lock) に設定。lock の MSB が 0 なら 第一引数(val: 0x80 とか)をout 。
    改めて in で READ して同じ値なら ロック成功。

    CBI/SBI の仕様変更が嫌ならこのやりかたをする。

      bst/bld なんて滅多に使わないからチェックは楽なんだが、互換性がないと困る場合もあるかも知れない。命令を新設するのはやりたくないので、互換性を維持してロックを作れる この方法もサポートする。

    これらは、0.9.0 の tbi_001 に入れてある。


SMP を『Icarus Verilog』で 確認



    ちょっと修正が必要だったのだが、『Icarus Verilog』で 確認できるようになった。

    基本的に rtavr と rtavr_smp の インターフェイスは同じにしている。わずかな修正で 従来のテストベンチを使えるようにできた。

    動かしてみると ... 当然と言えば当然なのだが、IORの調停が必要にならない限り CPU0 と CPU1 は 全く同じタイミングで動作する。そして、CPU_ID が入っている NVMCSR 以外のデータは全く同一で、同一データに対する結果も同じ。

    tb_shuffle や tb_queen は、コアテスト用なので IOR にアクセスしていない。当然ながら CPU0 と CPU1 は、最後まで同じ動作をする。

      ちなみに、トレースを取るのは、CPU0 だけだが、define PRC_WATCH_P1 とすると CPU1 だけを取るように変わる(未確認)。avr8l_trace も対応していて、-1 オプションを付けると、CPU_ID の値を 1 にする。(ただし、CPU0 と CPU1 で協調動作した場合のトレースは無理。)

    tbi_001 はロック命令のテストコードがあるので、CPU0 と CPU1 とで違う動作になった。

      BB5E は、0x1e (RAMARH) に対する OUT 命令で、BD40 は、0x20(RAMAR) に対する OUT 命令。0x1e に対しては調停の要求が出て 0x20 に対しては 要求が出ない。-- これは S0 のPRE命令デコードで決めている。

      この競合で勝つのは、lk_last でない方。-- lk_last の初期値を 1 にして CPU0 を勝たせている 。

      負けた CPU1 は 1 CLK ずれる。

      9BFF は、SBIS 命令で、次が同じアドレスに対する OUT 命令なので、2 クロック分を要求する。1 CLK 遅れた CPU1 はまた負けるのだ。

      結構良いところまで行っているのだが、バグっている。-- 負けた側が 2 回目の REQ を出していない。

      よくよく見れば、負けた側が 次の命令にいってしまっている。S1 での判定にしたが、引き伸ばせるのは、S0 だけで S1 に入ってしまったら、キャンセルすることしかできない。S0 で調停して負けたら そこで引き延ばさないとだめだ。-- 全然ダメのように見えるかも知れないが、コード量は少ないし、確認できるわけだから実は大した事はない。



      直してみた。それ自体は簡単だったのだが、ROM_PSEUDO_DP のコードを入れたときに ROM データを CLK_180 でラッチしていたのだった。そのため、判定で使える期間が 1/4 CLK になってしまっていた。これでは ボトルネックになりそうなので、ROM のタイミングを変更。

        ROM は CLK_0 で アドレスを出しておいて CLk_90 で 読み込み。データが安定するのはそれ以降だから CLK_180 でラッチしていた。なぜそれが必要だったかというと、ROM を書き換える場合 CLK_180 で 再度アクセスして、そのときに 出力データが変わってしまうため。命令データは、CLK_0 まで変化してはいけないのだ。

        で、正しい解決方法は良くわからない。とりあえず、CLk_180 で読み出しデータと ラッチデータをセレクタで 切り替えるようにしてみたが、正しい解決方法のような気がしない。まだレベルセンシティブ・ラッチの方がましかも知れない。

    • rtavr-0.9.1.tar.gz

      ... ここまで書いてコードも提示しないのは、どうかとも思うので 0.9.1 にした。.. これから 0.9.x の x をどんどん上げていく。

        0.9.1 では、rtavr_rom_4p.v は、セレクタ方式だが、次から レベルセンシティブ・ラッチに変更する。(セレクタ方式もとりあえずは残す) 。ちなみに 両方とも ROM_PSEUDO_DPにしたときの規模の増加は同じで、+ 26 スライス。

      0.9.1 での重要な修正はまだある。CBI/SBI の仕様を変更したものを default にしたこと。これで、互換コアと言いづらくなった。(互換モードは残しておくので、互換だと言い張るつもりだが)

Ver. 0.9.2

  • rtavr-0.9.2.tar.gz

    0.9.2 を置いた。

    変更点は、rtavr_rom_4p.v を レベルセンシティブ・ラッチに変更したぐらい。

    あとは、rtavr_defs.v の見直し。RTAVR_SMP を導入し、SMP の設定を楽にするのが目的。この変更ではじめて `undef を使った。

    ついでに、RTAVR_TARGET_MachXO2 を導入。いくつかの項目をチェックして自動で define する。Lattice MachXO2 に対応できているわけではなく、本体でも参照しない rtavr_defs.v の中だけの定義。ーまだまだ準備といったところ。

    それより、scripts/unifdef.awk の追加と doc/TOOLS-j.txt の追加がメイン。 unifdef.awk は、ifdef を展開して、ひとつの Verilog ファイルを作るのが目的。 どのコードが実際有効なのか、よくわからなくなった時に使うのが目的だが、一応ちゃんと Implement できるから、これを使って Implement しても OK 。複数のファイルを remove → add するより便利かも。

    あとソースの規模の見積もり。全体で 8118 行もあるが、50A 用の config なら、実際に使われるのは、3902 行というのが分かる。ほとんどの機能を ON したはずの large でも、4606 行。large-smp でようやく 5497 行。

    追記) scripts/unifdef.awk はバグっていたので修正: あと、debug 用出力も可能にしておいた。次で修正版にする。

そろそろ

    実際に動かすことをメインにしていこうと思う。
    ツールつくるの面倒なので、SMP とか 論理設計に走ってしまったが、もう十分堪能した。
    発注しなおした基板も到着したし、いいかげん作りだそう。

Ver. 0.9.3

  • rtavr-0.9.3.tar.gz

    0.9.3 を置いた。これで当面凍結。

    変更点は、rtavr_defs.v から rtavr_common.v を分離して、config/ に置いたことと、トップディレクトリに Makefile を置いて unifdef.awk でひとつにまとめた ソースを生成できるようにしたこと。

    バグ修正は、unifdef.awk と ROM_PSEUDO_DP のラッチ版。一応 tests での make で結果が一致する状態になっている。

    まだあった。MachXO2 版の準備。xo2_sample で JTAG と接続するようにしておいた。

(次はツール編)
posted by すz at 12:40| Comment(0) | TrackBack(0) | AVR_CORE

2011年05月20日

AVR互換コア(FIFOモジュール)

少し大きめの FPGA なら AVR互換コア(rtavr) を複数インプリメントできる。

その時に、rtavr 同士が通信できるモジュールとして FIFO モジュールを検討してみた。

    突然こういうことをやり出したのには、理由がある。それについては別記事にする。

    FIFO モジュールの説明は、

  • XAPP256

    に書いてあるのだが、このインターフェイスを参考に 一般の Verilog で記述してみた。

    実装方式として、Dual Port 分散RAM による方式(以下 DPORT) と シフトレジスタによる方式(以下 SHIFT) の 2 つを考えた。

    DPORT 方式は、Dual Port 分散RAM をサポートしている FPGA なら素直に記述すれば、推論してくれるはず。SHIFT 方式は、generate を使って二重ループという .. なんだか複雑な記述になってしまった。成功すれば、DPORT 方式より コンパクトになるが、失敗すると 全部 FF にされて 規模が増えてしまう。



これは、シミュレーション結果。


// Target Device: xc3s200a-4ft256
// Product Version: ISE 12.4
// Design Goal: Balanced
// SHIFT DPORT
// Number of Slice Flip Flops: 12 19
// Number of 4 input LUTs: 20 39
// Number used as Shift registers: 8 -
// Number used for Dual Port RAMs: - 16
// Number of occupied Slices: 14 26
// Number of bonded IOBs: 26 26
// Number of BUFGMUXs: 1 1
// Post-PAR Static Timing Report
// Maximum frequency(MHz): 114.077 125.691
//

SHIFT 方式だと、14 スライスで済んだ。

    最初に作ったのは、9 スライスだったのだが、COUNT 出力を付けたりして使いやすい仕様にした結果 + 5 スライス増えた。
    SHIFT 方式について肝心な部分だけ転載すると ..

    assign OUT = r_out;
    assign EMPTY = ( r_pos == 4'hf );
    assign FULL = ( r_pos == 4'he );
    wire [3:0] fifo_count = r_pos + 1;
    assign COUNT = fifo_count;
    always @(posedge CLK)
    begin
    if (CLR) r_pos <= 4'hf;
    else
    begin
    if (WR & RD)
    begin
    end
    else if (RD)
    begin
    if (~EMPTY) r_pos <= r_pos - 4'h1;
    end
    else if (WR)
    begin
    if (~FULL) r_pos <= r_pos + 4'h1;
    end
    end
    end
    always @(negedge CLK)
    begin
    if (~EMPTY) r_out <= fifo[r_pos];
    end

    generate
    genvar i,j;
    for (i=0; i<WIDTH; i = i+1)
    begin : data_in
    for (j=1; j<16; j = j+1)
    begin : data_in_bit
    always @(posedge CLK)
    begin
    if (WR) fifo [j][i] <= fifo[j-1][i];
    end
    end
    always @(posedge CLK)
    begin
    if (WR) fifo [0][i] <= IN[i];
    end
    end
    endgenerate

    なにはともあれ、これはうまくいった。


さて、これを使った、AVR のモジュールの仕様を考えてみる。

    まず、レジスタや 割り込みをあまり使いたくないという事情がある。空きが少なくて 厳しいのだ。それに複数付けたくなるかも知れない。最低限のリソースで済むようにしないと。

    次に、機能は送受信。相手も同じモジュールを使う。で、FIFO をどちら側に付けるか --これは送信側ということにする。

  • 割り込み

    USART などは、3 種類も割り込みを使っている。が、これを 1 つで済ます。

    割り込みの意味は、NOTIFY で CPU で IOR に書きこんで発生させる。割り込みを受けた側が スタータスを見てなにをすべきか判断する。

    割り込み許可のフラグも必要だから、レジスタを 2 bit 使う。

  • データレジスタ

    これは、USART の UDR のようにREAD すれば受信データが読めて、WRITE すれば送信データを 書きこむ ことにすれば良さそうだ。

  • ステータス

      受信データがある
      送信中(相手が全部を受け取っていない)
      送信が可能(空きがある)

    これぐらいは必要そうだ。これで、合計 5 bit 。

    あと、送信バッファがどれぐらい空いているのか も知りたい。送信中フラグ と 送信可能フラグをやめて 4 bit の情報にしよう。-- この変更で 合計 7 bit 。

  • コントロール

      送信データの破棄
      NOTIFY 割り込みの送出

    ぐらい? あと、NOTIFY 割り込みを相手が受け取ったかどうかの情報が欲しい。これはステータスに入れる。

さて、こんな仕様などより悩ましいのは、名前。-- 名前を付けないとファイルも作れない。何にしよう。

    機能的には、通信ポート。... CPI(Communication Port Interface) ということにして、モジュール名は rtavr_ior_cpi 。

    あとはなんでもかんでも CP を付ける。

    レジスタ名は、CPCSR (CP Control and Status Register) , CPDR (CP Data Register)

    フラグ名は沢山ある。

    Communication Port Interface(CPI):

    +-------+-------+-------+-------+-------+-------+-------+-------+
    0x09 | CPIR | CPIE | CPIS | CPRXS | CPTXS     |
    +-------+-------+-------+-------+-------+-------+-------+-------+

    +-------+-------+-------+-------+-------+-------+-------+-------+
    0x0a | CPDR |
    +-------+-------+-------+-------+-------+-------+-------+-------+

    CPIR : CP Interrupt Recv. Flag
    CPIE : CP Interrupt Enable Flag
    CPIS : CP Interrupt Send Flag
    CPRXS : CP Recv. Status (1: Recieved)
    CPTXS : CP Trans. Status (xxx0 : Empty , 1xxx : Full )

    こんな感じか。CPTXS は ちょっと細工。2bit で 詰まり具合が分かるようにする。

    Control の方は

    • CPTXS[0] に 1 を書くと バッファを破棄。
    • CPIS に 1 を書くと 割り込みを送出して、受け取られたら 0 に戻る。
    • CPIR / CPIE は、AVR のほかの装置と同じようにする。(詳細未定)


実装完了

    // Target Device: xc3s200a-4ft256 , top-level rtavr
    // SHRINKED DPORT SHIFT
    // Number of Slice Flip Flops: 388 407 403
    // Number of 4 input LUTs: 1089 1173 1162
    // Number of occupied Slices: 662 710 699
    // Number used as a route-thru: 34 35 35
    // Number used for Dual Port RAMs: 16 32 16
    // Number used as Shift registers: - - 8
    // Number of bonded IOBs: 12 36 36
    // IOB Flip Flops - - -
    // Number of BUFGMUXs 1 1 1
    // Number of RAMB16BWEs 3 3 3
    // Number of DCMs - - -
    // Number of BSCANs - - -
    // Post-PAR Static Timing Report
    // Maximum frequency(MHz): 46.198 43.459 42.531


    SHIFT で +37 スライス / DPORT で +48 スライス。

    output [7:0] CPI0_TXD : 送信データ
    output CPI0_TX : 送信データあり
    input CPI0_TX_ACK : 送信データあり ACK
    output CPI0_IXS : 割り込み送信
    input CPI0_IXS_ACK : 割り込み送信 ACK

    input [7:0] CPI0_RXD : 受信データ
    input CPI0_RX : 受信データあり
    output CPI0_RX_ACK : 受信データあり ACK
    input CPI0_IXR : 割り込み受信
    output CPI0_IXR_ACK : 割り込み受信 ACK

    信号線は、こんな風に定義した。クロックは共有しているのが前提。
    送信/受信は、対称になっていて、上と下をたすきがけ(クロス)で接続する。

    データそのもの以外の信号線は、全部ハンドシェークする。ACK は、posedge_CLK_180 で受け取り。

    規模は小さいのだが、rtavr に組み込むのは、ものすごく面倒だった。
    組み込み方法が 多数あるので、それに対応しないといけないし、接続用の信号も rtavr_fifo - rtavr_ior_cpi - rtavr_ior - rtavr と 3 段もパススルーしないといけない。

    それは、ともかく ... これでプロセッサ間通信のしくみが確立できた。次の記事『AVR互換コア(SMP化)』に続く。
posted by すz at 23:32| Comment(0) | TrackBack(0) | AVR_CORE

2011年05月16日

bitstreamベースのAVRコア(案)

MachXO2 Pico Dev Kit』の記事で触れたのだが、分散メモリがある FPGA では、シフトレジスタのコストが低い。Spartan-3 だと、1 FF と 16bit の シフトレジスタのコストが同じ。

で、思いついてしまったのだが、bitstreamベースでAVR互換コアを再設計したら 規模がだいぶ減るのだろうか?

bitstreamベースと勝手に名付けたが、要するに 1 CLK を 8 フェーズに分割して 1 bit づつ演算することを考えたのだ。

なあに、現状でも 4 フェーズだ。8 フェーズにしたところで、どうということはない。それに、ALU は、可変 bit 長に拡張したのを既に作ってたりする。(1bit はそのままでは無理だが 4,8,16.. に対応)。 IOR や メモリは従来どおりでないと困るから基本そのまま使うことにすれば、変更すべきものは意外と少ない。

まずは、本当に減る可能性があるものなのか、ちょっと検討。


    //Number of Slice Flip Flops 2 4 8 16 32
    //Number of 4 input LUTs 23 39 72 159 305
    //Number of occupied Slices 12 21 38 84 158
    // Number used as a route-thru - 1 1 1 1
    //Number of bonded IOBs 20 24 32 48 80
    //Number of BUFGMUXs 1 1 1 1 1

    これは、可変長版 rtavr_alu に レジスタ(1つ)をくっつけたものの Implement 結果。8bit で 38 スライスしか使っていない。これが、1 bit になったところで.. とは思うのだが、加減算は他にもある。インデックスレジスタや LDD 命令のオフセットなど。全部対応したら、多少は減るかも知れない。

    まぁもともと、興味からやってみたいだけだ。とりあえず rtavr のソースを分岐させて作ってみよう。

    ... こんなことばかりやっているから、ツールの整備とか実機の準備が進まないわけだが、こちらの方が楽しいからしょうがない。実機を使い出したらまた興味が変わるのだろうが、今のところ実機で動かすのは "おまけ" のように感じていたりする。

    ちなみに、rtavr はドキュメントを作りはじめている。それが終わったら ver 0.9 にする。実機テストが終わるまで ver 1.0 にはしないつもり。

設計で注意すべきこと

  • パイプライン制御は、ずいぶん変わる。1 CLK 立たないと結果が出ないから S3_writeback が必要。
  • メモリの REAd/WRITE は現在でも 前半 READ / 後半 WRITE に分かれている。前半 S3 (WRITE)/ 後半 S2(READ) として時分割?。
  • 現状のものを使ってパイプライン制御だけをテストすることは可能。ただし、しっかり設計しないと、作りなおすハメになりそう。
  • インデックスレジスタは、16 bit 分あるから 2bit づつの加算。
  • フォワーディングが無理ならパイプランストールの条件を増やす。とにかく規模削減を優先。

    デルタシグマメモ:『MachXO2 Pico Dev Kit』の記事より

  • ΔΣ変調の部屋

  • AN283: シグマ・デルタ ADC/DAC の原理 (pdf)』 - アナログ・デバイセス社の アプリケーション・ノート

  • 簡易シグマデルタ ADC』 - Lattice 社のリファレンス・デザイン



    login すれば、ソースコードと ドキュメントをダウンロードできるようだ。
     - 差動LVDS入力 + RC だけ
     - 54 LUT と書いてある。

  • XAPP211: PN Generators using the SRL Macro』 - Xikinx 社: シフトレジスタを使った擬似乱数


      PN ジェネレータ というのは、シフトレジスタ を作って作る。N bit の 結果を得るのに N クロック必要になるわけだが、この条件なら、SRL16 が使われ LUT が少なく済む。

      また、N bit の 結果を得るのに N クロックかけて良いとなると、(LSB first の)bit-stream どうしの演算をベースにできる。

      加算なら 1 bit の FULL-ADDR と ラッチ1 つで 済む。
      積和でも 加算 x N と 遅延用の ラッチ N^2 / 2 があれば良い。そして遅延専用なら やはり SRL16 が使える。

      なかなか設計が楽しそうだ。

GPR の検討


    +----------- < ------------+
    | _______________ |
    r16 +---->|7|6|5|4|3|2|1|0| ---+---->

    +----------- < ------------+
    | _______________ |
    r17 +---->|7|6|5|4|3|2|1|0| ---+---->
    :
    :
    +----------- < ------------+
    | _______________ |
    r30 +---->|7|6|5|4|3|2|1|0| ---+---->

    +----------- < ------------+
    | _______________ |
    r31 +---->|7|6|5|4|3|2|1|0| ---+---->


    考えてみたのだが、GPR 自体も シフトレジスタにするのが良さそうだ。

    16 本の シフトレジスタがあって ぐるぐるシフトしているイメージ。(実際は使わないレジスタは止めるが後で対応)

    入力にセレクタがあって、どれを入力するか 独立に決める構造。

      ちなみに、元の構造は、縦横が違う。各bit に LUT が割り当てられていて、レジスタ番号をアドレスとして選択。Dual Port なら 同じデータのものを 2 個使う。gpr_16 だと 8bit x 2 で 合計 16 LUT なのは同じ。

    全部パラレルに読み出せるし書ける。ただし、インデックスレジスタの INC/DEC には 2T かかる。
    読み出しだけなら 上位レジスタと下位レジスタをパラレルに読みだして 1T 。

    加減算 は、1 つの FULL-ADDR で 1T 。AND/OR などの論理演算も 1T だが SWAP 命令は 2T かかる。

    GPR 自体は 16 LUT で済むが、入力セレクタは load_data(8) + ALU + 元データ で 10。load_data 自体 ROM やら IOR など 巨大セレクタだし、ALU 自体も それぞれの演算結果からの選択。実際は 何to1 やら分からない。

    ALU も IMM(8) + GPR(16) から 1 つを選択して B 入力になる。A 入力は GPR(16) to 1 。

    save_data に格納するには、S2 開始から 1T。ここから 1/2 T で WRITE する。続く 1/2 T は 時分割で、READ 用につかう。


    | | S1 | S2 | S3 |
    |0|1|2|3|4|5|6|7|0|1|2|3|4|5|6|7|0|1|2|3|4|5|6|7|0|1|2|3|4|5|6|7|
    EA(PREDEC) ================================
    EA(POSTINC) ===============
    MEM READ ========
    Rd/Rr READ ================
    Rd Writeback ================
    MEM WRITE ========
    Rd Load from MEM =================

    まとめるとこんな関係か。Rd の書き込みが、演算の場合はその場でやり、メモリからの場合は 1 T 後というのが、嫌らしいかも。

    あと、GPR に対して入力するデータは、1T の期間中 HOLD されていないといけない。だから posedge_CLK_0 できっちりラッチしたデータ以外は使えない。またデータが揃うのは、1 T 後。要するに 1 T 毎にかちかち動くということだ。rtavr は、1/4 クロックとかややこしいタイミングがあったら、却ってわかりやすくなるかも知れない。

OP1の整理(ALU拡張)

    これを始める前にやっておくべきことがある。

    それは、OP1 に分類しているオペレーションの整理。

    // COM 1001:010d:dddd:0000 # # -X0XX1
    // NEG 1001:010d:dddd:0001 # # XXXXXX
    // SWAP 1001:010d:dddd:0010 # # ------
    // INC 1001:010d:dddd:0011 # # -XXXX-
    // DEC 1001:010d:dddd:1010 # OP1_SEL = 4 # -XXXX-
    // ASR 1001:010d:dddd:0101 # # -XXXXX
    // LSR 1001:010d:dddd:0110 # # -XX0XX
    // ROR 1001:010d:dddd:0111 # # -XXXXX

    OP1 に分類したものは、8 つあるが、どれもこれもクセがある。SWAP やシフト系は、bitstream では特別な処理が必要だからしかたがないのだが、他のものは ALU に吸収してしまいたい。

    で、考えたのだが、ALU に 次のオプションを追加すると良さそうだ。

    ES[1] : A に 定数 0 を使う。
    ES[0] : B に 定数 0xff を使う。

    こうすると .. フラグとかいろいろ細工がいるものの

    // NEG : 0 - B (A == B) : S = 2(SUB), ES[1] = 1
    // COM : A ^ 0xff : S = 5(EOR), ES[0] = 1
    // INC : A - 0xff : S = 2(SUB), ES[0] = 1
    // DEC : A + 0xff : S = 0(ADD), ES[0] = 1

    こんな風に演算を変換できるのだ。
    NEG の場合は、 B(Rr_in) に A(Rd_in) と同じ内容 を 出力するように S0 をいじる。

    あとはフラグだが、結構面倒。一応なんとかなりそうだ。

      この拡張をしたら、12 スライス減った。結構嬉しい。

    ところで、この拡張した ALU と シフトができるアキュームレータ を組み合わせたものを CoolRunnerII CPLD XA2C64A-VQ44 に Implement してみた。

    // ALU_WIDTH 8
    // Macrocells Used 47/64
    // Pterms Used 201/224
    // Registers Used 8/64
    // Pins Used 32/33
    // Function Block Inputs Used 117/160

    ピンが足りなくなるので、シフト用端子を In/Out 共用にした。(74281 ベース) 。これで、33pin 中 32pin 使う。実際に、AVR を構成した ALU にシフトを付けているので、AVR と比べて 機能的に足りないのは、SWAP だけ。ちゃんと CPLD に収まるし、意外にも良いかんじに仕上がったのかも知れない。

    TTL ALU 74281』の記事の続きをいずれ書こうと思う。

(続く)
posted by すz at 23:41| Comment(0) | TrackBack(0) | AVR_CORE

2011年05月15日

AVR互換コアのクロック

AVR互換コアのクロック関係のコードを大幅に変更した。それについてや、その他クロックに関することをまとめておこう。

rtavr のクロック(従来)

    _____ _____
    CLK2X |_____| |_____| |__
    ___________
    CLK |___________| |__

    | --------- T -------- |

    rtavr は基本 2 つのクロック入力を使う。( "Number of BUFGMUXs" が 2) 。この関係は 上記のようになっている。1 つの期間は、L,L から始まって H,H で終わる。

    `define USE_DMY_CLOCK とすることで、CLKを入力しなくても良くできるが、"Number of BUFGMUXs" が 2 なのは変わらない。

    2 つのクロック入力 CLK と CLK2X との間の スキューの関係が定義されていないので、どうも最大周波数は信用できないようだ。CLK を 自分で生成した場合でも怪しい。

    それで、CLK2X だけで駆動すようにまずしてみた。そうすると、"Number of BUFGMUXs" がちゃんと 1 になった。

    しかし、速いはずの 標準速版 GPR(gpw_16w) の最大周波数が 30数 MHz まで落ちてしまった。これまた、なにか誤認しているようで信用できない。

Spartan-3 の DCM

    そもそも、同期した 2 つのクロック入力は、DCM で生成するのが前提。.... だが、周波数を自由に生成できる CLKFX の機能を使うと、2 つの クロックを生成できないようだ。

    基本的に rtavr は、FPGA のなかで メインの回路の面倒をみるサブシステムと捉えている。BUFGMUX や DCM をあまり消費したくない。

    結局のところ、CLK2X だけ入力して動かすことになる。

ARTEMISボードとクロック

    ARTEMISボードは 2 つのクロックが使えるように配慮している。

    CLK1 -- メインクロック 水晶モジュール使用
    33 MHz を予定
    CLK2 -- UM232R/UM162 ボードからの供給
    UM232R: 6/12/24/48 MHz
    UM162 : 8 MHz

    スタンドアローンで動かすことは、いまのところ考えておらず、UM232R/UM162 ボードが必ずつく。なので、水晶モジュールは、たぶん使わない。

      水晶モジュールは、5mm x 7mm 3.3V のタイプを使用していて、入手性自体は悪くない。ただ、少々高い。秋月で格安だった、SG-645PCP (33MHz) は売り切れてしまった。自分自身はいくつか確保しているが、ひとには薦められない。

      .. そもそも 33.000 MHz だから他の(きりのよい)クロックを生成できないのだった。実は不便なのかも。

    ただ、UM232R/UM162 ボードが供給できる周波数は制限がある。特に UM162 は 8 MHz しか供給できない。そうなると、DCM の CLKFX を使いたくなる。

      Spartan-3A の最低周波数は 5 MHz 。MEGA32U2 を 16 MHz で動かしても 8 MHz だけが条件にあう。 UM232Rの場合は、MProg などでピンの機能を設定する。近い値ということで、12 MHz を使うつもり。

      で、N MHz を生成するなら N/8 , N/12 で良いはず。2N/4N も問題ないから、ほぼ好きな周波数を生成できる。

    だから、1 つのクロック入力しか使わない設定を通常使用することになる。

水晶を直接使用する方法

  • MAX7256による水晶発振の実験
  • MAX7256によるCR発振の実験
    という記事を見つけた。... FPGA 側は、インバータひとつで良いらしい。外付けは、並列の R1 (100K) と 直列の R2 (1K)。 (R1 でよく見るのは、1M Ωだったりするがどうなんだろう?)

    これでいけることを確認できたなら、ARTEMISボードの次のバージョンは、水晶を付けられるようにしてみたい。幸いにも使っていないピンが 1 つだけある。(現在のバージョンは GND に結線してしまっている。)

    ただし、現状配線がギリギリで かなり厳しい。使っていないピンをGND に結線したのも絶対どこにも配線できないと考えたため。

    それはともかく、当面は SDRAM を使わないので、拡張ポート(2) を使って確認してみようかと思う。

    あと、IBUFG を使うと 入力遅延を設定できる。

    IBUFG BUFG_CLKIN # (
    .IBUF_DELAY_VALUE(100)
    ) (.I(XIN) , .O(CLKIN));
    assign XOUT = ~CLKIN;

    XOUT を 遅延させて出力すると、XIN と XOUT を直結するだけで 適当なクロックが生成できたりするのだろうか?

    WDT のクロックなど、それなりの周波数で発振してくれさえすれば良い場合もあるから、何か役に立つかもしれない。

    追記: Spartan-2 での 実装例を見つけた。

    • 汎用発振器の実験

      動いてしまったとあるが、なにかポイントがあるはず。... ちょっと見てみる。



        回路: パラレルの R1 は 1M Ωで R2 はない。水晶は 14.318 MHz

        UCF :

        NET "XIN" LOC = "P93";
        NET "XOUT" LOC = "P95";

        XC2S50-TQ144 で、普通の I/O ピン

        コード:

        wire x_inv;
        IBUF x_in(.I(XIN),.O(x_inv));
        OBUF_F_2 x_out(.O(XOUT),.I(~x_inv));

        IBUF とか OBUF のプリミティブを使っているが、パラメータは設定していない。


      R1 は付けるが、本当に普通に作れば動くのかも知れない。... 念のため実験ではマネをしてみよう。

    • FPGA PARK: DCOとは?

      ロジックだけで発振させる実装例も発見!

      これが出来ると、SPI-WRITER を作ったときに 外部クロックの設定が不要になり デバイスに対して 1:1 で bitstream ファイルを対応させられる!

rtavr のクロック(再設計)

    _____ _____
    CLK2X |_____| |_____| |__
    ___________
    CLK |___________| |__

    ___________ ___
    CLK_0 | |____________|
    ___________
    CLK_90 _______| |________
    ___________
    CLK_180 |___________| |__
    _____ ___________
    CLK_270 |___________| |__


    | --------- T -------- |

    実をいうと、4相クロックを使うようにできないか検討中。

    実際の回路では、CLK2X のみ使うのは前述のとおりだが、最大クロックがどれぐらいなのか、また分からなくなってきている。DCMを使ってインプリメントしたら、正しい周波数を出してくれるかも知れない。

    さて、上記の単相 CLK2X と 4相クロックを同一のコードで使うにはどうしたら良いのだろう?

    always @(`posedge_CLK_180) // posedge CLK
    begin
    if (RESET == 0)
    begin
    r_doa2 <= 0;
    end
    else if (~__CLK_180)
    begin
    r_doa2 <= DOA2;
    end
    end

    こんなコードでうまくいかないかトライ中。

    posedge CLK_180 なら、CLK_180 は 0 に決まっているのだが、CLK2X を使うときにこの if 文が必要になるのだ。

    問題は、2 倍速で動かすケース。

    always @(`posedge_CLK_90_270) // posedge CLK2X
    begin

    CLK2X では問題ないが、4相ではどうするのだろう?

    ... いろいろやってみたが、posedge CLK2X 使うしかなさそうだ。で、はまった。

      .. 再検討中(後述)

    posedge_CLK_90_270 の場合は、前なのか後なのか判断するのに CLK (or CLK_0 or CLK_180) が使える。

    _____ _____
    CLK2X |_____| |_____| |__
    ___________
    CLK |___________| |__
       (= CLK_180)
    ___________ ___
    CLK_0 | |____________|

    PE PE
    NE NE

    | --------- T -------- |

    PE ポイントでは、切り替わらないから問題ないわけだ。だが、NE ポイントでは、両者が切り替わる。だから、CLK_90 か CLK_270 を使わないといけない。

    コードを機械的に変換したので、そういう問題があるのに気がつくのに時間がかかってしまった。

      CLK2X のときも同じ事情がある。自分で生成する場合は普通の信号と同じ扱いで良いから問題はないが、外部から CLK を入力する場合、動かない。だが、使うつもりがないので放置する。(いずれ、外部からの CLK 入力そのものを削除予定)

    あと、4相の場合のシミュレーションも必要だ。

    reg [3:0] r_clk_0 = 4'b0110;
    reg [3:0] r_clk_90 = 4'b0011;
    reg [3:0] r_clk_180 = 4'b1001;
    reg [3:0] r_clk_270 = 4'b1100;
    reg [3:0] r_clk2x = 4'b1010;

    wire CLK_0 = r_clk_0[3];
    wire CLK_90 = r_clk_90[3];
    wire CLK_180 = r_clk_180[3];
    wire CLK_270 = r_clk_270[3];
    wire CLK2X = r_clk2x[3];

    assign CLK_OUT = CLK_180;

    // always @(posedge CLK4X or negedge CLK4X) // simulator only
    always @(posedge CLK4X)
    begin
    r_clk_0 <= { r_clk_0[2:0] , r_clk_0[3] };
    r_clk_90 <= { r_clk_90[2:0] , r_clk_90[3] };
    r_clk_180 <= { r_clk_180[2:0] , r_clk_180[3] };
    r_clk_270 <= { r_clk_270[2:0] , r_clk_270[3] };
    r_clk2x <= { r_clk2x[2:0] , r_clk2x[3] };
    end

    実際にも確かめられるようにするなら、こういうのが良いかも。SRL16 になるから LUT を 5 つ消費するだけ。ただ 4 倍クロックが必要。

    そういえば、CLKFX を使う場合、FLKFB も CLK0 も不要なのだが、RST がいる。これにもはまった。この RST 次のいんちきコードで簡易化できないだろうか?
     - FPGA を コンフィグした直後だけ 16 CLK の リセット。RST を使うという条件を満たすだけのもの。SPARTAN-3A では ロックが外れると 自働的に RST がかかる(UG331 参照) ので、問題ないだろう。

    reg [15:0] r_rst_once= 16'hffff;

    wire RST = r_rst_once[15];
    always @(posedge CLKIN)
    begin
    r_rst_once <= { r_rst_once[14:0] , 1'b0 };
    end



変更した結果

    rtavr 自体はできた。が、くみ合わせは非常に多くなって確認が面倒。

    // target xc3s200a-4vq100
    // gpr_16w: SYS_DRIVEN_CLOCK4PH
    // DCM_4X 4X DMY_4X
    Number of Slice Flip Flops 408 394 398
    Number of 4 input LUTs 1090 1115 1119
    Number of occupied Slices 707 702 702
    Total Number of 4 input LUTs 1193 1190 1194
    Number used as a route-thru 103 75 75
    Number used for Dual Port RAMs 80 80 80
    Number used as Shift registers - - 4
    Number of bonded IOBs 15 14 12
    Number of BUFGMUXs 3 4 4
    Number of DCMs 1 - -
    Number of BSCANs 1 - -
    Number of RAMB16BWEs 3 3 3
    (Maximum frequency: MHz) 51.658 62.913 61.485

    // target xc3s200a-4vq100
    // gpr_16: SYS_DRIVEN_CLOCK4PH
    DCM 4X 4X DMY_4X
    Number of Slice Flip Flops 404 390 395
    Number of 4 input LUTs 1075 1116 1123
    Number of occupied Slices 682 693 699
    Total Number of 4 input LUTs 1149 1154 1160
    Number used as a route-thru 74 36 37
    Number used for Dual Port RAMs 16 16 16
    Number used as Shift registers - - 5
    Number of bonded IOBs 15 15 12
    Number of BUFGMUXs 3 5 4
    Number of DCMs 1 - -
    Number of BSCANs 1 - -
    Number of RAMB16BWEs 3 3 3
    (Maximum frequency: MHz) 52.499 59.326 60.599

    DCM_4x (top-level: artemis_isp )
    4X (top-level rtavr )
    DY_4X (top-level rtavr , USE_DMY_CLOCK)

    DCM_4X は、DCM を 実際の回路に Implement した場合。4X は、rtavr を top-level にした場合で周辺回路構成はいつもの 50A 用。DMY_4X はシミュレータ用で 4倍の CLK4X を入力して内部で SRL16 により CLK_XX などを生成。

    たとえば、gpr_16w の場合、最大周波数はどれもさほど変わらないはず。なのに、大きく違う。DCM_4Xだけが、複数クロックの関係を知っているのであればつじつまが合いそうだ。

      単純に最大遅延を求めて 1/1 CLK として 周波数を出せば高い値になる。だが、実際の遅延の意味は 3/4 CLK だったりするわけだ。最大遅延が 3/4 CLK なら 周波数を 3/4 しなければならない。

      逆に CLK2X の場合 (CLK2X の) 2 CLK の部分を 1 CLK として計算すると最大周波数が低くなりすぎる。たとえば 45 MHz と出たとして それが CLK2X の最大周波数だとすれば悲しくなる。.. といっても CLK が 45 MHz というのも間違いでもう少し低いような気がする。


rtavr-wk12

  • rtavr-wk12.tar.gz

    上で書いたことを反映したもの。


    // TUNED NORMAL USE_GPR_16
    // Number of Slice Flip Flops: 425 413 406
    // Number of 4 input LUTs: 1196 1122 1099
    // Number of occupied Slices: 740 704 678
    // Total Number of 4 input LUTs: 1284 1215 1159
    // Number used for Dual Port RAMs: 80 80 16
    // Number of bonded IOBs: 15 15 15
    // Number of BUFGMUXs 2 2 2
    // Number of DCMs 1 1 1
    // Number of BSCANs 1 1 1
    // Number of RAMB16BWEs 3 3 3
    // Post-PAR Static Timing Report^
    // Maximum frequency(MHz): 48.347 41.377 46.742

    規模は、DCM を使ってインプリメントして、こうなった。(いんちき RST 入り) -- なんと、gpr_16w まで 50A に 入りそうだ。

CLK 見直し(その2)

    4相クロックは、そもそも計測用。なのに 4相以外の CLK2X が入ってしまって気持ち悪い。
    ... というわけで、パラメータから CLK2X を全部外して、4相だけでなんとかすることにした。

    ___________ ___
    CLK_0 | |____________|
    ___________
    CLK_90 _______| |________
    ____
    AND _______| |________________

    ___________
    CLK_180 |___________| |__
    _____ ___________
    CLK_270 |___________| |__
    _____
    AND __________________| |________

    CLK_90_270 : (CLK_0 & CLK_90) | ( CLK_180 & CLK_270)
    CLK_0_180 : (CLK_270 & CLK_0) | ( CLK_90 & CLK_180)

    CLK_0_180 も 90°前にずらしたのを独立に作る。

    あと、クロックのオプションについても整理した。

    SYS_DRIVN_CLK4PH USE_DMY_CLK input
    o x 4相
    o o CLK4X (4倍クロック: シミュレーション用)
    x o/x CLK2X

    この2つ。実際の回路では CLK2X のみ使うことを想定。SYS_DRIVN_CLK4PH は、性能検証用で、USE_DMY_CLK はシミュレータで動作を確かめるためのもの。

    ちなみに、コアの種類は 3 種類を想定。(それ以外に SPARTAN3A/SPARTAN6 で 違うパラメータあり)

    NORMAL : gpr_16w を使用。
    TUNED : gpr_16w を使用し、規模が増えるチューニング・
             パラメータ ON
    USE_GPR_16 : gpr_16 を使用し規模を減らす。

    そして、周辺装置のコンフィグ。これは、いろいろ設定できるが、代表は 3 つ。

    50A : XC3S50A 向けに機能を絞ったもの。
    LARGE : フル機能
    ARTEMIS : ARTEMIS ボード (50A) 向け にさらに機能を絞ったもの。

    全部の組み合わせが Implement 可能なので、これだけ試すと 3 x 3 x 3 = 91 通りもある。Spartan-6 も確認しておきたいし、なかなか大変。

参考にしたもの
posted by すz at 12:25| Comment(3) | TrackBack(0) | AVR_CORE

2011年05月06日

AVR互換コア(UCF,コンフィグファイル)

AVR互換コア(ver 1)は、完成させたつもりだったのだが、実際に FPGA のコンフィグファイルを作るところでハマっている。

入力専用ピンには、PORT を割り当てられない -- という問題も出ている。

しばらく、AVR互換コアのカテゴリで続けようと思う。

UCFファイル

    前記事で書いたが、上記の問題は解決した。今の UCF ファイルはこうしている。


    #pin2ucf - Thu May 05 20:52:49 2011
    #The following constraints were newly added
    NET "CLK2X" LOC = "P44"; # GCLK1 (8MHz or 6/12/24/48 MHz )
    NET "CLK" LOC = "P41"; # GCLK15 (NC)
    NET "PW_SLEEP" LOC = "P31"; # VS0 (LED)
    NET "RESET" LOC = "P23"; # M1 (LED)
    NET "PORTC<0>" LOC = "P27"; # CSO_B (FLASH /S)
    NET "PORTC<1>" LOC = "P53"; # CCLK (FLASH C)
    NET "PORTC<2>" LOC = "P51"; # DIN (FLASH DO)
    NET "PORTC<3>" LOC = "P29"; # VS2 (LED)
    NET "PORTC<4>" LOC = "P46"; # MOSI (FLASH DIO)
    NET "PORTC<5>" LOC = "P7"; # IP_3 (RX)
    NET "PORTC<6>" LOC = "P34"; # (TX)
    NET "PORTC<7>" LOC = "P24"; # M2 (LED)

    M1/M2 , VS0/VS2 は、H で点灯する。M1/M2 の LED は、コントローラと共用で使わないのが基本。
    CLK,PW_SLEEP は信号を出すつもりがなかったのだが、rtavr が top-level なので出てしまっている。これもデバッグ用で使わないのが基本。

    PORT で操作できるのは、VS2 のみ。もちろんデバッグ用。

    CLK2X は コントローラから受け取る。MEGA32U2 だと 8 MHz 固定。AE-UM232R だと 6/12/24/48 MHz 。

    FLASH のアサインは固定のはずなので、残りで ボードに合わせる必要があるのは、TX/RX のみ。

    自作の artemis ボード向けだが、この程度ならボード専用という感じではない。artemis のカテゴリにしようと思ったが AVR互換コアの延長と位置づける。

    ところで、artemis ボード自体の UCF ファイルは、別に作った。

    こちらは、top-level の module とのセット。SPI FLASH ライタは、rtavr を直接 top-level にする。

ハマった理由?

    忘れていたが、私の環境は、完全なものではなかった。

    SSD の netpc を使っているので容量が厳しく、使わなさそうなファイルを削除していたのだった。PlanAhead すらまるごと削除。バックアップは取ってあるが、ピンポイントで足りないファイルを戻すのならともかく、全部を戻すのはとても嫌 -- というか無理。

    まともにインストールしたマシンで試してみて、うまくいくようなら完全に環境の問題。

Ver wk11



  • rtavr-wk11.tar.gz

    これをベースにする。

    変更点は、ROM のライトプロテクト機能の追加(IOR_HAVE_NVM)と gpr の小変更。

      gpr_16 の dual-port 分散 RAM が 32 と倍になっていた。

      < wire [7:0] v_DOBH = gpr[r_addrb];
      ---
      > wire [7:0] v_DOBH = DOB;
      142c142
      < assign JA[11:8] = gpr[r_addrb][3:0];
      ---
      > assign JA[11:8] = DOB[3:0];
      192c192
      < if (~CLK) s1_DOBL <= gpr[r_addrb];
      ---
      > if (~CLK) s1_DOBL <= DOB;

      wire は、C の define のようなものと思って使っていたが、ちょっと違うらしい。gpr[r_addrb] を複数の場所で参照すると 32 になったのだ。一旦 wire でまとめると 16 に戻る。規模も減った。

      gpr_16w は 80 で想定どおりだが、同じように変更。

    それ以外に TOPレベル用の UCF ファイルなどを追加。artemis ボード用だが リファレンスということで。

    rtavr.ucf は上記のものだが、変更予定。rtavr を TOPレベルにすると BSCAN を入れられない。

      別記事で検討したが、BSCAN_SPARTAN3 プリミティブを使うと JTAG に SPI スレーブなど接続できるらしい。


ISP 機能を設計してみた。

    FPGA の Implement は時間がかかるし面倒。プログラムだけを書き換えられた方が良さそう。ログを抜いて来ることでデバッグにも使える。

    // SIN 0aa1 aaaa XXXX XXXX <<< MOSI stream (MSB first)
    // XXXX XXXX load_data <<< MISO stream (MSB first)
    //
    // SOUT 1aa1 aaaa store_data
    // XXXX XXXX XXXX XXXX
    //
    // SSTPR 0110 100a PR[a]
    // XXXX XXXX XXXX XXXX
    //
    // SLD 0010 0I00 XXXX XXXX ( if I == 1 , PR = PR +1 )
    // XXXX XXXX load_data
    //
    // SST 0110 0I00 store_data ( if I == 1 , PR = PR +1 )
    // XXXX XXXX XXXX XXXX

    こういうコマンド仕様で、Tiny40 の TPI をベースにして決めた。

      基本は、アドレスポインタの設定をする SSTPR と、64K 空間から 1 バイト読み書きする SLD/SST 。これだけで十分なのだが、IOR 専用の SIN/SOUT も付けてみた。

      TPI はその他に、初期化を行うための SKEY , SLDCS/SSTCS 命令がある。それらを使ったシーケンスなしに、いきなり使えるようにした。ただし、JTAG 側で使えるようにするためのシーケンスは必要。

      SIN/SOUT はアドレス幅が小さいだけ。SLD/SST でも IOR , RAM にアクセスできる。

      .. といっても。IOR は RESET 中は 普通 値を変更できない。例外は、NVM で、ROM の ライトプロテクトの解除機能だけが使える。

      ちなみに、NVM は、NVMCMD に 0x1d を書くと、ライトプロテクトが解除されて、NVMCSR が 0x80(初期値)が 0 になる仕様にしている。プログラムでも使用できるので、ログを ROM に残すことが出来る。

    ISP へのアクセスは、SPI 。TPI はシリアルがベースで LSB First だが、普通に MSB first にした。
    コマンドは全部 2 バイト。1 バイト送ったら 次に1 バイトのデータを 送るか受けるかする。

    CS(正論理) が一旦 L になると、内部の状態(bit数カウンタ)がリセットされる。

    rtavr 側は、リセット中だけ バスを開放する。RESET と SPI の CS を連動させるつもり。

      書き込み終了まで RESET 状態が続いてくれないと困るのだが、JTAG の仕様をちゃんと理解していないので、この仕様で良いか少々不安。


    no SIN/SOUT
    Number of Slice Flip Flops 52 45
    Number of 4 input LUTs 63 47
    Number of occupied Slices 52 39
    Total Number of 4 input LUTs 78 62
    Number of bonded IOBs 39 39
    Number of BUFGMUXs 1 1

    規模は、こんな風になった。FF の数は、reg として定義している bit数の合計と一致する。

    SIN/SOUT が不要だと気がついて削ってみたら 13 スライス減った。

トップレベルに ISP と rtavr を Implement


    BSCAN_SPARTAN3A bscan (
    .RESET(BSCAN_RESET)
    , .SHIFT(BSCAN_SHIFT)
    , .CAPTURE(BSCAN_CAPTURE)
    , .UPDATE(BSCAN_UPDATE)

    , .TDI(TDI)
    , .TCK(TCK)
    , .TMS(TMS)

    // signals for USER1
    , .DRCK1(DRCK1)
    , .SEL1(SEL1)
    , .TDO1(TDO1)

    // signals for USER2
    , .DRCK2(DRCK2)
    , .SEL2(SEL2)
    , .TDO2(TDO2)
    );

    BSCAN_SPARTAN3Aの信号線は、こんな風になっている。それを ISP に次のように接続してみた。

    isp ISP (.CLK(CLK)
    , .MOSI(TDI)
    , .SCK(DRCK2)
    , .CS(SEL2)
    , .MISO(TDO2)

    , .ISP_STORE(ISP_STORE)
    , .ISP_LOAD(ISP_LOAD)
    , .ISP_ADDR(ISP_ADDR)
    , .ISP_STORE_DATA(ISP_STORE_DATA)
    , .ISP_LOAD_DATA(ISP_LOAD_DATA)
    );

    rtavr は、こう。ISP_XX は、相互に接続しているだけ。

    rtavr RTAVR (.RESET(RESET), .CLK2X(CLK2X)
    , .CLK(CLK)

    , .DDR(DDR[23:0])
    , .PORT(PORT[23:0])
    , .PIN(PIN[23:0])

    , .ISP_STORE(ISP_STORE)
    , .ISP_LOAD(ISP_LOAD)
    , .ISP_ADDR(ISP_ADDR[15:0])
    , .ISP_STORE_DATA(ISP_STORE_DATA[7:0])
    , .ISP_LOAD_DATA(ISP_LOAD_DATA[7:0])
    );

    さて、問題のひとつは、RESET 。JTAG でデバッグしている間は外部リセットを考えなくてよいだろう。だが、候補は 2 つある。BSCAN_RESET と SEL2 とどちらを使うのが良いのだろう?

    wire RESET = BSCAN_RESET;
    wire CLK2X = CLK2;

    とりあえずこんな風にした。クロックは、HOST インターフェイスからもらう。

    で、rtavr の接続。

    assign VS0 = DDR[19] ? PORT[19] : "H";
    assign PIN[19] = VS0;
    assign VS2 = DDR[23] ? PORT[23] : "H";
    assign PIN[23] = VS2;
    `ifdef IOR_HAVE_SPI
    assign PIN[`SS_BIT] = SEL1;
    assign PIN[`SCK_BIT] = DRCK1;
    assign PIN[`MOSI_BIT] = TDI;
    assign TDO1 = PORT[`MOSI_BIT];
    `endif
    `ifdef IOR_HAVE_USART
    assign PIN[`RX_BIT] = TXD;
    assign RXD = DDR[`TX_BIT] ? PORT[`TX_BIT] : "H";
    assign PIN[`TX_BIT] = RXD;
    `endif

    USER1 の方を SPI スレーブとして接続し、LED が付いている VS0/VS2 に PC3/PC7 を接続してみた。( SPI / USART が PORTC に割りつけられていて PC3/PC7 だけ空いている)

    次の問題は、規模。+40 スライス以上増えるのだから、今までの 50A の設定では無理。SPI(SLAVE) と USART のどちらかだけ構成するなら、余裕なのだが... 片方が動いたなら、もう片方のデバッグに使いたい。

    Implement してみたところ 722 スライスだった。もうすこしだから、デバイスの設定を変えることで入らないかやってみよう。


      追加:
      `define RTAVR_PORT_ESCALATION

      上記のような DDR/PIN/PORT に分離するインターフェイスの指定

      変更前:
      `define FIXED_MSTR 1 // 1: Master 0: Slave
      変更後:
      `define FIXED_MSTR 0 // 1: Master 0: Slave

      これは、SPI を Slave 専用にする設定。ここまでは最初から変更している。

      変更前:
      //`define FIXED_OCR0B 127 //
      `define FIXED_OCR0A 128 //
      変更後:
      `define FIXED_OCR0B 127 //
      `define FIXED_OCR0A 128 //

      これは、OCR0A/OCR0B ともに固定値にするもの。

      削除:
      `define PORT_HAVE_PIN_WRITE

      PIN への Write で PORT を反転する機能 -- 必要ない。

      追加:
      `define FIXED_SPR 0 // 0: 1/2 CLK , 1: 1/4 CLK 2: 1/8 CLK

      Slave 専用なので、クロック生成しない : 固定で良い。

      変更前:
      `define UBRR_BITS 8 // boud parameter (2 - 16)
      変更後:
      `define UBRR_BITS 7 // boud parameter (2 - 16)

      UBRR のビット数を減らす。1 減らすと内部カウンタも減る。

      -- ちょっと計算。CLK2X を 33MHz として CLK 16.5 MHz 。1/8 (U2X =1) なら、7bit あれば 最小 16.1 Kbps 。19.2 Kbps にするには、分周比は (UBRR - 1) だから、106 を設定する。8 MHz なら 25 で 12 MHz なら 38 。
      値を固定にできるオプションもあって、たとえば、

      `define FIXED_UBRR 106

      とすると 4 スライス稼げる。

    これで 702 になって 50A に入るようになった。

    だがもうすこし稼いでおきたい。

    ... といっても今まで散々チューニングしてきたのだ。なかなかすぐには出てこない。UBRR をあれこれしても浮くのはほんの僅か。

    しょうがないので、

    `define IOR_NO_COMPA // tempolary shrinking option
    `define IOR_NO_COMPB // tempolary shrinking option

    を作ることにした。これで、2 つ割り込みを減らす。-8 スライス。

  • rtavr-wk11b.tar.gz

    スナップショット。isp_sample ディレクトリを新設して、実際のボードのインプリメントを始めた。

    あと、RAM_WIN の機能を シミュレータに仕込んだ。これだけ、デバッグができていないので確認したかったのだが、目視は面倒。SREG のたぐいだと思えば CPU 専用シミュレータに仕込んでも変ではないだろう。

    ところで、最小規模がどれぐらいになるか気になったので、見てみることにした。

    base (- ldd/std) old-alu old-old-alu
    Number of Slice Flip Flops 228 223 229 233
    Number of 4 input LUTs 825 804 800 827
    Number of occupied Slices 475 465 464 482
    Total Number of 4 input LUTs 849 832 827 855
    Number used as a route-thru 24 28 27 28
    Number used for Dual Port RAMs 16 16 16 16
    Number of bonded IOBs 12 12 12 12
    IOB Flip Flops 1 1 1 1
    Number of BUFGMUXs 2 2 2 2
    Number of RAMB16BWEs 3 3 3 3
    Maximum frequency:(MHz) 54.437   54.460 51.584 50.005

    INT0 と PORTC のみのデバイス構成で、LDD 命令の拡張は入れる。port は bit反転機能を外している。これが基本。LDD 命令なしが、コンパイラのデフォルトだから、もうすこし減らせる。... といっても僅か 10 スライス。これで 20% 命令数が減るのだからお得だ。

    さて、ALU を module 化したわけだが、以前のに戻すとどうなるか? さらに -1 だが、遅くなった。-- だが、今の ALU はちょっといじってあるから、アンフェア。さらに 最初の ALU として独立していないのは、どうだったか。... というと 現状から見て +17 スライス。

    改めてみると、随分良くなっているのだな。ALU だけで 3 種類もあるのはどうかと思うので、module 化版ひとつだけにするか。

(続く)
関連記事:

著作権について

    ここで提示しているコードは正しく動作しないとはいえ、既に著作権は発生しています。
    著作権は、すzが保持しており放棄はしていません。

    教育目的および私用目的では、もともと著作権の範囲外なので自由につかえます。また、ライセンスとして、GPL を適用しています。GPL に従う範囲において 個別の許可なく使用することができます。許諾のための連絡も不要です。

    なお、GPL なので、生成したバイナリを作り直せる範囲のソース開示が必要になります。FPGA だと 通常 チップ全体になってしまいます。また、開示したソースを GPL の範囲で再利用されることを妨げることはできなくなります。このコードを利用する場合この点に留意してください。

    個別の許可を得れば、GPL 以外の条件での使用は可能です。が、作業中のものには許可は出さない予定です。作業が完了(もしくは中断)したとき、ライセンスは見直します。

    なお、すでに公開してしまったものの、ライセンスを取り消すことはできないと考えていますが、ライセンスの追加は可能です。また、新しく公開するものについては、ライセンスしないことすら可能で、どのような制限もありません。変更する可能性がありますので、この点にも留意してください。
posted by すz at 11:36| Comment(0) | TrackBack(0) | AVR_CORE

2011年04月30日

AVR互換コア(チューニング)

AVR互換コア(ver 1)もほぼ完成と思いながら、性能が分からないのが気になっていた。前記事の 『AVR互換コア(テスト3)』の後半で、ようやく性能の見方が分かったが、目標性能に達していないことが分かりチューニングもすることにした。

まずは、いままで作ってきたものが性能的にどうだったのか、振り返って整理してみよう。

  • rtavr-wk10c -- 35.689MHz

    性能を気にしだした最初の版。注目しているのは、GPR で ボトルネックの解消というよりは、無駄な and/or ダメなロジックを 見つけるのが主題。

    性能以外では、IOR のアドレスデコードの方法を全面的に変更したりもしている。

  • rtavr-wk10d -- 39.876MHz

    10d では、2倍速ではなく、等倍速の GPR を作った。等倍速の GPR は、2倍速との比較のためかねてから作りたいと思っていたもの。

    この作業をすることで、2倍速と共通のチューニングポイントや問題を発見。対処している。

    ところで、この最大周波数は、最初 CLK2X のことかと思ったのだが どうも CLK のようだ。CLK2X だったりするとひどく恥ずかしい値だ。だが、CLK である確信までは持てていない。

      デバイスを書き忘れていた。Spartan-3A (xc3s200a-4ft256) 。

    また、CLK は、CLK2X から生成しているから、遅延が発生している。このずれ -- スキューもたぶん折り込み済み。だから、同期がとれた CLK を外部から入力すると 多分最大周波数は上がる。(逆に下がったりするかも)

10E



  • rtavr-wk10e.tar.gz

    rtavr-wk10d まで、大きな問題があることが分かった。

    ldi r30, lo8(val_a)
    ldi r31, hi8(val_a)
    ld r20, Z

    こういったコードは典型的な使い方なのだが、上記の周波数では動かないのだ。へたしたら 1/2 以下でもあぶない。

    理由は、r31 が書き換わるのと ld での インデックス算出が同時に動くため。クロックが十分遅ければちゃんと反映されるが、ぎりぎりの周波数では、全然間に合わないはずで、ISE もそこまで面倒を見てくれていないと思える。

    上記のケースの競合を正確に判断し、パイプラインストールさせる対策にした。

    パイプラインストールの増加による性能低下も見積もった。

      N-queen による性能低下見積もり

      実行命令数 : 5974
      従来のクロック数 : 6319
      ストール追加のクロック数 : 6416

      1.5 % 程度の実行効率低下だが、従来版の最大周波数は比較にならないレベル。

    さて、性能の見方が分かったのは、このあたりから。

    いままで Slowest path を見ていたのだが、GPR/CLK の Default period を見なければいけない。

    それを見て ボトルネックを解析し、まず 2 つの修正を検討した。

    • A) FLAGS_TUNE
      最初のボトルネックは、FLAG 。特に Z Flagだった。

      FLAGS 計算の元になる Rd_out を GPR に書きこむデータ(GPR_DI) から取っていたのだが、セレクタが間に入る。セレクタを通す前のデータは、例えば ALU_OUT でここから FLAGS 計算 をするようにした。

      面倒なのが、Z Flag (Zero フラグ)で、GPR_DI から作った di_is_zero という信号をあちこちで使っていた。これをなんとかするために、広範囲で変更を行っている。

    • B) NEXTPC_TUNE

      PC の次の値を決めるセレクタが重くボトルネックになっていることが、分かった。

      選択につかう 信号を論理回路で合成していた。例えば

      wire v_skip = ( f_cpse | f_sbrx | f_sbix );

      wire s0_inv_jump = ~s1_invalid & ( f_ij | f_ic | f_br | f_rj | f_rc )
      | i1_int | i3_ret;

      こんな具合。これらは、命令デコードでそれ自体重い論理なのだが、さらに 複数で演算している。

      これらを S0 に持って行って 1 クロック前に 演算し結果をラッチするようにした。

    この 2 つは実に効果があった。等速版 GPR では、40 MHz 弱が 50 MHz 弱 までになった。

    実は、似たような所がもう一ヶ所あった。

    • C) NEXTPC_TUNE_2

      修正前
      wire f_sbix_sel = INST[9]; // 0 : SBIC/SBRC 1: SBIS/SBRS
      wire s2_skip = ( s2_cpse | s2_sbrx | s2_sbix ) & s2_sbix_sel & SBIX_BIT_IN
      | ( s2_sbrx | s2_sbix ) & ~s2_sbix_sel & ~SBIX_BIT_IN ;
      修正後
      wire f_sbix_sel = INST[9];
      wire s2_skip = s2_skip3 & ~( s2_sbix_sel ^ SBIX_BIT_IN ) ;

      まずは、s2_cpse | s2_sbrx | s2_sbix を s2_skip3 というひとつのラッチにする。そして、それが使えるように式を変形して単純化する。 (実は s2_cpse のとき s2_sbix_sel は常に 1 なので元々単純化可能だった)

      これは前2つと比べるとわずかだが、性能が向上する。

    次は、SBIX_BIT_IN の生成にトライ。

    • D) SBRX_TUNE
    • E) SBRI_TUNE
      SBRX_TUNE は、Rd のビット選択で、SBRI_TUNE IOR のビット選択。

      上記と同じように、ラッチ化したら良いだろうと 安易な考えでやってみた。

      SBRX_TUNE は、GPR 内部で生成し、値が確定するタイミングが早くなるので、確かに効果はあった。だがほんのわずか。

      SBRI_TUNE は逆効果。たぶん ラッチすること自体が新たなボトルネックになったのだろう。そうであれば、SBRX_TUNEも2倍速 GPR では逆効果になる可能性がある。


    | gpr_16w | gpr_16 |
    A1 A2 B C C2 C3 D E freq. slices freq. slices
    - - - - - - 39.145 717  39.426 689
    o o - - - - 44.267 706 42.098 675
    o o o - - - 48.249 733 41.873 701
    o o o o - - 49.529 732  44.751 696
    o o o o o - 49.965 732 41.135 700
    o o o o o o 47.090 735 43.185 704
    o o - o - - 43.182 673 (fixed)
    o - - o - - 44.373 703 42.772 670 (fixed)
    o o o o - - 48.183 731 
    o o @ o o - - 47.501 722 (PD_RADR)
    o o @ o - - 45.918 719 (ROM CLK2X)
    o o @ o o - - 52.225 737 (PF_RADR 2)
    (仕様変更後) PM_DRIVEN_CLK2X ,lached ADDRBH
    - - - - - - 37.161 710 38.008 677
    o - - o - - 46.062 708 43.791 678 (o)
    o o - o - - 42.405 682
    o - o o - - 52.149 714 (o) 43.126 689
    o - o o o - - 50.885 736
    o - o o o - - 53.022 736 46.707 696
    o o o o o - - 49.613 713
    o o o o o - - 52.225 737 49.266 717
    A1) FLAGS_TUNE
    A2) FLAGS_TUNE_2
    B) NEXTPC_TUNE
    C) NEXTPC_TUNE_2
    C2) NEXTPC_TUNE_3
    C3) NEXTPC_TUNE_4
    D) SBRX_TUNE
    E) SBRI_TUNE

    これは、それぞれの効果と規模をまとめた表。 gpr_16w (等速版)はすなおな傾向だが、gpr_16 (二倍速版)はなにか変。それはともかく、規模が減る A) と C) だけ入れるのが良さそうだ。

      バグがあったので、上記は正確ではない。最後の (fixed) のみ正確。あと FLAGS_TUNE から GPR 分を FLAGS_TUNE_2 に分離。

    D) / E) は、コードそのものを削除するかも。(特に E) 。

    gpr_16 については、ボトルネックが CLK2X 側にありそうだ。だが、だいたいこのあたりが目標性能なのだ、gpr_16 向けのチューニングはもうするつもりはない。

    gpr_16w については、リソースを多少使っても高速にしないと意味がない。今回は、これぐらいにしておくが、いずれ折を見て、再度トライしてみたい。

    (ここから 10E 変更)

    NEXTPC_TUNE のカテゴリ内で変更

    • PD_JUMPS_I/_R

      S0 でのラッチは 4 種類を 1 つにしていたのだが、r_pc を決める ロジックに (f_ij | f_ic) , (f_rj | f_rc) があったので、 4 種類を 2 つに分けることにした。

    • PD_RADR

      相対ジャンプのアドレスには、rjmp/rcall と条件分岐がある。これを S0 でひとつにまとめて ラッチすることにした。

    これで 規模が増えるが 次の PC を決めるセレクタのボトルネックはなくなるはず -- なんて思ったが規模は随分減って遅くなった。

    規模が減るのは期待していなかった。多分 加算を1 つにまとめられたのが大きいのだろう。性能が落ちたのは、S0 の仕事を増やしたから? S0 は、1/2 CLK から ROM を読むから 1/2 CLK しか猶予がない。

    1/4 CLK から ROM を読めば、早くなるのかも。-- これわずか 2 文字の追加で対応できた。

    が、さらに遅くなった。-- 安易に 1/4 CLK と 3/4 CLK の 2 回 READ にしたためかも知れない。

    ちゃんと調べると全然違う理由だった。

      r_pc + PD_RADR の加算を何故か セレクタで選択された後始めるようなのだ。そして セレクタの選択側に フラグが入っているので、加算開始が随分遅れる。

      wire [11:0] v_pc =
      i1_int ? { 7'b0000000, INT_VEC }
      : i3_ret ? v_ret_addr
      : ~s1_invalid & (PD_JUMPS_I) ? JA
      : ~s1_invalid & (PD_JUMPS_R | f_br) ? r_pc + PD_RADR
      : r_pc + 1;

      問題なのは、このコード。

      wire [11:0] br_pc = r_pc + PD_RADR;
      wire [11:0] v_pc =
      i1_int ? { 7'b0000000, INT_VEC }
      : i3_ret ? v_ret_addr
      : ~s1_invalid & (PD_JUMPS_I) ? JA
      : ~s1_invalid & (PD_JUMPS_R | f_br) ? br_pc
      : r_pc + 1;

      こう変えるだけで何故か 最初から加算を始めるようになった。だが、まだ遅い。

      いっそのこと S0 で br_pc を計算した結果をラッチした方が速くなりそうだ。

      ... と思ってやってみたら今度こそ速くなって 52.225 MHz と初めて 50MHz を超えた。

    ところで、ROM の読み込み開始を速くしたら、gpr_16w では動くのだが、gpr_16 は動かなくなった。

      どうもこういうことらしい。

      0/4 1/4 2/4 3/4 0/4 1/4 2/4 3/4 0/4
      __ _____ _____ _____ _____
      |_____| |_____| |_____| |_____| |_____|

      ADDRAL ========================
      ADDRAH ========================
      ADDRBL ========================
      ADDRBH ========================
      | |
      @ @

      ADDRAL ADDRAH
      ADDRBL ADDRBH

      2倍速である gpr_16 は、アドレスを 2 回読み込む。そのタイミングは、0/4 CLK と 2/4 CLK 。
      いきなり読み込むから、CLK の negedge でラッチできなくて、アドレスは ラッチ前の信号(ROM 出力 + 論理回路)にしている。
      ROM の読み込みを前倒しすると、2回目の 2/4 CLK には次のアドレスに変わってしまっている。

      ただ例外があって ADDRAH は negedge でラッチした信号。値が変わってマズイのは ADDRBH のみ。

      タイミングの仕様がいい加減なのが原因で gpr_16w の方も(なぜそうしたか)よくわからない実装になっている。

      この際、

      ADDRAH,ADDRBH は CLK negedge でラッチした信号
      (1/4 -- 3/4 CLK で使う)。
      ADDRAL,ADDRBL は ラッチしない信号
      (-1/4 -- 1/4 CLK で使う)

      と定義して、それに合うよう変更しようと思う。

    さて、r_pc がネック解消した。次は EA 。

    wire [7:0] v_ea = (INST[15:13] == 3'b101)
    ? { INST[12] ^ INST[8] , ~INST[8] , INST[10:9] , INST[3:0] }
    : f_bset_bclr ? 8'h3f
    : { 3'b000 , INST[7:3] } ;

    wire [15:0] v2_ea = (v_ea_push | i2_call) ? { 4'h0, SP_IN[11:0] }
    : (v_ea_pop | i2_ret) ? { 4'h0, sp_inc[11:0] }
    : v_ea_abs ? { 8'h00, v_ea }
    : EA_INDEX;

    現状は、こんな風に 2 段階になっている。f_bset_bclr が最初のネックで、v_ea_abs , v_ea_push , v_ea_push 。このあたりを S0 に持っていけば良さそうだ。

10F



  • rtavr-wk10f.tar.gz

    チューニングでいろいろ入れてみたが 結局 ...

    gpr_16 42.405 MHz / 682 slices FLAGS_TUNE/NEXTPC_TUNE_2
    gpr_16w 52.149 MHz / 714 slices FLAGS_TUNE/NEXTPC_TUNE/NEXTPC_TUNE_2


    これが結論。コストをかければもう少し性能は上がるがこれぐらいがバランスが良いだろう。


    Spartan-6 (xc6slx9-2ftg256) も見てみた。Spartan-6 では LARGE_MAPPER を外す。

    gpr_16 43.722 MHz / 319 slices FLAGS_TUNE/NEXTPC_TUNE_2
    gpr_16w 49.247 MHz / 343 slices FLAGS_TUNE/NEXTPC_TUNE_2

    -2 だと Spartan-3A (-4) と 同程度? -3 だと、gpr_16w が 同じ構成で 70.107MHz にまで上がる。

    それはともかく、有用な FLAGS_TUNE/NEXTPC_TUNE_2 はマクロを外して標準にして、失敗した FLAGS_TUNE_2/SBRX_TUNE/SBRI_TUNE あたりは、削除していこうと思う。

    これでチューニングも終わり。RAMWIN がおかしいのを直して Ver 1 にしよう。

10F の後


    gpr_16 52.138 MHz / 680 slices (FLAGS_TUNE/NEXTPC_TUNE_2 相当)
    gpr_16w 61.897 MHz / 712 slices (FLAGS_TUNE/NEXTPC_TUNE_2 相当)
    (xc3s200a-4ft256)

    現在こうなっている。変更点は以下の 2 つ。あと、FLAGS_TUNE/NEXTPC_TUNE_2 相当と書いたのは、ifdef を削ってしまったから。コードは同じ。

    • SP,SREG の WRITE タイミングが 2/4 CLK になっていた。3/4 CLK に修正(バグ修正)

      いろいろな組み合わせを調べていて、RAM を 標準速に戻したら動かなくなっていた。それで、SP,SREG のバグが見つかった。だが、修正しても RAM 標準速の問題は直っていない。

      で、結局 標準速の方は削除してしまった。

    • ROM への書き込み機能をオプション化した。

      ROM の書き換え機能は使わないだろうと思いオプション化。

    どうも、SP,SREG の WRITE タイミングが ボトルネックだったらしい。一気に性能が上がった。

    参考までに、xc6slx9-3 も調べた。( 買ったチップは -2 グレードで もっと遅い。)

    gpr_16 72.748 MHz / 333 slices (FLAGS_TUNE/NEXTPC_TUNE_2 相当)
    gpr_16w 88.386 MHz / 305 slices (FLAGS_TUNE/NEXTPC_TUNE_2 相当)
    (xc6slx9-3ftg256, undef IOR_LARGE_MAPPER)


    おかしなことに、gpr_16 と gpr_16w の規模が逆転している。

    ... どうも分散RAM の重みが違うのが理由らしい。

    性能が上がって喜んでいたのだが、ROM で ブロック RAM を 2 倍消費しているのに気がついた。
    SUPPORT_ROM_WRITE では、ちゃんと Dual-Port で使ってくれるのだが、性能が落ちて面白くない。

    そこで、ROM / RAM それぞれに専用のクロックを生成することにした。


    __ _____ _____ _____ _____
    CLK2X |_____| |_____| |_____| |_____| |___
    ___________ ___________
    CLK |___________| |___________| |__
    _______________________
    WE ______________________________| |_

    _____ _____
    CLK_A __________| |_____________________________| |___


    作りたいのは、こういうクロック。read では前半の CLK2X posedge で立ち上がり、write では後半の posedge で立ち上がる。ただし、BUFGMUXs は増やしたくない。


    wire CLK_A = CLK2X & ~(WE ^ CLK);

    こんなので良いのか? とも思うのだがやってみよう。Icarus Verilog で確認して Implement してみた。


    gpr_16w, undef SUPPORT_ROM_WRITE :
    63.694 MHz / 704 slices / BUFGMUXs 2 / RAMB16BWEs 5
    gpr_16w, SUPPORT_ROM_WRITE :
    62.313 MHz / 712 slices / BUFGMUXs 2 / RAMB16BWEs 3
    (xc3s200a-4ft256)

    combinatorial pin. This is not good design practice. Use the CE pin to control the loading of data into the flip-flop.

    とは言われるものの Implement は成功する。

    では、FF を使うのはどうだろう?

    reg CLK_A;
    always @(CLK2X)
    begin
    if (~CLK2X) CLK_A <= 0;
    else if (CLK2X & ~(WE ^ CLK)) CLK_A <= 1;
    end

    こんな風にしてみる。

    WARNING:Xst:737 - Found 1-bit latch for signal <CLK_A>. Latches may be generated from incomplete case or if statements. We do not recommend the use of latches in FPGA/CPLD designs, as they may lead to timing problems.
    WARNING:Route:455 - CLK Net:ROM/CLK_A may have excessive skew because

    これも Implement は出来る。

    gpr_16w, undef SUPPORT_ROM_WRITE :
    64.300 MHz / 705 slices / BUFGMUXs 2 / RAMB16BWEs 5
    gpr_16w, SUPPORT_ROM_WRITE :
    63.492 MHz / 714 slices / BUFGMUXs 2 / RAMB16BWEs 3
    (xc3s200a-4ft256)

    skew はあまり問題ではないと思うし、SUPPORT_ROM_WRITE のオプション化はあきらめて、これで行くか。


    gpr_16, SUPPORT_ROM_WRITE : LATCH
    49.697 MHz / 701 slices / BUFGMUXs 2 / RAMB16BWEs 3
    gpr_16, SUPPORT_ROM_WRITE : LOGIC
    49.495 MHz / 698 slices / BUFGMUXs 2 / RAMB16BWEs 3
    gpr_16, undef SUPPORT_ROM_WRITE : LOGIC
    49.841 MHz / 678 slices / BUFGMUXs 2 / RAMB16BWEs 5
    (xc3s200a-4ft256)

    2倍速 GPR の方を見てみたら具合が悪かった。選ぶなら まんなかだが、698 slice にもなっている。

    しょうがないので、従来ベースで再度作ってみた。

    gpr_16, SUPPORT_ROM_WRITE : 従来通り
    50.500 MHz / 687 slices / BUFGMUXs 2 / RAMB16BWEs 3
    gpr_16w, SUPPORT_ROM_WRITE : 従来通り
    57.531 MHz / 708 slices / BUFGMUXs 2 / RAMB16BWEs 3
    (xc3s200a-4ft256)

    やはり不思議だ。gpr_16w なら +4 または +6 スライスで ブロック RAM のクロック自体をいじれるのに、2倍速の gpr_16 は、+11 または +14 も必要なのだ。上限が近いからこの差は大きく、gpr_16 は、従来版しか採用できそうもない。


    ラッチ版 (gpr_16w 向け)
    `define RAM_CLK_LATCH
    `define ROM_CLK_LATCH
    ロジック版
    `define RAM_CLK_LOGIC
    `define ROM_CLK_LOGIC
    従来版 (gpr_16 向け)
    (なし)

    define はこのようにした。あと SUPPORT_ROM_WRITEは、常に採用して ifdef 削除。

    さて、どうするか決まったところで、Spartan-6 も見てみよう。

    Spartan-6 では、gpr_16w だけを使う。また、IOR_LARGE_MAPPER を undef する。
    デバイスクラスは -2 では面白く無いので、-3 にする。


    gpr_16w, 従来版:
    80.347 MHz / 319 slices / BUFGMUXs 2 / RAMB16BWEs 3
    gpr_16w, RAM_CLK_LATCH/ROM_CLK_LATCH:
    84.303 MHz / 314 slices / BUFGMUXs 2 / RAMB16BWEs 3
    gpr_16w, RAM_CLK_LATCH/ROM_CLK_LATCH NEXTPC_TUNE
    92.575 MHz / 356 slices / BUFGMUXs 2 / RAMB16BWEs 3
    gpr_16w, RAM_CLK_LATCH/ROM_CLK_LATCH NEXTPC_TUNE + EA_TUNE
    92.696 MHz / 363 slices / BUFGMUXs 2 / RAMB16BWEs 3
    (xc6slx9-3ftg256, undef IOR_LARGE_MAPPER)

    NEXTPC_TUNE_3/_4 は、S0 の方で新たなボトルネックになったようで、性能が落ちた。NEXTPC_TUNE + EA_TUNE の組み合わせは若干有効だが規模増加に見合う程ではない。


    gpr_16w, 従来版:
    57.531 MHz / 708 slices / BUFGMUXs 2 / RAMB16BWEs 3
    gpr_16w, RAM_CLK_LATCH/ROM_CLK_LATCH:
    63.492M MHz / 714 slices / BUFGMUXs 2 / RAMB16BWEs 3
    gpr_16w, RAM_CLK_LATCH/ROM_CLK_LATCH NEXTPC_TUNE
    63.187 MHz / 718 slices / BUFGMUXs 2 / RAMB16BWEs 3
    gpr_16w, RAM_CLK_LATCH/ROM_CLK_LATCH NEXTPC_TUNE + EA_TUNE
    58.248 MHz / 719 slices / BUFGMUXs 2 / RAMB16BWEs 3
    (xc3s200a-4ft256)

    これは、EA_TUNEを入れると逆にさがった。 NEXTPC_TUNEですら余計なようだ。

    2倍速 GPR の方も再確認

    gpr_16, 従来版
    50.312 MHz / 687 slices / BUFGMUXs 2 / RAMB16BWEs 3
    gpr_16, RAM_CLK_LATCH/ROM_CLK_LATCH
    49.697 MHz / 701 slices / BUFGMUXs 2 / RAMB16BWEs 3
    gpr_16, RAM_CLK_LATCH/ROM_CLK_LATCH NEXTPC_TUNE
    56.199 MHz / 716 slices / BUFGMUXs 2 / RAMB16BWEs 3
    (xc3s200a-4ft256)

    NEXTPC_TUNE_3/_4 は不要だから削除。EA_TUNE は保留。

    チューニングする気はなかったのだが、整理してバグとか取ったら、結果として性能が上がった。
    目標値以上になったし、規模的にも厳しくなってきた。あとは、設定の説明とか付けて Ver 1 にしようと思う。

  • rtavr-wk10g.tar.gz

    その前に最後のスナップショット。

最後のつもりだったのに、問題が出た。

    ようやく、実際の FPGA でコンフィグする準備を始めた。

    まずは、SPI FLASH ライタ用の UCF ファイルを作るところから始めたのだが...

    RX に割り当てているポートのピンが入力専用で、いまの構造では割り当てられないことが分かった。
    RTAVR_PORT_ESCALATION を define すれば、上位レイヤで自由に設定は可能だが...


    rtavr_defs.v :
    `define PORT_IGNORE_COND (0)
    `define PORT_IGNORE_OUTPUT_COND (i == 21)

    rtavr.v :
    for (i=16; i<24; i=i+1)
    begin : port_c_out
    if (!(`PORT_IGNORE_COND) && (`PORT_IGNORE_OUTPUT_COND))
    assign PIN[i] = PORTC[i-16];
    else if (!(`PORT_IGNORE_COND))
    begin
    assign PORTC[i-16] = DDR[i] ? PORT[i]
    : (~DDR[i] & PORT[i]) ? "H"
    : "Z";
    assign PIN[i] = DDR[i] ? PORT[i] : PORTC[i-16];
    end
    end

    こんな風にして対処した。XX_COND にしたのは、複数のピンに対処可能にするため。

    動かすまでいくつか変更が必要かも知れない。一応 SPI FLASH WRITER が 作れるものを Ver 1 と決めているから、実際に作って初めて Ver 1 にした方が良いかも知れない。

    ここから先は別記事にするが、UCF を作って PAR は出来たが、.bit は作れていない。

追記: ROM の WRITE PROTECT

    ROM を リードオンリーで作ると具合が悪いので、WRITE PROTECT することを考えることにした。

    具合が良いことに、tiny40 は NVM というレジスタを持っている。TPI では、NVMCMD に 0x1d を 書かないと プログラムメモリ(以下 PM)を書き換えられない。

    そして、NVMCSRの NVMBSY が 1 である間は PMを書き換えられないという意味になっている。

    あと、PMを書くときは LO8 → HI8 の順で必ず書く。

    この仕様にした。違うのは、tiny40 では TPI でしか PM に書けないが、rtavr は、自己プログラミングしかできない。

    NVMCMD を WRITE-ONLY にしたら規模は、わずか +1 slice の増加で済んだ。これを標準にすることにしよう。

(続く)

関連記事:

著作権について

    ここで提示しているコードは正しく動作しないとはいえ、既に著作権は発生しています。
    著作権は、すzが保持しており放棄はしていません。

    教育目的および私用目的では、もともと著作権の範囲外なので自由につかえます。また、ライセンスとして、GPL を適用しています。GPL に従う範囲において 個別の許可なく使用することができます。許諾のための連絡も不要です。

    なお、GPL なので、生成したバイナリを作り直せる範囲のソース開示が必要になります。FPGA だと 通常 チップ全体になってしまいます。また、開示したソースを GPL の範囲で再利用されることを妨げることはできなくなります。このコードを利用する場合この点に留意してください。

    個別の許可を得れば、GPL 以外の条件での使用は可能です。が、作業中のものには許可は出さない予定です。作業が完了(もしくは中断)したとき、ライセンスは見直します。

    なお、すでに公開してしまったものの、ライセンスを取り消すことはできないと考えていますが、ライセンスの追加は可能です。また、新しく公開するものについては、ライセンスしないことすら可能で、どのような制限もありません。変更する可能性がありますので、この点にも留意してください。
posted by すz at 22:46| Comment(0) | TrackBack(0) | AVR_CORE

2011年04月21日

AVR互換コア(テスト3)

AVR互換コアのテストも 新たな段階に入った。最終目的は gcc で動くコアなので、avr-gcc のバグがネックになっている。もはやコア自体のデバッグではなく avr-gcc のデバッグがメイン。

    注意:ここからはAVR_Toolchain のバイナリも更新しながらになるので、Version を合わせないとこれから書くことが再現しないかも知れない。また、ソースコードに添付する rom_data も コンパイラが変わるのでバージョン毎に変わる場合がある。

まずは、テストのための AVR_Toolchain について。

    4/9 版 : rtavr-wk09c.tar.gz まで使用
  • AVR_Toolchain-20110409-win.tar.gz -- Windows 版 AVR_Toolchain (25.9 MB )
  • AVR_Toolchain-20110409-lin.tar.gz -- Linux 版 AVR_Toolchain (21.2 MB)
  • AVR_Toolchain-20110409-src.tar.gz -- AVR_Toolchain パッチ集 (同じものはバイナリにも添付)

    4/21 版 (アップデート) : rtavr-wk10.tar.gz のみ使用
  • AVR_Toolchain-20110421-src.tar.gz -- AVR_Toolchain パッチ集 (同じものはバイナリにも添付)
  • AVR_Toolchain-update-20110421-win.tar.gz -- Windows 版 AVR_Toolchain アップデート(5.8 MB)
  • AVR_Toolchain-update-20110421-lin.tar.gz -- Linux 版 AVR_Toolchain アップデート(5.1 MB)

    4/22 版 (アップデート) :
  • AVR_Toolchain-20110422-src.tar.gz -- AVR_Toolchain パッチ集 (同じものはバイナリにも添付)
  • AVR_Toolchain-update-20110422-win.tar.gz -- Windows 版 AVR_Toolchain アップデート(8.2 MB)
  • AVR_Toolchain-update-20110422-lin.tar.gz -- Linux 版 AVR_Toolchain アップデート(7.5 MB)

    4/24 版 (アップデート) : (-Os では影響なし)
  • AVR_Toolchain-20110424-src.tar.gz -- AVR_Toolchain パッチ集 (同じものはバイナリにも添付)
  • AVR_Toolchain-update-20110424-win.tar.gz -- Windows 版 AVR_Toolchain アップデート(5.8 MB)
  • AVR_Toolchain-update-20110424-lin.tar.gz -- Linux 版 AVR_Toolchain アップデート(5.1 MB)

    注意1) アップデートは cd AVR_Toolchain してから上書きで展開する。4/22 版は、4/9 版に当てても OK 。
    注意2) 4/24 版は、4/22 版を当てた上でに上書き。
    注意3) Linux 版 は、cc1/cc1plus に実行権が付いていないので chmod +X が必要。

  • その他のツール (覚え書き)

    Verilog シミュレータとして、『Icarus Verilog for Windows』-- Windows 版 setup (GTKWave 同梱) を使っている。Icarus Verilog は 結果を比較するのに必要。GTKWave も使うが結果比較がメインになっていて 補助的になって来ている。

    AVR Studio 4.18SP3 のシミュレータ は attiny40 に対応しているので これも併用。Xiliinx の ISE も Implement のチェックや Verilog の文法チェックに併用している。

  • avr8l_trace (rtavr ソースに添付 )

    AVR のプログラムを実行させて、メモリやレジスタに変化があったときに、フラグやPC, SP と共に そのデータ(+アドレス or レジスタ番号)をログするもの。

    テストベンチの方でも全く同じものを出力するようにしていて、2つのログを取って diff をかけ検証するのがもともとの目的。

    これからは、gcc のデバッグに使う。レジスタ・メモリの変化をログし、SLEEP 命令での終了時に レジスタ・メモリダンプを取るので、ログみて追跡が可能。

    これは、simulavr-0.1.2.6 のコア decode.c だけを取ってきて、でっち上げたもの。ライセンスは GPL 。

ここまでの経緯



    avr8l_trace と AVR互換コア rtavr のログを比較することで、コアのデバッグをしてきたが、前記事で書いたように 用意したテストプログラムは 完全に一致するようになった。

    ただ、テストプログラム自体は 正しく動いていない。tb_shuffle にある shuffle プログラムは、PC でも動かせ 正しい結果を得ることができるようになっている。この結果と avr8l_trace の結果が一致しないのだ。

    AVR がちゃんとしているなら、その原因は avr-gcc 。これからは、avr8l_trace を動かして gcc のオブジェクトを検証していくことがメインになる。

バグ発見



    shuffle プログラムの初期化 shuffle_init の結果もまた違っていた。


    0120: 42 81 22 0c 44 97 a7 39 a0
    0130: 00 00 00 00 00 00 00

    shuffle_init の結果はここなのだが、正しい結果は、

    0120: 42 39 81 f6 22 61 0c 38 44
    0130: 22 97 ff a7 23 39 a0

    int16_t の配列に初期値を書き込んでいるだけなのだが、まるで int8_t の配列に書くような結果になっている。

    st 命令での X/Y/Z レジスタの 増加が +2 ではなく +1 になっている ... と目星をつけて avr-gcc のソースを見たら。まさにそういうところがあった。

    AVR8L への対応は、オリジナルのコード or AVR8Lのコード という 構造になっている。変更点で st Z 命令 ( Z レジスタであることは 逆アセンブルすることで分かる ) という条件で探すことでバグはすぐ見つかった。

    これを直すことで、shuffle() 関数が return して来るようになった。shuffle() 関数をなんども実行して データをシャッフルしていくのだが、ループ 1 回目の結果は OK になった。

      avr-gcc での AVR8L への対応は 基本的に オリジナルのコードと同じ動作をするようにしている。なので、おそらくバグは少ない。ひょっとしたらこれだけかも知れない。

reduced-ldd/std 命令対応



    AVR8L への対応コードは かなりひどい。特に ldd/std 命令の代替コードがでかくなる。最悪は 1 命令が 5 命令になるが、普通でも 2 倍以上になるところがかなりある。

      ひどいと言っても、他の対応はあり得ない。AVR8L は、あくまでおまけ。通常の AVR コア用のコードに影響がある変更はできないだろう。 誰が設計しても多分こう作るはず。別のアーキテクチャとして設計すれば、話は別できれいなコードも出せるのだろうが、それをするだけの価値があるのかどうか?

    rtavr では、0-31 バイトの範囲だけしかアクセスできない 縮小 ldd/std 命令(標準は 0-63)を追加したので、これに対応することにした。

    対応といっても本質はたいしたことがない。オリジナルのコード or AVR8Lのコード の if 文を変えるだけである。(if 文の条件は結構面倒)

    対応したのは、14 ヶ所。 2 倍以上になるところに限定した。

      これらは、-mreduced-lddstd オプションを付けないと有効にならない。オプションを付けないと AVR8L の範囲のコードになる。

    以上の変更をしたのが、最初の変更である 4/21 版 (AVR_Toolchain-20110421)。

    これでテストをやってみることにする。

ver wk10



今回は、これからスタート。4/21 版を使い tb_shuffle での結果。

*** inst-count 34407 ***
*** gpr dump ***
r16:01 r17:27 r18:e1 r19:00 r20:0f r21:69 r22:37 r23:01
r24:2c r25:0b r26:37 r27:01 r28:26 r29:01 r30:17 r31:01
*** ram dump ***
*
0060: 15 00 0e 00 0d 00 0c 00 1b 00 0a 00 0d 00 12 00
0070: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
*
0100: 00 00 00 01 e6 00 ea 62 25 d5 97 51 ac 3e 17 a2
0110: 26 a2 24 23 23 3f 69 08 00 17 01 80 4f 27 01 27
0120: 01 26 01 27 01 00 4b e2 6a 9b f9 f4 7b ca b2 57
0130: 72 66 ff 66 d3 2c 0b 80 4f 0f 3f 01 00 00 00 2b
****************


shuffle() を 16 回 call した結果。命令数は 34407 で今までとは桁が違う。これでも avr8l_trace と rtavr は結果が一致している。

0120: e2 6a 9b f9 f4 7b ca b2 57
0130: 72 66 ff 66 d3 2c 0b


    NORMAL ldd/std opt.
    138c2ff005f01fae0e780ef64dcc849b OK 2416 2074
    738a9ac281929bc25eb22b147db37878 OK 4909 4263
    54686801a3f07d4466e05ecce3c83b32 OK 7292 6338
    3bca3564801286aa41827b2ecd4476ac OK 9984 8710 NG
    d0847570a698d026c4705c4ca76b500e NG 12536 10938
    466a156a287445f2e712c2d2d1b960cc
    c7aa70cab6ca732c2854cf44f21b76e4
    e448582824c7b3ce0e36d20773db98ab
    52cd7f4a03e54d5cc398b943730ebc49
    7a6f419c754675e5e0fae738556c996b
    e5be69be8c04f2f4cb44811a6ab02e2b
    d53c90f4456ad096d1924ac4bf7c56c4
    702ab66b79efc91ef2704f6a9c9ef74c
    50e4625837e849a23e437df4b87c481c
    887aefbc940d6cc0de68e2caa357ee4e
    5b4451aeb62f0ac8fd0a494986751e02

16 回分の結果を先に載せるとこう。1 回目は合ったが、16 回目は違う。


1回目:
0120: 8c 13 f0 2f f0 05 ae 1f 78
0130: 0e f6 0e cc 4d 9b 84
2回目:
0120: 8a 73 c2 9a 92 81 c2 9b b2
0130: 5e 14 2b b3 7d 78 78
3回目:
0120: 68 54 01 68 f0 a3 44 7d e0
0130: 66 cc 5e c8 e3 32 3b
4回目:
0120: ca 3b 64 4d 12 80 aa 86 82
0130: 41 2e 7b 44 cd ac 76

3bca 3564 8012 86aa 4182 7b2e cd44 76ac
3bca 4d64 8012 86aa 4182 7b2e cd44 76ac
XXXX

5回目:
0120: 24 7f 70 01 98 a6 26 d0 70
0130: c4 4c 5c 6b a7 0e 50

d084 7570 a698 d026 c470 5c4c a76b 500e
7f24 0170 a698 d026 c470 5c4c a76b 500e
XXXX


細かく調べると 4回目まで合っている 5 回目の頭が違う。(マチガイ:後述)

    ldd/std 版もチェックしたが全く同じ結果。命令数は随分減る。1 割以上。rtavr は ldd/std 共に 1 クロックだから 実際それだけ速い。

    text data bss dec hex
    1286 0 16 1302 516 NORMAL
    1042 0 16 1058 422 ldd/std opt
    914 0 16 930 3a2 tiny2313

    サイズを比較してみると.. 2.5割も違う。tiny コアと比べて 1割増えるだけ。ROM は 8KB までだから サイズは重要。ldd/std に対応して良かった。


4回目 5 4 3 4 8 0 2 6
5回目 6 5 6 4 10 0 3 6
差異 1 1 3 0 2 0 1 0

shuffle 1 回は 8 回のループになっていて それぞれが、8 つのケースの switch 分のどこかを通る。

どこを通ったかの積算値はあって 正しければこういう配分。


0060: 05 00 04 00 03 00 04 00 08 00 00 00 02 00 06 00
0070: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

0060: 06 00 05 00 06 00 04 00 0a 00 00 00 03 00 06 00
0070: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

AVR では 0x60 から int16_t の配列に入っている。値は正しいから違うパスを通ったわけではない。
5 回目で通っていないところは、とりあえず関係ない。また 通ったところも過去に通っているから常に違うというわけではない。

コンパイラがこういう性質のコードをバグで出すとは考えにくく、libgcc のバグの方が濃厚。

    オリジナルは信用できる、AVR8L のコードは信用できないが、前のバグのように 違う場合は常に違うと思える。


0000048a <__mulhi3>:
000004b0 <__divmodhi4>:
000004d6 <__udivmodhi4>:

使っている関数は 3 つだけなのでこれらをチェックしてみよう。

    これら 3 つの関数は すべて libgcc.S -- アセンブラなのだった。バグが出そうなのは

  • レジスタの重複使用 または存在しないレジスタへのアクセス

      r0 - r15 は存在しない。これを使うアセンブラだと、avr-as でエラーになるのだった。

  • __tmp_reg__ , __zero_reg__ の扱い (レジスタの重複使用 or 0 に戻さない)

      __tmp_reg__ , __zero_reg__ は r18/r19 に define している。r0/r1 や Atmel のコード での定義 r16/r17 だと思ってコードを作ると重複してしまう。

  • 引数のレジスタ渡しの 数が減ったことに対応していない。(r18/r19 を使う等)

      独自仕様として r18/r19 を __tmp_reg__ , __zero_reg__ に割り付けたが、もとは 第四引数。割り付けなくとも 使っていたら 要注意なのだ。また、r18/r19 を 使ってしまっても __tmp_reg__ , __zero_reg__ を使わないなら問題ない。といっても __zero_reg__ を使ってしまったら return 前に clr しておく必要がある。

    まぁこういうところ。ちなみに、92-gcc-4.4.3-avrtiny10-fix2.patch で libgcc.S も幾つか直しているのだが、全部はみていない。

    改めてみてみると 未対応のコードが沢山ある。... 直すの面倒だが 意識的に使わないことが難しいから、直さざるを得ないだろう。

    __mulhisi3 : ToDO
    __umulhisi3 : ToDo
    __mulsi3: 引数 4 対応はしたが未確認
    __udivmodsi4: 引数 4 対応はしたが未確認

チェックしたが、上記 3 関数は問題なさそう。... となると gcc か。ちょっとばかり気が重い。

5 回目の最初だけが違うというのはひとつのヒント。

c = (*(state + 1) >> 4) & 0x7;

この c で switch していて シフトする元の値は 4 回目の 2 個目 4d64 だから c は 6 。

case 6:
out[i] = (*state / (16-i)) * b;
nop();
break;

... やっぱり 割り算/掛け算が怪しい。これだけ取り出して確認するか。

バグ発見2



    よおくみたら、4 回目の 2 loop 目 で既に違っていた。この値は 次の 1 loop 目に影響する。-- 調べるところが違っていたわけだ。

    4 回目 loop 2回目の 結果は次のようになった。

    lc 003 i 1 *state = 6801 b = a3 c = 7 result 5d65 (3564)
    84 d0 70 75 98 a6 26 d0 70 c4 4c 5c 6b a7 0e 50
    6 5 6 4 10 0 3 6

    case 7:
    out[i] = (*state) * 3;
    nop();
    break;

    動いたコードは、もっと単純で x3 しているだけ。

    これと同じ入力で __mulhi3 を実行してみると結果が違うことが分かった。

    結果:
    __mulhi3 : 0x0003 (正: 0x3803)
    bufi[1] : 0x2565 (正: 0x5d65)

    どうも、上位バイトが おかしい。そういう目で libgcc.S を見てみたら...あった!

    #if defined (__AVR_TINY__)
    subi r_arg1L, lo8(0)
    sbci r_arg1L, hi8(0)

    あと一ヶ所 同様のバグがあり、直したところ ... shuffle() , N-queen 共に動くようになった。

    N-queen :
    命令数
    DEPATH = 4 2006
    DEPATH = 5 5974
    DEPATH = 6 19236
    DEPATH = 7 66824
    DEPATH = 8 252463

    DEPATH = 8 で、完全に一致。

    shuffle も ループ 100 回を試す。

                      命令数
    91a970f2f7442a4a4dca2e464b62dd4c 225580
    71 65 100 88 207 66 104 99

    0120: a9 91 f2 70 44 f7 4a 2a ca
    0130: 4d 46 2e 62 4b 4c dd

    91a9 70f2 f744 2a4a 4dca 2e46 4b62 dd4c
    91a9 70f2 f744 2a4a 4dca 2e46 4b62 dd4c


    これも、完全に一致。

    さて、実を言うと このテストは、-mreduced-lddstd オプションで ldd/std 命令を使っている。( 4/21 版より ldd/std を使うパターンを増やしている。)

    で、オプションなしを試したところ.... 全然動かない。

バグ発見3



    幸いなことに、『動くコード』と『動かないコード』がある。『動くコード』を『動かないコード』に近づければ、どこかで動かなくなるわけだから、機械的にバグを見つけられる。

    やってみたところ。

      < subi r28,lo8(-(1))
      < sbci r29,hi8(-(1))
      < ld r24,Y+
      < ld r25,Y
      < subi r28,lo8((1)+1)
      < subi r29,hi8((1)+1)
      ---
      > ldd r24,Y+1
      > ldd r25,Y+2

    ここにバグがあるということが分かった。

    -- subi/sbci でないといけないのに subi/subi になっている。バグとわかって眺めてもすぐには気がつかなかった。

    で、hi8() に対して subi しているところを探すと実に沢山あった。(当然ながらすべて AVR8L 対応のコード。ベースにそんなコードはない。)

    以上のバグをまとめて、update を作る。今回は、オプションの部分ではないので、avr-libc まで作り直さないといけない。

    パッチ集は用意できた。
    今回の追加パッチ:

    81-gcc-4.4.3-avrtiny10-bug2.patch
    82-gcc-4.4.3-avrtiny10-bug3.patch
    95-gcc-4.4.3-avrtiny10-lddstd2.patch

    あと 4/21 版更新。

    92-gcc-4.4.3-avrtiny10-fix2.patch


    これで大分使い物になったはず。... といっても libgcc や avr-libc の対応は大変なので ちゃんとするには 時間がかかる。

    全部自前のコードで 32bit 乗算を使わなければ、たぶん大丈夫というレベル。

    これからの計画

    gcc は、これで終わりにしたいが... __mulhisi3 / __umulhisi3 など libgcc.S ぐらいはなんとかしておこうと思う。avr-libc は量が多すぎなので、ボチボチやる。

    ようやく avr-gcc も使えるようになったから、AVR互換コア のテストも、もう少しやる。

    その後は、FPGA ボードの組み立てとかツール作りで、しばらくお休み。

    FPGA が使えるようになったら、AVR互換コアを使って FLASH を読み書きするファームウェアを作る。それの完了で 晴れて AVR互換コア完成。

    まだまだ道のりは遠い。

10A



  • rtavr-wk10a.tar.gz

    Windows 版バイナリも用意できたので確認してみる。

    shuffle

    inst-count
    normal -mreduced-lddstd
    5 loop 13164 11206
    100 loop 264330 225580

    91a9 70f2 f744 2a4a 4dca 2e46 4b62 dd4c
    -0120: a9 91 f2 70 44 f7 4a 2a ca
    -0130: 4d 46 2e 62 4b 4c dd
    -0120: a9 91 f2 70 44 f7 4a 2a ca
    -0130: 4d 46 2e 62 4b 4c dd

    N-queen :

    normal -mreduced-lddstd
    DEPATH = 5 10241 5974
    DEPATH = 6 32872 19236
    DEPATH = 7 114609 66824
    DEPATH = 8 434083 252463

    これからは、これらのテストがルーチンワーク化してくる。AVR互換コアを ちょっと変更したら確認することになるからだ。

    で、ループの上限とか テストによって変えたかったりするし、あちこちのファイルを変更するのは面倒でしかたがない。

    それで tb_xx を tests の下に移動しパラメータを統一的に扱えるようにした。

    パラメータは tests/build.inc に集約。tests で make することでテストする。

    make の引数は、個別のテスト用に shuffle quteen tbi 。
    全体では all clean 以外に rom rom_clean check など。

      ちゃんとサポートしたのは、tb_shuffle, tb_queen, tbi_001 。あとは移動しただけ。

    make で新たに使ったコマンドは diff , head 。結果を比較し、その先頭 (最大 20行)だけ表示するのに使う。

    ところで、notyet なものがいくつかある。

    • rtavr_alu.v

      ALU をモジュールとして分離 するつもりはあまりないのだが、規模を見るためにちゃんと作っておこうか悩み中。(分離してしまうと フラグの計算に GPR_DI が使えず規模が増える見込み)

    • rtavr_intcnt.v

      割り込みコントローラだけを分離したものを 既にテストベンチで使っている。こちらは分離しても良いかなと思っていたりする。

    • rtavr_gpr16w.v

      失敗したまま放置中。かみきさんの記事によると トリプルポートの分散RAM というのは ISE で合成してくれるらしい。ただし、dual-port の倍のリソースを使うらしい。多分 書き込み専用 1 , 読み出し専用 2 になるから インデックスレジスタのことを考慮すると 16bit になる。今の 4 倍 64 個の LUT を使うのだろう。

      それでも、インデックスレジスタと Rd の同時書き込みはできないからパイプライン制御は今と同じ。

      ちょっと面白くないので、実際にクロックのボトルネックが分かって改善することになるまで保留しておく。失敗したコードはもう削除したい。

    • rtavr_ior_timer16.v

      16bit のタイマー1はやっぱり欲しい。用意はしているのだが、IOR 空間への割付けと 16bit アクセスの方法のところで 悩み中の後、放置。

    • rtavr_ior_twi.v

      いずれ作るつもり。これはテンプレートで中身はまだない。

    どうも Version 1 になった後も notyet は残りそう。

libgcc.S : __mulhisi3 / __umulhisi3 と __mulsi3

    __mulhisi3 / __umulhisi3 (libgcc.S) を見てみた。

    乗算のないコアでは、両方とも 最後に __mulsi3 に rjmp している。

    __mulsi3 は、32bit の乗算。引数が 4 バイト x 2 なので 普通のコアでは r19/r18 も引数になっている。
    AVR8L では、2 つめの引数全部 r21/r20/r19/r18 がスタックになる。

    これの対処は、処理の前に r19/r18 に スタックから LD してきて最後に r19 を CLR 。

    in r30,__SP_L__
    in r31,__SP_H__
    subi r30, lo8(-3);
    sbci r31, hi8(-3);
    ld r_arg2L, Z+
    ld r_arg2H, Z+
    ld r_arg2HL, Z+
    ld r_arg2HH, Z

    :
    #if defined (__AVR_TINY__)
    clr __zero_reg__ ; clear zero_reg
    #endif
    ret

    4/22 版ではこうなっている。

    まずは、試してみよう。


      int32_t t_mul32(int32_t a, int32_t b) {
      return (a - 1) * ( b - 1 );
      }

      main側
      buf32[0] = t_mul32(0x1234, 0x5678);


    結果は 0x062597b5 になるはずだ。

    実行した結果は、

    0060: 4e eb 65 5d 01 00 00 1e b5 97 25 06 00 00 00 00
    == == == ==

    確かに合っている。

    次に __umulhisi3 rjmp までのコードはこれだけ。

    // mov_l r22, r20
    // mov_h r23, r21
    // clr r24
    // clr r25
    // clr r20
    // clr r21

    上位 2 バイトを それぞれ 0 にする。

    それはすぐ分かるが問題は引数。普通のコアでは、r21/r20 と r19/r18 に uint16_t で入っているようだ。
    たぶん r19/r18 だけがスタックに入っている。

    対処は、最初に r19/r18 をロードしてきて、rjmp 前に r21/r20/r19/r18 をストアする。
    r19/r18 の値は変更しないから、skip 。r21/r20 の CLR は必要なく r24 をストア。

    #if defined (__AVR_TINY__)
    in r30,__SP_L__
    in r31,__SP_H__
    subi r30, lo8(-5);
    sbci r31, hi8(-5);
    st Z+, r24
    st Z , r24
    #else
    clr r20
    clr r21
    #endif

    これで良いはず。

    __mulhisi3 は u が付かないから signed で マイナスの 対処がある。

    // mov_l r18, r24
    // mov_h r19, r25
    // clr r24
    // sbrc r23, 7
    // dec r24
    // mov r25, r24
    // clr r20
    // sbrc r19, 7
    // dec r20
    // mov r21, r20

    これはもう面倒なのですなおに、ロードしてストアで対処する。

    #if defined (__AVR_TINY__)
    in r30,__SP_L__
    in r31,__SP_H__
    subi r30, lo8(-3);
    sbci r31, hi8(-3);
    ld r18, Z+
    ld r19, Z
    subi r30, lo8(1);
    sbci r31, hi8(1);
    #endif
    ()
    #if defined (__AVR_TINY__)
    st Z+, r18
    st Z+, r19
    st Z+, r20
    st Z, r21
    clr __tmp_reg__
    #endif

    ロードしたとき Z は元に戻す。

    ただ元のコードを見ると ...いきなり r19/r18 を潰している。
    乗算ありの場合は、r19 を使っているし。たぶんもともとバグっている。

      この2つの関数は、他と記法が違うし取ってつけたような感じ。int16_t 2つなら 第一,第二引数用の r25/r24/r23/r22 を使うのが普通なのに そうでないみたいだし。よく分からない。

      普通に uint16_t, int16_t の乗算をすると __mulhi3 が使われる。結果を 32bit にしたら __mulsi3 。... 固定小数点とか特殊用途向け?

        mov_l r22, r20
        mov_h r23, r21

        最初の mov は、これでないとつじつまが合わない。とりあえず直しておく。


      追記: mulhisi3 がどのように使われるか分かった。

      (define_expand "<any_extend:u>mulhisi3"
      [(set (reg:HI 18) (match_operand:SI 1 "register_operand" ""))
      (set (reg:HI 20) (match_operand:SI 2 "register_operand" ""))
      (set (reg:SI 22)
      (mult:SI (any_extend:SI (reg:HI 18))
      (any_extend:SI (reg:HI 20))))
      (set (match_operand:SI 0 "register_operand" "") (reg:SI 22))]
      "!optimize_size"
      "")

      これは、avr.md での定義。重要なのは、!optimize_size -- 要するに -Os では決して使われない。そして -Os しか使うつもりはないから ... どうでもよいということ。

      あと、(reg:HI 18), (reg:HI 20) (reg:SI 22) というのが見える。引数が変則なのはこのせい。それは良いが (reg:HI 18) を引数に使えない AVR8L ではどうなるのだろう? 使うつもりはないが気になる。


      t_mul1.c:41: error: unrecognizable insn:
      (insn 11 10 12 3 t_mul1.c:39 (set (reg:HI 18 r18)
      (reg:SI 47)) -1 (nil))
      t_mul1.c:41: internal compiler error: in extract_insn, at recog.c:2048

      こうなった。なるほど無理なものは無理。なら、"!optimize_size" を "!optimize_size && !AVR_TINY" とすべき。で、__umulhisi3 / __mulhisi3 自体は不要。

10B



  • rtavr-wk10b.tar.gz

    テスト環境の整理と小変更。

    • PORT_HAVE_PIN_WRITE (50A デフォルト化)

      PIN に WRITE すると PORT が反転する機能を デフォルトにした。(+ 4 slices)

    • PW_SLEEP

      SLEEP 中 であることを示す PW_SLEEP を常に出力。(+ 0 slices)

    • PRC_WATCH_WDR

      WDR も PRC_WATCH_WDR を define することで PW_WDR を 出力 (+4 slices)

    PW_WDR を LED につなげれば、目視で確認できるかも知れないし PRC_WATCH から分離して独立させた。

    あと、PW_WDR のバグ(typo)修正。他にも 1 つ typo 修正。

    さて、遅延の調べ方が少し分かったので調べてみたら、意外なことに IOR からの読み込みがボトルネックだった。

    GPR を外してみて調べると EA が出力されてから GPR_DI が確定するまで 22ns もかかっている。
    IOR 側だけで調べると、レジスタ値が変化してから DO が確定するまでの時間だった。

    IOR は、POS EDGE で変化するが、基本同じクロックでは読み込まない。... と思っていたが違った。タイマーをはじめいろいろある。セレクタだけの 非同期なのがマズイようだ。

    RAM のように 2X クロックで駆動して、1/4 でラッチするとかしないとダメなようだ。いっそのこと DCM 前提で 4 相化したほうが良いような気がしてきた。

    ... と書いたが、レベルセンシティブなラッチを試してみることにした。

    always @(CLK , DO_async)
    begin
    if (~CLK) r_do = DO_async;
    end

    こんな記述。CLK = L の間は従来どおりだが H になった時点で固定する。

      ラッチを使うと Warning が出て不安なので、『XST ユーザガイド』を見てみた。これは、case 文や if 文の 記述ミスで バグが出やすいので意図的に出しているとのこと。機能的に問題があるわけではなさそう。

      always @(CLK or DO_async)
      begin
      if (~CLK) r_do = DO_async;
      end

      あと、『XST ユーザガイド』の例では always の指定で信号を or でつないでいる。, を or に直そうと思う。それか 1/4 CLK ( CLK2X posedge で CLK == 0 )で 取り込むか。


    DOラッチ ラッチなし
    Total Number Slice Registers 399 391
    Number used as Flip Flops 391 391
    Number used as Latches 8 -
    Number of 4 input LUTs 1,101 1,113
    Number of occupied Slices 684 687
    Total Number of 4 input LUTs 1,138 1,149
    Number used as a route-thru 37 36
    Number used for Dual Port RAMs 16
    Number of bonded IOBs 12
    Number of BUFGMUXs 2
    Number of RAMB16BWEs 3

    規模が増えるのを心配していたのだが、なんと減った! 

    たぶん セレクタ(LUT)ばかりで FF を使っていないところに FF を入れた結果になったのだろう。

      現状、IOR のセレクタは、モジュール単位と上位レイヤの 2 段階のセレクタになっている。階層をなくした方が 効率が良いような気がする。ただ、レジスタの数は数十個あって面倒。

    この修正で、IOR 自体のボトルネックは、PORT 出力になった。12ns ぐらいだから気にしなくて良くなった。

    では、再度 GPR を外した調査。

    ボトルネックは GPR_DI で同じだが、演算系に変わった s_smd_alu_sub_all, s2_cmd_load, s2_cmd_op1 がトップ 3 でどれも 17.5 ns ぐらい。3/4 CLK で 確定している必要があるから 42.8 MHz ぐらい。

    では、GPR はどうなのだろう? 調べてみると INDEX が 16ns 弱。

    INDEX は、3/4 CLK から GPR を読み込んで 0 or -1 したのを セレクタにかけて 4/4 CLK で EA としてラッチ。 これが 16ns だと 15.6 MHz CLK でしか動かないことになる。セレクタ以降が入っていない値だからもっと遅くなるかも知れない。

    これは.. よくしらべないと。

      少なくとも、PAD からの入出力には余分に時間がかかる。距離も遠くなるし。16ns からいくらか割り引いても良いようだ。

    実際のところよく分かっていない。.. のだが、 JA に関係するところでまずそうなところを直した。

    • JA, INDEX の算出の 簡易化

      もともと v_DOBL, v_DOBH という共通の 結果を使っていたのだが、JA, INDEX では余計なものが入っている。

      v_DOBL, v_DOBH とかを展開して、余計なものを省いた 式に変更。... これは少し規模が増える。

    • フォワーディングするかどうかの判断。

      FORWARD_TO_BL,BH というのがあって、addr をラッチして比較を後でしている。addr をラッチするぐらいなら、そのタイミングで条件そのものを ラッチした方が良い。

      ... これは少し規模が減る。

    • その他。

      DOBH は、CLK の negedge で ドライブしていたのだが、ややこしいので、CLK2X の negedge で ドライブするように変更。

      r_clk 削除。冗長なのでヤメ。


    もうひとつ、無駄を見つけた。

    wire [7:0] v_DOBL = FORWARD_TO_BL ? DI2 : s1_DOBL;
    wire [7:0] v_DOBH = FORWARD_TO_BH ? DI2 : gpr[r_addrb];

    DI2 を使っているが、これは 書き込み用だった。インデックスレジスタの 更新値も含んでいる。
    ただの DI で良かった。

                                 変更後   変更前(DI)  変更前
    Total Number Slice Registers 392 392 399
    Number used as Flip Flops 384 384 391
    Number used as Latches 8 8 8
    Number of 4 input LUTs 1,100 1,106 1,101
    Number of occupied Slices 680 683 684
    Total Number of 4 input LUTs 1,136 1,143 1,138
    Number used as a route-thru 36 37 37
    Number used for Dual Port RAMs 16 16 16

    結果として規模は 4 減った。

10C



  • rtavr-wk10c.tar.gz

    まずは、上記のことを反映

    大胆な変更をしても make だけでチェックできるのは便利。以前なら小変更でもバグを入れたかどうかの確認が大変だった。

    さて、IOR のアドレスデコードを 上位レイヤと各モジュールの二段階でやっているのを 上位レイヤで全部やることにした。

    結構な変更量があるが、この際やっておきたい。いずれ Spartan-6 にも載せるし、どのようにセレクタを組むかの制限をなくして ISE まかせにするほうが良さそうだ。

    ただ、50A 用では規模が増えるかも知れない。余裕がある 今が入れるチャンス。


    変更後 変更前
    Total Number Slice Registers 392 392
    Number used as Flip Flops 384 384
    Number used as Latches 8 8
    Number of 4 input LUTs 1,077 1,100
    Number of occupied Slices 668 680
    Total Number of 4 input LUTs 1,113 1,136
    Number used as a route-thru 36 36
    Number used for Dual Port RAMs 16 16


    ... なんて思ったのだが、規模は減った。(-12)

    現時点のフルスペックの IOR では、当然ながらさらに減る。
    IOR だけ Implement してみると...


    変更後 変更前
    Total Number Slice Registers 326 326
    Number used as Flip Flops 318 318
    Number used as Latches 8 8
    Number of 4 input LUTs 543 606
    Number of occupied Slices 409 450
    Total Number of 4 input LUTs 565 637
    Number used as a route-thru 22 31
    IOB Flip Flops 48 48
    Number of BUFGMUXs 1 1


    なんと -41 。一割近い。6 input LUT がある Spartan-6 ではもっと効果があるはず。

    ところで、RAMWIN がなにか変。最初 RAMWIN も enable にしていたのだが なにかバグっているらしいので外して比較した。

    Spaartan-6 (xc6slx9-2ftg256) で確認。同じ IOR のみ。

    変更後 変更前
    Number of Slice Registers 326 326
    Number used as Flip Flops 318 318
    Number used as Latches 8 8
    Number of Slice LUTs 466 427
    Number of occupied Slices 190 181
    Number of LUT Flip Flop pairs used 541 501
    Number of bonded IOBs 162 162
    IOB Flip Flops 48 48

    増えた。何故? 現状 define IOR_LARGE_MAPPER で切り分けられるようにしている。コードが二重になっているから 修正するとき二ヶ所直さないといけないので前のコードを消そうと思ったが、これは残さないと。


    変更後 変更前
    Number of Slice Registers 405 392
    Number used as Flip Flops 397 384
    Number used as Latches 8 8
    Number of Slice LUTs 852 822
    Number of occupied Slices 316 325
    Number of LUT Flip Flop pairs used 924 903
    Number with an unused Flip Flop (531) (525)
    Number with an unused LUT ( 72) ( 81)
    Number of fully used LUT-FF pairs (321) (297)
    Number of RAMB16BWERs 3 3
    Number of bonded IOBs 12 12
    IOB Flip Flops 48 48
    Number of BUFG/BUFGMUXs 2 2

    ついでに 50A 用構成の 比較データを残しておこう。... 今度は減った。あと、FF の数が増えている。+13 .. これは何だろう? 同じ値になる FF を作った?

    それはともかく、316 スライスで済むのか。... Spartan3 の 半分以下。

      ロジックセル スライス 乗算器
      (換算) ブロックRAM DCM
      50A 1584 704 6K x 9bit 3 2 501 円
      200A 4032 1792 32K x 9bit 16 4 1014 円
      LX9 9152 1430 64K x 9bit 16 4 1286 円

    200A が 1792 スライスで 668 消費。LX9 が 1430 スライスで 316 消費しているわけだ。

    200A は、2.68 個入り、LX9 は 4.53 個入る。200A の 2倍にもならないわけか。性能や価格差からいうと それでも買いだが。

    wk10c は、ここまで。
      その後の変更:
    • IOR_DRIVEN_CLK2X

      IOR のレベル・センシティブ ラッチを 1/4 CLK で取り込む同期型にする。規模は 668 で変わらない。
      どちらが良いか分からないのだが、 レベル・センシティブ ラッチだと Warning が沢山でるので、これをデフォルトにする。

    そろそろ、いいか -- とは思うのだが、性能を知りたい 。いったい何 MHz までいけるのだろうか? へたな分析では、全然わからない。40 MHz はクリアできたのだろうか?


GPR 高速版



    実際のところ より高クロックで動くのかどうか分からないのだが、2倍速をやめたやつを作ってみた。テストは通るようだが、まだちゃんと検証していないので soc/notyet に置くことにする。(10C にも置いてあるが全然だめ)

    基本的な考え方は、上位バイトと下位バイトの 2 つの組に分けて、すなおなアクセスにする。

    rtavr_gpr_16w rtavr_gpr_16
    Total Number Slice Registers 392 392
    Number of 4 input LUTs 1,124 1,077
    Number of occupied Slices 712 668 (+44)
    Total Number of 4 input LUTs 1,190 1,113
    Number used as a route-thru 66 36
    Number used for Dual Port RAMs 80 16 (16 x 5)

    やってみたらこうなった。44 スライスの増加 -- そして Dual Port が 5 倍!

    上位バイト
    gpr_hi[r_wb_addr] WRITE or INDEX
    gpr_hi[s2_addrbh] Rr_in (or alt INDEX)
    gpr_hi[s2_addrbl] Rd_in
    下位バイト
    gpr_lo[r_wb_addr] WRITE
    gpr_lo[r_addral] INDEX
    gpr_lo[s2_addrbh] Rr_in (or alt INDEX)
    gpr_lo[s2_addrbl] Rd_in

    アクセスは、こうなっている。ポートが増える毎に Dual Port 1 個分増えるようだ。-- もともと 2 になっている所に + 3 ポート で計 5 個分。

    ちなみに 上位バイト と 下位バイト が同じにならないのは、そういう設計だから。(下位バイトは実際に 4 つ同時にアクセスするケースがある。)
    あと、Dual Port 1 個 は 16 個のレジスタになるから、本当は 32 個使える。KX_AVR とかが 64 になっていたのは、こういう理由。

      分散RAM とポート数の関係について、不思議に思っていたのだがようやく分かった。

    さて、作っていて GPR_DI (= Rd_out) の フォワーディングの処理が不要だと分かった。分散RAM は WRITE FIRST なのだ。むしろ ラッチが必要になるところが出た (INDEX)。

    そうなると .. 2倍速の GPR も事情は同じだ。FORWARD_TO_BH は常に 0 で良い。それと、バグ (ではあるがあまり深刻ではない問題)に気がついた。

    • バグ: インデックスレジスタの下位バイトへは フォワーディングされない。

      ldi r30, lo8(val_a)
      ldi r31, hi8(val_a)
      ld r20, Z

      このコードは動くのだが、

      ldi r31, hi8(val_a)
      ldi r30, lo8(val_a)
      ld r20, Z

      これは 2倍速版では 動かない。-- いままでそんなことが起きたことはないのは、そのようなコードは gcc は出さないから。必ず lo0 - hi8 の順で使う。アドレス計算では必然だし、変数の アドレス取得でも lo8 - hi8 の順になっている。直すには 検出して パイプラインストールさせれば良いのだが、規模が増える。

      下位バイト
      gpr_lo[r_wb_addr] WRITE (r30)
      gpr_lo[r_addral] INDEX (r30)
      gpr_lo[s2_addrbh] alt INDEX (r31)
      gpr_lo[s2_addrbl] Rd_in (r20)

      ちなみに 最後の ld r20, Z が 4 port 使おうとするケース。-- 実際には、 alt INDEX は、r31 なので アクセスされないが、GPR はそんなことは知らないのだ。



    変更後 変更前
    Total Number Slice Registers 391 392
    Number of 4 input LUTs 1,063 1,077
    Number of occupied Slices 667 668
    Total Number of 4 input LUTs 1,099 1,113
    Number used as a route-thru 36 36
    Number used for Dual Port RAMs 16 16

    フォワーディングの件、修正してみると、-1 減った。規模よりは、JA/INDEX の 条件が減るのが嬉しい。

    チェック+ストールのコードも作ってみた。

    N-queen クロック数
    STALL_WB_CONFLICT_1 7600  : WBする命令 + INDEX 命令でストール
    STALL_WB_CONFLICT_2 6985 : LO8 に WB する命令 + INDEX 命令でストール
    STALL_WB_CONFLICT_3 6319 : r26/r28/r30 に WBする命令 + 対応する INDEX
                   命令でストール
    チェックなし 6319

    (実行命令数 5974 )

    STALL_WB_CONFLICT_3 に引っかかる組み合わせは存在しない。STALL_WB_CONFLICT_2 のチェックは簡単なのだが、1割もストールで遅くなる。(命令数は同じだから クロック数の差が ストール回数)

    入れるのなら、STALL_WB_CONFLICT_3 だが、実際これに引っかかる命令は出ないだろう。で、規模だけ増える。... なかなか悩ましい。

    ところで、STALL_WB_CONFLICT_2 は、高速版にも 有効だ。このケースがなくなれば、下位バイトの INDEX のポートと WRITE のポートを 共有できるのだ。


    高速版 GPR (rtavr_gpr_16w) 修正後 修正前
    Total Number Slice Registers 392 392
    Number of 4 input LUTs 1,117 1,124
    Number of occupied Slices 710 712
    Total Number of 4 input LUTs 1,182 1,190
    Number used as a route-thru 65 66
    Number used for Dual Port RAMs 64 80

    で、やってみた。Dual Port RAMs は 64 と想定どおりにできたが、規模はあまり変わらない。それで、1 割クロックが増える。

    なら、元のコードの方が良い。

10D



  • rtavr-wk10d.tar.gz

    上記のことを反映。少々ややこしいので、ifdef 関係の説明から

    rtavr_defs.v :

    • STALL_WB_CONFLICT_1 -- WBする命令 + INDEX 命令でストール

      調査専用

    • STALL_WB_CONFLICT_2 -- LO8 に WB する命令 + INDEX 命令でストール

      基本調査用 ただし、rtavr_gpr_16w.v の GPR_TUNE_2 (後述) を使う場合は必要。

    • STALL_WB_CONFLICT_3 -- r26/r28/r30 に WBする命令 + 対応する INDEX命令でストール

      標準の rtavr_gpr_16.v を使う場合は基本必要。ただし、avr-gcc では これが必要になるパターンのコードは出ない。

    rtavr_gpr_16.v ローカル define :

    • GPR_TUNE_1 -- v_DOBL,v_DOBH を展開し不要セレクタを削減

    • GPR_TUNE_2 -- FORWARD_TO_BH を常に 0 相当にする。

      なぜか dual port が +8 の 24 になる。

      rtavr_gpr_16w.v ローカル define :

    • GPR_TUNE_1 -- gpr_lo の port 共有化で port 数削減。
      (STALL_WB_CONFLICT_2 の define 必須)

      規模がほとんど削減されない上に パイプラインストールの確率が上がり 10% 遅くなるため、推奨しない。

    tests ビルド用パラメータ build.inc
      build.inc :
    • ROM_HDLS, ROM_HDLS_PT

      使用する gpr の選択。

      rtavr_defs.temp :

      上記 define はこちらを修正する。

    規模について

    rtavr_pgr_16.v : (3) (2) (1)
    Number of Slice Flip Flops 391 391 391
    Number of 4 input LUTs 1070 1066 1068
    Number of occupied Slices 672 670 668
    Total Number of 4 input LUTs 1107 1102 1104
    Number used as a route-thru 37 36 36
    Number used for Dual Port RAMs 24 24 16
    Number of bonded IOBs 12 12 12
    Number of BUFGMUXs 2 2 2
    (3) WK10D 標準
    (2) - STALL_WB_CONFLICT_3
    (3) - GPR_TUNE_1/GPR_TUNE_2

    STALL_WB_CONFLICT_3 のチェックは重くなかったので標準にした。+2 だけの増加で最終的に 672 スライス。

    Dual Port RAM用 LUT が 24 になっている。GPR_TUNE_2 を入れたら +8 になった。+8 ということは 4bit 分だけ 3 port ということ。FORWARD_TO_BH を外すだけだから、port を増やす必要などないはずだが .. 規模には影響しないから増やしたのかも。

    rtavr_gpr_16w:
    Total Number Slice Registers 392
    Number of 4 input LUTs 1,133
    Number of occupied Slices 714
    Total Number of 4 input LUTs 1,198
    Number used as a route-thru 65
    Number used for Dual Port RAMs 80

    rtavr_gpr_16w は、STALL_WB_CONFLICT_3 を外している。最終的に 714 スライス。なんとか規模を減らせないかと試行錯誤したが、これで FIX 。

    rtavr_gpr_16w は、単純化した版として性能の調査に欲しかったのだ。あと、仕様の説明にも使えるし。

    これで Version 1 までに作りたいものは、一応作った。

Slowest paths について

    rtavr_gpr_16w を使い、PW_WATCH を define して 時計アイコンの 『Analize Post-Place & Route Static Timing』で 『Slowest paths』を見てみた。

      PW_WATCH を define すると 重要な信号線を外部に output として出力する。テストベンチで使うのが目的だが、『Slowest paths』では、外部に出した信号しか対象にしないようなので、define することにした。

    最も遅いのは、GPR/s2_DOBL - PW_SREG(Z Flag) で 20.679 ns 。

      s2_DOBL は Rd の値でラッチしたデータ。posedge CLK2X で常に ラッチしている。有効なのは 3/4 CLK (- 1/4 CLK)。PW_SREG は SREG で タイミングは posedge CLK で 2/4 クロック。

      以下 S Flag/V Flag と続く。

      E2.O1 net (fanout=2) 2.064 PW_SREG_1_OBUF
      E2.PAD Tioop 3.996 PW_SREG_1_OBUF

      この 6.060 ns 余計だから引くと 14.619 ns 。3/4 CLK の期間しかないから 最大周波数は、51 MHz 。

      SREG を取り込むタイミングを 3/4 クロックにすれば、まるまる 1 CLK になる。

    フラグに混じって GPR/s2_DOBL - PW_LOAD_REG がエントリしていて 19.078 ns 。

      D7.O1 net (fanout=7) 2.406 PW_LOAD_REG_7_OBUF
      D7.PAD Tioop 3.996 PW_LOAD_REG_7_OBUF

      これらを引いて 12.676 ns 。タイミングは posedge CLK2X で 3/4 クロック。

      こちらはまるまる 1 CLK の猶予があるから 最大は 78 MHz 。

    これらに続くのは、GPR/s2_DOBL - PW_STORE_MEMS で 14.347 ns 。これは、メモリに書くデータ。

      B4.O1 net (fanout=17) 3.297 PW_STORE_MEM_1_OBUF
      B4.PAD Tioop 3.996 PW_STORE_MEM_1_OBUF

      これを引くと、7.054 ns 。これは、1 CLK まるまるだし 他と大分差がある。

    さて、SREG を取り込むタイミングぐらいは、簡単に変更できる。`ifdef IOR_DRIVEN_CLK2X で SREG/SP はメモリなどと同じタイミング の 3/4 での取り込みにすることにした。この修正をしてもタイミングは変わらなかったので 最大は、68.4 MHz 。

    ちょっと甘めかも知れないが、gpr_16w を使えば規模は増えるがこれぐらいで動くということだ。

    2倍速の gpr_16 では、gpr_16 自体がボトルネックだから これだけを Imlement して見る。

    最も遅いのは、r_addrb - INDEX(hi8) で 13.704 ns 。


      E10.O1 net (fanout=1) 1.251 INDEX_9_OBUF
      E10.PAD Tioop 3.996 INDEX_9_OBUF

      同じようにこれを引くと 8.457 ns 。

      r_addrb は negedge CLK2X でラッチして、INDEX はセレクタ と加算(LDD/STD) を介して negedge CLK でラッチ。

      INDEX はセレクタ と加算(LDD/STD) での遅延も大きそうだ。仮に 0 としても最大周波数は 59.1 MHz でボトルネックなのは間違いない。

    いろいろやってみたが、INDEX → EA の遅延がよく分からない。そもそも LDD 命令の加算値など S0 でも分かる。S0 → GPR で受け渡して S1 では関与しないというのが良さそうに思えてきた。

    これをやることにしたのだが、その前にやることがある。

    ldi r30, lo8(val_a)
    ldi r31, hi8(val_a)
    ld r20, Z

    こういうコード -- r31(Z の hi8) を更新したすぐ後に Z を使う -- はわりと多いのだが、フォワーディングすることになり、ボトルネックになる。値の取り込みは 3/4 クロックだから EA のラッチまで 1/4 CLK しかない。値が変わらないなら 2/4 CLK の期間が使える。gpr_16w でも事情は同じで 4/4 CLK の期間が使えるのが、値が変わると 1/4 CLK になってしまう。

    • STALL_WB_CONFLICT_3 -- r26/r28/r30 に WBする命令 + 対応する INDEX命令でストール

    これを

    • STALL_WB_CONFLICT_3 -- r26-r31 に WBする命令 + 対応する INDEX命令でストール

    • STALL_WB_CONFLICT_4 -- r26/r28/r30 に WBする命令 + 対応する INDEX命令でストール

    という風に定義を変える。

    N-queen クロック数
    STALL_WB_CONFLICT_1 7600  : WBする命令 + INDEX 命令でストール
    STALL_WB_CONFLICT_2 6985 : LO8 に WB する命令 + INDEX 命令でストール
    STALL_WB_CONFLICT_3 6416 : r26-r31 に WBする命令 + 対応する INDEX
                   命令でストール
    STALL_WB_CONFLICT_4 6319 : r26/r28/r30 に WBする命令 + 対応する INDEX
                   命令でストール
    チェックなし 6319

    (実行命令数 5974 )

    性能への影響はあるが 1.5 % 程度。


      最も遅いのは、s1_DOAH - INDEX_12 で 10.087 。s1_DOAH は、PREDEC した後のラッチ で 3/4 CLK で取り込み。

      T10.O1 net (fanout=1) 0.475 INDEX_12_OBUF
      T10.PAD Tioop 3.996 INDEX_12_OBUF

      これを引いて、5.616 ns 。1/4 CLK でこれだと、後はセレクタを 0 としても 44.5 MHz 。

      ちょっと変更して ラッチ しない版で試すと r_addrb から 15.136 ns 。例によって引いた値は、10.659 ns 。これは 1/2 CLK で 46.7 MHz 。

      ちなみに、チューニングも合わせてやっていて、今の版は ラッチあり 681 スライス/ なし 685 スライス。ただし、(新)STALL_WB_CONFLICT_3 でないと動かないものになっている。


新たなボトルネック

    gpr_16w で、

    Minimum period: 25.546ns{1} (Maximum frequency: 39.145MHz)

    なんてのが出ている。CLK2X の周波数だろうから 19.6 MHz 。上記と差がありすぎる。-- こうなってしまう理由は何だろう。

  • 内部で生成した CLK の 遅延が大きい?

  • 2 clock で 1set なのを ISE が理解してくれない?

  • 他のボトルネックを見逃していた?

    どうも Default period を見ないといけないようだ。Default period には、CLK2X と CLK があるが、値を眺めると 39.145MHz は CLK の周波数に思える。

    CLK の方は、18.769 ns がボトルネックで s2_sbix_bit - r_pc 。要するにフラグのフォワーディング関係が重いということらしい。

    じゃぁ.. ということで、FLAGS_TUNE という define を作り、今まで考えてきた高速化を入れてみることにした。

    • FLAGS 計算の元になる Rd_out を GPR_DI から ALU_OUT に変更。(セレクタ削減)

    • di_is_zero を GPR_DI から ALU_OUT と OP1_OUT から作るように変更。(セレクタ削減)

    • CPSE で使う di_is_zero は、GPR で生成。

    • OP3 グループから CPSE と NOP を削除 (GPR_DI を使わなくなったので不要)

    これで、gpr_16w が 44.205MHz になった。スライスは +1 のみ。

    次のボトルネックでは、s2_cmd_op2 - Z - s0_inv_jump/r_sleep というものらしい。s2_cmd_op2 - Z はちょっと置いておいて、s0_inv_jump をチェックしてみよう。

    あと、r_pc のデコード。条件がかなり多い。整理できる一部のものは、S0 に持っていくのが良いのかも知れない。r_pc も 演算しているものがあるが、これも S0 で計算してラッチしてしまえる。ちょっと検討してみよう。

    NEXTPC_TUNE という define を作り次の 4 つを S0 でラッチしてみたところ 48.924MHz になった。

      PD_EXT PD_BR PD_JUMPS PD_SKIPS

      PD_JUMPS , PD_SKIPS は、いくつかの命令の グループ。r_pc のセレクタを間引くのが目的。
      PD_BR は、早く判断したいため。
      PD_EXT は、PD_BR を作るついで。

    ただし、規模は、+15 スライス。50A に入れるのは厳しい。

    次のボトルネックは、SBIX_BIT_IN 。これは、Rd を bit 指定で読み込むもの。これは、Rd の出力と同時に作れそうな程のもの。もともと GPR での Rd == Rr の出力と 同様に作ろうかと考えていたもの。

    作ってみたが、関係なかった。関係あるのは、IOR の SBRI の方。

    IOR にラッチいれたから対応できる。SBRI_TUNE で 49.193MHz 。

    で、ボトルネックは変わらず。... s2_skip という状態を reg 3 つから 作っているところがあって セレクタを減らすために reg 化できるので、NEXTPC_TUNE に追加。

    .... もうぼちぼち良いかという感じになってきた。40 MHz弱 を 50 MHz にするのは容易かったが、それ以上は難しそうだ。1 つボトルネックを潰しても、同程度のものが沢山あって、複数潰して 1 段階上がるという雰囲気。それに 最初に立てた 40 MHz - 50 MHz という目標性能ぴったりではないか。

    あとは、整理。2倍速版でも データを取って どの設定がバランスが取れているかみていこう。

(続く)
関連記事:

著作権について

    ここで提示しているコードは正しく動作しないとはいえ、既に著作権は発生しています。
    著作権は、すzが保持しており放棄はしていません。

    教育目的および私用目的では、もともと著作権の範囲外なので自由につかえます。また、ライセンスとして、GPL を適用しています。GPL に従う範囲において 個別の許可なく使用することができます。許諾のための連絡も不要です。

    なお、GPL なので、生成したバイナリを作り直せる範囲のソース開示が必要になります。FPGA だと 通常 チップ全体になってしまいます。また、開示したソースを GPL の範囲で再利用されることを妨げることはできなくなります。このコードを利用する場合この点に留意してください。

    個別の許可を得れば、GPL 以外の条件での使用は可能です。が、作業中のものには許可は出さない予定です。作業が完了(もしくは中断)したとき、ライセンスは見直します。

    なお、すでに公開してしまったものの、ライセンスを取り消すことはできないと考えていますが、ライセンスの追加は可能です。また、新しく公開するものについては、ライセンスしないことすら可能で、どのような制限もありません。変更する可能性がありますので、この点にも留意してください。
posted by すz at 22:49| Comment(0) | TrackBack(0) | AVR_CORE

2011年04月17日

AVR互換コア(テスト2)

AVR互換コアのテストも かなり進んだ。もう一息と思っている。今までの状況を一旦まとめてテストの仕上げに入る。

    テストが終われば完成 -- ではない。規模が増えているので Spartan 3A の XC3S50A に入らない。少々の最適化が必要。

まずは、ベースと テストのための AVR_Toolchain について。

  • rtavr-wk09.tar.gz -- AVR互換コア テスト2 の ベースバージョン

    AVR互換コア以外に テスト用のコードと 結果比較のための AVRシミュレータ(simulavr のコアを使用) も含んでいる。

  • AVR_Toolchain-20110409-win.tar.gz -- Windows 版 AVR_Toolchain (27MB ぐらい)
  • AVR_Toolchain-20110409-lin.tar.gz -- Linux 版 AVR_Toolchain (25MB ぐらい)
  • AVR_Toolchain-20110409-src.tar.gz -- AVR_Toolchain パッチ集 (同じものはバイナリにも添付)

    AVR8L 用としては AVR_Toolchain は as5 beta ですら 使い物にならないので、自分で改造した AVR_Toolchain を使う。.. といっても テストがなんとかできるレベル。実用として使うレベルにはなっていないので注意。

    展開すると AVR_Toolchain ディレクトリが出来る。AVR_Toolchain/bin にパスを通せば avr-gcc が使える。展開する場所はどこでも良いし AVR_Toolchain を rename しても良い。AVR Studio 4.18SP3 でも設定することで使用可能。他の MCU にも対応していて Atmel が提供しているものと同レベル。ただしテストしていない。

  • その他のツール

    Verilog シミュレータとして、『Icarus Verilog for Windows』-- Windows 版 setup (GTKWave 同梱) を使っている。Icarus Verilog は 結果を比較するのに必要。GTKWave も使うが結果比較がメインになっていて 補助的になって来ている。

    AVR Studio 4.18SP3 のシミュレータ は attiny40 に対応しているので これも併用。Xiliinx の ISE も Implement のチェックや Verilog の文法チェックに併用している。

avr8l_trace



    AVR互換コアを作ったは良いが、どうやってテストしたものか途方にくれていたのだが...ついに超強力なツールを手に入れることが出来た。それが、これ。

    どういうものかというと AVR のプログラムを実行させて、メモリやレジスタに変化があったときに、フラグやPC, SP と共に そのデータ(+アドレス or レジスタ番号)をログするもの。

    テストベンチの方でも全く同じものを出力するようにしていて、2つのログを取って diff をかければ検証できてしまうのだ。

    もっともテストに使うプログラムは作らないといけない。だが、自分で検証しなくて良いので 書きっぱなしにできるのだ。そして gcc が多少バグっていても問題ない。バグも含めてトレースするから プログラムが必ずしも正しいものである必要はないのだ。

    これを作って一気に気が楽になった。

    ちなみに、これは simulavr-0.1.2.6 のコア decode.c だけを取ってきて、でっち上げたもの。 decode.c の不要な命令は全部削ってしまったので、AVR8L 専用になっている。また、コアだけテストできれば良いので (SREG/SP 以外の)IOレジスタも割り込みもない。

    使い方は、ROM ファイルとして Verilog 用データの rom_data.mem を読み込んで実行させる。終了条件は、実行命令数 と SLEEP 命令。-- 割り込みがないので SLEEP 命令が来たら終了。

    AVR互換コアのテストベンチもほぼ同じ。ただし、実行命令数 をカウントしていないので、クロック数で代替している。

いままでのテストの内容



    shuffle という テストプログラム を作成して、実行させ avr8l_trace を使って 結果を比較してバグを潰してきた。

    shuffle は 16 バイトのデータを 入力して 16 バイトのデータを 出力する一方向関数。MD5 とか 乱数に近いものだが、いろんな命令を使うようにしている。

    テスト専用として作っているので、一方向関数としては不完全。また適当に作ったのでテストとしても不完全。もともとは、出力データを比較することで検証しようとしていたのだが、avr8l_trace の完成でそれは不要になった。なにより gcc のバグ(だと思う)で、最後まで動かずどこかで無限ループしている。

      独自拡張の ldd/std 命令を使っているのを忘れていた。これじゃ avrtiny40 のシミュレータでは動かない。でも、avr8l_trace で 25万命令実行しても shuffle() から戻ってこない。... やっぱり avr-gcc のバグか。

    このテストで 沢山バグを潰すことができた。今や 3000 命令近い実行で結果が一致している。

    その先、一致しないところがあるのだが、ずっと動かした先を調べるのが億劫になって来ている。どういう所がバグっていそうか分かってきたので、今度は 弱点を攻める短いテストプログラムで検証していこうと考えている。

見つかったバグの整理



どういうバグがあったのか整理しておく。これから出るバグも似た様なものになりそうだ。あと、未解決のものがある。

  • フラグの結果不正

    LDI で Z flag を変化させた。
    ANDI/ORI で C flag を変化させた。
    MOV で (複数) flag を変化させた。
    COM で V flag を変化させなかった。

    CP/CPC/SUB/SBC/SUBI での H flag/V flag の間違い
    CPC/SBC/SBCI での Z flag の間違い

    フラグの変更ルールというものを知らなかった。沢山バグが出たのも当然か。

    特に H flag/V flag の計算式が add 系と sub 系で違うのを知らなかったので、2 度引っかかった。あと、Z flag は sub 系のキャリー付きだけ 動作が違うのも。

  • 命令デコードの不正

    RET + PUSH と並んでいるときの SP値 不正
    RCALL + SKIP命令と並んでいるとき call先の1命令が実行されない。
    CBI/SBI で 関係ないレジスタを書き換える。
    LDD/STD デコード 不完全
    CALL でのスタック積み順 (HI byte → LOW byte が正しいが逆だった)

    RET + PUSH や RCALL + SKIP命令は、命令デコードの無効化が完全でなかったのが原因。CBI/SBI と LDD/STD は デコードそのものが 不完全。スタック積み順 はデータシートで見つけられなかったので、どちらか分からなかった。

  • ラッチ関係

    C_in に ラッチが必要だった。( ROR で結果不正 )
    Z_in も不安なのでラッチ (バグが見つかったわけではない)

    上記の 2 系統のバグは想定内だが、これは気がつかなかった。
    C_in などは、IOレジスタなので CLK の 1/2 ( posedge ) で変更する。が、レジスタに取り込むのは、これより後 ( CLK の 3/4 ) だから C_in が変更されてしまい それを元に計算するようなところの値も変わってしまう。Z_in は計算には使わないので問題ない。蛇足だった。

  • SKIP 命令の設計ミス(未修正)

    SKIP 命令はちょっとマズイ。バグは分かっているが、対処方法を決めかねている。

    追記: どうするかは、『AVR互換コア(設計メモ)』の記事に書いた。


テスト t_skip

    最初のテストプログラムは次のようにした。


    uint8_t t_skip(uint8_t a, uint8_t *p) {
    if (a & 1) *p++ = a;
    *p = 0;
    return a & 1;
    }

    main 側:
    t_skip(0xff, buf);
    t_skip(0x00, buf);
    sleep();

    オブジェクト:
    0000007e <t_skip>:
    3f: 2fe6 mov r30, r22
    40: 2ff7 mov r31, r23
    41: fd80 sbrc r24, 0
    42: 9381 st Z+, r24
    43: 8330 st Z, r19
    44: 7081 andi r24, 0x01 ; 1
    45: 9508 ret


    skip 命令の後に POSTINC ... 上記バグにヒットするコード。ちゃんと C だけで作ることが出来た。
    ちなみに、r19 は zero_reg 。(自製 AVR_Toolchain の AVR8L のときの 独自仕様)

    で、このコードは まだ 09 では動かない。設計ミスに対処してなんとか動かすことが出来た。

    -S mem[0060] = 00 : PC 0043 FLAGS 00 SP 0139
    +S mem[0060] = 00 : PC 0042 FLAGS 00 SP 0139

    だが diff で引っかかる。PC は S2 の時点ではもう覚えていない。テストベンチでは、2 クロック前 -- S0 だったはずの時の 値を出力しているのだがパイプラインストールが起きるとずれる。

    この不一致は、パイプラインストールが起きた目印ということにして許容しよう。直すの面倒だし。

    あと別のバグが見つかった。

    SLEEP 命令のとき RET/RETI 命令だと誤認されて RET/RETI の処理も動いてしまった。またもや デコードが甘かった。


    あと cpse も同じように確認したい。

    uint8_t t_skip2(uint8_t a, uint8_t b, uint8_t *p) {
    if (a != b) *p++ = a;
    *p = 0;
    return a & 1;
    }

    000000cc <t_skip2>:
    66: 93df push r29
    67: 93cf push r28
    68: b7cd in r28, 0x3d ; 61
    69: b7de in r29, 0x3e ; 62
    6a: 2fe4 mov r30, r20
    6b: 2ff5 mov r31, r21
    6c: 1386 cpse r24, r22
    6d: 9381 st Z+, r24
    6e: 8330 st Z, r19
    6f: 7081 andi r24, 0x01 ; 1
    70: 91cf pop r28
    71: 91df pop r29
    72: 9508 ret

    cpse は Rd/Rr の不一致でスキップ。avr-gcc もやるなぁ。ちゃんと想定どおりに使ってくる。ちなみに push/pop は フレームポインタ Y の処理。結果として不要だったわけだが、まぁ -Os ではこうなるのかも。

    ところで tb_shuffle での残り問題。


    -L r18 = 01 : PC 0260 FLAGS a1 SP 0124
    +L r18 = 01 : PC 0260 FLAGS a3 SP 0124

    -L r24 = 00 : PC 017a FLAGS 83 SP 0126
    +L r24 = 00 : PC 017a FLAGS 82 SP 0126

    260: 2322 and r18, r18

    17a: 2b84 or r24, r20

    AND/OR で Z flag が違う。

    これも フラグの計算の問題。AND/OR/EOR は H Flag と C Flag だけ変更しない。

    これを修正したところ shuffle は 5000 命令クリア。大分安定してきた。shuffle はまだちゃんと動かないので N-queen を試したところクリア。

    ここで 規模を調べてみたところ 50A 用の rtavr_defs.v で 701 スライス。... 711 からなぜか減った。

09A



  • rtavr-wk09a.tar.gz

    09A 版 スナップショット。

    これでほぼ完成か? いろいろ整理して Ver 1 にしたい。

    ただ、その前にフラグ計算を 整理しておきたい。それで規模が減るなら 採用して余裕を確保しておきたいのだ。第一、今のは美しくない。S1 の段階で 減算と 加算 あとキャリー付き減算をまとめておく。
    この分解能の情報があれば、V/H/Z の処理を大分まとめられる。

    S2 になってから 組み合わせ回路でまとめても論理はかわらないと思うので S1 に持って行ってみる。

      これをやってみたのだが、707 スライスと 却って増えた。ただ、こうやってまとめた先に 単一 ALU化がある。 単一 と言わないまでも OP1/OP2 の加減算を 1 つに出来れば規模は減るはず。エンバグしたかどうかの検証も楽になったし。演算までまとめてみてから判断しようと思う。

09B



  • rtavr-wk09b.tar.gz

    OP3/OP2 の 処理を 1つの ALU にまとめるという ALU 化をやってみたところ 707スライスが、679 スライスまで減った。


      Number of Slice Flip Flops 391
      Number of 4 input LUTs 1,088
      Number of occupied Slices 679
      Total Number of 4 input LUTs 1,124
      Number used as logic 1,072
      Number used as a route-thru 37
      Number used for Dual Port RAMs 16
      Number of bonded IOBs 11
      Number of BUFGMUXs 2
      Number of RAMB16BWEs 3

      ちなみに C Flag を ALU の 9bit 目から 独立させて H Flag と同じ処理にしてみたところ 691 まで増えてしまった。逆に ALU を 2 分割して H Flag を取り出してやると もっと 減るのかも。

      .. これ実際にやってみると 701 と増える。なかなか難しいものだ。

      09B は、ALU_8BIT , ALU_4BITX2 の 2 つの オプションをいれた。これは 整理したときに消してしまうつもりで 今だけのオプションになりそうだ。

    とりあえず、変更したら tb_shuffle/tb_queen/tbi_001 でチェックしているが OK 。tbi_001 は 随時追加している。

    若干の余裕もできたし、ひとまずの完成としよう。これを元に ソースコードも整理する。ifdef のネストがひどいことになっているので、固定化した ifdef は 整理してしまう。

      完成とは書いたが、これがスタート地点でもある。ようやく チューニングしたり いろいろいじるベースが出来たのだ。いじってみて結果を比較することで確認するから、動くバージョンが 1 つあるのはすごく重要。

      avr8l_trace もあるが、結果が違うことはわかっても詳細は分からない。動く rtavr があれば、Verilog シミュレータで内部状態の違いまで比較することが出来る。

      ここから、大胆な変更とかもやりはじめることができるのだ。( ALU 化 もかなり大胆に変更している。)

      あと、やっておきたいのは、SLEEP 命令か。割り込み使うのなら欲しい場合もありそうだ。ただ、使わなくとも プログラムは書けるから オプション。

      もちろん他にやりたいこともある。それは Version 1.0 入れないが、SLEEP だけは別ということ。 SLEEP 命令がないと、機能として不完全という気がするのだ。

    sleep の実装

    実際に作ってみたところ、簡単だった。スリープ状態 r_sleep をひとつ作り、

    r_sleep <= (~s1_invalid & f_sleep) | (r_sleep & ~v_int);

    こんな風に制御する。f_sleep は S1 でのデコード。無効にされてなかったら、次のクロックからスリープ状態。
    v_int は、割り込みが受け付けられたことを示す。要するに割り込みで状態解除。

    ただし、 (~s1_invalid & f_sleep) のときに 割り込みが受け付けられると、割り込み開始と同時にスリープ状態になってしまうので、割り込みを 1 クロック遅らせる。

    あと、 (~s1_invalid & f_sleep | r_sleep ) のときは、パイプラインストールさせる。

    パイプラインストールさせると ROM のアドレスも更新されず同じ状態がずっと続く。クロックを止めたりはしないが、状態が変わらないので消費電流の低減に多少は寄与するはず。

    これだけ入れてどれぐらい規模が増えるか見てみたら 1 スライスだけだった。


    Number of Slice Flip Flops 391 392
    Number of 4 input LUTs 1,088 1,090
    Number of occupied Slices 679 680
    Total Number of 4 input LUTs 1,124 1,126
    Number used as logic 1,072 1,074
    Number used as a route-thru 37
    Number used for Dual Port RAMs 16
    Number of bonded IOBs 11
    Number of BUFGMUXs 2
    Number of RAMB16BWEs 3

    FF 1 個と LUT2 個。でちょうど 1 スライス -- うまいこと入ったものだ。

    1 スライスだけならオプションにするまでもない。標準として入れよう。



    テストは、tb_int0 を使った。int0 を起こす SBI をしてから 実際の割り込みが起きるのは 2 クロック後なので sleep 命令を入れられる。

    これは、ちょうど sleep と割り込みが同時に起きたケースで、割り込みが 1 クロック待たせられている。ところで sei + sleep で間に割り込みを入れてはいけないというルールがあるが、sei が S2 のとき sleep が S1 なので 特別な処理をしなくともこのルールは守られる。... というかもともと、S1 実行中の命令がない状態にしてから 割り込みを起こすので、問題ないようになっていたのだった。



    これは、tb_int0 の最後。だれも 割り込みを起こさないのでこの状態がずっと続く。

実はまだまだだった。

    N-queen が動いたと書いたが、ミスで tbi_001 (の古いバージョン)を動かしていただけだった。... やってみたら N-queen も shuffle と同じく終わらない。

    で、フラグ値が違う問題が 1 つ。

    ( L r16 = 7f )

    L r29 = 01 : PC 00e1 FLAGS 00 SP 0129
    L r17 = ff : PC 00e2 FLAGS 15 SP 0129
    -L r16 = 81 : PC 00e3 FLAGS 35 SP 0129
    +L r16 = 81 : PC 00e3 FLAGS 15 SP 0129

    e1: 50d0 subi r29, 0x00 ; 0
    e2: 9510 com r17
    e3: 9501 neg r16


    NEG の H Flag の計算は特別処理になっているが、計算式が違っていた。

    さらに ... tbi_001 での違い .. パイプラインストールでずれているだけかと思ったら

    L r30 = 64 : PC 00db FLAGS 02 SP 0135
    L r31 = 00 : PC 00dc FLAGS 02 SP 0135
    -S mem[0064] = 00 : PC 00df FLAGS 02 SP 0135
    +S mem[0064] = 01 : PC 00de FLAGS 02 SP 0135
    L r24 = 01 : PC 00e0 FLAGS 00 SP 0135

    dd: 1386 cpse r24, r22
    de: 9381 st Z+, r24
    df: 8330 st Z, r19
    e0: 7081 andi r24, 0x01 ; 1

    書いている値が違う。1 回しか メモリに書いていないから 0xde はスキップされて 0xdf の st で書いていることになる。

    r19 は __zero_reg__ なのだが だれも変更していない。 にもかかわらず 0x01 を書いている。

    ... 原因が分かった。表示通り 0xde を実行して 0xdf をスキップしていたのだった。

    2 クロック目ではなく 3 クロック目をスキップするコードになっていた。これを直すことで、tbi_001 は完全に一致した。

    あとは、shuffle と N-queen を動かすことか。使いたいのは gcc なわけで、結局は動かないままでは先に行けない。

    ちょっと tb_queen を見てみる。DEPATH が 3 までなら最後まで行くようだ。

     
    実行命令数(avr8l_trace)
    DEPATH = 1 97
    DEPATH = 2 680
    DEPATH = 3 1845


    それは良いとして、DEPATH = 3 で結果比較すると 違いが出る。


    rtavr:
    163 L r29 = 01 : PC 00a8 FLAGS 21 SP 0129
    164 L r20 = 00 : PC 00a9 FLAGS 21 SP 0129
    165 L r21 = 00 : PC 00aa FLAGS 21 SP 0129
    166 L r28 = 29 : PC 00ab FLAGS 20 SP 0129
    167 L r29 = 01 : PC 00ac FLAGS 00 SP 0129

    168 L r16 = fe : PC 00da FLAGS 35 SP 0129
    169 L r17 = ff : PC 00db FLAGS 35 SP 0129
    170 L r28 = 2a : PC 00dc FLAGS 21 SP 0129
    171 L r29 = 01 : PC 00dd FLAGS 21 SP 0129

    avr8l_trace:
    163 L r29 = 01 : PC 00a8 FLAGS 21 SP 0129
    164 L r20 = 00 : PC 00a9 FLAGS 21 SP 0129
    165 L r21 = 00 : PC 00aa FLAGS 21 SP 0129
    166 L r28 = 29 : PC 00ab FLAGS 20 SP 0129
    167 L r29 = 01 : PC 00ac FLAGS 00 SP 0129

    168 S mem[0129] = b1 : PC 00b0 FLAGS 35 SP 0128
    169 S mem[0128] = 00 : PC 00b1 FLAGS 35 SP 0127
    170 L r28 = 2c : PC 00b1 FLAGS 21 SP 0127
    171 L r29 = 01 : PC 00b2 FLAGS 21 SP 0127

    a8: 4fdf sbci r29, 0xFF ; 255
    a9: 9149 ld r20, Y+
    aa: 8158 ld r21, Y
    ab: 50cc subi r28, 0x0C ; 12
    ac: 50d0 subi r29, 0x00 ; 0
    ad: 3042 cpi r20, 0x02 ; 2
    ae: 0753 cpc r21, r19
    af: f554 brge .+84 ; 0x1b4 <__stack+0x75>
    b0: d000 rcall .+0 ; 0x162 <__stack+0x23>
    b1: 5fcd subi r28, 0xFD ; 253


    da: 9500 com r16
    db: 9510 com r17
    dc: 5fcf subi r28, 0xFF ; 255
    dd: 4fdf sbci r29, 0xFF ; 255

    0x1b4 にはプログラムはない。一体どこに 分岐するつもりなのか?... と思ったら 逆アセンブラのバグ。分岐先は 0xda 。結局 brge が分岐すかしないかの違い。brge は BRBC 4 と等価で 直前の cpc の S Flag が間違っていることになる。そして正しいフラグ値は 0x35 で S=1 。rtavr は次の命令で変更されているから ログでは分からない。



    これ見ると F554 でのフラグ値は 0x35 で正しい。S も 1 。だがセレクタを通した FLAGS_BIT_IN が 0 。

    wire v_flags_bit = (FLAGS_BIT == 0) ? C_out
    : (FLAGS_BIT == 1) ? Z_out
    : (FLAGS_BIT == 2) ? N_out
    : (FLAGS_BIT == 3) ? S_out
    : (FLAGS_BIT == 4) ? V_out
    : (FLAGS_BIT == 5) ? H_out
    : (FLAGS_BIT == 6) ? T_out
    : I_in ;

    ... なんと S と V が逆だった。これを直すことで DEPATH = 3 の結果が一致した。しかし ... いろいろあるものだ。

    もともと フラグに関しては それらしいコードを埋めるのがやっとだったから、まぁバグが多いのは仕方ない。

    これで最後だと良いのだが...

09C



  • rtavr-wk09c.tar.gz

    09C で 上記のバグを全部直した。あと ifdef も削りだしている。


    09B 09C
    Number of Slice Flip Flops 391 391
    Number of 4 input LUTs 1,088 1,096
    Number of occupied Slices 679 683
    Total Number of 4 input LUTs 1,124 1,132
    Number used as logic 1,072 1,080
    Number used as a route-thru 37 36
    Number used for Dual Port RAMs 16
    Number of bonded IOBs 11
    Number of BUFGMUXs 2
    Number of RAMB16BWEs 3


    少しだが規模は増えた。

    さて、とりあえず用意したテストプログラムは一致するようになった。今度は avr8l_trace でも動かない tb_queen と tb_shuffle を動かす作業に入る。

    その目的で avr8l_trace には SLEEP で レジスタとメモリをダンプする機能を付けた。ところどころ sleep を入れながら avr-gcc をデバッグしていくつもり。

    ちょっとやってみた。

    shuffle() が retuen したら sleep() するようにして、suffle() の中の最後でも sleep()
    これはちゃんと止まるが、suffle() の中のsleep() を nop() にすると止まらない。


    main 側
    5b: d058 rcall .+176 ; 0x168 <shuffle>
    ...
    60: 9588 sleep
    61: 894b ldd r20, Y+19 ; 0x13

    shuffle 側
    00000168 <shuffle>:
    b4: 930f push r16
    b5: 931f push r17
    b6: 93df push r29
    b7: 93cf push r28
    b8: b7cd in r28, 0x3d ; 61
    b9: b7de in r29, 0x3e ; 62
    ba: 51ca subi r28, 0x1A ; 26
    bb: 40d0 sbci r29, 0x00 ; 0
    bc: b72f in r18, 0x3f ; 63
    bd: 94f8 cli
    be: bfde out 0x3e, r29 ; 62
    bf: bf2f out 0x3f, r18 ; 63
    c0: bfcd out 0x3d, r28 ; 61
    c1: 2fe8 mov r30, r24
    c2: 2ff9 mov r31, r25

    :
    :
    238: f731 brne .-52 ; 0x43e <shuffle+0x2d6>
    239: 9588 sleep
    23a: 5ec6 subi r28, 0xE6 ; 230
    23b: 4fdf sbci r29, 0xFF ; 255
    23c: b72f in r18, 0x3f ; 63
    23d: 94f8 cli
    23e: bfde out 0x3e, r29 ; 62
    23f: bf2f out 0x3f, r18 ; 63
    240: bfcd out 0x3d, r28 ; 61
    241: 91cf pop r28
    242: 91df pop r29
    243: 911f pop r17
    244: 910f pop r16
    245: 9508 ret

    結果
    S mem[0126] = 5c : PC 005b FLAGS 80 SP 0125
    S mem[0125] = 00 : PC 005c FLAGS 80 SP 0124
    S mem[0124] = 01 : PC 00b4 FLAGS 80 SP 0123
    S mem[0123] = 27 : PC 00b5 FLAGS 80 SP 0122
    S mem[0122] = 01 : PC 00b6 FLAGS 80 SP 0121
    S mem[0121] = 26 : PC 00b7 FLAGS 80 SP 0120
    L r28 = 20 : PC 00b8 FLAGS 80 SP 0120
    L r29 = 01 : PC 00b9 FLAGS 80 SP 0120
    L r28 = 06 : PC 00ba FLAGS a0 SP 0120
    L r29 = 01 : PC 00bb FLAGS 80 SP 0120
    L r18 = 80 : PC 00bc FLAGS 80 SP 0120
    L r30 = 27 : PC 00c1 FLAGS 80 SP 0106
    L r31 = 01 : PC 00c2 FLAGS 80 SP 0106
         :
    :
    S mem[0134] = 62 : PC 0233 FLAGS 80 SP 0106
    S mem[0135] = 25 : PC 0234 FLAGS 80 SP 0106
    SLEEP : PC 0239 FLAGS 82 SP 0106
    *** inst-count 1999 ***
    *** gpr dump ***
    r16:17 r17:01 r18:00 r19:00 r20:62 r21:25 r22:36 r23:01
    r24:62 r25:25 r26:36 r27:01 r28:06 r29:01 r30:17 r31:01
    *** ram dump ***
    *
    0060: 05 00 01 00 00 00 01 00 01 00 00 00 00 00 00 00
    0070: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    *
    0100: 00 00 00 02 61 01 a1 9a f5 a6 4f 10 3c 5b f2 62
    0110: 25 62 25 62 25 62 25 08 00 17 01 42 81 26 01 26
    0120: 01 26 01 27 01 00 c6 b7 27 6d 1c 78 cc 55 5b 85
    0130: 62 25 62 25 62 25 42 81 00 00 3f 01 00 00 00 2b
    ****************


    sleep でのレジスタ・メモリが取れているから これをじっくり見ればどこが悪いか分かるはず。
    あと、shuffle の結果もメモリ中にある。

    138c2ff005f01fae0e780ef64dcc849b
    2 1 2 1 2 0 0 0

    これが PC で取った正しい結果。... 全然合っていないような。..

    下が それぞれの case を通った回数。メモリをみると 5 1 0 1 1 0 0 0 だったと分かる。... 合わないのも当然か。スタックの 戻りアドレス も壊れている。これじゃだめだ。


    L r29 = 01 : PC 0230 FLAGS 80 SP 0106
    L r24 = c6 : PC 0231 FLAGS 94 SP 0106
    L r25 = b7 : PC 0232 FLAGS 94 SP 0106
    S mem[0126] = c6 : PC 0233 FLAGS 94 SP 0106
    S mem[0127] = b7 : PC 0234 FLAGS 94 SP 0106

    22f: 51c3 subi r28, 0x13 ; 19
    230: 40d0 sbci r29, 0x00 ; 0
    231: 2784 eor r24, r20
    232: 2795 eor r25, r21
    233: 938d st X+, r24
    234: 939d st X+, r25

    壊しているのはここ。... となると X がおかしい

    逆に追っていくと、関数の頭のほう Z(r31,r30) が 0127 になっているのが元。

      思うに avr-gcc のコード生成で、アセンブラ(相当)を出力するのコードのなかのどれかで、X/Y/Z レジスタ を間違えているものがあるんじゃないだろうか?

      avr-gcc の AVR8L 対応のコードは ベースと 1:1 に対応するコードしか生成していない。機械的な変換だから こういう単純なバグしか入らないはず。

      そういう観点で一回チェックしてみよう。

(続く)
関連記事:

著作権について

    ここで提示しているコードは正しく動作しないとはいえ、既に著作権は発生しています。
    著作権は、すzが保持しており放棄はしていません。

    教育目的および私用目的では、もともと著作権の範囲外なので自由につかえます。また、ライセンスとして、GPL を適用しています。GPL に従う範囲において 個別の許可なく使用することができます。許諾のための連絡も不要です。

    なお、GPL なので、生成したバイナリを作り直せる範囲のソース開示が必要になります。FPGA だと 通常 チップ全体になってしまいます。また、開示したソースを GPL の範囲で再利用されることを妨げることはできなくなります。このコードを利用する場合この点に留意してください。

    個別の許可を得れば、GPL 以外の条件での使用は可能です。が、作業中のものには許可は出さない予定です。作業が完了(もしくは中断)したとき、ライセンスは見直します。

    なお、すでに公開してしまったものの、ライセンスを取り消すことはできないと考えていますが、ライセンスの追加は可能です。また、新しく公開するものについては、ライセンスしないことすら可能で、どのような制限もありません。変更する可能性がありますので、この点にも留意してください。
posted by すz at 22:47| Comment(0) | TrackBack(0) | AVR_CORE

2011年04月05日

AVR互換コア(テスト)

もう最初に想定した機能レベルは実装が済んでしまった。そろそろ、いろいろなパターンでちゃんと動くかテストする段階だ。gcc は完成度が低く不満があるが どうせ gcc だけでは 使う命令が少なくてテストしきれないのだ。ここらで見切りを付けてテストに入りたい。

まずは、ベース。

これをベースにする。テストに使う gcc は自分で作ったもの。問題はあるが なんとかごまかして使っていこうと思う。再現性の問題があるので、バイナリを固定する。具体的には以下のもの。


    08 で変更したものは、INT1 , RAM window の追加。RAM/ROM の タイミング(1/4 read, 3/4 write) 、2word LDS/STS のスキップ。どれも テストはしていない。

    あと iort40.h の整備。... 大体入れたいものは入れた。これ以上機能は追加しないで実際に動かす方に重点を移す。

    ちなみに、RAM/ROM タイミング変更 + 2word LDS/STS disable を基本としようと思う。USART/SPI master と INT1 , Timer0 が一応使え 4KB のプログラムメモリがあって 680 スライス -- Spartan-3A の 50A に載る。

さて、ここまで楽しく作ってきたが テストはやっぱり面倒そうだ。一体どうしたら良いのだろう?

ちょっと案を考えてみよう。

ひとつは、他のシステムのテスト用ツールを借りる方法。-- 調べてみると simulavr がそれを持っている。

    よくわからないものの、命令ごとに python のスクリプトが用意されていたりするようだ。だが、改造は必要そうだし python は苦手だし ... 最終手段と考えておこう。


もうひとつ考えてみた。

ある命令列を実行して、どこかの IOR を SBI/CBI する。そうすると、レジスタを ダンプするような test bench を組んでおく。で、正しい 結果と diff を取る。

    INT0 を使って PORT に SBI して posedge で割り込みを起こせば良さそう。

正しい結果は、別のもので作る。たとえば 実際の AVR でも良い。テストできないのは、1word LDS/STS のみ だし、それは gcc では当面使えない。SBI/CBI すると割り込みを起こすようにしておいて、レジスタダンプを取り、ホストに転送するようにしておけば良い。

    そう考えると テストコードは ひとつの .s として作り、SBI/CBI は マクロとして仕込むのが良さそうだ。.s は 基本 gcc で生成するが バグとかあるはずで、手で修正した .s で管理する。

    あ、テストベンチでレジスタダンプを取るにしても 横から GPR をアクセスできない。... いや 本体と GPR を分けて テストベンチ上で統合すればできるか。... でも実際に割り込みを起こして 割り込みルーチンの中でダンプさせた方が楽か。ダンプの指示には PORT も使える。

メモリの内容をテストするなら、レジスタにロードして ダンプさせれば良いし、SREG や SP のテストも同様にできる。

もうひとつは、md5 とが gzip のような 圧縮・伸張 あと暗号化、ランダム。こういった データを変換するもの。正しく動いたかは出力を照合することで判断できる。

もっとも ダメならどこでおかしくなったのか調べる必要があるから、レジスタダンプのやりかたも併用することになる。

それと基本的な命令パターンの確認もやらないといけないだろう。どういう命令が動いたのか一覧表を作ってテストしてないものが分かるようにしないと。一区切りするタイミングがわからない。

追記: libgcc.a で使っていない命令

    どうせ gcc だけでは 使う命令が少なくてテストしきれない。なんて書いたが本当だろうか?

    gcc のコード生成を丹念に調べれば 使っていない命令は分かる。だが、コンパイラが libgcc.a を勝手に使うわけだから libgcc.a も gcc の一部と考えた方がよい。... というわけで libgcc.a で使っていない命令を調べてみた。

    調べる方法:

    avr-ar -x libgcc.a
    avr-ld -r -o ../xx.o *.o
    avr-objdump -d ../xx.o | ....

    ar で全部バラして 1 つの オブジェクトにまとめなおす。それに対して ディスアセンブルて集計。

    で見てみると 使っていない命令は、

      NOP , ROR , BSET , CBI, SBI , BLD , RETI , SLEEP , WDR

    だった。NOP,ROR 以外は 一般命令とは言えないようなもの。

      ちなみに BSET は sei などの元の命令。( cli は使っているが、SREG をリストアしていて sei は使わない ) 。

    あと 条件分岐命令は、brcc, brcs, breq, brge, brlt, brne , brpl , brtc の 8種類。命令としては、BRBC/BRBS の 2 つになるが、16 パターンのうち 8 パターンだけ使っていることになる。

    次に、コード生成で現れる命令を調べてみたところ ..
    上記のうち 使っている命令は、

      NOP, ROR, BLD
      WDR(__builtin_avr_wdr), SLEEP(__builtin_avr_sleep),
      BSET(__builtin_avr_sei)

    があった。ちなみに、__buildin_avr_xx が使えるのは、sei/cli/wdi/sleep/swap/delay_cycles (他 fmul 等もある )

    bld/bst は、6bit の arithmetic shift right とか補助的に使われているようだ。

    うまくパターンの組み合わせを引き出すのは難しそうだが、gcc のコードだけでいけるかも知れない。

テストプログラムの選定

    最初は dhrystone を考えていたのだが、ここみると 2.5KB 以上の RAM が必要だそうだ。ソースをチェックしたが int で 51 x 51 の配列を使っている。

    次に、以前 USBNIX で使った minicrypt を検討。minicrypt は、いにしえの UNIX version 7 で使われた 暗号のコード -- 簡略化された エニグマ -- を移植したもの。。。。だと思っていたのだが、過去記事みたら記憶違いだった。サイズの問題で入らないので方針を変更してでっち上げたものだった。

    そのままだとグローバル変数のアクセスで gcc の問題に引っかかるので、修正が必要だった。それはともかく、出てくる命令の種類が少ないような ...

    で、次に md5 。これは良さそうな気がするのだが、サイズもデカそうな... 今は minicrypt に md5 のエッセンスを混ぜられないか検討中。

    md5 を簡略化してみたが、使う命令が 27 と少なかった。ううむ。

08A



  • rtavr-wk08a.tar.gz

    テストするためのフレームワークとして、INT0 を使うことにした。... が、良く考えたら INT0 は まだちゃんとテストしていない。


    ISR(INT0_vect) {
    bit_clear(PORTC, PC2);
    }

    int main() {
    MCUCR = (3 << ISC00); // posedge
    bit_clear(PORTC, PC2);
    bit_set(DDRC, PC2);
    bit_set(GIFR,INTF0);
    bit_set(GIMSK,INT0);
    sei();

    nop();
    bit_set(PORTC, PC2);
    PINC;
    PINC;
    bit_set(PORTC, PC2);
    nop();
    nop();
    return 0;
    }

    テストプログラムは、こういう風にした。PC2 に SBI することで INT0 の posedge で INT0 を発生させる。割り込み処理の方では、PC2 を元に戻す。

    INT0 をソフトウェア割り込みとして使ったのは初めてなのだが、例外(フォルト)ではなく、あくまで割り込みなので、数命令ずれて割り込みが発生する。それを確認するために PINC; とか nop とかいれている。

    実はこれをコンパイルするのが一苦労。add4 でようやく ちゃんとコンパイルできるようになった。

    出来たコードの一部を載せるとこう。

    0000005e <__vector_1>:
    2f: 933f push r19
    30: 932f push r18
    31: b72f in r18, 0x3f ; 63
    32: 932f push r18
    33: e030 ldi r19, 0x00 ; 0
    34: 98ea cbi 0x1d, 2 ; 29
    35: 912f pop r18
    36: bf2f out 0x3f, r18 ; 63
    37: 912f pop r18
    38: 913f pop r19
    39: 9518 reti

    00000074 <main>:
    3a: ec80 ldi r24, 0xC0 ; 192
    3b: bf8a out 0x3a, r24 ; 58
    3c: 98ea cbi 0x1d, 2 ; 29
    3d: 9ae2 sbi 0x1c, 2 ; 28
    3e: 9a58 sbi 0x0b, 0 ; 11
    3f: 9a60 sbi 0x0c, 0 ; 12
    40: 9478 sei
    41: 0000 nop
    42: 9aea sbi 0x1d, 2 ; 29
    43: b38b in r24, 0x1b ; 27
    44: b38b in r24, 0x1b ; 27
    45: 9aea sbi 0x1d, 2 ; 29
    46: 0000 nop
    47: 0000 nop
    48: e080 ldi r24, 0x00 ; 0
    49: e090 ldi r25, 0x00 ; 0
    4a: 9508 ret




    シミュレーション結果の一部はこれ。9AEA が INT0 を発生させる SBI 。次の B38B が PIN の変化を見るための IN 命令 (x2) 。この IN 命令 2 つを実行して 割り込みが受け付けられる。

    実を言うとこの動作は、実際のAVR とタイミングが違う。まず PIN の読み込み。マニュアルには、PORT に out した次の命令で PIN から in しても反映されないと書いてある。nop を1命令入れないと正しい値は読めない。 ( 当然ながら PORT を in する場合は nop は不要 )

    だが、RTAVR は nop は不要。周辺装置は postedge で動作するので PORT の変化は postedge 。これを PIN に移すのは、negedge 。次の PIN は その次の postedge でちゃんと読める。

    ちなみに イメージの INST は S1 の状態。実行(S2) は 1クロックずれる。9ARA の sbi の実行の次のクロックで 割り込みの要求が出ているのだが、それが受け付けられるのは さらに 1 クロック後。なので、sbi から 2 命令すべって 割り込みが実行されている。

      説明したのは、08A の動作で、実はバグがあった。INT0 の受けつけで不要なラッチがあって 無意味に 1 クロック遅らせていた。

      あと INTF0 に 1 を書くと 割り込み原因が クリアされる仕様 になっていなかったので修正している。

      あと、low lovel の受付とか 細かい仕様の違いは残っているが、直す予定はない。

    これを元にして、レジスタダンプの テストベンチを作る予定。

    あと ldd/std 命令を入れてみた。1word LDS/STS と競合するのは、変位が 32-63 の範囲。0-31 に制限すれば 競合を避けられる。

    動くかどうかは分からないが、まずは規模を見積もるのが目的。50A 用の rtavr_defs.v に 追加すると 665 スライスが 682 スライスになった。... なんとかなるかも。

      ldd/std も 1 クロックで動作させる。だが、実行アドレスの加算が 遅延を増やしそう。

      対策のひとつとして、上位 3bit は加算しないことにした。気休めかも知れないが。これによって、8KB アラインをまたぐ加算は不可能になった。ROM のサポート範囲は 8KB までだし、問題は起きないはず。

      あと対策として考えられるのは、ラッチする 前で 加算していたのを ラッチ後の出力に対して行う。使うまでに値が安定すれば良いから 余裕があるかも知れない。ただ、規模が増えたりすると面白くないし ... 実際に比べてみて決めるつもり。あと、ちょっと面倒 ステートが S1 → S2 に移動するから 状態用のラッチもいるし。

    ちなみに avr-gcc には、 -mmorder1 とか -morder2 とかのオプションがある。同じようにして、ldd/std に対応させるようなオプションは作れそう。ただ binutils も 対応しないといけない。

    binutils -- いじっていたら 1word lds/sts がサポートできそうな感じになってきた。これなら..というわけで バイナリを全部作りなおすことに。パッチも整理する。

暫定的に AVR_Toolchain-20110408 版を置いた。

    rtavr-wk08a の tb_int0 ぐらいは問題なくビルドできる。tb_queen も rom_data が作れるようになった。
    だが、

    queen_r.c:(.text+0xa): warning: internal error: out of range error
    queen_r.c:(.text+0x10): warning: internal error: out of range error
    queen_r.c:(.text+0x70): warning: internal error: out of range error
    queen_r.c:(.text+0x80): warning: internal error: out of range error
    queen_r.o: In function `main':
    queen_r.c:(.text+0x282): warning: internal error: out of range error
    c:/avr_toolchain/bin/../lib/gcc/avr/4.4.3/../../../../avr/lib/avrtiny10\libc.a(printf.o): In function `printf':
    /xhl1/as5/avr-libc-1.7.1-fix2/build2/avr/lib/avrtiny10/../../../../libc/stdio/printf.c:44: warning: internal error: out of range error

    なんてエラーが出ている。まだまだ。

    ところで、ldd/std のテスト。tb_008 の後ろに追加。



    Z に 0x60 を入れて +2/+3/+4/+5 のオフセットで std して ldd しているのだが、動いている。実際の AVR は 2 クロックかかるが、こいつは (無条件で) 1 クロック。使いこなせれば強力かも知れない。

    テストしていて思ったのだが、Tiny40 は 256B の RAM がある。なのに LDS/STS のアドレススペースは 7bit 。下位 128B しかアクセスできない。LDD/STD を完全に置き換えるなら 8bit 分確保できるのに。

    これは! 今回作ったような 半分しか OFFSET できない LDD/STD を入れる構想がもともとあったのでは?

    ldd/std のテスト(gcc)

    ちょっと ldd/std を本当に使うかテストしてみた。

    .global aaa
    .type aaa, @function
    aaa:
    /* prologue: function */
    /* frame size = 0 */
    mov r30,r24
    mov r31,r25
    std Z+1,__zero_reg__
    /* epilogue start */
    ret
    .size aaa, .-aaa

    これは、-mmcu=rtavr40 -mreduced-lddstd -Os でコンパイルした場合。
    引数を Z に移して +1 のアドレスに 0 を入れている。ちゃんと使ってくれている!


    .global aaa
    .type aaa, @function
    aaa:
    /* prologue: function */
    /* frame size = 0 */
    mov r30,r24
    mov r31,r25
    subi r30,lo8(-(1))
    sbci r31,hi8(-(1))
    st Z,__zero_reg__
    subi r30,lo8(1)
    sbci r31,hi8(1)
    /* epilogue start */
    ret
    .size aaa, .-aaa

    -mmcu=rtavr40 -Os だけだと、こう。 std Z+1,__zero_reg__ を 5 命令で置き換えている。最適化オプションを変えても無意味。なぜなら、この 5 命令が 1 単位になっているから。

    せめて後ろの 2 命令は捨てれば良いのにと思うし、実際そういう最適化もあちこち入っているのだが... このケースには入っていないのかも。

AVR_Toolchain 20110409

    out of range error が直った。これでいく。

08B



  • rtavr-wk08b.tar.gz

    上で書いた変更を反映した。
  • tb_008 に ldd/std テストを追加。
  • ldd/std の 変位の加算

    あと、テストのためのプログラム tb_shuffle 追加。乱数のようなもので 種を元にデータを変更していく。演算の種類を増やしたものを適当に作ってみた。どこかでループしていたりするかも知れないが乱数自体が目的ではないので気にしない。

    PC で検算できるようにしておいた。すくなくとも結果が同じでないとダメなわけだから 演算結果をダンプしてみようと思う。

08C



  • rtavr-wk08c.tar.gz



    tb_shuffle をテストしたが、shuffle_init からリターンするときに 0x555 に飛んでいってしまうというバグがまず見つかった。

    調べたところ、

    b4: 40f0 sbci r31, 0x00 ; 0
    b5: 9508 ret

    0000016c <shuffle>:
    b6: 930f push r16
    b7: 931f push r17
    b8: 93df push r29

    ここが該当する ところで、ret の後 push が続くと 起きるバグということが分かった。

    push 命令が無効化されているのに v_ea_push が 1 になってしまうために、v2_ea の計算が push 用になってしまうのが原因。



    08C ではこのように直った。

    だが、2つ目のバグがすぐ見つかる。



    次は、shuffle() のリターンで。0x25E に飛んでいくバグ。期待されるのは、0x05E。
    これは、上位バイト のメモリ(0x162)に 0x62 が書き込まれている ためと分かった。



    さらに調べると。ここで 0x62 を書き込んでいる。... ここまでは分かった。が、なぜなのかサッパリ分からない。gcc のバグかも知れないし。

    ... これは、AVR Studio のシミュレータでどうなるか追ったほうが良いかも知れない。

      動き切って結果が違うのなら、実機を使ったり 、別のマシンで検証用の計算結果を作ったりすることができるが、アドレス計算がもとで動かないのであれば、その方法は使えない。

      simulavr を使うという手もあるが、動ききりさえすれば、検証するだけで済むから、今回だけ なんとか凌ぎたい。手間からいうと AVR Studio のシミュレータの方がてっとり早そうだ。

      こんなことが何度も起きるようなら、ちょっと方法を考えようと思う。案としては、simulavr をベースに トレースするようなものを作る。

    実際に AVR Studio 4.18SP3 までインストールしてみたところ

    • SP3 は、"Simulator 2" で attiny40 をサポートしていた。
    • 違うパスの avr-gcc を使うことができるようになっていた。

    ... というわけで、変更なしに シミュレータにかけることができることがわかった。ソースから生成しても 同じ HEX になることを確認して

    Debug → "Start Debugging"

    とすると Disassenbler リストが出てきた。この上で 動かしていくことができる。
    まずは、走り切るのかどうか。

    • 1 回目の shuffle() call すら無限ループらしく戻って来ない。

    どうも動かないものが出来てしまったようだ。途中まで テストはできるが ... その先が困ってしまう。なかなかに、厳しい。

      ちょっとやってみよう。


      0xc6 まで AVR Studio で動かしてみた。

      これに対応する。rtavr のシミュレーション結果



      ざっと見て、Stack Pointer が 0x106 で EA として出ている Z ポインタが 0x128 で一致していることは分かる。あと、この時点で 263 命令をこなしている。

      ここから 1 命令づつ追っていけば .. どこかで正しくなくなるはず。 根気よくやれば、必ず分かるのである。... だが、どこまでやれば良いのか

      もう少し進めて 0x220 で止めてみた。




      X ポインタから r24/r25 をロードしているが、正しい値は 0x5c/0x42 だが、ロードしている値は、0x00/0x42 。SP/X は正しい値。

      ここで既におかしくなっている。... のだが、実行命令数は 2780 。ちょっと多すぎる。やはりシステマティックにやらないとやってられない。

    さて、どうしよう。

    • rtavr 側では、SP / SREG は いつでもわかる(ようにできる)。だが レジスタの内容は直接は分からない。
    • 分かるのはせいぜい Rd を指定して書き込んだ値。
    • メモリの内容もわからない。... が、書き込んだ値は分かる(ようにできる) 。

    ここから考えて ...

    • rtavr.v から GPR と RAM を切り離し test bench の方で 組み込んで Watch できるようにする。
    • あるいは、切り離さずに Watch するデータを output として test bench で見えるようにする。
    • test bench では、$fdisplay() とかを使って Watch するデータを契機に ログを取る。
      取るログは、SREG と SP あと レジスタに ロードした値 (+レジスタ番号) と メモリにストアした値。(+EA )

    • このログに合わせたデータを シミュレータから取り出す。

    • 検証は、形式を合わせて diff をとれば良い。

    • こういうことをしたいなら、simulavr を使うのが 良さそうだ。... が、attiny40 には対応していないように見える。改造しないと。

    • simulavr は、python 以外に TCL にも対応しているそうだ。それを使うという手もある。... だがどうせ改造するなら、ダイレクトに log を取るコードにしてしまうのも手かも知れない。


    L r17 = 00 : PC 0013 FLAGS 02 SP 0000
    L r29 = 3f : PC 0015 FLAGS 00 SP 0000
    L r29 = 01 : PC 0016 FLAGS 00 SP 0000
    L r26 = 00 : PC 0019 FLAGS 02 SP 013f
    L r27 = 60 : PC 001a FLAGS 00 SP 013f
    L r30 = 00 : PC 001b FLAGS 02 SP 013f
    L r31 = 06 : PC 001c FLAGS 00 SP 013f
    L r16 = 45 : PC 001d FLAGS 00 SP 013f
    L r26 = 00 : PC 0024 FLAGS 02 SP 013f
    L r27 = 60 : PC 0025 FLAGS 00 SP 013f
    L r16 = 00 : PC 0026 FLAGS 02 SP 013f
    S mem[0060] = 00 : PC 0028 FLAGS 2d SP 013f
    S mem[0061] = 00 : PC 0028 FLAGS 2d SP 013f
    S mem[0062] = 00 : PC 0028 FLAGS 2d SP 013f
    S mem[0063] = 00 : PC 0028 FLAGS 2d SP 013f
    S mem[0064] = 00 : PC 0028 FLAGS 2d SP 013f
    S mem[0065] = 00 : PC 0028 FLAGS 2d SP 013f
    S mem[0066] = 00 : PC 0028 FLAGS 2d SP 013f
    S mem[0067] = 00 : PC 0028 FLAGS 2d SP 013f
    S mem[0068] = 00 : PC 0028 FLAGS 2d SP 013f
    S mem[0069] = 00 : PC 0028 FLAGS 2d SP 013f
    S mem[006a] = 00 : PC 0028 FLAGS 2d SP 013f
    S mem[006b] = 00 : PC 0028 FLAGS 2d SP 013f

    こんな風にレジスタ、メモリの変化をログすることは出来た。ただし注意点があって、(1) PREDEC/POSTINC による変化はログされない。(2) PC は常に +1 の値。

    面白いことに、bss のクリアは PREDEC/POSTINC を使っているので、(ログでは)レジスタの変化なしで連続にメモリに書き込んでいる。ループの終了判定もフラグだけ変化させる cpi/cpc なのでレジスタの変化なし。

    改造した結果バグが入っていないことを検証するなら自己完結するのだが、正しいかどうか検証するなら他のもので同じデータを作らないといけない。

    さて、検証用データをどう作ろう。... って simulavr でなんとかするつもりだが、別の方法もある。たとえば、kx_avr など他のコアを rtavr に組み込むとか。.. 想定のうちだからなんとかなるかも知れない。組み込めてしまえば、同じ test bench が使える。

    さて、どっちが楽だろう?

    あ、kx_avr は xilinx のプリミティブ使っているから無理。AVR Core は VHDL だし。... ちょっと厳しいか。

    simulavr を見てみる。...

    AVR Core の部分は、decoder.c のようだ。でこんな風に使っている。

    opi = decode_opcode (opcode);
    result = opi->func (core, opcode, opi->arg1, opi->arg2);

    ざっとみたところ 命令は、16bit 長だから 65536 個の配列を作っておいて、そこに opcode に対応する関数を登録しているようだ。で、table 引き 1 回で済ましている。

    decoder.c だけ使ってあと適当にでっち上げても動かせそうな感触。ちょっとやってみるか。

    L r17 = 00 : PC 0013 FLAGS 02 SP 0000
    L r28 = 3f : PC 0015 FLAGS 00 SP 0000
    L r29 = 01 : PC 0016 FLAGS 00 SP 0000
    L r17 = 00 : PC 0019 FLAGS 00 SP 013f
    L r26 = 60 : PC 001a FLAGS 00 SP 013f
    L r27 = 00 : PC 001b FLAGS 00 SP 013f
    L r30 = 06 : PC 001c FLAGS 00 SP 013f
    L r31 = 45 : PC 001d FLAGS 00 SP 013f
    L r18 = ff : PC 001f FLAGS 00 SP 013f
    S mem[060] = ff : PC 0020 FLAGS 00 SP 013f
    L r17 = 00 : PC 0024 FLAGS 00 SP 013f
    L r26 = 60 : PC 0025 FLAGS 00 SP 013f
    L r27 = 00 : PC 0026 FLAGS 00 SP 013f
    S mem[060] = 00 : PC 0028 FLAGS 00 SP 013f
    S mem[13f] = 2b : PC 002c FLAGS 35 SP 013f


    なんかでっち上げることが出来た。使わない命令は全部削ってしまって 1word LDS/STS と reduced LDD/STD に対応。IOR は SREG と SP のみ。当然割り込みもない。

    フォーマットもすり合わせた。($fdisplay は fprintf とちょっと違うようだ)

      あと PC は 2 進んでいるようだ。ROM のアドレスは 既に次の fetch アドレスになっている。

    rom ファイルは、rom_data.mem があるのを前提にできるから 安易に fscanf で読み込む。

    使い方は、

    avr8l_trace [-l loops] [-f rom_file]

    パラメータなしだと rom_data.mem を読み込んで 200 回実行する。

    それは良いのだが、結果が全然違う。まぁどちらかがバグっているわけで、同じになるようにデバッグしていけば良い。必ず先に進めるわけでだいぶ気が楽になった。

      2 行目 L r28 のレジスタ番号が違っているのは、レジスタ番号を適当に出力していたから。そもそも書き戻す番号は GPR から外に出ていない。.. 対応したら合った。

      PC が +2 も進んでいると 見るとき混乱するし バグっているかも知れないので、test bench 側でラッチして正しい PC にすることにした。

      4/6 行目 FLAG が 02 になっている。ldi が並んでいるところだし。これは.. ldi で Z flag を変化させていたバグ。CMD_OP3_WRF というのを前に作ったので同様に CMD_OP2_WRF を新設。

      ... とまぁこんな感じでデバッグが進む。

      その後の動きが違うのは、decoder に提供したプリミティブ関数がバグったため。

08D



  • rtavr-wk08d.tar.gz

    すぐバグが見つかるのだが、ここまでを一旦スナップショット。


    バグは、0x60 から連続でメモリに書きこんでいるところ。フラグが 0x2d になっているが 0x35 が正しい。
    フラグは、cpc 命令での変化で、S と V が違う。V が違えば S も違うので V の計算が原因。
    調べたら確かに間違い。修正できた。

    次に 0x4e あたり フラグが 0x81 になっているが 正しくは 0xa1 。似た様な感じで SUBI での H flag の計算のバグ。

    続く MOV で今度はフラグを変更してしまっている。これは、OP3_WRF の条件の間違い。

    さて、これを直して diff を取ってみたら今度は SP がずれているのが見えてきた。


    (avr8l_trace)
    < S mem[013f] = 2b : PC 002a FLAGS 02 SP 013f
    < S mem[013e] = 00 : PC 002a FLAGS 02 SP 013e
    < S mem[013d] = 00 : PC 0038 FLAGS 02 SP 013d
    < S mem[013c] = 00 : PC 0039 FLAGS 02 SP 013c
    < S mem[013b] = 01 : PC 003a FLAGS 02 SP 013b
    < S mem[013a] = 3f : PC 003b FLAGS 02 SP 013a
    (rtavr)
    > S mem[013f] = 00 : PC 002a FLAGS 02 SP 013e
    > S mem[013e] = 2b : PC 002b FLAGS 02 SP 013d
    > S mem[013d] = 00 : PC 0038 FLAGS 02 SP 013c
    > S mem[013c] = 00 : PC 0039 FLAGS 02 SP 013b
    > S mem[013b] = 01 : PC 003a FLAGS 02 SP 013a
    > S mem[013a] = 3f : PC 003b FLAGS 02 SP 0139

    AVR Studio に判定を仰ぐと 0x13f には 0x2b が 入るのが正しい。が、0x3b 終了時の SP は 0x139 が正しい。後者は、avr8l_trace でメモリに書く前に SP を変更しておかないと 表示が合わなくなるという問題。

    前者には問題が 2つ rcall での HI-byte と LO-byte の積む順番が逆ということと、PC 。rtavr では PC を進ませているが、 avr8l_trace では、進ませずに一気に書く。PC は表示だけの問題だから rtavr に合わせるよう細工する。

    これを直すと次は

    > L r31 = 00 : PC 0041 FLAGS 00 SP 0139
    39a41
    > L r23 = 80 : PC 004b FLAGS 00 SP 0126

    なんだろう?
    実際のトレースは、

    L r28 = 26 : PC 003e FLAGS 00 SP 0139
    L r29 = 01 : PC 003f FLAGS 00 SP 0139
    L r18 = 00 : PC 0040 FLAGS 00 SP 0139
    L r31 = 00 : PC 0041 FLAGS 00 SP 0139 <<<
    L r24 = c0 : PC 0045 FLAGS 00 SP 0126
    L r23 = 80 : PC 004b FLAGS 00 SP 0126 <<<
    L r16 = 26 : PC 004c FLAGS 80 SP 0126

    コードはこれ。

    3e: 51c3 subi r28, 0x13 ; 19
    3f: 40d0 sbci r29, 0x00 ; 0
    40: b72f in r18, 0x3f ; 63
    41: 94f8 cli
    42: bfde out 0x3e, r29 ; 62
    43: bf2f out 0x3f, r18 ; 63
    44: bfcd out 0x3d, r28 ; 61
    45: ec80 ldi r24, 0xC0 ; 192
    46: bf8a out 0x3a, r24 ; 58
    47: 98ea cbi 0x1d, 2 ; 29
    48: 9ae2 sbi 0x1c, 2 ; 28
    49: 9a58 sbi 0x0b, 0 ; 11
    4a: 9a60 sbi 0x0c, 0 ; 12
    4b: 9478 sei
    4c: 2f0c mov r16, r28

    なるほど、cbi/sbi は ロードしてストアしているが、なぜか レジスタへのロードもしているわけか。
    -- 修正。

    次、

    < S mem[0139] = 00 : PC 005a FLAGS 80 SP 0126


    59: 2f90 mov r25, r16
    5a: 8b4b std Y+19, r20 ; 0x13

    LDD/STD がバグっていた。-- 修正。



    < L r20 = 40 : PC 0104 FLAGS 99 SP 0106
    ---
    > L r20 = 02 : PC 0104 FLAGS 99 SP 0106

    ... 値が全然違う。

    103: 9555 asr r21
    104: 9547 ror r20
    105: 956a dec r22



    これ、間違っていたので直そうとしたが、まだダメ。フラグは posedge で変化するので GPR が取り込むタイミングで値が変わってしまっている。ROR などで C_in を GPR が 取り込む場合 ラッチしておかないとダメなようだ。これは直すが、他に該当するものがないか気になる。


    < L r27 = 00 : PC 010f FLAGS a1 SP 0106
    < L r20 = 00 : PC 0110 FLAGS a1 SP 0106
    < L r21 = 00 : PC 0111 FLAGS a1 SP 0106
    ---
    > L r27 = 00 : PC 010f FLAGS a3 SP 0106
    > L r20 = 00 : PC 0110 FLAGS a3 SP 0106
    > L r21 = 00 : PC 0111 FLAGS a3 SP 0106

    10d: 1fbb adc r27, r27
    10e: 5aa0 subi r26, 0xA0 ; 160
    10f: 4fbf sbci r27, 0xFF ; 255
    110: 914d ld r20, X+


    これは ... Z flag が立ってしまっているわけだが、SBC/SBCI は 前の状態が Z=1 でなければ 0 になってしまう仕様だった。... 知らなかった。--- 直した。



    < S mem[0106] = 39 : PC 0138 FLAGS 80 SP 0105
    < S mem[0105] = 01 : PC 0139 FLAGS 80 SP 0104
    ---
    > S mem[0106] = 39 : PC 0138 FLAGS 80 SP 0105
    > S mem[0105] = 00 : PC 0139 FLAGS 80 SP 0104

    rcall での PUSH 。HI/LO 入れ替えた修正をしたが失敗。

    これを直す前に... 気分転換に規模確認。

    50A 用の設定で 711 ... 厳しくなった。が、動かす方が先。

      修正の多くは、ロジックを増やすものではないので、あまり規模が増えるのを心配していないのだが、フラグの処理は別。ちょっと最適化を考えないと。

      LDD/STD をあきらめれば 10 スライスぐらいは減るが、なんとか残したい。

    次、

    < L r22 = b8 : PC 024b FLAGS ac SP 0104
    < L r23 = 84 : PC 024c FLAGS 8c SP 0104
    ---
    > L r22 = b8 : PC 024b FLAGS b4 SP 0104
    > L r23 = 84 : PC 024c FLAGS 94 SP 0104

    24b: 0f66 add r22, r22
    24c: 1f77 adc r23, r23

    V flag (overflow) の計算式が add と sub で違った。前の修正は add 用を sub 用に変えただけだった。


08E



  • rtavr-wk08e.tar.gz

    行き詰まった。とりあえず、スナップショット。


    < L r18 = b8 : PC 0249 FLAGS 94 SP 0104
    < L r21 = 84 : PC 024a FLAGS 94 SP 0104

    < L r18 = 38 : PC 0249 FLAGS 99 SP 0104
    < L r21 = d0 : PC 024a FLAGS b4 SP 0104

    < L r25 = d0 : PC 0255 FLAGS 82 SP 0104
    < L r24 = 38 : PC 0256 FLAGS 82 SP 0104
    ---
    > L r25 = 00 : PC 0255 FLAGS 82 SP 0104
    > L r24 = 00 : PC 0256 FLAGS 82 SP 0104

    247: ff80 sbrs r24, 0
    248: c002 rjmp .+4 ; 0x496 <__mulhi3_skip1>
    249: 0f26 add r18, r22
    24a: 1f57 adc r21, r23

    00000496 <__mulhi3_skip1>:
    24b: 0f66 add r22, r22
    24c: 1f77 adc r23, r23
    24d: 1763 cp r22, r19
    24e: 0773 cpc r23, r19
    24e: 0773 cpc r23, r19
    24f: f029 breq .+10 ; 0x4aa <__mulhi3_exit>
    250: 9596 lsr r25
    251: 9587 ror r24
    252: 5080 subi r24, 0x00 ; 0
    253: 4080 sbci r24, 0x00 ; 0
    254: f791 brne .-28 ; 0x48e <__mulhi3_loop>

    000004aa <__mulhi3_exit>:
    255: 2f95 mov r25, r21
    256: 2f82 mov r24, r18
    257: 9508 ret

    問題の場所。レジスタの変化がないのに、違うところが動いている。最後には <__mulhi3_exit>: には来る。

    cp/cpc が見えるし、レジスタやメモリに変化がなくても SREGが変化したり や PC が +1 でないケースもログを取るようにしている。だがこんな感じなのだ。

    ちなみに AVR Studio で 255 -- <__mulhi3_exit> まで実行させると 2679 サイクル。結構動いているかも。

    AVR Studio で確認したら 247 の sbrs でスキップしないと 次の rjmp で 249/24a の次に飛ぶ。3 回目の sbrs で スキップして 249/24a が初めて実行される。r24 の値は 0x11 。

    で、調べてみると skip 周りなにか変。そもそも s2 に入ってから skip することが分かっても遅い。副作用のある s0 があるから s1 で skip することが分かるようにするか skip 命令だと分かった時点で 副作用のある s0 を 止めておかないといけない。だが、現状そうなっていない。

      skip する命令は 3 つ

      ひとつはレジスタ。s2 のときにラッチするから s1 で分かる。あと CPSE 命令。Rd == Rr の条件だからこれも可能。

      あとひとつは 、IO レジスタ。これは? 1bit バスをもうひとつ作るか 時分割? どちらも嫌だ。

      一方、副作用のある s0 を 止める仕組みもある。だが、skip する場合 skip も遅らせないといけない。一応 2 word LDS/STS 用の仕組みはあるから拡張することはできる。

      どうやら 副作用のある s0 を 止める方が楽らしい。

      折衷案もあり得る。skip 命令を 2 種類作る。レジスタ系は S1 でやった方が規模が小さくなるような気がする。

      ... 検討してたはずなのに実装がおかしくなったのは、たぶん CPSE 命令に後で気がついたため。対応しようとして段々おかしくしていった気がする。

    ... というわけで じっくり検討しようと思う。

    skip 命令は一ヶ所しかないようなので、ちょっと後回し。

    次の問題は、 CPC で Z flag が立ってしまうもの。SBC/SBCI と同様だった。サイクル数 458。

    次。ANDI/ORI で C flag を 0 にしていた。サイクル数 507。

    次。BRTC で ブランチしない。C flag を 0 にしていた。サイクル数 828。
    これは、rcall の後ろにある 無効化された スキップ命令が、BRTC を無効化してしまっていたため。

    次。27b の com 命令のフラグが違う。サイクル数 1034。
    81 が正しいが 99 。V flag が間違っている。COM は無条件に 0 。

    次からは不定値が出だす。このまま続けてもどうせ int0 を起こして 結果が変わるので少しコードを変更。

    次のコードは、上記のバグが出た所。

    107: 2f94 mov r25, r20
    108: 7097 andi r25, 0x07


    265: 9508 ret
    000004cc <__divmodhi4_neg1>:
    266: f7f6 brtc .-4 ; 0x4ca <__divmodhi4_exit>
    267: 9590 com r25
    268: 9581 neg r24
    269: 4f9f sbci r25, 0xFF ; 255
    26a: 9508 ret

    278: 955a dec r21
    279: f7a9 brne .-22 ; 0x4de <__udivmodhi4_loop>
    27a: 9580 com r24
    27b: 9590 com r25
    27c: 2f68 mov r22, r24


    不定値は、00f1 の LD Z+ 。5 回目 で サイクル数 1244。アドレス(Z)は 0x130 。
    どうも 書きこんでいないエリアを初めてアクセスしたようだ。RAM に初期値 0 を設定することでクリア。

08F



  • rtavr-wk08f.tar.gz

    次 4 回目の 0x260 の and 。結果が 1 なのに Z flag が 1 になっている。既に 2000 命令を越えている。
    verilog シミュレータで該当箇所を探すのも億劫になってきた。

    とりあえず、スナップショット。この問題もあるが skip 命令も修正しないと。

    skip 命令を修正したら、仕切りなおして別記事にしようと思う。随分動くようになったし、テスト方法も新たなもの検討すべき時期になった。

    バグも沢山出ている。場当たり的に直してきたが、一度整理して 同じようなバグがないか 調べてみる必要もある。

    そういえば、avr8l_trace 仕様を変更した。-d オプションを付けると、フラグが変化したとき等のログを出力するようになる。ただ、これだと差分が大量にでるので標準にはしない。

(続く)
関連記事:

著作権について

    ここで提示しているコードは正しく動作しないとはいえ、既に著作権は発生しています。
    著作権は、すzが保持しており放棄はしていません。

    教育目的および私用目的では、もともと著作権の範囲外なので自由につかえます。また、ライセンスとして、GPL を適用しています。GPL に従う範囲において 個別の許可なく使用することができます。許諾のための連絡も不要です。

    なお、GPL なので、生成したバイナリを作り直せる範囲のソース開示が必要になります。FPGA だと 通常 チップ全体になってしまいます。また、開示したソースを GPL の範囲で再利用されることを妨げることはできなくなります。このコードを利用する場合この点に留意してください。

    個別の許可を得れば、GPL 以外の条件での使用は可能です。が、作業中のものには許可は出さない予定です。作業が完了(もしくは中断)したとき、ライセンスは見直します。

    なお、すでに公開してしまったものの、ライセンスを取り消すことはできないと考えていますが、ライセンスの追加は可能です。また、新しく公開するものについては、ライセンスしないことすら可能で、どのような制限もありません。変更する可能性がありますので、この点にも留意してください。
posted by すz at 21:22| Comment(0) | TrackBack(0) | AVR_CORE

2011年04月02日

AVR互換コア(設計メモ)

どのように設計してきたか過程は書いて来た。だが、知識がないところからスタートしているので色々変更することになった。結局どうしたのかは記事を見てもよくわからない。時が経つと自分ですら分からなくなってきそうなので、いくつかのポイントについて、ここでまとめていこうと思う。

2倍速 動作タイミング


当初 postedge のみとか 1 クロックのなかで固定のタイミングだけで動かそうとしていた。が、全然無理だった。理由は、分散メモリ・ブロックメモリを同期型として使うため。

それ以外に、2倍速のGPR もある。この 2倍速のクロックと 標準のクロックの間に若干のスキューがあることを前提にしている。GPR は、ちょっとばかりややこしいことになった。まとめておかないとマズそうな気がする。

    まず、標準速の CLK の生成について。
    2倍速のクロックを外部から供給して、1X の 標準のクロックを生成することにした。だから、2倍速のクロックの方のエッジが必ず先行する。


    ______ ______ ______ ______
    CLK2X ___| |______| |______| |______| |______

    (1) (1) (1) (1)
    _____________ ______________
    CLK ____________| |_____________| |___

    (2)--------------------------(2)


    (t1) (t2) (t3) (t4)

    ____________ _____________
    r_clk ____________| |______________|



    おおげさに書くとこう。CLK2X の negedge で CLK を駆動している (1)。で 標準クロックで動作する回路とインターフェイスする場合は、CLK の negedge でラッチする (2)。

    標準クロックで動作する回路から CLK2X 側が受け取る場合、基本どのエッジでも良い。標準クロック側は 基本 negedge で動作している。(t1)-(t4) 。

    ただし、演算結果とか遅延のあるものは、(t1) (t2) で受け取らない。

    逆に、CLK2X 側から信号を渡す場合、(2) のタイミングで確定していないといけない。(t4) で reg の値が確定しさらに遅延が加わるものは渡せない。

    ちなみに、フェーズを知るために posedge CLK2X で駆動する r_clk というのも使っている。冗長のような気もするが、現状はこうなっている。

    さて GPR からみて、実際どのようになっているか見てみよう。

  • input
    (t1) posedge r_clk==0 ADDRAH,ADDRBH,(ADDRAL)
    (t3) posedge r_clk==1 ADDRAL,ADDRBL, DI
    (2) CLK negedge ADDRBL,ADDRBH


    基本 (2) で変化する信号を 最初に受け取れるのは、(t1)。2X だからそれに (t3) を加えた posedge のタイミングが基本。(DI 以外の) これらの信号は、reg の出力そのものになるようにしている。(2) のタイミングのものは、標準クロックで動作する回路の延長とみなしている。

    DI は、演算結果。標準クロックの 3/4 で受け取るから、CLK2X 側の条件は厳しい。逆に 演算自体の 遅延の条件は緩くなっている。

    ちなみに、GPR を 標準クロックだけで組むと受け取るタイミングは、標準クロックの 1/2 である posedge CLK になるはずだ。演算側の遅延条件が厳しくなる。

  • output
    (t1) posedge r_clk==0 INDEX(lo)
    (t3) posedge r_clk==1 INDEX(hi),JA(hi)
    (t2) negedge r_clk==0 JA(lo)
    (t4) negedge r_clk==1 DOBH
    (2) DOBL

    s2_execute での Rd/Rr 入力である DOBH/DOBL はこのタイミング。DOBL は、ラッチしていて 標準クロックに同期済み。DOBH は (t4) だからスキュー分前にずれている。そうなると、遅延のない回路では negedge CLK では受け取れない。演算の入力や posedge CLK でラッチする書き込みデータに使うので問題ない。

    INDEX は S1 で実行アドレス(EA)として使う。JA は icall などの ジャンプアドレス(EA)。
    これらは、(2) のタイミングでラッチする。タイミングがさまざまだが、(t4) 以外なので問題ない。

標準クロック 動作タイミング



    ここから先は 2倍速のことは考えなくて良い。(2倍速と比べれば)簡単なのだが、注意すべきことはある。

    基本 negedge で動かすわけだが、ブロックメモリは、posedge 。あと周辺装置である IOR も posedge 。SREG や スタックポインタ(SP)も posedge 。


            | S0 | S1 | S2 |
    ______ ______ ______
    CLK  _____| |______| |______| |______|

    | | | | | |
    PC INST | | STORE |
    DECODE EXECUTE |
    EA GPR STORE
    GPR LOAD


    1つの命令の流れをかくとこんな感じ。PC は早くから決まっているが、1/2 CLK 後でようやく 使われて、一定遅延後 INST が出てくる。

    S0 は、ここからスタートして、デコードの一部 LD/ST の判断と PREDEC/POSTINC の指示を作る。

    S1 は司令塔で、それ以外の指示を全部作る。GPR のロードもこのとき(S1 の期間)。

    S2 では、EA は最初から決まっているが、アドレスデコードはそこから。RAM も 1/2 CLK 遅れで動作する。STORE の場合、用意したデータをこのタイミングで書くだけだから問題ない。LOAD の場合、1/2 CLK でロードを開始して 3/4 CLK で GPR に書き戻す。

    結構厳しいタイミングなので、RAM も 2X を使って 1/4 CLK で読み込み 3/4 CLK で書き込みとしたいところだが、対処していない。

      ブロックRAMは、200MHz で動作するはずで、ロードもかなり高速。分散RAM は普通のFF と同等でこちらも速い。だから大丈夫だとタカを括っているわけだ。対応策も検討しているわけで、対応は実際に困ったことになってからで良い。
      だが、標準クロックのGPR を作ってみようとしたとき、ここがネックになる。忘れないようにしないと。

        追記: ちょっとやってみた。試行錯誤したが、結局 CLK のかわりに CLK2X を使って、書き込みは CLK が H のときだけ というつくりにするのが良いという結果になった。

        読み込みを無駄に 2 回するわけだが、2回目の読み込み -- 3/4 のタイミングで違う値になったとしても その結果を反映するタイミングがそもそも存在しない。これで問題なさそうだ。

          この変更をすると、6 スライス減る。なぜなのだろう? (バグではなさそう)

          ついでに、SPH (スタックポインタの 上位 8bit) の bit 数を設定できるようにしてみた。変更するビット数を制限することで 最適化で消すやりかた。4 にすると 7 スライスも減った。2 だと 15 スライスも減る。0 に固定すると 19スライス。ぎりぎりになったときのために仕込んでおこう。

        ただ、こういう対処をするなら、標準クロックのGPR を作っても CLK2X 自体は必要ということになる。CLK2X をなくせないなら 標準クロックのGPR を作るのは あまり意味がなさそうな気がしてきた。

      ついでに、ブロックRAM について補足

      ブロックRAM を 標準の Verilog の記述だけで定義するには、かなりの制限がある。そして 失敗すると 分散RAM で実装しようとして いつまでも終わらない。18bit 分あるから 2bit 分を属性に使おうとしてみたがそれすら失敗して保留している。また 2 つのポートを 違うbit 幅にすることが可能だが 標準的な記述方法では定義できなかった。

      あと ブロックRAM は 同期式で posedge で駆動する以外のことはできない。だからタイミングを変える方法ば 2X で駆動するぐらいしかない。

      ブロックRAM は 初期値を与えて ROM として使える。初期値を与える方法は 2 通りあるが、データファイルを用意して $readmemh() 等で読み込むのがベスト。initial で値を代入する方法もあるが、最初に分散RAM で構成しようとするので 時間がかかる。

    IOR の方は、非同期動作で、S2 の先頭から読み込みを開始する。だから SBI/CBI で使う read-modfy-write ができる。

フラグ値の先取り(フォワーディング)



    SREG も IOR の一部であり、書き込むタイミングは postedge (STORE と同じ)。

    だが、SREG_IN / SREG_OUT は別パスで出ていて、バス経由で書き込む時以外は SREG_IN で常に更新している。

    SREG_IN を使って更新するのは、s2_execute 。参照するのは、条件分岐命令とスキップ命令。

    スキップ命令は、現在の s2_execute の結果で現在実行中の s1_decode を 無効化する。条件分岐命令は、現在の s2_execute の結果で 次のサイクルの s1_decode を 無効化する。(それに加えて分岐も実行)。

演算結果の先取り(フォワーディング2)



    s2_execute の後半 で 演算結果を書き戻すが、それと平行して GPR はレジスタ値(Rd/Rd)を 読み出そうとしている。

    GPR は 2倍速であり 読み出しの 1 つは、前半で終わっている。順序が逆だから間にあうはずもない。
    どうやるかというと、いま書き込むデータが 読み込んだレジスタのものならば セレクタで 書き込んだデータに置き換える。これが使えるのは、DOBL/DOBH 。これは、Rd/Rr と icall などの ジャンプアドレス(JA)。あと PREDEC/POSTINC なしのときの インデックスレジスタ(X/Y/Z) に使っている。

    PREDEC/POSTINC ありの場合は、どうやっても無理で、命令自体を止める(パイプラインストール)。
    止めれば競合が解消するわけで、問題を回避できる。

POSTDEC/PREINC とそのキャンセル(パイプラインストール)



    そもそも PREDEC/POSTINC と 演算結果の書き戻しは同時にできない設計になっている。更新できるのは、2 レジスタまでで、PREDEC/POSTINC は 2 レジスタを更新する。

      PREDEC/POSTINC では 1 レジスタしか更新しない場合が 圧倒的に多いので、当初の計画では、演算結果の書き戻しをまず行って、その後 2 レジスタを更新するケースでのみ、パイプラインストールさせようとしていた。が、無理があった。たとえ計画どおり作れてもそれでクロックを上げられないことになるなら本末転倒なのでやめた。

    このしくみは、次のようにして実装している。

    • s0_fetch では、部分的にデコードしているが、PREDEC/POSTINCの LD/ST だと分かると S0_WB_INST=1 として s1_decode に知らせる。
    • s1_decode では、今デコードしている命令が GPR への 書き込みをするものならば、パイプラインストールすることに決めて s0_fetch には S0_VALID=0 で通知する。
    • s0_fetch では、PREDEC/POSTINC を S0_VALID でマスクしていて、GPR への指示をとりやめる。s0_fetch の副作用がある動作はこれだけなので、他は気にする必要はない。

    ついでに補足

    AVR 互換コア命令の所要クロック数はおおむねオリジナルの AVR8L と同じにしている。AVR8L は単純化されていて mega や tiny よりクロック数が少ないものがある。

    そして LD/ST 命令の所要クロック数は、オリジナルよりさらにすくなく 基本1クロック。2 クロックかかる場合があるが、それは上で説明したケースのみ。

追加: POSTDEC/PREINC と SKIP 命令

    テストの段階で設計ミスしていたことに気がついた。

    条件分岐命令や JUMP/CALL などは、S1 実行中に 次の命令の S0 を無効化している。これに対して SKIP 命令は、S2 実行中に 次の命令の S1 を無効化する設計になっている。(それすらバグっていたが)

    設計ミスというのは、POSTDEC/PREINC があっても S0 が実行されてしまうこと。

    対処する方法は色々考えられる。

    • SKIP 命令 での判断を S1 でやってしまう。-- (1)
    • SKIP 命令 であれば SKIP する/しないに関わらず パイプラインストールさせる。-- (2)
    • POSTDEC/PREINC のキャンセルを S1 でできるようにする。-- (3)

    (1) について:
    SKIP 命令の SKIP 条件には次の 3 つがある。レジスタの 指定ビット , 2 つのレジスタの一致 , IOレジスタの指定ビット。

    現状どれも S2 でしかアクセスできない。レジスタに関しては もともとフォワードする仕組みがあるので 信号を引き出すだけで対処できる。だが、 IOレジスタは(擬似)バスアクセスであり、 アクセスパスがない。1 bit のパスを付けて dual-port 化 する方法はあるが規模が増える。時分割で dual-port 化 する方法もある -- IOレジスタに対する アドレスを 1/4 クロック 前にずらせば良い。ただ IOレジスタのアクセスは セレクタの塊だから ボトルネックになる可能性がある。フォワードしなくとも 前にずらしたいところだ。

    (2)について:
    SKIP 命令 + POSTDEC/PREINC 付き LD/ST命令 の組み合わせだけが 1 クロック余計にかかる。出現確率からいうと 性能にほとんど影響がない。
    パイプラインストールについては、仕組みはあるので条件追加で済む。ただ、SKIP する場合 2 クロック分 SKIP させなければならない。これは、2 word 命令の SKIP と同じようなもので、(後述の)2word LDS/STS 命令用の処理に条件を追加すれば良い。

    (3)について:
    POSTDEC/PREINCの処理自体は S1 である。すみやかに(1/4 クロック以内)条件が確定するのであれば S1 に入って からのキャンセルも可能だ。(1) が可能ならば、(3) は当然可能でありむしろ条件が緩い。

    対処方法としては (2)が一番簡単であり これをまず選択する。だが、(3) も 魅力がある。(3) という選択をした上で IOレジスタに対する アドレスを 1/4 クロック 前にずらしたり、レジスタの条件を S2 開始時にラッチしてしまうのも (遅延)性能的に有利になるかも知れない。回路規模も増えるとはかぎらない。

    他の方法もいずれ検討したい。

2word LDS/STS 命令



    avr-gcc (というか binutils) で 1word LDS/STS 命令に対応できていないことが分かったので、急遽 2word LDS/STS 命令を作って バグ付きの gcc に対応できるようにしている。

    2word 命令は、これだけ。で、条件スキップ命令では、2word 目もスキップしなければならない。後で気がついて対応コードを入れたが未テスト。

    その後、AVR_Toolchain を改造していくことにした。まだ出来てはいないが 1word LDS/STS に対応する。

    対応すると、全部 1word LDS/STS にしてしまうので、2word LDS/STS 命令 は使えなくなる。だから この追加を enable にするのは 推奨しない。

    ちなみに LDS/STS できるのは、RAM の 下位 128B のみ。それより後ろに変数を置くことはできるが、ポインタを使ったアクセスしか出来なくなる。ポインタへの代入は LDI 命令になるので シンボルも使える。

縮小 LDD/STD 命令



    avr-gcc の命令生成部を 直しているのだが、LDD/STD 命令 が便利そうに見える。AVR8L はレジスタが少なく スタックに変数を置くことが多くなるが、ロード・ストアに 5 命令かかる。フレームポインタの Y に offset を加算してアクセスして 元に戻すために減算するわけだ。ADIW/SBIW 命令も使えないから ポインタの加減算 は 2 命令。

    アクセス可能な範囲が 0 - 63 までという条件があるが、LDD/STD 命令 だと 1 命令でできる。AVR8L は命令効率が悪いからこれだけでも欲しい。

    LDD/STD 命令 と 1word LDS/STS 命令 は命令スペースが重なっているが、検討したところ、オフセットを 0 - 31 に制限すれば 両立可能なことが分かった。

    なので、2word LDS/STS 命令などより、こっちを積極的にサポートすることにした。

    実装してみたが、基本的な動作はできている。あと実際の AVR は 2 クロックかかるが、こいつは (無条件で) 1 クロック。
    サポートするための規模の増加は、17 スライス。50A にも入る。

    あと 自製 avr-gcc でこの命令を使うには、オプションが必要。

    avr-gcc -mmcu=rtavr40 -mreduced-lddstd

    とやって使う。
    avr-objdump での逆アセンブルには、

    avr-objdump -mavr201 -d xxx.o

    とする。そうしないと 1word lds/sts まで ldd/std として出力される。

タイムチャート


どの期間信号が有効で、いつ取り込むかについてのタイムチャートを作ったので添付しておく。

取り込むタイミングが、なかなか複雑なことになっている。

0/4 1/4 2/4 3/4 0/4 1/4 2/4 3/4 0/4 1/4 2/4 3/4 0/4
| S0 | S1 | S2 |
__ _____ _____ _____ _____ _____ _____
|_____| |_____| |_____| |_____| |_____| |_____| |___
__ ___________ ___________ ___________
|_____________| |____________| |____________| |

PM_OUT =========================
ADDRAL =========================
ADDRBL =========================
@
ADDRAH ========================
ADDRBH ========================
@
INST ========================
DI(N-1) ................========
@
| | | |
INDEX(lo8) =========================
INDEX(hi8) =========================
JA(lo8) ......===================
JA(hi8) ......===================
@
| | | |
DOBL ========================
DOBH ========================
CMD_XX ========================
EA ========================
@
| | | |
__ _____ _____ _____ _____ _____ _____
|_____| |_____| |_____| |_____| |_____| |_____| |___

XX_DOB .....===================
DI .............===========
@
| | | |
PC =========================
@
PM_OUT(N+1) =========================

デバイス関係



    ちょっと上記とは内容がちがうが、ここにメモ。

    モデルとした AVR は ATtiny40 。avr-libc のヘッダファイルとかを流用できるようにしようと考えてこれをモデルにした。ただし、互換は目指していない。... というより無理。ADC/AIN などはなから無理だし。
    そのうえ USART も付けたい。

    現状は、gcc のビルドも出来ていて、rtavr 専用のヘッダも用意する方向で考えている。専用のヘッダを使った場合、rtavr を自由に config して、それに ヘッダを合わせるようなことも検討中。

    それでも モデルは ATtiny40 なのだ。何を付けて、何はサポートしないかちょっと書いておく。

  • (削除) ADC , AIN → (追加) USART

    ADC, AIN を削除して、空いたアドレスや割り込みを使って USART を入れる。

  • (変更) PORT 新インターフェイス → 旧インターフェイス

    新インターフェイスは機能が増え プルアップ専用の PEUx も追加になっている。
    これを普通の インターフェイスに変更。できたら新インターフェイスも作ってみたいが、多分不可能。

  • (削除) PCINT0/1/2 → (追加) INT1

    割り込み線の PORTへの割り当ては自由にできるので、PCINT0/1/2 を廃止して INT1 を追加。
    必要なら INT2/3 も追加することを検討する方針。

    GIFR/GIMSK は bit1 に割り当て。ISC1 は、MCUCR の上位から割り当て (bit5,4)

      MCUCRには BOD の許可や SM(スリープモード), SE(スリープ許可)がある。これらの機能を全部無視すれば、4 つの ISC を入れられる。ただ、SLEEP 命令はいずれサポートしたい。SM や SE が欲しくなるかも知れないので INT2/3 を入れるかどうかは保留中。


  • (削除) OSCCAL , CLKPSR, CLKMSR , PRR , QTCSR , NVMCMD, NVMCSR , PCMSK0/1/2

    このあたりは、全く対応するつもりがない。というか対応出来ない。

  • (保留) WDTCSR , CCP

    WDT はいずれ対応したい。新インターフェイスとして CCP があるが、WDT しか対応しないのなら 旧インターフェイスにするかも。

  • (保留) Timer1

    単に 16bit 版の タイマーが欲しいだけなので、楽をして Timer0 をベースにしようかと。

  • (保留) TWI

    これまた、スレーブ専用の新インターフェイスになっている。

    ドライバも新規で書く必要があるのなら、既存のものを採用するかも。ただ、構想上、同じ線でマスターとスレーブ両方実装できる必要がある。

  • その他

    SPI と Timer0 は実装済み。基本はサブセットで細いところまで互換にするつもりはない。

関連記事:

著作権について

    ここで提示しているコードは正しく動作しないとはいえ、既に著作権は発生しています。
    著作権は、すzが保持しており放棄はしていません。

    教育目的および私用目的では、もともと著作権の範囲外なので自由につかえます。また、ライセンスとして、GPL を適用しています。GPL に従う範囲において 個別の許可なく使用することができます。許諾のための連絡も不要です。

    なお、GPL なので、生成したバイナリを作り直せる範囲のソース開示が必要になります。FPGA だと 通常 チップ全体になってしまいます。また、開示したソースを GPL の範囲で再利用されることを妨げることはできなくなります。このコードを利用する場合この点に留意してください。

    個別の許可を得れば、GPL 以外の条件での使用は可能です。が、作業中のものには許可は出さない予定です。作業が完了(もしくは中断)したとき、ライセンスは見直します。

    なお、すでに公開してしまったものの、ライセンスを取り消すことはできないと考えていますが、ライセンスの追加は可能です。また、新しく公開するものについては、ライセンスしないことすら可能で、どのような制限もありません。変更する可能性がありますので、この点にも留意してください。
posted by すz at 16:12| Comment(0) | TrackBack(0) | AVR_CORE

2011年03月23日

AVR Toolchain (その2)

AVR Toolchainの記事の続き

レジストしないと入手できないバイナリの一部としてしか AVR Toolchain のバイナリは存在しないようだ。
しかも全体が巨大になってきている。まだβだが、AVR Studio 5 なんて 500MB 超。さらに、avrtiny10 の対応がイマイチで、何度か入れ替えないといけなさそうな感じがする。

幸い、古そうなものの、パッチ集が見つかったので 自分でビルドしてみることにした。

    ビルドできるようになれば、自作 AVR Core 対応も盛り込める可能性が出てくるし、やってみる価値はある。

    Linux 専用なら問題ないだろうが、作りたいのは MinGW+MSYS 版。使っている環境はちょっと古いのでハードルが高いがまぁやってみよう。

    今の環境は、以前 mips 用に gcc-4.2.4 を ビルドしようとしたことがあって、mpfr や gmp 、gettxt はすでに入っている。だが、パッチは gcc-4.4.3 用で gcc-4.4.3 では、make-3.80 を要求される。

    以前試したときは、make-3.8x が動かなくて gcc-4.3 以降のビルドを断念した経緯がある。

準備



mpfr , gmp をスタティックリンクしているので、一応書く必要がある。

両方とも

./configure --prefix=/mingw
make; make install

これでインストールしている。使ったバージョンは、 mpfr-2.4.2 と gmp-4.3.2 だったり mpfr-2.4.1 と gmp-5.0.1 だったり。

gettext, libiconv については下記参照。

binutils



binutils のパッチは、binutils-2.20.1 に対するもの 。binutils-2.20.1 を入手しパッチを全部当てたものに

    ./configure --prefix=/c/AVR_Toolchain --target=avr --disable-shared \
    --disable-threads --enable-languages=c,c++ --disable-dssi \
    --disable-plugin

として ビルドを試みた。make doc できる環境がないので bfd でエラーになるが、bfd/Makefile を修正して先に進む。(doc po となっている所の doc を削除)

次に、ld でエラーになる。eavrtiny10.o がないという旨のエラー。これは、ld/Makefile.in に ルールが入っていないためと分かった。ちなみに ld/Makefile.am には入っているが、反映されない。
avrxmega7 と同じように avrtiny10 のルールを追加すれば OK だった。

これで ビルド完了。make install も OK 。

gcc



gcc のビルドで難儀していたのだが、ここに情報があった。

  • --with-build-time-tools が,忘れがちですけど重要
    (as や ld があるディレクトリを指定する模様)
  • make は、MSYSのバイナリを使え

とのこと。
ちなみに、atmel の avr-gcc に対して avr-gcc -v とすると それを作ったときの configure オプションが分かる。

make をインストールして

    ./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

とやって ビルドを試みた。

--enable-fixed-point したおかげで、巨大な libgcc.a が生成され時間もものすごくかかる。

さらに、ar でエラーになる。具体的には libgcc.a の生成が avr25 avr30 と成功して avvr31 で メモリ不足。(ヒープが取れない? Win32 error 487)

ググると

    regtool -i set /HKLM/Software/Cygwin/heap_chunk_in_mb 1024
    regtool -v list /HKLM/Software/Cygwin

という解決方法と rebase 使って

    rebase -b 0x30000000 /c/AVR_Toolchain/bin/msys-1.0.dll
    rebase -b 0x30000000 /c/AVR_Toolchain/avr/bin/msys-1.0.dll

とかする解決方法が見つかった。

regtool が見つからないし、見つかったとしても 使っている MSYS が 古いので不安がある。安全そうな後者を選んだ。だが、効果がないような? バイナリは出来ていて libgcc が作れないだけだしひどく時間がかかるので、linux 版からコピーすることに。

avr-libc


avr-libc のパッチは 1.7.0 に対するものが存在しているが、既に 1.7.1 が出ており、対応済みの模様。すなおに、1.7.1 を使う。


./configure --build=`config.guess` --host=avr --prefix=/c/AVR_Toolchain

configure はこう。

    (後述する) rtavr 対応のパッチを入れると (環境が古いので) autoconf / automake (と m4) のバージョンアップが必要になった。


ビルド完了



これでビルドすることができた。.. が多々問題があることが分かり自製パッチを作成してバイナリを作ることにした。

出来たもの


使ったパッチは、source ディレクトリに置いてある。あと Windows 版には 必要になった dll もいれてある。

dll のソースまで入れていない。元は上記から取ってきたもの。ひょっとしたら違う環境では足りない dll が出るかも。上記の Tree にあるはず。

ちなみに、lzma は、7-zip で解凍できる。

バイナリを使う人がいるかどうか分からないが、一応置いておく。

追記: avrtiny10 対応の分析

多くの人は興味がないだろうが、私に取っては最大の興味事。どんな風に対応しているのかちょっと見てみることにする。

まずは gcc 。avrtiny10 は、メインの パッチと 2 つの バグ修正パッチから成っている。

2441 54-gcc-4.4.3-avrtiny10.patch
43 55-gcc-4.4.3-avrtiny10-bug-12510.patch
48 62-gcc-4.4.3-avrtiny10-non-fixedpoint.patch

メインのパッチは、2441 行もあるが、その内訳は、

1680 gcc/config/avr/avr.c
98 gcc/config/avr/avr.h
31 gcc/config/avr/avr.md
443 gcc/config/avr/libgcc-fixed.S
160 gcc/config/avr/libgcc.S
29 gcc/config/avr/t-avr


md が少ないのは意外だが、プリミティブは avr.c に集約されている。

基本的に 元のコードを修正して attiny10 の場合はこう、そうでなければ元のコードという形が多い。だから純粋な修正量はパッチの 1/3 ぐらいに見積もれる。

libgcc-fixed.S は多いが、結局 disable にされていて使っていない。libgcc.S の修正は、sbiw を subi + sbci に直しているような所が多い。

    __do_copy_data のところは、まるごと disable にされている。これでは初期値がコピーされないが、後でちゃんと対応するつもりだったのだろう。

    良く見ると lpm Z+ 系のコードが(当然ながら)あって ld Z+ で同じようにできる。アドレスに 16384(0x4000) を加算するぐらいの変更で、対応コードを作れる。


さて、avr.c の修正を ちょっと見てみる。MAX_LD_OFFSET 周りが目立つ。 あとは、adiw/sbiw 対応とか。同じような修正が沢山入っているだけという印象がある。


感触としては、自分で直せそうな感じ。ただし、全然違う修正が必要になった場合だいぶ難儀しそう。

ところで、lds/sts 自体を変更しているところが見あたらない。どうも binutils の as で 対応することを期待しているように思える。
binutils にも

52-binutils-2.20.1-avrtiny10.patch

がある。内訳は次のようになっている。

11 bfd/archures.c
11 bfd/bfd-in2.h
15 bfd/cpu-avr.c
25 bfd/elf32-avr.c
50 gas/config/tc-avr.c
11 include/elf/avr.h
45 include/opcode/avr.h
22 ld/Makefile.am
0 ld/Makefile.in
12 ld/configure.tgt
16 ld/emulparams/avrtiny10.sh


ほとんどは、単に アーキテクチャの追加のためのコード。
lds/sts に対応するとすれば、include/opcode/avr.h になる。これには、なにやら修正は入っているのだが、対応は入っていない。

AVR_INSN (sts, "i,r", "1001001ddddd0000", 2, AVR_ISA_SRAM, 0x9200)
AVR_INSN (lds, "r,i", "1001000ddddd0000", 2, AVR_ISA_SRAM, 0x9000)

この部分だが、条件を変更しただけ。これよくよく見ると AVR_ISA_SRAM は、SRAM 持っているアーキテクチャの意味で 2word LDS/STS を持っていることになっている。

例えば、AVR_ISA_LDS1 と AVR_ISA_LDS2 を追加して、include/opcode/avr.h を定義しなおすことは比較的簡単そうだ。だが、それをパーズするコードが必要なわけで、gas/config/tc-avr.c と opcodes/avr-dis.c にコードを追加しなくてはならない。あと ldd/std と重なる部分があるから何か対応しないといけない。-- これはちょっと面倒そうなので、パス。

    一応調べてみると bfd_mach_avrtiny10 というのがあって、ディスアセンブルするときに、

    if (info->mach == bfd_mach_avrtiny10 )

    のようにして判断可能らしい。gas の方は、in/out に近い処理をすることになるはず。

    AVR_INSN (out, "P,r", "10111PPrrrrrPPPP", 1, AVR_ISA_1200, 0xb800)
    AVR_INSN (in, "r,P", "10110PPdddddPPPP", 1, AVR_ISA_1200, 0xb000)

    AVR_INSN (sts, "i,r", "10101AAAddddAAAA", 1, AVR_ISA_LDS1, 0xa800)
    AVR_INSN (lds, "r,i", "10100AAAddddAAAA", 1, AVR_ISA_LDS1, 0xa000)

    こんな風に定義しておいて、AAA.. の処理を PPP.. の処理を参考に作ることになる。

    ... と思ったらそんな甘いものではなかった。1 ワード lds/sts のアドレスは、リロケータブル前提だから、BFD_RELOC_16 のような リロケータブル情報が必要で、やってられない印象。rtavr は 2 word LDS/STS を標準にすることで 逃げておこう。

    もうちょっと見てみたのだが、いっそのこと使わないというのが楽そう。そうすると ldi x 2 + ld/st になるはず。レジスタを消費するが、2word が 3word になるだけだ。それをするためには、MAX_LD_OFFSET を 0 にしてしまえば良さそう。

      MAX_LD_OFFSET は、ldd とかの話で間違い。lds/sts は、gcc/config/avr/avr.c で 1/2/4 バイト用が定義されている。

      ldi r28/ldi r29/ld X+/ld X+/... といった命令列に変換しないといけなさそう。 それは良いのだが、それを定義する方法がイマイチ分からない。勝手に X 使ったらまずそうだし。

      あと、 1 ワード lds/sts に対応するとすれば、命令数のカウントの値を変えないといけなさそう。多くカウントする分にはバグにならないかも知れないが。

    逆に rtavr 専用のコードにするには、 MAX_LD_OFFSET を大きくしてしまう。これで 2KB までシンボルを割り当てられる。ただ、rtavr 専用のコード を作っても生き残らないだろう。とりあえず lds/sts を使わない方向で考えておく。

そんなことより、自作 AVRコア用の 定義を入れてみたい。せっかくソースコードがあるんだし。attiny40 を パッチで grep して同じように変更すれば良い。

まずは名前を決めなくては。rtavr で attiny40 もどきだから -mrtavr40 あたり? ついでだから generic な rtavr も入れておこう。あと define も __RTAVR_rtavr40__ あたりにしておくか。

avr-libc も attiny40 をゼロから入れているパッチがあるから参考になる。... と思ったら対応してなかった。まぁ 1.7.1 で attiny40 を grep すれば なんとかなる。... はずが...

結構たいへん。変更するところがかなりある。configure とかも変更しないといけないし。それと crtXX と ioXX.h の名前を決めないといけない。元が crttn40.o と iotn40.h なので 対応したものが crtrt40.o と iort40.h ということにしておこう。

追記: ちょっと N-Queeen をビルドしてみる。

kxavr の作者の kwhr0 さんのところの N-Queen をビルドしようと やってみた。

そうしたら 2種類 のエラーが出た。

  • relocation truncated to fit: R_AVR_7_PCREL against `no symbol'
  • libc/stdio/fputc.c:41: undefined reference to `__prologue_saves__'


    ほかの AVR_Toolchain ではどうなのか気になったので as4e-ide-2.7.0.851-linux.gtk.x86.zip に含まれる AVR_Toolchain (AVR_8_bit_GNU_Toolchain_3.1.0_200) で試してみたところ、全く同じエラーだった。

    ちなみに、 -S でアセンブラソースを作成してみると、出力コードもほぼ同じ。(自分で改造した 初期値のコピー関係だけわずかに違う)

    このコードのサイズは、874バイト。attiny2313 にターゲットを変更すると 400 バイトにまで減る。

こういう問題があることは分かった。そして 自分でビルドした AVR_Toolchain は、AVR_8_bit_GNU_Toolchain_3.1.0_200 相当だということも分かった。

さて、なにげに ここ に as5-beta の ソースが公開されている。

ちょっと見てみたが、binutils のパッチは デバイスの追加があるが基本変更なし。avr-libc は 1.7.1 に対するもので、新デバイス追加と ちょっとしたバグFIX -- avrtiny10 は無関係。

肝心の gcc は、4.5.1 対応になってしまった。..が、ざっと見たところ単純移植のような印象。まだまだのようだ。

N-Queen の命令を調べてみた。

    queen_r.c 自体はコンパイルできている。

    tiny2313 190 命令
    tiny40 427 命令

    これだけの差になったのだが、tiny40 の命令を見てみると sbci/subi だけで 197 命令。

    2 バイトの演算を置き換えているところが多かったからそれかとも思ったのだが、tiny2313 の adiw/sbiw は合計 3 、ldd/std は合計 10 。2 バイトの演算はこれぐらいしかないのに、

    しょうがないので、実際のコード生成をみてみた。... ところ avrtiny10 なら XXX の命令列 そうでないなら元の命令列というコードの部分で 命令数を 計算して ポインタ経由で代入している。そして 命令数は変更していない。(as5_beta も同じ)

    たぶんこれが、R_AVR_7_PCREL で引っかかる原因。カウントした命令数は 条件分岐とかの範囲の計算に使われているのだろう。... と思い直してみたところ ... 直った。

      修正箇所が 80 ほどあった。手を付けたのを後悔するほどだったが、なんとか完遂。

      コードを見ると 元の命令列と同じ動作になるような 命令列 を生成している。2 命令が 3 命令になる程度ならそれでも良いが ... 2 命令が 6 命令とか 6 命令が 12 命令とか ひどいものがある。しかも __zero_reg__ とか使っているし。効率が悪ければ使わない... みたいなことが出来ないのだろうか?

        コード生成が XMEGA と それ意外に分かれているところがある。この両方に -- XMEGA の部分まで -- avrtiny10 なら XX というのが埋めこまれている。すぐ上の条件すら見ていない機械的変換 -- ひどいものだ。

        まだひどいところがあった。インデックスレジスタに offset を足して、後でoffset を引いているようなところが多いのだが、12 命令も使っているものは 2 回に分けて subi/sbci している。本来なら 8 命令で済むものだった。 subi/sbci が多いのもたぶんこれが原因。

        また、インデックスレジスタを多用することになるのは、レジスタが少ないからだろう。Y あたりをベースにして、offset 付きで ld/st しようとするわけだ。あと Y 自体を変更する場合 __temp_reg__ を使わざるを得ない。それは仕方ないとしても何故セーブ対象の r16 を使うのだろう? 理由は理解できていない。

        それはともかく、込み入った C コードの命令効率はあまり良くならないことは分かった。当初 3割マシぐらいだと漠然と思っていたが、コードによって随分違う。込み入ったコードは 2 倍まで覚悟しないといけないようだ。

      ちなみに __zero_reg__ は ZERO_REGNO_AVRTINY10(r17) に割り当てている。どうやって制御しているのかは知らない。

      ここを 機械的変換している限り命令効率が悪い問題は解決しない。.. と言っても 2 倍程度。ブロックRAM は 4K あるので TINY2313 程度のことはできる。最大は 8KB あるから 足りなければ 2 倍にすれば良いという考え方もある。

    ついでに 2 つめの問題を調べると... libgcc.S にある __prologue_saves__,__epilogue_restores__ が avrtiny10 では disable されていた。(as5_beta も同じ)

    これは、どうやって直すのが正しいのだろうか? setjmp/longjmp のようなものなのだが、使って良いレジスタがよく分からないし、存在しないレジスタの分を空けるのかどうかも良くわからない。

gcc のコード生成をいじってみた。

いくつかコード生成をチューニング。
それに加えて LDS/STS を使わないコードにしてみたりしている。

適当なので、コンパイルだけはできるものの LDS/STS 周りはあまり期待できない。だが、元は 2word LDS/STS が出てしまうから attiny40 など実際のCPU では 動かないのだ。

    期待できないといっても、気をつけるのは X (r26/r27) の競合のみ。たまたま動く場合もあるようだ。

rtavr では、2word LDS/STS をサポートしたから本当は いじらない方が安全。いつでも元のコードにできるよう ifdef で切り分けてはある。

その他の コード生成 は、命令数カウントを直した上で 注意深く変更したつもり。
メモしておくと

  • XMEGA 用のコードの中にもある avrtiny10 対応コードを削除
  • MAX_LD_OFFSET のところの avrtiny10 対応コード は ldd 命令を機械的に変換したものだと分かったので、avrtiny10 では削除。
  • --R とコメントがあったところ、なにやら変なので素直なコード(ld -X/ld -X とか) に変更。

ちょっとやりすぎか。命令数のカウントのところだけにした方が信用できる。

  • 92-gcc-4.4.3-avrtiny10-fix2p1.patch (削除)

    これは、命令数のカウントのところだけ直した最初のもの。あと 、__prologue_saves__ などを修正した libgcc.S のパッチも入っているが、これまた適当なもの。変更する場所を示すのが目的と言っても良い程度。

    __zero_reg__, __temp_reg__ の変更

      avr-gcc のコード生成を見ていると __zero_reg__, __temp_reg__ は必要なようだ。他の AVR のコードに影響を与えないように変更しないといけないので、書きなおすようなことも困難。

      さて、__zero_reg__, __temp_reg__ は、普通のAVR では r1/r0 だが、avrtiny10 では、r17/r16 に変わっている。実を言うと相当に変。save/restore するようなコードで __temp_reg__ が必要なことがあり、r16 を save しても壊してしまうのだ。

      やはり save しないレジスタを割り当てなければならない。幸いなことに、レジスタ渡しの引数の数が 4 から 3 になっている。ここで空いた r18/r17 を使うべき。その他のレジスタはいつでも使える保証がない。

      ただ、こうしてしまうと既存のコードの修正が必要になる。既存のコードとは何かというと libgcc.S 。(libgcc-fixed.S はハナから使えない) 。r16/r17 を選んだのも 多分影響を減らすという意味合いがあるのだろう。でもレジスタ数が減った以上影響が小さいわけがない。ここらへんはやむを得ないとして 正しい選択をすべきではないかとおもう。

        基本的に、r18/r19 は 使ってしまってかまわない。r19 を 使った場合は、ret の前に

        #if defined (__AVR_TINY__)
        clr __zero_reg__ ; clear zero_reg (r19)
        #endif

        こういうコードを入れるだけで済む。ただ、r19 はもとは、第四引数だから その意味で使っているとすれば、根本的な対応が必要になる。r18 は temp だから 後始末もいらない。

      で これらの定義をしているところを探して変更してみることにした。

      avr-libc-1.7.1 :
      common/asmdef.h
      avr-gcc (4.4.3):
      gcc/config/avr/avr.md
      gcc/config/avr/libgcc.S
      gcc/config/avr/libgcc-fixed.S


      ところで、avr-libc を grep しているときに、avrtiny10 で disable されているものが多数あることが分かった。avr-libc はアセンブラのコードが結構あるのだが、とりあえず disable されている。また、可変引数の vfprintf などや strtok_P などといった LPM を使うものも disable されている。

      あと、libgcc.S でも問題を見つけた。long x long の演算には 8 バイトの引数が必要だが、6 バイト分しかレジスタ渡しできない。コードは出来てしまうが動かない。
      ( 動かないのは、__mulhisi3, __umulhisi3, __mulsi3, __udivmodsi4 )

      なかなか、avrtiny10 系は、大変なようだ。

      現時点の感想:

      レジスタを減らすというのは悪くはない考えだと思う。だが、gcc でのサポートについて検討が足りない。レジスタを減らせばメモリアクセスが増える。1word lds/sts 命令みたいな使えないものを入れないで 2word lds/sts を残し、変位付きのアクセスができる ldd/std も残すべきだったと思える。
        (
        gcc のコード生成部をみなおして改めてそう思った。関数コールでセーブされるレジスタなど Y 以外にないも同然。基本的に変数は スタック上のメモリに置くことになる。やはり ldd/std がないと コード効率が落ちる。ldd/std 1命令で出来ることを 既存の命令の組み合わせにすると、Y に 加減算 してアクセスし元に戻すことになるから 5 命令かかる。

        gcc での対応のやりかたも分かったから いずれ ldd/std を追加してみたい。 ( その場合 1word lds/sts は使えなくなるから 2word lds/sts も必要になる )

        あと欲しいのは、movw や sbiw とかの 2 バイト系。ポインタを多用することになるから。だが、実装は重い。2clock かけて良いなら別だが ...

      あと、レジスタを減らした以上影響は大きい。それでも単なる移植だから開発リソースさえあれば、なんとかなるはずだ。だが、現状を見ると開発リソースがあまりないように見える。いったいいつになったら対応が完了するのだろう? だいぶ時間がかかるのでは? という気がしてしょうがない。

      まぁソースがあるから自力でも出来るはずだが、avr-libc のチェックと対応は気が遠くなりそう。

    とりあえず整理


    • 92-gcc-4.4.3-avrtiny10-fix2.patch (削除)
    • 91-avr-libc-1.7.1-chgtmp.patch (削除)
      が add2 からの差分。一応整理した。

    • これは lds/sts が出ない。だが 色々問題ありそうなレベル。
    • __zero_reg__/__temp_reg__ を r18/r19 に変更して とりあえず libgcc.S と avr-libc はビルドできた。
    • libgcc.S では __mulhisi3, __umulhisi3, __mulsi3, __udivmodsi4 の問題が残っている。
    • avr-libc も disable されたものを チェックしていない。

      queen_r.c はコンパイルできたが、vfprintf がないためビルドできない。
      コードをチェックしてみたら、r19 を普通に使ってしまっているうえに、__zero_reg__ としても使っている。

      まだまだだが、バイナリは一応置いた。

    • AVR_Toolchain-add3win.tar.gz -- コード生成変更 Windows 版 (6MB)
    • AVR_Toolchain-add3lin.tar.gz -- コード生成変更 対応 Linux 版 (5MB)


        あと、iort40.h (+ rtavr_defs.h ) を整理。

        標準は XC3S50A で作れる最小構成分だけが使えるが、-DHAVE_LOCAL_DEFS を付けるとローカルの rtavr_defs.h をインクルードする。そして rtavr_defs.h は rtavr_defs.v から自動生成可能。


      int main() {
      int8_t i;
      char *p = (PSTR("abc") + 0x4000);

      for (i=0; i<100; i++) {
      func_a(i & 1);
      }
      func = func_a;
      while (*p) {
      PORTB = *p;
      p++;
      }
      return 0;


      これでこういうコードをコンパイルしてみた。


      0000006e <main>:
      37: 93df push r29
      38: 93cf push r28
      39: 932f push r18
      3a: b7cd in r28, 0x3d ; 61
      3b: b7de in r29, 0x3e ; 62
      3c: e090 ldi r25, 0x00 ; 0
      3d: 2f89 mov r24, r25
      3e: 7081 andi r24, 0x01 ; 1
      3f: 5fcf subi r28, 0xFF ; 255
      40: 4fdf sbci r29, 0xFF ; 255
      41: 8398 st Y, r25
      42: 50c1 subi r28, 0x01 ; 1
      43: 40d0 sbci r29, 0x00 ; 0
      44: dfea rcall .-44 ; 0x5e <func_a>
      45: 5fcf subi r28, 0xFF ; 255
      46: 4fdf sbci r29, 0xFF ; 255
      47: 8198 ld r25, Y
      48: 50c1 subi r28, 0x01 ; 1
      49: 40d0 sbci r29, 0x00 ; 0
      4a: 5f9f subi r25, 0xFF ; 255
      4b: 3694 cpi r25, 0x64 ; 100
      4c: f781 brne .-32 ; 0x7a <main+0xc>
      4d: e2e2 ldi r30, 0x22 ; 34
      4e: e4f0 ldi r31, 0x40 ; 64
      4f: e28f ldi r24, 0x2F ; 47
      50: e090 ldi r25, 0x00 ; 0
      51: e6a0 ldi r26, 0x60 ; 96
      52: e0b0 ldi r27, 0x00 ; 0
      53: 938d st X+, r24
      54: 939c st X, r25
      55: c003 rjmp .+6 ; 0xb2 <main+0x44>
      56: b986 out 0x06, r24 ; 6
      57: 5fef subi r30, 0xFF ; 255
      58: 4fff sbci r31, 0xFF ; 255
      59: 8180 ld r24, Z
      5a: 2388 and r24, r24
      5b: f7d1 brne .-12 ; 0xac <main+0x3e>
      5c: e080 ldi r24, 0x00 ; 0
      5d: e090 ldi r25, 0x00 ; 0
      5e: 912f pop r18
      5f: 91cf pop r28
      60: 91df pop r29
      61: 9508 ret


      できたのはこれ。大体合っていそうな感じ。

    • subi/sbci が目立つ。ひとつは、フレームポインタ Y に 加算してアクセスし元に戻すような使い方。addi という命令は存在しない。
    • __zoro_reg__ などはこのコードでは生成されないようだ。

    • レジスタ渡しにならない パラメータのアクセス


      int32_t func_a(int32_t a,int32_t b) {
      return b+1;
      }

      こんな関数がどのように展開されるか見てみよう。


      0000005a <func_a>:
      2d: 93df push r29
      2e: 93cf push r28
      2f: b7cd in r28, 0x3d ; 61
      30: b7de in r29, 0x3e ; 62
      31: 5fcb subi r28, 0xFB ; 251
      32: 5fdf subi r29, 0xFF ; 255
      33: 9129 ld r18, Y+
      34: 9139 ld r19, Y+
      35: 9149 ld r20, Y+
      36: 8158 ld r21, Y
      37: 50c8 subi r28, 0x08 ; 8
      38: 40d0 sbci r29, 0x00 ; 0
      39: 5f2f subi r18, 0xFF ; 255
      3a: 4f3f sbci r19, 0xFF ; 255
      3b: 4f4f sbci r20, 0xFF ; 255
      3c: 4f5f sbci r21, 0xFF ; 255
      3d: 2f62 mov r22, r18
      3e: 2f73 mov r23, r19
      3f: 2f84 mov r24, r20
      40: 2f95 mov r25, r21
      41: 91cf pop r28
      42: 91df pop r29
      43: 9508 ret


    • r19 が使われてそのままになっている。これはダメだがおいておいて ...

        gcc/config/avr/avr.h の CONDITIONAL_REGISTER_USAGE を直せば良さそう。

    • Y を push して Y に SP を代入している。で Y+5,Y+6,Y+7,Y+8 を レジスタにロードして、Y を元にもどしている。

      Y を使わず Z を使って、ロードするには、

      0000005a <func_a>:
      2f: b7cd in r30, 0x3d ; 61
      30: b7de in r31, 0x3e ; 62
      31: 5fcb subi r30, 0xFD ; 253
      32: 5fdf subi r31, 0xFF ; 255
      33: 9129 ld r18, Z+
      34: 9139 ld r19, Z+
      35: 9149 ld r20, Z+
      36: 8158 ld r21, Z


    • __mulsi3 などは、__zero_reg__ 使っていないし、関数の頭でこれをして、最後に __zero_reg__ を clr して ret すれば良さそう。

    いろいろ直す


    • 92-gcc-4.4.3-avrtiny10-fix2.patch
    • 92-avr-libc-1.7.1-attiny10.patch

      gcc の方だが、order_regs_for_local_alloc というのがある。CONDITIONAL_REGISTER_USAGE を直すのに合わせて変更した。

      だが、libgcc2.c:400 (__mulvDI3) で

      error: unable to find a register to spill in class 'GENERAL_REGS'
      error: this is the insn:
      (insn 986 985 987 135 ../../../../libgcc/../gcc/libgcc2.c:376 (set (reg:SI 46 [ D.2448 ])
      (minus:SI (reg:SI 46 [ D.2448 ])
      (reg:SI 409 [ temp.14 ]))) 892 {subsi3} (expr_list:REG_DEAD (reg:SI 409 [ temp.14 ])
      (nil)))

      というエラーが 出るようになってしまった。無理やりコンパイルしないようにすると通るから 問題が起きるのは これだけのようだ。

      レジスタが足りないという問題だから解決できないかも。-- 困った。

      それはともかく、avr-libc も 試しにいくつか直してみた。

      common/asmdef.h common/macros.inc

      にも手を入れて X_lpm を avrtiny10 に対応させ X_sbiw マクロを追加。これで いくつか修正。簡単なのはやり方が決まったから、少しづつ手を入れていこうと思う。

      追記: lpm は全部不要ということを忘れていた。アドレスさえ分かれば普通に C でアクセスできる。

    追記: 1word lds/sts に対応できたかも

      1word の lds/sts では新たなリロケート情報が必要になる。他のリロケート情報を元にまねして付けていったら ... なにやらビルドできた。

      ついでなので、今つくっている 機能縮小版の ldd/std にも対応した。まだ整理中だが、使えそうな雰囲気。


      avr-objdump -mavr201 -d minicrypt.o |less

      たとえば、こんな風に MACHINE まで指定すると lds が見える。

      2a: 89 e8 ldi r24, 0x89 ; 137
      2c: 9b ea ldi r25, 0xAB ; 171
      2e: ad ec ldi r26, 0xCD ; 205
      30: bf ee ldi r27, 0xEF ; 239
      32: 80 a9 sts 0x0000, r24
      34: 90 a9 sts 0x0000, r25
      36: a0 a9 sts 0x0000, r26
      38: b0 a9 sts 0x0000, r27

      MACHINE を指定しないと ldd/std と区別がつかないので次のようになる。

      2a: 89 e8 ldi r24, 0x89 ; 137
      2c: 9b ea ldi r25, 0xAB ; 171
      2e: ad ec ldi r26, 0xCD ; 205
      30: bf ee ldi r27, 0xEF ; 239
      32: 80 a9 ldd r24, Z+48 ; 0x30
      34: 90 a9 ldd r25, Z+48 ; 0x30
      36: a0 a9 ldd r26, Z+48 ; 0x30
      38: b0 a9 ldd r27, Z+48 ; 0x30

      そして、このオブジェクトは gcc で生成したもの。オプションを付けることで いくつかの 出力方法に対応できた。

      -mldssts1
      -mldssts2
      -mno-ldssts
      -mreduced-lddstd

      -mldssts1 と -mldssts2 は なにが違うかというと バイト数の計算。ブランチ命令で届く範囲の計算が変わる。アセンブラにまで 設定が行かないので アセンブラは 1word lds/sts しか出さない。( 対応済みでないアセンブラは 2word lds/sts ) 。

      対応済みでない アセンブラでは 2word lds/sts になってしまい困るので -mno-ldssts というのも付けてみた。ただこれはバグっている。binutils がうまく行くようなら外す。

      ついでにつけた -mreduced-lddstd は、ldd/std 命令を使うようにする。ただし 1word lds/sts と命令が重なるので disp は 0-31 の範囲だけ。いまのところ作ってみただけだが、rtavr で対応する目処が立ったので がんばるかも。

      パッチ集は整理した。

    • AVR_Toolchain-20110408-src.tar.gz -- パッチ集

      これに対応するバイナリも作る。

      バイナリは一応作ったのだが、問題がある。で、修正しようとしているのだが、ひとつの問題を直せていない。

    • 1word lds/sts のリロケーション情報が書けない。

      という問題で、avr-objdump.exe -mavr201 -r testmain.o とすると

      testmain.o: file format elf32-avr

      RELOCATION RECORDS FOR [.text]:
      OFFSET TYPE VALUE
      00000026 R_AVR_LO8_LDI_GS var_b
      00000038 R_AVR_LO8_LDI_GS var_b
      0000003c R_AVR_LO8_LDI .progmem.data
      0000003e R_AVR_HI8_LDI .progmem.data
      00000050 R_AVR_LO8_LDI_GS var_b
      00000052 R_AVR_LO8_LDI_GS var_a+0x00000001
      00000054 R_AVR_LO8_LDI_GS var_a
      00000056 R_AVR_LO8_LDI_GS var_b

      こうなる。var_a/var_b のところを R_AVR_7_LD1 にしているつもりが、違うタグになる。
      タグだけが違うようなかんじで、 リンクの際に

      testmain.c:(.text+0x54): warning: internal error: out of range error

      というエラーになっている。... 困った。

      これは、bfd/reloc.c に並べる順番が関係していた。R_AVR_7_LD1 を途中にいれていたのだが、最後にしたら OK 。

    • AVR_Toolchain-20110409-src.tar.gz -- パッチ集

      パッチ集はこれで FIX 。バイナリは準備中。

      あと binutils で、PSTR() で取るアドレスに対して 0x4000 を自動的に加算しようかと思う。これから調査。

    PSTR の処理の調査


      まず、PSTR 自体は avr-libc の avr/pgmspace.h で定義されている。

      展開すると

      #define PSTR(s) ((const __attribute__((__progmem__)) char *)(s))

      こうなっている。
      で、gcc でこの attribute の処理をするわけだが、

      どうも text_segment_operand () というのが関係あるらしい。コメントに

      /* Return true if OP is a program memory reference.*/

      なんて書いてある。

      だが、結局 ただのシンボルのアクセスになるようだ。

      .type __c.1676, @object
      .size __c.1676, 4
      __c.1676:
      .string "abc"
      :
      :
      ldi r24,lo8(__c.1676)
      ldi r25,hi8(__c.1676)

      こんな感じのアセンブラ出力。

      .o のリロケーション情報を見てみると

      0000003c R_AVR_LO8_LDI .progmem.data
      0000003e R_AVR_HI8_LDI .progmem.data

      こんな風になっている。
      これを処理するリンカのコード

      case R_AVR_HI8_LDI:
      contents += rel->r_offset;
      srel = (bfd_signed_vma) relocation + rel->r_addend;
      srel = (srel >> 8) & 0xff;
      x = bfd_get_16 (input_bfd, contents);
      x = (x & 0xf0f0) | (srel & 0xf) | ((srel << 4) & 0xf00);
      bfd_put_16 (input_bfd, x, contents);
      break;

      ヘッダファイル、gcc, アセンブラ、リンカ。どこにでも対応を入れられそうだが、どこが良いのだろう?
      リンカでは、セクション自体の定義を変える対応すら可能そうだ。

      まずは既存の AVR でオフセットを加算しているようなものがないか調べる。既にあればマネをすれば良い。

      問題は、存在しない場合。自分で入れるなら 変更が最も少ないところに入れたい。今のところ アセンブラかなと思っている。gcc のほうが良いかも知れない。

      ヘッダファイルでは、似たものを全部直さないといけないし、もれがあるとだいなしだから避けたい。セクション自体の定義も 他に影響ありそうなのでパス。

      アセンブラだと 対応するのは LDI 命令だけ。.progmem.data ということも知っているから 加算することは可能だろう。

      gcc になると 難しそうな感じだが、pm_hi8() とか gs() とか pm() とかがあるから なにか対応策がありそう。

      これをやりたい理由を書き忘れた。avr-libc での対応をどうするか決めたいのが理由。結構 lpm を使っているし xxx_P という関数群もある。修正方針が決まらないと手をつけられない。-- だが、へたに決めることもできない。なんとも悩ましい。

    バグ修正と ldd/std 対応。

      AVR互換コアのテストをしていて、バグを見つけた。st -Z x2 のところで、アドレスを -2 しておくのが -1 になっていた。

      これを直したら、動かなかったのが直った。で、気をよくして ldd/std 対応もちゃんと入れることにした。

      avrtiny10 対応は、オリジナル or 専用コード みたいなつくりになっているので、オリジナルを使う条件を 変更してやることで ldd/std を使えるようにする。それも全部は、入れない。単位処理が 2 倍以上のコードになっているところのみにする。

       line-no ldd/std reg #  offset GET_MODE  lim
      2306 LDD REG_Y 1 XEXP(x,1) src -1
      2317 LDD REG_Z 1
      2473 LDD REG_Y 1 disp src -1
      2488 LDD REG_Z 1
      2508 LDD REG_Y 2 disp src -1
      2521 LDD REG_Z 2
      3255 STD REG_Y 4 disp dest -3
      3273 STD REG_Z 4
      3569 STD REG_Y 1 XEXP(x,1) dest -1
      3580 STD REG_Z 1
      3706 STD REG_Y 1+ST XEXP(dest,1) dest -1
      3717 STD REG_Z 1+ST
      3854 STD REG_Y 2 disp dest -1
      3867 STD REG_Z 2

      それでもこれだけある。問題は lim で示した 境界条件。-- 実は計算方法が良くわからない。どうも 63 という数がよく出てきているので、オフセット(らしき数字) の条件を 31 ではなく 30 にしている。あと、4 バイト系は さらに -2 。

      これら ldd/std は、-mreduced-lddstd オプションを付けないと出ない。ルールを作って変更しているから、他には影響を与えない。

      パッチとバイナリは出来次第 upload する。コード生成が変わっただけなので、バイナリは cc1/cc1plus と avrtiny10 のライブラリのみ。
  • posted by すz at 20:11| Comment(1) | TrackBack(0) | AVR_CORE

    2011年03月05日

    AVR互換コア(その6 デバイス)

    AVR互換コア(その5)』からの続き。

    前記事では、avr-gcc で生成したコードを実際に動かしてみたが、Tiny40 のコードは問題があるので、新しいベータ版がでるのを待つことにして命令のテストは先延ばしにした。

    その間に、デバイスのデバッグをしたり、新しいデバイスを作ろうと思う。

    タイマと割り込みコントローラ





      最初に、プレスケーラの制御。TSM と PSR の設定 でプレスケーラをリセットする。10 を書くと TSM = 0/PSR = 1 でただちにリセットしてカウント開始。30 を書くと TSM = 1/PSR = 1 で、リセットのみ行う。

      次に CS を 1 に設定して、プレスケーラなしで TCNT0 のカウント開始 。



      これは、CS を 1 に設定して、1/8 プレスケーラを使ったとき。



      これは、いろいろ設定して、FB で COMPB / FD で COMPA / FF で OVF が成立するようにしたもの。

      内部リクエストは、FB と同時に出ているが、割り込みコントローラが受け付けるのは、1クロック後になった。 S1 で受け付けるのは、さらに 1 クロック(以上)後。



      これが、3 つの割り込みが重なったときの挙動。COMPB(0B) は受け付けられるが、その間に COMPA(0A) と OVF(09) が起きる。OVF の方が優先度が高いので、次に受け付けられるのは、OVF 。最後に COMPA が受け付けられる。

      以上、基本動作は大分できてきた。バグはいろいろあったが、もともと形式を整えただけで動くことを期待して作っていなかったので、全部省略。

    SPI と インターフェイス変更


    次は、SPI を作ることにした。USART の方が複雑なので、おなじシリアル系の SPI で練習してから挑む。

      で、SPI を作ろうとして、ハタと困ってしまった。SPI は PORT と独立には作れない。PORT のサポートがないと機能として成立しない。で、PORT へのアサインの機能を作ろうとしたときに 今のインターフェイスでは、却って面倒なことになることが分かった。

      というわけで、PORT から PIN/DDR/PORT を全部引き出して、上位レイヤの IOR で アサインすることにした。

      アサインの方法は、A/B/C を全部並べて 24bit の PIN/DDR/PORT にして、generate でマッピング。



      それらしい動作をさせることは出来た。いろいろ設定して、0xA5 を出力させているのだが、MSB からビット列が出力されている。転送終了で SPIF が 1 になって割り込みも発生できている。

      実はうまく動くのは、これだけ。問題のひとつに、入力がうまくいっていないことがある。どうも tri-state の扱いについて基本的なことで間違えているようだ。まぁ、これはおいおいやる。



      これは、2X を 0 にしたときの挙動。クロックのところがダメ。というより状態管理がダメ。

      SPI にはいろいろな機能がある。マスタにして SS を入力にした場合は、SS が L になると割り込みが発生するとか。あと SPIF をプログラムでクリアするには、SPSR を read してから SPDR を read または write するとか。

      これらも含めた状態管理がなんだか難しい。ちょっと甘かった。

      設計しなおさないと いけなさそうだが、一旦スナップショット。

    • rtavr-wk07.tar.gz

      整理してみた。


      //
      // i_sck : ( SCK_IN ^ CPOL ) & ~SS_IN
      // ___ ___ ___ ___ ___ ___ ___ ___
      // _______| |___| |___| |___| |___| |___| |___| |___| |____
      //
      // CPHA=0 IN SFT IN SFT IN SFT IN SFT IN SFT IN SFT IN SFT IN SFT
      // CPHA=1 SFT IN SFT IN SFT IN SFT IN SFT IN SFT IN SFT IN SFT IN
      //
      //
      // SFT : +-----------------+
      // | | DORD == 0 : ROL , 1 : ROR
      // +-- C -- SPDR --+
      //
      // IN CPHA == 0 : C
      // CPHA == 1 & DORD == 0 : SPDR[0]
      // CPHA == 1 & DORD == 1 : SPDR[7]
      //
      // OUT CPHA == 0 & DORD == 0 : SPDR[7]
      // CPHA == 0 & DORD == 1 : SPDR[0]
      //


      どうもこんな感じらしい。終了を検出するには、0 - 15 の 4 bit カウンタが都合が良い。

    07A




      整理して 書きなおしたらうまくいった。

      マスターでの SCK 周りはちょっと工夫した。出力する SCK と 内部処理でつかう SCK は、マスターでは別扱い。そうしないと 1 クロック 遅れてしまう。

    • rtavr-wk07a.tar.gz



      CPHA も それらしく。ただ、78 スライスも使っている。論理がごちゃごちゃしているのが敗因? もういちど見直してみよう。

      実際に使う場合、マスターだけしか使わないと思うし、CPHA や CPOL も動的には変更しない。定数値を使えるようにすることで、規模は小さくなるはず。どれぐらい規模が違うのか調べてみることにする。

    07B



    • rtavr-wk07b.tar.gz


      // NORMAL (1) (2) (3) (4) (5) (6) (7)
      // Number of Slice Flip Flops: 37 34 33 32 30 23 22 21
      // Number of 4 input LUTs: 105 98 75 65 58 48 45 47
      // Number of occupied Slices: 73 67 54 45 39 31 30 31
      // Total Number of 4 input LUTs: 111 104 81 65 58 48 45 47
      // Number of bonded IOBs : 38 37 35 35 35 35 35 35
      // IOB Flip Flops : -
      // Number of BUFGMUXs : 1
      // Number of RAMB16BWEs : -
      //
      // (1) NO_WCOL , NO_PSV_SS
      // (2) NO_WCOL , NO_PSV_SS, FIXED_MSTR=1
      // (3) NO_WCOL , NO_PSV_SS, FIXED_MSTR=1, DORD=0
      // (4) NO_WCOL , NO_PSV_SS, FIXED_MSTR=1, DORD=0, CPOL=0, CPHA=0
      // (5) NO_WCOL , NO_PSV_SS, FIXED_MSTR=1, DORD=0, CPOL=0, CPHA=0, SPR=2
      // (6) NO_WCOL , NO_PSV_SS, FIXED_MSTR=1, DORD=0, CPOL=0, CPHA=0, SPR=1
      // (7) NO_WCOL , NO_PSV_SS, FIXED_MSTR=1, DORD=0, CPOL=0, CPHA=0, SPR=0


      整理しなおした。
      オプションとして、規模を比較。FULL 機能は 73 スライスになった。多分使うのは、(4)あたり。39 スライスなら、まぁいいか。

      細いバグはあるかも知れないが、だいたいこんな感じで良いだろう。次は USART か。

      USART の作り方は、SPI でやった方法を踏襲しようと思う。できるだけフル機能で作って、定数も使えるようにする。定数を使えば Warning は出るが、最適化されて規模が小さくなる。特によく使う 8N1 のケースで無駄が多くならないようにする。また、USART にはいろいろな機能がありチップによって違う機能もあるが、Tiny2313 に準拠した上で、9bit の機能とかは後回しにする。SPIマスタ も後回しにするかも。

      また、シフト動作などについても SPI のやりかたをベースにする。XCK は入力にもできるから 内部クロック生成と TX/RX 動作は ほぼ独立でないといけない。

      それに、SPI 動作も配慮しないといけないから、結局 SPI のロジックを含んでいないと。

    07C



    • rtavr-wk07c.tar.gz

      まったく動くものではないが、スナップショットを取っておく。IOR に組み込めて、シミュレーションの準備が出来たというだけだが、結構苦労する。

      これを元にして、書き換えていくわけだ。いまのところ、8N1 だけで作って、だんだん拡張していく方針 -- ではあるが、それだけでも なかなか手強い。USART は少しばかり手間取りそう。

      ところで、GPR の 通常クロック版も作りたいとは思っている。... のだがこれも難しい。16 bit 化してもポート数が増えるわけではないのだった。Rd/Rr の Read と (Rd の書き戻し or インデックスレジスタ更新: PREDEC/POSTINC) の合計 3 ポート必要だが、2 ポートしかない。
      ... というわけで 分散 RAM を使っての 16bit 化はあきらめ。分散 RAM にマッピングできないのを承知の上で 書いてみるつもり。

    07D




    • rtavr-wk07d.tar.gz

      だいぶ形になってきた。送信は、0xa5 と 0x5a を 連続で UDR に書いたケース。1 つが終われば UDRE が 1 になって 2 つ終わって TXC が 1 になっている。

      受信は、PORT を介さず内部で ループバック。実はまだダメなのだが、このケースでは読めている。

      対応するフォーマットは以下のものだけにした。

      // SUPPORTED FORMAT :
      // | 0 | D0 | D1 | D2 | D3 | D4 | D5 | D6 | 1 | 7N1
      // | 0 | D0 | D1 | D2 | D3 | D4 | D5 | D6 | 1 | 1 | 7N2
      // | 0 | D0 | D1 | D2 | D3 | D4 | D5 | D6 | P | 1 | 7P1
      // | 0 | D0 | D1 | D2 | D3 | D4 | D5 | D6 | D7 | 1 | 8N1
      // | 0 | D0 | D1 | D2 | D3 | D4 | D5 | D6 | P | 1 | 1 | 7P2
      // | 0 | D0 | D1 | D2 | D3 | D4 | D5 | D6 | D7 | 1 | 1 | 8P2
      // | 0 | D0 | D1 | D2 | D3 | D4 | D5 | D6 | D7 | P | 1 | 8P1
      // | 0 | D0 | D1 | D2 | D3 | D4 | D5 | D6 | D7 | D8 | 1 | 9N1
      // | 0 | D0 | D1 | D2 | D3 | D4 | D5 | D6 | D7 | P | 1 | 1 | 8P2
      // | 0 | D0 | D1 | D2 | D3 | D4 | D5 | D6 | D7 | D8 | 1 | 1 | 9N2

      要するに スタートビットを除いたビット数が、8/9/10/11 のものだけ。ビット数も 7/8/9 のみ。9bit もサポートするが、パリティの代わりにソフトで値を决められるのみで機能が制限される。

      こういうふうにして、カウンタの制御が楽になるようにした。書き方も定数を使うと規模が小さくなるようにしたつもり。

      SPIマスタ機能サポートも検討中。もともとベースが SPI だけにたぶん大丈夫だろう。あとは、PORT がちゃんと入出力できれば ... 全体ができあがる。そうしたら version 番号を付けようと思う。

      FPGA 基板が (香港ポストから)発送された。ぼちぼち rtavr の方も区切りを付けて、電子工作とツール整備に移りたい。

    07E




    • rtavr-wk07e.tar.gz

      受信も出来た( つもり )。あまり代わり映えしないように見えるが、クロックの同期が入っている。パリティーなども対応した。



      これも代わり映えしないのだが、右は同期モード。まったく同じように見えるわけで OK 。

      さらに、規模縮小の為の定数化サポート。


      // (FULL) (1) (2) (3)
      // Number of Slice Flip Flops: 114 95 87 83
      // Number of 4 input LUTs: 177 112 105 98
      // Number of occupied Slices: 138 104 98 92
      // Total Number of 4 input LUTs: 188 123 112 109
      // Number of bonded IOBs : 37 35 35 35
      // IOB Flip Flops : 1 1 1 1
      // Number of BUFGMUXs : 1
      // Number of RAMB16BWEs : -
      //
      // (1) 8N1 with UBRR (U2X=1)
      // (2) 8N1 with UBRR_BITS(8)
      // (3) 8N1 with FIXED_UBRR(107) (U2X=1)

      規模はこんな感じになっている。(1) は、UBRR 以外を全部定数化したケース。これでだいぶ小さくなるが、UBRR は 12bit もあり、対応するカウンタの r_ps と合わせて 24 bit になる。
      これに対しての対策 として まず bit 数を指定できるようにした。8bit までしか使わなければ、少々減る。それとは別の対策として 定数化もある。

      まだつくっていないものとして SPI モードが残っている。 SPI と同じだけの機能があるから、規模が随分増えそう。ホストとの通信に USART 使うから SPI は絶対に使わないと思うので、基本的な機能だけで済ますつもり。

      そういえば、TWI も作りたいのだった。ソフトで済ませられればそれでも良いかとは思うのだが、後々通信させたいので、マスター・スレーブ両方必要。作ってしまった方が楽なような気がする。

        調べてみたら、tiny40 の TWI は、mega88 などの TWI とはまた違って スレーブのみの機能だった。ちなみに mega88 の TWI と mega8 の TWI ではまた違う。mega88 の方はいろいろ拡張されている。

        いっそのこと USI ベースで簡易版にしようかとも思ったが、tiny40 の TWI が スレーブのみになったのは理由があるはずで、たぶん最適解なのだろうと思うと互換にしておいたほうが無難なような気がする。

      あと、全体で変更したのは、割り込みの リセットライン。RX と UDRE は リセットラインは使わないのでは、パラメータから外した。

      FPGA 基板は 成田に到着。中途半端に中断したくないので、しばらくは眺めるだけになるかも。

      ついでなので今の規模。

        789 rtavr.v
        89 rtavr_defs.v
        250 rtavr_gpr_16.v
        639 rtavr_ior.v
        100 rtavr_ior_port.v
        344 rtavr_ior_spi.v
        209 rtavr_ior_timer0.v
        602 rtavr_ior_usart.v
        110 rtavr_rom.v
        216 rtavr_s0_fetch.v
        604 rtavr_s1_decode.v
        88 rtavr_sram_2KB.v
        4040 total

      ここから rtavr_defs.v , rtavr_ior_spi.v , rtavr_ior_usart.v を抜くと 3005 行。ifdef を多用するようになったから、その分も大きい。まぁそれにしても ... 随分と頑張ったものだ。

      さて、双方向I/O をそろそろなんとかしないと。

      ISE で top mododule でないからナントカカントカ言われてたので、薄々は気が付いていたのだが、やはりまずそうなので、rtavr_ior より一段 上の rtavr に持っていくことにした。


      generate
      genvar i;
      for (i=0; i<8; i=i+1)
      begin : port_a_out
      assign PIN[i] = (PORTA[i] == 0) ? 0:1;
      assign PORTA[i] = DDR[i] ? PORT[i]
      : (~DDR[i] & PORT[i]) ? "H"
      : "Z";
      end


      今はこういう風にしている。新しく "H" を覚えたので使ってみたが、実際にインプリメントしても これに関して Warning は出ていない。-- ひょっとしてこれでいけるのだろうか?

      ただ、rtavr が トップモジュールのときはこれで良いのだろうが、組み込むと同じ問題になってしまう。なにか一般的な定義を見つけるか、サブモジュールのときは、PIN/PORT/DDR をさらに上位に持っていくか。... 上位にもっていくと tiny10 系の ポート機能を付けてみるときにややこしいことになる。かと言って Xilinx のプリミティブにすると シミュレータにかけられなくなるし。

      また、USART の受信側。テストベンチでのループバックが出来るようになったのだが、クロック同期がちょっと変になった。それに、PORT の出力を PIN で読むことが出来ていないような.. (これは、XCK_IN と SS_IN で使っている)

      もう少し検討が必要だ。

        変になったのは、RXEN を 1 にする前に受信処理をしていたというバグだった。PORT の出力を PIN で読めないというのは単につないでなかったため。テストベンチ側でつながないといけないのを忘れていた。

        PIN/PORT/DDR をさらに上位に持っていくかどうかは、RTAVR_PORT_ESCALATION / IOR_PORT_ESCALATION という define で切り分けることにした。 IOR_PORT_ESCALATION を undef すると IOR で tri-state の処理をするのだが、ISE では、どちらにしても 規模が同じで Warning も 同じになった。だが、Icarus Verilog ではどうも具合が悪い。

        あと、generate するところをできるだけ共通化したくて function 使ってみたが、ISE では 同じものが出来るようだが、Icarus Verilog ではやはり具合が悪いので元に戻してしまった。

      あと、現状のフルオプションで Implement してみた。

      Number of Slice Flip Flops 452 3,584 12%
      Number of 4 input LUTs 1,236 3,584 34%
      Number of occupied Slices 797 1,792 44%
      Total Number of 4 input LUTs 1,289 3,584 35%
      Number used as logic 1,220
      Number used as a route-thru 53
      Number used for Dual Port RAMs 16
      Number of bonded IOBs 10 195 5%
      Number of BUFGMUXs 2 24 8%
      Number of RAMB16BWEs 3 16 18%

      フルオプションでは、50A には入らない。が、機能を削る指定をすればなんとか入りそう。

      あと、これをするにあたり 、いくつかバグが見つかったので直している。

      ... 思い出した。入出力がなんとかなったら PWM も作りたかったのだった。あと、16bit タイマーも本当は欲しい。WDT や SLEEP もデコードだけして放置していた。WDT を入れるのなら 未定義命令での RESET も入れたいところ。SLEEP 入れるなら 消費電流の低減になるように配慮したいし、今やらないとしても ToDo リストに入れておかないと忘れてしまいそう。

    07F



    • rtavr-wk07f.tar.gz

      機能を削る指定をしてみたのだが... 厳しい。704 スライス以下にしないといけないのだが、32biit LDS/STS を削っても 712 まで。これ以上削るのはちょっと困る。... で、タイマー機能の定数化をやっていないのに気がついた。1/1024 のプレスケーラとか OC0A/0C0B があるので、定数化でだいぶ稼げる。プレスケーラを指定する CS0 は、0 が設定できないといけないので少々工夫したが、... 1/8 のみを使い OC0A/0C0B を定数化すると 662 スライスにまでになった。1/64 を使い OC0A だけの 定数化 でも 683 スライス。これにLDS/STS を復活させて 697 。.. これなら 今後バグを修正しても(ちょっとだけ)余裕がある。

      697 スライスになったときの設定と 結果。ちなみに、USART の SPI マスタ 機能を 1 パターンだけ実装できた。定数化すると 最適化で全部消えるから以下の結果には関係ない。

      // CORE definition
      `define SUPPORT_LDST32

      // TIM0 configuration
      //`define TIM0_EXPORT_PS // using when other timer is used
      `define FIXED_CS0 3 // 0 : stopped 1: 1/1 2: 1/8 ..
      `define FIXED_WGM0 0 //
      `define FIXED_COM0B 0 //
      `define FIXED_COM0A 0 //
      //`define FIXED_OCR0B 127 //
      `define FIXED_OCR0A 128 //

      // USART configuration
      //`define USART_LOCAL_LB
      `define FIXED_MPCM 0 // 0:
      `define FIXED_U2X 1 // prescaler option
      `define FIXED_UMSEL 0 // 0: async 1:sync 3: spi master
      `define FIXED_UPM 0 // parity 0: none 2: even 3: odd
      `define FIXED_USBS 0 // stop-bit 0: st1 1: st2
      `define FIXED_UCSZ 3 // 2: 7bit 3: 8bit 7: 9bit
      `define FIXED_UDORD 0 // 0: LSB first 1: MSB first (only in spi)
      `define FIXED_UCPHA 0 // clock phase 0: IN-SFT 1: SFT-IN (only in spi)
      `define FIXED_UCPOL 0 // clock polarity 0: normal 1: reversed
      //`define FIXED_UBRR 107 // boud parameter (12bit)
      `define UBRR_BITS 8 // boud parameter (2 - 16)


      // SPI configuration
      //`define SPI_HAVE_WCOL
      //`define SPI_HAVE_PSV_SS
      `define FIXED_MSTR 1 // 1: Master 0: Slave
      `define FIXED_DORD 0 // 0: MSB first 1: LSB first
      `define FIXED_CPOL 0 // 0: Noraml SCK , 1: Reversed SCK
      `define FIXED_CPHA 0 // 0:IN_ SFT order , 1: SFT_IN order
      //`define FIXED_SPR 0 // 0: 1/2 CLK , 1: 1/4 CLK 2: 1/8 CLK
      // 3: 1/16 CLK , 4: 1/32 CLK 5,6: 1/64 CLK
      // 7: 1/128 CLK

      結果

      Number of Slice Flip Flops 394
      Number of 4 input LUTs 1107
      Number of occupied Slices 697
      Total Number of 4 input LUTs 1151


      そういえば割り込み番号が不連続なために 少し規模が大きくなっているようだ。IOレジスタのマッピングにしてもそう。rtavr で tiny40 用の avr-libc を少しでも流用しようとしたからこうなっているわけだ。

      だが、周辺装置の互換性も高くはないし、意味がないような気がしてきた。新たに定義しなおして効率を取ったほうが得策だろう。IOレジスタの空間も空くし新しいデバイスを付けやすくなるというメリットもある。ちょっと考えてみよう。

      さらに、思い出したのでメモ。tiny40 には、IOレジスタ経由で RAM にアクセスできる変な機能がある。何のためについているのか理解できなかったのだが、IOレジスタだから SBI/CBI ができて便利なのかも。あと rtavr は、インデックスレジスタはあっても 変位付きの命令がない。メモリアクセスは結構不便なようだ。C のコードを見ているとLDI x 2 でアドレスをロードして、ようやくメモリにアクセスできる。LDI + OUT で アドレスを設定してもコストは同じで レジスタの消費が少ないというメリットがある。LPM みたいなマクロになるだろうが、ないよりはマシかも。

      幸いなことに RAM はデュアルポートで片側が空いている。この機能を作ってみるのも良いかも知れない。

    07G



    • rtavr-wk07g.tar.gz

      そろそろ別記事にしようかと思ったのだが、もう少し続けることにした。

      今回は、avr-gcc を少しいじって考えることがあった。

      avr-libc で rtavr40 に対応したが、FPGAだから 割と自由に機能のアサインができる。
      定義を作っておいて、avr-libc を使うときも 反映できると良さそうな気がしてきた。

      具体的に書くと、rtavr_defs.v という定義から、rtavr_defs.h を自動生成して、それを使うと 正しい アサインになるようにする。

      これにあたって、IOR へのアサイン方法を parameter から define に変更した。

      定義可能なのは、

      • IOR への マッピング (各モジュールの BASE_ADDR)
      • 各モジュール機能の PORT への割り当て
      • 割り込みベクタ

      なお、ヘッダファイルやツールは、soc/support に置いてある。

      あと、割り込みコントローラでの優先順位の定義がバグっていた。番号の小さいものが優先度が高いようにしないといけない。


      wire [4:0] v_int_vec = IOR_INT0 ? 1
      `ifdef IOR_HAVE_TIM0
      : IOR_TIM0_OVF ? 9
      : IOR_TIM0_COMPA ? 10
      : IOR_TIM0_COMPB ? 11
      `endif
      (後略)

      もともと、こんな風になっているのだが、番号が全然間違っていた。
      正しくは、COMPA が 11 , COMPB が 12, OVF が 13 。

      これらの値は、define で rtavr_defs.v に持って行っているのだが、昇順に並べないと本当はいけない。 define にしたのは良いが 依存関係が出来てしまっている。

      順番が前後しても優先度が変わるだけなので、あまり困ることはないかも知れないのだが、実は昇順にすると 規模が小さくなる。ここの部分は、ちょっと順番が違うだけで数スライス違う。変更する場合はよくよく考えた方が良いようだ。

        上記のように正しく直すと 692 スライスまで減った。だが、COMPA/COMPB/OVF を 9/10/11 にしてみると 686 スライスまで減る。使っている 割り込みを 1-8 にきっちり詰めると ... 691 に増えたり。

      あと、OC0A/OC0B を使えるようにしてみる。まだちゃんとした PWM の機能にはなっていないが、とりあえずは、COMPA/COMPB と OVF で ON/OFF を切り替えるぐらいは出来た。

      これらの変更によっていままでのテストが動かなくなっている。全部直すのに手間がかかる。

        ところで、インターフェイスに OP1_SEL/OP2_SEL/OP3_SEL という ALU の機能を選択する信号がある。それぞれ 3bit で 状態を持っているため 1 つにしてしまうと 規模が減るのでは?と思いやってみたところ ...
        逆に 14 スライスも増えてしまった。見かけの FF を 6 つ減らしたのに 1 増える結果になっている。

        この変更は、notyet/opx_sel.patch に置いた。

        これらの信号線は独立した reg として記述してはあるが、実際は、どれか他の reg と 同じもので 最適化で 1 つになってたりするのだろう。それを 独立させてしまったので却って FF が増えたと思う。

        規模を縮小するのは、なかなかに難しい。

      書くのを忘れていた。IN/OUT での RAM アクセスを一応仕込んだ。(IOR_HAVE_RAMWIN) ただ、下位 256B しかアクセスできない。あと これを使う場合バス経由での RAM アドレッシングを IOR の 64B 分ずらしている。

        どうせなら上位アドレスもどこかに置いて設定可能にしたい。上下の アドレスは空いてないから 3bit 分を どこかの隙間を見つけてマップするつもり。

        ところで 64B ずらすのは不要だった。オリジナルも LD の アドレスと同じになるようにしている。

      あと、ブロックRAMのデータファイル。拡張子はなんでも良いが、ISE ではデフォルトは、.mem 。
      追加するときに all にしないといけないのが面倒なので .dat から .mem にすることにした。

    (続く)
    関連記事:

    著作権について

      ここで提示しているコードは正しく動作しないとはいえ、既に著作権は発生しています。
      著作権は、すzが保持しており放棄はしていません。

      教育目的および私用目的では、もともと著作権の範囲外なので自由につかえます。また、ライセンスとして、GPL を適用しています。GPL に従う範囲において 個別の許可なく使用することができます。許諾のための連絡も不要です。

      なお、GPL なので、生成したバイナリを作り直せる範囲のソース開示が必要になります。FPGA だと 通常 チップ全体になってしまいます。また、開示したソースを GPL の範囲で再利用されることを妨げることはできなくなります。このコードを利用する場合この点に留意してください。

      個別の許可を得れば、GPL 以外の条件での使用は可能です。が、作業中のものには許可は出さない予定です。作業が完了(もしくは中断)したとき、ライセンスは見直します。

      なお、すでに公開してしまったものの、ライセンスを取り消すことはできないと考えていますが、ライセンスの追加は可能です。また、新しく公開するものについては、ライセンスしないことすら可能で、どのような制限もありません。変更する可能性がありますので、この点にも留意してください。
    posted by すz at 21:03| Comment(0) | TrackBack(0) | AVR_CORE

    2011年02月27日

    AVR互換コア(その5)

    AVR互換コア(その4)』からの続き。

    新しいことを書く前に(その4)まで について整理しておこう。

    • AVR 互換のコアを Verilog で 0 から作成している。AVR の種類は、Tiny40 (もどき)。

        CPU コアは、従来のどの AVR とも違う AVR8L と言われているもの。『Reduced CORE tinyAVR』という表記も見られる。LDS/STS 命令に互換性がなく、レジスタも r16-r31 の 16 個しかない。

        それでも、gcc サポート済みで gcc からは avrtiny10 という アーキテクチャになっている。

        追記: メモリマップも違う。IOレジスタは 0 - 0x3f で GPR はマップされない。LPM 命令がなくなった代わりに 0x4000 から FLASH がマップされるのも大きな違い。他にも eeprom がないとか違いは結構ある。

    • (その4) では、実装を進めていったが、最終的に全命令と割り込みの実装が完了した。

      • rtavr-wk05g.tar.gz

        (その4) の最終版は、これ。(その5) では、これを整理したもの(論理は変えない)をベースにする。

        個々の命令の検証は未完了で、特にフラグの計算が未チェック。あとデコードも甘く、他の命令を誤認している所があるかも知れない。

        ToDo: 実際のコードを動かしながら、 デバッグしていく。

    • シミュレータとして、『Icarus Verilog for Windows』-- Windows 版 setup (GTKWave 同梱) を 使用。

      rtavr では、make を使っているので MSYS 併用を推奨。()

        ただ、Icarus Verilog は、シンタックスチェックが甘いのと ループした論理を書くと シミュレータ自体無限ループしてしまうという問題があって、ISE も併用している。

        ISE を使うときは、Spartan-3A XC3S200A-4ft256 への Implement をやって 規模 や 遅延も見ている。

    • 規模は、ROM なし / PORT なし / タイマー 0 あり / 割り込みコントローラあり で、537 スライス。これに PORTC と INT0 を組み込むと 562 スライス。

        コアのみの規模で 300 スライスという目標にしたが、まだ分離できないので規模はわからない。今は たぶん 400 スライスぐらい?

        ToDo: 規模を見るために コアだけを Implement できるようにする。

    • 性能は、2 倍速で動かしている GPR がボトルネック。最大遅延は、7.468ns で 2X クロック 133MHz / 実行 66 MHz ぐらい。

        目標は、40-50 MHz ということにしたのでクリア。

        ToDo: GPR を 16bit 幅にして 普通に作ったらどうなるか見てみたい。

    • ゴールは、XC3S50A で SPI-FLASH ライタにできること。

        ToDo: USART を必須としているが未実装。SPI も入れたい。最終的には TWI も。

    コア単体の規模


    まずは、コードの整理をしているので、コア単体の規模を見てみることにした。

      Number of Slice Flip Flops 191 3,584 5%
      Number of 4 input LUTs 745 3,584 19%
      Number of occupied Slices 421 1,792 23%
      Total Number of 4 input LUTs 762 3,584 21%
      Number used as logic 729
      Number used as a route-thru 17
      Number used for Dual Port RAMs 16

    421 スライス。まぁこんなものか。ちなみに、INT0 しかない 割り込みコントローラを 組み込むと +8 の429。

    目標より随分大きくなったが、最適化は当面先。まずはきちんと動かさないことには。

    そもそも、とうやったら最適化できるのだろう? それが分かったとして Spartan-3 向けとSpartan-6 向け で全然違ってくると面白くない。

    ポイントのひとつは、IOR のアドレスデコーダと 出力のセレクタだとは思う。いつか いろいろ試行錯誤してみよう。

    コードの整理


    大分進んだ。

    設定ファイル rtavr_defs.v の中身はいまこうなっている。結構な数だが、説明を残しておく。

      // CLOCK options
      // CLK is generated by GPR
      `define USE_DMY_CLOCK

      // use tri-state bus (emulation for SP3)
      //`define OUTPUT_MUX_TRI
      `define OUTPUT_MUX_NONE

      // tuning option
      `define NO_STALL_IDX
      `define GPR_WB_TUNE

      // for checking performance
      //`define GPR_2X_ONLY

      // top layer configuration
      `define RTAVR_INCLUDE_IOR
      `define RTAVR_INCLUDE_RAM
      //`define RTAVR_INCLUDE_ROM
      `define RTAVR_INCLUDE_INTCONT

      // IOR configuration
      //`define IOR_HAVE_PORTA
      //`define IOR_HAVE_PORTB
      //`define IOR_HAVE_PORTC
      `define IOR_HAVE_SREG_SP
      //`define IOR_HAVE_INT0
      `define IOR_HAVE_TIM0

      // ROM configuration
      //`define ROM_SIZE 1024
      `define ROM_SIZE 2048
      //`define ROM_SIZE 4096

      // S1 options :
      //`define OUTPUT_WB_ADDR
      //`define CHECK_CONFLICT


      (それぞれの説明)

    • USE_DMY_CLOCK
      // CLOCK options
      // CLK is generated by GPR

      汎用レジスタ(GPR) モジュールで CLK2X を分周して CLK を生成する。
      もともとは、DCM を使う予定だったので ダミー としたが本採用するつもり。

    • OUTPUT_MUX_TRI , OUTPUT_MUX_NONE

      // use tri-state bus (emulation for SP3)

      OUTPUT_MUX_TRI は、各モジュール が tri-state で出力をつなげる。記述は tri-state でも実際は、ロジックに変換されることを期待している。OUTPUT_MUX_NONE は、ROM/RAM の OE を使わない指定で デフォルト。なにも指定しないと OE==0 のとき 0 が出力される。OR で接続可能。

    • NO_STALL_IDX, GPR_WB_TUNE

      // tuning option

      チューニングオプションで、両方 define するのが前提。詳しくは (その4) 参照。

    • GPR_2X_ONLY

      // for checking performance

      GPR の CLK2X で動く部分の遅延を見るためのオプション。
      GPR 単独で Implement する。

    • RTAVR_INCLUDE_IOR/_RAM/_ROM/_INTCONT

      // top layer configuration
      トップレベルでの定義。各モジュールを 外に出すかどうかを切り分ける。
      INTCONTは、割り込みコントローラで モジュール としては独立していない。
      define を外すと単に使わない意味になる。

    • IOR_HAVE_PORTA/_PORTB/_PORTC/_SREG_SP/_INT0/_TIM0

      // IOR configuration
      IOR のサブモジュールを使うかどうかの定義。

      SREG_SP/INT0 は内部モジュール。TIM0 は割り込み部分が内部で、他は外部。

      SREG_SP は rtavr では必須だが、他は外すことができる。INT0 を外し、INTCONT を使うと 外部から割り込み線が使えることになる。(テストで使用)

      サブモジュールはこれから増やしていく。

      SREG_SP を 外せるようにしているのは、他の AVR CORE に IOR を組み込んでみたいため。

    • ROM_SIZE

      // ROM configuration
      ROM のサイズ指定。1024/2048/4096 ワードのどれかを指定。

      これから、include する データファイル の指定を追加する予定。

      データファイルは、

      initial
      begin
      rom[12'h000] = 16'h0000;
      :
      :
      end

      という形式で、HEX ファイルを変換するスクリプトを用意するつもり。

    • OUTPUT_WB_ADDR, CHECK_CONFLICT
      // S1 options

      使っていない。

    割り込みのクロック数



    (その4) で割り込みのシーケンスを示した。割り込みに 3 クロック、RETI に 3 クロックかかり 最短 6 クロックで復帰する。割り込みを 2 クロック でできるかもと書いたが.. 間違いだった。

    割り込みはもとより 2 クロックだった。

    • (1) 割り込まれる最後の命令の実行
    • (2) PCL を PUSH
    • (3) PCH を PUSH
    • (4) 割り込みベクタの命令の実行

    となっていて、RETIが 4 クロックだった。

    • (1) PCH を POP
    • (2) PCL を POP
    • (3) POP した値を PC へロード
    • (4) (分岐先の命令をロード: S0)
    • (5) 分岐先の命令の実行

    POP は、S2 への指示なので値が得られるのは、1 クロック後。だから (3) でようやく PC が分かり (4) で INST が分かる。分岐先の処理は、(4) では S0 をやっていて、S1 に来るのは (5) 。

    ... というわけで、最短 6 クロックで合っている。

    ただ、このチェックをしていたら 有効・無効の指示 S0_VALID と s1_invlid がバグっているのが分かった。

    GPRの検討



    今のボトルネックは、2 倍速で動かす GPR 。レジスタを分散メモリで実装したいがために 2 倍速にしたわけだが、普通に作るとどうなるのだろう?


      // 2X
      // Number of Slice Flip Flops: 67
      // Number of 4 input LUTs: 112
      // Number of occupied Slices: 76
      // Total Number of 4 input LUTs: 113
      // Number used for Dual Port RAMs: 16
      // Number of bonded IOBs: 96
      // Post-PAR Static Timing Report
      // Clock to Setup (CLK2X) MAX 7.468

    現在は、76 スライス。そのうち レジスタは 16 (? 1 個しか置けない) 多少増える程度なら置き換えたい。

    まず、分散RAM を絶対に使うことにすると、16 が 32 ? 48 とかに増える。インデックスレジスタを分離することになりそう。分散 RAM は、1bit しかないようだから 完全に分離すると多分 48 。

    それで速くなるなら、従来のと同じ仕様にして差し替えるのも良いだろう。

    本当に 普通に作ると AVR Core のように全部 FF にマップされるだろう。だが 8x16 = 128 bit だ。意外に規模が増えないかもしれない。

    これは、まぁいずれ。

    ROMの使用の検討



    UG62 - XSTユーザガイド』 と 『UG687 -- XSTユーザガイド(Spartan-6用)』を見ると Verilog で ROM の初期値を 設定する方法は、主に 2 通りあるようだ。

    ひとつは、データファイルを用意して、 $readmemh を使う方法。もうひとつは、initial で 記述する方法。もともと予定していたのは、後者で 記述自体を 別ファイルとして生成する方法。

    シミュレータによっては、どちらかしか使えないかも知れないので、両方とも生成してみることにした。

    以前 awk で ihex を変換するツールを作っていたので、生成は awk スクリプト。Windows なら MSYS に最初から含まれているので 今回から MSYS 必須ということにする。

    ちょっと中身を紹介しておくと、


    `ifdef ROM_INCLUDE_V
    `include "rom_data.v"
    `elsif ROM_INCLUDE_DAT
    initial
    begin
    $readmemh("rom_data.dat" , rom, 0 , SIZE-1);
    end
    `endif


    readmemh を使う場合は、1 エントリ 1 行で 16 進のデータ 4 桁のみのデータファイルを用意する。行数は、ROM のサイズに一致しなくてはならない。

    "rom_data.v" の方はこれではサッパリ分からないので 中身を紹介すると

    integer i;
    initial
    begin
    //
    rom[ 0] <= 16'hC010;
    rom[ 1] <= 16'hC017;
    :
    :
    rom[ 45] <= 16'hCFFF;
    for (i = 46 ; i < SIZE ; i = i + 1)
    rom[i] <= 16'hFFFF;
    end



    こんな風になっている。これも全部きっちり初期化しないとならない。が、for 文が使えるのでサイズを減らせる。

    awk には、SIZE を渡さないといけない。Makefile でこんな風に指定している。

    awk --assign rom_size=2048 -f ihex2v.awk $@ > rom_data.v
    awk --assign rom_size=2048 -f ihex2dat.awk $@ > rom_data.dat
    avr-objdump -d $< > rom_data.inst



    さて、この データをどうやって作るかというと.. AVR Toolchain を使う。WinAVR では、Tiny40 に対応していないので注意。

    これを シミュレータにかけたいわけだが、一部を紹介すると こんな風に作った。

    initial begin : test
    sys_reset();
    while ( CLK_COUNT < 20)
    begin
    @(posedge CLK2X);
    @(posedge CLK2X);
    CLK_COUNT = CLK_COUNT + 1;
    end
    $display("*** Simulation Complete! ***");
    $finish;
    end

    クロック数を指定してそこまで動かす。

    試したところ 両方ともシミュレータにかけることができた。ISE の方は、うまくいっていない。"rom_data.dat" の方 は map でエラーになる。"rom_data.v" の方 はエラーにならないのだが、終わらない。まぁ、基本は合っているはずだから、ISE は、おいおいやっていくことにしよう。

      うまくいかない理由は、PORTC を diable にしていたせいだった。入力しかないので、ほとんどが最適化で削除される。"rom_data.dat" の方が早々にエラーになるわけで 早く終るようだ。"rom_data.v" の方 は余計な最適化をやろうとして時間を浪費しているのだろう。

      ちなみに "rom_data.v" では、XST ユーザーズガイドと(ちょっと)違う記述にしている。どうも 全部 <= で代入しないと変な Warning が出るようだ。 あと rom の定義は、rom[SIZE-1:0] が正しいらしいのだが、rom[0:SIZE-1] でないとダメらしい。

      それはともかく、"rom_data.v" も残すが、"rom_data.dat" の方をデフォルトにしようと思う。

    06




    • rtavr-wk06.tar.gz

      これが、整理した最初のバージョン。みるからにうまくいっていない。最初の C010 は、リセットの割り込みベクタの命令で 011 番地に RJMP している。これは良いのだが... C011 を 2 回実行して 022 番地に飛んでしまっている。明らかにスタートアップに問題がある。

      ちなみに、最初に実行されるのは次の命令列。(バイトアドレスなので注意)

      00000022 <__ctors_end>:
      22: 11 27 eor r17, r17
      24: 1f bf out 0x3f, r17 ; 63
      26: cf e3 ldi r28, 0x3F ; 63
      28: d1 e0 ldi r29, 0x01 ; 1
      2a: de bf out 0x3e, r29 ; 62
      2c: cd bf out 0x3d, r28 ; 61
      2e: 12 d0 rcall .+36 ; 0x54 <main>
      30: 13 c0 rjmp .+38 ; 0x58 <_exit>


      こんな風にバグがあるが、ここまでこぎつけた。バグは直せば良いわけだ。当面は、このコードを動かせることを目標にする。

      ちなみに、C のソースと ihex2v.awk , ihex2dat.awk は、tn40test ディレクトリに置いてある。テストベンチと rom_data.v, rom_data.dat , rom_data.inst は、tb_rom ディレクトリにもコピーしてある。上記を再現するだけなら AVR Toolchain も MSYS (awk) も必須ではない。

    06A




    • rtavr-wk06a.tar.gz

      スタートアップは直った。これは、スタートアップのバグではなく、s1_invalid なのに jump してしまうというバグだった。あと jump 系は 2 命令なので、PC を進めて(+ 1) おいて ディスプレースメントを足すという処理に変更した。(RET/RETI でバグッたかも)

      で、最初の命令が xor r17,r17 。r17 が不定値でも結果は 0 なのだが、 Icarus Verilog は、結果を不定値とみなしてしまう。これでは困るので、GPR をゼロクリアしておくことにした。

      さて、やっていることは、スタックポインタを設定して main を call しているだけ。SP が 13F になって 2A を call しているので OK 。



      次は main だが、たった 2 命令だった。


      00000054
      :
      54: e0 9a sbi 0x1c, 0 ; 28
      56: ff cf rjmp .-2 ; 0x56 <main+0x2>


      sbi して、無限ループ。

      ここで sbi にバグがあった。IOR から ロードして、1bit セットして 書き戻すわけだが、ロードデータとストアデータがつながってなかった。

      つなげるための指令もない。これは、CMD_LOAD & CMD_STORE で代替することにした。ロードしてストアするものは、SBI/CBI (BCLR/BSET) しかないので これで良いし、なにより ロードしてストア するという条件そのもので 妥当そうに思える。

      ロードデータは ior から読みだして bit 操作した v_ior_dob2 と ROM/RAM も MUX した load_deta があるのだが どちらをつなげるのが良いか悩んだ。ISE で implement して比べたところ v_ior_dob2 の方が 4 スライス少なかったので v_ior_dob2 にした。

      どちらも同じデータになるので単に配線の問題なのだが、この程度で 4 スライスも変わってしまう。よくよく気をつけることにしよう。

        気になっていた OR での MUX を試してみた。IOR の サブモジュール PORTC と TIMER0 を OR で MUX したところ 17 スライスも減った。

      無限ループの方は問題なし。当面と書いたのに終わってしまった。この調子でどんどんコードを作って試したいが、06 のディレクトリ構造だとスナップショットを取るのに不便なので、C のソースとテストベンチを 1 つのディレクトリに置くよう変更した。

      メモ: 未修正のバグ:
    • RET/RETI での s1_invalid の設定。

      (上述)

    • 割り込みコントローラの INT_VEC ハンドリング。

      割り込み処理中に 別の割り込みが来ると値が変わってしまい、新たな割り込みの方を RST してしまう。


    06B



    • rtavr-wk06b.tar.gz

      C のプログラムを書き換えて グローバル変数を使ってみた。この場合、
      <__ctors_end>: が変わり次のようになる。


      00000022 <__ctors_end>:
      11: 2711 eor r17, r17
      12: bf1f out 0x3f, r17 ; 63
      13: e3cf ldi r28, 0x3F ; 63
      14: e0d1 ldi r29, 0x01 ; 1
      15: bfde out 0x3e, r29 ; 62
      16: bfcd out 0x3d, r28 ; 61

      0000002e <__do_clear_bss>:
      17: e010 ldi r17, 0x00 ; 0
      18: e6a0 ldi r26, 0x60 ; 96
      19: e0b0 ldi r27, 0x00 ; 0
      t;

      00000036 <.do_clear_bss_loop>:
      1b: 931d st X+, r17

      00000038 <.do_clear_bss_start>:
      1c: 36a2 cpi r26, 0x62 ; 98
      1d: 07b1 cpc r27, r17
      ;
      1f: d00f rcall .+30 ; 0x5e <main>
      20: c01b rjmp .+54 ; 0x78 <_exit>

      まずはここまで、確認してみる。

      その前に、... 次で動く main はこんな風になった。

      0000005e <main>:
      2f: e081 ldi r24, 0x01 ; 1
      30: e090 ldi r25, 0x00 ; 0
      31: 9390 0061 sts 0x0061, r25
      33: 9380 0060 sts 0x0060, r24
      35: dfec rcall .-40 ; 0x44 <func_a>
      36: 9310 0061 sts 0x0061, r17
      38: 9310 0060 sts 0x0060, r17
      3a: dfe7 rcall .-50 ; 0x44 <func_a>
      3b: cfff rjmp .-2 ; 0x76 <main+0x18>

      なんと、2 バイト命令の sts ( や lds ) を使っている。もちろん gcc のバグで ググると報告されている。(『ATtiny10 problems』スレッド)

      だが、最新版はどうやって入手できるのだろう? ベータサイトでは Published: 2010-12-03 の as4e-ide-2.7.0.851-XX しか入手できないのだが ... ソースコードも入手できていない。... 待つしかないのか?

      いっそのこと 2 word 命令の LDS/STS を実装してしまうという手はある。これだけが問題で、待ちきれなくなったら検討してみよう。1 word 命令の LDS/STS は従来の命令をオーバライトしているが、2 word の方は単に サポートしていないだけなので、可能ではある。

      これは非常に問題だが、まずは目の前の命令から。0x60 と 0x61 に 0 をストアするコードだから、2 回ループするだけのはずが... 実は無限ループしている。



      詳しく検証してみよう。1b(931d) の実行時の IE が ストアアドレス。1回目は 0x60 だが、2 回目は、FF9E ? ... なんだろう?

      続く命令は、cpi , cpc どちらもレジスタには書き戻さない。... が cpc で CMD_OP3_WR が 1 になっている。... cpse / nop はフラグも書き戻さないが cp/cpc は レジスタだけ書き戻さないのか。

      さらに、cpi は CMD_OP2 だが、全部書き戻すように コーディングしていた。



      これは直した。ちゃんと 61 になっている。... が ループは止まらない。

      まずは、CPI を見てみると .. 0 - IMM_DATA としていた。Rd_in - IMM_DATA が正しく、書き戻さないという点を除いて SUBI と同じ。

        SUBI と同じことをするよう変更したら、規模がすこし小さくなった。ならば、SUBIと SBCI をひとつにすると ... 15 スライスも減った。

        ううむ。ひとつの ALU にした方が規模が小さくなるのかも知れない。



      これを直したら先に進んだ。次は、1f: RCALL(d00f) で、main(0x2f) にちゃんと飛んでいる。で、main は動かないわけだ。

      さてどうしよう。どうせ 16bit の LDS/STS で届く範囲しかメモリがないはずだから、
      32bit LDS/STS を 16bit LDS/STS + NOP に書き換えることは可能だ。


      32bit:
      LDS 1001:000d:dddd:0000 # load direct (+ kkkk:kkkk:kkkk:kkkk)
      STS 1001:001d:dddd:0000 # store direct (+ kkkk:kkkk:kkkk:kkkk)

      16bit:
      LDS 1010:0kkK:dddd:kkkk # 16bit
      STS 1010:1kkK:dddd:kkkk # 16bit

      2 word 命令は 1 つしかなく、91r0/93r0 で始まる。これを aXrX に直してやれば良いわけだ

      ちょうど 逆アセンブルリストを バイト表記から ワード表記に変換するスクリプトを書いているので、細工して 変更するデータが分かるようにしよう。


      00000044 <func_a>:
      # 22: 9180 0060 lds r24, 0x0060
      22: a480 0000 lds r24, 0x0060
      # 24: 9190 0061 lds r25, 0x0061
      24: a491 0000 lds r25, 0x0061
      26: 1781 cp r24, r17
      27: 0791 cpc r25, r17
      28: f019 breq .+6 ; 0x58
      29: 9a28 sbi 0x05, 0 ; 5
      2a: 9a30 sbi 0x06, 0 ; 6
      2b: 9508 ret
      2c: 9830 cbi 0x06, 0 ; 6
      2d: 9828 cbi 0x05, 0 ; 5
      2e: 9508 ret

      0000005e <main>:
      2f: e081 ldi r24, 0x01 ; 1
      30: e090 ldi r25, 0x00 ; 0
      # 31: 9390 0061 sts 0x0061, r25
      31: ac91 0000 sts 0x0061, r25
      # 33: 9380 0060 sts 0x0060, r24
      33: ac80 0000 sts 0x0060, r24
      35: dfec rcall .-40 ; 0x44 <func_a>
      # 36: 9310 0061 sts 0x0061, r17
      36: ac11 0000 sts 0x0061, r17
      # 38: 9310 0060 sts 0x0060, r17
      38: ac10 0000 sts 0x0060, r17
      3a: dfe7 rcall .-50 ; 0x44 <func_a>
      3b: cfff rjmp .-2 ; 0x76 <main+0x18>

      こんな風にチェックして、変更するデータを作ることは awk で出来た。だが、実際のデータはいちいち手で修正しないといけない。

      すごく面倒だが、かと言って変更まで自動化するつもりはない。どうせ 文字列とグローバル変数を使わなければ、STS/LDS は出ないのだ。このテストと文字列のテストだけは、手で修正することにしよう。



      変更はうまくいったようだ。61 に 0 を 60 に 1 をストアしている。
      そして、<func_a> (022) を call 。画面では分からないが、00 と 36 を push している。



      次にいこう。 <func_a> では if 文があり、028 の breq(f019) がそれに相当する。1 回目は分岐せず sbi , sbi , ret (02b:9508) 。

      ret では、なぜか 0xff , 0xff を POP してきて fff に 飛んでいる。なにかエンバグしているようだ。

      これは、ROM と RAM の出力を OR でつなぐオプションが有効になってしまったため。ROM/RAM に OE があるときだけしか使えない。

      0xff は ROM の値。



      とにもかくにも、最後の無限ループにたどりついたようだ。

    06C




    • rtavr-wk06c.tar.gz

      ret がおかしいのを直した。いままで ret が 3 クロックだと思っていたので そこで間違っていた。

      ret は 4 クロックで、3 クロック分 命令の実行を抑止する。最初の 2 つは s0_inv_skip で 単なる抑止。最後の 1 つは、s0_inv_jump で 他の 分岐命令と同じ。画面は、最初の ret 命令で、そのようになっている。

      戻ったところで、9310 / 0061 になっているが、実は、32bit LDS/STS をサポートした。
      上記のリンクでは、

        (Jan 14, 2011 - 08:07 AM)
        Post subject: Re: RE: ATtiny10 problems
        lfmorrison wrote:
        There's more wrong than just that:
        The LDS and STS instructions are clearly the 32-bit variants, which don't exist in an ATtiny10. For an ATtiny10, it should have been using the 16-bit variant of the LDS and STS instructions.


        Now that I've read up on the instruction set I actually understand what you mean. Clearly something is going wrong. I'm going to try with IAR KickStart edition in the mean time.

      こう書いてある。1/14 に問題を認識しているからいつか直る。だが、32bit 版があると 本当はすごく便利なのだ。ROM にある STRING や定数を直接アクセスできる(= RAM にコピーしなくて良い)し、グローバル変数を沢山持てる。

      まぁこういうことをするには、avr-gcc のソースを入手してさらに改造しないといけないから、なかなか難しいのだが サポートできるようにしておくのも良いかも知れない。

      それに、規模は大きくなるとしても、コード自体については define で切り分けて対応することは 難しくなかった。

        32bit LDS/STS は、v_ldst のうちに入っている。未定義命令だったから 論理の少ない方を選んでこうなったわけだ。あらたに v_ldst32 を作るのは簡単。

        処理も簡単なのだ。v_ldst32 の 次のクロックで s2_ldst32 を 1 にして、そのときは EA を使わずに INST を使う。あとは、s0_inv_skip に v_ldst32 も加える。

      いちいち命令を 変換するのも面倒なので、当面このままにする。ちなみに、ここまでのスナップショットは、tbr_003 。命令が変わったから 別にする。

      次、ストリングを out する風にしてみた。

        void func_a(int8_t sel) {
        if (sel) {
        bit_set(DDRC, PB0);
        bit_set(PORTC, PB0);
        } else {
        bit_clear(PORTC, PB0);
        bit_clear(DDRC, PB0);
        }
        }

        main() {
        int8_t i;
        char *p = "abc";
        for (i=0; i<3; i++) {
        func_a(i & 1);
        }
        while (*p) {
        PORTB = *p;
        p++;
        }
        return 0;
        }

      main は、return すると、最後に無限ループする。今回はこれを試してみよう。


      00000022 <__ctors_end>:
      11: 2711 eor r17, r17
      12: bf1f out 0x3f, r17 ; 63
      13: e3cf ldi r28, 0x3F ; 63
      14: e0d1 ldi r29, 0x01 ; 1
      15: bfde out 0x3e, r29 ; 62
      16: bfcd out 0x3d, r28 ; 61
      17: d00a rcall .+20 ; 0x44 <main>
      18: c01b rjmp .+54 ; 0x68 <_exit>
      00000034 <func_a>:
      1a: 2388 and r24, r24
      1b: f019 breq .+6 ; 0x3e <__SP_H__>
      1c: 9ae0 sbi 0x1c, 0 ; 28
      1d: 9ae8 sbi 0x1d, 0 ; 29
      1e: 9508 ret
      1f: 98e8 cbi 0x1d, 0 ; 29
      20: 98e0 cbi 0x1c, 0 ; 28
      21: 9508 ret

      00000044 <main>:
      22: e080 ldi r24, 0x00 ; 0
      23: dff6 rcall .-20 ; 0x34 <func_a>
      24: e081 ldi r24, 0x01 ; 1
      25: dff4 rcall .-24 ; 0x34 <func_a>
      26: e080 ldi r24, 0x00 ; 0
      27: dff2 rcall .-28 ; 0x34 <func_a>
      28: e6e0 ldi r30, 0x60 ; 96
      29: e0f0 ldi r31, 0x00 ; 0
      2a: c003 rjmp .+6 ; 0x5c <main+0x18>
      2b: b986 out 0x06, r24 ; 6
      2c: 5fef subi r30, 0xFF ; 255
      2d: 4fff sbci r31, 0xFF ; 255
      2e: 8180 ld r24, Z
      2f: 2388 and r24, r24
      30: f7d1 brne .-12 ; 0x56 <main+0x12>
      31: e080 ldi r24, 0x00 ; 0
      32: e090 ldi r25, 0x00 ; 0
      33: 9508 ret

      00000068 <_exit>:
      34: 94f8 cli

      0000006a <__stop_program>:
      35: cfff rjmp .-2 ; 0x6a <__stop_program>


      出てきたコードはこれ。最初のループは inline 展開されている。次に -1 を sub しながら string をたどり、out している。

      これコードはあっているが、string をメモリに展開しない。展開しないのなら、ROM にあるデータのアドレスを Z に入れないとだめ。

      rom[ 54] <= 16'h6261;
      rom[ 55] <= 16'h0063;

      文字列はここにある。アドレスは、0x406C 。(ROM を 参照する場合 + 0x4000 する。アドレスはバイトアドレス)

      # 28: e6e0 ldi r30, 0x60 ; 96
      28: e6ec ldi r30, 0x6c ; 108
      # 29: e0f0 ldi r31, 0x00 ; 0
      29: e4f0 ldi r31, 0x40 ; 64

      こう直さないと 動かない。-- さすがにこれを手で修正するのはきつい。だが、まぁやってみよう。

        すこし説明しておく。今までの AVR は、LD 命令で ROM を見れなかったので、リードオンリーデータを初期値つきデータとして定義して RAM にコピーして使っていた。領域を使われるのが嫌なら LPM を使う。この AVR (AVR8L) は、インデックスを使った間接なら LD 命令で ROM も見れる。gcc はそういう風なコードを出すが、アドレスは以前のままなわけだが、これは、binutils の ld か (avr-libc での )ldの設定も関係あり gcc 自体の問題ではないかも知れない。

        avr-gcc が出すアセンブラを確認してみた。

        .data
        .LC0:
        .string "abc"
        :
        :
        ldi r30,lo8(.LC0)
        ldi r31,hi8(.LC0)

        これの .data を外すと .LC0 が ROM 相対値に変わる。.LC0 となっているところを .LC0 + 0x4000 とすると期待する値にはなる。

        .LC0:
        .string "abc"
        :
        :
        ldi r30,lo8(.LC0 + 0x4000)
        ldi r31,hi8(.LC0 + 0x4000)

        こう。
        これなら、C のソースで 合うように書ける。

        char *p = PSTR("abc") + 0x4000;

        これで期待した バイナリは作れることは作れる。しばらくこれで凌ぐか。



      02F の LD ,Z(8180) で 0x61 を読み込んでいる。そして 0 でないから 02B に 分岐。で 02B の OUT(B986) でちゃんと 0x61 を 出力している。次に +1 して ちゃんと 0x406D から 次の 0x62 を読み込んでいる。ここまで OK 。



      で、先を見ていくと .... だめだ止まっていない。0 を out してその後もずっと続く。

    06D



    • rtavr-wk06d.tar.gz



      この原因は、FLAGS_BIT_IN のデータだった。bit を選択するのに、SBIX_BIT という skip 命令用の index を使っていたのだが、これは S2 での判断なので、1 クロック遅れた値。分岐命令は、S1 で 判断するので、これを使ったらダメだった。FLAG 用に FLAGS_BIT を作って対処。

      さて、これで OK かと思ったら main からの ret で 0x800 に飛んでいる。(正確には 0x1800)。0x0018 に戻るのが正しいのだが...  rcall はその前でも使っていてちゃんと動いている。なぜこれだけおかしいのか?

      最初の main への ICALL は、0x13F/0x13E に対して 0018 を書いている。最後の ret は、0x13D/0x13E -- ということはずれているわけだ。



      この問題は、PUSH/POP での スタック操作の条件を見直すことで直った。最後に 034 がみえているが、<_exit> に到達している。cli があるわけだが、もともと I フラグは 0 なので状態が変わらない。035 の無限ループは検証済みで実際問題ない。

      2 つばかり C での検証をこなしたわけだが、string データの アドレスが違う (avr-gcc の)問題は致命的だ。もう少し続けたいが、大きな値の定数や string を使わないようにしようと思う。それと並行して 最新版を手に入れるようにしないと 。手に入らないようなら C での検証を一旦打ち切りにしようと思う。

        string は、PSTR + 0x4000 で凌げることが分かった。初期値 0 のグローバル変数も無理やり使えるようにした。だが、いろいろコードを作ってみると変なところが多々あるようだ。やはりちょっと待ってみることにしたい。

      命令毎にどこまで評価が終わったかの表を作らないと実用的なプログラムが動くとは思えない。avr-gcc 自体も信用できないから こまめに avr-gcc の version を上げたくない。デバイスも組みたいので、しばらく 検証自体を 先送りにするかも。

      やはり、割り込み周りのデバッグとデバイスのデバッグ・作成に移ろうかと思う。タイマーは作っただけだし、INT0 もおかしい。これらのテストに CPU はほとんど関係ない。というか CPU はジャマ。IOR だけのテストベンチに 割り込みコントローラだけで良い。

      で、デバイスは SPI → USART の順にすることにした。 SPI の方がまだ簡単。あと、デバイス編は別記事にしようと思う。

    (続く)
    関連記事:

    著作権について

      ここで提示しているコードは正しく動作しないとはいえ、既に著作権は発生しています。
      著作権は、すzが保持しており放棄はしていません。

      教育目的および私用目的では、もともと著作権の範囲外なので自由につかえます。また、ライセンスとして、GPL を適用しています。GPL に従う範囲において 個別の許可なく使用することができます。許諾のための連絡も不要です。

      なお、GPL なので、生成したバイナリを作り直せる範囲のソース開示が必要になります。FPGA だと 通常 チップ全体になってしまいます。また、開示したソースを GPL の範囲で再利用されることを妨げることはできなくなります。このコードを利用する場合この点に留意してください。

      個別の許可を得れば、GPL 以外の条件での使用は可能です。が、作業中のものには許可は出さない予定です。作業が完了(もしくは中断)したとき、ライセンスは見直します。

      なお、すでに公開してしまったものの、ライセンスを取り消すことはできないと考えていますが、ライセンスの追加は可能です。また、新しく公開するものについては、ライセンスしないことすら可能で、どのような制限もありません。変更する可能性がありますので、この点にも留意してください。
    posted by すz at 23:20| Comment(3) | TrackBack(0) | AVR_CORE

    2011年02月22日

    AVR互換コア(その4)

    AVR互換コアの仕様(その3)』からの続き。

    そろそろ、シミュレータ に移行する。ISE は重くてやってられないし、Implement するための基本の書き方はクリアしたので当面不要。パーツも揃ってきたので、動かしながら 機能を付けていく。

    シミュレータは、ここを見て、Windows 版 がある Icarus Verilog を使うことにした。

    まずは紹介。簡単なテストベンチを用意して、動かしてみたらこうなった。


    準備


    これの評価は後ですることにして、これを出すためのものについて

    用意するものは、これだけ。Icarus Verilog for Windows には、GTKWave が 同梱されているので、他にツールはいらない。MinGW+MSYS は make で 自動化するのに使っているが、コマンドプロンプトで 直接実行することも可能だから 必須ではない。

    シミュレータで動かすために変更したところは以下のもの。

    • rtavr_defs.v の定義で PORTA/PORTB/PORTC を外す。そうすると rtavr_ior_port を使わなくなってしまうので、rtavr_ior.v から分離して別ファイルにした。

    • rtavr_defs.v の定義で ROM を外す。ROM へのアクセスを テストベンチの TOP にまで持って行って、そこから データを送り込む。

    • エラーになったところは、ビット幅を指定しない 定数のみ。エラーになったところだけ修正。

    動作の検証


    最初のテストベンチは、nop を送り込むだけのもの。一体どういう風に動くのかまずは見てみることにした。

    一見してひどいと分かるが、これが最初だから別にチェックするものがある。

    • まず CLK 。想定どおり。 (後述)

    • ROM_ADDRA は PC を示しているが、RESET 後 次のクロックで 001 になってしまっていて、1 番地のデータが 最初の ROM 読み出しになってしまっている。そこはおかしいが、値が変わるタイミングはあっている。

    • その期間、S1 は不定値を元に動くため、次の期間 S2 への指示が不定値になっている。

    • CMD_OP3 が、1 になるのは正しい。NOP は OP3 の仲間で OP3_SEL = 0 。もうすこし詳しく書くと、CPSE の一種で 決して条件が成立しないという設計にしている。ただ、これだけ不定期間が長い。なぜ?

    いよいよ、楽しくなってきた。まずは RESET 周りをちゃんとして、その後いろんな命令をシミュレートしてみよう。その後、いくつか命令の組み合わせをやって、それがクリアできたら、パイプライン制御。

    05A




    • rtavr-wk05a.tar.gz

      変更すれば、前のテストベンチの出力も変わってしまう。面倒だがいちいち スナップショットを取ることにした。

      まず、RESET周りだが、CLK で止めていたのが最初の間違い。そんなことをすれば、他の回路は RESET がかからない。initial 使って 初期化はするが、RESET で CLK は止めないようにした。

      あと、S1 をあれこれいじってちゃんとスタートアップできるようにした。

        initial begin : test
        // LDI 1110:KKKK:dddd:KKKK
        sys_reset(16'he51a); // LDI r17, 0x5a
        // DEC 1001:010d:dddd:1010 #
        rom_out(16'h951a); // DEC, r17
        // MOV 0010:11rd:dddd:rrrr #
        rom_out(16'h2f21); // MOV r18,r17
        rom_out(16'h0000); // NOP
        rom_out(16'h0000); // NOP
        rom_out(16'h0000); // NOP
        rom_out(16'h0000); // NOP
        $display("*** Simulation Complete! ***");
        $finish;
        end

      今回は、LDI / DEC / MOV 。

      最初のクロックは捨てて、次から開始。

           E51A 951A 2F21 0000 0000
        S0 LDI DEC MOV NOP NOP
        S1 LDI DEC MOV NOP
        S2 LDI DEC MOV
        CMD_OP2 CMD_OP1 CMD_OP3(WR)
        XX_SEL 2 4 7

      こんな風に見る。シーケンスはあっていそうだ。

        LDI の OP2_SEL は 2 だがあっている。DEC も、4 で OK。(コードをみたら - 0xff していた)。MOV の OP3_SEL も 7 で OK。

      GPR はあっているのだろうか?

        そもそも、DEC なのに 0x5A から 0x5B に増えている。(→ DEC のバグ) さらに、次の命令で 0x5A を見ている。r18 のロードも不定値でないといけない。
        あと、GPR_ADDRxx が変化するタイミングはこれで良かったのか?

      -- いくつか問題がありそうだ。

      最初に動かすのだから、まぁこんなものか。思ったよりはマシな感じがする。

        後で調べたら、Rd_in と Rr_in が逆だったり、タイミングが変になっていたり、いろいろグダグダになっている。GPR 単独でデバッグするつもり。

    05B




    • rtavr-wk05b.tar.gz

      なんとなく正しそうな、結果を得た。

        initial begin : test
        // LDI 1110:KKKK:dddd:KKKK
        sys_reset(16'he51a); // LDI r17, 0x5a
        // DEC 1001:010d:dddd:1010 #
        rom_out(16'h951a); // DEC, r17
        // MOV 0010:11rd:dddd:rrrr #
        rom_out(16'h2f21); // MOV r18,r17
        // SUBI 0101:KKKK:dddd:KKKK #
        rom_out(16'h5510); // SUBI r17, 0x50
        // ADD/ADC 000C:11rd:dddd:rrrr #
        rom_out(16'h0f21); // ADD r18,r17

        rom_out(16'h0000); // NOP
        rom_out(16'h0000); // NOP
        rom_out(16'h0000); // NOP

        $display("*** Simulation Complete! ***");
        $finish;
        end

      今回命令も追加。LDI / DEC / MOV / SUBI / ADD の 計 5 命令。


           E51A 951A 2F21 5510 0F21 0000 0000
        S0 LDI DEC MOV SUBI ADD NOP NOP
        S1 LDI DEC MOV SUBI ADD NOP
        S2 LDI DEC MOV SUBI ADD

        Rd_in xxxx 0x5A xxxx 0x59 0x59
        Rr_in xxxx 0x59 0x09
        DI(Rd_out) 0x5A 0x59 0x59 0x09 0x62

      検証したが、一応正しいようだ。
      ちなみに、直前の結果を使っていないのは、最後のADD の Rd_in と最初の LDI だけ。

      バグで大きかったのは、GPR 。なにを勘違いしたのか ... negedge CLK2X でのみアクセスすべき ところを posedge CLK / negedge CLK に変えてしまっていた。

      それ以外だと

      • Rd_in と Rr_in の定義が逆。(上述)

      • negedge CLK2X での CLK 値の取得。(上記と同じ理由で まずい ので posedge CLK2X で一旦ラッチ

      • ADDRBL/ADDRBH のラッチとか フォワーディング関係。

      ... なんだか結構直してようやく、こんな感じになった。対処療法的な修正なので、ちょっと気持ち悪い。INDEX レジスタ操作でもう一回見直すので、そのときに再チェックしよう。

    05C




    • rtavr-wk05c.tar.gz

      STORE 命令 。大分苦労したが、なんとなく正しそうな、結果を得た。

      まず、インデックスレジスタの インクリメントとデクリメント。これは、read-modefy-write で できるようになった。

      だが、設計では、インデックスレジスタ の読み込みと Rd の書き込みを (無謀にも)PORTA だけでやろうとしていたのだった。どうにも上手くいかないようなので、仕様変更をすることにした。

      PORT A は、

        (1) Rd を WRITE する。
        (2) インデックスレジスタ(16bit) の READ (+ INC/DEC)

      の2つの使い方だけをする。これは変わらないが、(1) と (2) が競合したら、(2)( = 後の命令 ) を 1clock 遅らせる。

        // LDI 1110:KKKK:dddd:KKKK
        rom_out(16'he0a0); // LDI r26, 0x00
        rom_out(16'he0b1); // LDI r27, 0x01
        // ST.X++.Rr 1001:001r:rrrr:1101 #
        rom_out(16'h931d); // ST X++, r17 # X 0x0100 -> 0x0101
        rom_out(16'h931d); // reentrant

        // ST.X.Rr 1001:001r:rrrr:1100 #
        rom_out(16'h932c); // ST X, r18 # X 0x0101
        // ST.--X.Rr 1001:001r:rrrr:1110 #
        rom_out(16'h931e); // ST --X, r17 # X 0x0100
        // ST.--X.Rr 1001:001r:rrrr:1110 #
        rom_out(16'h932e); // ST --X, r18 # X 0x00ff

        // LD.Rd.X 1001:000d:dddd:1100 #
        rom_out(16'h913c); // LD X, r19 # X 0x00ff

      GtkWave の画面は、このようなシーケンスを出したもの。

      ST が並んでいて、最初の 931D だけが 2 クロックかかっている。これは、ひとつ前の LDI で Rd に書く -- (1) のと ST での インデックスレジスタの読み込み -- (2)が競合したために遅らせたわけだ。

      ST 自体はいくら並べても競合しない。最後に ST → LD があるがこれも競合しない。

      ちんみに、下に見える EA が インデックスレジスタ値。 PREDEC/POSTINC もしている。



      これは、続くシーケンス。


        // LD.Rd.X 1001:000d:dddd:1100 #
        rom_out(16'h913c); // LD X, r19 # X 0x00ff
        rom_out(16'h913c); // reentrant
        // LD.Rd.--X 1001:000d:dddd:1110
        rom_out(16'h914e); // LD --X, r20 # X 0x00fe
        rom_out(16'h914e); // reentrant
        // LD.Rd.X++ 1001:000d:dddd:1101 #
        rom_out(16'h915d); // LD X++, r21 # X 0x00fe -> 0x00ff
        rom_out(16'h915d); // reentrant
        rom_out(16'h916d); // LD X++, r22 # X 0x00ff -> 0x0100
        rom_out(16'h916d); // reentrant

        rom_out(16'h0000); // NOP
        rom_out(16'h0000); // NOP
        rom_out(16'h0000); // NOP
        $display("*** Simulation Complete! ***");
        $finish;
        end

      LD をやっているのだが、全然ダメみたいだ。ちなみに、LD はレジスタに書くから競合しまくる。2 クロックかかるわけだ。

      せめて、PREDEC/POSTINC しないときは、1 命令で済ませたいような気がする。PORTB は、2 つロードできるが、1 つしか必要ない。PORTA の代わりにPORTB から読ませることは可能。

      PORTB は、書き込むレジスタ値の先取り(フォワーディング)もしているし、うまく行くかも知れない。
      これは、基本が動いてから、回路規模と相談して決めようと思う。

      図らずも簡単なパイプライン制御のテストになってしまった。JUMP や SKIP は、割と簡単に入るような気がしてきた。実をいうと PUSH や POP のような 複数クロック命令をどう入れようか悩んでいたのだが、今の実装だと、前の命令を止めたときに S1 の状態が 止めた命令で固定される。サブステートを作ってやれば簡単に実装できるような気がしてきた。あと実装方法が決まらないのは割り込みぐらい。

    05D




    • rtavr-wk05d.tar.gz

      次は、LOAD 命令 。

      STORE 命令は OKなのに、LOAD はダメなのは、仕様上の問題だった。

      LOAD 命令 は、インデックスレジスタ番号と Rd を指定する。インデックスレジスタは 上位バイトと下位バイトがあるから 3 つ指定する必要があったが、全部 PORTA だったから 競合していた。

      最初の解決策は、下位バイト + 1 だと決まっているから、GPR 内で計算する方法。
      もうひとつ解決策があって、PORTB の Rr に 上位バイトのレジスタ番号を指定する方法。この方法にすると、アドレスだけでなく中身もロードできてしまう。それなら、PREDEC/POSTINC しないときだけでも、中身も使いたい。そうすれば、PORTA の 上位バイトが空いて競合が減る。
      これを NO_STALL_IDX という define で切り分けるようにした。

      ちょっと苦労したが、最終的には、どちらも動かすことができた。
      掲載しているのは、NO_STALL_IDX の方。LD の最後の命令(917C)は、LD X を 2 つ並べたが、(競合しないので)どちらも実行されている。

      さて、ISE で規模がどれぐらい変わるか確認してみた。

      // (1) (2) (3) (4)
      // Number of Slice Flip Flops: 226 228
      // Number of 4 input LUTs: 765 780
      // Number of occupied Slices: 471 475
      // Total Number of 4 input LUTs: 805 821
      // Number used as logic 749 764
      // Number used as a route-thru 40 41
      // Number used for Dual Port RAMs 16 16
      // Number of bonded IOBs : 67 67
      // IOB Flip Flops : - -
      // Number of BUFGMUXs : 2 2
      // Number of RAMB16BWEs : 1 1
      // Clock to Setup
      //on destination clock CLK2X:
      // Rise-Rise 10.052 9.871
      // Fall-Rise 10.911 10.828
      // Rise-Fall 9.137 10.367
      // Fall-Rise 10.484 11.251
      //
      // (1) NORMAL for simulating -- NO ROM/PORTA/PORTB/PORTC
      // (2) + NO_STALL_IDX


      スライスで +4 しか違わない。性能には微妙な違いがあるが、1 クロック遅らせるケースが減るのだ。これからは IO_STALL_IDX を標準にしよう。



      うまく行ったので、仕込んでいた RJMP / IJMP / SBRC/SBRS 命令に移った。

      // RJMP 1100:kkkk:kkkk:kkkk #
      rom_out(16'hc100); // RJMP +100
      rom_out(16'h0000); // NOP

      // LDI 1110:KKKK:dddd:KKKK
      rom_out(16'he2e0); // LDI r30, 0x20
      rom_out(16'he0f2); // LDI r31, 0x02

      // IJMP 1001:0100:0000:1001 #
      rom_out(16'h9409); // IJMP
      rom_out(16'h0000); // NOP
      // SBRC 1111:110r:rrrr:0bbb #
      // SBRS 1111:111r:rrrr:0bbb #
      rom_out(16'hfdf0); // SBRC r31,0
      rom_out(16'hfdf1); // SBRC r31,1
      rom_out(16'h0000); // NOP
      rom_out(16'hfff1); // SBRS r31,1
      rom_out(16'hfff0); // SBRS r31,0
      rom_out(16'h0000); // NOP

      命令列はこう。分岐してもアドレスが変わるだけで、命令列は決まっている。

      最初の RJMP(C100) は、アドレスが + 0x100 になって、S1 を無効にしている。RJMP は アドレス 0x012 ではなく、0x011 。INST が 0x011 になったときには、すでに次の命令を読み込んでいるわけだ。それを無効にしているだけだから、2 クロックで RJMP 命令が動いている。

      次は、Z レジスタに 0x220 を入れて IJMP 。S2 で書き込んだ 値を 同じクロックで 読み込めているから ちゃんと 0x220 に飛ぶ。

      SBRC/SBRR はスキップするだけ。Tiny10 系は、1命令 1 ワードと決まっているから 条件が成立したら、S1 を無効に するだけの処理で良い。-- ちょっと、仕様が変わったので変更するところがあったが、問題なし。

      条件分岐は、完成していないが、JMP 系と スキップ命令の 合わせ技のようなものだから、問題なく組み込めるだろう。

      あと、IN/OUT LDS/STS , SBI/CBI , BLD/BST といったものが残っている。これらを片付けてから、CALL / PUSH / POP / 割り込みに移ろうかと思う。

      最後に、実際のプログラムを動かしながら個々の命令を検証していく。

      ついに、完成が見えてきた。あとひといき。

        追記: IN/OUT LDS/STS は、OK だった。BLD/BST , SBI/CBI も組み込んだ。だが、SBI/CBI が少々論理を消費していて、ちょっと悩ましい。

          BLD / CBI(SBI) / BSET(BCLR) 実装するだけで、39 スライスも消費している。命令デコードもふくんでいるから 多くなっているのかも知れないが、書き方が悪いのではないかという気もしている。ちなみに BSET(BCLR) は、CBI(SBI) のロジックを間借り。0x3f (SREG) に対する SBI(CBI) として実装している。あと残っている命令は、PUSH/POP/ICALL/RCALL/RET/RETI (+ 割り込み)のみ。

        いまの性能のネックは、GPR 。POSTINC と PREDEC の両方を 1 クロックでやるのは無理があった。試しに PREDEC の機能をなくすと、割と良いバランスになった。オリジナル同様 PREDEC に 1 クロックかけたほうが良いらしい。

        だが、複数クロックの命令という概念を導入した、後でしか入れられない。とりあえずは、今の枠組みの中で工夫してみることにする。

        これなのだが、以下の GPR_WB_TUNE の部分に変えてみた。

        `ifdef GPR_WB_TUNE
        wire [7:0] DOA2 = (PREDEC & (~CLK | c) ) ? DOA - 1 : DOA;
        wire [8:0] WB_DATA = (PREDEC & (~CLK | c) ) ? {1'b0 , DOA} - 1
        : (POSTINC & (~CLK | c)) ? {1'b0 , DOA} + 1
        : {1'b0 , DOA};
        `else
        wire [8:0] DOA2 = {1'b0 , DOA} - (PREDEC & (~CLK | c));
        wire [8:0] WB_DATA = DOA2 + (POSTINC & (~CLK | c));
        `endif

        DOA2 が実効アドレス , WB_DATA が変更後の値。WB_DATA の依存関係が 2 段になっているをやめた。あと、-1 / +1 の結果を出しておいて 条件で選択するようにしてみた。

        // Number of Slice Flip Flops: 51 51
        // Number of 4 input LUTs: 92 112
        // Number of occupied Slices: 66 71
        // Total Number of 4 input LUTs: 100 113
        // Number used as logic 76 96
        // Number used as a route-thru 8 1
        // Number used for Dual Port RAMs 16 16
        // Number of bonded IOBs : 90 90
        // IOB Flip Flops : - -
        // Number of BUFGMUXs : 1 1
        // Number of RAMB16BWEs : - -

        こんな風に ロジックが増える結果になった。だが、

        ---------------+---------+---------+---------+---------+
        | Src:Rise| Src:Fall| Src:Rise| Src:Fall|
        Source Clock |Dest:Rise|Dest:Rise|Dest:Fall|Dest:Fall|
        ---------------+---------+---------+---------+---------+
        変更前
        ---------------+---------+---------+---------+---------+
        CLK2X_IN | 9.151| 10.150| 9.350| 10.241|
        ---------------+---------+---------+---------+---------+
        GPR_WB_TUNE :
        ---------------+---------+---------+---------+---------+
        CLK2X_IN | 6.282| 6.344| 6.299| 7.468|
        ---------------+---------+---------+---------+---------+

        こんなに、違ったのだった。これでもたぶんボトルネックだが、これぐらいなら、無理して 2 命令化する必要はないかも知れない。


    05E




    • rtavr-wk05e.tar.gz

      一旦スナップショット。とりあえずの命令列は、

      // IN 1011:0AA1:dddd:AAAA # 00
      // OUT 1011:1AA1:rrrr:AAAA # 00
      // LDS 1010:0kkK:dddd:kkkk #
      // STS 1010:1kkK:dddd:kkkk #
      // LDI 1110:KKKK:dddd:KKKK
      rom_out(16'hebcc); // LDI r28, 0xbc
      rom_out(16'he0d0); // LDI r29, 0x00
      rom_out(16'hbfcd); // OUT SPL , r28
      -------- 画面ここから
      rom_out(16'hbfde); // OUT SPH , r29
      rom_out(16'hafcc); // STS 0xbc, r28
      rom_out(16'hafdd); // STS 0xbd, r29
      // LD.Rd.Y 1000:000d:dddd:1000 #
      // LD.Rd.--Y 1001:000d:dddd:1010
      rom_out(16'h8138); // LD r19,Y
      rom_out(16'h0000); // NOP
      // MOV 0010:11rd:dddd:rrrr #
      rom_out(16'h2f43); // MOV r20,r19
      rom_out(16'h8138); // LD r19,Y


      // BST 1111:101d:dddd:0bbb #
      rom_out(16'hfb42); // BST 2, r20
      // BLD 1111:100d:dddd:0bbb #
      rom_out(16'hf930); // BLD r19, 0
      // BSET 1001:0100:0sss:1000 #
      rom_out(16'h9478); // BSET I (STI)
      // BCLR 1001:0100:1sss:1000 #
      rom_out(16'h94f8); // BCLR I (CLI)

      // BRBC 1111:01kk:kkkk:ksss #
      rom_out(16'hf487); // BRBC I
      rom_out(16'h0000); // NOP
      // BRBS 1111:00kk:kkkk:ksss #
      rom_out(16'hf087); // BRBS I
      rom_out(16'h0000); // NOP

      BST と BLD はまったく違う。BST は、SBRS/SBRC でも使う v_sbrx_bit を使って T に格納するだけ。BLD は、OP1/2/3 と並列で、Rd_in を演算して Rd_out (DI) に書きだす。

      BSET(BCLR) は、CBI(SBI) と 同じ処理で、CMD_LOAD で IOR から読み出し、CMD_STORE で 書き戻す。そして、CMD_LOAD で IOR から読み込むデータに 演算が入っている。

      最後に条件分岐を試している。+0x10 だけの 分岐で I が 0 に戻っているから BRBC で 分岐している。次の BRBS では分岐しないので、PC が増えない。

      まぁこんなところ。いよいよ PUSH/POP から初めて 複数クロックの CALL/RET を実装する段階になった。
      PUSH/POP の対象には、PC を含めないといけないし、 CALL/RETでも利用できるようにしないといけない。

      ICALL(RCALL) は IJMP(RJMP) と同じように動作した上で、PC を PUSH する。いままで s1_invalid にするだけだった次の命令で、 PUSH を指示しないといけない。

      PUSH は、CMD_STORE の一部で、何を STORE するかを EA_PUSH + PUSH_SEL で指示している。

      整理すると ICALL(RCALL) の 1 命令目では、CMD_STORE , EA_PUSH , PUSH_SEL を設定するが、
      2命令目でも同様。ただし、2 命令目では、PC の値が変わってしまっているので、覚えておかないといけない。

    05F




    • rtavr-wk05f.tar.gz

      CALL と RET が完成。追加した 命令列は、

      // tb_007
      // PUSH 1001:001d:dddd:1111 #
      rom_out(16'h933f); // PUSH r19
      rom_out(16'h934f); // PUSH r20
      // POP 1001:000d:dddd:1111 #
      rom_out(16'h913f); // POP r19
      rom_out(16'h914f); // POP r20

      // ICALL 1001:0101:0000:1001 #
      rom_out(16'h9509); // ICALL
      rom_out(16'h0000); // NOP
      rom_out(16'h0000); // NOP
      // RET 1001:0101:0000:1000 #
      // RETI 1001:0101:0001:1000 #
      rom_out(16'h9508); // RET
      rom_out(16'h0000); // NOP
      rom_out(16'h0000); // NOP

      933F から始まる PUSH - PUSH - POP - POP 。これでレジスタの内容 r19 = 0xbd , r20 = 0xbc を入れ替えてみた。.. うまくいった。

      次は、ICALL/RET 。 2/3 クロック目の状態を、新たに作り 例外として扱うことにした。-- s1_invalid なのに S2 に指令を出すとかいうことになった 。inst の invalid という意味になったが、面倒なのでこのまま。
      あと PC をどこに覚えておくか ちょっと工夫。8bit 分の r_tmp は作らざるを得なかったが、S2 に渡すPUSH するデータは、op2 での即値用 r_imm と共用した。POP したときのデータも覚えておかないとならないが、r_tmp と共用できた。


      // wk005f Implement Results
      // (e) (f)
      // Number of Slice Flip Flops: 244 252
      // Number of 4 input LUTs: 880 907
      // Number of occupied Slices: 526 529
      // Total Number of 4 input LUTs: 909 943
      // Number used as logic 864 891
      // Number used as a route-thru 36 36
      // Number used for Dual Port RAMs 16 16
      // Number of bonded IOBs : 67 67
      // IOB Flip Flops : - -
      // Number of BUFGMUXs : 2 2
      // Number of RAMB16BWEs : 1 1
      // Clock to Setup
      //on destination clock CLK2X:
      // Rise-Rise 8.635 7.953
      // Fall-Rise 9.173 8.910
      // Rise-Fall 9.130 7.933
      // Fall-Rise 9.962 9.009
      // (e) rtavr-wk05e
      // (f) rtavr-wk05f


      さて、次は割り込み。

      , input [4:0] INT_VEC
      , input INT_REQ
      , output INT_ACK

      というインターフェイスの外側は作った。INT_REQ は、割り込み要求で、I フラグとの AND 済み。INT_VEC は、割り込み番号で、保留されている割り込みの 優先度が最も高いものの番号。INT_ACK が 1 になると、次の割り込みに切り替わる。

      s1 では、命令の間 -- s1_invalid でないなら 割り込み を受付け 割り込み処理中状態にする。そして、割り込み処理中状態が終われば、INT_ACK を 1 クロック 1 にする。それ以上 1 にすると処理していない割り込みの保留がとける。

      割り込み処理では、INT_VEC のアドレスに call する。その後は i2_call に合流させて call の処理にかぶせる。i2_int というのも作ってこれを INT_ACK に割り当て。基本 call と同じだから 受け付けて 2 クロックで割り込み処理関数実行。そこが RETI なら +3 クロックで復帰。

      実装してテストしているのだが、05f で、なにかおかしくなっていた。調べたら RET/RETI のデコードが間違っていた。
      正しくは、

      wire f_ret = f_ext & INST[8]
      & (INST[3:0] == 4'b1000); // RET/RETI


      あと、BSET(SEI) の実行後 1 クロックは、割り込みは受け付けない。これはどう解釈したら良いのだろう? I フラグが変化したら? (I フラグ を変化させる 命令は、BSET(SEI) 以外に SBI と OUT がある。) それとも SEI だけの特殊効果?

      ちなみに、 SLEEP 命令には、必須の機能。SLEEP 実行前に待ちたい割り込みを受け付けてしまうと、SLEEP で止まってしまうことになる。さらに説明しておくと、他のプロセッサだと SLEEP (相当)命令で、SEI (相当) を自動的に行う方が一般的。

      ここは、SEI だけの特殊効果 ということにしておこう。あと、割り込みを受け付けたら I フラグが自動的に 0 になる。RETI で CMD_SETI という s2 への指示を作ったので、 CMD_CLRI というのも作ることにした。

      大分うまくいった .. と思ったのだが、RETI で 戻るアドレスが +1 されてしまっている。存在しない命令で置き換えたようなもので、今の仕組みではそうなってしまう.. ということは分かった。それで、命令を止めるべく検討を始めたら ... 分岐 や スキップする場合でも S0 での PREDEC/POSTINC を実行してたことが分かった。... というより後回しにしたのを忘れていた。

      ちょっと複雑なのと、分岐 や スキップ , 割り込みが関係するので、ここでまとめようと思う。


        S0 で PREDEC/POSTINC の指示を抑止 する理由は、次の 3 つがある。

        (1) S1 実行中の命令が、次の S2 で Rd に書き戻す。 (s0_inv_conflict)
        (2) S1 実行中の命令が、スキップする。 (s0_inv_skip)
        (3) S1 実行中の命令が、分岐 する。 (s0_inv_jump)
        (割り込み処理を含む)
        (4) S1 で割り込み処理が開始される。 (v_int)

        抑止したら当然 次のクロックでの S1 は実行しない。(s1_invalid)
        (割り込み処理は、例外)

        (1),(4) は、PC の更新をしない。(2) は +1 する。(3) はロード。

        ただし、割り込み処理が開始できる条件は、(2) (3) の状態でないこと。

      これで、書きなおしたら うまくいった... ようなかんじ。

    05G




    • rtavr-wk05g.tar.gz

      ついに割り込みも完成。本当に大丈夫なのか? ちょっと検証してみよう。

      今回の 命令列は、

      // tb_008
      IOR_INT0 = 1;
      rom_out(16'h0000); // NOP
      // BSET 1001:0100:0sss:1000 #
      rom_out(16'h9478); // BSET(SEI)

      // LDI 1110:KKKK:dddd:KKKK
      rom_out(16'he51a); // LDI r17, 0x5a
      rom_out(16'h0000); // NOP
      rom_out(16'h0000); // NOP

      // RETI 1001:0101:0001:1000 #
      rom_out(16'h9518); // RETI
      rom_out(16'h0000); // NOP
      rom_out(16'h0000); // NOP

      rom_out(16'h0000); // NOP
      rom_out(16'h0000); // NOP
      rom_out(16'h0000); // NOP

      INT0 を取り出して、テストベンチで操作できるようにした。まず IOR_INT0 = 1 で割り込み要求を出す。が、I=0 なので INT_REQ が 1 にならない。SEI(9478) してやると、v_int が 1 になる。これは準備段階で、まず S0 を抑止。S1 に入っている命令は止めない。

      止めるのは、24F の NOP(0000)から。3 クロック消費して、001 番地の RETI(9518) が実行される。3 クロック消費で、24F から実行開始。分岐系は 2 クロックだが、割り込みは 3 クロック。
      ... なにか間違えたかも。2 クロックでできるような気がしてきた。

      一応動いているのだから、変更は後回しでも良いか。

        ついでに ToDO メモ: GPR が ボトルネックになってしまったので、ケチらないで 16bit 幅にしたものを作ってみたい。

      規模はこんな風になった。


      // wk005g Implement Results
      // (f) (g) (g2)
      // Number of Slice Flip Flops: 252 252 290
      // Number of 4 input LUTs: 907 925 927
      // Number of occupied Slices: 529 537 562
      // Total Number of 4 input LUTs: 943 960 964
      // Number used as logic 891 909 911
      // Number used as a route-thru 36 35 37
      // Number used for Dual Port RAMs 16 16 16
      // Number of bonded IOBs : 67 67 10
      // IOB Flip Flops : - - -
      // Number of BUFGMUXs : 2 2 2
      // Number of RAMB16BWEs : 1 1 3
      // Clock to Setup
      //on destination clock CLK2X:
      // Rise-Rise 7.953 8.684 7.802
      // Fall-Rise 8.910 10.331 9.392
      // Rise-Fall 7.933 8.784 8.286
      // Fall-Rise 9.009 10.395 9.828


      537 スライス。少し増えただけですんだ。となりの (g2) は、PORTC と INT0 それに ROM を入れたもの。ポート 8 bit と RESET , CLK2X の 10 ピン構成。タイマーはもとより入れてあるから、動作できる最小構成。

      これに、USART を入れて、なんとか 50A で使えるようにするのが目標。あと、DCM も 50A で使うつもり。一応全機能入れているから、ここから大幅に大きくなることはない。なんとかなりそうな気がする。

      もともとの目標は、コアだけで 300 以内。この場合のコアには、タイマーも割り込みコントローラも入っていない。RAM すら入れなくて良いとも思う。果たして この目標はクリアできるのだろうか?

      さて、そろそろ次の段階に入る。実際のプログラムを動かしながら、個々の命令をテストしていく。

    (続く)
    関連記事:

    著作権について

      ここで提示しているコードは正しく動作しないとはいえ、既に著作権は発生しています。
      著作権は、すzが保持しており放棄はしていません。

      教育目的および私用目的では、もともと著作権の範囲外なので自由につかえます。また、ライセンスとして、GPL を適用しています。GPL に従う範囲において 個別の許可なく使用することができます。許諾のための連絡も不要です。

      なお、GPL なので、生成したバイナリを作り直せる範囲のソース開示が必要になります。FPGA だと 通常 チップ全体になってしまいます。また、開示したソースを GPL の範囲で再利用されることを妨げることはできなくなります。このコードを利用する場合この点に留意してください。

      個別の許可を得れば、GPL 以外の条件での使用は可能です。が、作業中のものには許可は出さない予定です。作業が完了(もしくは中断)したとき、ライセンスは見直します。

      なお、すでに公開してしまったものの、ライセンスを取り消すことはできないと考えていますが、ライセンスの追加は可能です。また、新しく公開するものについては、ライセンスしないことすら可能で、どのような制限もありません。変更する可能性がありますので、この点にも留意してください。
    posted by すz at 23:11| Comment(0) | TrackBack(0) | AVR_CORE

    2011年02月17日

    AVR互換コアの仕様(その3)

    (その2)からの続き。

    (その2)で書いたことを実装中だが、まだ 見せられるレベルで 作れていない。だが、一部のインターフェイスは FIX しつつある。課題とかも含めてここで整理しておこうと思う。
    (注意:この記事はインターフェイスの変更があったら随時更新するつもり)

    RAM -- まずは簡単な方から、

      module rtavr_sram_2KB(
      input CLK,
      input RESET,
      input WEA,
      input [10:0] ADDRA,
      input [7:0] DIA,
      output [7:0] DOA,

      input WEB, OE,
      input [10:0] ADDRB,
      input [7:0] DIB,
      output [7:0] DOB
      );

    • RAM をモジュールにする場合は、ブロックRAM を使うから 2KB 単位になる。2KB 以上必要とは思えないから、とりあえずは _2KB サフィックスを付けたものを使う。

    • CPU につなげるのは、ポートB の方。ポートA は未使用。

    • (追加4) WRITE_FIRST に統一 , あと RESET をやめた。

    • 128B とかで十分というケースでは、ROM の後ろを間借りしたい。... が、現状は書き込みは出来ても 16bit 単位なので使えない。これについては ROM の項目で述べる。

    ROM

      module rtavr_rom # (
      parameter SIZE = 1024
      // parameter SIZE = 2048
      // parameter SIZE = 4096
      ) (
      input CLK,
      input RESET,
      input [11:0] ADDRA,
      output [15:0] DOA,

      input WEB,OE,
      input [12:0] ADDRB,
      input [7:0] DIB,
      output[7:0] DOB
      );

    • サイズとして考えられるのは、3 通りしかない。なので、1KW/2KW/4KW の選択が出来るようにした。

    • (追加4) WRITE_FIRST に統一 , あと RESET をやめた。

    • 書き込みできる 8 bit の ポートB は、CPU のメモリ空間に接続する。が、word 単位で書かないといけないので、RAM の代わりにはならない。

    • Xilinx のプリミティブ RAMB16BWE_S18_S9 を直接使えば、それは可能なはずなのだが、汎用のシミュレータを使いたいし、今は 使わない。

      でも、もし汎用の書き方で ブロックRAM にうまく嵌ることが出来れば、それを採用するつもり。そのときは、RAM も統合可能なようにする。

    • ROM は、本来 18 bit 分あるはずで、これを 書き込み保護のために使おうかと思っている。2bit 余分なので、"00" 『ROM』 "1X" 『書き込み可能』 "X1" 『書き込み済み』 みたいな使い方をして、Write Once もサポートしたい。これらをサポートしても、基本のインターフェイスは変えないで parameter での指定にしたい。

    • 今のところ初期値も考えていない。今は、スクリプトを使って、AVR の HEX ファイルから、直接 初期値つき コードを生成するのが良いかと思っている。

    IOR -- 結構悩ましい。

    module rtavr_ior (
    input CLK,
    input RESET,
    input WR,RD,
    input [7:0] DI,
    input [5:0] ADDR,
    output [7:0] DO

    `ifdef IOR_HAVE_SREG_SP
    , input [7:0] SREG_IN
    , input [15:0] SP_IN
    , output [7:0] SREG_OUT
    , output [15:0] SP_OUT
    `endif
    // external I/O
    `ifdef IOR_HAVE_PORTA
    , inout [7:0] PORTA
    `endif
    `ifdef IOR_HAVE_PORTB
    , inout [7:0] PORTB
    `endif
    `ifdef IOR_HAVE_PORTC
    , inout [7:0] PORTC
    `endif

    // interrupt handling
    , input RST_INT0
    , output INT0
    , input RST_TIM0_OVF
    , output TIM0_OVF
    , input RST_TIM0_COMPA
    , output TIM0_COMPA
    , input RST_TIM0_COMPB
    , output TIM0_COMPB
    );
    parameter INT0_BIT = 18; // PC2

  • WR, RD, DI, DO, ADDR は CPU 接続用。RAM / ROM とおなじ接続。

  • SREG, SP をここに入れるかどうか悩んだが、AVR Core のインターフェイスをみると IOR に含めているので 入れることにした。これらは、バスからアクセスできないといけないので、どこにおいても邪魔なのだ。あと、他の Core で流用する場合 不要になるので、まるごと外すことも出来るようにした。

  • PORTA/PORTB/PORTC はそれぞれ独立で アタッチできるように変更。ちょっと多すぎる。できたら parameter にしたいが、どうやるか良く分かっていない。parameter 化するなら、それぞれの bit 数も指定したいところ。

  • PORT については、pull-up の機能がなく、DDR での 出力を設定できるのみ。このレベルだと、新PORT の機能も関係ない。もし、keeper や pull-up が制御できるなら 組み込みたいが後回し。

  • 割り込みは、出力と それをリセットする入力の 2 本づつにした。RS FF の R,Q が出ているイメージ。いまのところ INT0 と TIMER0 の割り込みに対応。デバイスを増やすとどんどん増えることになる、配列にしようか悩み中。

  • INT0 は parameter での指定で任意のポートの任意のビットにアサイン可能。動作の設定は IOR 内のレジスタでするが、いまは未実装。

  • あまりに AVR に依存した構造にすると、他の Core での流用が面倒になるので、これぐらいが良いだろうと判断している。

  • 割り込みと TIMER0 は、今は外せない。だが、割り込みなしで使うコードも実際に書けるから割り込みは外せるようにしたい。

    一方 TIMER0 は外すことは、あまり考えていない。たぶん使う。

  • 16bit の TIMER1 もインプリメントしたいが、機能が多いので 後回し。

  • (追記) よくよく考えれば、この PORT は、IOB 経由で 外部との接続をするのにしか使えない。内部の回路と接続するには、inout の定義が問題。それは別途追加すれば良いわけだが .. 現状 INT0 は PORTA/B/C にしか割り当てられない。これについて少々検討が必要だ。

    GPR -- ここから CPU 内部モジュール。

      module rtavr_gpr_16(
      input RESET,
      `ifdef USE_DMY_CLOCK
      input CLK2X_IN,
      output CLK_OUT,
      `else
      input CLK2X,
      input CLK,
      `endif
      input WE,
      input PREDEC, POSTINC,
      input [7:0] DI,
      input [3:0] ADDRAL,
      input [3:0] ADDRAH,
      input [3:0] ADDRBL,
      input [3:0] ADDRBH,
      output [7:0] DOAL,
      output [7:0] DOAH,
      output [7:0] DOBL,
      output [7:0] DOBH
      `ifdef CHECK_CONFLICT
      , output DI_CONFLICT
      `endif
      , output WB_VALID

      , output [11:0] JA < << (追加4)
      , output [15:0] INDEX < << (追加4)
      );


    • 前に説明したとおりのものだが整理しておくと

      o ADDRAL/DOAL (PREDEC/POSTINC)
      インデックスレジスタ 下位バイト(XL/YL/ZL)
      o ADDRAH/DOAH/DI (WE/PREDEC/POSTINC/WB_VALID)
      インデックスレジスタ 上位バイト(XH/YH/ZH)
      + Rd WRITE
      o ADDRBL/DOBL
      Rd READ + ICALL/IJMP 下位バイト
      o ADDRBH/DOBH
      Rr READ (STORE/OUT の Rd を含む)
      + ICALL/IJMP 上位バイト

    • DOBL/DOBH については、DI の先取り機能付き。

    • ADDRAL など レジスタ番号は S0 で指定し、DOAL など出力は S1 で使う。DI は S2 での指定で、1 クロック遅れだが、モジュール内部でレジスタ番号の指定を遅延させる。

    • WB_VALID は、『XH/YH/ZH の更新が保留されている』という意味で、1 クロック遅らせる要求。

    • DI_CONFLICT は、DI と インデックスレジスタ更新が競合したときのチェック用で、デバッグ専用。

    • CLK2X/CLK_OUT は、クロックの扱いが良く分かっていないので仮の対処。DCM を使って両方入力にする予定。

    • (追加4) WRITE_FIRST に統一 、

      先取り用ポートの追加。実効アドレスは、S2 で確定していけないので、S1 に処理を移動。それに伴い 早い時期から読めるように変更。逆に DOAL/DOAL は使わなくなる見込み。(ただし、まだ残しておく)
       - DOAL/DOAL は使わないので削除

    S0_fetch/S1_decode は入力・出力をはっきりさせないと混乱するので独立したモジュールにする。S2_execute については、関連モジュールが多いので、(今のところ)TOP レベルにする。S3 は存在せず、3段パイプラインの予定。
    S0_fetch -- まずは、これから。

      module rtavr_s0_fetch (
      input RESET,
      input CLK,
      `ifdef INCLUDE_ROM
      input [11:0] PC,
      input WEB, OE,
      input [12:0] ADDRB,
      input [7:0] DIB,
      output[7:0] DOB,
      `else
      input [15:0] PM_OUT,
      `endif
      output [15:0] INST,
      output[3:0] GPR_ADDRAL,
      output[3:0] GPR_ADDRAH,
      output[3:0] GPR_ADDRBL,
      output[3:0] GPR_ADDRBH,
      output GPR_PREDEC, GPR_POSTINC

      , output WB_INST <<< (追加1)
      , input WB_EN <<< (追加1)
      ,input S0_VALID <<< (追加5)
      ,output IDX_INST <<< (追加5)

      );

    • 本来のインターフェイスは、 PM_OUT(ROM 出力) を入力して、INST と GPR への指示を出力。

    • 今後の設計で、S1 への指示があれば増やしていく。
    • INCLUDE_ROM は、遅延を見るためだけに使う。

    • (追加1) WB_INST, WB_EN

      GPR に対してレジスタの更新がある指令 GPR_PREDEC, GPR_POSTINC を 出しているが、そうして良いかお伺いを立てることにした。 WB_INSTは、GPR_PREDEC, GPR_POSTINC を出力したいという意味で、 WB_EN が 1 になれば実際に出力する。

      許可を出すのは、S1 で、競合があるなら 0 にした上で、PC を更新しないことで 1 クロックずらす。0 にするケースは、競合以外にもあるが S1 の仕事で S0 は関知しない。

    • S0 は一旦 FIX 。特に S0 でやれることはない。変更するとすれば、S1 の一部を持って行って高速化するとき。

    • (追加5) S0_VALID, IDX_INST

      WB_EN は、 S0_VALID に名前を変更。IDX_INST は、(PREDEC/POSTINC 以外も含めた)インデックスレジスタ参照の場合 1 。

    S1_fetch -- 現状では一部だけ決まっている。説明した内容ですら入っていない。

      module rtavr_s1_decode (
      input RESET,
      input CLK,

      output CMD_LOAD, CMD_LOAD_WR, CMD_STORE,
      output CMD_OP3, CMD_OP2, CMD_OP1,
      output [2:0] OP3_SEL,
      output OP3_WC, CMD_OP3_WR,
      output [2:0] OP2_SEL,
      output [2:0] OP1_SEL,
      output [7:0] IMM_DATA,
      input [15:0] INST,
      input [11:0] JA, <<< 追加4

      output [11:0] PC,
      output EA_ABS,
      output [7:0] ABS_ADDR
      , input S0_WB_INST <<< 追加1
      , output S0_WB_EN <<< 追加1

      , output EA_PUSH, EA_POP <<< 追加2
      , output [1:0] PUSH_SEL <<< 追加2

      , output CMD_SBIX <<< 追加3
      , output CMD_SBRX <<< 追加3
      , output [2:0] SBIX_BIT <<< 追加3
      , input SBIX_BIT_IN <<< 追加3

      , input [15:0] EA_INDEX, <<< 追加4
      , input [15:0] SP_IN, <<< 追加4
      , output [15:0] SP_OUT, <<< 追加4
      , output [15:0] EA, <<< 追加4

      , input IDX_INST <<< (追加5)
      , output S0_VALID <<< (追加5)
      input [7:0] LOAD_DATA, <<< (追加5)
      output CMD_XBI, <<< (追加5)
      output CMD_BST, <<< (追加5)
      output CMD_BLD, <<< (追加5)
      input FLAGS_BIT_IN, <<< (追加5)
      output XBI_BIT_OUT, <<< (追加5)
      output CMD_SETI, <<< (追加5)
      output CMD_CLRI, <<< (追加5)

      input [4:0] INT_VEC, <<< (追加5)
      input INT_REQ, <<< (追加5)
      output INT_ACK, <<< (追加5)
      );

    • S0 の出力 -- INST を入力にして S2 への指示を決めていく。

    • CMD_LOAD -- LD と IN/LDS あと POP 命令で ロードする指示。(アドレスについては後述)

    • CMD_LOAD_WR -- Rd (で指定したレジスタ)に ロードした値を書き込む指示。

    • CMD_STORE -- ST と OUT/STS , PUSH 命令で Rd を 書き込む指示。

    • CMD_OP3 / CMD_OP3_WR / OP3_SEL / OP3_WC -- Rd / Rr の値で演算した結果を Rd に書き戻す。CMD_OP3 は、演算だけを指示。CMD_OP3_WR は Rd に 値を書き込む指示。

      CMD_OP3L は実際に書き戻す指示。OP3_SEL は 演算の種類で、OP3_WC はキャリーフラグの使い方を指示。

    • CMD_OP2 / OP2_SEL / IMM_DATA -- Rd と IMM_DATA で演算した結果を Rd に書き戻す指示。

    • CMD_OP1 / OP1_SEL -- Rd で演算した結果を Rd に書き戻す指示。

    • EA_ABS / ABS_ADDR -- CMD_LOAD/CMD_STORE での有効アドレス(EA) に ABS_ADDR を使う指示で、IN/OUT , LDS/STS のとき EA_ABS が 1 になる。

    • (追加2) EA_PUSH は、有効アドレス(EA) に SP-- を使う指示。PUSH の動作自体も指示していて、PUSH する値は PUSH_SEL で指示。

       -- PUSH_SEL が 00 の場合は Rr を使う。10 は PC の下位バイトで、11 が PCの上位バイト。

      EA_POP は、有効アドレス(EA) に ++SP を使う指示。書き込み先は PUSH_SEL を兼用する。

    • 命令のスキップは、S2 に対して なにも指示をしないことで実装。

    • プログラムカウンタ(PC) は S1 が内包する。ICALL/IJMP 以外は PC の次の値を S1 で決められる。

      ICALL/IJMP の 分岐先アドレスは、JA に出てくる。レジスタ番号は、S0 が (指示なしで)決める。

      JA は、GPR_DOBL/GPR_DOBH のラッチ前の値を先取り。GPR からもらう。

      .. ちょっと POP を 検討。書き込み先 が PC の場合、S1 内で処理をする。書きこむデータは、GPR_DI に入って来ているが、S1 には来ていない。だが、良く考えてみると、JA には、セレクタ経由で GPI_DI のデータが来ているのだ、しかも接続先は PC 。セレクタ をいじってやるだけで、下位 8 バイトは PC に送り込める。

    • (追加3) スキップの条件を得るための(S2 との)インターフェイス

      条件スキップ 命令には SBIC/SBIS , SBRC/SBRS , CPSE がある。それらの命令のときの条件を S2 から受け取りたい。

      SBIC/SBIS では、CMD_LOAD の指定で IOR から読み込んで来る。合わせて CMD_SBIX を指示して、SBIX_BIT で指定したのビットを SBIX_BIT_IN に入れてもらう。

      SBRC/SBRS では、なにもしなくても Rd_in にデータが来ているので、CMD_SBRX を指示して、SBIX_BIT で指定したのビットを SBIX_BIT_IN に入れてもらう。

      CPSE は、CMD_OP3 で 演算結果を出すところまでする。欲しいのは、演算結果が 0 がどうか。これは、Z フラグのデータを生成するための中間データでもある。上記の条件以外なら そのデータを SBIX_BIT_IN に入れてもらう。

      S1 では、SBIX_BIT_IN と それが 1 でスキップなのか 0 でスキップなのか を判断する内部データ s2_sbix_sel と合わせて スキップ条件を生成する。

      ところで、(GPR_DI == 0) というのをベタで書くのと、一旦 wire di_is_zero = (GPR_DI == 0); としてまとめるのと 結果が同じになるかどうか気になる。

      中途半端なものではあるが、今のもので比べてみた -- OK 規模は同じ。

      次、(GPR_DI == 0) というのは、セレクタを幾つか経由したものだから、結果が出るのが遅い。(Rd_in == Rr_in) とダイレクトに書いたらどうなるのだろう?


      (GPR_DI == 0) (Rd_in == Rr_in)
      Number of Slice Flip Flops: 284 284
      Number of 4 input LUTs: 793 805
      Number of occupied Slices: 521 529
      Total Number of 4 input LUTs: 830 843
      Number used as logic: 777 789
      Number used as a route-thru: 37 38

      スライスで +8 か。これでボトルネックを解消できるなら価値があるが.. ボトルネックじゃなかったら無駄。今は覚えておくだけにしよう。

    • (追加4) 実効アドレスの決定を S1 に移動。

      それに伴いいろいろなものが、S1 に移ってきた。

      RAM の仕様から、実効アドレスの決定は、S1 でやっておかないといけない。そうして初めて S2 で使える。

    • (追加5) 全命令実装後

      随分追加した。個々の命令のための ものと、割り込み関係が主。

      EA_POP は削除。PUSH_SEL も 1bit 削除。その代わりに入ったのが、LOAD_DATA で S1 内で POP 操作を行う。PUSH も S1 で行うが、書きこむデータはIMM_DATA を使って送り込む。

    S2_execute -- TOP レベルとして実装。完成形では、この中に 上記すべてのモジュールを含める。モジュール名は rtavr。

      module rtavr (
      input RESET,
      `ifdef INCLUDE_GPR
      input CLK2X
      `else
      input CLK
      `endif
      // external I/O
      , inout [7:0] PORTA
      , inout [7:0] PORTB
      , inout [7:0] PORTC

    • 本来はこういうシンプルなものになる予定。-- だが実際は、全部のサブモジュールを個別に外して外部に 持っていけるようにしていて、結構複雑になっている。

    • 特に GPR は、2倍速なので、遅延を見るときは、GPR と それ以外で見る。

    • インターフェイスとしては、上記に含まれるので特に書くことはない。

    • ついでなので、LOAD/STORE/OP3/OP2/OP1 に(現状)含まれない命令の一覧。

      // #mnemonic opcode desc
      // NOP 0000:0000:0000:0000 # OP3 に含めた。

      // BLD 1111:100d:dddd:0bbb # bit load from T flag
      // BST 1111:101d:dddd:0bbb # bit store to flag T

      // BRBC 1111:01kk:kkkk:ksss # S1 で実行
      // BRBS 1111:00kk:kkkk:ksss # S1 で実行

      // BSET 1001:0100:0sss:1000 # bit set in FLAGS
      // BCLR 1001:0100:1sss:1000 # bit clear from FLAGS

      // IJMP 1001:0100:0000:1001 # S1 で実行
      // ICALL 1001:0101:0000:1001 # S1 で実行

      // RET 1001:0101:0000:1000 # CMD_LOAD + EA_POP x 2 + XX で指示
      // RETI 1001:0101:0001:1000 # 〃

      // SLEEP 1001:0101:1000:1000 # sleep mode
      // WDR 1001:0101:1010:1000 # watch dog reset

      // RJMP 1100:kkkk:kkkk:kkkk # S1 で実行
      // RCALL 1101:kkkk:kkkk:kkkk # S1 で実行

      // CBI 1001:1000:AAAA:Abbb # CMD_LOAD + CMD_STORE + XX で指示 ?
      // SBI 1001:1010:AAAA:Abbb # 〃

      // SBIC 1001:1001:AAAA:Abbb # CMD_LOAD + CMD_SBIX
      // SBIS 1001:1011:AAAA:Abbb # 〃

      // SBRC 1111:110r:rrrr:0bbb # CMD_LOAD + CMD_SBRX
      // SBRS 1111:111r:rrrr:0bbb # 〃

    • NOP は、CPSE の変形として OP3 で 実装する。

    • CBI/SBI / SBIC/SBIS は LOAD/STORE の機能を使う。

    スナップショット:

    • rtavr-wk05g.tar.gz
    • rtavr-wk04.tar.gz
    • rtavr-wk03.tar.gz

      上記のインターフェイスで作っている現状のもの。05g で全命令実装。インターフェイスはほぼ FIX 。


      動くようなものでは全然ないが、とりあえず 規模とかが出た。

        // (1) (2) (3) 
        // Number of Slice Flip Flops: 247 270 284
        // Number of 4 input LUTs: 722 622 793
        // Number of occupied Slices: 472 430 521
        // Total Number of 4 input LUTs: 762 654 830
        // Number of bonded IOBs : 26 26 26
        // IOB Flip Flops : - - -
        // Number of BUFGMUXs : 2 2 2
        // Number used for Dual Port RAMs 16 16 16
        // Number of RAMB16BWEs : 2 2 2
        // Clock to Setup
        //on destination clock CLK2X 13.030 12.816 12.091
        // (3) rtavr-wk04


    • RAMB16BWEs が 2 個なのは、ROM 2KB にしているから。

    • クロックは余裕だが、中身もない。それでも、GPR がネックになりそうな感じ。

      既に 472 スライスまでになってしまった。このうち IOR が結構占めている。
      IOR の規模:

        // Number of Slice Flip Flops: 136
        // Number of 4 input LUTs: 223
        // Number of occupied Slices: 175
        // Total Number of 4 input LUTs: 232
        // Number of bonded IOBs : 106
        // IOB Flip Flops : 71
        // Number of BUFGMUXs : 1
        // Number of RAMB16BWEs : -
        // CLK to Setup 6.264


    • IOB Flip Flops の一部 (71 - 24) は、LUT に移るはず。

      規模についての目標を後退させる。初版は、全部を入れて 50A で動けば良い。( 動かすアプリケーションは、SPI FLASH ライタ。)
      -- それでも厳しい。SPI は ソフト SPI で良いとしても USART 入れたいし、Core は、今 300 スライスぐらいなのが、1.5 倍にはなりそうだし。

      (2) -- 2/17 時点

      NOP を CPSE と一緒にして、GPR の クロックの扱いを少し変更。なぜか大分減った。

      (3) -- 2/20 時点 (rtavr-wk04)

      まだまだだが、スナップショットを取っておく。(追加3) まで。


    メモとか追記とか

    • Spartan-3A アプリケーションノート

      ここに、『APP228 - Virtex デバイスのクォッド ポート メモリ (英語版)』というのがあった、デザインファイルもダウンロードできる。GPRと似たようなものなので、なにか重要なヒントがあるかも知れない。チェックしておきたい。

      ちょっと見た分には、普通の verilog で記述したファイルもあった。見てみると CLK2X と CLK を入力にしている。GPR も最終的にはそうするのだろう。

      ちょっと思い立って、CLK の nagaedge / posedge と CLK2X の posedge の 3 つに分けてみたが、問題なく Implement できた。GPR の 次の版ではこれを採用しよう。

      あと、倍速ベースで『XAPP229 - 多ビット入出力ブロック メモリ』という使い方もある。

      (追記)見てみたら DCM のモジュール CLKDLL を使っていた。CLKDLL には、RST, LOCKED というのがある。-- なるほど、 PLL みたいにロックという概念があるわけか。あと BUFG/IBUFG とかも使っている。単なる 2 倍速なら これをマネすれば問題ないのだろう。あと 3 倍速とかはどうやるのだろう?

      あ、『XAPP462 - Spartan-3 FPGA におけるデジタル クロック マネージャ (DCM) の使用 (日本語版)』-- これか。

    • top level は、rtavr という名前にした。その中に 直接 S2 が入っていて S0/S1 はサブモジュール。(上記で説明したとおり)。

      これを元にして、kx_avr や narve のための top level を別ファイルで作ろうかと思う。インターフェイスがこれで良いかのチェックになるし、ROM/RAM/IOR を CORE と別にテストできる。

    • S1 を検討中だが、パイプライン制御が混乱してきた。ちょっと整理。まず、こいつは 2 word 命令がないので簡単になる。それを念頭において...

      • 分岐: 次の命令はロード済だから、分岐する場合は次のサイクルを無効にする。
      • 条件スキップ: S2 で決まるから 今のサイクルを 無効にする。
      • XH/YH/ZH WB 条件による 1 クロック挿入 (パイプラインストール)
        次の命令を遅らせる。=次のサイクルを無効にするとともに PC を更新しない。
      • Rd の更新 と X/Y/Z の競合による 1 クロック挿入 (パイプラインストール)
        S0 で検出できるので、先取りして 次のサイクルを無効にするとともに PC を更新しない。
        S0 では、PREDEC/POSTINC を 無効にする。

      まだまだある。がとりあえず整理。

      • wire s_invalid 今のサイクルは無効
      • wire s_stall PC , INST を更新しない

      • reg n_invalid < 1 次を無効にする
      • S0_CONFLICT : S0 での競合検出
      • WB_VALID : XH/YH/ZH WriteBack 保留中 (GPR からの指示)

      次に面倒そうなもの。

      • ICALL/RCALL -- 2 クロック
        (1) PUSH PCH を S2 に指示 , 次を無効にする , 分岐先を PC に。
        (2) PUSH PCL を S2 に指示 (INST は無効だが、別状態, PCL は覚えておく)

      • RET/RETI -- 3 クロック
        (1) POP PCL を S2 に指示, PCL はテンポラリ
        (2) POP PCH を S2 に指示
        次を無効にする , 分岐先(PCL/PCH) を PC に。
        (3) 無効

      • 割り込み -- 3 クロック
        (1) 準備 S0 を止める。
        (2) PUSH PCH を S2 に指示 , 分岐先(PCL/PCH)を INT_VEC に。
        (3) PUSH PCL を S2 に指示 (INST は無効だが、別状態, PCL は覚えておく)

      実際の処理の説明が適当になってしまった。が、ここでは状態の把握ができれば良い。

    なにか大変な設計ミスをしたんじゃないかという気がしてきた。

      条件スキップで、スキップする場合 当然ながら次の命令は実行してはいけない。一方 PREDEC/POSTINC は、S0_fetch で更新してしまうことにしている。一体どういうことが問題になるのか CPSE + LD Rd, --X について整理してみよう。

      S0 S1 S2
      CPSE
      LD Rd,--X CPSE << LD の PREDEC/POSTINC を確定
      LD Rd,--X CPSE << LD の PREDEC/POSTINC を実行
      << CPSE の 条件が確定
      LD Rd,--X

      下位バイトの PREDEC/POSTINC の実行は、サイクルの前半でやるので、後半でスキップすることが分かっても間に合わない。

      S0 S1 S2
      CPSE
      LD Rd,--X (x) CPSE << 次の命令をストール
      LD Rd,--X x CPSE << S0 再実行
      LD Rd,--X x
      LD Rd,--X

      どうやって制御するのが良いか分からないが、こういう風に動かないといけない。
      ストール が間に入ったら それをまたいで制御しないといけないわけだ。

      ただ、分岐一般は、S1 で決めてしまうので、ここまで面倒なことにはならない。それが救いだが、条件が前半で確定しないといけないので、性能のボトルネックになるかも知れない。

       -- なかなかに嫌な予感がする。本当に作れるのだろうか?
       -- と弱気になったが、考えてみれば 複数クロック命令と同じようなもの。もともと避けては通れない。

    • rtavr-wk04 (追加3) まで

      とりあえず、スナップショットを取った。

      今はパイプライン制御を入れるためのお膳立て段階。skip の判断はできるようになったし、フラグの値も生成するようにした。分岐系も周りを固めつつある。割り込みは未検討だが、PUSH/POP の基本操作もいれつつある。

      こうやって見ると、まだまだと言いつつ、結構作りこみが進んでいる。 コメントというかメモが大分入っているし、インターフェイスの占める割合が多いが、総規模 2074 行。結構なところまで来たものだ。

      (IOR の拡張をしないなら) 多分 3000行は超えない。気が済むまで実装したら、シミュレータにかけて命令を動かしてみたい。

    • (追加4) インターフェイス見直し

      メモリ (GPR/ROM/RAM) はすべて同期型で WRITE_FIRST となるよう記述を変更。WRITE_FIRST では、アドレスをラッチするように明示する。目的に沿うのでこれを採用。

      メモリが同期型になると、いままでのインターフェイスでは不具合が出る。実行アドレスの決定は S1 でしてしまうことにして、必要な信号も S1 に引き込む。

      インターフェイスが変わったし、ラッチが入り過ぎていたり、入っていない不安もあるので、図にしてみた。

      だいたいは大丈夫のようだ。が、もともとの想定と ステートがずれているような ... 。ROM/RAM のアクセスが非同期だと想定していたが、同期になったため、アドレスの確定が前のステートに移動した。

      あと、モジュール間インターフェイスは全部大文字で統一しているが、内部は プレフィックスも適当な状態。

      s2 に出力するラッチ類は、s2_ を付け、s1 向けに出力するラッチは s1_ を付けようかと思う。あまり ステートを 意識しない内部のラッチは r_ 。

      ラッチ類への入力は、r_ 向けには v_ を使う。s1/s2 向けにも v_ を使ったりするが、意識する場合は、v1_ / v2_ にする。

      i_ は、最初に書いたもので、wire と reg の区別もない。 上記のルールで変換していこうと思う。

      ... ところで、なにか基本的な間違いをしているような気がしてきた。

      ROM/RAM の定義では、あらかじめ アドレスを 設定して置いて posedge で READ or WRITE する仕様になっている。こういう定義だと READ は posedge 以降しか行われないし、WRITE は posedge までにデータを確定しておく必要がある。READ して同じアドレスに書き戻す(read-modefy-write) ということはできない。

      ROM/RAM 自体はそれで良いのだが、他の論理回路は、negedge で動かすべきなのに、posedge で動かしていた。全部チェックして書き換えないと。

      それに加えて、GPR の仕様が合わない。GPR は read-modefy-write が出来ることが前提。どうしたら良いか分からないものの書きなおさないと。

      • S0/S1 では、negedge にする。IOR はそのまま。ちなみに、IOR は、非同期読み出しできるから、CBI/SBI で期待する read-modefy-write は可能。
      • 分散RAM は非同期読み出しが出来るから、アドレスを negedge でラッチする

      なんか、これで良いみたいだ。あと、ROM の読み出しが 想定より遅れる。パイプライン制御で、なにか不具合が起きるかも知れない。ROM を二倍速にして、読み出しを早める手が使えるから 対策はある。必要かどうかパイプライン制御のとき気をつけよう。

    • CLK について。

      いんちき クロックとして、2倍速の CLK2X から CLK を生成しているのだが、これで良いのじゃないかと思えてきた。

      • negedge CLK2X から CLK を生成。
      • GPR は、posedge CLK2X で駆動。negedge CLK2X でアドレスをラッチしているが、一番早いタイミングなので問題ない。
      • その他では、CLK2X は使わず、negedge CLK か posedge CLK のみを使う。

      こうなっているから、安全じゃないかと。DCM の2段使用は、推奨されていないようだから、DCM は、33 MHz の水晶から、望む周波数を作るためだけに使う。DCM の機能をみると N/1.5 , N/3, N/5.5 , N/11 (N は 2 - 32) が出来る。 22MHz, 11MHz, 6MHz , 3MHz の N 倍だけが整数 MHz になりそうだ。

    • シミュレータについて

      そろそろ、シミュレータ も考えようと思う。とりあえず、論理シミュレーションだけで十分。

      ここを見て、Windows 版 がある Icarus Verilog を使ってみようかと思う。以前 GTKWaveも使っているし、多分なんとかなるだろう。

      ISE は重いからあまり使いたくない。シミュレータに移行したら、当面シミュレータだけを使いそう。

    • 割り込みについて

      top level で、ベクタに変換するようにした。割り込みラインを ベクタに変換し、全部の OR を INT_REQ と定義。あと、INT_ACK が 1 だと 割り込み原因を ベクタから逆変換し 割り込みラインをRST する。
      割り込みラインをRST すると割り込みラインが減り 新たな ベクタに変わる。

      top level で、こういう処理を入れるのは、割り込みラインの定義をしている所だから。定義が変わっても 変更は、実体がある IOR と top level だけにしたい。

      これらの信号は、モジュールの外に出しているが、S1 で対応したときに S1 に渡す。

    (続く)
    関連記事:
  • posted by すz at 22:01| Comment(0) | TrackBack(0) | AVR_CORE

    2011年02月12日

    AVR互換コアの仕様(その2)

    AVR互換コアの仕様(メモ)からの続き。

    いまどの段階か整理しておく。

    作ろうとしているのは、Tiny10 などの "the Reduced Core TinyAVR" の互換プロセッサコア。レジスタが 16 個と少ないが、avr-gcc は対応している。これを採用してよりコンパクトなコアを作りたい。

    まずは、汎用レジスタ(GPR)のデータ構造をだいたい決めて 、それだけを verilog で 書いてみた。次に RAM と ROM 。今は I/Oレジスタ。全部プロトタイプで簡素なもの。こうやって コアのインターフェイスを決めようとしている段階。

    現時点のものをリストして簡単に特徴を記載すると --

    • rtavr_gpr_16.v (ver 003)

      分散 RAM 16x8 デュアルポート を中心にして構成。2 倍速で動作させて 1 クロックで 4 load 2 store にしている。 X/Y/Z レジスタの PREDEC/POSTINC の機能も含んでいる。

    • rtavr_sram_2KB.v (ver 002)

      ブロック RAM を 使った 2K x 8bit RAM 。8bit バス接続。もうひとつのポートは未使用。

    • rtavr_rom_8KB.v (ver 001)

      ブロック RAM 4 つ を 使った 4K x 16bit ROM 。命令用の 16 bit ポート と 8bit バス接続 の 2 つのポートがある。8bit バス接続の方は Write 可能にしている。

    • rtavr_ior_port.v (ver 001)

      I/O レジスタのインターフェイスを決めるために書いてみた PORT 。

    • rtavr_ior.v (ver 001)

      (新) PORT だけではイメージがつかめないので、I/O レジスタのトップレイヤ を作った。Timer0(8bit) も作り PORT と合わせて 1つのファイルに格納している。 (上の rtavr_ior_port.v は Obsolute)

      最終的には、Tiny40 のサブセット + USART にするつもり。

      注) ところで、これらのソースは、tabset 8 を想定しているが、ちょっとインデントがおかしくなっている。Web Pack ISE だと Edit → Preferences → ISE Text Editor での設定が標準では Tab width 3 になっている。 更新するときは、tabset 8 で整形しなおすつもり。

    こういうわけで、I/O レジスタ に入った。新たに決めようとしているのは、割り込み。Timer0 からの割り込みラインは作ったが、それをどう CPU につなげるのが良いか検討中。

    CPU につなげるものは、他にも 沢山あるが、ステータスレジスタ, スタックポインタ が最低限必要でこれも合わせて検討する。

    あと スライスを沢山使っているのが悩み。現在の rtavr_ior_port.v (ver 001) は、

    Number of Slice Flip Flops : 121
    Number of 4 input LUTs : 152
    Number of occupied Slices : 137
    Total Number of 4 input LUTs : 161
    Number of bonded IOBs : 49


    FPGA 内部のモジュールの接続にバスを使おうとしたのだが、やはりダメなようだ。... と言っても エラーになるのではなく勝手に変換されるようだ。

    • Spartan-3 は、"内部トライステート・リソースを持たない" そうだ。Xilinx の FPGA には持っているものもあるみたいだが、一般的ではないらしい。

    • 良くはわからないが、内部トライステートが可能でも トライステート・ドライバ数が増えるとダメらしい。その時は マルチプレクサ に自動で変換するものらしい。(syn_tristatetomux がキーワード?)

    • ただ ROM で最初に使ったときは、logic に変換したというメッセージが出たが、pull-up がナントカと出ていたから ワイヤード OR ? マルチプレクサ に pull-up は関係なさそうだし何だろう?

    • あと、 rtavr_ior.v では、下位モジュール同士で接続しているが、エラーにはなっていない。

      さて、どうするのが良いのだろう? バス使った方が記述が楽なのだが .. 少なくとも同一モジュール内では、明確に マルチプレクサ を使おうかとは思う。

      ここを見ると、CoreConnect, AMBA, WISHBONE といったオンチップバスが標準として定義されているらしい。これらは、一体どうやってバスを実現しているのだろう? ちょっと勉強してみよう。

    • WISHBONE (Opencores.org) に行くと、仕様書と 他のバスとの比較の文書がある。

    そういうわけで、いろいろやってみた。

      少なくとも同一モジュール内では、明確に マルチプレクサ を使うというルールにした。となると、面倒なのは上位モジュール。

      今までは、サブモジュールで勝手にアドレスデコードする方が効率が良かったのだが、サブモジュールもマルチプレクスするなら、上位モジュールでも アドレスデコードしないといけない。

    • rtavr_ior.v (ver 002)

      新しく作った I/O レジスタ(IOR) モジュールで、

    • (1) 完全マルチプレクス + 上位で全部アドレスデコード
    • (2) 完全マルチプレクス + サブモジュールでもアドレスデコード
    • (3) それぞれ自モジュールだけマルチプレクス してモジュール間は (記述だけ)トライステート

      の 3 つの規模を比較してみた。


      // DO: (1)NORMAL (2)NORMAL (3)TRISTATE(FAKE)
      // (SELF_DECODE) (SELF_DECODE)
      // Number of Slice Flip Flops: 121 121 121
      // Number of 4 input LUTs: 185 170 200
      // Number of occupied Slices: 153 145 163
      // Total Number of 4 input LUTs: 194 179 209
      // Number of bonded IOBs : 49 49 49
      // IOB Flip Flops : 48 48 48
      // Number of BUFGMUXs : 1 1 1
      // Number of RAMB16BWEs : - - -


      結果はこうなった。

    • サブモジュールでもアドレスデコードすると 二重に記述するから効率が下がると思ったのだが、結果は逆になった。

    • 論理的にはまったく同一で、モジュール間のインターフェイスだけが違うのだが、結果が違う。gcc の inline 関数のようなものではないようだ。

    • トライステート記述だと効率が落ちた。楽をすると結果も良くない.. ということか。

      rtavr_ior.v (ver 002) は、470 行もある。いろんな組み合わせを試せるように、記述も ifdef で切り分けているから ソース規模が増えている。これからは、(2) を採用して シンプルにしていこうと思う。

    • rtavr_sram_2KB.v (ver 003)
    • rtavr_rom_8KB.v (ver 002)
    • rtavr_rom_4KB.v (ver 002)

      そう決まれば、RAM/ROM も直しておこう。(2) を可能にするため、in/out に分ける。(2) 専用なら OE が不要になるが、OR でつなげることも出来るように OE を活かす。そうなると (3) も 残せるので、比較のために 残しておく。

      ついでに 4KB の ROM も作っておく。-- 50A に実装する場合 4KB までしか無理。そして (当面は) 50A に AVR を入れるのがやっと。だから 4KB が必要十分。


      // Clock to Setup 4KB 8KB
      //on destination clock CLK (2): 1.996 1.881
      (3): 1.900 2.101

      ROM の Clock to Setup を 比較してみた。が、2ns 程度ということしか分からなかった。

      これで、書き換え終了。(2) をメインにするが、セレクタではなく OR でつなげるのもいずれ試してみたい。

    • rtavr-wk01.tar.gz

      バージョンが良く分からなくなると困るので、ここまでを tar で固めて置く。(ファイル形式も変更: CR+LF → LF)

      次は コアに着手する。モジュールの構造で サイズが変わることが分かったので、SREG や スタックポインタ、割り込みのインターフェイスは今の時点では FIX しない。

      なお、これからは、個々のファイルはリンクしない。単独でリンクしても意味はなさそう。

    コアの設計(1) s0_fetch

    コアの設計をするには、まず命令表が必要だ。opcode がちゃんと分かるもの。

    クレア工房:AVRマイコンの命令』を参考資料にしようと思う。あと 『Tiny20/Tiny40』の記事で、整理しているので、それも参照。

      さて、最初に作るのは、命令コードを取ってくるところ。PC をインデックスに ROM から読み INST に格納するだけ ... ではない。

      少なくとも、設計したGPR の 4 つのレジスタ のアドレスをこの時点で FIX しないといけない。

      input [3:0] ADDRAL, ADDRAH, ADDRBL, ADDRBH;

      こういう風に名前を決めたから以降はこれを使う。
      それぞれの役割は、

    • ADDRAL : LD / ST でのインデックスレジスタ(X,Y,Z) の下位バイト or ADD 命令などの Rd の読み出し
    • ADDRAH : LD / ST でのインデックスレジスタ(X,Y,Z) の上位バイト or ADD 命令などの Rd の書きこみ
    • ADDRBL : ICALL での Zレジスタ の下位バイト (or ADD 命令などで Rd の読み出し)
    • ADDRBH : ICALL での Zレジスタ の上位バイト or ADD 命令などで Rd の読み出し
      これをもっと具体的な命令で定義する。

        s0_fetch でやること

        PC[11:0] から rom を読み込み その出力 PM_OUT[15:0] を
        INST[15:0] に書きこむ。

        つぎの値を確定する。

        ADDRAL,ADDRAH の組:

        0xa,0xb: (26 - 16, 27 - 16)
        LD.Rd.X 1001:000d:dddd:1100
        ST.X.Rr 1001:001r:rrrr:1100
        LD.Rd.X++ 1001:000d:dddd:1101
        ST.X++.Rr 1001:001r:rrrr:1101
        LD.Rd.--X 1001:000d:dddd:1110
        ST.--X.Rr 1001:001r:rrrr:1110

        0xc,0xd:(28 - 16, 29 - 16)
        LD.Rd.Y 1000:000d:dddd:1000
        ST.Y.Rr 1000:001r:rrrr:1000
        LD.Rd.Y++ 1001:000d:dddd:1001
        ST.Y++.Rr 1001:001r:rrrr:1001
        LD.Rd.--Y 1001:000d:dddd:1010
        ST.--Y.Rr 1001:001r:rrrr:1010

        0xe,0xf:(30 - 16, 31 - 16)
        LD.Rd.Z 1000:000d:dddd:0000
        ST.Z.Rr 1000:001r:rrrr:0000
        LD.Rd.Z++ 1001:000d:dddd:0001
        ST.Z++.Rr 1001:001r:rrrr:0001
        LD.Rd.--Z 1001:000d:dddd:0010
        ST.--Z.Rr 1001:001r:rrrr:0010

        PM_OUT[7:4] , INST[7:4] (一クロック遅れ)
        それ以外
        (例)ADD 0000:11rd:dddd:rrrr
        IN 1011:0AAd:dddd:AAAA
        OUT 1011:1AAr:rrrr:AAAA
        POP 1001:000d:dddd:1111
        PUSH 1001:001d:dddd:1111
        SBRC 1111:110r:rrrr:0bbb
        SBRS 1111:111r:rrrr:0bbb
        ORI 0110:KKKK:dddd:KKKK

        ADDRBL,ADDRBH の組:

        0xe,0xf:(30 - 16, 31 - 16)
        IJMP 1001:0100:0000:1001
        ICALL 1001:0101:0000:1001

        PM_OUT[3:0] ,PM_OUT[3:0]
        それ以外
        (例)ADD 0000:11rd:dddd:rrrr

        PREDEC の値
        LD.Rd.--X 1001:000d:dddd:1110
        ST.--X.Rr 1001:001r:rrrr:1110
        LD.Rd.--Y 1001:000d:dddd:1010
        ST.--Y.Rr 1001:001r:rrrr:1010
        LD.Rd.--Z 1001:000d:dddd:0010
        ST.--Z.Rr 1001:001r:rrrr:0010
        POSTINC の値
        LD.Rd.X++ 1001:000d:dddd:1101
        ST.X++.Rr 1001:001r:rrrr:1101
        LD.Rd.Y++ 1001:000d:dddd:1001
        ST.Y++.Rr 1001:001r:rrrr:1001
        LD.Rd.Z++ 1001:000d:dddd:0001
        ST.Z++.Rr 1001:001r:rrrr:0001

      あと I/O レジスタ。GPR と同じタイミングと決めたから、アドレスを FIX する。

        s0_fetch でやること(2)

        IOR_ADDR の値

        { PM_OUT[10:9], PM_OUT[3:0] }
        IN 1011:0AAd:dddd:AAAA
        OUT 1011:1AAr:rrrr:AAAA
        { 1'b0 , PM_OUT[7:3] }
        CBI 1001:1000:AAAA:Abbb
        SBIC 1001:1001:AAAA:Abbb
        SBI 1001:1010:AAAA:Abbb
        SBIS 1001:1011:AAAA:Abbb

      これ以外にメモリ空間からのアクセスがある。GPR/IOR のアクセスメソッドはこれ以外にないわけだから、これもセレクタで切り替えなければならない。だが、ここでは枠だけ考えておく。

    s0_fetch を作ってみた。

    • rtavr-wk02.tar.gz
    • rtavr_s0_fetch.v (ver 001) + rtavr_rom_8KB.v (ver 002)

      最初は ROM なしで作ったのだが、性能が分からないので、8KB ROM を 内蔵する版も作った。ちなみに、ファイル単位のリンクを作らないつもりだったが、気が変わった。-- やはり簡単に内容を見れた方が良いような気がしてきた。
       
      // - with 8KB ROM - NORMAL(old) NORMAL(new) NO_EXACC
      // Number of Slice Flip Flops: 34 24 24
      // Number of 4 input LUTs: 47 44 39
      // Number of occupied Slices: 39 36 33
      // Total Number of 4 input LUTs: 50 45 39
      // Number of bonded IOBs : 92 91 84
      // IOB Flip Flops : - - -
      // Number of BUFGMUXs : 1 1 1
      // Number of RAMB16BWEs : 4 4 4
      // Clock to Setup
      //on destination clock CLK 4.436 4.101 3.660
      new : remove EXACC_GPR

      結果を先に書くとこう。今度は思ったよりは小さい。ちなみに NO_EXACC は、メモリ空間からのアクセスなし版で比較用。

      性能は、余裕ありまくり。これなら確実に 他がボトルネックになる。

      内容は、上記で書いたものを verilog に変換しただけ。
      ... 実は、IOR でロードするものがなかったら SREG をロードするようにしてみた... のだが そのタイミングで 前の命令が s2_execute を実行しているので 意味がない。... とは思うが役に立つかも知れないので残しておく。

      追記: 間違い訂正

        普通の Tiny Core だと アドレス空間は、先頭に GPR (32B) 次に IOR (64B) があって、その後 RAM が続く。だが こいつは、先頭に IOR (64B) があって、その後 RAM。要するに GPR は メモリ空間からアクセスできない。-- 結構楽になる。


      次は、 命令デコードを後回しにして s2_execute にしようかと思う。... ところで、s3_writeback を置く予定で考えていたが、s2_execute でメモリアクセスした方が良いような気がしてきた。そのあたりも合わせて検討する。

      s2_execute を先に作って、それのための信号を 命令デコードで用意するという方針で、先に s2_execute を作るのは間違っていないはず。それで、ちょっとやっているのだがなかなかに手強い。

      それはともかく、いつも混乱するのでメモ。

      -----------------------------------------------------------------------
      PC[11:0]
      -----------------------------------------------------------------------
      (S0_fetch) rom

      -----------------------------------------------------------------------
      INST[15:0] PREDEC/POSTINC ADDRAL ADDRAH ADDRBL ADDRBH
      -----------------------------------------------------------------------
      (S1_decode) gpr

      -----------------------------------------------------------------------
      DOAL DOAH DOBL DOBH
      -----------------------------------------------------------------------
      (S2_execute) ---- ADDR --- Rd Rr

      -----------------------------------------------------------------------
      DI
      -----------------------------------------------------------------------

      混乱するのは、状態を覚えるラッチ類。これらは、どこかのステートに属するというより、ステートの間にあると考えたほうが混乱しないようだ。

      今 注目しているのは、S2_execute なわけだが、DOBL / DOBH の役割を それぞれ Rd / Rr に固定したほうが良さそう。(ICALL を除く) 。DOAL/DOAH の役割は、インデックス・レジスタ用。S2 の頭で メモリ空間への 読み書きの準備が出来ている。ストアする場合は、Rr を 書き出すし、ロードする場合は、S2 の最後までに 読み込んだデータを DI に送り込める。

        ちなみに、ストア系の命令は、Rd の位置に Rr が来る。ちょっと具合が悪いので、切り分けて (Rr 用の) DOBH からも 値が出てくるように変更した。

      算術命令は、基本 Rd/Rr を入力として DI に出力する。それにマッチしているので都合が良い。

      そうなると IOR も同じタイミングでアクセスしたくなる。... というわけで IOR を S0_fetch で扱うのはヤメ。

      さて、S2_execute が手強いわけだが、そもそも基本方針でつまる。普通は、ひとつの ALU のロジックを決めて、配線をそこに集める。AVR Core は ALU が別コンポーネントになっているし、KX_AVR も (よく分かっていないが) 同じようなつくりのようだ。

      だが、配線の上に ALU を載せていくという設計もある。こういうつくりだと、ALU が 2 つ 3 つ必要になる。ただし、メインの ALU は Rd/Rr を入力して DI に出力するものになるので、それは同じ。

      で、こっちの方が C 言語のプログラム風で 記述がしやすい。Navre も こっちの方針のようだ。配線の間に組み合わせ回路を置くのは別に悪い考えではないようにも思う。問題はどちらが 効率が良いのか? -- 結局両方作ってみて比べることになりそう。

      あと、命令のデコードの方針。IOR のテストで、アドレスデコーダをローカルに持った方がスペース効率が良いことは分かった。S2_execute でも同じような話がある。(S2 で) if 文が必要なくなるぐらい、(命令デコードで) 条件を細分化してしまう手もあるわけだ。だが、そうすると 却ってスペース効率が悪くなる。インターフェイスをどうするかについても悩ましい問題だ。

      ところで、後で気がついたこと。なんと LDS/STS 命令が 1 ワードのものに置き変わっている。

      IN 1011:0AAd:dddd:AAAA # load from I/O port to register
      OUT 1011:1AAr:rrrr:AAAA # store to I/O port from register
      LDS 1010:0kkK:dddd:kkkk # 16bit
      STS 1010:1kkK:dddd:kkkk # 16bit

      IN/OUT 命令の仲間のような位置づけ。アドレスも 6bit分の kkkkkk は IN/OUT と同じで、その上の 2bit が (K == 0) ? "10" : "01" 。要するに RAM の先頭から128B のみがアクセスできる。

      あと LD/ST 関係のメモ:

      X++ とか、PREDEC/POSTINC したレジスタを ロード・ストアする場合の動作は未定義。だが、次の命令で参照する場合、正しい値が取れないといけない。今の設計では、上位バイトが変更になる場合、1クロック遅らせるから 上位バイト自身は問題ないはず。下位バイトが変更になる場合、クロックの前半で値が変わるから、Rr (DOBH) での読み出しは問題ないはず。Rd (DOBL) での読み出しは、前の値が読めてしまう。また Rd の書き込みデータは読もうとしても、常に前のデータしか見えない。

      上の図で言うと(S1_decode)で1クロックかけて DOAL,DOAH,DOBL,DOBH の値を決めるが、同時にひとつ前の命令が、(S2_execute ) の状態で 更新するデータを 作っていて、それは DI の入力になる。DI のレジスタ番号も GPR は知っている (そうでなければ更新できない)。

      よくよく考えれば、更新しようとしているデータが何であれ、DOAL,DOAH,DOBL,DOBH の値を決める前に確定するから、先取りすればよいわけだ。

      ただし、BL,BH はそれで良いが、AL,AH はどうなのか? これはインデックスレジスタ値で、更新値が決まる前に参照して、さらに AL は更新までしてしまっている。値が決まっていないわけだから 1クロック遅らせて実行するしかない。なお、1クロック遅らせることを決めるのは、自分の命令の場合は、S0_fetch 。S1_decode では次の命令だけ止めることが出来る。(S1_decode = 次の命令の S0_fetch)

        もっと正確に書いとかないとコードに落とせない。混乱するのでもう一度書いておくが、後ろの命令との競合ではなく実行中のひとつ前の命令との競合。
        (1) POSTINC/PREDEC しないケースは、BL,BH と同じように置き換えて問題ない。
        問題があるのはPOSTINC/PREDECするケース。
        (2) AH は 更新されるとは限らないが、AL の更新後 - S1_decode時でないと分からない。つじつまを合わせることは不可能ではないが、面倒。POSTINC/PREDECするケース で AL/AH が競合するなら、1 クロックずらすことで対処したい。
        (3) このようなことが起きるのは、X を更新した次の命令で、X をインデックスに ++ してアクセスするような場合。ループの中では起きない。
        (4) そもそも、普通のAVR で ST は 2 クロック。Tiny10 は POSTINC が 1 クロックで PREDEC が 2 クロック。こいつは 両方1クロックだが、 前後共に 1 クロックづつ入って 3 クロックになる場合がある。ただし条件があるので避けるコーディングは可能。

      ところで、同じような話がある 。ひとつは条件分岐。AVR は、分岐する場合でも、2 クロックしかかからない。S1 になった時点で、PC が決まっていて次の命令は既に読み込もうとしている。だから間に合わない。S1 終了時に 読み込んだ命令をキャンセルして 分岐先の PC にすると やっと 2 クロックでの分岐ができる。条件分岐命令は、S1 での実行になるのだが、ひとつ前の命令が実行中で 条件を設定中なわけだ。上で書いたのと同じようにフラグは先取りする必要がある。

      もうひとつは、条件スキップ命令。CPSE , SBRC/SBRS, SBIC/SCIS の 3 種類。CPSE は演算結果でスキップするかどうか決める。SBRC/SBRS はレジスタのビット値で SBIC/SCIS は I/O レジスタのビット値。

      これらは、S2 で決まる値。スキップ対象の次の命令の S1 で先取りして、無効にするかどうか決めないといけない。

      結構面倒な話だが、気になっていたのは、IOR の アクセスタイミング。このように処理するのなら、決めた通りで良いようだ。ちなみに、今まで"先取り"と書いて来たが"フォワーディング"という用語がある。でも長いので"先取り"で通す。

    (AVR互換コアの仕様(その3)に続く)
    posted by すz at 16:04| Comment(0) | TrackBack(0) | AVR_CORE

    2011年02月07日

    AVR互換コアの仕様(メモ)

    Tiny10系の AVR互換コアを作りたいわけだが、どんなものをどんな風に作りたいのか? より具体的にしていこうと思う。

    まず目的は、『(1)自作したい(作ってみたい)』 というのがまずあるが、『(2)それを応用したい』というのもある。何に応用するかも多少はっきりしてきている。

    だが今のところフルスクラッチで作れるだけの実力はない。現時点では改造できるかすら怪しい。

    さて、どうやっていくか -- まぁこれは公開されているソースを読んでいくのが近道だろう。実際に動かすかどうか別にして、Implement して情報をみたり、シミュレータで動作を確かめたりすることもできる。

      参考にするものは、決めた。

      • KX_AVR (Verilog)

        コンパクトな AVR コアで、Xilinx の プリミティブを多用している。ただ、CPU コアだけで メモリや PORT はない。ソースコードを眺めてみたが結構読みにくい。-- 最適化されたものの常ではあるが読みこなすのは結構厳しい。

          読むのが難しい理由が分かった。LUT (Look-Up-Table -- ROMみたいなもの) を多用していて テーブルから 論理に変換しないと 何をやっているかすら分からない ... ということだ。そして、LUT など構成をガチガチにしているので、論理が簡単になっても 自動最適化できず、決められたものより小さくならない。

      • AVR Core (VHDL)

        CPU コアだけではなく、周辺装置も含まれている。JTAG や 外部バス まで付いていて、ATmega103 互換になっているらしい。だが、規模が大きいのだ。そして、一部の機能を disable して小さくできるようにはなっていない。

        ところで、こちらは VHDL 。実をいうと Verilog のコードを読んでみて、書いたり変更するのが楽そうに見えたので、メインを Verilog にしようと思っている。規模を小さくするつもりで、そのためには作り直しになると予想している。なので、参考にするが、書くのは Verilog のつもり。

      • Navre AVR clone (Verilog)

        これも ここに含まれるのは CPU コアのみ。ちょっと見てみたが、かなり読みやすい。ソースのサイズも小さい。だが 一部の命令がインプリメントされていない。gcc が使えるものでないと (2)の目的に合わない。あと パイプラインが 2段。作りたいのは、4 段パイプラインなので 2 段というのは微妙。

        これは、書き方の流儀というか作法の参考にしようかと思う。


      これらを理解するだけでは、実は目的を達成できない。なにしろ Verilog の 文法すら知らないのだ。それに、ブロックRAM や 分散RAM 他の Xilinx の プリミティブの知識がないとコンパクトなものは作れないようだ。参考になりそうなものをここにメモしていこう。


    ここから本題。上に書いたことをもとに、作りたい Tiny10系の AVR互換コアはどういうものなのか決めていこう。

    まずは核となる CPU コアについて

    • (avr-gcc上の) アーキテクチャ: attiny10
    • レジスタ: r16-r31 の 16 個。
    • 命令: Tiny系 から LPM/SPM , MOVEW/LDD/STD を除いたもの。ROM は 8KB まで。
    • アドレス空間: X/Y/Z で 16bit 空間にアクセス可能にする。

      メモリ空間(X,Y,Z)
      0x0000 - 0x001f I/O レジスタ
      0x0020 - 0x003f I/O レジスタ(予約)
      0x0040 - 0x043f RAM (1KB の場合)
      0x3FC0 - 0x3FC3 DEVICE ID (必要なら)
      0x4000 - ROM
      I/O 空間 (IN,OUT 等)
      0x00 - 0x3f I/O レジスタ
      RAM 空間 (LDS STS direct)
      0x00 - 0x7f RAM の下位 128B
      RAMAR/RAMDR (サポートしない予定)

    • 実行命令サイクル: Tiny10系 は参考にしない。むしろ Tiny系準拠。

    • 外部仕様 -- 調査中

      (決めたら載せる)

    • モジュール分割

      KX_AVR も Navre も CPU をモジュール分割していない。一方 AVR Core は、ALU/汎用レジスタ/IOレジスタ/IOレジスタマッピング/命令フェッチ-デコード という風にいくつかのサブモジュールを作っている。

      いまのところ 分散メモリをどう使うかの関係で、汎用レジスタ は サブモジュール化するつもり。他は決めていない。

    MCU 全体について


    • フラッシュメモリ

      ブロックRAMを使う。命令系の都合もあり 4Kx16bit(8KB) まで。

      デュアルポートを使って、ポートを 命令フェッチ(16bit) と バス(8bit) に振り分けたい。あと、パリティ用のビットがあるので、なにか有効に使えないか検討しようと思う。

    • RAM

      ブロックRAMを使う。2Kx8bit (2KB)。 バス(8bit)に接続するだけで十分のはず。

    • 汎用レジスタ (GPR)

      1 クロックで Load + Load + Store するのだから 2 出力 1 入力は最低必要。さらに X/Y/Z でのアクセスは、バスに 16bit 出力して プレ・デクリメント, ポスト・インクリメントできるようにしなくてはならない。SBRC/SBRS 命令というのもあって、さらに別ポートが必要(かも知れない)。

      それでも MOVW 系や乗算命令がないので、普通のAVR より単純。 ここが規模縮小のポイントなので、どう構成するか非常に重要。よくよく考えたい。

    • I/O レジスタ (デバイス)

      ベースは Tiny40 。デバイスのマッピングは、これから ADC など 0x0D - 0x14 を除いて USART をそこに置いたものが フルセットと想定。で、それの サブセットを作っていく。

      少なくとも PORT と TIMER は載せる。それに関連した割り込みも。そうしないとなにも出来ない。ただフルセットは考えていない。特にプルアップは外部に接続するときだけ必要だし、IOB の機能で自由にならないし。PORT では tristate まで考える。

      PORT の機能は、Tiny10系と従来のAVRでは相当違う。まずは従来のAVR互換の方針で検討する。

      TIMER は、8bit の Timer0 と 16bit の Timer1 が標準的でそれに準じようと思う。

      あと PWM の PORT との兼用とか ピン変化割り込みとか 様々な機能があるが、まずはサブセットを目指す。拡張したとしても 必要なければ削れるように考慮する。

      あとの機能もオプショナルにして 順次拡張していきたい。入れたいのは、USART / SPI / TWI 。

      構成方法については、AVR Core を参考にする。IOレジスタ/IOレジスタマッピング のサブモジュールを入れる(かも)。


    以上が本題。あと、新たに分かったことや 経緯など この下に追記していくつもり。

    追記: AVR Core の 汎用レジスタモジュールの定義

      input ireset : (内部)リセット
      input cp2en : クロック

      // 2 load + 1 store
      input reg_rd_wr
      input reg_rd_adr[4:0]
      output reg_rd_out[7:0]
      input reg_rd_in[7:0]

      input reg_rr_adr[4:0]
      output reg_rd_out[7:0]

      // 有効アドレス(EA)
       // reg_h_addr の意味
      // 001 : X
       // 010 : Y
       // 100 : Z
      // reg_h_wr は EA ライトバック指示
      input post_inc
      input pre_dec
      input reg_h_wr
      input reg_h_adr[2:0]
      output reg_h_out[15:0]

       // Z 値 LPM/ELPM 用(不要)
      output reg_z_out[15:0]


      定義は 予想と違う。MOVW 系がないようだ。
      reg_z_out を抜いて これだけを Implement すると...

      Number of Slice Flip Flops 256
      Number of 4 input LUTs 578
      Number of occupied Slices 412

      .. 分散RAM が使えていない。全部FF (32x8 = 512)だと こうなるわけか。

      それはともかく、この定義ベースで 作ってみよう。

      さて、そもそも分散RAM とはどういうものなのか? Spartan-3 の ug331 に説明があるが、どうも 16x1bit の デュアルポートが構成できるようだ。出来ることは 2 load + 1 store。X/Y/Z レジスタがなければ具合良く 16x8bit が入るわけだ。LD 命令を 考えると X/Y/Z レジスタ は、1 store とは別にインクリメント/デクリメントした値を書き戻さないといけないので、この中には入れられない(かも知れない)。

        実際のAVRは、どれぐらいのクロックが必要かちょっと見てみたところ

        normal Tiny10
        LD Rd,X 2 1/2   S L L
        LD Rd,X++ 2 2 S L L WB
        LD Rd,--X 2 2/3 S L L WB

        ST X,Rr 2 1 L L L
        ST X++,Rr 2 1 L L L WB
        ST --X,Rr 2 2 L L L WB

        と、むしろ速い場合がある。 あと、プレ・デクリメントのために 1 クロック消費している。

        この表をじっくり見ていると 16bit デュアルポートならなんとかなるような... あるいは 汎用レジスタだけ 2 倍速で動かすとか.. 倍速なら インクリメント/デクリメントも キャリー付き 8bit で済むし良いかも。
        2 倍速のクロックは入力するが、インターフェイス自体は標準速という 考えで作れば良いのかな?

        こういう高望みをして 果たして作れるのか? 不安になるがまぁトライしてみよう。

      よく分からなくなった。どういう機能が必要かというのは、パイプラインの処理がイメージ出来ていないと 分からない。なので、整理してみる。



      stage 0 fetch 命令フェッチ
      stage 1 decode 命令解析
      stage 2 execute 実行
      stage 3 writeback 結果の格納

      ひとつの命令に注目すると、上記の 4 つのステージがあって、1クロックづつ後に 次のステージが実行される。ひとつのステージに注目すると 続く命令が どんどん来る。(図では左が過去)

      あるステージは、続くステージに FF を使って 状態を引き渡す。(INST) などがそれに当たる。

      いま注目しているのは、汎用レジスタ GPR。その周りの情報の流れを書いてみた。



      この図は GPR を 普通に 分散メモリで構成したときの流れ。明らかに無理がある。
      ひとつの問題は、stage 2 でやることが多いということ。なぜそうなってしまうかというと、(Rd_adr) が 書き戻すアドレスも兼ねているため。ステージをずらすと (Rd_adr) が 2種類必要になる。また、X/Y/Z レジスタを 読みだして (BUS_ADDR) に入れたいがそのためのパスがない。

      ST 命令を考えると BUSに書きだすレジスタのために で 1つのポートを使うので、使える ポートは 8bit が 1つしかない。X/Y/Z レジスタの書き戻しはおろか 読み出しすら満足にできないわけだ。



      そこで 強引に 分散メモリを使うために、 倍速という 方法を考えたわけだ。

      (Rd_adr) を 前半は Read 、後半は Write という風に使い分ける。ステージを分けるように書いてみたが .. たぶん間違い。ステージを分けると 違う命令のために使うわけで、次に書くようなことをすると 競合が起きて具合が悪い。ステージを分けは最初の図のようにするしかないかも。

      X/Y/Z レジスタについてだが、まずは (BUS_ADDR) に 値をロードしたい。

      ST X,Rr を考えてみると ... Rr 読み出しにひとつのポートを使う。もうひとつのポートを使って 2 回に分けて 読み出す。図では反対になってしまったが、(Rd_addr) の方を使えば 書き戻しも出来るはず。

      一方 LD の方はまだ考えていない。BUS からの 読み出しにまるまる 1クロック使う前提 にすると、値が得られるのは stage 3 が終わった後。1 クロック挿入しないと どうにもならないような気がする。逆に 挿入するならば、GPR を自由に使えるので なんとでもなる。

      あと、Zレジスタは、IJMP , ICALLでも使う。これらは 2 命令での実行。... ということは stage 1 のときに Z レジスタを得て その次の PC にしないと いけない。... やはり GPR の読み出しは stage 1 に持っていった方が良いということか。

      だいぶ穴が多いが、なんとかなりそうな気もする。これを元に考えなおしてみることにしよう。

    Tiny10 で クロック数が変わったもの(メモ)

    話題が変わるが、LD/ST 以外でもあった。

      normal Tiny10
      RCALL 2 2/3
      ICALL 2 2/3
      RET 4 4/5
      RETI 4 4/5

      SBI 2 1
      CBI 2 1

      メモリアクセスが 2 クロックに 1回しかできないので、1 クロック遅れる場合が出る。一方 CBI/SBI が 1クロックになっている。

    話題を元に戻して上記で書いたことの整理。図にしてみた。

    • (1) ADD や MOV 命令 など



      Rd , Rr から読み出し Rd に書き戻すわけだが、リソースをどういう風に使うかの図。リソースとして、PORTA と PORTB がある。PORTA には、IN と OUT(図では使う側からみた OUT/IN になっている) があるが、アドレスは 1 つ。これを 1 クロックの間に 2 回アクセスする計画なので 上限に分けている。

      赤で S1_decode/S2_execute の別を書いた。読み込むのは S1_decode だが、書きだすのは S2_execute の最後。これについては後ろで説明。

      PORTB からは、Rd を読み出す。今の段階では 分けて使う理由がないので 2 つ専有している。

    • ST --X,Rd , ICALL




      ST --X,Rd , ICALL ではどう使うか を図示するとこんな風になる。

    • ST --X,Rd タイムチャート




      時間軸で上記の枠を配置するとこうなる。S2_execute にある枠を使うのは 1 クロック後になるわけだ。

      時間的に厳しいのは、XL を読みだして -1 (または +1) して書き戻すところ。命令をデコードした後に初めてアドレスが確定し、そこから読みだして、さらに演算して書き戻すことになる。でも、この位置にしか入らないので、なんとかしなくてはならない。( ちなみに XH-1 の書き戻しが 後ろになるのは、他の命令が S2_execute で使うのでそれに合わせないといけないため。)

      幸いなことに、S0_fetch は余裕ありまくりのはずなのだ。ブロックRAM は 200MHz 以上で動作することになっている。なので、S0_fetch の区間に S1_decode でやることを 一部持っていく。せめて アドレスを確定しておくと、S1_decode の最初から 読み出せる(はず)

    • LD Rr,--X



      では、LD 命令はどうなるのか? ... BUS にアドレス を出して 読み込むわけだから、S3_writeback が 終わったときに値が分かる。 だが、書き戻すための枠がない。

    • LD Rr,--X タイムチャート



      時間軸で書きなおすとこうなる。入れるとしたら Tn+3 になるが、次の命令の S2_execute で使うかも知れない。簡単に対処するなら 無条件に(次の命令を) 1clock 伸ばすことになる。

      次の命令が、PORTA を全く使わないならば、そこを借りて書き戻すことは可能。そうやって初めて 1 クロックになる。Tiny10 はそうやっているのかも知れない。普通の Tiny は 無条件に 2 クロック。こいつで 高速化すべきかどうかは、別途検討するが、高速化が不可能なような設計にはしないつもり。

      ざっと整理してみると、こんな感じか。汎用レジスタ (GPR) の構造が決まらないとなにも決まらないようだ。これだけをモデル化してみて、確認するのが第一ステップのようだ。

      ところで、更新中のデータにアクセスされたらどうするのか?

      更新ポイントは 2 ヶ所しかない。それぞれについて考えてみよう。

      まず --XL と XL++ 。これらは ロードと同じ枠で更新が入るから 他の命令には影響を与えない。問題は自分自身のみ。ST X++,Rd で Rd が XL だったら 更新前の値を Store しないといけない。... ということは、Rd の枠の 上を使えば問題ないということだ。

      ST --X,Rd だったら 更新後? ならば、 Rd の枠の 下を使わなくてはならない。

      ちょっと面倒だが、まだ簡単に処置できる方だ。

      次の更新ポイント。S2_execute での更新。一番複雑なのは、LD 命令で 2 つ更新中のものがある。それを次の命令で使われる可能性がある。自分自身でも競合するわけだが、それは LD で読んだ値で上書きすることになっているから気にしなくてよい。

        追記: 命令の方のデータシートを見たら、例えば Y レジスタ間接で YL や YH をロードする命令は書けるが、どのような結果になるかは未定義だそうだ。

      一番簡単なのは、次の命令で競合しているかどうかをチェックして競合すれば 次の命令を 1 clock 遅らせることだ。だが、チェックできているなら、(なにしろ更新中なわけで)内容は 覚えているので、それを使えば良い。だがよく考えてみよう。

      AND 命令での Rd のケースでは、値が確定するのは s2_execute の後半。それは 次の命令の s1_decode の後半ということでもある。s1_decode の先頭で 既に値を使っているわけで 明らかに無理。

      s1_decode の後半でしか使わないものはどうだろう? ひょっとしたら 間に合うのかも知れない。ただ、こうすることで、最高クロックが上がらないなら 潔く 遅らせた方が得だ。これはどこがクリティカルになるかで決まるから 現時点では決められない。両方作って選ぶという方針にしないとダメかも知れない。

      あとは、2 clock 遅れの LD 命令でロードした値がある。そもそも 競合するようなら 1 clock ずらすのだ。問題は 2 clock ずらすかどうか 。

      これは単に ストア待ちで 値自身は 決まっている。さすがに 2 clock ずらすのはどうかと思うので、更新中のデータを使う方向で検討しよう。

      以上で書いたことを 全部盛りこんで 汎用レジスタモジュールを定義しよう。

    汎用レジスタモジュールの定義(1)

      これもまた、図を描いてみた。



      まず、PORTB 。基本 PORTA とは独立に考えられるから、簡単なこっちから。


      • 基本ルール: ステージをまたがってデータを受け渡すには、かならずラッチが必要になる。そのラッチは出力側に置くことにする。

      • 分散RAM は、1 クロックで 2 回のアクセスをするわけだが、使う側はクロック単位で動作する。そうすると、PORTB のアドレス 2 つ分をあらかじめ決めておかないといけない。

      • 出力側も同じで、2つの出力は 別々ラッチとして分けなければならない。図ではラッチは入れてあるが、XL側は更新タイミングが違う。同期化するために、もう一段ラッチを入れないといけないかも。(これはとりあえず保留)

      • Rr_SEL としてセレクタをいれているが、使う側から見ると大きなお世話かも知れない。単に 2 出力にした方が良いのでは? ... そうすると、単純に ポートを 1 つから 2 つにしているだけの回路になる。



      次は難しそうな PORTA 側。

      • PORTB をベースにして考えると、こちらも基本は、ポートを 1 つ から 2 つに増やす回路。

      • ただし入力のひとつは、WriteBack (ちょっと違うけど)。

      • また出力とラッチの間に演算が必要。演算の種類は +1 / -1 と +0 (パススルー)。ただし 16bit の演算がしたいわけだから、キャリー(ボロー)あり・なしの区別も必要。

      • PORTB と同じように出力を 2 つに分けるのは大きなお世話で、同期化のためのラッチが必要(かも)。

      • 信号名を考えるのが面倒になったので適当になってきた。今はラフスケッチだから、これで良いだろう。また、信号をどのように使うかは、命令デコーダの仕事だから現時点で詳細を考える必要もない。


    GPR モジュールのコードを書いてみた。(仕切りなおし)

    • rtavr_gpr_16.v (ver 003)

      まずは、名前を決める。ファイル名にも使うし、module 名にも使う。AVR 命令のデータシートを見たら、Tiny10 のコアは、the Reduced Core TinyAVR というらしい。なので rtavr_ というプレフィックスにした。モジュール名は、rtavr_gpr_16 。16 個しかレジスタがないことを強調。

        Tiny10 系には 別の名前もあるらしい。ここにも命令一覧があるが、AVR8L と書いてある。

      モジュールインターフェイスは次のようにした。


      module rtavr_gpr_16(
      input CLK2X,
      `ifdef USE_DMY_CLOCK
      `else
      input CLK,
      `endif
      input WE, PREDEC, POSTINC,
      input [7:0] DI,
      input [3:0] ADDRAL, ADDRAH, ADDRBL, ADDRBH,
      output [7:0] DOAL, DOAH, DOBL, DOBH
      , output WB_VALID
      );

      基本上で書いたこと。これも名前を付けるのが重要な作業。C とは違って、この名前を上位レイヤにも見せないといけない。

      CLK2X が所謂クロック。CLK は、本来 input の予定だが、Timing とかの指示をどうするのか分からないので、とりあえず自分で生成。

      WE, PREDEC, POSTINC は更新指示。WE は DI を書きこむ。PREDEC, POSTINC は X/Y/Z レジスタ用。-- だが ADDRAL, ADDRAH で決まるので 実は制限はない。(いまのところ)

      ADDRAL, ADDRAH , DOAL, DOAH は、PORTA 側。ADDRBL, ADDRBH , DOBL, DOBH は、PORTB 側 。L/H の区別をしているが、実は ADDRAH == ADDRAL + 1 である必要はない。(いまのところ)

      DOAL, DOBL の更新タイミングだけ 90 度早い。これでは困りそうなので ラッチを入れた バージョンも作成。(後述)

      WB_VALIDは、PREDEC, POSTINC での更新を保留しているという意味。次の命令が レジスタを更新する (Rd がある) 命令ならば、1クロックずらさないといけない。参照も同様だが、より精度が高い判断ができるように 保留されている レジスタ(= 正しい値が読めない) を WB_ADDR で示すことは可能(デフォルト Off)。

      なお、WB_VALID は、インクリメント/デクリメントした結果 H(上位バイト) の値が変わったときのみ 真になる。 確立が低く決しておきないようにコントロールも可能だから これ以上リソースを割かない方が良いだろう。

      このインターフェイスに対して、単純な順に TTEST2/TEST3/TEST5/TEST6/TEST7 と 5種類入れてある。TEST2 は、演算もライトバックもない単純化したもの、TEST3 では、PREDEC を入れたが、L(下位バイト)しかライトバックしない。 TEST5 は、H(上位バイト) のライトバックも入れたもの。TEST6 で POSTINC に対応。TEST7 では、DOAL, DOBL の更新タイミングの正規化のため ラッチを 2 段にした。

      図で描いた構成と TEST5 が近い。だが、図の構成では POSTINC ではなく PREINC のアドレスが出てしまう。


      // TEST2 TEST3 TEST5 TEST6 TEST7
      // Number of Slice Flip Flops: 36 36 35 45 61
      // Number of 4 input LUTs: 27 37 51 93 93
      // Number of occupied Slices: 24 33 40 62 70
      // Total Number of 4 input LUTs: 27 44(+17) 60(+16) 100(+40) 100
      // Number used for Dual Port RAMs: 16 16 16 16 16
      // Number of bonded IOBs: 58 59 60 61 61
      // Post-PAR Static Timing Report
      // Clock to Setup 5.084 7.782 8.688 9.022 10.302

      これらが、どんな風に Implement されるのか見てみた。-- 思ったより LUT の増加が多い。TEST7 が最終仕様(のつもり) なのだが、70 スライスも使っている。縮小できなければ作る意味はないので少々不安。

      Dual Port RAM の数は 8 だが、そのために使用する LUT の数は 16 。Spartan-3 ug331 には、Dual Port (16x1 bit) が 1CLBに 1 個入るという説明がある。(図5-3)

      性能に関する指標は、Post-PAR Static Timing Report の Clock to Setup の値にした。複雑になるにつれ順当に増えているから、これで良いはず。

      コア全体の 性能目標は 40Mhz - 50MHz ぐらい。X2 なので 100MHz で動いて欲しいところ。取ってきた数字が 最大クロックを示すのであれば ちょっとオーバー。

      一応本体もソースから転記しておく。TEST7 相当。

      (モジュール宣言は省略: 上記とくっつける)

      reg CLK;

      reg [7:0] i_DOAL,i_DOAH;
      reg [7:0] i_DOBL,i_DOBH;
      reg [7:0] gpr [0:15];

      assign DOAL = i_DOAL;
      assign DOAH = i_DOAH;
      assign DOBL = i_DOBL;
      assign DOBH = i_DOBH;

      wire [3:0] ADDRA = (CLK == 1) ? ADDRAL : ADDRAH;
      wire [3:0] ADDRB = (CLK == 1) ? ADDRBL : ADDRBH;
      wire [8:0] DOA = { 0, gpr[ADDRA] };
      reg c;
      reg i_wb_valid;
      wire WB = (PREDEC | POSTINC);
      reg [7:0] i0_DOAL,i0_DOBL;


      reg [7:0] i_wb_data;

      wire [8:0] DOA2 = PREDEC ? DOA-(~CLK | c) : DOA;
      wire [8:0] WB_DATA = POSTINC ? DOA+(~CLK | c): DOA2;

      wire WE2 = (CLK == 0) ? (WE | i_wb_valid) : WB ;
      wire [7:0] DI2 = (WE == 1) ? DI : (i_wb_valid == 1) ? i_wb_data: WB_DATA ;
      assign WB_VALID = i_wb_valid;

      always @(posedge CLK2X)
      begin
      CLK <= ~CLK;
      end

      always @(posedge CLK2X)
      begin
      if (CLK == 1) i0_DOAL <= DOA2[7:0];
      if (CLK == 1) i0_DOBL <= gpr[ADDRB];

      if (CLK == 0) i_DOAL <= i0_DOAL;
      if (CLK == 0) i_DOBL <= i0_DOBL;
      if (CLK == 0) i_DOAH <= DOA2[7:0];
      if (CLK == 0) i_DOBH <= gpr[ADDRB];
      if (CLK == 0) i_wb_valid <= WB & c;
      if (CLK == 0) i_wb_data <= WB_DATA[7:0];

      if (WE2) gpr[ADDRA] <= DI2;
      c <= WB_DATA[8];
      end
      endmodule



      Verilog を初めて使ってみたが、書きたいことをストレートに書けて良い感じ。予定どおり Verilog でいこう。

      ... それにしても 散々検討した内容がこの程度だったとは。全体で 1000 行程にはなるはずなので、先は長いかも。

      最終仕様とは言いつつも、SBRC/SBRS 命令(該当ビット 0/1 でスキップ) の検討は未だ。-- s1_decode でレジスタ値が読めるから多分大丈夫。あと メモリマップされたアクセス。全然検討していないが、これのために仕様を変えることはない。

      仕様を変えるとすれば、スペース最適化のため。プリミティブを有効に使えるなら仕様も変える。だが、それは基本のコードが動いた後。そして基本のコードでプリミティブを多用するつもりはない。

      あとクロック。正しい記述方法が分れば仕様を変更する。90 度ずれた クロックを使うべきならそうするし、DualEdge で良いのならそれを使う。また、リセットを入れるべきなら入れる。

    さて、コアは一旦検討をやめて、周辺のインプリメントを見ておきたい。

    まずは、RAM 。上記のリンクを見て ちゃんと Implement できるかチェック。

    • rtavr_sram_2KB.v (ver 001)

      教えのとおりにやれば、問題なかった。短いし全部貼るとこう。

      module rtavr_sram_2KB(
      input CLK,WEA,WEB,
      input [10:0] ADDRA,ADDRB,
      input [7:0] DIA,DIB,
      output [7:0] DOA,DOB
      );

      reg [7:0] i_doa;
      reg [7:0] i_dob;
      reg [7:0] mem [0:2048-1];

      assign DOA = i_doa;
      assign DOB = i_dob;

      always @(posedge CLK)
      begin
      if(WEA) mem[ADDRA] <= DIA;
      i_doa <= mem[ADDRA];
      end

      always @(posedge CLK)
      begin
      if(WEB) mem[ADDRB] <= DIB;
      i_dob <= mem[ADDRB];
      end
      endmodule

      次のようになったから、これで良いのだろう。

      Number of BUFGMUXs: 1
      Number of RAMB16BWEs: 1


      ところでこれをバスにつなぎたい。... ググって見つかったのをマネした。

    • rtavr_sram_2KB.v (ver 002)
    • rtavr_sram_2KB.v (ver 005) (最新版 参考まで)

      module rtavr_sram_2KB(
      input CLK,WEA,
      input [10:0] ADDRA,
      input [7:0] DIA,
      output [7:0] DOA,

      input WEB,OE,
      input [10:0] ADDRB,
      inout wire[7:0] DBUS

      );

      reg [7:0] mem [0:2048-1];
      reg [7:0] i_doa;
      assign DOA = i_doa;

      always @(posedge CLK)
      begin
      if(WEA) mem[ADDRA] <= DIA;
      i_doa <= mem[ADDRA];
      end

      reg [7:0] i_dob;

      bufif1(DBUS,i_dob,OE);

      always @(posedge CLK)
      begin
      if(WEB) mem[ADDRB] <= DBUS;
      i_dob <= mem[ADDRB];
      end
      endmodule

      PORTB の方をバスにしてみた。top-level でしかうまく行かないとかだと嫌なのだが、うまく行っているようだし先に進む。

      次は ROM 。バス版を元に 16bit にする。WEA と DIA を外す。サイズを 4K word にする。-- ここまでは問題ない。

      だが、うまく ブロックRAM に割り当てないと、分散メモリに割り当てようとして ... 終わらないばかりか メモリを使い尽くして PC が変になる。

      とりあえずは、8bit のバスにつなげたい。... 試行錯誤のすえ、ひとつ通るパターンを見つけた。

        (追記)最新版は、READ_FIRST から WRITE_FIRST に変更している。

    • rtavr_rom_8KB.v (ver 001)
    • rtavr_rom.v (ver 005) (最新版 : 参考まで)

      module rtavr_rom_8KB(
      input CLK,
      input [11:0] ADDRA,
      output [15:0] DOA,

      input WEB,OE,
      input [12:0] ADDRB,
      inout wire [7:0] DBUS
      );

      reg [15:0] mem [0:4096-1];
      reg [15:0] i_doa;
      assign DOA = i_doa;

      always @(posedge CLK)
      begin
      i_doa <= mem[ADDRA];
      end

      reg [7:0] i_tmp;
      reg [15:0] i_dob;

      wire [11:0] i_addrb = ADDRB[12:1];
      wire WEBH = ADDRB[1:0] & WEB;
      wire WEBL = ~ADDRB[1:0] & WEB;
      wire OEH = ADDRB[1:0] & OE;
      wire OEL = ~ADDRB[1:0] & OE;

      bufif1(DBUS,i_dob[7:0],OEL);
      bufif1(DBUS,i_dob[15:8],OEH);

      always @(posedge CLK)
      begin
      if(WEBL) i_tmp <= DBUS;
      if(WEBH) mem[i_addrb] <= { DBUS , i_tmp };
      i_dob <= mem[i_addrb];
      end

      endmodule


      L → H の順で 書かないといけないが、こういう風に Write が出来るようになった。こだわったのは、RAM としても使えると便利だと思ったから。とりあえず Write 出来るようにしておけば、NVRAM の シミュレーションも出来るだろうし。あと、実際のブロックRAM は 18bit あるから 上位ビットを使ってプロテクトもできそうだ。

      それに、Write を一切できなくすると 初期化パターンを用意しないと最適化されてしまう。-- これが面倒だったり。


      Number of Slice Flip Flops : 8
      Number of 4 input LUTs : 10
      Number of occupied Slices : 9
      Total Number of 4 input LUTs : 10
      Number of BUFGMUXs : 1
      Number of RAMB16BWEs : 4

      Warning : 8 multi-source signals are replaced by logic (pull-up yes):

      結果はこんな風になった。ちょっと Warning が嫌な感じだが、まぁいいや後回し。

    とりあえず ROM/RAM は バスにつながったようだ。後 I/O レジスタをどうするのか? この検討が終われば コアの設計を再開できる。

      I/O レジスタには次の命令がある。

        命令 クロック数
      IN Rd,P 1
      OUT P,Rr 1
      SBI P,b 1
      CBI P,b 1
      SBIC P,b 1/2,3
      SBIS P,b 1/2,3

      SBIC/SBIS があるので s1_decode の段階で 読めていないといけないが、 今の設計では、IN 命令も条件は同じ。OUT については s2_execute で 書き終わらないと 次の命令の s1_decode で読めない。SBI/CBI は、一旦読んで書き戻すわけだが、上記の条件を満たせば良さそう。

      ... というわけで バスには接続できない。バスの口を持っていても良いが、GPR で既にバス用の口は持たないことにしたから (どういう風にするかは決めていないが) 同じようにする。

        (追記)最新版は、READ_FIRST から WRITE_FIRST に変更している。


      module rtavr_ior_32(
      input CLK,
      input WE,
      input [7:0] DI,
      input [4:0] ADDR,
      output [7:0] DO,
      );

      同じようにするからには、こんなのが CPU に対するインターフェイスになるはずだ。だが、ステータス・フラグ、割り込みライン、その他多数の外部インターフェイスがある。... やはりバスに接続したい。メモリ用のバスとはタイミングが違うので I/O レジスタ専用のバスにしようと思う。バスにするには、output を tristate にすれば良い。

      でどこのアドレスにマップするかは、パラメータで指定しよう。

    • rtavr_ior_port.v (ver 001)

      module rtavr_ior_port(
      input CLK,
      input WR,RD,
      input [7:0] DI,
      input [4:0] ADDR,
      inout [7:0] DO
      // external I/O
      , inout [7:0] PORT
      );
      parameter BASE_ADDR = 0; // PORTA 0x00
      // parameter BASE_ADDR = 4; // PORTB 0x04
      // parameter BASE_ADDR = 27; // PORTC 0x1b

      reg [7:0] i_pin;
      reg [7:0] i_ddr;
      reg [7:0] i_port;

      wire OE_pin = RD & (ADDR == BASE_ADDR);
      wire OE_ddr = RD & (ADDR == (BASE_ADDR + 1));
      wire OE_port = RD & (ADDR == (BASE_ADDR + 2));
      //wire WE_pin = WR & (ADDR == BASE_ADDR);
      wire WE_ddr = WR & (ADDR == (BASE_ADDR + 1));
      wire WE_port = WR & (ADDR == (BASE_ADDR + 2));

      bufif1(DO,i_pin,OE_pin);
      bufif1(DO,i_ddr,OE_ddr);
      bufif1(DO,i_port,OE_port);

      always @(posedge CLK)
      begin
      //if(WE_pin) i_pin <= DI;
      if(WE_ddr) i_ddr <= DI;
      if(WE_port) i_port <= DI;
      i_pin <= PORT;
      end
      generate
      genvar i;
      for (i=0; i<8; i=i+1)
      begin : port_out
      bufif1(PORT[i],i_port[i],i_ddr[i]);
      end
      endgenerate
      endmodule

      同じような調子で作ってみた。今度もひとつ呪文を覚えた。

      結果:

      Number of Slice Flip Flops : 16
      Number of 4 input LUTs : 21
      Number of occupied Slices : 19
      Total Number of 4 input LUTs : 21
      Number of bonded IOBs : 32
      IOB Flip Flops : 16

      Warning : 8 multi-source signals are replaced by logic (pull-up yes): DO....

      ... PORT の方は Warning が出ていないから、bufif1() を 複数使うと Warning が出るわけか。
      それはともかく、これから沢山作るのに 19 スライスも使っている! 。アドレスデコーダが悪い? 一ヶ所にまとめるべきなのか?

    ちょっと記事が長くなりすぎた。だが、これは前提となるもの単なる洗い出し。ここまでで、だいたい概要は掴めた感じがするから、次の段階は別記事をしよう。

    AVR互換コアの仕様(その2) に続く)

    ただし、ここに書いたことの訂正などは ここでやるしかない。まだ追記は続くかも。
    posted by すz at 20:40| Comment(1) | TrackBack(0) | AVR_CORE