体系结构实验(2)—— 不同指令集的对比

Chp2 LAB Comparison of Instruction Sets

P0

0.1 安装虚拟机 Win Xp (32 bit)

  1. 首先打开VMware workstation 安装 雨林木风 WinXP SP3 安装版 YS8.0

安装过程截图如下:

在这里插入图片描述

  1. 安装VMwareTools,方便再虚拟机和真机之间传输文件,安装完成后重新启动。

在这里插入图片描述

0.2 安装和使用WinDXL

0.2.1 安装和配置
  1. 将WinDLX 文件夹拖动到桌面上,然后点击箭头所指示的图标

在这里插入图片描述

  1. 双击WinDLX图标后,会出现带有六个图标的主窗口。双击其中的一个图标,将显示其子窗口。

在这里插入图片描述

  1. 为了初始化模拟器,点击File菜单中的Reset all菜单项,弹出一个“Reset DLX”对话框。然后点击窗口中的确认按钮即可。
    在这里插入图片描述

  2. 点击Configuration打开菜单,然后分贝点击Floating Point StagesMemory Size菜单项,选择如下标准配置

在这里插入图片描述

0.2.2 程序的装载和模拟
  1. 选择File Load Code or Data,窗口中会列出目录中所有汇编程序。然后选择prim.s , 先点击select 再点击load。 这是会出现弹窗,选择是(Y)
    在这里插入图片描述

  2. 点击主窗口中的Execution开始模拟。在出现的下拉式菜单中,点击Single Cycle或按F7键。这时可以分别打开子窗口进行查看

在这里插入图片描述

  • Pipeline

    窗口中用图表形示显示了DLX的五段流水段和浮点操作(加/ 减,乘和除)的单元。

  • Code

    可以看到代表存储器内容的三栏信息,从左到右依次为:地址(符号或数字)、命令的十六进制机器代码汇编命令。这些不同颜色指明命令处于流水线的哪一段,与pipeline中的信息相对应。其他方框中带有一个“X”标志,表明没有处理有效信息。

  • Clock Cycle Diagram
    显示流水线的时空图,当前的模拟正在第二时钟周期。

  • Register

    显示寄存器的相关信息

P1

Write a prim.c with the same function as prim.s provided by WinDLX, analyze the assembly code for prim.c.

用与WinDLX提供的prim.s相同的函数编写prim.c,分析prim.c的汇编代码

1.1 分析WinDLX提供的prim.s

1.1.1 WinDLX 汇编语言简介

(1)伪指令:

.data [address] 标识下面的数据存放在数据区,address指示数据区的起始地址;
.text [address] 标识下面的代码存放在代码区,address指示代码区的起始地址;
.global label 使得标有label的代码可以被全局访问;
.word word1,word2,... 在存储器中顺序存放列出的字;

.byte byte 1 ,byte2,... 在存储器中顺序存放列出的字节;
.double number1,... 在存储器中顺序存放列出的双精度数;
.ascii "stringl ",... 在存储器中顺序存放列出的字符串,每个字符串均不会被自动加零结尾;
.asciiz "stringl ",... 在存储器中顺序存放列出的字符串,每个字符串会被自动加零结尾;
.space size 在存储器中空出size大小的区域;
.align n 使得后面的数据/代码地址低n位为О对齐。

(2)关键指令:

addi : R[regb] <-- R[rega] + imm16 将寄存器a中的数值和立即数相加,结果保存在寄存器b中

seq: seq R[regc], R[rega], R[regb] 就是比较寄存器a和寄存器b的数值,如果相等那么寄存器c中的值为1否则为0。

bnez操作:bnez R[rega], label 如果R[rega]如果不为0,则跳转到lable处

1.1.2 prim.s源程序注释

该程序的主要功能是生成Count个质数,并存放在Table起始的地址空间中,其中每个质数占用4个字节单元。

找到质数的方法是枚举。value从第一个质数2开始,不断递增,判断value的值是否能被已经保存在table中的质数整除。如果可以,则不是质数,反之是质数,将value保存在table中,直到找到count个质数。

