分析SignalTap的仿真结果

写在前面:本博客为本人原创,严禁任何形式的转载!本博客只允许放在博客园(.cnblogs.com),如果您在其他网站看到这篇博文,请通过下面这个唯一的合法链接转到原文!

本博客全网唯一合法URL:http://www.cnblogs.com/acm-icpcer/p/9308863.html

本文先讲必备的基础知识,然后再重点关注如何看基于中断CPU的波形仿真。

一、中断与异常

1、什么是中断:

不可预知的干扰程序正常执行流程的事件。异常来源于CPU内部,如:除0错误;而中断来自于CPU外部,如键盘输入中断。

如上图所示,当异常或者中断出现的时候,CPU停止工作而去执行预先准备好的程序称为响应(响应通常驻留在操作系统的内核)。

2、怎么处理中断:

(1)查询中断:

说白了就是先把一张功能和“地图”一样的表存到一个固定的地方,当中断或者异常发生了,查查表就知道该到哪里执行中断处理程序了。或者也可以灵活一点儿应对:把地址写到一个寄存器内,因为寄存器的内容是可以修改的,所以CPU可以通过修改该寄存器的内容来修改程序的入口地址。中断发生时,还需要有额外的信息指导CPU来正确跳转,一般把这个信息写到一个特殊的寄存器CAUSE内部:硬件把发生源产生的相关信息写入到这个特殊的寄存器,CPU一读这个寄存器的内容就明白该怎么做了。

(2)向量中断:异常或者中断自带信息(称之为向量),向量信息一般较短,所以硬件还要把向量的左边和右边填充好适当的数据合成一个地址,这个地址合成好了以后就直接打入PC内部,这样就可以直接跳到相应的入口去处理异常和中断了。

3、一些细节:

(1)中断返回:返回到转去处理中断子程序之前保存好的被中断的程序的地址。一般是借助堆栈。

(2)中断屏蔽(关中断):不理正在处理中断的时候所产生的中断。决定是关中断还是开中断的工作交给寄存器STATUS去控制;

(3)中断嵌套(开中断):理中断的时候所产生的中断,如下图:

(4)中断优先级:决定优先响应哪个中断

二、带中断与异常处理的CPU设计

1、设计前提与假定:

(1)只有一个外部中断请求;

(2)只处理3种异常和中断:溢出(异常)、指令异常、系统调用;

(3)返回地址:发生异常时保存当前指令的地址,中断发生时保存下一条指令的地址;

(4)使用查询中断处理方式;

(5)响应时status寄存器左移4位关中断;

(6)响应结束时status寄存器右移4位恢复原来的内容。

2、需要实现的功能:

(1)返回地址能正确保存到EPC寄存器当中;

(2)把产生异常或者中断的原因记录到CAUSE寄存器当中;

(3)把status寄存器的中断屏蔽位左移4位(关中断);

(4)把地址写入PC。

3、CAUSE、STAUTS、EPC寄存器结构

4、如何判断产生了溢出?

 

代码实现:

 

5、mfc0、mtc0、syscall、eret指令:

对于mfc0(mfc0 rt ,rd)、mtc0(mtc0 rt,rd)两条指令,rt是通用寄存器的编号,rd是寄存器CAUSE、STAUTS、EPC的号码,4条指令的结构如下:

6、如何在单周期CPU代码的基础之上设计带异常和中断的CPU

(1)新增3个寄存器及其读写所需要的电路(主要是多路选择器)、包括向中断处理子程序的跳转和返回。这些所有的控制信号要有CU发出,所以也必然要对CU的代码进行修改。

(2)当然,还要实现相对应的对新加的寄存器(EPC、CAUSE、status)的读写功能(通过分析寄存器的位结构)

三、验证设计(仿真图分析)

