计算机组成原理P7

Cdostan MVP++

mips微系统设计文档

构建思路

本次课下需要我们用verilog语言搭建一个mips微系统。整个系统是建立在P6的基础上的,除了和相关操作相关的指令以外,其余指令均来自P6。但该微系统和我们P6搭建的CPU又是区别极大的,毕竟我们P6只是搭建了一个CPU,而本次我们需要搭建一个系统,CPU只是其中一个部分,我们还需要实现外设(计时器和中断发生器),并将CPU和外设连接起来形成一个整体也即系统。好在助教们非常好心,已经将所有的外设都给我们写好了,因此我们要做的只是将外设和CPU进行连接,使他们能进行数据的传输与交换,而这也即是我们要实现的系统桥部分。这样一看,P7似乎很简单? 是的,如果只需要实现系统桥的话,那确实非常简单,最大的难度在于检验线路连接是否正确,但这是我们构建的系统是没有动力的,一旦遇到障碍,它不会想去解决它,而是死马当活马医,执行错误的操作。因此我们为了要搭建出一个“积极向上”的系统,还有一个重头戏就是解决相应的异常和中断。而这无疑是P7中最困难的部分 (除了读懂教程以外)。因此总结下来,我们P7要做的事无非就两件:处理异常中断和搭建系统桥。

异常和中断

在这之后除非特定说明,异常都指CPU执行指令中出现的异常,中断都指外设传递中断信号给CPU,二者发生后都需要进入异常处理程序。要清晰的编写相关代码,我们首先要明确什么情况会出现异常以及中断。除此之外,与处理异常和中断有紧密联系的是新增模块CP0,简单来说,该模块就是根据当前信息判断是否发生异常或中断并输出是否进入异常处理程序的控制信号req,此外还需保存异常或中断的相关信息以及当前的受害PC以便异常处理程序结束后跳回。

异常

名称 指令 描述 异常码 判断流水级
ADEL all PC地址未对齐 4 F
ADEL all PC地址超范围 4 F
ADEL lw 取数地址未与4字节对齐 4 M
ADEL lh 取数地址未与2字节对齐 4 M
ADEL lh、lb 取Timer寄存器的值 4 M
ADEL load 计算地址时加法溢出 4 E
ADEL load 取数地址超出范围 4 M
ADES sw 存数地址未与4字节对齐 5 M
ADES sh 存数地址未与2字节对齐 5 M
ADES sh、sb 存Timer寄存器的值 5 M
ADES store 计算地址加法溢出 5 E
ADES store 向计时器的Count寄存器存值 5 M
ADES store 存数地址超出范围 5 M
SYSCALL syscall 系统调用 8 D
RI - 未知指令 10 D
OV add、addi、sub 算术溢出 12 E

每种异常的判断都是较为简单的,相应异常该在哪一流水级判断我也在表格中列出(我的实现),需要注意的是,我们判断相应异常并产生对应的异常码的时候,需要一级一级流水到CP0所在的流水级,比如我的CP0设在M级,那异常码就要流水到M级再考虑写入CP0,而不是一旦判断出异常马上准备写入CP0,除此之外,若在前一流水级和当前流水级都产生了异常,则以前一流水级的异常为准。

中断

在P7部分涉及到的中断是由中断发生器和两个计时器所给出的,他们会给CPU中断信号,相应的异常码为0,若要响应中断,则应将0写入CP0记录异常码的相应位置。

中断和异常的优先级:中断优先!!!

CP0

我的CP0的相应的端口的定义就采用了教程中的定义。

端口 方向 位数 解释
clk IN 1 时钟信号
reset IN 1 复位信号
en IN 1 写使能信号
CP0Add IN 5 寄存器地址
CP0In IN 32 CP0写入数据
CP0Out OUT 32 CP0读出数据
VPC IN 32 受害PC
BDIn IN 1 是否是延迟槽指令
ExcCodeln IN 5 记录异常类型
HWInt IN 6 输入中断信号
EXLClr IN 1 复位EXL
EPCOut OUT 32 EPC值
Req OUT 1 进入异常处理程序请求

在每个周期,我们需要将HWInt写入CP0中的CAUSE寄存器的[15:10]位,同时,当异常或中断发生时,我们需要结合CP0中的SR寄存器的值来判断是否进入异常处理程序,如果进入,还应写入异常码和受害PC,具体实现代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
wire Intreq=((HWint&IM)!=0)&&(!EXL)&&(IE);
wire Excreq=(EXCcode!=1)&&(!EXL);
assign Req=Intreq|Excreq;
always @(posedge clk)
begin
register[13][15:10]<=HWint;
if(en) register[CP0addr]<=CP0in;
if(EXLclr) register[12][1]<=0;
if(Req)
begin
register[12][1]<=1;
register[14]<=BDin?VPC-4:VPC;
register[13][6:2]<=Intreq?0:EXCcode;
register[13][31]<=BDin;
end
end

