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