程式人雜誌 -- 2013 年 11 月號 (開放公益出版品)

開放電腦計畫 (5) – 支援完整指令集的 CPU0sc 處理器:使用 Verilog 實作 (作者:陳鍾誠)

簡介

在上一期當中,我們用 Verilog 設計了一個只有四個指令的簡化版處理器 CPU0mc.v ,文章網址如下:

如果您讀過上述文章,應該已經瞭解如何用 Verilog 設計簡單的指令與 CPU 了。在本文中,我們將延續 上期的主題,更深入的說明如何用 Verilog 設計出一顆具有完整指令集的處理器 -- CPU0sc.v 。

程式碼

我們只要將上期的 CPU0mc 繼續延伸,加入更多的指令實作,就能做出具有完整指令集的處理器 CPU0sc.v,以下是 處理器 CPU0sc.v 的完整 Verilog 原始碼。

檔案:cpu0sc.v

`define PC   R[15]   // 程式計數器
`define LR   R[14]   // 連結暫存器
`define SP   R[13]   // 堆疊暫存器
`define SW   R[12]   // 狀態暫存器
// 狀態暫存器旗標位元
`define N    `SW[31] // 負號旗標
`define Z    `SW[30] // 零旗標
`define C    `SW[29] // 進位旗標
`define V    `SW[28] // 溢位旗標
`define I    `SW[7]  // 硬體中斷許可
`define T    `SW[6]  // 軟體中斷許可
`define M    `SW[0]  // 模式位元

