From
Ivan Roshin
→
To
All
2 April 2002
Hello, All!
═══════════════════ wait_1 .W ══════════════════
(c) Ivan Roshchin, Moscow
Fido: 2:5020/689.53
ZXNet: 500:95/462.53
E-mail: asder_ffc@softhome.net
WWW: http://www.ivr.da.ru
Programming delays in Z80 assembler
═════════════════════ ══════════════════════
("Radiomir. Your computer" 3/2002)
When writing programs for low-level work with external
devices there is often a need to program
delay for a certain time. To do this you can either
use a timer (if you have one), or insert it into the program
fragment whose execution time is equal to the required time
delays. I will not consider the first method here, but about
I’ll tell you more about the second one.
Each command of the Z80 processor takes a certain amount of time to complete.
number of cycles. Knowing the required delay in clock cycles,
you can write a program fragment with the appropriate time
execution. (I’ll immediately note that such a fragment should work
when interrupts are disabled.)
Formula for calculating the delay time in clock cycles:
N=F*t
where N is the number of clock cycles,
F - processor clock frequency,
t - delay time.
Since the command execution time in the Z80 processor can be
expressed only in an integer number of cycles, the resulting result
should be rounded to the nearest whole number.Example: let's say you need a delay of 5 ms, a clock frequency of 3.5
MHz. Then the required number of cycles is:
5*10^-3 s * 3.5*10^6 Hz = 17500.
Let's start by programming small delays. Minimum
the delay value corresponds to the shortest execution time
Z80 commands - 4 cycles.
For a 4-cycle delay, the NOP command is ideal - "no
operations." Its execution does not depend on the values of the flags and
registers, and it does not perform any actions (in other words,
has no side effects).
Using a chain of NOPs it is easy to get a delay of 8, 12,
16,... cycles (i.e. for a number of the form 4N).
A delay of 5 clock cycles can be obtained using the command
conditional exit from the subroutine in the case when the condition is not
is running. There are eight such teams:
RET NC (output at C=0)
RET C (output at C=1)
RET NZ (output at Z=0)
RET Z (output at Z=1)
RET P (output at S=0)
RET M (output at S=1)
RET PO (output at P/V=0)
RET PE (output at P/V=1)
They have no side effects. By adding NOPs, you can
get a delay of 9, 13, 17,... cycles (i.e. by a number of the form
4N+1).
Obviously, to use one of the above
commands need to know the state of at least one of the fourflags (C, Z, S or P/V) by the time it is executed. If the condition
flags are unknown, then a delay of 5 clock cycles cannot be obtained, but
at 9, 13, 17,... - you can. To do this, you must first set the flag
transfer using the SCF command (its execution time is 4 clock cycles),
then use the RET NC command, and then when
need to add NOPs. Setting the carry flag will be in
in this case a side effect.
A delay of 6 cycles can be obtained using one of
the following commands:
INC BC DEC BC
INC DE DEC DE
INC HL DEC HL
INC SP DEC SP
By adding NOPs, you can get a delay of 10, 14, 18,...
cycles (i.e. for a number of the form 4N+2).
The above commands do not affect the flags, but they do change
the contents of the corresponding register pair. If none of
four pairs cannot be changed, then you will get a delay of 6 clock cycles
it is impossible. But at 10, 14, 18,... cycles - you can, using the command
unconditional transition to the next command (JP $+3), on
the execution of which takes 10 cycles, and added when
necessary NOP commands.
The 7-cycle delay can be achieved in various ways.
One of them is to use the LD r,(HL) command, where r is one of
registers A,B,C,D,E,H,L. This command does not affect flags, but
changes the contents of the register involved in the operation. If notone register cannot be changed, but flags can be changed, then
The CP (HL) team is suitable. If neither registers nor
flags, - you can use the conditional jump command JR (if
unfulfilled condition). There are four such teams:
JR NC (transition at C=0)
JR C (jump at C=1)
JR NZ (transition at Z=0)
JR Z (transition at Z=1)
Naturally, in order for one of them to be used,
it is necessary to know the state of flag C or Z at the time of its
execution.
By adding NOPs, you can get a delay of 11, 15, 19,...
cycles (i.e. for a number of the form 4N+3).
════════════════════════ ════════════════════════
Best regards, Ivan Roshchin.
From
Ivan Roshin
→
To
All
2 April 2002
Hello, All!
═══════════════════ wait_2 .W ══════════════════
So, based on the above, how can we build
section of the program that provides the required delay? Let x -
the amount of delay in clock cycles. Divide x by 4, get the quotient a and
remainder b. Then b+4 is the length of the first command (4, 5, 6 or 7
clock cycles), and a-1 is the number of added NOPs.
Let's look at this with examples.
Example 1: x=18. Divide by 4: a=4, b=2. So the length of the first
commands will be 2+4=6 clock cycles, and the number of NOPs will be 4-1=3.
The corresponding program fragment will look like this:
INC HL
NOP
NOP
NOP
Example 2: x=29. Divide by 4: a=7, b=1. So the length of the first
commands - 5 clock cycles, number of NOPs - 6. As before
mentioned, a delay of 5 clock cycles is implemented by a conditional RET, and
to use it, you need to know the state of at least one of
four flags. Let's assume that there is nothing about the state of the flags
known. Then you have to do this: using
command sequence SCF: RET NC we get a delay of 9
clock cycles, and there will be one less NOP. As a result we get:
SCF
RET NC
NOP
NOP
NOP
NOP
NOP
Since, generally speaking, NOPs with such programming
there may be quite a lot of delays, so don’t"inflate" the program text and not make mistakes in counting NOPs when
typing, it is convenient to use the DS N assembler directive
(another recording option is DEFS N). It allocates a piece of memory
is N bytes long and (usually) pads it with zeros. Well, 0 is
This is precisely the NOP command code. Just make sure that
the assembler you use fills the area really
zeros. If this is not the case, you will have to specify a null value
explicitly: DS N,0.
Thus, the examples discussed above can be
written like this:
INC HL
DS 3
and
SCF
RET NC
DS 5
If you are unhappy that a long chain of NOPs takes up
there is a lot of memory space, then you can use the following fragment
programs:
LD B,N
DJNZ$
Its execution time is 13N+2 cycles (where N=1..255). So
Thus, if you need a delay of x clock cycles, divide x-2 by 13,
we get the quotient a and the remainder b. The value a shows how much
times the loop needs to be repeated (this is the number loaded into register B), and
b - how many clock cycles remain (delay for this time
will have to be implemented in the usual way). If b=1, 2 or 3 then,
since it is impossible to get a delay for this time, you will have to
decrease a by 1 and increase b by 13 (thereby increasing
remaining delay up to 14, 15 or 16 clock cycles).
Example: x=140. Divide by 13: a=10, b=8. Hence,the number of repetitions of the cycle will be 10; delay for the remaining 8
cycles are provided using two NOP instructions. We get:
LD B,10
DJNZ$
DS 2
════════════════════════ ════════════════════════
Best regards, Ivan Roshchin.
From
Ivan Roshin
→
To
All
2 April 2002
Hello, All!
═══════════════════ wait_3 .W ══════════════════
Now let's move on to considering ways to implement more
long delays. For delays from 188 to 65535 cycles it is convenient
use the following 98 byte procedure:
ORG #8000 ;or other address divisible by 256
TAB_ADR DB ADR_0256
DB ADR_1256
..............
DB ADR_31256
ADR_28 NOP
ADR_24 NOP
ADR_20 NOP
ADR_16 NOP
ADR_12 NOP
ADR_8 NOP
ADR_4 NOP
ADR_0 NOP ;4 clock cycles
RET
ADR_29 NOP
ADR_25 NOP
ADR_21 NOP
ADR_17 NOP
ADR_13 NOP
ADR_9 NOP
ADR_5 NOP
ADR_1 RET NZ ;5 cycles
RET
ADR_30 NOP
ADR_26 NOP
ADR_22 NOP
ADR_18 NOP
ADR_14 NOP
ADR_10 NOP
ADR_6 NOP
ADR_2 INC HL ;6 clock cycles
RET
ADR_31 NOP
ADR_27 NOP
ADR_23 NOP
ADR_19 NOP
ADR_15 NOP
ADR_11 NOP
ADR_7 NOP
ADR_3 LD A,(HL) ;7 clock cycles
RET
WAIT LD DE,-156
ADD HL,DE
LD A,L
AND 31
LD E,A
;To divide HL by 32, just shift HL by 5 bits
;to the right, but there is a shorter and faster way: move HL to
;3 bits to the left, placing the most significant bits of the result in
;accumulator, and then rearrange: A -> H -> L.XOR A
ADD HL,HL
R.L.A.
ADD HL,HL
R.L.A.
ADD HL,HL
R.L.A.
LD L,H
LD H,A
;Loop with execution time 32 cycles:
WAIT_1 DEC HL
LD A,H
OR L
NOP ;for
NOP ;delays
JP NZ,WAIT_1
EX DE,HL
LD H,TAB_ADR/256
LD L,(HL)
JP(HL)
Let me remind you that the operator "" is the calculation of the remainder of
division. In the above procedure it is used to
get the low byte of a double-byte value. If in the used
your assembler does not support this operator, then perhaps
there is a special operator for calculating the low byte, or
the assembler itself discards the high byte when the result
must be single byte. As a last resort, you can use
equality A256=A-((A/256)*256).
════════════════════════ ════════════════════════
Best regards, Ivan Roshchin.
From
Ivan Roshin
→
To
All
2 April 2002
Hello, All!
═══════════════════ wait_4 .W ══════════════════
Required delay duration in clock cycles (from 188 to
65535) is specified in the HL register pair before the call
procedures. It is not necessary to specifically take into account that
execution of commands for loading this number into HL and calling the procedure
will also take some time: this is already taken into account in the
procedure.
If, say, at some point in the program a delay of
500 cycles, just write the following:
LD HL,500
CALL WAIT
As we can see, the use of this procedure does not require
programmer with special knowledge about command execution time. Not
you need to think every time what sequence of commands
implement one or another delay. We put the required number and that’s it!
It couldn't be simpler!
This procedure is also very convenient to use if the exact
the delay value is not known in advance, and you have to “adjust”
it in the process of debugging the program. You can change the delay time
by changing only one number in the program code using a debugger.
During its operation, the procedure changes the values of register pairs
HL and DE, as well as the accumulator and flag register. If this
is undesirable, then, depending on the situation, you can either
save them on the stack and then restore them (with the PUSH commands,
POP), or set for the duration of the procedurean alternative set of registers if there is nothing there
desired (by commands EXX, EX AF,AF'). At the same time, from what is indicated in
HL delay time will need to subtract command execution time
saving and restoring registers (make sure to
the indicated delay time has not become less than the minimum - 188
bars!). Let me remind you: the execution time of the commands PUSH HL, PUSH DE,
PUSH AF - 11 bars; POP HL, POP DE, POP AF - 10 bars; EXX,
EX AF,AF' - 4 bars.
Example: let's say we need a delay of 750 clock cycles, and the values of HL and
DE should not be corrupted (alternative registers too). So,
HL and DE must first be stored on the stack (PUSH HL, PUSH DE), and
after calling the procedure - restore (POP DE, POP HL).
We calculate the execution time of these commands: 11+11+10+10=42 clock cycles.
In the program we write:
PUSH HL ;save
PUSH DE ;registers
LD HL,750-42
CALL WAIT
POP DE ;recovery
POP HL ;registers
Since the WAIT procedure is universal, in case
single use, it will obviously take up more space in
memory than specifically written for a specific implementation
delay program fragment. But if the procedure is called in
many places in the program, with different delay times, then this
will already be more profitable compared to if in every
there was a fragment of the program that implemented the delay.The WAIT procedure could, in principle, be made shorter and
do not bind to an address that is a multiple of 256. But when writing it I
strived to ensure that the minimum delay achieved with its
help was as little as possible, i.e. to increase the area
use of the procedure.
════════════════════════ ════════════════════════
Best regards, Ivan Roshchin.
From
Ivan Roshin
→
To
All
2 April 2002
Hello, All!
═══════════════════ wait_5 .W ══════════════════
Below is the text of another procedure for implementation
longer delays (from 469 to 2^32-1 cycles).
WAIT_LONG PUSH BC
PUSH AF
LD BC,-405
ADD HL,BC
LD BC,-1
EX DE,HL
ADC HL,BC
EX DE,HL
LD A,L
AND 3
ADD A,A
ADD A,A ;*4
ADD A,WAIT_CODE256
LD C,A
LD A,WAIT_CODE/256
ADC A,0
LD B,A
PUSH BC
LD A,L
RRA
RRA
CPL
AND 15
ADD A,WAIT_NOP256
LD C,A
LD A,WAIT_NOP/256
ADC A,0
LD B,A
PUSH BC
;Shift DEHL:
XOR A
ADD HL,HL
EX DE,HL
ADC HL,HL
EX DE,HL
R.L.A.
ADD HL,HL
EX DE,HL
ADC HL,HL
EX DE,HL
R.L.A.
LD L,H
LD H,E
LD E,D
LD D,A
;In DE - the number of repetitions of the cycle (each - 64 cycles).
LD BC,-1
WAIT_L1 ADD HL,BC
INC L ;Checking: L=0?
DEC L ;(without changing the C flag).
JP Z,WAIT_L2EX DE,HL
ADC HL,BC
EX DE,HL
JR WAIT_L1
WAIT_L2 LD A,H ;L=0, and HDE=0?
OR D ;Flag C is reset.
OR E
NOP ;For
NOP ;delays
RET C ;in 4+4+5=13 clock cycles.
JP NZ,WAIT_L1
RET ;Go to the address previously
;pushed onto the stack.
WAIT_NOP DS 15.0 ;15 NOP commands.
RET ;Go to the address previously
;pushed onto the stack.
WAIT_CODE NOP
JP WAIT_EXIT
RET C
JP WAIT_EXIT
INC HL
JP WAIT_EXIT
LD A,(HL)
JP WAIT_EXIT
WAIT_EXIT POP AF
POP B.C.
RET
The length of the procedure is 119 bytes. Required duration
the delay in clock cycles is indicated as follows: the most significant bits are loaded
to the register pair DE, the lower ones to HL. As in the previous one
procedure, execution time of loading and calling commands specifically
does not need to be taken into account.
Example: let's say you need a delay of 100,000 clock cycles. B
in hexadecimal it is #186A0. In the program we write:
LD HL,#0001
LD DE,#86A0
CALL WAIT_LONG
════════════════════════ ════════════════════════
Best regards, Ivan Roshchin.
From
Ivan Roshin
→
To
All
2 April 2002
Hello, All!
═══════════════════ wait_6 .W ══════════════════
If the program already uses the WAIT procedure for
delays less than 469 clock cycles, and the amount of required delay is not
is many times higher than 65535, then you can do without the procedure
WAIT_LONG (thereby saving in program size), simply
placing several calls to the WAIT procedure side by side.
Example: you need a delay of 150,000 clock cycles. Divide 150,000 by
65535, we get the quotient 2 and the remainder 18930. In the program we write:
LD HL,65535
CALL WAIT
LD HL,65535
CALL WAIT
LD HL,18930
CALL WAIT
Finally, I note that the WAIT and WAIT_LONG procedures are not
contain self-modifying areas; therefore it is possible
their firmware is in ROM.
════════════════════════ ════════════════════════
Best regards, Ivan Roshchin.