Jump to content
IGNORED

Pascal on the 99/4A


apersson850

Recommended Posts

5 hours ago, OLD CS1 said:

I just opened it with 7-Zip 23.01.

Thank you JasonACT for notice this.

I did use WinRar6 to make the rar-file and indeed with WinRar3 I can not open it either.

I have now replaced the rar with a rar made with WinRar 3 🙂

Or you can use this exe-file.

 

p-code-tool 4.1.exe

Edited by Rhodanaj
added exe-file
  • Thanks 2
Link to comment
Share on other sites

  • 1 month later...

From Hacker news:

Besides his contribution to language design, he authored one of the best puns ever. His last name is properly pronounced something like "Virt" but in the US everyone calls him by "Worth".

That led him to quip, "In Europe I'm called by name, but in the US I'm called by value."

 

😆

 

  • Like 8
Link to comment
Share on other sites

The proper German pronunciation is /viɐtʰ/; note that it does not rhyme with "dirt". English makes funny things with vowels that have a following r; this is not the case in most other languages, including German. (In turn, the "ending r" in German (following a vowel) resembles a weak a.)

 

A good approximation would be to take "vi" from "Victor" and "ut" from "hut" or "utter", and glue them together as vi-ut, spoken without pause.

 

  • Like 2
Link to comment
Share on other sites

  • 2 weeks later...

This is @apersson850's keyboard scan routine and I have many questions...

;---------------------------------------
;
;             SCANNERS
; Pascal utility that allows reading of the keyboard "on the fly".
; Keyscan is the real-time version, which reads  the keyboard in the actual 
; moment when the function is executed.
; Bufscan inspects the keyboard buffer to determine if there is any key stored there since
; the last use of keyscan, bufscan or the p-system's ordinary read function (readln).
; Note that keyscan flushes the buffer contents when it's executed, since keys
; read by keyscan usually are also buffered by the system.

EMPTY	.EQU	2D9EH	;Empty bit mask
FULL	.EQU	2DA0H	;Full bit mask
HEXFF	.EQU	40B8H	;0FFH in the PME ROM

BUFPOINT	.EQU	288AH	;Pointer to keyboard buffer
BUFFLAG	.EQU	28A2H	;Buffer mode flag
BUFGIVE	.EQU	28A4H	;Buffer store pointer
BUFTAKE	.EQU	28A6H	;Buffer fetch pointer
BUFEND	.EQU	31	;Last index in key buffer

VDPWA	.EQU	8C02H
VDPRD	.EQU	8800H

SP	.EQU	10
LINK	.EQU	11

VDPR1	.EQU	2811H	;Pascal's copy of VDP R1
VDPCOPY	.EQU	83D4H	;Scan's expected VDP R1 copy location
SAVE1	.EQU	83C6H	;Address to area that has to be saved

SCAN	.EQU	000EH	;Address of monitor scan routine

; Locations in RAM PAD
	.ASECT
	.ORG	8374H
KEYBOARD	.BYTE
KEYVALUE	.BYTE
	.BLOCK 6
GPLST	.BYTE
	.PSECT

PASCALWS	.EQU	8380H
GPLWS	.EQU	83E0H

;------------------------------------
;
; function keyscan(keyboard: integer): integer;
;
; Returns the keycode of the currently pressed key.
; If no key was pressed, returns -1.
; Also clears the keyboard buffer.
;

	.RELFUNC KEYSCAN,1

	MOV	*SP+,R0	;Load selected keyboard value
	MOVB	@PASCALWS+1,@KEYBOARD

	LWPI	SCANWS	;Save some of the RAM PAD
	LI	R0,SAVE1
	MOV	*R0+,R1
	MOV	*R0+,R2
	MOV	*R0,R3
	LI	R0,VDPCOPY
	MOV	*R0+,R4
	MOV	*R0+,R5
	MOV	*R0,R6
	MOVB	@VDPR1,@VDPCOPY	;Pascal's copy of VDP R1
	LWPI	GPLWS
	BL	@SCAN		;Do the scanning
	LWPI	SCANWS

	LI	R0,SAVE1
	MOV	R1,*R0+
	MOV	R2,*R0+
	MOV	R3,*R0
	LI	R0,VDPCOPY
	MOV	R4,*R0+
	MOV	R5,*R0+
	MOV	R6,*R0

	LWPI	PASCALWS
	CLR	R0
	MOVB	@KEYVALUE,R0	; Fetch detected key value
	CB	R0,@HEXFF	; Check if no key was pressed
	JNE	KEY_DOWN
	SETO	R0	; If not, return -1