module cpu0c(input clock); // CPU0-Mini 的快取版:cpu0mc 模組
  parameter [7:0] LD=8'h00,ST=8'h01,LDB=8'h02,STB=8'h03,LDR=8'h04,STR=8'h05,
    LBR=8'h06,SBR=8'h07,ADDI=8'h08,CMP=8'h10,MOV=8'h12,ADD=8'h13,SUB=8'h14,
    MUL=8'h15,DIV=8'h16,AND=8'h18,OR=8'h19,XOR=8'h1A,ROL=8'h1C,ROR=8'h1D,
    SHL=8'h1E,SHR=8'h1F,JEQ=8'h20,JNE=8'h21,JLT=8'h22,JGT=8'h23,JLE=8'h24,
    JGE=8'h25,JMP=8'h26,SWI=8'h2A,CALL=8'h2B,RET=8'h2C,IRET=8'h2D,
    PUSH=8'h30,POP=8'h31,PUSHB=8'h32,POPB=8'h33;
  reg signed [31:0] R [0:15];   // 宣告暫存器 R[0..15] 等 16 個 32 位元暫存器
  reg signed [31:0] IR;         // 指令暫存器 IR
  reg [7:0] m [0:256];          // 內部的快取記憶體
  reg [7:0] op;                 // 變數:運算代碼 op
  reg [3:0] ra, rb, rc;         // 變數:暫存器代號 ra, rb, rc
  reg [4:0] c5;                 // 變數:5 位元常數 c5
  reg signed [11:0] c12;        // 變數:12 位元常數 c12
  reg signed [15:0] c16;        // 變數:16 位元常數 c16
  reg signed [23:0] c24;        // 變數:24 位元常數 c24
  reg signed [31:0] sp, jaddr, laddr, raddr;
  reg signed [31:0] temp;
  reg signed [31:0] pc;

  integer i;  
  initial  // 初始化
  begin
    `PC = 0;                    // 將 PC 設為起動位址 0
    `SW = 0;
    R[0] = 0;                   // 將 R[0] 暫存器強制設定為 0
    $readmemh("cpu0s.hex", m);
    for (i=0; i < 255; i=i+4) begin
       $display("%8x: %8x", i, {m[i], m[i+1], m[i+2], m[i+3]});
    end
  end
  
  always @(posedge clock) begin // 在 clock 時脈的正邊緣時觸發
      pc = `PC;
      IR = {m[`PC], m[`PC+1], m[`PC+2], m[`PC+3]};  // 指令擷取階段:IR=m[PC], 4 個 Byte 的記憶體
      `PC = `PC+4;                                  // 擷取完成,PC 前進到下一個指令位址
      {op,ra,rb,rc,c12} = IR;                      // 解碼階段:將 IR 解為 {op, ra, rb, rc, c12}
      c5  = IR[4:0];
      c24 = IR[23:0];
      c16 = IR[15:0];
      jaddr = `PC+c16;
      laddr = R[rb]+c16;
      raddr = R[rb]+R[rc];
      case (op) // 根據 OP 執行對應的動作
        LD: begin   // 載入指令: R[ra] = m[addr]
          R[ra] = {m[laddr], m[laddr+1], m[laddr+2], m[laddr+3]};
          $display("%4dns %8x : LD    R%-d R%-d 0x%x ; R%-2d=0x%8x=%-d", $stime, pc, ra, rb, c16, ra, R[ra], R[ra]);
          end
        ST: begin   // 儲存指令: m[addr] = R[ra]
          {m[laddr], m[laddr+1], m[laddr+2], m[laddr+3]} = R[ra];
          $display("%4dns %8x : ST    R%-d R%-d 0x%x ; R%-2d=0x%8x=%-d", $stime, pc, ra, rb, c16, ra, R[ra], R[ra]);
          end
        LDB:begin   // 載入byte;     LDB Ra, [Rb+ Cx];   Ra<=(byte)[Rb+ Cx]
          R[ra] = { 24'b0, m[laddr] };
          $display("%4dns %8x : LDB   R%-d R%-d 0x%x ; R%-2d=0x%8x=%-d", $stime, pc, ra, rb, c16, ra, R[ra], R[ra]);
          end
        STB:begin   // 儲存byte;     STB Ra, [Rb+ Cx];   Ra=>(byte)[Rb+ Cx]
          m[laddr] = R[ra][7:0];
          $display("%4dns %8x : STB   R%-d R%-d 0x%x ; R%-2d=0x%8x=%-d", $stime, pc, ra, rb, c16, ra, R[ra], R[ra]);
          end
        LDR:begin   // LD 的 Rc 版;  LDR Ra, [Rb+Rc];    Ra<=[Rb+ Rc]
          R[ra] = {m[raddr], m[raddr+1], m[raddr+2], m[raddr+3]};
          $display("%4dns %8x : LDR   R%-d R%-d R%-d    ; R%-2d=0x%8x=%-d", $stime, pc, ra, rb, rc, ra, R[ra], R[ra]);
          end
        STR:begin   // ST 的 Rc 版;  STR Ra, [Rb+Rc];    Ra=>[Rb+ Rc]
          {m[raddr], m[raddr+1], m[raddr+2], m[raddr+3]} = R[ra];
          $display("%4dns %8x : STR   R%-d R%-d R%-d    ; R%-2d=0x%8x=%-d", $stime, pc, ra, rb, rc, ra, R[ra], R[ra]);
          end
        LBR:begin   // LDB 的 Rc 版; LBR Ra, [Rb+Rc];    Ra<=(byte)[Rb+ Rc]
          R[ra] = { 24'b0, m[raddr] };
          $display("%4dns %8x : LBR   R%-d R%-d R%-d    ; R%-2d=0x%8x=%-d", $stime, pc, ra, rb, rc, ra, R[ra], R[ra]);
          end
        SBR:begin   // STB 的 Rc 版; SBR Ra, [Rb+Rc];    Ra=>(byte)[Rb+ Rc]
          m[raddr] = R[ra][7:0];
          $display("%4dns %8x : SBR   R%-d R%-d R%-d    ; R%-2d=0x%8x=%-d", $stime, pc, ra, rb, rc, ra, R[ra], R[ra]);
          end
        MOV:begin   // 移動;        MOV Ra, Rb;         Ra<=Rb
          R[ra] = R[rb];
          $display("%4dns %8x : MOV   R%-d R%-d        ; R%-2d=0x%8x=%-d", $stime, pc, ra, rb, ra, R[ra], R[ra]);
          end
        CMP:begin   // 比較;        CMP Ra, Rb;         SW=(Ra >=< Rb)
          temp = R[ra]-R[rb];
          `N=(temp<0);`Z=(temp==0);
          $display("%4dns %8x : CMP   R%-d R%-d        ; SW=0x%x", $stime, pc, ra, rb, `SW);
          end
        ADDI:begin  // R[a] = Rb+c16;  // 立即值加法;   LDI Ra, Rb+Cx; Ra<=Rb + Cx
          R[ra] = R[rb]+c16;
          $display("%4dns %8x : ADDI  R%-d R%-d %-d ; R%-2d=0x%8x=%-d", $stime, pc, ra, rb, c16, ra, R[ra], R[ra]);
          end
        ADD: begin  // 加法指令: R[ra] = R[rb]+R[rc]
          R[ra] = R[rb]+R[rc];
          $display("%4dns %8x : ADD   R%-d R%-d R%-d    ; R%-2d=0x%8x=%-d", $stime, pc, ra, rb, rc, ra, R[ra], R[ra]);
          end
        SUB:begin   // 減法;        SUB Ra, Rb, Rc;     Ra<=Rb-Rc
          R[ra] = R[rb]-R[rc];
          $display("%4dns %8x : SUB   R%-d R%-d R%-d    ; R%-2d=0x%8x=%-d", $stime, pc, ra, rb, rc, ra, R[ra], R[ra]);
          end
        MUL:begin   // 乘法;        MUL Ra, Rb, Rc;     Ra<=Rb*Rc
          R[ra] = R[rb]*R[rc];
          $display("%4dns %8x : MUL   R%-d R%-d R%-d    ; R%-2d=0x%8x=%-d", $stime, pc, ra, rb, rc, ra, R[ra], R[ra]);
          end
        DIV:begin   // 除法;        DIV Ra, Rb, Rc;     Ra<=Rb/Rc
          R[ra] = R[rb]/R[rc];
          $display("%4dns %8x : DIV   R%-d R%-d R%-d    ; R%-2d=0x%8x=%-d", $stime, pc, ra, rb, rc, ra, R[ra], R[ra]);
          end
        AND:begin   // 位元 AND;    AND Ra, Rb, Rc;     Ra<=Rb and Rc
          R[ra] = R[rb]&R[rc];
          $display("%4dns %8x : AND   R%-d R%-d R%-d    ; R%-2d=0x%8x=%-d", $stime, pc, ra, rb, rc, ra, R[ra], R[ra]);
          end
        OR:begin    // 位元 OR;     OR Ra, Rb, Rc;         Ra<=Rb or Rc
          R[ra] = R[rb]|R[rc];
          $display("%4dns %8x : OR    R%-d R%-d R%-d    ; R%-2d=0x%8x=%-d", $stime, pc, ra, rb, rc, ra, R[ra], R[ra]);
          end
        XOR:begin   // 位元 XOR;    XOR Ra, Rb, Rc;     Ra<=Rb xor Rc
          R[ra] = R[rb]^R[rc];
          $display("%4dns %8x : XOR   R%-d R%-d R%-d    ; R%-2d=0x%8x=%-d", $stime, pc, ra, rb, rc, ra, R[ra], R[ra]);
          end
        SHL:begin   // 向左移位;    SHL Ra, Rb, Cx;     Ra<=Rb << Cx
          R[ra] = R[rb]<<c5;
          $display("%4dns %8x : SHL   R%-d R%-d %-d     ; R%-2d=0x%8x=%-d", $stime, pc, ra, rb, c5, ra, R[ra], R[ra]);
          end
        SHR:begin   // 向右移位;        SHR Ra, Rb, Cx;     Ra<=Rb >> Cx
          R[ra] = R[rb]>>c5;
          $display("%4dns %8x : SHR   R%-d R%-d %-d     ; R%-2d=0x%8x=%-d", $stime, pc, ra, rb, c5, ra, R[ra], R[ra]);
          end          
        JMP:begin   // 跳躍指令: PC = PC + cx24
          `PC = `PC + c24;
          $display("%4dns %8x : JMP   0x%x       ; PC=0x%x", $stime, pc, c24, `PC);
          end
        JEQ:begin   // 跳躍 (相等);        JEQ Cx;        if SW(=) PC  PC+Cx
          if (`Z) `PC=`PC+c24;
          $display("%4dns %8x : JEQ   0x%08x     ; PC=0x%x", $stime, pc, c24, `PC);
          end
        JNE:begin   // 跳躍 (不相等);    JNE Cx;     if SW(!=) PC  PC+Cx
          if (!`Z) `PC=`PC+c24;
          $display("%4dns %8x : JNE   0x%08x     ; PC=0x%x", $stime, pc, c24, `PC);
          end
        JLT:begin   // 跳躍 ( < );        JLT Cx;     if SW(<) PC  PC+Cx
          if (`N) `PC=`PC+c24;
          $display("%4dns %8x : JLT   0x%08x     ; PC=0x%x", $stime, pc, c24, `PC);
          end
        JGT:begin   // 跳躍 ( > );        JGT Cx;     if SW(>) PC  PC+Cx
          if (!`N&&!`Z) `PC=`PC+c24;
          $display("%4dns %8x : JGT   0x%08x     ; PC=0x%x", $stime, pc, c24, `PC);
          end
        JLE:begin   // 跳躍 ( <= );        JLE Cx;     if SW(<=) PC  PC+Cx  
          if (`N || `Z) `PC=`PC+c24;
          $display("%4dns %8x : JLE   0x%08x     ; PC=0x%x", $stime, pc, c24, `PC);
          end
        JGE:begin   // 跳躍 ( >= );        JGE Cx;     if SW(>=) PC  PC+Cx
          if (!`N || `Z) `PC=`PC+c24;
          $display("%4dns %8x : JGE   0x%08x     ; PC=0x%x", $stime, pc, c24, `PC);
          end
        SWI:begin   // 軟中斷;    SWI Cx;         LR <= PC; PC <= Cx; INT<=1
          `LR=`PC;`PC= c24; `I = 1'b1;
          $display("%4dns %8x : SWI   0x%08x     ; PC=0x%x", $stime, pc, c24, `PC);
          end
        CALL:begin  // 跳到副程式;    CALL Cx;     LR<=PC; PC<=PC+Cx
          `LR=`PC;`PC=`PC + c24;
          $display("%4dns %8x : CALL  0x%08x     ; PC=0x%x", $stime, pc, c24, `PC);
          end
        RET:begin   // 返回;            RET;         PC <= LR
          `PC=`LR;
          $display("%4dns %8x : RET                  ; PC=0x%x", $stime, pc, `PC);
          if (`PC<0) $finish;
          end
        IRET:begin  // 中斷返回;        IRET;         PC <= LR; INT<=0
          `PC=`LR;`I = 1'b0;
          $display("%4dns %8x : IRET             ; PC=0x%x", $stime, pc, `PC);
          end
        PUSH:begin  // 推入 word;    PUSH Ra;    SP-=4;[SP]<=Ra;
          sp = `SP-4; `SP = sp; {m[sp], m[sp+1], m[sp+2], m[sp+3]} = R[ra];
          $display("%4dns %8x : PUSH  R%-d            ; R%-2d=0x%8x, SP=0x%x", $stime, pc, ra, ra, R[ra], `SP);
          end
        POP:begin   // 彈出 word;    POP Ra;     Ra=[SP];SP+=4;
          sp = `SP; R[ra]={m[sp], m[sp+1], m[sp+2], m[sp+3]}; `SP = sp+4; 
          $display("%4dns %8x : POP   R%-d            ; R%-2d=0x%8x, SP=0x%x", $stime, pc, ra, ra, R[ra], `SP);
          end
        PUSHB:begin // 推入 byte;    PUSHB Ra;   SP--;[SP]<=Ra;(byte)
          sp = `SP-1; `SP = sp; m[sp] = R[ra];
          $display("%4dns %8x : PUSHB R%-d            ; R[%-d]=0x%8x, SP=0x%x", $stime, pc, ra, ra, R[ra], `SP);
          end
        POPB:begin  // 彈出 byte;    POPB Ra;  Ra<=[SP];SP++;(byte)
          sp = `SP+1; `SP = sp; R[ra]=m[sp];
          $display("%4dns %8x : POPB  R%-d            ; R[%-d]=0x%8x, SP=0x%x", $stime, pc, ra, ra, R[ra], `SP);
          end
      endcase
  end
endmodule

module main;                // 測試程式開始
reg clock;                  // 時脈 clock 變數

cpu0c cpu(clock);           // 宣告 cpu0mc 處理器

initial clock = 0;          // 一開始 clock 設定為 0
always #10 clock=~clock;    // 每隔 10 奈秒將 clock 反相,產生週期為 20 奈秒的時脈
initial #2000 $finish;      // 在 640 奈秒的時候停止測試。(因為這時的 R[1] 恰好是 1+2+...+10=55 的結果)
endmodule

程式碼解析與執行

在上一期的 CPU0mc.v 當中,我們直接使用下列程式將「機器碼」塞入到記憶體當中,但是這樣做顯然彈性不太夠。

    {m[0],m[1],m[2],m[3]}    = 32'h001F0018; // 0000       LD   R1, K1
    {m[4],m[5],m[6],m[7]}    = 32'h002F0010; // 0004       LD   R2, K0
    {m[8],m[9],m[10],m[11]}  = 32'h003F0014; // 0008       LD   R3, SUM
    {m[12],m[13],m[14],m[15]}= 32'h13221000; // 000C LOOP: ADD  R2, R2, R1
    {m[16],m[17],m[18],m[19]}= 32'h13332000; // 0010       ADD  R3, R3, R2
    {m[20],m[21],m[22],m[23]}= 32'h26FFFFF4; // 0014       JMP  LOOP
    {m[24],m[25],m[26],m[27]}= 32'h00000000; // 0018 K0:   WORD 0
    {m[28],m[29],m[30],m[31]}= 32'h00000001; // 001C K1:   WORD 1
    {m[32],m[33],m[34],m[35]}= 32'h00000000; // 0020 SUM:  WORD 0

因此,在本期的 CPU0sc.v 這個程式中,我們採用讀取外部檔案的方式,將機器碼寫在 「cpu0s.hex」這個檔案中, 然後再用下列指令將該 16 進位的機器碼檔案讀入。

    $readmemh("cpu0s.hex", m);

其中的 readmemh 是一個可以讀取 16 進位的文字檔的函數,上述指令會將 cpu0s.hex 這個檔案內的 16 進位字串 讀入到「記憶體變數」 m 當中。

以下是 cpu0s.hex 的完整內容。

輸入檔:cpu0s.hex

00 DF 00 B6  //  0           LD   R13, StackEnd
08 40 00 04  //  4           ADDI R4, 4
08 50 00 08  //  8           ADDI R5, 8
05 4D 50 00  //  c           STR  R4, [R13+R5]
04 6D 50 00  // 10           LDR  R6, [R13+R5]
07 5D 40 00  // 14           SBR  R5, [R13+R4]
06 6D 40 00  // 18           LBR  R6, [R13+R4]
08 E0 FF FF  // 1C           ADDI R14,R0,-1
30 E0 00 00  // 20           PUSH R14
13 85 40 00  // 24           ADD  R8, R5, R4
14 85 40 00  // 28           SUB  R8, R5, R4
15 85 40 00  // 2c           MUL  R8, R5, R4
16 85 40 00  // 30           DIV  R8, R5, R4
18 85 40 00  // 34           AND  R8, R5, R4
19 85 40 00  // 38           OR   R8, R5, R4
1A 85 40 00  // 3c           XOR  R8, R5, R4
1E 85 00 03  // 40           SHL  R8, R5, 3
1F 85 00 02  // 44           SHR  R8, R5, 2
10 45 00 00  // 48           CMP  R4, R5
20 00 00 18  // 4c           JEQ  L1
23 00 00 14  // 50           JGT  L1
25 00 00 10  // 54           JGE  L1
22 00 00 0C  // 58           JLT  L1
24 00 00 08  // 5c           JLE  L1
21 00 00 04  // 60           JNE  L1
26 00 00 00  // 64           JMP  L1
08 10 00 0A  // 68   L1:     ADDI R1, R0, 10
2B 00 00 08  // 6c           CALL SUM
31 E0 00 00  // 70           POP  R14
2C 00 00 00  // 74           RET
30 E0 00 00  // 78   SUM:    PUSH R14
12 30 00 00  // 7c           MOV  R3, R0     // R3 = i = 0
02 4F 00 24  // 80           LDB  R4, k1     // R4 = 1
08 20 00 00  // 84           ADDI R2, 0      // SUM = R2 = 0
13 22 30 00  // 88   LOOP:   ADD  R2, R2, R3 // SUM = SUM + i
13 33 40 00  // 8c           ADD  R3, R3, R4 // i = i + 1
10 31 00 00  // 90           CMP  R3, R1     // if (i < R1)
24 FF FF F0  // 94           JLE  LOOP       //   goto LOOP
01 2F 00 0D  // 98           ST   R2, s
03 3F 00 0D  // 9c           STB  R3, i
31 E0 00 00  // a0           POP  R14
2C 00 00 00  // a4           RET                   // return
01           // a8   k1:     BYTE 1                // char K1=1
00 00 00 00  // a9   s:      WORD 0                // int s
00           // ad   i:      BYTE 0                // char i=1
00 01 02 03  // ae   Stack:  BYTE  0, 1, 2, 3, 4, 5, 6, 7, 8, 9 , 10, 11
04 05 06 07  // b2
08 09 0A 0B  // b6
00 00 00 BA  // ba   StackEnd: WORD StackEnd
01 02 03 04  // be   Data:   BYTE 0, 1, 2, 3, 4, 5, 6, 7, 8
05 06 07 08  // c2

上述程式的內容,大致是先準備好堆疊,然後就開始測試 ADDI, STR, LDR, ADD, SUB, ... 等指令。 接著在呼叫 CMP R4, R5 之後進行跳躍測試動作,由於 R4=4, R5=8,所以 CMP 的結果會是「小於」, 因此在後面的 JEQ, JGT, JGE 等指令都不會真的跳躍,直到執行 JLT L1 時就會真的跳到 L1 去。

接著用 ADDI R1, R0, 10 將 R1 設為 10,然後就用 CALL SUM 這個指令呼叫 SUM 這個副程式,於是跳到 位於 0x78 的 SUM: PUSH R14 這一行,並開始執行副程式,該副程式會計算 1+2+...+R1 的結果,放在 R2 當中, 並在最後用 STB R2, s 這個指令存入變數 s 當中,然後在執行完 RET 指令後返回上一層,也就是 0x70 行的 POP R14 指令,接著在執行 RET 指令時,由於此時 R14 為 -1,因此 Verilog 程式就在完成 RET 指令時發現 PC 已經小於 0 了,因此執行$finish` 指令停止整個程式。

        RET:begin   // 返回;            RET;         PC <= LR
          `PC=`LR;
          $display("%4dns %8x : RET                  ; PC=0x%x", $stime, pc, `PC);
          if (`PC<0) $finish;
          end

執行結果

有了上述的程式 cpu0sc.v 與輸入的機器碼 cpu0s.hex 檔案之後,我們就可以用下列指令進行編譯與執行, 以下是該程式編譯與執行的結果。

D:\verilog>iverilog -o cpu0sc cpu0sc.v

D:\verilog>vvp cpu0sc
WARNING: cpu0sc.v:40: $readmemh(cpu0s.hex): Not enough words in the file for the
 requested range [0:256].
00000000: 00df00b6
00000004: 08400004
00000008: 08500008
0000000c: 054d5000
00000010: 046d5000
00000014: 075d4000
00000018: 066d4000
0000001c: 08e0ffff
00000020: 30e00000
00000024: 13854000
00000028: 14854000
0000002c: 15854000
00000030: 16854000
00000034: 18854000
00000038: 19854000
0000003c: 1a854000
00000040: 1e850003
00000044: 1f850002
00000048: 10450000
0000004c: 20000018
00000050: 23000014
00000054: 25000010
00000058: 2200000c
0000005c: 24000008
00000060: 21000004
00000064: 26000000
00000068: 0810000a
0000006c: 2b000008
00000070: 31e00000
00000074: 2c000000
00000078: 30e00000
0000007c: 12300000
00000080: 024f0024
00000084: 08200000
00000088: 13223000
0000008c: 13334000
00000090: 10310000
00000094: 24fffff0
00000098: 012f000d
0000009c: 033f000d
000000a0: 31e00000
000000a4: 2c000000
000000a8: 01000000
000000ac: 00000001
000000b0: 02030405
000000b4: 06070809
000000b8: 0a0b0000
000000bc: 00ba0102
000000c0: 03040506
000000c4: 0708xxxx
000000c8: xxxxxxxx
000000cc: xxxxxxxx
000000d0: xxxxxxxx
000000d4: xxxxxxxx
000000d8: xxxxxxxx
000000dc: xxxxxxxx
000000e0: xxxxxxxx
000000e4: xxxxxxxx
000000e8: xxxxxxxx
000000ec: xxxxxxxx
000000f0: xxxxxxxx
000000f4: xxxxxxxx
000000f8: xxxxxxxx
000000fc: xxxxxxxx
  10ns 00000000 : LD    R13 R15 0x00b6 ; R13=0x000000ba=186
  30ns 00000004 : ADDI  R4  R0  4      ; R4 =0x00000004=4
  50ns 00000008 : ADDI  R5  R0  8      ; R5 =0x00000008=8
  70ns 0000000c : STR   R4  R13 R5     ; R4 =0x00000004=4
  90ns 00000010 : LDR   R6  R13 R5     ; R6 =0x00000004=4
 110ns 00000014 : SBR   R5  R13 R4     ; R5 =0x00000008=8
 130ns 00000018 : LBR   R6  R13 R4     ; R6 =0x00000008=8
 150ns 0000001c : ADDI  R14 R0  -1     ; R14=0xffffffff=-1
 170ns 00000020 : PUSH  R14            ; R14=0xffffffff, SP=0x000000b6
 190ns 00000024 : ADD   R8  R5  R4     ; R8 =0x0000000c=12
 210ns 00000028 : SUB   R8  R5  R4     ; R8 =0x00000004=4
 230ns 0000002c : MUL   R8  R5  R4     ; R8 =0x00000020=32
 250ns 00000030 : DIV   R8  R5  R4     ; R8 =0x00000002=2
 270ns 00000034 : AND   R8  R5  R4     ; R8 =0x00000000=0
 290ns 00000038 : OR    R8  R5  R4     ; R8 =0x0000000c=12
 310ns 0000003c : XOR   R8  R5  R4     ; R8 =0x0000000c=12
 330ns 00000040 : SHL   R8  R5  3      ; R8 =0x00000040=64
 350ns 00000044 : SHR   R8  R5  2      ; R8 =0x00000002=2
 370ns 00000048 : CMP   R4  R5         ; SW=0x80000000
 390ns 0000004c : JEQ   0x00000018     ; PC=0x00000050
 410ns 00000050 : JGT   0x00000014     ; PC=0x00000054
 430ns 00000054 : JGE   0x00000010     ; PC=0x00000058
 450ns 00000058 : JLT   0x0000000c     ; PC=0x00000068
 470ns 00000068 : ADDI  R1  R0  10     ; R1 =0x0000000a=10
 490ns 0000006c : CALL  0x00000008     ; PC=0x00000078
 510ns 00000078 : PUSH  R14            ; R14=0x00000070, SP=0x000000b2
 530ns 0000007c : MOV   R3  R0         ; R3 =0x00000000=0
 550ns 00000080 : LDB   R4  R15 0x0024 ; R4 =0x00000001=1
 570ns 00000084 : ADDI  R2  R0  0      ; R2 =0x00000000=0
 590ns 00000088 : ADD   R2  R2  R3     ; R2 =0x00000000=0
 610ns 0000008c : ADD   R3  R3  R4     ; R3 =0x00000001=1
 630ns 00000090 : CMP   R3  R1         ; SW=0x80000000
 650ns 00000094 : JLE   0x00fffff0     ; PC=0x00000088
 670ns 00000088 : ADD   R2  R2  R3     ; R2 =0x00000001=1
 690ns 0000008c : ADD   R3  R3  R4     ; R3 =0x00000002=2
 710ns 00000090 : CMP   R3  R1         ; SW=0x80000000
 730ns 00000094 : JLE   0x00fffff0     ; PC=0x00000088
 750ns 00000088 : ADD   R2  R2  R3     ; R2 =0x00000003=3
 770ns 0000008c : ADD   R3  R3  R4     ; R3 =0x00000003=3
 790ns 00000090 : CMP   R3  R1         ; SW=0x80000000
 810ns 00000094 : JLE   0x00fffff0     ; PC=0x00000088
 830ns 00000088 : ADD   R2  R2  R3     ; R2 =0x00000006=6
 850ns 0000008c : ADD   R3  R3  R4     ; R3 =0x00000004=4
 870ns 00000090 : CMP   R3  R1         ; SW=0x80000000
 890ns 00000094 : JLE   0x00fffff0     ; PC=0x00000088
 910ns 00000088 : ADD   R2  R2  R3     ; R2 =0x0000000a=10
 930ns 0000008c : ADD   R3  R3  R4     ; R3 =0x00000005=5
 950ns 00000090 : CMP   R3  R1         ; SW=0x80000000
 970ns 00000094 : JLE   0x00fffff0     ; PC=0x00000088
 990ns 00000088 : ADD   R2  R2  R3     ; R2 =0x0000000f=15
1010ns 0000008c : ADD   R3  R3  R4     ; R3 =0x00000006=6
1030ns 00000090 : CMP   R3  R1         ; SW=0x80000000
1050ns 00000094 : JLE   0x00fffff0     ; PC=0x00000088
1070ns 00000088 : ADD   R2  R2  R3     ; R2 =0x00000015=21
1090ns 0000008c : ADD   R3  R3  R4     ; R3 =0x00000007=7
1110ns 00000090 : CMP   R3  R1         ; SW=0x80000000
1130ns 00000094 : JLE   0x00fffff0     ; PC=0x00000088
1150ns 00000088 : ADD   R2  R2  R3     ; R2 =0x0000001c=28
1170ns 0000008c : ADD   R3  R3  R4     ; R3 =0x00000008=8
1190ns 00000090 : CMP   R3  R1         ; SW=0x80000000
1210ns 00000094 : JLE   0x00fffff0     ; PC=0x00000088
1230ns 00000088 : ADD   R2  R2  R3     ; R2 =0x00000024=36
1250ns 0000008c : ADD   R3  R3  R4     ; R3 =0x00000009=9
1270ns 00000090 : CMP   R3  R1         ; SW=0x80000000
1290ns 00000094 : JLE   0x00fffff0     ; PC=0x00000088
1310ns 00000088 : ADD   R2  R2  R3     ; R2 =0x0000002d=45
1330ns 0000008c : ADD   R3  R3  R4     ; R3 =0x0000000a=10
1350ns 00000090 : CMP   R3  R1         ; SW=0x40000000
1370ns 00000094 : JLE   0x00fffff0     ; PC=0x00000088
1390ns 00000088 : ADD   R2  R2  R3     ; R2 =0x00000037=55
1410ns 0000008c : ADD   R3  R3  R4     ; R3 =0x0000000b=11
1430ns 00000090 : CMP   R3  R1         ; SW=0x00000000
1450ns 00000094 : JLE   0x00fffff0     ; PC=0x00000098
1470ns 00000098 : ST    R2  R15 0x000d ; R2 =0x00000037=55
1490ns 0000009c : STB   R3  R15 0x000d ; R3 =0x0000000b=11
1510ns 000000a0 : POP   R14            ; R14=0x00000070, SP=0x000000b6
1530ns 000000a4 : RET                  ; PC=0x00000070
1550ns 00000070 : POP   R14            ; R14=0xffffffff, SP=0x000000ba
1570ns 00000074 : RET                  ; PC=0xffffffff

結語

從這兩期的程式中,您應該可以瞭解到直接使用高階的 Verilog 流程式語法來設計處理器,像是 cpu0mc.v 與 cpu0sc.v , 都是相當容易的事,這完全是因為 verilog 支援了相當高階的運算,像是 「+, -, *, /, &, |, ^, <<, >>」等運算的原故。

不過、在上述程式當中,我們並沒有支援「硬體中斷」的功能,也沒有實作「軟體中斷」SWI 的函數呼叫,這樣 CPU0sc.v 就 只能是一顆單工 (Single Task) 的處理器,而無法支援多工 (Multi Task) 的功能了。

在下期中,我們將繼續擴充 CPU0sc.v 這個程式,加入支援「軟硬體中斷」的功能,該程式稱為 CPU0ic.v (i 代表 Interrupt, c 代表 cache memory)。

然後我們將再度用 16 進位的方式,寫出一個機器語言的程式,可以同時執行兩個「行程」(Task) ,並且每隔一小段時間就利用 硬體中斷進行「行程切換」,以示範如何設計一個可以支援「多工」能力的 CPU 處理器。

參考文獻