Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Standard Library

The base HaDes-V architecture provides only a minimal runtime: basic UART output, LED control, and a timer. DOOM requires a substantial subset of the C standard library, including string functions, memory allocation, formatted output, and file I/O. These were implemented in hades_stdlib.c.

Memory Allocation

DOOM uses a zone-based allocator (Z_Malloc/Z_Free) internally, but the zone allocator itself relies on a lower-level malloc/free/realloc/calloc to obtain its initial memory pool. A simple bump allocator was implemented:

static unsigned char *heap_ptr = NULL;

void *malloc(size_t size) {
    if (heap_ptr == NULL) {
        heap_ptr = (unsigned char *)&_end;
    }
    uint32_t aligned_addr = ((uint32_t)heap_ptr + 7) & ~7;
    heap_ptr = (unsigned char *)aligned_addr;
    void *allocated = heap_ptr;
    heap_ptr += size;
    return allocated;
}

The _end symbol is defined by the linker script and points to the first address after the BSS section in DDR2. The allocator always aligns to 8 bytes and grows upward into DDR2 memory. There is no real free(), as DOOM’s zone allocator manages its own freed blocks internally, and the bump allocator’s main purpose is to provide the initial pool. realloc is implemented by allocating new memory and copying the old data; calloc wraps malloc with memset to zero the block.

String & Memory Functions

All standard string and memory functions were implemented from scratch:

  • Memory: memset, memcpy, memmove
  • String length/comparison: strlen, strcmp, strncmp, strcasecmp, strncasecmp
  • String search: strchr, strrchr
  • String copy/duplicate: strncpy, strdup
  • Conversion: atoi, toupper, tolower, isspace
int strcasecmp(const char *s1, const char *s2) {
    while (1) {
        unsigned char c1 = *s1, c2 = *s2;
        if (c1 >= 'A' && c1 <= 'Z') c1 += 32;
        if (c2 >= 'A' && c2 <= 'Z') c2 += 32;
        if (c1 != c2) return c1 - c2;
        if (c1 == '\0') return 0;
        s1++; s2++;
    }
}

DOOM uses case-insensitive string comparisons for WAD lump name lookups (strcasecmp/strncasecmp), making these particularly important for correct WAD loading.

Formatted Output

A custom format-string parser provides the printf/sprintf/snprintf/fprintf family. The format parser supports:

SpecifierDescription
%sNull-terminated string
%d / %iSigned decimal integer
%x / %XLowercase/uppercase hexadecimal
%cSingle character
%%Literal percent sign
%0Nd / %NxZero-padded width specifier

The printf implementation routes output through the UART (uart_print_str). sprintf/snprintf write to a provided buffer. The implementation handles negative values, width padding, and edge cases such as null pointer strings (“(null)”).

Pseudo-Filesystem

DOOM’s startup code opens the WAD file using standard C stdio functions. Since there is no filesystem, the stdio functions were overridden to operate on the embedded WAD data:

FILE *fopen(const char *filename, const char *mode) {
    if (/* filename ends in ".wad" */) {
        virtual_file_offset = 0;
        return (FILE *)1;  // Dummy non-null handle
    }
    return NULL;
}

size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream) {
    size_t total_bytes = size * nmemb;
    memcpy(ptr, (void*)(_binary_doom1_wad_start + virtual_file_offset), total_bytes);
    virtual_file_offset += total_bytes;
    return nmemb;
}

fseek, ftell, and fclose are implemented to manipulate a single virtual_file_offset counter. fwrite, remove, rename, mkdir are no-ops or error stubs. This approach avoids modifying DOOM’s underlying file-reading code while transparently redirecting all WAD access to the linked-in binary data.

Compiler Stubs

The RISC-V GCC toolchain expects certain symbols that are not provided by a bare-metal environment:

static int dummy_impure[256];
void* _impure_ptr = dummy_impure;
int __errno = 0;

_impure_ptr is required by newlib-compatible libraries for reentrancy support. On bare metal without threading, a static dummy structure suffices. __errno provides errno storage used by DOOM’s error handling paths.