Jump to content
IGNORED

Pascal on the 99/4A


apersson850

Recommended Posts

12 hours ago, TheBF said:

Looking at your Assembly language code the only difference I see is that you put TB 21 in an infinite loop.

I used a timed loop that jumped out only when the characters stopped coming.

Yes I probably should as it does tend to hang the TI if one or more bytes are missed from a packet on reception. Not an issue up to 2400 bps. Not sure how much of a performance hit I would get. Easy to test.

  • Like 1
Link to comment
Share on other sites

On 3/16/2024 at 7:04 PM, TheBF said:

Looking at your Assembly language code the only difference I see is that you put TB 21 in an infinite loop.

I used a timed loop that jumped out only when the characters stopped coming.

Making that change slowed the transfer rate considerably... 

Link to comment
Share on other sites

1 hour ago, Vorticon said:

Making that change slowed the transfer rate considerably... 

It should not affect the speed as it does not need to delay character reception. 

They way mine works is if a character is detected (TB 21) I get it and exit the routine ELSE decrement the counter and test again. 

So it works just like normal expect is has some "elasticity" around when a character doesn't arrive. 

 

I think the key is implementing the ELSE clause in the code below.

You can see from the machine code that ELSE is just a JMP instruction  (>1000+offset) to ENDIF 

IF is a JNE to ELSE  

With that you never wait if the character is ready, you only wait if there is nothing coming in.

EQ UNTIL  is JNE back to BEGIN. 

 

But it's also possible that the way you are setup it is not ideal and I am way off base. 

 

CREATE ENDTRX
     C320 , CARD , \ CARD @@ R12 MOV,   \ select card
     1E05 ,        \ 5 SBZ,             \ CTS LOW, clear to send
     1D07 ,        \ 7 SBO,             \ LED OFF
     0300 , 0002 , \ 2 LIMI,
     C101 ,        \ R1 TOS MOV,
     NEXT,

.( .)
HEX \  ** decimal numbers used for UART bit no.s
CODE READCOM ( addr n -- n' )
\ [WARNING] SOURCE CODE USES DECIMAL RADIX
 0300 , 0000 ,  \ 0 LIMI,           \ full attention
 C236 ,         \ *SP+ W MOV,        \ addr ->W   (ie: R8)
 A108 ,         \  W TOS ADD,        \ calc last address ->TOS
 0700 ,         \     R0 SETO,          \ set timeout register >FFFF
 04C1 ,         \     R1 CLR,           \ reset char counter
                \  BEGIN,
                \ * handshake hardware ON *
 C320 , CARD ,  \   CARD @@ R12 MOV,   \ select card
 1E05 ,         \   5 SBZ,             \ CTS LOW, clear to send
 1D07 ,         \   7 SBO,             \ led ON
 A320 , UART ,  \   UART @@ R12 ADD,   \ >1300+>40 = UART CRU
 1F15 ,         \   21 TB,             \ test if char in uart
 1609 ,         \   EQ IF,
 3638 ,         \       *W+ 8 STCR,    \ char to buff & inc buff
 1D12 ,         \        18 SBO,       \ clr UART rcv buffer
                \  * handshake hardware off *
 C320 , CARD ,  \       CARD @@ R12 MOV,  \ select card
 1D05 ,         \       5 SBO,            \ CTS line HIGH. I am busy!
 1E07 ,         \       7 SBZ,            \ led OFF
 0700 ,         \       R0 SETO,      \ reset timeout to 0FFFF
 0581 ,         \       R1 INC,       \ count char
 1004 ,         \   ELSE,
 0600 ,         \       R0 DEC,       \ no char, dec TIMEDOUT
 1602 ,         \       EQ IF,        \ expired?
 0460 , ENDTRX , \           ENDTRX @@ B,
                \       ENDIF,
                \   ENDIF,
 8108 ,         \   W TOS CMP,         \ W =   end of buffer?
 16E9 ,         \ EQ UNTIL,
 0460 , ENDTRX , \ ENDTRX @@ B,

 

Link to comment
Share on other sites

1 hour ago, Vorticon said:

I'm wondering if the timing loop was too short. But I think I'm going to leave it there as it's working well enough as it is. I now know more about Xmodem than is safely allowed...

I used >FFFF in my timing which is only .9 seconds or so.

Link to comment
Share on other sites

2 hours ago, Vorticon said:

I now know more about Xmodem than is safely allowed...

 

2 hours ago, apersson850 said:

I know much more about the p-system than is safe for anyone... 😳

As always, should you or any of your TI Force be caught or killed, the Secretary will disavow any knowledge of your actions.8)

  • Haha 4
  • Sad 1
