Jump to content
IGNORED

Assembly on the 99/4A


matthew180

Recommended Posts

51 minutes ago, Lee Stewart said:

 

As I mentioned in my last post, MAPAGX is irrelevant to any existing SAMS card. CRU bit 2 is not currently used. This was a design proposal by Thierry.

 

...lee

I agree that the SBO 2/SBZ 2 instructions are irrelevant.  As far as I can tell, though, MAPAGX should work fine, with or without them.  I think it wouldn't need the last SWPB 0 either.  Leaving all three instructions out would be a significant saving in execution time.  How does this look for a routine?

 

* Set page number, for 8 Megs modification 
* Page # in R0, memory address in R1
* Add >0800 to enable mapping in a bank
MAPAGX SRL R1,12             Reduce address to 4K chunks 
       SLA R1,1              Registers are 2 bytes apart 
* Entry address to change mapping when the map register # has already been determined
* Page # in R0, Map register # in R1 (x 2)
MAPRGX LI   R12,>1E00        Superams CRU address 
       SWPB 0                Msb is in odd byte 
       SBO  0                Turn it on
       MOV  R0,@>4000(R1)    Use MOV instead of MOVB
       SBZ  0                Access off
       B    *R11

 

It seems to me that it conforms to the guidelines in that Super-AMS.pdf I uploaded.  I think it should work on the 1M, and up to 16M, memory boards.

  • Like 2
Link to comment
Share on other sites

Some SAMS ideas that others may find useful. ?

 

In my SAMS system I remember the last SAMS page that was used in a memory location.

When it comes time to pull in a page I test if it's already in memory and jump around the mapping code. 

Otherwise I update the variable, which adds one instruction to the mapping code. 

 

This works well on simple setups where I just have one 4K window reserved for SAMS pages.

 

Another thing I have done is to think of 1M SAMS as 16  64K segments.

I keep a variable for the active segment. 

This lets you use 16 bit address math and 64K is usually enough for a specific purpose in my programs. 

 

Also have a piece of code that given a 16 bit address, it computes the SAMS page and the offset which is added to the RAM window address. 

This can be done with a DIV by 4096, to get a quotient and remainder or with some bit twiddling that @Lee Stewart created for me. 

 

This gives you a contiguous 64K virtual memory space that you can access sequentially like it really existed. :) 

 

I would show you the code but it is in Forth's reverse polish Assembler so you need to read it hanging from the ceiling by your feet.

(so said @apersson850 one time) :)

And these ideas are not complicated for people to implement their own way. 

 

 

 

 

  • Like 2
Link to comment
Share on other sites

1 hour ago, hhos said:

I agree that the SBO 2/SBZ 2 instructions are irrelevant.  As far as I can tell, though, MAPAGX should work fine, with or without them.  I think it wouldn't need the last SWPB 0 either.  Leaving all three instructions out would be a significant saving in execution time.  How does this look for a routine?

 

* Set page number, for 8 Megs modification 
* Page # in R0, memory address in R1
* Add >0800 to enable mapping in a bank
MAPAGX SRL R1,12             Reduce address to 4K chunks 
       SLA R1,1              Registers are 2 bytes apart 
* Entry address to change mapping when the map register # has already been determined
* Page # in R0, Map register # in R1 (x 2)
MAPRGX LI   R12,>1E00        Superams CRU address 
       SWPB 0                Msb is in odd byte 
       SBO  0                Turn it on
       MOV  R0,@>4000(R1)    Use MOV instead of MOVB
       SBZ  0                Access off
       B    *R11

 

It seems to me that it conforms to the guidelines in that Super-AMS.pdf I uploaded.  I think it should work on the 1M, and up to 16M, memory boards.

 

It does, indeed. Though for clarity, I would change the SWPB line to

       SWPB R0

 

...lee

  • Like 2
Link to comment
Share on other sites

1 hour ago, retrodroid said:

