Jump to content
jchase1970

Assembly guidance

Recommended Posts

I have never entered a single line of assemble code before, so please bare with me on this.

 

can someone walk me through what I need to do using w994a- asm994a program, to write a simple "Hello World" program

 

What I did,

I created a new project

entered this code

7D00 045B LI RO,1

7D04 0354 LI R1,ST

7D08 0244 LI R2,11

7D0C A100 BLWP @>6028

7D10 0007 NN JMP NN

7D12 6100 ST TEXT 'HELLO WORLD'

 

which I found on the web, and is for the mini memory module assembler

 

What it tells me is

load screen position in R0

load string ST into R1

load string lenght into R2

execute using instruction 6028

loop to keep it on the screen

and the string itself

 

 

ok I load it into asm994a and click (start assemble)

 

and I get this result

>> Assembly Started - 03/13/10, 12:13:56PM

Pass 1: A.A99

Pass 2: A.A99

Error #2: Line #1: Label must begin with a-z, $, _ or .: 7D00 Undefined opcode: 045B

Error #4: Line #2: Label must begin with a-z, $, _ or .: 7D04 Undefined opcode: 0354

Error #6: Line #3: Label must begin with a-z, $, _ or .: 7D08 Undefined opcode: 0244

Error #8: Line #4: Label must begin with a-z, $, _ or .: 7D0C Undefined opcode: A100

Error #10: Line #5: Label must begin with a-z, $, _ or .: 7D10 Undefined opcode: 0007

Error #12: Line #6: Label must begin with a-z, $, _ or .: 7D12 Undefined opcode: 6100

Warning #1: Missing END directive

Assembly Complete - Errors: 12, Warnings: 1

>> Processing Complete - 03/13/10, 12:13:56PM

 

which I'm thinking 12 errors and 1 warning from 6 lines of code must not be good.

 

On top of this, If I get a output file, I'm not even sure what to do with it.

 

Hope this isn't to much,

John

Share this post


Link to post
Share on other sites

No need to enter the hex numbers in front of the statements.

They are not part of the source code, they are the output of the Mini Memory line-by-line assembler.

 

Here is how you do it with winasm99

 

      REF   VMBW           ; External function VMBW
SFIRST LI    R0,1           ; VDP target address
      LI    R1,ST          ; Source address in RAM
      LI    R2,11          ; Number of bytes to write
      BLWP  @VMBW          ; VMBW = Multiple Byte Write
      JMP   $              ; soft-halt ($ means program counter)
ST     TEXT  'HELLO WORLD'
      END   SFIRST

 

These are the settings I used in winasm99

hello_world.PNG

 

And here is how it looks like in the classic99 emulator

hello_world2.PNG

Edited by retroclouds
  • Like 1

Share this post


Link to post
Share on other sites

From the looks of things, you've got some simple syntax errors. The lines are being misinterpreted...7D00 is being seen as a label instead of an address, and 045B is being seen as the opcode to compile instead of the hex values of the opcode/argument (which is completely superfluous to include in an assembly file).

 

Instead of using those numbers, place an ORG with the address you want to assemble to at the top (ORG $7D00), and completely remove the others. You'll need to precede the ORG and all opcodes with a space or tab so that they too are not misinterpreted as labels.

 

Note that labels that ARE referenced will still need to be defined. That JMP NN looks suspect (NN would be the target address).

Share this post


Link to post
Share on other sites

Cool that there is some interest in TMS9900 assembly language.

We can use some games written in assembly ;)

You also might want to check out the development resources sticky thread.

Share this post


Link to post
Share on other sites

I haven't used assembly on the 9900 so someone that has will have to confirm the assembler supports this.

 

      REF   VMBW           ; External function VMBW
SFIRST LI    R0,1           ; VDP target address
      LI    R1,ST          ; Source address in RAM
      LI    R2,11          ; Number of bytes to write
      BLWP  @VMBW          ; VMBW = Multiple Byte Write
      JMP   $              ; soft-halt ($ means program counter)
ST     TEXT  'HELLO WORLD'
      END   SFIRST

 

This is just a matter of personal preference but instead of hard coding the string length at 11 there are several other ways of doing this.

      REF   VMBW           ; External function VMBW
SFIRST LI    R0,1           ; VDP target address
      LI    R1,ST          ; Source address in RAM
      LI    R2,ND-ST       ; Number of bytes to write
      BLWP  @VMBW          ; VMBW = Multiple Byte Write
      JMP   $              ; soft-halt ($ means program counter)
ST     TEXT  'HELLO WORLD'
ND     END   SFIRST

 

Or you can put the length in front of the text. Hopefully the assembler supports mixing numbers and text on the TEXT statement.

      REF   VMBW           ; External function VMBW
SFIRST LI    R0,1           ; VDP target address
      LI    R2,ST          ; Number of bytes to write
      LI    R1,ST+1        ; Source address in RAM
      BLWP  @VMBW          ; VMBW = Multiple Byte Write
      JMP   $              ; soft-halt ($ means program counter)
ST     TEXT  11,'HELLO WORLD'
      END   SFIRST

Share this post


Link to post
Share on other sites

Ok I got it to work, I had to use the assembler in the minimem cart not the editor/assmble cart? Is this normal?

 

also is there a way to now run the obj file directly from the disk? I know XB has an autoloader that alot of programs use.

 

Thanks for the help,

John

Share this post


Link to post
Share on other sites

A couple of things for assembly:

 

* Unless you are in to pain and frustration, do you coding using a modern text editor on a PC, preferably one that supports programming and that at least has syntax highlighting. I recommend Textpad, Notepad++, or Eclipse.

 

* Don't use the mini memory unless you are in to pain and frustration.

 

* To assemble you can use either Asm994a (part of the Win994a simulator install) or the real E/A cartridge, but using the real E/A will add a lot of extra steps. I think there may be some other assemblers out there, but the unreleased Asm994a v3.010 is the one to use (it fixes a bug with the JH instruction, otherwise the v3.009 will be fine for now.)

 

To write in assembly there are a few basics you need to understand, which I can not totally cover here in one post. Basically you need to understand what assemblers and compilers do, and what a linker does. On the TI there are generally two ways to make an assembly program:

 

1. Code for fixed memory locations. In this case the code is *NOT* relocatable, meaning where the program is loaded into RAM is fixed and will overwrite anything at those memory locations when loaded.

 

2. Code to create "relocatable" programs. Doing this requires a "linking loader" that decides where in memory to place your program, which also means memory address references need to be resolved while loading, hence the "linking" part of loading.

 

The advantage of relocatable code over fixed is that more than one assembly program can be loaded and managed by the linking-loader. This is usually only necessary if you intend your assembly to work in coordination with other programs, like calling the assembly from XB or something like that. Ultimately if you plan to write games or distribute only executable versions of your program, you will probably code for fixed locations, but it is not strictly necessary. However, if you are writing a program to run as a game cartridge, then you will have to write you code for fixed locations.

 

Currently coding for fixed locations is probably the fastest way to do the code / assemble / test / debug cycle using the Asm994a assembler and the Classic99 emulator. There are some things you have to get used to though, since writing for a fixed location like the cartridge memory area means the code is in ROM and you have to assign memory locations for all your variables instead of letting the assembler do it for you. This bit me in the ass a few times until I remembered that I could not write to a ROM... duh.

 

I have a very in depth explanation of clearing the screen in assembly which Owen has graciously put on his website until I get my own webpage straightened up: http://www.opry99er.com/clearing-the-screen.php

 

I'll include a version here with less verbosity, but a complete program that you can start messing with. I'll give an example of a cartridge based program since that is the fastest and easiest. I'll try to get a linking-loader version with steps written up tomorrow.

 

This is the XB program I will write in assembly:

10 CALL CLEAR
20 DISPLAY AT(12,11):"HELLO WORLD!"
30 GOTO 30

 

I do not use any console ROM or GROM routines or external references. This is straight-up pure unadulterated control of the computer writing straight to the VDP.

 

      DEF  STDHDR

* 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
R1LB   EQU  WRKSP+3           * R1 low byte
R2LB   EQU  WRKSP+5           * R2 low byte
R3LB   EQU  WRKSP+7           * R3 low byte
R4LB   EQU  WRKSP+9           * R4 low byte


**
* Set Up Cartridge
*
* Always mapped to the cartidge address range
      AORG >6000

STDHDR BYTE >AA               * Indicates a standard header
      BYTE >01               * Version number
      BYTE >01               * Number of programs (optional)
      BYTE >00               * Not used
      DATA >0000             * Pointer to power-up list (can't use in cartridge ROM)
      DATA PROG              * Pointer to program list
      DATA >0000             * Pointer to DSR list
      DATA >0000             * Pointer to subprogram list
      DATA >0000             * Pointer to ISR list
PROG   DATA >0000             * No next menu item
      DATA MAIN              * Program start address for this menu item
      BYTE 11                * Length of text for menu screen
      TEXT 'HELLO WORLD'
      EVEN

* Text to display.  This is redundant here since the text above used
* for the menu display could have also been used here, but for
* completness a separate message is reserved.

MSG1   TEXT 'HELLO WORLD!'    * Reserve and initialize the message bytes
MSG1E
      EVEN

*********************************************************************
*
* Main Entry Point
*
*
MAIN   LIMI 0                 * Prevent the console ISR from running
      LWPI WRKSP             * Set the workspace

* No VDP initialization since the console will have set the standard
* Graphics I mode (32x24) and loaded the standard character set.
* If full VDP initialization is necessary, then this code would be
* a lot larger since all the data for a full ASCII set would most
* likely be required.

* Clear the screen by writing a space to every screen position.  In
* VDP RAM the screen in a continous buffer of bytes.  32*24=768 total
* bytes.
      LI   R0,0              * Address in VDP RAM to write to
      LI   R1,>2000          * The byte to write (space)
      LI   R2,768            * Number of bytes to write
      BL   @VSMW             * Write the space multiple times

* Since the VDP is wired to the upper 8-bits of the 9900's data bus,
* the single byte to write must be in the upper byte of the R1
* register, which is why the HEX value is used above since it is
* eaiser to read (>20 HEX is 32 decimal, i.e. ASCII for space)

* To display the message the address of the screen has to be loaded,
* the buffer in CPU RAM designated, and the lenght of the data to
* write (in bytes).
      LI   R0,395            * (12,11) == 12 * 32 + 11 == 395
      LI   R1,MSG1           * Bytes to write
      LI   R2,MSG1E-MSG1     * Length (let the assembler do the math)
      BL   @VMBW             * Write the data

      LIMI 2                 * Enable interrupts to CTRL= works
LOOPZ  JMP  LOOPZ


* These are replacements for the console VDP "functions" that are usually
* called with a BLWP.  These are a bit faster and don't have any fluff.

* VDP Single Byte Write
VSBW   MOVB @R0LB,@VDPWA      * Send low byte of VDP RAM write address
      ORI  R0,>4000          * Set read/write bits 14 and 15 to write (01)
      MOVB R0,@VDPWA         * Send high byte of VDP RAM write address
      MOVB R1,@VDPWD         * Write byte to VDP RAM
      B    *R11

* VDP Single Byte Multiple Write
VSMW   MOVB @R0LB,@VDPWA      * Send low byte of VDP RAM write address
      ORI  R0,>4000          * Set read/write bits 14 and 15 to write (01)
      MOVB R0,@VDPWA         * Send high byte of VDP RAM write address
VSMWLP MOVB R1,@VDPWD         * Write byte to VDP RAM
      DEC  R2                * Byte counter
      JNE  VSMWLP            * Check if done
      B    *R11

* VDP Multiple Byte Write
VMBW   MOVB @R0LB,@VDPWA      * Send low byte of VDP RAM write address
      ORI  R0,>4000          * Set read/write bits 14 and 15 to write (01)
      MOVB R0,@VDPWA         * Send high byte of VDP RAM write address
VMBWLP MOVB *R1+,@VDPWD       * Write byte to VDP RAM
      DEC  R2                * Byte counter
      JNE  VMBWLP            * Check if done
      B    *R11

* VDP Single Byte Read
VSBR   MOVB @R0LB,@VDPWA      * Send low byte of VDP RAM write address
      MOVB R0,@VDPWA         * Send high byte of VDP RAM write address
      MOVB @VDPRD,R1         * Read byte from VDP RAM
      B    *R11

* VDP Multiple Byte Read
VMBR   MOVB @R0LB,@VDPWA      * Send low byte of VDP RAM write address
      MOVB R0,@VDPWA         * Send high byte of VDP RAM write address
VMBRLP MOVB @VDPRD,*R1+       * Read byte from VDP RAM
      DEC  R2                * Byte counter
      JNE  VMBRLP            * Check if finished
      B    *R11

* VDP Write to Register
VWTR   MOVB @R0LB,@VDPWA      * Send low byte (value) to write to VDP register
      ORI  R0,>8000          * Set up a VDP register write operation (10)
      MOVB R0,@VDPWA         * Send high byte (address) of VDP register
      B    *R11

      END

 

To run this code, follow these steps:

 

1. Download and install: Classic99, Win994a simulator (which includes the Asm994a assembler), a text editor other than the stock Notepad.

 

2. Copy and save the code using a DOS end-of-line format (Asm994a will not deal with a UNIX or MAC end-of-line format). I called my file hw.asm

 

3. Fire up Asm994a. Select "Add Source File" and navigate to the directory where you saved the hw.asm file. Make sure to set the file filter to "all files *.*" unless you named your source assembly file with a .a99 extension (I don't like .a99)

 

4. Check the "Def Regs", "Produce HEX Obj File", and "Produce Cart BIN File" check boxes.

 

5. In the "Cartridge Binary File" file name box, change the name from hw.bin to hwC.bin (the capital is necessary). Classic99 expects the cartridge image files to have the "C.bin" part at the end of the file.

 

6. Hit the "Start Assembly" button. You should get 0 warnings and 0 errors.

 

7. Fire up Classic99 and choose the Cartridge->User menu. Navigate to the directory where you have the source file, since this is where Asm994a will also have put your binary cartridge file unless you changed the path for some reason.

 

8. Press any key and notice menu has an option: "2 FOR HELLO WORLD" - so press 2

 

The screen will clear and HELLO WORLD! will be displayed centered on the screen.

 

At this point the edit / assemble / test / debug cycle is very fast and simple: edit your code, save, hit "Start Assembly" in Ass994a, hit File->Reset in Classic99, then 2 2 on the keyboard... Nice and easy!

 

Matthew

Share this post


Link to post
Share on other sites

Nice.

 

5. In the "Cartridge Binary File" file name box, change the name from hw.bin to hwC.bin (the capital is necessary). Classic99 expects the cartridge image files to have the "C.bin" part at the end of the file.

I have never had to make the "c" a capital for things to work. :)

Share this post


Link to post
Share on other sites

Filenames are not case sensitive in Windows (normally), so the uppercase C shouldn't matter (existance, yes, case, no). I've just tested here to make sure lowercase works, too, and it seemed okay.

 

Otherwise looks good!

Share this post


Link to post
Share on other sites

Here is a basic layout for a game loop. It is based on the VDP vsync which happens 60 times per second. You can move the @ around with the arrows and it performs basic bounds checking. Compile the same way as the previous example. Hope this helps.

 

Matthew

 

      DEF  STDHDR

* 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
R1LB   EQU  WRKSP+3           * R1 low byte
R2LB   EQU  WRKSP+5           * R2 low byte
R3LB   EQU  WRKSP+7           * R3 low byte
R4LB   EQU  WRKSP+9           * R4 low byte


**
* Set Up Cartridge
*
* Always mapped to the cartidge address range
      AORG >6000

STDHDR BYTE >AA               * Indicates a standard header
      BYTE >01               * Version number
      BYTE >01               * Number of programs (optional)
      BYTE >00               * Not used
      DATA >0000             * Pointer to power-up list (can't use in cartridge ROM)
      DATA PROG              * Pointer to program list
      DATA >0000             * Pointer to DSR list
      DATA >0000             * Pointer to subprogram list
      DATA >0000             * Pointer to ISR list
PROG   DATA >0000             * No next menu item
      DATA MAIN              * Program start address for this menu item
      BYTE 11                * Length of text for menu screen
      TEXT 'HELLO WORLD'
      EVEN

* Text to display.  This is redundant here since the text above used
* for the menu display could have also been used here, but for
* completness a separate message is reserved.

MSG1   TEXT 'HELLO WORLD!'    * Reserve and initialize the message bytes
MSG1E
      EVEN

VSTAT  DATA >8000             * VDP vsync status
V0     DATA 0                 * The value 0
V1     DATA 1                 * The value 1
V2     DATA 2                 * The value 2
V3     DATA 3                 * The value 3
V4     DATA 4                 * The value 4
PL1CHR DATA >4000             * Player 1 character '@'

**
* Scratch pad RAM use - Variables
*
* *THESE MUST BE IN RAM*
*           >8300             * Workspace
*           >831F             * Bottom of workspace
STACK  EQU  >8320             * Subrouting stack, grows down (8 bytes)
*           >8322             * The stack is maintained in R10 and
*           >8324             * supports up to 4 BL calls
*           >8326
PL1XY  EQU  >8328             * Player XY
PL1XY1 EQU  >832A             * Player new XY
PL1OLD EQU  >832C             * Original character under player 1
PL1SPD EQU  >832E             * Player input delay in ticks (60th / sec)


*********************************************************************
*
* Main Entry Point
*
*
MAIN   LIMI 0                 * Prevent the console ISR from running
      LWPI WRKSP             * Set the workspace
      LI   R10,STACK         * Set up the stack pointer

* Clear the screen
      LI   R0,0              * Address in VDP RAM to write to
      LI   R1,>2000          * The byte to write (space)
      LI   R2,768            * Number of bytes to write
      BL   @VSMW             * Write the space multiple times

* Display a message
      LI   R0,395            * (12,11) == 12 * 32 + 11 == 395
      LI   R1,MSG1           * Bytes to write
      LI   R2,MSG1E-MSG1     * Length (let the assembler do the math)
      BL   @VMBW             * Write the data

* Set the 9901 to always read joystick 1 (column 6)
      LI   R1,>0600          * Keyboard column 6 (joystick 1)
      LI   R12,>0024         * Set the CRU port
      LDCR R1,3              * Load the CRU, setting the column latch

* Set the player start position and input timer
      LI   R0,208
      MOV  R0,@PL1XY         * Set initial location to 6,16
      MOV  R0,@PL1XY1
      MOV  @V1,@PL1SPD       * Set to 1 tick to get things started
      LI   R0,>2000
      MOV  R0,@PL1OLD        * Set initial character under player 1 to space

* Wait for the VDP vsync
VWAIT
      CLR  R1
      MOVB @VDPSTA,R1        * Reading clears the VDP sync indicator
      COC  @VSTAT,R1
      JNE  VWAIT             * No vsync, check again

      BL   @DOPLAY

      JMP  VWAIT
*// MAIN


*********************************************************************
*
* Play Game
*
DOPLAY
      MOV  R11,*R10+         * Push return address onto the stack

* Process game
      BL   @PLYINP           * Process player input
*       BL   @DOAI             * AI
*       BL   @DOCOLL           * Process collisions
*       BL   @DOSOND           * Play sounds
      BL   @DODRAW           * Update the display

PLAY99 DECT R10               * Pop return address off the stack
      MOV  *R10,R11
      B    *R11
*// DOPLAY


*********************************************************************
*
* Get player input
*
PLYINP
      DEC  @PL1SPD
      JEQ  PLY00
      B    *R11
PLY00
      MOV  R11,*R10+         * Push return address onto the stack

* Make sure input will be read again in case there is no input
      MOV  @V1,@PL1SPD

      MOV  @PL1XY,R0

* Test the joystick
      LI   R12,>0006         * Base CRU address for joystick 1

      TB   1                 * Left
      JEQ  PLY02
      DEC  R0
      JMP  PLY10

PLY02  TB   2                 * Right
      JEQ  PLY03
      INC  R0
      JMP  PLY10

PLY03  TB   3                 * Down
      JEQ  PLY04
      AI   R0,32
      JMP  PLY10

PLY04  TB   4                 * Up
      JEQ  PLY10
      AI   R0,-32

* Make sure the move is legal
PLY10
      C    R0,@PL1XY         * No movement, then nothing to do
      JEQ  PLY99

* Bounds check
      CI   R0,768
      JHE  PLY99             * Unsigned compare catches < 0 and > 767

* Move okay
      MOV  R0,@PL1XY1        * Save the new location
      MOV  @V3,@PL1SPD       * Control the speed

PLY99
      DECT R10               * Pop return address off the stack
      MOV  *R10,R11
      B    *R11
*// PLYINP


*********************************************************************
*
* Update the display
*
DODRAW
      MOV  R11,*R10+         * Push return address onto the stack

* Make sure the player moved
      C    @PL1XY,@PL1XY1
      JNE  DD10
      MOV  @PL1XY,R0         * At least always draw at current location
      JMP  DD15
* Restore the character under the player
DD10   MOV  @PL1XY,R0         * Current location
      MOV  @PL1OLD,R1        * Original character data
      BL   @VSBW             * Restore background
* Get the character a the new location
      MOV  @PL1XY1,R0        * New location
      BL   @VSBR             * Read character
      MOV  R1,@PL1OLD        * Remember original character
* Draw player 1 at the new location
DD15   MOV  @PL1CHR,R1        * Player 1 character
      BL   @VSBW             * Draw player 1 at new location
      MOV  @PL1XY1,@PL1XY    * Remember new location

DD99
      DECT R10               * Pop return address off the stack
      MOV  *R10,R11
      B    *R11
*// DODRAW


* These are replacements for the console VDP "functions" that are usually
* called with a BLWP.  These are a bit faster and don't have any fluff.

* VDP Single Byte Write
VSBW   MOVB @R0LB,@VDPWA      * Send low byte of VDP RAM write address
      ORI  R0,>4000          * Set read/write bits 14 and 15 to write (01)
      MOVB R0,@VDPWA         * Send high byte of VDP RAM write address
      MOVB R1,@VDPWD         * Write byte to VDP RAM
      B    *R11

* VDP Single Byte Multiple Write
VSMW   MOVB @R0LB,@VDPWA      * Send low byte of VDP RAM write address
      ORI  R0,>4000          * Set read/write bits 14 and 15 to write (01)
      MOVB R0,@VDPWA         * Send high byte of VDP RAM write address
VSMWLP MOVB R1,@VDPWD         * Write byte to VDP RAM
      DEC  R2                * Byte counter
      JNE  VSMWLP            * Check if done
      B    *R11

* VDP Multiple Byte Write
VMBW   MOVB @R0LB,@VDPWA      * Send low byte of VDP RAM write address
      ORI  R0,>4000          * Set read/write bits 14 and 15 to write (01)
      MOVB R0,@VDPWA         * Send high byte of VDP RAM write address
VMBWLP MOVB *R1+,@VDPWD       * Write byte to VDP RAM
      DEC  R2                * Byte counter
      JNE  VMBWLP            * Check if done
      B    *R11

* VDP Single Byte Read
VSBR   MOVB @R0LB,@VDPWA      * Send low byte of VDP RAM write address
      MOVB R0,@VDPWA         * Send high byte of VDP RAM write address
      MOVB @VDPRD,R1         * Read byte from VDP RAM
      B    *R11

* VDP Multiple Byte Read
VMBR   MOVB @R0LB,@VDPWA      * Send low byte of VDP RAM write address
      MOVB R0,@VDPWA         * Send high byte of VDP RAM write address
VMBRLP MOVB @VDPRD,*R1+       * Read byte from VDP RAM
      DEC  R2                * Byte counter
      JNE  VMBRLP            * Check if finished
      B    *R11

* VDP Write to Register
VWTR   MOVB @R0LB,@VDPWA      * Send low byte (value) to write to VDP register
      ORI  R0,>8000          * Set up a VDP register write operation (10)
      MOVB R0,@VDPWA         * Send high byte (address) of VDP register
      B    *R11

      END

  • Like 1

Share this post


Link to post
Share on other sites

Filenames are not case sensitive in Windows (normally), so the uppercase C shouldn't matter (existance, yes, case, no). I've just tested here to make sure lowercase works, too, and it seemed okay.

 

Otherwise looks good!

 

Ah, well, I was reading the Classic99 docs (yes, I really did), and it said this:

 

"Classic99 expects carts to be named "fileG.BIN", where 'file' is the common part of the filename, 'G' is either G, C or D for GROM, ROM, or XB Bank 2, and .BIN is the extension."

 

I'm a unix guy, so I took the example literally since case matters in unix file names.

 

Matthew

Share this post


Link to post
Share on other sites

This is just a matter of personal preference but instead of hard coding the string length at 11 there are several other ways of doing this.

      REF   VMBW           ; External function VMBW
SFIRST LI    R0,1           ; VDP target address
      LI    R1,ST          ; Source address in RAM
      LI    R2,ND-ST       ; Number of bytes to write
      BLWP  @VMBW          ; VMBW = Multiple Byte Write
      JMP   $              ; soft-halt ($ means program counter)
ST     TEXT  'HELLO WORLD'
ND     END   SFIRST

Yes, tried it with classic99 and works ok, but the calculated length is too big.

Reason is that the TMS9900 is a 16 bit CPU and opcodes must be at even addresses.

That is why winasm automatically adjusted the address of label ND.

This causes the length to be 0C (12) instead of 0B (11).

 

   
  1  0000 0000        REF   VMBW           ; External function VMBW
  2                   AORG  >A000  <---- Assemble starting at absolute address >A000
  3  A000 0200 SFIRST LI    R0,1           ; VDP target address
  3  A002 0001  
  4  A004 0201        LI    R1,ST          ; Source address in RAM
  4  A006 A012  
  5  A008 0202        LI    R2,ND-ST       ; Number of bytes to write
  5  A00A 000C <----------- should be 0B  
  6  A00C 0420        BLWP  @VMBW          ; VMBW = Multiple Byte Write
  6  A00E 0000  
  7  A010 10FF        JMP   $              ; soft-halt ($ means program counter)
  8  A012 4845 ST     TEXT  'HELLO WORLD'
  8  A014 4C4C  
  8  A016 4F20  
  8  A018 574F  
  8  A01A 524C  
  8  A01C 44    
  9  A01D 0000        EVEN     *>>> Assembler Auto-Generated <<<
 10  A01E 0000 ND     END   SFIRST
 10            

 

Or you can put the length in front of the text. Hopefully the assembler supports mixing numbers and text on the TEXT statement.

      REF   VMBW           ; External function VMBW
SFIRST LI    R0,1           ; VDP target address
      LI    R2,ST          ; Number of bytes to write
      LI    R1,ST+1        ; Source address in RAM
      BLWP  @VMBW          ; VMBW = Multiple Byte Write
      JMP   $              ; soft-halt ($ means program counter)
ST     TEXT  11,'HELLO WORLD'
      END   SFIRST

 

I'm afraid that won't work. There are several reasons:

 

1) You can't mix numbers with TEXT. But you could do the below

ST     BYTE  11
      TEXT  'HELLO WORLD'

 

2) However, you would still be loading R1 with the address of ST instead of the required value. We also still have the issue with the 16 bit registers.

