128-bit RISC-V assembler
RISC-V has a 128-bit ISA that is fairly developed, but not standardized fully yet. I am maintaining a RISC-V userspace emulator library that is written in modern C++, and the ISA is actually specified as a template parameter when instantiating the machine. So, it felt natural to explore how much work it would be to change Machine<RISCV64> to Machine<RISCV128>.
Turns out, not too much work. The ISA is a bit unclear on some things, but I think I've got the gist of it. Unfortunately for me, I didn't have any way of using this .. very useful architecture. So naturally I had to write an assembler for it too.
The assembler is in the early stages, but it supports the most basic instructions. Enough to assemble the test assembly in the root folder, for now. The assembler is easy to expand on, but it lacks helpful hints when things are not correct. It also lacks many limit checks that would produce erroneous assembly.
It is written for C++17 (although currently does not use any fancy features, so should be C++11 compatible).
While there is no ELF format for 128-bit anything, this assembler outputs an ELFCLASS128 file that is a could-be 128-bit ELF format. It is loadable by libriscv, specifically the emu128 emulator project.
The assembler currently does a one-pass through the assembly, and corrects forward labels once they appear. This means that there may be potential inefficiencies. It is, however, very fast.
.org 0x100000 .global _start _start: ;; Entry point label ;; Build a 128-bit value using t1 as temporary register set t0, t1, 0xAAAA1111222233334444555566667777 xor sp, sp add sp, t0 ;; Set stack pointer li s0, 4 xor s1, s1 repeat: add s0, -1 bne s0, s1, repeat call my_function ;; A regular function call ;; We return here after the function ends. exit: li a7, 1 ;; Syscall 1 (exit) li a0, 0x666 ;; Exit code (1st arg) ecall ;; Execute syscall jmp exit ;; Loop exit to prevent problems hello_world: ;; String label .type hello_world, @object .string "Hello World!" ;; Zt-string my_function: add sp, -32 sq a0, sp+0 li t0, 2 ;; Syscall 2 (print) sq t0, sp+16 ;; Store 128-bit value lq sp+16, a7 ;; Load 128-bit value la a0, hello_world ;; address of string ecall ;; Execute syscall lq sp+0, a0 ret
The store is pretty useless, but it shows how to do a SP-relative store.
Instructions and pseudo-instructions
- Create a new label named 'my_label' which can be jumped to.
- li [dst], constant
- Loads integer constant into register 'dst'.
- set [dst], [tmp], constant
- Loads up to 128-bit constant into 'dst' using 'tmp' as intermediate register. Uses many instructions.
- la [dst], label
- Loads address at label into register 'dst'.
- lq [dst], [reg]+offset
- Load 128-bit value from [reg]+offset memory address.
- Other sizes: lb (8-bit), lh (16-bit), lw (32-bit), ld (64-bit).
- Unsigned: lbu (8-bit), lhu (16-bit), lwu (32-bit), ldu (64-bit).
- sq [reg]+offset, [dst]
- Store 128-bit value into [reg]+offset memory address.
- Other sizes: sb (8-bit), sh (16-bit), sw (32-bit), sd (64-bit).
- call label
- Make a function call to 'label' which can be returned from. Uses PC-relative addressing.
- farcall [tmp], label
- Make a function call to a far away 'label' which can be returned from. Register 'tmp' is used to build the full address.
- Return back from any function call.
- jmp label
- Jump directly to label.
- Perform system call from register A7. Arguments in A0-A6.
- Debugger breakpoint.
- Wait for interrupts (stops the machine).
- beq [r1] [r2] label
- Jump when r1 and r2 is equal.
- bne [r1] [r2] label
- Jump when r1 and r2 is not equal.
- blt [r1] [r2] label
- Jump when r1 is less than to r2.
- bge [r1] [r2] label
- Jump when r1 is greater or equal to r2.
- bltu [r1] [r2] label
- Jump when unsigned r1 is less than unsigned r2.
- bgeu [r1] [r2] label
- Jump when unsigned r1 is greater or equal to unsigned r2.
Arithmetic and logical operations:
add, sll, slt, sltu, srl, and, or, xor [dst] [reg or imm]
- Operation on register with register or immediate.
- For 32-bit operations append w to the instruction. For example add becomes addw.
- For 64-bit operations append d to the instruction. For example xor becomes xord.
sub, mul, div, divu, rem, remu [dst] [reg]
- Subtraction, multiplication, division, unsigned division, remainder, unsigned remainder.
- Operation on register with register.
- As above, append w for 32-bit and d for 64-bit.
Complete list of available instructions.
- db, dh, dw, dd, dq [constant]
- Insert aligned constant of 8-, 16-, 32-, 64- or 128-bits into current position.
- resb, resh, resw, resd, resq [times]
- Reserve aligned 1, 2, 4, 8 or 16 bytes multiplied by constant.
- incbin "file.name"
- Inserts binary data taken from filename at current position.
Complete list of available pseudo-ops.
- .org 0x10000
- Set the base address of the binary, which now starts at 0x10000.
- .align 4
- Align memory to the given power-of-two.
- .string "String here!"
- Insert a zero-terminated string.
Complete list of available directives.