⚡ 第24课:中断控制器

📖 本课目标

设计并实现8源可屏蔽中断控制器,支持优先级仲裁、中断向量化和多种EOI(End of Interrupt)模式。中断是CPU与外设高效协作的核心机制——没有中断,CPU只能忙等轮询,浪费99%的处理能力。

🧠 中断机制深度解析

中断的本质是:外设有事要做时,主动打断CPU当前的工作,让CPU优先处理紧急事务。这就像你正在看书,电话响了——你记下当前页码,接电话,挂了电话后继续看。

中断处理流程(CPU视角) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 正常执行 ────→ IRQ到来 ──→ CPU响应中断 ┌─────┐ ┌──────────────────┐ │ │ ←── 挂起 ──── │ 1. 完成当前指令 │ │ 程序 │ │ 2. 保存PC到栈 │ │ A │ │ 3. 保存标志寄存器│ │ │ │ 4. 禁止中断(I=1) │ └─────┘ │ 5. 跳转到ISR地址 │ ↑ └────────┬─────────┘ │ │ │ ┌────────▼─────────┐ │ │ 中断服务程序(ISR) │ │ │ - 保存寄存器 │ │ │ - 处理中断源 │ │ │ - 恢复寄存器 │ │ │ - EOI(通知控制器) │ │ │ - RTI(中断返回) │ │ └────────┬─────────┘ │ │ └────────────────────────────┘ CPU自动从栈恢复PC和标志 继续执行程序A

轮询 vs 中断

特性轮询(Polling)中断(Interrupt)
CPU利用率极低(忙等浪费周期)高(无事时CPU做别的工作)
响应延迟不确定(取决于轮询间隔)确定(几个时钟周期内)
编程复杂度简单(死循环检查)复杂(需要保存/恢复上下文)
多设备困难(需要依次轮询所有设备)自然支持(各设备独立请求)
典型场景简单嵌入式、无OS裸机操作系统、多任务系统
💡 经典设计参考:我们的中断控制器参考了Intel 8259A PIC(IBM PC使用的经典中断控制器)。8259A支持8级中断、固定优先级、ICW/OCW命令字序列。我们简化了命令接口,但保留了优先级仲裁和EOI模式的核心设计。

🏗️ 中断控制器架构

中断控制器架构 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 8个中断源 中断控制器 CPU ┌────────┐ ┌──────────────┐ │IRQ0 定时器│──→ bit0 ──→│ │ │IRQ1 键盘 │──→ bit1 ──→│ 优先级仲裁器 │──→ INT_REQUEST │IRQ2 UART │──→ bit2 ──→│ (固定优先级) │──→ INT_VECTOR[2:0] │IRQ3 VGA │──→ bit3 ──→│ │ │IRQ4 GPIO │──→ bit4 ──→│ ┌────────┐ │ ←── INT_ACK │IRQ5 保留 │──→ bit5 ──→│ │IRR IRR │ │ │IRQ6 保留 │──→ bit6 ──→│ │ISR ISR │ │ │IRQ7 NMI │──→ bit7 ──→│ │IMR IMR │ │ └────────┘ │ └────────┘ │ └──────────────┘ IRR = Interrupt Request Register (挂起寄存器) ISR = In-Service Register (服务中寄存器) IMR = Interrupt Mask Register (屏蔽寄存器) 优先级: IRQ0(最高) > IRQ1 > ... > IRQ7(最低) 内存映射: $FF20: IMR (屏蔽寄存器, R/W) $FF21: IRR (挂起寄存器, R/写1清位) $FF22: ISR (服务中寄存器, R/写1=EOI) $FF23: 模式寄存器 (bit0=AutoEOI)

🔧 Verilog实现

// interrupt_controller.v - 8源可屏蔽中断控制器

