NYYRIKKI's HelloWorld code

By janghang

Expert (120)

janghang's picture

11-07-2011, 18:51

The following is a part of NYYRIKKI's HelloWorld code. I have some questions on NMI related routines. Please see the routine in bold. I would like to understand this.

I guess that probably the program tried to avoid NMI. Whenever "JR OVERNMI" is executed, it jumps over to "OVERNMI: DJNZ..." until B becomes Zero. So it would never execute the rest of commands including NOP, NOP, RETN in COPYORDER. Why did the author put some commands which would not be executed eventually? is it possible to replace "JR OVERNMI" and the rest ones with just "DJNZ COPYORDER" ?

Anyway, could anybody explain about why the code is written like this?

...

;----------------------------------------------------------
; Let's set write address to start of name table
XOR A
OUT 99h,A
LD A,40h
OUT 99h,A

; Let's put characters to screen
LD HL,ORDER
LD B,ORDER_END-ORDER
COPYORDER:
LD A,(HL)
OUT 98h,A
INC HL
JR OVERNMI
NOP
NOP

; Here is address #66, that is entry for NMI
RETN ;Return from NMI

OVERNMI:
DJNZ COPYORDER

; The end
HALT

; Character set:
; --------------
ORDER:
DEFB 1,2,3,3,4,0,5,4,6,3,7
ORDER_END:

CHARS:

; H
DEFB %11111000
DEFB %10001000
DEFB %10001000
DEFB %11111000
DEFB %10001000
DEFB %10001000
DEFB %11111000
DEFB %00000000
...

Login or register to post comments

By jltursan

Prophet (2530)

jltursan's picture

11-07-2011, 19:10

Because this piece of code seems to be designed to run in the lowest memory and the NMI jump address is located in the middle of it, in address $66 and occupying 3 bytes; so the code need to leave this addresses free of "harmful" code, that's why there's an opcode sequence NOP,NOP,RETN.

By janghang

Expert (120)

janghang's picture

11-07-2011, 19:12

Thanks, jltursan. but how could we know the address around NOP, NOP, RETN is $66?

Because this piece of code seems to be designed to run in the lowest memory and the NMI jump address is located in the middle of it, in address $66 and occupying 3 bytes; so the code need to leave this addresses free of "harmful" code, that's why there's an opcode sequence NOP,NOP,RETN.

By jltursan

Prophet (2530)

jltursan's picture

11-07-2011, 22:08

I'm just guessing. The full listing would help; but seems written in a Z80 standard way; so the most probably starting address is 0 (ORG 0) as it's usually the first address executed. Of course there must be some lines before the code you posted, once assembled I'm sure that those opcodes will be in $66, $67 and $68.

By Tanni

Hero (556)

Tanni's picture

11-07-2011, 22:22

As far as I know, in an MSX system, NMI isn't used. But since in future versions, NMI could be used, a good programmer would avoid using the associated memory locations.


;----------------------------------------------------------
; Let's set write address to start of name table
XOR A
OUT 99h,A
LD A,40h
OUT 99h,A

; Let's put characters to screen
LD HL,ORDER
LD B,ORDER_END-ORDER
COPYORDER:
LD A,(HL)
OUT 98h,A
INC HL
JR OVERNMI
NOP
NOP

; Here is address #66, that is entry for NMI
RETN ;Return from NMI

OVERNMI:
DJNZ COPYORDER

; The end
HALT

To my mind, address #66 is at the first NOP, otherwise it wouldn't make much sense.

See datasheets.chipdb.org/Zilog/Z80/z80-documented-v0.91.pdf, page 19.

A disassembler should add the addresses before the mc statement.

0063        INC HL
0064        JR OVERNMI
0066        NOP              ; Here is address #66, that is entry for NMI
0067        NOP
0068        RETN             ; Return from NMI

            OVERNMI:

0069        DJNZ COPYORDER 

By janghang

Expert (120)

janghang's picture

11-07-2011, 22:55

Thanks, guys. Here the full listing is:


; This is a "Hello World" program for Z80 and TMS9918 / TMS9928 / TMS9929 /
; V9938 or V9958 VDP.
; That means that this should work on SVI, MSX, Colecovision, Memotech,
; and many other Z80 based home computers or game consoles.
;
; Because we don't know what system is used, we don't know where RAM
; is, so we can't use stack in this program.
;
; This version of Hello World was written by Timo "NYYRIKKI" Soilamaa
; 17.10.2001
;
;----------------------------------------------------------------------
; Configure this part:

DATAP: EQU #98 ; VDP Data port #98 works on all MSX models
; (TMS9918/TMS9929/V9938 or V9958)
; #80 works on SVI 
; (for other platforms you have to figure this out by your self)

CMDP: EQU #99 ; VDP Command port #99 works on all MSX models
; (TMS9918/TMS9929/V9938 or V9958)
; #81 works on SVI
; (for other platforms you have to figure this out by your self)
;-----------------------------------------------------------------------
; Program starts here:

ORG 0 ; Z80 starts always from here when power is turned on
DI ; We don't know, how interrupts works in this system, so we disable them.

; Let's set VDP write address to #0000
XOR A
OUT (CMDP),A
LD A,#40
OUT (CMDP),A

; Now let's clear first 16Kb of VDP memory
LD B,0
LD HL,#3FFF
LD C,DATAP
CLEAR:
OUT (C),B
DEC HL
LD A,H
OR L
NOP ; Let's wait 8 clock cycles just in case VDP is not quick enough.
NOP
JR NZ,CLEAR

; Now it is time to set up VDP registers:
;----------------------------------------
; Register 0 to #0
;
; Set mode selection bit M3 (maybe also M4 & M5) to zero and 
; disable external video & horizontal interrupt
LD C,CMDP
LD E,#80

OUT (C),A
OUT (C),E
;---------------------------------------- 
; Register 1 to #50
;
; Select 40 column mode, enable screen and disable vertical interrupt

LD A,#50
INC E
OUT (C),A
OUT (C),E
;---------------------------------------- 
; Register 2 to #0
;
; Set pattern name table to #0000

XOR A
INC E
OUT (C),A
OUT (C),E
;---------------------------------------- 
; Register 3 is ignored as 40 column mode does not need color table
;
INC E
;---------------------------------------- 
; Register 4 to #1
; Set pattern generator table to #800

INC A
INC E

OUT (C),A
OUT (C),E
;---------------------------------------- 
; Registers 5 (Sprite attribute) & 6 (Sprite pattern) are ignored 
; as 40 column mode does not have sprites

INC E
INC E
;---------------------------------------- 
; Register 7 to #F0
; Set colors to white on black

LD A,#F0
INC E
OUT (C),A
OUT (C),E
;----------------------------------------

; Let's set VDP write address to #808 so, that we can write
; character set to memory
; (No need to write SPACE it is clear char already)
LD A,8
OUT (C),A
LD A,#48
OUT (C),A

; Let's copy character set
LD HL,CHARS
LD B, CHARS_END-CHARS
COPYCHARS:
LD A,(HL)
OUT (DATAP),A
INC HL
NOP ; Let's wait 8 clock cycles just in case VDP is not quick enough.
NOP
DJNZ COPYCHARS

; Let's set write address to start of name table
XOR A
OUT (C),A
LD A,#40
OUT (C),A

; Let's put characters to screen
LD HL,ORDER
LD B,ORDER_END-ORDER
COPYORDER:
LD A,(HL)
OUT (DATAP),A
INC HL

JR OVERNMI
NOP
NOP

; Here is address #66, that is entry for NMI
RETN ;Return from NMI

OVERNMI:
DJNZ COPYORDER

; The end
HALT

; Character set:
; --------------
ORDER:
DEFB 1,2,3,3,4,0,5,4,6,3,7
ORDER_END:

CHARS:

; H
DEFB %10001000
DEFB %10001000
DEFB %10001000
DEFB %11111000
DEFB %10001000
DEFB %10001000
DEFB %10001000
DEFB %00000000
; e
DEFB %00000000
DEFB %00000000
DEFB %01110000
DEFB %10001000
DEFB %11111000
DEFB %10000000
DEFB %01110000
DEFB %00000000
; l
DEFB %01100000
DEFB %00100000
DEFB %00100000
DEFB %00100000
DEFB %00100000
DEFB %00100000
DEFB %01110000
DEFB %00000000
; o
DEFB %00000000
DEFB %00000000
DEFB %01110000
DEFB %10001000
DEFB %10001000
DEFB %10001000
DEFB %01110000
DEFB %00000000
; W
DEFB %10001000
DEFB %10001000
DEFB %10001000
DEFB %10101000
DEFB %10101000
DEFB %11011000
DEFB %10001000
DEFB %00000000

; r
DEFB %00000000
DEFB %00000000
DEFB %10110000
DEFB %11001000
DEFB %10000000
DEFB %10000000
DEFB %10000000
DEFB %00000000
; d
DEFB %00001000
DEFB %00001000
DEFB %01101000
DEFB %10011000
DEFB %10001000
DEFB %10011000
DEFB %01101000
DEFB %00000000
chars_end:

By NYYRIKKI

Enlighted (5836)

NYYRIKKI's picture

12-07-2011, 01:09

Those NOP's are just to adjust RETN exactly to address #66 That is the Z80 internal entry address for NMI and it is called only if hardware causes non maskable interrupt.

In systems like MSX the code will not ever be executed, but some other systems might have for example hardware timer that causes the routine (RETN) to be executed. Sure this is not perfect solution as if the harware interrupt needs to be handled somehow externally to release the interrupt then this will hang. I have also not set the SP register, so this is another point where this may fail if the SP does not happen to be inside RAM area. In general routine this is anyway something that I can't take care.

How ever I find it quite unlikely that any system would cause NMI after power on without any initialization. I think that would be pretty bad design. On the other hand for example in C64 you can cause NMI from keyboard (RUN-STOP + RESTORE)... and I think same is possible also on PC's (Ctrl + Alt + Del)

JR inside DJNZ loop is used to increase the delay between OUT's. In MSX such a big delay is not needed, but there might be other systems that have bigger difference between Z80 clock and TMS99xx clock. (ie. Z80 running on 8MHz or something like that)

So... these things are done just to be on safe side... This is not MSX specific code.

By Tanni

Hero (556)

Tanni's picture

12-07-2011, 02:11

That's what I don't understand: If RETN is on location 0066H, then you have four bytes left before it, occupied by

JR OVERNMI
NOP
NOP

This could be used for the DJNZ and a HALT or RET and there would still be one byte unused before the NMI entry point, so you needn't jump over the NMI routine.

But in this case (RETN is on 0066H and also uses 0067H because it is a two byte instruction) this entry would occupy three byte where the last one is already taken by the first byte of the DJNZ instruction.

OVERNMI:
DJNZ COPYORDER

So if the code is used on a system with NMI, the code would distroy the jump to the NMI routine unless the DJNZ opcode happens to be the same as the last byte (high byte) of the jump address. And HALT stops the CPU only until the next interrupt.

By NYYRIKKI

Enlighted (5836)

NYYRIKKI's picture

12-07-2011, 08:43

@Tanni Maybe I should highlight some things again:JR inside DJNZ loop is used to increase the delay between OUT's.This means that the jump causes delay it self. Similar delay and the loop does not fit to the 4 bytes. It looks ugly, but works.This is not MSX specific code.That means that there is no "reserved number of bytes" in #66