Emulare GameBoy Advance

Stato
Discussione chiusa ad ulteriori risposte.

Marcus Aseth

Utente Attivo
404
138
OS
Windows 10
Salve gente, sto provando a creare un'emulatore per il GBA (codice sotto per chi è interessato) il chè si traduce nell'emulare un processore ARM7TDMI, ed avrei una domanda al riguardo per chi ha familiarità con questa architettura.
Date le diverse pattern che può assumere un'istruzione (immagine sotto) come faccio a distinguere che tipo di pattern sto cercando di decodificare?
Ad esempio, nel codice sotto ho lo switch pronto per gestire tutte le "Data processing and FSR transfer" che come vedete ha un Opcode (a differenza della maggior parte delle altre), ma se ricevo una delle altre codifiche senza Opcode come faccio a rendermi conto?!(i valori da 0000 a 1111 nell'opcode sono tutti usati)
PS: se possibile preferirei risposte certe e non speculative, altrimenti si rischia di generare piu confusione che altro.(speculazioni basate su fatti và bene, ma non tirare ad indovinare in sostanza.) Nel peggiore dei casi, prima o poi troverò la giusta pagina nel manuale da oltre 1000 pagine che risponde a questo quesito :D

EDIT:Cmq così a occhio, i bit 27:26:25 potrebbero essere la risposta corretta? mi son reso conto mentre rileggevo il post, provo un pò a cercare il manuale su quei 3 nel frattempo

ARM7TDMI.PNG
.h
C++:
#pragma once


template<typename T, typename Parameter>
class NamedType
{
public:
    explicit NamedType(T v) :value(v) {}

    operator T() const { return value; }
    T value;
};

template<typename E>
constexpr decltype(auto) Code(E val)
{
    return static_cast<typename std::underlying_type<E>::type>(val);
}

using Byte = uint8_t;
using HalfWord = uint16_t;
using Word = uint32_t;
using DoubleWord = uint64_t;

using Instruction = NamedType < uint32_t, struct InstructionType>;

enum EQUIT_VAL
{
    QUIT_EMU,
    QUIT_DISASSEMBLER
};

class ClientWindow;
class Disassembler;

class GBAEmulator
{
public:
    GBAEmulator(HINSTANCE hInstance);
    ~GBAEmulator();

    HINSTANCE GetHInstance()const;
    bool GetIsRunning()const;
    void SetIsRunning(bool state);
    const std::array<Word, 16>& GetRegisters()const;
    Word GetCPSR()const;
    std::unique_ptr<Disassembler>& GetDisassembler();


    int Run();

    static LRESULT WINAPI ClientWinProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam);
    static LRESULT WINAPI DisassemblerWinProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam);
private:
    bool LoadRom(const std::string& pathName = "");
    void ClearMemory();
    void ReleaseMemory();
    bool UnhandledInstructionError(Instruction instruction)const;
    void DebugDumpMemoryToTxt(Byte* begin, UINT size, UINT entryPerLine);
    std::string InstructionToString(Word instruction)const;

    inline Instruction FetchInstruction()const;
    inline Byte ToCond(Instruction instruction)const;
    inline Byte ToOpcode(Instruction instruction)const;
    bool IsAllowedToExecute(Instruction instruction)const;
    bool DecodeInstruction(Instruction instruction);

    bool m_isRunning;

    HINSTANCE m_hInstance;
    std::unique_ptr<ClientWindow> m_window;
    std::unique_ptr<Disassembler> m_disassembler;


    const UINT memorySize = 34'000'000;
    Byte* memory;
    std::array<Word, 16> REG; //General Purpose Registers
    std::array<Word, 5> SPSR;  //Saved Program Status Register
    Word CPSR;                  //Current Program Status Register

  //31               23    20 19    16 15      10     7     4    0
  //╔═╦═╦═╦═╦═╦═══╦═╦════════╦════════╦══════════╦═╦═╦═╦═╦═╦══════╗
  //|N|Z|C|V|Q|Res|J|RESERVED|GE[3:0] | RESERVED |E|A|I|F|T|M[4:0]|   << CPSR
  //╚═╩═╩═╩═╩═╩═══╩═╩════════╩════════╩══════════╩═╩═╩═╩═╩═╩══════╝
};

.cpp
C++:
#include "GBAEmulator_PCH.h"
#include "GBAEmulator.h"
#include "ClientWindow.h"
#include "Disassembler.h"
#include "Console.h"
#include "Menu.h"
#include "GuiFunctions.h"

using namespace std;

enum class ECond
{
    EQ,       //                     Equal           | Z set
    NE,    //                 Not equal--------| Z clear
    CS_HS, //Carry set/unsigned higher or same | C set
    CC_LO, //Carry clear/unsigned lower--------| C clear
    MI,    //            Minus/negative        | N set
    PL,    //     Plus/positive or zero--------| N clear
    VS,    //                  Overflow        | V set
    VC,    //                No overflow--------| V clear
    HI,    //            Unsigned higher           | C set and Z clear
    LS,    //     Unsigned lower or same--------| C clear or Z set
    GE,    //Signed greater than or equal        | N set and V set, or N clear and V clear (N == V)
    LT,    //           Signed less than--------| N set and V clear, or N clear and V set (N != V)
    GT,    //        Signed greater than        | Z clear, and either N set and V set, or N clear and V clear (Z == 0,N == V)
    LE,    // Signed less than or equal--------| Z set, or N set and V clear, or N clear and V set (Z == 1 or N != V)
    AL     //    Always (unconditional)        |
};

enum class EOpcode
{
    AND, // Logical AND                 Rd := Rn AND shifter_operand
    EOR, // Logical Exclusive OR--------Rd := Rn EOR shifter_operand
    SUB, // Subtract                     Rd := Rn - shifter_operand
    RSB, // Reverse Subtract------------Rd := shifter_operand - Rn
    ADD, // Add                         Rd := Rn + shifter_operand
    ADC, // Add with Carry--------------Rd := Rn + shifter_operand + Carry Flag
    SBC, // Subtract with Carry         Rd := Rn - shifter_operand - NOT(Carry Flag)
    RSC, // Reverse Subtract with Carry-Rd := shifter_operand - Rn - NOT(Carry Flag)
    TST, // Test                         Update flags after Rn AND shifter_operand
    TEQ, // Test Equivalence------------Update flags after Rn EOR shifter_operand
    CMP, // Compare                     Update flags after Rn - shifter_operand
    CMN, // Compare Negated-------------Update flags after Rn + shifter_operand
    ORR, // Logical (inclusive) OR        Rd := Rn OR shifter_operand
    MOV, // Move------------------------Rd := shifter_operand (no first operand)
    BIC, // Bit Clear                    Rd := Rn AND NOT(shifter_operand)
    MVN // Move Not--------------------Rd := NOT shifter_operand (no first operand)
};

GBAEmulator::GBAEmulator(HINSTANCE hInstance) :
    REG{}, SPSR{}, CPSR{},
    memory(new Byte[memorySize]()),
    m_isRunning{ true },
    m_hInstance{ hInstance }
{
#ifdef _DEBUG
    CreateConsole();
    SetConsoleTitleA("Debug Console");
#endif

    //GBA resolution: 240x160
    LONG clientWidth = 480, clientHeight = 320;
    m_window = std::make_unique<ClientWindow>(clientWidth, clientHeight, hInstance, this);
}


GBAEmulator::~GBAEmulator()
{
    ReleaseMemory();
}

HINSTANCE GBAEmulator::GetHInstance()const
{
    return m_hInstance;
}

bool GBAEmulator::GetIsRunning()const
{
    return m_isRunning;
}

void GBAEmulator::SetIsRunning(bool state)
{
    m_isRunning = state;
}

std::unique_ptr<Disassembler>& GBAEmulator::GetDisassembler()
{
    return m_disassembler;
}

const std::array<Word, 16>& GBAEmulator::GetRegisters()const
{
    return REG;
}
Word GBAEmulator::GetCPSR()const
{
    return CPSR;
}

int GBAEmulator::Run()
{
    if (!m_window->IsValid())
    {
        return -1;
    }

    //DEBUG setting
    LoadRom("TestInstructions.gba");
    Gui::OpenDisassembler(this);

    MSG msg{};
    while (m_isRunning)
    {
        //Window Messages
        if (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE))
        {
            if ((msg.message == WM_QUIT) && (msg.wParam == EQUIT_VAL::QUIT_DISASSEMBLER))
            {
                m_disassembler.release();
            }

            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }

        //Update
        if (m_disassembler) m_disassembler->Updade(0.0f);

        //Render
        if (m_window) m_window->Draw();
        if (m_disassembler) m_disassembler->Draw();

        //Fetch - Decode - Execute
        m_isRunning = DecodeInstruction(FetchInstruction());
    }

    return 0;
}

bool GBAEmulator::LoadRom(const string& pathName)
{
    string path(pathName);
    bool isValidPath = true;
    if (pathName.empty())
    {
        char filename[MAX_PATH]{};

        OPENFILENAME openFile{};
        openFile.lStructSize = sizeof(OPENFILENAME);
        openFile.hwndOwner = m_window->Hwnd();
        openFile.lpstrFilter = "GBA .gba\0*.gba\0\0";
        openFile.lpstrFile = filename;
        openFile.nMaxFile = MAX_PATH;
        openFile.lpstrTitle = "Open rom";
        openFile.Flags = OFN_DONTADDTORECENT | OFN_FILEMUSTEXIST;
        isValidPath = GetOpenFileNameA(&openFile);
        path = string(filename);
    }



    if (isValidPath)
    {
        ifstream file(path, ifstream::in | ifstream::binary);
        if (file.fail())
        {
            return Error("Input File Stream failed" + path);
        }

        ClearMemory();

        file.read(reinterpret_cast<char*>(memory), memorySize);
        file.close();
    }
    else
    {
        return Error("Failed to open " + path);
    }

    return true;
}