新增指令注意

  • eret:在D级判断eret出现后,将NPC改为CP0给出的EPC即可。该指令不存在延迟槽,所以如果是在D级判断其是否出现的话,需要设置一个flush信号清空后一条指令,要注意的是:1.该flush信号的优先级是低于stall信号的,也就是当发生阻塞的时候flush信号无效,仍然执行阻塞的相关操作 2.flush信号触发时,这时应该将EPC流水给D级以保证宏观PC的正确性。
  • mtc0:看似该指令的转发和阻塞用正常的AT法即可,但那只是针对于其和P6中已存在的指令来说,不要忘记了eret指令和mtc0指令是高度相关的,因此还要考虑mtc0和eret的冒险,针对该冒险,我们进行阻塞即可,即在D级出现eret且E级或M级出现mtc0时,进行阻塞即可。

其他注意

  • 有关控制信号优先级:按reset>req>stall>flush即可
  • 有关宏观PC:因为我们外部中断的原因,因此宏观PC的正确性是十分重要的,我的设计中选取M级的PC作为宏观PC,因此在数据在流水线的传递中,我们在P6的基础上要进行一定程度的修改,在发生阻塞的时候,E级PC不能再置为0,要将D级PC正常传下去,同时若reset信号有效,我们应该将M级以前的PC全部修改为’h00003000,若req信号有效,我们应该将M级以前的PC全部修改为’h00004180,针对eret指令flush信号触发时,应该将EPC流水给D级。
  • 有关乘除槽:按教程中的提交要求,我们只需在乘除槽的开启条件和mtlo、mthi的写使能分别加上!req即可。

系统桥

