Обучающая сборка - часть 3

Как на самом деле работают процессоры?

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

Микропроцессор играет очень важную роль в вашем компьютере. Изготовленный на одном чипе, он отвечает за управление функциями машины. В этой статье мы специально рассмотрим MOS Technology 6502, невероятно популярный чип семидесятых годов. Хотя технология устарела, многое из того, что мы скажем, применимо и к современным машинам. Эта статья также является частью 3 в моей серии обучающих сборок (части 1, 2). Тем не менее, я стремлюсь сделать каждую часть как можно более автономной - может быть несколько вещей, которые зависят от предыдущих частей, но если все, что вас интересует, это то, как работает микропроцессор, надеюсь, эта часть все еще для вас!

6502 (шестьдесят пять сантиметров)

С точки зрения изучения языка ассемблера 6502 мы можем вспомнить, что он сильно связан с архитектурой, на которой он написан. Все, что вы пишете, на некотором уровне будет зависеть от набора инструкций и аппаратного обеспечения процессора. Для написания программы вам может не потребоваться глубокое знание того, как работает 6502, но для того, чтобы написать эффективную программу, вам потребуется. Мы уже начали это видеть. В первой части этой серии мы увидели, как инструкции могут отличаться на разных машинах, а в части 2 мы увидели, как оборудование позволяет нам более легко вычислять вещи. Мы начнем собирать воедино все, чему мы научились до сих пор. Поскольку мы продолжим в следующие недели, мы также сможем вернуться сюда и еще раз оценить связи между аппаратным и программным обеспечением, которые мы собираемся писать.

Чтобы понять архитектуру микропроцессора, в этой статье основное внимание будет уделено следующему рисунку:

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

Шины данных

Мы начнем с микропроцессора 6502 (MPU), с шин данных. Компьютерная шина предназначена для передачи данных и сигналов по машине. Для 6502 их три:

  • шина данных: как следует из названия, она передает данные. Шина данных имеет ширину 8 бит, что означает, что она может передавать только 8 бит за раз. Если вы хотите передать больше данных, по шине должны быть отправлены два отдельных сигнала. Это означает, что вы можете передать номер 00011000 за одну поездку, но 00110001 00110101 займет два. Обычно он передает данные из памяти в MPU, из MPU в память или из MPU в устройство ввода / вывода.
  • адресная шина: эта шина несет адреса. Этот адрес обычно будет источником или местом назначения для данных. Адресная шина также отличается от шины данных тем, что имеет 16-битную ширину. Это означает, что вы можете получить доступ к любому 16-битному адресу. Фактически это означает, что у вас есть доступ только к ~ 64000 адресам. После того, как вы что-то запомнили во всех них, готово! Нет больше памяти! Интересно, что это было проблемой совсем недавно - даже современные 32-разрядные компьютеры могут иметь доступ только к 3 ГБ оперативной памяти (эта проблема называется барьером 3 ГБ).
  • Control-bus: управляющая шина будет пытаться синхронизировать все внутри машины. Здесь он был удален для простоты.

MPU и регистры

Микропроцессор (MPU) на приведенном выше рисунке, в нашем случае - это 6502. Он содержит арифметико-логический блок (ALU), блок управления (CU) и регистры (некоторые из которых являются флагами, которые мы видели в неделя 2). CU контролирует работу процессора. Он решает, когда получит инструкцию, данные или адрес, как их интерпретировать.

Ниже приведен рисунок, показывающий внутреннее устройство 6502 (без CU). Помимо этого, также потребуется точное время. Вот почему он подключен к таймеру на рисунке выше.

Давайте рассмотрим эту диаграмму справа налево и обсудим, что происходит.