KEY_DOWN	SWPB	R0	;Full word
	MOV	R0,*SP
	SZC	@FULL,@BUFFLAG	; Set buffer flag to empty
	SOC	@EMPTY,@BUFFLAG
	MOV	@BUFTAKE,@BUFGIVE
	B	*LINK

SCANWS	.BLOCK 32

;------------------------------------
;
; function bufscan: integer;
;
; Returns the code of the next character in the keyboard buffer.
; Returns -1 if the buffer is empty.
; Also removes the character read, if any, from the buffer.
;

	.RELFUNC BUFSCAN

	MOV	@BUFFLAG,R0	; Anything in the buffer?
	COC	@EMPTY,R0
	JEQ	IS_EMPTY

	MOV	@BUFTAKE,R1	;Calculate address in buffer
	MOV	R1,R2
	A	@BUFPOINT,R2
	SWPB	R2	; Write to VDP
	MOVB	R2,@VDPWA
	SWPB	R2
	MOVB	R2,@VDPWA

	INC	R1	; pointer := (pointer+1) mod 32
	ANDI	R1,BUFEND
	MOV	R1,@BUFTAKE

	C	R1,@BUFGIVE	; Is buffer empty now?
	JNE	SOME_LEFT
	SOC	@EMPTY,R0	; Set flag to empty
SOMELEFT	SZC	@FULL,R0	; Always set flag to not full
	MOV	R0, @BUFFLAG
	CLR	*SP
	MOVB	@VDPRD,@1(SP)	; Move data from buffer to return value
	B	*LINK

IS_EMPTY	SETO	*SP
	B	*LINK

	.END

For now I'd like to focus on the KEYSCAN function which will help better understand the BUFSCAN one later on.

  • SCANWS is not defined 
  • VDPR1 - what do you mean by Pascal's copy of R1? That's pointing to an odd address instead of the expected >8382
  • VDPCOPY - if the GPLWS is at >83E0, shouldn't R1 be at >83E2?
  • I get from the code that the Pascal and standard OS CPU RAMPAD usage are different which is why we need to save these areas. Is R1 the only needed register by the ROM scan routine?
  • I need to clarify the stack operations with functions. The manual states that an external function pushes 1 or 4 words on the stack (depending on whether integer or real) before any parameters have been pushed. Furthermore, the routine must remove these words before returning the function's result. When *SP+,R0 is executed, the top stack value is popped off and the stack pointer is incremented. In the example given on page 24 of the linker manual, the stack pointer is DECT before storing the return result which is what I did in my code, but you don't do that in yours. 
  • Do you have a reference for the keyboard buffer details? It will make understanding that part easier.

Sorry for all the questions, but none of that is really covered in the manuals. Curious as to how you got all these OS details from!

Link to comment
Share on other sites

First, the code I uploaded was written in 1984, so I may not remember all the details.

  • Yes, SCANWS is defined right after the keyscan function.
  • When the computer goes to screen blanking due to inactivity, it uses the blanking bit in VDP control register R1. But it can't read what was in that register before, so a copy of that must be stored in memory, or it can't be properly reset when you press a key and the screen should wake up again. Pascal keeps this copy in a different place, so you have to juggle it a bit when accessing the keyboard directly.
  • VDPCOPY is in the interrupt workspace. I've probably found that it should be there, but I don't remember that exactly now.
  • Six words are saved from two different areas in RAM PAD. One of these words are then replaced by Pascal's copy of VDP control register R1. After the SCAN call these six words are restored.
    When you call an assembly routine all parameters are all returned results from functions are always one word long, except in the case of a real (a floating point number), which is four words long. If you call a procedure with two parameters, they are stored on the stack.
    SP -> Parameter 2
    Parameter 1

    However, if you call a function, which is supposed to return a one word result (like an integer), then the stack will be like this.
    SP -> Parameter 2
    Parameter 1
    Function result

    Since keyscan is defined as function keyscan(keyboard: integer): integer; external; this implies the stack look like this when it starts running:
    SP -> keyboard
    Function result

    So keyscan pops the keyboard parameter by using MOV *SP+,R0, where SP is .EQU 10, since R10 is the p-system's stack pointer. Now the stack pointer already points at the word that's reserved for returning the function's result, so there's no reason to mess with SP any more. Hence I don't remove the word reserved for the result on the stack, I simply replace the value in that word with the result.
  • The keyboard buffer is located in the VDP RAM.
    BUFPOINT points to where it's located.
    BUFFLAG is the status word for the buffer. FF40=empty, FF00=contains something, FF80=buffer is full.
    BUFGIVE and BUFTAKE are the store and fetch pointers into the keyboard buffer, in the range 0..31. So you have to add the value in BUFPOINT to hit the right place in VDP RAM.
    BEFEND defines the end of the keyboard buffer. It contains max 32 characters.
    BUFFLAG, BUFGIVE and BUFTAKE are all inside a subsidiary workspace for the p-system (R6-R8) in that workspace.
  • Back in the days, there weren't any good options but to disassemble the memory contents when the p-system was running and start looking in that code at what was going on. So that's where this knowledge comes from. Unfortunately I don't have the source code for the p-system...