module interrupt_controller (
    input  wire        clk,
    input  wire        rst_n,
    input  wire        cs,
    input  wire        rw,
    input  wire [1:0]  reg_addr,
    input  wire [7:0]  cpu_data_in,
    output reg [7:0]  cpu_data_out,
    input  wire [7:0]  irq_sources,
    output reg        int_request,
    output reg [2:0]  int_vector,
    input  wire        int_ack
);

    reg [7:0] irq_mask;       // 屏蔽寄存器
    reg [7:0] irq_pending;    // 挂起寄存器
    reg [7:0] irq_in_service; // 服务中寄存器
    reg       auto_eoi;

    // 优先级仲裁:找到最高优先级的有效请求
    reg [2:0] best_irq;
    reg       has_irq;

    always @(*) begin
        has_irq = 1'b0;
        best_irq = 3'd0;
        if (irq_pending[0] & irq_mask[0] & ~irq_in_service[0])
            begin best_irq = 3'd0; has_irq = 1'b1; end
        else if (irq_pending[1] & irq_mask[1] & ~irq_in_service[1])
            begin best_irq = 3'd1; has_irq = 1'b1; end
        else if (irq_pending[2] & irq_mask[2] & ~irq_in_service[2])
            begin best_irq = 3'd2; has_irq = 1'b1; end
        else if (irq_pending[3] & irq_mask[3] & ~irq_in_service[3])
            begin best_irq = 3'd3; has_irq = 1'b1; end
        else if (irq_pending[4] & irq_mask[4] & ~irq_in_service[4])
            begin best_irq = 3'd4; has_irq = 1'b1; end
        else if (irq_pending[5] & irq_mask[5] & ~irq_in_service[5])
            begin best_irq = 3'd5; has_irq = 1'b1; end
         (irq_pending[6] & irq_mask[6] & ~irq_in_service[6])
            begin best_irq = 3'd6; has_irq = 1'b1; end
        else if (irq_pending[7] & irq_mask[7] & ~irq_in_service[7])
            begin best_irq = 3'd7; has_irq = 1'b1; end
    end

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            irq_mask      <= 8'd0;
            irq_pending   <= 8'd0;
            irq_in_service<= 8'd0;
            auto_eoi      <= 1'b0;
            int_request   <= 1'b0;
            int_vector    <= 3'd0;
            cpu_data_out  <= 8'd0;
        end else begin
            // 捕获中断请求
            irq_pending <= irq_pending | (irq_sources & irq_mask);

            // CPU应答处理
            if (int_ack && has_irq) begin
                int_vector <= best_irq;
                irq_in_service[best_irq] <= 1'b1;
                irq_pending[best_irq] <= 1'b0;
                int_request <= 1'b0;

                if (auto_eoi)
                    irq_in_service <= 8'd0;
            end

            // 生成中断请求
            if (has_irq && !int_ack)
                int_request <= 1'b1;

            // CPU寄存器写入
            if (cs && !rw) begin
                case (reg_addr)
                    2'b00: irq_mask <= cpu_data_in;
                    2'b01: irq_pending <= irq_pending & ~cpu_data_in;
                    2'b10: irq_in_service <= irq_in_service & ~cpu_data_in;
                    2'b11: auto_eoi <= cpu_data_in[0];
                endcase
            end

            if (cs && rw) begin
                case (reg_addr)
                    2'b00: cpu_data_out <= irq_mask;
                    2'b01: cpu_data_out <= irq_pending;
                    2'b10: cpu_data_out <= irq_in_service;
                    2'b11: cpu_data_out <= {7'd0, auto_eoi};
                endcase
            end
        end
    end

endmodule
✅ Verilator验证通过 —— interrupt_controller.v 通过 verilator --lint-only 检查,无错误无警告。

📋 中断向量表设计

CPU收到中断请求后,根据int_vector跳转到对应的中断服务程序。向量表放在内存低地址:

中断向量表 (ROM起始处) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ $0000: JMP timer_isr ; IRQ0 - 定时器 $0003: JMP keyboard_isr ; IRQ1 - 键盘 $0006: JMP uart_isr ; IRQ2 - UART $0009: JMP vga_isr ; IRQ3 - VGA $000C: JMP gpio_isr ; IRQ4 - GPIO $000F: JMP reserved5_isr ; IRQ5 - 保留 $0012: JMP reserved6_isr ; IRQ6 - 保留 $0015: JMP nmi_isr ; IRQ7 - NMI(不可屏蔽) 每个ISR占3字节(JMP指令) CPU收到INT_ACK后,PC ← vector_table[irq_num*3]

中断服务程序模板

; 定时器中断服务程序模板
timer_isr:
    PHA            ; 保存A
    PHX            ; 保存X
    PHY            ; 保存Y
    ; ... 处理定时器中断 ...
    LDA #$01       ; 写1到bit0清除挂起
    STA $FF21      ; EOI: 清除IRR
    STA $FF22      ; EOI: 清除ISR
    PLY            ; 恢复Y
    PLX            ; 恢复X
    PLA            ; 恢复A
    RTI            ; 中断返回

🎯 练习

练习1:中断嵌套

当前设计不支持中断嵌套——一个中断正在服务时,所有同级和低级中断都被阻塞。修改设计:当高优先级中断到来时,允许打断当前低优先级中断的ISR。提示:在int_ack时只设置当前irq的ISR位,仲裁时排除ISR中优先级更高或相等的位。

练习2:优先级轮转

实现优先级轮转(Round-Robin):每次中断服务完成后,刚服务完的中断源优先级降到最低,其他中断源优先级提升。这保证了低优先级中断不会永远被饿死。添加一个优先级基准寄存器来实现轮转。

练习3:NMI处理

IRQ7设计为NMI(不可屏蔽中断)。NMI的特点是:不能被屏蔽(忽略IMR),用于处理灾难性事件(掉电、看门狗超时、总线错误)。修改控制器,让IRQ7绕过屏蔽寄存器,并确保NMI不会被丢失。

练习4:中断共享

当多个外设共享同一条IRQ线时(类似PCI的INTx共享),需要软件方法判断具体是哪个设备请求了中断。设计一个"共享中断"机制:ISR中依次检查每个共享设备的就绪位,找到请求者后处理之。修改硬件添加"中断确认"握手协议。

🏆 成就解锁:中断大师

你实现了完整的可屏蔽中断控制器!这包括:

中断是操作系统的灵魂——有了它,CPU终于可以高效地同时处理多个任务了!