Jump to content
IGNORED

Pascal on the 99/4A


apersson850

Recommended Posts

2 hours ago, apersson850 said:

The smallest normal storage unit is a word, i.e. 16 bits. Even a boolean is stored as 16 bits, and that happens for the file buffer too.

A packed array[0..1] of byte will squeeze in two 8-bit pieces in one 16-bit word.

 

When messing with values like this a type definition like this one comes in handy:

 

type
  byte = 0..255;
  dual = record
    case boolean of
      false:(single: integer);
      true: (dual: packed array[0..1] of byte);
  end; (* record *)

var
  buffer: dual;

With that in place, you can use code like this:

buffer.dual[0] := one_byte_val;
buffer.dual[1] := another_byte_val;
outfile^ := buffer.single;

There two different byte-sized data pieces are stuffed into two different elements of a packed byte-sized array, which shares the same memory as one 16-bit integer.

This type of handling is more efficient for combining two 8-bit values into one 16-bit (or the opposite) than using * (multiply), div or mod to accomplish the same thing.

 

There are other applications for variant records too.

One of the more conventional is to create a file type that allows for different content within the same memory space for different blocks in the file. If you have a data file, you can declare a record where one variant maps all the fields of whatever file header stuff you need. The other variant is just an array[0..255] of integer, or maybe a packed array[0.511] of char, or whatever you need. Then you write and read the first block in the file as the first type of record, the other blocks as the second. Makes it easy to map the file data areas.

A more odd application is to define a record as being either an integer or a pointer to an integer. Then you can store any 16-bit value into your variable as an integer, then access the same variable as a pointer and read or write data to any word in memory. You create the functions peek and poke that way.

A third use is as we did above, for data manipulation. The same dual type above can also be used to implement a Pascal version of SWPB (swap bytes).

 

Finally, when handling variable data in the same type, don't forget the intrinsics moveleft, moveright and fillchar. They don't care about the type declaration and are fast for larger amounts of data. You can move data of any size and anywhere in memory, but the intrinsics will check to see if both source and destination are on even addresses and the number of bytes to move is an even number, then they'll use MOV instead of MOVB internally. Only half as many moves in that case.

 

Now I do know you are learning how to combine assembly with Pascal, but UCSD Pascal does have support for a surprisingly large amount of things that can be done already at Pascal level, although it may not immediately be obvious.

To do a 2-complement, then keep only the lower 8 bits, can be done like this:

var
  n: integer;

n := ord(odd(-n) and odd(255));

You can use boolean operators like and only on booleans. The operator odd has the odd(!) functionality of changing the type of the data to boolean but keep the data content of the integer as it was. Thus you can do boolean operations bitwise on integers directly in Pascal. The operator ord converts the type back to integer again, so you can assign the result to an integer. The output of ord(integer_value) is integer_value with type integer. Unlike if you do ord(character), where the type is changed from char to integer and returns the ordinal value of the character. If you expose 'A' to this treatment you get 65.

 

Assembly's equivalen of NEG is - (minus sign). The equivalent of INV is not. So if you want to invert an integer, then keep the lower byte, you can do like this instead:

var
  n: integer;

n := ord(not(odd(n)) and odd(255));

This behavior is not according to standard Pascal! But it does work with UCSD Pascal.

You've got to love Pascal! This is the stuff you don't really get from the documentation or textbooks.

I should mention however that the 2's complement formula you gave needs to have a 1 added to it to give the correct result:

n := ord(not(odd(n)) and odd(255)) + 1

 

I went ahead and incorparated all the above into the hex conversion program. It definitely requires more gymnastics to get the desired file output, but it does work.

 

{INTEL HEX FORMAT CONVERSION PROGRAM
 CONVERTS FILE FROM HEX TO BINARY}

PROGRAM HEXCONVERT;
TYPE
 BYTE = 0..255;
 DUAL = RECORD
  CASE BOOLEAN OF
   FALSE:(SINGLE : INTEGER);
   TRUE: (DUAL : PACKED ARRAY[0..1] OF BYTE);
  END;

