五、RISC-V SoC内核——中断 代码讲解

tinyriscv这个SoC工程的内核cpu部分,采用经典的三级流水线结构进行设计,即大家所熟知的:取值—>译码—>执行三级流水线。

另外,在最后一个章节中会上传额外添加详细注释的工程代码,完全开源,如有需要可自行下载。

上一篇博文中注释了执行模块,现在来介绍中断模块

目录

0 RISC-V SoC注解系列文章目录

1. 中断结构图

2. csr_reg 控制与状态寄存器

2.1 中断和异常概述

2.2 csr_reg.v基础知识

2.3 csr_reg.v注解

3. clint.v 模块注解

3.1 接口定义

3.2 程序内容

4. ctrl.v模块(跳转和流水线暂停)

4.1 ctrl接口定义

4.2 功能概述(可参考原博客)

参考:


0 RISC-V SoC注解系列文章目录

零、RISC-V SoC软核笔记详解——前言

 一、RISC-V SoC内核注解——取指

 二、RISC-V SoC内核注解——译码

三、RISC-V SoC内核注解——执行

四、RISC-V SoC内核注解——除法(试商法)

五、RISC-V SoC内核注解——中断

六、RISC-V SoC内核注解——通用寄存器

七、RISC-V SoC内核注解——总线

八、RISC-V SoC外设注解——GPIO

九、RISC-V SoC外设注解——SPI接口

十、RISC-V SoC外设注解——timer定时器

十一、RISC-V SoC外设注解——UART模块(终篇)

1. 中断结构图

RISC-V内核的中断部分:涉及到    csr_reg.v、clint.v、ctrl.v等模块。由于中断模块和三级流水线紧密相关,所以我们这里放置内核的整体结构图来注解中断控制部分:

五、RISC-V SoC内核——中断 代码讲解_第1张图片

2. csr_reg 控制与状态寄存器

2.1 中断和异常概述

  • 中断(Interrupt) 机制,即处理器核在顺序执行程序指令流的过程中突然被别的请求打断而中止执行当前的程序,转而去处理别的事情,待其处理完了别的事情,然后重新回到之前程序中断的点继续执行之前的程序指令流。
  • 异常(Exception)机制,即处理器核在顺序执行程序指令流的过程中突然遇到了异常的事情而中止执行当前的程序,转而去处理该异常。同步异常:是指令异常,可以定位到具有的指令。异步异常:不能定位到具体的指令。
  • 中断和异常的区别:异常一般是由于处理器内部事件或者程序执行中的事件引起的。
  • 在广义上讲:中断和异常都被称为异常。

RISC-V架构异常处理机制:

  • 当前RISCV架构文档主要分为“指令集文档”和“特权架构文档”。RISC-V架构的异常处理机制定义在“特权架构文档”中。RISC-V的架构不仅可以有机器模式(Machine Mode)的工作模式,还可以有用户模式(User Mode)、监督模式(Supervisor Mode)等工作模式。在不同的模式下均可以产生异常,并且有的模式也可以响应中断。RISC-V架构要求机器模式是必须具备的模式,其他的模式均是可选而非必选的模式。
  • 中断,跳转,暂停之间的区别:中断和跳转都是触发暂停的原因。中断操作是为了提高程序的执行效率。中断或者for循环中,都会有跳转操作。

• 退出中断:使用中断返回指令MRET,在机器模式下必备。

• 中断类型:

       外部中断:外设发生的中断,发生在处理核外部的中断。

       计时器中断(外部中断中的一种):用mie寄存器中的mtie域进行控制。

       软件中断:来自软件(C语言等软件语言)自己触发的中断。

       调试中断:Dubug时的中断。

• 中断屏蔽:通过MIE寄存器,来控制不同类型的中断使能和屏蔽(外部中断、计时器中断、软件中断)。

2.2 csr_reg.v基础知识

CSR寄存器模块(csr_reg.v)和通用寄存器模块reg.v的读、写操作是类似的,这里就不重复了。

csr_reg.v中,主要用到的寄存器以及含义如下:

  • mtvec(Machine Trap Vector):保存发生异常时处理器需要跳转的地址(用来设置中断和异常的入口)。中断和异常需要跳转的基地址不同。
  • mcause(Machine Exception Cause):当产生中断和异常时,mcause寄存器中会记录当前产生的中断或者异常类型。
  • mepc(Machine Exception PC):保存发生异常指令的地址(中断返回的地址)。 在RISCV下产生中断或异常时,硬件自动将返回地址保存在mepc寄存器中,当在中断处理中返回时,硬件自动将mepc中的地址赋值给pc运行。(pc+4)
  • mstatus(Machine Status):全局中断使能和其他状态信息。 这个寄存器顾名思义是用来控制cpu核当前的一些状态信息的,比如全局中断使能等。 具体解释如下(默认不能嵌套中断):

