Базовый блок: D flip-flop
Всё в синхронном проектировании сводится к одному крошечному куску железа: D flip-flop. У него есть clock-вход, data-вход и data-выход. На каждом нарастающем фронте clock он захватывает значение D и держит его до следующего фронта. Это всё.
В Verilog:
always @(posedge clk) begin
q <= d;
end
Три строки, один flip-flop. <= - non-blocking, что ровно соответствует поведению реальных flip-flop (разбираем в Blocking vs Non-blocking). Sensitivity posedge clk делает его последовательностным.
Сложив много таких с комбинационной логикой между ними, получишь любую синхронную схему, которую захочется построить.
Регистр с reset
Реальные проекты всегда нуждаются в reset - способе привести систему в известное состояние при включении или по запросу:
Это синхронный reset - условие reset вычисляется на фронте clock как любой другой вход. Синтезатор выдаёт flip-flop с 2-to-1 mux на data-входе: когда reset high, mux подаёт ноль; иначе подаёт d.
Для большинства проектов синхронный reset - правильный выбор. Проще по timing, играет хорошо с FPGA, и reset-сигнал может прийти откуда угодно - ему не нужен свой clock-выровненный источник.
Регистр с enable
Часто хочется регистр, который обновляется, только когда что-то ему скажет. Используй if внутри тактируемого блока и положись на неявное "держи прежнее значение" пропущенного else:
Это канонический "load-enabled-регистр". Встречается везде - конфигурационные регистры, pipeline-стадии, которые продвигаются только когда downstream готов, счётчики, которые ставятся на паузу и возобновляются. Опущение последнего else намеренно и безопасно внутри тактируемого блока: flip-flop уже помнит прежнее значение, так что "ничего не делать" значит "держать".
В комбинационном блоке тот же код вывел бы latch. Разные правила, тот же синтаксис.
Счётчик
Счётчик - регистр, чьё следующее значение - текущее плюс один:
Счётчик инкрементируется на каждом такте, когда enable high. После 16 тактов оборачивается обратно в 0 (потому что мы объявили 4 бита, и 15 + 1 переполняется в 0). Это оборачивание - суть N-битной арифметики и ровно то, как ведёт себя настоящий аппаратный счётчик.
Shift register
Сложив flip-flop, получаешь shift register. Трюк - делать все сдвиги в одном non-blocking операторе:
Тело - out <= {out[WIDTH-2:0], in} - конкатенируем младшие биты текущего out с новым in и присваиваем всё это. Поскольку это non-blocking, RHS читает старое out до того, как LHS обновляется. Эффект - чистый N-битный сдвиг за один такт.
Это паттерн shift-register в самой маленькой форме. Обобщается на LFSR, serial-передатчики, deserializers - любой проект, где данные движутся через цепочку flip-flop на каждом такте.
Pipelines
Pipeline - цепочка регистров, разделённых комбинационной логикой. Каждая стадия обрабатывает данные с предыдущей и кормит следующую:
Три стадии, три такта latency, но новый результат каждый такт, как только pipeline полный. Latency - 3 такта, потому что данные проходят через три flip-flop; throughput - 1 операция за такт, потому что все три стадии работают одновременно над разными входами.
Так высокопроизводительные проекты достигают целевого throughput: держи стадии короткими, делай pipeline глубже и позволь параллелизму делать работу.
Асинхронный reset (когда нужен)
Иногда нельзя ждать фронта clock, чтобы выставить reset - чип отключается, clock залочен, внешний watchdog дёргает линию. В этих случаях:
always @(posedge clk or negedge reset_n) begin
if (~reset_n) q <= 0;
else q <= d;
end
В sensitivity list теперь и фронт clock, и фронт reset. Flip-flop реагирует немедленно на любое. reset_n по соглашению active-low (выставлен, когда low), поэтому проверка ~reset_n.
У асинхронного reset есть компромиссы: сложнее timing-анализ, может вызвать metastability при снятии, если не обрабатывать аккуратно, не портируется на все FPGA-архитектуры. По умолчанию - синхронный, асинхронный - только когда проект требует.
Что дальше
Теперь умеешь строить любой синхронный datapath. Следующий документ - Finite State Machines - собирает тактируемый регистр с case-оператором и даёт стандартную идиому FSM: рабочая лошадка любого контроллера, протокольного движка и блока принятия решений в цифровом проектировании.
Часто задаваемые вопросы
Что такое тактируемая логика в Verilog?
Логика, чьи обновления гейтятся clock-сигналом. Стандартная идиома - always @(posedge clk) target <= next_value;: на каждом нарастающем фронте clk target захватывает next_value. Эта одна строка описывает flip-flop в железе. Сложив много таких с комбинационной логикой между ними, строят счётчики, shift-регистры, pipelines - всё синхронное.
В чём разница между синхронным и асинхронным reset в Verilog?
Синхронный reset использует always @(posedge clk) if (reset) ... - reset сэмплируется на фронте clock как любой другой вход. Асинхронный reset использует always @(posedge clk or negedge reset_n) if (~reset_n) ... - блок срабатывает на фронте clock или на выставлении reset, так что reset действует немедленно. Синхронный - выбор по умолчанию; асинхронный - когда reset должен гарантированно сработать, даже если clock мёртв.
Как построить pipeline в Verilog?
Складывай несколько стадий always @(posedge clk) stageN_reg <= stageN_combinational; - комбинационная логика каждой стадии кормит регистр следующей, и все регистры захватывают на одном фронте clock. Результат - pipeline, куда новые данные входят каждый такт и выходят через N тактов, с throughput один результат за такт.
Что такое shift register в Verilog?
Цепочка flip-flop, где выход каждого кормит вход следующего. Каждый фронт clock сдвигает каждый бит на одну позицию. Каноническая Verilog-версия использует non-blocking присваивания: out <= {out[N-2:0], in}; - эта одна строка создаёт N-битный shift register, принимающий один бит за такт с in.