Мастерство пользовательских примитивов (UDP) в Verilog: продвинутое моделирование логики
Стандартные примитивы Verilog (nand, not) не всегда удобны или достаточны для сложной логики. Новые примитивные элементы — UDP (user-defined primitives) — позволяют моделировать комбинационную или последовательную логику.
У всех UDP ровно один выход (0, 1 или X, никогда Z — не поддерживается). Любой вход со значением Z трактуется как X.
Символы Verilog UDP
UDP определяются на том же уровне, что и module, но никогда между module и endmodule. Могут иметь много входных портов, но всегда один выходной, двунаправленные порты недопустимы. Все порты должны быть скалярными (1 бит).
Поведение описывается таблицей состояний примитива (primitive state table), перечисляющей комбинации входов и соответствующие выходы внутри table и endtable. Значения сигналов обозначаются символами:
| Символ | Комментарий |
|---|---|
| 0 | Логический 0 |
| 1 | Логический 1 |
| x | Неизвестно (0 или 1). Вход/выход или текущее состояние последовательных UDP |
| ? | 0, 1 или x. Не может быть выходом UDP |
| - | Без изменений, только в выходе UDP |
| ab | Переход из a в b (a,b ∈ {0,1,x}) |
| * | То же что ?? — любое изменение входного значения |
| r | То же что 01 — восходящий фронт на входе |
| f | То же что 10 — нисходящий фронт на входе |
| p | Потенциальный положительный фронт: 0→1, 0→x, x→1 |
| n | Потенциальный нисходящий фронт: 1→0, x→0, 1→x |
Пример комбинационного UDP: мультиплексор
// Выход всегда первый в списке портов
primitive mux (out, sel, a, b);
output out;
input sel, a, b;
table
// sel a b out
0 1 ? : 1;
0 0 ? : 0;
1 ? 0 : 0;
1 ? 1 : 1;
x 0 0 : 0;
x 1 1 : 1;
endtable
endprimitive
? означает, что сигнал может быть 0, 1 или x и не влияет на выход.
Тестбенч, инстанцирующий UDP и подающий стимулы:
module tb;
reg sel, a, b;
reg [2:0] dly;
wire out;
integer i;
// UDP нельзя инстанцировать с именованными портами
mux u_mux (out, sel, a, b);
initial begin
a <= 0; b <= 0;
$monitor("[T=%0t] a=%0b b=%0b sel=%0b out=%0b", $time, a, b, sel, out);
for (i = 0; i < 10; i = i + 1) begin
dly = $random; #(dly) a <= $random;
dly = $random; #(dly) b <= $random;
dly = $random; #(dly) sel <= $random;
end
end
endmodule
Simulation Log
xcelium> run [T=0] a=0 b=0 sel=x out=0 [T=4] a=1 b=0 sel=x out=x [T=5] a=1 b=1 sel=x out=1 [T=10] a=1 b=1 sel=1 out=1 [T=15] a=0 b=1 sel=1 out=1 [T=28] a=0 b=0 sel=1 out=0 [T=33] a=0 b=0 sel=0 out=0 [T=38] a=1 b=0 sel=0 out=1 [T=40] a=1 b=1 sel=0 out=1 [T=51] a=1 b=1 sel=1 out=1 [T=54] a=0 b=0 sel=1 out=0 [T=62] a=1 b=0 sel=1 out=0 [T=67] a=1 b=1 sel=1 out=1 [T=72] a=0 b=1 sel=1 out=1 [T=80] a=0 b=1 sel=0 out=0 [T=84] a=0 b=0 sel=0 out=0 [T=85] a=1 b=0 sel=0 out=1 xmsim: *W,RNQUIE: Simulation is complete.
Пример последовательного UDP
Последовательная логика может быть уровня-чувствительной или фронт-чувствительной — два вида последовательных UDP. Выходной порт должен быть объявлен как reg внутри UDP и может опционально инициализироваться в initial.
Последовательные UDP имеют дополнительное поле между входом и выходом, отделенное :, представляющее текущее состояние.
Уровнево-чувствительные UDP (Level-Sensitive UDPs)
primitive d_latch (q, clk, d);
output q;
input clk, d;
reg q;
table
// clk d q q+
1 1 : ? : 1;
1 0 : ? : 0;
0 ? : ? : -;
endtable
endprimitive
В таблице дефис - в последней строке означает отсутствие изменения q+.
module tb;
reg clk, d;
reg [1:0] dly;
wire q;
integer i;
d_latch u_latch (q, clk, d);
always #10 clk = ~clk;
initial begin
clk = 0;
$monitor ("[T=%0t] clk=%0b d=%0b q=%0b", $time, clk, d, q);
#10;
for (i = 0; i < 50; i = i+1) begin
dly = $random; #(dly) d <= $random;
end
#20 $finish;
end
endmodule
Simulation Log
xcelium> run [T=0] clk=0 d=x q=x [T=10] clk=1 d=1 q=1 [T=13] clk=1 d=0 q=0 [T=14] clk=1 d=1 q=1 [T=17] clk=1 d=0 q=0 [T=20] clk=0 d=1 q=0 [T=28] clk=0 d=0 q=0 [T=30] clk=1 d=1 q=1 [T=38] clk=1 d=0 q=0 [T=39] clk=1 d=1 q=1 [T=40] clk=0 d=1 q=1 [T=42] clk=0 d=0 q=1 [T=47] clk=0 d=1 q=1 [T=50] clk=1 d=0 q=0 [T=55] clk=1 d=1 q=1 [T=59] clk=1 d=0 q=0 [T=60] clk=0 d=0 q=0 [T=61] clk=0 d=1 q=0 [T=64] clk=0 d=0 q=0 [T=67] clk=0 d=1 q=0 [T=70] clk=1 d=0 q=0 [T=73] clk=1 d=1 q=1 [T=74] clk=1 d=0 q=0 [T=77] clk=1 d=1 q=1 [T=79] clk=1 d=0 q=0 [T=80] clk=0 d=0 q=0 [T=84] clk=0 d=1 q=0 [T=86] clk=0 d=0 q=0 [T=87] clk=0 d=1 q=0 [T=90] clk=1 d=1 q=1 [T=91] clk=1 d=0 q=0 [T=100] clk=0 d=0 q=0 [T=110] clk=1 d=0 q=0 Simulation complete via $finish(1) at time 111 NS + 0
Фронт-чувствительные UDP (Edge-Sensitive UDPs)
D-триггер моделируется как UDP. Обратите внимание: восходящий фронт clk задается 01 или 0?.
primitive d_flop (q, clk, d);
output q;
input clk, d;
reg q;
table
// clk d q q+
// выход на восходящем фронте clk (01)
0 : ? : 0;
(01) 1 : ? : 1;
(0?) 1 : 1 : 1;
(0?) 0 : 0 : 0;
// игнорируем нисходящий фронт clk
(?0) ? : ? : -;
// игнорируем изменения данных на стабильном clk
? (??) : ? : -;
endtable
endprimitive
В тестбенче UDP инстанцируется и управляется случайными значениями d после случайного числа тактов.
module tb;
reg clk, d;
reg [1:0] dly;
wire q;
integer i;
d_flop u_flop (q, clk, d);
always #10 clk = ~clk;
initial begin
clk = 0;
$monitor ("[T=%0t] clk=%0b d=%0b q=%0b", $time, clk, d, q);
#10;
for (i = 0; i < 20; i = i+1) begin
dly = $random;
repeat(dly) @(posedge clk);
d <= $random;
end
#20 $finish;
end
endmodule
Видно, что выход q следует за входом d с задержкой 1 такт — желаемое поведение D-триггера.
Simulation Log
xcelium> run [T=0] clk=0 d=x q=x [T=10] clk=1 d=1 q=x [T=20] clk=0 d=1 q=x [T=30] clk=1 d=1 q=1 [T=40] clk=0 d=1 q=1 [T=50] clk=1 d=1 q=1 [T=60] clk=0 d=1 q=1 [T=70] clk=1 d=0 q=1 [T=80] clk=0 d=0 q=1 [T=90] clk=1 d=1 q=0 [T=100] clk=0 d=1 q=0 [T=110] clk=1 d=1 q=1 [T=120] clk=0 d=1 q=1 [T=130] clk=1 d=1 q=1 [T=140] clk=0 d=1 q=1 [T=150] clk=1 d=0 q=1 [T=160] clk=0 d=0 q=1 [T=170] clk=1 d=0 q=0 [T=180] clk=0 d=0 q=0 [T=190] clk=1 d=0 q=0 [T=200] clk=0 d=0 q=0 [T=210] clk=1 d=1 q=0 [T=220] clk=0 d=1 q=0 [T=230] clk=1 d=1 q=1 [T=240] clk=0 d=1 q=1 [T=250] clk=1 d=1 q=1 [T=260] clk=0 d=1 q=1 [T=270] clk=1 d=1 q=1 [T=280] clk=0 d=1 q=1 [T=290] clk=1 d=1 q=1 [T=300] clk=0 d=1 q=1 [T=310] clk=1 d=1 q=1 [T=320] clk=0 d=1 q=1 [T=330] clk=1 d=1 q=1 [T=340] clk=0 d=1 q=1 [T=350] clk=1 d=1 q=1 [T=360] clk=0 d=1 q=1 [T=370] clk=1 d=0 q=1 [T=380] clk=0 d=0 q=1 [T=390] clk=1 d=0 q=0 [T=400] clk=0 d=0 q=0 [T=410] clk=1 d=1 q=0 [T=420] clk=0 d=1 q=0 [T=430] clk=1 d=1 q=1 [T=440] clk=0 d=1 q=1 [T=450] clk=1 d=1 q=1 [T=460] clk=0 d=1 q=1 [T=470] clk=1 d=1 q=1 [T=480] clk=0 d=1 q=1 Simulation complete via $finish(1) at time 490 NS + 0
Verilog
- Освоение Verilog: полное руководство по проектированию цифровых схем
- Освоение конкатенации в Verilog: практическое руководство
- Понимание назначений в Verilog: типы, синтаксис и лучшие практики
- Блокирующие и неблокирующие назначения в Verilog: ключевые различия
- Мастерство функций в Verilog для эффективного RTL дизайна
- Понимание задач (Tasks) в Verilog: отличия от функций, синтаксис и применение
- Генератор тактового сигнала в Verilog: проектирование и реализация
- Мастерство математических функций Verilog для эффективного аппаратного дизайна
- Мастерство $timeformat в Verilog: точность и контроль отображения времени
- Понимание и печать timescale в Verilog в любой области видимости