Error creating a compile for a zxn dot command

ZX80, ZX 81, ZX Spectrum, TS2068 and other clones
castingflame
Member
Posts: 15
Joined: Wed Apr 18, 2018 12:54 pm

Error creating a compile for a zxn dot command

Post by castingflame »

Hey all.

I am very much a novice but still plugging away.

I am trying to create a dot command for the Spectrum Next from assembly source. I have seen a command from someone else that worked correctly on my PC but it was using C not Assembly.

I modified it to the following...

Code: Select all

zcc +zxn -vn -startup=30 -clib=sdcc_iy -SO3 --max-allocs-per-node200000 --opt-code-size mm.asm -o mm -subtype=dot -create-app
I get the following error...

Code: Select all

C:\ZX\Dot Commands\MM>zcc +zxn -vn -startup=30 -clib=sdcc_iy -SO3 --max-allocs-per-node200000 --opt-code-size i2cmm.asm -o mm -subtype=dot -create-app
Error at file 'C:\z88dk\lib\config\..\..\\libsrc\_DEVELOPMENT\target\zxn\zxn_crt.asm' line 9134: symbol '_main' not defined
1 errors occurred during assembly
Errors in source file C:\z88dk\lib\config\..\..\\libsrc\_DEVELOPMENT\target\zxn\zxn_crt.asm:
Error at file 'C:\z88dk\lib\config\..\..\\libsrc\_DEVELOPMENT\target\zxn\zxn_crt.asm' line 9134: symbol '_main' not defined
I read in my travels today that the "Entry Point" needs to be called _main in assembly. While not being really sure what that meant I renamed my MAIN to _main but it did not resolve the issue.


Should I suppose to be using zcc for assembly?

Thanks for any help you can give, Paul
alvin
Well known member
Posts: 1872
Joined: Mon Jul 16, 2007 7:39 pm

Post by alvin »

I can give a more complete answer when I have time later tonight.

The immediate problem is you are not exporting the name "_main" from your asm file. Within each file all symbols are local unless you explicitly export them with a PUBLIC directive. The crt that starts the program (the startup value chooses one) tries to "call _main" so it must be able to see "_main".

Your asm file should probably start with something like this:

Code: Select all

SECTION code_user

PUBLIC _main

_main:

...
The compile line can be:

zcc +zxn -vn -startup=31 -clib=sdcc_iy mm.asm -o mm -subtype=dot -create-app

The "-SO3 --max-allocs-per-node200000 --opt-code-size" options only affect c files (they control the optimization level). "-startup=31" chooses a crt without stdin,stdout,stderr created which will be smaller but takes away printf,scanf,etc which I assume you are not using from asm. "-startup=30" attaches stdout.stderr to rst$10 for printing.

By default the dot command crt also processes the command line of the dot command into strings. On the stack will be:

char **argv (16 bit pointer)
int argc (16-bit number)
ret (return address to exit the program)

This added beaviour of the crt can be changed using pragmas which are communicated to the crt. I'll explain more about this tonight but putting your code into a section and making main public should solve the assemble issue.
castingflame
Member
Posts: 15
Joined: Wed Apr 18, 2018 12:54 pm

Post by castingflame »

I am definitely not doing it as you suggested. I will give it a go and also try and find some general example speccy assembly for z88dk too.

I?ll report back for others in the future.

Thank you very much Alvin for taking the time to help, it is very much appreciated.
alvin
Well known member
Posts: 1872
Joined: Mon Jul 16, 2007 7:39 pm

Post by alvin »

Continuing with a little more information.

z88dk works like commercial tools rather than like the assemblers normally used with the spectrum. So there are object files, libraries and linking and along with that comes the concept of local and global scope of labels.

A project can consist of a lot of files of various types *.asm (your asm code eg), *.c, *.o (object file), *.lib (library file), *.asm.m4 (asm code preprocessed by m4), *.c.m4, *.h.m4, *.inc.m4, etc... zcc knows what to do with each of them so *.asm goes to the assembler which turns it into an object file, *.c goes to the c compiler which turns it into an asm file then zcc sends to the assembler to turn into an object file, *.asm.m4 goes to m4 then the assembler to make an object file, etc. The road always leads to an object file. The last step, if a binary is being made, is to call the linker to put everything together. At the linking stage, all code is placed in memory according to a memory map, all unknown symbols are resolved, code is pulled out of libraries as needed, and a binary is produced.

