VOGONS


First post, by superfury

User metadata
Rank l33t++
Rank
l33t++

Currently it just reads values from memory one byte at a time, while shifting left to obtain 16-bit and 32-bit quantities. All structures(CPU and hardware) used in the emulator are little-endian-structs. Do I need to add any logic to convert to/from little endian(no bitfields are used anymore)? If so, where would that logic need to be? In the MMU for reading/writing 8/26/32-bit from memory? Do the structures(typedef struct) need to be changed?

Edit: I've modified all CPU registers and word/dword conversion unions to support big-endian byte order. Are there any other data structures(related to x86 PCs) that would need to be changed?

Currently memory as words are read like this(writes are reversed):
Word: addr|(addr+1<<8)
DWord: word addr|(word addr+2<<16)

Is that fully cross-platform compatible? Will it work correctly on big-endian machines?

Btw some registers, like IDTR and GDTR are read as word and byte quantities on the 80286. Is it still cross-platform when shifted that way?

E.g.
LDTR.Limit = readMMUword(addr)
LDTR.Base = readMMUword(addr)|(readMMUbyte(addr+2)<<16)

What about arrays(like descriptors) read from memory in 64-bit quantities using two 32-bit reads(thus converted to big endian), which are read in byte and word interleaved quantities?

Edit: I've modified the full CPU (as well as the SoundFont loading) to process anything from memory as Little-Endian, converted to Big-Endian when used for 16-bit or 24-bit purposes(and read byte-by-byte).

Does this mean UniPCemu can now run on any Big-Endian CPU as well as Little-Endian CPUs?

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

Reply 1 of 6, by vladstamate

User metadata
Rank Oldbie
Rank
Oldbie
superfury wrote:
Currently memory as words are read like this(writes are reversed): Word: addr|(addr+1<<8) DWord: word addr|(word addr+2<<16) […]
Show full quote

Currently memory as words are read like this(writes are reversed):
Word: addr|(addr+1<<8)
DWord: word addr|(word addr+2<<16)

Is that fully cross-platform compatible? Will it work correctly on big-endian machines?

Btw some registers, like IDTR and GDTR are read as word and byte quantities on the 80286. Is it still cross-platform when shifted that way?

E.g.
LDTR.Limit = readMMUword(addr)
LDTR.Base = readMMUword(addr)|(readMMUbyte(addr+2)<<16)

What about arrays(like descriptors) read from memory in 64-bit quantities using two 32-bit reads(thus converted to big endian), which are read in byte and word interleaved quantities?

The key to making code endian agnostic is to have macros for every 16, 32 and 64 bit reads from memory. Stuff like LDTR.Base should not be build like that but instead use a macro that shuffles the words and bytes accordingly based on endianness.

Last edited by vladstamate on 2016-11-18, 19:37. Edited 1 time in total.

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 2 of 6, by vladstamate

User metadata
Rank Oldbie
Rank
Oldbie

Something like this:

#ifdef LITTLE_ENDIAN

READ_16(addr) (mem[addr] | (mem[addr+1] << 8))
READ_32(addr) (READ_16(addr) | (READ_16(addr+2) << 16))
READ_64(addr) (READ_32(addr) | (READ_32(addr+4) << 32))

#else // BIG ENDIAN

READ_16(addr) ((mem[addr]<<8) | mem[addr+1])
READ_32(addr) ((READ_16(addr)<<16) | READ_16(addr+2))
READ_64(addr) ((READ_32(addr)<<32) | READ_32(addr+4))

#endif

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 3 of 6, by superfury

User metadata
Rank l33t++
Rank
l33t++
vladstamate wrote:
Something like this: […]
Show full quote

Something like this:

#ifdef LITTLE_ENDIAN

READ_16(addr) (mem[addr] | (mem[addr+1] << 8))
READ_32(addr) (READ_16(addr) | (READ_16(addr+2) << 16))
READ_64(addr) (READ_32(addr) | (READ_32(addr+4) << 32))

#else // BIG ENDIAN

READ_16(addr) ((mem[addr]<<8) | mem[addr+1])
READ_32(addr) ((READ_16(addr)<<16) | READ_16(addr+2))
READ_64(addr) ((READ_32(addr)<<32) | READ_32(addr+4))

#endif