It's worth noting that you can use R0-R7 as you like in PASCALWS at 8380H, but registers R8-R15 are in use by the system.

  • Like 1
Link to comment
Share on other sites

When you write a text string on the screen at assembly level, you load data into a certain place in VDP RAM. You must know where the screen table is to make this correctly. To do that, you define for yourself the entire mapping of the VDP RAM. Once you have decided where you want to place the character definitions, color data, sprites and so on, you write information about the location of these tables into control registers in the VDP. The utility VWTR (Video Write To Register) can be used for that.

These tables are read only. You have to remember what you put in them, since you can't find out by reading them back. This is normally not a problem in your own program, since if you can't remember that, you'll probably not get any working program together anyway. But for screen blanking it's a different game.

Screen blanking is done to avoid burn-in on screens. After some inactivity, the screen goes blank. This is controlled by the VDP interrupt service. It counts the number of interrupts, and when they reach a certain number, it will blank the screen. This is done by manipulating the screen blanking bit in VDP control register R1. But that register also controls the video mode, i.e. text, graphics or multicolor (and some other stuff). This means that you can't manipulate the blanking bit alone - you must load the other configuration bits as well at the same time. It doesn't matter so much as long as blanking is in effect - you can't see anything then anyway. But when you remove the blanking condition, you want to come back to the same graphics mode as you had before, or your screen will be garbled.

This is where the complexity starts. You, with your program, is responsible for setting up the VDP registers as you like them. But the interrupt service routine is responsible for blanking your screen and it knows nothing about how you set up the VDP in your program. Then comes the keyscanning, which is the one who knows that a key has been pressed and that the blanking should be removed due to that. The keyscanner also knows nothing about how you set up the VDP.

For this to work there has to be a common understanding between your program, the video interrupt service (blanking) and the keyscanner (unblanking). This common understanding is accomplished by agreeing about where to put a copy of VDP register R1, so that everybody involved can read that copy, then set/clear the blanking bit in it and send the whole thing to the VDP.

The necessity to mainpulate this value when directly calling the keyscanner from an assembly program under the p-system is because the p-system has a different convention for the placement of this register copy. That in turn is due to the p-systems different approach to keyscanning, where it will read the full keyboard all the time, even when your program is running and seemingly not looking for any keyboard input. This is in contrast to Extended BASIC, where keyscanning is done only to look for FCTN-Quit, not the full keyboard. There's no point in scanning everything when your program is running, since BASIC will not use that value anyway. If you do an INPUT or CALL KEY, it's the keys pressed after/at the moment that command is executed that are interesting, not the keys pressed up until the command is executed.

Due to this mechanism, the invocation and cancelling of screen blanking is a bit different in the p-system, and that's why you have to move around the VDP control register R1 copy and save a few other things, prior to do a BL @SCAN. Like I showed in my code.

On the other hand, since the keyboard buffer is the standard thing in the p-system, reading that is a bit easier. No data needs to be saved.

 

I did write that in PASCALWS, R8-R15 are used by the system. You've already used the stack pointer, but perhaps this list of register usage could be interesting.

R8  PME IPC. Address of next p-code instruction

R9  Frame pointer for current activation record on the stack. When a procedure is called, its local data, return link and some other stuff is allocated as a block on the stack. This register points to the start of that block.

R10 SP. The stack pointer used by the PME. The PME has only one stack (unlike Forth).

R11 Subroutine return linkage.

R12 Address of PME fetch routine. Each time the PME interprets a new p-code, it goes to the inner interpreter with a B *R12 (uses one word, unlike B @address, which uses two). The address is always 8300H, so this is just for compactness and speed.

R13 Read data address for the currently executing code. The PME can interpret p-code in RAM, VDP RAM or GROM. So either the PGRMRD or VDPRD is here, or 0 when reading from CPU RAM.

R14 Global data frame pointer for current segment. This is similar to R9, but this one doesn't point to the current procedure's local data, but to the global data defined for the currently running code segment.

R15 P-code memory type flag. 0=CPU RAM, <0=VDP RAM >0=GROM.

 

The p-system use other workspaces too, in addition to PASCALWS at 8380H. 83A0H, 83C0H (standard interrupt workspace), 83E0H (GPL WS), 2896 (BIOSWS) and 28B6H (pseudo-interrupt WS).

