東工大 情報工学科 情報実験第四 「組み込みシステム」& MieruEMBシステム
Verilog HDLに関する補足説明 (2011-10-06版)


ここでは,MieruEMB System V1.0 (2011-10-05版)のVerilog HDLへのコメントを掲載しています.

MieruEMB System V1.0 Verilog HDLのほとんどは,MieruPC2010 Version 1.4.0のコードを利用しています. MieruEMB System V1.0では,MieruPCのトップモジュールにあたるMieruPC.v をMieruEMB.v として名前を変更し, かつ内容に修正を加えています. ミニLCDのためのコントローラ minilcd.v が追加されています.また,I/Oに関するgpio.v に修正を加えています. これらを除いて,MieruPC2010 Version 1.4.0のコードを利用しています.

file name: define.v
    1  /******************************************************************************************************/
    2  /* MieruPC2010 Version 1.4.0                                                      ArchLab. TOKYO TECH */
    3  /******************************************************************************************************/
    4  
    5  /* Clock Interval Definition                                                                          */
    6  /******************************************************************************************************/
    7  `define DCM_CLKFX_MULTIPLY  7      // you can modify this value, (40MHz / 8) * 7 = 35MHz
    8  `define DCM_CLKFX_DIVIDE    8      // CLKFX_DIVIDE must be 1-32, CLKFX_MULTIPLY must be 2-32
    9  
   10  /******************************************************************************************************/
   11  `define SYS_CLOCK 40                                                           // 27MHz system clock
   12  `define TIMER_CNT_WAIT (`SYS_CLOCK/`DCM_CLKFX_DIVIDE*`DCM_CLKFX_MULTIPLY*1000) // for 1kHz timer
   13  `define CP0_CNT_WAIT   (`SYS_CLOCK/`DCM_CLKFX_DIVIDE*`DCM_CLKFX_MULTIPLY*10)   // for 100kHz CP0 timer
   14  `define LCD_WAIT       (`SYS_CLOCK/`DCM_CLKFX_DIVIDE*`DCM_CLKFX_MULTIPLY)      // for LCD 1Mbps
   15  
   16  /* Memory Controller / Initializer Constants                                                          */
   17  /******************************************************************************************************/
   18  `define ADDR 23:0                  // Address Range 24bit
   19  `define JADDR 21:0                 // Address Range for J format (ADDR - 2bit, 26bit max.)
   20  `define HALT_ADDR 24'h0000         // Branch Address to Halt
   21  
   22  `define MMC_START_BLOCK   81       // MMC Image Start Physical Address: 0x0000a200
   23  `define MMC_LAST_BLOCK  1104       // MMC Image End   Physical Address: 0x00089fff

define.vの最初の部分です. 7行目のDCM_CLKFX_MULTIPLYによって,システムの動作周波数を変更できます. 最初の設定では,35MHzで動作するようにしています. 例えば,45MHzに変更するためには,「`define DCM_CLKFX_MULTIPLY 9」とします. 50MHzに変更するためには,「`define DCM_CLKFX_MULTIPLY 10」とします. ただし,クロックを上げすぎると回路のタイミング制約を満たせないケースが生じるので, ISEのレポートを確認するようにしてください.

22行目と23行目のパラメータを変更することで,MMCカードから読み出すスタートブロックの番号と最後のブロック の番号を変更できます.標準は,81番目のブロックから512KBを読み出して, SRAMに格納する設定となっています.

file name: define.v
   25  /* Instruction Processing Stage definition                                                            */
   26  /******************************************************************************************************/
   27  `define CPU_START 5'h00
   28  `define CPU_IF0   5'h01
   29  `define CPU_IF1   5'h02
   30  `define CPU_IF2   5'h03
   31  `define CPU_IF3   5'h04
   32  `define CPU_IF4   5'h05
   33  `define CPU_ID    5'h06
   34  `define CPU_RF    5'h07
   35  `define CPU_EX    5'h08
   36  `define CPU_MA0   5'h09
   37  `define CPU_MA1   5'h0a
   38  `define CPU_MA2   5'h0b
   39  `define CPU_MA3   5'h0c
   40  `define CPU_MA4   5'h0d
   41  `define CPU_WB    5'h0e
   42  `define CPU_WCNT  5'h0f
   43  `define CPU_HALT  5'h10
   44  `define CPU_ERROR 5'h11

プロセッサMIPSCOREの命令処理のステージ定義が記述されています. この版に含まれるプロセッサは,(パイプライン処理をおこなわない)マルチサイクル版です.

ロード命令の場合には,IF0, IF1, IF2, IF3, IF4, ID, RF, EX, MA0, MA1, MA2, MA3, MA4, WB というステートを経由して, 1つの命令を処理するので,ロード命令の処理には14サイクルを必要とします. データメモリにアクセスしない加算命令では, IF0, IF1, IF2, IF3, IF4, ID, RF, EX, WB というステートを経由して,9サイクルを必要とします. このように,標準のプロセッサとしては(拡張や改良の余地のある)とても遅いものを採用しています.

file name: define.v
   46  /* Instruction Attribute Definition                                                                   */
   47  /******************************************************************************************************/
   48  `define READ_NONE            19'h00000
   49  `define READ_RS              19'h00000
   50  `define READ_RT              19'h00000
   51  `define READ_RD              19'h00000
   52  `define READ_HI              19'h00000
   53  `define READ_LO              19'h00000
   54  `define READ_HILO            19'h00000
   55  `define WRITE_NONE           19'h00000
   56  `define WRITE_RS             19'h00000
   57  `define WRITE_RT             19'h00001
   58  `define WRITE_RD             19'h00002
   59  `define WRITE_HI             19'h00004
   60  `define WRITE_LO             19'h00008
   61  `define WRITE_HILO           19'h0000c
   62  `define WRITE_RD_COND        19'h00010
   63  `define WRITE_RRA            19'h00020
   64  `define LOAD_1B              19'h00040
   65  `define LOAD_2B              19'h00080
   66  `define LOAD_4B_ALIGN        19'h00100
   67  `define LOAD_4B_UNALIGN      19'h00200
   68  `define LOAD_4B              19'h00300
   69  `define LOAD_ANY             19'h003c0
   70  `define STORE_1B             19'h00400
   71  `define STORE_2B             19'h00800
   72  `define STORE_4B_ALIGN       19'h01000
   73  `define STORE_4B_UNALIGN     19'h02000
   74  `define STORE_4B             19'h03000
   75  `define STORE_ANY            19'h03c00
   76  `define LDST                 19'h03fc0
   77  `define LOADSTORE_4B_UNALIGN 19'h02200
   78  `define BRANCH               19'h04000
   79  `define BRANCH_LIKELY        19'h08000
   80  `define READ_CP0             19'h00000
   81  `define WRITE_CP0            19'h10000
   82  `define SYSTEM_CALL          19'h20000
   83  `define BRANCH_ERET          19'h40000
   84  
   85  /******************************************************************************************************

define.vの最後の部分には,命令の特徴示す定義が記述されています. これらの定義は,MipsCore.v で利用されます.


トップモジュールを持つMieruEMB.vを見ましょう.

file name: MieruEMB.v
    1  /******************************************************************************************************/
    2  /* MieruEMB System Version 1.0 since 2011-09                                      ArchLab. TOKYO TECH */
    3  /*  See the README file on the parent directory for license information.                              */
    4  /******************************************************************************************************/
    5  /******************************************************************************************************/
    6  /* MieruPC2010 Version 1.4.0, 2011-10-05: Top Level Module                        ArchLab. TOKYO TECH */
    7  /******************************************************************************************************/
    8  `include "../rtl/define.v"
    9  `default_nettype none

多くのファイルでは,先にみた,define.vがインクルードされます(8行目). また,定義していないwireを利用するとエラーとなるように,`default_nettype none(9行目)を指定します.

file name: MieruEMB.v
   11  /******************************************************************************************************/
   12  module MieruEMB(CLK, SW, LCD, ULED, GPIO, GPI, MMC_CS_X, MMC_DIN, MMC_SCLK, MMC_DOUT, 
   13                  SRAM_A, SRAM_D, SRAM_OE_X, SRAM_WE_X,
   14                  LCD_CS0, LCD_CD, LCD_WR, LCD_RSTB, LCD_D);
   15      input             CLK, GPI;     // input clock & general purpose input 
   16      input [2:0]       SW;           // input switch
   17      input             MMC_DOUT;     // MMC Data Output
   18      output            LCD;          // LCD Serial Output
   19      output            MMC_CS_X;     // MMC Chip Select
   20      output            MMC_DIN;      // MMC Data Input
   21      output            MMC_SCLK;     // MMC Clock
   22      output [2:0]      ULED;         // User LED
   23      output reg [18:0] SRAM_A;       // SRAM Address
   24      output reg        SRAM_OE_X;    // SRAM Output Enable
   25      output            SRAM_WE_X;    // SRAM Write Enable
   26      inout [1:0]       GPIO;         // General Purpose I/O
   27      inout [7:0]       SRAM_D;       // SRAM Data Bus
   28  
   29      output        LCD_CS0, LCD_CD, LCD_WR, LCD_RSTB;
   30      output [7:0]  LCD_D;
   31      
   32      wire              FCLK, RST_OX;                  // system clock and reset signal
   33      wire              WE, CORE_WE, MEM_WE, PLOAD_WE; // Write Enable signals
   34      wire              PLOAD_DONE;                    // Program load is done
   35      wire [`ADDR]      ADDR, PLOAD_ADDR, CORE_ADDR;   // memory address
   36      wire [7:0]        DATA_IN, DATA_OUT, CORE_DATA, PLOAD_DATA; // data
   37  
   38      CLKGEN clkgen(.CLK_I(CLK), .RST_X_I(SW[0] | SW[1] | SW[2]), .CLK_O(FCLK), .RST_X_O(RST_OX));
   39      
   40      MIPSCORE core(.CLK(FCLK), .RST_X(RST_OX & PLOAD_DONE), 
   41                    .ADDR(CORE_ADDR), .DATA_IN(DATA_IN), .DATA_OUT(CORE_DATA), .WE(CORE_WE));
   42  
   43      PROGLOADER pload(.CLK(FCLK), .RST_X(RST_OX), 
   44                       .DATA_IN(DATA_IN), 
   45                       .ADDR(PLOAD_ADDR), .DATA_OUT(PLOAD_DATA), .WE(PLOAD_WE), .DONE(PLOAD_DONE));
   46                     
   47      IOCON iocon(.CLK(FCLK), .RST_X(RST_OX), 
   48                  .LCD(LCD), .GPIO(GPIO), .SW(SW), .GPI(GPI),
   49                  .ADDR(ADDR), .DATA_IN(DATA_IN), .DATA_OUT(DATA_OUT), 
   50                  .WE(WE), .MEM_WE(MEM_WE), .MEM_Q(SRAM_D),
   51                  .MMC_CSX(MMC_CS_X), .MMC_DIN(MMC_DIN), .MMC_DOUT(MMC_DOUT), .MMC_SCLK(MMC_SCLK));

MieruEMB.vの前半です. 38行目のCLKGENは,Xilinx FPGAが持つDCM(デジタルクロックマネージャ)を用いて, define.v にて指定したクロック信号FCLKを生成します.デフォルトの設定では,これは35MHzです. MieruEMB.vではクロック信号としてFCLKのみを利用します. CLKGENを除いて,CLKを用いてはいけません. また,CLKGENはリセット信号 RST_OXを生成します.

MieruEMBシステムでは,3つのスイッチを同時に押すことでリセットとなります. スイッチが押されるとハードウェア的にはGNDレベルになるので, 3つのスイッチを同時に押すことで SW[0], SW[1], SW[2] が全て0になります. (SW[0] | SW[1] | SW[2]) を入力とすることで,全てのスイッチが押されているときだけ0となる入力信号 を生成しています.

40行,41行ではプロセッサであるMIPSCOREのモジュールを生成しています. SDカードからプログラムを読み出してSRAMにコピーするまでは,プロセッサは動作させません. このため,40行目で,(RST_OX & PLOAD_DONE) をリセットの入力としています. PLOAD_DONEはプログラムのコピーが終わるまでは0なので,この間はリセット信号が有効となります.

43行から45行では,SDカードからプログラムを読み出してSRAMにコピーするモジュールを生成しています. 47行から51行では,メモリマップドI/Oの処理などを含むI/Oコントローラのモジュールを生成しています.

file name: MieruEMB.v
   52  
   53      reg lcd_reg;
   54      always @(posedge FCLK) lcd_reg <= ~LCD;
   55      
   56      assign ULED[0]  = (!PLOAD_DONE) ? 1          : lcd_reg;
   57      assign ADDR     = (!PLOAD_DONE) ? PLOAD_ADDR : CORE_ADDR;
   58      assign DATA_OUT = (!PLOAD_DONE) ? PLOAD_DATA : CORE_DATA;
   59      assign WE       = (!PLOAD_DONE) ? PLOAD_WE   : CORE_WE;
   60      
   61      reg [7:0] D_KEEP;
   62      always @(posedge FCLK or negedge RST_OX) begin
   63           if (!RST_OX) begin
   64               SRAM_A    <= 0;
   65               D_KEEP    <= 0;
   66               SRAM_OE_X <= 0;
   67           end else begin
   68               SRAM_A    <= ADDR[18:0];
   69               D_KEEP    <= DATA_OUT; 
   70               SRAM_OE_X <= MEM_WE;
   71           end
   72      end
   73  
   74      assign SRAM_D = (SRAM_OE_X) ? D_KEEP : 8'hzz;
   75      assign SRAM_WE_X = ~FCLK | ~SRAM_OE_X;
   76  
   77      reg [24:0] cnt_t;
   78      always @(posedge FCLK) cnt_t <= cnt_t + 1;
   79      assign ULED[2] = cnt_t[24];
   80      assign ULED[1] = (~SW[0]) | (~SW[1]) | (~SW[2]);
   81      
   82      minilcd_con lcdcon (FCLK, (RST_OX & PLOAD_DONE), CORE_ADDR[13:0], CORE_DATA, 
   83                          (CORE_WE & CORE_ADDR[23:16]==8'h90),
   84                          LCD_CS0, LCD_CD, LCD_RSTB, LCD_D, LCD_WR);    
   85  endmodule
   86  
   87  /******************************************************************************************************/

MieruEMB.vの後半です. 53行と54行は,シリアルLCDの出力をレジスタlcd_regに格納しています. MieruEMBでは,シリアルLCDは利用しないので,lcd_reg の値は0となります. 56行目では,ULED[0]すなわち基板上のD2(発光ダイオード)の出力を記述しています. ULED[0]はプログラムロードが終わっていない場合に1としているので, プログラムロードの間のみ点灯します.

57行目から59行目ではSRAMにアクセスするための信号を選択しています. プログラムロードの間は,モジュールploadの信号を用い, そうでない場合にはプロセッサの信号(CORE_ADDRなど)を用います.

61行から75行では,SRAMへの信号を生成しています. SRAM_Dは入出力線なので,SRAMがデータを出している時には, FPGAからの出力をハイインピーダンス(z)にしないといけません.

77行から79行では,ULED[2]の信号を生成しています. これは,24ビットカウンタの最上位ビットを割り当てているので,基板上のD4(発光ダイオード)は一定間隔で 点滅します. 80行は,ULED[1]の記述です.スイッチのどれかが押された時にD3が光ります.

82行から84行はミニLCDのコントローラであるlcdconを生成しています. このモジュールはMieruEMBに特有のもので,minilcd.vに記述されています.


ミニLCDのコントローラ minilcd.vを見ましょう.

file name: minilcd.v
    9  module minilcd_con(CLK, RST_X, VRAM_ADDR, VRAM_DATA, VRAM_WE, 
   10                     LCD_CS0, LCD_CD, LCD_RSTB, LCD_D, LCD_WR);
   11      input            CLK, RST_X;
   12      input [13:0]     VRAM_ADDR;
   13      input [7:0]      VRAM_DATA;
   14      input            VRAM_WE;
   15      output           LCD_WR;    
   16      output reg       LCD_CS0, LCD_CD, LCD_RSTB;
   17      output reg [7:0] LCD_D;
   18      
   19      reg          init;
   20      reg [14:0]   cmdcnt;
   21      reg [2:0]    writecnt;
   22      reg [23:0]   waitcnt;
   23      wire [15:0]  dout;
   24      wire [7:0]   D;
   25      
   26      assign LCD_WR = ~writecnt[2];
   27     
   28      minilcd_initmem mlcdmem(CLK, cmdcnt[5:0], dout);
   29  
   30      minilcd_vram vram(.CLK(CLK), .DIN(VRAM_DATA[3:0]), .WADDR(VRAM_ADDR), .WE(VRAM_WE), 
   31                        .DOUT(D), .RADDR(cmdcnt[14:1]));

モジュール minilcd_conの前半です. minilcd_conは,FPGAの内部にビデオRAM (VRAM, 30行と31行)を持ちます. これは,4bit x (128x128 エントリ) で,8KBのメモリです. コントローラはこのVRAMの値を読み出しながら,適切なタイミングでミニLCDに表示していきます. 一方,12行から14行で宣言しているVRAM_ADDR, VRAM_DATA, VRAM_WEを用いて,VRAMに値を書き込みます. VRAMは読み出しと書き込みが干渉しないように,読み出しポートを1つ,書き込みポートを1つもつ デュアルポートメモリとして実現されます.

このモジュールを利用するためには, このモジュールの出力LCD_CS0, LCD_CD, LCD_RSTB, LCD_D, LCD_WRをトップモジュールの同じ信号に接続します. 次に,これを生成するモジュールの中から, VRAM_ADDR, VRAM_DATA, VRAM_WEを制御します. VRAMに色情報を書き込むと,書き込まれたアドレスのピクセルにその色が表示されるので, ピクセルのアドレスを VRAM_ADDR,色情報をVRAM_DATAとして,VRAM_WEを1とすることで, VRAMに書き込まれます.1クロックで1ピクセルの色データを書き込むことが可能です.

モジュールminilcd_initmem(28行)は,ミニLCDの初期化のためのコマンドが格納されたROMです.

file name: minilcd.v
   33      always @(posedge CLK or negedge RST_X) begin
   34        if (!RST_X) begin
   35          LCD_CS0  <= 0;
   36          LCD_CD   <= 0;
   37          LCD_RSTB <= 0;
   38          LCD_D    <= 0;
   39          init     <= 1;
   40          cmdcnt   <= 0;
   41          writecnt <= 0;
   42          waitcnt  <= 0;
   43        end 
   44        else if (writecnt != 0) writecnt <= writecnt - 1;
   45        else if (waitcnt  != 0) waitcnt  <= waitcnt  - 1;
   46        else if (init == 1) begin
   47          waitcnt  <= {dout[15:11], 18'h00000};
   48          LCD_RSTB <= ~dout[10];
   49          LCD_CS0  <= dout[9];
   50          LCD_CD   <= dout[8];
   51          LCD_D    <= dout[7:0];
   52          init     <= (cmdcnt != 'h3a);
   53          cmdcnt   <= (cmdcnt == 'h3a) ? 0 : cmdcnt + 1;
   54          writecnt <= 7;
   55        end 
   56        else begin // after initialized
   57          LCD_RSTB <= 1;
   58          LCD_CS0  <= 0;
   59          LCD_CD   <= 1;
   60          LCD_D    <= (cmdcnt[0]) ? // convert 3 bit to 16 bit color
   61                       {D[1], D[0], D[0], D[0], D[0], D[0], D[0], D[0]} :
   62                       {D[2], D[2], D[2], D[2], D[1], D[1], D[1], D[1]} ;
   63          cmdcnt   <= (cmdcnt == 'h7fff) ? 0 : cmdcnt + 1;
   64          writecnt <= 7;
   65        end
   66      end
   67  endmodule

モジュール minilcd_conの後半です. ミニLCDは16ビットカラーを表示できるので,8bitを2サイクルに分けて送信します. ただし,VRAMの容量の問題から,1ピクセルを3ビット(R, G, Bそれぞれ1ビット)の8色として表現するので, 60行から62行で,3ビットの色を16ビットに変換しています.

file name: minilcd.v
   69  /******************************************************************************************************/
   70  /* MiniLCD Video Memory 8KB (128x128=16K pixel, 4bit per pixel = 8KB)                                 */
   71  /******************************************************************************************************/
   72  module minilcd_vram(CLK, DIN, DOUT, RADDR, WADDR, WE);
   73      input        CLK, WE;
   74      input [3:0]  DIN;
   75      input [13:0] RADDR, WADDR;
   76      output reg [3:0] DOUT;
   77      
   78      reg [3:0] mem [16383:0]; /* 4bit x 16K pixel = 8KB VRAM */
   79  
   80      always @(posedge CLK) begin
   81          if (WE) mem[WADDR] <= DIN;
   82          DOUT <= mem[RADDR];
   83      end
   84  endmodule
   85  

モジュールminilcd_vramです. VRAMは読み出しと書き込みが干渉しないように,読み出しポートを1つ,書き込みポートを1つもつ デュアルポートメモリとして実現されます. それぞれのピクセルには4ビットが割り当てられているので,16色への拡張は可能です.

file name: minilcd.v
   86  /******************************************************************************************************/
   87  /* MiniLCD initialize memory, [WAIT(5)] [RST] [CSX] [DC] [DATA(8)]                                    */
   88  /******************************************************************************************************/
   89  module minilcd_initmem(CLK, ADDR, DATA);
   90      input             CLK;
   91      input       [5:0] ADDR;
   92      output reg [15:0] DATA;
   93  
   94      always @(posedge CLK) begin
   95          case (ADDR)
   96          'h00: DATA = 16'h1200;
   97          'h01: DATA = 16'h1600; // Hardware Reset
   98          'h02: DATA = 16'h6200;
   99          'h03: DATA = 16'h2801; // Software Reset
  100          'h04: DATA = 16'ha011; // Sleep Out
  101          'h05: DATA = 16'h00ff; // VCOM 4 Level Control
  102          'h06: DATA = 16'h0140; //   - TC2 4clk, TC3 3clk delay
  
  158          'h3e: DATA = 16'h0000;
  159          'h3f: DATA = 16'h0000;
  160          endcase
  161      end     
  162  endmodule

モジュールminilcd_initmemです.初期化コマンドが格納されているROMです. コマンドが並んでいるだけなので,途中は省略しています.


いよいよプロセッサMipsCore.vを見ていきましょう.

file name: MipsCore.v
    8  /******************************************************************************************************/
    9  /* 32bit-32cycle mutiplier (signed or unsigned)                                                       */
   10  /******************************************************************************************************/
   11  module MULUNIT(CLK, RST_X, INIT, SIGNED, A, B, RSLT, BUSY);
   12      input         CLK;
   13      input         RST_X;
   14      input         INIT;
   15      input         SIGNED;
   16      input  [31:0] A, B;
   17      output [63:0] RSLT;
   18      output        BUSY;
   19  
   20      wire [31:0]   uint_a, uint_b;
   21      wire [63:0]   uint_rslt;
   22      reg           sign;
   23  
   24      MULUNITCORE mulcore(.CLK(CLK), .RST_X(RST_X), .INIT(INIT),
   25                          .A(uint_a), .B(uint_b), .RSLT(uint_rslt), .BUSY(BUSY));
   26  
   27      assign uint_a = (SIGNED & A[31])? ~A + 1 : A;
   28      assign uint_b = (SIGNED & B[31])? ~B + 1 : B;
   29      assign RSLT   = (SIGNED & sign)? ~uint_rslt + 1 : uint_rslt;
   30  
   31      always @( posedge CLK or negedge RST_X )
   32        if( !RST_X ) sign <= 0;
   33        else         sign <= (INIT)? A[31]^B[31] : sign;
   34  
   35  endmodule

MipsCore.vの先頭には,プロセッサコアが利用する幾つかのモジュールが記述されています.

32サイクルで32ビットの乗算をおこなうモジュールMULUNITです. 内部では,次に示すモジュールMULUNITCOREを利用しています.

file name: MipsCore.v
   37  /******************************************************************************************************/
   38  module MULUNITCORE(CLK, RST_X, INIT, A, B, RSLT, BUSY);
   39      input         CLK;
   40      input         RST_X;
   41      input         INIT;
   42      input  [31:0] A, B;
   43      output [63:0] RSLT;
   44      output        BUSY;
   45  
   46      reg [31:0]    multiplicand;
   47      reg [5:0]     count;
   48      wire [32:0]   sum;
   49      reg [63:0]    RSLT;
   50  
   51      assign BUSY   = (count < 32);
   52      assign sum    = RSLT[63:32] + multiplicand;
   53  
   54      always @( posedge CLK or negedge RST_X ) begin
   55          if( !RST_X ) begin
   56              multiplicand   <= 0;
   57              RSLT           <= 0;
   58              count          <= 0;
   59          end else if( INIT )begin
   60              multiplicand   <= A;
   61              RSLT           <= {32'h0, B};
   62              count          <= 0;
   63          end else begin
   64              multiplicand   <= multiplicand;
   65              RSLT           <= (RSLT[0]) ? {sum, RSLT[31:1]} : {1'h0, RSLT[63:1]};
   66              count          <= count + 1;
   67          end
   68      end
   69  endmodule
   70  

モジュールMULUNITCOREです. 同様に除算のためのモジュールDIVUNIT, DIVUNITCOREがありますが,省略します.

file name: MipsCore.v
  142  /******************************************************************************************************/
  143  /* 32bitx32 2R/W General Purpose Registers                                                            */
  144  /******************************************************************************************************/
  145  module GPR(CLK, REGNUM0, REGNUM1, DIN0, DIN1, WE0, WE1, DOUT0, DOUT1);
  146      input         CLK;
  147      input [4:0]   REGNUM0, REGNUM1;
  148      input [31:0]  DIN0, DIN1;
  149      input         WE0, WE1;
  150      output [31:0] DOUT0, DOUT1;
  151  
  152      reg [31:0]    r[0:31];
  153      reg [31:0]    DOUT0, DOUT1;
  154      
  155      always @(posedge CLK) DOUT0 <= (REGNUM0==0) ? 0 : r[REGNUM0];
  156      always @(posedge CLK) DOUT1 <= (REGNUM1==0) ? 0 : r[REGNUM1];
  157      always @(posedge CLK) if(WE0) r[REGNUM0] <= DIN0;
  158      always @(posedge CLK) if(WE1) r[REGNUM1] <= DIN1; 
  159  endmodule

32ビット,32本のレジスタを持つレジスタファイルを実現するモジュールGPRです. レジスタ番号0(Zeroレジスタ)の読み出しはゼロになるように調整しています.

file name: MipsCore.v
  161  /******************************************************************************************************/
  162  /* MipsCore: 32bit-MIPS multicycle processor                                                          */
  163  /******************************************************************************************************/
  164  module MIPSCORE(CLK, RST_X, ADDR, DATA_IN, DATA_OUT, WE);
  165      input              CLK, RST_X;
  166      input  [7:0]       DATA_IN;
  167      output [7:0]       DATA_OUT;
  168      output [`ADDR]     ADDR;
  169      output             WE;
  170      /**************************************************************************************************/
  171      /***** internal register                                                                      *****/
  172      reg [4:0]          state;
  173      reg [`ADDR]        pc, delay_npc, inst_pc, inst_npc;
  174      reg [`ADDR]        inst_eaddr;
  175      reg                exec_delay;
  176      reg [31:0]         inst_ir;
  177      reg [4:0]          inst_rt, inst_rd, inst_dst;
  178      reg [4:0]          inst_shamt;
  179      reg [15:0]         inst_imm;
  180      reg [`JADDR]       inst_addr;
  181      reg [18:0]         inst_attr;
  182      reg [6:0]          inst_op;
  183      reg [31:0]         inst_rrs, inst_rrt, inst_cpr, inst_rslt, inst_rslthi;
  184      reg [31:0]         hi, lo;           // high and low register for multiply and divide inst.
  185      reg                inst_cond;
  186      reg [3:0]          inst_datamask;
  187      reg [31:0]         inst_loaddata;

プロセッサであるモジュールMIPSCOREです. 命令やデータの読み出し時は,ADDRを指定して,DATA_INから命令やデータを取得します. データメモリへの書き込み時は,ADDRとDATA_OUTを指定して,WEを1にします. DATA_INとDATA_OUTは8ビット幅としています. [`ADDR] は,define.v で定義されている通り,[23:0] に置き換わります.

file name: MipsCore.v
  188      /**************************************************************************************************/
  189      /***** internal wire                                                                          *****/
  190      reg  [4:0]         IDRS, IDRT, IDRD;
  191      reg  [4:0]         IDSHAMT;
  192      reg  [15:0]        IDIMM;
  193      reg  [`JADDR]      IDADDR;
  194      reg  [18:0]        IDATTR;
  195      reg  [6:0]         IDOP;
  196      wire [4:0]         RFDST;
  197      reg  [31:0]        EXRSLT;
  198      reg  [31:0]        EXRSLTHI;
  199      reg  [`ADDR]       EXEADDR;
  200      reg  [`ADDR]       EXNPC;
  201      reg                EXC;    
  202      reg  [3:0]         EXMASK;
  203      reg  [1:0]         EXDATAEXT;
  204      reg                EXBUSY;
  205      wire               EXSIGNED;
  206      wire [31:0]        MALOADDATA, MALOADDATARAW, MALOADMASK;
  207      wire [63:0]        DURSLT, MURSLT;
  208      wire               DUBUSY, MUBUSY;
  209      wire               DIVMULINIT;
  210      wire [4:0]         GPRNUM0, GPRNUM1, CPRNUM;
  211      wire [31:0]        GPRREADDT0,  GPRREADDT1,  CPRREADDT;
  212      wire [31:0]        GPRWRITEDT0, GPRWRITEDT1, CPRWRITEDT;
  213      wire               GPRWE0, GPRWE1, CPRWE;
  214      wire               CPEXCSET, CPEXCCLR, CPEXCACK, CPEXCOCCUR, CPEXCBD;
  215      wire [3:0]         CPEXCCODE;
  216      wire [`ADDR]       CPEXCEPC, CPEXCNPC;
  217      wire [31:0]        SET32I; // 32bit sign extended of 16bit immediate
  218      wire [`ADDR]       SETADI; // sign extended address of 16bit immediate
  219      wire [31:0]        RRT_U, RRS_U;
  220      wire signed [31:0] RRT_S;
  221      wire               CPUEXE = 1; // signal to stall the processor!

内部ワイヤの宣言です.regとして宣言されている IDRSなども,ワイヤとして利用しています.

file name: MipsCore.v
  223      /**************************************************************************************************/
  224      /* Sub module declaration                                                                         */
  225      /**************************************************************************************************/
  226      DIVUNIT du(.CLK(CLK), .RST_X(RST_X), .INIT(DIVMULINIT), .SIGNED(EXSIGNED),
  227                 .A(GPRREADDT0), .B(GPRREADDT1), .RSLT(DURSLT), .BUSY(DUBUSY));
  228  
  229      MULUNIT mu(.CLK(CLK), .RST_X(RST_X), .INIT(DIVMULINIT), .SIGNED(EXSIGNED),
  230                 .A(GPRREADDT0), .B(GPRREADDT1), .RSLT(MURSLT), .BUSY(MUBUSY));
  231  
  232      GPR gpr(.CLK(CLK), .REGNUM0(GPRNUM0), .REGNUM1(GPRNUM1), .DIN0(GPRWRITEDT0), .DIN1(GPRWRITEDT1),
  233              .WE0(GPRWE0), .WE1(GPRWE1), .DOUT0(GPRREADDT0), .DOUT1(GPRREADDT1));
  234  
  235      MIPSCP0 cp0(.CLK(CLK), .RST_X(RST_X), .REG_NUM(CPRNUM), .REG_IN(CPRWRITEDT), .REG_WE(CPRWE),
  236                  .REG_OUT(CPRREADDT), .EXC_SET(CPEXCSET), .EXC_CLR(CPEXCCLR), .EXC_ACK(CPEXCACK),
  237                  .EXC_CODE(CPEXCCODE), .EXC_EPC(CPEXCEPC), .EXC_BD(CPEXCBD),
  238                  .EXC_OCCUR(CPEXCOCCUR), .EXC_NPC(CPEXCNPC));

先に説明した乗算器,除算器,レジスタファイルと,コプロセッサであるMIPSCP0を生成します. オペレーティングシステムやファイルシステム,タイマ割り込みなどを利用しない場合にMIPSCP0は必要ありません. その場合には,236行から238行をコメントアウトしてください.

file name: MipsCore.v
  240      /**************************************************************************************************/
  241      /* Mips::proceedstate()                                                                           */
  242      /**************************************************************************************************/
  243      always @(posedge CLK or negedge RST_X) begin
  244          if(!RST_X)                                   state <= `CPU_START;
  245          else if(~CPUEXE || state==`CPU_HALT)         state <= state;        
  246          else if(exec_delay && delay_npc==`HALT_ADDR) state <= `CPU_HALT;
  247          else if(state==`CPU_EX &&  EXBUSY)           state <= state;
  248          else if(state==`CPU_EX && ~EXBUSY)           state <= (inst_attr & `LDST) ? `CPU_MA0 : `CPU_WB;
  249          else if(state==`CPU_WB)                      state <= `CPU_IF0;
  250          else                                         state <= state + 1;
  251      end

命令処理のための状態stateを更新する部分です. このプロセッサは,(パイプライン処理をおこなわない)マルチサイクル版です. ロード命令,ストア命令の場合には,IF0, IF1, IF2, IF3, IF4, ID, RF, EX, MA0, MA1, MA2, MA3, MA4, WB というステートを経由して, 1つの命令を処理するので,ロード命令の処理には14サイクルを必要とします. データメモリにアクセスしない加算命令などでは, IF0, IF1, IF2, IF3, IF4, ID, RF, EX, WB というステートを経由して,9サイクルを必要とします. 248行において,LD/ST命令の時は,MA0に,そうでない場合には,WBに進むとしています.

file name: MipsCore.v
  253      /**************************************************************************************************/
  254      /* Mips:: instruction fetch stage and memory access address generation                            */
  255      /**************************************************************************************************/
  256      // inst_eaddr is incremented on MA0, MA1, and MA2 stage
  257      assign ADDR = (state == `CPU_IF0)                      ? (pc & ~'h3)        :
  258                    (state == `CPU_IF1)                      ? (pc & ~'h3) | 2'h1 :
  259                    (state == `CPU_IF2)                      ? (pc & ~'h3) | 2'h2 :
  260                    (state == `CPU_IF3)                      ? (pc & ~'h3) | 2'h3 :
  261                    (state >= `CPU_MA0 && state <= `CPU_MA3) ? inst_eaddr : 0;
  262                    
  263      always @( posedge CLK or negedge RST_X ) begin
  264          if(!RST_X) inst_pc <= 0;
  265          else if(CPUEXE) begin
  266              if     (state==`CPU_IF0)  inst_pc         <= pc;
  267              if     (state==`CPU_IF1)  inst_ir[ 7: 0]  <= DATA_IN;
  268              else if(state==`CPU_IF2)  inst_ir[15: 8]  <= DATA_IN;
  269              else if(state==`CPU_IF3)  inst_ir[23:16]  <= DATA_IN;
  270              else if(state==`CPU_IF4)  inst_ir[31:24]  <= DATA_IN;
  271          end
  272      end

257行から261では,命令フェッチのためのアドレスとロード,ストア命令のためのアドレスADDRを選択しています. IF0からIF3では,プログラムカウンタ pc の値から1バイトごとに読み出します(257行から260行). MA0からMA3では,EXで計算する inst_eaddr を利用します.

IF1からIF4のステージで得られた4バイトを267行から270行で命令レジスタ inst_ir に格納して,命令フェッチを 終了します.

file name: MipsCore.v
  274      /**************************************************************************************************/
  275      /* Mips:: instruction decode stage                                                                */
  276      /**************************************************************************************************/
  277      always@ (inst_ir) begin
  278          IDRS    = inst_ir[25:21];
  279          IDRT    = inst_ir[20:16];
  280          IDRD    = inst_ir[15:11];
  281          IDSHAMT = inst_ir[10:6];
  282          IDIMM   = inst_ir[15:0];
  283          IDADDR  = inst_ir[25:0];
  284          IDOP    = `NOP______;
  285          IDATTR  = `WRITE_NONE;
  286          
  287          case (inst_ir[31:26]) // OP
  288            6'd00: case (inst_ir[5:0]) // FUNCT 

  318                     6'd32: begin IDOP=`ADD______; IDATTR=`WRITE_RD;                                 end
  319                     6'd33: begin IDOP=`ADDU_____; IDATTR=`WRITE_RD;                                 end
  320                     6'd34: begin IDOP=`SUB______; IDATTR=`WRITE_RD;                                 end
  321                     6'd35: begin IDOP=`SUBU_____; IDATTR=`WRITE_RD;                                 end
  322                     6'd36: begin IDOP=`AND______; IDATTR=`WRITE_RD;                                 end
  323                     6'd37: begin IDOP=`OR_______; IDATTR=`WRITE_RD;                                 end
  324                     6'd38: begin IDOP=`XOR______; IDATTR=`WRITE_RD;                                 end
  325                     6'd39: begin IDOP=`NOR______; IDATTR=`WRITE_RD;                                 end
  326                     6'd42: begin IDOP=`SLT______; IDATTR=`WRITE_RD;                                 end
  327                     6'd43: begin IDOP=`SLTU_____; IDATTR=`WRITE_RD;                                 end
  328                   endcase

  373            6'd43: begin IDOP=`SW_______; IDATTR=`STORE_4B_ALIGN;                               end
  374            6'd46: begin IDOP=`SWR______; IDATTR=`STORE_4B_UNALIGN;                             end
  375          endcase
  376      end
  377  
  378      always @( posedge CLK or negedge RST_X ) begin
  379          if(!RST_X) {inst_rt, inst_rd, inst_shamt, inst_imm, inst_addr, inst_attr, inst_op} <= 0;
  380          else if(CPUEXE && state==`CPU_ID) begin
  381              inst_rt    <= IDRT;
  382              inst_rd    <= IDRD;
  383              inst_shamt <= IDSHAMT;
  384              inst_imm   <= IDIMM;
  385              inst_addr  <= IDADDR;
  386              inst_attr  <= IDATTR;
  387              inst_op    <= IDOP;
  388          end
  389      end

命令デコードです. 命令レジスタをフィールドごとに切り出して,処理している命令を解析します.

この部分も長いので途中で省略しています. 特に inst_op には,それぞれの命令に割り当てられた固有のIDを格納します. 例えば,inst_ir[31:26]のOPフィールドが0で,inst_ir[5:0]のFUNCTフィールドが6'd32であれば,ADD命令なので, 318行でIDOP=`ADD______; が設定され,387行で,inst_op に格納されます.

file name: MipsCore.v
  391      /**************************************************************************************************/
  392      /* Mips:: register file access                                                                    */
  393      /**************************************************************************************************/
  394      assign GPRNUM0 = (state==`CPU_ID) ? IDRS : inst_dst; // use IDRS if reg_read else write to inst_dst
  395      assign GPRNUM1 = (state==`CPU_ID) ? IDRT : 'd7;      // use IDRT if reg_read else SYSCALL $7<=0
  396      assign CPRNUM  = IDRD;    
  397      assign RFDST   = ((inst_attr & `WRITE_RD ) || (inst_attr & `WRITE_RD_COND)) ? inst_rd :
  398                        (inst_attr & `WRITE_RT ) ? inst_rt :
  399                        (inst_attr & `WRITE_RRA) ? 'd31 : (inst_attr & `SYSTEM_CALL) ? 'd2 : 'd0;
  400  
  401      always @( posedge CLK or negedge RST_X ) begin
  402          if(!RST_X) {inst_rrs, inst_rrt, inst_cpr, inst_dst} <= 0;
  403          else if(CPUEXE && state==`CPU_RF) begin
  404              inst_rrs   <= GPRREADDT0;
  405              inst_rrt   <= GPRREADDT1;
  406              inst_cpr   <= CPRREADDT;
  407              inst_dst   <= RFDST;
  408          end
  409      end

レジスタアクセスのコードです.レジスタファイルから読み出した32ビットのオペランドをinst_rrs, inst_rrtに 格納します(404行,405行) inst_dst は,その命令が結果を書き込むレジスタの番号(5ビット)です.

file name: MipsCore.v
  413      /**************************************************************************************************/
  414      /* Mips:: execute                                                                                 */
  415      /**************************************************************************************************/

  422      assign SET32I = (inst_imm[15]) ? {16'hffff, inst_imm} : {16'h0000, inst_imm};
  423      assign SETADI = SET32I[`ADDR];
  424      assign RRT_S = inst_rrt;
  425      assign RRS_U = inst_rrs;
  426      assign RRT_U = inst_rrt;
  427      assign EXSIGNED = (inst_op == `MULT_____ || inst_op == `DIV______);
  428      
  429      always @(DUBUSY or DURSLT or MUBUSY or MURSLT or inst_shamt or inst_imm or inst_addr or
  430               inst_op or inst_pc or inst_rrs or inst_rrt or SET32I or SETADI or
  431               RRT_S or RRS_U or RRT_U or hi or lo or inst_cpr) begin
  432          EXRSLT     = 0;
  433          EXRSLTHI   = 0;
  434          EXNPC      = 0;
  435          EXC        = 0;
  436          EXEADDR    = 0;
  437          EXMASK     = 0;
  438          EXDATAEXT  = 0;
  439          EXBUSY     = 0;
  440          case ( inst_op )

  463            `DIV______ : begin {EXRSLTHI, EXRSLT} = (RRT_U) ? DURSLT : 0; EXBUSY = DUBUSY;             end
  464            `DIVU_____ : begin {EXRSLTHI, EXRSLT} = (RRT_U) ? DURSLT : 0; EXBUSY = DUBUSY;             end
  465            `ADD______ : begin EXRSLT   = RRS_U + RRT_U;                                               end
  466            `ADDU_____ : begin EXRSLT   = RRS_U + RRT_U;                                               end

  546      always @( posedge CLK or negedge RST_X ) begin
  547          if (!RST_X) 
  548              {inst_rslt, inst_rslthi, inst_eaddr, inst_cond, inst_npc, inst_datamask} <= 0;
  549          else if (CPUEXE && state==`CPU_EX ) begin
  550              inst_rslt     <= EXRSLT;
  551              inst_rslthi   <= EXRSLTHI;
  552              inst_eaddr    <= EXEADDR;
  553              inst_cond     <= EXC;
  554              inst_npc      <= EXNPC;
  555              inst_datamask <= EXMASK;
  556          end else if (CPUEXE && state >= `CPU_MA0 && state <= `CPU_MA2) begin
  557              inst_eaddr    <= inst_eaddr + 1;
  558          end
  559      end

実行ステージのコードです.一部省略しています.例えば加算命令の加算は,465行にておこなわれます. 計算結果は550行で inst_rsltに格納されます.

file name: MipsCore.v
  561      /**************************************************************************************************/
  562      /* Mips:: memory access                                                                           */
  563      /**************************************************************************************************/
  564      ///// memory store
  565      assign DATA_OUT = (state == `CPU_MA0) ? inst_rslt[ 7: 0] :
  566                        (state == `CPU_MA1) ? inst_rslt[15: 8] :
  567                        (state == `CPU_MA2) ? inst_rslt[23:16] :
  568                        (state == `CPU_MA3) ? inst_rslt[31:24] : 0;
  569  
  570      assign WE       = (CPUEXE && (inst_attr & `STORE_ANY) &&
  571                         ((state == `CPU_MA0 && inst_datamask[0]) ||
  572                          (state == `CPU_MA1 && inst_datamask[1]) ||
  573                          (state == `CPU_MA2 && inst_datamask[2]) ||
  574                          (state == `CPU_MA3 && inst_datamask[3])));
  575  
  576      always @( posedge CLK or negedge RST_X ) begin  ///// memory load
  577          if (!RST_X) inst_loaddata <= 0;
  578          else if (CPUEXE)
  579            if      (state == `CPU_MA1) inst_loaddata[ 7: 0] <= DATA_IN;
  580            else if (state == `CPU_MA2) inst_loaddata[15: 8] <= DATA_IN;
  581            else if (state == `CPU_MA3) inst_loaddata[23:16] <= DATA_IN;
  582            else if (state == `CPU_MA4) inst_loaddata[31:24] <= DATA_IN;
  583      end

メモリアクセスステージです.

file name: MipsCore.v
  585      /**************************************************************************************************/
  586      /* Mips:: write back                                                                              */
  587      /**************************************************************************************************/
  588      assign MALOADDATARAW = inst_loaddata[31:0];
  589      assign MALOADMASK = {{8{inst_datamask[3]}}, {8{inst_datamask[2]}},
  590                           {8{inst_datamask[1]}}, {8{inst_datamask[0]}}};
  591      assign MALOADDATA = (EXDATAEXT[0]) ? {{24{MALOADDATARAW[ 7]}}, MALOADDATARAW[ 7:0]} :
  592                          (EXDATAEXT[1]) ? {{16{MALOADDATARAW[15]}}, MALOADDATARAW[15:0]} :
  593                          (MALOADDATARAW & MALOADMASK) | (inst_rslt & ~MALOADMASK);   
  594  
  595      assign GPRWE0 = ((CPUEXE && state==`CPU_WB && ~CPEXCOCCUR) &&
  596                       ~((inst_attr & `WRITE_RD_COND) && inst_cond==0));
  597      assign GPRWE1 = ((CPUEXE && state==`CPU_WB && ~CPEXCOCCUR) &&   (inst_attr & `SYSTEM_CALL));
  598      assign CPRWE  = ((CPUEXE && state==`CPU_WB && ~CPEXCOCCUR) &&   (inst_attr & `WRITE_CP0));
  599      assign GPRWRITEDT0 = (inst_attr & `LDST) ? MALOADDATA : inst_rslt;
  600      assign GPRWRITEDT1 = 32'h0;  // 2nd write port is just for SYSCALL (REG_A3 <= 0)
  601      assign CPRWRITEDT  = inst_rslt;
  602  
  603      
  604      always @( posedge CLK or negedge RST_X ) begin
  605          if(!RST_X) {hi, lo} <= 0;
  606          else if(CPUEXE && state==`CPU_WB && ~CPEXCOCCUR) begin
  607               if(inst_attr & `WRITE_HI) hi <= inst_rslthi;
  608               if(inst_attr & `WRITE_LO) lo <= inst_rslt;
  609          end
  610      end
  611  

ライトバックステージの前半です.レジスタファイルに書き込む信号を作っています.

file name: MipsCore.v
  612      /**************************************************************************************************/
  613      /* Mips:: setnpc()                                                                                */
  614      /**************************************************************************************************/
  615      assign CPEXCSET  = (CPUEXE && state==`CPU_EX && (inst_attr & `SYSTEM_CALL));
  616      assign CPEXCCLR  = (CPUEXE && state==`CPU_EX && (inst_attr & `BRANCH_ERET));
  617      assign CPEXCACK  = (CPUEXE && state==`CPU_WB && CPEXCOCCUR);
  618      assign CPEXCCODE = 4'd8; // EXC_SYSCALL
  619      assign CPEXCEPC  = (exec_delay) ? pc - 4 : pc;
  620      assign CPEXCBD   = exec_delay;
  621      
  622      always @( posedge CLK or negedge RST_X ) begin
  623          if(!RST_X) {pc, delay_npc, exec_delay} <= 0;
  624          else if(CPUEXE && state == `CPU_WB) begin
  625              if (CPEXCOCCUR) begin
  626                  pc          <= CPEXCNPC;
  627                  exec_delay  <= 0;
  628              end else if( exec_delay ) begin
  629                  pc          <= delay_npc;
  630                  delay_npc   <= 0;
  631                  exec_delay  <= 0;
  632              end else if( ((inst_attr & `BRANCH) || (inst_attr & `BRANCH_LIKELY)) && inst_cond ) begin
  633                  pc          <= pc + 4;
  634                  delay_npc   <= inst_npc;
  635                  exec_delay  <= 1;
  636              end else if  (inst_attr & `BRANCH_ERET) begin
  637                  pc          <= CPEXCNPC;
  638              end else if( (inst_attr & `BRANCH_LIKELY) && !inst_cond ) begin
  639                  pc          <= pc + 8;
  640              end else begin
  641                  pc          <= pc + 4;
  642              end
  643          end
  644      end
  645  endmodule

ライトバックステージの後半です.次に処理する命令のためにプログラムカウンタを更新します. 遅延分岐のため,制御が複雑になっています.

これでプロセッサのコードMipsCore.vはおしまいです.

このプロセッサでqn24b(N-queensの解の数を求めるプログラム)を動かしてみました.
開発キットには130_qn24bのディレクトリにソースコードがあります. gcc version 4.3.6 (Buildroot 2011.08), 最適化オプションO2でコンパイルして, N=13を実行したところ,実行時間は 29.70秒でした.
とても遅いです.高速化に挑戦してみてください.


Copyright(c) 2011 Tokyo Tech Kise Laboratory. All rights reserved.