Сводка
В данной статье мы рассмотрим примеры кода на языке Verilog с отчетами о компиляции и RTL. Всего мы представим 10 лучших примеров, начиная от простых операций с логическими элементами и заканчивая моделированием микропроцессоров и цифровых фильтров. Каждый пример сопровождается отчетом о его успешной компиляции, что позволяет убедиться в корректности представленного кода. Мы охватили широкий спектр тем для того, чтобы помочь разработчикам глубже понять синтаксис и семантику из документации Verilog, а также улучшить свои навыки в области моделирования электронных схем.
Пример 1. Двухпортовая Однотактовая Память RAM
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
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.

Рисунок 2 — RTL примера модуля FIFO.
Пример 3. Интегратор
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.

Пример 4. Дифференциатор
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.

Пример 5. Фазовый накопитель
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.

Пример 6. КИХ-фильтр 5 порядка
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.

Пример 7. LUT 6-ти входная
`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.

Пример 8. Полный 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.

Пример 9. 16-входной мультиплексор
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.

Пример 10. D-триггер
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.
