diff options
author | Ayke van Laethem <[email protected]> | 2020-05-21 23:43:08 +0200 |
---|---|---|
committer | Ron Evans <[email protected]> | 2020-05-25 22:08:28 +0200 |
commit | 3c55689566153047f1d2e5403188557481f90d42 (patch) | |
tree | 55bd119f08ba39f30d7a0351a8f99b31c76ff3c9 /src/runtime | |
parent | 95f509b109d4936bec9b6020cb34fbb4bd5cba16 (diff) | |
download | tinygo-3c55689566153047f1d2e5403188557481f90d42.tar.gz tinygo-3c55689566153047f1d2e5403188557481f90d42.zip |
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.
Diffstat (limited to 'src/runtime')
-rw-r--r-- | src/runtime/runtime.go | 2 | ||||
-rw-r--r-- | src/runtime/runtime_arm7tdmi.go | 10 | ||||
-rw-r--r-- | src/runtime/runtime_atsamd21.go | 35 | ||||
-rw-r--r-- | src/runtime/runtime_atsamd51.go | 31 | ||||
-rw-r--r-- | src/runtime/runtime_avr.go | 12 | ||||
-rw-r--r-- | src/runtime/runtime_cortexm_qemu.go | 10 | ||||
-rw-r--r-- | src/runtime/runtime_fe310_baremetal.go | 16 | ||||
-rw-r--r-- | src/runtime/runtime_fe310_qemu.go | 12 | ||||
-rw-r--r-- | src/runtime/runtime_nrf.go | 22 | ||||
-rw-r--r-- | src/runtime/runtime_stm32f103xx.go | 10 | ||||
-rw-r--r-- | src/runtime/runtime_stm32f407.go | 10 | ||||
-rw-r--r-- | src/runtime/runtime_tinygoriscv_qemu.go | 10 | ||||
-rw-r--r-- | src/runtime/runtime_unix.go | 13 | ||||
-rw-r--r-- | src/runtime/runtime_wasm.go | 15 | ||||
-rw-r--r-- | src/runtime/scheduler.go | 6 | ||||
-rw-r--r-- | src/runtime/scheduler_any.go | 2 | ||||
-rw-r--r-- | 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 41d0b9ace..3a4a8cd8d 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 0c36be540..70b764042 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 17bba3802..1055db0c6 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 93ae1216a..dd9da121f 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 18a763966..ae8a4d41e 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 1686c2d8e..eed201563 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 e04560c4a..4fa62e69a 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 132515e79..ce14abfdd 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 c3cde0026..9a0678c6d 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 8c29c264b..7407fa738 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 fd1be5b43..626dabd94 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 1155d553e..27caa398a 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 a23d9e0e6..e1adeb342 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 0b955c756..3cddd9d8e 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 49cf8e402..a8c9f16b1 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 41a904535..fb7ca6e96 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 d462ca15c..e40615fe8 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. |