Does anyone have any comments or suggestions on how to proceed, given my (and my project's) limitations?

 

I suspect my sound list player will use less code and CPU than Tursi's player, since my player is a simple expansion (adds looping and some other features) of the XB list player, IIRC.

 

I'm sure Tursi's player will give you a lot more options and more complex tunes / sounds if you can afford the overhead.  I really don't know the actual computational differences though, and maybe it is not really that much.  You should mess around with both and then decide.

 

Only you will know which is best for your project.  When you choose limitations like an unexpanded console, there will always be trade-offs you will have to consider.

 

Something to think about, Tursi might also have provided external tools used to create music/sounds for his player.  I did not write any such tools for my sound list player, so composition might be more of an effort for the basic sound list player.  Owen was already making music for XB, so that was not a concern he had when I did that player.

 

  • Like 1
Link to comment
Share on other sites

1 hour ago, matthew180 said:

 

I suspect my sound list player will use less code and CPU than Tursi's player, since my player is a simple expansion (adds looping and some other features) of the XB list player, IIRC.

 

I'm sure Tursi's player will give you a lot more options and more complex tunes / sounds if you can afford the overhead.  I really don't know the actual computational differences though, and maybe it is not really that much.  You should mess around with both and then decide.

 

Only you will know which is best for your project.  When you choose limitations like an unexpanded console, there will always be trade-offs you will have to consider.

 

Something to think about, Tursi might also have provided external tools used to create music/sounds for his player.  I did not write any such tools for my sound list player, so composition might be more of an effort for the basic sound list player.  Owen was already making music for XB, so that was not a concern he had when I did that player.

 

There are a lot of tools included with my player, but it's worth noting that it is also just a playback engine, it's not a tracker. You call it as often as you want - normally once a frame, so there are no options or the like for playback. The CPU load depends on the complexity of the tune but if you have notes changing every frame at 60hz it can be as high as about 30% CPU. For simpler tunes, it can be nearly unmeasurable. Super Space Acer seems to hover around 10-15% CPU. The memory cost is about 730 bytes for the player and 88 bytes for data (for music alone), and of course the data cost depends on how well the song compresses, but it compresses well enough that I've used it for standalone data compression as well as the dedicated music.

 

All that said, the tools may be useful for manipulating any music, depending on the complexity you need. I have provided tools to convert TI soundlists (to and from, though I don't remember how comprehensive going back to playlist is. The data format is a simple text file per sound channel - easy to edit. Tools are included to merge channels, convert bass tones to noise, change playback rate, remove duplicates (helps compression), adjust volume, adjust pitch, and more.

 

For samples, as much as a playback engine doesn't really show up in the samples:

https://www.youtube.com/watch?v=vb0D47OZML8

https://www.youtube.com/watch?v=Wtwhu_ETzQ0
https://www.youtube.com/watch?v=8uM2c2y0ZzU
https://www.youtube.com/watch?v=FjqEqPZa8g0

 

Is it easy to use? Very! Is it easy to learn? No. It will take time to learn how best to apply the tools to your system. There's even a strong argument it doesn't make much sense unless the tunes are complex, such as the converted music it was intended for. (Though it will compress simple tunes very well.)

 

But I don't provide any tools for composition either. I'm not interested in making trackers. There are plenty of high quality tools already out there for that.

 

https://harmlesslion.com/software/vgmcomp2/

 

Anyway, I think I'm way off topic. Now back to your program!

 

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

On 1/23/2024 at 2:51 PM, retrodroid said:

However, I may be able to source some of the desired sounds from existing non-TI hardware, in which case option 1 might make things easier.

 

Does anyone have any comments or suggestions on how to proceed, given my (and my project's) limitations?

I can tell you what I did for my no-expansion bare-console projects:

 

For Bounce'n'Pounce, I made a dual sound list player, one dedicated to playing music on the first two tone generators, and the other to playing sound effects on the 3rd tone and noise generator.  Each music or sound effect sound list is crafted to only play on the correct channel.  The workspace RAM usage is slight: one byte counter and a word pointer for each sound list, so 6 bytes total.  An additional feature for sound effect priority was to sort the sound effects in ROM in order of priority, and when a new sound effect is to be played, its address can be compared to the currently playing sound list address to determine if it can be played or not.  Game thread here.

 

For Tilda, I made my own advanced music and sound effect player.  For this I wanted the music to be played using all four generators, but also allow sound effects to play on any generator and allow music to resume after the sound effect ends.  Also, the music turned out to be quite large so I also needed a method of compression so that it will fit in a single ROM bank. The memory usage for this scheme was 4 words for music, and 4 words for sound effect, so 16 bytes total workspace usage, plus a little extra in the VDP memory for pattern-return-addresses.  Each word contains a 3-bit counter, and 13-bit address (the player code and sound+music data reside in the same ROM bank so this is sufficient.)  The compression works by making codes for volume changes a single byte and volume+note 2 bytes.  Also if the music contains repeated patterns, these can be split out into a subroutine-like gosub and return.  If the music needs to loop, it can use the gosub without the return. The notes and sub-pattern addresses are stored in a 256-entry table.  More info here.  I haven't released any of the tools for this, but I could maybe polish them up if it sounds useful.

 

 

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

15 hours ago, PeteE said:

I can tell you what I did for my no-expansion bare-console projects:

 

For Bounce'n'Pounce, I made a dual sound list player, one dedicated to playing music on the first two tone generators, and the other to playing sound effects on the 3rd tone and noise generator.  Each music or sound effect sound list is crafted to only play on the correct channel.  The workspace RAM usage is slight: one byte counter and a word pointer for each sound list, so 6 bytes total.  An additional feature for sound effect priority was to sort the sound effects in ROM in order of priority, and when a new sound effect is to be played, its address can be compared to the currently playing sound list address to determine if it can be played or not.  Game thread here.

 

For Tilda, I made my own advanced music and sound effect player.  For this I wanted the music to be played using all four generators, but also allow sound effects to play on any generator and allow music to resume after the sound effect ends.  Also, the music turned out to be quite large so I also needed a method of compression so that it will fit in a single ROM bank. The memory usage for this scheme was 4 words for music, and 4 words for sound effect, so 16 bytes total workspace usage, plus a little extra in the VDP memory for pattern-return-addresses.  Each word contains a 3-bit counter, and 13-bit address (the player code and sound+music data reside in the same ROM bank so this is sufficient.)  The compression works by making codes for volume changes a single byte and volume+note 2 bytes.  Also if the music contains repeated patterns, these can be split out into a subroutine-like gosub and return.  If the music needs to loop, it can use the gosub without the return. The notes and sub-pattern addresses are stored in a 256-entry table.  More info here.  I haven't released any of the tools for this, but I could maybe polish them up if it sounds useful.

 

 

Both of these sounds pretty great!  My needs are pretty simple so something like the BnP implementation would suffice I would think.  Let me do some more research and testing on my needs and the available options before you invest the time to polish them up, at least on my account.  

  

 

Link to comment
Share on other sites


Two problems for the price of one.

 

The code:
         CLR  @STATUS      CLEAR GPL STATUS BYTE.
         BLWP @GPLLNK    CALL GPLLNK  * ARTICLE BY RAG
         DATA >0034         To start a BEEP
         LIMI 2                   Enable interupt for sound processing
         LIMI 0                   Stop them
         {rest of code}

 

The problem:
    After the code ends, I continue to get the beep tone.

 

    Years ago, I had this problem and found Funnelweb's Loader, didn't have this problem.  This time I though I would be cleaver and use the MG GPLLNK - I have the same symptom with both the EA and MG GPLLNK.

 

    My questions:
    1)   Am I calling or returning wrong?
    2)   What assembly code would I use to shush the sound chip?  When I end the program with a BLWP @0000 on Classic99 the sound stops.
    3)  I've not develed in to the TMS9919 manual - as sound processing hasn't made it to my list of things to read up on yet, but it seems the sound chip continues to do it's soundy thing even after interrupts are turned off?

 

     Added bonus for me, there was some mention that sound lists can be processed from different memory (CPU, GROM, VDP RAM) - and it was alluded to, the handling might need to be different.