If we do MOV @ST,R1 then R1 gets loaded with >0B48 where >48 is the hex value for character 'H' of 'HELLO WORLD'

 

  1  0000 0000        REF   VMBW           ; External function VMBW
  2                   AORG  >A000
  3  A000 0200 SFIRST LI    R0,1           ; VDP target address
  3  A002 0001  
  4  A004 0202        LI    R2,ST          ; Number of bytes to write
  4  A006 A012 <----- Value of R1 is address of ST  
  5  A008 0201        LI    R1,ST+1        ; Source address in RAM
  5  A00A A013  
  6  A00C 0420        BLWP  @VMBW          ; VMBW = Multiple Byte Write
  6  A00E 0000  
  7  A010 10FF        JMP   $              ; soft-halt ($ means program counter)
  8  A012 0B   ST     BYTE  11
  9  A013 4845        TEXT  'HELLO WORLD'
  9  A015 4C4C  
  9  A017 4F20  
  9  A019 574F  
  9  A01B 524C  
  9  A01D 44    
 10  A01E 0000        END   SFIRST

 

Some of the tricks used for 8 bit processors don't work out for the TMS9900 16-bit processor.

Still you can easily work around the issues reported in the second solution.

However, I'd pick the first solution as its consumes less code and memory space ;)

Edited by retroclouds

Share this post


Link to post
Share on other sites

Thank you Matthew for the steps listed out there. I think I can do this. I'll start tinkering around with it. That cart/test method really makes it simple. At that point can the hwC.bin be flashed to a real cartridge and it work in a real TI?

 

John

Share this post


Link to post
Share on other sites

I'm not sure if the cartridge binary format created by Asm994a is a true machine code hex image that can be written to EPROM. It could be some file format that the Cory (the guy who made Win994a) made up and that Classic99 happens to read. I'm going to find out though, since I have all the equipment and one of Jon's new 64K cartridge boards. retroclouds should be able to chime in on this though, since he made PitFall into a cartridge, and I'm sure on Jon's site (hexbus) he details the process, I just have not looked yet.

 

Matthew

Share this post


Link to post
Share on other sites

I'm not sure if the cartridge binary format created by Asm994a is a true machine code hex image that can be written to EPROM. It could be some file format that the Cory (the guy who made Win994a) made up and that Classic99 happens to read. I'm going to find out though, since I have all the equipment and one of Jon's new 64K cartridge boards. retroclouds should be able to chime in on this though, since he made PitFall into a cartridge, and I'm sure on Jon's site (hexbus) he details the process, I just have not looked yet.

 

Matthew

 

You can burn the cartridge binary image created by Asm994a directly to EPROM. The only thing I had to do was to concatenate the 4 individual binary files into a 32K rom image. I have some more details here.

