Specasm: A Z80 assembler designed to run on the spectrum itself

Show us what you're working on, (preferably with screenshots).
markusr
Drutt
Posts: 28
Joined: Sun Feb 20, 2022 3:14 pm

Specasm: A Z80 assembler designed to run on the spectrum itself

Post by markusr »

On the face of it, developing directly on the spectrum doesn’t make a whole lot of sense. There’s the slow builds, the 32 column text mode, the little rubbery keyboard, and the fundamental problem that there just isn’t enough room to host the dev tools, the source code, the object code and the final executable in memory, all at the same time.

And yet; the keyboard’s not so bad once you get used to it, the 32 columns are just about enough for assembler, the attribute based graphics give us lots of colours for syntax highlighting and, most importantly, modern storage solutions for the spectrum (ESXDOS) are so fast there’s no need to keep everything in memory. So while developing directly on the spectrum may not make a whole lot of sense, it is sort of doable.

I wrote Specasm to discover just how doable. Specasm is an assembler for the ZX Spectrum that runs directly on the spectrum itself. It requires at least 48Kb and an ESXDOS SD card solution. It includes an editor with an integrated assembler, and a linker. The editor edits annotated object files directly, rather than text files, and supports incremental assemblation, i.e., it assembles the code as you type it. This makes the build times quite reasonable as all that needs to be done when building is to link.

I’ve spent some time over the last few weeks using it to code directly on the Spectrum and I think the best thing that can be said about the experience is that, it’s not awful. You’re unlikely to develop the next R-Type using Specasm but it should suffice for learning about the z80 (the incremental assemblation really helps here) and the Spectrum itself. This is how I intend to use it.

So far the largest program I’ve tried is the centipede game from “How to Write ZX Spectrum Games”, which is less than 1K compiled. I’ve tested Specasm on a 48kb Spectrum and a Harlequin 128 clone with a DivMMCEnjoy running ESXDOS 0.87, a 128kb Toastrack with a DivMMC Future running ESXDOS 0.89, and an OMNI HQ running ESXDOS 0.87.

Specasm can be downloaded from here. There’s a getting started guide in the README.
User avatar
PeterJ
Site Admin
Posts: 6879
Joined: Thu Nov 09, 2017 7:19 pm
Location: Surrey, UK

Re: Specasm: A Z80 assembler designed to run on the spectrum itself

Post by PeterJ »

Thanks for posting [mention]markusr[/mention],

It looks a great project. Also nice to see such great documentation!
markusr
Drutt
Posts: 28
Joined: Sun Feb 20, 2022 3:14 pm

Re: Specasm: A Z80 assembler designed to run on the spectrum itself

Post by markusr »

There's one area I could do with a bit of help with. I've really struggled to get the keyboard input routine working properly, despite trying lots of different variations. The main problem with the current version is that sometimes, an additional character appears when two keys are pressed at once. This is most commonly seen when pressing SYMBOL SHIFT and 'w' to get to the command mode. Often the 'w' is repeated and appears after the command prompt. It needs to be manually deleted before entering a command which is irritating.

Specasm is written in C and built with z88dk. The keyboard entry routine is here. I'm using z88dk's in_inkey() function to read the key. A loop is started after the key is read, in which we re-scan the keyboard a fixed number of times, sleeping for 20ms in between each scan. We exit the loop if a different key is read or the fixed number of loop iterations elapse. The loop is there to handle repeated key presses. The function specasm_sleep_ms(20) basically does a halt. Interrupts are enabled to faciliate the halt. I believe this is safe, from an IY point of view, as I'm not using any of the standard libraries IO functions, i.e., I'm using -startup=31. Does anyone have any suggestions to improve this routine? Ideally, the solution should be small, as I don't have a lot of bytes left.
dfzx
Manic Miner
Posts: 683
Joined: Mon Nov 13, 2017 6:55 pm
Location: New Forest, UK
Contact:

Re: Specasm: A Z80 assembler designed to run on the spectrum itself

Post by dfzx »

You might want to ask about your keypress routine at the z88dk ZX forum. The code's a bit involved so they might see the problem quicker than folks here.

However, I think there's a problem in there with, using your example, SYM-w. You press SYM and the wait at L47 escapes. With no other key down, k goes to 0 at L48 and the continue at L50 is hit. This goes round a few times until your other finger comes down on the 'w' key. Now k is assigned 'w'+SYM - if I'm reading the in_key() library code correctly a modifier value is added to the ASCII to show the SYM is also down. You enter the loop at L52 and go round while the 2 keys are pressed, new_key being assigned 'w'+SYM each interation.

Now then, which key gets released first? If it's the 'w', new_key goes 0 at L54, the SYM key being ignored by in_key(). In this case the code appears to work as you want.

But if the user releases the SYM key first, keeping the 'w' down momentarily longer, new_key will go to 'w' at L54. 'w' is not the same as 'w'+SYM so you break at L56 and handle the original 'w'+SYM at L58 (your key handling code understands the SYM modifier). k now goes to 'w' at L59. You go round the loop at L60, back into the calibration loop, read new_key again at L54, getting 'w'. That's the same as k so you loop until the key is released, handling the 'w' a second time at L58. With all keys released new_key is now 0 so you break out having handled the 'w' twice.

