Jump to content
IGNORED

Hello, and yet another emulator


MarkB

Recommended Posts

Hi everyone, I'm Mark, I'm new here so thought I'd say hi.
 

My first home computer was a '99 in 1982 and I learned to code TMS9900 assembly with the mini-memory cartridge.  It's still in the garage somewhere but I haven't powered it up in decades.  I have some time on my hands now (semi-retired) so decided to dust off my TI99/4A emulator written in C that I started writing for Windows 23 odd yrs ago.
 

At that time, it could boot from console ROM and basic video was working, but then I parked it.  I haven't used Windows in over 15 yrs - I work exclusively on Linux these days (debian and ubuntu) - so I ported it to Linux using GLUT to create display a framebuffer.  I also added sound emulation with pulse audio, tms9901 timer support and recently added cassette SAVE sound output emulation.  I'm thinking of creating a .wav file from cassette output, now that I have audio samples, and then see if I can OLD programs back in from .wav files.
 

This emulator is just for fun.  There are many superb emulators already out and I have no interest in trying to outdo or replace any of them.  If you want to know what's different or unique about my emulator, the answer is nothing that I know of, except I wrote it myself, which was as I said for fun.  Also, it isn't nearly as complete as some of the others.
 

It does have some useful (to me anyway) features for debugging such as runtime disassembling tms9900 and/or GPL code and showing comments beside them which was very useful for finding compatibility issues with games etc.
 

I've tested munchman, TI invaders, tombstone city, the attack and all work fine AFAICT.  Parsec might work whenever I get around to implementing bitmap mode.  I haven't tried implementing any additional hardware, disk drives, RS232, etc, but as an EE that to me is the most interesting part, so I will probably have a go at emulating legacy peripherals at some stage.


If anyone wants to browse the code it's on github here under MIT license: https://github.com/mburkley/ti994a Comments or suggestions welcome.


Oh and I'm happy to help with any other current projects that need help too now that I've built up knowledge of the internals of the tms9900 and family.


Cheers,


Mark

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

  • 2 weeks later...
On 9/24/2023 at 1:28 PM, dhe said:

Hi, 

  I wanted to express my interest, if you would be willing to do a weekly or monthly development diary.

  Code is easier to understand when someone explains it to you in small bytes, harder when it's a complete grown up project.

Thanks

Sure thing, let me see what I can put together.  It would be good to record the challenges I've found along the way.

  • Thanks 1
Link to comment
Share on other sites

I thought it might be useful to share some findings here while I remember them.


Code is mostly at : https://github.com/mburkley/ti994a/blob/main/sound.c and  https://github.com/mburkley/ti994a/blob/main/interrupt.c


I found the ROM code for CS1 operations pretty arcane.  I spent as much time trying to figure out what the ROM code is doing than implementing the actual cassette emulation.  It uses lots of tricks like incrementing return addresses (INCT 11, etc) to jump over code in the caller instead of setting flag values in registers.  It's almost as if the TI engineers went out of their way to obfuscate the code.  I know they were short on space but it still looks unnecessarily convoluted to me.
 

Timing is naturally very important.  The TMS9901 clock is used extensively for cassette operations.  This clock has a resolution of 3MHz divided by 64 with a 14-bit counter.  So it can count anywhere from from 21.33 microseconds up to 350 milliseconds.  One bit in a cassette file is 730 microseconds long.  Each bit is (or should be) either a half sine wave for zero or full sine wave for a one resulting in a frequency modulation encoding where a zero is a 689Hz tone and a one is a 1369Hz tone.
 

The TI ROM code uses a timer of 363 usec when saving to generate "half bit" timing.  It arms the timer and then enters an infinite loop (opcode >10FF) knowing that the timer ISR will break it out of the infinite loop by incrementing the return address:
 

1410 881E C *14,@>13F0 ; Compare *R14 with>10FF (JMP -2)
1412 13F0
1414 1602 JNE >141A
1416 05CE INCT 14 ;  Trick, jump from infinite loop
1418 0380 RTWP End


The output is flipped every 2 half bits and for a 1 is also flipped in the middle as well.  On reading it uses longer timer of 448 usec to synchronise with the preamble (768 bytes of zeros) and then 405 usec when reading.  If the input has changed after 405 usec then it is a 1 otherwise it is a 0.  After sampling the state, it re-enters a busy loop to wait for it to change again.  It also reads back the remaining time on the counter, during preamble sync. 
 

I used timerfd_create() in Linux to emulate this timer and made blocking poll() and read() calls.  The problem with this approach is a normal desktop process that isn't running as realtime can gets swapped out for longer than the time period during blocking calls.  So I create samples based on what the timer should have delayed for instead of actual elapsed time.  During busy loops, I do use clock_gettime() to keep synchronised to avoid reading or writing the file too quickly.  I guess I could just busy loop everywhere instead of blocking but to consume 100% of a 3GHz CPU to emulate a 3MHz system just feels wrong.  Having said that the process is running at > 50% CPU anyway so maybe not that big a deal.  But it wouldn't prevent preemption anyway and going down the whole isolcpus and real-time task route def sounds overkill.
 

My audio output is a pure sine wave, which isn't exactly what was created by the hardware.  But since we are only reading 1s and 0s that doesn't matter for the functionality.  I might implement a table sometime to make it sound more authentic though, instead of generating a sine which sounds a bit too clean.
 

I'm playing the audio through pulse audio when saving and loading.  It stutters quite a bit on load.  I might try and clean that up sometime by either making audio a separate thread which makes blocking calls to pulse audio or by just replaying a buffer if the sound generation falls behind.  But that would be just for aesthetics - it doesn't affect the functionality.
 

 

cassette-orig.png

cassette-sign.png

  • Like 3
Link to comment
Share on other sites

diff --git a/interrupt.c b/interrupt.c
index e2bd57a..7ed6993 100644
--- a/interrupt.c
+++ b/interrupt.c
@@ -204,6 +204,7 @@ uint8_t tms9901BitGet (int index, uint8_t state)
     if (tms9901.timerMode)
     {
         int timer = tms9901TimerFromNsec (timerRemain (TIMER_TMS9901));
+        timer = 0x3EF0;
         int bit = 1 << (index - 1);
 
         mprintf (LVL_INTERRUPT, "TMS9901 timer remain %04X, return bit %d as %d\n", timer, bit,
diff --git a/sound.c b/sound.c
index f2dae23..0862e8c 100644
--- a/sound.c
+++ b/sound.c
@@ -122,7 +122,7 @@ void soundWavFileOpenRead (void)
 
     cassetteBitsPerSample = hdr.bitsSample;
 
-    cassetteSampleCount = hdr.dataSize / 2;
+    cassetteSampleCount = hdr.dataSize / (cassetteBitsPerSample / 8);
 
     /*  Initialise the synchronisation time */
     clock_gettime (CLOCK_MONOTONIC, &startAudioRead);

 

It looks like reading back remaining time from timerfd is not consistent enough.  I'm hardcoding a value that the ROM expects for now until I think of something better.  Also there is a bug in sample count calculation if WAV files are encoded in 8-bit audio.  But I can load 3rd party files now which is fun.

Screenshot_2023-09-29_13-13-23.png

  • Like 3
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...