VOGONS


Writing a 386/486 emulator, having some issues

Topic actions

Reply 20 of 24, by UselessSoftware

User metadata
Rank Newbie
Rank
Newbie
superfury wrote on 2025-04-08, 21:42:
Afaik (at least older) Linux should plain work without FPU instructions (and using FPU emulation by trapping those opcodes if it […]
Show full quote
UselessSoftware wrote on 2025-04-08, 18:09:
Ah! Didn't realize that, thanks. I'll get that fixed. […]
Show full quote
superfury wrote on 2025-04-08, 10:45:
Have you tried test386.asm yet? […]
Show full quote

Have you tried test386.asm yet?

Also regarding the BT* instructions, isn't the offset (based on the shr 4(16) or 5(32) bit position shifted left by one(16) or two(32)) supposed to be signed? So bits 8000h is actually byte r/m offset-2048, bit 0(as a 16-bit word read and written)?
So for R/M offset 10000h, it's at a lower address.

Edit: Added a simple MS-DOS based testsuite for bit test string instruction (it tests both 16-bit and 32-bit versions on a 3 doubleword bit string in memory, with the pointer on the middle doubleword).
It will test the first doubleword (positive addresses, at the base address), then the second doubleword (base+1), then the previous doubleword (base-1).

It's fully running in 16-bit MS-DOS mode, but the 32-bit addresses will use operand and address size overrides (it uses EDX for addressing easily).

In UniPCemu's current commits, it seems to run properly at least (with additional bugfixes in the emulator performed). Oddly enough, the test386.asm testsuite doesn't verify that the positive and negative ranges are functioning properly (it just tests the register version of those opcodes for some odd reason).

I found a Youtube video that seems to explain it nicely:
https://www.youtube.com/watch?v=en_7DtfT8Cg

Ah! Didn't realize that, thanks. I'll get that fixed.

I did quickly put in a printf debug line that tells me if any BT opcodes are operating on an offset with the sign bit set but it never triggered. So that's not the cause of my current problems, but definitely still need to fix.

I did run test386 before, it runs successfully up into some of the protected mode tests that fail just because I haven't implemented a number of protections yet. I guess it's time to do those. Or comment out those tests and re-compile so it continues and get to them later.

I actually wonder if my FPU is just extremely broken and that's causing the problems. I've barely worked on it, and I do see that Linux executes a few FPU instructions as it loads. Maybe some errors there are tripping it up, I'm just not sure how much it relies on it for the boot process.

Afaik (at least older) Linux should plain work without FPU instructions (and using FPU emulation by trapping those opcodes if it's not implemented on a x87 (using a specific fault handler, enabled using a CR0 bit (EM) for opcodes D8-DFh. Those can be safely ignored (NOP except with a modr/m, no immediate) to emulate without a FPU (the OS will usually execute FNINIT and FNSTSW):

FNINIT
FNSTSW WORD PTR [FPU_STATUS]

My emulator simply does the following for example:
- FNINIT/FNSTSW: Disassemble, behave like a NOP.
- Any other FPU (D8-DF) instruction without EM set: NOP, but disassemble as an 'unimplemented FPU instruction'. Also fetch instruction ModR/M, but ignore it (to continue onwards to any next instruction).
- Any FPU instruction with EM set: trap to the OS using the emulation exception (#NM), like other CPU faults (in this case, just like #UD, except fetching modr/m for the undocumented instruction, as D8-DF instruction fetching is handled first).
In a way, it's like 0F18-0F1F, but all behaving like 0F1F, except optionally throwing an exception on execution (#NM) depending on the EM and TS bits in CR0.

I figured the old kernels shouldn't care, but they were still running a couple of FPU ops so it made me suspicious.

That's close to what I've been doing. When my CPU core is in no-FPU mode, it sets EM in CR0, moves past the ModRM byte, and does an #NM but I was doing it for every FPU op including FNINIT/FNSTSW.

Reply 21 of 24, by superfury

User metadata
Rank l33t++
Rank
l33t++
UselessSoftware wrote on 2025-04-09, 17:45:
superfury wrote on 2025-04-08, 21:42:
Afaik (at least older) Linux should plain work without FPU instructions (and using FPU emulation by trapping those opcodes if it […]
Show full quote
UselessSoftware wrote on 2025-04-08, 18:09:
Ah! Didn't realize that, thanks. I'll get that fixed. […]
Show full quote

Ah! Didn't realize that, thanks. I'll get that fixed.

I did quickly put in a printf debug line that tells me if any BT opcodes are operating on an offset with the sign bit set but it never triggered. So that's not the cause of my current problems, but definitely still need to fix.

I did run test386 before, it runs successfully up into some of the protected mode tests that fail just because I haven't implemented a number of protections yet. I guess it's time to do those. Or comment out those tests and re-compile so it continues and get to them later.

I actually wonder if my FPU is just extremely broken and that's causing the problems. I've barely worked on it, and I do see that Linux executes a few FPU instructions as it loads. Maybe some errors there are tripping it up, I'm just not sure how much it relies on it for the boot process.

Afaik (at least older) Linux should plain work without FPU instructions (and using FPU emulation by trapping those opcodes if it's not implemented on a x87 (using a specific fault handler, enabled using a CR0 bit (EM) for opcodes D8-DFh. Those can be safely ignored (NOP except with a modr/m, no immediate) to emulate without a FPU (the OS will usually execute FNINIT and FNSTSW):

FNINIT
FNSTSW WORD PTR [FPU_STATUS]

My emulator simply does the following for example:
- FNINIT/FNSTSW: Disassemble, behave like a NOP.
- Any other FPU (D8-DF) instruction without EM set: NOP, but disassemble as an 'unimplemented FPU instruction'. Also fetch instruction ModR/M, but ignore it (to continue onwards to any next instruction).
- Any FPU instruction with EM set: trap to the OS using the emulation exception (#NM), like other CPU faults (in this case, just like #UD, except fetching modr/m for the undocumented instruction, as D8-DF instruction fetching is handled first).
In a way, it's like 0F18-0F1F, but all behaving like 0F1F, except optionally throwing an exception on execution (#NM) depending on the EM and TS bits in CR0.

I figured the old kernels shouldn't care, but they were still running a couple of FPU ops so it made me suspicious.

That's close to what I've been doing. When my CPU core is in no-FPU mode, it sets EM in CR0, moves past the ModRM byte, and does an #NM but I was doing it for every FPU op including FNINIT/FNSTSW.

The behaviour is actually pretty simple for a non-FPU case:

Basically, fetch the modr/m, then, when executing the 'instruction':
UniPCemu performs the following fault cases:
Perform #NM fault (exception #7, return EIP on the stack pointing to the instruction that's faulting (like #GP etc.)) when:
- CR0 EM bit is set and an ESC opcode executed (ESC being a non-FWAIT FPU (not MMX or the like, so opcode D8-DF range) instruction).
- Either the MP bit is set or being an ESC opcode while the TS bit is set in CR0.

If it's not faulting, just perform a NOP cycle timing (or 1 instruction in IPS clocking mode like Dosbox/UniPCemu uses). Basically just 8 (modr/m pointing to memory) or 2 (modr/m pointing to a register) cycles in cycle-accurate mode otherwise.

The fault handling is just the fault with the return point being the FPU instruction. Don't handle anything extra there.

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

Reply 22 of 24, by UselessSoftware

User metadata
Rank Newbie
Rank
Newbie

Any idea why a lot of late 90's Award BIOSes might be showing this? I don't know why it might throw a checksum error. I'm loading the full 128 KB BIOS at 0xE0000.

TThUB65.png

One of my machine memory layouts that is doing it:

	//FIC PT-2000
{
{ MACHINE_MEM_RAM, 0x00000, 0xA0000, MACHINE_ROM_ISNOTROM, NULL },
{ MACHINE_MEM_ROM, 0xE0000, 0x20000, MACHINE_ROM_REQUIRED, "roms/machine/ficpt2000/PT2000_v1.01.BIN" },
{ MACHINE_MEM_RAM, 0x100000, 0xF00000, MACHINE_ROM_ISNOTROM, NULL },
{ MACHINE_MEM_ENDLIST, 0, 0, 0, NULL }
},

Looks right to me.

Reply 23 of 24, by superfury

User metadata
Rank l33t++
Rank
l33t++

BIOS checksum is usually ADD instructions, a loop to itherate over the entire (compressed, if applicable) BIOS ROM and some CMP instruction against a checksum byte.
The BIOS ROM might also be shadowed into RAM, causing issues if not properly implemented (chipset-dependent).

The i440fx/i430fx uses PCI for this, for example, to map reads to UMA TO RAM and writes to PCI (essentially shadowing the uncompressed ROM) at the F0000 64K memory block (this is possible down to C0000 even, in 8KB chunks if I remember correctly), using the 440fx PCI northbridge and PIIX/PIIX3 southbridge (configured using PCI accesses).

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

Reply 24 of 24, by superfury

User metadata
Rank l33t++
Rank
l33t++

Just looked up your BIOS's motherboard:
https://theretroweb.com/motherboards/s/fic-pt-2000#chips

It looks like a plain i430fx northbridge and 82271FB (PIIX) southbridge.

That's the same my emulator emulates on it's i430fx motherboard (although a different BIOS being used).

Look into the 82437FX/82438FX chipset's TSC, which is located at PCI address B:D:F address 0:0:0. There, at register 59h-5Fh (in this case 59h for the F0000h memory area) you'll find the register that maps the PAM0[7:4], which directs the area to:
- bit 4 or 1(depending on the area, as they're nibble fields): set=reads from RAM, cleared=reads from PCI (where the BIOS ROM might respond).
- bit 5 or 1: set=write to RAM, cleared=write to PCI (the BIOS ROM might respond if mapped there and not write-protected (that's configured in the PIIX PCI space, at B:D:F 0:1:0 byte 4E bit 2, set=write to BIOS flash are handled by the chip (BIOSS# asserted), cleared=not asserted (thus the chip doesn't respond to writes, ignoring them))
- bit 6 or 2 enables read caches.

The flash ROM that stores the BIOS ROM might be different depending on the motherboard though. You can look for 86box for more information of the implementations that are known (there's 4 known of them apparently). Basically it amounts to writes setting commands or data for commands, reads returning results. And writes addresses select an area to apply to (the block to write to or clear (reset to FFh)). The FFh command is simple: return to normal read mode. There's also an ID command that will report it's ID for flash ROM identification (ID byte selected by the A0 address line from what I remember).

The BIOS ROM is basically copied to low memory, then decompressed and put into the RAM at F0000-FFFFFh. Later, it might update the ESCD area into the ROM by flashing it, by performing a command to erase two blocks (at 1C000 and 1D000) followed by writing the data onto part of those blocks. The chip basically has four commands: read data (normal), erase block followed by confirm erase on the block, program followed by 1 byte to flash and an alternative program command (acts the same as normal program). During the commands (except read status, read ID and read data(default)), reads give the status of the flashing operation.
The exact process for all is in the chip's documentation. UniPCemu simply implements a 28F001BX-T chip.

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