Using MCoder2 in EightyOne

Anything Sinclair ZX Basic related; history, development, tips - differences between BASIC on the ZX80 and ZX81
User avatar
mrtinb
Posts: 1906
Joined: Fri Nov 06, 2015 5:44 pm
Location: Denmark
Contact:

Re: Using MCoder2 in EightyOne

Post by mrtinb »

I think the LIST command simply assumes the line is finished, and doesn’t show anything past the $76. However if you list a line number past the current one, it will show that.
Last edited by mrtinb on Mon Mar 21, 2022 9:20 am, edited 1 time in total.
Martin
https://zx.rtin.be
ZX81, Lambda 8300, Commodore 64, Mac G4 Cube
User avatar
Paul
Posts: 1511
Joined: Thu May 27, 2010 8:15 am
Location: Germanys west end

Re: Using MCoder2 in EightyOne

Post by Paul »

bwinkel67 wrote: Mon Mar 21, 2022 3:24 am What does the ZX81 do when it sees a HALT instruction?
The same as every other Z80 based computer. It stops execution and waits there. One could say it waits for something to happen. In a ZX81 the thing that happens is an NMI or a reset (if you have installed a button and press it before an NMI occurs)
It then does what the interrupt wants. NMI shows the screen an after that continues with the next statement.
In other words, don't call the halt statement in fast mode.
In theory, there is no difference between theory and practice. But, in practice, there is.
bwinkel67
Posts: 147
Joined: Mon Mar 23, 2020 2:38 am

Re: Using MCoder2 in EightyOne

Post by bwinkel67 »

Paul wrote: Mon Mar 21, 2022 8:04 am The same as every other Z80 based computer. It stops execution and waits there. One could say it waits for something to happen. In a ZX81 the thing that happens is an NMI or a reset (if you have installed a button and press it before an NMI occurs)
It then does what the interrupt wants. NMI shows the screen an after that continues with the next statement.
In other words, don't call the halt statement in fast mode.
The reason I ask is because it actually doesn't wait. With MCODER2, if you compile a simple program that just prints "HELLO WORLD" it will be compiled into the second REM statement and given the number 2 (i.e. 2 REM) with starting address of 20500 i.e. a USR 20500 call will start the program and print "HELLO WORLD" to the screen. Peeking that address gives you 33 (21 in hex) which is a LD instruction. The two instructions before that are both 118 (76 in hex), which means there are two back-to-back HALTs. If you do a USR call to either 20499 or 20498, the program continues to run, going to 20500 and printing "HELLO WORLD" to the screen, so it doesn't seem to HALT. So what's going on here?
User avatar
mrtinb
Posts: 1906
Joined: Fri Nov 06, 2015 5:44 pm
Location: Denmark
Contact:

Re: Using MCoder2 in EightyOne

Post by mrtinb »

  • First it halts
  • but microseconds later an interrupt from the ULA draws the screen
  • Then it halts again
  • but microseconds later an interrupt from the ULA draws the screen
  • And then it comes to the code that writes HELLO WORLD
Last edited by mrtinb on Mon Mar 21, 2022 1:11 pm, edited 1 time in total.
Martin
https://zx.rtin.be
ZX81, Lambda 8300, Commodore 64, Mac G4 Cube
bwinkel67
Posts: 147
Joined: Mon Mar 23, 2020 2:38 am

Re: Using MCoder2 in EightyOne

Post by bwinkel67 »

mrtinb wrote: Mon Mar 21, 2022 12:30 pm
  • First i halts
  • but microseconds later an interrupt from the ULA draws the screen
  • Then it halts again
  • but microseconds later an interrupt from the ULA draws the screen
  • And then it comes to the code that writes HELLO WORLD
Ah, thank you...that's what I wanted to know. I could just poke a NOP in there but seems like either way it runs.
Fruitcake
Posts: 346
Joined: Wed Sep 01, 2010 10:53 pm

Re: Using MCoder2 in EightyOne

Post by Fruitcake »

bwinkel67 wrote: Mon Mar 21, 2022 3:24 am So it seems to do two consecutive HALTs before doing a LD. ... What does the ZX81 do when it sees a HALT instruction?
Machine code is typically stored in a REM statement on the ZX81 as a convenient place to keep it so that it remains at a fixed location and gets saved to tape via the SAVE command. However, the BASIC listing routine does not know it contains machine code and will just try to display it like any other BASIC line. Hence you see it as a mix of seemingly random characters and tokens. Aside from looking a bit messy, this can actually cause problems.

If a $76 byte is encountered then the BASIC listing routine thinks this means the end of the line has been reached. It then tries to interpret the bytes that follow as the next BASIC line. This can result in some very corrupt looking listings until real BASIC lines are encountered and things get back into sync.

