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
この記事へのコメント
一つの階級の贅沢にだけ頼っているもう一つの階級の貧しいによって維持される。
Posted by air jordan shoes at 2011年02月11日 11:47
コメントを書く
お名前: [必須入力]

メールアドレス: [必須入力]

ホームページアドレス:

コメント: [必須入力]

認証コード: [必須入力]


※画像の中の文字を半角で入力してください。
この記事へのトラックバックURL
http://blog.sakura.ne.jp/tb/43288272
※ブログオーナーが承認したトラックバックのみ表示されます。

この記事へのトラックバック