RISC-V 汇编程序编程练习

RISC-V 汇编程序编程练习

一,实验目的

​ 通过编写和运行RISC-V汇编程序,熟悉RISC-V基本指令集中的指令、RISC-V汇编的编程规范,了解递归函数的调用过程。

二,实验步骤(包括实验结果,数据记录、截图等)

2.1 使用RV64I指令集编写32位整数乘法函数mymul

2.1.1 思路

先用c语言编写用加法实现有符号乘法的代码,然后再扮演人肉编译器,将c代码一行一行编译成Riscv汇编语言。

2.1.2 结果符号判断

定义sgn变量,初始值为0,根据a、b的符号赋予不同的值。

给sgn赋完值后,结果符号已确定,所以a、b均取绝对值进行后续运算。

absgn
≥ 0 \ge0 0 ≥ 0 \ge 0 00
< 0 < 0 <0 < 0 < 0 <00
< 0 < 0 <0 ≥ 0 \ge 0 01
≥ 0 \ge 0 0 < 0 < 0 <0-1

所以只要sgn为0,结果就为正值;否则取负。

对应代码如下:

 int sgn=0;

    if(a < 0)
    {
        a = -a;
        sgn ++;
    }

    if(b < 0)
    {
        b = -b;
        sgn --;
    }

if(sgn == 0) return res;
else return -res;
2.1.2 思路一:用位移实现乘法
2.1.2.1 C语言代码

考察二进制乘法的分解:对于 a × b a\times b a×b,将b逐位分解。只要b的第i位 ≠ 0 \ne0 =0,那么a便左移i位。最后将每一位的乘法结果相加即可。

int res = 0;
while (b) {
    if (b & 1) {
        res += a;
    }
    a <<= 1;
    b >>= 1;
}

完整代码:

int mymul(int a,int b) {
    int res = 0;
    int sgn = 0;
    if(a>0)
    {
        a=-a; sgn ++;
    }
    if(b<0)
    {
        b=-b; sgn--;
    }
    while (b) {
        if (b & 1) {
            res += a;
        }
        a <<= 1;
        b >>= 1;
    }
    if(sgn == 0) return res;
    else return -res;  
    
}
2.1.2.2 人肉编译器模式

逐行编译如下:

	.text
	.globl	mymul
	.type	mymul,@function
mymul:
    addi    sp,sp,-32
    sd  ra,24(sp)
    sd  s0,16(sp)
    addi    s0,sp,32 
    
    mv  a5,a0
    mv  a4,a1
    sw  a5,-20(s0)  #存a之处
    sw  a4,-24(s0)  #存b之处

    #初始化res
    sw  zero,-28(s0)
    #初始化sgn
    sw  zero,-32(s0)

.ifa:
	#if(a<0) 
    lw  a5,-20(s0)
    sext.w  a5,a5
    bgez    a5,.elsea
    
	#a=-a
    negw    a5,a5
    sw  a5,-20(s0)
    
    #sgn++
    lw  a3,-32(s0)
    addiw a3,a3,1
    sw  a3,-32(s0)

.elsea:

.ifb:
	#if(b<0)
    lw  a4,-24(s0)
    sext.w  a4,a4
    bgez    a4,.elseb
    
	#b=-b
    negw    a4,a4
    sw  a4,-24(s0)   
    
    #sgn--
    lw  a3,-32(s0)
    addiw a3,a3,-1
    sw  a3,-32(s0)

.elseb:

.while:
    #while(b!=0)
    lw  a4,-24(s0)
    beqz    a4,.outwhile
    ## if(b&1)
    andi    a6,a4,1
    beqz    a6,.outif
    # res+=a
    lw  a7,-28(s0)
    lw  a5,-20(s0)
    addw a7,a7,a5
    sw  a7,-28(s0)

.outif:
    slli    a5,a5,1
    srli    a4,a4,1
    sw  a5,-20(s0)
    sw  a4,-24(s0) 
    j   .while

.outwhile:

.ifsgn:
    lw  a3,-32(s0)
    lw  a7,-28(s0)
    beqz    a3,.end
    sext.w  a7,a7
    negw    a7,a7
    
