Jump to content
IGNORED

First steps in TI9900 assembly language with prior knowledge of 6502


Dexter

Recommended Posts

Three additions:

 

So now for the TMS9901. This circuit is mapped into port addresses 0x0000 - 0x003e for a total of 32 ports (0x40 / 2). The first 16 ports are named CONTROL / INT1 / INT2 ... / INT15. These ports can be used as input lines. When you call TB 1, for example, you get the current value of INT1.

 

1. The CONTROL line is used to switch between the two modes of the 9901, interrupt vs. timer mode. The others can be used as input lines.

 

2. When you transfer multiple bits via CRU (by LDCR or STCR), the bits are transferred right-to-left. If you write the value 0x000B, the bits will be: 1, 1, 0, 1, 0 ... 0 (each time increasing the CRU address). Also, if you transfer 8 bits or less, the bits are taken from the left byte of the word, while from 9 to 16, the first bit is the rightmost bit of the word.

 

Example (sorry, still in lecture mode ;) ):

 

R12 = 0x1100

R2 = 0x010B

 

LDCR R2,10

 

Result is:

1100 = 1

1102 = 1

1104 = 0

1106 = 1

1108 = 0

110A = 0

110C = 0

110E = 0

1110 = 1

1112 = 0

 

while LDCR R2,4 yields

 

1100 = 1

1102 = 0

1104 = 0

1106 = 0

 

 

3. Not all lines have their own pin on the 9901. The lines INT1 ... INT6 have own pins and are input-only. The lines INT7 ... INT15 share their pins with P15 ... P7 (in that order). They can be configured as inputs or outputs. The lines P1 ... P6 have their own pins again.

 

The confusing detail is that on these shared lines you will read the same value by two different ports, while writing to those two ports has different effects.

 

For instance, /INT7 is the same pin as P15. That is, TB 7 will always deliver the same value as TB 31. However, when you do a SBO 7, you arm the INT7 input to raise interrupts, while a SBO 31 will output a 1 via that pin.

 

4. The /INT lines are active low, but only in terms of triggering interrupts. If you don't let them trigger an interrupt, and you read the pin by TB or STCR, you will get the level that is present at that pin. There is no need to invert.

Link to comment
Share on other sites

Don't worry - as I said, I'll put the text in ninerpedia so that it won't get lost.

 

(However, people still need to remember that it will be found there...)

 

A mention in the development resources sticky, too. The CRU is a mystical beast.

Link to comment
Share on other sites

I’ve typed part of a joystick routine to see if I understand things so far. Judging from existing code, that’s the case.

* SUPPOSE WE WANT TO CHECK JOYSTICK 1
 
* SET THE COLUMN DECODER P5 P4 P3 P2 TO 6, JOYSTICK 1
       LI   R12,36            * SET THE BASE ADDRESS TO P2 (SEE TABLE MIZAPF)
       LI   R3,>0600          * SET THE COLUMN TO 6, THAT'S JOYST1 (SEE TABLE MIZAPF)
       LDCR R3,3              * LOAD THE 3 LSb's OF THE VALUE IN R3, I.E. 6. IS BINARY 110
                              * NOW THE BITS P4 P3 P2 ARE RESP. 1 1 0, I.E. COLUMN 6 IS SELECTED
 
* SET THE ROW TO THE BASE OF THE JOYSTICK BITS INT7  INT6  INT5  INT4  INT3,
*                                         I.E. UP    DOWN  RIGHT LEFT  BUTTON
* THAT'S BIT 3, MUTIPLIED BY 2, IS 6
       LI   R12,6             * BASE ADDRESS FOR THE ROW, FIRST BIT IS INT3 (SEE TABLE MIZAPF)
       TB   0                 * TEST JOYSTICK BUTTON (INT3, BASE)
                              * RESULT IS IN EQ BIT OF THE STATUS REG
       JEQ  SKIP1             * IF ZERO, THEN NO BUTTON PUSSHED, SKIP TO NEXT TEST
ACT_B                         * ACT ON BUTTON PUSHED
 
 
SKIP1
       TB   1                 * TEST JOYSTICK LEFT (INT4, BASE+1)
       TB   2                 * TEST JOYSTICK RIGHT (INT5, BASE+2)
       TB   3                 * TEST JOYSTICK DOWN (INT6, BASE+3)
       TB   4                 * TEST JOYSTICK UP (INT7, BASE+4)