Maybe, anyway. :)
Derek Fountain, author of the ZX Spectrum C Programmer's Getting Started Guide and various open source games, hardware and other projects, including an IF1 and ZX Microdrive emulator.
markusr
Drutt
Posts: 28
Joined: Sun Feb 20, 2022 3:14 pm

Re: Specasm: A Z80 assembler designed to run on the spectrum itself

Post by markusr »

This does indeed seem to be what's happening. I'll need to have a think about how best to fix this, but knowing what the actual problem is is going to help a lot. Thanks!
User avatar
utz
Microbot
Posts: 116
Joined: Wed Nov 15, 2017 9:04 am
Contact:

Re: Specasm: A Z80 assembler designed to run on the spectrum itself

Post by utz »

Awesome project, looks like this could be a worthy successor to MRS. I've been hoping for something like this that makes use of ESXDOS for a long time.

Skimming through the documentation, I had a couple of thoughts.

Expressions

It would be great if this gets extended a bit in the long run. If I understood correctly, right now there is no way to use symbols as constants, eg.

Code: Select all

.Foo equb some_value
...
  ld a,.Foo
Would be great to have that feature. Also, for writing self-modifying code, expressions in the form of label + numeric_offset would be very helpful, eg.

Code: Select all

.Foo
  ld a,some_value
  xor a
  ld (.Foo + 1),a
Perhaps this could be done using postfix or prefix notation. Should simplify parsing, at least.


Data Directives

Curious what's the reason for using equb, equw, repb, instead of the more common db, dw, and ds? Also, a directive for alignment would be much appreciated.


Number and String Formatting

This is hardly important, but consider swapping & for hex and # for string length encoding, as # as a hex prefix is more common in the Z80 world and swapping these would make Specasm code more portable.


Anyway, I'm just complaining on a high level here. Again, this is an excellent project, thanks a lot for putting this together.
SamC
Microbot
Posts: 168
Joined: Sun Sep 29, 2019 9:07 pm

Re: Specasm: A Z80 assembler designed to run on the spectrum itself

Post by SamC »

Thank you for the assembler!

Please follow the traditional, very basic syntax of directives:
https://www.nongnu.org/z80asm/directives.html
Please check e.g. Nagydani´s sources, these can be compiled with the z80asm.
A similar syntax has a native assembler for the V6Z80P´s FLOS (source of this asm is in the project archive).
markusr
Drutt
Posts: 28
Joined: Sun Feb 20, 2022 3:14 pm

Re: Specasm: A Z80 assembler designed to run on the spectrum itself

Post by markusr »

Thanks for the feedback.
utz wrote: Mon Feb 21, 2022 10:52 am Expressions

It would be great if this gets extended a bit in the long run. If I understood correctly, right now there is no way to use symbols as constants, eg.

Code: Select all

.Foo equb some_value
...
  ld a,.Foo
That's correct. All that can be done at the moment is to use labels where absolute addresses are expected and, in some limited places, you can subtract one label from another. It would be nice to have some sort of expression syntax though, particularly in light of the SMC example you presented. I think I can see how to do it but I'll need to free up some space in the specasm binary first. This will take some time and will almost certainly be a BC breaking change.
utz wrote: Mon Feb 21, 2022 10:52 am Data Directives

Curious what's the reason for using equb, equw, repb, instead of the more common db, dw, and ds? Also, a directive for alignment would be much appreciated.
These directives and the usage of '&' to denote hexadecimals come from the assembler in BBC BASIC. I'll change them to something more z80 friendly. It might even save me a few bytes. Note repb in Specasm expects its arguments in the reverse order to ds, so I'll need to swap them, although I think I can do this without breaking BC with existing .x files. I'll also change the character used to denote hexadecimal. Looking at some of the other z80 assemblers they seem to allow the programmer to use either '#' or '$' to signify hex. I think I'd find '$' less confusing, so I'll switch the '&' for a '$'.

I'll also add align. One problem with align is that it will break the byte count feature of the editor as the amount of bytes consumed by the align directive won't be known until link time. I'll just have to return 0 for its byte count in the editor and document this.
markusr
Drutt
Posts: 28
Joined: Sun Feb 20, 2022 3:14 pm

Re: Specasm: A Z80 assembler designed to run on the spectrum itself

Post by markusr »

SamC wrote: Mon Feb 21, 2022 6:14 pm Thank you for the assembler!

Please follow the traditional, very basic syntax of directives:
https://www.nongnu.org/z80asm/directives.html
Please check e.g. Nagydani´s sources, these can be compiled with the z80asm.
A similar syntax has a native assembler for the V6Z80P´s FLOS (source of this asm is in the project archive).
Thanks for the links. They're very useful. I'll update Specasm's directives to match the ones in the link you provided.
Thompson
Drutt
Posts: 13
Joined: Thu Dec 17, 2020 11:16 pm