.end:
    mv  a0,a7

    ld  ra,24(sp)
    ld  s0,16(sp)
    addi    sp,sp,32
    ret

2.1.3 思路二:用加法实现乘法
2.1.3.1 C语言代码

考察小学乘法的定义, a × b a\times b a×b a a a b b b相加,因此只需要使用循环语句,实现a个b相加即可。

int res = 0;
for(int i=0;i<b;i++)
  res+=a;

完整代码:

#include<stdlib.h>
#include<stdio.h>
int mul(int a,int b) {
    int res = 0;
    int sgn=0;

    if(a < 0)
    {
        a = -a;
        sgn ++;
    }

    if(b < 0)
    {
        b = -b;
        sgn --;
    }

    for(int i=0;i<b;i++)
        res+=a;
    
    if(sgn == 0) return res;
    else return -res;   
    
}

2.1.3.2 人肉编译器模式

将上述代码一行一行编译成riscv64的汇编语言。

  • 分配栈空间

因为本函数变量较多,原先分配的32个Byte的空间不够,因此必须重新分配栈空间。

最顶层的ra和s0各占8Byte,其余变量a,b,res,sgn,i各占4Byte,共需36Byte的栈空间。

栈空间分配如下:

image-20230311223758268
  • 逐行编译如图:
	.text
	.globl	mymul
	.type	mymul,@function
mymul:
	addi	sp,sp,-36
	sd	ra,28(sp)
	sd	s0,20(sp)
	addi	s0,sp,36

	#Your code here	
    #先把参数存入栈中
	mv	a5,a0
    mv	a4,a1
    sw	a5,-32(s0)
    mv	a5,a4
    sw	a5,-36(s0)

#初始化 result和sgn
    sw	zero,-20(s0)
    sw	zero,-24(s0)

#判断a和b的符号
  #判断a是否<0
.ifa:
    lw	a5,-32(s0)
    sext.w	a5,a5
    bgez	a5,.elsea

    #a<0,进入if语句,将a取相反数 
    lw	a5,-32(s0)
    negw	a5,a5  
    sw	a5,-32(s0) 

    #sgn++
    lw	a5,-24(s0)
    addiw	a5,a5,1
    sw	a5,-24(s0)

.elsea:

.ifb:
  #判断b是否<0
    lw	a5,-36(s0)
    sext.w	a5,a5
    bgez	a5,.elseb

    #b<0,进入if语句,将b取相反数
    lw	a5,-36(s0)
    negw	a5,a5
    sw	a5,-36(s0)

    #sgn--
    lw	a5,-24(s0)
    addiw	a5,a5,-1
    sw	a5,-24(s0)

.elseb:
#进入for语句
.for1:
  # int i = 0
    sw	zero,-28(s0)
    j	.for2

.in_for:
  # for 主体
    lw	a4,-20(s0)
    lw	a5,-32(s0)
    addw	a5,a5,a4
    sw	a5,-20(s0)

.for3:
  # i++
    lw	a5,-28(s0)
    addiw	a5,a5,1
    sw	a5,-28(s0)
.for2:
  # 判断 i 和 b 的大小
    lw	a4,-28(s0)
    lw	a5,-36(s0)
    sext.w	a4,a4
    sext.w	a5,a5
    blt	a4,a5,.in_for


.if_sgn:
# 根据sgn的值决定是否要加负号
    lw	a5,-24(s0)
    sext.w	a5,a5
    beqz    a5,.else_sgn
  # sgn != 0,变号
    lw	a5,-20(s0)
    negw	a5,a5
    sext.w	a5,a5
.else_sgn:
  # sgn == 0,不变号,直接返回result
    lw	a5,-20(s0)
    j	.end


.end:
 # end
    mv	a0,a5

	ld	ra,28(sp)
	ld	s0,20(sp)
	addi	sp,sp,36
	ret



2.2 使用RV64I指令集和mymul函数实现自然数阶乘函数myfac

2.2.1 C语言代码
int myfunc(int a)
{
    int result = 1;
    for(int i = 1; i<=a; i++)
    {
        result = mymul(result,i);
    }
    return result;
}
2.2.2 人肉编译器模式
  • 分配栈空间
