Skip to content

Commit a3854db

Browse files
committed
fix(sw): integrate sprintf_test into bare-metal build and test framework
Rewrite sprintf_test to work in the repo's freestanding (-nostdlib) environment and conform to the cocotb test harness contract. - Replace stdio.h calls (fputs, putc) with uart_puts/uart_putchar - Emit <<PASS>>/<<FAIL>> markers and halt loop per sw/README.md contract - Add -lgcc to link 64-bit division builtins needed by sprintf.c on RV32 - Fix size_t test case that assumed 64-bit (RV32 truncated to zero) - Fix copy-pasted Makefile comment ("Memory Test" -> sprintf test) - Register sprintf_test in TEST_REGISTRY (tests/test_run_cocotb.py) - Add SPRINTF_TEST_MAX_CYCLES (2M) for FP-heavy test suite - Document sprintf library and test app in sw/README.md
1 parent 165cb96 commit a3854db

File tree

6 files changed

+143
-78
lines changed

6 files changed

+143
-78
lines changed

CONTRIBUTORS.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,8 @@
2424
- Implemented freelist-based `malloc`/`free`
2525
- Modified linker script to carve out 8KB heap region
2626
- Wrote original packet parser software app
27+
28+
## Erez Strauss ([@erez-strauss](https://github.com/erez-strauss))
29+
30+
- Implemented portable `sprintf`/`snprintf` library with full format support (`%d`, `%f`, `%e`, `%g`, `%x`, flags, width, precision, length modifiers)
31+
- Created comprehensive test suite (~260 test cases) covering integer, floating-point, string, and truncation formatting

sw/README.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,33 @@ free(ptr); // Return to freelist
128128
- Arena: Fast allocation, bulk deallocation, no fragmentation, fixed lifetime
129129
- malloc/free: Flexible lifetime, individual deallocation, potential fragmentation
130130
131+
### Sprintf (`lib/include/sprintf.h`, `lib/src/sprintf.c`)
132+
133+
Portable `sprintf`/`snprintf` with no `<stdio.h>` dependency. Uses integer-scaling for floating-point formatting to avoid cascading FP-rounding errors.
134+
135+
```c
136+
#include "sprintf.h"
137+
138+
char buf[128];
139+
sprintf(buf, "x=%d y=%s", 42, "hello"); // Unbounded format
140+
snprintf(buf, sizeof(buf), "%.2f", 3.14159); // Bounded (C99 semantics)
141+
```
142+
143+
**Supported format specifiers:**
144+
- `%d`/`%i`, `%u`, `%o`, `%x`/`%X` — integer (signed/unsigned, octal, hex)
145+
- `%f`/`%F`, `%e`/`%E`, `%g`/`%G` — floating-point (fixed, scientific, shortest)
146+
- `%c` — character, `%s` — string, `%p` — pointer, `%%` — literal percent
147+
- Flags: `-` `+` `space` `0` `#`
148+
- Width/precision: literal or `*`
149+
- Length modifiers: `hh` `h` `l` `ll` `z` `t`
150+
151+
**Makefile setup:** The 64-bit integer arithmetic used internally requires `-lgcc`. Add this to your app's Makefile before the `include`:
152+
```makefile
153+
EXTRA_LDFLAGS := -lgcc
154+
SRC_C := ../../lib/src/uart.c ../../lib/src/sprintf.c your_app.c
155+
include ../../common/common.mk
156+
```
157+
131158
### Limits (`lib/include/limits.h`)
132159

133160
Integer limit constants for 32-bit systems.
@@ -308,6 +335,7 @@ Apps are also discoverable via `./tests/test_run_cocotb.py --list-tests`.
308335
| `ras_stress_test/` | BTB+RAS stress test mixing loops, branches, and function pointers |
309336
| `ras_test/` | Return Address Stack verification (deep nesting, coroutines, alignment) |
310337
| `spanning_test/` | 32-bit instruction fetch across word boundary verification |
338+
| `sprintf_test/` | sprintf/snprintf formatting test suite (~200 cases) |
311339
| `strings_test/` | String/ctype/stdlib library test suite |
312340
| `uart_echo/` | Interactive UART RX demo with echo, hex, and count commands |
313341

