VerilogでCPU設計入門


トラ技の付録基板(2006年4月号)で、CPUを作ってみましょう。ところで、このデバイスALTERA MAXU、EPM240T100C5は、240セルしかないので、正攻法では、どうがんばっても載りません。フラッシュも載っているのですが、これをROMにしてしまうと、かなり遅くなるし、使い方が難しいです。このセル数は、常識的には、数個のカウンタで埋まってしまいます。そこで、次のような方針で設計しました。

という方針で設計しました。フラッシュを使用していませんので、トラ技デバイスでは、16ワード位しか載りません。それでも、例えば、次のようなパターンを発生させることができます。(また、ROM容量が許せば、64KWORDまで拡張可能なアーキテクチャになっています。)





また、ビジュアルに内部の状態をブレークポイント付で確認することが出来ます。(例によってGUIは、C#で作成しています。)
これにより、容易にCPUの中身の動きを知ることができます。




MAX2にレイアウト後の遅延シミュレーションでも動作確認しています。(ヒゲは、遅延が一定していないためです。)




<CPUとは、順序機械>
一番単純な順序機械といえば、カウンタです。カウンタは、順番を生成しますので、それにROMをつければ、コントロール付きの順序機械になります。
下図で、カウンタをPC=プログラムカウンタと言うと格好よくなります。さて設計したCPUは、32ビット幅があるので、ROMの幅は32ビットになります。



そのままでは、なにかを判断して処理を変えるということができないので、PCをなんらかの形で制御する必要があります。また、その為には、一時的な記憶素子が必要で
す。記憶素子をレジスタの集まりとしてまとめたものをレジスタファイルと言います。上のレジスタファイルは、同時に読み書きできるポートが3つあります。ライトポートが一つ、リードポートが二つです。また、レジスタ数は、16ビットが4個です。

一番目のゼロレジスタは、RISCでは、定番です。最後のポートレジスタは、このCPUだけの特殊仕様です。(リソース削減のため)

名称 機能
ゼロ WriteしてもReadしても0、なので実体は、ただのGND
R0 汎用レジスタ
R1 汎用レジスタ
ポート 特殊レジスタ、そのまま、外部ポート出力になる


HDLソースで見てもらったほうが分かりやすいかもしれません。

module register_file(input clock,write,reset,
                                         input [1:0] A_address,
                                         input [1:0] B_address,
                                         input [1:0] write_address,
                                         input [15:0] alu_data,port_in_data,
                                         output [15:0] out_dataA, out_dataB,port_out_data);

   
        reg [15:0] port_reg,r0,r1;
        
        
        always @(posedge clock,posedge reset) begin
                if (reset) begin
                                port_reg<=0;
                                r0<=0;
                                r1<=0;
                        end else if (write) begin
                                case (write_address)
                                        `PORT: port_reg<=alu_data;
                                        `R0: r0<=alu_data;
                                        `R1: r1<=alu_data;
                                        //default r0<=alu_data;
                                endcase
                end
        end

        assign out_dataA=A_address==`PORT ? port_in_data :
                                         A_address==`R0 ? r0 :
                                         A_address==`R1 ?  r1 : 0;

                                        
        assign out_dataB=B_address==`PORT ? port_reg :
                                         B_address==`R0 ? r0 :
                                         B_address==`R1 ? r1 : 0;


        assign port_out_data=port_reg;

endmodule


さて、レジスタファイルの後段は、ALUです。本格的なALUを実装しています。

module alu (input [3:0] command,
                    input [2:0] jump_cond,
                    input [15:0] portA,portB,
                   output reg [15:0] alu_out);

        always @* begin
                case (command) 
                        `ADD_com :  alu_out=portA+portB;
                        `SUB_com :  alu_out=portA-portB;
                        `SHIFT_L_com: alu_out=portB >>1;
                        `SHIFT_R_com: alu_out=portB <<1;
                        `JMP_com :   
                                        case (jump_cond) 
                                                `Eq : alu_out=portA==portB;
                                                `Not_Eq:alu_out=portA !=portB;
                                                `Always :  alu_out=1;
                                                `Always_Not :alu_out=0;
                                                `LT :  if (portA < portB) alu_out=1;
                                                         else                    alu_out=0;
                                                `GT : if (portA > portB) alu_out=1;
                                                         else                   alu_out=0;
                                                default: alu_out=0;
                                        endcase
                        default:  alu_out=portA+portB;


                endcase
     end


endmodule

後は、PCのコントロールです。これは、後述するパターンジェネレータとしての機能が入っているために、ちょっとだけ複雑ですが、たいしたことはありません。
斜め字を無視して読むと、JUMPコマンドのとき、ALU出力のビット0が1だったら、immediate data の番地をロード、それ以外は、pc<=pc+1 ;つまり、ただのカウンタということです。
このimmediate_data は、32ビット幅のROMの下位16ビットに割り当てています。ですから、ROMの容量さえ許せば、64KWORDまで、どこにでも飛ばすことができます。

//control of PC
        always @(posedge clock, posedge reset) begin
                if (reset) pc<=0;
                else if (command==`JMP_com && alu_data[0]==1'b1) pc<=immediate_data;//Jump if jump condition is met.
              else if (command==`SYNC_com) begin//Sync Start Condition
                        if (sinc_pos_edge_detected) pc<=pc+1;//Exectute Next Address!
                      else pc<=pc;//Hold until sync pulse is detected.
              end else if (loop_flag ) begin//Loop Condition
                        if (loop_counter==0) pc<=pc+1;//Execute Next Address!
                        else pc<=pc;//Hold until loop counter becomes zero.
                end else pc<=pc+1;//else increment PC
        end

基本的には、以上の要素をつなげれば、CPUになります。すごく簡単ですね。勿論1CLKで、1INSTRUCTION処理でMUX2デバイスでも30MHz以上で動作可能です。

さて、ROM出力は、32ビットの内16ビットを飛び先に使いました。
残りのフィールドを含む割付は次のようになっています。

ビット位置 ビット幅 名称 内容
31:28 4 command コマンド
27:26 2 A_address レジスタファイルAポートのアドレス
25:24 2 B_address レジスタファイルBポートのアドレス
23:22 2 write_address レジスタファイルライトポートのアドレス
21:19 3 jump_cond ジャンプ条件
18:16 3 reserved
16:16 1 loop_flag ループ処理するかどうかのフラグ、1でループ処理
15:0 16 immediate 即値または、飛び先

命令の内容を解凍するデコードという処理はありません。ですので、32ビットという広いビット幅が要るのですが、その分簡単に設計できます。
このようにROMの出力を制御線に使うやり方をマイクロコード方式と呼びます。このCPUは、マイクロコード自体が、機械語命令になっています。

このCPUがNativeで、もっている命令は、次です。

`define ADD_com        4'b0000
`define LOAD_IM_com    4'b0001
`define SUB_com        4'b0010
`define SHIFT_L_com    4'b0011
`define SHIFT_R_com    4'b0100
`define JMP_com        4'b0101
`define SYNC_com       4'b0110 


よくアセンブラで見かけるMOV命令がありません。これは、RISCの定番である、ゼロレジスタとの加算で実現しています。
ソースでは、マクロ定義で、ビットフィールドを32ビットのROMコードに変換しています。

`define MOV( r0, r1)    {Add,r0,ZR, r1,Eq,Reserved,Zero_Im}

上のコードの意味は、
   r1 <=r0 +0
になります。
同様に、マクロで命令を定義することができます。アセンブラでよく見かけるNOPは、どう実装したらよいでしょうか?実は、ALL 0は、NOPにしています。
その意味は、
  ゼロレジスタ<=ゼロレジスタ+ゼロレジスタ
です。

parameter [1:0]  Port=`PORT,//IO PORT
                             R0= `R0,
                             R1= `R1,
                             ZR= `ZR;//ZERO REGISTER

parameter [15:0] Zero_Im=16'h0000;

parameter [3:0]  Load_Im=`LOAD_IM_com,
                            Add=`ADD_com,
                            Sub=`SUB_com,
                            Shift_L=`SHIFT_L_com,
                            Shift_R=`SHIFT_R_com,
                            Jmp=`JMP_com,
                            Sync=`SYNC_com;

parameter [2:0] Eq=`Eq,
                            Not_Eq=`Not_Eq,
                            Always=`Always,
                            Always_Not=`Always_Not,
                           LT=`LT,
                           GT=`GT,
                           LTE=`LTE,
                           GTE=`GTE;    
                            
parameter [2:0] Reserved=3'b000,
                           Loop_Enabled=3'b001;

`define MOV( r0, r1)    {Add,r0,ZR, r1,Eq,Reserved,Zero_Im}
`define CLR( r0)                { Add,ZR,ZR,r0,Eq,Reserved,Zero_Im}
`define JUMP(num)    {Jmp,ZR,ZR,ZR,Always,Reserved,16'd num}
`define LOAD_IM(r0,num) {Load_Im,Port,ZR,r0,Eq,Reserved,16'd num}
`define ADD(r0,r1,r2) { Add,r0,r1,r2,Eq,Reserved,Zero_Im}
`define SUB(r0,r1,r2) { Sub,r0,r1,r2,Eq,Reserved,Zero_Im}
`define JUMP_IF_LESS_THAN(r0,r1,num) {Jmp,r0,r1,ZR,LT,Reserved,16'd num} 
`define JUMP_IF_GREATER_THAN(r0,r1,num) {Jmp,r0,r1,ZR,GT,Reserved,16'd num} 
`define JUMP_IF_EQ(r0,r1,num) {Jmp,r0,r1,ZR,Eq,Reserved,16'd num} 


`define SHIFT_L(r0,r1) {Shift_L,ZR,r0,r1,Eq,Reserved,Zero_Im}
`define SHIFT_R(r0,r1) {Shift_R,ZR,r0,r1,Eq,Reserved,Zero_Im}
`define SYNC            {Sync,ZR,ZR,ZR,Eq,Reserved,Zero_Im}


ここまで、読んでくれてありがとうございます。そうです。アセンブラはないのです。
ROMコードは、例えば、上で定義したマクロを使って次のように書きます。
これぞ、究極の「コンピュータの原理を学ぶ」 かもしれません。 こういう命令が欲しいというのがあったら、上でマクロ定義してしまえば、よい訳です。
マクロ展開は、Veritakオプションで、preout.v として見れますので、思い通りに展開されているかチェックしてみてもよいでしょう。

function [31:0]romdata;
        input [4:0] address;
                case (address)                  //Commad  A_addr, B_addr, Write_addr, Jmp_cond,Reserved, IM
                        0:  romdata =`LOAD_IM(R0,1);
                        1:  romdata =`CLR(Port);           
                        2:  romdata =`Add_with_loop(Port,R0,100);
                        3:  romdata =`CLR(Port);
                        4:  romdata =`LOAD_IM(R0,2);               
                        5 : romdata =`Add_with_loop(Port,R0,50);
                        6:  romdata =`CLR(Port);   
                        7:  romdata =`LOAD_IM(R0,4);               
                        8 : romdata =`Add_with_loop(Port,R0,25);
                        9 : romdata =`LOAD_IM(Port,75);
                        10: romdata =`Add_with_loop(Port,ZR,25);
                        11: romdata =`LOAD_IM(Port,51);
                        12: romdata =`SYNC; 
                        13: romdata =`LOAD_IM(R1,44000);
                        14: romdata =`JUMP_IF_LESS_THAN(R1,Port,1);
                        15 : romdata =`SHIFT_R(Port,Port);
                        16: romdata =`JUMP(14);
                    //    3 : romdata =`LOAD_IM(R0,100);
                   //     4 : romdata =`LOAD_IM(R1, 62444);
                   //     5 : romdata =`ADD(R0,R1,Port);
                   //     6 : romdata =`LOAD_IM(Port,10000);
                   //     7 : romdata =`ADD(R0,R1,R0);
                   //     8 : romdata =`SUB(R0,R1,R0);
                  //      9 : romdata =`ADD(Port,R0,R0);
                  //      10 : romdata =`JUMP_IF_LESS_THAN(R0,R1,0);
                   //     11 : romdata =`JUMP_IF_EQ(R0,R1,3);
                        17 : romdata =0;
                        18 : romdata =0;
                        19 : romdata =0;
                        20 : romdata =0;
                             default    : romdata=0;
                endcase
 endfunction


<Confiugurable CPU>
このCPUで、設計していて面白いことに気づきました。それは、このCPUは、ROMワードいくつまでMAX2に載るとは、言えないことです。実際にROMで使う命令によります。ROMをメモリとしてではなく、LUTで構成しているので、実装する命令で影響を受けます。つまり、ALUでは、いろいろ定義してあるのですが、論理合成で、使わないロジックは、容赦なくRudctionされます。マジックの種明かしでした。

<パイプライン化ということ>
このCPUは、長いパスを持っています。多分、PCに始まりROM ->Register FILE −>ALU−> PC コントロールロジックー>PCに来るパスが最長だろうと思います。
最長のパスをクリティカルパスと言います。パイプライン化した普通のCPUでは、このパスは何段かのFF間の組み合わせ回路に分割されることになります。 


CPUの応用

<パターンGenerator>
ループフラグをONにすると、ループカウンタが0になるまで、その場所に留まり、命令を繰り返します。これは、Shift演算にも活用することができます。

//loop command
`define Shift_Right_with_Loop(r0,num)  {Shift_R,ZR,r0,r0,Eq,Loop_Enabled,16'd num }
`define Add_with_loop(r0,r1,num) { Add,r1,r0,r0,Eq,Loop_Enabled,16'd num}  //Use Bport for port_reg
ループカウンタは、即値と兼用ですので、全命令で適用できる訳では、ありません。しかし、任意時間幅のパターンを作る場合には、この性質は、重宝します。
また、SYNC命令で、POS EDGEで命令スタートということもできます。BINARYのシリアルパターンを作る場合は、16ビットごとに、CPUへCLCOCKを与えれば、シリアルパターン生成器にもなります。

<VPI>
今回も vpi_dll5.dll に追加しました。$SendMessageと、$MessageBox を追加しました。その名の通りですが、詳細はソースを参照ください。
今回追加したソースは次です。

登録部

//Jul.11.2006
      tf_data.type      =vpiSysFunc;//
      tf_data.sysfunctype       =vpiIntFunc;//
      tf_data.tfname    = "$SendMessage";
      tf_data.user_data = "$SendMessage";
      tf_data.calltf    = sys_SendMessage;
      tf_data.compiletf = 0;
      tf_data.sizetf    = sys_systems_size_tf;//func
          vpi_register_systf(&tf_data);

      tf_data.type      =vpiSysFunc;//
      tf_data.sysfunctype       =vpiIntFunc;//
      tf_data.tfname    = "$MessageBox";
      tf_data.user_data = "$MessageBox";
      tf_data.calltf    = sys_MessageBox;
      tf_data.compiletf = 0;
      tf_data.sizetf    = sys_systems_size_tf;//func
          vpi_register_systf(&tf_data);


実装部

static int sys_SendMessage(char*  name)
{
 
 vpiHandle systfref, argsiter, argh;
  
  s_vpi_value value;

  systfref = vpi_handle(vpiSysTfCall, NULL); /* get system function that invoked C routine */
  argsiter = vpi_iterate(vpiArgument, systfref);/* get iterator (list) of passed arguments */
                 
  unsigned message_parameter[4];        
  for (unsigned i=0;i<4;i++){                   
                argh = vpi_scan(argsiter);/* get the one argument - add loop for more args */
                if(!argh){
                                vpi_printf("$VPI sys_SendMessage: missing parameter. \n");
//                               vpi_sim_control(vpiFinish, 1);
                                 
                                  return 0;
                                
                        }       
                        
                value.format = vpiIntVal;
                vpi_get_value(argh, &value);
                message_parameter[i]=value.value.integer;
        }       
                
                
 unsigned result=       ::SendMessage(reinterpret_cast<HWND>(message_parameter[0]),//handle
                                  message_parameter[1],//WM_COMMAND
                                  message_parameter[2],//WPARAM
                                  message_parameter[3]);//LPARAM
                                        
                
                if(argh) vpi_free_object(argsiter);
                
                 value.value.integer =result;//;
                 value.format = vpiIntVal;/* return the result */

                 vpi_put_value(systfref, &value, NULL, vpiNoDelay);
                 
                return(0);
         
 }
static int sys_MessageBox(char*  name)
{
 
 vpiHandle systfref, argsiter, argh;
  
  s_vpi_value value;

  systfref = vpi_handle(vpiSysTfCall, NULL); /* get system function that invoked C routine */
  argsiter = vpi_iterate(vpiArgument, systfref);/* get iterator (list) of passed arguments */
                 
  
  unsigned message_id=0;
  string str_message;
  string str_caption;
  for (unsigned i=0;i<2;i++){                   
                argh = vpi_scan(argsiter);/* get the one argument - add loop for more args */
                if(!argh){
                                vpi_printf("$VPI sys_MessageBox: missing parameter. \n");
//                               vpi_sim_control(vpiFinish, 1);
                                 
                                  return 0;
                                
                        }       
                        
                value.format = vpiStringVal;
                vpi_get_value(argh, &value);
                if (i==0)               str_message=value.value.str;
                else if (i==1)  str_caption=value.value.str;

        }       
    argh = vpi_scan(argsiter);
        if (argh) {
                value.format = vpiIntVal;
                vpi_get_value(argh, &value);
                message_id=value.value.integer;
        }
        unsigned result=MessageBox(0,str_message.c_str(),
                                str_caption.c_str(),message_id);

                
                
 
                
                if(argh) vpi_free_object(argsiter);
                
                 value.value.integer =result;//;
                 value.format = vpiIntVal;/* return the result */

                 vpi_put_value(systfref, &value, NULL, vpiNoDelay);
                 
                return(0);
         
 }


テストベンチのソースです。
Verilog HDLソースから、MessageBoxを開くことができます。

`timescale 1ns/1ps
`ifdef GATE_SIM
`define CYCLE 26.92
`else
`define CYCLE 30
`endif
module test;
        
        reg clock=0;
        reg reset=0;
        reg [15:0] port_in_data=0;
        reg sync=0;

        wire [15:0] port_out_data;

        always #(`CYCLE/2) clock=~clock;
        
        initial begin
                reset=1;
                #105;
                reset=0;
                #1000000;
                $finish;
        end


        cpu cpu(.clock,.reset,.sync,.port_in_data,.port_out_data);

        always @(negedge clock) begin
                if (port_out_data==51) begin
                        repeat( 100) begin
                                @(negedge clock);
                        end     
                        sync=1;
                        @(negedge clock);
                        sync=0;
                end
        end
      localparam integer MB_YESNO=4;
        localparam integer MB_YES=6;
        localparam integer MB_NO=7;
        localparam integer TOP_MOST=32'h4_0000;
`ifndef GATE_SIM
        integer handle,i;
        reg [8*20:1] str;
        reg [31:0] data;
        integer result;
        parameter integer WM_COMMAND=32'h111;
        initial begin
        
                handle=$FindWindow("MyCPU");
                if (handle) begin
                        
                end else begin :loop0
                                result=$MessageBox("ようこそ。Visual デバッガを起動しますか?","VeritakMessageBox",MB_YESNO | TOP_MOST );
                                if (result==MB_YES)  begin
                                        $shell_execute("cpu");
                                        repeat(100) begin//Retry LOOP; Repeat until MyCPU is detected.
                                                $Sleep(100);//100ms wait
                                                handle=$FindWindow("MyCPU");
                                                if (handle) disable loop0;
                                        end
                                end
                end
                if (handle) begin       
                        $SendMessage(handle,WM_COMMAND,256,0);//ROM コードを送出                
                        for (i=0;i<32;i=i+1) begin
                                data=cpu.rom.romdata(i);
                                $SendMessage(handle,WM_COMMAND,i+256,data);
                        end
                        $MessageBox("GUIの準備ができました。必要ならMyCPUのROMリストで、ブレークポイントを設定してください。その後にOKボタンを押してください。","VeritakMessageBox");
                end     else begin
                        //$MessageBox("MyCPUが起動できませんでした。","VeritakMessageBox");

                end
        end
        always @(negedge clock) begin
                handle=$FindWindow("MyCPU");
                if (handle) begin//現在の状態を送出
                        //regfile
                             $SendMessage(handle,WM_COMMAND,1,cpu.rfile.out_dataA);
                             $SendMessage(handle,WM_COMMAND,2,cpu.rfile.out_dataB);
                             $SendMessage(handle,WM_COMMAND,3,cpu.rfile.alu_data);
                             $SendMessage(handle,WM_COMMAND,4,cpu.rfile.r0);
                             $SendMessage(handle,WM_COMMAND,5,cpu.rfile.r1);
                             $SendMessage(handle,WM_COMMAND,6,cpu.rfile.port_reg);



                            $SendMessage(handle,WM_COMMAND,7,cpu.ALU.portA);
                            $SendMessage(handle,WM_COMMAND,8,cpu.ALU.portB);
                            $SendMessage(handle,WM_COMMAND,9,cpu.ALU.alu_out);
                             $SendMessage(handle,WM_COMMAND,10,cpu.ALU.command);
                             $SendMessage(handle,WM_COMMAND,11,cpu.ALU.jump_cond);
                             $SendMessage(handle,WM_COMMAND,12,cpu.loop_counter);
                                //PC コマンドはラストに送る
                                result=$SendMessage(handle,WM_COMMAND,0,cpu.pc);
                                if (result==-1) begin//ブレーク要求がGUIから来たら
                                        $stop;
                                end
                        
                end


        end
`endif
        integer handle_7led;
        initial begin 
        
                                
                handle_7led=$FindWindow("7LED");//を探す        
        
                        if (!handle_7led)begin :loop1
                                result=$MessageBox("ポート出力を7セグメントLEDパネルに出力しますか?","VeritakMessageBox",MB_YESNO | TOP_MOST );
                                if (result==MB_YES) begin
                                        $shell_execute("LED7SEG");//7セグメントLEDを立ち上げる
                                        repeat(100) begin//最長100msx100=10sec待ち
                                                $Sleep(100);
                                                handle_7led=$FindWindow("7LED");//を探す
                                                if (handle_7led) disable loop1;//見つかったら脱出       
                                        end      
                                end
                        end
        end
        always @(negedge clock) begin
                handle_7led=$FindWindow("7LED");
                if (handle_7led) begin :LED//現在の状態を送出
                        reg [31:0] led_value={1'b0,decoder(port_out_data[15:12]),
                                                  1'b0,decoder(port_out_data[11:8]),
                                                  1'b0,decoder(port_out_data[7:4]),
                                                  1'b0,decoder(port_out_data[3:0])};
                             $SendMessage(handle_7led,WM_COMMAND,led_value,0);
                end
        end

        function [6:0] decoder(input  [3:0] din);//7segment LED
        case (din)
        4'b0000 : decoder = 7'b1111110;
                4'b0001 : decoder = 7'b0110000;
                4'b0010 : decoder = 7'b1101101;
        4'b0011 : decoder = 7'b1111001;
        4'b0100 : decoder = 7'b0110011;
                4'b0101 : decoder = 7'b1011011;
                4'b0110 : decoder = 7'b1011111;
                4'b0111 : decoder = 7'b1110000;
                4'b1000 : decoder = 7'b1111111;
        4'b1001 : decoder = 7'b1111011;
        4'b1010 : decoder = 7'b1110111;
        4'b1011 : decoder = 7'b0011111;
                4'b1100 : decoder = 7'b1001110;
                4'b1101 : decoder = 7'b0111101;
                4'b1110 : decoder = 7'b1001111;
                4'b1111 : decoder = 7'b1000111;
           endcase
        endfunction

endmodule


C#ソースです。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Collections;
using System.Runtime.InteropServices;


namespace cpu
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            command_array = new int[10000];
        }
        [DllImport("USER32.DLL")]
        public static extern int FindWindow(
            int hWnd, String lpText        );
        [DllImport("USER32.DLL")]
        public static extern void PostMessage(
            int hWnd, int com,int wp,int lp);
        int wp = 0;
        int lp = 0;
        
        int[] command_array;
        int a_port_address=0;
        int b_port_address=0;
        int write_port_address=0;
        int current_pc=0;
        uint clock = 0;

        uint regfile_R0 = 0;
        uint regfile_PORT = 0;
        uint regfile_R1 = 0;
        uint regfile_a_port=0;
        uint regfile_b_port=0;
        uint regfile_write_port=0;
        uint alu_port_a=0;
        uint alu_port_b=0;
        uint alu_out_port=0;
        uint loop_counter=0xffffffff;
        uint alu_operation = 0;
        uint alu_jump_cond = 0;

        string get_register_name(int num)
        {
            switch (num)
            {
                case (0): return "ゼロ";
                case (1): return "R0";
                case (2): return "R1";
                case (3): return "ポート";
                
            }
            return "";

        }

        private void make_code()
        {
            int command = (lp >> 28 ) & 0xf;
            int address = wp - 256;
            if (address == 0)
            {

                checkedListBox1.Items.Clear();
                clock = 0;
                command_array.Initialize();
            }
            if(address>=0) command_array[address] = lp;


            int a_port_address=(lp >>26) & 3 ;
            int b_port_address = (lp >> 24) & 3;
            int write_port_address=(lp>>22) & 3;
            int jump_cond = (lp >> 19) & 7;
            int loop_flag = (lp >> 16) & 1;
            uint immediate =(uint)( lp & 0xffff);
            string str;
            str =address.ToString("d4");
            str +=" : ";
            if (lp == 0)
            {

                str +="NOP";   
                checkedListBox1.Items.Add(str);
            }
            else if (command == 0)//Add
            {

                if (a_port_address == 0 && b_port_address == 0)//CLR
                {
                    string str_word = str+get_register_name(write_port_address);
                    str_word += "をクリア";
                     checkedListBox1.Items.Add(str_word);

                }else if (b_port_address == 0)//MOV
                {
                    string str_word = str+get_register_name(a_port_address);
                    str_word += "から";
                    str_word += get_register_name(write_port_address);
                    str_word += "へのコピー";
                    checkedListBox1.Items.Add(str_word);

                }
                else
                {//Add

                    string str_word =str+ get_register_name(write_port_address);
                    str_word += " <= ";
                    str_word += get_register_name(a_port_address);
                    str_word += " + ";
                    str_word += get_register_name(b_port_address);
                    if (loop_flag == 1) str_word += " ,ループ付";
                    checkedListBox1.Items.Add(str_word);

                }
            }
            else if (command == 1)//load IM
            {
                string str_word = str + get_register_name(write_port_address);
                str_word += "に";
                str_word += immediate.ToString("d");
                str_word += "dec , ";
                str_word += immediate.ToString("X");
                str_word += "hexをロード";
               
                checkedListBox1.Items.Add(str_word);

            }
            else if (command == 2)//SUB
            {

                string str_word = str + get_register_name(write_port_address);
                str_word += " <= ";
                str_word += get_register_name(a_port_address);
                str_word += " - ";
                str_word += get_register_name(b_port_address);

                checkedListBox1.Items.Add(str_word);
                

            }
            else if (command == 3)//
            {
                string str_word = str + get_register_name(write_port_address);
                str_word += " <= ";
                str_word += get_register_name(b_port_address);
                str_word += " >> 1 ";
                if (loop_flag == 1) str_word += " ,ループ付";


                checkedListBox1.Items.Add(str_word);

            }
            else if (command == 4)//
            {
                string str_word = str + get_register_name(write_port_address);
                str_word += " <= ";
                str_word += get_register_name(b_port_address);
                str_word += " << 1 ";
                if (loop_flag == 1) str_word += " ,ループ付";
                checkedListBox1.Items.Add(str_word);

            }
            else if (command == 5)//JUMP
            {
                string str_word = str  ;
                if (jump_cond==2) {//always
                                 
                    str_word += immediate.ToString("d");
                    str_word += "番地にジャンプ";
                    checkedListBox1.Items.Add(str_word);
                }else if (jump_cond==3) {//not always
                    str +="NOP";
                     checkedListBox1.Items.Add(str);
                }else {
                     str_word += get_register_name(a_port_address);
                     if (jump_cond == 0) str_word += " = ";
                     else if (jump_cond == 1) str_word += " != ";
                     else if (jump_cond == 4) str_word += " < ";
                     else if (jump_cond == 5) str_word += " > ";
                     else if (jump_cond == 6) str_word += " <= ";
                     else if (jump_cond == 7) str_word += " >= ";
                     str_word += get_register_name(b_port_address);
                     str_word += "なら";
                     str_word += immediate.ToString("d");
                     str_word += "番地にジャンプ";
                     checkedListBox1.Items.Add(str_word);
                }
                
                

            }
            else if (command == 6)//Sync
            {
                string str_word = str ;
                str_word += "Sync待ち";
               
                checkedListBox1.Items.Add(str_word);

            }
        }
        private void draw_values()
        {
            int address = wp - 256;
            if (address >= 0) make_code();
            else
            {
                switch (wp) {
                    case (0) :current_pc=lp;break;
                    case (1): regfile_a_port =(uint) lp; break;
                    case (2): regfile_b_port =(uint) lp; break;
                    case (3): regfile_write_port =(uint) lp; break;
                    case (7): alu_port_a = (uint)lp; break;
                    case (8): alu_port_b = (uint)lp; break;
                    case (9): alu_out_port = (uint)lp; break;
                    case (4): regfile_R0 = (uint)lp; break;
                    case (5): regfile_R1 = (uint)lp; break;
                    case (6): regfile_PORT = (uint)lp; break;
                    case (10): alu_operation = (uint)lp; break;
                    case (11): alu_jump_cond = (uint)lp; break;
                    case (12): loop_counter = (uint)lp; break;
                  
                }

            }
        }
        private void draw_alu(Graphics g,int x, int y)
        {
            Pen p = new Pen(Color.Black, 2);
             Point[] ps = {new Point(10+x, 30+y), new Point(220+x, 30+y), 
                    new Point(200+x, 130+y), new Point(30+x, 130+y)};
            //折れ線を引く
            g.DrawPolygon(p, ps);

            string a_port_str = alu_port_a.ToString("X4");
            string b_port_str = alu_port_b.ToString("X4");
            string write_port_str = alu_out_port.ToString("X4");

            string alu_op_str = "オペレーション";
            switch (alu_operation)
            {
                case (0): alu_op_str += "Add"; break;
                case (2): alu_op_str += "Sub"; break;
                case (3): alu_op_str += " >> "; break;
                case (4): alu_op_str += " << "; break;
                case (5): alu_op_str += " Jump";
                    switch (alu_jump_cond)
                    {
                        case (0): alu_op_str += "if Eq"; break;
                        case (1): alu_op_str += "if Not Eq"; break;
                        case (2): alu_op_str += "Always"; break;
                        case (3): alu_op_str += "Always Not"; break;
                        case (4): alu_op_str += "if < "; break;
                        case (5): alu_op_str += "if > "; break;
                        case (6): alu_op_str += "if <= "; break;
                        case (7): alu_op_str += "if >= "; break;
                    }
                    break;


            }            
            Font objFont1 = new Font("MS Pゴシック", 11);
            g.DrawString(a_port_str, objFont1, Brushes.Blue, 50+x, 30+y);
            g.DrawString(b_port_str, objFont1, Brushes.Blue, 150+x, 30+y);
            g.DrawString(write_port_str, objFont1, Brushes.Blue, 100+x, 110+y);
            g.DrawString(alu_op_str, objFont1, Brushes.Blue, 25 + x, 70 + y);



        }
        private void draw_regfile(Graphics g, int x, int y)
        {
            Pen p = new Pen(Color.Black, 2);
            Point[] ps = {new Point(10+x, 30+y), new Point(220+x, 30+y), 
                    new Point(220+x, 130+y), new Point(10+x, 130+y)};
            //折れ線を引く
            g.DrawPolygon(p, ps);

            string a_port_str = regfile_a_port.ToString("X4");
            string b_port_str = regfile_b_port.ToString("X4");
            string write_port_str = regfile_write_port.ToString("X4");

            string regfile_zero_str = "・ゼロレジ =0";
            string regfile_R0_str =   "・R0       ="+regfile_R0.ToString("X4");
            string regfile_R1_str =   "・R1       ="+regfile_R1.ToString("X4");
            string regfile_Port_str = "・ポートレジ="+regfile_PORT.ToString("X4");

            Font objFont1 = new Font("MS Pゴシック", 11);
            g.DrawString(a_port_str, objFont1, Brushes.Blue, 70, 140);
            g.DrawString(b_port_str, objFont1, Brushes.Blue, 180, 140);
            g.DrawString(write_port_str, objFont1, Brushes.Blue, 130, 60);




            g.DrawString(regfile_zero_str, objFont1, Brushes.Green, 50, 75);
            g.DrawString(regfile_R0_str, objFont1, Brushes.Green, 50, 90);
            g.DrawString(regfile_R1_str, objFont1, Brushes.Green, 50, 105);
            g.DrawString(regfile_Port_str, objFont1, Brushes.Green, 50, 120);

        }

        private void draw_pc(Graphics g, int x, int y)
        {
            Pen p = new Pen(Color.Black, 2);
            Point[] ps = {new Point(10+x, 10+y), new Point(140+x, 10+y), 
                    new Point(140+x, 40+y), new Point(10+x, 40+y)};
            //折れ線を引く
            g.DrawPolygon(p, ps);
            Font objFont1 = new Font("MS Pゴシック", 12);
            string str;
            str = "PC=";
            str += current_pc.ToString("d2");

            g.DrawString(str, objFont1, Brushes.Blue, 10, 20);
            string lc_str="LC=";
            lc_str += loop_counter.ToString("x4");
            Font objFont2 = new Font("MS Pゴシック", 10);
            g.DrawString(lc_str, objFont2, Brushes.Blue, 80, 45);

            if (checkedListBox1.Items.Count > current_pc)
            {
                checkedListBox1.SetSelected(current_pc , true);
            }

            string clock_str = "Total "+clock.ToString("x4") +"Clocks";
            g.DrawString(clock_str, objFont1, Brushes.Brown, 145, 5);
        }


        private void paint(object sender, PaintEventArgs e)
        {
            Graphics g = e.Graphics;
            Pen p = new Pen(Color.Black, 2);

            
            draw_regfile(g, 30, 30);
            g.DrawLine(p, 90, 160, 90, 230);
            g.DrawLine(p, 200, 160, 200, 230); 
           draw_alu(g,30,200);

           g.DrawLine(p, 150, 330, 150, 360);
           g.DrawLine(p, 150, 360, 290, 360);
           g.DrawLine(p, 290, 360, 290, 30);
           g.DrawLine(p, 290, 30, 150, 30);
           g.DrawLine(p, 150, 30, 150, 60);


        }
        protected override void WndProc(ref Message m)
        {
            const int WM_CLOSE = 0x0010;
            const int WM_ENDSESSION = 0x16;
            const int WM_SYSCOMMAND = 0x112;
            const int SC_CLOSE = 0xF060;
            const int WM_COMMAND = 0x111;
            const int WM_SIZE = 0x0005;
            switch (m.Msg)
            {
                case WM_ENDSESSION:
                    //OSのシャットダウンで閉じられようとしている
                    //Console.WriteLine("WM_ENDSESSION");
                    break;
                case WM_SYSCOMMAND:
                    //if (m.WParam.ToInt32() == SC_CLOSE)
                    //Xボタン、コントロールメニューの「閉じる」、
                    //コントロールボックスのダブルクリック、
                    //Atl+F4などにより閉じられようとしている
                    // Console.WriteLine("SC_CLOSE");
                    break;
                case WM_CLOSE:
                    //Application.Exit以外で閉じられようとしている
                    //Console.WriteLine("WM_CLOSE");
                    break;
                case WM_SIZE:
                    pictureBox1.Refresh();
                    pictureBox2.Refresh();
                    break;

                case WM_COMMAND:
                    wp = (int)m.WParam;
                    lp = (int)m.LParam;
                    clock++;
                    draw_values();
                    pictureBox1.Refresh();//再ドロー 
                    pictureBox2.Refresh();
                    
                    break;
            }

            base.WndProc(ref m);//baseの後に書き換えないとだめ
            if (m.Msg==WM_COMMAND && wp == 0)//この後$stopするので、このコマンドは最後に送ること
            {
                if (checkedListBox1.Items.Count > current_pc)
                {
                    if (checkedListBox1.GetItemChecked(current_pc))//BreakpointチェックがOnなら
                    {

                        m.Result = (IntPtr)(-1);//Mark As breakpoint

                    }
                }
            }
        }

        private void paint_picture_box2(object sender, PaintEventArgs e)
        {
            Graphics g = e.Graphics;
            Pen p = new Pen(Color.Black, 2);

            draw_pc(g, 0, 0);
        }

        private void StepButton(object sender, EventArgs e)
        {
            const int WM_COMMAND = 0x111;

            int veritakwin_handle=FindWindow(0,"VeritakWin");
                        if (veritakwin_handle !=0) {//VeritakWinが存在するなら 
            
                                PostMessage(veritakwin_handle,WM_COMMAND,133,0);//GO Command をVeritakWinに送る
                                        
                        }
        }

        private void checkedListBox1_double_click(object sender, MouseEventArgs e)
        {
            
        }

        private void Stop_Button_clicked(object sender, EventArgs e)
        {
            const int WM_COMMAND = 0x111;

            int veritakwin_handle = FindWindow(0, "VeritakWin");
            if (veritakwin_handle != 0)
            {//VeritakWinが存在するなら 

                PostMessage(veritakwin_handle, WM_COMMAND, 134, 0);//GO Command をVeritakWinに送る

            }
        }

        private void Reload_and_go_Button_clicked(object sender, EventArgs e)
        {
            const int WM_COMMAND = 0x111;

            int veritakwin_handle = FindWindow(0, "VeritakWin");
            if (veritakwin_handle != 0)
            {//VeritakWinが存在するなら 

                PostMessage(veritakwin_handle, WM_COMMAND, 153, 0);//GO Command をVeritakWinに送る

            }
        }
    }
}

CPU の拡張

さて、一応上の記述で動きますが、やはり自分でいじってみないと良く分からないかもしれません。是非、上を雛形として、


という変更を行ってみてください。以下は、いくつかの拡張案です。

もちろん、MAX2で動かすとなると無理かもしれませんが、シミュレーションを行う分には、自由です。

それでは、実際に、変更を加える例をやってみましょう。

仕様は、次のコマンドを追加するものとします。ifdef で追加しています。
ADDITIONAL_COMMANDがdefineされているときに、Enableされます。このようにしておくと、元に戻したいときに便利です。


なんと、乗算器まで、定義してしまいました。大丈夫でしょうか?
次は、ALUに演算記述を追加します。乗算は、8x8=16ビット符号なしになります。


それから、parameter 定義も追加しておきます。




最後にROM コードです。
入力の値を100倍して、出力ポートに書き出しています。



MAX2での合成結果です。ふぅ、収まりました。


41MHzで走るそうです。


遅延シミュレーションです。
クロックエッジから8ns程度遅れますが、ちゃんと100倍になって返ってきました。



もう、このマジックは、お分かりですね。ハードとしてのROMを解析するとADDや、SUB、その他ロジックは使われていないことを合成器は分かったのでしょう。
これを、純ROMとして定義してしまうと、乗算器は凄く食うので、到底載らないでしょう。

<次の拡張アイデア>
ところで、この乗算器は、JUMPが入っているために、2CYCLEかかってしまっています。ここにループ命令をもってくれば、ループ期間は、1CYCLEで処理できます。
これは、マクロ命令を定義するだけで可能です。或いは、JUMP命令と融合するというアイデアも可能でしょう。命令が終わったら常にジャンプするという具合です。
(IMMEDIATE命令を除き、このフィールドは、遊んでいます)幸い、マイクロフィールドは、未だ2ビット残っていますので、このビットを使って、ビットが立っているときだけ、そういう風にするということもできます。

或いは、このCPUを何個が並列で使うアイデアもありでしょう。割り込みなんて、1サイクルで処理できてしまいます。。
ステートマシンでは、複雑すぎる、CPUでは遅すぎるという場合に好適なハードとソフトの中間みたいなマシンです。こういうハードをマイクロシーケンサと呼んでいます。
昔のCISCマシン(68000の時代)は、マイクロシーケンサで制御されていたと思います。CPUの中のCPUそれが、マイクロシーケンサです。


アーカイブです。

<番外編>
ST2だとどうなるかやってみました。動作速度140Mz、リソース2%の消費でした。


ユーザContribution

ユーザ様から、拡張例をいただきました。





以下は、作者の方のコメントです。

ポート入出力を明示的に分けたり、命令セットや動作仕様を
自分なりにしっくり来るように変更したりしてみました。


ROMのマイクロコードだけ見ていると、アセンブラみたい(笑)

// バレルシフタ
1514: `SET(R0, 2);
1515: `SET(R1, 7);
1516: `SET(R2, 0);
1517: `BSHIFT_L(R0, R1, R0);
1518: `BSHIFT_R(R0, R1, R0);


でも、実は組み合わせ回路に合成される辺り、何だか、楽しいですね。

調子に乗って、命令とか増やしたり、
ROM出力のビット幅を増やしたりしてみましたが、
クリティカルパスが益々長くなってしまいました。 。

ソースです。

//  マイクロプログラム方式 CPU

// ネイティブコマンド
`define ADD_com         5'b00000
`define SET_com         5'b00001
`define SUB_com         5'b00010
`define SHIFT_L_com     5'b00011
`define SHIFT_R_com     5'b00100
`define JMP_com         5'b00101
`define SYNC_com        5'b00110

`define AND_com         5'b00111
`define OR_com          5'b01000
`define XOR_com         5'b01001
`define NOT_com         5'b01010

`define MUL_com         5'b01011

`define BSHIFT_L_com    5'b01100
`define BSHIFT_R_com    5'b01101


// ジャンプ条件
`define Eq              4'b0000
`define Not_Eq          4'b0001
`define Always          4'b0010
`define LT              4'b0011
`define GT              4'b0100
`define LTE             4'b0101
`define GTE             4'b0110

// レジスタアドレス
`define ZR              4'b0000
`define R0              4'b0001
`define R1              4'b0010
`define R2              4'b0011
`define R3              4'b0100
`define P_IN            4'b0101
`define P_OUT           4'b0110




// CPU
module cpu(input clock, reset,
           input [15:0] port_in_data,
           input sync,
           output [15:0] port_out_data);

localparam integer Loop_counter_width=16;
localparam integer Stop_count = 2**Loop_counter_width -1;
reg [Loop_counter_width-1:0] loop_counter;


reg [15:0] pc;
// reg [15:0] port_in_reg;

reg sync_ff, sync_ff2;

/*----------------------------------------------------------------------------
ROMワイヤーマップ
ビット位置  ビット幅  内容
39:35       5        命令
34:31       4        レジスタファイルAポートのアドレス
30:27       4        レジスタファイルBポートのアドレス
26:23       4        レジスタファイルライトポートのアドレス
22:19       4        ジャンプ比較条件
18:16       3        将来拡張用
16:16       1        loop_flag ループ処理するかどうかのフラグ、1でループ処理
15:0        16       即値または、飛び先
-----------------------------------------------------------------------------*/
wire [39:0] rom_data;

wire [4:0]  command        = rom_data[39:35];
wire [3:0]  A_address      = rom_data[34:31];
wire [3:0]  B_address      = rom_data[30:27];
wire [3:0]  write_address  = rom_data[26:23];
wire [3:0]  jump_cond      = rom_data[22:19];
wire [2:0]  reserved       = rom_data[18:16];
wire [15:0] immediate_data = rom_data[15:0];
wire loop_flag             = reserved[0];

wire [15:0] out_dataA, out_dataB;
wire [15:0] alu_data;


// SETコマンドの時は、"Aポートに代入する値" を接続する。
// ※ ALUで ゼロレジスタと加算して代入する事で、SETを実現する為。
wire [15:0] Aport_data = (command==`SET_com) ? immediate_data : port_in_data;



//ループカウンタリセット条件
wire Reset_loop_counter = (loop_counter<2)||
                          (loop_counter==Stop_count && immediate_data == 16'b0)
                          ? 1 : 0;
//ループカウンタの処理
always @(posedge clock, posedge reset) begin
        if(reset)
        begin
                loop_counter <= Stop_count;
        end
        else if(Reset_loop_counter)
        begin
                loop_counter <= Stop_count;
        end
        else if(loop_flag && loop_counter==Stop_count)
        begin
                // ループカウント値をセット
                loop_counter <= immediate_data;
        end
        //else if ((loop_flag) && (loop_counter>1))
        else if(loop_flag)
        begin
                // ループ処理
                loop_counter <= loop_counter - 16'b1;
        end
        else
        begin
                loop_counter <= Stop_count;
        end
end


// 入力同期エッジ検出
always @(posedge clock, posedge reset) begin
        if (reset) sync_ff<=0;
        else    sync_ff<=sync;
end
always @(posedge clock, posedge reset) begin
        if (reset) sync_ff2<=0;
        else    sync_ff2<=sync_ff;
end
// 立ち上がりエッジを検出
wire sync_pos_edge_detected = sync_ff & ~sync_ff2;



// マイクロプログラムカウンタ
always @(posedge clock, posedge reset) begin
        if(reset)
        begin
                pc <= 0;
        end
        // ジャンプ命令処理
        else if(command==`JMP_com && alu_data[0]==1'b1)
        begin
                //飛んでいけ
                pc <= immediate_data;
        end
        else if(command==`SYNC_com)
        begin   //同期待ちスタート処理
                if(sync_pos_edge_detected)
                begin
                        //同期信号を検出。次のアドレスを実行開始。
                        pc <= pc + 1;
                end
                else
                begin
                        //同期エッジが来るまで待つ
                        pc <= pc;
                end
        end
        else if(loop_flag)
        begin   //ループ処理
                if(Reset_loop_counter)
                begin
                        //ループ処理から抜ける
                        pc <= pc + 1;
                end
                else
                begin
                        //ダウンカウント中は待つ
                        pc <= pc;
                end
        end
        else
        begin
                // 何もない時は次のアドレスを実行開始
                pc <= pc + 1;
        end
end


/*
//input reg
always @(posedge clock, posedge reset)
begin
        if(reset)
        begin
                port_in_reg<=0;
        end
        else
        begin
                port_in_reg<=port_in_data;
        end
end
*/


// レジスタファイル
register_file register_file(.clock(clock),
                        .reset(reset),
                        .A_address(A_address),
                        .B_address(B_address),
                        .write_address(write_address),
                        .alu_data(alu_data),
                        .out_dataA(out_dataA),
                        .out_dataB(out_dataB),
                        .port_in_data(Aport_data),
                        .port_out_data(port_out_data));

// ALU
alu  alu(.command(command),
         .jump_cond(jump_cond),
         .portA(out_dataA),
         .portB(out_dataB),
         .alu_out(alu_data));

// ROM
rom rom(.rom_address(pc),.Data(rom_data));


endmodule





// レジスタファイル
// 記憶素子をレジスタの集まりとして、まとめてモジュール化。
// 同時に読み書きできるポートが3つ
// ライトポートが1つ、リードポートが2つ。
module register_file(input  clock,
                     input  reset,
                     input  [3:0] A_address,
                     input  [3:0] B_address,
                     input  [3:0] write_address,
                     input  [15:0] alu_data, port_in_data,
                     output [15:0] out_dataA, out_dataB, port_out_data);

// CPUレジスタ
reg [15:0] p_out_reg, r0, r1, r2, r3;

// ポートアウトレジスタ
always @(posedge clock, posedge reset) begin
        if(reset)
        begin
                p_out_reg <= 0;
        end
        else if(write_address == `P_OUT)
        begin
                p_out_reg <= alu_data;
        end
        else
        begin
                p_out_reg <= p_out_reg;
        end
end


// 汎用レジスタR0
always @(posedge clock, posedge reset) begin
        if(reset)
        begin
                r0 <= 0;
        end
        else if(write_address == `R0)
        begin
                r0 <= alu_data;
        end
        else
        begin
                r0 <= r0;
        end
end


// 汎用レジスタR1
always @(posedge clock, posedge reset) begin
        if(reset)
        begin
                r1 <= 0;
        end
        else if(write_address == `R1)
        begin
                r1 <= alu_data;
        end
        else
        begin
                r1 <= r1;
        end
end


// 汎用レジスタR2
always @(posedge clock, posedge reset) begin
        if(reset)
        begin
                r2 <= 0;
        end
        else if(write_address == `R2)
        begin
                r2 <= alu_data;
        end
        else
        begin
                r2 <= r2;
        end
end


// 汎用レジスタR3
always @(posedge clock, posedge reset) begin
        if(reset)
        begin
                r3 <= 0;
        end
        else if(write_address == `R3)
        begin
                r3 <= alu_data;
        end
        else
        begin
                r3 <= r3;
        end
end


// Aポート出力(ALUへ)
assign out_dataA = (A_address==`P_OUT) ? p_out_reg :
                   (A_address==`P_IN)  ? port_in_data :
                   (A_address==`R0) ? r0 :
                   (A_address==`R1) ? r1 :
                   (A_address==`R2) ? r2 :
                   (A_address==`R3) ? r3 : 16'b0;

// Bポート出力(ALUへ)
assign out_dataB = (B_address==`P_OUT) ? p_out_reg :
                   (B_address==`P_IN)  ? port_in_data :
                   (B_address==`R0) ? r0 :
                   (B_address==`R1) ? r1 :
                   (B_address==`R2) ? r2 :
                   (B_address==`R3) ? r3 : 16'b0;

// 出力ポート(そのまま出力)
assign port_out_data = p_out_reg;

endmodule



// ALU
module alu(input [4:0] command,
           input [3:0] jump_cond,
           input [15:0] portA,portB,
           output reg [15:0] alu_out);

always @(*) begin
        case (command) 
                `ADD_com:     alu_out=portA+portB;
                `SUB_com:     alu_out=portA-portB;
                `SHIFT_L_com: alu_out=portB << 1;
                `SHIFT_R_com: alu_out=portB >> 1;

                `BSHIFT_L_com:alu_out=portA << portB;
                `BSHIFT_R_com:alu_out=portA >> portB;

                `MUL_com:     alu_out=portA * portB;
                `AND_com:     alu_out=portA & portB;
                `OR_com:      alu_out=portA | portB;
                `XOR_com:     alu_out=portA ^ portB;
                `NOT_com:     alu_out=~portA;

                `JMP_com:
                case (jump_cond) 
                        `Eq:            alu_out=(portA == portB);
                        `Not_Eq:        alu_out=(portA != portB);
                        `Always:        alu_out=1;
                        `LT:    if (portA < portB)
                                        alu_out=1;
                                else
                                        alu_out=0;
                        `GT:    if (portA > portB)
                                        alu_out=1;
                                else
                                        alu_out=0;
                        `LTE:   if (portA <= portB)
                                        alu_out=1;
                                else
                                        alu_out=0;
                        `GTE:   if (portA >= portB)
                                        alu_out=1;
                                else
                                        alu_out=0;
                        default:        alu_out=0;
                endcase

                default: alu_out=portA+portB;
        endcase
end
endmodule






module rom(input  [15:0] rom_address,
           output [39:0] Data);


assign Data = romdata(rom_address);

//TAK start
wire [4:0]  command        = Data[39:35];
wire [3:0]  A_address      = Data[34:31];
wire [3:0]  B_address      = Data[30:27];
wire [3:0]  write_address  = Data[26:23];
wire [3:0]  jump_cond      = Data[22:19];
wire [15:0] im_val=Data[15:0];
wire [2:0] loop_cond=Data[18:16];
//TAK end

// コマンド
parameter [4:0] Set=`SET_com,
                Add=`ADD_com,
                Sub=`SUB_com,
                Shift_L=`SHIFT_L_com,
                Shift_R=`SHIFT_R_com,
                Jmp=`JMP_com,
                Sync=`SYNC_com,
                Mul=`MUL_com,
                And=`AND_com,
                Or =`OR_com,
                Xor=`XOR_com,
                Not=`NOT_com,
                BShift_L=`BSHIFT_L_com,
                BShift_R=`BSHIFT_R_com;

parameter [4:0] \セット =`SET_com,
                \加算 =`ADD_com,
                \減算 =`SUB_com,
                \左シフト =`SHIFT_L_com,
                \右シフト =`SHIFT_R_com,
                \ジャンプ =`JMP_com,
                \シンク命令 =`SYNC_com,
                \乗算 =`MUL_com,
                \& =`AND_com,
                \OR  =`OR_com,
                \XOR =`XOR_com,
                \NOT =`NOT_com,
                \左バレルシフト =`BSHIFT_L_com,
                \右バレルシフト =`BSHIFT_R_com;

// レジスタアドレス
parameter [3:0] P_OUT=`P_OUT,//IOポート出力
                P_IN=`P_IN,  //IOポート入力
                R0= `R0,
                R1= `R1,
                R2= `R2,
                R3= `R3,
                ZR= `ZR; // ゼロレジスタ (= ゼロGND)

// ジャンプ条件
parameter [3:0] Eq=`Eq,
                Not_Eq=`Not_Eq,
                Always=`Always,
                LT=`LT,
                GT=`GT,
                LTE=`LTE,
                GTE=`GTE;

// ループ処理
parameter [2:0] Reserved    = 3'b000,
                LoopEnabled = 3'b001;



// マクロ命令

// NOP
`define NOP          romdata={Add,ZR,ZR,ZR,Eq,Reserved,16'h 0000}
// コピー   : r0 → r1
`define MOV(r0,r1)   romdata={Add,r0,ZR,r1,Eq,Reserved,16'h 0000}
// クリア   : r0 = 0
`define CLR(r0)      romdata={Add,ZR,ZR,r0,Eq,Reserved,16'h 0000}
// 定数代入 : r0 = num
`define SET(r0,num)  romdata={Set,P_IN,ZR,r0,Eq,Reserved,16'd num}
// ジャンプ
`define JUMP(num)    romdata={Jmp,ZR,ZR,ZR,Always,Reserved,16'd num}

// 論理反転
`define NOT(r0,r1)   romdata={Not,r0,ZR,r1,Always,Reserved,16'h 0000}


// 足し算   : r2 = r0 + r1
`define ADD(r0,r1,r2) romdata={Add,r0,r1,r2,Eq,Reserved,16'h 0000}
// 引き算   : r2 = r0 - r1
`define SUB(r0,r1,r2) romdata={Sub,r0,r1,r2,Eq,Reserved,16'h 0000}
// 乗算     : r2 = r0 * r1
`define MUL(r0,r1,r2) romdata={Mul,r0,r1,r2,Eq,Reserved,16'h 0000}




// 条件文 : if(r0<r1) goto num;
`define JUMP_IF_LT(r0,r1,num) romdata={Jmp,r0,r1,ZR,LT,Reserved,16'd num}
// 条件文 : if(r0>r1) goto num;
`define JUMP_IF_GT(r0,r1,num) romdata={Jmp,r0,r1,ZR,GT,Reserved,16'd num}

// 条件文 : if(r0<=r1) goto num;
`define JUMP_IF_LTE(r0,r1,num) romdata={Jmp,r0,r1,ZR,LTE,Reserved,16'd num}
// 条件文 : if(r0>=r1) goto num;
`define JUMP_IF_GTE(r0,r1,num) romdata={Jmp,r0,r1,ZR,GTE,Reserved,16'd num}

// 条件文 : if(r0==r1) goto num;
`define JUMP_IF_EQ(r0,r1,num) romdata={Jmp,r0,r1,ZR,Eq,Reserved,16'd num}


// 1ビットシフト
`define SHIFT_L(r0,r1)     romdata={Shift_L,ZR,r0,r1,Eq,Reserved,16'h 0000}
`define SHIFT_R(r0,r1)     romdata={Shift_R,ZR,r0,r1,Eq,Reserved,16'h 0000}
// バレルシフト
`define BSHIFT_L(r0,r1,r2) romdata={BShift_L,r0,r1,r2,Eq,Reserved,16'h 0000}
`define BSHIFT_R(r0,r1,r2) romdata={BShift_R,r0,r1,r2,Eq,Reserved,16'h 0000}


// 外部入力・立ち上がりエッジまで待つ
`define SYNC           romdata={Sync,ZR,ZR,ZR,Eq,Reserved,16'h 0000}


// ループ繰り返し命令(JUMP命令分の1クロックを節約)
// ex : r2 = r0 + r1 を num+1 回繰り返す。その間、pc は 現アドレスで待機。
`define ADD_LOOP(r0,r1,r2,num) romdata={Add,r0,r1,r2,Eq,LoopEnabled,16'd num}
`define SUB_LOOP(r0,r1,r2,num) romdata={Sub,r0,r1,r2,Eq,LoopEnabled,16'd num}
`define MUL_LOOP(r0,r1,r2,num) romdata={Mul,r0,r1,r2,Eq,LoopEnabled,16'd num}
`define SHIFT_L_LOOP(r0,num) romdata={Shift_L,ZR,r0,r0,Eq,LoopEnabled,16'd num}
`define SHIFT_R_LOOP(r0,num) romdata={Shift_R,ZR,r0,r0,Eq,LoopEnabled,16'd num}


//Tak start  逆アセンブラ
reg [8*40:1] str;
function [8*3:1] get_rn(input [3:0] addr);
        case (addr) 
                        P_OUT: get_rn="OUT";
                        P_IN:    get_rn="IN";  //IOポート入力
                   R0:      get_rn="R0";
                   R1:      get_rn="R1";
                   R2:      get_rn="R2";
                   R3:      get_rn="R3";
                   ZR:      get_rn="ZR"; // ゼロレジスタ (= ゼロGND)
                        default: get_rn="???";
        endcase
endfunction

always @* begin
        case (command) 
                Add:
                        if(loop_cond==0) begin
                                if (A_address==ZR && B_address==ZR & write_address==ZR)  $sformat(str,"NOP");
                                else if (B_address==ZR &&  A_address==ZR ) $sformat(str,"%sをクリア",get_rn(write_address));    
                                else if (B_address==ZR ) $sformat(str,"%sから%sへコピー",get_rn(A_address),get_rn(write_address));
                                else $sformat(str,"%s=%s + %s",get_rn(write_address),get_rn(A_address),get_rn(B_address));
                         end else begin
                                if (A_address==ZR && B_address==ZR & write_address==ZR)  $sformat(str,"NOP %dループ付",im_val);
                                else if (B_address==ZR &&  A_address==ZR ) $sformat(str,"%sをクリア  %dループ付",get_rn(write_address),im_val);        
                                else if (B_address==ZR ) $sformat(str,"%sから%sへコピー  %dループ付",get_rn(A_address),get_rn(write_address),im_val);
                                else  $sformat(str,"%s=%s + %s  %dループ付",get_rn(write_address),get_rn(A_address),get_rn(B_address),im_val);
                        end
                Set:  $sformat(str,"%s=%h[Hex]",get_rn(write_address),im_val);
                Mul:
                        if (loop_cond==0) begin
                                 $sformat(str,"%s=%s * %s",get_rn(write_address),get_rn(A_address),get_rn(B_address));
                        end else $sformat(str,"%s=%s * %s   %dループ付",get_rn(write_address),get_rn(A_address),get_rn(B_address),im_val);
                Sub:
                        if (loop_cond==0) begin
                                 $sformat(str,"%s=%s - %s",get_rn(write_address),get_rn(A_address),get_rn(B_address));
                        end else  $sformat(str,"%s=%s - %s  %dループ付",get_rn(write_address),get_rn(A_address),get_rn(B_address),im_val);
                Sync:
                        $sformat(str,"シンク命令");
                        
                Not: $sformat(str,"%s=~%s",get_rn(write_address),get_rn(A_address));
                Jmp:
                        if (jump_cond==Always) $sformat(str,"%d番地へジャンプ",im_val);
                        else if (jump_cond==LT) $sformat(str,"%s < %s なら%d番地へジャンプ",get_rn(A_address),get_rn(B_address),im_val);
                        else if (jump_cond==GT) $sformat(str,"%s > %s なら%d番地へジャンプ",get_rn(A_address),get_rn(B_address),im_val);
                        else if (jump_cond==LTE) $sformat(str,"%s <= %s なら%d番地へジャンプ",get_rn(A_address),get_rn(B_address),im_val);
                        else if (jump_cond==GTE) $sformat(str,"%s >= %s なら%d番地へジャンプ",get_rn(A_address),get_rn(B_address),im_val); 
                        else if (jump_cond==Eq)  $sformat(str,"%s == %s なら%d番地へジャンプ",get_rn(A_address),get_rn(B_address),im_val); 
                Shift_L:
                                if (loop_cond==0)               $sformat(str,"%s=%s << 1",get_rn(write_address),get_rn(B_address));
                                else                          $sformat(str,"%s=%s << %d",get_rn(write_address),get_rn(B_address),im_val);
                         
                Shift_R: 
                                if (loop_cond==0)               $sformat(str,"%s=%s >> 1",get_rn(write_address),get_rn(B_address));
                                else                          $sformat(str,"%s=%s >> %d",get_rn(write_address),get_rn(B_address),im_val);

                BShift_L:
                              $sformat(str,"%s=%s <<%s",get_rn(write_address),get_rn(A_address),get_rn(B_address));
                         
                BShift_R: 
                              $sformat(str,"%s=%s >> %s",get_rn(write_address),get_rn(A_address),get_rn(B_address));

        endcase
end

//Tak end

// マイクロコード
function [39:0]romdata;
        input [15:0] address;
        case(address)
        //Commad  A_addr, B_addr, Write_addr, Jmp_cond, Reserved, IM
        0: `SET(P_OUT, 100);
        1: `SET(R0, 5);
        2: `MOV(R0, R1);
        3: `MOV(R1, R2);
        4: `MOV(R2, R3);
        5: `SUB(R1, R2, R0);
        6: `SUB(R3, R1, R3);
        7: `ADD(P_IN, P_OUT, P_OUT);
        8: `ADD(P_OUT, P_OUT, P_OUT);
        9: `SUB(P_IN, P_IN, R1);
        10: `SUB(P_OUT, P_IN, R2);
        11: `SET(R0, 2);
        12: `SET(R1, 8);
        13: `MUL(R0, R1, R3);
        14: `NOP;
        15: `MUL(R3, R3, R3);
        16: `ADD(P_OUT, R3, P_OUT);
        17: `SYNC;
        18: `JUMP(1000);

        // シフト・乗算
        1000: `SET(R0, 5);
        1001: `SET(R1, 2);
        1002: `ADD_LOOP(R0, R1, R0, 10);
        1003: `SHIFT_L_LOOP(R1, 5);
        1004: `SHIFT_R_LOOP(R1, 5);
        1005: `SUB_LOOP(R0, R1, R0, 10);
        1006: `SET(P_OUT, 5);
        1007: `MUL_LOOP(P_OUT, R1, P_OUT, 7);
        1008: `JUMP(1500);

        // 条件文
        1500: `SET(R0, 16);
        1501: `SET(R1, 2);
        1502: `SET(P_OUT, 0);
        1503: `SUB(R0, R1, R0);
        1504: `JUMP_IF_GT(R0, R1 ,1503);

        // 条件文
        1505: `SET(R2, 16);
        1506: `SET(R3, 2);
        1507: `SET(P_OUT, 0);
        1509: `SUB(R2, R3, R2);
        1511: `JUMP_IF_GTE(R2, R3 ,1509);

        // 1010_1010_1010_1010
        // ビット反転
        1512: `SET(R0, 43690);
        1513: `NOT(R0, R1);

        // バレルシフタ
        1514: `SET(R0, 2);
        1515: `SET(R1, 7);
        1516: `SET(R2, 0);
        1517: `BSHIFT_L(R0, R1, R0);
        1518: `BSHIFT_R(R0, R1, R0);

        1519: `JUMP(0);

        default:  romdata = 0;
        endcase
endfunction
endmodule


アーカイブです。


なお、記述の動作確認としてQuartasUで合成をしてみました。デバイスはStratix2 速度は、72MHzでした。


内部動作の確認をしたい為に、pcとrom_dataを引き出しています。
`Debug で、切り替えしています。

`define Debug

// CPU
module cpu(input clock, reset,
           input [15:0] port_in_data,
           input sync,
           output [15:0] port_out_data
`ifdef Debug
                                        ,
                                         output [39:0] rom_data,
                                         output reg [15:0] pc
`endif
);

localparam integer Loop_counter_width=16;
localparam integer Stop_count = 2**Loop_counter_width -1;
reg [Loop_counter_width-1:0] loop_counter;

`ifndef Debug
reg [15:0] pc;
`endif
// reg [15:0] port_in_reg;

reg sync_ff, sync_ff2;

/*----------------------------------------------------------------------------
ROMワイヤーマップ
ビット位置  ビット幅  内容
39:35       5        命令
34:31       4        レジスタファイルAポートのアドレス
30:27       4        レジスタファイルBポートのアドレス
26:23       4        レジスタファイルライトポートのアドレス
22:19       4        ジャンプ比較条件
18:16       3        将来拡張用
16:16       1        loop_flag ループ処理するかどうかのフラグ、1でループ処理
15:0        16       即値または、飛び先
-----------------------------------------------------------------------------*/
`ifndef Debug
wire [39:0] rom_data;
`endif


シミュレーションの最後ところです。青色が遅延SIMで、トップ階層の信号をVCDで記録したものです。
さすがにrom_dataの遅延が大きくなっていますが、RTL SIMと一致しています。
これで、動作の検証は終了です。



このCPUを
spartan3でも合成してみました。速度は40MHzです。



遅延シミュレーションです。StratixUと比べるとやはり遅延は大きくなります。






シンクロナスROMの実装
現在のFPGAのRAM/ROMは、クロックに同期して動作するタイプが主流ですので、今までのRTL SIMからその辺を修正する必要があります。

AlteraのROM作成
Mega−Wizardで生成します。
下のように、address_latachがdefault で付いてしまいます。この対応については後述します。
ビット幅は、40ビット、ワード数は、とりあえず現実的な値として2Kワードを指定します。



後段の出力は、レジスタなしにします。




ROMの初期化は、hexファイルを指定します。(hexファイルは後述)




以上で、Wizard終了です。




さて、問題は、プログラムカウンタPCとRAM内のアドレスラッチ動作がダブっていることです。タダで1CLK遅れてしまうので面白くありません。そこで、ROMに渡すPCは、レジスタ出力ではなく、組み合わせ回路のまま、ROMに渡します。
Xilinx版も同様で、pc_comを渡します。

// ROM
`ifdef ALTERA
        micro_rom rom(.address(pc_com),.q(rom_data),.clock(clock));//ROM にはpcではなくpc_comを渡す形に変更

`else
        `ifdef XILINX

                micro_rom_xilinx rom(.addr(pc_com),.dout(rom_data),.clk(clock));//ROM にはpcではなくpc_comを渡す形に変更
        `else
                rom rom(.rom_address(pc_com),.Data(rom_data),.clock(clock));//ROM にはpcではなくpc_comを渡す形に変更
                
        `endif
`endif


pc_comは、レジスタpcにする前の組み合わせ回路で、以下のようになります。
ここで、pc_comは、clockに対して同期した信号のみで構成することに注意します。FPGA内部は、非同期リセットのFFを除き、全部同期した信号で構成することが重要です。resetは、非同期なので、clockで、同期化した信号にします。

// マイクロプログラムカウンタ
reg reset_sync;
always @(clock) reset_sync<=reset;//同期化 pc組み合わせ回路は、同期化したものが必要

//pc 組み合わせ回路
//Altera sync rom は、必ずAddressラッチがついてしまうので、
//pcを渡してしまうのは、1CLK DELAYになってしまう。それを防ぐ
//ために次アドレスを組み合わせ回路で渡す。メモリ内ラッチはPCとDuplicate動作する
reg [15:0] pc_com;
always @* begin
        if(reset_sync)
        begin
                pc_com= 0;
        end
        // ジャンプ命令処理
        else if(command==`JMP_com && alu_data[0]==1'b1)
        begin
                //飛んでいけ
                pc_com= immediate_data;
        end
        else if(command==`SYNC_com)
        begin   //同期待ちスタート処理
                if(sync_pos_edge_detected)
                begin
                        //同期信号を検出。次のアドレスを実行開始。
                        pc_com= pc + 1;
                end
                else
                begin
                        //同期エッジが来るまで待つ
                        pc_com= pc;
                end
        end
        else if(loop_flag)
        begin   //ループ処理
                if(Reset_loop_counter)
                begin
                        //ループ処理から抜ける
                        pc_com= pc + 1;
                end
                else
                begin
                        //ダウンカウント中は待つ
                        pc_com= pc;
                end
        end
        else
        begin
                // 何もない時は次のアドレスを実行開始
                pc_com= pc + 1;
        end
end

always @(posedge clock, posedge reset) begin
        if(reset)
        begin
                pc <= 0;
        end
        else pc<=pc_com;
      
end


このようにすれば、RAM内のアドレスラッチは、PCのように振るまうので、今までのRTL SIMと同じタイミングで動作することになります。

XilinxのROM
coregenで、1ポートROMを生成します。



なにも指定せずに進みます。







ROMの初期化ファイルは、rom.coe(後述)を指定します。



ROM初期化ファイルの生成

Altera、Xilinx共専用の初期化ファイルが必要になります。これから先、いちいち他のツールで変換するのは、面倒なので、Verilog HDLソース中、RTL SIM時に自動生成するようにします。

HEXファイルの生成タスクです。micro_romの内容をINTEL HEXファイル(rom.hex)にするタスクです。

//memory の内容をAltera ROM初期化用に
//intel-hexフォーマットに変換する
//ファイル->rom.hex として出力する
//address 64K word max
//zxを含まないこと
task make_intel_hex(input [7:0]  bytes_per_word,
                                   input integer address_max);
                reg [9*8:1] str;
                integer fi;
                reg [15:0]   ad;
                integer bytes;
              reg [7:0] parity;
                begin
                        fi=$fopen("rom.hex","w");
                        
                        for (ad=0;ad <=address_max;ad=ad+1) begin
                                $sformat(str,":%2h%4h00",bytes_per_word,ad);
                                parity=bytes_per_word;
                                parity =parity + ad[8*1 +:8];//チェックサム
                                parity =parity + ad[0     +:8];//チェックサム
                                

                                $fwrite(fi,"%s",str);   
                                for (bytes=0;bytes<bytes_per_word;bytes=bytes+1) begin: byte_loop
                                        reg [7:0] byte;
                                        byte=micro_rom[ad][(bytes_per_word-1-bytes)*8 +:8]; 
                                        parity=parity+byte;//チェックサム
                                
                                        $fwrite(fi,"%2h",byte);
                                end
                                parity=~parity +8'h01;//チェックサム
                                $fwrite(fi,"%2h",parity);
                                $fwrite(fi,"\n");
                        end
                        $fdisplay(fi,":00000001FF");
                        $fclose(fi);
                        str="rom.ver";
                        //$convert_hex2ver("rom.hex",40,str);//チェックVeritak Uniqueファンクション
                end
endtask


micro_romの内容をXILINX Coeファイルにするタスクです。

//xilinx coe file
task make_coe_file(input [7:0]  bytes_per_word,
                                   input integer address_max);
        integer fi;
        integer ad;
        begin
                fi=$fopen("rom.coe","w");
                $fdisplay(fi,"memory_initialization_radix=16;");
                $fdisplay(fi,"memory_initialization_vector=");
                for (ad=0;ad <address_max;ad=ad+1) begin
                        $fdisplay(fi,"%h,",micro_rom[ad]);
                end
                $fdisplay(fi,"%h;",micro_rom[address_max]);
                $fclose(fi);
        end
endtask
                                  


以上のタスクをRTL SIM時に生成するようにします。

// マイクロコード
`define PHY_ADD_MAX (1024*2-1)
//ALTERA ROMの模擬コード
        reg [39:0] micro_rom [0:`PHY_ADD_MAX];
        reg [15:0] address_latch;

        always @(posedge clock) address_latch<=rom_address;
//      always @(posedge clock ) begin//q latch 出力付の場合、将来パイプライン化したときに必要になる。
  always @* begin
                        Data=micro_rom[address_latch];
        end
`ifdef RTL_SIM
        integer i;
        initial begin
                for (i=0;i<= `PHY_ADD_MAX;i=i+1) begin
                                micro_rom[i]=romdata(i);
                end
                make_intel_hex(5,`PHY_ADD_MAX);//RTL SIM時にINTEL HEXファイルを生成する Altera 用
                make_coe_file(5,`PHY_ADD_MAX);//RTL SIM時にXilinx用 Coeファイルを生成する
        end
`endif



以上で、シンクロナスROM実装への変更が終わりました。
XILINX,ALTERAの切り替えは、冒頭のDefineの定義の有無です。RTL シミュレーションの場合は、Veritak ProjectにDefineを入れ込めば指定しなくてもよいのですが、合成する場合は、それぞれ定義を変えます。



今回の全体ソースです。

//  マイクロプログラム方式 CPU

// ネイティブコマンド
`define ADD_com         5'b00000
`define SET_com         5'b00001
`define SUB_com         5'b00010
`define SHIFT_L_com     5'b00011
`define SHIFT_R_com     5'b00100
`define JMP_com         5'b00101
`define SYNC_com        5'b00110

`define AND_com         5'b00111
`define OR_com          5'b01000
`define XOR_com         5'b01001
`define NOT_com         5'b01010

`define MUL_com         5'b01011

`define BSHIFT_L_com    5'b01100
`define BSHIFT_R_com    5'b01101


// ジャンプ条件
`define Eq              4'b0000
`define Not_Eq          4'b0001
`define Always          4'b0010
`define LT              4'b0011
`define GT              4'b0100
`define LTE             4'b0101
`define GTE             4'b0110

// レジスタアドレス
`define ZR              4'b0000
`define R0              4'b0001
`define R1              4'b0010
`define R2              4'b0011
`define R3              4'b0100
`define P_IN            4'b0101
`define P_OUT           4'b0110


`define Debug
//`define ALTERA //Altera の場合コメントを取りXILINXをコメントアウト
//`define XILINX //Xilinxの場合コメントを取りALERAをコメントアウト

// CPU
module cpu(input clock, reset,
           input [15:0] port_in_data,
           input sync,
           output [15:0] port_out_data
`ifdef Debug
                                        ,
                                         output [39:0] rom_data,
                                         output reg [15:0] pc
`endif
);

localparam integer Loop_counter_width=16;
localparam integer Stop_count = 2**Loop_counter_width -1;
reg [Loop_counter_width-1:0] loop_counter;

`ifndef Debug
reg [15:0] pc;
reg [15:0] pc_com;//組み合わせロジック
`endif
// reg [15:0] port_in_reg;

reg sync_ff, sync_ff2;

/*----------------------------------------------------------------------------
ROMワイヤーマップ
ビット位置  ビット幅  内容
39:35       5        命令
34:31       4        レジスタファイルAポートのアドレス
30:27       4        レジスタファイルBポートのアドレス
26:23       4        レジスタファイルライトポートのアドレス
22:19       4        ジャンプ比較条件
18:16       3        将来拡張用
16:16       1        loop_flag ループ処理するかどうかのフラグ、1でループ処理
15:0        16       即値または、飛び先
-----------------------------------------------------------------------------*/
`ifndef Debug
wire [39:0] rom_data;
`endif
wire [4:0]  command        = rom_data[39:35];
wire [3:0]  A_address      = rom_data[34:31];
wire [3:0]  B_address      = rom_data[30:27];
wire [3:0]  write_address  = rom_data[26:23];
wire [3:0]  jump_cond      = rom_data[22:19];
wire [2:0]  reserved       = rom_data[18:16];
wire [15:0] immediate_data = rom_data[15:0];
wire loop_flag             = reserved[0];

wire [15:0] out_dataA, out_dataB;
wire [15:0] alu_data;


// SETコマンドの時は、"Aポートに代入する値" を接続する。
// ※ ALUで ゼロレジスタと加算して代入する事で、SETを実現する為。
wire [15:0] Aport_data = (command==`SET_com) ? immediate_data : port_in_data;



//ループカウンタリセット条件
wire Reset_loop_counter = (loop_counter<2)||
                          (loop_counter==Stop_count && immediate_data == 16'b0)
                          ? 1 : 0;
//ループカウンタの処理
always @(posedge clock, posedge reset) begin
        if(reset)
        begin
                loop_counter <= Stop_count;
        end
        else if(Reset_loop_counter)
        begin
                loop_counter <= Stop_count;
        end
        else if(loop_flag && loop_counter==Stop_count)
        begin
                // ループカウント値をセット
                loop_counter <= immediate_data;
        end
        //else if ((loop_flag) && (loop_counter>1))
        else if(loop_flag)
        begin
                // ループ処理
                loop_counter <= loop_counter - 16'b1;
        end
        else
        begin
                loop_counter <= Stop_count;
        end
end


// 入力同期エッジ検出
always @(posedge clock, posedge reset) begin
        if (reset) sync_ff<=0;
        else    sync_ff<=sync;
end
always @(posedge clock, posedge reset) begin
        if (reset) sync_ff2<=0;
        else    sync_ff2<=sync_ff;
end
// 立ち上がりエッジを検出
wire sync_pos_edge_detected = sync_ff & ~sync_ff2;



// マイクロプログラムカウンタ
reg reset_sync;
always @(clock) reset_sync<=reset;//同期化 pc組み合わせ回路は、同期化したものが必要

//pc 組み合わせ回路
//Altera sync rom は、必ずAddressラッチがついてしまうので、
//pcを渡してしまうのは、1CLK DELAYになってしまう。それを防ぐ
//ために次アドレスを組み合わせ回路で渡す。メモリ内ラッチはPCとDuplicate動作する
reg [15:0] pc_com;
always @* begin
        if(reset_sync)
        begin
                pc_com= 0;
        end
        // ジャンプ命令処理
        else if(command==`JMP_com && alu_data[0]==1'b1)
        begin
                //飛んでいけ
                pc_com= immediate_data;
        end
        else if(command==`SYNC_com)
        begin   //同期待ちスタート処理
                if(sync_pos_edge_detected)
                begin
                        //同期信号を検出。次のアドレスを実行開始。
                        pc_com= pc + 1;
                end
                else
                begin
                        //同期エッジが来るまで待つ
                        pc_com= pc;
                end
        end
        else if(loop_flag)
        begin   //ループ処理
                if(Reset_loop_counter)
                begin
                        //ループ処理から抜ける
                        pc_com= pc + 1;
                end
                else
                begin
                        //ダウンカウント中は待つ
                        pc_com= pc;
                end
        end
        else
        begin
                // 何もない時は次のアドレスを実行開始
                pc_com= pc + 1;
        end
end

always @(posedge clock, posedge reset) begin
        if(reset)
        begin
                pc <= 0;
        end
        else pc<=pc_com;
      
end





// レジスタファイル
register_file register_file(.clock(clock),
                        .reset(reset),
                        .A_address(A_address),
                        .B_address(B_address),
                        .write_address(write_address),
                        .alu_data(alu_data),
                        .out_dataA(out_dataA),
                        .out_dataB(out_dataB),
                        .port_in_data(Aport_data),
                        .port_out_data(port_out_data));

// ALU
alu  alu(.command(command),
         .jump_cond(jump_cond),
         .portA(out_dataA),
         .portB(out_dataB),
         .alu_out(alu_data));

// ROM
`ifdef ALTERA
        micro_rom rom(.address(pc_com),.q(rom_data),.clock(clock));//ROM にはpcではなくpc_comを渡す形に変更

`else
        `ifdef XILINX

                micro_rom_xilinx rom(.addr(pc_com),.dout(rom_data),.clk(clock));//ROM にはpcではなくpc_comを渡す形に変更
        `else
                rom rom(.rom_address(pc_com),.Data(rom_data),.clock(clock));//ROM にはpcではなくpc_comを渡す形に変更
                
        `endif
`endif

endmodule





// レジスタファイル
// 記憶素子をレジスタの集まりとして、まとめてモジュール化。
// 同時に読み書きできるポートが3つ
// ライトポートが1つ、リードポートが2つ。
module register_file(input  clock,
                     input  reset,
                     input  [3:0] A_address,
                     input  [3:0] B_address,
                     input  [3:0] write_address,
                     input  [15:0] alu_data, port_in_data,
                     output [15:0] out_dataA, out_dataB, port_out_data);

// CPUレジスタ
reg [15:0] p_out_reg, r0, r1, r2, r3;

// ポートアウトレジスタ
always @(posedge clock, posedge reset) begin
        if(reset)
        begin
                p_out_reg <= 0;
        end
        else if(write_address == `P_OUT)
        begin
                p_out_reg <= alu_data;
        end
        else
        begin
                p_out_reg <= p_out_reg;
        end
end


// 汎用レジスタR0
always @(posedge clock, posedge reset) begin
        if(reset)
        begin
                r0 <= 0;
        end
        else if(write_address == `R0)
        begin
                r0 <= alu_data;
        end
        else
        begin
                r0 <= r0;
        end
end


// 汎用レジスタR1
always @(posedge clock, posedge reset) begin
        if(reset)
        begin
                r1 <= 0;
        end
        else if(write_address == `R1)
        begin
                r1 <= alu_data;
        end
        else
        begin
                r1 <= r1;
        end
end


// 汎用レジスタR2
always @(posedge clock, posedge reset) begin
        if(reset)
        begin
                r2 <= 0;
        end
        else if(write_address == `R2)
        begin
                r2 <= alu_data;
        end
        else
        begin
                r2 <= r2;
        end
end


// 汎用レジスタR3
always @(posedge clock, posedge reset) begin
        if(reset)
        begin
                r3 <= 0;
        end
        else if(write_address == `R3)
        begin
                r3 <= alu_data;
        end
        else
        begin
                r3 <= r3;
        end
end


// Aポート出力(ALUへ)
assign out_dataA = (A_address==`P_OUT) ? p_out_reg :
                   (A_address==`P_IN)  ? port_in_data :
                   (A_address==`R0) ? r0 :
                   (A_address==`R1) ? r1 :
                   (A_address==`R2) ? r2 :
                   (A_address==`R3) ? r3 : 16'b0;

// Bポート出力(ALUへ)
assign out_dataB = (B_address==`P_OUT) ? p_out_reg :
                   (B_address==`P_IN)  ? port_in_data :
                   (B_address==`R0) ? r0 :
                   (B_address==`R1) ? r1 :
                   (B_address==`R2) ? r2 :
                   (B_address==`R3) ? r3 : 16'b0;

// 出力ポート(そのまま出力)
assign port_out_data = p_out_reg;

endmodule



// ALU
module alu(input [4:0] command,
           input [3:0] jump_cond,
           input [15:0] portA,portB,
           output reg [15:0] alu_out);

always @(*) begin
        case (command) 
                `ADD_com:     alu_out=portA+portB;
                `SUB_com:     alu_out=portA-portB;
                `SHIFT_L_com: alu_out=portB << 1;
                `SHIFT_R_com: alu_out=portB >> 1;

                `BSHIFT_L_com:alu_out=portA << portB;
                `BSHIFT_R_com:alu_out=portA >> portB;

                `MUL_com:     alu_out=portA * portB;


                `AND_com:     alu_out=portA & portB;
                `OR_com:      alu_out=portA | portB;
                `XOR_com:     alu_out=portA ^ portB;
                `NOT_com:     alu_out=~portA;

                `JMP_com:
                case (jump_cond) 
                        `Eq:            alu_out=(portA == portB);
                        `Not_Eq:        alu_out=(portA != portB);
                        `Always:        alu_out=1;
                        `LT:    if (portA < portB)
                                        alu_out=1;
                                else
                                        alu_out=0;
                        `GT:    if (portA > portB)
                                        alu_out=1;
                                else
                                        alu_out=0;
                        `LTE:   if (portA <= portB)
                                        alu_out=1;
                                else
                                        alu_out=0;
                        `GTE:   if (portA >= portB)
                                        alu_out=1;
                                else
                                        alu_out=0;
                        default:        alu_out=0;
                endcase

                default: alu_out=portA+portB;
        endcase
end
endmodule






module rom(input  [15:0] rom_address,
                    input clock,//シンクロナスROMに変更
           output reg [39:0] Data);


//assign Data = romdata(rom_address);//シンクロナスROMに変更

//TAK start
wire [4:0]  command        = Data[39:35];
wire [3:0]  A_address      = Data[34:31];
wire [3:0]  B_address      = Data[30:27];
wire [3:0]  write_address  = Data[26:23];
wire [3:0]  jump_cond      = Data[22:19];
wire [15:0] im_val=Data[15:0];
wire [2:0] loop_cond=Data[18:16];
//TAK end

// コマンド
parameter [4:0] Set=`SET_com,
                Add=`ADD_com,
                Sub=`SUB_com,
                Shift_L=`SHIFT_L_com,
                Shift_R=`SHIFT_R_com,
                Jmp=`JMP_com,
                Sync=`SYNC_com,
                Mul=`MUL_com,
                And=`AND_com,
                Or =`OR_com,
                Xor=`XOR_com,
                Not=`NOT_com,
                BShift_L=`BSHIFT_L_com,
                BShift_R=`BSHIFT_R_com;

parameter [4:0] \セット =`SET_com,
                \加算 =`ADD_com,
                \減算 =`SUB_com,
                \左シフト =`SHIFT_L_com,
                \右シフト =`SHIFT_R_com,
                \ジャンプ =`JMP_com,
                \シンク命令 =`SYNC_com,
                \乗算 =`MUL_com,
                \& =`AND_com,
                \OR  =`OR_com,
                \XOR =`XOR_com,
                \NOT =`NOT_com,
                \左バレルシフト =`BSHIFT_L_com,
                \右バレルシフト =`BSHIFT_R_com;

// レジスタアドレス
parameter [3:0] P_OUT=`P_OUT,//IOポート出力
                P_IN=`P_IN,  //IOポート入力
                R0= `R0,
                R1= `R1,
                R2= `R2,
                R3= `R3,
                ZR= `ZR; // ゼロレジスタ (= ゼロGND)

// ジャンプ条件
parameter [3:0] Eq=`Eq,
                Not_Eq=`Not_Eq,
                Always=`Always,
                LT=`LT,
                GT=`GT,
                LTE=`LTE,
                GTE=`GTE;

// ループ処理
parameter [2:0] Reserved    = 3'b000,
                LoopEnabled = 3'b001;



// マクロ命令

// NOP
`define NOP          romdata={Add,ZR,ZR,ZR,Eq,Reserved,16'h 0000}
// コピー   : r0 → r1
`define MOV(r0,r1)   romdata={Add,r0,ZR,r1,Eq,Reserved,16'h 0000}
// クリア   : r0 = 0
`define CLR(r0)      romdata={Add,ZR,ZR,r0,Eq,Reserved,16'h 0000}
// 定数代入 : r0 = num
`define SET(r0,num)  romdata={Set,P_IN,ZR,r0,Eq,Reserved,16'd num}
// ジャンプ
`define JUMP(num)    romdata={Jmp,ZR,ZR,ZR,Always,Reserved,16'd num}

// 論理反転
`define NOT(r0,r1)   romdata={Not,r0,ZR,r1,Always,Reserved,16'h 0000}


// 足し算   : r2 = r0 + r1
`define ADD(r0,r1,r2) romdata={Add,r0,r1,r2,Eq,Reserved,16'h 0000}
// 引き算   : r2 = r0 - r1
`define SUB(r0,r1,r2) romdata={Sub,r0,r1,r2,Eq,Reserved,16'h 0000}
// 乗算     : r2 = r0 * r1
`define MUL(r0,r1,r2) romdata={Mul,r0,r1,r2,Eq,Reserved,16'h 0000}




// 条件文 : if(r0<r1) goto num;
`define JUMP_IF_LT(r0,r1,num) romdata={Jmp,r0,r1,ZR,LT,Reserved,16'd num}
// 条件文 : if(r0>r1) goto num;
`define JUMP_IF_GT(r0,r1,num) romdata={Jmp,r0,r1,ZR,GT,Reserved,16'd num}

// 条件文 : if(r0<=r1) goto num;
`define JUMP_IF_LTE(r0,r1,num) romdata={Jmp,r0,r1,ZR,LTE,Reserved,16'd num}
// 条件文 : if(r0>=r1) goto num;
`define JUMP_IF_GTE(r0,r1,num) romdata={Jmp,r0,r1,ZR,GTE,Reserved,16'd num}

// 条件文 : if(r0==r1) goto num;
`define JUMP_IF_EQ(r0,r1,num) romdata={Jmp,r0,r1,ZR,Eq,Reserved,16'd num}


// 1ビットシフト
`define SHIFT_L(r0,r1)     romdata={Shift_L,ZR,r0,r1,Eq,Reserved,16'h 0000}
`define SHIFT_R(r0,r1)     romdata={Shift_R,ZR,r0,r1,Eq,Reserved,16'h 0000}
// バレルシフト
`define BSHIFT_L(r0,r1,r2) romdata={BShift_L,r0,r1,r2,Eq,Reserved,16'h 0000}
`define BSHIFT_R(r0,r1,r2) romdata={BShift_R,r0,r1,r2,Eq,Reserved,16'h 0000}


// 外部入力・立ち上がりエッジまで待つ
`define SYNC           romdata={Sync,ZR,ZR,ZR,Eq,Reserved,16'h 0000}


// ループ繰り返し命令(JUMP命令分の1クロックを節約)
// ex : r2 = r0 + r1 を num+1 回繰り返す。その間、pc は 現アドレスで待機。
`define ADD_LOOP(r0,r1,r2,num) romdata={Add,r0,r1,r2,Eq,LoopEnabled,16'd num}
`define SUB_LOOP(r0,r1,r2,num) romdata={Sub,r0,r1,r2,Eq,LoopEnabled,16'd num}
`define MUL_LOOP(r0,r1,r2,num) romdata={Mul,r0,r1,r2,Eq,LoopEnabled,16'd num}
`define SHIFT_L_LOOP(r0,num) romdata={Shift_L,ZR,r0,r0,Eq,LoopEnabled,16'd num}
`define SHIFT_R_LOOP(r0,num) romdata={Shift_R,ZR,r0,r0,Eq,LoopEnabled,16'd num}


//Tak start  逆アセンブラ
reg [8*40:1] str;
function [8*3:1] get_rn(input [3:0] addr);
        case (addr) 
                        P_OUT: get_rn="OUT";
                        P_IN:    get_rn="IN";  //IOポート入力
                   R0:      get_rn="R0";
                   R1:      get_rn="R1";
                   R2:      get_rn="R2";
                   R3:      get_rn="R3";
                   ZR:      get_rn="ZR"; // ゼロレジスタ (= ゼロGND)
                        default: get_rn="???";
        endcase
endfunction

always @* begin
        case (command) 
                Add:
                        if(loop_cond==0) begin
                                if (A_address==ZR && B_address==ZR & write_address==ZR)  $sformat(str,"NOP");
                                else if (B_address==ZR &&  A_address==ZR ) $sformat(str,"%sをクリア",get_rn(write_address));    
                                else if (B_address==ZR ) $sformat(str,"%sから%sへコピー",get_rn(A_address),get_rn(write_address));
                                else $sformat(str,"%s=%s + %s",get_rn(write_address),get_rn(A_address),get_rn(B_address));
                         end else begin
                                if (A_address==ZR && B_address==ZR & write_address==ZR)  $sformat(str,"NOP %dループ付",im_val);
                                else if (B_address==ZR &&  A_address==ZR ) $sformat(str,"%sをクリア  %dループ付",get_rn(write_address),im_val);        
                                else if (B_address==ZR ) $sformat(str,"%sから%sへコピー  %dループ付",get_rn(A_address),get_rn(write_address),im_val);
                                else  $sformat(str,"%s=%s + %s  %dループ付",get_rn(write_address),get_rn(A_address),get_rn(B_address),im_val);
                        end
                Set:  $sformat(str,"%s=%h[Hex]",get_rn(write_address),im_val);
                Mul:
                        if (loop_cond==0) begin
                                 $sformat(str,"%s=%s * %s",get_rn(write_address),get_rn(A_address),get_rn(B_address));
                        end else $sformat(str,"%s=%s * %s   %dループ付",get_rn(write_address),get_rn(A_address),get_rn(B_address),im_val);
                Sub:
                        if (loop_cond==0) begin
                                 $sformat(str,"%s=%s - %s",get_rn(write_address),get_rn(A_address),get_rn(B_address));
                        end else  $sformat(str,"%s=%s - %s  %dループ付",get_rn(write_address),get_rn(A_address),get_rn(B_address),im_val);
                Sync:
                        $sformat(str,"シンク命令");
                        
                Not: $sformat(str,"%s=~%s",get_rn(write_address),get_rn(A_address));
                Jmp:
                        if (jump_cond==Always) $sformat(str,"%d番地へジャンプ",im_val);
                        else if (jump_cond==LT) $sformat(str,"%s < %s なら%d番地へジャンプ",get_rn(A_address),get_rn(B_address),im_val);
                        else if (jump_cond==GT) $sformat(str,"%s > %s なら%d番地へジャンプ",get_rn(A_address),get_rn(B_address),im_val);
                        else if (jump_cond==LTE) $sformat(str,"%s <= %s なら%d番地へジャンプ",get_rn(A_address),get_rn(B_address),im_val);
                        else if (jump_cond==GTE) $sformat(str,"%s >= %s なら%d番地へジャンプ",get_rn(A_address),get_rn(B_address),im_val); 
                        else if (jump_cond==Eq)  $sformat(str,"%s == %s なら%d番地へジャンプ",get_rn(A_address),get_rn(B_address),im_val); 
                Shift_L:
                                if (loop_cond==0)               $sformat(str,"%s=%s << 1",get_rn(write_address),get_rn(B_address));
                                else                          $sformat(str,"%s=%s << %d",get_rn(write_address),get_rn(B_address),im_val);
                         
                Shift_R: 
                                if (loop_cond==0)               $sformat(str,"%s=%s >> 1",get_rn(write_address),get_rn(B_address));
                                else                          $sformat(str,"%s=%s >> %d",get_rn(write_address),get_rn(B_address),im_val);

                BShift_L:
                              $sformat(str,"%s=%s <<%s",get_rn(write_address),get_rn(A_address),get_rn(B_address));
                         
                BShift_R: 
                              $sformat(str,"%s=%s >> %s",get_rn(write_address),get_rn(A_address),get_rn(B_address));

        endcase
end

//Tak end

// マイクロコード
`define PHY_ADD_MAX (1024*2-1)
//ALTERA ROMの模擬コード
        reg [39:0] micro_rom [0:`PHY_ADD_MAX];
        reg [15:0] address_latch;

        always @(posedge clock) address_latch<=rom_address;
//      always @(posedge clock ) begin//q latch 出力付の場合、将来パイプライン化したときに必要になる。
  always @* begin
                        Data=micro_rom[address_latch];
        end
`ifdef RTL_SIM
        integer i;
        initial begin
                for (i=0;i<= `PHY_ADD_MAX;i=i+1) begin
                                micro_rom[i]=romdata(i);
                end
                make_intel_hex(5,`PHY_ADD_MAX);//RTL SIM時にINTEL HEXファイルを生成する Altera 用
                make_coe_file(5,`PHY_ADD_MAX);//RTL SIM時にXilinx用 Coeファイルを生成する
        end
`endif

//memory の内容をAltera ROM初期化用に
//intel-hexフォーマットに変換する
//ファイル->rom.hex として出力する
//address 64K word max
//zxを含まないこと
task make_intel_hex(input [7:0]  bytes_per_word,
                                   input integer address_max);
                reg [9*8:1] str;
                integer fi;
                reg [15:0]   ad;
                integer bytes;
              reg [7:0] parity;
                begin
                        fi=$fopen("rom.hex","w");
                        
                        for (ad=0;ad <=address_max;ad=ad+1) begin
                                $sformat(str,":%2h%4h00",bytes_per_word,ad);
                                parity=bytes_per_word;
                                parity =parity + ad[8*1 +:8];//チェックサム
                                parity =parity + ad[0     +:8];//チェックサム
                                

                                $fwrite(fi,"%s",str);   
                                for (bytes=0;bytes<bytes_per_word;bytes=bytes+1) begin: byte_loop
                                        reg [7:0] byte;
                                        byte=micro_rom[ad][(bytes_per_word-1-bytes)*8 +:8]; 
                                        parity=parity+byte;//チェックサム
                                
                                        $fwrite(fi,"%2h",byte);
                                end
                                parity=~parity +8'h01;//チェックサム
                                $fwrite(fi,"%2h",parity);
                                $fwrite(fi,"\n");
                        end
                        $fdisplay(fi,":00000001FF");
                        $fclose(fi);
                        str="rom.ver";
                        //$convert_hex2ver("rom.hex",40,str);//チェックVeritak Uniqueファンクション
                end
endtask

//xilinx coe file
task make_coe_file(input [7:0]  bytes_per_word,
                                   input integer address_max);
        integer fi;
        integer ad;
        begin
                fi=$fopen("rom.coe","w");
                $fdisplay(fi,"memory_initialization_radix=16;");
                $fdisplay(fi,"memory_initialization_vector=");
                for (ad=0;ad <address_max;ad=ad+1) begin
                        $fdisplay(fi,"%h,",micro_rom[ad]);
                end
                $fdisplay(fi,"%h;",micro_rom[address_max]);
                $fclose(fi);
        end
endtask
                                  

function [39:0]romdata;
        input [15:0] address;
        case(address)
        //Commad  A_addr, B_addr, Write_addr, Jmp_cond, Reserved, IM
        0: `SET(P_OUT, 100);
        1: `SET(R0, 5);
        2: `MOV(R0, R1);
        3: `MOV(R1, R2);
        4: `MOV(R2, R3);
        5: `SUB(R1, R2, R0);
        6: `SUB(R3, R1, R3);
        7: `ADD(P_IN, P_OUT, P_OUT);
        8: `ADD(P_OUT, P_OUT, P_OUT);
        9: `SUB(P_IN, P_IN, R1);
        10: `SUB(P_OUT, P_IN, R2);
        11: `SET(R0, 2);
        12: `SET(R1, 8);
        13: `MUL(R0, R1, R3);
        14: `NOP;
        15: `MUL(R3, R3, R3);
        16: `ADD(P_OUT, R3, P_OUT);
        17: `SYNC;
        18: `JUMP(1000);

        // シフト・乗算
        1000: `SET(R0, 5);
        1001: `SET(R1, 2);
        1002: `ADD_LOOP(R0, R1, R0, 10);
        1003: `SHIFT_L_LOOP(R1, 5);
        1004: `SHIFT_R_LOOP(R1, 5);
        1005: `SUB_LOOP(R0, R1, R0, 10);
        1006: `SET(P_OUT, 5);
        1007: `MUL_LOOP(P_OUT, R1, P_OUT, 7);
        1008: `JUMP(1500);

        // 条件文
        1500: `SET(R0, 16);
        1501: `SET(R1, 2);
        1502: `SET(P_OUT, 0);
        1503: `SUB(R0, R1, R0);
        1504: `JUMP_IF_GT(R0, R1 ,1503);
          1505: `NOP ;//NOP挿入  ディレイドスロット
        // 条件文
        1506: `SET(R2, 16);
        1507: `SET(R3, 2);
        1508: `SET(P_OUT, 0);
        1509: `SUB(R2, R3, R2);
        1511: `JUMP_IF_GTE(R2, R3 ,1509);
          1512:`NOP;//NOP挿入 ディレイドスロット
        // 1010_1010_1010_1010
        // ビット反転
        1513: `SET(R0, 43690);
        1514: `NOT(R0, R1);

        // バレルシフタ
        1515: `SET(R0, 2);
        1516: `SET(R1, 7);
        1517: `SET(R2, 0);
        1518: `BSHIFT_L(R0, R1, R0);
        1519: `BSHIFT_R(R0, R1, R0);

        1520: `JUMP(0);

        default:  romdata = 0;
        endcase
endfunction
endmodule

なお、今回の変更で、
StaratixUは、88MHz
Spartan 3は、44MHz となりました。

合成後の遅延シミュレーション結果(青色VCD)です。
Spartan3


同様にStaratixUです。

シンクロナスROMのアーカイブです。


RAMの実装

FPGAのRAMをデータメモリとして使うことを考えます。ここでは、外部RAMを使うことは考慮せずに、FPGA内のRAMを使うことを考えます。
さてここでの問題は、同期RAMにあります。とりあえずWIZARDでRAMを作ってみましょう。
Single_PortRAMを生成します。


とりあえず、16ビット幅2Kワードです。


Q出力なしにします。このダイアグラムの通り、RAMにおいても前段にFFが入ってしまいます。従って、1CLOCK遅れてしまうことに注意してください。CPUから見てWRITEは、書きっぱなしなので1CLOCKでWRITEできますが、READは、1CLOCKでは返ってきません。ここでの設計は大きな分かれ目です。2CLOCKを許容できないとするか、見た目に1CLOCK R/Wとなるように、全体のブロックを考えなおすかという問題になります。後者は、CPU全体のパイプライン化によって達成することができ、パイプラインにより、動作速度を向上させることができます。しかし良い事ばかりではなく、しわ寄せは、JUMP命令に現れます。JUMPアドレスが静的に決まっているならば、先読みにより回避することができますが。JUMPアドレスが動作時にしか決まらない場合、即ち、条件ブランチや、条件CALLがある場合、パイプライン分の段数に応じたクロック数がかかってしまいます。 (1CLOCKでREAD/WRITEするパイプラインの例は、こちらのパイプラインの検討を見てください。パイプラインが深いとそれだけペナルティも大きくなります。)

今回は、簡単な設計を目指しているので、パイプラインは使わず、READのみ2CLOCK、その他は1CLOCKとする方針で設計します。






アドレシングモード
下記の二つを備えます。R0をインデックスとしたアドレシングです。

以下追加部です。



Altera/Xilinx のRAMを入れ込む前に模擬バージョンで動作確認しておきます。

//ALTERA RAMの模擬コード
        `define PHY_ADD_MAX_RAM (1024*2-1)//Dec.17.2006
                reg [15:0] ram [0:`PHY_ADD_MAX_RAM];
                reg [15:0] ram_address_latch;
                reg ram_write_enable_latch;
                reg [15:0] ram_data_latch,ram_output_data;
                 

//アドレスラッチ、データラッチ、ライトEnableラッチがある        
                always @(posedge clock) ram_address_latch<=ram_address;
                always @(posedge clock) ram_write_enable_latch<=ram_write_enable;
                always @(posedge clock) ram_data_latch<=ram_input_port;

                        always @* begin
                        ram_output_data=ram[ram_address_latch];
                end

                assign ram_output_port=ram_output_data;
 
                always @(negedge clock) begin//立下りで内部RAMに書き込み
                        if (ram_write_enable_latch) begin
                                ram[ram_address_latch] <=ram_data_latch;
                        end
                end


RAMをレジスタと同じように扱うので、レジスタアドレスのアドレス空間に追加します。

// レジスタアドレス
`define ZR              4'b0000
`define R0              4'b0001
`define R1              4'b0010
`define R2              4'b0011
`define R3              4'b0100
`define P_IN            4'b0101
`define P_OUT           4'b0110
`define RAM_PORT        4'b0111 //Dec.17.2006


ROMのビットフィールドにも追加します。
ビット18は、RAMのWriteEnableにします。
ビット17は、インデックスアドレシングか絶対アドレスかを決めているフラグです。

ROMワイヤーマップ
ビット位置  ビット幅  内容
39:35       5        命令
34:31       4        レジスタファイルAポートのアドレス
30:27       4        レジスタファイルBポートのアドレス
26:23       4        レジスタファイルライトポートのアドレス
22:19       4        ジャンプ比較条件
18:18                   1        RAM_WRITE_Enable //Dec.17.2006
17:17                   1        RAM アクセスの場合のR0をindex reg として使用するかどうかのフラグ.1で間接アドレシング//Dec.17.2006
16:16       1        loop_flag ループ処理するかどうかのフラグ、1でループ処理
                             
   

15:0        16       即値または、飛び先,または、RAMアドレス


RAMのアドレス生成部と、RAMのデータ入力部です。RAMのデータ入力は、ALUの出力をそのまま出します。この辺は、CISCぽいですね。

wire index_flag         =reserved[1];//Dec.17.2006              

wire [15:0] out_dataA, out_dataB;
wire [15:0] alu_data;

wire ram_write_enable    = reserved[2];//Dec.17.2006
wire [15:0] ram_output_port;//Dec.17.2006
wire [15:0] ram_input_port=alu_data;//Dec.17.2006
wire [15:0] index_reg;//Dec.17.2006
wire [15:0] ram_address=index_flag? immediate_data+index_reg :immediate_data;//Dec.17.2006

レジスタファイルからは、R0をindex_regとして引き出します。

// レジスタファイル
register_file register_file(.clock(clock),
                        .reset(reset),
                        .A_address(A_address),
                        .B_address(B_address),
                        .write_address(write_address),
                        .alu_data(alu_data),
                        .out_dataA(out_dataA),
                        .out_dataB(out_dataB),
                        .port_in_data(Aport_data),.ram_port(ram_output_port),.r0(index_reg),//Dec.17.2006
                        .port_out_data(port_out_data));


ifdef でRAMのインスタンスは、切り替えを行います。

`ifdef ALTERA

         altera_ram2kx16 altera_ram (
                .address(ram_address),
                .clock(clock),
                .data(ram_input_port),
                .wren(ram_write_enable),
                .q(ram_output_port)
        );

`elsif XILINX


         ram_xilinx2k16   xilinx_ram(
        .addr(ram_address),
        .clk(clock),
        .din(ram_input_port),
        .dout(ram_output_port),
        .we(ram_write_enable));





`else
        //ALTERA RAMの模擬コード
        `define PHY_ADD_MAX_RAM (1024*2-1)//Dec.17.2006
                reg [15:0] ram [0:`PHY_ADD_MAX_RAM];
                reg [15:0] ram_address_latch;
                reg ram_write_enable_latch;
                reg [15:0] ram_data_latch,ram_output_data;
                 

//アドレスラッチ、データラッチ、ライトEnableラッチがある        
                always @(posedge clock) ram_address_latch<=ram_address;
                always @(posedge clock) ram_write_enable_latch<=ram_write_enable;
                always @(posedge clock) ram_data_latch<=ram_input_port;

                        always @* begin
                        ram_output_data=ram[ram_address_latch];
                end

                assign ram_output_port=ram_output_data;
 
                always @(negedge clock) begin//立下りで内部RAMに書き込み
                        if (ram_write_enable_latch) begin
                                ram[ram_address_latch] <=ram_data_latch;
                        end
                end


`endif


レジスタファイルの記述です。

// レジスタファイル
// 記憶素子をレジスタの集まりとして、まとめてモジュール化。
// 同時に読み書きできるポートが3つ
// ライトポートが1つ、リードポートが2つ。
// R0をIndexRegとして使用する
module register_file(input  clock,
                     input  reset,
                     input  [3:0] A_address,
                     input  [3:0] B_address,
                     input  [3:0] write_address,
                     input  [15:0] alu_data, port_in_data,ram_port, //Dec.17.2006
                     output [15:0] out_dataA, out_dataB, port_out_data,
                                         output reg [15:0] r0
);

// CPUレジスタ
reg [15:0] p_out_reg,  r1, r2, r3;

// ポートアウトレジスタ
always @(posedge clock, posedge reset) begin
        if(reset)
        begin
                p_out_reg <= 0;
        end
        else if(write_address == `P_OUT)
        begin
                p_out_reg <= alu_data;
        end
        else
        begin
                p_out_reg <= p_out_reg;
        end
end


// 汎用レジスタR0
always @(posedge clock, posedge reset) begin
        if(reset)
        begin
                r0 <= 0;
        end
        else if(write_address == `R0)
        begin
                r0 <= alu_data;
        end
        else
        begin
                r0 <= r0;
        end
end


// 汎用レジスタR1
always @(posedge clock, posedge reset) begin
        if(reset)
        begin
                r1 <= 0;
        end
        else if(write_address == `R1)
        begin
                r1 <= alu_data;
        end
        else
        begin
                r1 <= r1;
        end
end


// 汎用レジスタR2
always @(posedge clock, posedge reset) begin
        if(reset)
        begin
                r2 <= 0;
        end
        else if(write_address == `R2)
        begin
                r2 <= alu_data;
        end
        else
        begin
                r2 <= r2;
        end
end


// 汎用レジスタR3
always @(posedge clock, posedge reset) begin
        if(reset)
        begin
                r3 <= 0;
        end
        else if(write_address == `R3)
        begin
                r3 <= alu_data;
        end
        else
        begin
                r3 <= r3;
        end
end


// Aポート出力(ALUへ)
assign out_dataA = (A_address==`P_OUT) ? p_out_reg :
                   (A_address==`P_IN)  ? port_in_data :
                      (A_address==`RAM_PORT) ? ram_port: //Dec.17.2006
                   (A_address==`R0) ? r0 :
                   (A_address==`R1) ? r1 :
                   (A_address==`R2) ? r2 :
                   (A_address==`R3) ? r3 : 16'b0;

// Bポート出力(ALUへ)
assign out_dataB = (B_address==`P_OUT) ? p_out_reg :
                   (B_address==`P_IN)  ? port_in_data :
                        (B_address==`RAM_PORT) ? ram_port: //Dec.17.2006
                   (B_address==`R0) ? r0 :
                   (B_address==`R1) ? r1 :
                   (B_address==`R2) ? r2 :
                   (B_address==`R3) ? r3 : 16'b0;

// 出力ポート(そのまま出力)
assign port_out_data = p_out_reg;

endmodule


RAMのR/Wに関するマイクロ命令の追加部です。
CPU->RAMへの書き込みは、2CLKかかるのですが、CPUからは見えません。ですので1CLKで次の命令を実行できます。
RAM->CPUの読み込みは、2CLKかかるので、二つの命令で構成します。最初の命令は、アドレス指定だけです。次の命令で、読み込み、つまりレジスタへの書き込みを行います。このとき、マイクロフィールドのアドレス部は遊んでいますので、次の読み込みアドレスを指定できます。つまりパイプライン的な記述が可能で、読み込み命令が連続する場合は、1CLKで済みます。

wire indirect_flag=Data[17];//Dec.17.2006
wire ram_write=Data[18];//Dec.17.2006
// レジスタアドレス
parameter [3:0] P_OUT=`P_OUT,//IOポート出力
                P_IN=`P_IN,  //IOポート入力
                   RAM_PORT=`RAM_PORT, //RAM ポート Dec.17.2006
                R0= `R0,
                R1= `R1,
                R2= `R2,
                R3= `R3,
                ZR= `ZR; // ゼロレジスタ (= ゼロGND)

// ループ処理 または、RAMライト処理
//ループ値はRAMアドレスと共用なのでRAMに関しループは不可
parameter [2:0] Reserved    = 3'b000,
                LoopEnabled = 3'b001,
                   RAM_write_enable=3'b100,     //Dec.17.2006  ビット2をRAM WriteEnableに使用
                   RAM_read_port_enable_with_index=3'b011,//Dec.17.2006 ビット1ー> INDEX付き
                   RAM_write_enable_with_index=3'b111;  //Dec.17.2006  ビット2をRAM WriteEnableに使用ビット1−>INDEX付き


//RAMへライト  RAM[address]=source_reg Dec.17.2006 ライトは2クロックかかるがCPUからは見えない
`define MOV_TO_RAM(source_reg,address)  romdata={Add,source_reg,ZR,ZR,Eq,RAM_write_enable,16'd address}

// RAM[address+r0]=source_reg
`define MOV_TO_RAM_W_I(source_reg,address)  romdata={Add,source_reg,ZR,ZR,Eq,RAM_write_enable_with_index,16'd address}

//RAMからリード Dec.17.2006
//READ は2クロックかかるので、2マイクロ命令組で使う 1クロック目でアドレス指定、2クロック目でレジスタへのライト
// dest_reg=RAM[address]
`define MOV_FROM_RAM_pre(address)           romdata={Add,ZR,ZR,ZR,Eq,Reserved,16'd address}//アドレス指定,この間他のレジスタ演算を行っても良い
`define MOV_FROM_RAM(dest_reg,address)      romdata={Add,RAM_PORT,ZR,dest_reg,Eq,Reserved,16'd address}//レジスタへ書き込み

//dest_reg=RAM[address+r0]
`define MOV_FROM_RAM_W_I_pre(address)       romdata={Add,ZR,ZR,ZR,Eq,RAM_read_port_enable_with_index,16'd address}//アドレス指定,この間他のレジスタ演算を行っても良い
`define MOV_FROM_RAM_W_I(dest_reg,address)  romdata={Add,RAM_PORT,ZR,dest_reg,Eq,RAM_read_port_enable_with_index,16'd address}//レジスタへ書き込み



マイクロコードの例です。単発読み出しでは、xx_pre命令でアドレス指定した後に読み出す命令が必要ですが、連続読み出しでは、xx_pre命令が不要になっています。

function [39:0]romdata;
        input [15:0] address;
        case(address)
        //Commad  A_addr, B_addr, Write_addr, Jmp_cond, Reserved, IM
//simple test
         0:`SET(R0,1000);
         1:`SET(R1,2000);
         2:`SET(R2,3000);
         3:`MOV_TO_RAM(R0,0);
         4:`MOV_TO_RAM(R1,1);
         5:`MOV_TO_RAM(R2,2);

         6:`MOV_FROM_RAM_pre(0);
         7:`MOV_FROM_RAM(P_OUT,0);

         8:`MOV_FROM_RAM_pre(1);
         9:`MOV_FROM_RAM(P_OUT,1);

         10:`MOV_FROM_RAM_pre(2);
         11:`MOV_FROM_RAM(P_OUT,2);

//index register write test
        12:`SET(R3,2048);//for (r0=0;r0<2048;r0++){
        13:`SET(R1,1);//r1=1;
        14:`SET(R0,0);//r0=0;
        
  15:`MOV_TO_RAM_W_I(R0,0);//mem[r0+0]=r0
        16:`ADD(R0,R1,R0);// r0++;
        17:`JUMP_IF_LT(R0, R3 ,15);// }
//index register read test
        18:`SET(R0,0);//r0=0;
        19:`SET(R3,1000);
        20:`SET(R1,4);
  21:`MOV_FROM_RAM_W_I_pre(1048);//ram_address=r0+1048
  22:`MOV_FROM_RAM_W_I(P_OUT,1049);//P_OUT=mem[ram_address] ram_address2=r0+1049
  23:`MOV_FROM_RAM_W_I(P_OUT,1050);//P_OUT=mem[ram_addres2]
  24:`MOV_FROM_RAM_W_I(P_OUT,1051);//.. software pipeline
  25:`MOV_FROM_RAM_W_I(P_OUT,1052);//.. software pipeline
        26:`ADD(R0,R1,R0);// r0 +=4;
        27:`JUMP_IF_LT(R0, R3 ,21);// }

        45:`JUMP(0);    
..

RTL Sim

RAM Writeの様子です。1CLK毎に書けます。



Readの様子です。




XILINX RTL SIMと遅延SIM
遅延SIMは、PortOUTの出力をVCDで見ています。RTLと一致しています。





Altera RTL SIMと遅延SIM
同様にAltera Versionです。



RAM版のダウンロード(Generic,Altera,Xilinx、RTL/遅延SIM)

次の拡張案

 とりあえずRAMの実装は終わったのですが、普通のCPUでは、やっぱりあまり面白くないので、アーキテクチャとしてDSPを志向してみることにしました。FIRフィルタ、FFT,ダウンコンバータ、サーボ演算等で遊べたらよいな?と思います。リソース使用量はあまり気にしないで、できるだけ簡単な設計を心がけたいと思います。コンパイラ作成では、日本ではあまりなじみがないと思われるAntlrというツールを紹介する予定です。構文解析は、Flex/Bisonや、Boost::spiritがよく知られていますが、Antlrもそのようなツールです。このツールは、C++や、JAVA,Pythonを出力します。


DSPライクなアーキテクチャの検討

こういう石にしたい、というのを思いつくままに列挙してみました。

最近のFPGAのアーキテクチャをうまく生かすには?


プロセッサを高速化するには、垂直方向の高速化、すなわちDeep Pipelineが有効です。動的なジャンプがないとしたら、いくらでもパイプライン化で高速にすることができます。問題はジャンプであるということは、上で見ました。ところで、FFTや、FIRは、FOR LOOPによる演算になりますが、これらは、ジャンプ箇所とループ数が実行時に変化することはないので、プログラムによるHW化をすることができます。つまりDeep Pipelineを維持しながら、演算ループではJUMPペナルティが全くない処理をすることは可能です。ただ、予測分岐とか面倒なことはしないので、ジャンプ命令自体は4CLK位Delayed Slot(ジャンプ命令の後も実行してしまう)が発生してもよし、とします。また、最近のFPGAは、乗算器を、数個以上積んでいるのが普通で、遊んでいるのも勿体ない話です。これには水平方向の並列演算が有効でしょう。FPGAでは、シングルALUにこだわる理由はありません。

しかしながら、思いついた仕様は、ハード的に飛躍し過ぎるので、

テーマは、「Verilogでお手軽CPU」に戻ります

まずは、普通にTINY Cコンパイラが走るCPUを作ることを目標にします。
そこで、普通のCPUの仕様を考えてみます。

外部バスに接続可能なCPUの製作

テーマが紆余曲折していますが、事情があり、YACCベースでの32ビットCPUハード製作に変更します。TINY Cコンパイラの製作はハード製作後に行います。ごめんなさい。旧YACCとの違いは、

第一に

第二の目標は

です。YACCは、ドライストーンを良く見せることを第一義に設計したために、駆動周波数は速いのですが、外部RAMには接続できず汎用的とは言えません。そこで、多少速度を犠牲にして汎用的な設計を行ってみます。

最終目標は、自前Cコンパイラを使い、OS上で自前のCPUを走らせることです。道は遠く長いです。そこで、先人の知見を辿って見ることにしましょう。RISCの教科書とも言える設計例として、(Z80を上回る量の)世界中に様々なソースがあります。またこの他に、CPUシミュレータ、教育用OSも沢山あります。ちょっと集めただけで以下のように沢山のCPUソースがあります。しかし、見ただけでは、機械語からCソースを想像するようなものでなにも分からないでしょう。( そこに本稿の存在意義があります。)

ニックネーム 言語 特徴
YACC Verilog 拙作。5段パイプライン。
MIPSマイコンを作ろう Verilog リソース使用量はFZ80の1/2になったとのこと。
Plasma VHDL 3-5段パイプライン。自前RTOSでWEBサーバのデモあり
Yellow star 回路図 5段パイプライン。TLBを含む設計。
Ucore Verilog 5段パイプライン。
電通大 Verilog
九工大 VHDL ソースを見るには登録が必要(登録しました)
Minimips VHDL


まずは、要件を書き出してみます。


教科書は、


で、これは必須です。細かい命令セットの中身は東芝のTX-39ファミリーを見ればよいでしょう。 


パイプライン割付検討

本物のLINUXを走らせるためにはMMUなしでは走