// cxxChronoProbe.cpp - exercise the etl::chrono surface (Phase 5.3). // // Takes two steady_clock::now() readings around a busy loop, verifies // that the second is >= the first (clock is monotonic), then prints // the duration count via printf. Also validates the chrono rep is // `int32_t` (the etl_profile.h override) so that i64 libcalls don't // creep into chrono::now() comparison paths. // // Marker layout (16-bit little-endian at $025xxx unless noted): // $025010 = 0xBEEF main entered // $025012 = 1 if sizeof(steady_clock::duration::rep) == 4 (i32 rep) // 0 otherwise // $025014 = 1 if t1 >= t0 (monotonic), 0 otherwise // $025016 = (uint16_t)(t1.time_since_epoch().count() & 0xFFFF) // low 16 bits of the second reading; used by smoke to // confirm a non-zero value (clock is actually ticking) // $025018 = (uint16_t)(elapsed_ms & 0xFFFF) // low 16 bits of (t1 - t0).count() in milliseconds; will // be small but ETL chrono::duration_cast // returning the raw rep means even tiny elapsed values // write *something* here (proves the subtract path works) // $025000 = 0xC0DE reached end-of-main (sentinel for runInGno --check) #include #include #include "etl/chrono.h" int main(void) { *(volatile uint16_t *)0x025010UL = 0xBEEF; // Compile-time contract: clock-rep is i32 (etl_profile.h override). // Any chrono lib that snuck i64 back in would FAIL HERE — caught // on the demo build, not silently bloating the .omf. static_assert(sizeof(etl::chrono::steady_clock::duration::rep) == 4, "etl::chrono::steady_clock::rep must be i32 — check " "ETL_CHRONO_STEADY_CLOCK_DURATION in etl_profile.h"); static_assert(sizeof(etl::chrono::system_clock::duration::rep) == 4, "etl::chrono::system_clock::rep must be i32"); static_assert(sizeof(etl::chrono::high_resolution_clock::duration::rep) == 4, "etl::chrono::high_resolution_clock::rep must be i32"); *(volatile uint16_t *)0x025012UL = 1; // Two readings around a busy loop. Loop is sized to take long // enough that one VBL tick (16.67 ms) reliably elapses, but short // enough that the demo completes well within the runInGno timeout. auto t0 = etl::chrono::steady_clock::now(); // Busy-spin. volatile keeps the optimizer from collapsing it. volatile uint16_t spin = 0; for (uint16_t i = 0; i < 20000; i++) { spin = (uint16_t)(spin + 1); } auto t1 = etl::chrono::steady_clock::now(); // Monotonic check. Use the raw count() so the comparison is on // i32 reps, not the time_point operator< (which works too — just // belt-and-braces). long c0 = t0.time_since_epoch().count(); long c1 = t1.time_since_epoch().count(); *(volatile uint16_t *)0x025014UL = (uint16_t)((c1 >= c0) ? 1 : 0); // Low 16 bits of the absolute reading. If the VBL counter // wasn't ticking at all both readings would be 0 — make that // visible to the host. *(volatile uint16_t *)0x025016UL = (uint16_t)(c1 & 0xFFFFL); // Elapsed milliseconds. steady_clock's rep is already // milliseconds (period = etl::milli), so the count delta IS // the elapsed ms; no duration_cast required. long elapsed_ms = c1 - c0; *(volatile uint16_t *)0x025018UL = (uint16_t)(elapsed_ms & 0xFFFFL); // Human-readable. GNO redirects stdout to fd 1; printf %ld is // fully supported (Phase 1 audit closed the printf gaps). printf("steady_clock t0 = %ld ms\n", c0); printf("steady_clock t1 = %ld ms\n", c1); printf("elapsed = %ld ms\n", elapsed_ms); *(volatile uint16_t *)0x025000UL = 0xC0DE; return 0; }