3 Star 26 Fork 6

lanzhoo / TangNano9k_Tutorial

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
Interrupt_02.md 15.44 KB
一键复制 编辑 原始数据 按行查看 历史
lanzhoo 提交于 2022-12-22 13:33 . simple interrupt

中断(2):简单实现

硬件实现

复制04_arduino_step03到05_interrupt_step01,修改工程名字。

实现功能简述

本例程先跑通一个简单的实现。

硬件实现的功能包括:

  • 中断响应:完成当前指令时,检查中断请求信号,若有效,保存断点地址,跳转到中断入口地址;
  • 中断返回:当遇到中断返回指令时,跳转到保存的断点地址;

说明:

  • core提供一个中断请求输入端口;
  • 不实现自动中断允许/中断禁止、不实现中断嵌套;
  • 硬件不开展保护现场、恢复现场任务;
  • 暂不实现中断源管理模块(由多个中断源的中断请求仲裁产生core中断请求信号和中断入口地址,中断控制和管理(全局中断允许、中断掩码、中断优先级等))
  • 暂不实现相关CSR系统寄存器:固定中断服务程序入口、内部保存断点;
  • 当前中断处理机制不会自动解除int_pending信号。(当前没有实现中断允许(global int enable)、中断屏蔽(mask)、中断等待(pending)信号)

具体实现

1. core增加中断请求输入端口

module core (
...
    input wire ext_int,
    output wire int_active,
...
);

其中int_active用于表示当前处于中断处理状态,目前没用上。

2. 中断处理过程状态机

采用FSM的方式进行中断处理。

代码段1:

    //FSM section 1
    localparam S_INT_IDLE = 0, S_INT_TRIGGERED = 1, S_INT_ACTIVE=2, S_INT_MRET=3;
    reg [1:0] current_state = 0, next_state;    

    always @(negedge core_clk or negedge reset_n) begin
        if (!reset_n) begin
            current_state <= S_INT_IDLE;
        end else begin
            current_state <= next_state;
        end
    end

解读:声明四种状态。用core_clk的下降沿触发切换状态。

普通状态是S_INT_IDLE,当输入的中断请求信号有效(高电平)时,core_clk的下降沿触发切换进入S_INT_TRIGGERED状态。

之所以使用core_clk下降沿触发,是因为这时当前指令的执行动作已基本完成(除了回写和STORE操作也是由下降沿触发),下一条指令的地址也已确定。这时触发中断可以保证当前指令完成以及明确断点位置。

代码段2:

    //FSM section 2

    //ctrl signal
    reg ctrl_int_triggered;
    reg ctrl_int_mret;

    always @(*) begin
        next_state = 0;
        ctrl_int_triggered = 0;
        ctrl_int_mret = 0;

        case (current_state) 
            S_INT_IDLE: begin
                if (ext_int) begin  
                    next_state <= S_INT_TRIGGERED;
                end else begin
                    next_state <= S_INT_IDLE;                        
                end
            end
            S_INT_TRIGGERED : begin
                ctrl_int_triggered = 1'b1;
                next_state <= S_INT_ACTIVE;
            end
            S_INT_ACTIVE : begin
                if (inst_MRET) begin
                    next_state <= S_INT_MRET;
                end else begin
                    next_state <= S_INT_ACTIVE;
                end
            end
            S_INT_MRET : begin
                ctrl_int_mret = 1'b1;
                next_state <= S_INT_IDLE;
            end
            default: begin
                next_state <= S_INT_IDLE;
            end
        endcase
    end
    
    //FSM section 3
	always @(posedge ctrl_int_triggered) begin
        csr_mepc <= pc_next;
    end
    

解读:

  • 在idle状态下判断中断请求信号,进入triggered状态:
            S_INT_IDLE: begin
                if (ext_int) begin  
                    next_state <= S_INT_TRIGGERED;
  • 在triggered状态下,保存断点到csr_mepc,然后下一时钟进入active状态:
            S_INT_TRIGGERED : begin
                ctrl_int_triggered = 1'b1;
                next_state <= S_INT_ACTIVE;
	always @(posedge ctrl_int_triggered) begin
        csr_mepc <= pc_next;
    end                
  • 在active状态下,当遇到mret指令时,下一时钟进入mret状态:
            S_INT_ACTIVE : begin
                if (inst_MRET) begin
                    next_state <= S_INT_MRET;
  • 在mret状态下,下一时钟进入idle状态(ctrl_int_mret没用上)。

3. 中断服务程序入口

将入口地址固定为0x80002e00。

    wire [31:0]  isr_entrance = 32'h80002e00;
    assign pc_next_with_int = (ctrl_int_triggered) ? isr_entrance : pc_next;

