以下内容摘自《步步惊芯——软核处理器内部设计分析》一书
16.3
可编程中断控制器PIC分析
16.3.1 PIC介绍
可编程中断控制器Programmable Interrupt Controller(PIC)用来响应各种中断事件,如:键盘事件、串口数据到达等,PIC收集所有的中断,并通知CPU中断到达,后者转入到中断处理例程进行处理。OR1200最多支持32个中断。其功能实现主要依靠两个特殊寄存器:中断屏蔽寄存器PICMR、中断状态寄存器PICSR。通过PICMR可以设置是否屏蔽某些中断,通过PICSR可以知道中断源的信息。PICMR、PICSR是第9组特殊寄存器,如表16.7所示。
中断屏蔽寄存器PICMR的格式如表16.8所示,该寄存器可读、可写。标志IUM中的值表示对应中断是否被屏蔽,IUM为0x0,表示所有中断都被屏蔽,IMU为0xFFFFFFFF,表示所有中断都没有被屏蔽。在OR1200中,IUM的位数可以配置,配置范围是2-31,这是因为IUM的最低两位始终保持为1,即中断源0、1设置为不可屏蔽,可以将这两个中断源对应为电源关闭、系统复位。
中断状态寄存器PICSR的格式如表16.9所示,该寄存器可读、可写。标志IS中的值表示对应中断是否发生并等待处理,IS为0x0表示没有中断发生,第i位为1表示第i个中断源发生了中断。判断中断是否发生的方法有电平触发、边沿触发,OR1200支持的是电平触发,此时,IS的第i位实际对应的就是第i个中断源的当前电平值。
OR1200中断处理过程如下:
(1)中断源声明中断发生,PICSR[i]为1,如果PICMR[i]为1,那么通知处理器中断到达
(2)处理器转入中断处理例程进行中断处理
(3)中断已处理,中断处理例程通知中断源
(4)中断源取消中断声明
(5)中断处理例程设置PICSR[i]为0,退出中断处理例程
注意一点,单独设置PICSR[i]并不会清除中断,必须通知中断源,由后者取消中断声明。
16.3.2 PIC的对外连接关系及相关宏定义
OR1200处理器中可编程中断控制器
PIC的对外连接关系如图
16.3所示,通过箭头方向表示该信号是输入还是输出。以
spr_xxx开始的接口都是与特殊寄存器读写有关的信号,含义也很明了,此外,
pic_int是一个宽度可配置的接口,最大为
32,对应
32个外部中断源输入。当中断发生时通过
intr接口通知
CPU模块,其值为
1表示中断发生,连接到
CPU模块的接口
sig_int,同时还要通过
pic_wakeup接口通知电源管理模块
PM,如果中断发生时,处理器处于
Sleep、
Doze模式,
PM模块在接收到
pic_wakeup为
1后,会恢复正常模式,参考
16.1节中对
PM模块的分析。
OR1200中与可编程中断控制器
PIC有关的宏定义如下:
or1200_defines.v
`define OR1200_PIC_IMPLEMENTED //是否实施PIC模块,PIC模块是可选模块
`define OR1200_PIC_INTS 20 //定义外部中断源的数目,最多是32个,默认是20个
`define OR1200_PIC_OFS_PICMR 2'd0 //PICMR、PICSR寄存器在第9组特殊寄存器中的索引
`define OR1200_PIC_OFS_PICSR 2'd2
`define OR1200_PICOFS_BITS 1:0
`define OR1200_PIC_PICMR //需要定义这个宏,才可以使用PICMR寄存器
`define OR1200_PIC_PICSR //需要定义这个宏,才可以使用PICSR寄存器
`define OR1200_PIC_READREGS //有了这个宏定义,才可以读PIC中的特殊寄存器PICMR、PICSR
`define OR1200_PIC_UNUSED_ZERO
16.3.3 PIC代码分析
PIC的主要代码就是配置
PICMR、
PICSR寄存器,以及依据
PICMR、
PICSR的值判断中断是否发生,分析如下(为了方便理解,笔者改变了代码顺序):
or1200_pic.v
module or1200_pic(
clk, rst, spr_cs, spr_write, spr_addr, spr_dat_i, spr_dat_o, pic_wakeup, intr, pic_int
);
……
output pic_wakeup; //输出到PM模块
output intr; //输出到CPU模块
input [`OR1200_PIC_INTS-1:0] pic_int; //输入的中断信号,其宽度可通过OR1200_PIC_INTS配置
`ifdef OR1200_PIC_IMPLEMENTED
`ifdef OR1200_PIC_PICMR
reg [`OR1200_PIC_INTS-1:2] picmr; //PICMR寄存器的宽度也和OR1200_PIC_INTS相匹配,但少了两位,
//这是由于中断源0、1不可屏蔽,所以PICMR的宽度比中断输入
//pic_int的宽度少了两位
`else
wire [`OR1200_PIC_INTS-1:2] picmr;
`endif
`ifdef OR1200_PIC_PICSR
reg [`OR1200_PIC_INTS-1:0] picsr; //PICSR寄存器的宽度也和OR1200_PIC_INTS相匹配
`else
wire [`OR1200_PIC_INTS-1:0] picsr;
`endif
……
//如果spr_cs为1,那么依据spr_addr的最低两位判断指令l.mfspr/l.mtspr的访问目标是PICMR还是PICSR
//picmr_sel为1,表示访问目标是PICMR,picsr_sel为1,表示访问目标是PICSR
assign picmr_sel = (spr_cs && (spr_addr[`OR1200_PICOFS_BITS] == `OR1200_PIC_OFS_PICMR))
? 1'b1 : 1'b0;
assign picsr_sel = (spr_cs && (spr_addr[`OR1200_PICOFS_BITS] == `OR1200_PIC_OFS_PICSR))
? 1'b1 : 1'b0;
//将pic_int与{picmr,2’b11}相与,得出未屏蔽的中断信息保存在um_ints,注意中断源0、1不可屏蔽,
//与PICMR的值无关,此处直接就是2’b11
assign um_ints = pic_int & {picmr, 2'b11};
//um_ints不为0,表示有中断发生,设置intr为1,intr输出到CPU模块的sig_int接口
assign intr = |um_ints;
//中断发生后还要通过pic_wakeup接口通知PM模块
assign pic_wakeup = intr;
`ifdef OR1200_PIC_PICMR
always @(posedge clk or `OR1200_RST_EVENT rst)
if (rst == `OR1200_RST_VALUE)
picmr <= {1'b1, {`OR1200_PIC_INTS-3{1'b0}}};
else if (picmr_sel && spr_write) begin
picmr <= spr_dat_i[`OR1200_PIC_INTS-1:2]; //写PICMR,注意最低两位不受影响
end
`else
assign picmr = (`OR1200_PIC_INTS)'b1;
`endif
//给PICSR寄存器赋值,PICSR的值取决于指令l.mtspr写入的数据,以及当前发生的外部中断情况,
//从这里也可以发现:当中断发生时,单独使用l.mtspr清除PICSR寄存器并不会使得PICSR的值改
//变,因为外部设备没有取消中断声明,um_ints的值没变,所以必须要外部设备取消中断声明
`ifdef OR1200_PIC_PICSR
always @(posedge clk or `OR1200_RST_EVENT rst)
if (rst == `OR1200_RST_VALUE)
picsr <= {`OR1200_PIC_INTS{1'b0}};
else if (picsr_sel && spr_write) begin
picsr <= spr_dat_i[`OR1200_PIC_INTS-1:0] | um_ints;
end else
picsr <= picsr | um_ints;
`else
assign picsr = pic_int;
`endif
always @(spr_addr or picmr or picsr)
case (spr_addr[`OR1200_PICOFS_BITS]) // synopsys parallel_case
`OR1200_PIC_OFS_PICMR: begin
spr_dat_o[`OR1200_PIC_INTS-1:0] = {picmr, 2'b11}; //读PICMR,最低两位始终为1
spr_dat_o[31:`OR1200_PIC_INTS] = {32-`OR1200_PIC_INTS{1'b0}};
end
default: begin
spr_dat_o[`OR1200_PIC_INTS-1:0] = picsr; //读PICSR
spr_dat_o[31:`OR1200_PIC_INTS] = {32-`OR1200_PIC_INTS{1'b0}};
end
endcase
`else //没有配置PIC模块的情况如下
assign intr = pic_int[1] | pic_int[0]; //没有配置PIC模块时,中断0、1还可以使用
assign pic_wakeup= intr;
assign spr_dat_o[`OR1200_PIC_INTS-1:0] = `OR1200_PIC_INTS'b0;
assign spr_dat_o[31:`OR1200_PIC_INTS] = 32-`OR1200_PIC_INTS'b0;
`endif
endmodule
当中断发生后,PIC模块置高intr,参考图16.3可知,intr连接到CPU模块的sig_int接口,CPU内部的EXCEPTION模块接收该信号,并进行处理,与计时器中断相似,并不会立即响应该中断,而是需要满足一定条件才会响应,如下:
or1200_exception.v
assign int_pending = sig_int & (sr[`OR1200_SR_IEE] | (sr_we & to_sr[`OR1200_SR_IEE]))
& id_pc_val & delayed_iee[2] & ~ex_freeze & ~ex_branch_taken
& ~ex_dslot & ~(sr_we & ~to_sr[`OR1200_SR_IEE]);
各个条件的说明参考计时器中断,当条件满足后,int_pending为1,处理器进入外部中断异常的处理,具体过程与计时器中断一样,此处不再赘述。