For example, type:

1 REM XX
2 PRINT "HELLO"

POKE 16514,118
LIST

You'll see line 2 is displayed corrupted because the BASIC listing routine thinks the next line starts two bytes earlier than it really does due it encountering the $76 poked in. However, you can still RUN the program and can LIST 2 successfully.


Another problem concerns hidden floating point numbers. After every numeric literal in a BASIC statement, 6 hidden bytes follow that define the actual value in floating point form. This sequence begins with a byte of $7E. Should the machine code in a REM contain a byte of $7E then the BASIC listing routine will assume it to imply a hidden floating point value and so will skip over the following 5 bytes. Should the $7E occur within 5 bytes of the end of the REM statement then the BASIC listing routine gets confused and continues into the following line.

For example, type:

1 REM XX
2 PRINT "HELLO"

POKE 16514,126
LIST

You'll see line 1 and 2 merged. You can still RUN the program and can LIST line 2 successfully.


A more serious problem is if the REM statement is so long that when an attempt is made to display it, all characters cannot fit within a single screen. Normally you would expect to get an error report 5 but the BASIC listing routine can get really confused resulting in an infinite loop as it continually tries to display the line.

To overcome this problem you either make sure you never attempt to list the line containing the machine code or you can fool the BASIC listing routine into thinking it has reached the end of the BASIC program by poking two $76 bytes at the start of the REM (as Martin pointed out). The first byte tricks the BASIC listing routine into thinking it has reached the end of the line, as described above. The second byte makes the BASIC listing routine think it has reached the end of the BASIC program. This works because any value above 63 ($3F) is treated as indicating the end of the BASIC program. Although the largest BASIC line you can enter directly is 9999 ($270F), the BASIC system actually supports line numbers up to 16383 ($3FFF). You can poke the line number bytes of a line to have a value greater than 9999 and it will run fine, but with just display oddly in a LISTing. So by poking the second byte with $76 means the BASIC listing routine thinks it has reached the end of the BASIC program and so displays nothing further. Using $76 is just a convenient value since $76 normally implies 'end of line'.

With the above program, type:

POKE 16514,118
POKE 16515,118
LIST

and you'll only see line 1. You can still run the program successfully, and you can still LIST 2 successfully. But after typing LIST 2, press newline to redisplay the automatic listing and it will show from line 1 again. To overcome this type:

POKE 16419,2
LIST 2

Press newline again and the automatic listing begins from line 2. Location 16419 and 16420 hold the line number value for system variable S_TOP, which controls with line that appears at the start of the automatic listing.


A final reason for hidding the machine code in a line could be as a very simply protection mechanism to stop people LISTing the program and and seeing secret messages that a game might display, etc.


I suspect MCODER2 puts in the two $76 bytes to avoid the listing issues above, especially the infinite loop problem since it can create very long REM lines.


bwinkel67 wrote: Mon Mar 21, 2022 11:27 am If you do a USR call to either 20499 or 20498, the program continues to run, going to 20500 and printing "HELLO WORLD" to the screen, so it doesn't seem to HALT. So what's going on here?
The simple answer is you are not supposed to execute the program in this way. However, it works as you have found (and as Paul and Martin have said) because of the way the ZX81 border line generation mechanism works. Each HALT will halt the Z80 but only until the end of the next border line has been output. A non-maskable interrupt (NMI) then occurs and the program moves onto the next location. So after two border lines have been output then the program finally moves onto 20500 and runs the compiled program. So at worst you have simply introduced a delay equivalent to the duration of two border lines, i.e. 128us.
bwinkel67
Posts: 147
Joined: Mon Mar 23, 2020 2:38 am

Re: Using MCoder2 in EightyOne

Post by bwinkel67 »

Fruitcake wrote: Mon Mar 21, 2022 2:08 pm To overcome this problem you either make sure you never attempt to list the line containing the machine code or you can fool the BASIC listing routine into thinking it has reached the end of the BASIC program by poking two $76 bytes at the start of the REM (as Martin pointed out). The first byte tricks the BASIC listing routine into thinking it has reached the end of the line, as described above. The second byte makes the BASIC listing routine think it has reached the end of the BASIC program.
Thank you, that explains the why. I decided, as an experiment, to compile my game on a 16K ZX81 without using a modern computer (just for fun). Since it's larger than MCODER2 can compile in one try, my solution was to take the routine for drawing the screen and compile it separately (second) after the main game. That actually worked as it made the main game small enough. The one issue then is, how to call the screen. Two solutions, either add two USR calls in BASIC, which would work, or call the screen routine within the compiled game routine (what I tried).

