Add mdview markdown viewer, reorganize tests/examples and libc layout
- Split tests/ (libc feature tests) and examples/ (real apps); shared
app.mk in repo root, was examples/example.mk
- libc/io/* split into libc/{conio,env,errno,file,mouse,string,sys,
time,video}/ — clearer module boundaries
- New examples/mdview/: markdown viewer (Phases 1-5 + light nested
lists). Headers (H1-H4), HR, ulist/olist/quote with nesting via
leading spaces, fenced code blocks, inline emphasis (bold/italic/
underscore/code), wrap/unwrap mode with soft wrap (F2), horizontal
pan (← →) with '>' truncation indicator
- libc additions: scroll() in conio (ESTEX SCROLL), strlwr/strupr,
gets() test
- Makefile updates across tests/ for the new shared app.mk path
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
# bank_local_data — code AND static data live in BANK1.
|
||||
#
|
||||
# HUGE mode: HOME at 0x4100 (W1), DATA at 0x8000 (W2), BANK1 in W3.
|
||||
# sprinter-cc compiles bank1.c with --codeseg/--constseg/--dataseg BANK1
|
||||
# automatically (via --bank 1=bank1.c). mkexe gets `-p 0` so BSS pages
|
||||
# in the bank land as zero-filled — no runtime zeroing needed.
|
||||
|
||||
PROJ_ROOT := $(abspath $(CURDIR)/../..)
|
||||
EXAMPLE := banklocl
|
||||
MEMORY := huge
|
||||
EXTRA_FLAGS := --bank 1=bank1.c --mkexe -p --mkexe 0
|
||||
include $(PROJ_ROOT)/app.mk
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* bank1.c — code, const tables AND writable static data all live in the
|
||||
* BANK1 page mapped into window 3 while these functions run.
|
||||
*
|
||||
* Compile flags (see Makefile):
|
||||
* --codeseg BANK1 functions go to _BANK1 area
|
||||
* --constseg BANK1 `static const` tables go to _BANK1
|
||||
* --dataseg BANK1 uninitialized data (BSS) goes to _BANK1
|
||||
*
|
||||
* mkexe -p 0 zero-pads the bank's image, so any BSS bytes that don't
|
||||
* appear in the IHX file are 0x00 when the bank is loaded — free zero
|
||||
* init for `uint8_t buf[N];`.
|
||||
*
|
||||
* Note: a CRITICAL trampoline fix is required (see memory/sdcc_banking)
|
||||
* — the old bcall used `pop af; out (n), a` which clobbered the uint8_t
|
||||
* return value. Without that fix, banked functions returning a byte
|
||||
* would silently return the saved page number instead.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
|
||||
uint8_t bank1_buf[16];
|
||||
|
||||
static const uint16_t bank1_tab[5] = { 100, 200, 300, 400, 500 };
|
||||
|
||||
|
||||
void bank1_fill(int seed) __banked
|
||||
{
|
||||
uint8_t *p = bank1_buf;
|
||||
for (int i = 0; i < 16; i++)
|
||||
p[i] = (uint8_t)(seed + i);
|
||||
}
|
||||
|
||||
uint8_t bank1_peek(int idx) __banked
|
||||
{
|
||||
uint8_t *p = bank1_buf;
|
||||
if (idx < 0 || idx >= 16) return 0xFF;
|
||||
return p[idx];
|
||||
}
|
||||
|
||||
uint16_t bank1_sumtab(void) __banked
|
||||
{
|
||||
uint16_t s = 0;
|
||||
for (int i = 0; i < 5; i++) s += bank1_tab[i];
|
||||
return s;
|
||||
}
|
||||
|
||||
void bank1_map_print(void) __banked
|
||||
{
|
||||
printf( "bank1_buf = %p\n", bank1_buf);
|
||||
printf( "bank1_tab = %p\n", bank1_tab);
|
||||
printf( "bank1_fill = %p\n", bank1_fill);
|
||||
printf( "bank1_peek = %p\n", bank1_peek);
|
||||
printf( "bank1_sumtab = %p\n", bank1_sumtab);
|
||||
}
|
||||
|
||||
/*
|
||||
* malloc-from-banked test.
|
||||
*
|
||||
* SDCC's heap lives in HOME RAM (W2 = 0x8000-0xBFFF, see runtime/heap.s).
|
||||
* Window 2 is fixed and stays mapped regardless of W3 page swaps. So
|
||||
* malloc() called from a banked function should:
|
||||
* 1. Return a pointer somewhere in 0x80xx..0xBFxx (W2 heap)
|
||||
* 2. The pointer is usable both inside the bank (W2 still mapped) and
|
||||
* after returning to HOME (W2 unchanged across trampoline).
|
||||
*
|
||||
* The function allocates N bytes, fills them with a known pattern, and
|
||||
* returns the pointer to the caller for verification.
|
||||
*/
|
||||
uint8_t *bank1_alloc_and_fill(int n, uint8_t pattern) __banked
|
||||
{
|
||||
uint8_t *p = (uint8_t *)malloc(n);
|
||||
if (p == NULL) return NULL;
|
||||
for (int i = 0; i < n; i++)
|
||||
p[i] = (uint8_t)(pattern + i);
|
||||
printf(" bank1_alloc_and_fill: malloc(%d) -> %p\n", n, p);
|
||||
return p;
|
||||
}
|
||||
|
||||
void bank1_free(void *p) __banked
|
||||
{
|
||||
free(p);
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* bank_local_data — demonstrates that a banked function can own its own
|
||||
* static (BSS) array and a const lookup table without burning HOME
|
||||
* memory. When the function executes, BANK1 is mapped into window 3
|
||||
* and both code and data are directly addressable.
|
||||
*
|
||||
* No bank_read / bank_write needed — the trampoline maps the bank for
|
||||
* us and the C code touches the arrays normally.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
|
||||
extern void bank1_fill (int seed) __banked;
|
||||
extern uint16_t bank1_sumtab (void) __banked;
|
||||
extern uint8_t bank1_peek (int idx) __banked;
|
||||
extern void bank1_map_print(void) __banked;
|
||||
extern uint8_t *bank1_alloc_and_fill(int n, uint8_t pattern) __banked;
|
||||
extern void bank1_free (void *p) __banked;
|
||||
|
||||
/* MUST be `const`: crt0_banked reads _n_banks BEFORE gsinit, so
|
||||
* non-const initialized variables haven't been copied yet and would
|
||||
* read 0 → bank loading is skipped → garbage in window 3. */
|
||||
const uint8_t n_banks = 1;
|
||||
|
||||
uint8_t local0_buf[16];
|
||||
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
puts("bank_local_data: testing static data in BANK1");
|
||||
|
||||
bank1_map_print();
|
||||
|
||||
/* Verify crt0_banked parsed argv correctly. */
|
||||
printf("argc=%d, argv[0]=\"%s\"\n", argc, argv[0]);
|
||||
|
||||
printf("local0_buf = %p\n", local0_buf);
|
||||
for (int i = 1; i < argc; i++)
|
||||
printf(" argv[%d]=\"%s\"\n", i, argv[i]);
|
||||
|
||||
/* Seed the BSS array in BANK1. */
|
||||
bank1_fill(0x10);
|
||||
|
||||
/* Read it back through another banked function — verifies the data
|
||||
* survived after the trampoline returned to HOME and re-entered. */
|
||||
int ok = 1;
|
||||
for (int i = 0; i < 8; i++) {
|
||||
uint8_t v = bank1_peek(i);
|
||||
uint8_t expected = (uint8_t)(0x10 + i);
|
||||
if (v != expected) {
|
||||
printf(" bank1_buf[%d] = 0x%02X, expected 0x%02X\n",
|
||||
i, v, expected);
|
||||
ok = 0;
|
||||
}
|
||||
}
|
||||
if (ok)
|
||||
puts(" buf[0..7] OK");
|
||||
|
||||
/* Use the const lookup table that lives in BANK1. */
|
||||
uint16_t sum = bank1_sumtab();
|
||||
printf(" bank1_sumtab() = %u (expected 1500)\n", sum);
|
||||
|
||||
/* malloc from a banked function — the heap lives in W2 (HOME),
|
||||
* which stays mapped across W3 page swaps. Returned pointer
|
||||
* should be usable both inside the bank AND from HOME afterwards. */
|
||||
uint8_t *heap_p = bank1_alloc_and_fill(32, 0xA0);
|
||||
if (heap_p == NULL) {
|
||||
puts(" malloc from bank FAILED");
|
||||
} else {
|
||||
printf(" caller sees pointer = %p\n", heap_p);
|
||||
int ok = 1;
|
||||
for (int i = 0; i < 32; i++) {
|
||||
uint8_t expected = (uint8_t)(0xA0 + i);
|
||||
if (heap_p[i] != expected) {
|
||||
printf(" heap_p[%d] = 0x%02X, expected 0x%02X\n",
|
||||
i, heap_p[i], expected);
|
||||
ok = 0;
|
||||
}
|
||||
}
|
||||
if (ok) puts(" bank-allocated heap [0..31] OK from HOME");
|
||||
bank1_free(heap_p);
|
||||
}
|
||||
|
||||
puts("done");
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user