Share this post


Link to post
Share on other sites
I'm not sure if the cartridge binary format created by Asm994a is a true machine code hex image that can be written to EPROM. It could be some file format that the Cory (the guy who made Win994a) made up and that Classic99 happens to read. I'm going to find out though, since I have all the equipment and one of Jon's new 64K cartridge boards. retroclouds should be able to chime in on this though, since he made PitFall into a cartridge, and I'm sure on Jon's site (hexbus) he details the process, I just have not looked yet.

 

Classic99 doesn't read any of the custom formats that Cory created because he refused to share details of them, and they were too complex to unwind in an evening. It is unfortunate because his cartridge format is very nice. We will have to repeat his work and add description, manual, and cover picture fields to the MESS RPK format instead (the nice part of using XML is we can do that ;) ).

 

The cartridge format that Win99Asm outputs does appear to be a proper TI-formatted cartridge image.

Share this post


Link to post
Share on other sites

This is just a matter of personal preference but instead of hard coding the string length at 11 there are several other ways of doing this.

      REF   VMBW           ; External function VMBW
SFIRST LI    R0,1           ; VDP target address
      LI    R1,ST          ; Source address in RAM
      LI    R2,ND-ST       ; Number of bytes to write
      BLWP  @VMBW          ; VMBW = Multiple Byte Write
      JMP   $              ; soft-halt ($ means program counter)
ST     TEXT  'HELLO WORLD'
ND     END   SFIRST

Yes, tried it with classic99 and works ok, but the calculated length is too big.

Reason is that the TMS9900 is a 16 bit CPU and opcodes must be at even addresses.

That is why winasm automatically adjusted the address of label ND.

This causes the length to be 0C (12) instead of 0B (11).

The assembler should be checking to see if line after the TEXT is an opcode and shouldn't adjust the address unless it is.

END is an assembler directive, not an opcode. I gotta rate that as an oversight.

Does the assembler to the same thing if you have multiple strings together?

If it does it's a major oversight.

 

Or you can put the length in front of the text. Hopefully the assembler supports mixing numbers and text on the TEXT statement.

      REF   VMBW           ; External function VMBW
SFIRST LI    R0,1           ; VDP target address
      LI    R2,ST          ; Number of bytes to write
      LI    R1,ST+1        ; Source address in RAM
      BLWP  @VMBW          ; VMBW = Multiple Byte Write
      JMP   $              ; soft-halt ($ means program counter)
ST     TEXT  11,'HELLO WORLD'
      END   SFIRST

 

I'm afraid that won't work. There are several reasons:

 

1) You can't mix numbers with TEXT. But you could do the below

ST     BYTE  11
      TEXT  'HELLO WORLD'

I gotta wonder if the people that write the assemblers actually ever use them much.

That's gotta be the least consistent thing between assemblers.

And everybody coding for classic machines seems to have their own favorite assembler.

In this case you probably don't have as many choices though.

 

2) However, you would still be loading R1 with the address of ST instead of the required value. We also still have the issue with the 16 bit registers.

The pointer doesn't exactly come as a surprise to me since I've never programmed the 99 before, but aren't there instructions for dealing with 8 bit data?

Share this post


Link to post
Share on other sites

The pointer doesn't exactly come as a surprise to me since I've never programmed the 99 before, but aren't there instructions for dealing with 8 bit data?

 

There are a few. Each of these instructions has a word and byte variation:

 

A, AB - Add

C, CB - Compare

S, SB - Subtract

SOC, SOCB - Set ones corresponding (OR, and we have no idea why they didn't just call it "OR")

SZC, SZCB - Set zeroes corresponding (AND NOT, which is almost an "AND", but we got screwed)

MOV, MOVB - Move

 

That's it. All the other instructions operate totally on 16-bit words.

 

And yes, the alignment thing is an oversight in the Asm994a assembler. It has a few little quirks that you have to know about, mostly dealing with the DATA, BYTE, TEXT, and EVEN directives. For example, this works as expected:

 

MSG1   TEXT 'HELLO WORLD'
MSG2   TEXT 'THIS IS A TEST'

 

MSG2 in this case will have an odd address assigned to it. However, having an empty label will cause Asm994a to generate an EVEN statement:

 

MSG1   TEXT 'HELLO WORLD'
MSG1E  EVEN  <-- The Asm994a assembler generates an EVEN statement here
MSG2   TEXT 'THIS IS A TEST'
MSG2E  EVEN  <-- The Asm994a assembler generates an EVEN statement here

 

That was my practice until I read this thread and went back to check what Asm994a was doing with my "end" labels. The "list" file produced by the assembler had assembler added EVEN statements for each label on its own line. However, giving the "end" labels a BYTE directive suppresses the generated EVEN and you get your labels as expected. However, coding in the length blows letting the assembler do it for you (and wastes a byte)... So, this works:

 

MSG1   TEXT 'HELLO WORLD'
MSG1E  BYTE 11
MSG2   TEXT 'THIS IS A TEST'
MSG2E  BYTE 14

 

This is all specific to Asm994a which AFAIK is the only Windows GUI assembler we have, but it seems to produce solid binaries (at least the unreleased V3.010 does). I'm not sure if the original TI E/A produces labels as expected, I'll have to check.

 

Matthew

Share this post


Link to post
Share on other sites

The pointer doesn't exactly come as a surprise to me since I've never programmed the 99 before, but aren't there instructions for dealing with 8 bit data?

 

There are a few. Each of these instructions has a word and byte variation:

 

A, AB - Add

C, CB - Compare

S, SB - Subtract

SOC, SOCB - Set ones corresponding (OR, and we have no idea why they didn't just call it "OR")

SZC, SZCB - Set zeroes corresponding (AND NOT, which is almost an "AND", but we got screwed)

MOV, MOVB - Move

 

That's it. All the other instructions operate totally on 16-bit words.

So I suppose to load a byte you have to clear a 16 bit register (whatever way is fastest) and then Add Byte (AB) from a memory address? Or do you use a MOVB?

Share this post


Link to post
Share on other sites

It depends on the source and destination. There are 16 general purpose memory-mapped registers used by the CPU, all based from the workspace pointer (one of the 3 real hardware registers.) R0 through R15. Registers have high and low bytes, the high byte (MSB) is always even.

 

VAR1  DATA >0102 * VAR1 will be an even address
VAR2  BYTE >01   * VAR2 will be even since it follows a DATA
VAR3  BYTE >02   * VAR3 will be ODD
TABLE BYTE >01,>02,>03,>04,>05

MOV  @VAR1,R0    * Moves >0102 into R0
MOVB @VAR1,R0    * Moves >01 into the high byte of R0 leaving the low byte unchanged
LI   R0,>03      * Loads the Immediate value >0003 into R0
MOVB R0,@VAR3    * This will move >00 to VAR3, since byte instructions on registers work on the MSB
LI   R0,TABLE    * Puts the address of TABLE into R0
MOVB *R0+,R1     * Moves >01 into the MSB of R1
MOVB *R0+,R2     * Moves >02 into the MSB of R2
LI   R0,TABLE
MOV  *R0+,R1     * Moves >0102 into R1
MOV  *R0+,R2     * Moves >0304 into R2

 

Anyway, I think you get the idea. Byte access is to the actual address indicated, and if a register is used then the access is always the MSB. The auto-increment will add 1 or 2 to the value in the register depending on if the instruction was a word or byte based.

 

Instead of using the SWPB (swap byte) command, and because the registers are actually RAM-based, something you will see to get access to the low byte is something like this:

 

* Workspace
WRKSP  EQU  >8300             * Workspace
R0LB   EQU  WRKSP+1           * R0 low byte reqd for VDP routines
R1LB   EQU  WRKSP+3           * R1 low byte
R2LB   EQU  WRKSP+5           * R2 low byte
R4LB   EQU  WRKSP+9           * R4 low byte

LWPI   WRKSP                  * Set the registers to use memory starting at >8300
. . .

MOVB   @VAR1,@R0LB
MOVB   @R1LB,@VAR2

 

In the 99/4A there is 256-bytes of real 16-bit RAM that starts at memory address >8300, so you will pretty much always see the workspace register set to a value somewhere between >8300 and >83E0 (since there are 16 registers and each one is a 16-bit word, 32 bytes of memory are used for the registers. The workspace register is the base address.)

 

Matthew

Edited by matthew180

Share this post


Link to post
Share on other sites

Looks like TI did that because of the memory based registers.

So even if you load a byte, it's not in the correct position to use it for a loop counter without shifting? That's just wrong!

 

Hmmm... which is worse, dealing with 16 bits on the 6502 or 8 bits on the 9900?

I'm wondering.

 

The Wiki says the 9900 only has a 15 bit address buss... so a max of 32K without bank switching I'm assuming?

So that explains why they never shipped the machine with over 16K RAM and when other computers jumped to 64K built in RAM they didn't follow. So the 32K RAM expansions must bank out the ROMs and you can't use it from BASIC?

Share this post


Link to post
Share on other sites

Looks like TI did that because of the memory based registers.

So even if you load a byte, it's not in the correct position to use it for a loop counter without shifting? That's just wrong!

 

You could, but you'd have to use byte-oriented operations to make it happen. The increment and decrement opcodes are 16-bit word only.

 

The Wiki says the 9900 only has a 15 bit address buss... so a max of 32K without bank switching I'm assuming?

So that explains why they never shipped the machine with over 16K RAM and when other computers jumped to 64K built in RAM they didn't follow. So the 32K RAM expansions must bank out the ROMs and you can't use it from BASIC?

 

Not quite. The 15-bit address line is still 64k, just in word-size, 16-bits instead of 8-bits. So all opcodes and data have to begin and end on even-number addresses.

 

This isn't as bad as it sounds, the main limitation is that byte-sized data may end up with a one-byte loss at the end to pad it to an even boundary.

 

The 16k RAM in the console isn't CPU-addressable, it's actually video memory. This is why TI BASIC and Extended BASIC are so sluggish; everything is passing through an 8-bit port to and from video memory. The console itself only has 256 bytes of CPU RAM in itself, the rest is gained through the cartridge port or memory expansions. (Memory expansion adds 32k in an 8k and 24k block, the cartridge port is 8k, but is bank-switchable.)

 

You can't access the memory expansion in TI BASIC, but you have full access in Extended BASIC, although it is hard-coded to use the video memory for string and stack space, and the 24k block for program space and numeric variables. The lower 8k is reserved for assembly language subprograms, if desired.

 

Adamantyr

Share this post


Link to post
Share on other sites

Looks like TI did that because of the memory based registers.

So even if you load a byte, it's not in the correct position to use it for a loop counter without shifting? That's just wrong!

 

You are correct, loading a byte from a variable will load into the MSB of a register, so to use it as a loop counter you have to either clear the register, load the byte, and SWBP - or clear the register and load to the LSB with the trick I showed in a previous post. The other option is to use 2-byte variables, which is a waste sometimes, but perfectly acceptable if you are not going to be pushing memory limits. It is a pain in the ass though for sure.

 

Hmmm... which is worse, dealing with 16 bits on the 6502 or 8 bits on the 9900?

I'm wondering.

 

Haha, I don't know.

 

The Wiki says the 9900 only has a 15 bit address buss... so a max of 32K without bank switching I'm assuming?

So that explains why they never shipped the machine with over 16K RAM and when other computers jumped to 64K built in RAM they didn't follow. So the 32K RAM expansions must bank out the ROMs and you can't use it from BASIC?

 

Well, the 9900 has a 16-bit data bus and always accesses memory on an even address, so that would be 32K "words", which is 64K "bytes". The 16K that came with the computer was actually the dedicated VDP (9918A) memory! The only "real" 16-bit RAM in the machine is what is called the "scratch pad" memory at >8300 to >83FF. The 32K RAM expansion in the PEB is actually multiplexed to an 8-bit bus! Everything in the machine is pretty much on the multiplexed 8-bit bus except the scratch pad RAM and the console ROM, which makes the speed of the 16-bit CPU pretty much useless and not much of an advantage. The multiplexer adds 4 wait states for every memory access, and that on top of the read-before-write nature of the 9900 makes the system as a whole very slow.

 

See, the 99/4A was originally supposed to have the 9995 in it, which is a 16-bit CPU (9900 instruction set compatible) with an 8-bit data bus (think 8088.) The 9995 also has 256 bytes of *internal* scratch pad RAM. However, the 9995 was not ready in time, so they shoe-horned the 16-bit 9900 into the design. So, we have a nice mini-computer level CPU crammed into an 8-bit design. On top of that, the 99/4A designers did all kinds of other stuff to really cripple the machine. Ah well, can't change it now.

 

So that is what we are stuck with. It is still the machine I grew up with so like most TI users I have that nostalgic history that binds me to the computer. At least the 9900 assembly language is rather nice, except for a few nuisances, and much easier and more fun than x86 assembly! :-) Having 16-bit instructions also makes a lot of stuff easier that the 8-bit CPUs can only do with subroutines or multiple commands.

 

Lately I've put aside continued griping about how the machine was jacked by the designers and I just try to have fun with it. :-)

 

Matthew

Edited by matthew180

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