Jump to content
matthew180

Assembly on the 99/4A

Recommended Posts

18 minutes ago, Lee Stewart said:

Considering the necessity of jumps, I see this as shortest:

       MOV  R0,R0
       JEQ  DONE
       SETO R0
       JLT  DONE
       INCT R0
DONE   ...

 

...lee

I like this solution.

Edited by Asmusr

Share this post


Link to post
Share on other sites
12 minutes ago, intvnut said:

It's annoying MOV is 14 + 4*mem.  Does ABS set Carry in a useful way for positive vs. negative numbers?  It's faster than MOV for positive numbers, and uses fewer memory cycles in all cases.

Answering my own question by looking at the code in MAME.

void tms99xx_device::alu_abs()
{
	// LAECO (from original word!)
	// O if >8000
	// C is alwas reset
	set_status_bit(ST_OV, m_current_value == 0x8000);
	set_status_bit(ST_C, false);
	compare_and_set_lae(m_current_value, 0);

	if ((m_current_value & 0x8000)!=0)
	{
		m_current_value = (((~m_current_value) & 0x0000ffff) + 1) & 0xffff;
		pulse_clock(2);     // If ABS is performed it takes one machine cycle more
	}
	else
	{
		MPC++; // skips over the next micro operation (MEMORY_WRITE)
	}
	pulse_clock(2);
}

It appears Carry isn't set usefully.  But, it appears the other flags are set based on the original value, not the value after negation.   So, you could shave a couple cycles with:

       ABS  R0     ; 12 + 2*mem (if positive)
       JEQ  DONE   ;  8 + 1*mem (n/t)
       SETO R0     ; 10 + 3*mem
       JLT  DONE   ;  8 + 1*mem (n/t)
       INCT R0     ; 10 + 3*mem
DONE   ...         ;------------
                   ; 48 + 10*mem

I guess I had missed the fine print "If MSB(SA) = 0 and (SA) ≠ 0" here:

 

1243193655_ScreenShot2020-05-16at12_01_13PM.thumb.png.5cea092e981e5d094aa1dcbc7a2d82a3.png

 

Edited by intvnut
Correct # of mems for ABS.
  • Like 1
  • Thanks 1

Share this post


Link to post
Share on other sites
51 minutes ago, intvnut said:

It appears Carry isn't set usefully.  But, it appears the other flags are set based on the original value, not the value after negation.   So, you could shave a couple cycles with: ...

* R0 negative...
* Lee Stewart......
       MOV  R0,R0   ; 14 + 4*mem
       JEQ  DONE    ;  8 + 1*mem (n/t)
       SETO R0      ; 10 + 3*mem
       JLT  DONE    ;  8 + 1*mem
       INCT R0      ; 10 + 3*mem (n/t)
DONE   ...          ;-------------+
                    ; 40 + 9*mem |
                    ;-------------+

* intvnut.........
       ABS  R0     ; 14 + 3*mem (IF NEGATIVE)
       JEQ  DONE   ;  8 + 1*mem (n/t)
       SETO R0     ; 10 + 3*mem
       JLT  DONE   ;  8 + 1*mem 
       INCT R0     ; 10 + 3*mem (n/t)
DONE   ...         ;-------------+
                   ; 40 + 8*mem |
                   ;-------------+

We are closest when R0 < 0, with yours winning by only 1*mem. But, when R0 ≥ 0, yours is better by 2 + 2*mem,  so you win. :waving:

 

...lee

Edited by Lee Stewart
general corrections of timing
  • Like 1

Share this post


Link to post
Share on other sites

Ah yes, SETO does not affect the status bits. Good idea.

 

As for ABS, the Ed/Ass manual says the instruction does not affect the Carry bit, yet the TMS9900/9995 specs say it does. But it will always be reset. Here is a test I ran on my Geneve.

 