; *********** WINDLX Exp.2: Generate prime number table *************
;*********** (c) 1991 G¸nther Raidl		       *************
;*********** Modified 1992 Maziar Khosravipour	       *************

;-------------------------------------------------------------------
; 程序功能:生成Count个质数,并存放在Table表中。
;-------------------------------------------------------------------
		.data ; 数据段标识
		
		.global		Count  ;定义全局变量Count
Count:		.word		10     ; count=10, 类型是字
		.global		Table  ; 定义全局变量Table
Table:		.space		Count*4 ;空出4*count个单位地址空间,空间的首地址是Table (每个存放单元分配4个字节)

		.text;	代码段标识
		.global	main; 标识main可以被全局引用
main:
		; 初始化
		addi		r1,r0,0		;将r0的值与0相加,结果赋给r1,r1保存的是索引index
		addi		r2,r0,2 	;同理r2的值为2,r2中保存的是当前的待检测的值value
		;判断R2能不能被“Table中的数值”整除
NextValue:	addi	r3,r0,0 	;同理r3的值为0,保存的是遍历当前Table的索引i。
Loop:		seq		r4,r1,r3	; seq操作:这里用来判断是否已经遍历到Table的末尾,结果布尔值存在R4中
		bnez		r4,IsPrim	;如果r4不等于0,跳转到IsPrim;即遍历完数组都没有可以把r2整除的,说明r2是素数
		lw          r5,Table(R3);从Table(R3)中读取到R5中;
		divu		r6,r2,r5; 无符号除法        R6 = R2/R5(其实整除)
		multu		r7,r6,r5; 无符号乘法        R7 = R6*R5
		subu		r8,r2,r7; 无符号减法        R8 = R2–R7
		beqz		r8,IsNoPrim; 如果r8等于0,说明可以被整除,不是素数。跳转到IsNoPrim处
		addi		r3,r3,4  ; r3+=4  继续增加索引i数值,遍历table
		j           Loop ;无条件跳转到Loop处,继续循环
IsPrim: 	; 如果当前值是素数,
		sw          Table(r1),r2; 那么将 R2写入到table[r1]中
		addi		r1,r1,4 ;并且R1后移一个单位:r1+=4 指向下一个table单元
		lw		r9,Count; 将Count(10)放入R9
		srli	r10,r1,2; 逻辑右移2位;相当于r1除以4;因为R1每次循环增加4,因此与count比较时要除以4
		sge		r11,r10,r9; 判断R10是不是大于R9(count),如果是那么R11为1;
		bnez	r11,Finish; 如果是1(不为0),说明访问已经搜索完count个数,任务结束,跳转到Finish

IsNoPrim:	;如果当前值是不是素数,则检查下一个值
		addi	r2,r2,1 	; R2++
		j		NextValue ; 跳转到NextValue处,检查下一个值
Finish: 	;结束
		trap	0	; : trap 0通知WINDLX模拟器程序结束	

1.2 实现相同功能的prim.c

1.2.1 算法流程

算法流程图如下:

在这里插入图片描述

1.2.2 C语言实现prim.c

具体的prim.c 实现如下:

#include <stdio.h>
int Count = 10;  // 最多保存的质数是10个
int Table[10];  //存储查找到的质数
int  main() {
    int index = 0;	// table的索引从0开始
    int value = 2;  // 待检测的值
    while (index  < Count) {  // 判断是否已经找到了count个质数
        int i;
        for (i = 0; i < index; i ++) {  // 判断table中是否存在一个值,可以整除当前待检测的值
            if (value % Table[i] == 0) {	// 如果可以被整除,则不是素数
                // Is not Prime
                value++;
                break;
            }
        }
        // Is Prime
        Table[index] = value;   	// 反之,是素数, 将其保存在table中
        index ++;
        value++;
    }
    return 0;
}

P2

Observe the memory areas in bytes for the prime numbers given by the computer where prim.c running. Analyze what you have seen.

观察prim.c运行的计算机给出的质数的内存区域的字节数。分析你所看到的。

