Как мне написать виртуальную машину [закрыто]

У меня был отличный подход, это может помочь кому-то в будущем:

клиенту нужна страница, на которой вы можете поменять языки. Мне нужно было форматировать номера по этому параметру (не настройке браузера / не по какой-либо предопределенной настройке)

, поэтому я устанавливаю начальную настройку в зависимости от настроек конфигурации (i18n)

$clang = $this->Session->read('Config.language');
echo "<script type='text/javascript'>var clang = '$clang'</script>";

позже в скрипте я использовал функцию для определения того, какое числовое значение мне нужно

function getLangsettings(){
  if(typeof clang === 'undefined') clang = navigator.language;
  //console.log(clang);
  switch(clang){
    case 'de':
    case 'de-de':
        return {precision : 2, thousand : ".", decimal : ","}
    case 'en':
    case 'en-gb':
    default:
        return {precision : 2, thousand : ",", decimal : "."}
  }
}

, поэтому я использовал заданный язык страницы, и в качестве резервного я использовал настройки браузера.

, которые также должны быть полезны для тестирования.

в зависимости от ваших клиентов вам могут не понадобиться эти настройки.

23
задан πάντα ῥεῖ 12 December 2016 в 21:26
поделиться

7 ответов

Проверьте, что другие сделали в этой области!

Хороший способ получить информацию о конкретном типе приложения (и в вашем случае также хороший способ подобрать идиомы) - глядя на структуру и детали проекта с открытым исходным кодом того же типа. Кто-то может решить просто заглянуть, кратко просмотреть, а затем «забыть», чтобы начать свой собственный проект с нуля, но во всех случаях этот тип посещения выгоден.

Поскольку вы упомянули «простую архитектуру» и Zilog, я подумал, что процессор Z80 может быть хорошим совпадением. По разным причинам существует множество текущих и прошлых проектов в жанре эмулятора Z80. Кстати, одна из причин заключается в том, что на Z80 работало много старых видео-консолей игрового типа, что побудило ностальгирующих геймеров писать эмуляторы для запуска своих старых фаворитов; -)

Примером такого проекта является YAZE-AG , который включает в себя как полный эмулятор Z80, так и C / PM . Все это написано на C. Кроме того, оно относительно зрелое (версия 2.x) и активно. Я предполагаю, что это работа очень маленькой команды (возможно, одной ;-)).

Удачи!

14
ответ дан mjv 12 December 2016 в 21:26
поделиться

Если вы проектируете процессор и эмулируете его,

подготовьте ядро. Смысл, писать классы для регистров. Напишите один для флагов. Напишите контроллер памяти.

Подумайте о типе кодов операций. Кроме того, какова длина слов? Это 16-битный процессор? 8-битный?

Какой тип доступа к памяти вы хотите использовать? DMA? HDMA?

Какой тип прерываний вы хотите поддерживать? Будет ли процессор учебной платформой? Это будет просто процессор и память, или к нему будут подключены устройства? (звук, видео и т. д.).

Вот код из моего эмулятора, над которым я работаю (общественное достояние). Работал над этим в течение нескольких дней. До сих пор около 3200 строк кода (большая часть - это microcode.cs, который здесь не размещен из-за его размера 2600 строк).

using System;

namespace SYSTEM.cpu
{
    // NOTE: Only level-trigger interrupts are planned right now

    // To implement:
    // - microcode
    // - execution unit
    // - etc

    // This is the "core"; think of the CPU core like a building. You have several departments; flags, memory and registers
    // Microcode is external

    class core
    {
        public cpu_flags flags;
        public cpu_registers registers;

        public cpu_memory memory;

        public core(byte[] ROM, byte[] PRG)
        {
            flags = new cpu_flags();
            registers = new cpu_registers();

            memory = new cpu_memory(ROM, PRG);

            return;
        }
    }
}

using System;

namespace SYSTEM.cpu
{
    class cpu_flags
    {
        // SYSTEM is not a 6502 emulator. The flags here, however, are exactly named as in 6502's SR
        // They do NOT, however, WORK the same as in 6502. They are intended to similar uses, but the only identity is the naming.

        // I just like the 6502's naming and whatnot.

        // This would otherwise be a register in SYSTEM.cpu_core.cpu_registers. SR, with the bits used correctly.
        // This would be less readable, code-wise, so I've opted to dedicate an entire CLASS to the status register

        // Though, I should implement here a function for putting the flags in a byte, so "SR" can be pushed when servicing interrupts

        public bool negative, // set if the high bit of the result of the last operation was 1

            // bit 7, then so on
                overflow, // says whether the last arithmetic operation resulted in overflow (NOTE: No subtraction opcodes available in SYSTEM)

                // NO FLAG

                brk, // break flag, set when a BREAK instruction is executed

                // NO FLAG (would be decimal flag, but I don't see why anyone would want BCD. If you want it, go implement it in my emulator; in software)
                    // i.e. don't implement it in SYSTEM; write it in SYSTEM ASM and run it in SYSTEM's DEBUGGER

                irq,    // whether or not an interrupt should begin at the next interrupt period (if false, no interrupt)

                zero, // says whether the last arithmetic operation resulted in zero

                carry; // set when alpha rolls from 0xFFFF to 0x0000, or when a 1 is rotated/shifted during arithmetic

        public cpu_flags()
        {
            negative = true; // all arithmetic registers are FFFF by default, so of course they are negative

            overflow = false; // obviously, because no arithmetic operation has been performed yet

            brk = false;

            irq = true; // interrupts are enabled by default of course

            zero = false; // obviously, since all arith regs are not zero by default

            carry = false;  // obviously, since no carry operation was performed

            return;
        }

        // Explain:

        // These flags are public. No point putting much management on them here, since they are boolean

        // The opcodes that SYSTEM supports, will act on these flags. This is just here for code clarity/organisation

    }
}

using System;



// This implements the memory controller

// NOTE: NO BANK SWITCHING IMPLEMENTED, AND NOT PLANNED AT THE MOMENT, SO MAKE DO WITH TEH 64

// SYSTEM has a 16-bit address bus (and the maximum memory supported; 64K)
// SYSTEM also has a 16-bit data bus; 8-bit operations are also performed here, they just use the low bits

// 0x0000-0x00FF is stack
// 0xF000-0xFFFF is mapped to BIOS ROM, and read-only; this is where BIOS is loaded on startup.
// (meaning PROGRAM ROM can be up to 4096B, or 4K. Normally this will be used for loading a BIOS)
// Mapping other PROGRAM ROM should start from 0x0100, but execution should start from 0xF000, where ROM/BIOS is mapped

// NOTE: PROGRAM ROM IS 32K, and mapped from 0x0100 to 0x80FF

// ;-)

namespace SYSTEM.cpu
{
    class cpu_memory
    {
        // to implement:
        // device interaction (certain addresses in ROM should be writeable by external device, connected to the controller)
        // anything else that comes to mind.

        // Oh, and bank switching, if feasible

        private byte[] RAM; // As in the bull? ...

        public cpu_memory(byte[] ROM, byte[] PRG)
        {
            // Some code here can be condensed, but for the interest of readability, it is optimized for readability. Not space.

            // Checking whether environment is sane... SYSTEM is grinning and holding a spatula. Guess not.
            if(ROM.Length > 4096) throw new Exception("****SYSINIT PANIC****: BIOS ROM size INCORRECT. MUST be  within 4096 BYTES. STOP");

            if (PRG.Length > 32768) throw new Exception("****SYSINIT PANIC**** PROGRAM ROM size INCORRECT. MUST be within 61184 BYTES. STOP");

            if(ROM.Length != 4096) // Pads ROM to be 4096 bytes, if size is not exact
            {                       // This would not be done on a physical implementation of SYSTEM, but I feel like being kind to the lazy
                this.RAM = ROM;
                ROM = new byte[4096];
                for(int i = 0x000; i < RAM.Length; i++) ROM[i] = this.RAM[i];
            }

            if(PRG.Length != 32768) // Pads PRG to be 61184 bytes, if size is not exact
            {                   // again, being nice to lazy people..
                this.RAM = PRG;
                PRG = new byte[32768];
                for(int i = 0x000; i < RAM.Length; i++) PRG[i] = RAM[i];
            }

            this.RAM = new byte[0x10000]; // 64K of memory, the max supported

            // Initialize all bytes in the stack, to 0xFF
            for (int i = 0; i < 0x100; i++) this.RAM[i] = 0xFF; // This is redundant, but desired, for my own undisclosed reasons.

        // LOAD PROGRAM ROM AND BIOS ROM INTO MEMORY

            for (int i = 0xf000; i < 0x10000; i++)  // LOAD BIOS ROM INTO MEMORY
            {
                this.RAM[i] = ROM[i - 0xf000]; // yeah, pretty easy actually
            }

            // Remember, 0x0100-0x80FF is for PROGRAM ROM

            for (int i = 0x0100; i < 0x8100; i++) // LOAD PROGRAM ROM INTO MEMORY
            {
                this.RAM[i] = PRG[i - 0x100]; // not that you knew it would be much different
            }

            // The rest, 0x8100-0xEFFF, is reserved for now (the programmer can use it freely, as well as where PRG is loaded).
            // still read/writeable though

            return;
        }

// READ/WRITE:

