6809 ASM: Exchanging data between Assembly code and the BASIC interpreter on the Olivetti Prodest PC 128 and the Thomson MO6 (Part 3)


Here is part 3 of this mini series of articles about sharing data between the SIMIV BASIC interpreter and the 6809 CPU machine code on the Olivetti Prodest PC 128 and the Thomson MO6.

Disclaimer

The techniques, examples and concepts described in this series of articles are made for the Olivetti Prodest PC 128 and Thomson MO6 microcomputers. The methods described in the 1st and 2nd articles can be easily applied to the Thomson MO5 series, while the method described in this one is specific for the PC 128 and MO6 ( because the BASIC 1.0 does not support DEFUSR instruction). They can be converted for the TO7, TO8 and TO9 series, however, to keep the article’s size as small as possible, I did not add specific sections with the converted memory addresses for the TO7/TO8/TO9. If you want me to extend my articles for the TO7, TO8 and TO9 series, please let me know in the comments below, thanks!

Method 3: Sharing Variable’s values (a better way)

For this method we use a useful command offered by the SIMIV BASIC 128, DEF USR which allows us to define a user function in machine language (basically extending the capabilities of the BASIC interpreter).

Please Note: We are slowly getting into more complex “realms”, so we’ll need a bit more insights about what we are doing. So, descriptions from this article onwards will become more “beefier”.

What does DEFUSR exactly do? It allows us to define and call a user function written in machine code as it would be a regular BASIC function. It allows us to pass a parameter to the machine code using a BASIC variable. It’s a bit like doing Procedural Programming using Assembly for the body of the function and BASIC to call it.

Define a machine language user function

Before we can use machine language as a user function we need to “define” it. The definition process is simply assigning the machine code “entry-point” address to one of the available USR functions.

The general form to “define” a function is the following:

DEFUSRx=asm-function-address

Where:

  • “x” is a number between 0 and 9 that identifies the specific function (so we can define max 10 ASM functions).
  • “asm-function-address” is the address of the first instruction that composes the machine code routine or where our machine code starts (this is why it’s also known as “entry-point“).

So, for example:

DEFUSR3=&H5F02

The example above simply assigns address &H5F02 (as a function entry point) to the USR function number 3.

Use a machine code user function

The general form to call a machine code user function is very similar to when we call a regular BASIC function:

destination-variable=USRx(expression)

Where:

  • “destination-variable” is a BASIC variable that will receive the result of the USR function.
  • “x” is again our function id (a number between 0 and 9).
  • “expression” is, guess what? A value we want to share with our machine code as a parameter or as a variable or as a result of an operation or function.

So, for example:

G%=USR2(A%)

The above will jump to the entry-point address defined for USR2 after preparing the A% parameter to be shared with our machine code. The result of the routine called by USR2 will be placed in G% when we return to BASIC.

Internals of this method

When creating a user function in machine language the BASIC interpreter expects us to behave in a certain way in our code, so it’s vital for you to understand each detail of what follows in order to avoid to crash or deadlock your computer while testing your code.

1. Don’t touch registers DP and S…

First off, when using user defined machine code functions never touch Registers DP (Direct Page) and S (Stack Pointer). If you really need to do that then make absolutely sure to save them on the user stack at the beginning of your assembly code and restore them before giving back control to the BASIC interpreter.

If you do not follow this policy then bad things will happen when you give back control to the BASIC interpreter… I have heard stories of PC 128 that got infested by gremlins and even pixies!!! 😀

To save DP and S on the User Stack (using register U) we can do:

00100         PSHU     #72

On the 6809, PSHU instruction will push registers onto the User Stack, the number #72 is the decimal number for the following binary number:

&B01001000

Remember the “LSb” (I use LSb to identify the “Less Significant bit”) is the one on your right and it’s also known as b0 (or bit 0 zero). The number passed to PSHU is an 8 bit number where each bit when set to 1 tells PSHU which 6809 register should be pushed onto the user stack. DP register is identified by b3 and register S is identified by b6 hence to push them to the User Stack we need to pass a number with those bits set. With #72 we are telling PSHU to store only DP and S on the user stack.

To recover DP and S from the User Stack before giving back control to the BASIC we can use:

00100        PULU    #72

Same logic as for PSHU’s operand. The above instruction tells the 6809 to recover the values for DP and S previously stored on the user stack.

Please note: The syntax for PSHU in Infograme assembler is

00100         PSHU     S,DPR

And for PULU is