However, to make it more interesting, I tried to decode the key “J”, and two more.

* SUPPOSE WE WANT TO CHECK THE KEY "J"
 
* SET THE COLUMN DECODER P5 P4 P3 P2 TO 3, U 7 F J M 4 R V
       LI   R12,36            * SET THE BASE ADDRESS TO P2 (SEE TABLE MIZAPF)
       LI   R3,>0300          * SET THE COLUMN TO 6, THAT'S U 7 F J M 4 R V (SEE TABLE MIZAPF)
       LDCR R3,2              * LOAD THE 2 LSb's OF THE VALUE IN R3, I.E. 3. IS BINARY 11
                              * NOW THE BITS P3 P2 ARE RESP. 1 1, I.E. COLUMN 3 IS SELECTED
 
* SET THE ROW TO THE BASE OF THE JOYSTICK BITS   INT10  INT9  INT8  INT7  INT6  INT5  INT4  INT3,
*                                         I.E.       V     R     F     4     7     U     J     M
* THAT'S BIT 4, MUTIPLIED BY 2, IS 8
       LI   R12,8             * BASE ADDRESS FOR THE ROW, FIRST BIT IS INT4 (SEE TABLE MIZAPF)
       TB   0                 * TEST FOR KEY "J" (INT4, BASE)
                              * RESULT IS IN EQ BIT OF THE STATUS REG
       JEQ  SKIP1             * IF ZERO, THEN NOT PRESSED, SKIP TO NEXT TEST
ACT_B                         * ACT ON PRESSED
 
 
SKIP1
       TB   >FF               * TEST FOR KEY "M" (INT3, BASE-1)
       TB   >01               * TEST FOR KEY "U" (INT5, BASE+1)
       .
       .
       .

I hope I got it right?

 

Indeed, your table is very handy. I made a little table to see which bit represents which line.

post-41771-0-31696600-1429629901_thumb.png

post-41771-0-44960400-1429629874_thumb.png

Edited by Dexter
  • Like 1
Link to comment
Share on other sites

...

...
       TB   0                 * TEST JOYSTICK BUTTON (INT3, BASE)
                              * RESULT IS IN EQ BIT OF THE STATUS REG
       JEQ  SKIP1             * IF ZERO, THEN NO BUTTON PUSSHED, SKIP TO NEXT TEST
ACT_B                         * ACT ON BUTTON PUSHED
 
 
SKIP1
...

...

 

Remember that JEQ tests for whether the EQ bit is set (1), not whether it is reset (0). It seems you intend to jump to SKIP1 when EQ = 0, which would require JNE.

 

...lee

Link to comment
Share on other sites

       JEQ  SKIP1             * IF ZERO, THEN NO BUTTON PUSSHED, SKIP TO NEXT TEST
ACT_B                         * ACT ON BUTTON PUSHED
 
 
SKIP1
       TB   1                 * TEST JOYSTICK LEFT (INT4, BASE+1)
       TB   2                 * TEST JOYSTICK RIGHT (INT5, BASE+2)
       TB   3                 * TEST JOYSTICK DOWN (INT6, BASE+3)
       TB   4                 * TEST JOYSTICK UP (INT7, BASE+4)

 

Each TB sets the status register EQ bit, i.e. you must use a JEQ / JNE shortly after each TB, at least before the next one.

 

Also keep in mind from the illustration that a keypress in a selected column means that the /INT line is pulled down, so TB will put a 0 into the status register. Accordingly, a JEQ will react when the key is not pressed, because in that case the /INT line is 1. Your comment should read "if one, no button is pressed".

 

Take care not to confuse this test with the test against 0, e.g. MOV R1,R1. If R1 is zero, the EQ bit is set.

Link to comment
Share on other sites

As happens now and then, I was right for the wrong reason! :P

 

I should have corrected the comment:

 

TB 0 * TEST JOYSTICK BUTTON (INT3, BASE)
* RESULT IS IN EQ BIT OF THE STATUS REG
JEQ SKIP1
* IF ONE, THEN NO BUTTON PUSSHED, SKIP TO NEXT TEST
ACT_B
* ACT ON BUTTON PUSHED


SKIP1

 

...lee

Link to comment
Share on other sites

 

...

...
       TB   0                 * TEST JOYSTICK BUTTON (INT3, BASE)
                              * RESULT IS IN EQ BIT OF THE STATUS REG
       JEQ  SKIP1             * IF ZERO, THEN NO BUTTON PUSSHED, SKIP TO NEXT TEST
ACT_B                         * ACT ON BUTTON PUSHED
 
 
SKIP1
...

...

 

Remember that JEQ tests for whether the EQ bit is set (1), not whether it is reset (0). It seems you intend to jump to SKIP1 when EQ = 0, which would require JNE.

 

...lee

 

I often mix those kind of things up, and wonder why it doesn't work as expeced. I'm just getting old :) Same thing with the 6502, there it's BNE and BEQ. However, it seems the code was right, but the comment schould state "IF ONE, THEN NO BUTTON PUSHED..."

 

 

Each TB sets the status register EQ bit, i.e. you must use a JEQ / JNE shortly after each TB, at least before the next one.

 

Also keep in mind from the illustration that a keypress in a selected column means that the /INT line is pulled down, so TB will put a 0 into the status register. Accordingly, a JEQ will react when the key is not pressed, because in that case the /INT line is 1. Your comment should read "if one, no button is pressed".

 

Take care not to confuse this test with the test against 0, e.g. MOV R1,R1. If R1 is zero, the EQ bit is set.

Either it's a common mistake, or you read my mind *shivers*.

 

Indeed, the comment was not right.

This is very enjoyable! :thumbsup: I'll have to make decent notes of this all.

  • Like 1
Link to comment
Share on other sites

Thank you! This is an EXCELLENT explanation. I’m going to use some colors to clarify.

 

BLACK, my own comments / questions

RED, I don’t understand

GREEN, perfectly clear

BLUE, don’t agree ;)

 

Although, I’m not sure what you could explain more or better about the subject. :D

 

 

How about a bit of history?

 

The TI990 was designed as a successor to the TI960 and TI980 computers. The TI960 was a process/production control computer, quite similar to what we would now call a PLC. Its purpose was to interface to all the sensors and actuators of an industrial plant, and that is where the bit addressability comes in handy. A brochure for the TI960 is here:

http://bitsavers.informatik.uni-stuttgart.de/pdf/ti/960/960Flyer.pdf

The CPU would interface to multiple cages with I/O cards through a simple 15 wire bus (12 address, cruin, cruout, cruclk).

 

The TI990 operated two busses that were physically separate, the CRU bus (to communicate with industrial I/O cards) and the TILINE bus (for memory, disk controllers, etc.). As mentioned in an earlier post, the TMS9900 multiplexes the two buses onto shared pins. Up to 7 I/O cages could be hooked up, with up to 24 cards in each cage. A card typically provided 32 I/O's. A description of how TI envisioned the TI990 series can be found here:

http://bitsavers.informatik.uni-stuttgart.de/pdf/ti/990/945250-9701_990_Computer_Family_Systems_Handbook_3ed_May76.pdf

Section 2.1.10 has an extensive explanation of the above.

 

The TI960 heritage was also one of the considerations in choosing the workspace concept for registers, or so it seems.

  • Like 2
Link to comment
Share on other sites

@pnr: Thanks for the info. I always hated the 9900 CRU, why couldn't TI just do I/O like everyone else? However, I assumed there was a reason that would make some sense of the madness, and indeed there is.

 

I think that the CRU was also an experiment, as well as the memory-based registers.

Link to comment
Share on other sites

You can read the Joystick using the same set up and a STCR instead of multiple TBs. Reading 5 bits will give you the button and all four axes in one instruction, which is faster and usually more convenient since you can more quickly free up R12. (STCR R0,5 -- will store the bits in the 5 most significant bits of R0). Then you can simply use COC (Compare Ones Corresponding - sort of an AND/Compare combined instruction) to see if the bits you care about are set.

  • Like 2
Link to comment
Share on other sites

You can read the Joystick using the same set up and a STCR instead of multiple TBs...

 

That's how TurboForth does it:

 

 