Most spectrum assemblers don't have a concept of object file so code is usually organized as a bunch of asm files included into one single master file that is assembled. When you pull a lot of asm together in one place, you'll very likely get symbol collisions, for example you may have used "loop" in two different asm files. To avoid this, these assemblers often invent things like procs to make certain symbols local to a finite range of code so these symbol collisions don't occur.

With normal tools, source code is organized differently. You don't include asm files into a single master file for assembly. Instead you keep all your asm files logically separated and let the linker put them together. In z88dk, symbols in each file are local and cannot be seen outside the file they are defined in. To make them visible outside the file you make them global by using a PUBLIC directive. If you want to see a symbol defined elsewhere in a file you must import the symbol by declaring it EXTERN.

Suppose you have two asm files:

delay1.asm

Code: Select all

SECTION code_user

PUBLIC delay1   ;; make this symbol globally visible
EXTERN delay2   ;; this symbol is defined someplace else

delay1:

   ld b,50

loop:

   push bc
   call delay2
   pop bc

   djnz loop
   ret
delay2.asm

Code: Select all

SECTION code_user

PUBLIC delay2   ;; make this symbol globally visible

delay2:

   ld b,100

loop:

   djnz loop
   ret
To assemble, just list both files on the zcc compile line.

Despite both asm files defining "loop", there is no symbol conflict because "loop" can only be seen inside each of those files. delay1 and delay2 have been made public so their names are available in the global scope. delay1.asm imports symbol "delay2" from the global scope with EXTERN and calls the label. The linker will patch in delay2's address when the binary is produced. In delay2.asm, the symbol "delay1" is undefined despite it being available in the global scope because it was not imported with EXTERN. In fact another "delay1" could be defined inside delay2.asm that would be different from the public one because it would be local to delay2.asm only.

This sort of behaviour is essential for making object files, libraries and linking.

In your case, the startup code in the crt wants to call "_main" to start the program. So you have to supply "_main" in your asm somewhere and make the label globally visible with a PUBLIC directive otherwise the crt will not be abe to import the symbol with "EXTERN _main".

The other thing is how code and data is placed in memory. This is done by the linker using a memory map supplied either by the library or you. Normally you'd just use the one z88dk supplies. The memory map is organized as a sequence of named sections with each section optionally getting an org address. Named sections are in fact containers. If you specify that code or data goes to a particular container, the linker will pile your stuff there. In asm you can switch containers with a SECTION directive. The C compiler can be told on the command line to change SECTION it assigns code and data too as well. The memory map is going to define where these containers are placed. So some sections may be in the "main binary", they may be in a specific page or bank in the zxnext's memory space or they may be something you want output separately as a file. Your asm has to say which section you want to place stuff into and the one suggested "code_user" will be in the "main binary" - there is a little about sections discussed here https://www.z88dk.org/wiki/doku.php?id= ... y_language

The z88dk-supplied memory map has a concept of the "main binary" which is your code and data loaded into the current memory configuration of the spectrum or spectrum next. So this is stuff destined to be loaded as LOAD""CODE into banks 5,2,0 on a spectrum. Ie- it's the normal block of machine code. In addition to this there are sections defined (discussing the next here) to cover all of divmmc memory, page memory space and bank memory space. If you want to place something in page 50, you can assign it to "SECTION PAGE_50". z88dk's appmake (call by zcc if -create-app is present) knows about the zx next's memory and will propery generate output for it from the binaries that the linker produces. There is a further complication with the zx next in that memory can be paged into any mmu slot. So there is a distinction between physical memory pages and org. You may want to place something into physical PAGE_50 with org set to 0x4000 so you can place page 50 into mmu2 and execute that code at address 0x4000.. At the same time you may want to place something into PAGE_50 that you org at 0xc200 so that you can place page 50 into mmu6 and execute that code at address 0xc200. z88dk also accommodates this but I don't want to talk too much about this now or this is going to get even longer :) Anyway the point is SECTION allows you to place stuff and run stuff from whereever you want in the memory space.

.. something specifically about dot commands next.
castingflame
Member
Posts: 15
Joined: Wed Apr 18, 2018 12:54 pm

Post by castingflame »

I did manage to get a dot command built Alvin from someone elses source code but it did not run 100% correctly. I spent today just making a little bit of test demo assembly that ran as a .tap and I was going to try that again tomorrow to see if I can make a dot command out of it.