Is that code still valid when the memory only contains little-endian values? Won't that Big Endian case mess up data read from memory(Little Endian data read as Big Endian data)? There's also a few more cases, which I've noticed in my x86 (80(1)86-80286) code that read memory in different patterns:
- Loading the address of the IDTR and GDTR registers(24-bit values read from memory).
- Loading descriptors from memory. This is currently done the full 8 bytes in one go. The data is swapped for known 16-bit fields only during loading and storing.

The C standard defines the shift left as multiplication by 2 only. Since in little endian format(which is always in memory), shift left by 8/16 are always multiplications by 256 and 65536. So when the memory is read and shifted left by 8/16/24 it will always have the correct value read from memory?

So with little-endian data in memory(always the case in x86 emulation), then always read with shift-left and or(multiples of 😎, should always be read that way to ensure that the data the CPU uses is correct. Since shift-left shouldn't give invalid values, used that way, the Big-Endian method, with little-endian values in the memory array, would result in invalid data? Especially since that data is usually written one byte at a time by the hard disk DMA(8-bit values)?

http://stackoverflow.com/questions/7184789/do … d-on-endianness says in the comment of Kerrek SB, that left shift is multiplication by 2 and right shift division by 2. That much is apparently defined in the C standard. Since it's read using this method, it should be cross-endianness. The only thing I don't know is the way OR and AND instructions effect the resulting value. That should be the same way, seeing as shifts by 8/16/24 bits shift to individual bytes(the same as the size of values being or'ed into the data), thus providing no overlap.

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

Reply 4 of 6, by vladstamate

User metadata
Rank Oldbie
Rank
Oldbie

You are overthinking this too much. Endianess does not have any impact on AND/OR and other instructions and neither does it over shifts (be it left or right). The only time you are impacted by endianess is when you either read or write from memory anything that is not 8bit (a byte) quantity.

I am going to assume all this is from the point of view of you emulating a x86 (which is little endian) on either a little or big endian machine. When you emulate it on another little endian machine there is nothing you need to do as you emulated memory will align up correctly with the host.

When you emulate your x86 on a big endian machine (like say a PPC in a PS3) then any reads or writes from your emulated memory into registers (or stuff you read data into like IDTR and so on) need to go through a translation layer. This is the code I posted above. Once you read the data into registers all your operations will work as normal.

This is how for example the GPU (nv47) in PS3 was really little endian but it resided on a bus that was big endian. They just did a bit of address translation for any reads and write, the entire GPU was otherwise unchanged.

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 5 of 6, by vladstamate

User metadata
Rank Oldbie
Rank
Oldbie

For IDTR or GDTR if you have something like this:

typedef IDTR uint32_t;

you can do

IDTR *my_idtr = (IDTR*)some_address_on_host_memory;

However if you want to modify say just the top 8 bit of an IDTR then you cannot do

(*my_idtr & 0x00FFFFFF) | my_top_8_value

because that is not endian agnostic. You will have to wrap that in the macros I showed you.

Simple rules:

writing 16,32,64 bit quantities and reading them is endian agnostic but modifying subparts is not.

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 6, by superfury

User metadata
Rank l33t++
Rank
l33t++

Currently, all memory accesses in 8/16/32(And 24 in the case of the 80286 IDTR/GDTR, split up as a 16- bit and 8-bit access) are handled in the MMU itself.
Essentially, what it does:

//8-bit memory access
byte MMU_directrb(uint_32 address)
{
return MMU.memory[address];
}

void MMU_directwb(uint_32 address,byte value)
{
MMU.memory[address] = value;
}

//16-bit
word MMU_directrw(uint_32 address)
{
word result;
result = MMU_directrb(address+1);
result <<= 8;
result |= MMU_directrb(address);
return result;
}

void MMU_directww(uint_32 address, word value)
{
MMU_directwb(address,value&0xFF); //Low
value >>= 8;
MMU_directwb(address,value&0xFF); //High
}

This should even work on a Big-Endian PC, because shifting left is always multiplying with 256 in this case? The only exception to this in UniPCemu is the function to read/write descriptors: they both read using MMU_directrb and write using MMU_directwb, but they swap 16-bits variable endianness(using SDL_swaple16 macro) for any 16-bit variables in it after reading it and before writing it back to memory. Currently this runtime patching is also done with the SoundFont loaded in memory at runtime(The sf2 file is loaded directly into allocated RAM. The entries are patched when reading entries from it(into a temporary buffer)).

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