; JOYST ( joystick# -- value )
; Scans the joystick returning the direction value
_joyst  mov *stack,r1               ; get unit number
        ai r1,6                     ; use keyboard select 6 for #0, 7 for #1
        swpb r1
        li r12,36
        ldcr r1,3
        li r12,6
        stcr r1,5
        swpb r1
        inv r1
        andi r1,>001f
        mov r1,*stack
        li r12,_next
        mov r12,@>83d6              ; defeat auto screen blanking
        mov @bank1_,@retbnk         ; return to bank 1 if interuupts should fire
        limi 2                      ; briefly enable interrupts
        limi 0                      ; and turn 'em off again
        b @retb0                    ; return to caller in bank 0

 

For joystick #1 use a unit# of 0. For joystick 2 use a unit# of 1.

The returned value value is a bit code which can be decoded as follows:

  • 1=Fire
  • 2=Left
  • 4=Right
  • 8=Down
  • 16=Up

 

Since each direction has its own bit, combinations are possible: for example, UP+LEFT+FIRE returns a value of 19.

Link to comment
Share on other sites

 

How about a bit of history?

 

The TI990 was designed as a successor to the TI960 and TI980 computers. The TI960 was a process/production control computer, quite similar to what we would now call a PLC. Its purpose was to interface to all the sensors and actuators of an industrial plant, and that is where the bit addressability comes in handy. A brochure for the TI960 is here:

http://bitsavers.informatik.uni-stuttgart.de/pdf/ti/960/960Flyer.pdf

The CPU would interface to multiple cages with I/O cards through a simple 15 wire bus (12 address, cruin, cruout, cruclk).

 

The TI990 operated two busses that were physically separate, the CRU bus (to communicate with industrial I/O cards) and the TILINE bus (for memory, disk controllers, etc.). As mentioned in an earlier post, the TMS9900 multiplexes the two buses onto shared pins. Up to 7 I/O cages could be hooked up, with up to 24 cards in each cage. A card typically provided 32 I/O's. A description of how TI envisioned the TI990 series can be found here:

http://bitsavers.informatik.uni-stuttgart.de/pdf/ti/990/945250-9701_990_Computer_Family_Systems_Handbook_3ed_May76.pdf

Section 2.1.10 has an extensive explanation of the above.

 

The TI960 heritage was also one of the considerations in choosing the workspace concept for registers, or so it seems.

Wow, farout! :) Industry technology of the 70’s.

Well it’s not the simplest design one can think of, but it surely is expandable.

 

 

You can read the Joystick using the same set up and a STCR instead of multiple TBs. Reading 5 bits will give you the button and all four axes in one instruction, which is faster and usually more convenient since you can more quickly free up R12. (STCR R0,5 -- will store the bits in the 5 most significant bits of R0). Then you can simply use COC (Compare Ones Corresponding - sort of an AND/Compare combined instruction) to see if the bits you care about are set.

Thanks for pointing out.

 

 

 

@Willsy, @Lee

Hmmm, Forth, a complete mystery to me. Maybe one day...

Link to comment
Share on other sites

 

:imho I forthprogram isauthor if canread else scratchhead then;

 

Hilarious! Also true. It's very easy to write "write-only" code in Forth. Chuck Moore (inventor of Forth) claims that Forth is an amplifier. If you're skilled in the art, then you write exquisite Forth with care. If not, it's an un-readable mess. I'm somewhere inbetween, I think. Of course, what Mr. Moore doesn't say is that, beauty is often in the eye of the beholder! :grin:

Link to comment
Share on other sites

Actually Michael, I'm interested to hear what you think overall of this Forth code. Semi-readable?

 

Mark

 

 

\ screen drawing/management routines
: DefineGraphics ( --)
  \ define the user defined graphics and set colours of character sets
   1 GMODE FALSE SSCROLL !
   32 0 DO I 1 14 COLOR LOOP
   16 1 15 COLOR   \ brick colour
   17 4 15 COLOR   \ robot colour
   18 15 14 COLOR  \ empty tile colour
   19 1 15 COLOR   \ orb colour
   21 3 14 COLOR   \ time bar
   BrickUDG BallUDG1 RobotUDG1 OrbUDG EmptyUDG
   BarUDG2  BarUDG4  BarUDG6   BarUDG8 ;
: NextColumn ( --) \ advance to the next column
   2 +TO Column ;
: DrawIt ( a b c d --)
  \ emits the four ascii characters on the stack to the screen as follows:
  \ ac
  \ bd
   Column 1+ Row 1+ GOTOXY EMIT \ d
   Column 1+ Row GOTOXY EMIT    \ c
   Column Row 1+ GOTOXY EMIT    \ b
   Column Row GOTOXY EMIT       \ a
   NextColumn ;
: DrawBrick ( --) \ draws a brick tile at Column & Row
   128 129 130 131 DrawIt ;
: DrawBall ( --) \ draws a ball tile at Column & Row
   132 133 134 135 DrawIt ;
: DrawRobot ( --) \ draws a robot at Column & Row
   136 137 138 139 DrawIt ;