1、测试汇编代码及其注释:

    assign   rom[6'h00] = 32'h0800001d;    //    (00)     reset:   j start
    assign   rom[6'h01] = 32'h00000000;    //    (04)      nop
    assign   rom[6'h02] = 32'h401a6800;    //    (08)   EXC_BASE:   mfc0 r26, C0_CAUSE
    assign    rom[6'h03] = 32'h335b000c;    //    (0c)      andi r27, r26, 0xc
    assign    rom[6'h04] = 32'h8f7b0020;    //    (10)      lw     r27, j_table(r27)
    assign    rom[6'h05] = 32'h00000000;    //    (14)        nop
    assign    rom[6'h06] = 32'h03600008;    //    (18)        jr     r27
    assign    rom[6'h07] = 32'h00000000;    //    (lc)        nop
    assign    rom[6'h0C] = 32'h00000000;    //    (30)        nop
    assign    rom[6'h0D] = 32'h42000018;    //    (34)        eret
    assign    rom[6'h0E] = 32'h00000000;    //    (38)        nop
    assign    rom[6'h0F] = 32'h00000000;    //    (3c)    sys_entry:    nop    
    assign    rom[6'h10] = 32'h401a7000;    //    (40)    epc_plus4:    mfc0 r26, C0_EPC
    assign    rom[6'h11] = 32'h235a0004;    //    (44)        addi r26, r26, 4
    assign    rom[6'h12] = 32'h409a7000;    //    (48)       mtc0 r26, C0_EPC
    assign    rom[6'h13] = 32'h42000018;    //    (4c)        eret
    assign    rom[6'h14] = 32'h00000000;    //    (50)        nop    
    assign    rom[6'h15] = 32'h00000000;    //    (54)    uni_entry:    nop
    assign    rom[6'h16] = 32'h08000010;    //    (58)        j     epc_plus4
    assign    rom[6'h17] = 32'h00000000;    //    (5c)       nop
    assign    rom[6'h1A] = 32'h00000000;    //    (68)    ovf_entry:    nop
    assign    rom[6'h1B] = 32'h08000010;    //    (6c)        j epc_plus4
    assign    rom[6'h1C] = 32'h00000000;    //    (70)        nop
    assign    rom[6'h1D] = 32'h2008000f;    //    (74)    start:    addi r8,r0,oxf
    assign    rom[6'h1E] = 32'h40886000;    //    (78)        mtc0 r8,c0_status
    assign    rom[6'h1F] = 32'h8c080048;    //    (7c)        lw r8,0x48(r0)    
    assign    rom[6'h20] = 32'h8c09004c;    //    (80)    ov:    lw r9,0x4c(r0)
    assign    rom[6'h21] = 32'h01094020;    //    (84)        add r9,r9,r8
    assign    rom[6'h22] = 32'h00000000;    //    (88)        nop
    assign    rom[6'h23] = 32'h0000000c;    //    (8c)        syscall
    assign    rom[6'h24] = 32'h00000000;    //    (90)        nop
    assign    rom[6'h25] = 32'h0128001a;    //    (94)        div r9,r8        
    assign    rom[6'h26] = 32'h00000000;    //    (98)        nop
    assign    rom[6'h27] = 32'h34040050;    //    (9c)        ori r4,r1,0x50
    assign    rom[6'h28] = 32'h20050004;    //    (a0)        addi r5,r0,4
    assign    rom[6'h29] = 32'h00004020;    //    (a4)        add r8,r0,r0
    assign    rom[6'h2a] = 32'h8c890000;    //    (a8)        lw r9,0(r4)
    assign    rom[6'h2b] = 32'h20840004;    //    (ac)        addi r4,r4,4    
    assign    rom[6'h2c] = 32'h01094020;    //    (b0)        add r8,r8,r9
    assign    rom[6'h2d] = 32'h20a5ffff;    //    (b4)        addi r5,r5,-1
    assign    rom[6'h2e] = 32'h14a0fffb;    //    (b8)        bne r5,r0,loop
    assign    rom[6'h2f] = 32'h00000000;    //    (bc)        nop
    assign    rom[6'h30] = 32'h08000030;    //    (c0)    finish:    j finish    

2、仿真结果:

注释:

(00)跳转到(74),程序从(74)一直执行到(84)时通过加法产生溢出(即:00->74->78->7c->80->84);立即跳转到(08),即中断处理程序入口,CPU读取cause寄存器,然后通过查询跳转表的表项:

ram[5'h0b] = 32'h00000068;//(2c) ovf_entry

获得地址(68),CPU转去执行位于(68)的中断处理程序。

所以是:08->0c->10->14->18->68

接着按照我们设计好的溢出处理程序(EPC+4),执行完中断处理程序之后返回所以是:68->6c

然后回到系统调用指令的入口,执行完EPC+4后返回,所以是:40->44->48->4c

返回的地址存储在EPC寄存器当中,该寄存器的内容第一次在异常产生时写入EPC,然后在中断处理子程序内部+4,指向原来保存的现场的下一条指令的地址,即84+4=88,之后继续往下执行,所以有:88->8c

执行到(8c)时执行完了系统调用程序,那么就跳转到(08),系统调用入口并做相应的准备工作,这整个过程也就是也就是08->0c->10->14->18

简单的描述一下这个过程执行的代码就是:将立即数0xc与26号寄存器的值相加放到27号寄存器,然后通过27号寄存器的值查表项:ram[5'h09] = 32'h0000003c;//(24)  sys_entry

找到了系统调用的入口地址,所以接下来的整个过程就是:先是从18->3c,然后顺序向下执行即有3c->40->44-:>48->4c

执行完系统调用后然后返回原来的地址,也就是调用系统调用的地址的后一条指令的地址,即8c+4=90,所以继续向下走就是:90->94

然后又碰到了(94)的除数异常,继续和之前分析的一样去处理异常,也就是08->0c->10->14->18

只不过是处理程序不同,所以有18->54->58

然后照例回到系统调用指令的入口,执行完EPC+4后返回,所以和之前处理溢出时一样是:40->44->48->4c

返回原来执行的现场,接下来就是98->9c

此时就遇到了外部中断,但是没有与硬件绑定,那么程序就自己走loop循环,因为在(a0)处设置了程序循环计数器的值counter=4,所以循环4次,也就是走了四趟的:a8->ac->b0->b4->b8

然后就继续往下走:bc->c0

(c0)处finish,程序执行结束了。

 

总之,分析仿真图的要义是:保持耐心、注意护眼。

tz@COI HZAU

2018/7/14

原文地址:https://www.cnblogs.com/acm-icpcer/p/9308863.html