Jump to content
IGNORED

Pascal on the 99/4A


apersson850

Recommended Posts

A very handy Pascal keyword in cases like these is sizeof. With that you can inform the assembly program about the size of the data to work with. A sorting program like this one can sort anything.

const
  small = 10;
  big = 200;

type
  smallarr = array [0..small] of integer;
  bigarr = array[0..big] of integer;

var
  a: smallarr;
  b: bigarr;

procedure asmsort(len: integer; var list); external;

begin
  asmsort(sizeof(smallarr),a);
  asmsort(sizeof(bigarr),b);
end.

Note that in this example small is 10, but the size of smallarr is 11 words, since the index starts from zero. But sizeof will get that right for you and the compiler will make sure the value is adjusted automatically if you change the constant small.

 

Also note that the assembly routine asmsort contains an untyped parameter list. This is only allowed for external procedures. It means you can pass any type to the routine. In this case you can just as well sort a record or a set. Not that the latter will make any sense, but you can do it. If you want to do some sort of pattern matching, then maybe it makes more sense.

Edited by apersson850
  • Like 2
  • Thanks 1
Link to comment
Share on other sites

6 minutes ago, apersson850 said:

Also note that the assembly routine asmsort contains an untyped parameter list. This is only allowed for external procedures.

This opens up many possibilities !!

 

I did of course get what I wanted, in the way I got the array to the assembler. But to do it this way it is much nicer 🙂

I'll experiment a bit with the options that I've learned now. This is a lot easier than the way I handled arrays in assembler.... 

 

  • Like 2
Link to comment
Share on other sites

Variables are always in CPU RAM.

When a prinicpal code segment is loaded into memory, an environment record (E_Rec) is created. This record will contain a pointer to the segment information block (SIB) for the environment's actual code segment. The E_Rec will also contain a pointer to an environment record list, which in turn points to E_Rec data for all subsidiary (called) segments.

Within the E_Rec is a pointer to the segment's global data. That data, as well as the E_Rec itself, are allocated on the heap, with is at the top (low address) of 24 K RAM.

When procedures are called an activation record is created on the stack. Within this record is space for the procedure's local data. The stack is at the bottom (high address) of 24 K RAM.

This all means that parameters that are variables may be on the heap or stack, but it's still 24 K RAM that is the general area.

 

The parameters you see from your assembly routine are allocated on the stack too, on top of the activation record, so they themselves are also always in CPU RAM.

 

Constants are different animals, since they aren't stored in the environment record, but in the constant pool inside the code segment. Since the p-system in the 99/4A has two code pools, the primary one in VDP RAM and the secondary one in 24 K RAM, you can't know for sure where your code runs. It will be loaded in VDP RAM as long as it fits there, but then the 24 K RAM is the backup alternative.

However, a code segment containing linked external (assembly) routines will give the segment information item M_Type the value of 8 instead of 0. If it's p-code then M_Type is 0, but when the code type is M_9900 it's 8. When the code type is M_9900 the whole segment is loaded in 24 K RAM, since assembly code can't run from VDP RAM. So if your program isn't larger than that it's a single compilation unit, with a linked assembly program, then it will always be in CPU RAM and so will the constants.

Only if you for example write a unit, which has one or more linked assembly routines (like my turtlegraphics unit) and that unit is called from a main program that is p-code only, then a constant offered as a parameter to an assembly routine may add complexity into figuring out where the value really is. At least if we presume that this is what happens also in the 99/4A - all technical information available about this is for other computers, so I don't know for sure if they have kept the principle that constants are embedded into the code segment, or if they have let them loose so they can be stored on the heap. Other computers for which the description caters store p-code in CPU RAM only, not in video memory. The only variation is if the code pool resides between the heap and stack or separately in a different memory segment, from the point of view of the CPU.

 

Now how that works in the 99/4A is something to figure out on a rainy day! The trick to assign a constant to a variable before calling the assembly routine is an easy way to avoid having to know.

Thinking a bit further about this, I'd say this is not an issue if the constant is an integer. I've read that the compiler will generate code to resolve constants that are integers or real numbers. Arrays an other stuff can't be constants. So only string constants remain, and we already know what to do with them.

Not sure if long integers are included on this or the other side of the fence? Have rarely used them.

 

Update: Now I've checked. Things like bytes, integers and the value for nil are pushed onto the stack with p-code instructions like Load Constant Byte, Load Constant Integer and Load Constant Nil. The instructions contain the constant itself as an immediate value.

Real constants are instead pushed by Load Constant Real, which fetches the value from the segment's constant pool and converts it to the native real format of the PME on the fly. Other constants are loaded with the p-code Load Constant, which can fetch a constant spanning any nubmer of words from the current segment's constant pool. It can also byte-flip constants if the code was compiled and is executed on processors with different byte sex.

 

 

Edited by apersson850
  • Like 1
  • Thanks 2
Link to comment
Share on other sites

Now if you want to access global things in the Pascal host's data domain, there are other possibilities too. I recommend you check that part in the linker manual. I've extracted it and enclosed it here. It shows how to access global variables and constants directly from the assembly program, without having to pass them as parameters. It also shows how to store data in the host's global data area from the assembly level.

Linker.pdf

  • Like 2
  • Thanks 1
Link to comment
Share on other sites

5 hours ago, Rhodanaj said:

I did of course get what I wanted, in the way I got the array to the assembler.

Your solution is also sensitive to how the compiler allocates space for the variables.

For best performance in Pascal programs, you should declare variables with small memory footprint, like integers, first, then declare larger variables, like arrays, after that.

  • Like 1
  • Thanks 1
Link to comment
Share on other sites

3 hours ago, apersson850 said:

Your solution is also sensitive to how the compiler allocates space for the variables.

For best performance in Pascal programs, you should declare variables with small memory footprint, like integers, first, then declare larger variables, like arrays, after that.

I was glad that I got my array to the assembler, but I was not happy with the way I had to do it 🙂

Of course I will change my program now to the right way to handle arrays ! Thank you !

 

Even when I did not had the right answer at the question of @Vorticon , I got now a better understanding of how to work with arrays !!

I am glad that I used a program as an example.

Edited by Rhodanaj
  • Like 1
  • Thanks 1
Link to comment
Share on other sites

Yet another thing that may be interesting to know, when interfacing assembly to Pascal, is the PME register MP. The mark stack pointer holds the address to the local activation record for the task. You can access all local variables via MP.

Another PME register that may be useful is a global data frame pointer. This can be found via the EREC register, but is quicker to access, as you can skip one level of indirection.

 

On the TI 99/4A, the MP is located in R9 and the globala data frame pointer is R14.

  • Thanks 1
Link to comment
Share on other sites

If you are accessing records defined in the Pascal program from assembly level, you got to pay attention to how data is allocated.

 

The two record definitions here will store the integer a and b in opposite order!

 

type
  forwrec = record
    a: integer;
    b: integer;
  end;

  backrec = record
    a,b: integer;
  end;

In the first case, a and b are allocated in that order, but in the second case, b will be stored before a.

  • Thanks 1
Link to comment
Share on other sites

6 hours ago, apersson850 said:

If you are accessing records defined in the Pascal program from assembly level, you got to pay attention to how data is allocated.

 

The two record definitions here will store the integer a and b in opposite order!

 

type
  forwrec = record
    a: integer;
    b: integer;
  end;

  backrec = record
    a,b: integer;
  end;

In the first case, a and b are allocated in that order, but in the second case, b will be stored before a.

Now that's weird. Why is that?

Link to comment
Share on other sites

If you mean why does it happen then the answer is that this is how the compiler allocates memory based on what the source code looks like.

If you mean why did they design it like that then the answer is probably somewhere within the walls of University of California, San Diego.

 

My guess is that the compiler will stack all variables that are listed to belong to the same type, then allocated them as they are popped off that stack when it reaches the type declaration. Then it starts over again. If it works like that then it explains the behavior perfectly. But that's my guess. At least for older versions, like I or II, the compiler's source code is available. There it would be possible to find out, if you spend enough time figuring it out.

  • Like 2
  • Thanks 1
Link to comment
Share on other sites

It's of course no issue if you use Pascal. The compiler will sort it out for you.

But as soon as you venture into assembly land you have to keep track of such things yourself.

 

Even if you stay on the Pascal mainland, there are a few things you need to know to make the most efficient code.

 

The compiler is simple-minded

The Pascal compiller converts source to code, and that's it. Optimizing is up to you.

 

Variable declarations

Declare the most used variables first, if they are small. There are 16 p-codes with short-form addressing of local and eight for global variables. If your variables are word-sized (two bytes), you can declare 16/8 of them first and they'll all be handled by an instruction one byte long. If you declare an array[0..15] of integer or larger, then you can only use one of these fast and compact p-codes. The array will occupy the rest of the area.

 

Arithmetic

There are p-codes to increment and decrement. The statement p := p+1 is slower than p := succ(p). Math like (i+1)**2 is faster and more compact than i**2+2 (they don't give the same result).

 

Short branching

Keep loops small if possible. There are instructions that will test for equal/not equal and then jump max 127 bytes away in one instruction.

 

Conditionals

A simple if-else statement is more efficicent than a case statement with few choices. But as a general idea, case is fast. This is due to the hidden overhead of the case table. If you make a case statement with just two options, one being 1 and the other 100, then the case table will cover all values in the range 1 to 100. That's 101 words to cover two cases.

 

Indexing from zero

Accessing elements in an array[0..10] of integer is faster than from the array[1..10] of integer. This is because for the second, the compiler will generate code to subtract one from the index before accessing an array element. The first wastes one word of memory, if you can't use index 0, but it may be worth it.

Turning off range checking at compile time will also speed up things at run time, but again, it will no longer protect you from your own stupidity.

 

Intrinsics are fast

If you need to move, search or scan memory (any kind of variable), then use the intrinsics moveleft, moveright, scan and fillchar. They are fast and will handle any kind of data. The only thing they don't do is protecting you from your own mistakes.

  • Like 4
  • Thanks 2
Link to comment
Share on other sites

So I have a need to have a persistent memory location accessible by several assembly language routines when called by the host program. How do I go about doing this?

I think using the .PUBLIC or .PRIVATE directives are the way to go, but I don't quite know how to actually set these up as there is no example code given in the compiler or linker manuals. 

Help?

Link to comment
Share on other sites

5 minutes ago, TheBF said:

Have not use Pascal for 35 years but I think if you declare a variable in the Program section it is a global variable.

 

Yes but what I'm not clear on is how to access it from an assembly procedure without passing it as a parameter.

  • Confused 1
Link to comment
Share on other sites

Then it's either .PUBLIC or .PRIVATE.

 

A memory location declared with .PUBLIC is a global variable declared in the Pascal host. It can be accessed from both Pascal and assembly.

A memory location declared .PRIVATE is also stored in the global data block of the Pascal host, but Pascal doesn't know about it. Only accessible from assembly.

 

If you have more than one .PROC, then you can DEFine a label in the .PROC where it's declared and then .REF it from another assembly routine.

 

As long as you have .PROC and .FUNC, not .RELPROC or .RELFUNC, data items declared in your assembly program with .WORD or .BYTE will remain during execution. They will not move or be overwritten.

Edited by apersson850
  • Like 3
Link to comment
Share on other sites

3 hours ago, apersson850 said:

Then it's either .PUBLIC or .PRIVATE.

 

A memory location declared with .PUBLIC is a global variable declared in the Pascal host. It can be accessed from both Pascal and assembly.

A memory location declared .PRIVATE is also stored in the global data block of the Pascal host, but Pascal doesn't know about it. Only accessible from assembly.

 

If you have more than one .PROC, then you can DEFine a label in the .PROC where it's declared and then .REF it from another assembly routine.

 

As long as you have .PROC and .FUNC, not .RELPROC or .RELFUNC, data items declared in your assembly program with .WORD or .BYTE will remain during execution. They will not move or be overwritten.

Thanks. I actually found a good example of the usage of .PUBLIC and .PRIVATE on page 24 of the Linker manual. I do still have a question:

  • With the DEF/REF scheme, my understanding is that using .WORD or .BYTE defaults that location to 0. So does .BLOCK. So what will happen is that the reserved memory location will be zeroed out and anything stored in it from a previous call to the assembly routines will be wiped out which each new call to the assembly routine. Do I have this right?
Link to comment
Share on other sites

No. The .BYTE, .WORD and .BLOCK directives indicates which values to store at these locations at load time. Which implies that at the first time you run any of your assembly routines, that's the value they will have. But they don't change at run time, so nothing will happen to these values unless something changes them on purpose. Hopefully that's your program doing that.

 

Important to note is that this applies to .PROC and .FUNC programs only! They are loaded when execution starts and remain resident in memory at the same location until your host program ends and the program's environment is disposed by the operating system. The OS procedure Build_Env will store your assembly programs on the heap when loaded.

However, if you use the directives .RELPROC or .RELFUNC to define your code, it will not be stored in a fixed place on the heap, but instead end up in the code pool, where it will be subject to the code pool's segment manager's decisions about moving it in memory or simply overwriting it with some other code, if you run out of memory. That means that you can not trust any data to remain inside a .RELPROC, but you can inside a .PROC. If you use .RELPROC you must store data you need to remain between calls using .PUBLIC or .PRIVATE.

  • Like 2
  • Thanks 1
Link to comment
Share on other sites

27 minutes ago, apersson850 said:

No. The .BYTE, .WORD and .BLOCK directives indicates which values to store at these locations at load time. Which implies that at the first time you run any of your assembly routines, that's the value they will have. But they don't change at run time, so nothing will happen to these values unless something changes them on purpose. Hopefully that's your program doing that.

Once again great information not in the manuals. That's what I needed to know! Thanks!

  • Like 2
Link to comment
Share on other sites

At least some of the differences between what happens with statically relocatable (.PROC) and dynamically relocatabe (.RELPROC) code are mentioned in the manuals, to be fair.

But it's true that a lot of the things mentioned here and there doesn't make much sense, nor seem important, before you have gained quite a good understanding of the system's operational characteristics from all points of view.

  • Like 3
Link to comment
Share on other sites

I'm developing a set of simple assembly routines to access the RS232 card at the low level, but the computer is locking up during testing. I can't spot anything wrong but perhaps I'm missing something here.

Here are the assembly routines:

; routines for low-level rs232 access

        .proc   initrs232,1
; initialize the specified rs232 card
; 1 = cru 1300h, 2 = cru 1500h

        .def    cruadr
        mov     *r10+,r1        ; get desired rs232 card number
        ci      r1,1
        jne     rs2322
        li      r12,1300h
        jmp     init
rs2322  li      r12,1500h
init    mov     r12,@cruadr     ; save base cru address
        sbo     0               ; activate card
        sbo     7               ; turn card led on
        b       *r11
        
cruadr  .word

        .proc   rs232off
; shut down the rs232 card

        .ref    cruadr
        mov     @cruadr,r12     ; restore base cru address
        sbz     7               ; turn off card led
        sbz     0               ; deactivate rs232 card
        b       *r11
        
        .proc   bitset,2
; set/reset specified cru bit

        .ref    cruadr
        mov     @cruadr,r12     ; restore base cru address
        mov     *r10+,r1        ; get bit state request (0 or 1)
        ci      r1,1            ; verify if state is valid
        jgt     invalid
        ci      r1,0
        jlt     invalid
        mov     *r10+,r2        ; get cru bit number
        sla     r2,1            ; multiply by 2
        a       r2,r12          ; offset cru base address
        ci      r1,0
        jne     set2one
        sbz     0
        jmp     invalid
set2one sbo     0
invalid b       *r11

        .end

 

And here is the host test code:

 

program rs232test;
var
 i : integer;
 
procedure initrs232(cardnum : integer); external;
procedure rs232off; external;
procedure bitset(bitnum, state : integer); external;

procedure delay;
var
 i : integer;

begin
 for i := 1 to 50 do
end; (* delay *)

begin
 page(output);
 initrs232(1);          (* activate card #1 *)
 for i := 1 to 10 do
  begin
   bitset(7, 0);        (* turn card led off *)
   delay;
   bitset(7, 1);        (* turn card led on *)
   delay
  end;
 rs232off;              (* turn card off *)
end. (* rs232test *)

 

It's pretty straightforward stuff, so I'm kind of stumped...

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...