# Lab2: RISC-V RV32I[MA] emulator with ELF support ## Build a RISC-V GNU Compiler Toolchain Getting the RISC-V GNU Compiler Toolchain in [RISC-V GNU Compiler Toolchain](https://github.com/riscv/riscv-gnu-toolchain) github repository. ```shell= git clone --recursive https://github.com/riscv/riscv-gnu-toolchain ``` It will take a long time to download all the required files from the repository (about 4G of data to download), once you download it, follow the steps below to build toolchains. When the download is complete, go to the downloaded folder and start building the toolchain according to your needs (the requirement for this homework is `--with-arch=rv32i` `--with-abi=ilp32`) ```shell= cd riscv-gnu-toolchain export PATH=$PATH:/opt/riscv/bin ./configure --prefix=/opt/riscv --with-arch=rv32i --with-abi=ilp32 sudo make ``` Then wait a long time to build the corresponding compiler toolchain, which is about 45 minutes. When `make` is complete, check the folder to find the corresponding riscv compiler, and then we can use the compiler to start the homework requirements. # rewrite Mulitipiler mul.c : ```clike= /* riscv64-unknown-elf-gcc -march=rv32i -mabi=ilp32 -O3 -nostdlib test1.c -o test1 */ int mul(int, int); int power(int, int); int _start() { int i; int res = mul(5, 4); volatile char *tx = (volatile char *)0x40002000; const char *result1 = "The value of result is "; while (*result1) { *tx = *result1; result1++; } for (i = 31; i >= 0; i--) { if ((res >> i) & 0x01) *tx = '1'; else *tx = '0'; } *tx = '\n'; return 0; } int mul(int mul1, int mul2) { int result = 0; int i; for (i = 0; i < 32; i++) { if ((mul2 >> i) & 0x1) result = result + (mul1 << i); } return result; } ``` If mul(4,5) is passed in, it will get the following result when processing emu-rv32i: ```shell= ./emu-rv32i mul The value of result is 00000000000000000000000000010100 >>> Execution time: 161403 ns >>> Instruction count: 496 (IPS=3073053) >>> Jumps: 116 (23.39%) - 31 forwards, 85 backwards >>> Branching T=114 (76.00%) F=36 (24.00%) ``` Disassemble the ELF files: ```shell= $ riscv-none-embed-objdump -h mul mul: file format elf32-littleriscv Sections: Idx Name Size VMA LMA File off Algn 0 .text 000000d0 00010054 00010054 00000054 2**2 CONTENTS, ALLOC, LOAD, READONLY, CODE 1 .rodata 0000001c 00010124 00010124 00000124 2**2 CONTENTS, ALLOC, LOAD, READONLY, DATA 2 .comment 00000033 00000000 00000000 00000140 2**0 ``` Use objdump to see the compiled risc-v assembly results: ```shell= $riscv-none-embed-objdump -d mul mul: file format elf32-littleriscv Disassembly of section .text: 00010054 <_start>: 10054: 00000693 li a3,0 10058: 00100793 li a5,1 1005c: 00400813 li a6,4 10060: 00500513 li a0,5 10064: 02000593 li a1,32 10068: 40f85733 sra a4,a6,a5 1006c: 00177713 andi a4,a4,1 10070: 00f51633 sll a2,a0,a5 10074: 00178793 addi a5,a5,1 10078: 00070463 beqz a4,10080 <_start+0x2c> 1007c: 00c686b3 add a3,a3,a2 10080: feb794e3 bne a5,a1,10068 <_start+0x14> 10084: 000107b7 lui a5,0x10 10088: 12478793 addi a5,a5,292 # 10124 <mul+0x30> 1008c: 05400713 li a4,84 10090: 40002637 lui a2,0x40002 10094: 00e60023 sb a4,0(a2) # 40002000 <__global_pointer$+0x3fff06c0> 10098: 00178793 addi a5,a5,1 1009c: 0007c703 lbu a4,0(a5) 100a0: fe071ae3 bnez a4,10094 <_start+0x40> 100a4: 01f00793 li a5,31 100a8: 400025b7 lui a1,0x40002 100ac: 03000813 li a6,48 100b0: 03100513 li a0,49 100b4: fff00613 li a2,-1 100b8: 0100006f j 100c8 <_start+0x74> 100bc: 00a58023 sb a0,0(a1) # 40002000 <__global_pointer$+0x3fff06c0> 100c0: fff78793 addi a5,a5,-1 100c4: 00c78e63 beq a5,a2,100e0 <_start+0x8c> 100c8: 40f6d733 sra a4,a3,a5 100cc: 00177713 andi a4,a4,1 100d0: fe0716e3 bnez a4,100bc <_start+0x68> 100d4: 01058023 sb a6,0(a1) 100d8: fff78793 addi a5,a5,-1 100dc: fec796e3 bne a5,a2,100c8 <_start+0x74> 100e0: 400027b7 lui a5,0x40002 100e4: 00a00713 li a4,10 100e8: 00e78023 sb a4,0(a5) # 40002000 <__global_pointer$+0x3fff06c0> 100ec: 00000513 li a0,0 100f0: 00008067 ret 000100f4 <mul>: 100f4: 00000793 li a5,0 100f8: 00000613 li a2,0 100fc: 02000813 li a6,32 10100: 40f5d733 sra a4,a1,a5 10104: 00177713 andi a4,a4,1 10108: 00f516b3 sll a3,a0,a5 1010c: 00178793 addi a5,a5,1 10110: 00070463 beqz a4,10118 <mul+0x24> 10114: 00d60633 add a2,a2,a3 10118: ff0794e3 bne a5,a6,10100 <mul+0xc> 1011c: 00060513 mv a0,a2 10120: 00008067 ret ``` Compared the assembly code credit to [王昱翔](https://hackmd.io/atHu-zjwSbKJeF45nBYp6g) ```clike= .file "mul2.c" .option nopic .text .align 2 .globl main .type main, @function main: addi sp,sp,-32 sw s0,28(sp) addi s0,sp,32 li a5,-22 sw a5,-28(s0) li a5,-5 sw a5,-32(s0) sw zero,-20(s0) sw zero,-24(s0) j .L2 .L4: lw a4,-32(s0) lw a5,-24(s0) sra a5,a4,a5 andi a5,a5,1 beqz a5,.L3 lw a4,-28(s0) lw a5,-24(s0) sll a5,a4,a5 lw a4,-20(s0) add a5,a4,a5 sw a5,-20(s0) .L3: lw a5,-24(s0) addi a5,a5,1 sw a5,-24(s0) .L2: lw a4,-24(s0) li a5,31 ble a4,a5,.L4 li a5,0 mv a0,a5 lw s0,28(sp) addi sp,sp,32 jr ra .size main, .-main .ident "GCC: (GNU) 7.2.0" ``` Since the compared version has a reference to stdio.h, linker & loader will expand the code of stdio.h into the original c code, resulting in a lot of instructions for assmebly code. And my version does not reference any libraries, so the assmebly code obtained by objdump will appear much more streamlined. # rewrite Bit Reversal Bit reversal is an operation neeeded in many programsm that reverses the order of bits in a word. For example, reversing the word **0x55000011** will result in **0x880000AA**. The following C code shows a naive implementation of this function **reverse.c** : ```clike= /* riscv64-unknown-elf-gcc -march=rv32i -mabi=ilp32 -O3 -nostdlib test1.c -o test1 */ int reverse(int); int _start() { int i; int res = reverse(0x55000011); volatile char *tx = (volatile char *)0x40002000; const char *result1 = "The value of result is "; while (*result1) { *tx = *result1; result1++; } for (i = 31; i >= 0; i--) { if ((res >> i) & 0x01) *tx = '1'; else *tx = '0'; } *tx = '\n'; return 0; } int reverse(int v) { int i, r = 0; for (i = 0; i < 32; i++) { r <<= 1; r |= ((v >> i) & 0x1); } return r; } ``` Execting result: ```shell= ./emu-rv32i revserse The value of result is 10001000000000000000000010101010 >>> Execution time: 168316 ns >>> Instruction count: 501 (IPS=2976544) >>> Jumps: 87 (17.37%) - 1 forwards, 86 backwards >>> Branching T=85 (70.83%) F=35 (29.17%) ``` Disassemble the ELF files: ```shell= $ riscv-none-embed-objdump -h reverse revserse: file format elf32-littleriscv Sections: Idx Name Size VMA LMA File off Algn 0 .text 000000c8 00010054 00010054 00000054 2**2 CONTENTS, ALLOC, LOAD, READONLY, CODE 1 .rodata 0000001c 0001011c 0001011c 0000011c 2**2 CONTENTS, ALLOC, LOAD, READONLY, DATA 2 .comment 00000033 00000000 00000000 00000138 2**0 CONTENTS, READONLY ``` Use objdump to see the compiled risc-v assembly results: ```shell= $ riscv-none-embed-objdump -d revserse revserse: file format elf32-littleriscv Disassembly of section .text: 00010054 <_start>: 10054: 55000637 lui a2,0x55000 10058: 00000713 li a4,0 1005c: 00000793 li a5,0 10060: 01160613 addi a2,a2,17 # 55000011 <__global_pointer$+0x54fee6d9> 10064: 02000593 li a1,32 10068: 40e656b3 sra a3,a2,a4 1006c: 00179793 slli a5,a5,0x1 10070: 0016f693 andi a3,a3,1 10074: 00170713 addi a4,a4,1 10078: 00f6e7b3 or a5,a3,a5 1007c: feb716e3 bne a4,a1,10068 <_start+0x14> 10080: 00010737 lui a4,0x10 10084: 11c70713 addi a4,a4,284 # 1011c <reverse+0x2c> 10088: 05400693 li a3,84 1008c: 40002637 lui a2,0x40002 10090: 00d60023 sb a3,0(a2) # 40002000 <__global_pointer$+0x3fff06c8> 10094: 00170713 addi a4,a4,1 10098: 00074683 lbu a3,0(a4) 1009c: fe069ae3 bnez a3,10090 <_start+0x3c> 100a0: 01f00713 li a4,31 100a4: 400025b7 lui a1,0x40002 100a8: 03000813 li a6,48 100ac: 03100513 li a0,49 100b0: fff00613 li a2,-1 100b4: 0100006f j 100c4 <_start+0x70> 100b8: 00a58023 sb a0,0(a1) # 40002000 <__global_pointer$+0x3fff06c8> 100bc: fff70713 addi a4,a4,-1 100c0: 00c70e63 beq a4,a2,100dc <_start+0x88> 100c4: 40e7d6b3 sra a3,a5,a4 100c8: 0016f693 andi a3,a3,1 100cc: fe0696e3 bnez a3,100b8 <_start+0x64> 100d0: 01058023 sb a6,0(a1) 100d4: fff70713 addi a4,a4,-1 100d8: fec716e3 bne a4,a2,100c4 <_start+0x70> 100dc: 400027b7 lui a5,0x40002 100e0: 00a00713 li a4,10 100e4: 00e78023 sb a4,0(a5) # 40002000 <__global_pointer$+0x3fff06c8> 100e8: 00000513 li a0,0 100ec: 00008067 ret 000100f0 <reverse>: 100f0: 00000793 li a5,0 100f4: 00000713 li a4,0 100f8: 02000613 li a2,32 100fc: 40e556b3 sra a3,a0,a4 10100: 00179793 slli a5,a5,0x1 10104: 0016f693 andi a3,a3,1 10108: 00170713 addi a4,a4,1 1010c: 00f6e7b3 or a5,a3,a5 10110: fec716e3 bne a4,a2,100fc <reverse+0xc> 10114: 00078513 mv a0,a5 10118: 00008067 ret ``` Compared the assembly code credit to [林家葦](https://hackmd.io/MFuJDtz9RkW6QF2_fxJs-g) ```clike= .data str1: .string " reversei bit is " example: .4byte 0x12345678 shift1: .4byte 0xffff0000 shift2: .4byte 0xff00ff00 shift3: .4byte 0xf0f0f0f0 shift4: .4byte 0xcccccccc shift5: .4byte 0xaaaaaaaa .text main: lw a0,example jal bitreverse lw a1,example jal print li a0,10 ecall bitreverse: lw t3,shift1 and t1,a0,t3 srli t1,t1,16 srli t3,t3,16 and t2,a0,t3 slli t2,t2,16 or t0,t1,t2 lw t3,shift2 and t1,t0,t3 srli t1,t1,8 srli t3,t3,8 and t2,t0,t3 slli t2,t2,8 or t0,t1,t2 lw t3,shift3 and t1,t0,t3 srli t1,t1,4 srli t3,t3,4 and t2,t0,t3 slli t2,t2,4 or t0,t1,t2 lw t3,shift4 and t1,t0,t3 srli t1,t1,2 srli t3,t3,2 and t2,t0,t3 slli t2,t2,2 or t0,t1,t2 lw t3,shift5 and t1,t0,t3 srli t1,t1,1 srli t3,t3,1 and t2,t0,t3 slli t2,t2,1 or t0,t1,t2 mv a0,t0 ret print: mv t0,a0 li a0,1 ecall la a1,str1 li a0,4 ecall mv a1,t0 li a0,1 ecall ret ``` Because the reverse.c I made in C language uses shift operation and and operation as much as possible, so RISC-V compiler can be converted into assembly code very close to the instruction behavior, so the resulting assembly code is also much more streamlined.