All of your above comments nearly make sense to me. I don't quite understand in practice about the SECTIONs and ORGs, I'll have a look at your link in the morning and soak it in. I just used ORG 0x2000 (or what ever the dot command location should be). I'll review.

I am building a little hardware device for the Next and the dot command will be used for my testing purposes only. Obviously it will be a very valuable for me to be able to write them.

I feel like every day is a massive learning curve but it can be very personally rewarding. I thank you again for taking the time to help me and explain in detail. I'll keep plugging away tomorrow and feedback how it is going.

Kind Regards, Paul
castingflame
Member
Posts: 15
Joined: Wed Apr 18, 2018 12:54 pm

Post by castingflame »

Today I spent the morning converting my basic little 'message display & border flash' assembly file to run on z88dk. I got there just after lunch. I then tried to make a dot command out of it. While it was created, it did not work as expected. It launched but didn't really do a lot.

Is there any issue using the speccy ROM routines from within my dot command?

...

After reflecting a bit more on the dot command notes I removed the 'message to screen' section out of my code and left just the border flashing bit. Guess what, It worked!

Would this happen to be, by any chance, because I am using the PR_STRING routine which is located at 0x203C and my ORG is at 0x2000 for dot commands?


I don't understand too much about RST $18 but that will wait until tomorrow, with the other 1/2 of the z88dk info I printed off.


I am hoping to use z88dk for all of the machine side of the project now.
alvin
Well known member
Posts: 1872
Joined: Mon Jul 16, 2007 7:39 pm

Post by alvin »

castingflame wrote:....I just used ORG 0x2000 (or what ever the dot command location should be).
There's no need for any ORG. "-subtype=dot" sets up a memory map with org already set to 0x2000. Instead place your code in "SECTION code_user". This will place it in that container and the memory map will have that container at 0x2000.
I am building a little hardware device for the Next and the dot command will be used for my testing purposes only. Obviously it will be a very valuable for me to be able to write them.
Very nice. Yes and they're fairly easy to do... you just have to get over that hump.

Perhaps TMI which you can skip...
There are several dot command variations in z88dk (and we have to update them because Garry has been making changes in NextOS). The one you're using with "-subtype=dot" is the original 8k-limited one from esxdos and is the one that everyone else is using. In z88dk there are a couple more "-subtype=dotx" where you have one piece in the 8k area and one piece in main memory; this one loads itself and checks that ramtop is low enough to run or else errors out with a message. This allows dot commands to be fairly large but it uses some of basic's memory to run. Another is "-subtype=dotn" which would be a native zx next dot format. This one requests memory pages from NextOS and loads your program into those. It's the same as dotx except none of basic's memory is used. "dot" and "dotx" are compatible with original esxdos and can run on other machines with esxdos. "dotn" would be NextOS only. You can also launch the zx next in 48k or 128k spectrum mode from the boot menu. These are systems that run esxdos. "dotn" or dot commands written assuming NextOS present cannot run on the zx next in these modes. However the dot commands are stored in the same place on the sd card so you can accidentally run them from 48k/128k and cause crashes. You can end up in a situation where you have NextOS only dot commands that cannot be run from these modes. So we provide a solution for that too... you can assemble/compile the same source code into two different versions of the same dot command, one for esxdos and one for NextOS. Then a bit of code in front can choose the version to run depending on whether NextOS is present. Then you can have the same dot command running in all modes. The NextOS version can usually be made better.

----

Anyway, a little more info on "-subtype=dot" starting with your compile line. I will throw you in the deep end here showing all the machinery behind the curtain but the aim is mainly to let you know how things work. The next part I'll show just "how to do it".

zcc +zxn -vn -startup=31 -clib=sdcc_iy mm.asm -o mm -subtype=dot -create-app

"-startup" chooses the startup code by number. "-clib" chooses library (and c compiler if used). "-subtype" can modify the startup number and chooses output type for "-create-app".

The first thing zcc does is look up the machine configuration "zxn.cfg" in lib/config:
https://github.com/z88dk/z88dk/blob/mas ... ig/zxn.cfg

Here it finds out what clib and subtype do. As you can see it adds more options to the compile line. It's not really too important what's there; it's just the machinery that z88dk uses to target lots of different machines. For "-subtype=dot" you can see "-startupoffset=0x100". This means 0x100 is going to be added to your "-startup" number and this will change which crt startup code your compile uses to one suitable for dot commands (and this offset is only done for dot commands at this time).

The compile line above chooses crt31 but the subtype changes that to 31+256=287.

