150LE未満のMISC CPU
スタック1段のみ、キャリーフラグなし、6命令のMISC CPUを作りました。
(ほんとは5命令だったんだけど、このネタに使うのに1命令追加した)
以下が命令です。内部的には最初のコマンド以外、殆ど同じ動きをします。
- GO_IF_NON_ZERO_ELS_SUB スタックがゼロなら引数xxで示したアドレスへ、否ならスタック-xxをスタックに格納。マシン語0x0F,xx
- GET_STREAM_AND_STACK xx bitだけ取得したストリームの下位8bitをスタックする。マシン語0x81,xx
- PIPE_EXTERNAL_TO_FIFO 外部のデータをxxビットだけFIFOにセットする。マシン語0x82,xx
- GET_STREAM_AND_SUBST スタックの値-取得したストリームxxを計算しスタックを上書き。マシン語0x83,xx
- PIPE_SEEK_BITS (通常は命令としては使わない。プログラムを読み込むのにストリームの頭出しシフトをxx bit分するだけ。但し、Nop命令の代わりにはなるかもしれない)。マシン語0x84,xx
- GET_STREAM_PUSH_OUT (これがネタ用に追加した命令)GPIOに引数xxを反映させる。これで思う存分Lチカができるってワケ。マシン語0x85,xx
命令から推測がつくかもしれませんが、シリアルデータを右から左に流したり、条件によって複数あるデータ出力先を切り替えたりする「少し気の利いたFIFO」的なものを作ってました。
しかし条件が増え、デバイスによっては複雑な初期化シーケンスが必要なものもあると、回路規模が増大します。
AlteraのMAXII/MAXVはデバイスの規模にかかわらず1KByteのユーザーフラッシュ(UFM)があるので、初期化手順をここに記述してしまい、回路規模を抑えようというのが経緯です。
冒頭で示した図が、今回作ったCPUのブロック図です。UFMのデータを操作する手段はいくつか提供されていますが、どの手段でもユーザーロジックを消費します。
今回は規模の一番小さいSPI Slaveリードオンリメモリ(16bitアドレスモード = Extended Mode)としてインスタンスしています。回路規模はたったの40LEです。このCPUはこのUFMからコマンドとデータをシリアルリードする設計のため命令体系も回路も独特な構成になっています。
(UFMの取り扱いについてはこちらを参照http://www.altera.com/literature/hb/max2/max2_mii51010.pdf SPIの場合は9-24項以降参照)
メモリ側からはこのCPUがSPI Masterとして見えているので、AlteraでなくてもUFMを持っているLatticeのCPLDや、SPI EEPROMを接続したFPGAなら少しの変更で実装可能なはずです。
使い方
1.コマンドとプログラムを格納するUFMをMegaWizardでインスタンスします。インスタンス方法詳細はhttp://www.altera.com/literature/hb/max2/max2_mii51010.pdf を参照してください。
今回はufm_coreと言う名前でRead Only,Extended modeを選択しました。
2. CPUメインのverilogコードがプロジェクトトップに来るようにします。
なお、コード中の8bitカウンタCountrは今回のブログ記事のためにクロックを分周するのに使っています。必要ない場合は取り除きます。
(コードはブログ最後に記述しました)
3.Lチカ
マシン語でLチカプログラムを書きUFMに保存します。今回の設定ではリードオンリーで実装されているのでQuartus II上でUFMに流し込むバイナリを作ります。アドレスは16bitモードであることに注意してください。
まず、フローを考えます。
(i) スタックに255を格納する
(ii) GPIOに0xFFを出力する(LED全点灯)
(iii) スタックから1減算する
(iv) スタックはゼロ?ゼロでなかったら引数に書かれている(ここでは(iii)の命令が書かれている番地) に戻る。ゼロだったら スタック - 引数の値をスタックに格納(つまり、引数をマイナス値にしたものが格納される)し次の命令へ
(v) スタックに255を格納する
(vi) GPIOに0x00を出力する(LED全消灯)
(vii) スタックから1減算する
(viii) スタックはゼロ?ゼロでなかったら引数に書かれている(ここでは(vii)の命令が書かれている番地) に戻る。ゼロだったら スタック - 引数の値をスタックに格納(つまり、引数をマイナス値にしたものが格納される)し次の命令へ
(ix) スタックはゼロ?ゼロでなかったら引数に書かれている(ここではゼロ番地) に戻る。ゼロだったら→(ここでスタック=ゼロはあり得ない)
これで、8bitの出力全てにLEDが付いていたら、一定周期ごとに全点灯、前消灯を繰り返す事が出来ます。
上記をマシン語にします。16進数表記、ビッグエンディアンです。
(i) 8110 00FF
(ii) 8510 00FF
(iii) 8310 0001
(iv) 0F04
(v) 8110 00FF
(vi) 8510 0000
(vii) 8310 0001
(viii) 0F0B
(ix) 0F00
とまぁこんな感じで記述していきます。LED全点灯/消灯だとつまらないので、もう少し点灯パターンを増やしたのが下記の画面です。
これを実際に動作させてみました。MAX MMKボード上のLEDがちょうど8個あるので使ってみました。もちろんMAX Vでも動作します。(5M160から動作可能)
Minimal CPU on MAX II CPLD - YouTube
追記:せっかくLEDが8個もあるから、POVで「ウンコ」とか表示した方がおもしろかったかな。。。
Verilogコード全文
module ufm_module_top( input clk, input reset, input key, output[7:0] LED ); wire int_osc; reg ufm_ssel; wire ufm_sclk; wire ufm_mosi; wire ufm_miso; reg ufm_miso_reg; wire fifo_sys_clk; reg wait_clock; // STOP SCLK flag reg [7:0] ufm_com_select; // command selector // select= 8'b1xxx_xxxx -> command, select=8'b0xxx_xxxx -> data reg [7:0] ufm_bit_count; // clock counter reg [7:0] user_stack_data; // reg for stack data reg [7:0] user_reg_data; // general purpose reg reg[23:0] ufm_fifo; // 24bit UFM FIFO parameter GO_IF_NON_ZERO_ELS_SUB = 7'h0F; parameter GET_STREAM_AND_STACK = 7'h01; parameter PIPE_EXTERNAL_TO_FIFO = 7'h02; parameter GET_STREAM_AND_SUBST = 7'h03; parameter PIPE_SEEK_BITS = 7'h04; parameter GET_STREAM_PUSH_OUT = 7'h05; // "countr" can be remove if you wish. reg [6:0] countr; always@(posedge int_osc)begin countr <= countr + 1; end // fifo_sys_clk: UFM's clock, upto 9.7-MHz assign fifo_sys_clk = countr[6];//int_osc; always@(posedge ufm_sclk, negedge reset)begin if(~reset)begin ufm_miso_reg <= 1'b0; end else if(~ufm_ssel)begin ufm_miso_reg <= ufm_miso; end else begin ufm_miso_reg <= 1'b0; end end assign ufm_sclk = (wait_clock)? ufm_sclk: fifo_sys_clk; always@(negedge fifo_sys_clk, negedge reset)begin if(~reset)begin wait_clock <= 1'b0; ufm_ssel <= 1'b1; ufm_bit_count <= 8'b0; user_stack_data <= 8'b0; ufm_com_select <= {1'b0, GO_IF_NON_ZERO_ELS_SUB}; ufm_fifo <= 24'b0;//TODO: insert UFM Read command end else if(ufm_bit_count == 0)begin // Command mode or Data mode case(ufm_com_select[7]) 1'b1: case(ufm_com_select[6:0]) // latch the commands GET_STREAM_AND_STACK: begin wait_clock <= 1'b1; // Stop SCLK ufm_ssel <= 1'b0; user_stack_data <= ufm_fifo[15:8]; ufm_com_select <= {1'b1,PIPE_SEEK_BITS}; ufm_bit_count <= 8'd16;//8'd24; end GET_STREAM_PUSH_OUT: begin wait_clock <= 1'b1; // Stop SCLK ufm_ssel <= 1'b0; user_reg_data <= ufm_fifo[15:8]; ufm_com_select <= {1'b1,PIPE_SEEK_BITS}; ufm_bit_count <= 8'd16;//8'd24; end GET_STREAM_AND_SUBST: begin wait_clock <= 1'b1; // Stop SCLK ufm_ssel <= 1'b0; user_stack_data <= user_stack_data - ufm_fifo[15:8]; ufm_com_select <= {1'b1,PIPE_SEEK_BITS}; ufm_bit_count <= 8'd16;//8'd24; end default: begin wait_clock <= 1'b1; // Stop SCLK ufm_ssel <= (ufm_fifo[23]==0 && user_stack_data != 8'h00)? 1'b1 : 1'b0; user_stack_data <= user_stack_data; ufm_com_select <= ufm_fifo[23:16]; // if (ufm_fifo[23]==1 && bit_count == 0) this system freezes. (this can be used this for test) ufm_bit_count <= (ufm_fifo[23] == 1'b0)?8'b0 : ufm_fifo[15:8]; end endcase 1'b0: begin // UFM command data // send command to UFM if(ufm_ssel == 1'b1)begin // if(user_stack_data == 0)jump to address else skip wait_clock <= 1'b0; ufm_ssel <= 1'b0; ufm_bit_count <= 8'd48;//8'd24; user_stack_data <= user_stack_data; ufm_com_select <= {1'b1,GO_IF_NON_ZERO_ELS_SUB}; // change Command mode ufm_fifo <= {16'h0300,ufm_fifo[15:8]};//TODO: insert UFM Read command + address end else begin wait_clock <= 1'b1; // Stop SCLK ufm_ssel <= 1'b0; ufm_bit_count <= 8'd16;//8'd24; user_stack_data <= user_stack_data - ufm_fifo[15:8]; ufm_com_select <= {1'b1,PIPE_SEEK_BITS}; ufm_fifo <= ufm_fifo; end end endcase end else begin wait_clock <= 1'b0; ufm_ssel <=1'b0; ufm_fifo <= {ufm_fifo[22:0], ufm_miso_reg}; ufm_bit_count <= ufm_bit_count - 8'b1; end end assign LED = /*(~key)? ~user_stack_data :*/ ~user_reg_data;//~ufm_fifo[23:16]; assign ufm_mosi = (ufm_com_select[6:0]==GO_IF_NON_ZERO_ELS_SUB)? ufm_fifo[23] : 1'b0; // assign other_pin =(ufm_com_select == GET_STREAM_AND_STACK)? ufm_fifo[8]: other_pin; ufm_core u0( .ncs (ufm_ssel), .sck (ufm_sclk), .si (ufm_mosi), .osc (int_osc), .so (ufm_miso) ); /* input ncs; input sck; input si; output osc; output so; */ endmodule
◆参考
めくるめくギリギリCPUの世界 - .mjtの日記復帰計画
http://www.altera.co.jp/literature/hb/max2/max2_mii51010_j.pdf
http://www.altera.co.jp/literature/hb/max-v/mv51007_j.pdf