sw/apps/sprintf_test/Makefile

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15-
# Makefile for Memory Test application
16-
# Tests arena allocator and malloc/free implementations
15+
# Makefile for sprintf/snprintf test application
16+
# Tests custom sprintf/snprintf formatting implementation
17+
EXTRA_LDFLAGS := -lgcc
1718
SRC_C := ../../lib/src/uart.c ../../lib/src/string.c ../../lib/src/sprintf.c sprintf_test.c
1819
include ../../common/common.mk

sw/apps/sprintf_test/sprintf_test.c

Lines changed: 91 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,46 @@
1+
/*
2+
* Copyright 2026 Two Sigma Open Source, LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
117
/*
218
* sprintf_test.c
319
*
4-
* Standalone test suite for sprintf / snprintf.
20+
* Bare-metal test suite for sprintf / snprintf.
521
*
622
* Rules:
7-
* - NO calls to system printf/sprintf/snprintf/fprintf for validation.
23+
* - NO calls to system printf/sprintf/snprintf for validation.
824
* - Expected values are compile-time string/integer constants.
9-
* - Only stdio used: puts() / fwrite() for test-result reporting.
10-
* - All 129 original cases are covered; ~80 additional cases added.
25+
* - Output via uart_puts / uart_putchar only.
26+
* - Emits <<PASS>> / <<FAIL>> markers for cocotb test harness.
1127
*/
1228

1329
#include <sprintf.h>
30+
#include <uart.h>
1431

1532
#include <stdbool.h>
1633
#include <stdint.h>
17-
#include <stdio.h> /* puts, fwrite, fputs – output only, no printf family */
18-
#include <stdlib.h> /* EXIT_SUCCESS / EXIT_FAILURE */
1934
#include <string.h> /* strcmp, strlen, memset, memcmp */
2035

2136
/* ──────────────────────────────────────────────────────────────────────────
22-
* Tiny report helpers – no printf, just puts / fwrite
37+
* Tiny report helpers – uses UART, avoids sprintf for reporting
2338
* ────────────────────────────────────────────────────────────────────────── */
2439

2540
static int g_pass = 0;
2641
static int g_fail = 0;
2742

28-
/* Write a NUL-terminated string to stdout */
29-
static void out(const char *s)
30-
{
31-
fputs(s, stdout);
32-
}
33-
34-
/* Correct decimal printer */
43+
/* Correct decimal printer (avoids using sprintf for test infrastructure) */
3544
static void print_int(int n)
3645
{
3746
char tmp[24];
@@ -49,23 +58,23 @@ static void print_int(int n)
4958
}
5059
if (neg)
5160
tmp[--i] = '-';
52-
fputs(&tmp[i], stdout);
61+
uart_puts(&tmp[i]);
5362
}
5463

5564
static void print_str_escaped(const char *s)
5665
{
57-
putc('[', stdout);
66+
uart_putchar('[');
5867
for (; *s; s++) {
5968
if (*s == '\n') {
60-
putc('\\', stdout);
61-
putc('n', stdout);
69+
uart_putchar('\\');
70+
uart_putchar('n');
6271
} else if (*s == '\t') {
63-
putc('\\', stdout);
64-
putc('t', stdout);
72+
uart_putchar('\\');
73+
uart_putchar('t');
6574
} else
66-
putc(*s, stdout);
75+
uart_putchar(*s);
6776
}
68-
putc(']', stdout);
77+
uart_putchar(']');
6978
}
7079