Link to comment
Share on other sites

I am going to make a guess.

From my understanding the sound processing code is running on the interrupt. So code is executed every 16mS. 

  • GPLLNK sets up to make a sound. 
  • you enable interrupts
  • the sound producing code turns on the oscillator and probably returns because it is going to do a time delay for the sound duration.
  • When the interrupt returns to your code it hits LIMI 0.
  • So now the rest of the sound generating code never runs because interrupts are off, which includes the delay code and the code to turn off the sound. 

So either a put in a delay before LIMI 0 or do some other stuff before invoking LIMI 0 and see what happens.

Link to comment
Share on other sites

5 hours ago, dhe said:


Two problems for the price of one.

 

The code:
         CLR  @STATUS      CLEAR GPL STATUS BYTE.
         BLWP @GPLLNK    CALL GPLLNK  * ARTICLE BY RAG
         DATA >0034         To start a BEEP
         LIMI 2                   Enable interupt for sound processing
         LIMI 0                   Stop them
         {rest of code}

 

The problem:
    After the code ends, I continue to get the beep tone.

 

    Years ago, I had this problem and found Funnelweb's Loader, didn't have this problem.  This time I though I would be cleaver and use the MG GPLLNK - I have the same symptom with both the EA and MG GPLLNK.

 

    My questions:
    1)   Am I calling or returning wrong?
    2)   What assembly code would I use to shush the sound chip?  When I end the program with a BLWP @0000 on Classic99 the sound stops.
    3)  I've not develed in to the TMS9919 manual - as sound processing hasn't made it to my list of things to read up on yet, but it seems the sound chip continues to do it's soundy thing even after interrupts are turned off?

 

     Added bonus for me, there was some mention that sound lists can be processed from different memory (CPU, GROM, VDP RAM) - and it was alluded to, the handling might need to be different.

 

The normal state of the machine is with interrupts off (LIMI 0). If you have interrupts off, you should not need the "LIMI 2..LIMI 0" sequence. The GPL interpreter starts every GPL statement with that sequence—you do not need to supply it. That said, it really should not matter that you included that sequence.

 

You might also try clearing only the GPL status byte (I presume STATUS = >837C), though again, I do not think it matters that you are also clearing the byte following STATUS:

       CLR  R0
       MOVB R0,@STATUS
       BLWP @GPLLNK
       DATA >0034

 

Other than that, you can turn off the sound generators yourself—code to follow. ;-)

 

...lee

  • Like 2
Link to comment
Share on other sites

13 minutes ago, Lee Stewart said:

Other than that, you can turn off the sound generators yourself—code to follow. ;-)

 

* Mute all four sound generators
*   BL @MUTE to execute this routine
*
SOUND  EQU  >8400
MUTE   LI   R0,>9F00       byte to mute tone generator 0
       LI   R1,4           count for 4 generators
MUTE1  MOVB R0,@SOUND      mute next generator
       AI   R0,>2000       inc to next generator
       DEC  R1             done?
       JNE  MUTE1          do another if not
       RT                  return to caller

 

...lee

Edited by Lee Stewart
clarification
  • Like 3
Link to comment
Share on other sites

50 minutes ago, Lee Stewart said:

 

* Mute all four sound generators
*   BL @MUTE to execute this routine
*
SOUND  EQU  >8400
MUTE   LI   R0,>9F00       byte to mute tone generator 0
       LI   R1,4           count for 4 generators
MUTE1  MOVB R0,@SOUND      mute next generator
       AI   R0,>2000       inc to next generator
       DEC  R1             done?
       JNE  MUTE1          do another if not
       RT                  return to caller

 

...lee

So does the GPLLNK beep code never turn off the sound on its own?

 

Link to comment
Share on other sites

 

    On my own, I initially tried putting two swpb between limi 2 and limi 0.  It's a recommendation I read about when directly accessing vdp memory to slow down access.

      ** Didn't work **

 

   Following @TheBF advice:

   I then went ahead and moved the limi 2/limi 0 combination to the programs main loop.

   That fixed the tone not going off.

       ** Fixed **

 

  

1 hour ago, Lee Stewart said:

The GPL interpreter starts every GPL statement with that sequence—you do not need to supply it.

 

   That advice appears to be spot on, because I removed the limi combo from beneath:

         BLWP @GPLLNK    CALL GPLLNK  * ARTICLE BY RAG
         DATA >0034         To start a BEEP

 

  And it the beep works just find, I don't know that that was common knowledge BITD, as I saw lots of examples with limi combo right below the DATA statement.

 

   So far, I've not had luck with @Lee Stewart 's code to turn off GPLLNK's beep, I will play with it further. I think sound lists just got moved up, before SAMS.

 

Appreciation to both of you!

  • Like 1
Link to comment
Share on other sites

I was surprised how sound list are pretty easy once the magic addresses are discovered. 

I was able to start the whole thing with just Forth. I just needed to add 0LIMI and 2LIMI to let Forth do the interrupt control.

 

Below is how I play sound lists in VDP RAM.  

Maybe you can make some use of this to write it in Assembler with the comments I added,.

 

 83C2 EQU AMSQ      \ interrupt DISABLE bits
\ AMSQ bit meaning:
\ 80 all interrupts disabled
\ 40 motion disabled
\ 20 Sound disabled
\ 10 quit key disabled

 

This code will play a VDP sound list that is "MOVed" into 83CC

 0LIMI                     \ interrupts off
 
 83CC !                    \ store the Vdp address of your sound list in >83CC
 AMSQ C@  5 AND AMSQ C!    \ Read the byte at AMSQ address, AND the value with 5 
 01 83CE C!                \ store 1 in the byte at >83CE. Triggers sound list processing
 83FD C@  01 OR 83FD C!    \ set bit 1 at >83FD makes "VDP RAM the "source" of a sound list 
 
 2LIMI                     \ interrupts on starts the sound

 

  • Like 3
Link to comment
Share on other sites

1 hour ago, TheBF said:

I just needed to add 0LIMI and 2LIMI to let Forth do the interrupt control.

 

Thanks BF,

  Molesworth doesn't cover sound and Lottrup is a pretty quick about,  but, your quote above is the piece I was missing. A one second note, is equal in duration to 100's or 1000's of assembly instructions.  That's why, when putting it in the main loop {waiting for user input} worked - lots of time for the sound list to finish!

  • Like 1
Link to comment
Share on other sites

5 hours ago, dhe said:

 

    On my own, I initially tried putting two swpb between limi 2 and limi 0.  It's a recommendation I read about when directly accessing vdp memory to slow down access.

      ** Didn't work **

Yeah, this won't do anything, especially with respect to VDP memory.

 

LIMI 2 lets the CPU accept interrupt requests from devices, normally the VDP. While it's possible to trigger at exactly that moment, normally the VDP has already set the request and so as soon as the instruction completes, the interrupt is processed (the CPU jumps to the interrupt routine and executes there). When it's finished, it returns to your code and the LIMI 0 turns off interrupt request processing.

 

If the interrupt request from the VDP was not already waiting, then nothing really happens. The CPU checks the interrupt pin, it's not set, and the next instruction turns interrupt processing back off again.

 

Adding a delay between the two just allows more time for the interrupt to maybe be detected, but the net effect is probably not useful and just slows your program down.

 

It's really just polling, and you could get almost the same effect from reading the CRU pin yourself. But, LIMI is faster than loading R12 if you have to, and faster than reading the VDP status register and testing yourself (if you are going to use the console interrupt anyway.)

 

Looks like you got it all solved anyway!

 

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

On 1/28/2024 at 7:26 AM, dhe said:

I've not develed in to the TMS9919 manual - as sound processing hasn't made it to my list of things to read up on yet, but it seems the sound chip continues to do it's soundy thing even after interrupts are turned off?

 

Just to be clear, interrupts have nothing to do with generating sound.  Nothing. Zip. Nada.  The sound chip (SN76489) never stops producing tones; once you set a tone and volume, it continues until you change it, and turning a sound "off" is done by setting the channel volumes to the lowest level, basically muting each channel.

 

Also, the SN76489 "wakes up" (powers on) with all tone volumes at max level, and one of the first things the system has to do is turn the volumes down.  That is how the console generates its "beep" when you turn it on; it is actually the system turning the sound chip volume down.  The length of the beep is the time it takes to execute the "turn volume down" code.  This is also why a system with a dead CPU, or bad ROMs that do not get very far in execution, is called a "screaming banshee" (the sound chip is full on, the screen is black, and nothing is happening).

 

The sound chip is memory mapped, like the VDP, and you can write data to it at any time.  It is a very simple device with a few registers, so I highly recommend taking 30 minutes to just review the datasheet, it might help with some of the mystery.

 

The whole LIMI 2, LIMI 0 thing is only to allow the CPU to check for interrupts, and on the 99/4A that would be a VDP interrupt that will trigger the console ISR, which sets the ISR rate to 50 or 60 Hertz.  Note, you can also poll the VDP directly in your code (this is also covered in detail in this thread), rather than having the system ISR triggered via the VDP interrupt.

 

Unfortunately the CPU's ISR vector table is in ROM, so we cannot change what routine is run when CPU interrupts are enabled.  Thankfully the ISR does allow some of its housekeeping to be enabled/disabled, and there is a single hook at the end where a user-written routine can be called.  A breakdown of the console ISR is in this thread, as well as how to disable the functions you might not want.

 

Part of the ISR is to call the console routine to play a "sound list", which is a format specific to that routine.  Sound lists have nothing directly to do with the sound chip other than ultimately tones and volume levels get sent to the SN76489.

 

As for needing LIMI 2, LIMI 0 for sound, that is only required if you are working with an environment that uses or expects the console sound processing routine to play a sound list.  If you use the console ISR and the sound list routine, then you should have LIMI 2, LIMI 0 in your main loop somewhere, and you need to follow all the memory use rules (mostly the use of scratch-pad RAM) set up by the console ROM, GPL environment, and BASIC environment (if you are interfacing with BASIC).

 

Anyway, there are trade-offs when using the system ISR, and you need to decide if it is worth the effort.  But you need to understand that CPU interrupts, the ISR, and the sound list routine are side effects of the 99/4A architecture and have nothing directly to do with the SN76489 sound chip outputting sound; it does that all by itself with just power applied and a clock input.

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

7 minutes ago, matthew180 said:

Also, the SN76489 "wakes up" (powers on) with all tone volumes at max level, and one of the first things the system has to do is turn the volumes down.  That is how the console generates its "beep" when you turn it on; it is actually the system turning the sound chip volume down.  The length of the beep is the time it takes to execute the "turn volume down" code.

The beep is produced by a portion of GPL code.

 

But you're right that at the start, the first thing the console does is to turn off the volume, and sometimes you can hear some random tones and noises when powering up the system.

 

From TI Intern:

Power up routine
004F : DCLR @>83CE                          Clear sound bytes
0052 : ST   @>9400,>70                      Load speech write
0057 : ST   @>8400,>9F                      Set sound generators
005B : ST   @>8400,>BF 
005F : ST   @>8400,>DF
0063 : ST   @>8400,>FF
0067 : DST  @>8372,>FF7E                    Load data/substack
006B : MOVE >0007 TO REG>01 FROM GROM@>044E Load VDP register
0071 : CLR  @>8300
0073 : MOVE >0071 TO @>8301 FROM @>8300     Clear scratch pad >00->71
0078 : MOVE >003E TO @>8382 FROM @>8300     >82->C0
007E : MOVE >000B TO @>8374 FROM @>8300     >74->7F
0083 : MOVE >0008 TO @>83C2 FROM @>8300     >C2->CA
0089 : DST  @>8303,>0308                    9901 Set CRU
008D : I/O  @>8302,>03
0090 : DST  @>8303,>1001
0094 : I/O  @>8302,>03
0097 : ST   @>8303,>18
009A : I/O  @>8302,>03 
009D : INV  @>8300
009F : ST   @>8303,>02
00A2 : I/O  @>8302,>03
00A5 : ST   @>8303,>01
00A8 : I/O  @>8302,>03
00AB : DST  @>8303,>1602
00AF : I/O  @>8302,>03
00B2 : CALL GROM@>03CB              Accept tone
00B5 : CLR  VDP@>0000               Check VDP RAM
00B8 : ST   @>8370,>10

 

  • Like 3
Link to comment
Share on other sites

22 hours ago, matthew180 said:

 

Just to be clear, interrupts have nothing to do with generating sound.  Nothing. Zip. Nada.  The sound chip (SN76489) never stops producing tones; once you set a tone and volume, it continues until you change it, and turning a sound "off" is done by setting the channel volumes to the lowest level, basically muting each channel.

 

Also, the SN76489 "wakes up" (powers on) with all tone volumes at max level, and one of the first things the system has to do is turn the volumes down.  That is how the console generates its "beep" when you turn it on; it is actually the system turning the sound chip volume down.  The length of the beep is the time it takes to execute the "turn volume down" code.  This is also why a system with a dead CPU, or bad ROMs that do not get very far in execution, is called a "screaming banshee" (the sound chip is full on, the screen is black, and nothing is happening).

 

