Add fmt_decimal and count_digits routines

This commit is contained in:
Wesley Moore 2025-02-27 22:21:33 +10:00
parent b3605f4a04
commit 63e79b8bb6
No known key found for this signature in database
6 changed files with 230 additions and 0 deletions

1
.gitignore vendored
View file

@ -1,2 +1,3 @@
*.o
*.elf
.gdb_history

View file

@ -45,6 +45,12 @@ tests/math_mod.elf: hex.o math.o tests/math_mod.o
tests/math_clz.elf: hex.o math.o tests/math_clz.o
$(LD) -m elf32lriscv -T link.ld $^ -o $@
tests/fmt_count_digits.elf: hex.o math.o fmt.o tests/fmt_count_digits.o
$(LD) -m elf32lriscv -T link.ld $^ -o $@
tests/fmt_decimal.elf: math.o fmt.o tests/fmt_decimal.o
$(LD) -m elf32lriscv -T link.ld $^ -o $@
tests/btohex.elf: mem.o hex.o debug.o tests/btohex.o
$(LD) -m elf32lriscv -T link.ld $^ -o $@

83
fmt.s Normal file
View file

@ -0,0 +1,83 @@
.global fmt_decimal
.global count_digits
.extern clz
.extern divmod
.section .rodata
hexchars:
.ascii "0123456789ABCDEF"
powers10:
.word 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000
.set powers10_end, .
.text
# Write decimal representation of word into buffer
# arguments:
# a0: value to format
# a1: address of buffer to write to
# buffer must have room for at least 10 bytes
# temporaries used:
# t0
# return:
# a0: length of string written to buffer
fmt_decimal:
# until the value is zero:
# divmod value by 10
# write remainder to output buffer as ASCII char
# increment string length
# subtract remainder from value
# if we can know how many digits there will be up front we can write to the buffer
# in the right order, if not the buffer will have to be reversed at the end
addi sp, sp, -16
sw ra, 12(sp)
sw s1, 8(sp)
sw s0, 4(sp)
mv s0, a0 # save value in a0 to s0
mv s1, a1 # save buffer address to s1
jal count_digits
sw a0, 0(sp) # save digit count to return at the end
addi a0, a0, -1 # a0 -= 1
add s1, s1, a0 # add the digit count to the buffer address
1:
mv a0, s0 # load dividend from s0
li a1, 10 # set divisor to 10
jal divmod # divmod value by 10, a1 = remainder
mv s0, a0 # move quotent to s0
addi a1, a1, 0x30 # turn remainder into ASCII digit
sb a1, 0(s1) # write the digit to the output buffer
addi s1, s1, -1 # decrement buffer address
bnez s0, 1b
lw a0, 0(sp) # load digit count into a0
lw s0, 4(sp)
lw s1, 8(sp)
lw ra, 12(sp)
addi sp, sp, 16
ret
# Calculate number of decimal digits input word has.
# arguments:
# a0: value to count
# return:
# a0: count of decimal digits
count_digits:
mv a2, a0 # a2 = value
la a1, powers10 # a1 = powers10
la a4, powers10_end # a4 = powers10_end
li a0, 0 # a0 = powers array offset * 4
1:
bgeu a1, a4, 2f # if a1 >= powers10_end return 10
lw a3, 0(a1) # a3 = load value from powers array
addi a0, a0, 1 # increment offset
bltu a2, a3, 3f # if a2 < a3 return
addi a1, a1, 4 # a1 = index into powers array
j 1b # loop
2:
li a0, 10
3:
ret

50
tests/fmt_count_digits.s Normal file
View file

@ -0,0 +1,50 @@
# Test for count_digits
.org 0
# Provide program starting address to linker
.global _start
.extern count_digits
.extern tohex
/* newlib system calls */
.set SYSEXIT, 93
.set SYSWRITE, 64
.section .rodata
inputs:
.word 0, 1, 2, 3, 9, 10, 100, 999, 1000, 0x8000000, 0x80000000 # TODO: negative values
inputs_end:
.section .bss
buf: .skip 11
.text
_start:
li a0, '\n'
la a1, buf
sb a0, 8(a1) # append newline to buf
la s0, inputs # init loop variables
la s1, inputs_end
loop:
lw a0, 0(s0)
jal count_digits
la a1, buf
jal tohex
li t0, SYSWRITE # "write" syscall
li a0, 1 # 1 = standard output (stdout)
la a1, buf # load address of output string
li a2, 9 # length of output string
ecall # invoke syscall to print the string
addi s0, s0, 4 # increment input pointer to the next input
bltu s0, s1, loop # if the input address is less than inputs_end, loop
li t0, SYSEXIT # "exit" syscall
la a0, 0 # Use 0 return code
ecall # invoke syscall to terminate the program

52
tests/fmt_decimal.s Normal file
View file

@ -0,0 +1,52 @@
# Test for fmt_decimal
.org 0
# Provide program starting address to linker
.global _start
.extern fmt_decimal
/* newlib system calls */
.set SYSEXIT, 93
.set SYSWRITE, 64
.section .rodata
inputs:
.word 0, 1, 2, 3, 9, 10, 100, 0x80000000
inputs_end:
.section .bss
buf: .skip 11
.text
_start:
# li a0, '\n'
# la a1, buf
# sb a0, 8(a1) # append newline to buf
la s0, inputs # init loop variables
la s1, inputs_end
loop:
lw a0, 0(s0)
la a1, buf
jal fmt_decimal
mv a2, a0 # store length of decimal string into a2 for use by SYS_write
li a0, '\n'
la a1, buf
add t0, a1, a2 # offset to end of buffer
sb a0, 0(t0) # append newline to buf
li t0, SYSWRITE # "write" syscall
li a0, 1 # 1 = standard output (stdout)
la a1, buf # load address of output string
addi a2, a2, 1 # add one to length for newline
ecall # invoke syscall to print the string
addi s0, s0, 4 # increment input pointer to next inputs
bltu s0, s1, loop # if the input address is less than inputs_end, loop
li t0, SYSEXIT # "exit" syscall
la a0, 0 # Use 0 return code
ecall # invoke syscall to terminate the program

38
tests/test_fmt.sh Normal file
View file

@ -0,0 +1,38 @@
#!/bin/sh
test_count_digits() {
result=$("${QEMU}" -B 0x80000000 -s 2k tests/fmt_count_digits.elf)
expected=$(cat << END
00000001
00000001
00000001
00000001
00000001
00000002
00000003
00000003
00000004
00000009
0000000A
END
)
test $? -eq 0 && test "$result" = "$expected"
}
test_fmt_decimal() {
result=$("${QEMU}" -B 0x80000000 -s 2k tests/fmt_decimal.elf)
expected=$(cat << END
0
1
2
3
9
10
100
2147483648
END
)
test $? -eq 0 && test "$result" = "$expected"
}