修改原有的pc代码,将送PC寄存器的值由pc_next改为pc_next_with_int。这样当ctrl_int_triggered即S_INT_TRIGGERED状态下,下一PC值为isr_entrance:

    //PC
    reg [31:0] program_counter;
    wire [31:0] pc_next;
    wire [31:0] pc_next_with_int;

    always @(posedge core_clk or negedge reset_n) begin
        if (~reset_n) begin
            program_counter <= 32'b0;
        end
        else begin
//            program_counter <= pc_next;
            program_counter <= pc_next_with_int;
        end
    end

4. MRET指令

译码:

    wire opcode_SYSTEM = (opcode == 7'b1110011);

    wire inst_MRET = opcode_SYSTEM & (funct12 == 12'b001100000010);    

执行,将断点(csr_mepc)送pc_next:

    assign pc_next = (~reset_n_sync) ? 32'b0 :
                        branch_in_effect ? program_counter + i_imm_B :
                        inst_JALR ? rs1_plus_imm_i :
                          inst_JAL ? program_counter + i_imm_J : 
                            inst_MRET ? csr_mepc : pc_plus_four;  

5. top模块的修改

本例程中演示按键0的中断。

首先将按键0的按下事件产生一个wire_clk宽度的脉冲,采用移位寄存器实现:

    //external interrupt
    reg [1:0] int_delay;
    always @(posedge core_clk) begin
        int_delay <= {int_delay[0], button[0]};
    end
    wire ext_int_pulse = int_delay[1] & (~int_delay[0]);

然后将脉冲信号送core:

    core m_core (
...
        .ext_int(ext_int_pulse),
        //
        .monitor_port(monitor_port)
    );

软件实现

致谢:中断矢量表和中断服务入口程序的实现参考了esp32c3的开源代码。

软件的主要问题是要增加一个section,放置中断矢量表和中断服务程序。然后需要修改链接配置文件使该section的代码出现在指定位置(硬件写死了在0x80002e00)。

vectors.S

在arduino的环境下,hardware\riscv\tangnano9k\cores\riscv目录下,新增vectors.S文件。

新增section

    .section .exception_vectors.text,"a"

其中,"a"标志表示该段包含“ALLOC”属性,为可执行段,在由elf转hex时要包含。

没有该标志,则hex文件中不会有该section中的代码(即使在elf中有)。我在这上面折腾了很长时间。

中断矢量表

    .balign 0x100
    .global _vector_table
    .type _vector_table, @function
_vector_table:
    .rept (16)
    j _interrupt_handler
    .endr

填充了16个中服入口,都指向_interrupt_handler。

中断服务程序_interrupt_handler

    .global _global_interrupt_handler

    .global _interrupt_handler
    .type _interrupt_handler, @function
_interrupt_handler:
    save_general_regs
    jal _global_interrupt_handler
    restore_general_regs
    mret

保护现场,然后调用c代码的_global_interrupt_handler函数,然后恢复现场,然后执行mret指令。

保护现场和恢复现场

保护现场

    .equ SAVE_REGS, 8
    .equ CONTEXT_SIZE, (SAVE_REGS * 4)

.macro save_general_regs cxt_size=CONTEXT_SIZE
    addi sp, sp, -\cxt_size
    sw   ra, 0(sp)
    sw   a0, 4(sp)
    sw   a1, 8(sp)
    sw   a2, 12(sp)
    sw   a3, 16(sp)
    sw   a4, 20(sp)
    sw   a5, 24(sp)
    sw   a6, 28(sp)
.endm

恢复现场

.macro restore_general_regs cxt_size=CONTEXT_SIZE
    lw   ra, 0(sp)
    lw   a0, 4(sp)
    lw   a1, 8(sp)
    lw   a2, 12(sp)
    lw   a3, 16(sp)
    lw   a4, 20(sp)
    lw   a5, 24(sp)
    lw   a6, 28(sp)
    addi sp,sp, \cxt_size
.endm

通用寄存器组本来还有很多寄存器的,先保护用上了的一部分。

用来保证本文件被链接上的函数

调试过程中发现vectors.S中的内容没有被链接上(那时section后面没加"a"),临时找到一个变通办法,让main()调用这里的一个空函数。

#let main() call this to link this file    
    .global donothing
    .type donothing, @function
donothing:
    ret 

interrupt.c

在cores\riscv目录下新增interrupt.c文件。

在其中实现_global_interrupt_handler函数:

/* called from vectors.S */
#include "halTangnanoTiny.h"

void _global_interrupt_handler(){
  halUartSend(0x31);
}

这里是按键中断的功能实现,向串口发一个0x31字符。

将来可以扩展为调用ino文件中用户定义的回调函数。

main.c

修改cores\riscv目录下的main.c文件,先调用.S文件中的donothing()。

#include "Arduino.h"

extern void donothing();

int main(){          
    donothing(); //for linking the vectors.S

    setup();

    while (1) {
        loop();
    } 
        
    return 0;
}

Reindeer.ld

修改hardware\riscv\tangnano9k\variants\generic\Reindeer.ld,在data-segment前面增加.exception_vectors section的链接地址:

   . = SEGMENT_START("exception-segment", 0x80002E00);
  .exception_vectors           :
  {
    *(.exception_vectors.text .exception_vectors.text.* .exception_vectors.*)
  }
  

   . = SEGMENT_START("data-segment", 0x00000400);

riscv-none-elf-as.exe

由于需要对.S文件进行汇编,原来的环境中缺少riscv-none-elf-as.exe文件。该文件已经上传到仓库里。下载放到:hardware\riscv\tangnano9k\tools\gcc\bin目录下。

修改platform.txt,增加:

compiler.S.flags= 
compiler.S.cmd=riscv-none-elf-as

recipe.S.o.pattern="{compiler.path}{compiler.S.cmd}" {compiler.S.flags} {build.extra_flags} -I{build.path}/sketch {includes} "{source_file}" -o "{object_file}"

运行

先烧写fpga工程,然后下载arduino工程(blink03)。

打开串口助手,可以看到正常情况下周期性地上传递增数据(同时led变化)。

按下button0,可以在串口收到0x31。

附录:反编译代码等

elf文件

Blink03.ino.elf:     file format elf32-littleriscv


Disassembly of section .text:

80002000 <_start>:
80002000:	00001137          	lui	sp,0x1
80002004:	0680006f          	j	8000206c <main>

80002008 <_fini>:
80002008:	0000006f          	j	80002008 <_fini>

8000200c <setup>:
8000200c:	00008067          	ret

80002010 <loop>:
80002010:	ff010113          	addi	sp,sp,-16 # ff0 <__global_pointer$+0x3ec>
80002014:	00112623          	sw	ra,12(sp)
80002018:	40402503          	lw	a0,1028(zero) # 404 <gCount>
8000201c:	00150513          	addi	a0,a0,1
80002020:	40a02223          	sw	a0,1028(zero) # 404 <gCount>
80002024:	018000ef          	jal	ra,8000203c <halUartSend>
80002028:	00a00513          	li	a0,10
8000202c:	028000ef          	jal	ra,80002054 <halDelay>
80002030:	00c12083          	lw	ra,12(sp)
80002034:	01010113          	addi	sp,sp,16
80002038:	00008067          	ret

8000203c <halUartSend>:
8000203c:	ff010113          	addi	sp,sp,-16
80002040:	00a12623          	sw	a0,12(sp)
80002044:	00c12783          	lw	a5,12(sp)
80002048:	00f02823          	sw	a5,16(zero) # 10 <SAVE_REGS+0x8>
8000204c:	01010113          	addi	sp,sp,16
80002050:	00008067          	ret

80002054 <halDelay>:
80002054:	00050a63          	beqz	a0,80002068 <halDelay+0x14>
80002058:	00000793          	li	a5,0
8000205c:	00000013          	nop
80002060:	00178793          	addi	a5,a5,1
80002064:	fef51ce3          	bne	a0,a5,8000205c <halDelay+0x8>
80002068:	00008067          	ret

8000206c <main>:
8000206c:	ff010113          	addi	sp,sp,-16
80002070:	00112623          	sw	ra,12(sp)
80002074:	61d000ef          	jal	ra,80002e90 <donothing>
80002078:	f95ff0ef          	jal	ra,8000200c <setup>
8000207c:	f95ff0ef          	jal	ra,80002010 <loop>
80002080:	ffdff06f          	j	8000207c <main+0x10>

80002084 <_global_interrupt_handler>:
80002084:	ff010113          	addi	sp,sp,-16
80002088:	00112623          	sw	ra,12(sp)
8000208c:	03100513          	li	a0,49
80002090:	fadff0ef          	jal	ra,8000203c <halUartSend>
80002094:	00c12083          	lw	ra,12(sp)
80002098:	01010113          	addi	sp,sp,16
8000209c:	00008067          	ret

Disassembly of section .exception_vectors:

80002e00 <_vector_table>:
80002e00:	0400006f          	j	80002e40 <_interrupt_handler>
80002e04:	03c0006f          	j	80002e40 <_interrupt_handler>
80002e08:	0380006f          	j	80002e40 <_interrupt_handler>
80002e0c:	0340006f          	j	80002e40 <_interrupt_handler>
80002e10:	0300006f          	j	80002e40 <_interrupt_handler>
80002e14:	02c0006f          	j	80002e40 <_interrupt_handler>
80002e18:	0280006f          	j	80002e40 <_interrupt_handler>
80002e1c:	0240006f          	j	80002e40 <_interrupt_handler>
80002e20:	0200006f          	j	80002e40 <_interrupt_handler>
80002e24:	01c0006f          	j	80002e40 <_interrupt_handler>
80002e28:	0180006f          	j	80002e40 <_interrupt_handler>
80002e2c:	0140006f          	j	80002e40 <_interrupt_handler>
80002e30:	0100006f          	j	80002e40 <_interrupt_handler>
80002e34:	00c0006f          	j	80002e40 <_interrupt_handler>
80002e38:	0080006f          	j	80002e40 <_interrupt_handler>
80002e3c:	0040006f          	j	80002e40 <_interrupt_handler>

80002e40 <_interrupt_handler>:
80002e40:	fe010113          	addi	sp,sp,-32
80002e44:	00112023          	sw	ra,0(sp)
80002e48:	00a12223          	sw	a0,4(sp)
80002e4c:	00b12423          	sw	a1,8(sp)
80002e50:	00c12623          	sw	a2,12(sp)
80002e54:	00d12823          	sw	a3,16(sp)
80002e58:	00e12a23          	sw	a4,20(sp)
80002e5c:	00f12c23          	sw	a5,24(sp)
80002e60:	01012e23          	sw	a6,28(sp)
80002e64:	a20ff0ef          	jal	ra,80002084 <_global_interrupt_handler>
80002e68:	00012083          	lw	ra,0(sp)
80002e6c:	00412503          	lw	a0,4(sp)
80002e70:	00812583          	lw	a1,8(sp)
80002e74:	00c12603          	lw	a2,12(sp)
80002e78:	01012683          	lw	a3,16(sp)
80002e7c:	01412703          	lw	a4,20(sp)
80002e80:	01812783          	lw	a5,24(sp)
80002e84:	01c12803          	lw	a6,28(sp)
80002e88:	02010113          	addi	sp,sp,32
80002e8c:	30200073          	mret

80002e90 <donothing>:
80002e90:	00008067          	ret

Disassembly of section .eh_frame:

00000400 <__FRAME_END__>:
 400:	0000                	.2byte	0x0
	...

Disassembly of section .sbss:

00000404 <gCount>:
 404:	0000                	.2byte	0x0
	...

hex文件

:0404000000000000F8
:0200000480007A
:10200000371100006F0080066F000000678000003D
:10201000130101FF2326110003254040130515007D
:102020002322A040EF0080011305A000EF008002F2
:102030008320C1001301010167800000130101FF2B
:102040002326A1008327C1002328F00013010101EA
:1020500067800000630A050093070000130000007A
:1020600093871700E31CF5FE67800000130101FF52
:1020700023261100EF00D061EFF05FF9EFF05FF978
:102080006FF0DFFF130101FF23261100130510037A
:10209000EFF0DFFA8320C100130101016780000027
:102E00006F0000046F00C0036F0080036F00400379
:102E10006F0000036F00C0026F0080026F0040026D
:102E20006F0000026F00C0016F0080016F00400161
:102E30006F0000016F00C0006F0080006F00400055
:102E4000130101FE232011002322A1002324B1003D
:102E50002326C1002328D100232AE100232CF100DE
:102E6000232E0101EFF00FA2832001000325410072
:102E7000832581000326C100832601010327410128
:102E8000832781010328C10113010102730020304F
:042E90006780000057
:040000058000200057
:00000001FF

section dump

>C:\Arduino\hardware\riscv\tangnano9k\tools\gcc\bin\riscv-none-elf-objdump.exe -h Blink03.ino.elf

Blink03.ino.elf:     file format elf32-littleriscv

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .text         000000a0  80002000  80002000  00001000  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  1 .exception_vectors 00000094  80002e00  80002e00  00001e00  2**8
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  2 .eh_frame     00000004  00000400  00000400  00000400  2**2
                  CONTENTS, ALLOC, LOAD, DATA
  3 .sbss         00000004  00000404  00000404  00000404  2**2
                  ALLOC
  4 .comment      00000033  00000000  00000000  00001e94  2**0
                  CONTENTS, READONLY
  5 .riscv.attributes 00000020  00000000  00000000  00001ec7  2**0
                  CONTENTS, READONLY
Verilog
1
https://gitee.com/lanzhoo/TangNano9k_Tutorial.git
git@gitee.com:lanzhoo/TangNano9k_Tutorial.git
lanzhoo
TangNano9k_Tutorial
TangNano9k_Tutorial
master

搜索帮助