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
この記事へのコメント
コメントを書く
お名前: [必須入力]

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

ホームページアドレス:

コメント: [必須入力]

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


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

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