6502 Machine Code on an Apple II, 10 9 bytes
Code is actually platform-independent, other than it relies on the Apple's clock speed of 1.023 MHz for timing.
Code starts at address 0x0000:
0000: A2 CA F6 44 F0 FB D0 F8 F7
Saved an additional byte. Details are below original answer.
Original answer:
Code starts at address 0x0059:
0059: A2 08 F6 60 D0 FA CA D0 F9 F5
Disassembly:
loop1: 0059- A2 nn LDX #$nn ; 2 cyc
loop2: 005B- F6 60 INC $60,X ; 6 cyc
005D- D0 FA BNE loop1 ; 2-3 cyc
005F- CA DEX ; 2 cyc
0060- D0 F9 BNE loop2 ; 2-3 cyc
0062- pp DB $pp ; data byte
This is a fairly simple routine that increments a multi-byte counter. When the counter rolls over, the last branch instruction gets modified so that it points to an RTS instruction, which provides the exit for the routine.
loop1 is taken for each increment of the counter and takes 11 cycles per iteration. loop2 is taken for each byte carried over and takes 13 cycles per iteration. So if we increment the counter N times, we spend approximately:
11*N + 13*(1/256 + 1/(256^2) + 1/(256^3) + ...)*N
= 11.05*N cycles
1000 years is 365242*86400*1023000 = 32282717702400000 cycles
So we need N = 2921512914244345 +/- 10%
Or a range of 2629361622819911 - 3213664205668779
= 0x095763F583A447 - 0x0B6ACF816801AB
In the code above, set pp = 0xF5 and nn = 0x08.
This gives us a 7-byte counter in memory locations 0x62-0x68
(with MSB at lowest address, i.e. big endian). Only location 0x62
is initialized, so our starting counter value could be anywhere
from 0xF5000000000000 to 0xF5FFFFFFFFFFFF.
We'll increment the counter until it rolls over to 0, which
will cause the byte at 0x61 to increment by 1, which happens
to be the branch target for loop2. On the first byte carry
after rollover-- when the counter hits 0x100-- we'll hit the
modified branch instruction for the first time. This will take
us to address 0x5C (loop2+1). The 0x60 byte there is the opcode
for "Return from Subroutine" (RTS) which provides our exit.
So our total loop count is between, 0x0A000000000101 and
0x0B000000000100, which is a subset of the range we calculated
which gives us the necessary number of cycles +/- 10%.
Now that we have the exact starting and ending counter values,
we could go back and calculate the exact cycle counts, but given
how much margin there is, I'm willing to hand-wave that part.
You can actually test it out with smaller values of nn. For example nn of 4 will pause for several seconds.
9-byte answer:
0000- A2 CA LDX #$CA ; 2 cyc
0002- F6 44 INC $44,X ; 6 cyc
0004- F0 FB BEQ $0001 ; 2-3 cyc (rollover)
0006- D0 F8 BNE $0000 ; 2-3 cyc (non-rollover)
0008- F7 DB $F7 ; data byte
Saved one byte by folding the DEX opcode into the
argument for LDX.
Instead of 11.05 cycles, each counter increment is now
13 + 11*(1/256 + 1/(256^2) + ...) = 13.043 cycles.
Now we need 2475073064976549 +/- 10% iterations,
or a range of 0x7E9F591BF7A2E - 0x9AC2C23EA071C.
So now we initialize the 7-byte counter to something
between 0xF7000000000000 and 0xF7FFFFFFFFFFFF. This ends
up giving between 0x08000000000001 and 0x09000000000000
loop iterations, which is within the +/-10% needed.
When the counter rolls over to 0, the last branch
gets modified to jump to $0001, which leads to an increment
of the BNE opcode itself (to a CMP instruction, which
for our purpose is effectively a no-op). Code then falls
through to $0008, which now contains a 0 (because of counter
rollover). A 0 byte is a BRK instruction, which drops you
back to the system monitor, ending the routine.
fortune
CLI app) \$\endgroup\$Because it's too time-consuming to test,
: If your implementation is based on polling a real‑time clock (RTC), you may simply adjust the RTC to a date/time about 1 kiloyears in the future. In UNIX‑like environments like Linux or FreeBSD you can invoke thedate
command‑line utility to that end.What if you need a larger delay?
: Easy, employ time dilation. 😉 \$\endgroup\$