Files
vt1200/harness/c-hello/hello.c
T
arti 41b0979aef c-hello: first C program on the VT1200, running on real hardware
The 1987 TI K&R toolchain already compiled and linked a fib test, but nothing
had ever been executed on a VT1200. This is that program: hello.c draws HELLO
into video RAM (1 bpp, bit 0x04000000, no X11) and is loaded through the COFF
loader with hostload.py, on the real unit and in MAME.

Two runtime traps shaped the design, both recorded in the README:

  * boot.obj's exit() is `for(;;)` and _c_int00 ends in `reti`.  That is a
    standalone-ROM runtime, not a process: the child spun forever, and because
    the VT1200 OS is cooperative it took the whole terminal down (illegal
    opcode on hardware).  entry.asm replaces boot.obj -- set STK/FP, call
    _main, `rets` back to the create_process trampoline, the same return
    xhello uses -- and the image carries no .cinit (tables are built in code).
  * Pointers are bit addresses and pointer arithmetic scales by object size,
    so p + n on a char * is n bytes.  The pre-fix frame in evidence/ is that
    bug: 32 one-byte bars spread across a row.

Framebuffer geometry is not portable: the real unit is 1280 px wide at a
256-byte row pitch, MAME's canvas is 1024 px at 128.  build.sh builds both
(ROW_BYTES), and the README records how the real pitch was measured from a
real-unit frame rather than taken from MAME.

Evidence: real-unit frames in harness/c-hello/evidence/ (the pre-fix stripes
and the working HELLO), and dwt/loader/evidence/real-unit-xhello/ for the X
window that ran on the unit earlier.
2026-10-29 18:25:38 +02:00

104 lines
3.7 KiB
C

/*
* hello.c -- the VT1200's first C program.
*
* Writes the word HELLO straight into video RAM, no X11 involved:
*
* framebuffer bit 0x04000000, 256-byte row pitch on the real unit
* (the emulator uses 128; this image is linked for the real one)
* pixels 1 bpp, LSB-first within each byte
* text area black 256x72 box at x=40 y=800, white 5x7 glyphs scaled 8x
*
* The program also leaves a marker word at 0x018A8000 so the host can tell the
* C runtime reached main(): 0x48454C4C == 'HELL'.
*
* K&R C only: no prototypes, no '//', no unsigned suffixes, declarations first.
* The tables are filled in code, not by static initializers, because this image
* is entered through entry.asm and does not run boot.obj's .cinit pass.
*/
#define VRAM_BASE 0x04000000 /* bit address of the framebuffer */
#ifndef ROW_BYTES
#define ROW_BYTES 256 /* real unit: 256-byte row pitch */
#endif
#define ROW_BITS (ROW_BYTES * 8)
#define BOX_X 40 /* box left edge, pixels (1 bit per pixel) */
#define BOX_Y 800 /* box top edge, pixels */
#define BOX_W 256 /* 32 bytes */
#define BOX_H 72
#define MARKER 0x018A8000 /* free RAM, outside .text and .bss */
static unsigned char font[4][7]; /* H E L O (HELLO uses L twice) */
static unsigned char order[5];
static unsigned char box[BOX_H][BOX_W / 8];
/* 5x7 glyphs, bit 4 = leftmost pixel */
static void build_font()
{
font[0][0] = 17; font[0][1] = 17; font[0][2] = 31; font[0][3] = 17;
font[0][4] = 17; font[0][5] = 17; font[0][6] = 17; /* H */
font[1][0] = 31; font[1][1] = 16; font[1][2] = 16; font[1][3] = 30;
font[1][4] = 16; font[1][5] = 16; font[1][6] = 31; /* E */
font[2][0] = 16; font[2][1] = 16; font[2][2] = 16; font[2][3] = 16;
font[2][4] = 16; font[2][5] = 16; font[2][6] = 31; /* L */
font[3][0] = 14; font[3][1] = 17; font[3][2] = 17; font[3][3] = 17;
font[3][4] = 17; font[3][5] = 17; font[3][6] = 14; /* O */
order[0] = 0; order[1] = 1; order[2] = 2; order[3] = 2; order[4] = 3;
}
int main()
{
int c, r, gx, dx, dy, px, py;
unsigned int v;
unsigned int addr;
build_font();
/* 1. prove we are here */
*(unsigned int *)MARKER = 0x48454C4C;
/* 2. clear the box (black) */
for (py = 0; py < BOX_H; py++)
for (px = 0; px < BOX_W / 8; px++)
box[py][px] = 0;
/* 3. draw HELLO, each glyph pixel as an 8x8 block */
for (c = 0; c < 5; c++) {
for (r = 0; r < 7; r++) {
v = font[order[c]][r];
for (gx = 0; gx < 5; gx++) {
if (!(v & (16 >> gx)))
continue;
for (dy = 0; dy < 8; dy++) {
for (dx = 0; dx < 8; dx++) {
px = 8 + c * 48 + gx * 8 + dx;
py = 8 + r * 8 + dy;
box[py][px >> 3] |= 1 << (px & 7);
}
}
}
}
}
/* 4. blit the box into video RAM, one byte at a time.
*
* Pointers here are BIT addresses and pointer arithmetic scales by the
* object size, so a `char *` step is 8 bits: p + n is n bytes, not n*8.
* Address the bytes as addr + px*8 bits and cast, which is unambiguous.
*/
for (py = 0; py < BOX_H; py++) {
addr = VRAM_BASE + (BOX_Y + py) * ROW_BITS + BOX_X;
for (px = 0; px < BOX_W / 8; px++) {
*(unsigned char *)(addr + px * 8) = box[py][px];
}
}
/* 5. second marker: the blit finished */
*(unsigned int *)(MARKER + 0x20) = 0x444F4E45; /* 'DONE' */
return 0;
}