2.1 本地电脑结果分析

  1. 在visual Studio 上打开prim.c 代码,打上断点,选择【调试】,【开始调试】

在这里插入图片描述

  1. 选择【调试】,【窗口】,【反汇编】

在这里插入图片描述

  1. 打开内存 Ctrl+Alt+M,1

在这里插入图片描述

  1. 然后打开内存,查看Table为起始地址的内存空间。可以看到int型的整数分配了4个字节单元(32bits), 从小到大依次存放对应质数的16进制数。

在这里插入图片描述

大端存储模式:数据的低位保存在内存中的高地址中,数据的高位保存在内存中的低地址中;
小端存储模式:数据的低位保存在内存中的低地址中,数据的高位保存在内存中的高地址中;

如下图所示,反应了第一个质数2在不同的存储模式下的内存情况。很显然,我们的实验结果表明,采用的是后者——小端存储模式

在这里插入图片描述

2.2 WinDLX结果分析

  1. 首先点击Memory , Symbols 找到Table的地址为0x00001004

在这里插入图片描述

  1. 然后F7 单步执行指令,同时观察时钟周期,指令流水,和代码的运行情况
    在这里插入图片描述

  2. 点击Memory, display 查看内存空间地址。 然后查询第一部得到的Table地址(即0x00001004)
    在这里插入图片描述

    1. 观察存储的质数,一个质数占4个字节。其中低字节存储在地地址,高字节存储在高地址,因此为小端存储方式。

在这里插入图片描述

P3

交叉编译针对RISC-V目标的prim.c,或者用RISC-V汇编语言编写prim.c的汇编代码,并在RISC-V在线模拟器(www.kvakil.me/venus/)中运行。观察由RISC-V模拟器给出的素数的内存区域的字节数

3.1 RISC-V 简介

  • RISC-V 寄存器集介绍

在这里插入图片描述

  • 常见汇编提示符(assemble directives)
   .text:进入代码段。 
   .align 2:后续代码按22字节对齐。 
   .globl main:声明全局符号“main”。 
   .section .rodata:进入只读数据段 
   .balign 4:数据段按4字节对齐。 
   .string “Hello, %s!\n”:创建空字符结尾的字符串。 
   .string “world”:创建空字符结尾的字符串。
  • 内存分配

    RV32I为程序和数据分配内存。图中的顶部是高地址,底部是低地址。在RISC-V软件规范中,栈指针(sp)从0xbffffff0开始向下增长;程序代码段从0x00010000开始,包括静态链接库;程序代码段结束后是静态数据区,在这个例子中假设从0x10000000开始;然后是动态数据区,由C语言中的malloc()函数分配,向上增长,其中包含动态链接库。

在这里插入图片描述

  • 常用指令

    下面列举了编写prim.s代码需要用到的所有指令格式功能和用法。

在这里插入图片描述

在这里插入图片描述

3.2 RISC-V 实现prim.s

.data
    Count: .word 10                           # 定义全局变量Count=10
 Table: 
    .word 0                  				  # 存放结果的地址空间
           
.text
    main:
    	lw a0,Table							 # a0存放index的值,初始时a0为table的首地址
        addi a1,zero,2						 # a1存放的是当前待检测的Value值
        addi a4,zero,0					     # 当前table中存放的元素
        lw a5,Count							 # a5=Count
    NextValue:
    	addi a3,zero,0						 # a3存放的是遍历当前Table的索引i
        
    Loop:
    	beq a0,a3,IsPrim					 # 当前已经遍历到了末尾也被被整除,说明是素数
        lw a6,0(a3)							# a6存放当前遍历的值 table[i]
        rem a7,a1,a6						# a7= a1 % a6
        beq a7,zero, IsNotPrim				# 如果能被整除,说明不是素数
        addi a3,a3,4						# 否则遍历下一个 i+=4
        j Loop								# 跳转到Loop
        
    IsPrim:  								# 如果当前值是素数
    	sw a1,0(a0)						    # 则要把a1存放到table[a0]
    	addi a0,a0,4						# 索引向后移动4个字节
        addi a4,a4,1						# table中的元素个数加1:a4++
        beq a4,a5,Finish					# 如果a4==a5,说明已经找到了count个素数,结束程序
        
    IsNotPrim:								# 如果当前值不是素数
    	addi a1,a1,1						# 当前值加1, 检查下一个数
        j NextValue							# 跳转到NextValue处,检查下一个值
        
    Finish:
    	jalr zero, 0(ra)  					# 返回main

