§ Объявление пинов процессора
Любой процессор обычно опирается на АЛУ, потому что задача процессора — это обработка данных, произведение операции над данными, так что без особого устройства, который называется Арифметико-Логическое Устройство (АЛУ) дело не пойдет. Это очень важный блок в любом процессоре.Но вначале разберемся с входящими и выходящими пинами к модулю процессора.
/* verilator lint_off WIDTHEXPAND */ /* verilator lint_off WIDTHTRUNC */ /* verilator lint_off CASEX */ /* verilator lint_off CASEOVERLAP */ /* verilator lint_off CASEINCOMPLETE */ module core ( input clock, // Тактовая частота input reset_n, // =0 Сброс процессора input ce, // =1 Процессор активирован output [19:0] a, // Адресная шина (20 бит) input [ 7:0] i, // Входящие данные output reg [ 7:0] o, // Исходящие данные output reg w, // Разрешение записи в память output reg [11:0] flags, // Флаги процессора output reg halt, // Признак остановки выполнения output m0, // =1 Выполняется 1-й такт инструкции output reg ud // =1 Неизвестный опкод ); endmoduleТеперь подробнее рассмотрю каждый из пинов, который представлен тут.
- Тактовая частота. Обычно я выбираю 25 мгц, это стандарт для меня. Он совпадает с VGA пиксель-клоком и достаточен для всех задач, поскольку скорость распространения сигнала максимально составляет 40 нс, при этом около 15 нс расходуется на извлечение данных из памяти и 25 нс на вычисление результатов, чего вполне хватает.
- Сброс процессора через reset_n необходим всегда при запуске ПЛИС. Я подключаю его к выходу locked из PLL (генератора частот). Когда ПЛИС только запускается, то какое-то время генератор частот настраивает частоту, установив locked=0, и как только конфигурация будет закончена, то locked становится равным 1, но в то время пока он был 0, то тактовые сигналы на clock все равно отсылались, что дает возможность процессору сброситься до начального состояния.
- Сигнал CE по умолчанию установлен в 1 и это означает, что процессор будет выполнять инструкции. Если там 0, то процессор инструкции не выполняет. Этот сигнал фактически аналогичен сигналу WAIT, который стопает процессор на какое-то время. По сути, в данный момент этот сигнал не нужен, но он будет полезен при использовании контроллера памяти, который будет временно останавливать процессор при не готовых данных из памяти, например.
- Пины a, i, o, w — это стандартный способ подключить процессор к памяти. В процессорах, на самом деле, для экономии пинов, используется i + o в одном и то же пине, например d, но я всегда разделяю их. Пин
I
требуется для получения значения данных из памяти по адресу A, а пины O и W необходимы для записи в память по адресу A, если W=1. Если же W=0, то данные только читаются, а не пишутся - Флаги flags выведены на внешние пины только для отладочных целей, ни для чего более.
- Также это касается и HALT, M0 и UD. Они необязательны, но без них никуда, если я хочу подключить их к симулятору. Эти пины нужны для отладчика, как и флаги. Также, как и набор регистров, но на данный момент я не вывел их на внешние пины, пока что в этом нет необходимости.
§ АЛУ
У этого процессора не так много арифметико-логических операции, не считая операции сдвига, а также например, деления или различных инструкции FPU (Floating Point Unit). Всего их восемь: ADD, SUB, ADC, SBB, XOR, OR, AND, CMP. Да, всего лишь восемь.localparam ADD = 0, OR = 1, ADC = 2, SBB = 3, AND = 4, SUB = 5, XOR = 6, CMP = 7; wire [16:0] alu_r = alu == ADD ? op1 + op2 : alu == OR ? op1 | op2 : alu == ADC ? op1 + op2 + flags[CF] : alu == SBB ? op1 - op2 - flags[CF] : alu == AND ? op1 & op2: alu == XOR ? op1 ^ op2: op1 - op2;Порядок следования команд здесь не случайно именно такой. Эти инструкции участвуют в разных видах опкодов. Существуют групповые инструкции, когда вместо операнда выбирается номер функции, и он должен совпадать с тем, что написано выше. То есть номер функции АЛУ=5 отведен для SUB, и никак иначе.
Работают такие инструкции очень просто, если рассмотреть выше приведенный код, который по своей сути, является мультиплексором. Могу отметить то что функция CMP и SUB вычисляются одинаково, но в первом случае, при выполнении инструкции CMP сохраняются только флаги, а при выполнении функции SUB сохраняются и флаги, и записывается результат выполнения.
Также
flags[CF]
— это бит флага Carry, то есть, инструкция ADC это сложение + заём флага, а SBB — вычитание с заёмом флага переноса. Этот флаг имеет большое значение и используется в вычислениях.С самими вычислениями вроде как все понятно, но этого недостаточно. После того как процессор выполняет инструкцию АЛУ, он записывает еще и флаги, которых достаточно много и они нужны для дальнейших инструкции ветвления. Флаги после исполнения инструкции показывают, что далее делать процессору с результатами.
wire [ 3:0] alu_top = size ? 15 : 7; wire [ 4:0] alu_up = alu_top + 1'b1; wire is_add = alu == ADD || alu == ADC; wire is_lgc = alu == XOR || alu == AND || alu == OR;Для провода
alu_top
устанавливается либо 15, либо 7, это номер старшего бита в результате. Как видно, может быть 2 варианта результата, либо 16, либо 8 бит. Самый старший бит alu_up
(там где бит переноса) устанавливается в 8 и 16 соответственно. В регистре size
, если установлено 0, то используется 8 битные операнды op1, op2, а противном случае, эти операнды классифицируются как 16-битные.То как формируются операнды, будет рассказано в следующей главе.
Также для того чтобы сделать правильные вычисления, необходимо знать тип инструкции. Если инструкция — это инструкция сложения (ADD, ADC), то на проводе
is_add=1
, иначе 0. Аналогично, если инструкция — это инструкция логики (XOR, AND или OR), то на проводе is_lgc=1
.Теперь же самое важное. Это вычисление флагов. Дальнейший код показывает то, как это происходит.
wire _of = !is_lgc & (op1[alu_top] ^ op2[alu_top] ^ is_add) & (op1[alu_top] ^ alu_r[alu_top]); wire _sf = alu_r[alu_top]; wire _zf = (size ? alu_r[15:0] : alu_r[7:0]) == 0; wire _af = !is_lgc & (op1[4] ^ op2[4] ^ alu_r[4]); wire _pf = ~^alu_r[7:0]; wire _cf = !is_lgc & alu_r[alu_up];В данном коде сложно будет разобраться без пояснительной бригады. Для начала скажем так, что для любой логической инструкции (XOR, AND, OR) результат флагов OF, AF и CF будет одинаково равен 0, так что там и записано вначале
!is_lgc
. При наличии логической инструкции данная конструкция сбрасывает результат в 0 для данных флагов.Теперь переходим к флагам, если это не логическая инструкция (все остальные — ADD, ADC, SUB, SBB, CMP).
- Флаг PF равен 1, если количество бит в результате четно (младших 8 битах). Например если количество бит 0,2,4 или 6, то будет 1, иначе будет 0. Четность бит подсчитать крайне просто, применив побитовое XOR к каждому биту. Теория о том, как это происходит, выходит за пределы этой скромной статьи.
- Флаг CF устанавливается в 1, если произошел либо перенос при сложении, либо же заем при вычитании. В зависимости от того, какая была выбрана битность (8 или 16), бит просто копируется из самого старшего бита alu_r (результата вычисления)
- Флаг SF аналогичен флагу CF, но бит копируется с последнего бита результата (7 или 15 бит), а не с бита переноса
- Флаг ZF — это флаг нулевого результата. В зависимости от битности (16 или 8) проверяется либо младшие 8 бит, либо же все 16 бит результата на 0. Если там 0, то ставится флаг ZF=1
- Флаг AF вычисляется на основе побитового XOR над запрошенными операндами и результатом в бите 4. Это флаг полупереноса, который проверяет, был ли перенос из 3 в 4 бит при сложении или был ли заем из 4 в 3 бит при вычитании. Теория о том как это работает, у меня описана в моей статье.
- Флаг OF самый сложный для понимания, и он означает "переполнение" при вычислении результата. О том как это работает, тоже можно прочесть в моей статье. Могу лишь сказать что этот флаг совершенно незаменим при сравнениях с числами с дополненным знаком.
wire [11:0] alu_f = {_of, flags[10:8], _sf, _zf, 1'b0, _af, 1'b0, _pf, 1'b1, _cf};На этом проводе будет записано то самое итоговое значение флагов, которое будет сохранено после вычисления. Как видно, биты номер 1, 3 и 5 содержат фиксированные значения 1,0,0, а флаги с 8 по 10 бит просто копируются без изменений. Там находятся флаги управления DF,IF,TF. Они ставятся и снимаются другими инструкциями.