From 3c55689566153047f1d2e5403188557481f90d42 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Thu, 21 May 2020 23:43:08 +0200 Subject: [PATCH] runtime: refactor time handling This commit refactors both determining the current time and sleeping for a given time. It also improves precision for many chips. * The nrf chips had a long-standing TODO comment about a slightly inaccurate clock. This should now be fixed. * The SAM D2x/D5x chips may have a slightly more accurate clock, although probably within the error margin of the RTC. Also, by working with RTC ticks and converting in the least number of places, code size is often slightly reduced (usually just a few bytes, up to around 1kB in some cases). * I believe the HiFive1 rev B timer was slightly wrong (32768Hz vs 30517.6Hz). Because the datasheet says the clock runs at 32768Hz, I've used the same conversion code here as in the nrf and sam cases. * I couldn't test both stm32 timers, so I kept them as they currently are. It may be possible to make them more efficient by using the native tick frequency instead of using microseconds everywhere. --- src/runtime/runtime.go | 2 +- src/runtime/runtime_arm7tdmi.go | 10 +++++-- src/runtime/runtime_atsamd21.go | 35 +++++++++++++++++-------- src/runtime/runtime_atsamd51.go | 31 +++++++++++++++------- src/runtime/runtime_avr.go | 12 +++++++-- src/runtime/runtime_cortexm_qemu.go | 10 +++++-- src/runtime/runtime_fe310_baremetal.go | 16 ++++++++++- src/runtime/runtime_fe310_qemu.go | 12 ++++++--- src/runtime/runtime_nrf.go | 22 +++++++++++++--- src/runtime/runtime_stm32f103xx.go | 10 +++++-- src/runtime/runtime_stm32f407.go | 10 +++++-- src/runtime/runtime_tinygoriscv_qemu.go | 10 +++++-- src/runtime/runtime_unix.go | 13 +++++++-- src/runtime/runtime_wasm.go | 15 +++++++++-- src/runtime/scheduler.go | 6 ++--- src/runtime/scheduler_any.go | 2 +- src/runtime/scheduler_none.go | 2 +- 17 files changed, 168 insertions(+), 50 deletions(-) diff --git a/src/runtime/runtime.go b/src/runtime/runtime.go index 41d0b9ac..3a4a8cd8 100644 --- a/src/runtime/runtime.go +++ b/src/runtime/runtime.go @@ -61,7 +61,7 @@ func memequal(x, y unsafe.Pointer, n uintptr) bool { } func nanotime() int64 { - return int64(ticks()) * tickMicros + return ticksToNanoseconds(ticks()) } // timeOffset is how long the monotonic clock started after the Unix epoch. It diff --git a/src/runtime/runtime_arm7tdmi.go b/src/runtime/runtime_arm7tdmi.go index 0c36be54..70b76404 100644 --- a/src/runtime/runtime_arm7tdmi.go +++ b/src/runtime/runtime_arm7tdmi.go @@ -9,8 +9,6 @@ import ( type timeUnit int64 -const tickMicros = 1 - func putchar(c byte) { // dummy, TODO } @@ -60,6 +58,14 @@ func preinit() { } } +func ticksToNanoseconds(ticks timeUnit) int64 { + return int64(ticks) +} + +func nanosecondsToTicks(ns int64) timeUnit { + return timeUnit(ns) +} + func ticks() timeUnit { // TODO return 0 diff --git a/src/runtime/runtime_atsamd21.go b/src/runtime/runtime_atsamd21.go index 17bba380..1055db0c 100644 --- a/src/runtime/runtime_atsamd21.go +++ b/src/runtime/runtime_atsamd21.go @@ -231,9 +231,6 @@ func waitForSync() { } } -// treat all ticks params coming from runtime as being in microseconds -const tickMicros = 1000 - var ( timestamp timeUnit // ticks since boottime timerLastCounter uint64 @@ -243,6 +240,22 @@ var timerWakeup volatile.Register8 const asyncScheduler = false +// ticksToNanoseconds converts RTC ticks (at 32768Hz) to nanoseconds. +func ticksToNanoseconds(ticks timeUnit) int64 { + // The following calculation is actually the following, but with both sides + // reduced to reduce the risk of overflow: + // ticks * 1e9 / 32768 + return int64(ticks) * 1953125 / 64 +} + +// nanosecondsToTicks converts nanoseconds to RTC ticks (running at 32768Hz). +func nanosecondsToTicks(ns int64) timeUnit { + // The following calculation is actually the following, but with both sides + // reduced to reduce the risk of overflow: + // ns * 32768 / 1e9 + return timeUnit(ns * 64 / 1953125) +} + // sleepTicks should sleep for d number of microseconds. func sleepTicks(d timeUnit) { for d != 0 { @@ -259,22 +272,22 @@ func ticks() timeUnit { sam.RTC_MODE0.READREQ.Set(sam.RTC_MODE0_READREQ_RREQ) waitForSync() - rtcCounter := (uint64(sam.RTC_MODE0.COUNT.Get()) * 305) / 10 // each counter tick == 30.5us - offset := (rtcCounter - timerLastCounter) // change since last measurement + rtcCounter := uint64(sam.RTC_MODE0.COUNT.Get()) // each counter tick == 30.5us + offset := (rtcCounter - timerLastCounter) // change since last measurement timerLastCounter = rtcCounter - timestamp += timeUnit(offset) // TODO: not precise + timestamp += timeUnit(offset) return timestamp } // ticks are in microseconds func timerSleep(ticks uint32) { timerWakeup.Set(0) - if ticks < 214 { - // due to around 183us delay waiting for the register value to sync, the minimum sleep value - // for the SAMD21 is 214us. + if ticks < 7 { + // Due to around 6 clock ticks delay waiting for the register value to + // sync, the minimum sleep value for the SAMD21 is 214us. // For related info, see: // https://community.atmel.com/comment/2507091#comment-2507091 - ticks = 214 + ticks = 7 } // request read of count @@ -283,7 +296,7 @@ func timerSleep(ticks uint32) { // set compare value cnt := sam.RTC_MODE0.COUNT.Get() - sam.RTC_MODE0.COMP0.Set(uint32(cnt) + (ticks * 10 / 305)) // each counter tick == 30.5us + sam.RTC_MODE0.COMP0.Set(uint32(cnt) + ticks) waitForSync() // enable IRQ for CMP0 compare diff --git a/src/runtime/runtime_atsamd51.go b/src/runtime/runtime_atsamd51.go index 93ae1216..dd9da121 100644 --- a/src/runtime/runtime_atsamd51.go +++ b/src/runtime/runtime_atsamd51.go @@ -219,9 +219,6 @@ func waitForSync() { } } -// treat all ticks params coming from runtime as being in microseconds -const tickMicros = 1000 - var ( timestamp timeUnit // ticks since boottime timerLastCounter uint64 @@ -231,6 +228,22 @@ var timerWakeup volatile.Register8 const asyncScheduler = false +// ticksToNanoseconds converts RTC ticks (at 32768Hz) to nanoseconds. +func ticksToNanoseconds(ticks timeUnit) int64 { + // The following calculation is actually the following, but with both sides + // reduced to reduce the risk of overflow: + // ticks * 1e9 / 32768 + return int64(ticks) * 1953125 / 64 +} + +// nanosecondsToTicks converts nanoseconds to RTC ticks (running at 32768Hz). +func nanosecondsToTicks(ns int64) timeUnit { + // The following calculation is actually the following, but with both sides + // reduced to reduce the risk of overflow: + // ns * 32768 / 1e9 + return timeUnit(ns * 64 / 1953125) +} + // sleepTicks should sleep for d number of microseconds. func sleepTicks(d timeUnit) { for d != 0 { @@ -245,22 +258,22 @@ func sleepTicks(d timeUnit) { func ticks() timeUnit { waitForSync() - rtcCounter := (uint64(sam.RTC_MODE0.COUNT.Get()) * 305) / 10 // each counter tick == 30.5us - offset := (rtcCounter - timerLastCounter) // change since last measurement + rtcCounter := uint64(sam.RTC_MODE0.COUNT.Get()) + offset := (rtcCounter - timerLastCounter) // change since last measurement timerLastCounter = rtcCounter - timestamp += timeUnit(offset) // TODO: not precise + timestamp += timeUnit(offset) return timestamp } // ticks are in microseconds func timerSleep(ticks uint32) { timerWakeup.Set(0) - if ticks < 260 { + if ticks < 8 { // due to delay waiting for the register value to sync, the minimum sleep value // for the SAMD51 is 260us. // For related info for SAMD21, see: // https://community.atmel.com/comment/2507091#comment-2507091 - ticks = 260 + ticks = 8 } // request read of count @@ -269,7 +282,7 @@ func timerSleep(ticks uint32) { // set compare value cnt := sam.RTC_MODE0.COUNT.Get() - sam.RTC_MODE0.COMP[0].Set(uint32(cnt) + (ticks * 10 / 305)) // each counter tick == 30.5us + sam.RTC_MODE0.COMP[0].Set(uint32(cnt) + ticks) // enable IRQ for CMP0 compare sam.RTC_MODE0.INTENSET.SetBits(sam.RTC_MODE0_INTENSET_CMP0) diff --git a/src/runtime/runtime_avr.go b/src/runtime/runtime_avr.go index 18a76396..ae8a4d41 100644 --- a/src/runtime/runtime_avr.go +++ b/src/runtime/runtime_avr.go @@ -14,8 +14,6 @@ type timeUnit uint32 var currentTime timeUnit -const tickMicros = 1024 * 16384 - // Watchdog timer periods. These can be off by a large margin (hence the jump // between 64ms and 125ms which is not an exact double), so don't rely on this // for accurate time keeping. @@ -71,6 +69,16 @@ func putchar(c byte) { const asyncScheduler = false +const tickNanos = 1024 * 16384 // roughly 16ms in nanoseconds + +func ticksToNanoseconds(ticks timeUnit) int64 { + return int64(ticks) * tickNanos +} + +func nanosecondsToTicks(ns int64) timeUnit { + return timeUnit(ns / tickNanos) +} + // Sleep this number of ticks of 16ms. // // TODO: not very accurate. Improve accuracy by calibrating on startup and every diff --git a/src/runtime/runtime_cortexm_qemu.go b/src/runtime/runtime_cortexm_qemu.go index 1686c2d8..eed20156 100644 --- a/src/runtime/runtime_cortexm_qemu.go +++ b/src/runtime/runtime_cortexm_qemu.go @@ -13,8 +13,6 @@ import ( type timeUnit int64 -const tickMicros = 1 - var timestamp timeUnit func postinit() {} @@ -29,6 +27,14 @@ func main() { const asyncScheduler = false +func ticksToNanoseconds(ticks timeUnit) int64 { + return int64(ticks) +} + +func nanosecondsToTicks(ns int64) timeUnit { + return timeUnit(ns) +} + func sleepTicks(d timeUnit) { // TODO: actually sleep here for the given time. timestamp += d diff --git a/src/runtime/runtime_fe310_baremetal.go b/src/runtime/runtime_fe310_baremetal.go index e04560c4..4fa62e69 100644 --- a/src/runtime/runtime_fe310_baremetal.go +++ b/src/runtime/runtime_fe310_baremetal.go @@ -6,7 +6,21 @@ import ( "device/riscv" ) -const tickMicros = 32768 // RTC clock runs at 32.768kHz +// ticksToNanoseconds converts RTC ticks (at 32768Hz) to nanoseconds. +func ticksToNanoseconds(ticks timeUnit) int64 { + // The following calculation is actually the following, but with both sides + // reduced to reduce the risk of overflow: + // ticks * 1e9 / 32768 + return int64(ticks) * 1953125 / 64 +} + +// nanosecondsToTicks converts nanoseconds to RTC ticks (running at 32768Hz). +func nanosecondsToTicks(ns int64) timeUnit { + // The following calculation is actually the following, but with both sides + // reduced to reduce the risk of overflow: + // ns * 32768 / 1e9 + return timeUnit(ns * 64 / 1953125) +} func abort() { // lock up forever diff --git a/src/runtime/runtime_fe310_qemu.go b/src/runtime/runtime_fe310_qemu.go index 132515e7..ce14abfd 100644 --- a/src/runtime/runtime_fe310_qemu.go +++ b/src/runtime/runtime_fe310_qemu.go @@ -7,12 +7,18 @@ import ( "unsafe" ) -const tickMicros = 100 // CLINT.MTIME increments every 100ns - // Special memory-mapped device to exit tests, created by SiFive. var testExit = (*volatile.Register32)(unsafe.Pointer(uintptr(0x100000))) -var timestamp timeUnit +// ticksToNanoseconds converts CLINT ticks (at 100ns per tick) to nanoseconds. +func ticksToNanoseconds(ticks timeUnit) int64 { + return int64(ticks) * 100 +} + +// nanosecondsToTicks converts nanoseconds to CLINT ticks (at 100ns per tick). +func nanosecondsToTicks(ns int64) timeUnit { + return timeUnit(ns / 100) +} func abort() { // Signal a successful exit. diff --git a/src/runtime/runtime_nrf.go b/src/runtime/runtime_nrf.go index c3cde002..9a0678c6 100644 --- a/src/runtime/runtime_nrf.go +++ b/src/runtime/runtime_nrf.go @@ -12,8 +12,6 @@ import ( type timeUnit int64 -const tickMicros = 1024 * 32 - //go:linkname systemInit SystemInit func systemInit() @@ -64,7 +62,7 @@ func sleepTicks(d timeUnit) { for d != 0 { ticks() // update timestamp ticks := uint32(d) & 0x7fffff // 23 bits (to be on the safe side) - rtc_sleep(ticks) // TODO: not accurate (must be d / 30.5175...) + rtc_sleep(ticks) d -= timeUnit(ticks) } } @@ -74,6 +72,22 @@ var ( rtcLastCounter uint32 // 24 bits ticks ) +// ticksToNanoseconds converts RTC ticks (at 32768Hz) to nanoseconds. +func ticksToNanoseconds(ticks timeUnit) int64 { + // The following calculation is actually the following, but with both sides + // reduced to reduce the risk of overflow: + // ticks * 1e9 / 32768 + return int64(ticks) * 1953125 / 64 +} + +// nanosecondsToTicks converts nanoseconds to RTC ticks (running at 32768Hz). +func nanosecondsToTicks(ns int64) timeUnit { + // The following calculation is actually the following, but with both sides + // reduced to reduce the risk of overflow: + // ns * 32768 / 1e9 + return timeUnit(ns * 64 / 1953125) +} + // Monotonically increasing numer of ticks since start. // // Note: very long pauses between measurements (more than 8 minutes) may @@ -83,7 +97,7 @@ func ticks() timeUnit { rtcCounter := uint32(nrf.RTC1.COUNTER.Get()) offset := (rtcCounter - rtcLastCounter) & 0xffffff // change since last measurement rtcLastCounter = rtcCounter - timestamp += timeUnit(offset) // TODO: not precise + timestamp += timeUnit(offset) return timestamp } diff --git a/src/runtime/runtime_stm32f103xx.go b/src/runtime/runtime_stm32f103xx.go index 8c29c264..7407fa73 100644 --- a/src/runtime/runtime_stm32f103xx.go +++ b/src/runtime/runtime_stm32f103xx.go @@ -53,8 +53,6 @@ func initCLK() { } } -const tickMicros = 1000 - var ( timestamp timeUnit // microseconds since boottime timerLastCounter uint64 @@ -109,6 +107,14 @@ func initTIM() { const asyncScheduler = false +func ticksToNanoseconds(ticks timeUnit) int64 { + return int64(ticks) * 1000 +} + +func nanosecondsToTicks(ns int64) timeUnit { + return timeUnit(ns / 1000) +} + // sleepTicks should sleep for specific number of microseconds. func sleepTicks(d timeUnit) { for d != 0 { diff --git a/src/runtime/runtime_stm32f407.go b/src/runtime/runtime_stm32f407.go index fd1be5b4..626dabd9 100644 --- a/src/runtime/runtime_stm32f407.go +++ b/src/runtime/runtime_stm32f407.go @@ -109,8 +109,6 @@ func initCLK() { } -const tickMicros = 1000 - var ( // tick in milliseconds tickCount timeUnit @@ -118,6 +116,14 @@ var ( var timerWakeup volatile.Register8 +func ticksToNanoseconds(ticks timeUnit) int64 { + return int64(ticks) * 1000 +} + +func nanosecondsToTicks(ns int64) timeUnit { + return timeUnit(ns / 1000) +} + // Enable the TIM3 clock.(sleep count) func initTIM3() { stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_TIM3EN) diff --git a/src/runtime/runtime_tinygoriscv_qemu.go b/src/runtime/runtime_tinygoriscv_qemu.go index 1155d553..27caa398 100644 --- a/src/runtime/runtime_tinygoriscv_qemu.go +++ b/src/runtime/runtime_tinygoriscv_qemu.go @@ -13,8 +13,6 @@ import ( type timeUnit int64 -const tickMicros = 1 - var timestamp timeUnit func postinit() {} @@ -28,6 +26,14 @@ func main() { const asyncScheduler = false +func ticksToNanoseconds(ticks timeUnit) int64 { + return int64(ticks) +} + +func nanosecondsToTicks(ns int64) timeUnit { + return timeUnit(ns) +} + func sleepTicks(d timeUnit) { // TODO: actually sleep here for the given time. timestamp += d diff --git a/src/runtime/runtime_unix.go b/src/runtime/runtime_unix.go index a23d9e0e..e1adeb34 100644 --- a/src/runtime/runtime_unix.go +++ b/src/runtime/runtime_unix.go @@ -26,8 +26,6 @@ func clock_gettime(clk_id int32, ts *timespec) type timeUnit int64 -const tickMicros = 1 - // Note: tv_sec and tv_nsec vary in size by platform. They are 32-bit on 32-bit // systems and 64-bit on 64-bit systems (at least on macOS/Linux), so we can // simply use the 'int' type which does the same. @@ -57,7 +55,18 @@ func putchar(c byte) { const asyncScheduler = false +func ticksToNanoseconds(ticks timeUnit) int64 { + // The OS API works in nanoseconds so no conversion necessary. + return int64(ticks) +} + +func nanosecondsToTicks(ns int64) timeUnit { + // The OS API works in nanoseconds so no conversion necessary. + return timeUnit(ns) +} + func sleepTicks(d timeUnit) { + // timeUnit is in nanoseconds, so need to convert to microseconds here. usleep(uint(d) / 1000) } diff --git a/src/runtime/runtime_wasm.go b/src/runtime/runtime_wasm.go index 0b955c75..3cddd9d8 100644 --- a/src/runtime/runtime_wasm.go +++ b/src/runtime/runtime_wasm.go @@ -6,8 +6,6 @@ import "unsafe" type timeUnit float64 // time in milliseconds, just like Date.now() in JavaScript -const tickMicros = 1000000 - // Implements __wasi_ciovec_t and __wasi_iovec_t. type wasiIOVec struct { buf unsafe.Pointer @@ -68,6 +66,19 @@ func go_scheduler() { const asyncScheduler = true +func ticksToNanoseconds(ticks timeUnit) int64 { + // The JavaScript API works in float64 milliseconds, so convert to + // nanoseconds first before converting to a timeUnit (which is a float64), + // to avoid precision loss. + return int64(ticks * 1e6) +} + +func nanosecondsToTicks(ns int64) timeUnit { + // The JavaScript API works in float64 milliseconds, so convert to timeUnit + // (which is a float64) first before dividing, to avoid precision loss. + return timeUnit(ns) / 1e6 +} + // This function is called by the scheduler. // Schedule a call to runtime.scheduler, do not actually sleep. //export runtime.sleepTicks diff --git a/src/runtime/scheduler.go b/src/runtime/scheduler.go index 49cf8e40..a8c9f16b 100644 --- a/src/runtime/scheduler.go +++ b/src/runtime/scheduler.go @@ -75,14 +75,14 @@ func runqueuePushBack(t *task.Task) { } // Add this task to the sleep queue, assuming its state is set to sleeping. -func addSleepTask(t *task.Task, duration int64) { +func addSleepTask(t *task.Task, duration timeUnit) { if schedulerDebug { - println(" set sleep:", t, uint(duration/tickMicros)) + println(" set sleep:", t, duration) if t.Next != nil { panic("runtime: addSleepTask: expected next task to be nil") } } - t.Data = uint(duration / tickMicros) // TODO: longer durations + t.Data = uint(duration) // TODO: longer durations now := ticks() if sleepQueue == nil { scheduleLog(" -> sleep new queue") diff --git a/src/runtime/scheduler_any.go b/src/runtime/scheduler_any.go index 41a90453..fb7ca6e9 100644 --- a/src/runtime/scheduler_any.go +++ b/src/runtime/scheduler_any.go @@ -7,7 +7,7 @@ import "internal/task" // Pause the current task for a given time. //go:linkname sleep time.Sleep func sleep(duration int64) { - addSleepTask(task.Current(), duration) + addSleepTask(task.Current(), nanosecondsToTicks(duration)) task.Pause() } diff --git a/src/runtime/scheduler_none.go b/src/runtime/scheduler_none.go index d462ca15..e40615fe 100644 --- a/src/runtime/scheduler_none.go +++ b/src/runtime/scheduler_none.go @@ -4,7 +4,7 @@ package runtime //go:linkname sleep time.Sleep func sleep(duration int64) { - sleepTicks(timeUnit(duration / tickMicros)) + sleepTicks(nanosecondsToTicks(duration)) } // getSystemStackPointer returns the current stack pointer of the system stack.