五、RISC-V SoC内核——中断 代码讲解_第2张图片

        mstatus [3] mie:中断全局使能,1为开启全局中断,0为关闭全局中断。发生中断之后,1变为0。            

         mstatus [7] mpie:存储mie在中断发生之前的数值。

mie(Machine Interrupt Enable):指明处理器目前能处理和忽略的中断。三种中断类型在m模式和s模式下都有相应的中断使能位设置,这是通过mie寄存器实现的。
mscratch(machine srcatch register)机器模式擦写寄存器:mscratch寄存器用于机器模式下的程序临时保存某些数据。mscratch寄存器可以提供一种快速的保存、恢复机制。比如,在进入机器模式的异常处理程序后,将应用程序的某个通用寄存器的值临时存入mscratch寄存器中,然后在退出异常处理程序之前,将mscratch寄存器中的值读出恢复至通用寄存器。
   时钟周期计数器(64bit):mcycle、mcycleh

  • mcycle: 计数值的低32位
  • mcycleh:计数值的高32位

2.3 csr_reg.v注解

输入和输出接口如下:

input wire clk,  
input wire rst,  
// form ex  
input wire we_i,                        // ex模块写寄存器标志  
input wire[`MemAddrBus] waddr_i,        // (是来自id吧!!!)ex模块写寄存器地址 32位  
input wire[`RegBus] data_i,             // ex模块写寄存器数据 32位  
// form id  
input wire[`MemAddrBus] raddr_i,        // ex模块读寄存器地址 32位  
// to id  
output reg[`RegBus] data_o              // id模块读寄存器数据  
// from clint  
input wire clint_we_i,                  // clint模块写寄存器标志  
input wire[`MemAddrBus] clint_raddr_i,  // clint模块读寄存器地址  
input wire[`MemAddrBus] clint_waddr_i,  // clint模块写寄存器地址  
input wire[`RegBus] clint_data_i,       // clint模块写寄存器数据  
// to clint  
output wire global_int_en_o,            // 全局中断使能标志     
output reg[`RegBus] clint_data_o,       // clint模块读寄存器数据(未使用)  
output wire[`RegBus] clint_csr_mtvec,   // mtvec 保存发生异常时处理器需要跳转的地址  
output wire[`RegBus] clint_csr_mepc,    // mepc 保存发生异常指令的地址  
output wire[`RegBus] clint_csr_mstatus, // mstatus 全局中断使能和其他状态信息  

csr_reg.v主要功能:

程序中定义了几种CSR寄存器,以及一个64位的计数器(复位撤销后就一直计数,计数器统计自CPU复位以来共运行了多少个周期。(核心时钟可能是动态调整的))。

    写寄存器:根据写寄存器地址的后12位,将ex或者clint模块中的数据寄存在控制与状态寄存器(CSR寄存器)中;

    读寄存器(组合逻辑):读寄存器的地址来自译码id模块,并将从寄存器中读到的数据,送给译码id模块(根据读寄存器地址的后12位)。读寄存器的地址来自中断clint模块,并将从寄存器中读到的数据,送给clint模块(根据读寄存器地址的后12位)。

3. clint.v 模块注解

3.1 接口定义

input wire clk,  
input wire rst,  
  
// from core  
input wire[`INT_BUS] int_flag_i,         // 中断输入信号(定时器timer中断输入)  
  
// from id  
input wire[`InstBus] inst_i,             // 指令内容  
input wire[`InstAddrBus] inst_addr_i,    // 指令地址  
  
// from ex  
input wire jump_flag_i,    
input wire[`InstAddrBus] jump_addr_i,  
input wire div_started_i, //除法开始标志(在执行除法操作时为1,程序不能响应同步中断)  
  
// from ctrl  
input wire[`Hold_Flag_Bus] hold_flag_i,  // 流水线暂停标志(未使用)  
  
// from csr_reg  
input wire[`RegBus] data_i,              // CSR寄存器输入数据(未使用)  
input wire[`RegBus] csr_mtvec,           // mtvec寄存器  
input wire[`RegBus] csr_mepc,            // mepc寄存器  
input wire[`RegBus] csr_mstatus,         // mstatus寄存器  
  
input wire global_int_en_i,              // 全局中断使能标志  
  
// to ctrl  
output wire hold_flag_o,                 // 流水线暂停标志  
  
// to csr_reg  
output reg we_o,                         // 写CSR寄存器标志  
output reg[`MemAddrBus] waddr_o,         // 写CSR寄存器地址  
output reg[`MemAddrBus] raddr_o,         // 读CSR寄存器地址  
output reg[`RegBus] data_o,              // 写CSR寄存器数据  
  
// to ex  
output reg[`InstAddrBus] int_addr_o,     // 中断入口地址  
output reg int_assert_o                  // 中断标志  

3.2 程序内容

• RISC-V中断分为两种类型,一种是同步中断,即ECALL、EBREAK等指令所产生的中断,另一种是异步中断,即GPIO、UART等外设产生的中断。