The crts for the zx next are numbered 0-31 right now with this offset added to choose a crt with the same characterists as these 0-31 but for dot commands. The difference will be in the memory map (org 0x2000) and the start and exit code. The crt has to ensure things are safe to return to basic (save IY, HL', SP) and the exit code has to restore basic state and make sure the return values are what's expected by esxdos. "dotn" type has to allocate nextos pages on start and deallocate on exit, etc.

There are many different crts for convenience. They mainly differ by what text input and text output streams are present.
( the final crt number is "__STARTUP" in https://github.com/z88dk/z88dk/blob/mas ... asm.m4#L41 )

crt0 has a text output stream that is the familiar 32x24 spectrum resolution with fixed with 8x8 fonts.
crt8 uses the standard 256x192 spectrum display but uses proportional font printing using fzx.
crt16 has a text output stream for the timex 512x192 mode using fixed with 8x8 fonts to the text resoluton is 64x24.
.. and so on.

The goal is to support all of the next's screen modes. These canned crts set up one text output stream (on stdout) and are there for convenience but you can set up your own set of text output streams and can have several text output windows set up possibly using different display resolutions. On the next, it's easy to split the display into different modes using the copper or line interrupts.

All z88dk code in the newlib is independent of the rom so it will all work as dot commands or if you decide to page out the rom and put your own code in the bottom 16k. There is one exception:

crt30 which uses rst$10 to print. This is there specifically for dot commands but it can be used generally too. Dot commands normally print using the rom. Since all the code is from the rom, it's also much smaller which makes it attractive for 8k dots. However there is no input stream (stdin) on this crt.

For your asm program there is crt31 which has no text streams at all. The downside is you do your own print / input code (rst$10 can be used in your dot command too) and the upside there is no extra code added to support the printing mentioned above.

All the canned crts are found inside the zx next target:
https://github.com/z88dk/z88dk/tree/mas ... xn/startup

The one you're using for "-startup=31" and "-subtype=dot" is 31+256=287.
https://github.com/z88dk/z88dk/blob/mas ... sm.m4#L116

The first line of code in this crt is at "__Start". The goal of the crt is to do any initialization, call your "_main" and then do any cleanup. What it does is up to you and is controlled by pragmas, with default behaviour determined by z88dk.

The list of default pragmas for dot commands is here:
https://github.com/z88dk/z88dk/blob/mas ... ig.inc#L68
(CRT_ORG_CODE, CRT_ORG_DATA, etc are the pragmas these TAR_* are in a different form because they are defaults)

What these mean is documented here:
https://www.z88dk.org/wiki/doku.php?id= ... figuration

You can change what the crt does by modifying these settings using a list of pragmas in another file.

----

How to do it.

The compile line will be something like:

zcc +zxn -vn -startup=31 -clib=sdcc_iy mm.asm -o mm -pragma-include:zpragma.inc -subtype=dot -create-app

Your asm will be placed in "SECTION code_user" and the entry point will be "_main" made global with "PUBLIC _main".

You tell the crt what you want it to do with pragmas. I'll give you a pragma file you can modify. Call it "zpragma.inc"

zpragma.inc

Code: Select all

#pragma output CRT_ENABLE_COMMANDLINE = 2   // pass esxdos command line without processing
I thought there were going to be more pragmas in the file when I started writing but the defaults are already set to the minimum for an asm program.
If you don't want to use this pragma file you can get rid of "-pragma-include:zpragma.inc" in the compile line.


The crt will:

1. save IY, HL', SP for return to basic
2*. set up the command line
3. call _main
4. restore IY, HL', SP for basic
5*. return status to esxdos (may generate error or OK)


* SET UP THE COMMAND LINE

If "CRT_ENABLE_COMMANDLINE=3" which is the default, the crt will parse the command line into zero-terminated strings and generate argc,argv as expected by c programs. You can also use this in your asm program if you prefer an argc/argv interface to the dot command's argument list.

If "CRT_ENABLE_COMMANDLINE=2", the crt will not do anything about the command line. Your program will be passed the parameter buffer as sent by esxdos.


* RETURN STATUS

Is in HL.

If HL=0 this means OK

If HL > 0 and HL < 256 then you are choosing a canned error message from esxdos:
https://github.com/z88dk/z88dk/blob/mas ... os.m4#L108

If you want to cause a "Nonsense in ESXDOS, 0:1" error on return you would "ld hl,__ESXDOS_ENONSENSE" before returning from _main. All ports, constants, etc are already defined in z88dk so you can get hold of this constant like this:

Code: Select all

EXTERN __ESXDOS_ENONSENSE
...
ld hl,__ESXDOS_ENONSENSE
ret
If HL >= 256, HL is the address of a custom message string that terminates in last_char+0x80. This will be printed as an error at the bottom of the screen on exit.


--

So when _main starts:

1. On entry

* If "CRT_ENABLE_COMMANDLINE=2"

HL = address of command line buffer as passed by esxdos
BC = address of command line buffer with dot name in front as passed by nextos

the stack will also contain copies of these:

HL as passed by esxdos (this is a pointer to the command line buffer)
BC as passed by nextos (this is a pointer to the command line buffer with dot name at front)
return address

* If "CRT_ENABLE_COMMANDLINE=3" (the default)

HL = char **argv as expected by c (array of zero terminated strings for each word on command line)
BC = int argc as expected by c (number of words on command line)

the stack will also contain copies of these:

HL = argv
BC = argc
return address

And when main exits:

HL = return code as described under return status above.

You can exit from any point with:

EXTERN __Exit
jp __Exit

The crt always resets the stack and sets up for return to esxdos.
castingflame
Member
Posts: 15
Joined: Wed Apr 18, 2018 12:54 pm

Post by castingflame »

Never TMI. Detail is good. I'll just keep reading it over.

Containers
There's no need for any ORG. "-subtype=dot" sets up a memory map with org already set to 0x2000. Instead place your code in "SECTION code_user". This will place it in that container and the memory map will have that container at 0x2000
I read about the containers yesterday (in the smallest room of the house) but I guess I didn't join the blanks. I was using SECTION code_user as well as ORG ! I looked in the crt_config.inc as you described and can see the following ..

Code: Select all

 defc TAR__crt_org_code              = 0x2000
:)


Dot command types

After reading about dot, dotx and dotn I did actually try compiling those variants yesterday in a bit of a 'clutching at straws' moment. I saw a RAMTOP error message with dotx so knew it was compiled as a proper dotx variant. I will probably want to use the dotn type so that I am out the way of the normal running system as much as possible and dot, dotx as fallback. I guess I will know more about my own requirements as I write it. As mentioned previously, the dot command is just for my testing purposes as the device will eventually use a device driver but these dot commands are fantastically useful so I want to be able to get used to writing them.
you can assemble/compile the same source code into two different versions of the same dot command, one for esxdos and one for NextOS. Then a bit of code in front can choose the version to run depending on whether NextOS is present.
And there is me thinking I will have to create various duplicates. How very streamlined! Great work there :D


Thanks to your fantastic post, I now have a list of things I am going to try today. Saturday is normally a bit more laid back for me and I generally play with my 3D printer. That will have to wait! :P I'll go through my list and update later.


I hope my journey will help others too and I will happily pass on what I learn in the FB Next group when the masses get their hands on the full machine.

Alvin, many many thanks!
alvin
Well known member
Posts: 1872
Joined: Mon Jul 16, 2007 7:39 pm

Post by alvin »

castingflame wrote:After reflecting a bit more on the dot command notes I removed the 'message to screen' section out of my code and left just the border flashing bit. Guess what, It worked!
Would this happen to be, by any chance, because I am using the PR_STRING routine which is located at 0x203C and my ORG is at 0x2000 for dot commands?
Yes when the dot command runs, ESXDOS (or NextOS's equivalent) is present in 0-8k and your dot command is in 8k-16k so the rom is not visible.

RST$10 is a special case in that ESXDOS places code at $10 to page in the rom and jump to $10 in the basic rom. When this code runs, esxdos and your dot are paged out and the 48k rom is back in 0-16k. On return, the paging is restored and your dot command is returned to.

"RST$18 ; defw addr" allows you to call a basic rom routine at address "addr". While that happens esxdos and your dot command are paged out again. So you can't use any strings or data that are inside the 8k-16k area. For example if you are using basic's PR_STRING routine you can't give it a string that's inside the dot command in 8k-16k because the rom routine won't be able to see it (the 48k rom will be there). Instead you can copy the string to ram or you can make your own PR_STRING.
alvin
Well known member
Posts: 1872
Joined: Mon Jul 16, 2007 7:39 pm

Post by alvin »

That's how I think it works - I haven't actually tried it. You can try it by calling PR_STRING with rst$18 and see if it will work.
alvin
Well known member
Posts: 1872
Joined: Mon Jul 16, 2007 7:39 pm

Post by alvin »

castingflame wrote:After reading about dot, dotx and dotn I did actually try compiling those variants yesterday in a bit of a 'clutching at straws' moment. I saw a RAMTOP error message with dotx so knew it was compiled as a proper dotx variant.
By default dotx splits the program between divmmc in 8k-16k and main ram which has org 32768 by default. So if you "CLEAR 32767" before running the dot command, it will probably work. You should also be able to call PR_STRING from there because section code_user should be in the 32768 part.
I will probably want to use the dotn type so that I am out the way of the normal running system as much as possible and dot, dotx as fallback.
If your program fits in 8k (or ~7k for compatibility with esxdos) the dot should be the main target. dotn and dotx are for > 8k. Using dotn even for small dots is mainly harmless but it will mean that NextOS is asked for ram pages to run it and that's not really necessary.

I hope my journey will help others too and I will happily pass on what I learn in the FB Next group when the masses get their hands on the full machine.
It's a lot of fun too, so happy experimenting and keep us posted!
castingflame
Member
Posts: 15
Joined: Wed Apr 18, 2018 12:54 pm

Post by castingflame »

Thanks once again for you Posts. :cool:


Update Time


I have spent some time going over all of the docs several times and re-reading your posts here. I have also spend a while documenting and understanding how the Next MMU is laid out and works.

The reason I was struggling with dotx and dotn was that I was only copying 1 file to the Next not 2! I didn't manage to find the names in any docs but I worked out that the additional files were .dtx (dotx) and .dtn(dotn)



I have done some tests and have the following results;


1. Removed ORG from all source and I am using just SECTION code_user (I understand about the SECTIONS a little more now)

2. Using RST16 to print from my asm test program dot, dotx (with a CLEAR statement) and dotn all work correctly.

3. Using PR_STRING ($203C) crashes under all dot variants.

4. After looking at the PR_STRING ROM routine I saw it uses RST16. Out of interest I tried copying the ROMS
PR_STRING code into my asm program. I created dot, dotx and dotn variants and they all worked like a charm! I have to say I surprised myself there :)



TODO: tomorrow

1. Test using RST$18 also with with dot, dotx and dotn

2. Investigate why a 3rd party dot command works using ASM to compile and not zcc. (Please no clues just yet please ;) )

