Close

Time

A project log for Novasaur CP/M TTL Retrocomputer

Retrocomputer built from TTL logic running CP/M with no CPU or ALU

alastair-hewittAlastair Hewitt 03/31/2022 at 00:190 Comments

The realtime clock (RTC) was introduced in this log. There was mention of instructions to get the time in a more convenient format. That code was written about a year ago, but has only just been integrated as various loose ends are being tied up.

The RTC counters are reset on power up or cold boot. The clock then keeps track of time with an accuracy of around 14ppm. The first application was to add an UPTIME command to the monitor. This displays the time since power up (cold boot) in the format DDD HH:MM:SS

Clock Division

The hardware abstraction layer (HAL) divides down the 8.25MHz CPU clock by a total of 182,474,244,096,000. This is done via the following steps:

Real-Time Clock

The realtime clock consists of the four bytes: TIME0-TIME3. As shown above, these don't count typical increments like seconds, minutes, hours etc. They are optimized for 7-bit arithmetic and the ability to update everything within one additional virtual-machine cycle. These counters are updated at the following rates:

One other thing to note. The counts for TIME0-TIME2 are negative and count up to zero. This is done to simplify the overflow condition and just test the sign bit. The final count TIME3 is the only positive count and represents the number of complete days the machine has been on.

Calculating Time

A general formula can be used to calculate the actual time unit:

Where max is the highest value of that unit held by the counter and ticks are the ticks per unit. This leads to the following formulas for the standard time units:

However, these formulas assume the counts are positive numbers. In this case they are not and this type of math will not work.

Modular Arithmetic

There are two approaches to dealing with the negative counts: convert the numbers to their positive versions, or use the negative versions and then convert the result. An important observation here is the fact the results will be sexagesimal. This allows the use of modular or clock arithmetic and favors the latter method.

The counts are considered to be positive 8-bit integers and adjusted with a final offset to wrap to the correct sexagesimal number using a MOD60 operation:

This may look more complex, but it can be efficiently coded to fit in a single 2-cycle extended instruction (utilizing a jump page in the 0xED range of instructions). The MOD60 unary function is also used to convert the result to binary-coded decimal (BCD), or more specifically, binary-coded sexagesimal

Sexagesimal Adjust Accumulator

The reason for converting the result to BCD is the typical use case where the time displayed as a timestamp. The realtime clock is reset to zero on cold boot and the current time can be calculated by adding the time the machine started to the current uptime tracked by the realtime clock.

This requires the addition of two BCD numbers and is possible with the use of the Decimal Adjust Accumulator (DAA) instruction. This will adjust the result back to BCD after the addition, along with setting the carry from the 100's column.

The same technique can be used to add times with a Sexagesimal Adjust Accumulator (SAA) instruction. This is similar to DAA except the carry is generated when the most significant digit is greater than 5 rather than 9. The result is mod-60 rather than mod-100.

Kernel Timers

One additional use of the realtime clock is to addition of timers to the kernel. These can be programmed to trigger at some point in the future based on a number of ticks. The concept is fairly simple: Each time the kernel main loop runs it will check to see if TIME0 has changed. If it has then the kernel will check the timers.

Each timer consists of a count and an address to call. Each time the kernel detects a tick it will decrement the counters and check to see if any have reached zero. When a timer reaches zero the kernel will delete the timer and execute the code specified by the address. The timer code can add the timer back again for a repetitive event.

Timers are added to a heap and the kernel will scan the entire heap to find expired timers. The timer is deleted by replacing the expired timer with the timer removed from the top of the heap. When the expired timer is at the top of the heap then there are no more timers.

Discussions