image-20230311224620776
  • 逐行编译
	.text
	.globl	myfac
	.type	myfac,@function	
myfac:
	addi	sp,sp,-28
	sd	ra,20(sp)
	sd	s0,12(sp)
	addi	s0,sp,28
	
	#Your code here	

# 存a
    mv      a5,a0
    sw      a5,-28(s0)

# int result = 1
    li      a5,1
    sw      a5,-20(s0)

.for1:
# for: int i = 1
    li      a5,1
    sw      a5,-24(s0)
    j       .for2

.in_for:
# for主体
    lw      a4,-24(s0)
    lw      a5,-20(s0)
    mv      a1,a4
    mv      a0,a5
    call    mymul
    mv      a5,a0
    sw      a5,-20(s0)

.for3:
# i++
    lw      a5,-24(s0)
    addiw   a5,a5,1
    sw      a5,-24(s0)

.for2:
# 判断i和a的大小
    lw      a4,-24(s0)
    lw      a5,-28(s0)
    sext.w  a4,a4
    sext.w  a5,a5
    ble     a4,a5,.in_for

.end:
    lw      a5,-20(s0)
    mv      a0,a5

	ld	ra,20(sp)
	ld	s0,12(sp)
	addi	sp,sp,28
	ret








2.3 编译和测试结果

将文件按如下位置放置:

image-20230317092243120

makefile如下:


CC=riscv64-unknown-elf-gcc
CFLAGS=-g
EXEC=test
# mul1或mul2均可
EXECOBJ=mul2.o fac2.o main.o
SRC=mul2.s fac2.s main.c

all: $(EXEC)

$(EXEC): $(EXECOBJ)
	$(CC) $(CFLAGS) $^ -o $@

$(EXECOBJ): $(SRC)
	$(CC) $(CFLAGS) -c $^


.PHONY: clean

clean:
	rm -rf $(EXECOBJ) $(EXEC)
	

最终,思路一和思路二都顺利通过了测试,结果如下:

image-20230317085350978

三,实验分析和总结

3.1 各伪操作标识符解释

  • .text:代码段,等效于“.section .text”。
  • .globl myfac:定义一个全局符号 myfac。只要一起编译,在另一个文件也可以通过引用”myfac“跳转到myfac代表的地址
  • .type myfac,@function :.type伪操作用于定义符号的类型,将名为myfac的符号定义为一个函数(function)。

3.2 代表地址的符号

  • .part和part:前面有“.”表示该符号仅对当前文件可见,没有"."则对全局可见

四,实验收获、存在问题、改进措施或建议

4.1 汇编语言

汇编语言(Assembly Language)是任何一种用于电子计算机、微处理器、微控制器或其他可编程器件的低级语言,亦称为符号语言。在汇编语言中,用助记符代替机器指令的操作码,用地址符号或标号代替指令或操作数的地址。

与高级语言相比,汇编语言更接近计算机底层的硬件结构,因此在某些场合下可以提供更高效、更精确的控制。通常使用汇编语言编写程序时,需要了解CPU的架构、指令集和寄存器等底层细节。在不同的设备中,汇编语言对应着不同的机器语言指令集,通过汇编过程转换成机器指令。特定的汇编语言和特定的机器语言指令集是一一对应的,不同平台之间不可直接移植。

汇编语言也是高级语言和机器语言之间的桥梁,可以通过汇编语言将高级语言翻译成机器指令,也可以通过汇编语言对机器指令进行调试和优化。虽然汇编语言相对于高级语言来说更加繁琐和复杂,但它也具有灵活性和效率优势,在某些场合下仍然具有重要的应用价值。

4.2 Segmentation fault

我在某次测试时,遇到了如下错误:

image-20230317091003436

仔细检查,发现是在最后end的代码段中,不慎将ld敲成了lw。为什么lw就不行,具体原因未知。

.end:
    mv  a0,a7
    
	#以下lw应该是ld
    lw  ra,24(sp)
    lw  s0,16(sp)
    addi    sp,sp,32
    ret
  • 8
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值