3. Keep reading the docs and posts over!




P.S. I cant seem to find what the acronyms CRT & BBS mean. I'm guessing not Cathode Ray Tube and Bulletin Board System!





Cheers, Paul
User avatar
dom
Well known member
Posts: 1302
Joined: Sun Jul 15, 2007 10:01 pm

Post by dom »

crt basically the startup code for a platform - see https://en.wikipedia.org/wiki/Crt0

bss where 0 initialised variables live - see https://en.wikipedia.org/wiki/.bss
castingflame
Member
Posts: 15
Joined: Wed Apr 18, 2018 12:54 pm

Post by castingflame »

Thanks for the replay Dom.

So BSS = Block Started by Symbol and CRT = C Runtime :)

I didn't know they were C terms but then again don't know a lot. They make much more sense now within what I have read about CRT & BSS in the z88dk docs.

Cheers
castingflame
Member
Posts: 15
Joined: Wed Apr 18, 2018 12:54 pm

Post by castingflame »

I have really been struggling with RST $18.

This is a simplified version of how I normally use PR_STRING...

Code: Select all

ld de, msg        ;address of the string into de
ld bc, 10                   ;decimal length of the string into bc
call 203Ch                 ;Call the ROM routine PR_STRING
jp quit

msg: defw "Hello World!"