7180
/* ──────────────────────────────────────────────────────────────────────────
@@ -83,30 +92,30 @@ static void check(
8392
return;
8493
}
8594
g_fail++;
86-
out("FAIL ");
87-
out(name);
88-
putc('\n', stdout);
95+
uart_puts("FAIL ");
96+
uart_puts(name);
97+
uart_putchar('\n');
8998
if (!str_ok) {
90-
out(" expected : ");
99+
uart_puts(" expected : ");
91100
print_str_escaped(expected_str);
92-
out(" (ret ");
101+
uart_puts(" (ret ");
93102
print_int(expected_ret);
94-
out(")\n");
95-
out(" got : ");
103+
uart_puts(")\n");
104+
uart_puts(" got : ");
96105
print_str_escaped(got_str);
97-
out(" (ret ");
106+
uart_puts(" (ret ");
98107
print_int(got_ret);
99-
out(")\n");
108+
uart_puts(")\n");
100109
} else {
101-
out(" strings match but return values differ: expected=");
110+
uart_puts(" strings match but return values differ: expected=");
102111
print_int(expected_ret);
103-
out(" got=");
112+
uart_puts(" got=");
104113
print_int(got_ret);
105-
putc('\n', stdout);
114+
uart_putchar('\n');
106115
}
107116
}
108117

109-
/* Floating-point: accept ±1 ULP in the last printed digit */
118+
/* Floating-point: accept +/-1 ULP in the last printed digit */
110119
static void check_fp(
111120
const char *name, const char *expected_str, int expected_ret, const char *got_str, int got_ret)
112121
{
@@ -134,19 +143,19 @@ static void check_fp(
134143
}
135144
}
136145
g_fail++;
137-
out("FAIL ");
138-
out(name);
139-
putc('\n', stdout);
140-
out(" expected : ");
146+
uart_puts("FAIL ");
147+
uart_puts(name);
148+
uart_putchar('\n');
149+
uart_puts(" expected : ");
141150
print_str_escaped(expected_str);
142-
out(" (ret ");
151+
uart_puts(" (ret ");
143152
print_int(expected_ret);
144-
out(")\n");
145-
out(" got : ");
153+
uart_puts(")\n");
154+
uart_puts(" got : ");
146155
print_str_escaped(got_str);
147-
out(" (ret ");
156+
uart_puts(" (ret ");
148157
print_int(got_ret);
149-
out(")\n");
158+
uart_puts(")\n");
150159
}
151160

152161
/* Convenience macros */
@@ -169,9 +178,9 @@ static void check_fp(
169178
/* section header */
170179
static void section(const char *s)
171180
{
172-
out("\n=== ");
173-
out(s);
174-
out(" ===\n");
181+
uart_puts("\n=== ");
182+
uart_puts(s);
183+
uart_puts(" ===\n");
175184
}
176185

177186
/* ══════════════════════════════════════════════════════════════════════════
@@ -428,7 +437,7 @@ static void test_snprintf_trunc(void)
428437
{
429438
section("snprintf truncation / return value");
430439

431-
/* Truncation: "hello world" buf size 5 "hell\0", ret=11 */
440+
/* Truncation: "hello world" -> buf size 5 -> "hell\0", ret=11 */
432441
{
433442
char got[5];
434443
int r = snprintf(got, 5, "%s", "hello world");
@@ -451,12 +460,12 @@ static void test_snprintf_trunc(void)
451460
g_pass++;
452461
else {
453462
g_fail++;
454-
out("FAIL size=0 no-write\n");
455-
out(" ret=");
463+
uart_puts("FAIL size=0 no-write\n");
464+
uart_puts(" ret=");
456465
print_int(r);
457-
out(" got[0]='");
458-
putc(got[0], stdout);
459-
out("'\n");
466+
uart_puts(" got[0]='");
467+
uart_putchar(got[0]);
468+
uart_puts("'\n");
460469
}
461470
}
462471

@@ -535,7 +544,7 @@ static void test_length_mods(void)
535544
T("ll signed min", "-9223372036854775808", 20, "%lld", -9223372036854775807LL - 1);
536545
T("ll unsigned max", "18446744073709551615", 20, "%llu", 18446744073709551615ULL);
537546
T("z size_t", "65536", 5, "%zu", (size_t) 65536);
538-
T("z size_t large", "1099511627776", 13, "%zu", (size_t) 1099511627776ULL);
547+
T("z size_t large", "4294967295", 10, "%zu", (size_t) 4294967295UL);
539548
T("lx", "ffffffffffffffff", 16, "%llx", 18446744073709551615ULL);
540549
T("lX", "FFFFFFFFFFFFFFFF", 16, "%llX", 18446744073709551615ULL);
541550
}
@@ -554,7 +563,7 @@ static void test_pointer(void)
554563
g_pass++;
555564
else {
556565
g_fail++;
557-
out("FAIL p NULL has 0x prefix\n");
566+
uart_puts("FAIL p NULL has 0x prefix\n");
558567
}
559568
}
560569
{
@@ -566,7 +575,7 @@ static void test_pointer(void)
566575
g_pass++;
567576
else {
568577
g_fail++;
569-
out("FAIL p 0xDEAD has 0x prefix\n");
578+
uart_puts("FAIL p 0xDEAD has 0x prefix\n");
570579
}
571580
}
572581
{
@@ -579,11 +588,11 @@ static void test_pointer(void)
579588
g_pass++;
580589
else {
581590
g_fail++;
582-
out("FAIL p width=20: ");
591+
uart_puts("FAIL p width=20: ");
583592
print_str_escaped(got);
584-
out(" ret=");
593+
uart_puts(" ret=");
585594
print_int(r);
586-
putc('\n', stdout);
595+
uart_putchar('\n');
587596
}
588597
}
589598
}
@@ -629,9 +638,9 @@ static void test_n(void)
629638
g_pass++;
630639
else {
631640
g_fail++;
632-
out("FAIL %n pos: expected 5 got ");
641+
uart_puts("FAIL %n pos: expected 5 got ");
633642
print_int(pos);
634-
putc('\n', stdout);
643+
uart_putchar('\n');
635644
}
636645
}
637646
{
@@ -643,9 +652,9 @@ static void test_n(void)
643652
g_pass++;
644653
else {
645654
g_fail++;
646-
out("FAIL %n after %d: expected 5 got ");
655+
uart_puts("FAIL %n after %d: expected 5 got ");
647656
print_int(pos);
648-
putc('\n', stdout);
657+
uart_putchar('\n');
649658
}
650659
}
651660
}
@@ -663,7 +672,7 @@ static void test_regression(void)
663672
g_pass++;
664673
else {
665674
g_fail++;
666-
out("FAIL NUL via %c\n");
675+
uart_puts("FAIL NUL via %c\n");
667676
}
668677
}
669678
T("d 10 digits", "1000000000", 10, "%d", 1000000000);
@@ -697,9 +706,8 @@ static void test_regression(void)
697706