前面说过,系统桥实现的其实就是将外设和CPU进行连接,使他们能进行数据的传输与交换,实现较为简单,唯一需要注意的由于信号过多相应信号是否连上且连对,直接呈现代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
assign dm_data_addr=(cpu_data_addr>=0&&cpu_data_addr<='h2fff)?cpu_data_addr:0;
assign dm_data_wdata=cpu_data_wdata;
assign dm_data_byteen=(cpu_data_addr>=0&&cpu_data_addr<='h2fff)?cpu_data_byteen:0;
assign tc0_data_addr=(cpu_data_addr>='h7f00&&cpu_data_addr<='h7f0b)?cpu_data_addr:0;
assign tc0_data_wdata=cpu_data_wdata;
assign tc0_we=(cpu_data_addr>='h7f00&&cpu_data_addr<='h7f0b)?(|cpu_data_byteen):0;
assign tc1_data_addr=(cpu_data_addr>='h7f10&&cpu_data_addr<='h7f1b)?cpu_data_addr:0;
assign tc1_data_wdata=cpu_data_wdata;
assign tc1_we=(cpu_data_addr>='h7f10&&cpu_data_addr<='h7f1b)?(|cpu_data_byteen):0;
assign itp_data_addr=(cpu_data_addr>='h7f20&&cpu_data_addr<='h7f23)?'h7f20:0;
assign itp_data_byteen=(cpu_data_addr>='h7f20&&cpu_data_addr<='h7f23)?cpu_data_byteen:0;
assign cpu_data_rddata=(cpu_data_addr>=0&&cpu_data_addr<='h2fff)?m_data_rddata:
(cpu_data_addr>='h7f00&&cpu_data_addr<='h7f0b)?tc0_data_rddata:
(cpu_data_addr>='h7f10&&cpu_data_addr<='h7f1b)?tc1_data_rddata:
0;

按以上思路搭建该微系统,可以较为顺利地完成

测试方案

无异常:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
ori $1,$0,3
andi $2,$1,0xffff
addi $3,$2,3
lui $4,0x0011
add $5,$3,$4
add $5,$4,$4
add $5,$5,$5
sub $6,$5,$4
sub $6,$4,$5
and $7,$6,$0
and $7,$6,$2
or $8,$7,$0
or $8,$8,$7
slt $9,$8,$7
slt $9,$7,$8
sltu $10,$8,$7
sltu $10,$7,$8
sw $2,1($1)
sh $2,-1($1)
sh $2,-3($1)
lb $11,-2($1)
sb $11,0($1)
sb $11,-1($1)
sb $11,-2($1)
sb $11,-3($1)
lh $12,-1($1)
lh $12,-3($1)
lw $13,-3($1)
mult $12,$13
mfhi $14
add $15,$14,$13
multu $15,$14
mflo $16
ori $17,$16,$0
div $17,$16
mfhi $18
add $18,$18,$18
divu $18,$17
mflo $19
add $19,$19,$19
mthi $19
mtlo $17
mflo $20
add $20,$20,$19
mfhi $21
add $21,$21,$20
ori $1,$0,0x1011
ori $2,$1,0x0111
ori $11,$0,2
lui $3,0
lui $3,0x0111
lui $3,0xffff
ori $4,$3,0xffff
lui $3,0x0111
add $5,$3,$0
add $5,$4,$0
add $5,$3,$1
add $5,$3,$4
add $5,$4,$4
add $5,$5,$5
loop3:add $11,$11,$4
beq $11,$0,loop4
sub $6,$3,$0
sub $6,$4,$0
sub $6,$3,$1
sub $6,$3,$4
ori $7,$0,1
sub $8,$4,$7
sub $6,$4,$8
mtc0 $6,$14
mfc0 $7,$14
sub $6,$6,$6
sub $6,$3,$4
loop3:sw $5,4($1)
sw $6,4($2)
lw $9,4($1)
lw $10,4($2)
beq $5,$10,loop1
beq $5,$9,loop2
loop1:sw $10,8($2)
loop2:bne $6,$10,loop3
loop4:sw $9,12($2)

异常:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
.text
ori $t0,$0,0x1001
mtc0 $t0,$12
lui $t0,0x7fff
bne $t0,$t1,loop
syscall
loop:addi $1,$t0,1

.ktext 0x4180
addi $s0,$s0,1
mfc0 $s1,$14
mfc0 $s2,$13
ori $s3,$0,124
and $s2,$s2,$s3
beq $s2,$0,intreq
mfc0 $s2,$13
lui $s3,0x8000
and $s2,$s2,$s3
beq $s2,$s3,delayreq
addi $s1,$s1,4
mtc0 $s1,$14
eret

delayreq:
ori $t0,$0,0x3000
eret

intreq:
sb $0,0x7f20($0)
eret

思考题

请查阅相关资料,说明鼠标和键盘的输入信号是如何被 CPU 知晓的?

输入设备通过USB等接口传输信号到计算机并到达CPU时,通常会产生一个中断,进入驱动程序后根据中断信号的值产生相应的操作。

请思考为什么我们的 CPU 处理中断异常必须是已经指定好的地址?如果你的 CPU 支持用户自定义入口地址,即处理中断异常的程序由用户提供,其还能提供我们所希望的功能吗?如果可以,请说明这样可能会出现什么问题?否则举例说明。(假设用户提供的中断处理程序合法)

按理来说可以,但若这样可能会影响异常处理的时间和性能,不如使用预定义的地址相应的快速,且用户自定义入口地址可能会和正常运行的程序的地址冲突,导致发生错误。

为何与外设通信需要 Bridge?

这样符合“高内聚,低耦合”的思想,使得特定的模块只用考虑自己做的事情就好了,不需要考虑相应的数据是从哪里来的,达到分工明确,实现较好的封装。

请阅读官方提供的定时器源代码,阐述两种中断模式的异同,并分别针对每一种模式绘制状态移图。

模式0可以让中断信号一直保持直到技术使能信号再次被置为1,而模式1则在每次计数为0时发出一个中断信号,只维持一个周期。
模式0:

模式1:

倘若中断信号流入的时候,在检测宏观 PC 的一级如果是一条空泡(你的 CPU 该级所有信息均为空)指令,此时会发生什么问题?在此例基础上请思考:在 P7 中,清空流水线产生的空泡指令应该保留原指令的哪些信息?

受害PC写入CP0发生错误,导致异常处理程序结束后无法跳转到正确的地址继续执行相应指令。应保留原指令的地址。

为什么 jalr 指令为什么不能写成 jalr $31, $31

若jalr的延迟槽指令发生异常或响应中断,则异常处理程序结束后会重新回到jalr并执行,但此时$31号寄存器的值已经被修改,再次跳转的时候便会错误执行。

  • Title: 计算机组成原理P7
  • Author: Cdostan
  • Created at : 2025-02-09 21:25:31
  • Updated at : 2025-02-10 18:18:39
  • Link: https://cdostan.github.io/2025/02/09/计组/计组P7/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments