【分析解答】

第 3 版 · OCR 辅助 · 原书检索 · 文字用于定位,公式与图表请回原页核验 · 本次导入快照

## 【分析解答】

一个数 $x$ 乘以 6,相当于 $4x + 2x$,而 $4x$ 可以通过将 $x$ 左移两位来实现,$2x$ 可以通过将 $x$ 左移一位来实现。这样就只要用两条左移指令和一条加法指令来实现乘 6 操作。以此类推,当乘以一个较小的常数时,只要将这个较小的常数分解成若干个 2 的幂次相加,就可以用若干条左移指令和一条加法指令来实现乘法运算。可用以下指令序列实现题目要求。

s11  $s1,  $s0,  2  #将$s0的内容左移两位,送$s1
s11  $s0,  $s0,  1  #将$s0的内容左移一位,送$s0
addu  $s1,  $s1,  $s0  #将$s1和$s0的内容相加(不考虑溢出),送$s1
13. 有些计算机提供了专门的指令,能从 32 位寄存器中抽取其中任意一个位串置于另一个寄存器的低位有效位上,并高位补 0,如图 4.3 所示。MIPS 指令系统中没有这样的指令,请写出最短的一个 MIPS 指令序列来实现这个功能,要求 $i = 5, j = 22$,操作前后的寄存器分别为 $s0 和 $s2。 图 4.3 题 13 中操作前后示意图 ## 【分析解答】 可以先左移 9 位,然后右移 15 位,MIPS 指令序列为 <!-- page: 104 --> s11 $s2, $s0, 9 #将$s0的内容左移 9 位, 送$s2 srl $s2, $s2, 15 #将$s2的内容右移 15 位, 送$s2 这里要注意,第二条指令不能用算术右移指令 sra,因为算术右移高位添加的是符号,所以不能保证高位一定补 0。此外,第一条指令中的目的操作数寄存器和第二条指令的源操作数寄存器都只能用 $s2$,而不能改成其他寄存器,否则会破坏其他寄存器的值。 14. 以下程序段是某个过程对应的指令序列。入口参数 int a 和 int b 分别置于 $a0 和 $a1 中,返回参数是该过程的结果,置于 $v0 中。要求为以下 MIPS 指令序列加注释,并简单说明该过程的功能。
add $t0, $zero, $zero
loop: beq $a1, $zero, finish
add $t0, $t0, $a0
sub $a1, $a1, 1
j loop
finish: addi $t0, $t0, 100
add $v0, $t0, $zero
【分析解答】
add $t0, $zero, $zero #将寄存器$ t0 置 0
loop: beq $a1, $zero, finish #若$a1 的值等于 0, 则转 finish 处
add $t0, $t0, $a0 #将$ t0 和$a0 的内容相加, 送$ t0
sub $a1, $a1, 1 #将$a1 的值减 1
j loop #无条件转到 loop 处
finish: addi $t0, $t0, 100 #将$ t0 的内容加 100
add $v0, $t0, $zero #将$ t0 的内容送$ v0
该过程的功能是计算“$a \times b + 100$”。 15. 用一条 MIPS 指令或最短的指令序列实现以下 C 语言语句:$b = 25|a$。假定编译器将 $a$ 和 $b$ 分别分配到 $t0$ 和 $t1$ 中。如果把 25 换成 65536,即语句 $b = 65536|a$,则用 MIPS 指令或指令序列如何实现? 【分析解答】 只要用一条指令“ori $t1, $t0, 25”就可实现 $b = 25|a$。但是,如果把 25 换成 65536,则不能用一条指令“ori $t1, $t0, 65536”来实现,因为 $65536 = 1000000000000000B$,它不能用 16 位立即数表示。可用以下两条指令实现语句 $b = 65536|a$。
lui $t1, 1 #将 0001 0000H 置于寄存器$ t1
or $t1, $t0, $t1 #将$ t0 和$ t1 的内容进行&quot;或&quot;运算, 送$ t1
16. 请说明 beq 指令的跳转范围,并解释为什么汇编程序在对下列 MIPS 汇编源程序中的 beq 指令进行汇编时会遇到问题,应该如何修改该程序段?
here: beq $s0, $s2, there
...
there: addi $s1, $s0, 4
【分析解答】 在 MIPS 指令系统中,分支指令 beq 是 I-型指令,跳转目标地址采用相对寻址方式,16 <!-- page: 105 --> 位偏移量 offset 用补码表示,因此,既可以跳转到当前指令前,也可以跳转到当前指令后。其跳转目标地址的计算公式为 $\mathrm{PC} + 4 + \mathrm{offset} \times 4$。因此,当 offset 的范围为 0000 0000 0000 0000B~0111 1111 1111 1111B 时,beq 指令向后正跳,相对于本条指令,向后正跳 $1 \sim 2^{15}$ 条指令,其跳转地址范围为 $4 \sim 4 + (2^{15} - 1) \times 4 = 2^{17}$。当 offset 的范围为 1000 0000 0000 0000B~1111 1111 1111 1111B 时,beq 指令向前负跳,相对于本条指令,向前负跳 $1 \sim 2^{15} - 1$ 条指令,其跳转地址范围为 $-4 \times (2^{15} - 1) \sim -4$。 当上述指令序列中 here 和 there 表示的地址相差超过上述给定范围时,beq 指令会发生汇编错误。可将上述指令序列改成以下指令序列。因为无条件跳转指令 j 的跳转范围足够大,所以可以直接从 here 跳转到 there。
here: bne $s0, $s2, skip
j there
skip: ...
...
there: addi $s1, $s0, 4
17. 下列指令序列是一个完整的过程,用来对两个数组进行处理,并产生结果存放在 $v0 中。假定每个数组有 2500 个元素,下标变量为 int 类型。两个数组的首地址分别存放在 $a0 和 $a1 中,数组长度分别存放在 $a2 和 $a3 中。根据注释简单说明该过程的功能。假定该过程运行在一个时钟频率为 2GHz 的处理器上,add、addi 和 sll 指令的 CPI 为 1; lw 和 bne 指令的 CPI 为 2,则最坏情况下运行该过程所需的时间是多少秒?
sll $a2, $a2, 2
sll $a3, $a3, 2
add $v0, $zero, $zero
add $t0, $zero, $zero
outer: add $t4, $a0, $t0
lw $t4, 0($t4)
add $t1, $zero, $zero
inner: add $t3, $a1, $t1
lw $t3, 0($t3)
bne $t3, $t4, skip
addi $v0, $v0, 1
skip: addi $t1, $t1, 4
bne $t1, $a3, inner
addi $t0, $t0, 4
bne $t0, $a2, outer
# $a2 的内容左移 2 位, 即乘 4
# $a3 的内容左移 2 位, 即乘 4
# $v0 初始化为 0
# $t0 初始化为 0
# 计算数组 1 当前元素的地址
# 数组 1 当前元素存放在 $t4
# $t1 初始化为 0
# 计算数组 2 当前元素的地址
# 数组 2 当前元素存放在 $t3
# $t3 和 $t4 的内容不相等, 则转 skip
# $v0 加 1
# $t1 加 4
# 数组 2 未处理完, 继续转 inner 执行
# $t0 加 4
# 数组 1 未处理完, 继续转 outer 执行
【分析解答】 该过程的功能是统计两个数组中相同元素的个数,多次相等则重复计数。过程执行时最坏的情况是两个数组的所有元素都相等,这样,指令“addi $v0, $v0,1”在每次循环中都被执行。因为 add、addi 和 sll 指令的 CPI 为 1,lw 和 bne 指令的 CPI 为 2,所以,在最坏情况下所需的时钟周期总数为 $4 + [4 + (6 + 3) \times 2500 + 3] \times 2500 = 56267506$,时钟周期为 $1 / 2\mathrm{GHz} = 0.5\mathrm{ns}$,因此,执行该过程的总执行时间最多为 $56267506 \times 0.5\mathrm{ns} = 28133753\mathrm{ns} \approx 0.028\mathrm{s}$。 <!-- page: 106 --> 18. 以下程序段是某个过程对应的 MIPS 指令序列,其功能为复制一个存储块数据到另一个存储块中,存储块中每个数据为 int 类型,源数据块和目的数据块的首地址分别存放在 $a0 和 $a1 中,复制的数据个数存放在 $v0 中,作为返回参数返回给调用过程。在复制过程中遇到 0 则停止,最后一个 0 也需要复制,但不被计数。已知该程序段中有多个 Bug,请找出它们并修改之。
addi $v0, $zero, 0
loop: lw $v1, 0($a0)
sw $v1, 0($a1)
addi $a0, $a0, 4
addi $a1, $a1, 4
beq $v1, $zero, loop
【分析解答】 修改后的代码如下。
addi $v0, $zero, 0
loop: lw $v1, 0($a0)
sw $v1, 0($a1)
beq $v1, $zero, exit
addi $a0, $a0, 4
addi $a1, $a1, 4
addi $v0, $v0, 1
j loop
exit:
19. 考虑下面的 C 语言程序段:
for (i=0; i&amp;lt;=100; i=i+1;)
a[i]=b[i]+c;
假定数组 $a$ 和 $b$ 的每个元素是 int 型变量,首地址分别存放在寄存器 $a0 和 $a1 中。寄存器 $t0 和 $s0 分别对应变量 $i$ 和 $c$。要求写出与之对应的 MIPS 指令代码,并计算这段代码运行过程中所执行的指令条数和数据的访存次数? 【分析解答】 每个数组元素占 4 字节,所以循环体内每一步地址增量是 4。上述程序段对应的 MIPS 汇编形式的指令代码序列如下。
add $t0, $zero, $zero #i=0
loop: add $t4, $a1, $t0 #$t4=address of b[i]
lw $t5, 0($t4) #$t5=b[i]
add $t6, $t5, $s0 #$t6=b[i]+c
add $t7, $a0, $t0 #$t7=address of a[i]
sw $t6, 0($t7) #a[i]=b[i]+c
addi $t0, $t0, 4 #i=i+4
slti $t8, $t0, 401 #if(i&amp;lt;401) then $t8=1 else $t8=0
bne $t8, $zero, loop #if($t8=1) go to loop
该段代码运行过程中共执行了 $1 + 101 \times 8 = 809$ 条指令。其中,访存指令条数(数据的 <!-- page: 107 --> 访问次数)为202条。 20. 某高级语言源程序中的一个 while 语句为“while (save[i] == k) i += 1;”,若对其编译时,编译器将 $i$ 和 $k$ 分别分配在寄存器 $s3$ 和 $s5$ 中,数组 save 的基址存放在 $s6$ 中,则生成的 MIPS 汇编代码段如下。
loop: s11 $t1, $s3, 2 #R[$t1]←R[$s3]&amp;lt;&amp;lt;2,即R[$t1]=i×4
add $t1, $t1, $s6 #R[$t1]←R[$t1]+R[$s6],即R[$t1]=Address of save[i]
lw $t0, 0($t1) #R[$t0]←M[R[$t1]+0],即R[$t0]=save[i]
bne $t0, $s5, exit #if R[$t0]≠R[$s5] then goto exit
addi $s3, $s3, 1 #R[$s3]←R[$s3]+1,即 i=i+1
j loop #goto loop
exit:
假设从loop处开始的指令存放在内存80000处,则上述循环对应的MIPS机器码如图4.4所示。 [tbl-21.md](tbl-21.md) 图4.4 题20中程序的机器级代码 根据上述叙述,回答下列问题,要求说明理由或给出计算过程。 (1) MIPS的编址单位是多少?数组save每个元素占几个字节? (2) 为什么指令“sll $t1, $s3, 2”能实现 4 × i 的功能? (3) 该循环指令序列中哪些是 R-型指令?哪些是 I-型指令? (4) $t0 和 $s6 的编号各为多少? (5) 指令“j loop”的操作码是什么(用二进位表示)? (6) 标号 exit 的值是多少?如何根据指令计算得到? (7) 标号 loop 的值是多少?如何根据指令计算得到?MIPS中跳转指令的跳转范围是多少? 【分析解答】 (1) MIPS的编址单位是字节。从图4.4中可看出,每条指令32位,占4个地址,所以一个地址中有8位,因此,MIPS的编址单位是字节。因为每次循环取数组元素时,其下标地址都要乘以4,所以save数组的每个元素占4字节。 (2) 因为这是左移指令,左移 2 位,相当于乘以 2² = 4。 (3) 从图 4.4 可看出,第 1 条和第 2 条指令为 R-型指令,第 3、4、5 条指令为 I-型指令。 (4) 从图 4.4 中第 3 条和第 4 条指令可看出,$t0 的编号为 8,从第 2 条指令可看出 $s6 的编号为 22。 (5) 指令“j loop”的操作码为“000010B”。 <!-- page: 108 --> (6) 标号 exit 的值是 80024,其含义是循环结束时跳出循环后执行的首条指令的地址。它由当前分支指令(条件转移指令)的地址 80012 加上 4 得到下条指令的地址,然后再加上相对位移量 $2 \times 4$ 得到,即 $80012 + 4 + 2 \times 4 = 80024$。 (7) 标号 loop 的值为 80000,是循环入口处首条指令的地址,由跳转指令 j 的 32 位地址 80020 的高 4 位 (0000B),与指令中给出的低 26 位 (20000) 拼接成 30 位地址,然后再在低位添两个 0(相当于 ×4)得到,即 $20000 \times 4 = 80000$。因为跳转指令的地址与其跳转到的目标指令地址的高 4 位一样,所以,如果将 4GB 的主存空间分隔成 16 个 4GB/16 = 256MB 的子空间,那么跳转到的目标指令总是和跳转指令在同一个子空间,不可能跳出它本身所在的 256MB 的子空间,所以跳转目标地址范围的大小是 256MB,即假定跳转指令地址的高 4 位为 X,则跳转目标地址范围是 X000 0000H~XFFF FFFCH。 21. 以下 C 语言程序段中有两个过程 sum_array 和 compare,假定 sum_array 第一个被调用,全局变量 sum 分配在寄存器 $s0 中。要求写出每个过程对应的 MIPS 汇编表示,并说明每个过程对应的栈帧中需要保存的信息。
int sum=0;
int sum_array(int array[], int num)
{
int i;
for (i=0; i&lt;num; i=&quot;&quot; i++)=&quot;&quot; if=&quot;&quot; compare(num,=&quot;&quot; sum+=array[i];=&quot;&quot; t0=&quot;&quot; {=&quot;&quot; }=&quot;&quot;&gt;
return sum;
}
int compare(int a, int b)
{
if (a&amp;gt;b)
return 1;
else
return 0;
}
【分析解答】 程序由两个过程组成,全局变量 sum 分配给 $s0,在过程调用时,全局变量无须入栈保存。 为了尽量减少指令条数,并减少访存次数。在每个过程的过程体中总是先使用临时寄存器 $t0~$ t9,临时寄存器不够或者某个值在调用过程返回后还需要用,就使用保存寄存器 $s0~$ s7。 MIPS 指令系统中没有寄存器传送指令,为了提高汇编表示的可读性,引入一条伪指令 move 来表示寄存器传送,汇编器将其转换为具有相同功能的机器指令。伪指令“move $t0, $s0”对应的机器指令为“add $t0, $zero, $s0”。 (1) 过程 sum_array:过程中使用的 array 数组是入口参数,而不是局部变量,无须在该过程的栈帧中给它分配空间,array 数组的首地址作为入口参数被传递给过程 sum_array(假定在 $a0 中)。此外,还有另一个入口参数为 num(假定在 $a1 中),最后有一个返回参数 sum。因为该过程中又调用了 compare 过程,因此它不是叶子过程。所以,其栈帧中除</num;> <!-- page: 109 --> 了保留所用的保存寄存器外,还必须保留返回地址 $ ra,以免在调用过程 compare 时被覆盖。是否保存 $ fp 要看具体情况,如果确保后面一直都用不到 $ fp,则可以不保存它,但编译器往往无法预测这种情况,所以通常采用统一的做法,即总是保存 $ fp 的值。因而,该过程的栈帧中要保存的信息有返回地址 $ ra 和帧指针 $ fp,其栈帧空间大小至少为 $ 4 \times 2B = 8B $。 汇编语言表示如下。 [tbl-22.md](tbl-22.md) (2) 过程 compare: 入口参数为 $a$ 和 $b$,分别在 $a0 和 $a1 中,有一个返回参数,没有局部变量,是叶子过程,且过程体中没有用到任何保存寄存器,所以栈帧中不需要保留任何信息。 [tbl-23.md](tbl-23.md) 22. 以下是一个计算阶乘的 C 语言递归过程,请按照 MIPS 过程调用协议写出该递归过程对应的 MIPS 汇编语言程序,要求目标代码尽量短(提示:乘法运算可用乘法指令“mul rd,rs,rt”来实现)。 int fact(int n) <!-- page: 110 -->
{
if (n&amp;lt;1)
return(1);
else
return(n * fact(n-1));
}
## 【分析解答】 过程 fact 有一个输入参数 $n$,按 MIPS 过程调用协议,$n$ 应在 $a0$ 中,返回参数应存放在 $v0$ 中。过程内没有局部变量,故无须在其栈帧中保留局部变量所用空间;需递归调用 fact 过程,所以必须在其栈帧中保留返回地址 $ra$。过程体内全部使用临时寄存器 $t0~$ t9,因而无须在其栈帧中保存通用寄存器。因为是递归调用,所以需在栈帧中保留输入参数。因而,该过程的栈帧中要保存的信息有返回地址 $ra$、帧指针 $fp$ 和输入参数 $a0$,其栈帧空间大小至少为 $4 \times 3B = 12B$。
fact: addi $sp, $sp, -12 #generate stack frame
sw $ra, 8($sp) #save $ra on stack
sw $fp, 4($sp) #save $fp on stack
addi $fp, $sp, 0 #set $fp
sw $a0, 0($fp) #save $a0(n) on stack
slti $t0, $a0, 1 #if (n&amp;lt;1) $t0=1 else $t0=0
bne $t0, $zero, exit1 #if (n&amp;lt;1) goto exit1
addi $a0, $a0, -1 #n=n-1
jal fact #call fact
lw $t1, 0($fp) #restore n
mul $v0, $t1, $v0 #v0=n * fact(n-1)
j exit
exit: addi $v0, $zero, 1 #v0=1
exit: lw $ra, 8($sp) #restore $ra
lw $fp, 4($sp) #restore $fp
addi $sp, $sp, 12 #free stack frame
jr $ra #return to caller
<!-- page: 111 --> ## 5.1 教学目标和内容安排 主要教学目标:使学生了解 CPU 的主要功能、CPU 的内部结构、指令的执行过程、数据通路的基本组成、数据通路的定时、数据通路中信息的流动过程、控制器的实现方式、硬连线路控制器的设计、做程序控制器的设计、异常和中断的概念等,为进一步深入理解流水线 CPU 的设计原理和高级流水线技术打下基础。