quit:
The above works fine. So I wish to do the same but using RST $18 so ...

Code: Select all

ld de, msg              ;address of the string into de
ld bc, 10                        ;decimal length of the string
RST 0x18
defw 0x203C                   ;Pass address of PR_STRING to RST$18
jp quit

msg: defm "Hello World!"
quit:
(sorry about wonky comments, not sure how t get those lined up (here))


I can find nearly zero practical examples how to do this. It is the last thing on my list for dot commands and it would be useful to be able to call any ROM routine via RST$18. In the example above I have not included the relocation of what would be the equivalent to 'msg' being out of the way at a high memory location but I have learned how to do that bit (yes, easy when you know how).

I wonder if registers are being overwritten along the way ... (Guess)


I asked this same question on the assembly FB group an hour ago but as yet, no cheddar. I realise that this is fundamentally a programming question before it is a dot command question so I understand if it is out of scope or 'off-piste' of the direct dot command questions and not really your 'job' to answer! :cool:

Cheers.
castingflame
Member
Posts: 15
Joined: Wed Apr 18, 2018 12:54 pm

Post by castingflame »

Hold that thought a moment, I have done something stupid ...
alvin
Well known member
Posts: 1872
Joined: Mon Jul 16, 2007 7:39 pm

Post by alvin »