Re: Specasm: A Z80 assembler designed to run on the spectrum itself

Post by Thompson »

Every good development tool is priceless.
Do you plan to extend its compatibility to ZX Spectrum Next (which also uses esxDOS, but provide much more memory, in both old ZX 128 16k banks, and new and more flexible 8k slots from at least 1GB memory)

If it's interesting for you there is a Z80 assembler in ongoing development, running directly on the machine: Odin. It has also integrated editor, linker and works as a .dot command - but only on the Next.
https://specnext.dev/blog/2020/07/15/o ... assembler/
https://gitlab.com/next-tools/odin

On Youtube one can find Z80 programming tutorials with Odin by Jim Bagley - it starts with BASIC, but quickly go to Z80 assembly tutorial
https://www.youtube.com/channel/UCEhXaS ... Hnmgex3FvQ
User avatar
ZjoyKiLer
Dizzy
Posts: 67
Joined: Thu Sep 09, 2021 3:20 pm

Re: Specasm: A Z80 assembler designed to run on the spectrum itself

Post by ZjoyKiLer »

Hello folks.

The main contributor of Odin and sjasmplus, Peter Helcmanovsky (aka Ped7g), told me that he could explain a couple of things to you. If you would be curious about those projects, he will be glad to answer questions or showcase Odin on stream.

Please, join our ZX Spectrum Discord server if you are interested: https://discord.gg/scZWGduuKw
markusr
Drutt
Posts: 28
Joined: Sun Feb 20, 2022 3:14 pm

Re: Specasm: A Z80 assembler designed to run on the spectrum itself

Post by markusr »

Thompson wrote: Mon Feb 21, 2022 9:35 pm Do you plan to extend its compatibility to ZX Spectrum Next (which also uses esxDOS, but provide much more memory, in both old ZX 128 16k banks, and new and more flexible 8k slots from at least 1GB memory)
I would like to do a 128k version sometime later in the year to include some of the functionality that didn't make it into the 48k version; help, cut and paste (currently we only have copy and move) and import and export (which are currently separate tools). I'd also like to squeeze the next opcodes into the 48k version, if I can find the space. I have some of them added locally on a branch but I postponed this work as I ran out of bytes. I do however, like the idea of cross compiling for the Next on a 48k spectrum.
Alcoholics Anonymous
Microbot
Posts: 194
Joined: Mon Oct 08, 2018 3:36 am

Re: Specasm: A Z80 assembler designed to run on the spectrum itself

Post by Alcoholics Anonymous »

markusr wrote: Sun Feb 20, 2022 4:58 pm There's one area I could do with a bit of help with. I've really struggled to get the keyboard input routine working properly, despite trying lots of different variations. The main problem with the current version is that sometimes, an additional character appears when two keys are pressed at once. This is most commonly seen when pressing SYMBOL SHIFT and 'w' to get to the command mode. Often the 'w' is repeated and appears after the command prompt. It needs to be manually deleted before entering a command which is irritating.
I might try something like this although since I haven't tested it, it may be a big fail :)

Code: Select all

do
{
   in_wait_nokey();
   while ((k = in_inkey()) == 0) ;

   specasm_handle_key_press(k);

   // auto-repeat delay
   
   for (unsigned char i = 0; i != 250; ++i)
   {
      z80_delay_ms(3);  // 750ms until repeat, poll keyboard every 3ms
      if ((new_key = in_inkey()) != k) break;
   }
   
   // auto-repeat
   
   while (new_key == k)
   {
      specasm_handle_key_press(k);
      
      for (unsigned char i = 0; i != 250; ++i)
      {
         z80_delay_ms(2);  // 500ms between repeats, poll keyboard every 2ms
         if ((new_key = in_inkey()) != k) break;
      }
   }
}
while (!quitting);