3.3 结果分析

将上述代码在RISC-V在线模拟器(www.kvakil.me/venus/)中运行。观察由RISC-V模拟器给出的素数的内存区域的字节数,每一个质数占用4个字节。低字节存放在低地址处,高字节存放在高地址处,因此可以判定为小端存储

在这里插入图片描述

P4

交叉编译MIPS目标的prim.c,或者用MIPS汇编语言编写prim.c的汇编代码,在Mars模拟器上运行。观察Mars模拟器给出的质数的存储区域的字节数。

4.1 安装Mars 模拟器

  1. 首先从官网下下载压缩包MARS MIPS simulator - Missouri State University

在这里插入图片描述

  1. 然后一路下一步安装即可

  2. 初步熟悉Mars模拟器的使用

在这里插入图片描述

4.2 MIP简介

  • 寄存器

    (1)两个特殊寄存器:

    $0:不管你存放什么值,其返回值永远是零。

    $31:永远存放着正常函数调用指令(jal)的返回地址。

    (2)$at

    由编译器生成的复合指令使用,

    (3)$v0, $v1

    用来存放一个子程序 (函数) 的非浮点 运算的结果或返回值。如果这两个寄存器不够存放 需要返回的值,编译器将会通过内存来完成。

    (4)$ a0-a3

    用来传递子函数调用时前4个非 浮点参数。

    (5)$ t0-t9:

    依照约定,一个子函数可以不用保 存并随便的使用这些寄存器。在作表达式计算时,这些寄存器是非常好的暂时变量。当调用一个子函数时,这些寄存器中的 值有可能被子函数破坏掉。所以也是最不安全的。

    (6)$ s0-s8:

    依照约定,子函数必须保证当函数返回时这些寄存器的内 容必须恢复到函数调用以前的值, 或者在子函数里不用这些寄存器或把它们保存 在堆栈上并在函数退出时恢复。 这种约定使得这些寄存器非常适合作为寄存器变量、 或存放一些在函数调用期间必须保存的原来的值。(类比:x86汇编中的函数序言和函数尾声)

    (7)$ k0, k1:

    被OS的异常或中断处理程序使 用。被使用后将不会恢复原来的值。因此它 们很少在别的地方被使用。

    (8)$gp:

    如果存在一个全局指针,它将指向运行时决定 的静态数据(static data)区域的一个位置。这意味 着,利用gp作基指针,在gp指针32K左右的数 据存取,系统只需要一条指令就可完成。如果没有全局指针,存取一个静态数据区域 的值需要两条指令:一条是获取有编译器和loader决定好的32位的地 址常量。另外一条是对数据的真正存取。为了使用$ gp, 编译器在编译时刻必须知道 一个数据是否在$ gp的64K(上下32k)范围之内。并不是所有的编译和运行系统支持gp的使用。

    (9)$ sp:

    堆栈指针的上下需要显 式的通过指令来实现。因此 MIPS通常只在子函数进入和 退出的时刻才调整堆栈的指针。 这通过被调用的子函数来实现。SP通常被调整到这个被调用 的子函数需要的堆栈的最低的 地方,从而编译器可以通过相 对sp的偏移量来存取堆栈上 的堆栈变量。

  • 指令

    MIP的指令和上文提到的RISC-V的指令几乎一样,它们采用的都是精简指令系统计算结构(RISC), 在下面的编程实例中可以看到,MIP编写的汇编程序可以非常方便的由RISC-V实现的程序改写(只需要改写寄存器的写法)

4.3 MIP实现prim.s

.data 						    # 数据段
count: .word 10					# 定义count=10
table: .space 40				# 定义table,存放素数的地址空间