If you've already written the BASIC program (the game for me), MCODER2 lets you load it in, but only once and after it attaches, anything else you add must be typed in. So I loaded my original game, deleted the screen routine (by hand), attached MCODER2 (via USR 32462), and then remove the BASIC listing (via USR 17281) -- Btw, right after the step where MCODER2 has been attached to your program, you must save and then reload to clear MCODER2's attach code which frees up almost 2K of memory. Before that last step (i.e. removing the BASIC listing), I compiled it three times. First time to generate its REM, then poked the REM to 1 to allow a second compile, then added a STOP after the first BASIC line of my program and compiled again -- this is a way to get the next compiled starting point. Then, you simply deleted the new "2 REM" and re-poke the "1 REM" that contains the game back to "2 REM" and remove the STOP. Then change the USR call within the game to now reflect the starting point of the next machine code routine and compile a third time. Now you can remove all of BASIC, type in the remaining screen routine, and compile it again (first poke the game REM from 2 to 1) and you are all done (once you remove the final BASIC via USR 17281).

The odd thing is, however, when compiling the new BASIC (screen), it gave a slightly different starting point (25036) whereas when I added the STOP to the game part, poked the REM to be 2, and compiled I got 25035. There is no way going back since that code has been deleted (or even if saved you'd be forced to retype the screen part, which was pretty long), so I left it at 25035 since it worked. Obviously, one solution is to peek through the 2nd REM statement to find out where the USR call is and poke in to change 25035 to 25036 (doable but a bit time consuming). I did come up with a way to have the two routines share information -- added my own REM at the very beginning (1 REM 12345678) and poked and peeked values I needed between the two routines (the address starts at 20500) from both sides.

Just wanted to see how MCODER2 could do it with an original 16K ZX81 without having a modern computer, since I would have loved to have owned MCODER2 back in 1983. It all worked and worked pretty seamlessly (i.e. you can definitely do it without modern computer and emulator, with just a ZX81 and a cassette deck). I'm really impressed with that compiler, written for such a small memory space on a machine that primarily only had a tape save as its data store. I did back it up 4 times: saved to "tape" about 4 times at 4 different critical stages, though here, my "tape" was a WAVE recording on my computer...so that's the only place I cheated since it will capture the audio directly from the ZX81 much cleaner than an actual cassette recorder (and with a cassette recorder I likely would have saved it twice, on two separate tapes, at each stage, as I often did as a kid in the 80's).
Fruitcake
Posts: 346
Joined: Wed Sep 01, 2010 10:53 pm

Re: Using MCoder2 in EightyOne

Post by Fruitcake »

bwinkel67 wrote: Mon Mar 21, 2022 9:30 pm Just wanted to see how MCODER2 could do it with an original 16K ZX81 without having a modern computer
Interesting to know it would actually be possible to do it all on a real machine; you just have to be a bit inventive but clearly possible.

bwinkel67 wrote: Mon Mar 21, 2022 9:30 pm I'm really impressed with that compiler
It seems once you learn to work around its limitations then it is possible to achieve some impressive results using it, as several others on this forum have also demonstrated.


Aside from the original MCODER I, I'm also aware of another compiler for the ZX81. It was made by Silversoft but I don't have any direct experience of it so don't know how it compares. It's available online but sadly no instructions sheet for it:
http://www.zx81stuff.org.uk/zx81/tape/ZXCompiler

I suspect MCODER II is superior, but perhaps someone else has used it and can comment on how it compares.
bwinkel67
Posts: 147
Joined: Mon Mar 23, 2020 2:38 am

Re: Using MCoder2 in EightyOne

Post by bwinkel67 »

Btw, running my game in FAST, regardless of whether I start at the HALT or at the correct location (LD) seems to stop the computer. I tried it with the version I compiled with 48K of memory (i.e. so no mismatch on a USR call).

Still, if I want to fix it (i.e. not having my second routine start with a HALT, relying on the screen interrupt to move it along), this simple BASIC program will find the address 25035 in my code (I start at 20514 since it's the entry point into my game). I then simply poke the address it returns with 204, which changes 25035 to 25036, and that should correct it:

Code: Select all

200 LET A=20514
210 LET C1=PEEK A
220 LET C2=PEEK (A+1)
230 IF C2=97 AND C1=203 THEN GOTO 260
240 LET A=A+1
250 GOTO 210
260 PRINT A
Fruitcake
Posts: 346
Joined: Wed Sep 01, 2010 10:53 pm

Re: Using MCoder2 in EightyOne

Post by Fruitcake »

bwinkel67 wrote: Mon Mar 21, 2022 11:46 pm I then simply poke the address it returns with 204, which changes 25035 to 25036, and that should correct it
The other option is to poke the $76 with $00, which is a NOP instruction and so does nothing apart from delay for 4 clock cycles. But your solution is preferable to make the code run as efficiently as possible.
Post Reply