Оглавление


§ Инструкции 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 :             // ADD
    opc[5:3] == ADC ? a + op20 + psw[CF] :   // ADC
    opc[5:3] == SBB ? a - op20 - psw[CF] :   // SBB
    opc[5:3] == AND ? a & op20 :             // ANA
    opc[5:3] == XOR ? a ^ op20 :             // XRA
    opc[5:3] == OR  ? a | op20 :             // ORA
                      a - op20;              // SUB|CMP
В зависимости от выбранного 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 высчитывает полуперенос по стандартной формуле перексоривания 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} : // AND, XOR, OR
        {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), все флаги затронуты. Больше тут ничего не требуется.
Теперь перейдем к основной части, это к исполнению инструкции. На удивление, она довольно проста.
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 инструкции АЛУ были успешно запрограммированы в верилоге.
Приложение к материалам