VAR
 CHECKSUM, DATACHECK, DATALEN, I, J, BYTEVAL : INTEGER;
 INFILE : TEXT;
 OUTFILE : FILE OF INTEGER;
 REC : STRING[255];
 FNAME : STRING[20];
 HEXBYTE : STRING[2];
 BUFFER : DUAL;

FUNCTION HEX2DEC(HVAL : STRING) : INTEGER;
{CONVERT HEX DIGIT PAIR TO INTEGER VALUE}

VAR
 CH : CHAR;
 DVAL, VAL : INTEGER;

 BEGIN
  VAL := 0;
  CH := HVAL[1];
  IF (ORD(CH) > 47) AND (ORD(CH) < 58) THEN
   DVAL := (ORD(CH) - 48) * 16
  ELSE
   DVAL := (ORD(CH) - 55) * 16;

  VAL := VAL + DVAL;
  CH := HVAL[2];
  IF (ORD(CH) > 47) AND (ORD(CH) < 58) THEN
   DVAL := ORD(CH) - 48
  ELSE
   DVAL := ORD(CH) - 55;

  VAL := VAL + DVAL;
  HEX2DEC := VAL;
 END; {HEX2DEC}

BEGIN
 PAGE(OUTPUT);
 WRITELN('INPUT FILENAME? ');
 READLN(FNAME);
 RESET(INFILE,FNAME);
 WRITELN('OUTPUT FILENAME? ');
 READLN(FNAME);
 REWRITE(OUTFILE,FNAME);

 WHILE NOT EOF(INFILE) DO
  BEGIN
   {READ A RECORD}
   READLN(INFILE, REC);
   {EXTRACT THE DATA LENGTH}
   HEXBYTE := COPY(REC,2,2);
   DATALEN := HEX2DEC(HEXBYTE);
   IF DATALEN <> 0 THEN
    BEGIN
     DATACHECK := DATALEN;
     I := 10;
     J := -1;
     {READ EACH HEX DIGIT PAIR AND CONVERT TO INTEGER}
     WHILE I <= (DATALEN + 8) DO
      BEGIN
       HEXBYTE := COPY(REC,I,2);
       BYTEVAL := HEX2DEC(HEXBYTE);
       DATACHECK := DATACHECK + BYTEVAL;
       J := SUCC(J);
       IF J < 2 THEN
        BUFFER.DUAL[J] := BYTEVAL
       ELSE
        BEGIN
         J := 0;
         OUTFILE^ := BUFFER.SINGLE;
         PUT(OUTFILE);
         BUFFER.DUAL[J] := BYTEVAL;
        END;
       I := I + 2;
      END;
      {CASE OF ODD NUMBER OF DATA BYTES}
      IF J = 0 THEN
       BEGIN
        BUFFER.DUAL[1] := 0;
        OUTFILE^ := BUFFER.SINGLE;
        PUT(OUTFILE);
       END;
      WRITELN;
     {COMPUTE RECORD CHECKSUM BY DOING A 2'S COMPLEMENT
      ODD(N) MAKES N BOOLEAN AND ORD(N) CONVERTS IT BACK TO INTEGER
      SO HERE TAKING THE NEG OF N AND MASKING THE MSB}
     DATACHECK := ORD(NOT(ODD(DATACHECK)) AND ODD(255)) + 1;
     {EXTRACT RECORD CHECKSUM}
     HEXBYTE := COPY(REC,10 + DATALEN,2);
     CHECKSUM := HEX2DEC(HEXBYTE);
     {COMPARE COMPUTED AND EXTRACTED CHECKSUMS. MUST MATCH}
     IF CHECKSUM <> DATACHECK THEN
      WRITELN('WARNING! CHECKSUM ERROR!');
    END;
  END;
 CLOSE(INFILE);
 CLOSE(OUTFILE,LOCK);
 WRITELN;
 WRITELN('FILE CONVERSION DONE');
END.

 

Link to comment
Share on other sites

Just now, Vorticon said:

I should mention however that the 2's complement formula you gave needs to have a 1 added to it to give the correct result:

