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.
Этот коммит содержится в:
родитель
95f509b109
коммит
3c55689566
17 изменённых файлов: 168 добавлений и 50 удалений
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
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
|
||||
|
|
|
@ -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
|
||||
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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
Загрузка…
Создание таблицы
Сослаться в новой задаче