In the example above I have not included the relocation of what would be the equivalent to 'msg' being out of the way at a high memory location but I have learned how to do that bit (yes, easy when you know how).
That's the only thing I can see that is potentially wrong with what you've done:

Code: Select all

ld de, msg        ;address of the string into de
ld bc, 10               ;decimal length of the string into bc
call 203Ch                 ;Call the ROM routine PR_STRING
jp quit

msg: defw "Hello World!"

quit:
It *looks* like the labels "msg" and "quit" are inside the dot command, in which case the rom will not be able to see the text at "msg" when it is called. If you're copying the text string into main ram, make sure you are loading "de" with the address in ram, not the location of the string stored in the dot command.

You can get z88dk to do most of the work of moving stuff into main ram btw, and if you use the dotx form (or dotn but the nextos api has changed and dotn must be updated before it's used) then you can place your string in the main ram portion and z88dk will automatically load it there at startup.

But generally if your command is small enough to fit in 8k then you'd prefer not to use any extra ram and I'd just replace the PR_STRING rom call with your own loop that calls RST$10.

Usually you want dot commands to use RST$10 for printing as that's "expected" but it is also possible to use z88dk's stdio/stdin drivers to print without using the rom. These can do 32 columns, 64 columns, 128 columns, proportional text. The asm function "asm_write" works a lot like PR_STRING , "asm_puts" will print a 0-terminated string and you can also use "asm_printf" to print c-style strings with integers, floats, etc embedded in them. But as no rom code is used, the print code will make the dot command bigger which may or may not be a problem.
alvin
Well known member
Posts: 1872
Joined: Mon Jul 16, 2007 7:39 pm

Post by alvin »

I should add: using startup=30 will mean stdio uses RST$10 to print as expected by dot commands and you still have access to asm_write ( https://github.com/z88dk/z88dk/blob/mas ... te.asm#L24 ), asm_puts ( https://github.com/z88dk/z88dk/blob/mas ... ts.asm#L29 ), asm_printf ( https://github.com/z88dk/z88dk/blob/mas ... tf.asm#L29 ), etc.

Although asm_printf says there are no input parameters in registers, they are on the stack instead because the number of parameters has no upper bound.
castingflame
Member
Posts: 15
Joined: Wed Apr 18, 2018 12:54 pm

Post by castingflame »

I was not using CSpect or ZXsarUX. I tried both emulators but neither those nor the Real TBBlue/Next board work with the above code.
castingflame
Member
Posts: 15
Joined: Wed Apr 18, 2018 12:54 pm

Post by castingflame »

Sorry, you had posted since last I looked a few moments ago. Yes I smlified my code for someone else to read and forgot to move my data to higher RAM to test again. I'll try that & the rest of your comments ...
alvin
Well known member
Posts: 1872
Joined: Mon Jul 16, 2007 7:39 pm

Post by alvin »

I'll have some time in a bit and will give it a go.
alvin
Well known member
Posts: 1872
Joined: Mon Jul 16, 2007 7:39 pm

Post by alvin »

This works:

zzz.asm

Code: Select all

; zcc +zxn -vn -startup=31 -clib=sdcc_iy zzz.asm -o zzz -subtype=dot -create-app
; copy "ZZZ" to /bin on the sd card and run with ".ZZZ"

SECTION code_user

PUBLIC _main

_main:

   ; copy message to ram
        
   ld hl,msg_src
        ld de,msg_ram
        ld bc,msg_src_end - msg_src
        ldir
        
        ; use rom to print message
        
        ld de,msg_ram
        ld bc,msg_src_end - msg_src
        
        rst 0x18
        defw 0x203c

   ld hl,0   ; report success to esxdos
        ret

msg_src:

   defm "Hello World!"

msg_src_end:

defc msg_ram = 32768
I am just taking memory at 32768 so really a "CLEAR 32767" should be done in basic before running.

I will show some other methods I was taking about next.
castingflame
Member
Posts: 15
Joined: Wed Apr 18, 2018 12:54 pm

Post by castingflame »

Code: Select all

SECTION code_user                ;z88dk


;EQUATES
;*******
store equ 0x9000 

PUBLIC _main                        ;z88dk 

_main:


            ;load data into my memory location
            ld a, 0x7A        ; load ASCII 'x' into A
            ld (store),a           ; load A register value into the address of store
        
        ld de, store        ;address of the strings location into de
        ld bc, 1        ;decimal length of the string
        RST 0x18
        defw 0x203C        ;Pass address of PR_STRING to RST$18
        jp quit

; stuff

quit:
So I have saved ASCII 'x' into memory address 9000. as in above code. I created a dotx (used clear statement) but it just failed.


The whole point of me using RST$18 was really because it was an option to access other ROM functions, not just PR_STRING as I have multiple ways of printing now, from within my dot commands.

I just read you will have a go A.A. :)
castingflame
Member
Posts: 15
Joined: Wed Apr 18, 2018 12:54 pm

Post by castingflame »

You gave it a go!! Cool :)

