Оглавление


§ Инструкции MOV

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