1 Commits
Author SHA1 Message Date
PleegWat 160a66c1bf Add ISA definitions for symphony
Adds both a 'stock' ISA (copied from the game) and one modified for test
code
2026-09-05 15:34:59 +02:00
10 changed files with 16 additions and 358 deletions
+1 -13
View File
@@ -16,22 +16,10 @@ All functions in the standard library should follow the following outline:
; Arguments: <which register contains what argument> ; Arguments: <which register contains what argument>
; Result: <what is the result, and where is it stored> ; Result: <what is the result, and where is it stored>
; Clobbers: <list of registers that are clobbered> ; 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> <label>: <;SHOULD BE INLINED>
<CODE> <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 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.
+2 -7
View File
@@ -3,7 +3,7 @@
This is a standard library for symphony. 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. 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. Some modules also require [[src/start.asm]] at the very start of the program. If you just want to use the standard library [[src/stdlib.asm]] is your main header, include it after your code.
If you are using it as a learning resource have a look at the [teaching folder](teaching). 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 | | preserved | sp, r8 - r12 |
| scratch | flags, r1 - r7 | | scratch | flags, r1 - r7 |
| arguments | r1 - r7 | | arguments | r1 - r7 |
| result | flags, r1 - r7 | | result | r1-r7 |
| return address | r13 | | 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. 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,14 +32,9 @@ 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 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. 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 ### Stack
Grows downwards from 0xXXFE_0000 (so top of memory -0x1_0000). 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 ### Types
#### String #### String
+1 -1
View File
@@ -16,7 +16,7 @@ Performs a jump-and-link function call, with label size up to 32 bits.
_HARDWARE:_ Store PC+4 in the result register when in jump mode. _HARDWARE:_ Store PC+4 in the result register when in jump mode.
## `li %a(register) %b:U32(immediate | label)` ## `li %a(register) %b:U32(immediate | label)
Loads an immediate up to 32 bits long Loads an immediate up to 32 bits long
_HARDWARE:_ No requirements _HARDWARE:_ No requirements
+12 -15
View File
@@ -332,13 +332,12 @@ ret
li %r(register), %a:S33(immediate | label) li %r(register), %a:S33(immediate | label)
; assert(0 - 0x80000000 <= %a, "%a is too low") ; assert(0 - 0x80000000 <= %a, "%a is too low")
; assert(%a <= 0xffffffff, "%a is too high") ; assert(%a <= 0xffffffff, "%a is too high")
%s = trailing_zeros(%a) %nota = ~%a
%sa = %a >> %s when 0x0000 <= %a && %a <= 0xffff: 00110001 rrrr0000 %a[15:0] ; First 64k
%nota = %a ^ 0xffffffff when %nota <= 0xffff : 00110001 rrrr0000 %nota[15:0] 00100011 rrrr0000 0000rrrr 00000000 ; Last 64k
when %a <=u 0xffff : 00110001 %r[3:0]0000 %a[15:0] ; First 64k when !(%a & 0x0f) && %a < 0xffff0: 00110001 rrrr0000 %a[19:4] 00100111 rrrrrrrr 00000000 00000100 ; 16-byte aligned
when %nota <=u 0xffff: 00110001 %r[3:0]0000 %nota[15:0] 00100011 %r[3:0]0000 0000%r[3:0] 00000000 ; Last 64k when !(%a & 0x03) && %a < 0x3fffc: 00110001 rrrr0000 %a[17:2] 00100111 rrrrrrrr 00000000 00000010 ; 4-byte aligned
when %sa <=u 0xffff : 00110001 %r[3:0]0000 %sa[15:0] 00110111 %r[3:0]%r[3:0] 00000000 %s[7:0] ; Aligned 00110001 rrrr0000 %a[31:16] 00100111 rrrrrrrr 00000000 00010000 00110001 rrrrrrrr %a[15:0] ; Any 32-bit
00110001 %r[3:0]0000 %a[31:16] 00110111 %r[3:0]%r[3:0] 00000000 00010000 00110001 %r[3:0]%r[3:0] %a[15:0] ; Any 32-bit
# Load 32-bit immediate %a into %r # Load 32-bit immediate %a into %r
qcall %a:U32(label) qcall %a:U32(label)
@@ -346,16 +345,14 @@ qcall %a:U32(label)
; assert(%a <= 0xffffffff, "%a is too high") ; assert(%a <= 0xffffffff, "%a is too high")
%r = 13 %r = 13
%f = 15 %f = 15
%s = trailing_zeros(%a) %nota = ~%a
%sa = %a >> %s when 0x0000 <= %a && %a <= 0xffff: 01011000 rrrrffff %a[15:0] ; First 64k
%nota = %a ^ 0xffffffff when %nota <= 0xffff : 00110001 rrrr0000 %nota[15:0] 00100011 rrrr0000 0000rrrr 00000000 01001000 rrrrffff 0000rrrr 00000000 ; Last 64k
when %a <=u 0xffff : 01011000 %r[3:0]ffff %a[15:0] ; First 64k when !(%a & 0x0f) && %a < 0xffff0: 00110001 rrrr0000 %a[19:4] 00100111 rrrrrrrr 00000000 00000100 01001000 rrrrffff 0000rrrr 00000000 ; 16-byte aligned
when %nota <=u 0xffff: 00110001 %r[3:0]0000 %nota[15:0] 00100011 %r[3:0]0000 0000%r[3:0] 00000000 01001000 %r[3:0]ffff 0000%r[3:0] 00000000 ; Last 64k when !(%a & 0x03) && %a < 0x3fffc: 00110001 rrrr0000 %a[17:2] 00100111 rrrrrrrr 00000000 00000010 01001000 rrrrffff 0000rrrr 00000000 ; 4-byte aligned
when %sa <=u 0xffff : 00110001 %r[3:0]0000 %sa[15:0] 00110111 %r[3:0]%r[3:0] 00000000 %s[7:0] 01001000 %r[3:0]ffff 0000%r[3:0] 00000000 ; Aligned 00110001 rrrr0000 %a[31:16] 00100111 rrrrrrrr 00000000 00010000 00110001 rrrrrrrr %a[15:0] 01001000 rrrrffff 0000rrrr 00000000 ; Any 32-bit
00110001 %r[3:0]0000 %a[31:16] 00110111 %r[3:0]%r[3:0] 00000000 00010000 00110001 %r[3:0]%r[3:0] %a[15:0] 01001000 %r[3:0]ffff 0000%r[3:0] 00000000 ; Any 32-bit
# Jump and link to 32-bit label # Jump and link to 32-bit label
halt%c(condition) halt%c(condition)
1000cccc 00001111 00000000 00000000 1000cccc 00001111 00000000 00000000
# Halt on condition %c # Halt on condition %c
-27
View File
@@ -1,27 +0,0 @@
; 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 MAGIC_BAD = 0x8001
; Extended error codes, these would require loading a 32 bit value.
pub const EXT_OK = 0x00000000
-30
View File
@@ -1,30 +0,0 @@
; 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
-19
View File
@@ -1,19 +0,0 @@
;@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
-1
View File
@@ -3,7 +3,6 @@ pub include imath
pub include array pub include array
pub include console pub include console
pub include mem pub include mem
pub include string_to_int
; Needs to be last! ; Needs to be last!
pub include LUTs pub include LUTs
-188
View File
@@ -1,188 +0,0 @@
; Internal register assignments:
; r1 - Partially parsed output integer
; r2 - Parsing position
; r3 - Character being parsed
; r4 - Set to -1 if the input is negative
; Convert string to integer (akin to libc atoi(), strotoi())
; Arguments:
; r1 - Pointer to string
; Result:
; r1 - Parsed integer
; r2 - Pointer to first rejected input byte
; Clobbers:
; flags
; r3 - last character read
; r4 - Negative marker
; Note: Unless the input is "0", tail-calls into a base-specific specialization.
pub auto:
mov r2, r1
mov r1, 0
mov r4, 0
load_8 r3, [r2]
cmp r3, 0x2D ; '-'
jne auto_positive
sub r4, zr, 1 ; Set r4 to -1
add r2, r2, 1
auto_positive:
load_16 r3, [r2] ; 2-byte prefix "0b", "0o", "0x", etc.
or r3, r3, 0x20 ; 2nd char to lower case
add r2, r2, 2
cmp r3, 0x3062 ; "0b"
je bin_loop
cmp r3, 0x306F ; "0o"
je oct_loop
cmp r3, 0x3078 ; "0x"
je hex_loop
sub r2, r2, 2 ; no matching prefix, move pointer back
jmp dec_loop
; Epilogue
done:
add r1, r1, r4 ; If r4 is -1, negate r1. Else it's 0 and no effect.
xor r1, r1, r4
mov flags, 0 ; No error
jmp r13
; Convert decimal string to integer
; Arguments:
; r1 - Pointer to string
; Result:
; r1 - Parsed integer
; r2 - Pointer to first rejected input byte
; Clobbers:
; flags
; r3 - last character read
; r4 - Negative marker
pub dec:
mov r2, r1
mov r1, 0
mov r4, 0
load_8 r3, [r2]
cmp r3, 0x2D ; '-'
jne dec_positive
sub r4, zr, 1 ; Set r4 to -1
add r2, r2, 1
dec_loop:
load_8 r3, [r2]
dec_positive:
sub r3, r3, 0x30 ; '0'
cmp r3, 9
ja done
add r2, r2, 1
lsl flags, r1, 2 ; Use flags to help multiply by 10
add r1, r1, flags
lsl r1, r1, 1
add r1, r1, r3
jmp dec_loop
; Convert binary string to integer
; Arguments:
; r1 - Pointer to string
; Result:
; r1 - Parsed integer
; r2 - Pointer to first rejected input byte
; Clobbers:
; flags
; r3 - last character read
; r4 - Negative marker
pub bin:
mov r2, r1
mov r1, 0
mov r4, 0
load_8 r3, [r2]
cmp r3, 0x2D ; '-'
jne bin_positive
sub r4, zr, 1 ; Set r4 to -1
add r2, r2, 1
bin_positive:
load_16 r3, [r2] ; check for prefix
or r3, r3, 0x20 ; 2nd char to lower case
cmp r3, 0x3062 ; "0b"
jne bin_loop
add r2, r2, 2
bin_loop:
load_8 r3, [r2]
sub r3, r3, 0x30 ; '0'
cmp r3, 1
ja done
add r2, r2, 1
lsl r1, r1, 1
add r1, r1, r3
jmp bin_loop
; Convert octal string to integer
; Arguments:
; r1 - Pointer to string
; Result:
; r1 - Parsed integer
; r2 - Pointer to first rejected input byte
; Clobbers:
; flags
; r3 - last character read
; r4 - Negative marker
pub oct:
mov r2, r1
mov r1, 0
mov r4, 0
load_8 r3, [r2]
cmp r3, 0x2D ; '-'
jne oct_positive
sub r4, zr, 1 ; Set r4 to -1
add r2, r2, 1
oct_positive:
load_16 r3, [r2] ; check for prefix
or r3, r3, 0x20 ; 2nd char to lower case
cmp r3, 0x306F ; "0o"
jne oct_loop
add r2, r2, 2
oct_loop:
load_8 r3, [r2]
sub r3, r3, 0x30 ; '0'
cmp r3, 7
ja done
add r2, r2, 1
lsl r1, r1, 3
add r1, r1, r3
jmp oct_loop
; Convert hexadecimal string to integer
; Arguments:
; r1 - Pointer to string
; Result:
; r1 - Parsed integer
; r2 - Pointer to first rejected input byte
; Clobbers:
; flags
; r3 - last character read
; r4 - Negative marker
pub hex:
mov r2, r1
mov r1, 0
mov r4, 0
load_8 r3, [r2]
cmp r3, 0x2D ; '-'
jne hex_positive
sub r4, zr, 1 ; Set r4 to -1
add r2, r2, 1
hex_positive:
load_16 r3, [r2] ; check for prefix
or r3, r3, 0x20 ; 2nd char to lower case
cmp r3, 0x3078 ; "0x"
jne hex_loop
add r2, r2, 2
hex_loop:
load_8 r3, [r2]
sub r3, r3, 0x30 ; '0'
cmp r3, 10
jb hex_add
sub r3, r3, 0x11 ; 'A' - '0'
and r3, r3, 0xdf ; to lower case
cmp r3, 5 ; 0-5: 6 letters
ja done
add r3, r3, 10 ; Adjust for digits below
hex_add:
add r2, r2, 1
lsl r1, r1, 4
add r1, r1, r3
jmp hex_loop
-57
View File
@@ -1,57 +0,0 @@
; test harness for string_to_int
; Registers:
; r8 - test address
; r9 - expected result
; r10 - actual length
; r11 - expected length
li r8, tests
li r12, end_of_tests
next_test:
; Set up arguments and run test
add r1, r8, 5 ; Start of test string
mov r13, atoi_test_ret
jmp stdlib.string_to_int.auto
atoi_test_ret:
; Load reference data
load_32 r9, [r8] ; Expected result
sub r10, r2, r8
sub r10, r10, 5 ; Actual string length
add r11, r8, 4 ; Address of expected length
load_8 r11, [r11] ; Expected length
; Verify results
cmp r1, r9
incorrect_result: jne incorrect_result
cmp r10, r11
incorrect_length: jne incorrect_length
; Next test
add r8, r8, 0x10
cmp r8, r12
jl next_test
; Done
success: jmp success
include ../src/stdlib
@0x20000
tests:
; inlen is the number of input bytes the function is expected to consume
; addr result inlen instr
@0x20000 U32 0 U8 0 "\0"
@0x20010 U32 0 U8 1 "0\0"
@0x20020 U32 1 U8 1 "1\0"
@0x20030 U32 2 U8 1 "2:\0"
@0x20040 U32 42 U8 2 "42\0"
@0x20050 U32 67 U8 2 "67lol\0"
@0x20060 U32 0x69a U8 5 "0x69a@\0"
@0x20070 U32 0x4B4 U8 5 "0x4B4g\0"
@0x20080 U32 0o23 U8 4 "0o239\0"
@0x20090 U32 0b1011 U8 6 "0b1011\0"
@0x200a0 U32 0b10001 U8 7 "0b100012\0"
@0x200b0 U32 0xFFFFFFEB U8 3 "-21\0" ; Yuk
@0x200c0
end_of_tests: