Лисья Нора

Оглавление


§ Объявление пинов процессора

Любой процессор обычно опирается на АЛУ, потому что задача процессора – это обработка данных, произведение операции над данными, так что без особого устройства, который называется Арифметико-Логическое Устройство (АЛУ) дело не пойдет. Это очень важный блок в любом процессоре.
Но вначале разберемся с входящими и выходящими пинами к модулю процессора.
/* 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
Теперь подробнее рассмотрю каждый из пинов, который представлен тут.

§ АЛУ

У этого процессора не так много арифметико-логических операции, не считая операции сдвига, а также например, деления или различных инструкции 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).
Ну и самый финальный аккорд:
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. Они ставятся и снимаются другими инструкциями.