Downside, my day is done :( A BIG thanks and i'll drawl over it tomorrow. I have tried to do this for 3 days! Mind you, a lot learned along the way.

I'll go to bed a happy boy knowing I will also have that in my Arsenal. Good Man :D
alvin
Well known member
Posts: 1872
Joined: Mon Jul 16, 2007 7:39 pm

Post by alvin »

Yes you can go to bed happy :)

I'll put a couple more examples here of doing things a little differently so you're aware of them.

The last one I showed up there was a plain dot command. It copied the string into ram before calling the PR_STRING rom routine to print it. This is not good because the ram is being used by basic so there's potential there that important things are being overwritten.

This next one is a dotx command that splits a dot command between the dot area in 8k-16k and ram. The start address of ram is 32768 by default but you can change this with a pragma which I will not show here. In a real dotx command you would like to set the ram address as high as possible to give basic as much room as possible. The crt for dotx commands (crt = the initialization code remember) will check that ramtop is low enough to load the dot command. Otherwise it will error out with RAMTOP no good, along with the necessary clear address printed. So dotx commands make sure basic is out of the way so that it safe to run them.

zzz2.asm

Code: Select all

; zcc +zxn -vn -startup=31 -clib=sdcc_iy zzz2.asm -o zzz2 -subtype=dotx -create-app
; copy "ZZZ2" and "ZZZ2.DTX" to /bin on the sd card and run with ".ZZZ2"
; you will have to "CLEAR 32767" to run but you can see what happens without

SECTION code_dot   ;; place in dot portion 8k-16k

PUBLIC _main

_main:
   
   ; use rom to print message
   
   ld de,msg
   ld bc,msg_end - msg
   
   rst 0x18
   defw 0x203c

   ld hl,0   ; report success to esxdos
   ret

SECTION data_dtx  ;; place in ram portion
   
msg:

   defm "Hello World!"

msg_end:
I'm going to draw your attention to something more technical. z88dk uses memory maps to control where things are placed in memory and for dotx there is a different memory map. The memory map is split into two halves, the "CODE" portion ( https://github.com/z88dk/z88dk/blob/mas ... otx.inc#L7 ) that is in the dot command area 8k-16k and the "DTX" portion ( https://github.com/z88dk/z88dk/blob/mas ... tx.inc#L70 ) that is in main ram. You can control where your asm and data is placed by assigning to the right sections.

In the above I put "_main" into section "code_dot" which is in the 8k-16k area (part of CODE: https://github.com/z88dk/z88dk/blob/mas ... tx.inc#L59 ) and I put "msg" into section "data_dtx" which is part of the main ram area (part of DTX: https://github.com/z88dk/z88dk/blob/mas ... x.inc#L141 ). So when the dot command is loaded "msg" is already in ram and you can just print it with the rom routine.

The memory map has a lot of sections in it. Don't be too bedazzled with it - there are a lot because the c library is defining a lot to allow users fine control over library code placement if they decide to make a custom memory map. There are just two main parts "CODE" and "DTX" so to place your stuff in either part you can just choose a suitably named section in each half.

The output of the dotx is two binaries "ZZZ2" and "ZZZ2.DTX", both of which must be copied to /bin. This type is meant to be compatible with esxdos so it will remain as two parts even though nextos allows the two parts to be merged into one. That's obviously cleaner and will be done for the nextos-only dotn command.
Post Reply