cout970 / mips-emulator

0 stars 0 forks source link

Unsigned add/sub means "doesn't trap on overflow", not "the argument is unsigned" #1

Open iamgreaser opened 8 years ago

iamgreaser commented 8 years ago

Not sure if you still have this issue in the emu that you use in Magneticraft, but a quick skim through the code reveals that you've misunderstood what MIPS means by an "unsigned" add/sub. (SLTU behaves how you'd expect. SLTIU does not.)

The difference between signed and unsigned add/sub is that signed adds fire a trap on signed integer overflow, while unsigned adds do not. If you had a C cross-compiler and looked at the dissasembly you probably wouldn't find a single not-unsigned add or sub.

As far as I am aware, the immediate argument of {ANDI, ORI, XORI} is zero-extended, while the immediate argument of every other I-op is sign-extended... although for LUI this doesn't matter as you're just shifting the damn thing left 16 bits anyway and not actually extending from the top.

SLTIU is a really odd case. The comparison is unsigned, but the immediate input is sign-extended.

Your J ops appear to be correct. (But JALR actually uses rd, not necessarily $31/$ra, to store the return address - you probably don't notice this though as you almost always end up using the default anyway.)

For further reference, this thing really helps - it's mostly PS1-specific but should cover everything you need to know for a usermode-only emulator: http://problemkaputt.de/psx-spx.htm#cpuspecifications

Just be wary that nocash uses some really weird instruction notation.

If you really don't care about trapping, you can possibly get away with making ADD=ADDU, SUB=SUBU, and ADDI=ADDIU, although you seem to be using longs to calculate everything so it should be fairly easy to detect signed overflow.

cout970 commented 8 years ago

Sorry I'm not an expert on MIPS I just learn the basic in the university and I learn the rest on internet, so the emulator it's very different from an real MIPS R2000 processor. I tried to made a separated mod only for the computer and I found some errors in the emulator. I'm pretty sure that I fixed the bug with SLTIU.

One think that I want to ask you, because you seems to know more about MIPS than me, do you know any good C compiler that allows you to create a kernel for mips, I mean to export the result program to plain binary, without any elf format, that most of the compilers uses. It can be very helpful to write my own OS in high level, assembly is to slow for big projects.

Anyway thanks for helping me to find bugs in the emulator.

iamgreaser commented 8 years ago

I just use binutils + gcc + newlib.

Here's my setup, pretty much (note that I run Linux):

binutils:

mkdir xbinutils-mips
cd xbinutils-mips
../binutils-2.25.1/configure --target=mipsel-none-elf --disable-multilib --disable-nls --enable-lto
make -j8
sudo make install
cd ..

GCC, stage 1:

mkdir xgcc-mips
cd xgcc-mips
../gcc-5.3.0/configure --target=mipsel-none-elf --disable-nls --enable-lto --enable-languages=c,c++
make -j8 all-gcc
sudo make install-gcc
cd ..

newlib:

mkdir xnewlib-mips
cd xnewlib-mips
../newlib-2.3.0.20160104/configure --target=mipsel-none-elf --enable-lto
make -j8
sudo make install
cd ..

GCC, stage 2:

cd xgcc-mips
make -j8
sudo make install
cd ..

If nothing errors (har har) you should have a working MIPS cross-compiler.

As for making a plain binary file, you'll want to learn how to make a GNU linker script. Here's the one I use for the EEPROM for OCMIPS:

OUTPUT_FORMAT("elf32-littlemips");
OUTPUT_ARCH(mips);

SECTIONS
{
    . = 0xA0001000;
    .text : { *(.text) }
    .rodata : { *(.rodata*) }
    .data : { *(.data) }
    .bss : { *(.bss) }
}

Yes, it produces an ELF file. But then you run objcopy on it to convert it to a raw binary file.

Something like this:

mipsel-none-elf-gcc -G0 -g -O1 -fno-toplevel-reorder -fomit-frame-pointer -Wl,-T,bootrom.ld -nostdlib -o boot.elf bootrom.c && \
mipsel-none-elf-objcopy -Obinary boot.elf boot.bin && \
true

(where bootrom.ld is the linker script, and the rest is pretty easy to work out)

You'll still need bits of assembly (especially if you ever implement kernel mode + exceptions), e.g. this helps:

//extern int _gp[];
void _start(void)
{
    asm volatile (
        "la $sp, 0xA0004000\n"
        //"la $gp, %0\n"
        :
        : "i"(_gp)
        :
    );

"la" is "load address" and it is a pseudo-op - it will use one or two actual ops. I've commented out the stuff that sets $gp, but if you're using a standard linker script and you have .sbss/.sdata sections (the -G0 flag is supposed to make them not happen but your libc will probably still have them anyway) you'll find that useful.

You'll probably want to write an ELF loader anyway, in which case it's probably best to set a starting address for your program (use -Wl,-Ttext-segment=0xA0005000 with the default linker script for example), build a statically-linked binary, and just not relocate anything.

The flags you'll want to bash into GCC all the time are -mips1 -msoft-float . For speed, -mno-check-zero-division might come in handy.

I hope that helps.