void GBAEmulator::ClearMemory()
{
    if (memory)    SecureZeroMemory(memory, memorySize);
}

void GBAEmulator::ReleaseMemory()
{
    if (memory) delete[] memory;
}



LRESULT WINAPI GBAEmulator::ClientWinProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{
    static GBAEmulator* emu = nullptr;

    switch (Msg)
    {
        case WM_CREATE:
        {
            emu = static_cast<GBAEmulator*>(reinterpret_cast<CREATESTRUCT*>(lParam)->lpCreateParams);
            break;
        }

        case WM_DESTROY:
        {
            emu->m_isRunning = false;
            if (emu->m_disassembler.get())
                PostQuitMessage(EQUIT_VAL::QUIT_DISASSEMBLER);
            PostQuitMessage(EQUIT_VAL::QUIT_EMU);
            break;
        }

        case WM_SYSCOMMAND:
        {
            if (wParam != SC_MAXIMIZE)
            {
                return DefWindowProc(hWnd, Msg, wParam, lParam);
            }
            break;
        }

        case WM_COMMAND:
        {
            int menuID = LOWORD(wParam);

            switch (menuID)
            {
                case static_cast<int>(EMENU_ELEMENT::OPEN)
                    :
                {
                    emu->LoadRom();
                    break;
                }

                case static_cast<int>(EMENU_ELEMENT::EXIT)
                    :
                {
                    Gui::Exit(emu);
                    break;
                }

                case static_cast<int>(EMENU_ELEMENT::DISASSEMBLER)
                    :
                {
                    Gui::OpenDisassembler(emu);
                    break;
                }

            }

            break;
        }

        default:
        {
            return DefWindowProc(hWnd, Msg, wParam, lParam);
            break;
        }
    }

    return 0;
}

LRESULT WINAPI GBAEmulator::DisassemblerWinProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{
    static GBAEmulator* emu = nullptr;

    switch (Msg)
    {
        case WM_DESTROY:
        {
            Gui::CloseDisassembler();
            break;
        }

        case WM_SYSCOMMAND:
        {
            if (wParam != SC_MAXIMIZE)
            {
                return DefWindowProc(hWnd, Msg, wParam, lParam);
            }
            break;
        }

        default:
        {
            return DefWindowProc(hWnd, Msg, wParam, lParam);
            break;
        }
    }

    return 0;
}

void GBAEmulator::DebugDumpMemoryToTxt(Byte* begin, UINT size, UINT entryPerLine = 0)
{
    //TODO: REDO THIS

    ofstream output("MemoryDump.txt");

    output << hex;
    for (size_t i = 0; i < size; i += 4)
    {
        Byte* mem = begin + i;
        for (int offset = 3; offset >= 0; offset--)
        {
            long high = static_cast<long>(*(mem + offset) >> 4);
            long low = static_cast<long>(*(mem + offset) & 0x0F);

            if (high != 0)    output << high;
            else            output << "0";

            if (low != 0)    output << low;
            else            output << "0";
            std::cout << " ";
        }

        entryPerLine++;
        if (entryPerLine >= 1)
        {
            output << endl;
            entryPerLine = 0;
        }
    }
}

Instruction GBAEmulator::FetchInstruction()const
{
    //Program counter: Register 15 is the (PC)
    return Instruction((memory[REG[15]] << 24) | (memory[REG[15] + 1] << 16) | (memory[REG[15] + 2] << 8) | (memory[REG[15] + 3]));
}

Byte GBAEmulator::ToOpcode(Instruction instruction)const
{
    //Bit [24:23:22:21]
    return Byte((instruction & 0x01E00000) >> 21);
}

Byte GBAEmulator::ToCond(Instruction instruction)const
{
    //Bit [31:30:29:28]
    return Byte((instruction & 0xF0000000) >> 28);
}

bool GBAEmulator::IsAllowedToExecute(Instruction instruction)const
{
    static constexpr Byte N{ 8 };
    static constexpr Byte Z{ 4 };
    static constexpr Byte C{ 2 };
    static constexpr Byte V{ 1 };

    Byte state = (CPSR & 0xF0000000) >> 28;

    switch (ToCond(instruction))
    {
        // Z set
        case Code(ECond::EQ):
            return (state & Z);

            // Z clear
        case Code(ECond::NE):
            return (state ^ Z);

            // C set
        case Code(ECond::CS_HS):
            return (state & C);

            // C clear
        case Code(ECond::CC_LO):
            return (state ^ C);

            // N set
        case Code(ECond::MI):
            return (state & N);

            // N clear
        case Code(ECond::PL):
            return (state ^ N);

            // V set
        case Code(ECond::VS):
            return(state & V);

            // V clear
        case Code(ECond::VC):
            return (state ^ V);

            // C set and Z clear
        case Code(ECond::HI):
            return ((state & C) && (state ^ Z));

            // C clear or Z set
        case Code(ECond::LS):
            return ((state ^ C) || (state & Z));

            // N set and V set, or N clear and V clear (N == V)
        case Code(ECond::GE):
            return ((state & N) == (state & V));

            // N set and V clear, or N clear and V set (N != V)
        case Code(ECond::LT):
            return ((state & N) != (state & V));

            //Z clear, and either N set and V set, or N clear and V clear(Z == 0, N == V)
        case Code(ECond::GT):
            return ((state ^ Z) && ((state & N) == (state & V)));

            //Z set, or N set and V clear, or N clear and V set (Z == 1 or N != V)
        case Code(ECond::LE):
            return ((state & Z) || ((state & N) != (state & V)));

            //(unconditional)    
        case Code(ECond::AL):
            return true;
    }
    return true;
}


bool GBAEmulator::DecodeInstruction(Instruction instruction)
{
    Byte opcode = ToOpcode(instruction);

    switch (instruction)
    {
        //  Data processing and FSR transfer
        switch (opcode)
        {

            /*

                31    28      24    21   19    16 15    12 11         0
                ╔════════╦══╦═╦════════╦═╦════════╦════════╦════════════╗
                |  cond  |00|I| opcode |S|   Rn   |   Rd   | shifter_op |
                ╚════════╩══╩═╩════════╩═╩════════╩════════╩════════════╝

                I bit [25] Distinguishes between the immediate and register forms of <shifter_operand>.

                S bit [20] Signifies that the instruction updates the condition codes.

                Rn Specifies the first source operand register.

                Rd Specifies the destination register.

                shifter_operand Specifies the second source operand. See Addressing Mode 1 - Data-processing
                operands on page A5-2 for details of the shifter operands.
            */

            case Code(EOpcode::AND):
            {
                return UnhandledInstructionError(instruction);
                break;
            }
            case Code(EOpcode::EOR):
            {
                return UnhandledInstructionError(instruction);
                break;
            }
            case Code(EOpcode::SUB):
            {
                return UnhandledInstructionError(instruction);
                break;
            }
            case Code(EOpcode::RSB):
            {
                return UnhandledInstructionError(instruction);
                break;
            }

            //ADD{<cond>}{S} <Rd>, <Rn>, <shifter_operand>
            case Code(EOpcode::ADD):
            {
                if (IsAllowedToExecute(instruction))
                {




                }
                break;
            }

            case Code(EOpcode::ADC):
            {
                return UnhandledInstructionError(instruction);
                break;
            }
            case Code(EOpcode::SBC):
            {
                return UnhandledInstructionError(instruction);
                break;
            }
            case Code(EOpcode::RSC):
            {
                return UnhandledInstructionError(instruction);
                break;
            }
            case Code(EOpcode::TST):
            {
                return UnhandledInstructionError(instruction);
                break;
            }
            case Code(EOpcode::TEQ):
            {
                return UnhandledInstructionError(instruction);
                break;
            }
            case Code(EOpcode::CMP):
            {
                return UnhandledInstructionError(instruction);
                break;
            }
            case Code(EOpcode::CMN):
            {
                return UnhandledInstructionError(instruction);
                break;
            }
            case Code(EOpcode::ORR):
            {
                return UnhandledInstructionError(instruction);
                break;
            }
            case Code(EOpcode::MOV):
            {
                return UnhandledInstructionError(instruction);
                break;
            }
            case Code(EOpcode::BIC):
            {
                return UnhandledInstructionError(instruction);
                break;
            }
            case Code(EOpcode::MVN):
            {
                return UnhandledInstructionError(instruction);
                break;
            }


            default:
                return UnhandledInstructionError(instruction);

        }//END  Data processing and FSR transfer
        break;

        default:
            return UnhandledInstructionError(instruction);
    }
    return true;
}

string GBAEmulator::InstructionToString(Word instruction)const
{
    stringstream stream;
    stream << hex;

    for (int stride = 3; stride >= 0; stride--)
    {
        long high = ((instruction >> (stride * 8)) & 0xF0) >> 4;
        long low = ((instruction >> (stride * 8)) & 0x0F);

        if (high != 0) stream << high;
        else stream << "0";
        if (low != 0) stream << low;
        else stream << "0";
        stream << " ";
    }
    return stream.str();
}

bool GBAEmulator::UnhandledInstructionError(Instruction instruction)const
{
    string message("Instruction not yet implemented. INSTRUCTION: " + InstructionToString(instruction));
    ConOut(message);
    MessageBoxA(m_window->Hwnd(), message.c_str(), "Error", MB_OK);
    return false;
}
 
Ultima modifica:

driverfury

Nuovo Utente
9
4
A me sembra abbastanza chiaro e certo che i tre bits[27:25] determinano se i successivi bit siano opcode o altro.

Insomma, hai la struttura delle istruzioni e sai che se i bits[27:25] sono uguali a 0b001 allora per certo i bit successivi sono opcode.

Devi semplicemente limitarti ad implementare l'instruction set.
 
  • Mi piace
Reazioni: Marcus Aseth

Marcus Aseth

Utente Attivo
404
138
OS
Windows 10
Grazie @driverfury, alla fine c'ero arrivato (tra ieri e stanotte) l'unico problema è che ho scritto la funzione sotto basandomi sul diagramma dell' "arm architecture reference manual" anzichè "arm7dmi" e solo ora mi son reso conto ri-aprendo questo topic, che i due sono diversi =_= (immagine sotto) Per sicurezza, cancello e rifaccio.
Edit: forse non c'è bisogno di cancellare, per ora faccio alcuni test e vedo se dà i risultati giusti :S

arm_arch_ref_manual.PNG

C++:
EInstructionType ToInstructionType(const RawInstruction& bits)
{

    switch (ToEncodingBits(bits))
    {
        case 0b000:
        {
            // Multiplies  - Extra Load/Store
            if (TestForSet(bits, 7) && TestForSet(bits, 4))
            {
                if (TestForUnset(bits, 6) && TestForUnset(bits, 5))
                {
                    //This can be a multiply instruction, a SWP or SWPB instruction, an LDREX or STREX instruction,
                    //or an unallocated instruction in the arithmetic or load / store instruction extension space
                    return EInstructionType::MULTIPLIES;
                }
                return EInstructionType::EXTRA_LOAD_STORE;
            }

            // Data Processing With Shift
            if (TestForSet(bits, 4)) {
                if (TestForSet(bits, 24) && TestForUnset(bits, 23) && TestForUnset(bits, 20))
                {
                    return EInstructionType::MISC_INSTRUCTIONS;
                }
                return EInstructionType::DATA_PROCESSING_REG_SHIFT;
            }
            return EInstructionType::DATA_PROCESSING_IMM_SHIFT;

        }break;

        // Data Processing / Move
        case 0b001:
        {
            if (TestForSet(bits, 24) && TestForUnset(bits, 23))
            {
                if (TestForSet(bits, 21))
                {
                    return EInstructionType::MOVE_IMM_TO_STAT_REG;
                }
                return EInstructionType::UNDEFINED_INSTRUCTION;
            }
            return EInstructionType::DATA_PROCESSING_IMMEDIATE;
        }break;

        // Load/Store
        case 0b010:
        {
            return EInstructionType::LOAD_STORE_IMMEDIATE_OFFSET;
        }break;

        case 0b011:
        {
            if (TestForUnset(bits, 4))
            {
                return EInstructionType::LOAD_STORE_REG_OFFSET;
            }

            if ((bits & 0x07'F0'00'F0) == 0x07'F0'00'F0)
            {
                return EInstructionType::ARCHITECTURALLY_UNDEFINED;
            }
            return EInstructionType::MEDIA_INSTRUCTION;
        }break;

        case 0b100:
        {
            return EInstructionType::LOAD_STORE_MULTIPLE;
        }break;

        // Branch / Coprocessor
        case 0b101:
        {
            return EInstructionType::BRANCH_AND_BRANCH_W_LINK;
        }break;

        case 0b110:
        {
            return EInstructionType::COPROCESSOR_LOAD_STORE_AND_DOUBLE_REG_TRANSFER;
        }break;

        case 0b111:
        {
            if (TestForSet(bits, 24))
            {
                return EInstructionType::SOFTWARE_INTERRUPT;
            }

            if (TestForSet(bits, 4))
            {
                return EInstructionType::COPROCESSOR_REG_TRANSFER;
            }
            return EInstructionType::COPROCESSOR_DATA_PROCESSING;
        }break;
    }

    return EInstructionType::UNDEFINED_INSTRUCTION;
}
 
Ultima modifica:
  • Mi piace
Reazioni: BWD87 e driverfury

driverfury

Nuovo Utente
9
4
Comunque bel progetto, lo stai facendo a scopo didattico?

Se il codice sarà open-source credo che lo utilizzerò.
 

Marcus Aseth

Utente Attivo
404
138
OS
Windows 10
Si, diciamo di si, una sorta di passatempo :)
Si sarà open source se mai lo finisco, quindi speriamo bene :S
Cmq ancora molta strada da fare, per ora ho soltanto 3 finestre aperte e penso ne passerà un bel pò prima che ci sia qualcosa di interessante da vedere all'interno
Magari posso usare questo topic per postare update al riguardo :)
gba_emu_wip1.PNG
 

_Achille

Utente Èlite
3,067
725
CPU
Intel i5-6600K @4.6 GHz
Dissipatore
Cryorig H5
Scheda Madre
ASRock Z170 Extreme 6
HDD
WesternDigital 1TB & Crucial MX200 250GB
RAM
Corsair Ven 16GB DDR4 2133MHz
GPU
Sapphire RX 580 Nitro+
Monitor
Dell S2418H
PSU
RM550X
Case
NZXT S340
Periferiche
Anne Pro 2, Razer Abyssus
OS
Windows 10 Pro
Si, diciamo di si, una sorta di passatempo :)
Si sarà open source se mai lo finisco, quindi speriamo bene :S
Cmq ancora molta strada da fare, per ora ho soltanto 3 finestre aperte e penso ne passerà un bel pò prima che ci sia qualcosa di interessante da vedere all'interno
Magari posso usare questo topic per postare update al riguardo :)
Visualizza allegato 296929
Complimenti Marcus. Spero che tra non moltissimo sarò in grado di digerire pure io progetti simili
 
  • Mi piace
