MIPS Interruptのハードウェア実装(CP0)

CPU受付完了時にassertするIACKピンの実装

We also want to have to ability to service external interrupts. This is useful if a device external to the processor needs attention. To do this, we'll add 2 pins to the processor. The first pin, called IRQ (interrupt request), will be an input that will allow an external device to interrupt the processor. Since we don't want the processor to service any external interrupts before it is finished executing the current instruction, we may have to make the external device wait for several clock cycles. Because of this, we need a way to tell the external device that we've serviced its interrupt. We'll solve this problem by adding the second pin, called IACK (interrupt acknowledge), that will be an output. The following waveform defines the timing for external interrupt requests.

f:id:varmil:20171204155204p:plain

Exceptions and Interrupts for the MIPS architecture

これによってPICのIRR->ISRへの切替が行われるようになる。

CP0のverilog実装

mips32r1_xum/CPZero.v at master · grantae/mips32r1_xum · GitHub

非常に分かりやすい。が、MIPS Release 1では Cause.Ⅳ Cause.WP など使用しないフラグもあるので、その辺は自分で作る時は排除しても良さそう。また、このリポジトリではCPUのInt(0)をUART用、Int(4:1)をSwitch用に使用している(PICは使っていない。のでIACK pinも存在しない)

// これがコア側のInt*(4:0)に対応していて、CP0内部でクロック立ち上がり時に、
// Cause_IP[6:2] <= Int[4:0]; でIP部分に代入している
//-- Hw Interrupts --//
input  [4:0] Int,               // Five hardware interrupts external to the processor

.....

// IE, IP, IMを見て割り込みを起こすかどうか決める
// 2行目はEXL, ERLを見ているので、既に例外処理中だったら割り込みを起こさないということか
wire   Enabled_Interrupt = EXC_NMI | (Status_IE & ((Cause_IP[7:0] & Status_IM[7:0]) != 8'h00));
assign EXC_Int = Enabled_Interrupt & ~Status_EXL & ~Status_ERL & ~ID_IsFlushed;

.....

// この実装では割り込みはIDステージで検知する。stall中はReadyしない。
assign  ID_Exception_Detect = EXC_Sys | EXC_Bp | EXC_RI | EXC_CpU | EXC_Int;
assign  ID_Exception_Ready =  ~ID_Stall & ID_Exception_Detect & ~ID_Exception_Mask;

.....

// ここもクロック立ち上がり時に判定、つまりIPに代入した次のクロックでここへ入ってくる。
// ID stage
else if (ID_Exception_Ready) begin
    Cause_BD <= (Status_EXL) ? Cause_BD : ID_IsBD;
    Cause_CE <= (COP3) ? 2'b11 : ((COP2) ? 2'b10 : ((COP1) ? 2'b01 : 2'b00));
    Cause_ExcCode30 <= Cause_ExcCode_bits;
    Status_EXL <= 1;
    EPC <= (Status_EXL) ? EPC : ID_RestartPC;
    BadVAddr <= BadVAddr;

.....

/*** Cause Register ExcCode Field ***/
 always @(*) begin
     // Ordered by Pipeline Stage with Interrupts last
     if      (EXC_AdEL) Cause_ExcCode_bits <= 4'h4;     // 00100
     else if (EXC_AdES) Cause_ExcCode_bits <= 4'h5;     // 00101
     else if (EXC_Tr)   Cause_ExcCode_bits <= 4'hd;     // 01101
     else if (EXC_Ov)   Cause_ExcCode_bits <= 4'hc;     // 01100
     else if (EXC_Sys)  Cause_ExcCode_bits <= 4'h8;     // 01000
     else if (EXC_Bp)   Cause_ExcCode_bits <= 4'h9;     // 01001
     else if (EXC_RI)   Cause_ExcCode_bits <= 4'ha;     // 01010
     else if (EXC_CpU)  Cause_ExcCode_bits <= 4'hb;     // 01011
     else if (EXC_AdIF) Cause_ExcCode_bits <= 4'h4;     // 00100
     else if (EXC_Int)  Cause_ExcCode_bits <= 4'h0;     // 00000     // OK that NMI writes this.
     else               Cause_ExcCode_bits <= 4'bxxxx;
 end

EXC_Sys(システムコール例外に関して)

コア側の制御ユニットで、命令デコード時に「syscall」だったらEXC_Sys wireをassertする。それをCP0がInputとして受取り、検知する。ソフトウェア例外用に用意されている IP[1:0] は特に使用していないようなので無視して良いだろう。

TLB例外を追加する場合

  • TLBL(code=2), TLBS(code=3) を上記CP0クラスに追加する必要あり。input wireとして EXC_TLBL EXC_TLBS を追加して中のコードもいじる必要があるだろう

f:id:varmil:20171204204836p:plain