Dominando Primitivas Definidas pelo Usuário (UDP) em Verilog: Modelagem Avançada de Lógica
Primitivas padrão Verilog como nand e not nem sempre são fáceis ou suficientes para representar lógica complexa. Novos elementos primitivos chamados UDP (User-Defined Primitives) ou primitivas definidas pelo usuário podem ser definidas para modelar lógica combinacional ou sequencial.
Todas UDPs têm exatamente uma saída que pode ser 0, 1 ou X (nunca Z, não suportado). Qualquer entrada com valor Z será tratada como X.
Símbolos Verilog UDP
UDPs podem ser escritas no mesmo nível que definições de module, mas nunca entre module e endmodule. Podem ter muitas portas de entrada mas sempre uma porta de saída, portas bidirecionais não válidas. Todos sinais de porta devem ser escalares (1-bit).
Comportamento de hardware descrito como tabela de estados primitiva listando combinações possíveis de entradas e saídas correspondentes dentro de table e endtable. Valores de sinais de entrada/saída indicados pelos símbolos:
| Símbolo | Comentários |
|---|---|
| 0 | Lógica 0 |
| 1 | Lógica 1 |
| x | Desconhecido, pode ser 0 ou 1. Usado como entrada/saída ou estado atual de UDPs sequenciais |
| ? | Lógica 0, 1 ou x. Não pode ser saída de UDP |
| - | Sem mudança, só permitido em saída de UDP |
| ab | Mudança de valor de a para b (a ou b = 0, 1, ou x) |
| * | Mesmo que ??, indica qualquer mudança no valor de entrada |
| r | Mesmo que 01 -> borda de subida na entrada |
| f | Mesmo que 10 -> borda de descida na entrada |
| p | Borda de subida potencial: 0->1, 0->x, ou x->1 |
| n | Borda de descida potencial: 1->0, x->0, 1->x |
Exemplo UDP Combinacional
// Saída sempre primeiro sinal na lista de portas
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
O ? indica que o sinal pode ser 0, 1 ou x e não importa para decidir a saída final.
Testbench instanciando o UDP:
module tb;
reg sel, a, b;
reg [2:0] dly;
wire out;
integer i;
// Instancia UDP - UDPs não podem ser instanciados com conexão por nome de porta
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);
// Aciona a, b, sel após atrasos aleatórios diferentes
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
Log de Simulação
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.
Exemplo UDP Sequencial
Lógica sequencial pode ser sensível a nível ou borda, havendo dois tipos de UDPs sequenciais. Porta de saída deve ser declarada como reg na definição do UDP e pode ser inicializada opcionalmente em initial.
UDPs sequenciais têm campo adicional entre entrada e saída delimitado por : representando o estado atual.
UDPs Sensíveis a Nível (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
Na tabela acima, hífen - na última linha indica sem mudança no valor de 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; // Para ver efeito de X
for (i = 0; i < 50; i = i+1) begin
dly = $random; #(dly) d <= $random;
end
#20 $finish;
end
endmodule
Log de Simulação
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
UDPs Sensíveis a Borda (Edge-Sensitive UDPs)
Um D flip-flop modelado como UDP Verilog. Borda de subida do clock especificada por 01 ou 0?.
primitive d_flop (q, clk, d);
output q;
input clk, d;
reg q;
table
// clk d q q+
// obtém saída na borda de subida do clk (01)
0 : ? : 0;
(01) 1 : ? : 1;
(0?) 1 : 1 : 1;
(0?) 0 : 0 : 0;
// ignora borda de descida do clk
(?0) ? : ? : -;
// ignora mudanças de dados em clk estável
? (??) : ? : -;
endtable
endprimitive
No testbench, UDP instanciado e acionado com valores aleatórios de d após número aleatório de clocks.
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; // Para ver efeito de X
for (i = 0; i < 20; i = i+1) begin
dly = $random; repeat(dly) @(posedge clk); d <= $random;
end
#20 $finish;
end
endmodule
Pode-se ver na imagem que saída q segue entrada d após 1 clock de atraso, comportamento desejado para D flip-flop.
Log de Simulação
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
- Domine o Verilog: Tutorial Completo para Projeto de Circuitos Digitais
- Dominando Concatenação Verilog: Guia Prático
- Entendendo Atribuições em Verilog: Tipos, Sintaxe e Boas Práticas
- Verilog Blocking vs Non-Blocking: Diferenças Essenciais Explicadas
- Dominando Funções em Verilog para Design RTL Eficiente
- Entendendo Tasks em Verilog: Funções vs Tasks, Sintaxe e Uso
- Gerador de Clock Verilog: Guia de Design e Implementação
- Dominando Funções Matemáticas do Verilog para Design de Hardware Eficiente
- Dominando $timeformat do Verilog: Precisão e Controle de Exibição
- Entendendo e Imprimindo Timescales do Verilog em Qualquer Escopo