АЛУ: арифметико-логическое устройство

Урок собирает АЛУ — вычислительное «сердце» процессора, которое по команде складывает, вычитает и применяет логику.

АЛУ (арифметико-логическое устройство) — комбинационный блок, который по управляющему коду операции выполняет одну из нескольких функций (сложение, вычитание, AND, OR...) над двумя операндами и выставляет флаги результата.

Зачем АЛУ устроено именно так

Процессору нужно уметь много операций, но строить отдельную схему под каждую и переключаться дорого. Решение элегантно: АЛУ всегда вычисляет все операции сразу, а мультиплексор по управляющему коду выбирает нужный результат. Так одно компактное устройство обслуживает всю арифметику и логику. Это прямое применение всего, что мы собрали: сумматор + логические вентили + мультиплексор.

Устройство АЛУ

        A ──┬───────────────┐
            │   ┌─────────┐  │
            ├──→│ Сумматор│──┤
        B ──┼──→└─────────┘  │   ┌─────┐
            ├──→[ AND ]──────→│MUX│──→ результат
            ├──→[ OR  ]──────→│   │
            └──→[ XOR ]──────→└─────┘
                                ↑
                          код операции (op)

  флаги: Z (ноль), N (знак), C (перенос), V (переполнение)

Управляющий код op идёт на селектор мультиплексора. Вычитание делается через дополнительный код: A - B = A + (NOT B) + 1, поэтому отдельная схема вычитания не нужна — достаточно инвертировать B и подать перенос 1 в сумматор.

Как работает под капотом: собираем АЛУ

def alu(A, B, op, bits=8):
    mask = (1 << bits) - 1
    if op == "ADD":
        raw = A + B
    elif op == "SUB":
        raw = A + ((~B) & mask) + 1      # A + (-B) в доп. коде
    elif op == "AND":
        raw = A & B
    elif op == "OR":
        raw = A | B
    elif op == "XOR":
        raw = A ^ B
    result = raw & mask
    # флаги
    Z = int(result == 0)                 # ноль
    N = (result >> (bits - 1)) & 1       # знак (старший бит)
    C = int(raw > mask)                  # перенос за разрядность
    return result, {"Z": Z, "N": N, "C": C}

for op in ("ADD", "SUB", "AND", "OR", "XOR"):
    r, f = alu(0b00001100, 0b00001010, op)   # 12 и 10
    print(f"{op}: {r:>3} ({r:08b})  флаги {f}")

Вывод:

ADD:  22 (00010110)  флаги {'Z': 0, 'N': 0, 'C': 0}
SUB:   2 (00000010)  флаги {'Z': 0, 'N': 0, 'C': 1}
AND:   8 (00001000)  флаги {'Z': 0, 'N': 0, 'C': 0}
OR:  14 (00001110)  флаги {'Z': 0, 'N': 0, 'C': 0}
XOR:   6 (00000110)  флаги {'Z': 0, 'N': 0, 'C': 0}

Зачем флаги

АЛУ не просто считает — оно сообщает о свойствах результата через биты-флаги: Z (результат ноль), N (отрицательный), C (был перенос), V (переполнение знакового). На эти флаги опираются команды условных переходов: «если результат ноль — прыгнуть», «если меньше — прыгнуть». Так из арифметики рождается возможность ветвлений в программах.

ФлагКогда поднятИспользуется в
Z (Zero)результат равен 0проверка равенства
N (Negative)старший бит = 1сравнение знаковых
C (Carry)перенос за разрядностьсравнение беззнаковых, многоразрядная арифметика
V (Overflow)переполнение знаковогосравнение знаковых

Глубже в тему

Идея «считать всё сразу, а выбрать одно мультиплексором» поначалу кажется расточительной — зачем тратить вентили на операции, результат которых выбросят? Но в комбинационной схеме это, наоборот, экономно. Альтернатива — физически переключать структуру схемы под каждую команду — потребовала бы либо перепрошиваемого железа, либо длинных цепочек управляемых ключей, что и медленнее, и больше по площади. А вентили AND, OR, XOR работают параллельно и независимо; пока сумматор складывает, логические блоки уже посчитали свои результаты. Селектор MUX лишь решает, чей ответ выпустить наружу. Так одно компактное устройство обслуживает всю арифметику и логику, и именно поэтому АЛУ — это прямое применение трёх предыдущих уроков: сумматора, логических вентилей и мультиплексора, собранных вместе.

Особенно изящно в АЛУ устроено вычитание. Благодаря дополнительному коду A - B = A + (NOT B) + 1: достаточно инвертировать все биты B и подать в сумматор входной перенос, равный единице. Никакой отдельной «схемы вычитания» не существует — это тот же сумматор, которому через мультиплексор на одном из входов подсунули инверсию B. Один управляющий бит «инвертировать B и добавить 1» превращает сложение в вычитание. Это и причина, по которой компьютеры повсеместно используют именно дополнительный код для отрицательных чисел: он делает вычитание бесплатным с точки зрения железа.

Флаги — это то, что превращает АЛУ из калькулятора в основу принятия решений. Сами по себе вычисления не дают программе ветвлений: чтобы выполнить «если a равно b, прыгнуть», процессор вычитает b из a и смотрит на флаг Z. «Если a меньше b» для беззнаковых чисел читается по флагу C, для знаковых — по комбинации флагов N и V. Именно поэтому в ассемблере операция сравнения CMP — это, по сути, вычитание, у которого выбрасывают численный результат и оставляют только флаги. Из голой арифметики так рождается управление потоком — циклы, условия, переходы.

Разница между флагами C и V — классический источник путаницы, и её стоит разобрать на примере. Флаг C (перенос) относится к беззнаковой интерпретации: он поднят, когда результат не влез в разрядность как беззнаковое число. Флаг V (переполнение) относится к знаковой интерпретации: он поднят, когда знаковый результат вышел за допустимый диапазон, например при сложении двух больших положительных чисел получился отрицательный. Для одних и тех же бит эти флаги могут быть разными, потому что одни и те же биты можно читать и как беззнаковое, и как знаковое число. Аппаратура не знает, что вы «имели в виду», — она выставляет оба флага, а уже команда перехода выбирает, на какой из них смотреть.

Частые ошибки

  • Думать, что АЛУ «выбирает» операцию заранее. Чаще оно считает все варианты, а мультиплексор берёт нужный — экономнее по схеме.
  • Путать флаги C и V. C — перенос (для беззнаковых), V — переполнение знакового; для одних и тех же чисел они могут отличаться.
  • Забывать, что вычитание — это сложение. SUB реализуется как ADD с инверсией B и переносом 1.

Итог

  • АЛУ выполняет арифметику и логику над двумя операндами; операцию задаёт код op (селектор MUX).
  • Вычитание реализуется через дополнительный код, отдельной схемы не нужно.
  • Флаги Z/N/C/V описывают результат и питают команды условных переходов.
Проверьте себя
1. Как АЛУ обычно реализует выбор операции?
AОтдельной схемой под каждую команду, физически переключаемой
BВычисляет несколько операций сразу, а мультиплексор выбирает нужный результат по коду op
CПерепрошивается под каждую команду
DЗапрашивает операцию у памяти
2. Как АЛУ выполняет вычитание A - B?
AОтдельной схемой вычитания
BКак A + (NOT B) + 1, используя дополнительный код
CМногократным сложением
DЧерез дешифратор
3. Для чего нужен флаг Z (Zero) АЛУ?
AПоказывает переполнение
BПоднимается, когда результат равен нулю, и используется для проверки равенства в переходах
CУказывает на знак числа
DСчитает число операций