698707
int main(void)
699708
{
700-
out("╔══════════════════════════════════════════════════╗\n");
701-
out("║ sprintf standalone static test suite ║\n");
702-
out("╚══════════════════════════════════════════════════╝\n");
709+
uart_puts("sprintf/snprintf Test Suite\n");
710+
uart_puts("==========================\n");
703711

704712
test_literal();
705713
test_char();
@@ -721,15 +729,24 @@ int main(void)
721729
test_n();
722730
test_regression();
723731

724-
out("\n══════════════════════════════════════════════════════\n");
725-
out("Results: ");
732+
uart_puts("\n==========================\n");
733+
uart_puts("Results: ");
726734
print_int(g_pass);
727-
out(" passed, ");
735+
uart_puts(" passed, ");
728736
print_int(g_fail);
729-
out(" failed (total ");
737+
uart_puts(" failed (total ");
730738
print_int(g_pass + g_fail);
731-
out(")\n");
732-
out("══════════════════════════════════════════════════════\n");
739+
uart_puts(")\n");
740+
741+
if (g_fail == 0) {
742+
uart_puts("ALL TESTS PASSED\n");
743+
uart_puts("<<PASS>>\n");
744+
} else {
745+
uart_puts("SOME TESTS FAILED\n");
746+
uart_puts("<<FAIL>>\n");
747+
}
733748

734-
return (g_fail == 0) ? 0 : -1;
749+
/* Halt */
750+
for (;;) {
751+
}
735752
}

tests/test_run_cocotb.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,12 @@ class CocotbRunConfig:
160160
app_name="spanning_test",
161161
description="Spanning instruction test",
162162
),
163+
"sprintf_test": CocotbRunConfig(
164+
python_test_module="cocotb_tests.test_real_program",
165+
hdl_toplevel_module="frost",
166+
app_name="sprintf_test",
167+
description="sprintf/snprintf formatting test suite",
168+
),
163169
"strings_test": CocotbRunConfig(
164170
python_test_module="cocotb_tests.test_real_program",
165171
hdl_toplevel_module="frost",

0 commit comments

Comments
 (0)