The sound chip is memory mapped, like the VDP, and you can write data to it at any time.  It is a very simple device with a few registers, so I highly recommend taking 30 minutes to just review the datasheet, it might help with some of the mystery.

 

The whole LIMI 2, LIMI 0 thing is only to allow the CPU to check for interrupts, and on the 99/4A that would be a VDP interrupt that will trigger the console ISR, which sets the ISR rate to 50 or 60 Hertz.  Note, you can also poll the VDP directly in your code (this is also covered in detail in this thread), rather than having the system ISR triggered via the VDP interrupt.

 

Unfortunately the CPU's ISR vector table is in ROM, so we cannot change what routine is run when CPU interrupts are enabled.  Thankfully the ISR does allow some of its housekeeping to be enabled/disabled, and there is a single hook at the end where a user-written routine can be called.  A breakdown of the console ISR is in this thread, as well as how to disable the functions you might not want.

 

Part of the ISR is to call the console routine to play a "sound list", which is a format specific to that routine.  Sound lists have nothing directly to do with the sound chip other than ultimately tones and volume levels get sent to the SN76489.

 

As for needing LIMI 2, LIMI 0 for sound, that is only required if you are working with an environment that uses or expects the console sound processing routine to play a sound list.  If you use the console ISR and the sound list routine, then you should have LIMI 2, LIMI 0 in your main loop somewhere, and you need to follow all the memory use rules (mostly the use of scratch-pad RAM) set up by the console ROM, GPL environment, and BASIC environment (if you are interfacing with BASIC).

 

Anyway, there are trade-offs when using the system ISR, and you need to decide if it is worth the effort.  But you need to understand that CPU interrupts, the ISR, and the sound list routine are side effects of the 99/4A architecture and have nothing directly to do with the SN76489 sound chip outputting sound; it does that all by itself with just power applied and a clock input.

Excellent overview of the situation, thanks!

Link to comment
Share on other sites

For those who may not know:

When I did my internal 32 K RAM, 16 bit wide, expansion I did like many others had done. I installed 64 K RAM and let the normal memory expansion be the part of that RAM where memory expansion normally shows up. But instead of letting the remaining 32 K RAM just sit there unused, I made it possible to page in that part, in 8 K chunks, on all remaining addresses. If these 8 K banks are enabled, they'll overlay the system memory/devices normally at these addresses. So I can copy system ROM into RAM, switch in the RAM and then modify what's now system RAM as I like.

This also makes it possible to give the machine a contiguous 64 K RAM that's 16 bit wide. Only works with assembly of course, but you can write a program as big as the CPU can address. When all of that is enabled, you can't access system resources like VDP, sound and stuff, since they are also overlaid by RAM. But with some thought you may be able to live without that part, or you can use that part of RAM for data storage and switch it away when accessing the screen.

Finally, the ability to page in RAM banks over various system devices also works in reverse. I can page out the internal memory expansion, in which case the standard memory expansion (if available) becomes visible. So an assembly program can swap out the internal 32 K RAM expansion, use the external 32 K RAM and still have another 32 K internal RAM to use for the program. Or whatever you like.

 

The paging is done with eight CRU bits at base address >0400. Thus it can be reached regardless of which memory page is currently in place.

Combined with a standard memory expansion, perhaps implemented on a RAMdisk card or something, this gives the machine a system memory of 96 Kbytes. Add video RAM and you have 112 Kbytes.

I made a simple RAMdisk combining the 64 Kbytes that's not the normal 32 K RAM expansion plus the 56 Kbytes of RAM in the Maximem module. DSR resides in a RAM chip on my IO card. That gave me a 120 Kbytes RAMdisk, a bit larger than a standard SS/SD disk. I wrote drivers only for sector read/write, but that's enough to add this "device" as a storage unit when using Pascal. With the compiler on a RAMdisk compilation time is cut in half (even with source and object code on a standard disk), since the compiler uses very frequent disk access at compile time.

 

Although not a pure assembly programming thing, I wrote this recap here anyway, since it does take some assembly to actually use it.

Edited by apersson850
  • Like 7
  • Thanks 1
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...