• 程序设计思路:当检测到中断(中断返回)信号时,先暂停整条流水线,设置跳转地址为中断入口地址,然后读、写必要的CSR寄存器(mstatus、mepc、mcause等),等读写完这些CSR寄存器后取消流水线暂停,这样处理器就可以从中断入口地址开始取指,进入中断服务程序。

程序:

1.中断仲裁(组合逻辑):同步中断>异步中断>中断返回

    同步中断:如果执行阶段的指令为除法指令,则先不处理同步中断,等除法指令执行完再处理。

    异步中断:定时器中断(外设中断)和全局中断使能(mstatus[3])打开时,触发异步中断。

    中断返回:中断返回指令。

2.CSR寄存器状态机跳转

    提取中断返回的地址和引起中断的编码,以及CSR寄存器状态跳转。

3.写CSR寄存器(mstatus、mepc、mcause)

    先写中断返回地址mepc

    再写mstatus,以关闭全局中断(mstatus[3]=0)

    将中断异常编码写入mcause寄存器

    中断返回,返回的同时需要将全局中断位恢复(mstatus[3]=mstatus[7])

4.发送中断信号给执行模块(将csr寄存器写完之后,才可以发送中断信号给执行模块)

    inst_i:判断是否是同步中断。

    inst_addr_i:当前指令的地址。

    inst_flag_i:计时器中断的中断flag信号。

    int_assert_o:中断有效信号,信号为1时,开始运行中断处理程序。

4. ctrl.v模块(跳转和流水线暂停)

4.1 ctrl接口定义

input wire rst,  
  
// from ex  
input wire jump_flag_i, //跳转标志  
input wire[`InstAddrBus] jump_addr_i,  
input wire hold_flag_ex_i,  //暂停标志  
  
// from rib  
input wire hold_flag_rib_i,  
  
// from jtag  
input wire jtag_halt_flag_i,  
  
// from clint  
input wire hold_flag_clint_i,  
  
output reg[`Hold_Flag_Bus] hold_flag_o,  
  
// to pc_reg  
output reg jump_flag_o,  
output reg[`InstAddrBus] jump_addr_o  

4.2 功能概述(可参考原博客)

跳转就是改变PC寄存器的值。又因为跳转与否需要在执行阶段才知道,所以当需要跳转时,则需要暂停流水线(正确来说是冲刷流水线。流水线是不可以暂停的,除非时钟不跑了)。
冲刷流水线指的是,在指令中流淌的是NOP的指令。
tinyriscv的流水线结构如下图所示:

五、RISC-V SoC内核——中断 代码讲解_第3张图片

1.其中长方形表示的是时序逻辑电路,云状型表示的是组合逻辑电路。过程如下:

2.在执行阶段,当判断需要发生跳转时,发出跳转信号和跳转地址给ctrl(ctrl.v)模块。ctrl模块判断跳转信号有效后会给pc_reg、if_id和id_ex模块发出流水线暂停信号,并且还会给pc_reg模块发出跳转地址。

3.在时钟上升沿到来时,if_id和id_ex模块如果检测到流水线暂停信号有效则送出NOP指令,从而使得整条流水线(译码阶段、执行阶段)流淌的都是NOP指令,已经取出的指令就会无效,这就是流水线冲刷机制。

为了提高MCU的效率。因此根据不同的暂停信号,暂停不同的流水线阶段。具体操作如下:

/* 
`define Hold_None 3'b000 
`define Hold_Pc   3'b001 
`define Hold_If   3'b010 
`define Hold_Id   3'b011 
*/  
    always @ (*) begin  
        jump_addr_o = jump_addr_i;  
        jump_flag_o = jump_flag_i;  
        // 默认不暂停  
        hold_flag_o = `Hold_None;  
        // 按优先级处理不同模块的请求  
        if (jump_flag_i == `JumpEnable || hold_flag_ex_i == `HoldEnable || hold_flag_clint_i == `HoldEnable) begin  
            // 对于跳转操作、来自执行阶段的暂停、来自中断模块的暂停则暂停整条流水线。   
            hold_flag_o = `Hold_Id;  
        end else if (hold_flag_rib_i == `HoldEnable) begin  
            // 对于总线暂停,只需要暂停PC寄存器,让译码和执行阶段继续运行。  
            hold_flag_o = `Hold_Pc;  
        end else if (jtag_halt_flag_i == `HoldEnable) begin  
            // 对于jtag模块暂停,则暂停整条流水线。  
            hold_flag_o = `Hold_Id;  
        end else begin  
            hold_flag_o = `Hold_None;  
        end  
    end  

总线暂停,一般是因为要访问外设。暂停pc寄存器是为了将下一条指令所需的数据在当前指令的访存操作中提取出来;只暂停pc寄存器,是因为访问外设期间的操作,和当前指令的执行没有冲突。

你可能感兴趣的:(数字IC设计,RISC-V,risc-v,fpga开发,verilog,soc)