Browse Source

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.
pull/1136/head
Ayke van Laethem 5 years ago
committed by Ron Evans
parent
commit
3c55689566
  1. 2
      src/runtime/runtime.go
  2. 10
      src/runtime/runtime_arm7tdmi.go
  3. 35
      src/runtime/runtime_atsamd21.go
  4. 31
      src/runtime/runtime_atsamd51.go
  5. 12
      src/runtime/runtime_avr.go
  6. 10
      src/runtime/runtime_cortexm_qemu.go
  7. 16
      src/runtime/runtime_fe310_baremetal.go
  8. 12
      src/runtime/runtime_fe310_qemu.go
  9. 22
      src/runtime/runtime_nrf.go
  10. 10
      src/runtime/runtime_stm32f103xx.go
  11. 10
      src/runtime/runtime_stm32f407.go
  12. 10
      src/runtime/runtime_tinygoriscv_qemu.go
  13. 13
      src/runtime/runtime_unix.go
  14. 15
      src/runtime/runtime_wasm.go
  15. 6
      src/runtime/scheduler.go
  16. 2
      src/runtime/scheduler_any.go
  17. 2
      src/runtime/scheduler_none.go

2
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

10
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

35
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

31
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)

12
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

10
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

16
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

12
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.

22
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
}

10
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 {

10
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)

10
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

13
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)
}

15
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

6
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")

2
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()
}

2
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.

Loading…
Cancel
Save