apersson850
-
Content Count
1,063 -
Joined
-
Last visited
Posts posted by apersson850
-
-
The p-system runs on the TI 99/4A all right. It's just a different operating system. Unlike the CP/M card, or the Geneve, which are full blown computers on cards in the PEB box. But the UCSD p-system has a different file format, compared to the native TI file system. But since Pascal allows direct disk sector access, without any assembly support, you can relatively easy write a Pascal program which can read a TI disk and convert between DIS/VAR 80 and text file formats, for example. But you can't load a memory image program, since it will not conform with the memory usage of the p-system.
To make things even more complex, TI Extended BASIC uses two different file formats, when programs are stored on disk. Which format is used depends on the size of the program.
-
Nice. And as you point out, if you have a section like DUMP, not designed to work in a concurrent system, then you notice the biggest problem compared to a pre-emptive one.
But you can't have all advantages without some cons.
-
All right, then your intention is to load different types of files, not just memory image files intended to be loaded by option #5 in the E/A module. Which means almost everything could happen. For a moment I wondered about how to handle a Pascal file, but then I realized that you can't even see that file from the normal OS, so it's not an issue.
But anyway, let's think about option 5 for a moment.
You are aware of that these files contain a header, which tells you their size, the start address to load them at and if there's another file to read in? You are also aware of that the file I/O system in the machine will load this data chunk into a VDP RAM buffer, and then it's the loader's task to move it to the correct destination? Most of these files start at >A000 and can fill all the way down to >FFFF, if you load three files in sequence. It's normal not to make them bigger than 8 K bytes, to avoid a problem with overfilling the VDP RAM buffer.
But there's nothing preventing such a file from having a header telling you that it should load at >2000, which normally is where the loader is hiding, to be out of harms way from the loaded file itself. While it's correct that the loader isn't needed once the loaded program has started, that isn't too helpful if the loader overwrites itself while moving the loaded file from VDP RAM buffer to the final destination.
There are some limitations imposed by the program often used to create memory image files, but nothing prevents you from doing that yourself. I've written software for use by Extended BASIC, where BASIC contains a minimal loader, installed by multiple CALL LOAD statements. That loader then reads a memory image file I created myself, a file which is moved into memory to handle the real assembly support. To run this BASIC program, you load the file, type RUN and then the program will load the next file by itself. This works both from disk and from cassette, which was one target for that program.
After that, this program creates, stores and retreives memory image files with data created by the program, and my full assembly suppot routine handles that. But in my case I've written all of it myself, so I've planned where things should go in memory. If you are doing a general loader, you can't know from the beginning. So you either have to accept that if it's a specially designed program, you can't load it, or you'll have to try to find a way to load it anyway.
This also means that these memory image files are just that, images of memory when they are loaded. They can't go anywhere else but where they are designed to do. There's no information in these files that will allow you to select a different address to load them at. Well, you can load them anywhere, but they'll only work if loaded precisely where they are supposed to.
The are systems for the 99/4A where you can load serveral programs, or rather subprograms/procedures, as normally not more than one main program is running at the same time. It's so rare that we can leave full concurrency to the side for the moment.
When using Forth, you can load code (word definitions) as you like. The system will make a new definition work regardless of if other definitions were loaded first or not. The only requirement is that definitions required for a new definition must be loaded first, or they'll not be found. But completely independent chunks of code can be loaded, and will work, in any order.
When using Pascal with the p-system, you can create libraries with code, which will be loaded when referenced, at any free location in memory. The operating system will release them from memory if the space is needed but the code is not, and reload them again from disk, if they are needed another time. The p-system will even move them in memory, if they are still needed but occupies memory needed by something else. Thus the files are dynamically relocateable, i.e they can be loaded at any memory location and even be moved later, after running for a while, to another location. P-code can even be loaded either in CPU RAM or VDP RAM, and executed from the memroy where it is. This capability is unique to the p-system, as far as the 99/4A goes, and is the reason why the p-system is the best development platform available for the 99/4A. But it's a system by itself, with its own file system, so you'll never encounter that.
The normal assembly object files are also relocateable, albeit only at load time. But this, together with the REF/DEF directives, means that you can load relocateable assembly code in any order, to stuff everything you need into memory. The linking loader will take care of resolving the addresses through the REF/DEF table, where it will gather and later find the informaiton it needs to figure out which address a certain DEFined label actually ended up at, so if can be properly REFerenced. It will also take care of converting all other relocatable addresses to absolute addresses at load time, even if they aren't defined to be externally referenced.
But that's pretty much it. There are not any other programs I know of that has any abilityto exist at the same time in memory, if they weren't designed for that from the beginning. You do have Extended BASIC's merge command, but that's a separate system by itself. It needs preparation and planning too. So either I've not fully understood what you are planning to accomplish, or you haven't understood how to implement it.
-
So your next challenge will be to make them dynamically relocatable? They'll read in the memory image file into VDP RAM (where no CPU code runs anyway), inspect it and move themselves if they find out that the file read needs to be where the loader already is?
-
Yes, you can take control of the disk access directly (I've done that, but for another reason). But then you run into the issue that different disk controllers work in different ways, so you either have to try to figure out which one you are dealing with, or you'll only be compatible with the model you write the code for. The purpose of the DSR is of course to hide such differences from the user's applications.
-
I trhought the same, but at least it should be one direction at a time only, so that you can take advantage of the auto-incrementing VDP memory access feature. Done like that, there's no speed penalty at all when reading/writing, just for setting up the address.
-
1
-
-
True. If you want the benefits of things, like sprite motion, happening in real time, then you also need to detect things you want to know about, like sprite collision, in real time.
-
Several of the posts here are pretty humoristic, for sure, but this is not the reason. Renaming the TMS 9919 to SN 76489 didn't change the name of the computer either.

-
It follows the naming convention TI used for its minicomputers of the era. The TI 990/4, /5, /9, /10, /10A, and /12 were all different models of the 990 series.
Exactly. There you have the principle used by TI at that time. As this is a microcomputer, not a mini, removing one zero, thus making this the TI 99 instead of the TI 990, makes sense.
Then the different models in the TI 990 series had different numbers, indicating their capacity. The TI 990/9 was the original model. The TI 990/4 and TI 990/5 had the TMS 9900 CPU inside. The model 5 just had some more memory.
Thus making the new home computer the TI 99/4 made sense. Later there were smaller models (e.g. the TI 99/2) and larger models (e.g. TI 99/8) planned and developed.
Adding the A to the type designation is just what TI did when they made a revision of something with about the same capability. The TI 990/10 and the TI 990/10A are both at roughly the same level, but the TI 990/10A was modernized with a new set of chips, based on the TMS 99105 CPU.
On a similar level, the TI 99/4 and the TI 99/4A are of similar capacity, or at least equal enough not to motivate a model name change to TI 99/5. So they added the A, as they always did, and came up with the TI 99/4A. It would have been the same even if the TMS 9918A chip would have been called TMS 99182 instead, or whatever.
-
2
-
-
Well, I can load it from disk, so that's not an issue. But I don't have the source. Disassemble you can always do, but it's more informative with the real source.
-
1
-
-
Good, then I didn't have to look to avoid this question spoiling my entire day!
-
2
-
-
I'm dusting off my old Mini Memory cart and I have always wanted a disassembled source code for the LINES program.
Does it exist in archives or on another site?
I thought I had it, but I'm probably mixing it up with having the LINES program as an object file on disk. The original is on tape. So it seems I can't help you.
-
1
-
-
After the PAB declaration, there's a BSS 255. I don't remember now, but does the DATA directive restore the byte counter to an even value? If not, everything after that BSS is shifted one byte.
A really ugly example of coding of the DSRWS overlaying the code. I guess to save a few bytes, but... Not your fault, I understand you got it from elsewhere.
-
Well. considering that I figured this out 29 years ago, I may not remember correctly. But as far as I do, it's because when you get an interrupt, it effectively makes a BLWP into the interrupt service routine. When the interrupt service is ready, it will RTWP back to the interrupted program. What I do here is that I RTWP back to a different program, thus accomplishing the task switch. This is not a normal interrupt service routine, since usually such a routine runs and do what it should do (move sprites or whatever) and then return in a stealth manner. Except for the intended work, like moving sprites, no other program should really be aware that it ever ran. But this is completely opposite. The interrupt service in a case like this, when it's a scheduler, literally has the command over life and death for the other processes. The trick here is instead that the different processes running all think they own the computer, but instead are unaware about that the CPU now and then is given to some other process, of which they (in this simple example) are completely unaware that it even exists.
So if you look at it again, you see that I don't do a "manual BLWP", because I restore the WP, PC and ST into the current WS. When using BLWP, the CPU stores the same registers in the called WS.
In a more complex concurrent environment, the different processes running are frequently aware of each other, since they may communicate with other processes through semaphores or monitors or whatever you've implemented for that task.
-
2
-
-
The Belgian company Web converting equipment N.V., which no longer exists, used a control board developed for them by a third party consultant company. This board was controlled by a TMS 9995 CPU.
It's still possible to find some patent applications from Web Converting on the internet, but the company was bought by the Swedish company Strålfors, which nowadays is Postnord.
I worked at Strålfors at the time when Web Convering was acquired, and was involved in replacing this board with a Strålfors design instead.
-
You just yield in the loop, or every n iterations of the loop. If all the tasks are cooperative then execution will come back to your loop.
You also have the option to NOT yield if you are doing something time critical.
Yes, that's true. In this case you have to think about when you need to do something special in the code to make sure it's cooperative. With pre-emptive multitasking, you have to make sure you do things that require that they aren't interrupted using procedures that take care of that (like CHOUT in my example above) or disable interrupts when doing them. The latter usually isn't recommended, since you may lock up the whole thing that way.
-
1
-
-
Can you shows the code for the interrupt handler that does the task switch?
I would love to see it.
It's rather funny. Tonight I went up to my 99/4A (the one that's set up and ready to run), turned it on and there it is. Live and kicking. It hasn't been running for several years. What's even more astonishing is that I feel at home almost immediately, loading the disk manager, catalog a few disks to find the program, look at it and then print it from E/A (in Maximem). I print to a PC capturing the file with Hyper Terminal.
Anyway, here it is. This simple example runs two processes, which both print on the screen, but with different delay loops in between. Thus when running it, I see output like "BBBBABBBBABBBBABBBBA" on the screen, just to prove that the whole thing works.
As you can see, there are 12 instructions in the scheduler, so it's not too wasteful of resources. It runs in 79.3 us. Of course, to handle a more complex system, with an arbitrary number of tasks (maybe with different priorities), which could be ready to run or waiting for events to happen, it would become more complex.
In such a case, you need a more complex Task information block (TIB) for each task. It will have to contain the priority and slots for links to other TIB, so that you can link any number of tasks in the ready queue, and also link one or more TIB that are waiting on for example a counting semaphore or some event to happen.
Note that this example will not run without the 64 K RAM expansion of my design, where RAM can replace ROM under software control (CRU bits based at >0400) in the console.
By the way, i did notice the comment says it's written in November 1988. They last long, these floppies.
******************************* * * Multitasking with time-sharing on the 99/4A * A-DATA 881101 * REF VDPWA,VDPWD,VWTR,KSCAN DEF START MEMCON EQU >400 CRU address of memory control IVECT1 EQU 4 Interrupt vector location for level 1 interrupt SCSIZE EQU 960 Screen size SCRPOS DATA 0 Current screen location ENDSCR DATA SCSIZE WRMASK DATA >4000 * VDP character write routine * Assumes character to write is MSBy of R1 * Avoids use of any other register in WS of calling routine * CHOUT is a non-splittable operation, since the VDP address load operation and * the data byte write must be allowed to be completed. Otherwise, characters * will be misplaced on the screen. CHOUT LIMI 0 This operation can't be splitted MOVB @SCRPOS+1,@VDPWA Load VDP address SOC @WRMASK,@SCRPOS MOVB @SCRPOS,@VDPWA SZC @WRMASK,@SCRPOS MOVB R1,@VDPWD Load VDP data INC @SCRPOS C @SCRPOS,@ENDSCR Outside screen? JL ONSCR CLR @SCRPOS Begin at screen top again ONSCR LIMI 1 B *R11 * Screen clearing routine CLRSCR LI R0,>4000 SWPB R0 MOVB R0,@VDPWA SWPB R0 MOVB R0,@VDPWA LI R0,' ' LI R1,SCSIZE CLRLOP MOVB R0,@VDPWD DEC R1 JNE CLRLOP B *R11 ******** * * Starting point of program * START LIMI 0 LWPI SCHDWS * Initiate VDP chip LI R0,>1F0 Text mode BLWP @VWTR SWPB R0 MOVB R0,@>83D4 VDP R1 copy LI R0,>717 Text mode color register BLWP @VWTR BL @CLRSCR * Move monitor ROM to expansion RAM. * This allows changing of any byte in the normal OS, here the interrupt vector. LI R12,MEMCON CRU address of expansion memory control SBO 1 Enable RAM @>4000->5FFF CLR R0 Source pointer LI R1,>4000 Destination pointer LI R2,>2000 Byte counter MVLOP1 MOV *R0+,*R1+ DECT R2 JNE MVLOP1 SBO 0 Enable RAM @>0000->1FFF LI R0,>4000 Source pointer CLR R1 Destination pointer LI R2,>2000 Byte counter MVLOP2 MOV *R0+,*R1+ DECT R2 JNE MVLOP2 SBZ 1 LI R0,SCHDWS Address of scheduler WS MOV R0,@IVECT1 LI R0,SCHED Address of scheduler routine MOV R0,@IVECT1+2 * Set up PSI for internal clock interrupt, and nothing else. CLR R12 SBZ 0 Interrupt mode SBZ 2 Disable VDP interrupt SBZ 1 Disable external interrupt LI R1,>03AB Counter value for 10ms interrupt LDCR R1,15 Automatically enters clock mode with this data SBZ 0 Out of clock mode SBO 3 Enable clock interrupt * To start the whole thing up, some task, here task A, must be "restored" from * the beginning, although it hasn't yet executed. Both save areas are initiated * with values that will allow the tasks to start from their own starting points. MOV @CURTSK,R1 Load values to restore for first task MOV *R1+,R13 WP MOV *R1+,R14 PC MOV *R1+,R15 ST RTWP Faked restore ***** * * Task A * WSA BSS 32 Task A workspace TASKA LI R1,'A ' LOOPA2 LI R0,>8000 Delay count LOOPA1 DEC R0 JNE LOOPA1 BL @CHOUT JMP LOOPA2 * Task B WSB BSS 32 Task B workspace TASKB LI R1,'B ' LOOPB2 LI R0,>2000 Delay count LOOPB1 DEC R0 JNE LOOPB1 BL @CHOUT JMP LOOPB2 ******** * * Scheduler routine SAVEA DATA WSA,TASKA,1 Task A savearea. Interrupt mask is 1 SAVEB DATA WSB,TASKB,1 Task B savearea. Interrupt mask is 1 CURTSK DATA SAVEA Pointer to save area for currently running task READYQ DATA SAVEB Pointer to save area for task ready to run SCHDWS BSS 32 Scheduler workspace SCHED SBO 3 Clear interrupt condition MOV @CURTSK,R1 Swap off current task MOV R13,*R1 WP MOV R14,@2(R1) PC MOV R15,@4(R1) ST MOV @READYQ,R2 Swap on ready task MOV *R2,R13 WP MOV @2(R2),R14 PC MOV @4(R2),R15 ST MOV R1,@READYQ Alter task bookkeeping MOV R2,@CURTSK RTWP Return from scheduler END-
3
-
-
Yes, I'm familiar with the two different principles for concurrent processing. Real time processing was kind of my speciality at university, and I still do it for work.
The problem with volountary task switching is of course when something enters a loop that's longer than expected, maybe even eternal. Then you are dead on the water.
The UCSD p-system for the 99/4A supports voloutary task switching (you wait on a semaphore, or something like that, and get a switch). What I did there was expand it to pre-emptive switching. Unfortunately, they had made some shortcuts in the heap management code, now when they expected volountary switching only, so heap operations crashed when I tried to use them with pre-emptive switching. Without the source code to unit HEAPOPS, it's virtually impossible to figure out what's going on, and even if you do, by disassembling the p-code, there's no real way to create a corrected unit HEAPOPS without access to the source for unit KERNEL.
Since dynamic memory allocation is a key feature in such a concurrent system, I gave up there. But as long as I stay away from dynamic memory allocation, it does work.
-
When XB loads a XB program it moves the Line Number Table...
Yes, you're right. I forgot that the line number table also moves to 24 K RAM. But apart from that, it was correct.
-
When you do have the memory expansion together with XB, then 24 K RAM segment will be used for code lines and numeric variables.
The rest, like the symbol table, string variables, line number table, return stack and scratch pad for string manipulations etc. is still in VDP RAM. You also have disk I/O buffering there.
The scratch pad employs garbage collection, so it will eventually use all available VDP RAM, then collect the unused garbage and start over again. Hence there's not really any corner of VDP RAM it will not touch, when it has been running for a while.
The 8 K RAM part of the expansion is reserved for assembly language support for XB.
-
1
-
-
In my independent multi tasking scheduler (not when using the p-system), I used the TMS 9901 as the timer to invoke the scheduler. But since I have a console with 64 K RAM internally, I can overlay console ROM when I want to. Thus I can change the interrupt vector in the system to point to my own interrupt service routine. That allows me to create the most efficient task scheduler possible, since the TMS 9901 hardware will do the counting and the interrupt will invoke the scheduler when it triggers.
Then it's just the standard issue of keeping track of running and waiting tasks.
In the p-system I used a different trick. Since the interrupt service routine there partially runs in RAM, you can easily modify it. The p-system is also cartridge independent, since the p-system operating system is on a card in the PEB, not in a cartridge in the cartridge slot. Thus you can for example plug in Mini Memory when running the p-system. So I inserted a wedge in the standard interrupt service in RAM, went to a routine in Mini Memory and then returned to the standard interrupt service. The part in the Mini Memory RAM does the task switching. There are also some support routines to implement monitors and some other stuff, that's convenient to have in a pre-emptive multitasking system.
-
Well, here's something to look at.
Internal 64 K RAM memory expansion, CRU I/O bank enabling and VDP access delay logic. Load interrupt and reset switches also included.
https://goo.gl/photos/do8zDtjpNnr7Z1ca8
Peripheral expansion box with real time clock and digital/analog I/O board. Later, cards with real time clock, like CorComp's Triple Tech, became available, but there was nothing to buy when I built my clock card.
https://goo.gl/photos/op1NZnKRfvuu86dJ6
Analog joystick with buttons, 7-segment digits and LED. It's not a modification of the 99/4A itself, but connects to it. Inside there's a dual port 4*4 bit memory, which holds the numbers to show on the display, LED status and controllable leading zero ripple blanking for the display. This is accessed by a four bit data bus, two bit address bus and one WE signal from the computer. The rest of the wires are for power supply and analog signals from the joystick. The electronics inside the joystick are built on three separate PCBs, stacked on top of each other.
-
2
-
-
You have understood that Extended BASIC can't run at the same time as the screen is in Bitmap mode, right? XB uses a lot of VDP RAM resources for it's own stuff, even if you have the 32 K RAM expansion in place. The disk drive system also uses VDP RAM for buffering data being read or written.
It's possible to start XB, run an assembly program which uses bit map mode and die from there.
When you want to start XB, run assembly with bit map mode and then return to XB, you must make sure you save the XB environment in VDP RAM before start fiddling with creating the bit map mode. Otherwise you can't return.
Since bit map mode requires approx. 12 K bytes in VDP RAM, it's quite a lot of data to hide elsewhere. In a system like mine, which has 96 K RAM instead of 32 K, that's not a problem, but it is in a standard 99/4A.
You can dump it as memory images to files on disk, but you have to do that while you are still in the standard graphics mode, or you'll literally see the disk file system working on the screen.
-
Correct, the 9995 does not need the read-before-write because it has a byte-oriented data bus. This has an advantage only for byte writes.
No, because to keep the internal structure of memory accesses with the TMS 9900 as simple as possible, memory access is done the same for byte and word operands. So there's a read before write for word operands too. This is the reason for the TI 99/4A never having autoincrementing memory mapped I/O at the same address for reading as for writing.
The TMS 9995 also has internal memory on a 16-bit bus. This is equivalent to the RAM pad in the 99/4A. The internal memory is typically used for workspace and other frequently used parts of code and data.

Trying to learn TI programming
in TI-99/4A Computers
Posted
The reason for the dual formats is, as you may have figured out by know, the buffer size, that limits the file size when saving as a memory image file. When you have the memory expansion with Extended BASIC, you can write programs larger than will fit in the memory image buffer, so they have to be stored as a file with a number of records, just like "ordinary" data files.