読者です 読者をやめる 読者になる 読者になる

Lynx-EyEDの電音鍵盤 新館

広帯域制御屋の駄文とか

アルテラCPLDで150LE未満のワンチップCPUを作ったお話

FPGA/CPLD

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


f:id:Lynx-EyED:20140711222045j:plain
命令から推測がつくかもしれませんが、シリアルデータを右から左に流したり、条件によって複数あるデータ出力先を切り替えたりする「少し気の利いた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を選択しました。
f:id:Lynx-EyED:20140711224204p:plain
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全点灯/消灯だとつまらないので、もう少し点灯パターンを増やしたのが下記の画面です。
f:id:Lynx-EyED:20140711231347p:plain

これを実際に動作させてみました。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