When the p-system does access other cards in the box, it has to turn off the p-code card and run code that has been installed in low memory expansion during the boot sequence. When that's done, it uses a workspace there too. The p-system also uses pseudo-interrupts. Instead of turning on real interrupts with LIMI 2, it will test the interrupt input with a TB instruction, and run an interrupt sequence if that bit is on. This was done since the real interrupt's vector is in ROM, but the p-system want's to run a different code at interrupt. The keyboard buffer is one reason, the more advanced and completely different automatic sprite motion is another. Pseudo-interrupts have their workspace in low memory expansion as well.

There are actually two different inner interpreters, but they both start at 8300H. One is used for p-code in CPU RAM, the other for p-code in VDP RAM or GROM. When a transfer is done to code in a different memory type, the interpreter is exchanged with a different version. Both are stored in the p-code card's memory. The reason for using two is that when running from CPU RAM, p-codes are read by a MOVB *R8+, so the IPC is incremented automatically. When running from VDP RAM or GROM, p-code is ready by MOVB *R13. That autoincrements the address to read from in that memory, but a separate INC R8 is needed to keep the IPC in sync.

 

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

This is really great info! Thank you!

For the record though, I am very familiar with the standard VDP organization and various graphic-related tables as well as the VDP registers. Your terminology of "VDP control register R1" threw me off and I thought this was related to some new mysterious work space in the VDP. It was late and I was tired so did not make the connection 😝

All clear now. I can't imagine how much sleuthing it must have taken you to figure out all these details back in the day and it's a shame that all this golden information is not compiled in one document. You should consider writing a UCSP Pascal programmer's reference handbook! 

  • Like 3
Link to comment
Share on other sites

So I adapted your keyscan code for my purposes and it works. Here's the code:

 

;GETKEY FUNCTION
;SCAN THE KEYBOARD AND CHECK FOR ANY KEY PRESSED
;CLEARS THE KEYBOARD BUFFER AFTER EVERY SCAN
;RETURNS EITHER KEY VALUE OR >FF IF NO KEY IS PRESSED

;MEMORY LOCATIONS DEFINITIONS
          .ASECT
          .ORG     8374H
KEYBOARD  .BYTE                 ;KEYBOARD SELECTION BYTE FOR SCAN ROUTINE
KEYVALUE  .BYTE                 ;RETURNED KEY VALUE BYTE
          .PSECT
          
PASCALWS  .EQU    8380H
GPLWS     .EQU    83E0H
SP        .EQU    10            ;R10 IS THE FUNCTION STACK POINTER
SAVEPAD   .EQU    83C6H         ;ADDRESS OF PAD AREA TO SAVE
STDVDPR1  .EQU    83D4H         ;STANDARD LOCATION OF VR1
PASVDPR1  .EQU    2811H         ;PASCAL LOCATION OF VR1
SCAN      .EQU    000EH         ;ADDRESS OF ROM SCAN ROUTINE

;KEYBOARD BUFFER VALUES
BUFFLAG   .EQU    28A2H         ;BUFFER FLAG.FF40=EMPTY,FF00=NOT EMPTY,FF80=FULL
BUFGIVE   .EQU    28A4H         ;BUFFER STORE POINTER
BUFTAKE   .EQU    28A6H         ;BUFFER FETCH POINTER

          .RELFUNC GETKEY
SCANWS    .BLOCK  32

          CLR     @KEYBOARD     ;SELECT THE ENTIRE KEYBOARD FOR SCANNING
          
          LWPI    SCANWS        ;SAVE SOME OF THE RAM PAD LOCATIONS
          LI      R0,SAVEPAD
          MOV     *R0+,R1
          MOV     *R0+,R2
          MOV     *R0,R3
          LI      R0,STDVDPR1
          MOV     *R0+,R4
          MOV     *R0+,R5
          MOV     *R0,R6
          
          MOVB    @PASVDPR1,@STDVDPR1 ;RELOCATE THE PASCAL VR1 TO STD LOCATION
          LWPI    GPLWS         ;EXECUTE THE ROM SCAN ROUTINE
          BL      @SCAN
          
          LWPI    SCANWS        ;RESTORE RAM PAD VALUES
          LI      R0,SAVEPAD
          MOV     R1,*R0+
          MOV     R2,*R0+
          MOV     R3,*R0
          LI      R0,STDVDPR1
          MOV     R4,*R0+
          MOV     R5,*R0+
          MOV     R6,*R0
          
          LWPI    PASCALWS      ;GET KEY VALUE
          CLR     R0
          MOVB    @KEYVALUE,R0
          SWPB    R0
          MOV     R0,*SP        ;PLACE KEY VALUE ON FUNCTION RETURN STACK
          
          LI      R1,0FF40H    ;SET KEYBOARD BUFFER FLAG TO EMPTY
          MOV     R1,@BUFFLAG
          MOV     @BUFTAKE,@BUFGIVE ;RESET KEYBOARD BUFFER POINTER TO START
          B       *R11
          .END

One question: in your original code you do some masking gymnastics with the keyboard buffer flag in order to set it to empty:

 

SZC	@FULL,@BUFFLAG	; Set buffer flag to empty
SOC	@EMPTY,@BUFFLAG

 

Why are you doing it that way instead of what I did as below?

 

LI      R1,0FF40H    ;SET KEYBOARD BUFFER FLAG TO EMPTY
MOV     R1,@BUFFLAG

 

Also, when the host pascal program terminates (listing below) after I press the space key and DONE! is printed on the screen, the system simply hangs. I see no reason as to why this is happening...

 

PROGRAM KEYTEST;

VAR
  KEYVAL,I : INTEGER;
  
FUNCTION GETKEY : INTEGER; EXTERNAL;

BEGIN
  PAGE(OUTPUT);
  KEYVAL := GETKEY;
  WHILE KEYVAL <> 32 DO
    BEGIN
      IF KEYVAL <> 255 THEN
        WRITELN(KEYVAL);
      FOR I:=1 TO 50 DO;
      KEYVAL := GETKEY;
    END;
  WRITELN('DONE!');
END.

 

  • Like 2
Link to comment
Share on other sites

My guess (don't remember) is that the buffer flag bits I managed one by one, instead of just overwriting the whole thing, was due to not knowing if the other bits were used for something I had not figured out yet.

But now I see you are moving data at @FF40 into BUFFLAG. Your BUFEMPTY is a constant, not a data word. That could be the cause for the lockup as well, if the keyboard buffer handling gets some completely random data to work with.

Why it hangs I don't know either. I'm pretty sure I've tested it and it worked. The only thing I've never done is putting the data for SCANWS before the code. I've always got the idea, without really figuring it out, that it starts executing right after the .FUNC or .RELFUNC directive. If I'm right, there could be some data that's interpreted as some opcode which causes some side effect. Try moving SCANWS to where I had it (after the function) and test again.

Edited by apersson850
Link to comment
Share on other sites

Hmmm... I'll have to dig further. Clearly the problem is at the return of the Pascal host, so something is being corrupted somewhere by the scan routine. I tried using your bit masking method for setting the keyboard buffer flag but no change. 

Link to comment
Share on other sites

4 hours ago, apersson850 said:

Try moving the SCANWS block to the bottom instead.

That worked! Now why is that?

One last question: I don't quite understand why you do this MOV @BUFTAKE,@BUFGIVE. Seems like you are equating BUFGIVE to BUFTAKE. Shouldn't BUFGIVE be 2 bytes higher in memory than BUFTAKE since this is how it is equated initially? I'm assuming that BUFTAKE is incremented with each keystroke.

Here's the final version. I'm going to add it to the *SYSTEM.LIBRARY.

 

;GETKEY FUNCTION (BASED ON ORIGINAL SOURCE FROM ANDERS PERSSON)
;SCAN THE KEYBOARD AND CHECK FOR ANY KEY PRESSED
;CLEARS THE KEYBOARD BUFFER AFTER EVERY SCAN
;RETURNS EITHER KEY VALUE OR >FF IF NO KEY IS PRESSED

;MEMORY LOCATIONS DEFINITIONS
          .ASECT
          .ORG     8374H
KEYBOARD  .BYTE                 ;KEYBOARD SELECTION BYTE FOR SCAN ROUTINE
KEYVALUE  .BYTE                 ;RETURNED KEY VALUE BYTE
          .PSECT
          
PASCALWS  .EQU    8380H
GPLWS     .EQU    83E0H
SP        .EQU    10            ;R10 IS THE FUNCTION STACK POINTER
SAVEPAD   .EQU    83C6H         ;ADDRESS OF PAD AREA TO SAVE
STDVDPR1  .EQU    83D4H         ;STANDARD LOCATION OF VR1
PASVDPR1  .EQU    2811H         ;PASCAL LOCATION OF VR1
SCAN      .EQU    000EH         ;ADDRESS OF ROM SCAN ROUTINE

;KEYBOARD BUFFER VALUES
BUFEMPTY  .EQU    2D9EH         ;BUFFER EMPTY BIT MASK
BUFFULL   .EQU    2DA0H         ;BUFFER FULL BIT MASK
BUFFLAG   .EQU    28A2H         ;BUFFER FLAG.FF40=EMPTY,FF00=NOT EMPTY,FF80=FULL
BUFSTORE  .EQU    28A4H         ;BUFFER STORE POINTER
BUFGET    .EQU    28A6H         ;BUFFER FETCH POINTER

          .RELFUNC GETKEY

          CLR     @KEYBOARD     ;SELECT THE ENTIRE KEYBOARD FOR SCANNING
          
          LWPI    SCANWS        ;SAVE SOME OF THE RAM PAD LOCATIONS
          LI      R0,SAVEPAD
          MOV     *R0+,R1
          MOV     *R0+,R2
          MOV     *R0,R3
          LI      R0,STDVDPR1
          MOV     *R0+,R4
          MOV     *R0+,R5
          MOV     *R0,R6
          
          MOVB    @PASVDPR1,@STDVDPR1 ;RELOCATE THE PASCAL VR1 TO STD LOCATION
          LWPI    GPLWS         ;EXECUTE THE ROM SCAN ROUTINE
          BL      @SCAN
          
          LWPI    SCANWS        ;RESTORE RAM PAD VALUES
          LI      R0,SAVEPAD
          MOV     R1,*R0+
          MOV     R2,*R0+
          MOV     R3,*R0
          LI      R0,STDVDPR1
          MOV     R4,*R0+
          MOV     R5,*R0+
          MOV     R6,*R0
          
          LWPI    PASCALWS      ;GET KEY VALUE
          CLR     R0
          MOVB    @KEYVALUE,R0
          SWPB    R0
          MOV     R0,*SP        ;PLACE KEY VALUE ON FUNCTION RETURN STACK
          
          SZC     @BUFFULL,@BUFFLAG ;EMPTY KEYBOARD BUFFER
          SOC     @BUFEMPTY,@BUFFLAG
          MOV     @BUFGET,@BUFSTORE
          
          B       *R11
          
SCANWS    .BLOCK  32
          .END

 

Link to comment
Share on other sites

9 hours ago, Vorticon said:

That worked! Now why is that?

Because the assembly procedure/function starts running right after the .PROC, .RELPROC, .FUNC or .RELFUNC directive. In your case you were executing the 16 words of your workspace as instructions. Had there been a jump code in there you would have been lost forever on your first call. Now it did something the p-system didn't like, but did let your program live as long as it was running. Since figuring out the keycode from your pressing of keys was done after that, it still happened correctly.

This is the reason for why you assumed SCANWS wasn't defined in my first program.

9 hours ago, Vorticon said:

One last question: I don't quite understand why you do this MOV @BUFTAKE,@BUFGIVE. Seems like you are equating BUFGIVE to BUFTAKE. Shouldn't BUFGIVE be 2 bytes higher in memory than BUFTAKE since this is how it is equated initially?

You're confusing addresses with data content. BUFSTORE and BUFGET are equated to two different addresses. But when the buffer is empty these two addresses have the same content.

When the system detects a keypress it will increment the value at BUFSTORE. When a key is read by something, like read(ch), the value at BUFGET is incremented. But BUFSTORE and BUFGET themselves will not change. The have the values of the addresses where the data is stored.

The assembler's syntax with MOV @BUFSTORE is not chosen on random. Read as MOVe value at BUFSTORE it makes full sense. BUFSTORE will always be 28A4H, but @BUFSTORE will go from 0 to 31 and then back to 0 again, as key presses are recorded in the circular keyboard buffer. Compare MOV @BUFSTORE,R1 with LI R1,BUFSTORE. In the first case R1 will get the value between 0 and 31 currently at BUFSTORE. In the second R1 will become 28A4H.

 

As a final comment, I have a unit I call extrascreen, which is already fixed and ready. It contains the keyscan, bufscan and then input routines similar to Extended BASIC's ACCEPT VALIDATE and some other handy stuff. Considering how dangerous standard Pascal's input routines are, it's a must for a robust program. Why not include that in your SYSTEM.LIBRARY instead? After all, we can benefit from each other's work, not necessarily make something similar that's still not compatible.

 

Yet another recommendation: You end your keyscan by returning 255 if there's no key pressed. Which means every time you use it, on Pascal level you check if it's equal to 255 to find out if no key was down.

If you instead return -1 as my keyscan does, you can on Pascal level check if the value is <0. Since comparing with zero takes less space in p-code, you increase speed and save memory on each comparison if you return -1 for no read.

Edited by apersson850
Link to comment
Share on other sites

Dang it! That @ symbol always gets me. Thanks for clarifying. So in order to access for example the last character stored, we add the contents of BUFSTORE - 1 to BUFPOINT, correct?

 

Regarding the SCANWS, the assembler would issue an INCORRECT STRUCTURE if I tried to reserve the SCANWS workspace before the .RELFUNC directive which is what I normally do in standard assembly on the TI where all space is allocated prior to the first executable instruction. That is why I ended up putting it after the .RELFUNC directive. Any reason why the assembler complains in that situation?

 

As for using your routines, I totally agree that there is no need to reinvent the wheel and I am happy to use them as is. However, I am still learning the vagaries of the Pascal environment which tries its darn best to keep you in your lane and not stray into unsanctioned territory, so I need to experiment, fail, fail and fail again until I succeed and learn from the experience. What I absolutely abhor is using code I don't understand the inner workings of, which is why I pester you with so many questions, and you have my sincere thanks for being so patient with me. Case in point is that supposedly simple key scan process which ended up being a much more involved process than anticipated! By the way can you include the source for the extrascreen unit so I can study it?

 

Finally, now I get why you seemed to insist on using -1 as the return value for when no key is pressed. but you do have to spend 3 extra instructions (CB, JNE, SETO) in order to do so, and I wonder if that does not cancel out the benefit of speed boost from doing a <0 comparison in the host pascal program. 

 

By the way, while I know how to create a new library, how do I add units to an existing one without overwriting it? I could not figure out how to add my GETKEY routine to the *SYSTEM.LIBRARY and the manual only shows how to create a new library.

Link to comment
Share on other sites

2 hours ago, Vorticon said:

Dang it! That @ symbol always gets me. Thanks for clarifying. So in order to access for example the last character stored, we add the contents of BUFSTORE - 1 to BUFPOINT, correct?

Yes, since you write the contents (plural), you do mean @BUFSTORE +@BUFPOINT, I presume? However, normally you want to retreive the last character not yet fetched, which is indexed by BUFGET in your program.

2 hours ago, Vorticon said:

Regarding the SCANWS, the assembler would issue an INCORRECT STRUCTURE if I tried to reserve the SCANWS workspace before the .RELFUNC directive which is what I normally do in standard assembly on the TI where all space is allocated prior to the first executable instruction. That is why I ended up putting it after the .RELFUNC directive. Any reason why the assembler complains in that situation?

This is due to the code manager in the p-system. The code manager loads a segment of code into memory when that code is requested for execution. The code may be a single segment for the whole program or, in the case of large programs, one out of several segments making up the whole program. Segment management is used to make larger programs than memory possible to run. The requirement is that when doing an inter-segment call, the caller's and the called routine's segments must both be in memory at the same time.
When the assembler handles a program, it starts generating code with any of the function/procedure directives. Thus nothing that should be loaded into memory can exist before the first procedure/function directive. You can have .EQU stuff, since they are just definitions of constants, and you can have an .ASECT directive before .PROC, because the .ASECT is a dummy section that's used just to simplify mapping memory that's not inside your own code body. In the example above you could just as well replace the .ASECT part with KEYBOARD .EQU 8374H and KEYVALUE .EQU 8375H - I used the .ASECT just to show how handy it is.

But you can't have a .PSECT (Program SECtion) that actually generates data to be loaded into memory before your .FUNC directive. Now a .BLOCK is just a reserved area, but in this case it's equivalent to a .WORD directive, which does indeed load a value into memory.

 

The assembler in the p-system does allow .REF and .DEF, but that's used to reach into other procedures or functions within the same program. So if for example SCANWS should be used also by BUFSCAN in my example (it's not, but if), then there would need to be a .DEF SCANWS in the first function and a .REF SCANWS in the second. When the two functions are linked with a Pascal program, they could just as well have been in two different files, in which case it's easier to understand the need to make a DEFinition in one file and a REFerence to it in the other.

