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.
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
を追加して中のコードもいじる必要があるだろう