Небольшой дисклеймер: перед прочтением данной статьи ознакомьтесь с первой частью, дабы вникнуть в суть происходящего. Желаю вам приятного прочтения 🙂
Вступление
Сидел я тут на днях и думал, как можно улучшить мой эмулятор «Intel 4004» и перечитывая комментарии под первой частью, я осознал одну очень простую вещь — моё творение на 4004-ый не очень то и похоже.. Абсолютно рандомные опкоды, инструкции, которых в данном процессоре отродясь не было, например, инструкции HLT, AND и OR (HLT так вообще появилась только в Intel 4040).
После некоторых раздумий я принял следующее решение — нужно переписать эмулятор с нуля, с корректными опкодами, инструкциями и так далее 😉
Как всё писалось?
Я начал активно смотреть datasheet, стал рассматривать другие проекты по теме 4004-го, особенно мне понравился эмулятор пользователя markablov, написанный на языке JavaScript (именно оттуда впоследствии были взяты необходимые опкоды).
Как и в прошлый раз, я создал класс CPU и начал с реализации памяти (насущные 256 байт), аккумулятора и счётчика команд (память в этот раз я запихал в инициализацию для удобства):
class CPU: def __init__(self): # 256 bytes of memory self.memory = bytearray(256) # accumulator self.acc = 0 # program counter self.pc = 0
В этот раз в эмуляторе используется всего 7 инструкций из 46 возможных (так что полноценным его назвать нельзя, скорее урезанным, в прошлый раз было также).
Вот список используемых инструкций:
|
Инструкция |
Описание инструкции |
|
NOP |
Без операции |
|
INC |
Увеличение индексного регистра |
|
ISZ |
Пропуск индексного регистра, если он равен нулю |
|
ADD |
Добавить индексный регистр к аккумулятору с переносом |
|
SUB |
Вычитание индексного регистра из аккумулятора с заимствованием |
|
LD |
Загрузка индексного регистра в аккумулятор |
|
XCH |
Обмен индексного регистра и аккумулятора |
Их реализация была выполнена путём создания функций:
# NOP instruction (No Operation) def NOP(self): self.pc += 1 # INC instruction (Increment index register) def INC(self): self.acc = (self.acc + 1) % 256 self.pc += 1 # ISZ instruction (Increment index register skip if zero) def ISZ(self, address): self.memory[address] = (self.memory[address] + 1) % 256 if self.memory[address] == 0: self.pc += 2 else: self.pc += 1 # ADD instruction (Add index register to accumulator with carry) def ADD(self, address): self.acc = (self.acc + self.memory[address]) % 256 self.pc += 2 # SUB instruction (Subtract index register to accumulator with borrow) def SUB(self, address): self.acc = (self.acc - self.memory[address]) % 256 self.pc += 2 # LD instruction (Load index register to Accumulator) def LD(self, address): self.acc = self.memory[address] self.pc += 2 # XCH instruction (Exchange index register and accumulator) def XCH(self, address): temp = self.acc self.acc = self.memory[address] self.memory[address] = temp self.pc += 2
Обо всём по порядку:
-
NOP просто увеличивает значение счётчика команд (pc) на 1, что позволяет перейти к следующей инструкции в программе.
-
INC увеличивает значение аккумулятора (acc) на 1, ограничивая его значением до 0-255, и затем увеличивает pc на 1.
-
ISZ увеличивает значение в ячейке памяти с заданным адресом на 1, снова ограничивая его до 0-255. Если значение в ячейке становится равным 0, pc увеличивается на 2, иначе увеличивается на 1.
-
ADD добавляет значение из ячейки памяти с заданным адресом к значению аккумулятора, ограничивает результат до 0-255 и увеличивает pc на 2.
-
SUB вычитает значение из ячейки памяти с заданным адресом из значения аккумулятора, ограничивает результат до 0-255 и увеличивает pc на 2.
-
LD загружает значение из ячейки памяти с заданным адресом в аккумулятор и увеличивает pc на 2.
-
XCH обменивает значение аккумулятора и значение в ячейке памяти с заданным адресом, увеличивает pc на 2.
Дальше была создана функция run, в которой были прописаны опкоды с выполнением определённой функции:
def run(self): while self.pc < len(self.memory): opcode = self.memory[self.pc] # NOP instruction opcode if opcode == 0x0: self.NOP() # INC instruction opcode elif opcode == 0x6: self.INC() # ISZ instruction opcode elif opcode == 0x7: self.ISZ(self.memory[self.pc + 1]) # ADD instruction opcode elif opcode == 0x8: self.ADD(self.memory[self.pc + 1]) # SUB instruction opcode elif opcode == 0x9: self.SUB(self.memory[self.pc + 1]) # LD instruction opcode elif opcode == 0xA: self.LD(self.memory[self.pc + 1]) # XCH instruction opcode elif opcode == 0xB: self.XCH(self.memory[self.pc + 1]) else: print('Unknown opcode!!!') return self.pc += 1
Как составляется программа?
Программу надо составлять прямо в коде, а конкретно в program.py. Вот пример программы, которая отнимает от числа 12 число 5 и прибавляет число 2:
from cpu import CPU cpu = CPU() # Write the numbers 12, 5 and 2 to memory at arbitrary addresses (e.g. 0x10, 0x11 and 0x12) cpu.memory[0x10] = 12 cpu.memory[0x11] = 5 cpu.memory[0x12] = 2 # Execute the commands to subtract the numbers 12 and 5, and then add the number 2 to the resulting number cpu.LD(0x10) cpu.SUB(0x11) cpu.ADD(0x12) cpu.NOP() # The result of the program will be stored in the accumulator print('') print(f' Result: {cpu.acc}') print('')
Заключение
На этот раз я учёл ошибки прошлого эмулятора и в этой работе постарался сделать всё максимально верным.
У меня есть следующие идеи по развитию проекта на будущее:
-
Добавить ещё больше инструкций 4004-го.
-
Используя библиотеку tkinter, создать окно, где пользователь вводит программу и ему выводится результат (дабы не приходилось устанавливать сам Python, различные IDE к нему для запуска и теста эмулятора).
Полный код можно посмотреть на моём GitHub.
С вами был Yura_FX. Спасибо, что дочитали данную статью до конца. Не забывайте делиться своим мнением в комментариях 🙂
ссылка на оригинал статьи https://habr.com/ru/articles/805825/
Добавить комментарий