Сказ о том, как я эмулятор Intel 4004 на Python писал (часть 2)

Небольшой дисклеймер: перед прочтением данной статьи ознакомьтесь с первой частью, дабы вникнуть в суть происходящего. Желаю вам приятного прочтения :)

Вступление

Сидел я тут на днях и думал, как можно улучшить мой эмулятор «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 возможных (так что полноценным его назвать нельзя, скорее урезанным, в прошлый раз было также).

Список инструкций из datasheet

Список инструкций из datasheet

Вот список используемых инструкций:

Инструкция

Описание инструкции

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('')

Результат программы

Результат программы

Заключение

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

У меня есть следующие идеи по развитию проекта на будущее:

  1. Добавить ещё больше инструкций 4004-го.

  2. Используя библиотеку tkinter, создать окно, где пользователь вводит программу и ему выводится результат (дабы не приходилось устанавливать сам Python, различные IDE к нему для запуска и теста эмулятора).

Полный код можно посмотреть на моём GitHub.

С вами был Yura_FX. Спасибо, что дочитали данную статью до конца. Не забывайте делиться своим мнением в комментариях :)

© Habrahabr.ru