Комментарии 29
вам еще учиться и учиться..
Да, это так, но я уверен, что с дальнейшей практикой и обучением я смогу достичь ещё более высокого уровня :)
Прежде всего нужно научиться читать даташиты (например, прочитать, что есть не только аккумулятор и указатель команд, но и регистр переносов, регистр стека, регистры общего назначения). Освоить двоичную арифметику (что б не было "вы не можете получить число выше 15"). А уж потом браться за эмуляторы процессоров. Ничего сложного нет, но вы слишком рано начали делать эмулятор.
Минусовать не стал чисто из-за того, чтоб не отбить желание программировать. Но плюсов вы точно не заслужили.
Не, ну это не серьезно, только настроился на интересное чтиво, а статья уже и закончилась.
Ну, elif для всех опкодов использовать эт конечно мощно :)) Я когда-то в 14 лет написал интерпретатор BASIC-подобного языка и похожим образом реализовывал команды, причём таким образом были реализованы и основные конструкции (объявление функций, if/else, циклы), но так делать не стоит.
От абстрактного эмулятора процессора толку не очень много. Но гораздо интереснее эмулировать уже готовые устройства: с экранчиком, кнопочками или звуком, можете даже что-то своё придумать. Потом этот "эмулятор" можно воплотить в настоящее аппаратное устройство - например, на ESP32 ;)
По советам: выделите отдельный словарь (коллекцию вида ключ = значение) для каждого опкода и реализацию для него, а-ля так (Python не знаю, но код должен быть понятен):
void opMovImmediate(BinaryReader reader)
{
byte reg1 = reader.ReadByte(), reg2 = reader.ReadByte();
...
}
Dictionary<byte, Action<BinaryReader>> opcodes;
...
opcodes.Add(I4004.MovImmediate, opMovImmediate);
byte opcode = reader.ReadByte();
if(opcodes.ContainsKey(opcode))
opcodes[opcode](reader);
else
throw new ArgumentException("Unimplemented opcode " + opcode);
Сами номера опкодов лучше выделить в отдельное перечисление. Можно вот так:
enum I4004
{
Nop = 0,
MovImmediate = 0x2
}
Регистры можно реализовывать по разному, однако учтите, что в x86 и некоторых других архитектурах с подходом а-ля один регистр - одна переменная не прокатит, поскольку bl/bh - младшие разряды "большого" 16-битного регистра bx и.т.п. Можно представить регистры как кучу и адресовать его соответственным образом.
Плюсик всё равно поставил. Пилить эмуляторы и интерпретаторы - дико увлекательное и интересное занятие на самом деле.
Благодарю за Ваши советы. Постараюсь их учесть :)
одна переменная не прокатит, поскольку bl/bh - младшие разряды "большого" 16-битного регистра bx
Почему же?
struct registry_x
{
byte l;
byte h;
operator word () { return h << 8 + l; };
operator = (word w) { l = word & 0xff; h = word >> 8; };
};
registry_x bx; // Одна переменная )))
bx.l; = 1;
bx.h; = 2;
bx = 3;
Такое можно делать только в некоторых языках, да и придётся помнить про Big/Little Endian
Я бы вообще предпочёл union или type… забыл термин. Когда (byte*) &my_word
. Но с современными стандартами и компиляторами, куда ни плюнь — всюду UB.
В Паскале это кажется называлось absolute, или case-type, типа такого:
type registersplit=record
case x of { неважно что писать в case..of, вплоть до бреда }
1: l:byte; h:byte;
2: x: word;
3: ex: longint; { а не было dword в турбо-6.0 }
end;
end;
var a:registersplit;
begin
a.h=32;
writeln(a.x); { выведет 32*256=8192 ибо DS инициализируется нулями, a.l=0 }
end.
Не знаю, есть ли это вот ещё в Delphi/Lazarus, но если есть, точно не должно вести себя как UB.
опкоды лучше (особенно в учебных целях) разбирать побитно - становится понятно, как процессор дешифрует команды.
Я не знаю, какая ISA взята за основу, но это точно не i4004. Регистра B в i4004 не существует (в i8008 - есть), а регистры просто индексные - rr0..rr15. Опкоды левые. Некоторых инструкций даже в теории нет в i4004 (HLT появился только в i4040).
Запись в память куда сложнее, чем просто одна инструкция. Да и память не линейна, о организована в виде банков и регистров памяти.
Если что, у писал 2 эмулятора (i4004 и i4040), которые совместимы с железом с точностью до тактов.
Скажите пожалуйста, верные ли опкоды для инструкций я нашёл?
NOP (No Operation): опкод - 0000.
ADD (Сложение): опкод - 0001.
SUB (Вычитание): опкод - 0100.
AND (Логическое И): опкод - 0111.
OR (Логическое ИЛИ): опкод - 1000.
Загрузка числа в аккумулятор (acc): опкод - 1001.
Загрузка числа в регистр: опкод - 1101.
Такой простой эмулятор можно реализовать даже без классов, если не стоит задача расширять его функционал до бесконечности.
Можно даже попробовать сделать для этого эмулятора свой язык программррвания
Знаете, это было бы очень здорово))
я б посоветовал сделать нормальный эмулятор, а для него - написать свой ассемблер. Бесполезно в плане применения, но даст хорошую практику.
Что-то потипу смеси питона и плюсов. Ну либо 0х00 можно только через return 0 сделать нормально, а если автоматически как в питоне, я это даже не представляю как реализовать
Спасибо тебе дружище за эту статью. Очень давно интересовала тема ассемблера и как оно все работает на самом низком уровне, но никак не мог подступиться. Увяз в чтении литературы, подобной Таненбауму и т. д. Вместо того, чтобы просто взять и начать что-то делать, мне всегда кажется что еще рано, мало знаний, надо еще что-то почитать. Твоя статья и твой до неприличия простой и понятный код, с простым примером вдохновили меня. Оказывается вот оно как просто. Машина просто шаг за шагом берет и читает инструкцию, выполняет ее, а потом переходит к следующей. Благодаря тебе, я скачал эмулятор 8086 и начал изучать ассемблер, жутко интересно. Очень хочется уловить момент перехода с ассемблера на более-менее сносный компилятор какого-нибудь простого ЯП.
Я очень рад, что Вам понравилось. Я старался :)
Очень хочется уловить момент перехода с ассемблера на более-менее сносный компилятор какого-нибудь простого ЯП
Найдите книгу Хендрикс "Компилятор Small-c для микро-ЭВМ" (изд. Радио и связь, 1989) - там это объясняется практически на примере. Только кодогенерация для ассемблера 8080.
Эмулятор, написанный на интерпретируемом языке - это сильно. Не удивлюсь, если физический 4004 из семидесятых будет быстрее эмулятора ))
Ну и практическая польза не видна, только как упражнение для самостоятельного изучения языка, вряд ли стоило писать об этом статью на Хабр.
Сказ о том, как я эмулятор Intel 4004 на Python писал