1.中断源(ID)定义
在地址图(详细内容点击这里)上对中断源的定义如图1所示。可以看到,目前地址图V1.2定义了63个中断源(中断源0为没有中断)。
图1 中断源(ID)
中断源在CPU内部的实现如下所示,其中具体的外部中断请求都会连接到各自的模块输出。例如:pwm3_cmp_irq(pwm3比较结果的中断请求)是PWM模块中的输出。而汇集了所有类型的中断源i_plic_irq将会被输入到外部平台级中断处理(PLIC)内核,进行仲裁,选举出将要处理的中断请求。即当同时有多个外部中断请求产生时,PLIC内核只会允许发生一个中断。
wire i2c3_irq; wire i2c2_irq; wire i2c1_irq; wire i2c0_irq; wire [3:0] pwm3_cmp_irq; wire [3:0] pwm2_cmp_irq; wire [3:0] pwm1_cmp_irq; wire [3:0] pwm0_cmp_irq ; wire spi3_irq; wire spi2_irq; wire spi1_irq; wire spi0_irq; wire [31:0] o_GPIO_irq; wire uart3_irq; wire uart2_irq; wire uart1_irq; wire uart0_irq; wire rtc_cmp1_irq; wire rtc_cmp0_irq; wire watchdog_irq; //注意这里的外部中断源排列是由高到低 wire [PLIC_IRQ_NUM - 1:0] i_plic_irq = { i2c3_irq, i2c2_irq, i2c1_irq, i2c0_irq, pwm3_cmp_irq[3:0], //56-59 pwm2_cmp_irq[3:0], //52-55 pwm1_cmp_irq[3:0], //48-51 pwm0_cmp_irq[3:0], //44-47 spi3_irq, spi2_irq, spi1_irq, spi0_irq, o_GPIO_irq[31:0], // 8-39 uart3_irq, uart2_irq, uart1_irq, uart0_irq, rtc_cmp1_irq, rtc_cmp0_irq, watchdog_irq, 1'b0 //注意中断源0表示没有中断 };
2.中断源关口(gateway)定义
每个中断源都有一个关口,主要处理中断信号并将其发送到PLIC内核,内核将这些中断信号锁存在中断悬挂位中。每个中断源都有单独的优先级。当目标中断源使能被拉高,且优先级大于阈值,则PLIC内核会向目标发送中断通知。目标接收到外部中断后,向内核发送一个中断声明,请求检索有最高优先级的悬挂中断,并清除相应的中断悬挂位。在中断完成后,目标向关口发送完成信息。
每个中断源只能在PLIC内核悬挂一个中断请求。关口仅在收到来自同源的先前的中断处理程序的通知已完成后,才将新的中断请求转发到PLIC内核。
图2所示为PLIC对中断处理的流程。可以看到,gateway一次仅将一个中断请求转发给PLIC,直到接收到中断完成后才转发后续的中断请求。目标可能需要一段时间来响应新的中断到达,但随后将向PLIC内核发送中断请求以获取中断ID。 PLIC内核将自动返回ID并清除相应的IP位,此后,其他任何目标都无法声明相同的中断请求。 处理程序处理完中断后,它将向gateway发送中断完成消息,以允许新的中断请求。
图2 PLIC 中断处理流程 [1]
相应的PLIC gateway代码(只显示相关部分)如下:
// 为每个中断源实现关口 IRQ_gateway IRQ_gateway_inst ( .clk ( clk ), //时钟 .rst_n ( rst_n ), //复位 .i_interrupt ( plic_irq_i_r ), //中断源 .o_plic_valid ( irq_i_gated_valid ), //输出中断是否有效通过关口 .i_plic_read ( irq_i_gated_ready ), //cpu 读 .i_plic_write ( rib_complete_irq ) //cpu 写 ); assign irq_i_gated_hsked = irq_i_gated_valid & irq_i_gated_ready;//定义关口握手信号,由中断有效通过关口信号 & CPU读信号决定 //gateway模块 module IRQ_gateway ( input clk, //时钟 input i_interrupt, //中断源 output o_plic_valid, //输出中断是否有效通过关口 input i_plic_read, //cpu 读 input i_plic_write, //cpu 写 input rst_n //复位 ); reg inFlight; assign o_plic_valid = (inFlight == 1'h0) ? i_interrupt : 1'b0; //如果inFlight为0,且有中断源到来,输出中断有效,否则输出中断无效 wire int_valid = i_interrupt & i_plic_read; //cpu读 & 中断源到来 always @(posedge clk or negedge rst_n) if (!rst_n) begin inFlight <= 1'h0; end else begin if (i_plic_write) //如果cpu写入,inFlight为0 inFlight <= 1'h0; else if (int_valid) //cpu读 & 中断源到来,inFlight为1 inFlight <= 1'h1; end endmodule
3.中断仲裁
比较和选择悬挂中断的部分代码如图3所示。一共有10层比较数组。其实现的逻辑是将每层数组相邻的数两两比较,给出其中较大的数,并放入下一层数组,直到得到最大的数。因为sifive公司定义了1024个中断源,为了与此兼容,FII RISC-V CPU用于中断仲裁的模块也是最大可以容纳1024个中断源(实际为1023个,因为中断源0表示没有中断发生),那么每经过一层数组比较,下一层数组就缩减了一倍。因为log(2)1024 = 10,所以这里用了10层比较数组来选举出优先级最高的中断源。
图3 仲裁代码
图4是一个3层比较数组的例子,方格中的数字指的是数组的索引。可以通过图4来理解图3中代码的数组索引运用。
图4 比较结构
图5所示是将数字1-8随机放进数组索引0-7的举例。可以看到经过每层比较后,最终剩下的是1-8中最大的数,8。
图5 仲裁举例
4.中断悬挂(部分)
相关代码如下:
// 为每个中断源实现中断悬挂位 // 如果清除了挂起的中断源,则可以接受来自关口的新中断 assign irq_i_gated_ready = (~irq_pend_r); // 当关口输出握手信号,中断源悬挂被立起来 assign irq_pend_set = irq_i_gated_hsked; //声明了最高优先级的悬挂中断后,相应的中断悬挂被清除 assign irq_pend_clr = rib_claim_irq; // 如果中断被声明,清除相应的中断源悬挂 assign irq_pend_ena = (irq_pend_set | irq_pend_clr); //使能由中断悬挂设立信号或是悬挂清除信号立起来 assign irq_pend_nxt = (irq_pend_set | (~irq_pend_clr)); //输入d flip flop的悬挂信号 fii_dfflr #(1) irq_pend_dfflr(irq_pend_ena , irq_pend_nxt, irq_pend_r, clk, rst_n); //锁存
相关参考文章:
RISC-V教学教案
5.文章参考
[1] Www2.eecs.berkeley.edu, 2021. [Online]. Available: https://www2.eecs.berkeley.edu/Pubs/TechRpts/2016/EECS-2016-129.pdf. [Accessed: 29- Mar- 2021].