X16 and the BASIC USR function (multi-argument and 65C02 special powers revealed)
Posted: Mon Oct 09, 2023 8:40 am
I'm no expert about USR, but here are some notes on what I've learned about it. Will evolve this article as I learn more.
I came across the following article for the C64 and wanted to see how well it would all port over to the X16:
https://www.defiancestudios.com/2020/03 ... rough-usr/
So - is USR even useful? Why not just POKE your code with a DATA sequence and just SYS over to wherever you POKE'd it?
Well - the intent is USR is help share data between your BASIC program and machine code. Variables in BASIC are actually 16-bit signed integers. Sort of - it's worse than that, as all the variables in BASIC are actually stored in some 5 byte structure (so they can "morph" between an integer type and floating point; this later evolved into the VARIANT type in VisualBasic). On top of that, BASIC has its own funky floating point representation (this being pre-IEEE float-spec days). So in BASIC if you say Z=42, how do you get that value 42 over into an accumulator register, so you can do some logic on the machine code side? This is where the USR function is suppose to help.
But in your ML code you don't really want anything to do with floating point values. The parameter you pass to USR gets translated (or copied) over into a zeropage region called the FAC (it should be 5 bytes but it seems to me as much as 8 bytes is actually used). You shouldn't really need to know or care where that is, but we may come back to that in a moment.
The system has two helper-functions, called fac2ya and givayf. I'm not sure what category these are - they aren't traditional KERNAL calls. But fac2ya converts the FAC content over into registers Y and A. TBD, I can't find this function yet on the X16 - so there are some missing details here - but the general point is, if you pass a small integer value to USR (like USR(250) ) your integer value is going to show up in the A register. For larger values >255 or passing actual floating point, some other stuff happens - but we'll just focus on integer values <= 255 for now.
Then in your ML code, you manipulate Register A and/or Register Y. Then call givayf and your register values get written back to the FAC and popped back as the return value of your USR call. Note from my experiments on the X16, the contents of the FAC are corrupted several times over by the time the systems returns back to your BASIC call - which is why the FAC address doesn't matter, you can't PEEK into it anyway after your code resumes from the USR call. I did find the giveayf function in the X16, but you'll still have to do some figuring-out on how to interpret the return value of USR. If you don't do anything (i.e. if you don't call giveayf in your machine code), then the default behavior is that USR returns the same value that was passed in.
Ok - so, none of that sounds really useful: in order to use USR, you have to spend a billion instructions converting the FAC to/from your registers anyway, so what's the point? Well - exactly. Your USR ML better to be extremely useful to make all this worthwhile. So it's a judgement call. For now, it's just a curiosity - so let's press on.
Here is a chart comparing the pertinent addresses related to USR:
In the next post I'll give a more complete example - including how to pass multiple arguments and something nifty the 65c02 can do.
I came across the following article for the C64 and wanted to see how well it would all port over to the X16:
https://www.defiancestudios.com/2020/03 ... rough-usr/
So - is USR even useful? Why not just POKE your code with a DATA sequence and just SYS over to wherever you POKE'd it?
Well - the intent is USR is help share data between your BASIC program and machine code. Variables in BASIC are actually 16-bit signed integers. Sort of - it's worse than that, as all the variables in BASIC are actually stored in some 5 byte structure (so they can "morph" between an integer type and floating point; this later evolved into the VARIANT type in VisualBasic). On top of that, BASIC has its own funky floating point representation (this being pre-IEEE float-spec days). So in BASIC if you say Z=42, how do you get that value 42 over into an accumulator register, so you can do some logic on the machine code side? This is where the USR function is suppose to help.
But in your ML code you don't really want anything to do with floating point values. The parameter you pass to USR gets translated (or copied) over into a zeropage region called the FAC (it should be 5 bytes but it seems to me as much as 8 bytes is actually used). You shouldn't really need to know or care where that is, but we may come back to that in a moment.
The system has two helper-functions, called fac2ya and givayf. I'm not sure what category these are - they aren't traditional KERNAL calls. But fac2ya converts the FAC content over into registers Y and A. TBD, I can't find this function yet on the X16 - so there are some missing details here - but the general point is, if you pass a small integer value to USR (like USR(250) ) your integer value is going to show up in the A register. For larger values >255 or passing actual floating point, some other stuff happens - but we'll just focus on integer values <= 255 for now.
Then in your ML code, you manipulate Register A and/or Register Y. Then call givayf and your register values get written back to the FAC and popped back as the return value of your USR call. Note from my experiments on the X16, the contents of the FAC are corrupted several times over by the time the systems returns back to your BASIC call - which is why the FAC address doesn't matter, you can't PEEK into it anyway after your code resumes from the USR call. I did find the giveayf function in the X16, but you'll still have to do some figuring-out on how to interpret the return value of USR. If you don't do anything (i.e. if you don't call giveayf in your machine code), then the default behavior is that USR returns the same value that was passed in.
Ok - so, none of that sounds really useful: in order to use USR, you have to spend a billion instructions converting the FAC to/from your registers anyway, so what's the point? Well - exactly. Your USR ML better to be extremely useful to make all this worthwhile. So it's a judgement call. For now, it's just a curiosity - so let's press on.
Here is a chart comparing the pertinent addresses related to USR:
Code: Select all
C64 X16
fac = $0061 00C3 // 5 bytes (6th byte for sign?)
frmnum = $ad8a D209 frmnum? // get and evaluate data for type mismatch
usradd = $0311 0311 same // pointer for USR funtion for ml
fac2ya = $b1aa ???? // fac1 to int in y(lo) a(hi)
givayf = $b391 D998 givayf0? // int y(lo) a(hi) to fac1
err = $a437 D827 fcerr? // BASIC error handling routine