Оглавление
§ Инструкции MOV
Пожалуй, самой простой инструкцией, которую я могу считать после NOP, это MOV. Ее сделать легко, поскольку потребуется лишь скопировать с одного в другой источник данные. К тому же, достаточно простым кодом можно сразу же охватить все 64 MOV. Ниже представлен этот код.
8'b01xx_xxxx: case (t)
0: begin cp <= hl; sw <= 1; if (m53 && m20) pc <= pc; end
1: begin
n <= opc[5:3];
b <= !m53;
we <= m53;
d <= op20;
out <= op20;
end
4: begin sw <= 0; t <= (m53 || m20) ? 5 : 0; end
6: begin t <= 0; end
endcase
Этот код охватывает даже HALT, останов процессора. На проводе m53 появляется 1 тогда, когда в качестве операнда приемника (слева) указана память. Аналогично с m20, но указана память из операнда источника (справа). Если указаны и операнд приемник, и источник как память, то процессор останавливает свое выполнение в том плане, что не двигается никуда PC и он постоянно читает из HL байт и пишет обратно байт в HL.
На такте #1 классическая схема записи в регистр или память – все зависит от того, какой будет приемник. Если m53=1, то есть, приемник это память, то we=1, b=0, и op2 (через out) записывается в память. Если же там регистр, то он выбирается в n и туда записывается d (8 бит).
На такте #4 выбирается количество тактом. В случае если либо в операнде приемнике, либо в источнике указана память, то такая инструкция будет 7Т исполняться, иначе 5Т.
§ Арифметико-логическое устройство
Это ядро, можно даже сказать, сердце процессора. Именно обращение к инструкциям перемещения данных и работы с ними, обработки, занимают большую часть всей программы. Обычно именно по этой причине модули АЛУ греются в процессорах больше, так как количество переходов состояния из 0 в 1 и наоборот здесь наиболее частое.
Всего 8 инструкции АЛУ:
ADD (ADI) – сложение A + операнд
ADC (ACI) – сложение A + операнд + CarryFlag
SUB (SUI) – вычитание A – операнд
SBB (SBI), CMP (CPI) – вычитание A – операнд – CarryFlag
AND (ANA, ANI) – побитовое A & операнд
XOR (XRA, XRI) – побитовое A ^ операнд
OR (ORA, ORI) – побитовое A | операнд
Там где ANA, XRA и ORA в качестве операнда используется регистр или память, а там где ANI, XRI, ORI – то используется непосредственный операнд, который идет за опкодом.
Отличие инструкции SUB от CMP в том, что результат не записывается для CMP, но флаги пишутся для обоих. Составим небольшой код, по сути, являющийся мультиплексором на 8 входов.
wire [8:0] alur =
opc[5:3] == ADD ? a + op20 :
opc[5:3] == ADC ? a + op20 + psw[CF] :
opc[5:3] == SBB ? a - op20 - psw[CF] :
opc[5:3] == AND ? a & op20 :
opc[5:3] == XOR ? a ^ op20 :
opc[5:3] == OR ? a | op20 :
a - op20;
В зависимости от выбранного opc[5:3] (ALU MODE), будет записана определенная операция в alur. Основное вычисление производится именно здесь. Теперь необходимо рассчитать флаги.
wire sf = alur[7];
wire zf = alur[7:0] == 0;
wire hf = a[4] ^ op20[4] ^ alur[4];
wire af = (a[4] | op20[4]) & (opc[5:3] == AND);
wire pf = ~^alur[7:0];
wire cf = alur[8];
Объяснение флагов.
- Флаг SF просто копирует бит 7 из результата alur
- ZF проверяет наличие 0 на младших 8 битах результата
- HF высчитывает полуперенос по стандартной формуле [[/cpu/flag_half перексоривания]] 4-го бита результата и операнда
- Флаг AF здесь стоит особняком. Для XOR, OR логических операторов результат в HF=0, кроме AND, где результат будет равен битовому OR между 4-м битом двух операндов. Я не знаю, зачем это там было сделано.
- Флаг PF вычисляется обычно и означает четность
- CF просто копирует бит 8 из результат (заем или перенос)
Как видно, я здесь добавил некоторые константы для удобства чтения кода.
localparam
CF = 0, PF = 2, HF = 4, ZF = 6, SF = 7;
localparam
ADD = 0, ADC = 1, SUB = 2, SBB = 3,
AND = 4, XOR = 5, OR = 6, CMP = 7;
И последний штрих, это вычисление флагов по результату.
wire [7:0] aluf =
opc[5:3] == AND || opc[5:3] == XOR || opc[5:3] == OR ?
{sf, zf, 1'b0, af, 1'b0, pf, 1'b1, 1'b0} :
{sf, zf, 1'b0, hf, 1'b0, pf, 1'b1, cf};
На самом деле, тут только 2 случая. Если это логические операции (AND, XOR, OR), то вместо HF пишется AF, и в CF=0. Для арифметических операции (ADD, ADC, SUB, SBB, CMP), все флаги затронуты. Больше тут ничего не требуется.
Теперь перейдем к основной части, это к исполнению инструкции. На удивление, она довольно проста.
8'b10xx_xxxx: case (t)
0: begin cp <= hl; sw <= 1; end
1: begin d <= alur; b <= (opc[5:3] != CMP); n <= 7; psw <= aluf; end
4: begin sw <= 0; t <= m20 ? 5 : 0; end
6: begin t <= 0; end
endcase
Алгоритм.
- Такт 0, по классике, выставляется указатель в памяти и переключается на него
- Такт 1, запись результата из alur в d, но запись в регистр A (n=7) производится только тогда, когда выбранная инструкция не CMP. Для флагов сохраняется всегда.
- Такт 4, если операнд был взят из памяти, то прибавляется +2Т к выполнению
И вот, 64 инструкции АЛУ были успешно запрограммированы в верилоге.