00100         PULU     S,DPR

2. Register X points at the address of the return variable…

Another important thing is that at the moment of the exit the register X must point to the same address were it was pointing when our ASM code has got called.

3. Register A tells to our code and to the BASIC interpreter which data-type we are dealing with…

Finally the register A should contain a value that identifies the data type we want to use to return the value to the BASIC interpreter. Allowed values are:

  • 2 for an integer type
  • 3 for a String type
  • 4 for a Single Precision type
  • 8 for a Double Precision type

The coding used above corresponds also to the number of bytes required by each data-type or its descriptor. More details on this argument will be presented in part 6 when available.

The register A will contain one of the values above also at the moment our machine code is called to let us know which type of data has been passed to our code.

4. Strings are a special case…

On top of that, in case we are receiving a string-type (aka the BASIC code has passed to our machine code a string):

  • Register B is used to store the length of the string, which in the old 8bit BASIC couldn’t be longer than 255 bytes.
  • Register U is used to point at the first character of the string.

To access the value of each data type we can use the Indexed Addressing, which can help to reduce a bit the number of instructions required to access such value, here is a list:

  • $02,X to access the MSB of an integer (if you want to access the LSB then you can use $03,X).
  • $01,X to access the MSB of a Single Precision type.
  • $00,X to access the first byte of a string.

A practical example of use

The following code will define two user functions (so we can have a look at some of the implementation details of when we create/use multiple functions). The Assembly code is an example of how you could create libraries of functions. Instead of having the label START I use the label LIBR and I have created an extremely simple header (the corresponding RTS instruction) to help prevent the use of the library as  fully executable. We’ll discuss all the details in the ASM section.

A bit more detail about location &H5F00: We now know it’s very useful because it’s in BANK 0 and so it’s in non-commutable RAM and therefore it doesn’t suffer from issues when we use multiple commutable banks. Another important characteristic of this location is that it’s at the END of BANK 0 so when we reserve it for our machine code we also leave most of the space in BANK 0 for the BASIC interpreter buffers and stack so that we can still write pretty complex programs in BASIC. However BASIC 128 still uses location &H5F03 so, given the volume of the code below we had to move our start of the machine code to location &H5F05 (to be sure nothing will overwrite our code).

All code

100 REM Using USR functions to 
110 REM exchange data between
120 REM ASM and BASIC 
130 CLEAR ,,,&H5EFF
140 FBADDR(0)=&H5F05:FBADDR(1)=&H5F06:FBADDR(2)=&H5F0B:GOSUB 500
150 DEFUSR1=FBADDR(1):DEFUSR2=FBADDR(2)
160 A%=65:B%=USR1(A%)
170 PRINT A%, B%
180 A%=1024:C%=USR2(A%)
190 PRINT A%, C%
200 END
500 REM Load machine code
510 I%=0:RESTORE 600
520 FADDR=FBADDR(I%)
530 READ OPC%:IF OPC%>255 THEN I%=I%+1:IF I%>2 THEN RETURN ELSE GOTO 520
540 IF OPC%<=255 THEN POKE FADDR,OPC%:FADDR=FADDR+1
550 GOTO 530
600 ' LIBR:
610 DATA &H39,&H100
620 ' USR1:
630 DATA &H6C,&H03,&H7E,&H5F,&H05,&H100
640 ' USR2:
650 DATA &H10,&HAE,&H02,&HCC,&H00,&H00
660 DATA &H3F,&H0A,&HC1,&H00,&H26,&H09
670 DATA &H1E,&H02,&H83,&H00,&H01,&H1E
680 DATA &H02,&H26,&HF1,&HED,&H02,&H86
690 DATA &H02,&H7E,&H5F,&H05,&H00,&H00
700 DATA &H100

For beginners: In the program above I “partitioned” all the binary code (lines from 600 to 700) into “sections” to help the reader to understand which binary codes belong to which function/section of the ASM library source presented below.

FBADDR(0) (Function Base ADDRess 0) represent the library header

FBADDR(1) Is the entry-point to the first available function (or routine)

FBADDR(2) Is the entry-point to the 2nd routine

Lines from 150 to 190 are to demonstrate the usage of method discussed in this article and the ASM functions.

ASM Code