: DrawOrb ( --) \ draws an Orb at Column & Row
   152 153 154 155 DrawIt ;
: DrawEmpty ( --) \ draws an empty tile at Column & Row
   144 144 144 144 DrawIt ;
: C@++ ( addr -- val addr+1)
  \ fetches a byte from addr and leaves the next address on top of stack
   DUP C@ SWAP 1+ ;
: InfoPanel ( --)
  \ set up the bottom of the display (score, # of lives, etc)
   0 18 GOTOXY ." Time"
   TRUE ZEROS !
   0 19 GOTOXY ." Score:" Score U.
   FALSE ZEROS !
   25 19 GOTOXY ." Lives:" Lives 1- .
   0 20 GOTOXY ." Level:" Level 1+ .
   18 4 $AB 28 HCHAR ;
: DecodeLevel ( level --)
  \ walk thru 38 bytes of level data, decoding, and drawing the screen
  \ level data is encoded in groups of 2 bits, as follows:
  \ 00 - empty space
  \ 01 - brick
  \ 10 - orb
  \ 11 - unused
   38 * 'LevelData +    \ address of level data in memory
   C@++ C@++ -ROT       \ addr robotaddr balladdr
   16 /MOD 2* TO RobotRow 2* TO RobotCol
   16 /MOD 2* TO BallRow 2* TO BallCol
   0 TO Row 0 TO OrbCount
   9 0 DO               \ 9 rows
     0 TO Column
     4 0 DO             \ 4 bytes to a row
       C@++ SWAP        \ get a byte of data
       DUP         $03 AND   \ get 000000xx from the byte
       OVER   2 >> $03 AND  \ get 0000xx00 from the byte
       2 PICK 4 >> $03 AND  \ get 00xx0000 from the byte
       3 PICK 6 >> $03 AND  \ get xx000000 from the byte
   \ now decode the extracted bit patterns...
       4 0 DO
         CASE
           0 OF DrawEmpty ENDOF
           1 OF DrawBrick ENDOF
           2 OF DrawOrb 1 +TO OrbCount ENDOF
         ENDCASE
       LOOP
       DROP
     LOOP
     2 +TO Row
   LOOP
   DROP
   InfoPanel \ draw the score, # lives
   BallRow  TO Row   BallCol TO Column  DrawBall \ set up ball
   RobotRow TO Row  RobotCol TO Column  DrawRobot ( set up robot) ;
: Tick ( --)
  \ update time bar at bottom of playfield
  \ the last character of the time bar is sucessively replaced.
  \ it starts with an 8 pixel wide character, then a 6 pixel,
  \ then a 4 pixel, then a 2 pixel wide character.
  \ Finally, it is replaced with an empty character, and the length
  \ of the bar is reduced.
   18 TimeCol GCHAR \ get the ascii code from the end of the time bar
   DUP $A8 = IF
    \ replace with empty character...
     DROP $AB 18 TimeCol 32 1 HCHAR
     -1 +TO TimeCol
   ELSE
     1- \ otherwise reduce the ascii value by 1
   THEN
   18 TimeCol ROT 1 HCHAR  \ draw new ascii character
   Time 100 = IF     \ time getting low?
     Speech? IF Hurry THEN \ say "hurry hurry hurry"
     21 8 14 COLOR    \ change bar to red
     TRUE TO FlashOrbs?
   THEN ;
: CheckTime ( --)
  \ update Time and check if time bar should be updated
   -1 +TO Time
   Time 4 MOD 0= IF Tick THEN ;
: Bonus ( --)
  \ convert un-used time to bonus points, re-draw time bar as bonus
  \ is computed
   Time 0> IF
     TRUE ZEROS !
     1000            \ sound pitch parameter
     Time 4 / 1 DO
       Tick
       5 +TO Score
       6 19 GOTOXY Score U.
       DUP 7 0 SOUND 8 -
       DUP 8 - 7 1 SOUND
     LOOP
     DROP
     0 15 0 SOUND 0 15 1 SOUND \ turn off sound
     4 18 GOTOXY SPACE
     ZEROS 0!
   THEN ;
: Animate ( --)
   \ animates either the ball or the robot
   Ball IsPlayer? = IF
     BallFrames Frame# CELLS + @ EXECUTE
   ELSE
     RobotFrames Frame# CELLS + @ EXECUTE
   THEN Frame# 1+ 4 MOD TO Frame# ;
Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...