Paulo do you think there can be an easy way to define additional opcodes for the z80? I am thinking in general here as z80 implementations exist for fpgas and we now have a specific case where new opcodes are being added for a machine.
The zx next, which I am working on now, is adding these instructions:
swapnib ED 23 A bits 7-4 swap with A bits 3-0
mul ED 30 multiply HL*DE = HLDE (no flags set)
add hl,a ED 31 Add A to HL (no flags set)
add de,a ED 32 Add A to DE (no flags set)
add bc,a ED 33 Add A to BC (no flags set)
outinb ED 90 out (c),(hl), hl++
ldix ED A4 As LDI, but if byte==A does not copy
ldirx ED B4 As LDIR, but if byte==A does not copy
lddx ED AC As LDD, but if byte==A does not copy, and DE is incremented
lddrx ED BC As LDDR, but if byte==A does not copy
That's a partial list as more is coming.
I have also been thinking about adding via m4 but that is not ideal because all asm would have to be in an m4 file and this would require changes in zcc. I could also do it in pre-processing with copt.
Yes. I'm currently working on the full Rabbit implementation, and it would be easy to add a new cpu-type to the input files.
You can have a look at the still incomplete parser generator:
https://github.com/z88dk/z88dk/blob/feature/z80asm_rabbit/src/z80asm/tools/cpu.def
https://github.com/z88dk/z88dk/blob/feature/z80asm_rabbit/src/z80asm/tools/cpu.pl
So it's possible to do in mainline z80asm now?
Then the assembler invoke would be "z80asm --cpu=cpuname". Have you thought of any naming to accommodate these devices? Maybe "z80asm --cpu=z80-zxn" (z80 variant of zx next).
Yes. Give me a couple of days.
I agree with your suggestion for the naming. I assume this z80-zxn CPU does not have any of the z180 and rabbit opcodes and it has all the undocumented z80 opcodes. Correct?
Yes everything z80 is in there, it's just the additional instructions and ones yet to come.
Just a note: MUL will be confusing:
And, we're clear to use the MLT instruction with the z180 too?
It seems to generate correct code.
The MLT performs unsigned multiplication on two 8-bit numbers yielding a 16-bit result. MLT may specify BC, DE, HL, or SP registers. The 8-bit operands are loaded into each half of the 16-bit register and the 16-bit result is returned in that register.
Yes z80asm has separate tables for z80, z80-zxn, z180, r2k and r3k.
Another update... sorry this may be a frequent activity now but people find it helpful to get it in.
New Z80 opcodes on the NEXT (more to come)
======================================================================================
T=4+ 8T swapnib ED 23 A bits 7-4 swap with A bits 3-0
T=4+ 8T mirror a ED 24 mirror the bits in A
M=2+ 11T test NN (tst A,NN) ED 27 NN AND A with NN and set all flags. A is not affected.
8T bsla de,b ED 28 shift DE left by B places - uses bits 4..0 of B only
8T bsra de,b ED 29 arithmetic shift right DE by B places - uses bits 4..0 of B only
8T bsrl de,b ED 2A logical shift right DE by B places - uses bits 4..0 of B only
8T bsrf de,b ED 2B shift right DE by B places, filling from left with 1s - uses bits 4..0 of B only
8T brlc de,b ED 2C rotate DE left by B places - uses bits 3..0 of B only
T=4+ 8T mul d,e (mlt de) ED 30 multiply DE = D*E (no flags set)
T=4+ 8T add hl,a ED 31 Add A to HL (no flags set) not sign extended
T=4+ 8T add de,a ED 32 Add A to DE (no flags set) not sign extended
T=4+ 8T add bc,a ED 33 Add A to BC (no flags set) not sign extended
M=3+, T=4 16T add hl,NNNN ED 34 LO HI Add NNNN to HL (no flags set)
M=3+, T=4 16T add de,NNNN ED 35 LO HI Add NNNN to DE (no flags set)
M=3+, T=4 16T add bc,NNNN ED 36 LO HI Add NNNN to BC (no flags set)
M=6+ 23T push NNNN ED 8A HI LO push 16bit immediate value, note big endian order
16T outinb ED 90 outi without modifying B
M=5+ 20T nextreg reg,val ED 91 reg,val Set a NEXT register (like doing out($243b),reg then out($253b),val
M=4+ 17T nextreg reg,a ED 92 reg Set a NEXT register using A (like doing out($243b),reg then out($253b),A )
** reg,val are both 8-bit numbers
T=4+ 8T pixeldn ED 93 move down a line on the ULA screen
T=4+ 8T pixelad ED 94 using D,E (as Y,X) calculate the ULA screen address and store in HL
T=4+ 8T setae ED 95 Using the lower 3 bits of E (X coordinate), set the correct bit value in A
13T jp (c) ED 98 PC[13:0] = IN (C) << 6
M=4+ 16T ldix ED A4 As LDI, but if byte==A does not copy
M=4+ 14T ldws ED A5 (de)=(hl), l++, d++ for layer 2 vertical tile copy
M=4+ 16T lddx ED AC As LDD, but if byte==A does not copy, and DE is incremented
M=4+ 21T ldirx ED B4 As LDIR, but if byte==A does not copy
M=4+ 21T* ldpirx ED B7 (de) = ( (hl&$fff8)+(E&7) ) when != A
M=4+ 21T lddrx ED BC As LDDR, but if byte==A does not copy, and DE is incremented
** Instructions that have been removed due to limited fpga space.
They have been removed from z80asm in the current main branch.
"mul" "ld a32,dehl" "ld dehl,a32" "ex a32, dehl" "ld hl,sp" "inc dehl" "dec dehl" "add dehl,a"
"add dehl,bc" "add dehl,NN" "sub dehl,a" "sub dehl,bc" "popx" "fillde" "ldirscale"
Memory mapping - specify which 8k ram page is placed into
the corresponding 8k slot of the z80's 64k memory space.
Originally, `mmux` were intended as instructions but they have since
been demoted to TBBLUE registers set via `nextreg`. We're keeping
these as effective macros.
20T mmu0 NN ED 91 50 NN macro: Ram page in slot 0-8k
20T mmu1 NN ED 91 51 NN macro: Ram page in slot 8k-16k
20T mmu2 NN ED 91 52 NN macro: Ram page in slot 16k-24k
20T mmu3 NN ED 91 53 NN macro: Ram page in slot 24k-32k
20T mmu4 NN ED 91 54 NN macro: Ram page in slot 32k-40k
20T mmu5 NN ED 91 55 NN macro: Ram page in slot 40k-48k
20T mmu6 NN ED 91 56 NN macro: Ram page in slot 48k-56k
20T mmu7 NN ED 91 57 NN macro: Ram page in slot 56k-64k
17T mmu0 a ED 92 50 macro: Ram page in slot 0-8k
17T mmu1 a ED 92 51 macro: Ram page in slot 8k-16k
17T mmu2 a ED 92 52 macro: Ram page in slot 16k-24k
17T mmu3 a ED 92 53 macro: Ram page in slot 24k-32k
17T mmu4 a ED 92 54 macro: Ram page in slot 32k-40k
17T mmu5 a ED 92 55 macro: Ram page in slot 40k-48k
17T mmu6 a ED 92 56 macro: Ram page in slot 48k-56k
17T mmu7 a ED 92 57 macro: Ram page in slot 56k-64k
* Times are guesses based on other instruction times. All of this subject to change.
md5-4db8024055286b9e4971778eff81ed55
Pseudo-instructions for the copper unit have been defined so that copper instructions can be
generated within z80 asm. The instructions are namespaced with a leading "cu."
so that they are not confused with regular z80 instructions. Each is 16-bits.
cu.wait VER,HOR (0<=VER<=311, 0<=HOR<=55)
cu.move REG,VAL (0<=REG<=127, 0<=VAL<=255)
cu.nop (0x0000 equivalent to ignored "cu.move 0,0")
cu.stop (0xffff equivalent to impossible "cu.wait 511,63")
md5-4db8024055286b9e4971778eff81ed55
Pseudo-instructions for the dma unit have been defined so that dma programs can be
generated within z80 asm. The instructions are namespaced with a leading "dma." so that they
are not confused with regular z80 instructions. The parameters to the dma instructions are
error checked.
dma.wr0 ...
dma.wr1 ...
dma.wr2 ...
dma.wr3 ...
dma.wr4 ...
dma.wr5 ...
dma.wr6 ... \
dma.cmd ... / aliases
This post gives an example dma program and this link gives technical information.
Any chance of an ld hl,(sp+dd) - that would be really useful.
And yes, swapping round to use dehl would allow us to use it without an ex.
They've changed the order to DEHL for us, very nice :) I'll update the instruction list above.
Any chance of an ld hl,(sp+dd) - that would be really useful.
It sounds like they are open to that too.
What is missing really?
ld r,(sp+d)
ld rp,(sp+d)
ld (sp+d),r
ld (sp+d),rp
ld ix,sp ;; mirrors ld hl,sp already added
ld iy,sp
add sp,nn
That's brilliant. Creating a zxn_rules.1 along the lines of the rabbit version should be easy and allow us to use some of those opcodes in a trivial way.
From the rabbit file there's these extra addressing modes that are useful for C code:
ld hl,(sp+n) - n unsigned byte
ld ix,(sp +n)
ld iy,(sp +n)
ld (sp+n),hl
ld (sp+n),ix
ld (sp+n),iy
ld hl,(hl + d) - d is signed byte
ld hl,(ix + d)
ld hl,(iy + d)
add sp,d
Supporting the other pairs instead of hl would be useful, but not hugely critical really.
From the rabbit file, it looks like I made quite a lot of use of bool hl, this basically turns hl into a boolean and sets flags. Thus a comparison to zero is easy and a true boolean value is yielded. In the rabbit world, this is a single byte opcode which makes it particularly efficient.
These are useful:
and hl, de
or hl, de
xor hl,de
So: and|or|xor hl,NNNN to cut out the ld de,NNNN
neg hl
rr hl
rl hl
I've checked in a zxn_rules.1 file, but not hooked it in as of yet, you'll need to add:
COPTRULESCPU DESTDIR/lib/zxn_rules.1
to the zxn.cfg.
Test file attached to show it working (probably only with sccz80 at the moment)
There is only one emulator currently that is accepting the opcodes and it's only a partial emulation so I think we should wait a bit before enabling the opcodes outside the assembler.
@pauloscustodio Any chance the new list up there can be included soon?
A complete emulator (ZEsarUX) is going to implement the instructions so once that's there, I think it's safe to enable them in the entire toolchain.
I'm on it...
fillde be fill de (as in verb-object)?test N be tst N to be the same as on the Z180?I've committed the change to implement the current opcodes.
I've changed test to be the same as the Z180, tst N.
We don't have any control of the opcode names... they should probably conform to existing instructions on other z80 derivatives where they are the same but all we can do is suggest.
test should be tst (z180)
swapnib should be swap (gameboy z80)
I'm not sure if there are any others in there.
I've committed the following variants of z80-zxn opcodes:
swap as synonym to swapnibfill de as synonym to filldetest as synonym to tstNew instructions that would help most directly would include stack relative addressing.
I looked at two sources for inspiration:
https://github.com/z88dk/techdocs/blob/master/rabbit/RabbitInstructions.pdf
See Appendix B page 228 for an alphabetical listing:
https://github.com/z88dk/techdocs/blob/master/zilog/z380_cpu_um.pdf
Instructions that would help most for the c compilers, given constraints:
ld hl,(sp+n)
ld rp,(sp+n) \
ld r,(sp+n) / Take up opcode space, in z380
ld (sp+n),hl
ld (sp+n),rp \
ld (sp+n),r / Take up opcode space, in z380
add sp,d
ld ix,sp
more important if stack relative addressing isn't possible:
ld hl,(ix+d)
ld rp,(ix+d)
ld (ix+d),hl
ld (ix+d),rp
In the above "d" is a two's complement 8-bit number but "n"
is an unsigned 8-bit number. The z380 treats "n" as signed
for stack relative addressing too but it doesn't make too
much sense to index below the stack pointer. If you're sharing
the ix+d logic with sp+n then two's complement will happen.
the rabbit constrained itself to these but the z380 goes whole hog
including constant NNNN operand:
andw hl,de
orw hl,de
xorw hl,de
negw hl
rlw de
rrw hl
rrw de
The rabbit added this:
bool hl (or rp)
to convert non-zero value to 1.
If you can wait another week I will be rewriting the integer math so maybe
something will come out of that.
BTW, the "TEST" instruction seems to be the same as "TST" for z180 and later.
"SWAPNIB" has similar function to the gameboy's swap and the z380's SWAP.
It may be a good idea to use the same mnemonics where it makes sense.
@pauloscustodio @suborb
A couple of new instructions also added to the main list above:
21T* ldirscale ED B6 As LDIRX, if(hl)!=A then (de)=(hl); HL_A'+=BC'; DE+=DE'; dec BC; Loop.
14T* ldpirx ED B7 (de) = ( (hl&$fff8)+(E&7) ) when != A
* = guessing
ldirscale is going to scale a source graphic up or down in size. If DE' > 1 then there will be an exploding effect in the destination (pixels will be skipped).
ldpirx is intended as a pattern lookup for fills.
I fixed up "ldirscale" in the posts above to match the correct register use:
21T* ldirscale ED B6 As LDIRX, if(hl)!=A then (de)=(hl); HL_A'+=BC'; DE+=DE'; dec BC; Loop.
What's hl_a'? How should it be treated?
Bit of poking around:
hl_a' = 16.8, bc' = 8.8 so:
l += b', when overflows h++
a' += c'
Is that right? In which case, what is a' used for?
h,l,a' is 16.8 fixed point
b',c' is 8.8 fixed point
You're doing a 24-bit addition: h,l,a' += 0,b',c'
a' = a' + c' then l = l + b' + carry then h = h + carry. But no actual flags are affected AFAIK.
I think we're missing one, latest email suggests 38 opcodes implemented, but we've only got 37 listed above.
There's only 37 now in public.
Phew. Let's hope there's some stack relative addressing soon.
I guess that's among the 80 instructions under consideration. I've asked Jim to doublecheck our list and he'll do that tomorrow.
Playing around with stuff, it would be really good if add hl,nnnn etc set the flags (well carry). This way we can use them as part of a long + constant expression.
Having said that, if they were adc rather than just add then we could use them wholly.
I've checked in a zxn_rules.1 file, but not hooked it in as of yet, you'll need to add:
COPTRULESCPU DESTDIR/lib/zxn_rules.1to the zxn.cfg.
I've hooked it in but for file "zxn_rules.2" for now. ZEsarUX still does not emulate the new opcodes so waiting for that. This other rules file only contains a rule to inline the nextreg instruction but it should be added to rules.1 once ZEsarUX is ready.
More evidence for the need for stack relative addressing. The following is the prologue for getting a variable from the stack:
4 bytes, 21T
ld hl, nnnn
add hl,sp
Using ZXN instructions we get:
6 bytes, 16T:
ld hl,sp
add hl,nnnn
Then there's the l_gint call which is a constant 51T or 24T if inlined. So to access a local variable, sccz80 is at least 7T slower.
We use it a lot in sccz80, so in the md5sum test it makes about a 2% improvement in speed, but a 10% larger binary.
A few more memory management instructions are about to be added:
(updated)
12T* mmu0 NN ED 91 50 NN macro: Ram page in slot 0-8k (not complete yet)
12T* mmu1 NN ED 91 51 NN macro: Ram page in slot 8k-16k (not complete yet)
12T* mmu2 NN ED 91 52 NN macro: Ram page in slot 16k-24k
12T* mmu3 NN ED 91 53 NN macro: Ram page in slot 24k-32k
12T* mmu4 NN ED 91 54 NN macro: Ram page in slot 32k-40k
12T* mmu5 NN ED 91 55 NN macro: Ram page in slot 40k-48k
12T* mmu6 NN ED 91 56 NN macro: Ram page in slot 48k-56k
12T* mmu7 NN ED 91 57 NN macro: Ram page in slot 56k-64k
These ones specify which 8k memory page is banked into an 8k slot of the z80's 64k memory space.
So, eg, MMU6 100 would put 8k ram page 100 into the 48k-56k memory region.
I'll update the main table above https://github.com/z88dk/z88dk/issues/312#issuecomment-322659205
d7f998a634246983d0c93d9456e70c88e6976c50 added mmu opcodes to the disassembler.
Thanks guys - it looks like I was too quick. They may demoting mmu to nextreg rather than dedicated instructions so that the memory state is r/w without having to introduce another set of instructions to read. Do we want to keep these as pseudo-instructions / macro equivalent?
I think we should keep them - its easy to remember. Just give me the new opcodes when they are frozen.
The mmu instructions have been demoted to nextreg and the equivalent byte sequence is listed above https://github.com/z88dk/z88dk/issues/312#issuecomment-322659205
If we're going to do these mmus:
12T* mmu0 NN ED 91 50 NN macro: Ram page in slot 0-8k
12T* mmu1 NN ED 91 51 NN macro: Ram page in slot 8k-16k
12T* mmu2 NN ED 91 52 NN macro: Ram page in slot 16k-24k
12T* mmu3 NN ED 91 53 NN macro: Ram page in slot 24k-32k
12T* mmu4 NN ED 91 54 NN macro: Ram page in slot 32k-40k
12T* mmu5 NN ED 91 55 NN macro: Ram page in slot 40k-48k
12T* mmu6 NN ED 91 56 NN macro: Ram page in slot 48k-56k
12T* mmu7 NN ED 91 57 NN macro: Ram page in slot 56k-64k
We should probably consider doing these too which can set mmu with register a:
12T* mmu0 a ED 92 50 macro: Ram page in slot 0-8k
12T* mmu1 a ED 92 51 macro: Ram page in slot 8k-16k
12T* mmu2 a ED 92 52 macro: Ram page in slot 16k-24k
12T* mmu3 a ED 92 53 macro: Ram page in slot 24k-32k
12T* mmu4 a ED 92 54 macro: Ram page in slot 32k-40k
12T* mmu5 a ED 92 55 macro: Ram page in slot 40k-48k
12T* mmu6 a ED 92 56 macro: Ram page in slot 48k-56k
12T* mmu7 a ED 92 57 macro: Ram page in slot 56k-64k
These I had use for tonight.
I don't know if we should go so far as to read the memory state:
ld a,mmu0
ld a,mmu1
ld a,mmu2
ld a,mmu3
ld a,mmu4
ld a,mmu5
ld a,mmu6
ld a,mmu7
These are actually complex, have side effects, and probably shouldn't be done.
ld a,mmu0
=
ld bc,0x243b
ld a,0x50
out (c),a
inc b
in a,(c)
I'll implement the mmuN a and mmu N,a.
As for the ld a,mmuN, I think the ease of use outweights the disadvantages, although the ld seems inappropriate, as the instruction is doing much more than moving data to A.
Proposal, that we can extend later for other similar uses:
eval a = mmuN - push BC, sequence above, pop BC;
eval(bc) a = mmuN - the same without saving BC; eval means compute something while using the registers in parentheses, or saving all registers in none are given
The ld a,mmuN pseudo instructions are, I think, better suited to user-defined macros rather than something baked into the assembler.
Yeah I think ld a,mmuN equivalents are better suited outside the assembler too. I don't think they would even be useful for the compilers - I just committed a c inlined macro version of this but I'm looking at making the macro substitution earlier in the compile process so that the peephole optimizers have a go at it and can improve the code given surrounding context.
Some instructions are on the bubble due to fpga space:
fillde, mul dehl (to be replaced by 8-bit multiply), ld hl,sp, ld a32,dehl, ld dehl,a32, ex a32,dehl, popx, ex hl,ix, ex hl,iy, inc dehl, dec dehl, add dehl,a, add dehl,bc, add dehl,nnnn, sub dehl,a, sub dehl,bc
Still waiting to hear what the final list is.
Never heard of some of them. :)
Given this news I take it to mean there’s no hope of stack relative. So combined with the possible removal of the opcodes we do actually use I’ll make a note to strip out custom support for zxn from sccz80 and the cpu specific opt file when this is confirmed.
Yes stack relative is very unlikely.
A copper unit has been added like the amiga copper. It understands two instructions - wait and move - that are triggered on raster line and horizontal position. I'm not sure yet if its instruction queue is stored in ram or on the fpga (probably the latter). The queue size is 1k or 512 instructions.
The point is should z80-zxn assemble these instructions as if they were z80 so that they can be inlined in regular asm to, eg, construct an instruction list for upload to the copper? Or should they be done by macro (currently that's m4 which would require parameters in parentheses which is different from how they want the mnemonics defined like normal wait a,b).
Edit: docs available now.
Maybe namespacing the instructions would ensure they are easy to see in source and won't be mixed up with regular opcodes:
copperlist:
cu.wait 193,0 ;; wait for border
cu.move 7,3 ;; set cpu to 14 MHz
cu.wait 311,0 ;; wait for line before start of screen
cu.move 7,2 ;; set cpu to 7 MHz
cu.move 0,0 ;; end program
I've updated the z80-zxn instruction list with how it is now. It is likely the instructions marked REMOVED are permanently gone due to fpga space but if you'd like to wait to make changes until the list is final, that's ok too.
However, I would like to suggest the copper instructions be added to z80asm. I am writing some copper code now and it is a royal pain to figure out the hex opcodes by hand. The copper is an independently running unit that understands two instructions and can make changes to the computer's configuration in sync with the video display (like the amiga).
The choice is macro or z80asm doing them but if I go macro route, that's m4 involved and that means the instructions will be in slightly less nice syntax WAIT(line,horz) instead of cu.wait lin,hor but more importantly the asm file will have to be changed to .asm.m4 which still scares some people for such a simple application.
The idea is to namespace them with leading "cu." so they are not confused with z80 instructions:
cu.wait VER,HOR -> 16-bit encoding 0x8000 + (HOR << 9) + VER
(0<=VER<=311, 0<=HOR<=55) BIG ENDIAN!
cu.move REG,VAL -> 16-bit encoding (REG << 8) + VAL
(0<= REG <= 127, 0 <= VAL <= 255) BIG ENDIAN!
I'm hoping a few more will be added but we'll see.
This one is not defined as a separate copper instruction (it is equivalent to cu.move 0,0) but it might be nice to have anyway:
cu.stop -> 16-bit encoding 0xffff (impossible cu.wait)
cu.nop -> 16-bit encoding 0x0000 (do nothing cu.move)
z80asm already uses dots for defgroup members I think but if the opcodes are parsed first I don't think there is a problem?
Thoughts?
Parsing the cu.xxx opcodes is no problem and can be added easily to z80asm. I will make them appear only with --cpu=z80-zxn
Just out of curiosity: how is the copper processor started on a list of instructions?
The copper's instruction memory is 1k (512 copper instructions max) and is on the fpga chip. You write to the copper's instruction memory via a couple of NEXTREG registers:
(R) 0x60 (96) => Copper data
bits 7-0 = Byte to write at "Copper list"
Note that each copper instruction is composed by two bytes (16 bits).
(R) 0x61 (97) => Copper control LO bit
bits 7-0 = Copper list index address LSB.
After the write, the index is auto-incremented to the next memory position.
(Index is set to 0 after a reset)
(R) 0x62 (98) => Copper control HI bit
bits 7-6 = Start control
00 = Copper fully stoped
01 = Copper start, execute the list, then stop at last adress
10 = Copper start, execute the list, then loop the list from start
11 = Copper start, execute the list and restart the list at each frame
bits 1-0 = Copper list index address MSB
So you would send the program by setting the index to 0 in registers 0x61 / 0x62 (0x62 would also have the copper at fully stopped). And then you would write the instructions by outing to nextreg 0x60 which auto-increments the internal index. To start it, you would write the appropriate bits to 0x62 and you would normally choose the mode that restarts with vbi (start control=11).
I've corrected cu.write to cu.wait - it's a wait for a particular beam position on screen.
Are those ZXN folk are aware the the MUL instruction is now a capability subset of the z180 MLT ?
MUL should at least be equivalent to z180 version, to prevent years of confusion, imho.
Is there a forum link to point this out?
mul d,e, test NN and swapnib have equivalents in the z180 and other processors. Yes the mnemonics should be changed and I will point that out and keep in mind the mnemonics are tentative too. Are there any others anyone has spotted?
Although there is a difference in mul d,e in that it doesn't change the flags atm so maybe a simple variant is warranted.
Thanks.
I was more wondering why / whether the intention was that MUL would only work with D and E ? Z180 MLT can act on any 16 bit register.
And TST should be the same name too, yes. Thanks.
I'm not sure why it's only d and e. The z80 is not in fact being changed - it's more like logic beside the z80 and most instructions are aimed at helping with games on layer 2 which involves a lot more memory pushing than the original machine (8x). The z180 and later derivatives simply replaced the undocumented opcode space with their new instructions but the z80-zxn can't do that because software used the undocumented instructions. Replacing them leads to incompatibility so you're more constrained over what undocumented opcodes you can replace. I'm not sure if that has anything to do with being picky about how many mlt there are.
Here's a first copper program if you're interested:
(incorrect - copper is big endian)
;; COPPER LIST
;; This is an asm file that is passed through m4 before assembling.
;; M4 is a sophisticated macro processing facility common on linux/unix
;; machines and is incorporated into z88dk as a macro pre-processor.
;; Its syntax can look odd for people not familiar with the tool.
;; To see what m4 does to this file, ask zcc to process it
;; zcc +zxn -vn -m4 copper.asm.m4
include(`z88dk.m4') ; useful m4 macros
dnl#include(`config_private.inc') ; symbol values for zxn target
define(`__IO_REGISTRY_REG_SPRITE_LAYER_SYSTEM', 0x15)
define(`CU_WAIT', `defw 0x`'eval((0x8000 + (($1)&(0x1ff)) + ((($2)&(0x3f))<<9)),16,4)')
define(`CU_MOVE', `defw 0x`'eval(((($1)&(0x7f))<<8) + (($2)&(0xff)),16,4)')
define(`CU_STOP', `defw 0x0000')
SECTION rodata_user
PUBLIC _copper
PUBLIC _copper_end
_copper:
; There are two hi-res windows (x,width,y,height) = (2,28,1,19) and (32,28,3,19)
; Further these are 64x24 character coordinates but the copper operates in 32x24
; character coordinates so we want even sizes for the 64-col (x,width) to divide
; by two to get exact 32x24 x coordinates.
; To make the timex hi-res display appear on top of the scrolling layer 2
; we'll make use of the nextreg layer priorities register 0x15 (__IO_REGISTRY_REG_SPRITE_LAYER_SYSTEM)
; and have layer 2 on top by default and then ula on top for the hi-res terminals.
; layer 2 is on top by default
; (this part is not actually necessary because layer 2 on top set later)
CU_WAIT(193, 0) ; wait just past visible screen area
CU_MOVE(__IO_REGISTRY_REG_SPRITE_LAYER_SYSTEM, 0) ; layer 2 on top
; the first window is on its own for vertical y char coord in [1,3)
; change to ula on top while beam is over first window
Z88DK_FOR(`LOOP', eval(1*8), eval(3*8-1),
`
CU_WAIT(LOOP, eval(2/2)) ; wait for x = 2/2 start first window
CU_MOVE(__IO_REGISTRY_REG_SPRITE_LAYER_SYSTEM, 0x08) ; ula on top
CU_WAIT(LOOP, eval((2+28)/2)) ; wait for x=(2+28)/2 end first window
CU_MOVE(__IO_REGISTRY_REG_SPRITE_LAYER_SYSTEM, 0) ; layer 2 on top
')
; two windows appear on the same scan line for y char coord in [3,20)
; change to ula on top while beam is over either window
Z88DK_FOR(`LOOP', eval(1*8), eval(20*8-1),
`
CU_WAIT(LOOP, eval(2/2)) ; wait for x = 2/2 start first window
CU_MOVE(__IO_REGISTRY_REG_SPRITE_LAYER_SYSTEM, 0x08) ; ula on top
CU_WAIT(LOOP, eval((2+28)/2)) ; wait for x=(2+28)/2 end first window
CU_MOVE(__IO_REGISTRY_REG_SPRITE_LAYER_SYSTEM, 0) ; layer 2 on top
CU_WAIT(LOOP, eval(32/2)) ; wait for x = 32/2 start first window
CU_MOVE(__IO_REGISTRY_REG_SPRITE_LAYER_SYSTEM, 0x08) ; ula on top
CU_WAIT(LOOP, eval((32+28)/2)) ; wait for x=(32+28)/2 end first window
CU_MOVE(__IO_REGISTRY_REG_SPRITE_LAYER_SYSTEM, 0) ; layer 2 on top
')
; the second window is on its own for vertical y char coord in [20,22)
; change to ula on top while beam is over second window
Z88DK_FOR(`LOOP', eval(20*8), eval(22*8-1),
`
CU_WAIT(LOOP, eval(32/2)) ; wait for x = 32/2 start second window
CU_MOVE(__IO_REGISTRY_REG_SPRITE_LAYER_SYSTEM, 0x08) ; ula on top
CU_WAIT(LOOP, eval((32+28)/2)) ; wait for x=(32+28)/2 end second window
CU_MOVE(__IO_REGISTRY_REG_SPRITE_LAYER_SYSTEM, 0) ; layer 2 on top
')
CU_STOP
_copper_end:
Still working out how to do it nicely and it should be better with the pseudo-instructions in z80asm.
It makes the 16x16 multiply a fair bit slower (I think the following is correct?), from 4T to 74T, though still less than then 5-600T of the vanilla z80.
(Ooops, mul is 8T not 4T, so those numbers are wrong)
int mult(int v1, int v2)
{
#asm
ld hl,2
add hl,sp
ld e,(hl)
inc hl
ld d,(hl)
inc hl
ld a,(hl)
inc hl
ld h,(hl)
ld l,a
l_mult:
ld a,d ;a= v2:msb
ld d,h ;v1:msb
ld c,e ;c = v2:lsb
ld b,l ;b = v1:lsb
mul de
ex de,hl ;hl = v1:msb * v2:lsb
ld d,a ;
mul de ;de = v2:msb * v1:lsb
add hl,de
ld e,c ;e = v2:lsb
ld d,b ;d = v1:lsb
mul de ;de = v1:lsb * v2:lsb
ld h,l
ld l,0
adc hl,de
ret
}
The z180 and later derivatives simply replaced the undocumented opcode space with their new instructions but the z80-zxn can't do that because software used the undocumented instructions.
That is fair enough, and the last thing they want would be to break any z80 compatibility...
Replacing them leads to incompatibility so you're more constrained over what undocumented opcodes you can replace. I'm not sure if that has anything to do with being picky about how many
mltthere are.
Except that the opcodes for the z180 MLT are already in the ED space (ED4C, ED5C, ED6C, and ED7C, for BC, DE, HL, and SP respectively) and, if I read correctly, these opcodes are only an undocumented _shadow_ or _alias_ of the real z80 opcode NEG (ED44). Therefore using the existing MLT opcodes doesn't mask any additional or useful undocumented z80 feature.
With respect, it sounds to me as if they are just being a tiny bit lazy and not thinking through their options, having lost the 16x16 multiply.
It would take quite a few cycles off the above 16x16 multiply routine not to have to juggle the MSB and LSB bytes around. But more importantly, MLT compatibility would save having to maintain two separate math solutions, forever...
l_mult:
ld a,d ; a = xh / 4
ld d,h ; d = yh / 4
ld h,a ; h = xh / 4
ld c,e ; c = xl / 4
ld b,l ; b = yl / 4
mlt de ; yh * xl / 17
mlt hl ; xh * yl / 17
add hl,de ; add cross products / 11
mlt bc ; yl * xl / 17
ld h,l ; drop cross products MSB / 4
ld l,0 ; / 7
add hl,bc ; add in the LSB product / 11
ret ; / 10
I've corrected above - copper is big endian so the 16-bit value should be written that way when generating code.
@suborb I think there is an error in your multiply:
hl = de * hl
de = x, hl = y
z80-zxn
4 ld a,d ; a = xh
4 ld d,h ; d = yh
4 ld h,a ; h = xh
4 ld c,e ; c = xl
4 ld b,l ; b = yl
8 mul de ; yh * xl
4 ex de,hl
8 mul de ; xh * yl
11 add hl,de ; add cross products
4 ld e,c
4 ld d,b
8 mul de ; yl * xl
4 ld a,l ; cross products LSB
4 add a,d ; add to MSB final
4 ld h,a
4 ld l,e ; hl = final
ret
83 cycles, 19 bytes
hl = de * hl
de = x, hl = y
z180-like
4 ld a,d ; a = xh
4 ld d,h ; d = yh
4 ld h,a ; h = xh
4 ld c,e ; c = xl
4 ld b,l ; b = yl
8 mlt de ; yh * xl
8 mlt hl ; xh * yl
11 add hl,de ; add cross products
8 mlt bc ; yl * xl
4 ld a,l ; cross products LSB
4 add a,b ; add to MSB final
4 ld h,a
4 ld l,c ; hl = final
ret
71 cycles, 16 bytes
z180: 94 cycles
I'm not sure why I included the carry either. Think my head must've been in 32 bit land.
Having a go at a full 16x16 multiply, I get this.
I can't see a way to do it without using the stack or index registers.
l_mult:
ld a,d ; a = xh / 4
ld d,h ; d = yh / 4
ld h,a ; h = xh / 4
ld c,d ; c = xh / 4
ld b,h ; b = yh / 4
push bc ; / 11
ld c,e ; c = xl / 4
ld b,l ; b = yl / 4
mlt de ; yh * xl / 17
mlt hl ; xh * yl / 17
add hl,de ; add cross products / 11
ld e,h ; MSB yh * xl + xh * yl / 4
ld h,l ; LSB yh * xl + xh * yl / 4
ld l,0 ; 0 LSB / 7
ld d,l ; 0 MSB / 4
mlt bc ; LSB product yl * xl / 17
add hl,bc ; add LSB X to LSB product / 11
ex de, hl ; move LSW to DE, MSB X to HL / 4
pop bc ; / 10
mlt bc ; MSB product yh * xh / 17
adc hl,bc ; add MSB X to MSB product + C / 15
ex de, hl ; move MSW to DE, LSW to HL / 4
ret
Thoughts?
For the newlib, the complete set of math functions that have to be replaced are in:
https://github.com/z88dk/z88dk/tree/master/libsrc/_DEVELOPMENT/math/integer
Those entry points are what the compilers and library call into and they are mainly stubs that select between the fast integer functions (they do loop unrolling and leading zero elimination) and the small integer functions in the small subdirectory. The z180 / z80-zxn won't likely benefit from unrolling for multiplication at least so the small subdir is probably what has to be replaced for the multiply. zsdcc does distinguish between 8bit and 16bit multiply so in fact some of the library entry points should probably be turned into inlined mlt instructions.
I think there are some issues in that 16x16->32 multiply to do with carries not being added at the right places.
I tried this one which I think is correct:
l_small_mul_32_16x16:
; multiplication of two 16-bit numbers into a 32-bit product
;
; enter : de = 16-bit multiplicand = y
; hl = 16-bit multiplicand = x
;
; exit : dehl = 32-bit product
; carry reset
;
; uses : af, bc, de, hl
4 ld b,l ; xl
4 ld c,d ; yh
4 ld d,l ; xl
4 ld l,c
11 push hl ; xh yh
4 ld l,e ; yl
; bc = xl yh
; de = xl yl
; hl = xh yl
; stack = xh yh
17 mlt de ; xl * yl
17 mlt bc ; xl * yh
17 mlt hl ; xh * yl
7 add hl,bc ; sum cross products
4 sbc a,a
6 and $01
4 ld b,a ; carry from cross products
4 ld c,h ; LSB of MSW from cross products
4 ld a,d
4 add a,l
4 ld d,a ; de = final LSW
9 pop hl
17 mlt hl ; xh * yh
10 adc hl,bc ; hl = final MSW
3 ex de,hl
; 158 cycles, 27 bytes
ret
z180 timing is different so it's a little strange.
There's a bug in z80asm's copper instructions:
"test.asm"
SECTION rodata_user
PUBLIC _copper
PUBLIC _copper_end
EXTERN __REG_SPRITE_LAYER_SYSTEM
EXTERN __RSLS_LAYER_PRIORITY_SUL, __RSLS_LAYER_PRIORITY_SLU
_copper:
cu.wait 100,1
cu.move 0x15, 0x08
cu.move __REG_SPRITE_LAYER_SYSTEM, __RSLS_LAYER_PRIORITY_SUL
cu.wait 200,1
_copper_end:
zcc +zxn -c -vn --list -clib=sdcc_iy test.asm
Produces this list file:
1 0000 MODULE test_asm
2 0000 LINE -1, "test.asm"
-1 0000
0 0000
1 0000 SECTION rodata_user
2 0000
3 0000 PUBLIC _copper
4 0000 PUBLIC _copper_end
5 0000
6 0000 EXTERN __REG_SPRITE_LAYER_SYSTEM
7 0000 EXTERN __RSLS_LAYER_PRIORITY_SUL, __RSLS_LAYER_PRIORITY_SLU
8 0000
9 0000 _copper:
10 0000
11 0000 82 64 cu.wait 100,1
12 0002 15 08 cu.move 0x15, 0x08
13 0004 cu.move __REG_SPRITE_LAYER_SYSTEM, __RSLS_LAYER_PRIORITY_SUL
14 0004 82 C8 cu.wait 200,1
15 0006
16 0006 _copper_end:
17 0006
Here you can see that if the copper instruction contains extern symbols, nothing is generated.
Sorry, my mistake. I assumed constant expressions, I.e. known at compile time.
I will change them to link time expressions.
@pauloscustodio I just found out today that:
16T* push NNNN ED 8A HI LO push 16bit immediate value note big endian order
stores the NNNN argument in big endian form.
@aralbrec is there a rationale behind the seemingly unique, within the Z80 vocabulary, big-endian implementation of this push NNNN instruction?
Their add hl NNNN, add de NNNN and add bc NNNN instructions are little-ended, aren’t they?
Is it to match the copper processor instructions?
Nothing to do with the copper. From what I understand it's just how push nnnn is implemented. If you're writing to the stack you have to:
1. dec sp
2. write MSB to stack
3. dec sp
4. write LSB to stack
It's something to do with needing the MSB first to push to the stack first. He's using existing logic inside the z80 soft-core so it "just has to be this way". push rp like rp=HL happens to name the MSB register first. I don't know if internally the 16-bit nnnn is loaded backwards somewhere so they have to be swapped. You can't really understand it without knowing the logic.
I understand there is no control over what is being done in the zxn camp, and I'm a relative beginner at this, but I'm going to say it anyway.
Creating an instruction that creates the precedent of storing a big-ended number (which most probably will be an address too) in a Z80 CPU is just asking for trouble and confusion, which will last from now until eternity.
IMHO, if something can't be done right, then it is better not to do it at all.
I'm sure Federico Faggin would have something to say about this, if he knew.
Surely, we could suggest a better alternative or related use of this specific op-code than creating an intrinsically broken instruction?
What about a push pc+a instruction, for a relocatable relative call/jump?
Or an add hl,pc instruction for broader code relocation?
I'm sure you can pick from many other better suggestions...
Surely, we could suggest a better alternative or related use of this specific op-code than creating an intrinsically broken instruction?
The instructions are fairly locked now and many were stripped off to make sure everything would fit into the fpga. Right now, it's just about getting the bugs out for the cased run which is supposed to happen in the next month I think.
push nnnn is a useful instruction, although not quite as useful if it could have been faster. I revised the instruction time up to 22 cycles (this is all guesswork btw) because there are four opcode fetches followed by two stack writes if a straightforward implementation has been made. A "ld hl,nnnn; push hl" takes 21 cycles but the fact the former doesn't use a register does relieve register pressure in code.
I'd personally rather have it than not. If there is good reason that the pre-existing z80 core has to have the bytes reversed then I think you just have to go with it. This instruction does appear in z80 derivatives like the z380 where it is little endian as you'd expect.
This one is back, apparently it never went away:
8T* popx ED 8B discard word on stack (inc sp; inc sp)
Re-opened for comment.
We've added copper instructions to the assembler to help write programs for the copper device on the zx next. I've found this to be very helpful. The copper is described here.
I'm wondering about doing the same thing for the dma device. This would maybe set a precedent for doing this for all targets because the z80 has several standard peripherals like the dma, sio, pio, ctc. Anyway the zx next's dma is a subset of the z80 dma plus a fixed transfer time feature. It is described here.
The additional macros/instructions I am thinking about are for writing the seven dma registers WR0-WR6 that the dma chip presents. Error checking is a bit complicated; I'm not sure how practical it is for the z80asm parser.
dma.wr0 n [, w, x, y, z] with whitespace following comma including newline and maybe comment to the end of the line so params can be listed on following lines (not a huge loss if comments can't be accommodated)
n: bit 7 must be 0, bits 1..0 must be 01 else error "base register byte is illegal"
If bit 3 of n is set then accept one following byte\
If bit 4 of n is set then accept one following byte/ set together, expect word instead
If bit 5 of n is set then accept one following byte\
If bit 6 of n is set then accept one following byte/ set together, expect word instead
The number of bytes following n depends on the number of set bits in n. If both bits corresponding to a word are set, the parameter must be a word instead of two bytes. Eg, if 3 & 4 are both set, a complete Port A starting address follows so this should be a word parameter instead of two separate byte parameters.
dma.wr1 n [,w]
or 0x04 into n
n: bit 7 must be 0, bits 2..0 must be 100 else error "base register byte is illegal"
If bit 6 of n is set then accept one following byte w.
In w bits 5..4 must be 0, bits 1..0 must not be 11 error "port A timing is illegal"
In w if any of bits 7,6,3,2 are set warning "dma does not support half cycle timing"
dma.wr2 n [,w,x]
n: bit 7 must be 0, bits 2..0 must be 000 else error "base register byte is illegal"
If bit 6 of n is set then accept one following byte w
In w bit 4 must be 0, bits 1..0 must not be 11 error "port B timing is illegal"
In w if any of bits 7,6,3,2 are set warning "dma does not support half cycle timing"
If bit 5 of w is set then accept one following byte x that can be anything.
dma.wr3 n [,w,x]
or 0x80 into n
n: bit 7 must be 1, bits 1..0 must be 00 else error "base register byte is illegal"
If any of bits 5,4,3,2 of n are set then warning "dma does not support some features"
If bit 3 of n is set then accept one following byte that can be anything.
If bit 4 of n is set then accept one following byte that can be anything.
dma.wr4 n, [w,x]
or 0x81 into n
n: bit 7 must be 1, bits 1..0 must be 01 else error "base register byte is illegal"
If bit 4 of n is set then error "dma does not support interrupts"
If bits 6..5 of n are 00 or 11 error "dma mode is illegal"
If bit 2 of n is set then accept one following byte\
If bit 3 of n is set then accept one following byte/ set together, expect word instead
Again if both bits 2 & 3 are set, w,x must be combined into a single word parameter.
dma.wr5 n
or 0x82 into n
n: bits 7..6 must be 10, bits 2..0 must be 010 else error "base register byte is illegal"
If bit 3 of n is set then warning "dma does not support ready signals"
dma.wr6 n [,w] or dma.cmd n [,w]
n:
accept 0xcf, 0xd3, 0x87, 0x83, 0xbb
warning on 0xc3, 0xc7, 0xcb, 0xaf, 0xab, 0xa3, 0xb7, 0xbf, 0x8b, 0xa7, 0xb3 "dma does not implement this command"
anything else error "illegal dma command"
if n = 0xbb accept a following byte w
If bit 7 of w is set error "read mask is illegal"
If any of these are missing following bytes in the comma list then maybe error "missing register group member(s)". if there are too many bytes "too many arguments".
The bytes output by the assembler would be n (after OR mentioned) [,w,x,y,z]
Example dma program with special mnemonics:
(has error checking)
dma.wr0
DMA_WR0_TRANSFER | DMA_WR0_A_TO_B | DMA_WR0_X34_A_START | DMA_WR0_X56_LEN
0xc000, ; A start address
0x4000 ; Transfer length
dma.wr1
DMA_WR1_A_IS_MEM | DMA_WR1_A_INC | DMA_WR1_X6_A_TIMING,
DMA_WR1X6_A_CLEN_2 ; A cycle length
dma.wr2
DMA_WR2_B_IS_MEM | DMA_WR2_B_INC | DMA_WR2_X6_B_TIMING,
DMA_WR2X6_B_CLEN_2 ; B cycle length
dma.wr4
DMA_WR4_CONT | DMA_WR4_X23_B_START,
0x4000 ; B start address
dma.wr5 0
dma.cmd DMA_LOAD
dma.cmd DMA_ENABLE_DMA
The same program without:
(does not have error checking)
defb 0x7d ; WR0
defw 0xc000 ; A start address
defw 0x4000 ; transfer length
defb 0x54 ; WR1
defb 0x02 ; A cycle length is 2
defb 0x50 ; WR2
defb 0x02 ; B cycle length is 2
defb 0xad ; WR4
defw 0x4000 ; B start address
defb 0x82 ; WR5
defb 0xcf ; LOAD
defb 0x87 ; ENABLE DMA
Maybe a better job can be done with the named constants.
Any comments? I think I would go for this. The error checking could be helpful.
If the periphery is not implemented at a known fixed address, as is the case with most Z80 systems, then it would be difficult to make this work, I guess.
For Z180 with its periphery implemented at constant address locations, even across at least 3 subtypes, it would be easier, and would be very useful.
If the periphery is not implemented at a known fixed address, as is the case with most Z80 systems, then it would be difficult to make this work, I guess.
A dma program is detached from the io address - really you're just defining a block of bytes and the next step would be sending the program to the dma device on its io address so there is no coupling to a specific io address or a specific dma here.
For the other devices sio, pio, ctc I'm not sure if there would be an analogue - maybe not. But for sure the dma.wr* macro would be applicable to the z180 and to multiple dmas on the z80 too (of course with all z80 dma operations supported - the zxn version described above generates errors and warnings for some z80 dma features).
@pauloscustodio Is this in the realm of possibility?
Yes, this is possible. Sorry for the silence.
I assume the values are all compile-time constants, so that the error checking can be done at compile time, i.e. DMA_WR0_TRANSFER | DMA_WR0_A_TO_B | DMA_WR0_X34_A_START | DMA_WR0_X56_LEN is an expression that can be evaluated at compile time and the constants are defined elsewhere. Correct?
Yes that's right. It's all constants just like the cu.* instructions.
In the cu.* instructions there is some range checking as well and they also deal with constants that could be extern. Hopefully extern constants would also be possible with dma.*? But it seems it's a more complicated case with variable number of arguments and some arguments being words.
I can see the first byte to dma.wr* probably has to be known at assemble time so that the instruction size and parameter sizes can be known. So the first byte at least can't be resolved at link time.
Question:
dma.wr0 n [, w, x, y, z] with whitespace following comma including newline and maybe comment to
the end of the line so params can be listed on following lines (not a huge loss if comments can't be
accommodated)
or 0x01 into n
n: bit 7 must be 0, bits 1..0 must be 01 else error "base register byte is illegal"
as n must have bits 1..0 = 0b01, the "or 0x01 into n" is not needed. Correct?
as n must have bits 1..0 = 0b01, the "or 0x01 into n" is not needed. Correct?
OR it in anyway. The fact "dma.wr0" is used means this is a write to the dma's WR0 register which must have bit 0 set to indicate this so the instruction itself implies it. The n will set other bits in the byte to indicate options and may not set bit 0 itself.
@aralbrec, it took a while, but I got the parser to parse multi-line statements. Please check dma.wr0 on branch feature/z80asm_zxn_dma while I go for the other dma commands,
It seems to be working:
include "config_zxn_private.inc"
dma.wr0 __DMA_WR0_TRANSFER | __DMA_WR0_A_TO_B | __DMA_WR0_X34_A_START | __DMA_WR0_X56_LEN, 0xc000, 0x4000
dma.wr0 __DMA_WR0_TRANSFER | __DMA_WR0_A_TO_B | __DMA_WR0_X34_A_START | __DMA_WR0_X56_LEN,
0xc000,
0x4000
dma.wr0 __DMA_WR0_TRANSFER | __DMA_WR0_A_TO_B | __DMA_WR0_X34_A_START | __DMA_WR0_X56_LEN,
0xc000, ; A start address
0x4000 ; length
;dma.wr0 __DMA_WR0_TRANSFER | __DMA_WR0_A_TO_B | __DMA_WR0_X34_A_START | __DMA_WR0_X56_LEN, 0xc000
;dma.wr0 __DMA_WR0_TRANSFER | __DMA_WR0_A_TO_B | __DMA_WR0_X34_A_START | __DMA_WR0_X56_LEN, 0xc000, 0x4000, 0x1000
;dma.wr0 __DMA_WR0_TRANSFER | __DMA_WR0_A_TO_B | __DMA_WR0_X34_A_START | __DMA_WR0_X56_LEN,
; 0xc000
;dma.wr0 __DMA_WR0_TRANSFER | __DMA_WR0_A_TO_B | __DMA_WR0_X34_A_START | __DMA_WR0_X56_LEN,
; 0xc000,
; 0x4000,
; 0x1000
; should produce error - my bad do not OR 0x01 into n
dma.wr0 __DMA_WR0_A_TO_B | __DMA_WR0_X34_A_START | __DMA_WR0_X56_LEN, 0xc000, 0x4000
dma.wr0 __DMA_WR0_TRANSFER | __DMA_WR0_A_TO_B | __DMA_WR0_X4_A_START_H | __DMA_WR0_X56_LEN, 0xc0, 0x4000
;dma.wr0 __DMA_WR0_TRANSFER | __DMA_WR0_A_TO_B | __DMA_WR0_X4_A_START_H | __DMA_WR0_X56_LEN, 0xc000, 0x4000
;dma.wr0 __DMA_WR0_TRANSFER | __DMA_WR0_A_TO_B | __DMA_WR0_X3_A_START_L | __DMA_WR0_X5_LEN_L, 0xc000, 0x4000
;dma.wr0 __DMA_WR0_TRANSFER | __DMA_WR0_A_TO_B | __DMA_WR0_X3_A_START_L | __DMA_WR0_X5_LEN_L, 0xc000, 0x40
;dma.wr0 __DMA_WR0_TRANSFER | __DMA_WR0_A_TO_B | __DMA_WR0_X3_A_START_L | __DMA_WR0_X5_LEN_L, 0xc0, 0x4000
dma.wr0 __DMA_WR0_TRANSFER | __DMA_WR0_A_TO_B | __DMA_WR0_X3_A_START_L | __DMA_WR0_X5_LEN_L, 0xc0, 0x40
zcc +zxn -vn -c --list test.asm
The commented out lines are all caught errors.
as n must have bits 1..0 = 0b01, the "or 0x01 into n" is not needed. Correct?
OR it in anyway. The fact "dma.wr0" is used means this is a write to the dma's WR0 register which must have bit 0 set to indicate this so the instruction itself implies it. The n will set other bits in the byte to indicate options and may not set bit 0 itself.
I made a mistake with dma.wr0 - I've just checked the others and they are ok. "OR 0x01 into N" should not be done for dma.wr0. That bit is not implied by the instruction and in fact doing that will fail to catch errors when trying to use a working z80 dma program that will not work on the zxn dma.
Trying to make the dma lines more atttractive is not easy:
include "config_zxn_private.inc"
dma.wr0 __DWR0_TRANSFER | __DWR0_A_TO_B | __DWR0_X34_A_START | __DWR0_X56_LEN, 0xc000, 0x4000
dma.wr0 __DWR0_TRANSFER | __DWR0_A_TO_B | __DWR0_X34_A_START | __DWR0_X56_LEN,
0xc000,
0x4000
dma.wr0 __DWR0_TRANSFER | __DWR0_A_TO_B | __DWR0_X34_A_START | __DWR0_X56_LEN,
0xc000, ; A start address
0x4000 ; length
;dma.wr0 __DWR0_TRANSFER | __DWR0_A_TO_B | __DWR0_X34_A_START | __DWR0_X56_LEN, 0xc000
;dma.wr0 __DWR0_TRANSFER | __DWR0_A_TO_B | __DWR0_X34_A_START | __DWR0_X56_LEN, 0xc000, 0x4000, 0x1000
;dma.wr0 __DWR0_TRANSFER | __DWR0_A_TO_B | __DWR0_X34_A_START | __DWR0_X56_LEN,
; 0xc000
;dma.wr0 __DWR0_TRANSFER | __DWR0_A_TO_B | __DWR0_X34_A_START | __DWR0_X56_LEN,
; 0xc000,
; 0x4000,
; 0x1000
; should produce error - my bad do not OR 0x01 into n
dma.wr0 __DWR0_A_TO_B | __DWR0_X34_A_START | __DWR0_X56_LEN, 0xc000, 0x4000
dma.wr0 __DWR0_TRANSFER | __DWR0_A_TO_B | __DWR0_X4_A_START_H | __DWR0_X56_LEN, 0xc0, 0x4000
;dma.wr0 __DWR0_TRANSFER | __DWR0_A_TO_B | __DWR0_X4_A_START_H | __DWR0_X56_LEN, 0xc000, 0x4000
;dma.wr0 __DWR0_TRANSFER | __DWR0_A_TO_B | __DWR0_X3_A_START_L | __DWR0_X5_LEN_L, 0xc000, 0x4000
;dma.wr0 __DWR0_TRANSFER | __DWR0_A_TO_B | __DWR0_X3_A_START_L | __DWR0_X5_LEN_L, 0xc000, 0x40
;dma.wr0 __DWR0_TRANSFER | __DWR0_A_TO_B | __DWR0_X3_A_START_L | __DWR0_X5_LEN_L, 0xc0, 0x4000
dma.wr0 __DWR0_TRANSFER | __DWR0_A_TO_B | __DWR0_X3_A_START_L | __DWR0_X5_LEN_L, 0xc0, 0x40
I'm not sure if there is a better way to name the constants. There has to be a prefix to identify the dma (__D used here instead of the bulkier __DMA in the post above). The suffixes I think are fairly good TRANSFER A_TO_B X3_A_START_L. The Xn identify the bit being set and from the order (lowest to highest bit) it also tells you the parameters that follow and in what order.
I'm not sure if there is a better way to name the constants.
Maybe you could create constants for the most common operations, e.g.
__DMWR0_TX_A2B = __DWR0_TRANSFER | __DWR0_A_TO_B
dma.wr1 n [,w] or 0x04 into nI assume that here too the OR is not to be done.
I assume that here too the OR is not to be done.
In the rest of them the OR should be there. Those bits must be set to identify wr1, wr2, etc.
I've completed the implementation of the dma commands on branch feature/z80asm_zxn_dma.
Please have a run before I merge.
The dma commands are available in all CPUs, not only z80-zxn, but I suppose some of the error messages are ZX Next-specific. Should the dma commands be available only in --cpu=z80-zxn?
That's great. I will give them a try now.
Yes for now they should be z80-zxn specific and I will start issues for z80 and z180. I don't know if the rabbits have dma units or not. The z80 dma is very close but it has a few more things in.
Yes I think it's working. I also tested a few externs with the argument list.
The example dma program from a few posts up:
include "include/zxn-dma.inc"
dma.wr0 __D_WR0_TRANSFER_A_TO_B | __D_WR0_X34_A_START | __D_WR0_X56_LEN, 0xc000, 6912 ;; 6912 was 0x4000
dma.wr1 __D_WR1_A_IS_MEM_INC | __D_WR1_X6_A_TIMING, __D_WR1X6_A_CLEN_2
dma.wr2 __D_WR2_B_IS_MEM_INC | __D_WR2_X6_B_TIMING, __D_WR2X6_B_CLEN_2
dma.wr4 __D_WR4_CONT | __D_WR4_X23_B_START, 0x4000
dma.wr5 __D_WR5_STOP
dma.cmd __D_LOAD
dma.cmd __D_ENABLE_DMA
Also can you add pop x with a space before the x? This is going to be the official mnemonic. At the moment we have popx without space.
Please create a new issue in the future. It gets hard to track what was done when with all the re-opening.
I will create one issue for pop x.
The Z180 multiplication instructions also were quite useful in SDCC (or having at least one of them, preferably the one for hl). After all, often an 8x8->16 multiplication is sufficient. It seems even more useful than the 16x16->32 Rabbit one, since the 16x16->32 makes too many registers unavailable for other purposes.
Philipp
They have the one for de only, is that still useful without excessive shuffling to hl?
We've started on some integer multiplies for the target library here.
I would like to see them bring in signed versions of some of these instructions (add hl,a ; mul d,e) and return of the 16x16->32 bit multiply they dropped due to limited fpga space. But this is not likely to happen from the core team itself - more as a proposed patch from reviewers - so whether it will or can happen is a question.
Where can I find the instruction set?
http://devnext.referata.com/wiki/Extended_Z80_instruction_set
Mentions the 16x16->32 multiply, but not 8x8->16.
Also, it seems quite unfortunate, that there is no equivalent of the Rabbits 16-bit load instructions ld hl, (hl+d) and ld hl, (ix+d)/ld hl, (sp+d) and ld (sp+d), hl/ld (ix+d), hl. Thos really make a big difference for 16-bit code.
mlt de is fine, the advantage of mlt hl over mlt de should be very small.
Philipp
The most accurate information is in z88dk. The wiki is maintained by the community who don't keep up / have access to some of the information.
https://github.com/z88dk/z88dk/issues/312#issuecomment-322659205
So here's my comment on the instruction in https://github.com/z88dk/z88dk/issues/312#issuecomment-322659205 from a compiler writer perspective (SDCC). I'll comment on how useful I consider the new instructions, and suggest to rename some (since they already exist in other Z80-derivatives under a different name). I will make a later post with suggestions for additional instructions.
T=4+ 8T* swapnib ED 23 A bits 7-4 swap with A bits 3-0
This one is useful for speeding up some shifts. SDCC already emits this instruction for gbz80. For consistency with gbz80. I suggest to rename this instruction "swap" (as it is called on the GameBoy).
T=4+ 8T mul d,e ED 30 multiply DE = D*E (no flags set)
This one is very useful. Both for the very common 8x8->16 multiplications (either explicit or for array addressing) and as building block for the support routines for wider multiplications. SDCC already emits this instruction for z180. I suggest to rename this instruction "mlt de" (as it is called on the Z180).
T=4+ 8T add hl,a ED 31 Add A to HL (no flags set) not sign extended
T=4+ 8T* add de,a ED 32 Add A to DE (no flags set) not sign extended
T=4+ 8T* add bc,a ED 33 Add A to BC (no flags set) not sign extended
I do not have experience with such instructions. But I believe they will be useful, e.g. for using an 8-bit index into a char array.
M=3+, T=4 16T add hl,NNNN ED 34 LO HI Add NNNN to HL (no flags set)
M=3+, T=4 16T* add de,NNNN ED 35 LO HI Add NNNN to DE (no flags set)
I don't see much point in those. The only point is reducing register pressure a bit. I don't see a godd use case in SDCC that benefits from not setting flags. So those are not really an advantage over something like
ld de, NNNN
ld hl, de
Which is the same code size as the first proposed new instruction, and via ex de, hl can also be used instead of the second proposed new instruction at just one byte of extra code size.
M=3+, T=4 16T* add bc,NNNN ED 36 LO HI Add NNNN to BC (no flags set)
This one seems a bit more useful, since transferring the addition result from hl to bc would take two bytes. However, I again don't see an advantage for not setting flags. In fact, if this one would set flags (or wouldbe changed to adc) it could be even more useful as a building block for 32- and 64-bit additions.
M=4+ 16T* outinb ED 90 out (c),(hl), hl++
I can see the point in this one, even though SDCC would not emit it (but it looks good for use in asm code).
M=4+ 16T ldix ED A4 As LDI, but if byte==A does not copy
M=4+ 21T ldirx ED B4 As LDIR, but if byte==A does not copy
M=4+ 16T* lddx ED AC As LDD, but if byte==A does not copy, and DE is incremented
M=4+ 21T* lddrx ED BC As LDDR, but if byte==A does not copy, and DE is incremented
M=4+ 21T* ldirscale ED B6 As LDIRX, if(hl)!=A then (de)=(hl); HL_A'+=BC'; DE+=DE'; dec BC; Loop.
M=4+ 12T* ldpirx ED B7 (de) = ( (hl&$fff8)+(E&7) ) when != A
I currently do not see the point in those. They look like made for some specific use case that I do not know about. Do they really just skip a byte if it is A (if they stopped instead they might be useful for string processing)?
T=4+ 8T mirror a ED 24 mirror the bits in A
T=4+ 8T mirror de ED 26 mirror the bits in DE
Those don't look that useful to me. I can see that mirroring bits is hard to do without them in the Z80 instruction set. But the need for mirroring bits is very rare in my experience. And SDCC would not be able to detect C code mirroring bits easily, so it would not emit those.
M=6+ 22T* push NNNN ED 8A HI LO push 16bit immediate value note big endian order
I don't see much point in this one. The only advantage it provides is reducing register pressure. Otherwise it provides no advantage over
ld qq, NNNN
push qq
Which is also just 4 bytes of code.
M=3+ 8T* pop x ED 8B discard word on stack (inc sp; inc sp)
I don't see the point at all.
inc sp
inc sp
Does exactly the same at exactly the same cost in code size.
M=5+ 16T* nextreg reg,val ED 91 reg,val Set a NEXT register (like doing out($243b),reg then out($253b),val
M=4+ 12T* nextreg reg,a ED 92 reg Set a NEXT register using A (like doing out($243b),reg then out($253b),A )
** reg,val are both 8-bit numbers
T=4+ 8T pixeldn ED 93 Move down a line on the ULA screen
T=4+ 8T pixelad ED 94 using D,E (as Y,X) calculate the ULA screen address and store in HL
T=4+ 8T setae ED 95 Using the lower 3 bits of E (X coordinate), set the correct bit value in A
Looks like stuff specific to the peripherals of the device. Is it really worth using opcodes as opposed to some I/O location?
M=2+ 11T test NN ED 27 NN And A with NN and set all flags. A is not affected.
This can be useful for some code, but such code is not that common. SDCC already emits this instruction for z180. I suggest to rename it "tst" (as it is called on the Z180).
Summary:
Great, very useful in SDCC:
mul d, e/mlt de
Useful for SDCC:
swapnib/swap a
add hl,a
add de,a
add bc,a
add bc,NNNN
test/tst NN
Marginally useful for SDCC:
push NNNN
add de,NNNN
push NNNN
Nearly useless for SDCC:
add hl,NNNN
mirror a
mirror de
pop x
Having worked on various SDCC backends, including all z80-related ones (gbz80, z180, r2k, r3ka, tlcs90) I noticed some instruction being particularly useful, and making a big difference in code size and code speed. In particular, there are some instructions in the Rabbit that are used by SDCC resulting in much lower code size for the r2k/r3ka backends vs. the z80 backend. If possible, I'd like to see some implemented in the zxn.
ld hl, (hl+NN)
Load hl with the value at the address sum of hl and an 8-bit offset (unsigned is preferable but it deosn't matter much).
This instruction, present in the Rabbits is very useful when working with pointers. Pointers are very common in C code, sometimes explicit, sometimes implicit. Example use cases: Reading a 16-bit value from a pointer. Reading a 16-bit value from a fixed offset into an array. Reading a 16-bit value from a member of a pointed-to struct (e.g. for traversing linked lists).
ld (sp+N), hl
ld (sp+N), de
ld (sp+N), bc
ld (ix+d), hl
ld (ix+d), de
ld (ix+d), bc
ld hl, (sp+N)
ld de, (sp+N)
ld bc, (sp+N)
ld hl, (ix+d)
ld de, (ix+d)
ld bc, (ix+d)
The ix variant is essentially an alternative to the sp variant. Implementing both probably doesn't make that much sense. These instruction are present in the Rabbit.
For C, variables (and function arguments) that cannot be allocated to registers are placed on the stack. Using ix as a stack pointer is an okish way of accessing the stack for 8-bit variables, but it still comes with too much overhead. These instruction allow efficient transfer of 16-bit values between registers and the stack.
bool hl
This instruction present in the Rabbit casts the value in hl to bool and sets the flags accordingly (the z flag is what really matters).
The instruction has a variety of uses. The first one obviously being casts to bool. It also helps a lot with testing 16-bit values for being zero, which is quite common, also for pointers. Another use is efficient zeroing of h (e.g. before loading an 8-bit value into l, and then adding some 16-bit value present in de or bc).
sex gg
This instruction (not implemented in any of the architectures currently supported by SDCC) would for an 16-bit register gg, sign-extend the value in the lower 8 bits into the full 16-bit register. Even having this instruction for just one register pair out of hl, de, bc would be very useful.
For efficiency, on 8-bit systems 8-bit values are used a lot. But sometimes 16 bits are needed for range; the Z80 has 16-bit addresses and the C standard sometimes requires promotion to int. Thus 8-bit values often need to be cast to 16-bit values. For unsigned values, this can easily be done by zero-extending. But for signed values one has to generate relatively complex code. Having direct support in the instruction set would be quite useful; the instruction could also be sued as a building block for wider casts (i.e. 8 to 32 bits, 16 to 32 bits, to 64 bits).
add sp, d
This instruction present on the Rabbit adds a signed 8-bit value to the stack pointer.
After a function call with stack parameters, the stack pointer needs to be adjusted. Similar for function entry and exit at functions that store local variables on the stack. Adjusting the stack pointer is thus a very common task. Unfortunately, on the Z80 doing so is quite complex (except for small values, where inc/dec sp and push/pop can be sued).
Philipp
I've just done a quick test on how often some of the proposed instructions are actually used by SDCC (by compiling the SDCC regression tests for gbz80, z180, r2k). Of course for the total effect one needs to consider more than just their frequency (after all, a rare instruction could save a lot of code at each of the few places where it can be used). Still the data seems helpful.
tst [z180]: 2
bool [r2k]: 14
ld hl, d(iy) [r2k]: 26
ld d (ix), hl [r2k]: 39
mlt [z180]: 103
swap [gbz80]: 141
ld hl, d(ix) [r2k]: 143
ld hl, d(hl) [r2k]: 211
ld d(sp), hl [r2k]: 1361
ld hl, d(sp) [r2k]: 3969
add sp, d [r2k]: 15281
Philipp
The special instructions:
M=4+ 16T ldix ED A4 As LDI, but if byte==A does not copy
M=4+ 21T ldirx ED B4 As LDIR, but if byte==A does not copy
M=4+ 16T* lddx ED AC As LDD, but if byte==A does not copy, and DE is incremented
M=4+ 21T* lddrx ED BC As LDDR, but if byte==A does not copy, and DE is incremented
M=4+ 21T* ldirscale ED B6 As LDIRX, if(hl)!=A then (de)=(hl); HL_A'+=BC'; DE+=DE'; dec BC; Loop.
M=4+ 12T* ldpirx ED B7 (de) = ( (hl&$fff8)+(E&7) ) when != A
T=4+ 8T mirror a ED 24 mirror the bits in A
T=4+ 8T mirror de ED 26 mirror the bits in DE
T=4+ 8T pixeldn ED 93 Move down a line on the ULA screen
T=4+ 8T pixelad ED 94 using D,E (as Y,X) calculate the ULA screen address and store in HL
T=4+ 8T setae ED 95 Using the lower 3 bits of E (X coordinate), set the correct bit value in A
are specifically for games and graphics.
The ldix-family of instructions is for copying graphics while skipping over transparent bytes,
ldirscale is for exploded sprites - the additions are implementing fixed point adjustments to display position and source address.
mirror is for reversing images, pixeldn / pixelad / setae are very specialized for the spectrum's native display file organization.
nextreg is for controlling the hardware state of the machine. These are very useful in practice - I would say it's one of the better additions.
All the above I wouldn't expect the compiler to generate, however they would be present in the libraries and user code.
I agree pop x really doesn't have much use. It would make sense to replace it with add sp,d. push nnn I would keep an eye out for using to push constants on the stack for function calls.
There are some other issues with, eg, the 8x8->16 multiply. There are requests for having a signed counterpart and for bringing back the 32-bit multiply which was found to be very useful for fixed point calculations.
The added instructions do not affect flags because they are implemented outside the z80 alu but I do agree many would be more useful if they did affect flags.
Available space on the fpga also cramps what can be added. We'll see what happens - there is a deadline approaching.
There won't be much use for push NNNN as long as there is at least one free register pair. It is a 4-byte, 22T instruction. Using two old Z80 instructions (ld qq, NNNN; push qq) is 4 bytes, too and at 21T actually faster!
Philipp
And just to note (because it is buried in lots of comments) the NNNN in push NNNN is stored big endian too. I'll be keeping a safe distance from that very bodged op code.
@spth work showing the use of add sp,d looks very promising.
Perhaps that's a strong proposal from this community?
Can we assist / vote / agitate anywhere?
add sp,d would be usable from both sdcc and sccz80 and stop sccz80 from jumping through hoops to preserve the return value with a large frame.
ld hl,(sp+n) and ld (sp+n),hl make significant improvements to the Rabbit generator in sccz80 and are also used by sdcc.
On the Rabbit these instructions are very cheap (2 bytes and 11 or less Rabbit clocks so ~22T). I think @aralbrec has put forward a case for these on several occasions but has sadly been rejected.
In terms of what's being used by sccz80, I think add hl,nnnn is the only one at the moment that's used (I think we do a ld bc,nnn, add hl, bc for structure access), I can see a rules file uses push nnnn, but this will be from the days when that was a quick instruction.
I dislike the mul d,e mnemonic.
It incorrectly states that it does mul with e, and the result is stored in d.
Whereas mul de, or the z180 mnemonic mlt de, more obviously and correctly refers to both the d and e registers being modified.
There seems to be no history of discussion about the instruction mnemonics on the SpecNEXT forum, or elsewhere. So, there seems no avenue to discuss this.
Would it be appropriate to make mul de a synonym of mul d,e, like was done with swap, fill and tst?
For #837.
Noted on the SpecNext Kickstarter - Update 41 that... Z80N has been enhanced with six more instructions: 5 x barrel shift/rotate and 1 JP.
Is there any information on these additional 6 instructions, and their opcodes, etc?
And, should/could they flow into z80asm and into sdcc, too?
I can add them to z80asm, if someone tells which they are.
I have the information; I'll put it here later today. I've been busy lately. I think we should settle on z?80 mnemonics as alternates where there are equivalents and the official mnemonics so there isn't a proliferation. So "mul de" becomes "mlt de" only, eg.
The main list is updated:
https://github.com/z88dk/z88dk/issues/312#issuecomment-322659205
The additions are barrel shifts and a special jp(c) being used for instruction dispatch from disk streaming. It's important for video and other speculated uses.
ldirscale has been dropped for the time being.
I think we should prune away any instruction aliases except for ones that match other zilog related processors. So mlt de accepted as mul d,e (and we lose mul de which will mean fixes in the library) and tst A,NN accepted as test NN.
The list is updated to reflect these things too.
There are other assemblers accepting mul de so maybe that one should be kept.
z80asm will be updated.
done
On Sun, Dec 23, 2018 at 7:21 PM aralbrec notifications@github.com wrote:
There are other assemblers accepting mul de so maybe that one should be
kept.—
You are receiving this because you modified the open/close state.
Reply to this email directly, view it on GitHub
https://github.com/z88dk/z88dk/issues/312#issuecomment-449658292, or mute
the thread
https://github.com/notifications/unsubscribe-auth/AAEUdUucX1bkaTme-CiBsCZJFrSn2gZMks5u79e6gaJpZM4O0DbA
.