in_wait_nokey();
This will also get you auto-repeat and you can adjust the loop lengths and delays to get the best feeling repeat rate. The loop variables are unsigned char so I'm avoiding > 255 there and the millisecond delay should be kept very short to remain responsive. I've used the library z80_delay_ms() function which is a precise delay loop independent of the interrupt (#include <z80.h>). I think yours was based on halt so 20ms for each count.

Sometimes auto-repeat may not be desired if, say, specasm_handle_key_press() generates an error. In that case you may want to repeat the outermost do-loop so that the keypress is cleared by the in_wait_nokey().

That's the other difference -- the in_wait_nokey() is making sure the keyboard has to have all keys released between keypresses. This might be annoying, I'm not sure. If it is, then you can move that out of the loop to before the main do-loop just to make sure keys pressed when the dot command starts don't register. You will still be left with the case that someone presses SYM+W, then releases SYM before W so that you will end up with W treated as a new keypress. That may still work in practice as it's something hard to know without trying.

Note that this code does not need the interrupt running and you may want to leave interrupts disabled. If you're going to have interrupts running, another approach is to use the LASTK system variable to do key input and take care of auto-repeat:

Code: Select all

extern unsigned char _SYSVAR_LASTK;

in_wait_nokey();

do
{
   _SYSVAR_LASTK = 0;
   while ((k = _SYSVAR_LASTK) == 0);
   specasm_handle_key_press(k);
}
while (!quitting);

in_wait_nokey();
Some system variable locations are defined by z88dk. What I've done here may or may not work depending on how good my memory is but it will be close to correct.
Alcoholics Anonymous
Microbot
Posts: 194
Joined: Mon Oct 08, 2018 3:36 am

Re: Specasm: A Z80 assembler designed to run on the spectrum itself

Post by Alcoholics Anonymous »

Originally I was pointed here because someone mentioned that specasm wasn't running on the Spectrum Next so I'll spend a few lines talking about that.

The dot command is built with +zx as target and dotx as the subtype. dotx allows dot commands to be bigger than 8K by using some of the main ram. These commands are split into two parts: the normal 8K part and the extra part. These are stored in /bin and /bin/extra directories. The /bin/extra directory is hardcoded into the main 8K binary so that it can load the second part when it is started.

Under NextZXOS, dot commands are stored in /dot and /dot/extra. This is done to separate dot commands that only work under NextZXOS from standard ESXDOS ones. Normally you can just copy an esxdos dot command into /dot and it will work on the Next. But because these dotx have the /bin/extra path hardcoded in them, the second part would have to be located in /bin/extra rather than dot/extra. You could do a Frankenstein and put specasm in both places to make it work but a version compiled for the Next specifically would be much better. This can be done by changing the target to +zxn, subtype to dotn and the includes mentioning "zx" may have to be changed to "zxn" (it may still work without doing that because the set of functions for the zx is a proper subset of the zxn). I believe the dotx subtype is still supported for the zxn target but that will be removed from z88dk at some point. The dotn is much better as it takes advantage of the Next's extra ram and allows dot commands to use the full memory space (up to 2MB) without interfering with basic. The same code for dotx will compile for dotn just by changing the subtype.


===


makusr, I like the project (have you implemented a real linker as in you can produce object files and put them together? -- that's very rare for the spectrum and even for common spectrum assemblers on PCs) but I think you may have been caught in a precarious situation by the vagaries of the spectrum and its rom interrupt. I was tipped at that when I found this in your makefile "CFLAGS=+zx -SO2 -clib=sdcc_ix --reserve-regs-iy" and I was wondering why were you doing that? :D Then I figured it out. (SO3 btw will get a much better result -- was there bad experience with that?)

It's a bit clever. "clib=sdcc_ix" means sdcc is going to be given the ix register for its stack frames and the library will use iy if an index register is needed. This keeps the library code and compiler out of each other's way. The preferred "clib=sdcc_iy" does the opposite: the compiler gets iy for its stack frame (via assembler trick that swaps ix and iy in assembled instructions) and the library code uses ix. But you have the rom interrupt running so you can't have the code changing iy at all. So "sdcc_ix", along with "reserve-regs-iy", will keep the compiler away from iy and since the library rarely uses iy this is almost safe.

The problem is it's not completely safe. Although most library code does not use IY, a small number of functions do. This includes a lot of code that touches 32-bit or above integers (see strtrol() which I think you're using), some isolated functions where it is useful to have an extra 8-bit register borrowed from the index register (see itoa / utoa) and all of stdio (sprintf, printf families included). stdio uses an index register to point at a device description block. I believe the z80 targetted code is using these things?

On the spectrum next this is not a concern as basic's interrupt will fix the IY value for the duration of the interrupt but that is not the case under normal spectrum basic. On a normal spectrum I think you have to do one of: run with interrupts disabled, be very careful about choosing to run library functions that do not touch iy, or replace the basic isr with your own im2 isr that fixes the iy register before jumping into the basic isr. This applies to both your tap and dot versions of the programs. The issue with the spectrum rom is that its keyboard reading uses IY to index into the system variables area. If you change IY this results in random writes in memory that will eventually hit something important and cause a weird crash.

Some other ideas:

- You may not be at the code optimization level yet but one thing the compilers don't deal well with are a lot of local variables, a lot of parameters to functions and long functions. Sometimes this can be alleviated by changing locals to static locals and/or considering carrying around global state where it makes sense.

- Make use of library functions where you can as they're written in assembler and will be both smaller and faster than any c equivalent. I spotted a c implementation of itoa(), eg, when that is implemented in the z80 library including radix selection but I am not sure anymore if that was in the pc set of files or not. When using printf or sprintf make sure you are limiting the set of % converters to what you are using via pragma, eg '#pragma printf = "%s %c"`. The compiler will prune out code for other converters you are not using.

- If you're paying the expense of printf, you could consider using the spectrum rom control codes to run the editor in order to save a lot of space (subtype=30 for dots). But if you've got your own fancy editor you may still prefer that. I always like the unix way of separating programs into individual function: editor, assembler, linker which you're pretty much doing here. Separating a little further the editor completely might free up more space for the assembler and editor individually. An idea further down the line might be to have a single dot act as your command interface that can load up these other things in the 0x8000+ area. This code would lie in the divmmc area and load up the .X part of these other dot commands to execute them as needed.

- If you have a little more space available, the z88dk drivers can do multiple windows with text scrolling and input at 32x24, 64x24 (4x8 font) or proportional fonts (fzx). Just some thoughts.

Anyway I look forward to seeing where you go with it.
markusr
Drutt
Posts: 28
Joined: Sun Feb 20, 2022 3:14 pm

Re: Specasm: A Z80 assembler designed to run on the spectrum itself

Post by markusr »

Alcoholics Anonymous wrote: Thu Feb 24, 2022 4:40 am I might try something like this although since I haven't tested it, it may be a big fail :)

Code: Select all

do
{
   in_wait_nokey();
   while ((k = in_inkey()) == 0) ;

   specasm_handle_key_press(k);

   // auto-repeat delay
   
   for (unsigned char i = 0; i != 250; ++i)
   {
      z80_delay_ms(3);  // 750ms until repeat, poll keyboard every 3ms
      if ((new_key = in_inkey()) != k) break;
   }
   
   // auto-repeat
   
   while (new_key == k)
   {
      specasm_handle_key_press(k);
      
      for (unsigned char i = 0; i != 250; ++i)
      {
         z80_delay_ms(2);  // 500ms between repeats, poll keyboard every 2ms
         if ((new_key = in_inkey()) != k) break;
      }
   }
}
while (!quitting);

in_wait_nokey();
Thanks, this is great. I'll give it a try at the weekend. I guess the key here, and the piece I missed, is the use of in_wait_nokey() to make sure partial keypresses don't get registered when multiple keys are released. The use of different timings for the initial loop and the auto-repeat loop is a nice touch. One thing I'll need to check is how the use of in_wait_nokey() affects the navigation with the cursor keys in the editor. I often tend to keep the CAPS key pressed down on the 48k when navigating around the editor using different cursor keys. Perhaps these keys can have their own special treatment.

My initial version of the keyboard entry routine did use z80_delay_ms. I switched to using halt as my z80_delay_ms loop didn't seem to work on the OMNI when turbo mode is enabled and the CPU runs at a higher speed (it ran too fast). I'm guessing z80_delay_ms must be tuned to the speed of the 48k spectrum. Switching to halt fixed the issue on the OMNI, but required me to enable interrupts, which leads to other problems as you mention in your second post. So I'm going to disable interrupts, switch back to z80_delay_ms and try to do some auto calibration at the start of the main function to select the appropriate delay.
markusr
Drutt
Posts: 28
Joined: Sun Feb 20, 2022 3:14 pm

Re: Specasm: A Z80 assembler designed to run on the spectrum itself

Post by markusr »

Alcoholics Anonymous wrote: Thu Feb 24, 2022 6:19 am Originally I was pointed here because someone mentioned that specasm wasn't running on the Spectrum Next so I'll spend a few lines talking about that.
There are four binaries in the Specasm v1 release.
  • specasm - The integrated editor/assembler
  • salink - The linker
  • saexport - Converts annotated object files (.x) created by specasm to text files
  • saimport - Converts text files to .x files. Essentially, this is the assembler without the editor
The first two binaries, specasm and salink are actually tap files and not dotx files. The launcher tool is used to create dot aliases for them. Saexport and saimport are both dotx files.

What was reported as not working on the Next? Was it just the dotx files or were the editor and linker (the tap files) not working either?

I could include dotn versions of saimport and saexport in the release, although I have no way of testing these (not until the 2nd Kickstarter delivers that is). A question though. Would this be a simple matter of rebuilding them for the Next? Would the dotn files work with the esxdos APIs or would I need to provide a Next porting layer that used the Nextos APIs?
makusr, I like the project (have you implemented a real linker as in you can produce object files and put them together? -- that's very rare for the spectrum and even for common spectrum assemblers on PCs)
Thanks. Yes, it's very rudimentary at the moment but there is a separate linker. Basically, it just links all the .x files (the annotated object files produced by specasm and saimport) in the same directory into a binary. Currently, the programmer has very little control over how this happens. He can specify which file comes first in the binary and the start address, and that's about it. It will generate a map file though, on request. The good thing is that there's loads of room left in the linker, so much more functionality can be accomodated.
but I think you may have been caught in a precarious situation by the vagaries of the spectrum and its rom interrupt. I was tipped at that when I found this in your makefile "CFLAGS=+zx -SO2 -clib=sdcc_ix --reserve-regs-iy" and I was wondering why were you doing that? :D Then I figured it out. (SO3 btw will get a much better result -- was there bad experience with that?)
So I had a bad experience with -SO3. It seemed to be generating incorrect code. Note I'm using a version of z88dk built from main, rather than an official release, so perhaps this was the reason. The choice of compiler options came from other z88dk examples I found on the Internet. The examples I found for building dotx files used sdcc_ix, and so I did that too. Note that neither of the dotx files enable interrupts explicitly. The only binary that does is specasm. This uses sdcc_iy with crt 31. I thought this combination would be safe, even with interrupts enabled, but I guess it's not. I have seen some weird crashes that happen on certain builds and not others, which cannot be explained by the differences between the builds (so save your .x files regularly until I get this sorted out).

In summary though, it sounds like I should be able to build the tap files and the dotx files with the same compiler options, i.e., with sdcc_iy, and if I disable interrupts in specasm.tap and redo the key handling, all will hopefully be well. Does this sound like a reasonable conclusion?

Thanks for the optimization tips. I'll work my way through these. I've already been through a few rounds of optimisation. Orignally, all functions had an error argument. I think I saved 1.5kb by removing this and making it global. The use of pragma printf = "%s %c"` looks like a quick win for salink, saimport and export. The spectrum build uses z88dk's version of itoa. The provided itoa implementation is only used when building for modern machines, basically for the CI.
Alcoholics Anonymous
Microbot
Posts: 194
Joined: Mon Oct 08, 2018 3:36 am

Re: Specasm: A Z80 assembler designed to run on the spectrum itself

Post by Alcoholics Anonymous »

markusr wrote: Thu Feb 24, 2022 6:10 pm What was reported as not working on the Next? Was it just the dotx files or were the editor and linker (the tap files) not working either?
I'm afraid I don't know - it's a report of "not working" without further details. So I just had a quick look to try to figure out every possible thing that might go wrong. However, I think it may have to do with your use of .launcher which is not part of the NextZXOS distribution by default. Some esxdos api functions remain unimplemented in NextZXOS because they are undocumented and have not been used by existing software to know how they are supposed to work. One such function is in use in launcher and that has now been implemented. I'll try things myself after other things keeping me busy are tied up. The esxdos api has never been formally documented because Miguel wanted to be able to change things for a rewrite but I think that opportunity is fast disappearing.
I could include dotn versions of saimport and saexport in the release, although I have no way of testing these (not until the 2nd Kickstarter delivers that is). A question though. Would this be a simple matter of rebuilding them for the Next? Would the dotn files work with the esxdos APIs or would I need to provide a Next porting layer that used the Nextos APIs?
They should just work by changing to +zxn and subtype=dotn. NextZXOS implements the esxdos api and a lot of extensions to it. The extended esxdos api is documented in "NextZXOS_esxDOS_APIs.pdf" https://gitlab.com/thesmog358/tbblue/-/ ... s/nextzxos
So I had a bad experience with -SO3. It seemed to be generating incorrect code. Note I'm using a version of z88dk built from main, rather than an official release, so perhaps this was the reason.
It's not impossible as SO3 is very aggressive and that's why it typically leads to much better results. I currently use an older release 1.99C as that's the last version I worked on and know that SO3 should be fine. With sdcc upgrading and changing code generation, this could expose bugs in the SO3 set or in other related sdcc code. I just haven't had time to fully play with the latest versions.
In summary though, it sounds like I should be able to build the tap files and the dotx files with the same compiler options, i.e., with sdcc_iy, and if I disable interrupts in specasm.tap and redo the key handling, all will hopefully be well. Does this sound like a reasonable conclusion?
Yes if interrupts remain disabled then a normal sdcc_iy compile should be fine. On a standard spectrum, this is fine because nothing useful is going on in the interrupt. On a Spectrum Next, it's preferred that the interrupt is allowed to run when basic compatibility is desired because there could be drivers running. On the Next, however, you don't have to worry about iy so it's fine to run with rom interrupts enabled.

The other idea of creating an im2 table can normally be easily done too. Set it up at 0x8000 containing 0x81 bytes and org your program/dotx second part to 0x8184 with jp to isr at 0x8181 and isr located at 0x8101. Such an isr can save iy and call into the basic isr. You'll lose 300 bytes or so but it's very easy to set up if the basic isr has to run. One short asm file in z88dk.
User avatar
RMartins
Manic Miner
Posts: 776
Joined: Thu Nov 16, 2017 3:26 pm

Re: Specasm: A Z80 assembler designed to run on the spectrum itself

Post by RMartins »

markusr wrote: Sun Feb 20, 2022 4:58 pm ...

Specasm is written in C and built with z88dk. The keyboard entry routine is here. I'm using z88dk's in_inkey() function to read the key. A loop is started after the key is read, in which we re-scan the keyboard a fixed number of times, sleeping for 20ms in between each scan. We exit the loop if a different key is read or the fixed number of loop iterations elapse. The loop is there to handle repeated key presses. The function specasm_sleep_ms(20) basically does a halt. Interrupts are enabled to faciliate the halt. I believe this is safe, from an IY point of view, as I'm not using any of the standard libraries IO functions, i.e., I'm using -startup=31. Does anyone have any suggestions to improve this routine? Ideally, the solution should be small, as I don't have a lot of bytes left.
The implementation of the in_inkey asm routine might help.

You need to understand the behaviour of the function.
The first comments are particularly relevant:

Code: Select all

asm_in_inkey:

   ; exit : if one key is pressed
   ;
   ;           hl = ascii code
   ;           carry reset
   ;
   ;         if no keys are pressed
   ;
   ;            hl = 0
   ;            carry reset
   ;
   ;         if more than one key is pressed
   ;
   ;            hl = 0
   ;            carry set
So receiving 0 (zero) doesn't always mean there is no key pressed.

If you want complete control, I would suggest implementing a simple ASM routine, that just creates a bit map of the keys using just 8 bytes, one for each half row.
The best approach, in my opinion, would be to generate a queue of events, based on comparing a previous read of the 8 rows, with the latest build
markusr
Drutt
Posts: 28
Joined: Sun Feb 20, 2022 3:14 pm

Re: Specasm: A Z80 assembler designed to run on the spectrum itself

Post by markusr »

Alcoholics Anonymous wrote: Wed Mar 02, 2022 5:56 am I'm afraid I don't know - it's a report of "not working" without further details. So I just had a quick look to try to figure out every possible thing that might go wrong. However, I think it may have to do with your use of .launcher which is not part of the NextZXOS distribution by default.
Ah, okay. Specasm will be very inconvenient to use without .launcher, particularly when trying to link something.
Yes if interrupts remain disabled then a normal sdcc_iy compile should be fine. On a standard spectrum, this is fine because nothing useful is going on in the interrupt. On a Spectrum Next, it's preferred that the interrupt is allowed to run when basic compatibility is desired because there could be drivers running. On the Next, however, you don't have to worry about iy so it's fine to run with rom interrupts enabled.
Thanks for the confirmation. I've applied these changes locally (and disabled interrupts in Specasm) and so far, everything seems to be working fine. No weird crashes. Needs a bit more testing though.
The other idea of creating an im2 table can normally be easily done too. Set it up at 0x8000 containing 0x81 bytes and org your program/dotx second part to 0x8184 with jp to isr at 0x8181 and isr located at 0x8101. Such an isr can save iy and call into the basic isr. You'll lose 300 bytes or so but it's very easy to set up if the basic isr has to run. One short asm file in z88dk.
As an interim solution, I've disabled interrupts in Specasm and switched from halt to z80_delay_ms for the delay. To get it to work with the OMNI turbo mode I do some calibration in the BASIC loader and pass this calibration info (one byte of data) from the BASIC program to Specasm via a byte in what I think is the printer buffer (23328). Hopefully, this is safe. All this needs to be reworked, but my hope is that this will be sufficient in the short term to make Specasm more stable.
markusr
Drutt
Posts: 28
Joined: Sun Feb 20, 2022 3:14 pm

Re: Specasm: A Z80 assembler designed to run on the spectrum itself

Post by markusr »

RMartins wrote: Wed Mar 02, 2022 9:35 am
If you want complete control, I would suggest implementing a simple ASM routine, that just creates a bit map of the keys using just 8 bytes, one for each half row.
The best approach, in my opinion, would be to generate a queue of events, based on comparing a previous read of the 8 rows, with the latest build
Thanks. This is an interesting idea. I may well ultimately end up writing a custom keyboard entry routine in assembler. I've noticed another issue with the keyboard entry. Sometimes, keys pressed directly after pressing the ENTER key are ignored. The reason for this is that when you press ENTER, the line you've just typed gets assembled and if you're at the bottom of the screen, the screen gets scrolled. All this takes some time, long enough in some cases to miss key presses. Perhaps this problem could be solved with an interrupt driven key scanning routine that populates such a queue of events.
markusr
Drutt
Posts: 28
Joined: Sun Feb 20, 2022 3:14 pm

Re: Specasm: A Z80 assembler designed to run on the spectrum itself

Post by markusr »

I've released v2 of Specasm. The new release addresses some of the issues discussed in this thread. In particular
  • equb, equw, repb have been renamed db, dw, and ds respectively.
  • The order of arguments expected by ds is the reverse of what repb expected. Both parameters are mandatory.
  • Hexadecimal numbers are now denoted by '$' instead of '&'
  • The align directive has been implemented. You can now align code and data to a power of 2 up to 256 bytes.
  • Specasm now disables interrupts. It uses z80_delay_ms for its delay loop and relies on a calibration value passed in by its BASIC loader. This makes Specasm much more reliable.
  • Two new directives, '+' and '-', can be used to pull in .X files in other directories into the project. Files included with '+' are relative to the /specasm directory. Files included with '-' are relative to the directory of the including file or are absolute paths.
  • The filename passed to the 's' command is now optional. It is no longer required if Specasm knows the name of the file you're editing, either because it has loaded it or has previously saved it.
  • Auto repeat disabled for SYM SHIFT + w.
  • All Specasm binaries are now built with the same compile options and std library. This speeds up the builds a bit.
Specasm v2 is binary compatible with Specasm v1, i.e., you can load your v1 .x files with Specasm v2. The .s files aren't compatible however, as the names of some of the directives have changed. So if you used v1 of saexport to export a .x file to a .s file you won't be able to reimport it with v2 of saimport.

So some progress, but the key handling routine still needs work (a complete rewrite) and there's still no support for expressions.
User avatar
PeterJ
Site Admin
Posts: 6879
Joined: Thu Nov 09, 2017 7:19 pm
Location: Surrey, UK

Re: Specasm: A Z80 assembler designed to run on the spectrum itself

Post by PeterJ »

Maybe a little old new now, but I noticed that @markusr has released version 3 of Specasm. It's available here:

https://github.com/markdryan/specasm/releases
markusr
Drutt
Posts: 28
Joined: Sun Feb 20, 2022 3:14 pm

Re: Specasm: A Z80 assembler designed to run on the spectrum itself

Post by markusr »

Thanks for posting this @PeterJ. I meant to do this myself after publishing the release, but forgot.

If you do happen to be using Specasm, I highly recommend updating to the v3 release. It fixes a really nasty bug with the align directive which can result in the addresses of labels being incorrectly computed by the linker, which isn't a good thing and is very confusing when it happens.

I am planning to do a bit more development on Specasm in the Autumn, so hopefully there will be a new version with some new features out by the end of the year.
markusr
Drutt
Posts: 28
Joined: Sun Feb 20, 2022 3:14 pm

Re: Specasm: A Z80 assembler designed to run on the spectrum itself

Post by markusr »

Specasm v4, the most unremarkable version of Specasm yet, has just been released. This release boasts better test coverage, one small fix to the find command and fewer bytes. It’s really the latter change that motivated the release. The Specasm binary has shrunk by 1.8kb freeing up space for some new features, which admittedly haven’t been written yet. Most of the savings come from upgrading to the new release of z88dk (v2.2) and switching optimisation levels from -SO2 to -SO3. A few hundred bytes were also shaved off the BASIC loader.

I mentioned earlier in this thread that I had encountered issues (errors parsing certain instructions) when using -SO3 optimisation. I’m unable to reproduce these issues with the latest version of the compiler. Worryingly, I’m also unable to reproduce them with the previous version of the compiler I was using. At the time I first encountered these issues I was convinced that the compiler was at fault, but now, I’m not so sure. Which is unfortunate, as there’s nothing more disconcerting than a bug that magically vanishes. What’s worse is that I was never able to reproduce this bug in the automated tests, which are built with gcc and run natively on my laptop and in the CI and not on the Spectrum itself or in an emulator. It was only reproducible on the Spectrum.

In attempt to prove to myself that the bug really was fixed, I had a go at getting Specasm’s tests to run natively on the Spectrum. This was a bit of a challenge, as there’s over 160kb of test data. When testing on Linux, this data is built into the test binary, but this isn’t an option on the Spectrum. No matter. ESXDOS to the rescue! By moving the test data to a couple of files and reading in the data, for one test at a time from the SD card, there’s enough memory to run most of the unittests (the editor tests are too big). Over 4000 tests can be run directly on the Spectrum! They take 90 or so seconds to run on a real hardware and all pass with the new compiler and with -SO3 enabled. So I’m reasonably confident that the bug is fixed, or at least is not present in this release.

The release was validated on a harlequin 128 and a Spectrum 128 Toastrack using a DivMMC Enjoy Pro One with ESXDOS 0.87.

So that’s Specasm v4. I’m now contemplating what wonders can be achieved for version v5 with the liberated 1.8kb.
markusr
Drutt
Posts: 28
Joined: Sun Feb 20, 2022 3:14 pm

Re: Specasm: A Z80 assembler designed to run on the spectrum itself

Post by markusr »

Specasm v5 has been released! Unlike Specasm v4, this release does have some new features namely, the ability to define local and global constants and to use expressions in the place of immediate integer values. For example, @utz's examples from earlier in the thread can now be written as follows

Code: Select all

.Foo equ some_value

  ld a,=Foo

Code: Select all

.Foo
  ld a,some_value
  xor a
  ld (=Foo + 1),a
Constants can be local or global. Local constants are visible only in the file in which they are declared. Global constants, those that start with the capital letter, can be accessed from any file in the project. Constants do not need to be declared before they are used. For example,

Code: Select all

.totallen equ len + 1
.len equ 10
is allowed.

Constants share the same namespace as labels. Constants and expressions are restricted to 16 bit integers for now. They are downcast to 8 bit ints where necessary. Expressions when used in instructions and data directives must be preceded by an '='. The '=' is not needed to introduce an expression when defining a constant with an equ statement.

Like most Specasm features, constants and expressions suffer from a number of limitations. Global constants cannot reference local constants and expressions are not parsed and evaluated until link time. This is partially because expressions involving labels need to be computed at link time anyway, but also because there isn't enough room left in the assembler/editor for an expression parser. This means that if you type an invalid expression into the editor, e.g.,

Code: Select all

ld a, =some_value +
or

Code: Select all

.val equ 3
im =val
you won't be informed that there is an error until link time.

And probably the biggest limitation is that expressions cannot be used with the ix and iy registers, e.g.,

Code: Select all

add a, (ix + =10*10)
is not allowed.

For more information on what is supported and what is not please see here.

So that's Specasm v5. Tested mostly on a 128 Toastrack and a harlequin 128. I haven't actually tried this version on a 48kb Spectrum but it should work.
Post Reply