ALU имеет узнаваемую V-образную форму. Его роль в 6502 - выполнять вычисления. Он будет принимать два входа, один в «левом входе» и один в «правом входе». С его помощью вы можете выполнять сложение (инструкция ADC) или вычитание (инструкция SBC). Левый вход АЛУ подключен к аккумулятору, регистру A. При выполнении логических или арифметических операций обычно одно из значений сохраняется в аккумуляторе, а другое где-то в памяти. Результат любой операции обычно также сохраняется в аккумуляторе. Именно после этого накопительного поведения регистр получает свое имя.

Следующие две части - это 8-битные регистры X и Y. Они часто используются как способы хранения значений. Они полезны, так как 6502 поставляется с несколькими инструкциями по их использованию. Среди них INX, который увеличивает значение в X на 1, DEY, который уменьшает Y на 1, и TXA, который передает содержимое X в A. Наличие этого разнообразия регистров дает нам возможность перемещать и изменять данные без необходимости указывать память место нахождения. Позже в этой статье мы увидим, что это может улучшить скорость написанного кода.

Затем в P хранится ряд регистров:

  • N: отслеживает, если результат в ALU отрицательный.
  • V: флаг переполнения (помните, с прошлой недели!)
  • B: используется для обработки перерывов (мы обсудим это подробнее в более позднем посте).
  • D: это помогает обрабатывать числа BCD. Это другой способ представления данных. Мы не будем здесь это обсуждать.
  • I: это отслеживает, как будут обрабатываться прерывания (мы обсудим это подробнее в более позднем посте).
  • Z: отслеживает, равен ли результат вычисления нулю.
  • C: флаг переноса (также с прошлой недели!)

Наличие всех этих регистров позволяет нам отслеживать данные и проводить сравнения. Например, мы могли бы захотеть перейти к другой части нашей программы, если результат вычисления равен нулю. Проверяя регистр Z, мы можем узнать, следует ли нам это делать. Некоторые из этих регистров используют определенные инструкции (BRK устанавливает B в 1). Когда мы с ними столкнемся, я опишу их влияние на регистр.

Указатель стека (SP) указывает на определенный бит в месте в памяти, называемом стеком. Используя его, мы можем отслеживать, где в стеке мы находимся. Я расскажу об этом более подробно, когда мы обсудим стек позже в этом посте.

Какую бы программу мы ни писали, ее нужно будет хранить где-то в памяти. Когда мы запустим его, нам нужно будет определить, где в памяти находится каждая инструкция, и передать ее процессору для интерпретации и выполнения. Program-Counter (PC) - это 16-разрядный реестр. Отметив здесь, мы сможем узнать расположение следующей инструкции, которую программа должна будет выполнить. Давайте посмотрим на это подробнее прямо сейчас!

Инструкции и счетчик программ

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

Извлечь: на этом этапе содержимое программного счетчика (ПК) помещается на адресную шину. Установив здесь отметку, мы сможем узнать следующую инструкцию, которую программа должна будет выполнить. Затем он будет помещен в специальный внутренний регистр, называемый регистром инструкций (IR). Цикл выборки завершен!

Декодировать и выполнять: с помощью соответствующей инструкции в IR блок управления (CU) может начать ее декодирование. На этом этапе он может генерировать сигналы, необходимые для выполнения инструкции. Время, которое на это потребуется, будет зависеть от того, что создается. Некоторые инструкции могут выполняться внутри MPU (например, INX), тогда как некоторым потребуется что-то еще из памяти (например, данные или адрес памяти). Последний тип инструкций займет больше времени, так как вам нужно совершить поездку в какое-то место в памяти, чтобы вычислить его. Затем стоит написать код таким образом, чтобы использовать первый тип инструкций. Время, необходимое для выполнения инструкции, измеряется в тактах. 6502 использует тактовую частоту в один мегагерц, поэтому тактовый цикл занимает 1 микросекунду.

Наконец, ПК будет увеличен, чтобы он указывал на следующую инструкцию. Теперь процесс может начаться снова.

Карты памяти

Мы обсудили, как с помощью 16-битной адресной шины мы можем получить доступ к 65 535 различным ячейкам памяти. Хотя мы обращаемся к каждому из этих адресов одними и теми же методами, это не означает, что каждый раздел памяти используется для одной и той же цели. 64 535 адресов разделены на страницы. Каждая страница представляет собой блок из 256. Это означает, что адрес от 0 до 255 (или от 0 до 100 в шестнадцатеричном формате) соответствует странице 0, адрес от 256 до 511 соответствует странице 1 и т. Д. Таким образом, в вашем коде вы будете отслеживать страницу, на которой вы находитесь, и свое местоположение на странице. Когда вы пересекаете границу страницы (например, переходите со страницы 12 на 13), это может привести к необходимости выполнения дополнительной инструкции. Это потому, что вы обновляете не только свое местоположение на странице, но и номер страницы.

Определенные страницы в памяти 6502 предназначены для выполнения определенных задач. Карта памяти - это рисунок, который описывает, как предполагается использовать места в вашей системе. Независимо от того, какую систему 6502 вы используете (Apple II, c64, NES и т. Д.), Многие функции останутся прежними. Однако карта памяти может быть разной. Ниже приведена очень общая карта памяти 6502.

Нулевая страница

Первая страница в 6502 занимает важное место. Нулевая страница, диапазон значений от 0 до 255, является единственной областью памяти, к которой можно получить доступ с 8-битным адресом. Поскольку анализ 8-битного адреса выполняется быстрее, чем анализ 16-битного, нулевая страница становится областью в памяти, где данные могут быть прочитаны и записаны более эффективно. Поэтому разумно хранить там важные данные, которые нужно будет много читать.

Стек

Вторая страница тоже очень важна. Стек находится в диапазоне от 256 до 511. Эта область памяти является списком «последним вошел - первым ушел» (LIFO). Фактически это означает, что вы можете извлечь из стека только последнее, что вы в него положили. Это похоже на аккуратную стопку бумаг на столе, вы можете получить доступ только к верхней. Затем у нас есть указатель стека (регистр S), который указывает нам адрес памяти, соответствующий вершине этой стопки. 6502 даже дает нам некоторое ручное управление, позволяя нам перемещать и извлекать объекты в стек по желанию, используя PHA и PLA. Может показаться странным, зачем вам нужна такая область памяти. Попробуем привести пример, где это было бы полезно.

Во-первых, немного терминологии - мы будем называть набор инструкций, которые мы часто используем, подпрограммой. Вы можете представить это как функцию, которую вы напишете на Python. Теперь напишем немного псевдокода:

1. a = 1
2. b = 2
3. 
4. c = subroutine1()
5. x = a + b + c
6.
7. subroutine1:
8.     y = subroutine2()
9.     return y
10. subroutine2:
11.     z = 2
12.     return z

Мы представим числа, которые мы видим слева, как места в памяти. В ячейке памяти 1 будет храниться a = 1 и так далее. По мере прохождения каждой строки счетчик программы будет увеличиваться, чтобы мы знали, что будет дальше. Однако, в конце концов, мы перейдем к строке 4, и нам придется вызвать subroutine1(). Чтобы решить эту проблему, нам придется перейти к строке 7. Когда эта подпрограмма завершится, как мы узнаем, как вернуться домой к строке 5?

Что произойдет, чтобы гарантировать доступность этой информации, так это то, что место, куда нам нужно будет вернуться, будет помещено в стек. Теперь мы можем продолжить как обычно, начиная со строки 7, зная, что, нажав на оператор return, мы можем извлечь эту информацию из стека, добавить местоположение строки 5 к счетчику программы и вернуться домой.

Однако здесь пример становится немного сложнее. Когда мы дойдем до строки 8 (внутри subroutine1) мы теперь должны ввести subroutine2() в строке 10. Автоматически местоположение строки 9 (куда нам нужно будет вернуться) будет помещено в стек. Теперь содержимое стека выглядит следующим образом :

1. line9
2. line5

Местоположение строки 9 - это последнее, что мы вставили, и поэтому оно должно быть первым, что мы извлечем из него. Как мы увидим, это будет именно то поведение, которое мы хотим.

Итак, теперь мы находимся в строке 10 и внутри subroutine2(). Мы можем продвигаться к строке 12, как обычно. На строке 12 подпрограмма завершается, и мы получаем оператор return. Что нам делать? Куда мы пойдем дальше? На этом этапе, вытащив место наверху стека, мы знаем, что вернемся к строке 9.

Теперь, когда мы снова в строке 9, мы видим, что это еще один оператор возврата. Давайте извлечем из стека еще раз и получим местоположение строки 5. Теперь программа вернется сюда и завершит работу.

Это действительно отличная идея, и стоит попытаться ее осмыслить. Подводя итог (по крайней мере, на мой взгляд), стопку можно использовать как способ для вашей машины «сбрасывать панировочные сухари». Сохраняя то место, куда он должен будет вернуться, как только он «заблудится» в подпрограмме, механизм LIFO стека гарантирует, что он сможет найти свой путь домой.

Представьте на мгновение, что вы продолжаете вводить подпрограммы. У вас никогда не будет возможности удалить что-то из стека. В конце концов, стек заполнится локациями и закончится место, что приведет к сбою. Это называется переполнением стека (эй! Это название этого веб-сайта!).

I/O

Следующая страница зарезервирована для устройств ввода-вывода. Доступ к этим областям памяти позволит нам взаимодействовать с внешним миром. Данные здесь будут соответствовать внешним устройствам. Чтение и запись в эти области памяти позволит нам связываться с принтерами, игровыми контроллерами и другими периферийными устройствами.

баран

На этой диаграмме оперативная память (RAM) сопоставлена ​​с $ 0300 до $ E000 (помните, это шестнадцатеричные значения!). Это область, в которую мы можем записывать данные.

ПЗУ

Ячейки между $ E000 и $ FFFF отображаются в постоянное запоминающее устройство (ПЗУ). Как следует из названия, мы не можем писать сюда. Фактически, любая попытка сделать это будет просто проигнорирована. Примером ПЗУ могут быть данные, хранящиеся на игровом картридже. Когда консоль загружается, эта память может быть прочитана, но, очевидно, не может быть записана, иначе данные игры будут изменены. Если вы хотите что-то изменить в ПЗУ, это сначала нужно будет перенести в ОЗУ.

Мы, программисты, должны отслеживать, где хранятся данные и правильно ли они хранятся. При переходе от системы к системе нам также необходимо знать, что память может отображаться по-разному.

Мы закончим обсуждение памяти несколько другой темой: порядок байтов. Независимо от того, являются ли процессор и язык прямым или обратным порядком байтов, зависит от того, как он упорядочивает байты в числе. Например, в системе с прямым порядком байтов старший бит числа помещается справа. Для хранения шестнадцатеричного числа ABCD требуется 2 байта. Поскольку 6502 является прямым порядком байтов, поэтому будут упорядочены 2 байта: CD, AB. Машины с прямым порядком байтов делают обратное. Это станет важным, когда мы начнем писать код сами.

Выводы

На этой неделе мы рассмотрели основные части аппаратного обеспечения 6502, показывая нам, как микропроцессор работает в процессе. Что для нас сейчас важно, так это большой диапазон регистров и карты памяти, которые дает нам 6502. Это очень сильно влияет на то, что мы можем делать с машиной. По мере того, как мы углубимся в набор инструкций и сами напишем программы, мы увидим, насколько наше программное обеспечение будет взаимосвязано с оборудованием. На данный момент может показаться, что мы усвоили много разных и не связанных между собой идей, но по мере нашего прогресса мы увидим, как все это сочетается друг с другом.

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

Это третья из моей серии «обучающих сборок».

Эта статья адаптирована из моего личного блога. Большая часть материалов, о которых я говорю, будет поступать из двух основных источников: Программирование на языке ассемблера 6502 Лэнса Левенталя и Программирование 6502 Родни Закса.