00100 * Library Initialisation 
00110         ORG     $5F00
00120         FCB     $00,$00,$00,$00,$00
00200 LIBR    RTS
00300 * 
00310 * USR1
00320 * Increment a number by 1 
00330 * Let's start by checking the parameter and increment it's value
00340 USR1    INC     $03,X
00350         JMP     LIBR
00400 *
00410 * USR2
00420 * Read a pressed key 
00430 * Let's start by checking the parameter
00440 USR2    LDY     $02,X
00450         LDD     #$00
00460 U2LOOP  SWI
00470         FCB     $0A
00480         CMPB    #$00
00490         BNE     U2DONE
00500         EXG     D,Y
00510         SUBD    #1
00520         EXG     D,Y
00530         BNE     U2LOOP
00540 U2DONE  STD     $02,X
00550         LDA     #$02
00560         JMP     LIBR
00900 *
00910 * End of Library
00930         END     LIBR

So what does the ASM code do?

Lines from 100 to 200 are the very basic Library Header I mentioned above. This is required to avoid the execution of the library as it would be a fully executable code. We want to prevent this because each function in the library requires it’s parameters to be passed and each function will return to the BASIC interpreter after being executed.

Lines 300-340 are for the first function in the library. USR1 simply increment a number we pass to it, so it’s something similar to B%=A%+1.

Lines 400-550 are the second function in our small library. This one is a bit more interesting and uses an SWI (SoftWare Interrupt GETCHAR) to read if a key is pressed on the keyboard. If a key is pressed then it will return its ASCII code to the BASIC interpreter otherwise it will wait a certain number of cycles and if no key is pressed within such wait then it will return 0 to the BASIC.

It works that we pass USR2 the amount of cycles we want to wait for a user to press a key and, if the user presses a key within the number of cycles, then it will put the corresponding ASCII character code in the B Register and then from there it will will store it where Register X points so that the BASIC can place it in C%. This function is really useful because it can read also combinations like CTRL+C etc… and if you want to read the BASIC or SHIFT key you can use the $OC SWI (eventually).

To keep the logic simple I used the EXG (EXchanGe instruction, that swaps two registers) between D and Y to do both key reading and cycle countdown without the need to copy anything in memory or elsewhere. I used it this way because SWI $0A requires register B to store the key value and SUBD (given the fact that register D is a combination of A and B ) needs B to not be altered by other instructions. So, since Register Y was not being used, it is a temporary buffer for whatever value I needed to use on the next step of the function.

For beginners: USR2 routine can be useful in a number of applications, if you pass it 0 cycles to wait it will return immediately, so for low latency apps and games you can use 0 or small values. While for less latency critical apps you can pass higher values and create an editor with the cursor blinking being stable instead of the crazy flickering like it happens when you use the BASIC function INKEY$ 😉

Ok so the important parts related to the method presented in this article are:

Line 00340 we immediately increment the LSB pointed by X using Indexed Addressing (so basically we execute the INC opcode at X + 3 bytes). Since we pass an integer value then A will already contain $02 so no need to change A Register value.

Line 00440 we load Register Y with the content of the memory address pointed by X + 02 and since Y is a 16bit Register we load the byte plus the next one (aligned), so, basically, the whole value of A%.

Line 00450 since Register A will contains the data type of A% and B may contain some previous stuff we reset both of them by assigning 0 to D (D is a combination of A and B).

Lines from 00460 to 00490 we call the GETCHAR SWI and we check if there was a key pressed when it returns, if no we keep executing our code. If there was a pressed key then we’ll quit the LOOP (U2LOOP).

Line 00500 we swap D with Y.

Line 00510 we decrement  the value in D.

Line 00520 we swap back D with Y (this is a trick, so if we need to exit then D has the 0000 value and if we do not need to exit then the SWI will write the key code, if any, in the right register).

Line 00530 if the counter is still not 0 then we repeat the loop otherwise we’ll keep the execution.

Executing the example code

If you run the BAISC code then it will show you:

65      66
1024    <value-of-the-key>

First output line is for USR1 and it’s very simple to understand.

Second output line shows the number of cycles we have passed to USR2 function and then either 0 if no key was pressed within the cycles OR a number that identify the key you have pressed.

Observations about this method

What we need to note of this approach is that we no longer require a known location (as it happened on previous two methods). In fact the BASIC interpreter will position all pointers to the passed values already in the 6809 registries for us, so all we have to do in ASM is to use these registries appropriately.

Pros

  • DEF USR makes it easy to extend BASIC functionalities via machine code.
  • No need for a pre-shared known location
  • No need for a pre-shared known data-type (USR will let the machine code know which type of variable has been passed as a parameter)
  • It is effectively the only proper Procedural Programming form available in SIMIV BASIC.
  • It’s a good method to create specialised functions for a Game Engine etc.
  • It makes it easier to access data from the BASIC and to report to BASIC (if the user uses a wrong return type then BASIC will give an error message Type Mismatch).