n := ord(not(odd(n)) and odd(255)) + 1

That was supposed to be the 1's complement. For 2's complement i used the odd(-n) instruction. A minus sign in Pascal is like NEG in assembly and that's the same as 2's complement.

Link to comment
Share on other sites

@Vorticon, now when you've read all the TI manuals associated with the p-system and Pascal - have you read Advanced UCSD Pascal programming techniques? It was published in 1985 (or maybe 1986) and one of the two authors was involved in creating the UCSD p-system and Pascal at the University of California, San Diego.

 

If you have not, I recommend it. To make it easier for you I attach a pdf of the book here... 🙂

 

AdvancedUCSDPascalProgrammingTechniques.pdf

  • Thanks 2
Link to comment
Share on other sites

11 minutes ago, apersson850 said:

@Vorticon, now when you've read all the TI manuals associated with the p-system and Pascal - have you read Advanced UCSD Pascal programming techniques? It was published in 1985 (or maybe 1986) and one of the two authors was involved in creating the UCSD p-system and Pascal at the University of California, San Diego.

 

If you have not, I recommend it. To make it easier for you I attach a pdf of the book here... 🙂

 

AdvancedUCSDPascalProgrammingTechniques.pdf 19.1 MB · 0 downloads

That's going to make for excellent bedtime reading :) I checked on Amazon and it's going for nearly $80! I'll stick with the PDF thank you...

  • Like 1
Link to comment
Share on other sites

<pedantry>

Not that anyone here but me cares, but the complements under discussion are properly labeled as “two’s complement” or “2’s complement” and “ones’ complement” or “1s’ complement”. The placement of the apostrophe indicates the plurality of the operation. Only a single ‘2’ is involved in computing the two’s complement (a single subtraction from 216 for our 16-bit machine, effected internally in the CPU as an inversion followed by adding 1),

  1|0000 0000 0000 0000
- 0|0000 0000 0001 0011
---|-------------------
= 0|1111 1111 1110 1101

 

but many more than a single ‘1’ are involved in computing the ones’ complement (each bit is toggled).

</pedantry>

 

...lee

  • Like 3
  • Confused 1
Link to comment
Share on other sites

I see a source of confusion in this document I linked you to. 

Intel Hex Format (interlog.com)

 

It is using record type 02.

 

My encoder is the using record type 00. 

Wikipedia has a fuller explanation of each record type.

 

My apologies.  I did not know about the extended record types when I found this document.

I thought IntelHEX was one thing. 

  • Like 1
Link to comment
Share on other sites

1 hour ago, Vorticon said:

Well no, for 8-bit systems it uses type 00. 

I read it as the addressing range is the parameter that determines the record type and 64Kbytes was the range of 8 bit machines. 

This would put the 9900 in that camp too. (barring writing an extended version to capture SAMS memory)

 

Anyway. If your decoder was set up for record type 00 then we were on the same page all along.

From what I can see the MaxForth IntelHEX output file is compliant.

 

  • Like 1
Link to comment
Share on other sites

I guess with the new Pcode Tool version, transferring data files to the USCD Pascal environment using the Intel Hex format is now a moot point. Nonetheless, it's still an interesting exercise.

So in that spirit I experimented today with serial transfers between the PC and the TI under UCSD Pascal, and it's a mixed bag.

I can send a text file to the PC terminal using the TRANSFER utility in the Filer and it works as it should. However, sending a text file the other way from the PC to the TI has been very problematic. The contents of the file are sent OK, but then they are followed by a whole bunch of garbage even though I made sure I terminated the text file with a CTRL-C (>03) which is ETX (End transmission) using a hex editor. I am unable to save the input to file because it's too big but I can visualize it if I transfer to the CONSOLE: from REMIN:

I doubt many people here have bothered using the serial connection with Pascal, except perhaps @apersson850. I thought I'd ask anyway.

The other option is to use UNITREAD to read a specific number of blocks (512 bytes) from the serial port directly, and that's what I'm going to try to do tomorrow. The caveat here is that the file to be transferred needs to occupy a whole number of blocks, or otherwise manually padded to achieve that. Not a problem with the pattern and color files which are exactly 12 blocks each (6144 bytes).

  • Like 1