Reazioni: Marcus Aseth

DispatchCode

Moderatore
Staff Forum
Utente Èlite
2,208
1,845
CPU
Intel I9-10900KF 3.75GHz 10x 125W
Dissipatore
Gigabyte Aorus Waterforce X360 ARGB
Scheda Madre
Asus 1200 TUF Z590-Plus Gaming ATX DDR4
HDD
1TB NVMe PCI 3.0 x4, 1TB 7200rpm 64MB SATA3
RAM
DDR4 32GB 3600MHz CL18 ARGB
GPU
Nvidia RTX 3080 10GB DDR6
Audio
Integrata 7.1 HD audio
Monitor
LG 34GN850
PSU
Gigabyte P850PM
Case
Phanteks Enthoo Evolv X ARGB
Periferiche
MSI Vigor GK30, mouse Logitech
Net
FTTH Aruba, 1Gb (effettivi: ~950Mb / ~480Mb)
OS
Windows 10 64bit / OpenSUSE Tumbleweed
Secondo me ti conviene riscrivere la parte di decodifica se hai visto che ci sono differenze. Te lo dico per esperienza personale. :P

Due anni fa stavo scrivendo un emulatore per 8086 (più DOS, in parte almeno), emulando Pic, gestione della memoria e buona parte degli opcode dell'8086 (usavo SDL però). Sto rimettendo mano ora al progetto, riscrivendolo quasi per intero però, migliorando alcune cose (so già che avrò problemi per il supporto della grafica, visto che la strada non è univoca). Poi pubblicherò qualcosa, quando ci sarà qualche risultato. :D
 

Marcus Aseth

Utente Attivo
404
138
OS
Windows 10
La differenza che credevo di aver notato a quanto pare non è una differenza, l'istruzione segnata come "Branch Exchange" nella prima immagine rientra nelle "miscellaneus instructions" della seconda immagine, ed infatti la interpreto correttamente qui
Codice:
            // Data Processing With Shift
            if (TestForSet(bits, 4)) {
                if (TestForSet(bits, 24) && TestForUnset(bits, 23) && TestForUnset(bits, 20))
                {
                    return EInstructionType::MISC_INSTRUCTIONS;
                }

quindi al massimo devo aggiungere più enumerazioni se voglio gestire quelle misc più nel dettaglio :)
 
Ultima modifica:

Marcus Aseth

Utente Attivo
404
138
OS
Windows 10
Non ho ancora implementato nessuna istruzione perchè voglio assicurarmi di poter visualizzare in maniera facile cosa sta succedendo quando le eseguo, per ora ho aggiunto una barra che mostra il contenuto dell'istruzione corrente.
Ci sono una quindicina di layout diversi (come nei diagrammi sopra) quindi mi serviranno tipo altre 14 di queste funzioni, ma penso che il risultato finale ne valga la pena. Piu che altro devo decidere quali informazioni siano davvero importanti e come mostrarle nella maniera più utile possibile, man mano che vado avanti dovrei rendermene conto.

1529904091940.png

C++:
void Disassembler::DrawDataProcImmShift() const
{
    auto& val = get<Instruction_DataProcImmShift>(m_emu->GetInstruction());
    vector<D2D1_POINT_2F> fields{ { 0, 4 }, { 4, 7 }, { 7, 11 }, { 11, 12 }, { 12, 16 }, { 16, 20 }, { 20, 25 }, { 25, 27 }, { 27, 28 }, { 28, 32 } };
    vector<wstring> text{ { L"cond" }, { L"0 0 0" }, { L"opcode" }, { L"S" }, { L"Rn" }, { L"Rd" }, { L"shift amount" }, { L"shift" }, { L"0" }, { L"Rm" } };
    vector<wstring> vals{ { ECondStrings[ToUnderlying(val.cond())] }, { L"0 0 0" }, { EOpcodeStrings[ToUnderlying(val.opcode())] }, { to_wstring(val.S()) }, { to_wstring(val.Rn()) }, { to_wstring(val.Rd()) }, { to_wstring(val.ShiftAmmount()) }, { to_wstring(val.Shift()) }, { L"0" }, { to_wstring(val.Rm()) } };

    FLOAT bottomTextOffset = 25.f;
    FLOAT spacersThickness = 3.f;
    for (size_t id = 0; id < fields.size(); id++)
    {
        FLOAT top = m_frame.bottom;
        FLOAT bottom = m_frame.bottom + bottomTextOffset;
        FLOAT left = m_frameSideSpacing + (m_frameBitWidth * fields[id].x);
        FLOAT right = m_frameSideSpacing + (m_frameBitWidth * fields[id].y);
        
        //spacers
        m_context->DrawLine(D2D1_POINT_2F{ right, m_frame.top }, D2D1_POINT_2F{ right, m_frame.bottom }, m_fillBrush1, spacersThickness);

        //bottom text
        DrawEntry(text[id], D2D1_RECT_F{ left, top, right, bottom }, m_fillBrush1, m_textFormat2);

        //values
        DrawEntry(vals[id], D2D1_RECT_F{ left, m_frame.top, right, m_frame.bottom }, m_fillBrush1, m_textFormat2);
    }
}
 

Allegati

  • Disassembler_wip.PNG
    Disassembler_wip.PNG
    25.1 KB · Visualizzazioni: 156
Ultima modifica:
  • Mi piace
Reazioni: DispatchCode

DispatchCode

Moderatore
Staff Forum
Utente Èlite
2,208
1,845
CPU
Intel I9-10900KF 3.75GHz 10x 125W
Dissipatore
Gigabyte Aorus Waterforce X360 ARGB
Scheda Madre
Asus 1200 TUF Z590-Plus Gaming ATX DDR4
HDD
1TB NVMe PCI 3.0 x4, 1TB 7200rpm 64MB SATA3
RAM
DDR4 32GB 3600MHz CL18 ARGB
GPU
Nvidia RTX 3080 10GB DDR6
Audio
Integrata 7.1 HD audio
Monitor
LG 34GN850
PSU
Gigabyte P850PM
Case
Phanteks Enthoo Evolv X ARGB
Periferiche
MSI Vigor GK30, mouse Logitech
Net
FTTH Aruba, 1Gb (effettivi: ~950Mb / ~480Mb)
OS
Windows 10 64bit / OpenSUSE Tumbleweed
Ottima rappresentazione, quella ti sarà sicuramente utile.

Per la decodifica potrebbe essere comodo e conveniente l'utilizzo di una tabella di lookup secondo me.
Nel mio caso, una volta letto l'opcode dalla "memoria", facevo questo:

C:
void decode_instruction(cpu_state_ptr cpu)
{
  uint8_t opcode = read_memory_8(cpu, cpu->mem_offset);
  cpu->opcode    = opcode;
 
  one_byte_opcodes[opcode](cpu);
}

one_byte_opcodes è un array dove ad ogni posizione corrisponde una funzione, che nel mio caso è il singolo opcode.

C:
ob_opcodes one_byte_opcodes[ONEBYTE_OPCODES] = 
                                       {//  0x00  0x01  0x02  0x03  0x04  0x05  0x06  0x07  0x08  0x09  0x0A  0x0B  0x0C  0x0D  0x0E  0x0F
                                           &op01,&op01,&opxx,&op03,&op04,&op04,&opxx,&opxx,&opxx,&op09,&opxx,&opxx,&opxx,&opxx,&opxx,&opxx, // 0x00
                                           &opxx,&opxx,&opxx,&opxx,&opxx,&opxx,&opxx,&opxx,&opxx,&op19,&opxx,&opxx,&opxx,&opxx,&opxx,&opxx, // 0x01
                                           &opxx,&opxx,&opxx,&opxx,&opxx,&opxx,&opxx,&opxx,&opxx,&opxx,&op2B,&op2B,&op2C,&opxx,&opxx,&opxx, // 0x02
                                           &opxx,&op31,&opxx,&opxx,&opxx,&opxx,&opxx,&opxx,&opxx,&op39,&op3B,&op3B,&op3C,&op3C,&opxx,&opxx, // 0x03
                                           &op47,&op47,&op47,&op47,&op47,&op47,&op47,&op47,&op4E,&op4E,&op4E,&op4E,&op4E,&op4E,&op4E,&op4E, // 0x04
                                           &op5x,&op5x,&op5x,&op5x,&op5x,&op5x,&op5x,&op5x,&op58,&op58,&op58,&op58,&op58,&op58,&op58,&op58, // 0x05
                                           &opxx,&opxx,&opxx,&opxx,&opxx,&opxx,&opxx,&opxx,&opxx,&opxx,&opxx,&opxx,&opxx,&opxx,&opxx,&opxx, // 0x06
                                           &op70,&opxx,&op72,&op73,&op74,&op75,&op76,&op77,&opxx,&op79,&opxx,&opxx,&opxx,&opxx,&opxx,&opxx, // 0x07
                                           &op80,&op80,&opxx,&op83,&opxx,&opxx,&opxx,&opxx,&op89,&op89,&op8B,&op8B,&opxx,&opxx,&op8E,&opxx, // 0x08
                                           &op90,&opxx,&opxx,&opxx,&opxx,&opxx,&opxx,&opxx,&opxx,&opxx,&opxx,&opxx,&opxx,&opxx,&opxx,&opxx, // 0x09
                                           &opA1,&opA1,&opA2,&opA2,&opxx,&opxx,&opxx,&opxx,&opxx,&opxx,&opxx,&opxx,&opxx,&opxx,&opxx,&opxx, // 0x0A
                                           &opBx,&opBx,&opBx,&opBx,&opBx,&opBx,&opBx,&opBx,&opBx,&opBx,&opBx,&opBx,&opBx,&opBx,&opBx,&opBx, // 0x0B
                                           &opxx,&opxx,&opxx,&opC3,&opxx,&opxx,&opC6,&opxx,&opxx,&opxx,&opxx,&opxx,&opxx,&opCD,&opxx,&opxx, // 0x0C
                                           &opxx,&opD1,&opxx,&opxx,&opxx,&opxx,&opxx,&opxx,&opxx,&opxx,&opxx,&opxx,&opxx,&opxx,&opxx,&opxx, // 0x0D
                                           &opxx,&opxx,&opxx,&opxx,&opxx,&opxx,&opxx,&opxx,&opE8,&opE9,&opxx,&opEB,&opxx,&opxx,&opxx,&opxx, // 0x0E
                                           &opxx,&opxx,&opxx,&opxx,&opxx,&opxx,&opxx,&opF7,&opxx,&opF9,&opxx,&opxx,&opxx,&opxx,&opxx,&opxx  // 0x0F
                                       };
 
  • Mi piace
Reazioni: driverfury

Marcus Aseth

Utente Attivo
404
138
OS
Windows 10
non mi è chiaro il funzionamento (o come mapperebbe alle varie decodifiche dell'ARM, o il guadagno di fare quello al posto delle 3 funzioni con 3 switch che ho al momento), percui non sono disposto a rischiare il codice che ho gia eheh :)
 

Marcus Aseth

Utente Attivo
404
138
OS
Windows 10
Ok, dopo che ci ho riflettuto un pò di più penso di capire, il problema è che come si vede nella prima immagine solo il primo tipo di codifica (Data Processing & FSR Transfer) ha Opcode ordinati che potrei usare come index in un array, gli altri hanno una serie di bit sparsi (ognuna con le sue regole) che vanno controllati per decidere che istruzione è, percui richiederebbe che sia io a creare degli opcode "immaginari" da associare in uno switch alle istruzioni senza opcode per poi mandare quell'opcode come index nella lookup table. Valutandolo contro il setup che ho al momento, non vedo un guadagno netto, cmq suggerimento interessante, non avevo pensato si potesse fare anche così :)
 

DispatchCode

Moderatore
Staff Forum
Utente Èlite
2,208
1,845
CPU
Intel I9-10900KF 3.75GHz 10x 125W
Dissipatore
Gigabyte Aorus Waterforce X360 ARGB
Scheda Madre
Asus 1200 TUF Z590-Plus Gaming ATX DDR4
HDD
1TB NVMe PCI 3.0 x4, 1TB 7200rpm 64MB SATA3
RAM
DDR4 32GB 3600MHz CL18 ARGB
GPU
Nvidia RTX 3080 10GB DDR6
Audio
Integrata 7.1 HD audio
Monitor
LG 34GN850
PSU
Gigabyte P850PM
Case
Phanteks Enthoo Evolv X ARGB
Periferiche
MSI Vigor GK30, mouse Logitech
Net
FTTH Aruba, 1Gb (effettivi: ~950Mb / ~480Mb)
OS
Windows 10 64bit / OpenSUSE Tumbleweed
Che libreria grafica hai utilizzato per il disassembler (giusto per curiosità)?
Credo stia utilizzando la Windows API, poi magari sbaglio (dedotto dal suo primo codice nel primo post).

Ok, dopo che ci ho riflettuto un pò di più penso di capire, il problema è che come si vede nella prima immagine solo il primo tipo di codifica (Data Processing & FSR Transfer) ha Opcode ordinati che potrei usare come index in un array, gli altri hanno una serie di bit sparsi (ognuna con le sue regole) che vanno controllati per decidere che istruzione è, percui richiederebbe che sia io a creare degli opcode "immaginari" da associare in uno switch alle istruzioni senza opcode per poi mandare quell'opcode come index nella lookup table. Valutandolo contro il setup che ho al momento, non vedo un guadagno netto, cmq suggerimento interessante, non avevo pensato si potesse fare anche così :)

Prova a guardare come gli Switch vengono compilati. Sicuramente se utilizzi dei flags (/O2, ad esempio) otterrai anche risultati migliori. Comunque puoi anche decodificare in maniera differente, in base a com'è l'istruzione stessa.
Non è escluso che guardando il disassemblato possa anche venirti in mente poi qualche idea.

Complimenti per l'impostazione grafica comunque, pare proprio ben fatta (e ti sarà probabilmente molto utile nel debug). Io avevo fatto qualcosa di analogo, ma decisamente più grezzo: stampavo solo lo stato interno della mia CPU, così capivo ad esempio quale opcode non era supportato oppure se interpretavo male qualche bit (poi nel mio caso era un CISC, e non un RISC... quindi puoi immaginare).
 
  • Mi piace
Reazioni: Marcus Aseth
Stato
Discussione chiusa ad ulteriori risposte.

Entra

oppure Accedi utilizzando
Discord Ufficiale Entra ora!