Simple DMA example

Do you live and breathe hexadecimal? Do you like speaking to hardware directly?

Moderator: Programming Moderators

User avatar
SevenFFF
Posts: 221
Joined: Mon Jun 05, 2017 5:30 pm
Location: USA

Simple DMA example

Post by SevenFFF » Mon Feb 26, 2018 3:02 am

I've been reading the chip specs and the wiki entry, and making less than complete sense of it. While I persevere, would it be possible for somebody familiar with DMA to give a simple concrete example?

Say, a memory-to-memory copy from $C000 to $4000, $1B00 bytes long.

Happy even if you just list out the bytes. Decoding them will help me cement my understanding :)

Cheers, Robs
Robin Verhagen-Guest
SevenFFF / Threetwosevensixseven / colonel32
NXtel Spectron 2084blog

User avatar
SevenFFF
Posts: 221
Joined: Mon Jun 05, 2017 5:30 pm
Location: USA

Re: Simple DMA example

Post by SevenFFF » Tue Feb 27, 2018 12:28 am

OK, having read and absorbed the Zilog manual, this is what I came up with. And a bunch of variants - without timing, different modes, etc. None of them work on the real hardware, sadly. Code assembles in Zeus.

Please give me a clue what I'm doing wrong!

Code: Select all

; dmatest.asm

zeusemulate             "128K"
zoLogicOperatorsHighPri = false
zoSupportStringEscapes  = false
Zeus_PC                 = Start
Stack                   equ Start
optionsize              5
UseDMA                  optionbool 15, -15, "Use LDIR instead of DMA", false

                        org $8000
Start                   proc
                        di
                        ld sp, Stack
                        ld a, $BE
                        ld i, a
                        im 2
                        call Cls
                        call ClsAttr
                        ei

                        if not enabled UseDMA

                          ; REGISTER 0 GROUP
                          ; ----------------
                          ; Bit     Effect
                          ; 7       Must be 0 (register bitmask)
                          ; 6       Write associated register flag -> Block length high byte
                          ; 5       Write associated register flag -> Block length low byte
                          ; 4       Write associated register flag -> Port A address high byte
                          ; 3       Write associated register flag -> Port A address low byte
                          ; 2       Direction: 0 B->A, 1 A->B
                          ; 1-0     Operation: 01 transfer, 10 search, 11 search/transfer. Must not
                          ;                    be 00 or will conflict with bitmasks for registers 1/2.

                          DMA(%0 11 11 1 01)            ; A->B transfer, Port A address and block length to follow.
                          DMA($00)                      ; Port A address low byte (no need to send this byte if $00?)
                          DMA($C0)                      ; Port A address high byte
                          DMA($00)                      ; Block length address low byte (no need to send this byte if $00?)
                          DMA($1B)                      ; Block length address high byte

                          ; REGISTER 1 GROUP
                          ; ----------------
                          ; Bit     Effect
                          ; 7       Must be 0 (register bitmask)
                          ; 6       Write associated register flag -> Port A timing byte
                          ; 4-5     Port A behavior: 00 decrements, 01 increments, 10/11 fixed
                          ; 3       Port A type: 0 Memory, 1 I/O port
                          ; 2-0     Must be %100 (register bitmask)

                          DMA(%0 1 01 0 100)            ; Port A increments, type memory, timing byte follows

                          ; PORT A TIMING BYTE
                          ; -----------------
                          ; Bit     Effect
                          ; 7       0 /WR ends 1/2 cycle early
                          ; 6       0 /RD ends 1/2 cycle early
                          ; 5       Must be 0
                          ; 4       Must be 0
                          ; 3       0 /MREQ ends 1/2 cycle early
                          ; 2       0 /IORQ ends 1/2 cycle early
                          ; 1-0     00 Cycle length 4
                          ;         01 Cycle length 3
                          ;         10 Cycle length 2
                          ;         11 Do not use

                          DMA(%11 00 11 10)             ; Port A cycle length 2

                          ; REGISTER 2 GROUP
                          ; ----------------
                          ; Bit     Effect
                          ; 7       Must be 0 (register bitmask)
                          ; 6       Write associated register flag -> Port B timing byte
                          ; 4-5     Port B behavior: 00 decrements, 01 increments, 10/11 fixed
                          ; 3       Port B type: 0 Memory, 1 I/O port
                          ; 2-0     Must be %000 (register bitmask)

                          DMA(%0 1 01 0 000)            ; Port B increments, type memory, timing byte follows

                          ; PORT B TIMING BYTE
                          ; -----------------
                          ; Bit     Effect
                          ; 7       0 /WR ends 1/2 cycle early
                          ; 6       0 /RD ends 1/2 cycle early
                          ; 5       Must be 0
                          ; 4       Must be 0
                          ; 3       0 /MREQ ends 1/2 cycle early
                          ; 2       0 /IORQ ends 1/2 cycle early
                          ; 1-0     00 Cycle length 4
                          ;         01 Cycle length 3
                          ;         10 Cycle length 2
                          ;         11 Do not use

                          DMA(%11 00 11 10)             ; Port B cycle length 2

                          ; REGISTER 3 GROUP
                          ; ----------------
                          ; Controls search match behaviour, not needed

                          ; REGISTER 4 GROUP
                          ; ----------------
                          ; Bit     Effect
                          ; 7       Must be 1 (register bitmask)
                          ; 6-5     Operation mode: 00 byte at a time (releases and reclaims bus after every byte)
                          ;           01 continuous (holds bus continuously, if a port is not ready,
                          ;              stops and waits until it is)
                          ;           10 burst (holds but continuously, if a port is not ready,
                          ;              stops and releases busses)
                          ;           11 do not change
                          ; 4       Write associated register flag -> Interrupt control byte
                          ; 3       Write associated register flag -> Port B address high byte
                          ; 2       Write associated register flag -> Port B address low byte
                          ; 1-0     Must be %01 (register bitmask)

                          DMA(%1 10  x 11 01)           ; Burst, port B address follows
                          DMA($00)                      ; Port B address low byte (no need to send this byte if $00?)
                          DMA($40)                      ; Port B address high byte

                          ; REGISTER 5 GROUP
                          ; ----------------
                          ; Bit     Effect
                          ; 7-6     Must be %10 (register bitmask)
                          ; 5       Restart (1) or stop (0) on end of block
                          ; 4       CE/Wait multiplex (1) or CE only (0)
                          ; 3       Ready active low (0) or high (1)
                          ; 2-0     Must be %010 (register bitmask)

                          DMA(%10 0 x x 010)            ; Stop on end of block

                          ; REGISTER 6 GROUP
                          ; ----------------
                          ; Rather than setting bits, register 6 accepts commands which take instant effect.
                          ; The command codes below include the bitmask for register 6.
                          ;
                          ; Command         Effect
                          ; $C3 (%11000011) Reset
                          ; $C7 (%11000111) Reset Port A Timing
                          ; $CB (%11001011) Reset Port B Timing
                          ; $CF (%11001111) Load
                          ; $D3 (%11010011) Continue
                          ; $AF (%10101111) Disable Interrupts
                          ; $AB (%10101011) Enable Interrupts
                          ; $A3 (%10100011) Reset and disable interrupts
                          ; $B7 (%10110111) Enable after RETI
                          ; $BF (%10111111) Read status byte
                          ; $BF (%10001011) Reinitialize status byte
                          ; $A7 (%10100111) Start read sequence
                          ; $B3 (%10110011) Force ready
                          ; $87 (%10000111) Enable DMA
                          ; $83 (%10000011) Disable DMA
                          ; $BB (%10111011) Write associated register command -> Read Mask

                          DMA($B3)                      ; Force ready (maybe not needed?)
                          DMA($87)                      ; Finally, enable DMA to begin

                          Border(Green)                 ; Green border means we did (or failed to do!) DMA

                        else
                          ld hl, $C000
                          ld de, $4000
                          ld bc, $1B00
                          ldir
                          Border(Blue)                  ; Blue border means we did LDIR
                        endif
Loop:
                        halt
                        jp Loop
pend



DMA                     macro(Byte)                     ; Can use OTIR for the entire DMA programming sequence,
                        ld a, Byte                      ; but get it working with a regular OUT first...
                        out ($0B), a                    ; $xx0B = MB02 DMA Port (can also use $xx6B)
mend



Cls                     proc
                        di
                        ld (EXIT+1), sp                 ; Save the stack
                        ld sp, $5800                    ; Set stack to end of screen
                        ld de, $0000                    ; All pixels unset
                        ld b, e                         ; Loop 256 times: 12 words * 256 = 6144 bytes
                        noflow
CLS_LOOP:               defs 12, $D5                    ; 12 lots of push de
                        djnz CLS_LOOP
EXIT:                   ld sp, $0000                    ; Restore the stack
                        ei
                        ret
pend



ClsAttr                 proc
                        ClsAttrFull(DimBlackWhiteP)
                        ret
pend



ClsAttrFull             macro(Colour)
                        ld a, Colour
                        ld hl, ATTRS_8x8
                        ld (hl), a
                        ld de, ATTRS_8x8+1
                        ld bc, ATTRS_8x8_COUNT-1
                        ldir
mend



Border                  macro(Colour)
                        if Colour=0
                          xor a
                        else
                          ld a, Colour
                        endif
                        out (ULA_PORT), a
mend


SCREEN                  equ $4000                       ; Start of screen bitmap
ATTRS_8x8               equ $5800                       ; Start of 8x8 attributes
ATTRS_8x8_END           equ $5B00                       ; End of 8x8 attributes
ATTRS_8x8_COUNT         equ ATTRS_8x8_END-ATTRS_8x8     ; 768
ULA_PORT                equ $FE                         ; out (254), a
DimBlackWhiteP          equ $38
Black                   equ 0
Blue                    equ 1
Red                     equ 2
Magenta                 equ 3
Green                   equ 4
Cyan                    equ 5
Yellow                  equ 6
White                   equ 7

include                 "ParaBootStub.inc"      ; Parasys remote debugger slave stub

org $BE00                                       ; Have an IM 2 ISR at $BE00...
                        loop 257
                          db $BF
                        lend
org $BFBF
                        ei                      ; ...which doesn't do anything
                        reti                    ; except avoid the ROM IM 1 ISR being called

org $C000
import_bin              "..\images\test.scr"    ; Test screen used as the source of the copy

if zeusver < 72                                 ; Make sure we have the latest features ('don't care' x bits in binary literals)
  zeuserror "Upgrade to Zeus v3.99 or above, available at http://www.desdes.com/products/oldfiles/zeus.htm."
endif

output_z80              "..\bin\dmatest.z80", $0000, Start
Robin Verhagen-Guest
SevenFFF / Threetwosevensixseven / colonel32
NXtel Spectron 2084blog

User avatar
SevenFFF
Posts: 221
Joined: Mon Jun 05, 2017 5:30 pm
Location: USA

Re: Simple DMA example

Post by SevenFFF » Tue Feb 27, 2018 2:03 am

Cracked it, finally. Will write up the working code tomorrow...
Robin Verhagen-Guest
SevenFFF / Threetwosevensixseven / colonel32
NXtel Spectron 2084blog

User avatar
Timbucus
Posts: 229
Joined: Tue May 30, 2017 7:43 pm

Re: Simple DMA example

Post by Timbucus » Tue Feb 27, 2018 11:40 pm

The ZX Next DMA is not a full implementation and I understand there are Typo's in official documents the best source at the moment is Z88DK as Allan has been doing sterling work documenting the Next...

https://github.com/z88dk/z88dk/blob/mas ... zxn_dma.m4
I'm Infinite Imaginations when not in work... PAWS for thought.

User avatar
SevenFFF
Posts: 221
Joined: Mon Jun 05, 2017 5:30 pm
Location: USA

Re: Simple DMA example

Post by SevenFFF » Wed Feb 28, 2018 12:30 am

Cheers Tim. Good to know that. I mostly had it right, but I missed a few setup commands and a few execute prep commands. Been sick today with a migraine so not had chance to write up yet. Yep, AA is a legend :)
Robin Verhagen-Guest
SevenFFF / Threetwosevensixseven / colonel32
NXtel Spectron 2084blog