.text 
main:
la $t0,table					# t0存放index的值,初始时t0=0
la $s0,table					# s0存放table的地址
addi $t1,$zero,2				# t1存放的是当前待检测的Value值,初始值为2
addi $t4,$zero,0				# t4 记录当前table中已经存放的元素个数
lw $t5,count					# t5=count,最多保存的素数个数

Nextvalue:
addi  $t3,$s0,0					# t3存放的是遍历当前Table的索引i

Loop:
beq $t0,$t3,IsPrim				# 当前已经遍历到了末尾也被被整除,说明是素数
lw $t6,0($t3)					# t6存放当前遍历的值 table[i]
rem $t7,$t1,$t6					# t7= t1 % t6
beq $t7,$zero, IsNotPrim		# 如果能被整除,说明不是素数
addi $t3,$t3,4					# 否则遍历下一个 i+=4
j Loop						# 跳转到Loop

IsPrim:
sw $t1,0($t0)						# 则要把a1存放到table[a0]
addi $t0,$t0,4						# 索引向后移动4个字节
addi $t1,$t1,1                      # 检查下一个数
addi $t4,$t4,1						# table中的元素个数加1:a4++
beq $t4,$t5,Finish					# 如果a4==a5,说明已经找到了count个素数,结束程序
j Nextvalue

IsNotPrim:
addi $t1,$t1,1					# 当前值加1, 检查下一个数
j Nextvalue						# 跳转到NextValue处,检查下一个值

Finish:
 	li $v0, 10
	syscall

4.3 结果分析

由下图的结果可以看到,一个质数占用4个字节的空间。比如说第一个质数2,它的存储格式为0x00000002 , 在连续的地址空间共存储了10个质数,验证了程序的正确性。

在这里插入图片描述

为了进一步探究存储模式,在数据段添加如下的一行字符串

msg: .asciiz "Hello world"

由下图可以看出,其中低字节在低地址处,高字节在高地址处,因此属于小端存储的方式。

在这里插入图片描述

P5

不同指令集的对比和总结

  • 存储模式

    上述三种指令集均为小端存储模式。

  • 整数计算

    RISC-V 中没有字节或半字宽度的整数计算操作。操作始终是以完整的寄存器宽度。内存访问需要的能量比算术运算高几个数量级。因此低宽度的数据访问可以节省大量的能量,但低宽度的运算不会。ARM-32 具有一个不寻常的功能,对于大多数算术逻辑运算中的一个操作数,你可以选择对它进行移位。尽管这些指令的使用频率很低,但它使数据路径和数据通路更加复杂。与此相对的是,RV32I 提供了单独的移位指令。RV32I 也不包含乘法和除法,它们包含在可选的 RV32M 扩展中。与x86-32 不同,即使处理器没有添加乘除法扩展,完整的 RISC-V 软件栈也可以运行,这可以缩小嵌入式芯片的面积。MIPS-32 汇编程序可能用一系列移位以及加法指令来替换乘法,以提高性能,这可能会使程序员看到处理器执行了汇编程序中没有的指令,进而造成混淆。RV32I 可以忽略了这些特性:循环移位指令和整数算术溢出检测,这两个特性都可以用若干条 RV32I 指令来实现。

  • 寻址方式

    与 x86-32 不同,RISC-V 没有特殊的堆栈指令。将 31 个寄存器中的某一个作为堆栈指针,标准寻址模式使用起来和压栈(push)和出栈(pop)类似,并且不增加 ISA 的复杂性。

    与 MIPS-32 不同,RISC-V 不支持延迟加载(delayed load)。与延迟分支的设计相似,为了更好的适应五级流水线,MIPS-32 重新定义了 load 指令的语义,load 上来的数据在 load 指令两个指令后才可用。

  • 条件分支

    RISC-V 去掉了 MIPS-32,Oracle SPARC 等指令集中的延迟分支特性等。对RISCV省略了 x86-32 中的循环指令:loop,loope,loopz,loopne,loopnz。

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zyw2002

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值