Author SHA1 Message Date
PleegWat 836acb48af Add test and fix for edgecase 2026-09-15 21:23:25 +02:00
PleegWat 016c6f7008 Add decimal for small (up to 8 digit) numbers 2026-09-15 21:11:14 +02:00
PleegWat ae5f90456c Add hex 2026-09-10 21:03:45 +02:00
PleegWat 431e00ca7e Add octal support 2026-09-10 15:33:46 +02:00
PleegWat 40e4272cdd Int to string conversion
Initially only binary output
2026-09-08 17:00:26 +02:00
ShatteredMINT e654d451a7 Merge pull request 'Globals and start.asm' (#19) from Gelthor/symphony_stdlib:graphics into main
Reviewed-on: TCShenanigans/symphony_stdlib#19
2026-09-06 20:13:05 +02:00
Gelthor 4801520424 Split framebuffer stride into stride and depth
Stride is now U16, depth is U16, and stores the bits per pixel of the
buffer.
2026-09-06 17:37:49 +01:00
Gelthor 1ba70f10cd Merge remote-tracking branch 'origin/main' into graphics 2026-09-06 16:26:55 +01:00
Gelthor ca5cff0a79 Function premble should list error codes, if any 2026-09-06 16:25:40 +01:00
Gelthor 7368044335 Global addresses now in hex and listed in order 2026-09-06 15:49:23 +01:00
Gelthor 21deca64fd Remove graphics code from this PR 2026-09-06 15:48:37 +01:00
ShatteredMINT 590732cd48 Merge pull request 'Initial version of atoi' (#15) from PleegWat/symphony_stdlib:str_to_int into main
Reviewed-on: TCShenanigans/symphony_stdlib#15
2026-09-06 16:14:57 +02:00
Gelthor b63af2f15a merge upstream 2026-09-06 12:14:49 +02:00
Gelthor 46abf8d893 Add globals and error codes
Added support for global variables.

This requires including `start.asm` as the first code. This includes
a `jmp` to 0x100 for user code, and a magic number that modules
can check to verify th the user has correctly reserved the space.

The magic number is 0xb301534c:

* This has the high bit set so is not a valid instruction

* On builds that ignore the high bit this decodes to `nor zr, zr,
  0x534c` a no-op.

* The immediate is "SL" for standardl ibrary.

Due to a bug start pads with U... 0, instead of @addr.

`start.asm` also initialises `sp` so it start just below the 64KiB LUT
memory alocation.

Added some error codes for the upcoming graphics module.
2026-09-06 11:02:27 +01:00
Gelthor 04f3f03ad5 Moved new *.asm files into src/
v
2026-09-04 16:42:35 +01:00
Gelthor 39ff477f6e Merge remote-tracking branch 'gelthor' into graphics 2026-09-04 16:09:51 +01:00
Gelthor 4d752ef67c Added initial draft of globals and errno 2026-09-04 16:07:24 +01:00
9 changed files with 435 additions and 3 deletions
+13 -1
View File
@@ -16,10 +16,22 @@ All functions in the standard library should follow the following outline:
; Arguments: <which register contains what argument>
; Result: <what is the result, and where is it stored>
; Clobbers: <list of registers that are clobbered>
; Globals: <list of globals are accessed. OPTIONAL>
; Errors: <list of any status codes returned in flags. OPTIONAL>
<label>: <;SHOULD BE INLINED>
<CODE>
```
Functions should be in the appropriate asm file, if you are unsure where functionality fits make a seperate file and ask in the pull request
Functions that are provided for convenience/reference but should be inlined in production code should be marked with `;SHOULD BE INLINED` after their label
Functions that are provided for convenience/reference but should be inlined in production code should be marked with `;SHOULD BE INLINED` after their label.
If the function returns a status code in flags, the preamble should list all it might return.
## Globals
Any function that uses global variables should document this in the preamble comment, see above.
A file/module should check on intialisation that the address `globals.MAGIC_ADDRESS` contains the 16 bit value `globals.MAGIC_VALUE`, to ensure that the user has properly included [[src/start.asm]] and reservered the global variable area.
No opinion is offered on whether modules can assume globals variables are initialised to zero.
+7 -2
View File
@@ -3,7 +3,7 @@
This is a standard library for symphony.
It is both intended as a practical toolkit to develop more complex software as well as a teaching resource.
If you just want to use the standard library [[src/stdlib.asm]] is your main header, include it after your code.
If you just want to use the standard library [[src/stdlib.asm]] is your main header, include it after your code. Some modules also require [[src/start.asm]] at the very start of the program.
If you are using it as a learning resource have a look at the [teaching folder](teaching).
@@ -20,7 +20,7 @@ If you are intersted in contributing have a look at [[CONTRIBUTING.md]]
| preserved | sp, r8 - r12 |
| scratch | flags, r1 - r7 |
| arguments | r1 - r7 |
| result | r1-r7 |
| result | flags, r1 - r7 |
| return address | r13 |
Arguments not fitting into the 7 registers should be passed on the top of the stack, meaning they should be the last values pushed before the function call.
@@ -32,9 +32,14 @@ Should a function return more values than fit into the 7 registers, the caller h
This reduces the amount of argument registers to 6 and all arguments above that shall go on the stack, the pointer to the result stack shall **always** be passed in an argument register.
This register points at the highest available address for results, with the 8th result being stored there, the 9th below it and so on.
Some functions return a success/failure status code in flags. A set low bit will indicate some error condition, the function may return more than one possible value to report different errors. A flags value of 0 is set on success. No other even values are used. This can be checked with `je` or `jne` immediatly on return to the caller. All other functions clobber flags.
### Stack
Grows downwards from 0xXXFE_0000 (so top of memory -0x1_0000).
### Globals
Global variables are stored near the bottom of RAM in the address range 0x0010..0x0100. Programs should `include src/start` as the first line before their own code and before other includes, or otherwise reserve this space, the first instruction should be a jump to user code.
### Types
#### String
Executable
+30
View File
@@ -0,0 +1,30 @@
; Error numbers should be 16 bit ODD numbers, so they can be loaded as immediates
; and can be checked with:
;
; qcall falible_function
; jne falible_ok ; Jump no error
; ; handle error
; falible_ok:
; ; Happy path
; ; ...
; OR
; qcall falible_function
; je handle_error ; Jump if error
; ; Happy path
; ; ...
; handle_error:
; ; handle error
pub const OK = 0x0000
pub const SCREEN_INVALID_MODE = 0x0001
pub const SCREEN_INVALID_WIDTH = 0x0003
pub const SCREEN_FB_TOO_SMALL = 0x0005
pub const SCREEN_OUTSIDE_FB = 0x0007
pub const INTTOSTR_BAD_BASE = 0x0101
pub const INTTOSTR_BAD_BUFFER = 0x0103
pub const MAGIC_BAD = 0x8001
; Extended error codes, these would require loading a 32 bit value.
pub const EXT_OK = 0x00000000
+30
View File
@@ -0,0 +1,30 @@
; Addresses of global variables
pub const MAGIC_ADDRESS_LONG = 0x0c ; U32 Location of the magic value
pub const MAGIC_VALUE_LONG = 0xb301534c ; U32 Full 32 bits of the magic value
pub const MAGIC_ADDRESS = 0x0e ; U16 Location of the low 16 bits of magic value
pub const MAGIC_VALUE = 0x534c ; U16 Low 16 bits of the magic value
; If not double buffering these point to the same buffer
; If double buffering they must the same size
pub const FB_DISPLAY_PTR = 0x10 ; U32 Currently displayed buffer
pub const FB_DISPLAY_STRIDE = 0x14 ; U16 Bytes in each row of the displayed buffer
pub const FB_DISPLAY_DEPTH = 0x16 ; U16 Bits per pixel of the display buffer
pub const FB_DRAW_PTR = 0x18 ; U32 Draw to this buffer
pub const FB_DRAW_STRIDE = 0x1c ; U16 Bytes in each row of the draw buffer == FB_DISPLAY_STRIDE
pub const FB_DRAW_DEPTH = 0x1e ; U16 Bits per pixel of the draw buffer == FB_DISPLAY_DEPTH
pub const FB_SIZE_BYTE = 0x20 ; U32 In bytes
pub const FB_WIDTH_PX = 0x24 ; U16 Width of screen in pixels
pub const FB_HEIGHT_PX = 0x26 ; U16 Height of screen in pixels
; Log2 of width in pixels, e.g.
; * 10 => 1024 * 768
; * 8 => 256 * 192
pub const FB_LOG_WIDTH = 0x28 ; U8
pub const FB_LOG_STRIDE = 0x29 ; U8 Log2 of FB_xxx_STRIDE in bytes
pub const FB_BYTES_PER_PIXEL = 0x2a ; U8 Specialisations should hardcode this
; Log2 of bytes per pixel
; * 0 => 8 bits per pixel
; * 2 => 32 bits per pixel
pub const FB_LOG_BPP = 0x2b ; U8 Specialisations should hardcode this
+239
View File
@@ -0,0 +1,239 @@
include errno
; Convert integer to string
; Arguments:
; r1 - Integer
; r2 - Start of buffer
; r3 - Buffer length
; r4 - Base (2, 8, 10, or 16)
; Result:
; none
; Clobbers:
; Based on specialization
pub auto:
cmp r4, 2
je bin
cmp r4, 8
je oct
cmp r4, 16
je hex
cmp r4, 10
jmp dec
mov flags, errno.INTTOSTR_BAD_BASE
jmp r13
; The output buffer is too small for this integer
badbuffer:
mov flags, errno.INTTOSTR_BAD_BUFFER
jmp r13
; All digits have been output. Null terminate and exit.
done:
store_8 [r2], zr
mov flags, errno.OK
jmp r13
; Convert integer to binary string
; Arguments:
; r1 - Integer
; r2 - Start of buffer
; r3 - Buffer length
; Result:
; none
; Clobbers:
; flags
; r4 - counter
; r5 - character
pub bin:
mov r4, 31
cmp r2, 2
jl badbuffer
bin_find_one:
lsr r5, r1, r4 ; Select bit and advance counter
and r5, r5, 1
sub r4, r4, 1
cmp r4, zr ; Start printing if last bit ...
jl bin_print
cmp r5, zr ; .. or if nonzero
je bin_find_one
add flags, r4, 3 ; Check buffer size
cmp flags, r3
ja badbuffer
bin_print:
add r5, r5, 0x30 ; '0' ; Add digit to buffer
store_8 [r2], r5
add r2, r2, 1
cmp r4, zr ; Done if last bit
jl done
lsr r5, r1, r4 ; Select bit and advance counter
and r5, r5, 1
sub r4, r4, 1
jmp bin_print
; Convert integer to octal string
; Arguments:
; r1 - Integer
; r2 - Start of buffer
; r3 - Buffer length
; Result:
; none
; Clobbers:
; flags
; r4 - input counter
; r5 - character
; r6 - output size counter
pub oct:
mov r4, 30
mov r6, 12
oct_find_one:
lsr r5, r1, r4 ; Select bit and advance counter
and r5, r5, 7
sub r4, r4, 3
sub r6, r6, 1
cmp r4, zr ; Start printing if last bit ...
jl oct_print
cmp r5, zr ; .. or if nonzero
je oct_find_one
cmp r6, r3 ; Check buffer size
ja badbuffer
oct_print:
add r5, r5, 0x30 ; '0' ; Add digit to buffer
store_8 [r2], r5
add r2, r2, 1
cmp r4, zr ; Done if last bit
jl done
lsr r5, r1, r4 ; Select bit and advance counter
and r5, r5, 7
sub r4, r4, 3
jmp oct_print
; Convert integer to hexadecimal string
; Arguments:
; r1 - Integer
; r2 - Start of buffer
; r3 - Buffer length
; Result:
; none
; Clobbers:
; flags
; r4 - input counter
; r5 - character
pub hex:
mov r4, 28
hex_find_one:
lsr r5, r1, r4 ; Select bit and advance counter
and r5, r5, 0xF
sub r4, r4, 4
cmp r4, zr ; Start printing if last bit ...
jl hex_print
cmp r5, zr ; .. or if nonzero
je hex_find_one
lsr flags, r5, 2 ; Check buffer size
add flags, flags, 3
cmp flags, r3
ja badbuffer
hex_print:
add r5, r5, 0x30 ; '0' ; Add digit to buffer
cmp r5, 0x39 ; '9'
jle hex_no_letter
add r5, r5, 0x07 ; 'A' - '0' - 10
hex_no_letter:
store_8 [r2], r5
add r2, r2, 1
cmp r4, zr ; Done if last bit
jl done
lsr r5, r1, r4 ; Select bit and advance counter
and r5, r5, 0xF
sub r4, r4, 4
jmp hex_print
; Convert integer to decimal string
; Arguments:
; r1 - Integer
; r2 - Start of buffer
; r3 - Buffer length
; Result:
; none
; Clobbers:
; flags
; r4, r5
pub dec:
mov r4, 0x5F5
lsl r4, r4, 16
add r4, r4, 0xE100 ; 100 million; 8 digits
cmp r1, r4
jl dec_small
; TODO large
; Small numbers have at least most 8 digits, thus their BCD form fits in
; one register. Bit 26 is the highest which can be set.
;
; Utilizes double dabble
; r4, r5 are loop counters
;
; for( r4 = 24 ; r4 > 0 ; r4-- )
; for( r5 = r4 ; r5 <= 28 ; r5+= 4 )
; if( ((r1 >> r5) & 0xF) >= 5 )
; r1 += 3 << r5
dec_small:
mov r4, 24
dec_small_dabble_outer:
mov r5, r4
dec_small_dabble_inner:
lsr flags, r1, r5
and flags, flags, 0xF
cmp flags, 5
jl dec_small_dabble_noinc
mov flags, 3
lsl flags, flags, r5
add r1, r1, flags
dec_small_dabble_noinc:
add r5, r5, 4
cmp r5, 28
jle dec_small_dabble_inner
sub r4, r4, 1
cmp r4, zr
jg dec_small_dabble_outer
; Print the BCD in r1
; r4 is a counter
; r5 is the output character
mov r4, 28
dec_find_one:
lsr r5, r1, r4 ; Select bit and advance counter
and r5, r5, 0xF
sub r4, r4, 4
cmp r4, zr ; Start printing if last bit ...
jl dec_print
cmp r5, zr ; .. or if nonzero
je dec_find_one
lsr flags, r5, 2 ; Check buffer size
add flags, flags, 3
cmp flags, r3
ja badbuffer
dec_print:
add r5, r5, 0x30 ; '0' ; Add digit to buffer
store_8 [r2], r5
add r2, r2, 1
cmp r4, zr ; Done if last bit
jl done
lsr r5, r1, r4 ; Select bit and advance counter
and r5, r5, 0xF
sub r4, r4, 4
jmp dec_print
Executable
+19
View File
@@ -0,0 +1,19 @@
;@0 ; Reserve space for globals
; First initialise the stack pointer
nor sp, zr, 0xffff
; Jump over the globals to user code
jmp 0x100
U32 0 ; 4 bytes
; MAGIC_VALUE
;@0x0c
U32 0xb301534c
; Pad with zeroes since the @addr feature is currently broken.
; * https://discord.com/channels/828292123936948244/1545010596246847568
;
U1920 0 ; 240 bytes
;@0x100 ; start of user code
+2
View File
@@ -3,7 +3,9 @@ pub include imath
pub include array
pub include console
pub include mem
pub include string
pub include string_to_int
pub include int_to_string
; Needs to be last!
pub include LUTs
+33
View File
@@ -0,0 +1,33 @@
; Compare two strings
; Arguments:
; r1 - Pointer to string 1
; r2 - Pointer to string 2
; Result:
; r1:
; - `0` if both segments are equal.
; - `<0` if the first segment is less than the second segment.
; - `>0` if the first segment is greater than the second segment.
; flags - Compare result
; Clobbers:
; r3 - last byte of r1 tested
; r4 - last byte of r2 tested
pub compare:
load_8 r3, [r1]
add r1, r1, 1
load_8 r4, [r2]
add r2, r2, 1
cmp zr, r3 ; End of string 1
je done
cmp zr, r4 ; End of string 2
je done
cmp r3, r4
je compare ; Loop, tail recursion, what's the difference
done:
cmp r3, r4
; `flags` is the comparison result in the format of `cmp`. Convert it to the desired format.
; 00 => 0x40000000 > 0
; 01 => 0x00000000 = 0
; 10 => 0xC0000000 < 0
xor r1, flags, 1
lsl r1, r1, 30
jmp r13
+62
View File
@@ -0,0 +1,62 @@
; test harness for int_to_string
; Registers:
; r8 - test address
; r9 - test method
; r12 - end of tests address
li r8, tests
li r12, end_of_tests
next_test:
; Set up arguments and run test
load_32 r1, [r8] ; Input value
li r2, scratch ; Output buffer
mov r3, 64 ; Output buffer size
add r4, r8, 4 ; Base
load_8 r4, [r4]
mov r13, itoa_test_ret ; return address
jmp stdlib.int_to_string.auto
itoa_test_ret:
nop ; For breakpoint after itoa but before r1 clobber
; Strcmp to check results
add r1, r8, 5 ; expected output
li r2, scratch ; actual output
mov r13, itoa_strcmp_ret
jmp stdlib.string.compare
itoa_strcmp_ret:
; Verify results
halt.ne
; Next test
add r8, r8, 0x40
cmp r8, r12
jl next_test
; Done
halt
include ../src/stdlib
@0x20000
tests:
; inlen is the number of input bytes the function is expected to consume
; addr int base result
@0x20000 U32 0b0 U8 2 "0\0"
@0x20040 U32 0xFFFFFFFF U8 2 "11111111111111111111111111111111\0"
@0x20080 U32 0b101010 U8 2 "101010\0"
@0x200c0 U32 0o0 U8 8 "0\0"
@0x20100 U32 0o640 U8 8 "640\0"
@0x20140 U32 0xFFFFFFFF U8 8 "37777777777\0"
@0x20180 U32 0x0 U8 16 "0\0"
@0x201c0 U32 0x32fF6 U8 16 "32FF6\0"
@0x20200 U32 0xdeadbeef U8 16 "DEADBEEF\0"
@0x20240 U32 0xFFFFFFFF U8 16 "FFFFFFFF\0"
@0x20280 U32 0 U8 10 "0\0"
@0x202c0 U32 42069 U8 10 "42069\0"
@0x20300 U32 12345678 U8 10 "12345678\0"
@0x20340 U32 99999999 U8 10 "99999999\0"
@0x20380
end_of_tests:
scratch: U1024 0