Последнее время в интернете очень много хайпа по поводу нейросетей. И конечно, у людей работа нейросетей обычно ассоциируется с разными сгенерированными котиками-картинками, нейросетевыми девушками, забавными видео, и ещё появилось много генерированных нейросетевых текстов и статей (которые часто вызывают раздражение).
Сейчас я хочу показать реальный пример, как нейросеть может быть использована в практических целях программистом FPGA. Я буду использовать бесплатные возможности нейросети Github Copilot https://github.com/copilot.
Github Copilot допускает 50 чат запросов в месяц бесплатно.
Для использования Github Copilot потребуется VPN, так как доступ к нему для россиян к сожалению похоже ограничен.
Предположим, есть задача: подключить энкодер к ПЛИС. На самом деле в гугле довольно легко найти готовые исходные тексты для модуля Verilog для подключения энкодера.
Вот есть такой модуль:
module quad(clk, quadA, quadB, count);
input clk, quadA, quadB;
output [7:0] count;
reg [2:0] quadA_delayed, quadB_delayed;
always @(posedge clk) quadA_delayed <= {quadA_delayed[1:0], quadA};
always @(posedge clk) quadB_delayed <= {quadB_delayed[1:0], quadB};
wire count_enable = quadA_delayed[1] ^ quadA_delayed[2] ^ quadB_delayed[1] ^ quadB_delayed[2];
wire count_direction = quadA_delayed[1] ^ quadB_delayed[2];
reg [7:0] count;
always @(posedge clk)
begin
if(count_enable)
begin
if(count_direction) count<=count+1; else count<=count-1;
end
end
endmodule
Допустим, что я не хочу пока сильно вникать, работает этот Verilog модуль или нет, но мне хочется понять, всё ли тут правильно? Мне нужен тестбенч для симуляции этого модуля. Я попрошу Copilot написать мне модуль Testbench Verilog, чтобы я смог его просимулировать. Важно, что задавать вопросы в чат с Copilot буду на русском языке. Ниже я приведу весь наш диалог с нейросетью. Но сразу напишу, что результат поразительный...
Итак, я написал первый запрос в чат нейросети и еще даже не привел свой код для работы с энкодером, а Copilot уже предложил к нему тестбенч.
Во втором запросе я ввёл код моего варианта кода и Copilot согласился с ним и адаптировал тестбенч для сигналов именно моего модуля.
В третьем запросе я попросил добавить в тестбенч создание VCD файла, чтобы можно было бы просмотреть сигналы проекта в графическом виде и нейросеть с лёгкостью сделала это.
Потом я провел симуляцию модуля Verilog и тестбенча с помощью Icarus Verilog и увидел, что выход счетчика не определен, всегда значение "x":
C:\Users\nick\Documents\Verilog>iverilog -o qqq tb_quad.v quad.v
C:\Users\nick\Documents\Verilog>vvp qqq
VCD info: dumpfile tb_quad.vcd opened for output.
Time quadA quadB count
0 0 0 x
50000 0 1 x
70000 1 1 x
90000 1 0 x
110000 0 0 x
130000 0 1 x
150000 1 1 x
170000 1 0 x
190000 0 0 x
210000 0 1 x
230000 1 1 x
250000 1 0 x
310000 0 0 x
330000 1 0 x
350000 1 1 x
370000 0 1 x
390000 0 0 x
410000 1 0 x
430000 1 1 x
450000 0 1 x
510000 0 0 x
530000 0 1 x
550000 1 1 x
570000 1 0 x
590000 0 0 x
610000 1 0 x
630000 1 1 x
650000 0 1 x
tb_quad.v:71: $finish called at 710000 (1ps)
В четвертом запросе я спросил почему выход counter не определён (я так-то и сам знаю почему), но для чистоты эксперимента спросил это у нейросети. И Copilot ответил мне, что нужно инициализировать счетчик либо сигналом сброса, либо простым присвоением начального значения.
Я исправил модуль quad как посоветовала нейросеть вот так:
reg [7:0] count = 0;
И после этого симуляция с Icarus Verilog прошла успешно:
C:\Users\nick\Documents\Verilog>iverilog -o qqq tb_quad.v quad.v
C:\Users\nick\Documents\Verilog>vvp qqq
VCD info: dumpfile tb_quad.vcd opened for output.
Time quadA quadB count
0 0 0 0
50000 0 1 0
70000 1 1 0
75000 1 1 255
90000 1 0 255
95000 1 0 254
110000 0 0 254
115000 0 0 253
130000 0 1 253
135000 0 1 252
150000 1 1 252
155000 1 1 251
170000 1 0 251
175000 1 0 250
190000 0 0 250
195000 0 0 249
210000 0 1 249
215000 0 1 248
230000 1 1 248
235000 1 1 247
250000 1 0 247
255000 1 0 246
275000 1 0 245
310000 0 0 245
330000 1 0 245
335000 1 0 244
350000 1 1 244
355000 1 1 245
370000 0 1 245
375000 0 1 246
390000 0 0 246
395000 0 0 247
410000 1 0 247
415000 1 0 248
430000 1 1 248
435000 1 1 249
450000 0 1 249
455000 0 1 250
475000 0 1 251
510000 0 0 251
530000 0 1 251
535000 0 1 252
550000 1 1 252
555000 1 1 251
570000 1 0 251
575000 1 0 250
590000 0 0 250
595000 0 0 249
610000 1 0 249
615000 1 0 248
630000 1 1 248
635000 1 1 249
650000 0 1 249
655000 0 1 250
675000 0 1 251
tb_quad.v:71: $finish called at 710000 (1ps)
Вот как выглядят сигналы модуля в gtkwave после симуляции:

Я вижу, что в целом этот конкретный модуль quad Verilog работает и может считать повороты энкодера по часовой и против часовой стрелки. Но у меня есть замечание - даже по waveform сигналам видно, что такой модуль считает не именно щелчёк поворота энкодера, а выполняет несколько инкрементов или декрементов за щелчёк. В принципе, возможно это не критично для некоторых приложений. Но если это важно, то всегда можно исправить, если попросить Copilot это сделать. Нужно учитывать, к примеру, только фронт сигнала quadA. Итоговый вариант, который я получил с помощью нейросети вот такой:
module quad(
input clk,
input quadA,
input quadB,
output reg [7:0] count = 0
);
reg [2:0] quadA_delayed = 0;
reg [2:0] quadB_delayed = 0;
// Сдвигаемые регистры для подавления метастабильности
always @(posedge clk) begin
quadA_delayed <= {quadA_delayed[1:0], quadA};
quadB_delayed <= {quadB_delayed[1:0], quadB};
end
// Счет только по фронту quadA (0→1) после синхронизации
always @(posedge clk) begin
if (!quadA_delayed[2] && quadA_delayed[1]) begin
if (quadB_delayed[1])
count <= count - 1;
else
count <= count + 1;
end
end
endmodule
Симуляция в Icarus Verilog выдаёт вот такие временные диаграммы сигналов в GtkWave:

На самом деле взаимодействие с нейросетью Github Copilot очень впечатляет.
Видно, что постепенно нейросеть перестаёт быть просто игрушкой, но вполне уже можно пробовать использовать её для решения практических задач.
Ниже приведен мой полный диалог с чатом Github Copilot. Форматирование диалога конечно отличается по стилю, так как на нашем сайте у движка совсем другие CSS параметры. Но в целом я постарался сохранить диалог в первоначальном виде. Мои запросы к нейросети в блоках на бледно желтом фоне. Ответы Copilot и его пояснения между моими желтоватыми блоками.
| Есть модуль Verilog для опроса двух линий quadA и quadB от энкодера вращения. Модуль Verilog должен выдавать 8 бит счетчика, значение которого увеличивается или уменьшается в зависимости он направления вращения энкодера. Код модуля приведен ниже. Напиши Verilog Testbench для этого модуля, чтобы проверить его работоспособность. |
Ниже приведён базовый шаблон Verilog testbench для модуля энкодера на основе сигналов quadA и quadB. В комментариях указаны типовые фрагменты для эмуляции вращения вперед и назад. Если потребуется подстроить имена или добавить дополнительные порты в зависимости от реального модуля, внесите правки.
| Verilog |
// Пусть ваш модуль называется quadrature_counter module quadrature_counter( input wire clk, input wire rst, input wire quadA, input wire quadB, output reg [7:0] count ); // Модуль -- сюда вставьте вашу реализацию endmodule // Тестбенч module tb_quadrature_counter; reg clk = 0; reg rst = 1; reg quadA = 0; reg quadB = 0; wire [7:0] count; // Instantiate the DUT quadrature_counter uut ( .clk(clk), .rst(rst), .quadA(quadA), .quadB(quadB), .count(count) ); // Clock generation: 10ns period always #5 clk = ~clk; // Процедура сброса initial begin rst = 1; #20; rst = 0; end // Стимуляция сигналов quadA/quadB для вращения вперед и назад initial begin // Ждем сброса @(negedge rst); // Немного подождем после сброса #10; // Квадратурная последовательность (вперед) // Например, 4 шага вперед repeat (4) begin {quadA, quadB} = 2'b00; #20; {quadA, quadB} = 2'b01; #20; {quadA, quadB} = 2'b11; #20; {quadA, quadB} = 2'b10; #20; end // Немного подождем #40; // Квадратурная последовательность (назад) // Например, 3 шага назад repeat (3) begin {quadA, quadB} = 2'b10; #20; {quadA, quadB} = 2'b11; #20; {quadA, quadB} = 2'b01; #20; {quadA, quadB} = 2'b00; #20; end // Завершение симуляции #50; $finish; end // Мониторинг initial begin $display("Time quadA quadB count"); $monitor("%4t %b %b %3d", $time, quadA, quadB, count); end endmodule |
Как это работает:
- Вращение вперед:
00→01→11→10 (clockwise) - Вращение назад:
10→11→01→00 (counter-clockwise) - После каждого шага добавлено небольшое ожидание для имитации шагов.
Как использовать:
- Скомпилируйте (vlog) оба файла (quadrature_counter.v и тестбенч).
- Запустите симуляцию (e.g., ModelSim, Vivado, etc.).
- Убедитесь, что при вращении вперед счетчик увеличивается, а при вращении назад уменьшается.
Если есть вопросы по интеграции – вставьте сюда ваш модуль, я адаптирую testbench.
|
Мой модуль module quad(clk, quadA, quadB, count); reg [2:0] quadA_delayed, quadB_delayed; wire count_enable = quadA_delayed[1] ^ quadA_delayed[2] ^ quadB_delayed[1] ^ quadB_delayed[2]; reg [7:0] count; always @(posedge clk) begin endmodule. |
Отлично, теперь вы предоставили свой модуль, вот полный рабочий Verilog testbench для проверки вашего quad-модуля:
| Verilog |
`timescale 1ns/1ps module tb_quad; reg clk = 0; reg quadA = 0; reg quadB = 0; wire [7:0] count; // Instantiate Device Under Test quad dut ( .clk(clk), .quadA(quadA), .quadB(quadB), .count(count) ); // Clock generator (10ns period) always #5 clk = ~clk; // Quadrature forward rotation (CW): 00 -> 01 -> 11 -> 10 -> 00 ... task step_forward; begin {quadA, quadB} = 2'b00; #20; {quadA, quadB} = 2'b01; #20; {quadA, quadB} = 2'b11; #20; {quadA, quadB} = 2'b10; #20; end endtask // Quadrature backward rotation (CCW): 00 -> 10 -> 11 -> 01 -> 00 ... task step_backward; begin {quadA, quadB} = 2'b00; #20; {quadA, quadB} = 2'b10; #20; {quadA, quadB} = 2'b11; #20; {quadA, quadB} = 2'b01; #20; end endtask initial begin // Monitor signals $display(" Time quadA quadB count"); $monitor("%6t %b %b %3d", $time, quadA, quadB, count); // Start from known state {quadA, quadB} = 2'b00; #30; // 3 steps forward step_forward; step_forward; step_forward; #40; // 2 steps backward step_backward; step_backward; #40; // Alternate forward & backward step_forward; step_backward; #40; $finish; end endmodule |
Пояснения:
- Используются задачи step_forward (по часовой стрелке) и step_backward (против часовой стрелки), чтобы симулировать вращение энкодера.
- Для каждого шага происходит переход через все 4 состояния (00→01→11→10 и наоборот).
- Мониторинг значений сигнала count после каждого шага.
Ожидаемый результат:
- После каждого полного шага вперед счётчик увеличивается на 1.
- После каждого полного шага назад счётчик уменьшается на 1.
- Тестбенч выводит значения на каждой итерации.
Готово для запуска без изменений!
|
Добавь в тестбенч создание VCD файла |
Конечно! Вот ваш testbench tb_quad с добавлением генерации VCD-файла для просмотра сигналов в waveform viewer (например, GTKWave):
| Verilog |
`timescale 1ns/1ps module tb_quad; reg clk = 0; reg quadA = 0; reg quadB = 0; wire [7:0] count; // Instantiate Device Under Test quad dut ( .clk(clk), .quadA(quadA), .quadB(quadB), .count(count) ); // Clock generator (10ns period) always #5 clk = ~clk; // Quadrature forward rotation (CW): 00 -> 01 -> 11 -> 10 -> 00 ... task step_forward; begin {quadA, quadB} = 2'b00; #20; {quadA, quadB} = 2'b01; #20; {quadA, quadB} = 2'b11; #20; {quadA, quadB} = 2'b10; #20; end endtask // Quadrature backward rotation (CCW): 00 -> 10 -> 11 -> 01 -> 00 ... task step_backward; begin {quadA, quadB} = 2'b00; #20; {quadA, quadB} = 2'b10; #20; {quadA, quadB} = 2'b11; #20; {quadA, quadB} = 2'b01; #20; end endtask initial begin // VCD DUMP $dumpfile("tb_quad.vcd"); // имя файла $dumpvars(0, tb_quad); // лог всё, что связано с этим модулем // Monitor signals $display(" Time quadA quadB count"); $monitor("%6t %b %b %3d", $time, quadA, quadB, count); // Start from known state {quadA, quadB} = 2'b00; #30; // 3 steps forward step_forward; step_forward; step_forward; #40; // 2 steps backward step_backward; step_backward; #40; // Alternate forward & backward step_forward; step_backward; #40; $finish; end endmodule |
Как посмотреть результат:
- Прогоните симуляцию (vvp, iverilog либо другой симулятор Verilog).
- Откройте файл tb_quad.vcd в GTKWave или другом VCD viewer:
>gtkwave tb_quad.vcd
Вы сможете проследить изменения clk, quadA, quadB и count во времени.
|
Почему при симуляции сигнал count всегда равен x? |
Причина появления значения x на выводе count в Verilog — это отсутствие инициализации регистра count.
В языке Verilog все регистры (reg), если не заданы явно, по умолчанию инициализируются в неопределённое значение (x).
Ваш модуль содержит такую строку:
reg [7:0] count;
а в блоке always @(posedge clk) отсутствует сброс (reset) или явная инициализация.
Как исправить
Самый надёжный способ — добавить сигнал сброса (reset), либо проинициализировать регистр в начальное значение при запуске симуляции:
Вариант 1: Добавьте асинхронный или синхронный reset
| Verilog |
module quad(clk, quadA, quadB, count, rst); input clk, quadA, quadB, rst; output [7:0] count; reg [2:0] quadA_delayed, quadB_delayed; always @(posedge clk) quadA_delayed <= {quadA_delayed[1:0], quadA}; always @(posedge clk) quadB_delayed <= {quadB_delayed[1:0], quadB}; wire count_enable = quadA_delayed[1] ^ quadA_delayed[2] ^ quadB_delayed[1] ^ quadB_delayed[2]; wire count_direction = quadA_delayed[1] ^ quadB_delayed[2]; reg [7:0] count; always @(posedge clk) begin if (rst) count <= 0; // сбросить счётчик на reset else if(count_enable) if(count_direction) count <= count + 1; else count <= count - 1; end endmodule |
И не забудьте подключить rst в testbench и подать импульс сброса (обычно в начале симуляции).
Вариант 2: Явная инициализация
В SystemVerilog (или если ваш симулятор Verilog поддерживает begin/end-инициализацию для reg):
reg [7:0] count = 0;
Поддержка такой инициализации может зависеть от симулятора (iverilog поддерживает, ModelSim — только в SystemVerilog).
Кратко:
- Значение x у регистра — нет инициализации (типичная причина).
- Добавьте сброс (reset) или инициализируйте регистр явно, чтобы избежать этого.
Дополнение
Для промышленных проектов ВСЕГДА реализуйте и используйте сброс (reset), особенно в FPGA и ASIC.
Пример с reset для вашего тестбенча:
| Verilog |
reg rst = 1; // Клок генератор... // Подключить .rst(rst) в инстанциацию quad initial begin rst = 1; #20; rst = 0; // далее тест как обычно end |
Без инициализации регуляр reg — всегда x до первого присваивания!
|
Исправь модуль quad так, что бы подсчет импульсов поворота энкодера происходил только один раз за счелчёк энкодера, только в момент фронта сигнала quadA |
Вот исправленный модуль, учитывающий импульс только по фронту (переходу 0→1) сигнала quadA. В этом случае анализируем только момент появления фронта quadA, и определяем направление через значение quadB (классическая схема для опроса энкодера):
| Verilog |
module quad( input clk, input quadA, input quadB, output reg [7:0] count ); reg quadA_delayed; // Фиксация значения quadA для обнаружения фронта always @(posedge clk) quadA_delayed <= quadA; // Обновление счетчика только на фронте quadA always @(posedge clk) begin if (!quadA_delayed && quadA) begin // обнаружен фронт 0 -> 1 if (quadB) count <= count - 1; // если quadB=1 во время fronta, то назад else count <= count + 1; // если quadB=0, то вперед end end endmodule |
Пояснения
- quadA_delayed — хранит предыдущее значение quadA на каждом такте.
- !quadA_delayed && quadA — истинно только на фронте 0→1 quadA.
- При каждом таком фронте, если в этот же момент quadB=0 — вращение вперёд, если quadB=1 — назад.
Этот подход гарантирует увеличение или уменьшение счетчика ровно один раз за реальный «щёлчёк» (импульс фронта) энкодера!
|
Похоже работает, но нужно добавить регистры reg [2:0] quadA_delayed, quadB_delayed; для защиты от метастабильности. |
Конечно, это хороший подход для синхронизации и подавления метастабильности!
Вот исправленный вариант вашего модуля, использующий трехтактовые регистры задержки для quadA и quadB — счет идет только по фронту (0→1) синхронизированного сигнала:
| Verilog88 |
module quad( input clk, input quadA, input quadB, output reg [7:0] count ); reg [2:0] quadA_delayed = 0; reg [2:0] quadB_delayed = 0; // Сдвигаемые регистры для подавления метастабильности always @(posedge clk) begin quadA_delayed <= {quadA_delayed[1:0], quadA}; quadB_delayed <= {quadB_delayed[1:0], quadB}; end // Счет только по фронту quadA (0→1) после синхронизации always @(posedge clk) begin if (!quadA_delayed[2] && quadA_delayed[1]) begin if (quadB_delayed[1]) count <= count - 1; else count <= count + 1; end end endmodule |
Пояснения:
- Метастабильные (async) сигналы quadA и quadB проходят через 3-регистровые цепочки синхронизации.
- Фронт определяется по срезу: прежнее значение (quadA_delayed[2]) было 0, новое (quadA_delayed[1]) — 1.
- Направление определяет quadB, синхронизированный этим же выходом регистра.
Такой модуль корректно считает импульсы только по фронту синхронизированного quadA!

Подробнее...