Jump to content


+AtariAge Subscriber
  • Content Count

  • Joined

  • Last visited

Everything posted by TheBF

  1. I have not used the Pascal system on TI but I am wondering if the documentation explains stack usage per process? Could it be that the stack size is set somewhere at start up and its a shared stack space that gets consumed in chunks by each process? This might leave your print code with nothing left to format a number to text. Full on conjecture here.
  2. Ok, so I see some cool stuff in languages like Python and LISP where you can chop up a string in many sub-strings all at once. I wondered can I used this fast string cutting method to do the job. The method I used is borrowed (stolen) from an old Forth word called -TRAILING. This word takes an [address,length] pair as input and is scans backwards on the string looking for blank characters (ie: spaces ASCII 32) . If it finds a space, it shortens the string length and keeps going until it finds a non-blank character. Simple. So I created a routine that does that and I created the opposite. Meaning scan backwards on a string, ignoring non-spaces until you find a space. I call that one -ALPHA With those 2 routines we combine them to create /WORD ("cut word") This routine cuts a word off the end of a string delimited by blanks but it also re-sizes the remainder string. It uses some ugly stack gymnastics to calculate the new string lengths for both strings so unless you are really bored just assume it works. Armed with /WORD we can put this in a loop call SPLITSTR. This word cuts off a string starting from the end and leaves each sub-string on the stack as an [address,len] pair It also counts how many times it does this until the last word is found and puts that count on the top of the stack. So we are left with a variable number of strings on the stack with a count on top. The code is in the spoiler and the GIF lets you see how quickly we can get the results and even print them reversed.
  3. CUTTING STRINGS FASTER One the things that BASIC offers the programmer is a handy string data type. Along with the string data type itself there are a set of functions to cut the strings into various pieces and find strings within other strings. Many implementations of strings rely on copying the sub-string to a string stack or to another string. Copying bytes back and forth in memory eats up a lot of time. This is even more serious in TI-99 BASIC where the strings are held in VDP memory. What if, when you cut a string, you did not copy it? How would that work you ask? One way that has entered the Forth world is to simply record the memory location of the new sub-string and its length in a temporary location. For example take the string “All my ex’s live in Texas”. Let us say that this string is currently at address >3000 in low memory and it is 25 bytes long plus 1 byte for the count. In ASM it would look like this: ADOLL BYTE 25 TEXT ‘All my ex’s live in Texas’ In Forth it would be this: : A$ ( -- addr len) S” All my ex’s live in Texas” ; The word ADOLL and A$ in both cases are the labels (name) of the strings. So the way we could represent ADOLL is to just use the address and the length. In ASM this would be in registers typically and in Forth they would be on the DATA stack. For this discussion the numbers would be >3000,25. With this representation, If we wanted to do a LEFT$ operation STRING all we have to do is change the number 25 to the new length. That’s it. No copying and we have created a new string! In Forth we can make LEFT$ like this: : LEFT$ ( $addr len newlen – $addr len) NIP ; STRING 12 LEFT$ The Forth word NIP removes the second item on the DATA stack. One command is all we needed to create the function LEFT$. The output of LEFT$ falls onto the DATA stack where you can print it or save it or whatever you want to do. And RIGHT$ is not much harder: : RIGHT$ ( $addr len newlen – $addr len) OVER SWAP - 0 MAX /STRING ; The word /STRING (pronounced “cut string) above is a surprisingly powerful yet very simple ANS Forth word that takes an (address, len) pair and a number. It subtracts the number from the length and adds the number to the address. This gives us a new string address starting point and a new length. We add the 0 MAX to prevent a negative number as a size. /STRING DEMONSTATION CODE A$ TYPE All my ex’s live in Texas A$ 4 /STRING TYPE my ex’s live in Texas In conventional notation /STRING does this: DEF CUTSTRING (ADDR,LEN,N) ADDR=ADDR+N LEN=LEN-N What we did above was equivalent in conventional notation to: DEF RIGHT$(ADDR,LEN, NEWLEN) NEWLEN=LEN-NEWLEN CUTSTRING (ADDR,LEN,NEWLEN) (These Forth functions return 2 outputs so they don’t fit well with functional notation. The examples are here to help translate the Forth reverse polish notation and stack movement which can make you feel dizzy when you are not used to it) You can easily see how this is much, much faster than copying the bytes to a new string or buffer. There are no loops and no copying. In ASM you could transfer these new values to new registers or a better way would be to implement a stack using a register as a pointer and pushing the new values onto the stack for later use. Forth uses its own DATA stack for this purpose and as such the output of each string manipulation can be the input for the next string function. Consider this expression where the output of LEFT$ is on the stack waiting for RIGHT$. Then TYPE takes the address and length from RIGHT$ and prints it on the screen: A$ 20 LEFT$ 5 RIGHT$ TYPE Using this technique we can re-create TI BASIC’s SEG$ with only four words! : SEG$ ( addr len start size -- addr len) >R \ push the size onto the rstack temporarily /STRING \ cut the string using the start parameter DROP \ NOT going to use the length as is, so drop it R> \ move the SIZE parameter back onto the data stack ; So if you do strings this way you do all the processing with the address and len and only copy back to memory when you are finished the string manipulation. This makes a very fast system. In the next installment we will use this method to do something you might see in LISP or Python and it runs pretty fast even on our TI-99.
  4. I did not LOL. I will edit it. It's those mornings...
  5. Just an observation on different language paradigms. Not to derail your topic. I managed a relatively large Turbo Pascal program back in the late nineties. I love the language from a theoretical perspective. When your code is done well and complete it just looks ... right. I remember a computer magazine years ago having a "centre fold" in the Apr 1 issue. The picture was a red satin sheet, with a Pascal program listing on paper spread out on the satin. It's like that. :-) But as I consider all your thoughts on these details II am wondering are you fighting to fit the program to the rigid Pascal compiler. This kind of thought process has become less palatable to me now that I play with Forth. The compiler does so little so that you can /must make it what you need. This so shameful from the Wirth perspective and yet it is quite liberating for the programmer. The caveat being you have to think more like a language writer before you start writing the actual program code. However Pascal program structure is quite similar (forward references were not preferred by Wirth) so maybe it's not that different. Morning coffee thoughts. Wish I had more to say about your specific question. Guess my answer at a high level is I try to encapsulate based on the way I would talk about the program. i.e. the subprogram/functions try to mirror my spoken language. After that I don't have a proscribed prescribed methodology. (Correction courtesy professor Stewart)
  6. I have always thought that shying away from global variables because the are global was a red herring. If all globals are bad all the time we should never use a file. It''s accessible to the entire program too. If the program requires a global variable that records a specific state for the program, what's the harm if it is used for the designed purpose? In Pascal it is even nicer in that you can encapsulate access to that global variable to a specific function or subprogram that provides appropriate protections no?
  7. Is there any space in the world for an intellectual property like yours for the CPU with VIDEO processor, some other I/O, some on chip very fast RAM for registers sets and perhaps a small RTOS ? Selling it as a realtime platform? I ask because the simple 9900 has very repeatable timing and many processors now do not. And using the RTWP based context switcher that I played with creates a very fast task switcher. Or has ARM sucked up all that air in that space? (been out the electronics field as a job for 16 years) :-)
  8. 136,000 instructions per second. So sad and yet we love our 9900 just the same. So your new machine full tilt should come in at over 4MIPS based on some of your other posts. That's a nice number.
  9. VDP Strings exploration ​​Continuing with our VDP memory management tools, we can go further and make strings that are similar to BASIC. ​And just for fun we will add a few commands to make using the string look more like BASIC too. Lets make a new command call "DIM" that takes a size input number and text name and makes space for string in VDP memory: : DIM ( n -- ) CREATE VHERE , \ compile time: compile the DP address into the Forth word VALLOT \ allocate 'n' bytes of VDP ram. DOES> @ ; \ run time: fetch the VDP address ​ ** EDIT ** ​Although the code above works just fine, it is not optimal. For those whose looked closely you can see that DIM is actually a constant the gives us ithe address of VHERE at compile time, ​with the addition of VALLOT. Forth systems typically optimize the word CONSTANT in assembler to get a value to the top of stack very fast. ​CAMEL99 Forth is no exception so... a faster implementation is to do this: : DIM ( size -- ) VHERE CONSTANT VALLOT ; We store VHERE as a fast constant and allocate the size from the number of the stack in VDP RAM. Testing showed the 1st version takes 210uS when compiled. The second version takes 105uS. Much better performance and we save space too!. We can also use a VDP version of the Forth word COUNT which takes a counted string address and extracts the length and adds 1 to the address to point to the start of the text. So here is VCOUNT. The only difference form the standard word is VCOUNT uses [email protected] to read the length byte from VDP RAM. This we be used for printing strings. : VCOUNT ( vdp$adr -- vdpadr len ) DUP 1+ SWAP [email protected] ; \ convert counted string to addr,len So to make a string the syntax is now: 80 DIM A$ 80 DIM B$ Not quite BASIC but you could work with it. We need a way to get text into these strings. The standard word PLACE takes an address and length of a string and "PLACEs" it at a 2nd address as a counted string. We will make VDP version called VPLACE. : VPLACE ( $adr len VDP-adr -- ) 2DUP VC! CHAR+ SWAP VWRITE ; \ place $adr len in VDP-adr So VPLACE will take a string in CPU RAM and PLACE it in VDP ram. Now for a little syntax candy. We can PARSE the console input with word called ... PARSE .. what else? PARSE returns Address and length of the parsed string. How convenient! Let's do this: \ collect characters up to final " and put in VDP memory : =" ( VDP$ -- ) [CHAR] " PARSE ROT VPLACE ; So now we have made syntax for working with our VDP strings that looks like this: 80 DIM A$ 80 DIM B$ A$ =" Now is the time for all good men " B$ =" to come to the aid of their country." And finally we need to print these strings. Forth has word called TYPE so we can just emulate that word, but read the bytes from VDP RAM. VTYPE takes the address and length, calculates the end and start address of the string with "BOUNDS" and then loops through the addresses, reading a VDP byte and EMITing to the screen. : VTYPE ( vdp_addr len -- ) BOUNDS ?DO I [email protected] EMIT LOOP ; To continue with BASIC syntax emulation we can make a word called PRINT, that starts on a newline. : PRINT CR VCOUNT VTYPE ; So with these words we have a simple string system in Forth that puts strings in VDP RAM. The GIF shows how it all works then prints A$ and B$ 100 times.
  10. Well of course these things make the programming look pretty, but there are overheads and I have the luxury of working with an unfinished system. With all the permutations you mention for the VDP modes it is probably best dealt with as you have done it. It's not truly a free block of memory that you can use and release. But if you lock the Graphics down like BASIC does these are great features to have in a TI-99 language. And for tutorial purposes it does show how one can extend Forth to work with different kinds of memory in a system. Part of my exploring this was to answer a personal question that I asked after I discovered Forth on the on 99 in the 1980s. Could TI have made a more responsive system if they had built it in Forth with code primitives? From what I see your work and Mark's and my own experiments, it would have been faster than GPL. That said to make it easily hook to other languages it might have required a byte code Forth system, which makes it one level slower than ITC but I think the virtual instructions per second would still be faster than GPL seems to do and with fast code primitives to support it I am pretty much convinced it would be faster. All that deep thinking plus 5 bucks is enough to get you a big fancy coffee at Starbucks. :-)
  11. Creating VDP Constants ​Now that we know how to make a VDP variable in Forth it becomes trivial to create a constant that is stored in VDP RAM. A variable remember, records a '0' in VDP RAM and simply returns the address where it is stored. So VDP constant would need to take an input number and store that rather than '0' and... it would need to get that value and return it to Forth when we use it in a program. Here's how we do it. : VCONST: ( n -- <text>) CREATE VHERE , ( n) V, \ compile time: VDP address in CPU RAM, put n in VDP RAM DOES> @ [email protected] ; \ run time: fetch the VDP address, \ then fetch the const from that VDP address ​The screen capture shows how we could use them.
  12. Managing other TI-99 Memory Spaces Using these concepts it becomes super easy to make a set of Forth words to manage the VDP memory as well. All we need to begin is a VARIABLE to keep track of the last used memory address and then we create a set of words like we have for CPU RAM but built for VDP RAM. Here is how that could look. \ Simple VDP RAM manager \ Works the same way as the Forth dictionary \ Best for static allocation VARIABLE VP \ VDP memory pointer (4K block) HEX 1000 VP ! \ set the start of free VDP RAM : VHERE ( -- addr) VP @ ; \ FETCH the value in VDP pointer : VALLOT ( n -- ) VP +! ; \ add n to the value in VDP pointer Now we have VHERE which gives us the next free VDP address and VALLOT which lets us allocate VDP memory. Super simple. So what can we do with that? We can create our little number compilers like we made for Forth CPU memory. We will call them V, and VC, so we remember that they only work in VDP memory. \ VDP number "compilers". Put a number or character in VDP RAM \ and advance the VP pointer by the proper amount (2 or 1) : V, ( n -- ) VHERE V! 2 VALLOT ; : VC, ( n -- ) VHERE VC! 1 VALLOT ; So what does that give us? Well how about creating VDP variables? : VAR: ( -- <text>) \ accessed with [email protected] and V! CREATE VHERE , \ compile time: compile VHERE address in CPU RAM, ​ 0 V, \ put '0' in VDP RAM at that address DOES> ( -- addr) \ run time: DOES> returns the address after create ​ @ ; \ so we fetch the contents of that address, ​ \ which is the VDP address we stored there at compile time. This uses a feature of Forth that can make some people confused. Ask questions if you need help. (hint: If you know object oriented programming, this is like creating an object that has only one method) The new word VAR: above creates new Forth words. This has 2 aspects: 1. What happens when we create the new word in the dictionary? This is called "compile time action" 2. What happens when the word we created actually runs in a program. This is called "run time action" #1 is handled by the code after the word CREATE. #2 is handled by the code after the word DOES> (in other words what the word "does" when you run it) So read the code for VAR: and see that after it "creates" a word in the dictionary, it compiles VHERE into Forth memory and then... it compiles a '0' into VHERE. which is a VDP address remember. (read that again if you need to). The word is recording 2 pieces of information: The VDP address where it put something AND it puts a zero in that VDP address. Usage of VAR: VAR: X VAR: Y VAR: Z So we have made 3 new Forth words X,Y and Z. When we type X, Y, or Z at the Forth console, or use then in a program what happens? The "DOES>" code will run for each one of them! When DOES> runs it returns the address in Forth (CPU) ram right after the name of the word. This is exactly where we put the VHERE address! How convenient! So all we need to do is 'fetch' what is in the address using the Forth word "@" and that returns to us an address in VDP RAM. That's what a Forth variable does. Gives us an memory address so we can put something there or read it. CAMEL99 has the word [email protected] which reads 16 bits from VDP RAM and V! which stores 16 bits in VDP RAM. These are like @ (peek) and ! (poke) in Forth memory space. ( Please note: TURBOFORTH also has [email protected] but it reads 1 byte, not 2 bytes as in CAMEL99 Forth ) ( CAMEL99 has [email protected] to read 1 byte from VDP RAM. ) So to use our new "VDP VARS" we would say: DECIMAL 9 X V! 1 Y V! X [email protected] Y [email protected] + Z V! Z [email protected] . Next we will create named VDP buffers and VDP strings.
  13. I have not tried. I am waiting for al the smoke to clear in this thread.
  14. ./elf.texi:11: raising the section level of @subsubsection which is too low make[3]: *** [Makefile:394: bfd.info] Error 1 make[3]: Leaving directory '/cygdrive/c/Users/Brizio/Retro/gcc-installer/build/binutils-2.19.1/bfd/doc' Making info in po make[3]: Entering directory '/cygdrive/c/Users/Brizio/Retro/gcc-installer/build/binutils-2.19.1/bfd/po' ( if test 'x../.././bfd/po' != 'x.'; then \ posrcprefix='../.././bfd/'; \ else \ posrcprefix="../"; \ fi; \ rm -f SRC-POTFILES-t SRC-POTFILES \ && (sed -e '/^#/d' \ -e '/^[ ]*$/d' \ -e "[email protected]*@ $posrcprefix& \\\\@" < ../.././bfd/po/SRC-POTFILES.in \ | sed -e '$s/\\$//') > SRC-POTFILES-t \ && chmod a-w SRC-POTFILES-t \ && mv SRC-POTFILES-t SRC-POTFILES ) ( rm -f BLD-POTFILES-t BLD-POTFILES \ && (sed -e '/^#/d' \ -e '/^[ ]*$/d' \ -e "[email protected]*@ ../& \\\\@" < ../.././bfd/po/BLD-POTFILES.in \ | sed -e '$s/\\$//') > BLD-POTFILES-t \ && chmod a-w BLD-POTFILES-t \ && mv BLD-POTFILES-t BLD-POTFILES ) cd .. \ && CONFIG_FILES=po/Makefile.in:po/Make-in \ CONFIG_HEADERS= /bin/sh ./config.status I have heard people say that Forth is a read only language... But all joking aside, installing Tursi's GCC port is worth enduring all the pain of this holy-crap complex installation. From what we have seen it generates excellent 9900 code. Well... you know, for a C compiler. (ok just a little joking)
  15. Is this because you are using an FPGA library ALU that is more efficient ?
  16. Thanks for this link. I was not aware of this method but I like it alot.
  17. Hey Lee, When you post Forth code, what language do you select to make the colors look like that?
  18. Here is how I rendered it in Camel99 Forth \ classic pipe & bar illusion by sometimes99er on Atariage GRAPHICS HEX CREATE SHAPES 0102 , 0408 , 1020 , 4080 , 8040 , 2010 , 0804 , 0201 , 8080 , 8080 , 8080 , 8080 , 80C0 , A090 , 8884 , 8281 , 8142 , 2418 , 1020 , 4080 , 0302 , 0408 , F000 , 0000 , E010 , 0804 , 0402 , 0202 , 0102 , 0408 , 0810 , 1010 , 1008 , 0804 , 0300 , 0000 , \ write character definitions to VDP RAM all at once : CHARACTERS ( 'pattern ascii# char-cnt -- ) 8* SWAP ]PDT SWAP VWRITE ; DECIMAL SHAPES CHAR 0 9 CHARACTERS : RUN PAGE CR ." 01" CR ." 0 1" CR ." 0 03 1" CR ." 0 0 21 1" CR ." 0 0 010 02" CR ." 0 0 0 0 0 2" CR ." 0 0 0 0 0 0" CR ." 0 0 0 0 0 0" CR ." 0 0 0 0 0 0" CR ." 760 0 0 0 0 " CR ." 85 0 0 0 0" CR ." 760 0 0" CR ." 85 0 0" CR ." 760" CR ." 85" BEGIN KEY? UNTIL ;
  19. That is really cool. Now I have to figure out how you did it!
  20. I can see the effect more on this one with green. Pretty nice job what you did in 8 lines.
  21. I got no effect on a small window so I tried full screen and then I get just the beginning of the effect. (but very slight)
  22. Sorry dude. I completely missed that one. I did not run it and saw this other image on a youtube video. You got there first.
  23. Thanks for this. Apologies for not replying faster. You are correct, I could play with the numbers to get what I want. I have been using a multi-tasking system for my Forth system and I re-created the ISR in a little task. Forth handles all this stuff like Assembler so I too use CPU RAM for my position and motion tables and the write the block of sprite positions to VDP RAM all at once. So I am on the right track there at least. :-) I have been reviewing the ISR code and looking at the debugger to see how it works. Here is what I think I see: 1. The Motion values are signed bytes. Positive: 0 to 7F, Negative: 80 to FF 2. The Aux values, beside each motion pair, are incremented every tick of the interrupt, by the amount in the motion value These are the little counters that run for each sprite's X and Y value. 3. When the AUX value rolls over the sprite is moved 1 pixel. So bigger values of Motion cause the AUX register to rollover faster of course therefore those sprite moves faster. So at this stage I am debating how best to handle this in Forth just to see what I can do without writing an ASM version. Forth deals with ints better than bytes so I am thinking that I will use 16 bit tables for motion and timers (AUX) and just shift the motion values to upper byte. Since everything is multiplied by >100 it will all count the same speed as using bytes, but my Forth operators will be faster for 16 bit values. That's all I have at this time. Thanks again for confirming my thoughts on the matter.
  24. You're a mutant. Call professor X
  • Create New...