*        
*           LAECOPX
*   FFFE    10000***
*   FFFF    10000***
*   0000    00100***
*   0001    11000***
*   0002    11000***
*   7FFE    11000***
*   7FFF    11000***
*   8000    10001***
*   8001    10000***

  • Thanks 1

Share this post


Link to post
Share on other sites
26 minutes ago, Lee Stewart said:
* Lee Stewart......
       MOV  R0,R0   ; 14 + 4*mem
       JEQ  DONE    ;  8 + 1*mem (n/t)
       SETO R0      ; 10 + 3*mem
       JLT  DONE    ;  8 + 1*mem (n/t)
       INCT R0      ; 10 + 3*mem
DONE   ...          ;-------------+
                    ; 50 + 12*mem |
                    ;-------------+

* intvnut.........
       ABS  R0     ; 14 + 3*mem (IF NEGATIVE)
       JEQ  DONE   ;  8 + 1*mem (n/t)
       SETO R0     ; 10 + 3*mem
       JLT  DONE   ;  8 + 1*mem (n/t)
       INCT R0     ; 10 + 3*mem
DONE   ...         ;-------------+
                   ; 50 + 11*mem |
                   ;-------------+

Your worst case scenario with R0 < 0 is 1 memory access less than mine, so you win. :waving:

 

...lee

I only got there with your help, naturally.  :D  You shaved more cycles off mine than I did off yours.

 

For the R0 < 0 case, the JLT gets taken, so we never actually end up with 50 + 11*mem.  That case ends up being 42 + 8*mem, since we skip the INCT, but the JLT becomes 10 cycles.

 

So, the final cycle counts end up being:

  • R0 > 0: 48 + 10*mem
  • R0 = 0: 22 + 3*mem  (ABS is 12+2*mem, JEQ is 10+1*mem)
  • R0 < 0: 42 + 8*mem (ABS is 14+3*mem, JLT becomes 10+1*mem)

To put it in perspective, a single SRA R0, 15 is 42 + 3*mem.  In zero wait-state memory, this is only slightly slower than that shift in the worst case.  Nice!

Edited by intvnut
Clarify we never actually have 11*mem, because JLT taken when R0<0.
  • Like 1

Share this post


Link to post
Share on other sites
36 minutes ago, mizapf said:

Ah yes, SETO does not affect the status bits. Good idea.

 

As for ABS, the Ed/Ass manual says the instruction does not affect the Carry bit, yet the TMS9900/9995 specs say it does. But it will always be reset. Here is a test I ran on my Geneve.

 

 

*        
*           LAECOPX
*   FFFE    10000***
*   FFFF    10000***
*   0000    00100***
*   0001    11000***
*   0002    11000***
*   7FFE    11000***
*   7FFF    11000***
*   8000    10001***
*   8001    10000***

I'm mildly confused here.  Neither the "JLT" or "JEQ" depend on the carry flag to operate, so it does not effect the assembly code presented here.  Only the "JOC" and "JNC" test the carry flag to make a decision.  The examples you've presented here seem to indicate that the TMS900/9995 docs are correct.  Assuming that the carry flag was set before executing the ABS, in each case, that seems to be the case.  Is that what this means?

 

Share this post


Link to post
Share on other sites

Yes, my test was done in two passes; first setting the status register to 0000, then again setting the status register to FFFF. The 9995 has a helpful command: LST. The asterisks show the bits that are not affected by the operation.

 

It is easy to see that C=0 for every value presented to ABS:

 

- If the value is nonnegative, it remains unchanged.

- If the value is negative, it is replaced by its two's complement.

 

NEG on 0 implies C=1, because the ALU obviously calculates the one's complement first, then increments it by 1. This increment may cause a carry. As you see, it only occurs for NEG 0. But for 0, ABS does nothing.

  • Thanks 1

Share this post


Link to post
Share on other sites
1 hour ago, mizapf said:

As for ABS, the Ed/Ass manual says the instruction does not affect the Carry bit, yet the TMS9900/9995 specs say it does.

In the datasheet, I see the ABS instruction listed in the ST3 (carry) row of the status-flags table, where the condition is "if CARRY OUT = 1".  However, absolute value operation is performed on negative numbers *only* (there is a pre-test of the value, which is why values that are already positive are not written), and is calculated by inverting the the bits and adding 1 (this is the same operation as NEG).  As soon as you invert the negative number, the MSbit becomes zero, so it is not possible to have a carry out.  ABS is probably listed as affecting the carry bit because the instruction will cause it to change, i.e. be set to 0 if it was previously a 1.

Share this post


Link to post
Share on other sites
25 minutes ago, mizapf said:

Yes, my test was done in two passes; first setting the status register to 0000, then again setting the status register to FFFF. The 9995 has a helpful command: LST. The asterisks show the bits that are not affected by the operation.

 

It is easy to see that C=0 for every value presented to ABS:

 

- If the value is nonnegative, it remains unchanged.

- If the value is negative, it is replaced by its two's complement.

 

NEG on 0 implies C=1, because the ALU obviously calculates the one's complement first, then increments it by 1. This increment may cause a carry. As you see, it only occurs for NEG 0. But for 0, ABS does nothing.

 

The unsigned vs. signed greater than flags (L> and A>) on TMS9900 family always throw me off when I return for a visit. 

 

On most other CPUs I've encountered, there isn't a separate unsigned greater-than flag.  Rather, the carry bit serves that purpose.  If you treat a 2's complement subtract as merely "invert and inject a carry," then the carry will be clear after A - B when A < B (unsigned), and set otherwise.  That gives you "unsigned less than" and "unsigned greater than/equal" just looking at the carry bit.  Add in the EQ bit and you'd have "unsigned less than/equal" and "unsigned greater than."  You can get by with four flag bits rather than five.

 

In general, the TMS9900 family is weird about their carry bit compared to other architectures I've worked with.  You can set it or clear it, but it's difficult to consume it without a branch.  You can't use it directly for extended precision adds, subtracts, or shifts.  You gotta branch, or do a funky dance with STST.

 

It makes sense that ABS always zeros it out given how it's implemented.  Without the L> bit, I could see using the carry flag to indicate whether ABS had modified the number.

 

31 minutes ago, HOME AUTOMATION said:

There can only be one "ZERO".:rolling::rolling::rolling::rolling::rolling::rolling::rolling::rolling:

Not in floating point or 1's complement!  ;););)   I'm pretty sure that includes the 99/4A's weird Radix-100 floating point.  :D 

  • Like 1

Share this post


Link to post
Share on other sites

My Linear Algebra professor in my first semester once said, "the zeros cannot hide".

 

(From the math point of view, it meant that a zero vector makes every family of vectors linearly dependent. Of course, there was another subtle meaning concerning the audience...)

Edited by mizapf
Oh man, could you possibly agree on null or zero in English?
  • Like 1
  • Haha 1

Share this post


Link to post
Share on other sites

Hello All!

 

I'm having trouble understanding the E/A Manual's description of the SAVE Utility in section 24.5 "SAVE UTILITY." Of course there's no example even though page 421 ends with 80% of the page blank. Why?

 

The goal here is to convert my EA3 loadable program to EA5, so it can then be converted to a cartridge binary .bin via Fred Kaal's Module Creator. 

 

EA page 420: "Your program must contain DEF SFIRST,SLAST,SLOAD...."

 

SFIRST -- How exactly do I make this a pointer to the start of my program? Right now the start of my program has a label 'ENK" which is the program name. 

 

SLOAD -- How do I make this a pointer for "where the saved program is to be located" on a cartridge binary file? Assuming this is going to be a GROM address?

 

SLAST -- I again assume I'll just make this the label for the END directive in my program? This part seems clear from the EA manual, I think.

 

It is my understanding an EA5 option runnable file is needed for input into Fred's Module Creator?

 

Perhaps it is best to use Matthew's simplified code outline to illustrate the solution. How may we use SFIRST,SLAST,SLOAD in the example below?

 

     DEF  START

* VDP Memory Map
*
VDPRD  EQU  >8800             * VDP read data
VDPSTA EQU  >8802             * VDP status
VDPWD  EQU  >8C00             * VDP write data
VDPWA  EQU  >8C02             * VDP set read/write address

* Workspace
WRKSP  EQU  >8300             * Workspace
R0LB   EQU  WRKSP+1           * R0 low byte reqd for VDP routines

* Program execution starts here
START  LIMI 0
      LWPI WRKSP


      LIMI 2
LP9999 JMP  LP9999

      END

As always, I appreciate your inputs and assistance.  - James

Share this post


Link to post
Share on other sites

 

*DON'T NEED DEF

*SFIRST IS THE FIRST LINE OF CODE IN THE PROGRAM AND IT MUST BE  ACTUAL CODE, NOT DATA OR TEXT

 

SFIRST

SLOAD    LIMI 0

              LWPI WRKSP

              LIMI 2

LP9999   JMP LP9999

 

SLAST     END

Share this post


Link to post
Share on other sites

But the SAVE utility uses REFs for SFIRST, SLOAD, SLAST, so I'd expect you have to DEF them.

 