Link to comment
Share on other sites

3 hours ago, Vorticon said:

I'm wondering if the timing loop was too short. But I think I'm going to leave it there as it's working well enough as it is. I now know more about Xmodem than is safely allowed...

Possibly. I used >FFFF which waits for about 1 second before determining there are no more characters to get.

Link to comment
Share on other sites

Regarding concurrency, does the TI implementation automatically rotate tasks in the ready queue when they have the same priority level? Apparently some implementations do.

 

Additionally on this topic, I typed in this test program from the Advanced UCSD Pascal Programming Techniques book pages 32-33 and it's giving me a stack overflow error on execution. I can't see anything wrong with it... I expected to see the messages printed sequentially to the screen ad infinitum.

 

program semtest;
var
 console : semaphore;
 
procedure conwrite(outiam : integer; outmsg : string);
begin
 wait(console);
 writeln('I am ', outiam, ' and my message is ', outmsg);
 signal(console);
end;

process msgwriter(whoiam : integer; mymsg : string);
begin
 repeat
  conwrite(whoiam, mymsg)
 until false;
end;

begin
 seminit(console, 1);
 start(msgwriter(1, 'Shakespeare'));
 start(msgwriter(2, 'monkey'));
 start(msgwriter(3, 'typewriter'));
end.

 

Link to comment
Share on other sites

No, the TI doesn't rotate processes with the same priority unless they wait for something. Adding that feature was what my implementation of multitasking was all about. Unfortunately it didn't work with dynamic memory allocation.

That's why I have a new interest in creating an attach functionality and use that instead, to see if it works better. Or perhaps a different version of new and dispose is needed.

 

Try increasing the private stack space for the processes. They must have room for everything they do, including system calls to print text.

Link to comment
Share on other sites

Increasing the stack size worked. However, only the first task is executing, which makes sense since all the tasks have the same priority. 

Without the system rotating the equal priority tasks, this whole concurrency thing becomes moot unless a task priority can be changed on the fly, which is not supported. If I gave each successive task a higher priority than the one ahead, then all 3 will execute once then the highest priority task will subsequently execute repeatedly without ever relinquishing control.

EDIT: actually that does not work either because the program never gets to run the tasks following the first one. 

 

program semtest;
var
 console : semaphore;
 pid1, pid2, pid3 : processid;
 
procedure conwrite(outiam : integer; outmsg : string);
begin
 wait(console);
 writeln('I am ', outiam, ' and my message is ', outmsg);
 signal(console);
end;

process msgwriter(whoiam : integer; mymsg : string);
begin
 repeat
  conwrite(whoiam, mymsg)
 until false;
end;

begin
 seminit(console, 1);
 start(msgwriter(1, 'Shakespeare'), pid1, 900, 128);
 start(msgwriter(2, 'monkey'), pid2, 900, 129);
 start(msgwriter(3, 'typewriter'), pid3, 900, 130);
end.

Am I missing something here?

 

Link to comment
Share on other sites

Posted (edited)

What you are missing is that once you start the first task, which has the same priority as the parent (your main program), then your first task takes over. It didn't inherit the farther's priority (you indicate that with priority -1), but you gave it the default priority, so the effect is the same. Which means that the main program waits for the first task to finish before it can resume starting the following two tasks. Unless the first task gives up the CPU voluntarily, it will never end.

 

I haven't tried it, but you can try creating an array[0..2] of semaphores. Initialize all to zero. Then you let the task do its output and after that, signal semaphore (task#+1)mod 3 and wait for semaphore task#. Thus it will allow the next to run and stop itself. Note that task numbers must also be 0..2 for this to work as I wrote it.

 

In this case you create a volountary task switch. It should work even if the processes have different priority, since regardless of that it will stop itself on every turn.

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

I tried your suggestion but it doesn't quite work although it really should. Any glaring errors below?

 

program semtest;
var
 console : semaphore;
 pid1, pid2, pid3 : processid;
 sem : array[0..2] of semaphore;
 task : integer;
 
procedure conwrite(outiam : integer; outmsg : string);
begin
 wait(console);
 writeln('I am ', outiam, ' and my message is ', outmsg);
 signal(console);
end;

process msgwriter(whoiam : integer; mymsg : string);
begin
 repeat
  task := succ(task) mod 3;
  conwrite(whoiam, mymsg);
  signal(sem[(task + 1) mod 3]);
  wait(sem[task]);
 until false;
end;

begin
 seminit(console, 1);
 seminit(sem[0], 0);
 seminit(sem[1], 0);
 seminit(sem[2], 0);
 task := -1;
 start(msgwriter(1, 'Shakespeare'), pid1, 900, -1);
 start(msgwriter(2, 'monkey'), pid2, 900, -1);
 start(msgwriter(3, 'typewriter'), pid3, 900, -1);
end.

 

 

Link to comment
Share on other sites

Try this.

program semtest;
var
 pid1, pid2, pid3 : processid;
 sem : array[0..2] of semaphore;
 
procedure conwrite(outiam : integer; outmsg : string);
begin
 writeln('I am ', outiam+1, ' and my message is ', outmsg);
end;

process msgwriter(whoiam : integer; mymsg : string);
begin
 repeat
  conwrite(whoiam, mymsg);
  signal(sem[(whoiam + 1) mod 3]);
  wait(sem[whoiam]);
 until false;
end;

begin
 seminit(sem[0], 0);
 seminit(sem[1], 0);
 seminit(sem[2], 0);

 start(msgwriter(0, 'Shakespeare'), pid1, 900, -1);
 start(msgwriter(1, 'monkey'), pid2, 900, -1);
 start(msgwriter(2, 'typewriter'), pid3, 900, -1);
end.

Let us know if you understand the difference or not.

Link to comment
Share on other sites

I understand how it is supposed to work, but it's still not working as it should (see video below). We should be seeing all 3 task displaying in succession.

Why would using a separate global task number give a different result from your version where you use the whoiam parameter?

Also the console semaphore can be left in as it has no bearing on the execution of the tasks themselves.

 

 

 

Link to comment
Share on other sites

The console semaphore could be left in, yes, since it makes no difference of any kind whatsoever. For the same reason it can be removed, which I wanted to show.

 

Visualizing how a multitasking program works just by looking at the code is bloody difficult, to say the least. I'll be happy to admit that it took a minute of thinking before I realized that the sequence of events we see is correct. It's what to expect.

This is one of those annoying things books tend to leave "as an exercise for the reader" to figure out.

I'll not be that mean, but to begin with I'll give you a few hints, just because it is indeed a good exercise to increase your understanding of what's going on by figuring it out.

  1. The reason for the seemingly erratic order is the same as for why a global task number doesn't work as a semaphore index.
  2. Signal will start another task if there is at least one waiting for that semaphore.
  3. Wait will start another task if there is one in the Readyqueue.

Do you see the reason now?

Link to comment
Share on other sites

Sorry but I still don't see it. I even made a grid with the state of the wait queue, ready queue, active process and semaphore states and the order of the processes still does not match.

Signal will increase the semaphore count if the its wait queue is empty but otherwise move the process in the wait cue to the back of the ready cue, right? Also does each semaphore have its own wait queue or is the wait queue common to all?

Wait will start another task at the head of the ready queue if the semaphore count is zero, otherwise the count is decremented by one and the same task continues to run.

Applying these rules and trancing the processes does not match what the program is outputting. I have one hair left.

  • Haha 3
Link to comment
Share on other sites

Posted (edited)

The clue to this is the "forward" action of signal. When process 0 starts it makes screen output and signals semaphore 1, but at that time P1 has not started yet, so nothing happens. P0 continues until it hits wait S0.

Then the main program takes over and starts P1. P1 prints and signals S2, but P2 hasn't started either, so P1 continues to S1. S1 is already signalled by P0, so P1 makes another loop. Prints on the screen and signals S2 again, but P2 has still not started, so the semaphore just counts again. Now P1 hits S1 which now is zero, so P1 waits.

Main program resumes and starts P2. P2 prints and signals S0. At this point P0 is waiting for S0, so now P0 resumes, prints, signals S1 and so on.

 

So the seemingly irregular execution is because we already have deposited two different but unused signal when P2 eventually starts the first time. Thus a process that later signals a semaphore with an emtpy waiting queue will make one more loop, as its own semaphore has counted to 2. This leads to an execution sequence that may seem random, but is actually deterministic. The only somewhat unexpected behavior of it is that it destroys hair.

 

This is also the reason for why the global task variable doesn't work. The principle for this program is that each process waits for its own semaphore only and signals the "next of kin's" semaphore only. Since some processes takes two turns, they will signal and wait for different semaphores on each turn if the semaphore pointer is he same variable everybody is messing with.

 

Each semaphore has a queue, where one or more tasks can be waiting. It will be a linked list of TIB's (Task Information Blocks). If signal finds a TIB at the semaphore queue, it will move it to the readyQ and then the system will do a task switch, provided the normal rules are applicable. That is, the new TIB at the head of the readyQ has at least the same priority as the running one.

 

Wait will always do that if the semaphore is zero when executed. Then, and only then, there will be a task switch to somebody else.

Signal will always start another task if there is one waiting for that semaphore when it's executed. Then, and only then, there will be a task switch to somebody else.

 

Add to this that there's also an aging feature, which means that if tasks of equal priority have waited for different length of time, the oldest will run first. This implies that when a task is signalled in your program, it's not necessarily that task that runs. It could be the main program's termination code that runs again, to check if all child tasks have terminated. The main program will not terminate until they have and in your program that takes forever. When the main program has taken another turn at that one of your child tasks (the oldest) will run again.

 

This is tricky to see. Need recommendations for a wig?

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

Thanks! There is no mention anywhere about the aging feature, which definitely can complicate things. 

Without a functional ATTACH or automatic rotation of equal priority tasks , this whole thing is of very limited value it would seem and can be replaced with standard procedures.

I'll take a blonde wig please.

  • Haha 1
Link to comment
Share on other sites

Posted (edited)
2 hours ago, Vorticon said:

Thanks! There is no mention anywhere about the aging feature, which definitely can complicate things. 

Now I can't find it in the Internal Architectur Guide either. But I do have a note in my own disassembly of the system about "incrementing age". Either I've just dreamed this or it's one of those "reserved words" that are in the system's data structrues at arbitrary locations. One that has been made useful this way.

Anyway, the fact that the main program waits for a semaphore in BLK_EXIT as long as the children are still running does make it a bit more complex.

 

It was the automatic rotation I implemented. If I install my multitasking support on my machine and then run your program as it was from the beginning, it will work. The problem was instead that some functions in operating system's units HEAPOPS and EXTRAHEAP couldn't handle that. That's the reason for my thoguths about trying to make attach work instead. You can simulate task rotation by attaching a timer event to a semaphore in a high priority task, which thus will start each time it's signalled and then immediately wait for the timer semaphore again. The fact that it was running at all will rotate the reaydQ automatically.

Edited by apersson850
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...