~ cd 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.
Пример 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;
endmoduleRTL FIFO показано на рисунке 2.

Рисунок 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;
endmoduleRTL интегратора состоит из триггера, сумматора и умножителя и показан на рисунке 3.
Пример 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
endmoduleRTL Дифференциатора состоит из триггера и разнатора и показан на рисунке 4.
Пример 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
endmoduleRTL Фазового накопителя состоит из триггера и сумматора и показан на рисунке 5.
Пример 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
endmoduleRTL КИХ-фильтр 5 порядка состоит из 10 триггеров(5 памяти константных чисел и 5 коэф. фильтра), 5 сумматоров и 5 умножителей и показан на рисунке 6.
Пример 7. LUT 6-ти входная
Пример 7 — LUT — это таблица, которая выдает заранее определенные значения. 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;
endmoduleRTL LUT 6-ти входная состоит из 2 конъюнкций, дизъюнкции и суммы по модуля 2 и показан на рисунке 7.
Пример 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;
endmoduleRTL Полного шестнадцатибитного сумматора состоит из 2-х примитивов сложения и показан на рисунке 8.
Пример 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];
endmoduleRTL 16-входного мультиплексора на рисунке 9.
Пример 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
endmoduleRTL 8-ми битного D-триггера показано на рисунке 10.