VOGONS


Compaq Deskpro 386 CPU emulation issues?

Topic actions

First post, by superfury

User metadata
Rank l33t++
Rank
l33t++

A simple new thread for my latest problem(AT BIOS booting floppy disks).

DMA is still incorrectly initialized(filled with AAAAh values except FEh for page registers(except final being zeroed). I see it (when booting) executing commands 3,7,8 and A? No errors(except uninitilized BIOS settings) are listed by the BIOS? It should be reading a sector, but it doesn't execute the right FDC commands? (nor can it do so due to invalid DMA register contents)

Edit: Starting with Checkpoint 06h, which is a ripple through DMA register that writes 0xAa as well(maybe premature exits). It's test1.asm, row 439(Intel Assembler source code variant, http://www.intel-assembler.it/portale/5/ibm-a … -souce-code.asp ).

The ROM I'm using is the AT type 2 motherboard ROM ( http://www.minuszerodegrees.net/bios/bios.htm ).

Last edited by superfury on 2018-02-23, 16:57. Edited 3 times in total.

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 1 of 163, by superfury

User metadata
Rank l33t++
Rank
l33t++

Just modified the DMA Page register logic to behave in a more controller-like way(remapping 80-87 and 88-8F to the two controllers based on the controller number instead, halving the code size(and readability).

DMA emulation: https://bitbucket.org/superfury/unipcemu/src/ … dma.c?at=master
FDC emulation: https://bitbucket.org/superfury/unipcemu/src/ … ppy.c?at=master

The reset of the FDC seems to work properly(DMA is set when executing the read operation, not when resetting the controller using function AH=00h). After some quick intervention of the XT-IDE BIOS (which tries to boot the unbootable HDD by default, which happens very quickly now with the latest bugfixes, requiring A to be pressed immediately when the cursor jumps to the top-left of the screen after pressing F1 to continue booting from the BIOS giving an error(BIOS not setup yet, requiring a bootable setup disk for the IBM AT(Same applies to the Compaq Deskpro 386))). I see it starting to execute the INT13h handler with AH=02h(read sector command).

So far up to the read handler (F000:21D7) everything works fine. Now to see what it continues to do...

It loads the NEC command(E6) and DMA command(46), which seems correct afaik(Read sector FDC command and proper DMA command register value).

Edit: It eventually executes SETUP_DBL, which executes a Read ID command, which seems to fail? This is @F000:271D.
Edit: It executes a READ ID command, @F000:2733 going to F000:2781.
Edit: Then it gets to call NEC_TERM at F000:2791 going to F000:2613.

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 2 of 163, by superfury

User metadata
Rank l33t++
Rank
l33t++

After some fixing on the FDC Read ID command to terminate correctly(correct results, errors and actually raising the required IRQ(which was forgotten)). Now MS-DOS 6.22 starts booting from the floppy disk on the IBM PC/AT! 😁

Edit: Eventually, it will fault and simply recalibrate(&sense interrupt), seek(&sense interrupt) and execute the unknown command 0x1D faulting the controller? This will make it unable to continue booting MS-DOS 6.22?

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 3 of 163, by superfury

User metadata
Rank l33t++
Rank
l33t++

Just managed to fix various CMOS calculations(which were overflowing when setting the century byte(0x32th byte in CMOS RAM) to a large enough value). I've also added a support setting(in the file only) for supporting binary vs BCD-style century bytes(binary style only used when the program writes a non-BCD value to byte 0x32 of CMOS RAM). Of course the CMOS time encoder(writes time to CMOS RAM data)/decoder(reads time from CMOS RAMdata) take it into account processing actual time(updating time using the encoder, while writing the time bytes in CMOS uses the decoder).

Now the AT diagnostics ROM(SuperSoft/Landmark diagnostics BIOS) correctly writes the century bytes with any value it likes(as long as it doesn't roll over while it's testing the RAM) and reads the data back unmodified. 😁

It detects two problems: keyboard(AT bios passes all tests during POST) due to unknown problems(?) and CPU(protected mode test faulting on IRET within task).

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 4 of 163, by superfury

User metadata
Rank l33t++
Rank
l33t++

Just tried running the IBM AT diagnostic disk(ATADG207.IMG file), but after following it(it says the CMOS battery has been disconnected) it gives me a 0152 ERROR - SYSTEM BOARD?

Filename
468-AT Diagnostcs error on CMOS cleared.jpg
File size
9.9 KiB
Downloads
No downloads
File comment
Diagnostics error on AT Diagnostics disk?
File license
Fair use/fair dealing exception

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 5 of 163, by vladstamate

User metadata
Rank Oldbie
Rank
Oldbie

Hey Superfury, where did you find the IBM AT diagnostic disk? A quick search on Google did not find me anything. I would like to run it on CAPE too.

YouTube channel: https://www.youtube.com/channel/UC7HbC_nq8t1S9l7qGYL0mTA
Collection: http://www.digiloguemuseum.com/index.html
Emulator: https://sites.google.com/site/capex86/
Raytracer: https://sites.google.com/site/opaqueraytracer/

Reply 6 of 163, by superfury

User metadata
Rank l33t++
Rank
l33t++

That AT diagnostics/setup disk on minuszerodegrees can't be used(on UniPCemu), so I did a bit of searching and found it by Googling it using the search terms "ibm pc diagnostics at 2.07" winworld. That disk image is the correct 360K disk image(7-zip reports leftover data on the one from Minuszerodegrees as well).

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 7 of 163, by vladstamate

User metadata
Rank Oldbie
Rank
Oldbie

Thank you! I seem to be able to boot from the one on minuszerodegrees. I can play around in menus, it seems to detect my system components but there are some problems when I test the system board. Yay for more ways to discover and fix bugs! 😀

YouTube channel: https://www.youtube.com/channel/UC7HbC_nq8t1S9l7qGYL0mTA
Collection: http://www.digiloguemuseum.com/index.html
Emulator: https://sites.google.com/site/capex86/
Raytracer: https://sites.google.com/site/opaqueraytracer/

Reply 8 of 163, by superfury

User metadata
Rank l33t++
Rank
l33t++

You do know that the one on minuszerodegrees is probably corrupted in some way? It had more than 512 bytes left at the end(usually floppy disks are using 512-byte sectors). UniPCemu checks if the size is readable when mounting and rejects that disk image, because it cannot reliably read those remaining 511- bytes at the end of the file(it's a 360K disk image with a part of a sector extra data)?

Also, just added an automatic versioning script to my emulator and common emulator framework. Now it can create four builds for Android using Android Studio(all based on the tags and commit date of the currently checked out commit): dev(-)Debug, dev(-)Release, product(-)Debug and product(-)Release, where dev is development versioning, product is release versioning, Debug is an unoptimized build(afaik) and Release is an optimized build. 😁 That should make the Android executables more recognizable and properly upgradable(due to versions no longer reporting 1(total version now, increasing by 1 each commit) and 1.0(proper version according to the "git describe --tags" command, which gives the lastest tag of the build(20171214_1304, which is the latest released build before/at the commit) and any other relevant information for finding the build in the source code(it's followed by the number of commits since the tag(if any), (part of) the hash of the commit(for looking it up), branch information etc..

Did this according to:
https://proandroiddev.com/configuring-android … de-b168952f3323

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 9 of 163, by vladstamate

User metadata
Rank Oldbie
Rank
Oldbie

You are right that the file from minuszerodegrees is larger than it should be (by 1 sector). Other than that however it is identical to the one obtained from the Google search. I do need to fix my img code to reject the incorrect size 360k disk.

YouTube channel: https://www.youtube.com/channel/UC7HbC_nq8t1S9l7qGYL0mTA
Collection: http://www.digiloguemuseum.com/index.html
Emulator: https://sites.google.com/site/capex86/
Raytracer: https://sites.google.com/site/opaqueraytracer/

Reply 10 of 163, by superfury

User metadata
Rank l33t++
Rank
l33t++

Took me a whole day to get automatic versioning fully working in a cross-compiler way. Now a single configuration file is used(which combines with other files to create the entire .res file from the .rc file for Windows). There's now two files containing settings: gitversion.h, which contains the basic settings that usually don't change for the executable and a file that's committed into the repository, but always empty when pushed(it's to be ignored when written by the compiler scripts(Makefile for MinGW64/MSys2 and batch file for the Visual Studio project(which still needs to be manually called before compiling to update the version code of the executable. Still need to figure out how to do so automatically from Visual Studio))). The scripts simply write(with some extra logic for removing incorrect line endings on the batch file) the current git commit string(git describe --tags result) to a define in gitcommitversion.h. That header file is included in gitversion.h which adds the default(defaulting to version 1.0 if it isn't specified in the file(if you've forgotten to call the batch file for Visual Studio)) version, while also adding project-specific fields(application name etc.) as defines. gitversion.h is then included in version.h, which converts them into the format for the main resource file. The main resource file then uses said formatted defines and generates the icon path and other parameters for the executable(so basically the icon and versioning info(File version, various text fields in the executable's properties: product version, company name, file description(git commit data), internal name(product name), legal copyright/legal trademarks/legal trademarks 2(all emptied), original filename, product name and company domain(emptied)).

So now, all PC and Android builds of my emulators(UniPCemu and GBemu) have more detailed data on them(Android only has the git commit data), allowing for easier identification of the executable(and thus know to what commit etc. it belongs to). 😁

The diagnostics boot disk does still error out on the CMOS (timer) ? Could this be because the time is synchronized to realtime(high resolution Windows clock) instead of the speed the emulation runs at itself? So it might be due to a CPU/CMOS speed (on the RTC clock) mismatch?

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 11 of 163, by superfury

User metadata
Rank l33t++
Rank
l33t++

Just managed to update the Visual Studio projects for my two projects using the common emulator framework to call the batch file, thus automatically updating the version info when compiling for Windows in all cases. The same is applied(but through the Android Studio itself, using special gradle settings files) to the Android builds.

Now I don't have to look for what version an executable is build(when someone may ask): it's plainly visible in the Windows and Android builds properties from now on. The only platforms I don't know how to do so(if they have such a thing at all) is on the Linux and PSP platforms(PSP probably doesn't do that, because it requires plain pictures to be createn for it's display other than the application name). Linux probably needs some direct I/O code specifically for giving that information.

Edit: Just taken care of Linux, adding the --version parameter to the common emulator framework which shows the application name and version(which is obtained from the Makefile directly).

The only platform that doesn't support any versioning now is the PSP, but those use a completely different way of even giving such information(they require a png or jpg picture, if I'm not mistaken, to display any sort of information besides the application name). 😁

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 12 of 163, by superfury

User metadata
Rank l33t++
Rank
l33t++

I guess to make it pass the CMOS test on that diagnostics floppy, I'll have to add an alternate timing mode to the RTC. So that instead of running in realtime(Windows clock), it'll run based on the last CMOS clock time(last saved CMOS status loaded in the CMOS during emulator start) combined with emulated time(increased based on the CPU, so just like all other hardware(except Sound Blaster recording)). That way the setup routine shouldn't find any errors anymore?

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 13 of 163, by superfury

User metadata
Rank l33t++
Rank
l33t++

Just implemented the cycle-accurate(128kHz) variant of CMOS timekeeping for all platforms. Now all but the Sound Blaster recording can run at cycle-accurate speeds, if set to cycle-accurate mode.

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 14 of 163, by vladstamate

User metadata
Rank Oldbie
Rank
Oldbie
superfury wrote:

Just implemented the cycle-accurate(128kHz) variant of CMOS timekeeping for all platforms.

I think (but I could be wrong) that the IBM PC AT used Real Time Clock chip of MC146818 which according to the datasheet does not do 128Khz. Where did you find documentation of that frequency for the RTC part of the NVRAM/CMOS ? This is according to the BOCHS port list

0070-007F ----	CMOS RAM/RTC (Real Time Clock  MC146818)

YouTube channel: https://www.youtube.com/channel/UC7HbC_nq8t1S9l7qGYL0mTA
Collection: http://www.digiloguemuseum.com/index.html
Emulator: https://sites.google.com/site/capex86/
Raytracer: https://sites.google.com/site/opaqueraytracer/

Reply 15 of 163, by superfury

User metadata
Rank l33t++
Rank
l33t++

Just tried the setup with the new (almost 100%) cycle-accurate system. It still fails with a "0152 error -system board"?

I know that it's supposed to be a 32kHz crystal, but the multiplier is used for clocking correctly:

Main clock handler at 131072Hz:

OPTINLINE void RTC_Handler(byte lastsecond) //Handle RTC Timer Tick!
{
uint_32 oldrate, bitstoggled=0; //Old output!
oldrate = CMOS.currentRate; //Save the old output for comparision!
++CMOS.currentRate; //Increase the input divider to the next stage(22-bit divider at 64kHz(32kHz square wave))!
bitstoggled = CMOS.currentRate^oldrate; //What bits have been toggled!

if (CMOS.DATA.DATA80.info.STATUSREGISTERB&SRB_ENABLEPERIODICINTERRUPT) //Enabled?
{
if (bitstoggled&(CMOS.RateDivider<<1)) //Overflow on Rate(divided by 2 for our rate, since it's square wave signal converted to Hz)?
{
RTC_PeriodicInterrupt(); //Handle!
}
}

if (CMOS.DATA.DATA80.info.STATUSREGISTERB&SRB_ENABLESQUAREWAVEOUTPUT) //Square Wave generator enabled?
{
if (bitstoggled&CMOS.RateDivider) //Overflow on Rate? We're generating a square wave at the specified frequency!
{
CMOS.SquareWave ^= 1; //Toggle the square wave!
//It's unknown what the Square Wave output is connected to, if it's connected at all?
}
}

if ((CMOS.DATA.DATA80.info.STATUSREGISTERB&SRB_ENABLEUPDATEENDEDINTERRUPT) && ((CMOS.DATA.DATA80.info.STATUSREGISTERB&SRB_ENABLECYCLEUPDATE) == 0) && (CMOS.UpdatingInterruptSquareWave == 0) && (dcc!=DIVIDERCHAIN_RESET)) //Enabled and updated?
{
if (CMOS.DATA.DATA80.info.RTC_Seconds != lastsecond) //We're updated at all?
{
RTC_UpdateEndedInterrupt(); //Handle!
}
}

if (((CMOS.DATA.DATA80.info.RTC_Hours==CMOS.DATA.DATA80.info.RTC_HourAlarm) || ((CMOS.DATA.DATA80.info.RTC_HourAlarm&0xC0)==0xC0)) && //Hour set or ignored?
((CMOS.DATA.DATA80.info.RTC_Minutes==CMOS.DATA.DATA80.info.RTC_MinuteAlarm) || ((CMOS.DATA.DATA80.info.RTC_MinuteAlarm & 0xC0) == 0xC0)) && //Minute set or ignored?
((CMOS.DATA.DATA80.info.RTC_Seconds==CMOS.DATA.DATA80.info.RTC_SecondAlarm) || ((CMOS.DATA.DATA80.info.RTC_SecondAlarm & 0xC0) == 0xC0)) && //Second set or ignored?
(CMOS.DATA.DATA80.info.RTC_Seconds!=lastsecond) && //Second changed and check for alarm?
(CMOS.DATA.DATA80.info.STATUSREGISTERB&SRB_ENABLEALARMINTERRUPT)) //Alarm enabled?
{
RTC_AlarmInterrupt(); //Handle the alarm!
}
}

Main cycle handler:

OPTINLINE void RTC_updateDateTime()
{
//Update the time itself at the highest frequency of 64kHz!
//Get time!
UniversalTimeOfDay tp;
accuratetime currenttime;
byte lastsecond = CMOS.DATA.DATA80.info.RTC_Seconds; //Previous second value for alarm!
CMOS.UpdatingInterruptSquareWave ^= 1; //Toggle the square wave to interrupt us!
if (CMOS.UpdatingInterruptSquareWave==0) //Toggled twice? Update us!
{
if (((CMOS.DATA.DATA80.info.STATUSREGISTERB&SRB_ENABLECYCLEUPDATE)==0) && (dcc!=DIVIDERCHAIN_RESET)) //We're allowed to update the time(divider chain isn't reset too)?
{
if (CMOS.DATA.cycletiming==0) //Normal timing?
{
if (getUniversalTimeOfDay(&tp) == 0) //Time gotten?
{
applytimedivergeanceRTC:
if (epochtoaccuratetime(&tp,&currenttime)) //Converted?
{
//Apply time!
applyDivergeance(&currenttime, CMOS.DATA.timedivergeance,CMOS.DATA.timedivergeance2); //Apply the new time divergeance!
CMOS_encodetime(&currenttime); //Apply the new time to the CMOS!
}
}
}
else //Applying delta timing instead?
{
RTC_emulateddeltatiming += RTC_timetick; //Add time to tick!
CMOS.DATA.timedivergeance += RTC_emulateddeltatiming/1000000000.0; //Tick seconds!
RTC_emulateddeltatiming = fmod(RTC_emulateddeltatiming,1000000000.0); //Remainder!
double temp;
temp = (double)(CMOS.DATA.timedivergeance2+(RTC_emulateddeltatiming/1000.0)); //Add what we can!
RTC_emulateddeltatiming += fmod((double)RTC_emulateddeltatiming,1000.0); //Save remainder!
if (temp>=1000000.0) //Overflow?
{
CMOS.DATA.timedivergeance += (temp/1000000.0); //Add second(s) on overflow!
temp = fmod(temp,1000000.0); //Remainder!
}
CMOS.DATA.timedivergeance2 = (int_64)temp; //us to store!
tp.tv_sec = 0; //Direct time!
tp.tv_usec = 0; //Direct time!
goto applytimedivergeanceRTC; //Apply the cycle-accurate time!
}
}
}
RTC_Handler(lastsecond); //Handle anything that the RTC has to handle!
}

Where the ratedivider (tap) setting is set like this:

uint_32 getGenericCMOSRate()
{
INLINEREGISTER byte rate;
rate = CMOS.DATA.DATA80.data[0xA]; //Load the rate register!
rate &= 0xF; //Only the rate bits themselves are used!
if ((rate) && (dcc!=DIVIDERCHAIN_DISABLED)) //To use us, also are we allowed to be ticking?
{
if (rate<3) //Rates 1&2 are actually the rate of 8&9!
{
--rate; //Rate is one less: rates 1&2 become 0&1 for patching!
rate |= 8; //Convert rates 0&1 to rates 8&9!
}
--rate; //Rate is one less!
return (1<<rate); //The tap to look at(as a binary number) for a square wave to change state!
}
else //We're disabled?
{
return 0; //We're disabled!
}
}

Divider chain updates:

OPTINLINE void updatedividerchain() //0-1=Disabled, 2=Normal operation, 3-5=TEST, 6-7=RESET
{
switch ((CMOS.DATA.DATA80.data[0xA]>>4)&7) //Divider chain control(dcc in Bochs)!
{
case 0:
case 1:
dcc = DIVIDERCHAIN_DISABLED; //Disabled!
break;
case 2:
dcc = DIVIDERCHAIN_ENABLED; //Enabled!
break;
case 3:
case 4:
case 5:
dcc = DIVIDERCHAIN_TEST; //TEST
break;
case 6:
case 7:
default:
dcc = DIVIDERCHAIN_RESET; //RESET!
break;
}
}

Can you see something going wrong? The cycletiming variable is set when configured for cycle-accurate timing, while zeroed for the realtime synchronization(synchronized to the OS's highest speed RTC clock, e.g. Unix time on Linux(gettimeofday) or device-specific equivalent.

Last edited by superfury on 2018-01-28, 02:09. Edited 1 time in total.

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 16 of 163, by vladstamate

User metadata
Rank Oldbie
Rank
Oldbie

Hmm, if you say so. The datasheet says the lowest time you can get an interrupt via dividers is 30us which is a clock of 32Khz. In other words the RTC only should update at 32Khz as it cannot give the CPU data any more often than that. But again, I have not studied this enough to be 100% sure.

YouTube channel: https://www.youtube.com/channel/UC7HbC_nq8t1S9l7qGYL0mTA
Collection: http://www.digiloguemuseum.com/index.html
Emulator: https://sites.google.com/site/capex86/
Raytracer: https://sites.google.com/site/opaqueraytracer/

Reply 17 of 163, by superfury

User metadata
Rank l33t++
Rank
l33t++

I've added the code for my timing of the RTC above in my previous post.

Edit: Wait a sec..... Is this call:

RTC_Handler(lastsecond); //Handle anything that the RTC has to handle!

not in the wrong position? It's handler is supposed to be called at 64kHz, like the time update directly above it, but it's called at 128kHz instead, thus timing at double the speed?

Edit: Just moved the RTC_Handler call up one row, within the if. It should now be running at 64kHz(square wave output raw signal going up and down). The fastest it can trigger is a change on bit/tap 1 for the RTC update cycle(bit/tap 0 is for the square wave output). Thus it can toggle at a max of 32kHz RTC or 64kHz square wave raw(thus 32kHz square wave signal).
Although my frequency generator generates a 128kHz signal, which is divided by two using a flipflip(xor) to create a 64kHz for the RTC. Oddly enough, the RTC was updating at double the rate of the rest of the chip(so at the max rate of the square wave x 2)? Don't remember why, though.

It's fixed now(still that error?), running at 64kHz raw input to the 22-stage divider(counter), square wave looking for toggles on bit 3+(16kHz-) and RTC on bit 4+(8kHz-).

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 18 of 163, by superfury

User metadata
Rank l33t++
Rank
l33t++

Looking at the docs again. The RTC ticks at one tap higher than the square wave, so minimum of value(decimal) 3=>tap 3=8kHz interrupts.

Edit: BUT, all rates are decreased by one. Thus rate 3(8kHz)=>tap 2. Tap 2 is shifted left one to obtain tap3 for the non-square wave variant thus every 8 ticks.

Let's look at the taps:
Tap 0 is 65536Hz
Tap 1 is 32768Hz
Tap 2 is 16kHz.(square wave minimum output value 3)
Tap 3 is 8192Hz.(interrupt turned on toggle minimum value 3)

So the rate is actually the tap which toggles in my setting, while value 1/2 are redirected to 8/9 and value 0 is disabled?

But currently, value 1 maps to tap 0, while value 3 maps to tap 2. But the tap is shifted left for the RTC, thus producing the correct frequency of 8kHz in the new settings?

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 19 of 163, by superfury

User metadata
Rank l33t++
Rank
l33t++

It seems it's correct: 8kHz is the maximum speed:

The lower four bits (0-3) of status register A select a divider for the 32,768 Hz frequency. The resulting frequency is used for […]
Show full quote

The lower four bits (0-3) of status register A select a divider for the
32,768 Hz frequency. The resulting frequency is used for the periodic interrupt
(IRQ 😎. The system initializes these bits to 0110 binary (or 6 decimal). This
selects a 1,024 Hz frequency (and interrupt rate). Setting the lowest four bits
to a different value changes the interrupt rate *without* interfering with the
time keeping of the RTC/CMOS chip.

The formula to calculate the interrupt rate is:

freq. = 32768 >> (rate-1)

where '>>' stands for the 'shift left' operation and 'rate' is the value of the
lower four bits of status register A. This value must be between 1 and 15
(selecting 0 stops the periodic interrupt). The interrupt frequency you can
choose is thus always a power of 2 between 2 Hz and 32768 Hz. There is a
caveat, however. With an input signal of 32768 Hz, the timer output 'rolls
over' if you set the value 'rate' to 2 or 1. Instead of producing periodic
interrupts of 61.0 æs or 30.5 æs respectively, they produce 7.81 ms and 3.91 ms
interrupts. The fastest interrupt rate you can generate is 8 kHz (122 æs
interrupts), by setting 'rate' to 3. Higher frequencies require a higher base
frequency, and this would require a different crystal than is installed in the
IBM PC-AT.

From: https://www.compuphase.com/int70.txt

BUT, to make the square wave channel run at the same frequency, it'll have to take one stage in the divider lower(otherwise you'll end up with half the frequency intended)? That's why I'm using a 64kHz signal and double step for the interrupt.

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io