+OLD CS1 Posted November 15, 2023 Share Posted November 15, 2023 4 minutes ago, JasonACT said: I'm having trouble opening the .rar file, is there any particular version of software I need to download? I've tried (and old) WinRAR and a much newer 7Zip already. I just opened it with 7-Zip 23.01. 2 Quote Link to comment Share on other sites More sharing options...
JasonACT Posted November 15, 2023 Share Posted November 15, 2023 Ah, thanks, I'll download an updated version of 7-Zip. 1 Quote Link to comment Share on other sites More sharing options...
Rhodanaj Posted November 15, 2023 Share Posted November 15, 2023 (edited) 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 November 15, 2023 by Rhodanaj added exe-file 2 Quote Link to comment Share on other sites More sharing options...
+TheBF Posted January 4 Share Posted January 4 R.I.P. Niklaus Wirth. 6 Quote Link to comment Share on other sites More sharing options...
+TheBF Posted January 4 Share Posted January 4 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." 😆 8 Quote Link to comment Share on other sites More sharing options...
+mizapf Posted January 4 Share Posted January 4 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. 2 Quote Link to comment Share on other sites More sharing options...
+Vorticon Posted January 13 Share Posted January 13 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! Quote Link to comment Share on other sites More sharing options...
apersson850 Posted January 13 Author Share Posted January 13 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. 1 Quote Link to comment Share on other sites More sharing options...
+Vorticon Posted January 14 Share Posted January 14 Thanks! I'm still however a bit fuzzy about the role of the screen blanking in all this. Also what is a VDP control register? Quote Link to comment Share on other sites More sharing options...
apersson850 Posted January 14 Author Share Posted January 14 (edited) 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 January 14 by apersson850 2 1 Quote Link to comment Share on other sites More sharing options...
+Vorticon Posted January 14 Share Posted January 14 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! 3 Quote Link to comment Share on other sites More sharing options...
apersson850 Posted January 14 Author Share Posted January 14 Quite a lot of this information is here. Memory map.pdf 2 Quote Link to comment Share on other sites More sharing options...
+Vorticon Posted January 14 Share Posted January 14 1 hour ago, apersson850 said: Quite a lot of this information is here. Memory map.pdf 97.25 kB · 0 downloads Good start for sure! Quote Link to comment Share on other sites More sharing options...
+Vorticon Posted January 14 Share Posted January 14 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. 2 Quote Link to comment Share on other sites More sharing options...
apersson850 Posted January 14 Author Share Posted January 14 (edited) 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 January 14 by apersson850 Quote Link to comment Share on other sites More sharing options...
+Vorticon Posted January 14 Share Posted January 14 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. Quote Link to comment Share on other sites More sharing options...
apersson850 Posted January 14 Author Share Posted January 14 Try moving the SCANWS block to the bottom instead. Quote Link to comment Share on other sites More sharing options...
+Vorticon Posted January 14 Share Posted January 14 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 Quote Link to comment Share on other sites More sharing options...
apersson850 Posted January 15 Author Share Posted January 15 (edited) 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 January 15 by apersson850 Quote Link to comment Share on other sites More sharing options...
+Vorticon Posted January 15 Share Posted January 15 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. Quote Link to comment Share on other sites More sharing options...
apersson850 Posted January 15 Author Share Posted January 15 (edited) 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 January 15 by apersson850 1 1 Quote Link to comment Share on other sites More sharing options...
+Vorticon Posted January 15 Share Posted January 15 Got it. Thanks. FYI: I've been compiling your excellent explanations of various topics on UCSD Pascal and maybe someday I'll put them all together into a programmer's guide for everyone's benefit Quote Link to comment Share on other sites More sharing options...
+TheBF Posted January 15 Share Posted January 15 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> 2 Quote Link to comment Share on other sites More sharing options...
apersson850 Posted January 15 Author Share Posted January 15 (edited) 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 January 15 by apersson850 Quote Link to comment Share on other sites More sharing options...
+Vorticon Posted January 16 Share Posted January 16 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 Quote Link to comment Share on other sites More sharing options...
Recommended Posts
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.