User avatar
robpearmain
Posts: 59
Joined: Tue May 30, 2017 5:35 pm
Location: York
Contact:

Re: Simple DMA example

Post by robpearmain » Tue Apr 17, 2018 1:33 pm

Please could you post your working example

Thanks
Rob Pearmain
Bipboi (Zx Spectrum 48k), Harry Hedgehog (ZX Spectrum [1K]), Luna C (PC), Turbotoons (PC)

ZX Spectrum 48k, +, 128k, Next (board)

User avatar
SevenFFF
Posts: 221
Joined: Mon Jun 05, 2017 5:30 pm
Location: USA

Re: Simple DMA example

Post by SevenFFF » Tue Apr 17, 2018 3:51 pm

There's a DMA copy and a DMA fill here, with LDIR copy and LDIR fill for comparison. See the DMACopy.asm file.
Robin Verhagen-Guest
SevenFFF / Threetwosevensixseven / colonel32
NXtel Spectron 2084blog

User avatar
robpearmain
Posts: 59
Joined: Tue May 30, 2017 5:35 pm
Location: York
Contact:

Re: Simple DMA example

Post by robpearmain » Fri Apr 20, 2018 8:31 am

Awesome, thanks
Rob Pearmain
Bipboi (Zx Spectrum 48k), Harry Hedgehog (ZX Spectrum [1K]), Luna C (PC), Turbotoons (PC)

ZX Spectrum 48k, +, 128k, Next (board)

User avatar
robpearmain
Posts: 59
Joined: Tue May 30, 2017 5:35 pm
Location: York
Contact:

Re: Simple DMA example

Post by robpearmain » Sun Apr 22, 2018 6:59 am

SevenFFF wrote:
Tue Apr 17, 2018 3:51 pm
There's a DMA copy and a DMA fill here, with LDIR copy and LDIR fill for comparison. See the DMACopy.asm file.
Thanks for providing all the code. I guess the “ParaSys boot stub - it asks Zeus to provide the slave code, loads it into memory and runs it“. Do I need to include this? I notice you have a CSpect flag, I guess for emulation, to ignore if running in CSpect (where is this set?)
Rob Pearmain
Bipboi (Zx Spectrum 48k), Harry Hedgehog (ZX Spectrum [1K]), Luna C (PC), Turbotoons (PC)

ZX Spectrum 48k, +, 128k, Next (board)

User avatar
SevenFFF
Posts: 221
Joined: Mon Jun 05, 2017 5:30 pm
Location: USA

Re: Simple DMA example

Post by SevenFFF » Tue Apr 24, 2018 3:37 pm

robpearmain wrote:
Sun Apr 22, 2018 6:59 am
Thanks for providing all the code. I guess the “ParaSys boot stub - it asks Zeus to provide the slave code, loads it into memory and runs it“. Do I need to include this? I notice you have a CSpect flag, I guess for emulation, to ignore if running in CSpect (where is this set?)
Sorry Rob, yes you can remove all the ParaSys code. I have Zeus set up to send code down a USB cable to the ESP socket on the board every time I assemble. Let me know if you're interested in getting that working, otherwise you can remove it all.

The CSpect flag actually gets set by a Zeus user-defined checkbox (which is helpfully missing from the code). So in fact it will always be false for you. Sorry! That's a nice feature of the enabled operator in Zeus - it degrades gracefully by returning false when the symbol isn't defined.

Code: Select all

optionsize              12
Mode                    optionlist 15, -15, "Mode","DMA Copy","DMA Fill","LDIR Copy","LDIR Fill"
Cspect                  optionbool 155, -13, "Cspect", false
The only reason it is there is because CSpect returns 255 for unemulated ports, which confuses ParaSys by implying there is data ready to receive on the UART, but then no data ever comes and it sits there indefinitely waiting. It has no use in this code other than to disable ParaSys, so you can safely ignore it :)

Generally, because I code for the board not an emulator, there is stuff I write that neither CSpect or ZEsarUX emulates yet. So I have these flags so I can write a benign alternative that stops them crashing, whenever I come across something like that.
Robin Verhagen-Guest
SevenFFF / Threetwosevensixseven / colonel32
NXtel Spectron 2084blog

Post Reply