Jump to content
IGNORED

IntyBASIC compiler v0.9 recharged! ;)


nanochess

Recommended Posts

Hi.

 

I went ahead at full speed for IntyBASIC v0.9 with new powerful features to ease programming, in order to help to the many programmers that are using actively IntyBASIC: Cybearg, catsfolly, freeweed, atari2600land, Tarzilla, grips03 and many other people. (put a message in this thread if I missed you!)

 

Most suggestions made it and some really needed features: ;)

  • Added ON [expr] GOTO/GOSUB statement (to alleviate the need for dozens of IF statements and it's faster!)
  • STACK_CHECK statement to verify your code on the go for unwanted recursion (GOSUB without RETURN)
  • INCLUDE statement to separate your code/graphics/music in different files (needed for bigger games!)
  • DEFINE statement with expressions as parameters. (atari2600land suggestion)
  • Fixed numbers and two fixed operators for playing with them (+. and -.) (GroovyBee suggestion)
  • Multiplication and division operators optimized for constant 256. (intvnut suggestion)
  • Remainder operator optimized for constants 32, 64, 128 and 256.
  • Extra comments in manual for SOUND, MUSIC, MODE and DEFINE. (freeweed suggestion)
  • IntyColor limited to 16 GRAM per block in order to support PLAY without modifications.

Thanks everyone for using IntyBASIC, it feels great to contribute! :)

 

Enjoy it!

 

intybasic_compiler_v0.9.zip

  • Like 7
Link to comment
Share on other sites

Thanks so much! Awesome!

 

When using fixed numbers, do they need to be defined as #variable, since they're 16-bit values, or just as variable, since the examples in the manual seem to imply this is possible?

 

A=1.5 Fixed number (translated to $8001, note high byte is fraction)

...
A=A-B Simple substraction
A=A+.B Fixed addition
Edited by Cybearg
  • Like 1
Link to comment
Share on other sites

Thanks so much! Awesome!

 

When using fixed numbers, do they need to be defined as #variable, since they're 16-bit values, or just as variable, since the examples in the manual seem to imply this is possible?

 

Woops! my mistake, anyway only in the manual text, of course the fixed numbers are 16-bits so they must be saved in variables starting with # unless you want to "cut" the integer fraction, somewhere in manual I said it's a good idea to copy 16-bits fixed numbers to 8-bits variables to make them integer, or using AND 255 or %256.

Link to comment
Share on other sites

Cooooool! You really have no idea how much this has facilitated some pretty advanced games in a short period of time. I've got 2 on the go, one of which really just needs some bug fixes and minor tweaks, the other which some of these changes will likely solve some convoluted logic I'm getting stuck in. Looking forward to sharing first looks soon; you guys seem pretty supportive of GoSub and willing to provide tons of advice and troubleshooting.

 

Another thing I can't remember if I've suggested - is it possible to have the SPRITE statement take an expression instead of a fixed value for the MOB number? I'm not sure how you've implemented this so I'm just guessing here.

 

Can't wait to tear into the ON GOTO stuff when I'm over this cold.

  • Like 3
Link to comment
Share on other sites

Hi.

 

I went ahead at full speed for IntyBASIC v0.9 with new powerful features to ease programming, in order to help to the many programmers that are using actively IntyBASIC: Cybearg, catsfolly, freeweed, atari2600land, Tarzilla, grips03 and many other people. (put a message in this thread if I missed you!)

 

Most suggestions made it and some really needed features: ;)

  • Added ON [expr] GOTO/GOSUB statement (to alleviate the need for dozens of IF statements and it's faster!)
  • STACK_CHECK statement to verify your code on the go for unwanted recursion (GOSUB without RETURN)
  • INCLUDE statement to separate your code/graphics/music in different files (needed for bigger games!)
  • DEFINE statement with expressions as parameters. (atari2600land suggestion)
  • Fixed numbers and two fixed operators for playing with them (+. and -.) (GroovyBee suggestion)
  • Multiplication and division operators optimized for constant 256. (intvnut suggestion)
  • Remainder operator optimized for constants 32, 64, 128 and 256.
  • Extra comments in manual for SOUND, MUSIC, MODE and DEFINE. (freeweed suggestion)
  • IntyColor limited to 16 GRAM per block in order to support PLAY without modifications.

Thanks everyone for using IntyBASIC, it feels great to contribute! :)

 

Enjoy it!

 

 

Fantastic work, Oscar! Thanks for all your effort! :)

  • Like 1
Link to comment
Share on other sites

One thing that I wanted to propose is adding support for "ROM segments." This is something that the as1600 assembler supports* and is offered in the SDK-1600 as part of the "CART.MAC" library of macros.

 

The library tries to abstract it by automatically keeping track of de facto memory maps. However, in IntyBASIC, you could allow the programmer to set his own. That way, the programmer can be in control of which memory blocks he will use--especially in light of multiple PCBs and Flash-carts becoming available now.

 

I'm thinking something like this:

    REM 4K segment
 
    ROMSEG $D000, $DFFF

And all code afterwards will be assembled within those addresses. The compiler could also take care of detecting overflows within the segment.

 

I do this today in P-Machinery v2.0 by checking the assembled location on each "ROMSEG" directive encountered (and at the end of the program), and triggering an assembler error on overflow. However, P-Machinery is a macro veneer over the assembler, I don't know if you could do the same from your compiler, since its a two step process.

 

-dZ.

 

 

* The "support" offered by the assembler is only the ability to set the address of assemblage with the ORG or RMB directives. All setup and checking would necessarily have to be done by the framework or compiler.

Edited by DZ-Jay
Link to comment
Share on other sites

Cooooool! You really have no idea how much this has facilitated some pretty advanced games in a short period of time. I've got 2 on the go, one of which really just needs some bug fixes and minor tweaks, the other which some of these changes will likely solve some convoluted logic I'm getting stuck in. Looking forward to sharing first looks soon; you guys seem pretty supportive of GoSub and willing to provide tons of advice and troubleshooting.

 

Cool! 8)

 

 

Another thing I can't remember if I've suggested - is it possible to have the SPRITE statement take an expression instead of a fixed value for the MOB number? I'm not sure how you've implemented this so I'm just guessing here.

 

I've thought about it but not so easy to implement, I'll add it to my TO-DO list.

 

 

Can't wait to tear into the ON GOTO stuff when I'm over this cold.

Chicken soup and hot tea! ;)

Link to comment
Share on other sites

One thing that I wanted to propose is adding support for "ROM segments." This is something that the as1600 assembler supports* and is offered in the SDK-1600 as part of the "CART.MAC" library of macros.

 

The library tries to abstract it by automatically keeping track of de facto memory maps. However, in IntyBASIC, you could allow the programmer to set his own. That way, the programmer can be in control of which memory blocks he will use--especially in light of multiple PCBs and Flash-carts becoming available now.

 

I'm thinking something like this:

    REM 4K segment     ROMSEG $D000, $DFFF
And all code afterwards will be assembled within those addresses. The compiler could also take care of detecting overflows within the segment.

 

I do this today in P-Machinery v2.0 by checking the assembled location on each "ROMSEG" directive encountered (and at the end of the program), and triggering an assembler error on overflow. However, P-Machinery is a macro veneer over the assembler, I don't know if you could do the same from your compiler, since its a two step process.

 

-dZ.

 

 

* The "support" offered by the assembler is only the ability to set the address of assemblage with the ORG or RMB directives. All setup and checking would necessarily have to be done by the framework or compiler.

It's a good idea and I've thought seriously about it and trying to make it automated.

 

What refrains me is that I don't want to replicate an assembler inside my compiler (probably the only way to calculate how many space uses the code) but I could keep a count of words per instruction.

 

This way the user would know before assembly if his/her code exceeds the segments. Still in my TO-DO list ;)

 

Currently the only way is to use ASM ORG as suggested in manual, and check the generated assembly listing manually to verify if segments have been exceeded.

Link to comment
Share on other sites

a DIM question for Nanochess.

 

If I DIM a variable like


Intermission: Procedure
DIM SPRITEX (
DIM SPRITEY (
Rem Do the intermission stuff
.
.
END

then come back and call this intermission again later in the game , does the DIM just re-Dim the existing memory? or am I asking for a memory leak?

Edited by Tarzilla
Link to comment
Share on other sites

Stupid question, but why not DIM your array at the very start of your program, to avoid any possible weirdness? Your question is actually a very good one, but to be safe I always try to "predeclare" every variable. This way you're never surprised by side effects.

 

I suppose with arrays you might want to clear them out every time you run this subroutine, and that costs time if you have to iterate through the array blanking it out - perhaps DIM is faster. Although that does open up another question: does DIM actually zero out the memory? This is not always reliable depending on hardware and compiler implementation...

 

Damnit, now I want to mess around with a bunch of test code and see what happens.

Link to comment
Share on other sites

Stupid question, but why not DIM your array at the very start of your program, to avoid any possible weirdness? Your question is actually a very good one, but to be safe I always try to "predeclare" every variable. This way you're never surprised by side effects.

 

I suppose with arrays you might want to clear them out every time you run this subroutine, and that costs time if you have to iterate through the array blanking it out - perhaps DIM is faster. Although that does open up another question: does DIM actually zero out the memory? This is not always reliable depending on hardware and compiler implementation...

 

Damnit, now I want to mess around with a bunch of test code and see what happens.

 

In the past I would have done pre-declare stuff at the beginning, but now that Include files are supported I want to isolate things in Include files together so anything related to my title screens is in one "title.bas" and not have to worry about forgetting the DIM statement at the top of the main file. This way I can build up a set of library routines.

Link to comment
Share on other sites

 

In the past I would have done pre-declare stuff at the beginning, but now that Include files are supported I want to isolate things in Include files together so anything related to my title screens is in one "title.bas" and not have to worry about forgetting the DIM statement at the top of the main file. This way I can build up a set of library routines.

 

Not a bad idea. Especially when you're talking intermission screens - something we have the luxury to waste space on these days :)

Link to comment
Share on other sites

Cool! 8)

 

 

I've thought about it but not so easy to implement, I'll add it to my TO-DO list.

 

 

Chicken soup and hot tea! ;)

 

Actually, if you're using the stock cart.mac, you can use the ROMSEGSZ directive in your code generator to move bits of code and data to the ROM segment that has room for them.

 

That is, if you generate your code in chunks and know how many words that chunk takes, you can use ROMSEGSZ to tell cart.mac "move me to a segment with enough room for 'size' words."

 

I guess with a traditional BASIC program, you don't have many straightforward "clean break" boundaries to do that with. But, any block of code that has a "clean entry" (meaning other points in the code GOTO or GOSUB it, but nothing falls-through to it) and a "clean exit" (meaning the last instruction is an unconditional GOTO or RETURN) is a candidate for blocking via ROMSEGSZ.

 

That should make it really easy to push games past the 8K words (16K bytes) boundary.

 

Also, data vs. code should be easy to separate as well.

 

Right now, cart.mac supports a limited number of memory maps, but it was designed to be very flexible. The 16K memory map supports games up to 16K words (32K bytes) with ROM at $5000 - $6FFF, $D000 - $DFFF, and $F000 - $FFFF. The 42K memory map adds several ROM segments to get the total ROM memory map up to 42K words (84K bytes) and 8000 words of RAM. You could modify that to be all ROM (50K words of ROM!) or come up with other memory maps. (5/6/9/A/B? 5/6/D/E/F? etc.)

 

I have alpha support now in AS1600 and the development version of cart.mac for page-flipped memory. That's perhaps beyond the current IntyBASIC. But if someone wants to build a truly huge IntyBASIC game, I'd be happy to help make that happen.

 

I do need to get this latest AS1600 out there along with jzIntv. There are other fixes in jzIntv that need to get out there, including improved PAL timing accuracy.

  • Like 1
Link to comment
Share on other sites

Oooook. While y'all discuss fun advanced stuff, I'm suddenly reverting to a 1st year comp sci student. And not a very good one at that. So, question for nanochess (or anyone else that is reading this and thinking "man, freeweed, this is grade school shit!"):

 

Your sample code has very convenient PRINT routines for drawing a value to the screen (typically a score or other counter). And they work great with small values (8 bit). However, try using them on a 16 bit value and wow, do you ever start chewing up cycles with the division and remainder calculations. It's pretty obvious how much work is being done under the covers. I guess I've been spoiled because I'm used to either hardware that does it for me, or compilers that implement much quicker integer divisions.

 

Multiplication and division are trivial to optimize for powers of 2, using shifts. My brain has always grokked this very intuitively. And a mults by other factors are pretty trivial to implement with some adds. Division.. gets complicated. A lot of efficient solutions for 16 bit divisions rely on using 32 bit variables, which is out (unless I want to make this more complicated than it needs to be). And others get into loop structures etc. That can bog down a machine depending on exactly what is more or less efficient in the instruction set, but even if it doesn't, it's a lot of code to just plop in there. I suppose a GOSUB could make it slightly less onerous but I don't know how to pass and return parameters into procedures this way.

 

Please tell me I'm missing something very obvious here. I have to assume that I'm not the first person who wants a score counter higher than 255 displayed on the screen in decimal. Do people really write these routines into every program?

 

I feel kinda stupid for even asking but my brain is telling me this is much simpler than I'm making it out to be, yet it won't tell me the solution. Oh, and if it's some stock answer - could you build this into the next version of the compiler? :)

  • Like 1
Link to comment
Share on other sites

Your sample code has very convenient PRINT routines for drawing a value to the screen (typically a score or other counter). And they work great with small values (8 bit). However, try using them on a 16 bit value and wow, do you ever start chewing up cycles with the division and remainder calculations. It's pretty obvious how much work is being done under the covers. I guess I've been spoiled because I'm used to either hardware that does it for me, or compilers that implement much quicker integer divisions.

 

I just downloaded the 0.9 BASIC compiler to look for what it's implemented for printing numbers. I didn't find anything, but I may have just overlooked it.

 

SDK-1600 does provide 16-bit and 32-bit decimal print routines that use repeated subtraction for various powers of 10. (ie. it computes the 10000s place by subtracting 10000 repeatedly. That happens at most 9 times, and the subtract/iterate is way faster than a modulo or divide.)

 

These routines are actually fairly efficient, and are in the public domain. They offer left-justified, right-justified, and zero-filled formatting. I used a version of PRNUM16 in Space Patrol (and Pumpkin Spice Patrol), and it was plenty fast in that cycle-starved game.

IntyBASIC is free to use these routines as they are public domain.

  • Like 1
Link to comment
Share on other sites

Stupid question, but why not DIM your array at the very start of your program, to avoid any possible weirdness? Your question is actually a very good one, but to be safe I always try to "predeclare" every variable. This way you're never surprised by side effects.

 

I suppose with arrays you might want to clear them out every time you run this subroutine, and that costs time if you have to iterate through the array blanking it out - perhaps DIM is faster. Although that does open up another question: does DIM actually zero out the memory? This is not always reliable depending on hardware and compiler implementation...

 

Damnit, now I want to mess around with a bunch of test code and see what happens.

 

In old-school BASIC "DIM" could only be done once per variable, as it allocated the memory and defined the symbol. Calling it again on the same variable was an error. To re-allocate (and re-size array as necessary), we used "REDIM". I think Microsoft BASIC introduced that.

 

-dZ.

Link to comment
Share on other sites

 

SDK-1600 does provide 16-bit and 32-bit decimal print routines that use repeated subtraction for various powers of 10. (ie. it computes the 10000s place by subtracting 10000 repeatedly. That happens at most 9 times, and the subtract/iterate is way faster than a modulo or divide.)

 

These routines are actually fairly efficient, and are in the public domain. They offer left-justified, right-justified, and zero-filled formatting. I used a version of PRNUM16 in Space Patrol (and Pumpkin Spice Patrol), and it was plenty fast in that cycle-starved game.

IntyBASIC is free to use these routines as they are public domain.

 

Hm. I'll take a peek. This is what the IntyBASIC manual says about division:

 

A=A/B Note it does division by repeated substraction (can be slow)

Division by 2/4/8/16 is internally enhanced as logical shift.

Division by 256 is internally optimized.

 

"can be slow" could be interpreted as "9 subtractions is slower than a single shift". Or it could be horrendously slow when operating with big numbers (think 5 digit). The latter is what I see, when trying to break down a value into its component digits. It can literally take almost a full second to work out the digits in a score once the value gets high enough (tested on code that does nothing but this and printing the result to the screen).

 

Because I'm a purist I was avoiding any inline ASM, but I'm gonna take a peek at these routines. What I want is completely infeasible otherwise. Thanks!

 

Also I think that EVERY div/mult by ANY power of 2 should be optimized as a shift by a compiler, but that's just me :)

Link to comment
Share on other sites

Also I think that EVERY div/mult by ANY power of 2 should be optimized as a shift by a compiler, but that's just me :)

 

Of course! I think intvnut and nanochess make a distinction for the constant 256 because this can be further optimized by performing a byte-swap + logical and:

    ; R0 contains constant to divide
    SWAP  R0,     1     ; \_ Divide by 256
    ANDI  #$00FF, R0    ; /

The SWAP instruction does what it says on the tin: swaps the low and high order bytes of a register.

  • Like 1
Link to comment
Share on other sites

I just downloaded the 0.9 BASIC compiler to look for what it's implemented for printing numbers. I didn't find anything, but I may have just overlooked it.

 

Oh, and here's what I was talking about initially. From the manual:

 

PRINT [AT [expr],]expr[,expr]

 

Pokes 12-bits value directly into screen, useful for variable things and GRAM

cards.

 

Example. Printing a digit in yellow:

 

PRINT (DIGIT+16)*8+6

 

And from some of the sample code:

 

print at 0,(#score/100%10+16)*8+6

print at 20,(#score/10%10+16)*8+6

print at 40,(#score%10+16)*8+6

 

He's using this /10%10 routine to isolate the digit, adding 16 to get the GROM card #, shifting it over 3 bits for the register, and OR'ing with 6 just to set it to yellow. It works pretty good for 8 bit values, but falls apart once you're into 5 decimal digits.

Edited by freeweed
Link to comment
Share on other sites

 

Hm. I'll take a peek. This is what the IntyBASIC manual says about division:

 

A=A/B Note it does division by repeated substraction (can be slow)

Division by 2/4/8/16 is internally enhanced as logical shift.

Division by 256 is internally optimized.

 

"can be slow" could be interpreted as "9 subtractions is slower than a single shift". Or it could be horrendously slow when operating with big numbers (think 5 digit). The latter is what I see, when trying to break down a value into its component digits. It can literally take almost a full second to work out the digits in a score once the value gets high enough (tested on code that does nothing but this and printing the result to the screen).

 

Because I'm a purist I was avoiding any inline ASM, but I'm gonna take a peek at these routines. What I want is completely infeasible otherwise. Thanks!

 

Also I think that EVERY div/mult by ANY power of 2 should be optimized as a shift by a compiler, but that's just me :)

 

Well, it appears IntyBASIC implements general division by repeated subtraction, subtracting the divisor from the dividend repeatedly, like so:

    ; B = 1
    MVII #1,R0
    MVO R0,V1
    ; A = 30000 / B
    MVII #30000,R0
    MVI V1,R4
    MOVR R0,R5
    TSTR R4
    BEQ T1
    MVII #-1,R0
T2:
    INCR R0
    SUBR R4,R5
    BC T2
T1:
    MVO R0,V2

This particular division would compute 30000/1 as 30000 separate subtracts in a 21 cycle loop. That's 610,000 cycles, and would take 2/3rd of a second. Ouch!

 

What my PRNUM routine does is repeated subtraction with various powers of 10. The number 99999 would only require 50 "subtract" steps, 10 steps for each digit. My subtraction steps are more costly than 21 cycles each, but still, it manages to get a number on the display pretty quickly, in 100s of cycles, not 100,000s of cycles.

 

SDK-1600 also includes DIVI and DIVU routines for signed and unsigned division. IntyBASIC is free to use these routines if nanochess wants to pick them up. That 30000/1 divide earlier would execute in about 1200 cycles or less, about 500x faster.

Ironically, I don't have a multiply routine directly in SDK-1600, probably because multiplies are usually by constants, and it's better to reduce those to the minimal sequence of shifts/adds inline than call a library function. Attached is a macro file with multiplies from 1 to 127, and C code that was used to generate it. Anyone is welcome to incorporate this into their code. nanochess: If you want to include this algorithm in IntyBASIC (even this C code), go for it. I'm putting it in the public domain.

 

 

mpyk.mac.txt

mult_by_constant.c.txt

  • Like 1
Link to comment
Share on other sites

The Bee3 BIOS has BCD conversion acceleration ;).

 

Got any programming specs to share publicly for that? It's kinda hard to develop for a feature like that without an emulator or hardware that supports it, so right now that's something only you can leverage.

 

JLP and jzIntv have multiply / divide acceleration support, and it works well for decimalization. The documentation is online in the jzIntv's JLP simulation, in the INTVPROG archives (I believe), and so on.

 

Sure, reading the code is kinda boring, but there's a great comment at the top that summarizes everything. Anyone who wants to know more is free to ask and I'll be happy to elaborate.

 *
 *   -- 16-bit RAM from $8040 - $9F7F
 *   -- Save Game Arch V2 support at $8023 - $802F
 *   -- Random number generator at $9FFE
 *   -- CRC-16 accelerator at $9FFC / $9FFD (poly 0xAD52, right-shifting)
 *   -- Multiply / divide acceleration at $9F80 - $9F8F
 *      $9F8(0,1):  s16($9F80) x s16($9F81) -> s32($9F8F:$9F8E)
 *      $9F8(2,3):  s16($9F82) x u16($9F83) -> s32($9F8F:$9F8E)
 *      $9F8(4,5):  u16($9F84) x s16($9F85) -> s32($9F8F:$9F8E)
 *      $9F8(6,7):  u16($9F86) x u16($9F87) -> u32($9F8F:$9F8E)
 *      $9F8(8,9):  s16($9F88) / s16($9F89) -> quot($9F8E), rem($9F8F)
 *      $9F8(A,B):  u16($9F8A) / u16($9F8B) -> quot($9F8E), rem($9F8F)
 *
Edited by intvnut
  • Like 1
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...