Z88dk: z80asm: implement zx next opcodes as z80n (was: serving fpga z80 variants)

Created on 11 Aug 2017  Â·  107Comments  Â·  Source: z88dk/z88dk

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.

enhancement z80asm

All 107 comments

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:

  • in zxn it is coded as ED 30 and means HLDE := HL*DE
  • in r2k it is coded as F7 and means HLBC := BC*DE

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)

Issue_312_zxn_optimisations.txt

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...

  1. Shouldn't fillde be fill de (as in verb-object)?
  2. Shouldn't 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 swapnib
  • fill de as synonym to fillde
  • test as synonym to tst

New instructions that would help most directly would include stack relative addressing.

I looked at two sources for inspiration:

  1. The Rabbit family of processors which are based on the z80 and z180. They were
    also constrained by opcode space and added some instructions for their compiler.

https://github.com/z88dk/techdocs/blob/master/rabbit/RabbitInstructions.pdf

  1. The Z380 family by Zilog. They expanded the instruction set and have a full
    set of stack relative instructions as well as 16-bit logical, multiplication,
    division and accessing the exx set in a finer grain. I highly recommend that Victor
    look at how Zilog added the instructions while remaining compatible (they defined
    escape opcodes in the instruction listing, eg) and borrow some of their names.
    Their degree of compatibility won't be as high because the z80 in the next must
    also implement crazy undocumented opcodes.

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.1

to 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 mlt there 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 n

I 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
.

Was this page helpful?
0 / 5 - 0 ratings