2012年06月17日

MCPU -- A Minimal 8 Bit CPU

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

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

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

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

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

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

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

MCPU の概要

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

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

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

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

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

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

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

最初のインプリメント

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

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

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

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

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


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

構想

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

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

EFB の調査

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

    まずは、アドレスマップ

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

ソースコード

  • qfn32samples-04.zip

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


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

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

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

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

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

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

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

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

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

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

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

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


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

    code_data 01AAAAAA | 01BBBBBB |

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

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

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

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

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

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

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

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

     ・ qfn32samples-05.zip

新たな CPU へ

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

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

    改造の要点

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

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

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

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

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

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

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

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

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

    命令表

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

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

      (inst == 110)
      10 XX X ST

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

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

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

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

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

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

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

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

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

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

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

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

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

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

      mcpu2 だと 45.5MHz が 63.0 MHz に。

追記

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

      MCPU3 命令表(全 21 命令)

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

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

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

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

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

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

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

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

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

      _____ _____
      CLK | |_____| |_____|

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

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

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

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

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



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

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

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

関連記事
posted by すz at 10:34| Comment(0) | TrackBack(0) | CPLD
この記事へのコメント
コメントを書く
お名前:

メールアドレス:

ホームページアドレス:

コメント: [必須入力]

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


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

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