jchase1970 Posted March 13, 2010 Share Posted March 13, 2010 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 Quote Link to comment Share on other sites More sharing options...
+retroclouds Posted March 13, 2010 Share Posted March 13, 2010 (edited) 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 And here is how it looks like in the classic99 emulator Edited March 13, 2010 by retroclouds 1 Quote Link to comment Share on other sites More sharing options...
Nukey Shay Posted March 13, 2010 Share Posted March 13, 2010 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). Quote Link to comment Share on other sites More sharing options...
+retroclouds Posted March 13, 2010 Share Posted March 13, 2010 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. Quote Link to comment Share on other sites More sharing options...
JamesD Posted March 13, 2010 Share Posted March 13, 2010 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 Quote Link to comment Share on other sites More sharing options...
jchase1970 Posted March 14, 2010 Author Share Posted March 14, 2010 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 Quote Link to comment Share on other sites More sharing options...
matthew180 Posted March 14, 2010 Share Posted March 14, 2010 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 Quote Link to comment Share on other sites More sharing options...
sometimes99er Posted March 14, 2010 Share Posted March 14, 2010 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. Quote Link to comment Share on other sites More sharing options...
Tursi Posted March 14, 2010 Share Posted March 14, 2010 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! Quote Link to comment Share on other sites More sharing options...
matthew180 Posted March 14, 2010 Share Posted March 14, 2010 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 1 Quote Link to comment Share on other sites More sharing options...
matthew180 Posted March 14, 2010 Share Posted March 14, 2010 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 Quote Link to comment Share on other sites More sharing options...
+retroclouds Posted March 14, 2010 Share Posted March 14, 2010 (edited) 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 March 14, 2010 by retroclouds Quote Link to comment Share on other sites More sharing options...
sometimes99er Posted March 14, 2010 Share Posted March 14, 2010 Good stuff. And I could have made the same mistakes. Quote Link to comment Share on other sites More sharing options...
jchase1970 Posted March 14, 2010 Author Share Posted March 14, 2010 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 Quote Link to comment Share on other sites More sharing options...
sometimes99er Posted March 14, 2010 Share Posted March 14, 2010 At that point can the hwC.bin be flashed to a real cartridge and it work in a real TI? I think not yet, you can burn EPROM/PROM however. Take a look at hexbus.com. Quote Link to comment Share on other sites More sharing options...
matthew180 Posted March 14, 2010 Share Posted March 14, 2010 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 Quote Link to comment Share on other sites More sharing options...
+retroclouds Posted March 14, 2010 Share Posted March 14, 2010 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. Quote Link to comment Share on other sites More sharing options...
Tursi Posted March 15, 2010 Share Posted March 15, 2010 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. Quote Link to comment Share on other sites More sharing options...
JamesD Posted March 15, 2010 Share Posted March 15, 2010 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? Quote Link to comment Share on other sites More sharing options...
matthew180 Posted March 15, 2010 Share Posted March 15, 2010 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 Quote Link to comment Share on other sites More sharing options...
JamesD Posted March 15, 2010 Share Posted March 15, 2010 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? Quote Link to comment Share on other sites More sharing options...
matthew180 Posted March 15, 2010 Share Posted March 15, 2010 (edited) 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 March 15, 2010 by matthew180 Quote Link to comment Share on other sites More sharing options...
JamesD Posted March 15, 2010 Share Posted March 15, 2010 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? Quote Link to comment Share on other sites More sharing options...
+adamantyr Posted March 15, 2010 Share Posted March 15, 2010 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 Quote Link to comment Share on other sites More sharing options...
matthew180 Posted March 15, 2010 Share Posted March 15, 2010 (edited) 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 March 15, 2010 by matthew180 Quote Link to comment Share on other sites More sharing options...
Recommended Posts
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.