Cons

  • We can have max 10 functions defined.
  • We can only pass one single parameter to a USR function (even when using an expression, it must have a single result)
  • The value pointed by registry X is actually a copy of the parameter we have passed, this is why the original variable value is not changed when we return to BASIC. This is not a con, per se but if you wanted to modify the original value then yes it’s a con.

Ok that’s it for now, thanks for reading and I hope you’ve found some useful information here. If you enjoyed this post, please don’t forget to support my blog by:

  • Visiting my on-line hacking and engineering merchandise shop on redbubble.com by clicking here
  • Or you can also make a donation with the PayPal link in the column on your right
  • Or share this article

If you like my articles and want to keep getting informed on new ones you can follow me on on of those 21st Century thingies called FacebookTwitterInstagram or Pinterest

And as always if you have any questions please feel free to use the comments section below.

Thank you! 🙂

What Next?

16 thoughts on “6809 ASM: Exchanging data between Assembly code and the BASIC interpreter on the Olivetti Prodest PC 128 and the Thomson MO6 (Part 3)

  1. Pingback: Exchanging data between Assembly code and the BASIC interpreter on the Olivetti Prodest PC 128 and the Thomson MO6 (Part 2) | Paolo Fabio Zaino's Blog

  2. Pingback: Exchanging data between Assembly code and the BASIC interpreter on the Olivetti Prodest PC 128 and the Thomson MO6 (Part 1) | Paolo Fabio Zaino's Blog

  3. Very good article Paolo, I am writing many asm library functions but i have difficult to decide the right sharing variable method. During you post this new article i have discovered a new possible method. Managing the address $277A you can adds new customized basic command, pointing to asm function. Do you know something ?

    Liked by 1 person

    • Hi Dino,
      yes it was possible to extend the BASIC commands set, but that required to know how SIMIV BASIC works internally. When extending BASIC instructions you’ll access data in a similar way to method 2 where you directly access the BASIC memory location where the data and the descriptors are located and also need to handle the memory commutation (if I remember well).

      If I have time I’ll try to add an article with an example of how to extend the BASIC instructions. But as you can see the length of these articles is growing and so also testing time (it has been quite many years since my last program for the 6809 and the PC 128 loool).

      Best regards!

      Like

  4. i successfully added new basic token, with routine execution but i am trying to understand how retrieve parameters.
    I’ll wait for your very useful articles.
    Thank you very much.
    Best regards.

    Liked by 1 person

      • Thank you very much Dino!
        And so happy to hear there is still a community 🙂

        I am doing my best to write my articles in a way that make google translator translate them at the best it can so to help also people who don’t speak English… hopefully this will make it happen to have a new set of cool software from Italy and France and maybe even other places 🙂

        Like

  5. Hello Paolo, I have an issue passing vatriable parameter by exec instruction. I know that it will be the next article, but i can’t wait for it. When i use exec in basic program it stops with “symtax error” instead of direct line command that it works.
    Thank a lot.
    Dino

    Like

    • Hi Dino,
      sorry to hear you can’t wait for it. I am very busy at work at the moment so the article will have to wait.

      The reason for the syntax error is probably because you are trying to read the BASIC line incorrectly, more details on the next article.

      Like

  6. jsr $21c0
    beq exit
    jsr $eff8
    stx $2a5f
    jsr $21c0
    beq exit
    jsr $eff8
    stx $2a66
    exit
    rts

    for example this code doesn’t work only running in basic program. In direct mode basic works fine.

    Like

    • As far as I can see (given that I have just few minutes here), you are calling the sequence wrongly. The EXEC command has a very strict sequence (and iteration form) to read each passed parameter, it would take too much space to mention all the details here and there is no point since there will be a full article about it.

      Like

  7. Pingback: Exchanging data between Assembly code and the BASIC interpreter on the Olivetti Prodest PC 128 and the Thomson MO6 (Part 4) | Paolo Fabio Zaino's Blog

  8. Pingback: Exchanging data between Assembly code and the BASIC interpreter on the Olivetti Prodest PC 128 and the Thomson MO6 (Part 5) | Paolo Fabio Zaino's Blog

Leave a Reply or Ask a Question

This site uses Akismet to reduce spam. Learn how your comment data is processed.