        // NOTE: SYSTEM's cpu is LITTLE ENDIAN
        // WHEN DOUBLE-READING, THE BYTE-ORDER IS CONVERTED TO BIG ENDIAN
        // WHEN DOUBLE-WRITING, THE BYTE TO WRITE IS BIG ENDIAN, AND CONVERTED TO LITTLE ENDIAN

        // CPU HAS MAR/MBR, but the MEMORY CONTROLLER has ITS OWN REGISTERS for this?

    // SINGLE OPERATIONS

        public byte read_single(ref cpu_registers registers, ushort address) // READ A SINGLE BYTE
        {                               // reading from any memory location is allowed, so this is simple
            registers.memoryAddress = address;
            return registers.memoryBuffer8 = this.RAM[registers.memoryAddress];

        }

        public ushort read_double(ref cpu_registers registers, ushort address) // READ TWO BYTES (converted to BIG ENDIAN byte order)
        {
            ushort ret = this.RAM[++address];
            ret <<= 8;
            ret |= this.RAM[--address];

            registers.memoryAddress = address;
            registers.memoryBuffer16 = ret;

            return registers.memoryBuffer16;
        }

        public void write_single(ref cpu_registers registers, ushort address, byte mbr_single) // WRITE A SINGLE BYTE
        {
            if (address < 0x0100) return; // block write to the stack (0x0000-0x00FF)
            if (address > 0xEFFF) return; // block writes to ROM area (0xF000-0xFFFF)

            registers.memoryAddress = address;
            registers.memoryBuffer8 = mbr_single;

            this.RAM[registers.memoryAddress] = registers.memoryBuffer8;

            return;
        }

        public void write_double(ref cpu_registers registers, ushort address, ushort mbr_double) // WRITE TWO BYTES (converted to LITTLE ENDIAN ORDER)
        {
            // writes to stack are blocked (0x0000-0x00FF)
            // writes to ROM are blocked   (0xF000-0xFFFF)

            write_single(ref registers, ++address, (byte)(mbr_double >> 8));
            write_single(ref registers, --address, (byte)(mbr_double & 0xff));

            registers.memoryBuffer16 = mbr_double;
            return;
        }

        public byte pop_single(ref cpu_registers registers) // POP ONE BYTE OFF STACK
        {
            return read_single(ref registers, registers.stackPointer++);
        }

        public ushort pop_double(ref cpu_registers registers) // POP TWO BYTES OFF STACK
        {
            ushort tmp = registers.stackPointer++;          ++registers.stackPointer;
            return read_double(ref registers, tmp);
        }

    // PUSH isn't as easy, since we can't use write_single() or write_double()
    // because those are for external writes and they block writes to the stack
    // external writes to the stack are possible of course, but
        // these are done here through push_single() and push_double()

        public void push_single(ref cpu_registers registers, byte VALUE) // PUSH ONE BYTE
        {
            registers.memoryAddress = --registers.stackPointer;
            registers.memoryBuffer8 = VALUE;

            this.RAM[registers.memoryAddress] = registers.memoryBuffer8;
            return;
        }

        public void push_double(ref cpu_registers registers, ushort VALUE) // PUSH TWO BYTES
        {
            this.RAM[--registers.stackPointer] = (byte)(VALUE >> 8);
            this.RAM[--registers.stackPointer] = (byte)(VALUE & 0xff);

            registers.memoryAddress = registers.stackPointer;
            registers.memoryBuffer16 = VALUE;

            return;
        }
    }
}

using System;

namespace SYSTEM.cpu
{
    // Contains the class for handling registers. Quite simple really.

    class cpu_registers
    {
        private byte sp, cop; // stack pointer, current opcode
        //

        private ushort pp, ip, // program pointer, interrupt pointer
            mar, mbr_hybrid; // memory address and memory buffer registers,
                        // store address being operated on, store data being read/written
                        // mbr is essentially the data bus; as said, it supports both 16 and 8 bit operation.

                        // There are properties in this class for handling mbr in 16-bit or 8-bit capacity, accordingly
                        // NOTE: Paged memory can be used, but this is handled by opcodes, otherwise the memory addressing
                        //       is absolute

                        // NOTE: sp is also an address bus, but used on the stack (0x0000-0x00ff) only
                        // when pushing to the stack, or pulling, mbr gets updated in 8-bit capacity



                        // For pulling 16-bit word from stack, shifting register 8 left is needed, otherwise the next 
                        // POP operation will override the result of the last

        // Alpha is accumulator, the rest are general purpose
        public ushort alphaX, bravoX, charlieX, deltaX;

        public cpu_registers()
        {
            sp = 0xFF;  // stack; push left, pop right
            // stack is from 0x0000-0x00ff in memory
            pp = 0xf000; // execution starts from 0xf000; ROM is loaded
            // from 0xf000-0xffff, so 4KB of ROM. 
            // 0xf000-0xffff cannot be written to in software; though this disable
            // self-modifying code, effectively.

            ip = pp; // interrupt pointer starts from the same place as pp

            alphaX = bravoX = charlieX = deltaX = 0xffff;

            cop = 0x00; // whatever opcode 0x00 is, cop is that on init

            mar = mbr_hybrid = 0x0000;

            return;
        }

        // Registers:

        public ushort memoryAddress // no restrictions on read/write, but obviously it needs to be handled with care for this register
        {                       // This should ONLY be handled by the execution unit, when actually loading instructions from memory
            set { mar = value; }
            get { return mar; }
        }

    // NOTE: 8-bit and 16-bit address bus are shared, but address bus must have all bits written.
    // when writing 8-bit value, byte-signal gets split. Like how an audio/video splitter works.

        public byte memoryBuffer8 // treats address bus as 8-bit, load one byte
        {
            set {   // byte is loaded into both low and high byte in mbr (i.e. it is split to create duplicates, for a 16-bit signal)
                mbr_hybrid &= 0x0000;   
                mbr_hybrid |= (ushort)value;
                mbr_hybrid <<= 0x08;
                mbr_hybrid |= (ushort)value;
            } get {
                return (byte)mbr_hybrid;
            }
        }

        public ushort memoryBuffer16 // treats address bus as 16-bit, load two bytes
        {
            set {
                mbr_hybrid &= 0x0000;
                mbr_hybrid |= value;
            } get {
                return mbr_hybrid;
            }
        }

        public byte stackPointer // sp is writable, but only push/pull opcodes
        {                        // should be able to write to it. There SHOULD
            set { sp = value; }  // be opcodes for reading from it
            get { return sp; }
        }

        public byte currentOpcode
        {
            set { cop = value; }
            get { return cop; }
        }

        public ushort programPointer // says where an instruction is being executed from
        {
            set { pp = value; }
            get { return pp; }
        }

        public ushort interruptPointer // says where the next requested interrupt should begin 
        {                   // (copied into PP, after pushing relevant registers)
            set { ip = value; }
            get { return ip; }
        }

        public byte status(cpu_flags flags) // status word, containing all flags
        {
            byte ret = 0;
            if (flags.negative) ret |= 0x80;
            if (flags.overflow) ret |= 0x40;
            if (flags.brk) ret |= 0x10;
            if (flags.irq) ret |= 0x04;
            if (flags.zero) ret |= 0x02;
            if (flags.carry) ret |= 0x01;

            return ret;
        }

    }
}

using System;

using System.Collections.Generic;

namespace SYSTEM.cpu
{
    class cpu_execution
    {
        public core processor; // the "core", detailing the CPU status, including memory, memory controller, etc
        public cpu_microcode microcode; // the microcode unit (note, microcode is plug and play, you could use something else here)

        public cpu_execution(byte[] ROM, byte[] PRG) // initialize execution unit and everything under it
        {
            processor = new core(ROM, PRG);
            microcode = new cpu_microcode();

            return;
        }

        public void fetch() // fetch current instruction
        {
            processor.registers.currentOpcode = processor.memory.read_single(ref processor.registers, processor.registers.programPointer);
            return;
        }

        public void execute() // execute current instruction
        {
            processor = microcode.use(processor);
            return;
        }



    }
}

microcode.cs, который эмулирует код операции, здесь не включен, потому что это 2600 строк кода.

Все это C #.

11
ответ дан new123456 12 December 2016 в 21:26
поделиться

Обычным упражнением является создание простого калькулятора. Он имеет только ограниченное количество операций (обычно 4, * / + -), один тип данных (число), и вы, вероятно, очень хорошо понимаете, как он должен работать. Это делает отладку намного проще.

Несмотря на простоту, вам уже приходится иметь дело с некоторыми фундаментальными проблемами с ВМ. Вам нужно проанализировать последовательность команд, сохранить несколько объектов, над которыми вы работаете, и обработать вывод.

По совпадению, ИС калькуляторов являются предшественниками процессоров, поэтому этот подход также имеет смысл с исторической точки зрения.

6
ответ дан MSalters 12 December 2016 в 21:26
поделиться

Что-то из эпохи Zilog было бы хорошо, потому что вы, вероятно, можете найти какое-то программное обеспечение, которое работало на реальных машинах Z-80, и использовать его в качестве финального теста.

Первой настоящей программой, которую я написал (кроме одностраничных заданий), был эмулятор для миникомпьютера HP2100A, который я использовал в старшей школе. Я написал это в B, предшественнике C, и я не думаю, что это слишком сложно для первой программы на C. Во всяком случае, это может быть слишком просто. Конечно, что-то вроде 80686 гораздо сложнее, чем Z-80, но это уже сделали QEMU, VirtualBox и другие.

Самая трудная часть этого будет всей системой прерывания, которая соединяет машину с внешним миром.

Возможно, вы захотите прочитать о LLVM и решить, действительно ли вы хотите написать виртуальную машину или эмулятор.

4
ответ дан Michael Dillon 12 December 2016 в 21:26
поделиться

Предлагаю проверить книгу Элементы вычислительных систем . В процессе изучения книги вы создадите виртуальный компьютер, начиная с базовых логических элементов. К тому времени, как вы закончите с книгой, у вас будет рудиментарная операционная система, компилятор и т. Д.
Исходный код, который доступен онлайн, также реализует архитектуру компьютера поверх Java.

7
ответ дан crashmstr 12 December 2016 в 21:26
поделиться

Несколько мыслей:

  • Старые наборы инструкций будут проще, поэтому они могут быть хорошим местом для начала.
  • Выберите архитектуру RISC: декодирование потока команд будет намного проще.
  • Игнорировать такие вещи, как прерывания, NMI и т. Д.
  • В точной эмуляции загрузки всегда есть много мелких деталей. Вместо этого выберите что-то очень простое, например, запуск выполнения с нулевого адреса со всеми регистрами, установленными на ноль.
  • Настоящим программам также понадобятся такие вещи, как настоящая аппаратная эмуляция, так что не делайте этого.
  • Возможно, вы захотите расширить набор инструкций несколькими специальными инструкциями ввода-вывода, чтобы прочитать число, написать символ (или даже строку) и т. Д., Чтобы вы могли писать простые тестовые программы, которые на самом деле очень простой ввод / вывод.
  • Разбор формата объектного файла, такого как elf, может потребовать много работы сам по себе. С такими инструментами, как objdump, вы, вероятно, можете извлечь только текстовый раздел (то есть инструкции) как двоичный файл (по крайней мере, ascii hex).
  • Начните с написания дизассемблера для любого набора команд, который вы хотите эмулировать, или, по крайней мере, для начального подмножества, для которого вы будете стрелять. В любом случае он вам понадобится для отладки.
  • Узнайте, как заставить работать газ (ассемблер gnu) для выбранного вами набора инструкций, чтобы вы могли создавать известные хорошие объектные файлы и тестовые программы.

Если вы не знакомы с другими языками программирования и / или не разбираетесь в ассемблере, это довольно сложный первый C-проект. Тем не менее, удачи!

4
ответ дан Dale Hagglund 12 December 2016 в 21:26
поделиться

Это не одобрение продукта, а наблюдение ...

Я бы взял книгу Deitel and Deitel для начала. (вероятно, , этот , если вы хотите сделать это в C). Кажется, у них всегда есть одна глава по созданию виртуальной машины вместе с некоторыми инструкциями по написанию кода на ассемблере для вашей виртуальной машины, независимо от того, язык, которому они учат.

Редактировать - Добавлено

(хотя я бы проверил его в библиотеке, прежде чем покупать его на случай, если я неправильно понимаю, что вы хотите написать)

2
ответ дан David 12 December 2016 в 21:26
поделиться
Другие вопросы по тегам:

Похожие вопросы: