10 Лучших примеров кода Verilog

Содержание

Сводка

В данной статье мы рассмотрим примеры кода на языке Verilog с отчетами о компиляции и RTL. Всего мы представим 10 лучших примеров, начиная от простых операций с логическими элементами и заканчивая моделированием микропроцессоров и цифровых фильтров. Каждый пример сопровождается отчетом о его успешной компиляции, что позволяет убедиться в корректности представленного кода. Мы охватили широкий спектр тем для того, чтобы помочь разработчикам глубже понять синтаксис и семантику из документации Verilog, а также улучшить свои навыки в области моделирования электронных схем.

Пример 1. Двухпортовая Однотактовая Память RAM

Пример 1 — Объявления модуля памяти с использованием памяти ПЛИС c помощью Verilog(см. массивы и память). Этот модуль не использует регистры, а напрямую обращается к IP-блоку памяти. Здесь имеется в виду ресурс, заложенный в микрочип, отделенный от логических ячеек.
module TWISE_PORTS_RAM(data, addr_we, addr_re, we, clk, q); parameter BITS_DATA = 8; // кол-во битов в линии данных parameter BITS_ADDR = 6; // кол-во битов в линии адреса input [(BITS_DATA - 1): 0] data; // линия данных input [(BITS_ADDR - 1): 0] addr_we; // линия адреса для записи input [(BITS_ADDR - 1): 0] addr_re; // линия адреса для чтения input we; // линия включения записи input clk; // тактовый сигнал output reg [(BITS_DATA - 1): 0] q; // линия выхода // регистр, использующий примитив FPGA reg [(BITS_DATA - 1): 0] ram [((2**BITS_ADDR) - 1): 0]; always @(posedge clk) begin // запись if (we) ram[addr_we] <= data; // чтение q <= ram[addr_re]; end endmodule

При приходе восходящего фронта в тактовом сигнале clk и высоком сигнале записи write, происходит запись в память по адресу addr_we. При одинаковой ситуации тактовым сигналом происходит чтение с памяти по адресу addr_re. RTL модель показана на рисунке 1.

RTL однотакотовой двухпортовой памяти и отчет о компиляции
Рисунок 1 — RTL однотакотовой двухпортовой памяти.

Пример 2. FIFO

