Jump to content
IGNORED

Assembly on the 99/4A


matthew180

Recommended Posts

It is a tricky one. I found three cases in Classic99 over the years that led to understanding the sequence (which matches what you have there, @Asmusr

 

// For operations like MOV R3,*R3+ (from Corcomp's memory test), the address
// value is written to the same address before the increment occurs.
// There are even trickier cases in the console like MOV *R3+,@>0008(R3),
// where the post-increment happens before the destination address calculation.
// Then in 2021 we found MOV *R0+,R1, where R0 points to itself, and found
// that the increment happens before *R0 is fetched for the move.

 

  • Like 5
  • Thanks 1
Link to comment
Share on other sites

The "9900 Family Systems Design Handbook" says:

 

Workspace Register Indirect Auto-Increment (Word operand):

CYCLE       TYPE               ADDRESS BUS              DATA BUS
  1      Memory read         Register address        Register contents
  2      ALU                 Unchanged               Source data register (internal)
  3      ALU                 Unchanged               Source data register (internal)
  4      Memory write        Register address        Register contents + 2
  5      Memory read         Register contents       Operand

 

This is independent of the position of the *Rx+, whether in source or destination position. For that reason, this procedure is called "data derivation sequence" - it always fetches the operand. The actual instruction then needs to do a memory write for the destination operand:

 

For A, AB, MOV etc:

 

CYCLE            TYPE                   ADDRESS BUS          DATA BUS
  1           Memory read               Program counter      Instruction
  2           ALU                       No change            Source data register (internal)
  Ns          Data derivation sequence (source)
  3+Ns        ALU                       No change            Source data register (internal)
  Nd          Data derivation sequence (destination)
  4+Ns+Nd     ALU                       No change            Source data register (internal)
  5+Ns+Nd     Memory write              Destination address  Result

 

So the DDS (source) puts 8300 (as the address of R0) on the address bus, gets the value (8300) from there; in cycle 4 it writes the value+2 (8302) to that memory location. From the initially loaded address (8300) it then gets the value 8302 (incremented during cycle 4). [@8300=8302, SD=8302]

 

The DDS (destination) puts 8300 on the address bus, gets 8302 from there, writes 8304 to 8300. Then it fetches some unknown value from 8302 to be discarded. The MOV operation finally writes to 8302 the value gained in the DDS (source), which is 8302.

 

So R0(8300) = 8304, R1(8302) = 8302.

 

By the way, the TMS9995 replaces the DDS with an ADS = "address derivation sequence", and it does not fetch the data at that location anymore.

  • Like 5
Link to comment
Share on other sites

I was running out when I saw the post and replied in haste.

I missed the other register part of the question. Homer Simpson is alive and well here in the great white north.  

 

So is this considered to be a bug, a feature or a side-effect?

 

Link to comment
Share on other sites

More EA, I like the flexibility this approach provides for passing variables. But, I think there is a bug.


But I think there is another bug in the EA manual.

 

image.thumb.png.d5e900b8be50089f2f53829003d8f71c.png

 

I think the second MOV (Y Value) also needs to do an increment of R11.

  • Like 2
Link to comment
Share on other sites

11 hours ago, TheBF said:

I was running out when I saw the post and replied in haste.

I missed the other register part of the question. Homer Simpson is alive and well here in the great white north.  

 

So is this considered to be a bug, a feature or a side-effect?

 

You mean the *R0+,*R0+? This is normal behaviour as specified, but somewhat difficult to grasp intuitively, because you really need to know the sequence of actions inside the CPU.

  • Like 2
  • Thanks 1
Link to comment
Share on other sites

3 hours ago, dhe said:

More EA, I like the flexibility this approach provides for passing variables. But, I think there is a bug.


But I think there is another bug in the EA manual.

 

image.thumb.png.d5e900b8be50089f2f53829003d8f71c.png

 

I think the second MOV (Y Value) also needs to do an increment of R11.

 

You are correct. Otherwise, “DATA 1804” will almost always bypass the next 10 bytes because it translates to a JOC instruction that jumps to 4 words (8 bytes) beyond the MOV. The reason it will almost always take the jump is that the carry flag from the “S 2,0” instruction is always set except when R0 goes from 0 to a negative result.

 

There is another bug:  “MOV 0,RESULT” should be “MOV 0,@RESULT”.

 

...lee

  • Like 4
Link to comment
Share on other sites

3 hours ago, Lee Stewart said:

 

You are correct. Otherwise, “DATA 1804” will almost always bypass the next 10 bytes because it translates to a JOC instruction that jumps to 4 words (8 bytes) beyond the MOV. The reason it will almost always take the jump is that the carry flag from the “S 2,0” instruction is always set except when R0 goes from 0 to a negative result.

 

There is another bug:  “MOV 0,RESULT” should be “MOV 0,@RESULT”.

 

...lee

I never thought of passing data to subroutines that way. The learning never stops...

  • Like 1
Link to comment
Share on other sites

As a general thing I'd concur. But in this case it's more or less a code thing. You could imagine that the difference between BL @DSRLNK DATA 8 and BL @DSRLNK DATA 10 could equally well be described by BL @DSRLNK vs. BL @SUBLNK or something similar. Now instead of calling two different labels to do (almost) the same thing, you call the same label with different handling instructions stored as data behind the call.

 

You can do the same trick when you use BLWP, but in that case you access data with MOV *R14+. If you want to reach parameters in the caller's workspace, you index through R13. MOV @6(R13) will fetch the content of R3 in the caller's workspace.

  • Like 1
Link to comment
Share on other sites

Another favourite I've seen is to manipulate R11 to use different return addresses from a routine, for example:

 

BL @CALC
DATA >AAAA *Return address if result is zero.
DATA >BBBB *Return address if result is positive.
DATA >CCCC *Return address if result is negative.

 

  • Like 1
Link to comment
Share on other sites

3 hours ago, Vorticon said:

I never thought of passing data to subroutines that way. The learning never stops...

       BLWP @>005A(9)         past eof: expand file
       DATA >D800             save R0, R1, R3, R4

       MOV  0,3               desired sector offset
       BL   @A4658            call subroutine
       DATA A4964             append enough sectors to reach offset in R3

       BLWP @>005A(9)
       DATA >D801             restore R0, R1, R3, R4

 

This is from the disk controller DSR ROM.  Every instance of BLWP @>005A(9) that I checked is followed by a DATA directive.  If I remember right there are 5? services that this routine handles?  One of them is store(save) registers, another is restore registers.  There are 203 instances of BLWP @>005A(9) in the dc.ROM.asm source file.  I'd say it's a fairly common practice.😃

  • Like 1
Link to comment
Share on other sites

16 minutes ago, hhos said:

       BLWP @>005A(9)         past eof: expand file
       DATA >D800             save R0, R1, R3, R4

       MOV  0,3               desired sector offset
       BL   @A4658            call subroutine
       DATA A4964             append enough sectors to reach offset in R3

       BLWP @>005A(9)
       DATA >D801             restore R0, R1, R3, R4

 

This is from the disk controller DSR ROM.  Every instance of BLWP @>005A(9) that I checked is followed by a DATA directive.  If I remember right there are 5? services that this routine handles?  One of them is store(save) registers, another is restore registers.  There are 203 instances of BLWP @>005A(9) in the dc.ROM.asm source file.  I'd say it's a fairly common practice.😃

So that is effectively co-opting the return function to become caller.

It's clever, but it makes my old Pascal text books quiver on the shelf. :) 

 

It also makes me wonder if it is really the best way to handle these situations or was it an "in house" coding practice that was started

by someone in the early days at TI and then just carried on by tradition.

What do the experts here think?

  • Like 1
Link to comment
Share on other sites

2 hours ago, TheBF said:

So that is effectively co-opting the return function to become caller.

It's clever, but it makes my old Pascal text books quiver on the shelf. :) 

 

It also makes me wonder if it is really the best way to handle these situations or was it an "in house" coding practice that was started

by someone in the early days at TI and then just carried on by tradition.

What do the experts here think?

 

Not presuming to be an expert, but, considering that there is only free space of 158 bytes at the end of the DSR ROM, I would guess conserving space was the prime objective.

 

...lee

  • Like 1
Link to comment
Share on other sites

3 hours ago, InsaneMultitasker said:

And the DATA statements after a BL or BLWP make for more challenging, iterative disassembly work.

 

Especially when a variable number of DATA statements is possible for the same routine, as is the case in XB ROMs. :-o

 

...lee

Link to comment
Share on other sites

15 hours ago, Asmusr said:

Isn't there some principle saying that it's bad practice to mix code and data? The closest I could find was the Harward architecture, but that's not exactly it. Anyway, that's what I always try to do, so I wouldn't dump my parameter values in between my subroutine calls.

Well, maybe.  IBM mainframes have many assembler macros (and I mean heaps, for their macro assembler, making it a much higher level language than you'd expect) that embed data next to / following the code for "non rent" code generation (re-entrant code is typically more complex, where old macros may have, at runtime, written to nearby RAM for conditional data - and as time progressed, you could specify additional options in the standard macros to avoid it)...

 

I don't want to be a party pooper, but TI didn't invent any of this stuff - it's been there since the 1960's, and a lot of what I've seen with the TI was copied from them.

 

The reason?  IBM mainframes have a 4KB offset system in their machine code instructions, so you optimise data access around the code you're executing using a single base register.  You really can't avoid it on that hardware, compared to much newer architectures that have still not killed off that platform...  Despite over 30 years of attempts to do so.

 

ARM, that's smarter still, instructions that execute (or not) depending on the status register set by previously executed instructions - avoiding conditional jumps.

  • Like 1
Link to comment
Share on other sites

Turbo Pascal 3 doesn't run on the TI 99/4A. A more detailed expression would be "something not doable from UCSD Pascal on the TI 99/4A".

You can do a lot with it as it is, like creating peek and poke on Pascal level, even access the VDP RAM directly from Pascal. But if you need to involve CRU IO, then there is no way it can be done from Pascal level, as there are no p-codes that handle that kind of IO.

Link to comment
Share on other sites

On 2/21/2024 at 12:10 PM, Asmusr said:

Isn't there some principle saying that it's bad practice to mix code and data? The closest I could find was the Harward architecture, but that's not exactly it. Anyway, that's what I always try to do, so I wouldn't dump my parameter values in between my subroutine calls.

If you're writing in assembly language you're already breaking most of the so-called 'good practices' ;)

 

Don't think of data after a BL as data, though, think of it more like arguments to a function call.

 

  • Like 3
  • Haha 1
Link to comment
Share on other sites

On 2/21/2024 at 12:35 PM, Stuart said:

Another favourite I've seen is to manipulate R11 to use different return addresses from a routine, for example:

 

BL @CALC
DATA >AAAA *Return address if result is zero.
DATA >BBBB *Return address if result is positive.
DATA >CCCC *Return address if result is negative.

 

That one I've never seen. That would take a moment to figure out if you'd never seen it before. ;)

 

Link to comment
Share on other sites

16 hours ago, Tursi said:

If you're writing in assembly language you're already breaking most of the so-called 'good practices' ;)

 

Don't think of data after a BL as data, though, think of it more like arguments to a function call.

 

That's one of the things I love about it!  I have to say, after coding "properly" for some many years it took some real disciple to stop myself from trying to abstract functions in assembly, it was never worth the effort and usually became impractical due to running out of registers or stack space.

Edited by retrodroid
Link to comment
Share on other sites

3 hours ago, Tursi said:

That one I've never seen. That would take a moment to figure out if you'd never seen it before. ;)

 

I haven't come across something like that. It's a case statement. 
 

 

Much simpler, but in TI books, I have seen a lot of:

BL @MOVPAC 

DATA ERROR

 

if the routine is successful it does

INCT R11

but if not

MOV *R11+,R11

RT

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...