(Ah, this is possibly ambiguous ... you say you don't need another DEF for your program.)

  • Like 1

Share this post


Link to post
Share on other sites
59 minutes ago, mizapf said:

But the SAVE utility uses REFs for SFIRST, SLOAD, SLAST, so I'd expect you have to DEF them.

 

(Ah, this is possibly ambiguous ... you say you don't need another DEF for your program.)

DEF-ing them worked out.

Share this post


Link to post
Share on other sites
3 hours ago, senior_falcon said:

 

*DON'T NEED DEF

*SFIRST IS THE FIRST LINE OF CODE IN THE PROGRAM AND IT MUST BE  ACTUAL CODE, NOT DATA OR TEXT

 

SFIRST

SLOAD    LIMI 0

              LWPI WRKSP

              LIMI 2

LP9999   JMP LP9999

 

SLAST     END

This worked...kind of. The SAVE program launched and it did create three files: DSK4.ENKA, DSK4.ENKB, and DSK4.ENKC. 

SAVE Program.png

 

I got all excited when I used EA Option-5 to launch DSK4.ENKA. Everything ran as expected via the EA Option-5 Prompt. 

 

Upon restarting Classic99 and re-selecting EA Option-5, the program launched but seemed to be graphically distorted and didn't seem to run properly. 

 

Here's how it should look upon launch:

 

 

 

 

ENK Program Begins.png

 

 

 

Here's how it looks after I reset Classic99 and selected EA Option-5, entered DSK4.ENKA, then pressed enter.

 

 

 

ENKA after SAVE.png

 

 

Is it possible EA5 is only launching the first 8K segment named "DSK4.ENKA?"

 

The program is non-responsive and just hangs. Strange because it runs fine via the EA5 option immediately after using the SAVE program. Only after resetting Classic99 will EA5 with Option-5 result in the black screen of death above?  I'm doing something wrong and hope it is obvious to the experts.

Share this post


Link to post
Share on other sites

When doing Load and run from EA, you have a particular definition of VDP RAM and some other configurations. They are still there when you execute the save utility.

But the next time, you can't make any assumptions about anything in the computer. You have to define everything yourself. Like VDP and character definition setup, for example.

That's probably what you haven't done.

Share this post


Link to post
Share on other sites
4 hours ago, apersson850 said:

You have to define everything yourself. Like VDP and character definition setup, for example.

That's probably what you haven't done.

Ugh! So close yet so far. Where do I begin to “define everything“ myself? Sounds daunting.  
 

  • Like 1

Share this post


Link to post
Share on other sites
On 1/23/2020 at 7:06 PM, PeteE said:

Here's an example cartridge header...

       AORG >6000         ; Cartridge header in all banks

HEADER
       BYTE >AA     ; Standard header
       BYTE >01     ; Version number 1
       BYTE >01     ; Number of programs (optional)
       BYTE >00     ; Reserved (for FG99 this can be G,R,or X)
       DATA >0000   ; Pointer to power-up list
       DATA PRGLST  ; Pointer to program list
       DATA >0000   ; Pointer to DSR list
       DATA >0000   ; Pointer to subprogram list

PRGLST DATA >0000   ; Next program list entry
       DATA START   ; Program address
       BYTE CRTNME-CRTNM       ; Length of name
CRTNM  TEXT 'CARTRIDGE NAME'
CRTNME
       EVEN

START                         ; Your program starts here
       LWPI WRKSP             ; Load the workspace pointer to fast RAM
       LIMI 0                 ; Interrupts off

I believe the answers I need are in the code example from PeteE above. Post #1029, this thread:

 Others have replied that this is very clear and to the point. I'll continue to try to understand it.

 

Questions:   

 

1. Number of programs optional?

2. G,R,X stand for?

3. DATA >0000 = pointer to power-up list, DSR list, and subprogram list? I'm lost.

 

Maybe I don't have to know what's going on here to get it to work? I'll just experiment a little with cutting and pasting this into my code?

 

Hopefully heading in the right direction. Will this serve @apersson850's suggestion to "define everything myself?"

 

- James

 

 

Share this post


Link to post
Share on other sites
9 minutes ago, Airshack said:

Questions:   

 

1. Number of programs optional?

2. G,R,X stand for?

3. DATA >0000 = pointer to power-up list, DSR list, and subprogram list? I'm lost.

 

Maybe I don't have to know what's going on here to get it to work? I'll just experiment a little with cutting and pasting this into my code?

 

James, you don't need the cartridge header for EA/5 programs.

 

I believe the suggestion to "define everything yourself" means to set the VDP registers yourself, you can check by comparing the VDP registers for your two versions in Classic99 debugger window.  The VDP registers are labeled "VDP0" to "VDP7".  If you need help setting the registers, I can share the code I use.

 

But to answer your questions:

  1. Some cartridges may not have programs, only GROM data, or DSR or subroutine lists.
  2. From the FinalGrom99 page:
    [R] RAM Mode: provides up to 512 KB of ROM and 512 KB of RAM
    [G] GRAM Mode: turns occupied GROM into writable GRAM
    [X] RAM/GRAM Mode: provides both RAM and GRAM
  3. Those are linked lists, and >0000 is the NULL pointer which indicates the list is empty, or the end of the list if used in the final next list entry.  If there were more than one PRGLST entry, each next list entry pointer is followed by the BIOS to enumerate the list of programs, and stops when it finds >0000.

 

 

 

Share this post


Link to post
Share on other sites

I didn't see anyone else mention it, so maybe they already know you aren't using them, but if you are, the most common reason that coverting from EA#3 to EA#5 fails is that you don't have the Editor/Assembler routines in memory when you start from EA#5. So when you BLWP @VSBW, for instance, there IS no VSBW, and you jump off to empty code and crash.

 

The easy fix is just to load the EA routines as an extra source file, so you have (for instance) your three program files plus one to low RAM for the utilities. There are a few copies of a pre-made file for that floating around the forums.

 

If it's not that, then yeah, as noted above you have made some assumption about the state of the hardware. Make sure your program sets every register in the VDP and initializes any memory it uses to appropriate values (ie: don't assume they are zero). You should probably also load the character set yourself - but in fairness THAT doesn't usually become an issue till you move to cartridge. Usually it's the VDP setup. Check the registers in the Classic99 debugger and you should quickly see if they are different.

  • Like 1

Share this post


Link to post
Share on other sites

In my program, I copy a couple k of code at >2000 at boot, then temporarily place it in my supercart, then I put it back in >2000 and those ea commands work.

Share this post


Link to post
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.

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