all repos — xyw @ df6b8d925172b6f23d627ba48521b3a9aeb736cd

A minimal virtual machine and assembler for terminals.

Implemented timer support (untested)
h3rald h3rald@h3rald.com
Fri, 06 Feb 2026 14:40:43 +0100
commit

df6b8d925172b6f23d627ba48521b3a9aeb736cd

parent

846f9715bd78b9352ebdc1a4bf6f5589e28ea513

3 files changed, 212 insertions(+), 2 deletions(-)

jump to
M devices/clock.cdevices/clock.c

@@ -1,4 +1,10 @@

#include <time.h> +#ifdef _WIN32 +#include <windows.h> +#else +#include <sys/time.h> +#include <unistd.h> +#endif #include "../xyw.h" /* clock device */

@@ -12,10 +18,129 @@ #define CLOCK_SECOND 0x7

#define CLOCK_TIMER 0x8 #define CLOCK_ON_TIMER_ELAPSED 0xA +static int clock_timer_active = 0; +static int clock_timer_elapsed_pending = 0; + +#ifdef _WIN32 +static LARGE_INTEGER clock_timer_freq; +static LARGE_INTEGER clock_timer_target; // Target time when timer elapses +#else +static struct timespec clock_timer_target; // Target time when timer elapses +#endif + +// Get current timer value (remaining ticks until target) +static xyw_word clock_get_timer_value(void) +{ + if (!clock_timer_active) + return 0; + +#ifdef _WIN32 + LARGE_INTEGER now; + QueryPerformanceCounter(&now); + // Calculate remaining time in 1/256 second units + if (now.QuadPart >= clock_timer_target.QuadPart) + { + // Timer has elapsed + clock_timer_active = 0; + clock_timer_elapsed_pending = 1; + return 0; + } + double remaining_sec = (double)(clock_timer_target.QuadPart - now.QuadPart) / clock_timer_freq.QuadPart; + xyw_word remaining_ticks = (xyw_word)(remaining_sec * 256.0); + if (remaining_ticks == 0) + { + // Less than 1 tick remaining, consider elapsed + clock_timer_active = 0; + clock_timer_elapsed_pending = 1; + return 0; + } + return remaining_ticks; +#else + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + // Calculate remaining time in nanoseconds + long long remaining_ns = (clock_timer_target.tv_sec - now.tv_sec) * 1000000000LL + + (clock_timer_target.tv_nsec - now.tv_nsec); + if (remaining_ns <= 0) + { + // Timer has elapsed + clock_timer_active = 0; + clock_timer_elapsed_pending = 1; + return 0; + } + // Convert to 1/256 second units (1/256 sec = 3906250 ns) + xyw_word remaining_ticks = (xyw_word)(remaining_ns / 3906250); + if (remaining_ticks == 0) + { + // Less than 1 tick remaining, consider elapsed + clock_timer_active = 0; + clock_timer_elapsed_pending = 1; + return 0; + } + return remaining_ticks; +#endif +} + +void clock_init(xyw_byte *data) +{ + (void)data; + clock_timer_active = 0; + clock_timer_elapsed_pending = 0; +#ifdef _WIN32 + QueryPerformanceFrequency(&clock_timer_freq); +#endif +} + xyw_byte clock_input(xyw_byte *data, xyw_byte addr, xyw_byte *error) { (void)error; (void)data; + + /* Handle timer reads */ + if (addr == CLOCK_TIMER) + { + xyw_word val = clock_get_timer_value(); + return (xyw_byte)(val >> 8); + } + if (addr == CLOCK_TIMER + 1) + { + xyw_word val = clock_get_timer_value(); + return (xyw_byte)(val & 0xFF); + } + // Reading CLOCK_ON_TIMER_ELAPSED blocks until timer elapses (if active) + if (addr == CLOCK_ON_TIMER_ELAPSED) + { + // If timer is active, block until it elapses using absolute target time + if (clock_timer_active) + { + xyw_word remaining = clock_get_timer_value(); + if (remaining > 0) + { +#ifdef _WIN32 + // Calculate remaining time and sleep + LARGE_INTEGER now; + QueryPerformanceCounter(&now); + if (clock_timer_target.QuadPart > now.QuadPart) + { + double remaining_sec = (double)(clock_timer_target.QuadPart - now.QuadPart) / clock_timer_freq.QuadPart; + DWORD ms = (DWORD)(remaining_sec * 1000.0); + if (ms > 0) + Sleep(ms); + } +#else + // Use clock_nanosleep with absolute target time for precision + clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &clock_timer_target, NULL); +#endif + // After sleep, update timer state + clock_get_timer_value(); + } + } + // Return and clear the pending flag + xyw_byte pending = clock_timer_elapsed_pending ? 1 : 0; + clock_timer_elapsed_pending = 0; + return pending; + } + time_t now = time(NULL); struct tm *lt = localtime(&now); if (!lt)

@@ -45,3 +170,48 @@ default:

return 0; } } + +void clock_output(xyw_byte *data, xyw_byte addr, xyw_byte *error) +{ + (void)error; + + // When writing to CLOCK_TIMER or CLOCK_TIMER+1, start/restart the timer + if (addr == CLOCK_TIMER || addr == CLOCK_TIMER + 1) + { + // Read the full 16-bit timer value from device data + xyw_word timer_val = XYW_PEEKW(&data[CLOCK_TIMER]); + + if (timer_val > 0) + { + clock_timer_active = 1; + clock_timer_elapsed_pending = 0; + // Calculate target time: now + timer_val * (1/256 second) +#ifdef _WIN32 + LARGE_INTEGER now; + QueryPerformanceCounter(&now); + // target = now + (timer_val / 256) seconds in performance counter units + long long ticks_to_add = (long long)timer_val * clock_timer_freq.QuadPart / 256; + clock_timer_target.QuadPart = now.QuadPart + ticks_to_add; +#else + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + // 1/256 second = 3906250 nanoseconds + long long ns_to_add = (long long)timer_val * 3906250LL; + clock_timer_target.tv_sec = now.tv_sec + (ns_to_add / 1000000000LL); + clock_timer_target.tv_nsec = now.tv_nsec + (ns_to_add % 1000000000LL); + // Handle nanosecond overflow + if (clock_timer_target.tv_nsec >= 1000000000L) + { + clock_timer_target.tv_sec++; + clock_timer_target.tv_nsec -= 1000000000L; + } +#endif + } + else + { + // Writing 0 deactivates the timer + clock_timer_active = 0; + clock_timer_elapsed_pending = 0; + } + } +}
M devices/clock.hdevices/clock.h

@@ -1,3 +1,5 @@

#include "../xyw.h" +void clock_init(xyw_byte *data); xyw_byte clock_input(xyw_byte *data, xyw_byte addr, xyw_byte *error); +void clock_output(xyw_byte *data, xyw_byte addr, xyw_byte *error);
M xywrun.cxywrun.c

@@ -29,13 +29,19 @@ #define TERMINAL_INPUT 0xff10

#define TERMINAL_ON_KEYPRESS 0xff12 #define TERMINAL_ON_ARGUMENT 0xff14 +/* Clock device addresses */ +#define CLOCK_TIMER 0xff28 +#define CLOCK_ON_TIMER_ELAPSED 0xff2a + #ifdef _WIN32 #include <conio.h> +#include <windows.h> int getch() { return _getch(); } #else +#include <unistd.h> int getch() { return getchar();

@@ -285,7 +291,7 @@ {&xyw_memory[0xff00], system_init, system_input, 0},

// Terminal device {&xyw_memory[0xff10], 0, terminal_input, terminal_output}, // Clock device - {&xyw_memory[0xff20], 0, clock_input, 0}, + {&xyw_memory[0xff20], clock_init, clock_input, clock_output}, // File device {&xyw_memory[0xff30], 0, 0, 0}, // ...

@@ -330,6 +336,16 @@ else

{ // No error handler, halt execution return xyw_memory[SYSTEM_ERROR]; + } + } + // Timer elapsed handling + xyw_word on_timer_handler = XYW_PEEKW(&xyw_memory[CLOCK_ON_TIMER_ELAPSED]); + if (on_timer_handler != 0) + { + if (read(CLOCK_ON_TIMER_ELAPSED)) + { + xyw_dbg("Timer elapsed, jumping to handler at %04X\n", on_timer_handler); + xyw_eval(on_timer_handler); } } // Process terminal arguments if not already done

@@ -467,8 +483,10 @@

int eval_result = xyw_eval(0x0000); if (!eval_result) { - // If terminal handler is set, enter stdin reading loop + // Check if we need to enter an event loop (keypress handler or timer) xyw_word on_keypress_handler = XYW_PEEKW(&xyw_memory[TERMINAL_ON_KEYPRESS]); + xyw_word on_timer_handler = XYW_PEEKW(&xyw_memory[CLOCK_ON_TIMER_ELAPSED]); + if (on_keypress_handler) { xyw_dbg("terminal.on_keypress handler set to %04X, entering input loop\n", on_keypress_handler);

@@ -483,6 +501,26 @@ return 0;

} xyw_memory[SYSTEM_STATE] |= SYSTEM_STATE_RUNNING; write(TERMINAL_INPUT, (xyw_byte)c); + } + } + else if (on_timer_handler) + { + xyw_dbg("clock.on_timer_elapsed handler set to %04X, entering timer loop\n", on_timer_handler); + while (system_running_state() || system_waiting_state()) + { + xyw_memory[SYSTEM_STATE] |= SYSTEM_STATE_WAITING; + if (read(CLOCK_ON_TIMER_ELAPSED)) + { + xyw_memory[SYSTEM_STATE] |= SYSTEM_STATE_RUNNING; + xyw_dbg("Timer elapsed, calling handler at %04X\n", on_timer_handler); + xyw_eval(on_timer_handler); + // Refresh handler in case it was changed + on_timer_handler = XYW_PEEKW(&xyw_memory[CLOCK_ON_TIMER_ELAPSED]); + if (!on_timer_handler) + { + break; + } + } } } }