Пример 2 — Данный FIFO написан для заполнения слова буфера по битно. Модуль использует регистровую память на логических клетках. Вставьте экземпляр памяти из примера 1, чтоб использовать ресурсы(память) чипа. Объяснение по портам и параметрам не требуется, потому что код приведен с комментариями. Более подробно описано в статье про FIFO. Процедурный блок always «реагирует» на восходящий фронт тактового сигнала clk и асинхронного сигнала reset. Когда появляется восходящего фронта сигнала reset, сбрасывается все управляющие регистры FIFO и выставляется на логическую 1 сигнал пустоты буфера empty. Если буфер полон, выставляется сигнал full. Когда происходит переход тактового сигнала к логической 1, при высоком сигнале write_enable и не полном буфере осуществляется запись в буфер и увеличение счетчика и указателя. Если clk восходящий фронт, при высоком сигнале read_enable и при не полном буфере читаются данные из буфера, уменьшается счетчика и увеличивается указатель. Для заполнения буфера FIFO по другой величине битов, необходимо увеличить длину битов линии data_in, data_out на одинаковое кол-во и переделать счетчик и указателей write_pointer и read_pointer.
module fifo ( input wire clk, // тактовый сигнал input wire reset, // сигнал сброса input wire write_enable, // сигнал разрешения записи input wire read_enable, // сигнал разрешения чтения input wire data_in, // входные данные output reg data_out, // выходные данные output wire empty, // сигнал пустого буфера output wire full // сигнал заполненного буфера ); // Параметры FIFO parameter FIFO_DEPTH = 8; // глубина FIFO // Внутренние сигналы и регистры reg [7:0] fifo[FIFO_DEPTH-1:0]; // регистры FIFO reg [3:0] write_pointer; // указатель записи reg [3:0] read_pointer; // указатель чтения reg [3:0] count; // счетчик элементов reg empty_reg, full_reg; // регистры состояния // Логика FIFO always @(posedge clk or posedge reset) begin if (reset) begin // Сброс FIFO write_pointer <= 0; read_pointer <= 0; count <= 0; empty_reg <= 1; full_reg <= 0; end else begin // Запись данных if (write_enable && !full_reg) begin fifo[write_pointer] <= data_in; write_pointer <= write_pointer + 1; count <= count + 1; empty_reg <= 0; if (count == FIFO_DEPTH - 1) full_reg <= 1; end // Чтение данных if (read_enable && !empty_reg) begin data_out <= fifo[read_pointer]; read_pointer <= read_pointer + 1; count <= count - 1; full_reg <= 0; if (count == 1'h0) empty_reg <= 1; end end end // Выходные сигналы состояния FIFO assign empty = empty_reg; assign full = full_reg; endmodule

RTL FIFO показано на рисунке 2.

RTL примера модуля FIFO и отчет о компиляции

Рисунок 2 — RTL примера модуля FIFO.

Пример 3. Интегратор

Пример 3Интегратор — это устройство или элемент, который выполняет функцию интегрирования. Данный интегратор реализован с выбором коэффициента дискретизации или без его наличия. Точечная нотация фиксированной точки равна 1.15. Именно, на такой нотации работает сигнальные процессоры. Код создавался и тестировался Quartus9.1, поэтому была необходимость прописывать умножение отдельным модулем. Иным образом программа искажала generate RTL(см generate).
module integr(data, clk, reset, out); // 1 - включение коэф дискретизации, 0 - отключение parameter KOEF_ENA = 1; // мой пример... Частота дискретизации 500 КГц. 1/500 = 0.002 // В данном случае точечная нотация с фиксированой точкой 1.15 // следовательно. 0.002*(2**16) = 65.536 => PERIOD = 65 parameter [15: 0] PERIOD = 82; parameter DATA_WIDTH = 16; input [(DATA_WIDTH - 1): 0] data; input reset, clk; output [(DATA_WIDTH - 1): 0] out; wire [(DATA_WIDTH*2 - 1): 0] koef_after; wire [(DATA_WIDTH - 1): 0] add1; reg [(DATA_WIDTH - 1): 0] z1 = 0; generate if (KOEF_ENA) begin unsigned_multiply #(DATA_WIDTH - 1) mult1(PERIOD, data, koef_after); end else begin assign koef_after = {data, 15'h00}; end endgenerate assign add1 = (koef_after >> 15) + z1; assign out = add1; always @(posedge clk, posedge reset) begin if (reset) begin z1 <= 0; end else begin z1 <= add1; end end endmodule module unsigned_multiply #(parameter WIDTH=8) ( input [WIDTH-1:0] dataa, input [WIDTH-1:0] datab, output [2*WIDTH-1:0] dataout ); assign dataout = dataa * datab; endmodule

RTL интегратора состоит из триггера, сумматора и умножителя и показан на рисунке 3.

RTL примера модуля интегратора и отчет о компиляции
Рисунок 3 — RTL примера модуля интегратора.

Пример 4. Дифференциатор

Пример 4Дифференциатор — это устройство, которое позволяет получить производную сигнала. Дифференциатор представляет собой нерекурсивный фильтр 1 порядка с коэффициентом -1.
module diff(data, clk, reset, out); parameter DATA_WIDTH = 16; // Битовая длина линии данных input signed [(DATA_WIDTH - 1): 0] data; // Линия данных input reset, clk; // асихронный сброс и тактовый сигнал output signed [(DATA_WIDTH - 1): 0] out; // линия выхода // z(-1) запоминает входной сигнал reg [(DATA_WIDTH - 1): 0] z1 = 0; // вычетание входных даных и предыдуших данных assign out = data - z1; always @(posedge clk, posedge reset) begin if (reset) begin z1 <= 0; end else begin z1 <= data; end end endmodule

RTL Дифференциатора состоит из триггера и разнатора и показан на рисунке 4.

RTL примера модуля дифференциатора и отчет о компиляция
Рисунок 4 — RTL примера модуля дифференциатора.

Пример 5. Фазовый накопитель

Пример 5Фазовый накопитель — это БИХ-фильтр(рекурсивный фильтр) 1 порядка. Фазовый накопитель применяется во многих схема, но чаще его используется в схеме NCO генератора, управляемый числовой последовательность. Входной 16 битный сигнал складывается с предыдущим значением сумматора. Если значение сумматора выше заложенных битов, регистр запоминает лишь первые 16 бит(см. регистры).
module FN(out, sig, clk, reset); parameter DATA_WIDTH = 16; input signed [(DATA_WIDTH - 1): 0] sig; // Линия данных input reset, clk; // асихронный сброс и тактовый сигнал output signed [(DATA_WIDTH - 1): 0] out; // линия выхода wire [(DATA_WIDTH - 1): 0] add1; //Линия сумматора reg signed [(DATA_WIDTH - 1): 0] z1 = 0; // буфер // суммированые входного и предидущего выходного сигнала assign add1 = sig + z1; assign out = add1; always @(posedge clk, posedge reset) begin if (reset) begin z1 <= 0; end else begin z1 <= add1; end end endmodule

RTL Фазового накопителя состоит из триггера и сумматора и показан на рисунке 5.

RTL примера модуля Фазового накопителя и отчет о компиляции
Рисунок 5 — RTL примера модуля Фазового накопителя.

Пример 6. КИХ-фильтр 5 порядка

Пример 6КИХ-фильтр 5 порядка(FIR) — нерекурсивный фильтр 5 порядка. КИХ-фильтры используют для фильтрации данных. С помощью КИХ-фильтров строят ФНЧ, ФВЧ, режекторный и полосовый фильтры. Данный код использует нотацию для фиксированной запятой 1.15.
module fir(data, clk, reset, out); parameter DN = 5; // порядок фильтра parameter K0 = 655; // коэф равный 0,02 parameter K1 = 16_384; // коэф равный 0,5 parameter K2 = 21_299; // коэф равный 0,65 parameter K3 = 36; // коэф равный 0,0011 parameter K4 = 3276; // коэф равный 0,1 parameter DATA_WIDTH = 16; // Битовая длина линии данных input signed [(DATA_WIDTH - 1): 0] data; // линия данных input reset, clk; // асихронный сброс и тактовый сигнал output signed [(DATA_WIDTH - 1): 0] out; // выходной сигнал reg [(DATA_WIDTH - 1): 0] z [(DN - 1): 0]; // память филтра wire [(DATA_WIDTH*2 - 1): 0] k_w [(DN - 1): 0]; // Задание начальных значений памяти фильтра initial begin z[0] = 0; z[1] = 0; z[2] = 0; z[3] = 0; z[4] = 0; end // перемножение z^(-1) регистра на коэф assign k_w[0] = z[0] * K0; assign k_w[1] = z[1] * K1; assign k_w[2] = z[2] * K2; assign k_w[3] = z[3] * K3; assign k_w[4] = z[4] * K4; assign out = (k_w[0] + k_w[1] + k_w[2] + k_w[3] + k_w[4]) >> (DATA_WIDTH - 1); always @(posedge clk, posedge reset) begin if (reset) begin // очистка памяти z[0] <= 0; z[1] <= 0; z[2] <= 0; z[3] <= 0; z[4] <= 0; end else begin z[0] <= data; z[1] <= z[0]; z[2] <= z[1]; z[3] <= z[2]; z[4] <= z[3]; end end endmodule

RTL КИХ-фильтр 5 порядка состоит из 10 триггеров(5 памяти константных чисел и 5 коэф. фильтра), 5 сумматоров и 5 умножителей и показан на рисунке 6.

RTL примера модуля КИХ-фильтр 5 порядка и отчет о компиляции
Рисунок 6 — RTL примера модуля КИХ-фильтр 5 порядка.

Пример 7. LUT 6-ти входная

Пример 7LUT — это таблица, которая выдает заранее определенные значения. LUT бывает 2-х типов: функциональная и хеш-таблица. В данном случае применяется функциональная LUT. Функциональная LUT — это комбинационная схема с определенным кол-во входов. Хорошим примером является LUT логической клетки семейства FPGA Cyclone. В примере используется директива `default_nettype, которая задает объявление по умолчанию не объявленных цепей.
`default_nettype wire module LUT6(out, sig0, sig1, sig2, sig3, sig4, sig5); input sig0, sig1, sig2, sig3, sig4, sig5; output out; assign w1 = (sig1 & sig3) | sig0; assign w2 = w1 & sig4 & (sig5 ^ sig2); assign out = w2; endmodule

RTL LUT 6-ти входная состоит из 2 конъюнкций, дизъюнкции и суммы по модуля 2 и показан на рисунке 7.

RTL примера модуля LUT 6-ти входная и отчет о компиляции
Рисунок 7 — RTL примера модуля LUT 6-ти входная.

Пример 8. Полный N-битный сумматор

Пример 8Полный сумматор — это логический элемент, который суммирования числа с переносом и осуществляет перенос. N-битный сумматор — это сумматор с длинной линиями данных равной N. В этом примере шестнадцатибитный.
module sum(out, cout, dataa, datab, cin); parameter DATA_WIDTH = 16; // битовая длина input [(DATA_WIDTH - 1): 0] dataa, datab; // линии данных input cin; // входной перенос output [(DATA_WIDTH - 1): 0] out; // результат суммирования output cout; // выходной перенос assign {cout, out} = dataa + datab + cin; endmodule

RTL Полного шестнадцатибитного сумматора состоит из 2-х примитивов сложения и показан на рисунке 8.

Полный N-битный сумматор и отчет о компиляции
Рисунок 8 — RTL примера модуля полного сумматора.

Пример 9. 16-входной мультиплексор

Пример 9Мультиплексор — это логический элемент, который осуществляет выборку сигналов из всех входных информационных сигналов через адресные управляющие входы.
module mux16 #( parameter DATA_WIDTH = 16 // битовая длина ) ( output [(DATA_WIDTH - 1): 0] out, // результат // линии данных input [(DATA_WIDTH - 1): 0] sig0, sig1, sig2, sig3, input [(DATA_WIDTH - 1): 0] sig4, sig5, sig6, sig7, input [(DATA_WIDTH - 1): 0] sig8, sig9, sig10, sig11, input [(DATA_WIDTH - 1): 0] sig12, sig13, sig14, sig15, input [3:0] addr // линия адреса ); wire [(DATA_WIDTH - 1): 0] con_mux [15: 0]; assign con_mux[0] = (0 == addr)? sig0: 0, con_mux[1] = (1 == addr)? sig1: 0, con_mux[2] = (2 == addr)? sig2: 0, con_mux[3] = (3 == addr)? sig3: 0, con_mux[4] = (4 == addr)? sig4: 0, con_mux[5] = (5 == addr)? sig5: 0, con_mux[6] = (6 == addr)? sig6: 0, con_mux[7] = (7 == addr)? sig7: 0, con_mux[8] = (8 == addr)? sig8: 0, con_mux[9] = (9 == addr)? sig9: 0, con_mux[10] = (10 == addr)? sig10: 0, con_mux[11] = (11 == addr)? sig11: 0, con_mux[12] = (12 == addr)? sig12: 0, con_mux[13] = (13 == addr)? sig13: 0, con_mux[14] = (14 == addr)? sig14: 0, con_mux[15] = (15 == addr)? sig15: 0; assign out = con_mux[0] | con_mux[1] | con_mux[2] | con_mux[3] | con_mux[4] | con_mux[5] | con_mux[6] | con_mux[7] | con_mux[8] | con_mux[9] | con_mux[10] | con_mux[11] | con_mux[12] | con_mux[13] | con_mux[14] | con_mux[15]; endmodule

RTL 16-входного мультиплексора на рисунке 9.

16-входной мультиплексор и отчет о компиляции
Рисунок 9 — RTL примера модуля 16-входного мультиплексора.

Пример 10. D-триггер

Пример 10 — Реализован 8-ми битный D-триггер с сигналом предустановки preload, синхронным сбросом reset и включения ena.
module triggers (data, data_load, clk, ena, sreset, preload, q); // длина информационных сигналов parameter DATA_WIDTH = 8; // информационный сигнал input [(DATA_WIDTH - 1): 0] data, data_load; // тактовый сигнал, сигнал включения, сброса и предустанвоки input clk, ena, sreset, preload; // выход output [(DATA_WIDTH - 1): 0] q; // объявление его регистровым reg [(DATA_WIDTH - 1): 0] q; always @(posedge clk) begin if (sreset) begin q <= 0; end else begin if (preload) begin q <= data_load; end else begin q <= data; end end end endmodule

RTL 8-ми битного D-триггера показано на рисунке 10.

RTL D-триггер и отчет о компиляции
Рисунок 10- RTL примера модуля 8-ми битного D-триггера.