Link to comment
Share on other sites

Back in the days when physical TI 99/4A computers were the only version we had, there was a desire to be able to exchange the p-system's text files with the ordinary operating system's display/variable 80 file type. Then they were both in the same physical machine. But the format created by one system couldn't be understood by the other.

Me and a friend, Lars Thomasson, solved that by implementing a dis/var 80 file reader and writer in Pascal. Using unitread and unitwrite, you can access sectors on a diskette directly, so we wrote code to read the text in the D/V 80 file on one disk and store it in a p-system text file on another, or the opposite.

First version allowed only a single non-fragmented D/V 80 file on that disk, but the second version could handle the disk directory of standard disks as well. Now we did this only with text files, but that was because these were the only type we considered interesting. Nothing says you couldn't do it with a binary file as well, since blockread and blockwrite will allow the use of untyped files under Pascal. Just a long row of bytes.

 

Now I'm not sure if you have any common media (like the diskette) to share between your two systems here, so it may be a moot point in your case. But it was pretty easy to do in Pascal, as it doesn't need any assembly support to read physical disk sectors.

 

I realized I should point out that when calling the RSP/IO via unit-procedures there's a control word you have to consider. Reading incoming data from the remote in port (REMIN:) can be subject to text file considerations. CR may be expanded to CR/LF, DLE may be used for indentation expansion etc. These features are enabled/disabled via bits in the control parameter.

 

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

Well, you did read the documentation, so I presume it would be an insult to tell you where it is now. 🙃 But a hint is to check the compiler manual for unitwrite/unitread.

 

I found the assembly procedure scroll that was missing with extrascreen above. And since you seem to be keen on accessing the directory on a disk, I found a program that does that. Note that this one looks in the p-system's directory, not the standard one. But the principle is the same, just the data structure is different.

Dirdemo.pdf Scroll.pdf

  • Like 1
Link to comment
Share on other sites

5 hours ago, apersson850 said:

Well, you did read the documentation, so I presume it would be an insult to tell you where it is now. 🙃 But a hint is to check the compiler manual for unitwrite/unitread.

 

I found the assembly procedure scroll that was missing with extrascreen above. And since you seem to be keen on accessing the directory on a disk, I found a program that does that. Note that this one looks in the p-system's directory, not the standard one. But the principle is the same, just the data structure is different.

Dirdemo.pdf 64.73 kB · 4 downloads Scroll.pdf 61.81 kB · 4 downloads

I did read about UNITREAD/UNITWRITE but did not see anything about any "control word"...

In any case I did manage to get text files transferred using UNITREAD.

 

{THIS PROGRAM RECEIVES A TEXT FILE
 FROM REMIN: AND SAVES IT TO DISK
 MAXIMUM FILE SIZE IS 12 BLOCKS (6144 BYTES)}

 PROGRAM TEXTTRAN;
 VAR
  I, NBYTES : INTEGER;
  FN : STRING[20];
  ERROR : BOOLEAN;
  BUFFER : PACKED ARRAY[1..6144] OF CHAR;
  OUTFILE : TEXT;

 BEGIN
  PAGE(OUTPUT);
  ERROR := FALSE;
  REPEAT
   WRITELN('DESTINATION FILENAME:');
   READLN(FN);
   WRITELN;
   REWRITE(OUTFILE,FN);
   WRITELN('NUMBER OF BYTES TO TRANSFER:');
   READLN(NBYTES);
   IF (NBYTES <1) OR (NBYTES > 6144) THEN
    BEGIN
     WRITELN('BYTES NUMBER MUST BE 1-6144!');
     ERROR := TRUE;
    END;
   UNTIL ERROR = FALSE;

  WRITELN;
  WRITELN('INITIATE TRANSFER...');
  WRITELN;

  {TRANSFER SOURCE FILE TO BUFFER}
  UNITREAD(7,BUFFER,NBYTES);

  {SAVE DATA FROM BUFFER TO DISK}
  FOR I := 1 TO NBYTES DO
   WRITE(OUTFILE,BUFFER[I]); 

  WRITELN;
  WRITELN('TRANSFER DONE!');
  CLOSE(OUTFILE,LOCK);
 END.

 

Oddly enough however, I noticed that there were 10 bytes of junk added to the end of the file even though the file itself transferred perfectly. Easy enough to edit out but not sure why this is happening. On the PC side I'm using the "Send File" option of TeraTerm which sends a raw file without any communication protocol. I do suspect however that I will have issues with larger files once the RS232 buffer is full since there is no flow control here.

Things did not go well for binary transfers however. No matter what I tried, I always got garbage bytes from beginning to end. Here's the binary transfer program:

 

{THIS PROGRAM RECEIVES ANY FILE
 FROM REMIN: AND SAVES IT TO DISK
 MAXIMUM FILE SIZE IS 12 BLOCKS (6144 BYTES)}

 PROGRAM BINTRAN;
 TYPE
  BYTE = 0..255;
  DUAL = RECORD
   CASE BOOLEAN OF
    FALSE:(SINGLE : INTEGER);
    TRUE: (DUAL : PACKED ARRAY[0..1] OF BYTE);
   END;

 VAR
  I, J, NBYTES : INTEGER;
  FN : STRING[20];
  ERROR : BOOLEAN;
  INVALUE : DUAL;
  BUFFER : PACKED ARRAY[1..6144] OF BYTE;
  OUTFILE : FILE OF INTEGER;

 BEGIN
  PAGE(OUTPUT);
  ERROR := FALSE;
  REPEAT
   WRITELN('DESTINATION FILENAME:');
   READLN(FN);
   WRITELN;
   REWRITE(OUTFILE,FN);
   WRITELN('NUMBER OF BYTES TO TRANSFER:');
   READLN(NBYTES);
   IF (NBYTES <1) OR (NBYTES > 6144) THEN
    BEGIN
     WRITELN('BYTES NUMBER MUST BE 1-6144!');
     ERROR := TRUE;
    END;
   UNTIL ERROR = FALSE;

  WRITELN;
  WRITELN('INITIATE TRANSFER...');
  WRITELN;

  {TRANSFER SOURCE FILE TO BUFFER}
  UNITREAD(7,BUFFER,NBYTES);

  {SAVE DATA FROM BUFFER TO DISK}
  J := -1;
  FOR I := 1 TO NBYTES DO
   BEGIN
    J := J + 1;
    IF J < 2 THEN
     INVALUE.DUAL[J] := BUFFER[I]
    ELSE
     BEGIN
      J := 0;
      OUTFILE^ := INVALUE.SINGLE;
      PUT(OUTFILE);
      INVALUE.DUAL[J] := BUFFER[I];
     END;
   END;

  {SINCE FILES ARE ALWAYS WRITTEN AS
   WORDS, THIS SECTION HANDLES THE 
   CASE OF AN ODD NUMBER OF BYTES}
  IF J = 0 THEN
   BEGIN
    INVALUE.DUAL[1] := 0;
    OUTFILE^ := INVALUE.SINGLE;
    PUT(OUTFILE);
   END;
  WRITELN;
  WRITELN('TRANSFER DONE!');
  CLOSE(OUTFILE,LOCK);
 END.

 

All in all, I think I've already spent an inordinate amount of time experimenting with this, and my conclusion is that raw serial file transfers without protocols is a very dicey and unreliable affair. It's a moot point anyway with the latest version of Pcode Tool, but it was fun to play with this for a bit. Time to move on.

Link to comment
Share on other sites

11 minutes ago, Vorticon said:

I did read about UNITREAD/UNITWRITE but did not see anything about any "control word"...

In any case I did manage to get text files transferred using UNITREAD.

 

Oddly enough however, I noticed that there were 10 bytes of junk added to the end of the file even though the file itself transferred perfectly. Easy enough to edit out but not sure why this is happening. On the PC side I'm using the "Send File" option of TeraTerm which sends a raw file without any communication protocol. I do suspect however that I will have issues with larger files once the RS232 buffer is full since there is no flow control here.

Things did not go well for binary transfers however. No matter what I tried, I always got garbage bytes from beginning to end. Here's the binary transfer program:

 

All in all, I think I've already spent an inordinate amount of time experimenting with this, and my conclusion is that raw serial file transfers without protocols is a very dicey and unreliable affair. 

I have no doubt your conclusion is correct.

With such a slow machine flow control is critical on the receive side in my experience without resorting to some new tricks for RS232 interrupt handling that were invented by someone who's name escapes me just now. I think I got a copy from @InsaneMultitasker 

 

 

Link to comment
Share on other sites

Unitread uses the standard interrupt mechanism on the RS232 card, albeit somewhat improved by the fact that the p-system does pre-store the CRU and entry addresses, so the reaction to the interrupt is quicker.

Maybe a stupid question - but you did set the serial port baud rate and stuff properly? Easy to forget...

Of course, when a disk is written to no interrupts are serviced as long as the disk IO is in progress.

 

Now I extracted the error window unit as well. It's used to display a message on the screen, without disturbing the text that was there prior to showing the message. In the file is the Pascal unit, the assembly support and an example of how to use it.

If you study the program, then you can look at how the parameters are retreived in the assembly support part. Instead of popping them off the stack by advancing the stack pointer item by item, I let the stack pointer remain as a parameter frame pointer, access the parameters by indexing from that pointer and thus let them stay on the stack. All the workspace is used anyway.

showerrors.pdf

Edited by apersson850
Link to comment
Share on other sites

19 minutes ago, TheBF said:

I have no doubt your conclusion is correct.

With such a slow machine flow control is critical on the receive side in my experience without resorting to some new tricks for RS232 interrupt handling that were invented by someone who's name escapes me just now. I think I got a copy from @InsaneMultitasker 

 

 

I used the approach proposed and put into practice by Jeff Brown, who used it in his ZT4 / Term80 terminal emulator 25+ years ago.  The idea is to disallow as much of the ROM ISR as possible, shut down interrupts and "corrupt" the VDP write address in such a way that you can quickly capture the RS232 interrupt and continue processing. I implemented a variation of his idea in TIMXT to achieve 38.4k data reception on the /4a. 

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

The p-system does some of this improvements by itself, since it pre-scans for relevant interrupts and stores their entry points at boot time.

When running, it polls the interrupt inputs and then execute the relevant code when it sees them. Still this is integrated with happening at suitable times in the PME's interpretation of p-code, so it will not react as fast as if you have a hardware interrupt allowed to happen between any machine instruction, of course.

 

I have two cards I designed myself in the expansion box. They both have interrupt capability, so I've tested adding an unknown (to the p-system designers) hardware interrupt, and that can work too.

Edited by apersson850
Link to comment
Share on other sites

7 hours ago, apersson850 said:

The p-system does some of this improvements by itself, since it pre-scans for relevant interrupts and stores their entry points at boot time.

When running, it polls the interrupt inputs and then execute the relevant code when it sees them. Still this is integrated with happening at suitable times in the PME's interpretation of p-code, so it will not react as fast as if you have a hardware interrupt allowed to happen between any machine instruction, of course.

 

I have two cards I designed myself in the expansion box. They both have interrupt capability, so I've tested adding an unknown (to the p-system designers) hardware interrupt, and that can work too.

In your substantial computer experience Is there any other machine besides TI-99 where the term "polls the interrupts" is used. It's like an oxymoron to me. :)  

In my little world interrupts are about removing the need to poll.

 

Link to comment
Share on other sites

7 hours ago, Vorticon said:

Do you know if the RS232 card's registers are properly setup with the default comm settings at system startup or only upon invoking REMIN: / REMOUT: ?

It should start with this configuration:

 

PRINTER:   RS232/2.BA=9600.DA=7.PA=O.EC

REMIN/REMOUT:   RS232/1.BA=300.DA=7.PA=O.EC

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