You can not use .DEF to define a different starting point than the top of your procedure, like when calling a program from BASIC or E/A Load and Run, and that's why you shouldn't have your workspace at the top.

 

Another thing to keep in mind is that all normal assembly code is relocatable. Thus it may be loaded anywhere in memory. You don't know that in advance. But it's static once loaded, so if you have a .WORD or defined you own workspace inside your procedure, you'll know that whatever you store during one invocation of the function will still be there on the next call. Such procedures are defined by the .FUNC or .PROC directives.

To make full use of the p-system's code pool handling, you may want to make the procedures not only relocatable during loading but also after loading. If you use the directives .RELPROC or .RELFUNC the p-system's code manager will also save enough information to be able to move your procedure out of harm's way if the stack is growing a lot, for example. This in turn will also imply that your procedure may not run in the same memory location between two calls. Thus data saved inside the procedure may be lost between calls.

If you need to keep data for a dynamically relocatable assembly procedure, you can reserve space in the Pascal host's global data area by using a .PRIVATE directive. You can also access global data declared in the Pascal program by the .PUBLIC and .CONST directives.

2 hours ago, Vorticon said:

By the way can you include the source for the extrascreen unit so I can study it?

That can be arranged.

2 hours ago, Vorticon said:

Finally, now I get why you seemed to insist on using -1 as the return value for when no key is pressed. But you do have to spend 3 extra instructions (CB, JNE, SETO) in order to do so, and I wonder if that does not cancel out the benefit of speed boost from doing a <0 comparison in the host pascal program.

Short literals in p-code (I think it's up to 31 - not sure without checking) are embedded into the opcode, so it's just pushed on the stack. Longer literals (255 will fall within long unsigned byte) require additional instructions in the PME inner interpreter to fecth the value, so there's at least no speed penalty for having a few more instructions in the KEYSCAN function. But there is a memory gain each time the result is checked on Pascal level.

2 hours ago, Vorticon said:

By the way, while I know how to create a new library, how do I add units to an existing one without overwriting it? I could not figure out how to add my GETKEY routine to the *SYSTEM.LIBRARY and the manual only shows how to create a new library.

To create a new library with a single file content, you simply compile your program as a unit. Then you can reference that from your program with a uses statement. And possibly a compiler directive to figure out which file it's residing in.
To create a library with multiple units and other resources, like SYSTEM.LIBRARY already is, you need to use the library utility on your Utilities disk. That program can build a library by letting you include different units, compiled at different times, into one single library file. If you want to add something to SYSTEM.LIBRARY you'll let the library program create a new file, into which you let it include all the code segments you like to keep from SYSTEM.LIBRARY (probably all of them) and add one or more units of your own. When done, you write the new library to a file MYLIB.CODE, copy the original SYSTEM.LIBRARY to some other disk, delete it from the system volume, copy your MYLIB.CODE to the system disk and finally change the name of it to SYSTEM.LIBRARY.

Now you have a new SYSTEM.LIBRARY which contains everything it had from the beginning plus your own additions.

 

Now to make this convenient, I recommend that you store a complete unit in the library file, not just an external routine. A complete unit is simply included with a uses thatunit in your own program, and then you just call the function it defines like if it was in your own program. You can't have an external function in the interface part, so you have to write a small Pascal function that's the front end of getkey, then declare your assembler function as external in the implementation part, call that from your getkey function and link the compiled unit with the assembler routine using the Linker program.

Once that's done, you have a stand-alone unit file which contains both the Pascal and assembly program. It can be inserted as a unit into your library file (SYSTEM.LIBRARY if you like) and referred to by that uses clause in your programs. No need to do any linking with your application program using the function, as it's already linked with its own unit.

 

The largest programs I've written for the TI 99/4A have around half a dozen pre-compiled units that are referenced. In such a case you don't want these included in the SYSTEM.LIBRARY, as they aren't general but designed for that particular program. In such cases my main program would start with

uses

  unit in SYSTEM.LIBRARY,

  another unit in SYSTEM.LIBRARY,

  (*U mylibfile.code)

  unit in mylibrary,

  another unit in my library;

 

Once that's declared you have access to all of it just like if it was in your own program. All assembly linking is already done in each unit, if needed, so once your program is compiled you just run it.

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

6 hours ago, Vorticon said:

Dang it! That @ symbol always gets me.

<sidebar>

   @Vorticon I don't know if helps, but since you have written a considerable number of lines of Forth...

   I think of @ in Assembler like the fetch (@) operator in Forth when it's applied to the <src> operand.

   The analogy doesn't quite work as well when it's the <dst> operand but it might give you a memory aid.

</sidebar> 

 

  • Like 2
Link to comment
Share on other sites

Here is the unit extrascreen. Or so I thought. I just realized that the assembly support routine scroll is missing. It's probably in another file. I'll have to look a little more. Update: Can't find the scroll routine. It's probably one procedure inside some other larger file.

 

The comments in the interface section should pretty much explain how to use extrascreen.

 

Extrascreen.pdf

Edited by apersson850
Link to comment
Share on other sites

16 hours ago, apersson850 said:

Here is the unit extrascreen. Or so I thought. I just realized that the assembly support routine scroll is missing. It's probably in another file. I'll have to look a little more. Update: Can't find the scroll routine. It's probably one procedure inside some other larger file.

 

The comments in the interface section should pretty much explain how to use extrascreen.

 

Extrascreen.pdf 172.46 kB · 3 downloads

Thanks. I can roll my own scroll routine as an exercise :)

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...