From 4cf7c54c0fb7b70f83ccee3089946b06d2ce5f7a Mon Sep 17 00:00:00 2001 From: unknown <71151164+ZERICO2005@users.noreply.github.com> Date: Sun, 28 Sep 2025 20:33:57 -0600 Subject: [PATCH] added mulhu CRT routines --- src/crt/i48mulhu.src | 70 ++++++ src/crt/imulhu.src | 145 +++++++++++ src/crt/llmulhu.src | 101 ++++++++ src/crt/lmulhu.src | 42 ++++ src/crt/smulhu.src | 36 +++ test/standalone/mulhu/autotest.json | 40 +++ test/standalone/mulhu/makefile | 19 ++ test/standalone/mulhu/src/crt_wrap.asm | 111 +++++++++ test/standalone/mulhu/src/main.c | 330 +++++++++++++++++++++++++ 9 files changed, 894 insertions(+) create mode 100644 src/crt/i48mulhu.src create mode 100644 src/crt/imulhu.src create mode 100644 src/crt/llmulhu.src create mode 100644 src/crt/lmulhu.src create mode 100644 src/crt/smulhu.src create mode 100644 test/standalone/mulhu/autotest.json create mode 100644 test/standalone/mulhu/makefile create mode 100644 test/standalone/mulhu/src/crt_wrap.asm create mode 100644 test/standalone/mulhu/src/main.c diff --git a/src/crt/i48mulhu.src b/src/crt/i48mulhu.src new file mode 100644 index 000000000..0332105ff --- /dev/null +++ b/src/crt/i48mulhu.src @@ -0,0 +1,70 @@ + assume adl=1 + + section .text + + public __i48mulhu + +; UDE:UHL = ((uint96_t)UDE:UHL * (uint96_t)UIY:UBC) >> 48 +__i48mulhu: +; CC: 88 bytes +; minimum: 87F + 39R + 39W + 2 +; maximum: 89F + 39R + 39W + 3 +; including __i48mulu: +; minimum: 895F + 243R + 179W + 342 +; maximum: 897F + 243R + 179W + 343 + push ix + push iy + push bc + ld ix, 0 + lea iy, ix + add ix, sp + push de + push hl + + ; x_lo * y_lo + lea de, iy + call __i48mulu + push de ; UHL * UBC (low carry) + + ; x_hi * y_lo + lea de, iy + ld hl, (ix - 3) + call __i48mulu + push de ; hi24 + push hl ; lo24 + + ; x_lo * y_hi + lea de, iy + ld bc, (ix + 3) + ld hl, (ix - 6) + call __i48mulu + pop bc ; lo24 + add hl, bc + ex de, hl + pop bc ; hi24 + adc hl, bc + + pop bc ; UHL * UBC (low carry) + ex de, hl + add hl, bc + jr nc, .no_low_carry + inc de +.no_low_carry: + push de ; high carry + + ; x_hi * y_hi + lea de, iy + ld bc, (ix + 3) + ld hl, (ix - 3) + call __i48mulu + pop bc ; high carry + add hl, bc + ld sp, ix + pop bc + pop iy + pop ix + ret nc ; no high carry + inc de + ret + + extern __i48mulu diff --git a/src/crt/imulhu.src b/src/crt/imulhu.src new file mode 100644 index 000000000..d13aa9eeb --- /dev/null +++ b/src/crt/imulhu.src @@ -0,0 +1,145 @@ + assume adl=1 + + section .text + + public __imulhu + +; UHL = ((uint48_t)UHL * (uint48_t)UBC) >> 24 +__imulhu: +; TODO: Optimize this routine as this is mostly just a copy paste of __i48mulu with some stuff removed. +; +; CC: 118*r(PC)+39*r(SPL)+38*w(SPL)+37 +; CC: 117 bytes | 118F + 39R + 38W + 37 + push de + ; backup af + push af + push ix + ld ix, 0 + add ix, sp + + ; On stack to get upper byte when needed + push de ; de will also be used to perform the actual multiplication + push hl + push iy + push bc + + ; bc = a[0], a[1] + ld a, l ; a = b[0] + ld iy, (ix - 5) ; iy = b[1], b[2] + + ; or a, a ; carry is already cleared + sbc hl, hl + push hl ; upper bytes of sum at -15 + ; Stack Use: + ; ix-1 : deu b[5] + ; ix-2 : d b[4] + ; ix-3 : e b[3] + ; ix-4 : hlu b[2] + ; ix-5 : h b[1] + ; ix-6 : l b[0] + ; ix-7 : iyu a[5] + ; ix-8 : iyh a[4] + ; ix-9 : iyl a[3] + ; ix-10 : bcu a[2] + ; ix-11 : b a[1] + ; ix-12 : c a[0] + ; ix-13 : sum[5] + ; ix-14 : sum[4] + ; ix-15 : sum[3] + ; ix-16 : sum[2] + ; ix-17 : sum[1] + ; ix-18 : sum[0] + + ; ====================================================================== + ; sum[0-1] + + ; a[0]*b[0] + ld d, c ; d = a[0] + ld e, a ; e = b[0] + mlt de + push de ; lower bytes of sum at -18 + + ; ====================================================================== + ; sum[1-2] + ld l, d ; hl will store current partial sum + + ; a[1]*b[0] + ld d, b ; d = a[1] + ld e, a ; e = b[0] + mlt de + add hl, de + + ; a[0]*b[1] + ld d, c ; d = a[0] + ld e, iyl ; e = b[1] + mlt de + add hl, de + + ld (ix - 17), hl + + ; ====================================================================== + ; sum[2-3] + ld hl, (ix - 16) ; hl will store current partial sum + + ; a[0]*b[2] + ld d, c ; d = a[0] + ld e, iyh ; e = b[2] + mlt de + add hl, de + + ; a[1]*b[1] + ld d, b ; d = a[1] + ld e, iyl ; e = b[1] + mlt de + add hl, de + + ; a[2]*b[0] + ld d, (ix - 10) ; d = a[2] + ld e, a ; e = b[0] + mlt de + add hl, de + + ld (ix - 16), hl + + ; ====================================================================== + ; sum[3-4] + ld hl, (ix - 15) ; hl will store current partial sum + + ; a[1]*b[2] + ld d, b ; d = a[1] + ld e, iyh ; e = b[2] + mlt de + add hl, de + + ; a[2]*b[1] + ld d, (ix - 10) ; d = a[2] + ld e, iyl ; e = b[1] + mlt de + add hl, de + + ld (ix - 15), hl + + ; ====================================================================== + ; sum[4-5] + ld hl, (ix - 14) ; hl will store current partial sum + + ; a[2]*b[2] + ld d, (ix - 10) ; d = a[2] + ld e, iyh ; e = b[2] + mlt de + add hl, de + + ld (ix - 14), l + ld (ix - 13), h + + ; clean up stack and restore registers + pop de + pop hl + pop bc + pop iy + + ld sp, ix + pop ix + pop af + pop de + ret diff --git a/src/crt/llmulhu.src b/src/crt/llmulhu.src new file mode 100644 index 000000000..d3033419d --- /dev/null +++ b/src/crt/llmulhu.src @@ -0,0 +1,101 @@ + assume adl=1 + + section .text + + public __llmulhu + +; BC:UDE:UHL = ((uint128_t)BC:UDE:UHL * (uint128_t)(SP64)) >> 64 +__llmulhu: + push ix + push iy + ld ix, -36 + add ix, sp + ld sp, ix + lea ix, ix + 36 + + ld (ix - 3), bc + ld (ix - 6), de + ld (ix - 9), hl + + ld bc, 0 + ld (ix - 13), bc + ld (ix - 30), bc + ld c, (ix + 12) + ld (ix - 33), bc + ld iy, (ix + 9) + ld (ix - 36), iy + + ; x_lo * y_lo + ld c, b + ld d, b + inc de + dec.s de + call __llmulu + ld (ix - 16), bc + ld (ix - 19), de + ld bc, 0 + ld (ix - 14), b + + ; x_hi * y_lo + inc.s de + ld d, b + ld e, (ix - 2) + ld hl, (ix - 5) + call __llmulu + ld (ix - 21), bc + ld (ix - 24), de + ld (ix - 27), hl + + ld c, (ix + 16) + ld (ix - 33), c + ld iy, (ix + 13) + ld (ix - 36), iy + + ; x_lo * y_hi + ld bc, 0 + inc.s de + ld d, b + ld e, (ix - 6) + ld hl, (ix - 9) + call __llmulu + lea iy, ix - 27 + call __llmulhu_add + lea iy, ix - 18 + call __llmulhu_add + ld (ix - 16), bc + ld (ix - 19), de + ld bc, 0 + ld (ix - 14), b + + ; x_hi * y_hi + inc.s de + ld d, b + ld e, (ix - 2) + ld hl, (ix - 5) + call __llmulu + lea iy, ix - 18 + call __llmulhu_add + ld sp, ix + pop iy + pop ix + ret + +__llmulhu_add: + ; similar to __lladd, except iy points to the stack and is destroyed + push bc + ld bc, (iy + 0) + add hl, bc + ex de, hl + ld bc, (iy + 3) + adc hl, bc + ex de, hl + pop bc + jr nc, .no_carry48 + inc bc +.no_carry48: + ld iy, (iy + 6) + add iy, bc + lea bc, iy + ret + + extern __llmulu diff --git a/src/crt/lmulhu.src b/src/crt/lmulhu.src new file mode 100644 index 000000000..8eacbc183 --- /dev/null +++ b/src/crt/lmulhu.src @@ -0,0 +1,42 @@ + assume adl=1 + + section .text + + public __lmulhu + +; E:UHL = ((uint64_t)E:UHL * (uint64_t)A:UBC) >> 32 +__lmulhu: + push iy + push de + ld iy, 0 + push iy + ld iyl, a + push iy + push bc + ld iyl, iyh ; ld iy, 0 + lea bc, iy + inc de + dec.s de + ld d, b + call __llmulu + ; E = B + ; UHL = C + ; H = UDE + ; L = D + add iy, sp + push de + ld e, (iy - 1) ; H = UDE + ld (iy - 1), c ; UHL = C + pop hl ; UHL = C + ld h, e ; H = UDE + ld l, d ; L = D + ld iyl, b ; E = B + pop bc + pop de ; reset SP + pop de ; reset SP + pop de + ld e, iyl ; E = B + pop iy + ret + + extern __llmulu diff --git a/src/crt/smulhu.src b/src/crt/smulhu.src new file mode 100644 index 000000000..4b920047c --- /dev/null +++ b/src/crt/smulhu.src @@ -0,0 +1,36 @@ + assume adl=1 + + section .text + + public __smulhu + +; HL = ((uint32_t)HL * (uint32_t)BC) >> 16 +__smulhu: +; CC: 32 bytes +; ADL = 1: 33F + 12R + 9W + 17 +; ADL = 0: 33F + 8R + 6W + 17 + push af + push de + push bc + ld d, l + ld e, c + mlt de ; L * C + ld a, d + ld d, l + ld e, b + mlt de ; L * B + ld l, b + ld b, h + mlt bc ; H * C + mlt hl ; H * B + add a, c + ld c, b + ld b, 0 + adc hl, bc + add a, e + ld c, d + adc hl, bc ; result is [0, $FFFE] + pop bc + pop de + pop af + ret diff --git a/test/standalone/mulhu/autotest.json b/test/standalone/mulhu/autotest.json new file mode 100644 index 000000000..be5eeed3c --- /dev/null +++ b/test/standalone/mulhu/autotest.json @@ -0,0 +1,40 @@ +{ + "transfer_files": [ + "bin/DEMO.8xp" + ], + "target": { + "name": "DEMO", + "isASM": true + }, + "sequence": [ + "action|launch", + "delay|1000", + "hashWait|1", + "key|enter", + "delay|300", + "hashWait|2" + ], + "hashes": { + "1": { + "description": "All tests passed", + "timeout": 5000, + "start": "vram_start", + "size": "vram_16_size", + "expected_CRCs": [ + "38E2AD5A" + ] + }, + "2": { + "description": "Exit", + "start": "vram_start", + "size": "vram_16_size", + "expected_CRCs": [ + "FFAF89BA", + "101734A5", + "9DA19F44", + "A32840C8", + "349F4775" + ] + } + } +} diff --git a/test/standalone/mulhu/makefile b/test/standalone/mulhu/makefile new file mode 100644 index 000000000..41673220d --- /dev/null +++ b/test/standalone/mulhu/makefile @@ -0,0 +1,19 @@ +# ---------------------------- +# Makefile Options +# ---------------------------- + +NAME = DEMO +ICON = icon.png +DESCRIPTION = "CE C Toolchain Demo" +COMPRESSED = NO +ARCHIVED = NO + +CFLAGS = -Wall -Wextra -Wshadow -Wconversion -Wformat=2 -Wno-sign-conversion -Oz +CXXFLAGS = -Wall -Wextra -Wshadow -Wconversion -Wformat=2 -Wno-sign-conversion -Oz + +PREFER_OS_LIBC = NO +PREFER_OS_CRT = NO + +# ---------------------------- + +include $(shell cedev-config --makefile) diff --git a/test/standalone/mulhu/src/crt_wrap.asm b/test/standalone/mulhu/src/crt_wrap.asm new file mode 100644 index 000000000..162c9cd8e --- /dev/null +++ b/test/standalone/mulhu/src/crt_wrap.asm @@ -0,0 +1,111 @@ + assume adl=1 + + section .data + + public _prev_reg +_prev_reg: + ; L H U E D U C B U A Y I U X I U + db 0,0,0, 0,0,0, 0,0,0, 0, 0,0,0, 0,0,0 + + public _next_reg +_next_reg: + ; L H U E D U C B U A Y I U X I U + db 0,0,0, 0,0,0, 0,0,0, 0, 0,0,0, 0,0,0 + + section .text + + private _set_prev_reg +_set_prev_reg: + ld (_prev_reg + 0), hl + ld (_prev_reg + 3), de + ld (_prev_reg + 6), bc + ld (_prev_reg + 9), a + ld (_prev_reg + 10), iy + ld (_prev_reg + 13), ix + ret + + private _set_next_reg +_set_next_reg: + ld (_next_reg + 0), hl + ld (_next_reg + 3), de + ld (_next_reg + 6), bc + ld (_next_reg + 9), a + ld (_next_reg + 10), iy + ld (_next_reg + 13), ix + ret + + public _CRT_smulhu +_CRT_smulhu: + push iy + ld iy, 0 + add iy, sp + ld b, (iy + 10) + ld c, (iy + 9) + ld h, (iy + 7) + ld l, (iy + 6) + pop iy + call _set_prev_reg + call __smulhu + jq _set_next_reg + + public _CRT_imulhu +_CRT_imulhu: + push iy + ld iy, 0 + add iy, sp + ld hl, (iy + 6) + ld bc, (iy + 9) + pop iy + call _set_prev_reg + call __imulhu + jq _set_next_reg + + public _CRT_lmulhu +_CRT_lmulhu: + push iy + ld iy, 0 + add iy, sp + ld hl, (iy + 6) + ld e, (iy + 9) + ld bc, (iy + 12) + ld a, (iy + 15) + pop iy + call _set_prev_reg + call __lmulhu + jq _set_next_reg + + public _CRT_i48mulhu +_CRT_i48mulhu: + ld iy, 0 + add iy, sp + ld hl, (iy + 3) + ld de, (iy + 6) + ld bc, (iy + 9) + ld iy, (iy + 12) + call _set_prev_reg + call __i48mulhu + jq _set_next_reg + + public _CRT_llmulhu +_CRT_llmulhu: + ld iy, 0 + add iy, sp + ld hl, (iy + 18) + push hl + ld hl, (iy + 15) + push hl + ld hl, (iy + 12) + push hl + ld bc, (iy + 9) + ld de, (iy + 6) + ld hl, (iy + 3) + call _set_prev_reg + call __llmulhu + ld sp, iy + jq _set_next_reg + + extern __smulhu + extern __imulhu + extern __lmulhu + extern __i48mulhu + extern __llmulhu diff --git a/test/standalone/mulhu/src/main.c b/test/standalone/mulhu/src/main.c new file mode 100644 index 000000000..00ae1f802 --- /dev/null +++ b/test/standalone/mulhu/src/main.c @@ -0,0 +1,330 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//------------------------------------------------------------------------------ +// Config +//------------------------------------------------------------------------------ + +#define RANDOM_TEST_COUNT 256 + +// define to 0 or 1 +#define DEBUG_DIAGNOSTICS 0 + +#define AUTOTEST_SEED 0x7184CE + +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + +#define C(expr) if (!(expr)) { return __LINE__; } + +#define TEST(test) { ret = test; if (ret != 0) { return ret; }} + +#ifndef DEBUG_DIAGNOSTICS +#error "DEBUG_DIAGNOSTICS needs to be defined to 0 or 1" +#endif + +#if RANDOM_TEST_COUNT < 4 +#error "RANDOM_TEST_COUNT is out of range" +#endif + +#if DEBUG_DIAGNOSTICS +#define test_printf printf +#else +#define test_printf(...) +#endif + +#define CMP(format, x, y, truth, guess) do { \ + if (truth != guess) { \ + test_printf("I: " format "\n * " format "\nT: " format "\nG: " format "\n", x, y, truth, guess); \ + return __LINE__; \ + } \ +} while(0) + +#define rand8() ((uint8_t)random()) + +#define rand16() ((uint16_t)random()) + +#define rand24() ((uint24_t)random()) + +#define rand32() ((uint32_t)random()) + +__attribute__((__unused__)) static uint48_t rand48(void) { + union { + uint48_t u48; + struct { + uint32_t lo32; + uint16_t hi16; + }; + } split; + split.lo32 = (uint32_t)random(); + split.hi16 = (uint16_t)random(); + return split.u48; +} + +__attribute__((__unused__)) static uint64_t rand64(void) { + union { + uint64_t u64; + struct { + uint32_t lo32; + uint32_t hi32; + }; + } split; + split.lo32 = (uint32_t)random(); + split.hi32 = (uint32_t)random(); + return split.u64; +} + +uint16_t CRT_smulhu(uint16_t, uint16_t); +uint24_t CRT_imulhu(uint24_t, uint24_t); +uint32_t CRT_lmulhu(uint32_t, uint32_t); +uint48_t CRT_i48mulhu(uint48_t, uint48_t); +uint64_t CRT_llmulhu(uint64_t, uint64_t); + +static __attribute__((__unused__)) +uint16_t truth_smulhu(uint16_t x, uint16_t y) { + return (uint16_t)(((uint32_t)x * (uint32_t)y) >> 16); +} + +static __attribute__((__unused__)) +uint24_t truth_imulhu(uint24_t x, uint24_t y) { + return (uint24_t)(((uint48_t)x * (uint48_t)y) >> 24); +} + +static __attribute__((__unused__)) +uint32_t truth_lmulhu(uint32_t x, uint32_t y) { + return (uint32_t)(((uint64_t)x * (uint64_t)y) >> 32); +} + +static __attribute__((__unused__)) +uint48_t truth_i48mulhu(uint48_t x, uint48_t y) { + const uint48_t x_hi = (x >> 24); + const uint48_t y_hi = (y >> 24); + const uint48_t x_lo = (x & UINT24_MAX); + const uint48_t y_lo = (y & UINT24_MAX); + uint48_t result = x_lo * y_lo; + result >>= 24; + result += x_hi * y_lo + x_lo * y_hi; + result >>= 24; + result += x_hi * y_hi; + return result; +} + +static __attribute__((__unused__)) +uint64_t truth_llmulhu(uint64_t x, uint64_t y) { + const uint64_t x_hi = (x >> 32); + const uint64_t y_hi = (y >> 32); + const uint64_t x_lo = (x & UINT32_MAX); + const uint64_t y_lo = (y & UINT32_MAX); + uint64_t result = x_lo * y_lo; + result >>= 32; + result += x_hi * y_lo + x_lo * y_hi; + result >>= 32; + result += x_hi * y_hi; + return result; +} + +typedef struct reg_group { + union { + struct { + uint24_t HL; + uint24_t DE; + uint24_t BC; + }; + struct { + uint8_t L; + uint8_t H; + uint8_t UHL; + uint8_t E; + uint8_t D; + uint8_t UDE; + uint8_t C; + uint8_t B; + uint8_t UBC; + }; + }; + uint8_t A; + uint24_t IY; + uint24_t IX; +} reg_group; +extern reg_group prev_reg; +extern reg_group next_reg; + +void print_reg(void) { + test_printf( + "A: %02X -> %02X\n"\ + "HL: %06X -> %06X\n"\ + "DE: %06X -> %06X\n"\ + "BC: %06X -> %06X\n"\ + "IY: %06X -> %06X\n"\ + "IX: %06X -> %06X\n", + prev_reg.A , next_reg.A , + prev_reg.HL, next_reg.HL, + prev_reg.DE, next_reg.DE, + prev_reg.BC, next_reg.BC, + prev_reg.IY, next_reg.IY, + prev_reg.IX, next_reg.IX + ); +} + +__attribute__((__unused__)) +static bool test_A_UBC_UDE_UIY_UIX(void) { + if ( + (prev_reg.A == next_reg.A ) && + (prev_reg.BC == next_reg.BC ) && + (prev_reg.DE == next_reg.DE ) && + (prev_reg.IY == next_reg.IY ) && + (prev_reg.IX == next_reg.IX ) + ) { + return true; + } + print_reg(); + return false; +} + +__attribute__((__unused__)) +static bool test_A_UBC_UD_UIY_UIX(void) { + if ( + (prev_reg.A == next_reg.A ) && + (prev_reg.BC == next_reg.BC ) && + (prev_reg.UDE == next_reg.UDE) && + (prev_reg.D == next_reg.D ) && + (prev_reg.IY == next_reg.IY ) && + (prev_reg.IX == next_reg.IX ) + ) { + return true; + } + print_reg(); + return false; +} + +__attribute__((__unused__)) +static bool test_A_UBC_UIY_UIX(void) { + if ( + (prev_reg.A == next_reg.A ) && + (prev_reg.BC == next_reg.BC ) && + (prev_reg.IY == next_reg.IY ) && + (prev_reg.IX == next_reg.IX ) + ) { + return true; + } + print_reg(); + return false; +} + +__attribute__((__unused__)) +static bool test_A_UIY_UIX(void) { + if ( + (prev_reg.A == next_reg.A ) && + (prev_reg.IY == next_reg.IY ) && + (prev_reg.IX == next_reg.IX ) + ) { + return true; + } + print_reg(); + return false; +} + +int test_smulhu(void) { + for (int i = 0; i < RANDOM_TEST_COUNT; i++) { + uint16_t truth, guess, x, y; + x = rand16(); + y = rand16(); + truth = truth_smulhu(x, y); + guess = CRT_smulhu(x, y); + CMP("%04X", x, y, truth, guess); + C((test_A_UBC_UDE_UIY_UIX())); + } + return 0; +} + +int test_imulhu(void) { + for (int i = 0; i < RANDOM_TEST_COUNT; i++) { + uint24_t truth, guess, x, y; + x = rand24(); + y = rand24(); + truth = truth_imulhu(x, y); + guess = CRT_imulhu(x, y); + CMP("%06X", x, y, truth, guess); + C((test_A_UBC_UDE_UIY_UIX())); + } + return 0; +} + +int test_lmulhu(void) { + for (int i = 0; i < RANDOM_TEST_COUNT; i++) { + uint32_t truth, guess, x, y; + x = rand32(); + y = rand32(); + truth = truth_lmulhu(x, y); + guess = CRT_lmulhu(x, y); + CMP("%08lX", x, y, truth, guess); + C((test_A_UBC_UD_UIY_UIX())); + } + return 0; +} + +int test_i48mulhu(void) { + for (int i = 0; i < RANDOM_TEST_COUNT; i++) { + uint48_t truth, guess, x, y; + x = rand48(); + y = rand48(); + truth = truth_i48mulhu(x, y); + guess = CRT_i48mulhu(x, y); + CMP("%012llX", (uint64_t)x, (uint64_t)y, (uint64_t)truth, (uint64_t)guess); + C((test_A_UBC_UIY_UIX())); + } + return 0; +} + +int test_llmulhu(void) { + for (int i = 0; i < RANDOM_TEST_COUNT; i++) { + uint64_t truth, guess, x, y; + x = rand64(); + y = rand64(); + truth = truth_llmulhu(x, y); + guess = CRT_llmulhu(x, y); + CMP("%016llX", x, y, truth, guess); + C((test_A_UIY_UIX())); + } + return 0; +} + +int run_tests(void) { + srandom(AUTOTEST_SEED); + int ret = 0; + TEST(test_smulhu()); + TEST(test_imulhu()); + TEST(test_lmulhu()); + TEST(test_i48mulhu()); + TEST(test_llmulhu()); + + return ret; +} + +int main(void) { + os_ClrHome(); + int failed_test = run_tests(); + if (failed_test != 0) { + char buf[sizeof("Failed test L-8388608\n")]; + boot_sprintf(buf, "Failed test L%d\n", failed_test); + fputs(buf, stdout); + } else { + fputs("All tests passed", stdout); + } + + while (!os_GetCSC()); + + return 0; +}