assign descreve uma verdade permanente
Uma declaração wire cria um sinal. Um assign descreve o que o conduz. A relação é contínua: o que estiver no lado direito, o lado esquerdo equivale a ele, em cada ponto do tempo simulado:
wire y;
assign y = a & b;
Duas coisas implícitas nesse par de linhas:
yexiste como um wire no circuito.yé em todos os momentos o AND bit a bit deaeb. Mude qualquer entrada eysegue.
Não há clock e nem evento disparando a atualização. O simulador vê a ou b mudar, marca y como sujo e reavalia a expressão. Em hardware isso mapeia para algumas portas AND: combinacional, sem estado, instantâneo.
A forma implícita
Você pode combinar a declaração e a atribuição em uma linha:
wire y = a & b;
Isso é o mesmo que as duas linhas acima. Útil para wires inline com os quais ninguém fora deste escopo se importa. Muitos guias de estilo na verdade preferem a forma implícita porque coloca a declaração logo ao lado da equação.
O que assign pode conduzir
O alvo de assign precisa ser um tipo net - em Verilog puro, isso é wire (ou um dos primos mais raros como tri, wand, wor). Não pode ser um reg. Se você acidentalmente declarar seu alvo como reg:
reg y;
assign y = a & b; // ERRO: não pode conduzir reg com assign
O compilador vai te avisar. Ou troque o alvo para wire, ou mova a lógica para um bloco always @(*) onde reg é o alvo legal.
O lado direito pode ser qualquer coisa que avalie para um valor: literais, sinais, parameters, expressões de operadores, chamadas de função. Pode misturar várias larguras de entrada; as regras padrão de alargamento aplicam.
Quando usar assign vs always
Ambos podem produzir lógica combinacional. A escolha é principalmente sobre como o código se lê:
assigné melhor quando a relação é uma única expressão. Somadores, muxes simples construídos com?:, máscaras, bits de paridade, qualquer coisa que você consegue escrever em uma linha.always @(*)é melhor quando você precisa de declarações procedurais. Declaraçõescasemulti-way,if/else ifaninhados, qualquer coisa que se beneficia deregs intermediários nomeados. Cobrimos isso em Always Block.
Aqui está o mesmo mux 4-para-1 escrito das duas formas:
Ambos os modules sintetizam essencialmente para o mesmo multiplexador. A versão assign é uma linha de código; a versão always tem seis. Para quatro casos isso é próximo; para dezesseis casos o bloco case é claramente mais fácil de ler.
Padrões comuns
Lógica combinacional simples
assign sum = a + b;
assign carry = a[7] & b[7];
assign equal = (data == 8'hFF);
Uma expressão, um wire. O pão com manteiga do assign.
Um mux 2-para-1
assign out = sel ? a : b;
Expressão condicional única - o sintetizador a transforma em um único mux 2-para-1. A grafia mais limpa possível de "selecione entre a e b".
Empacotamento de bits
assign status = {error, overflow, ready, busy, 4'b0};
Concatenação no lado direito de um assign é como você empacota flags em um byte de status. O resultado é calculado e conduzido continuamente.
Saída tri-state
assign data_pin = output_enable ? data_out : 1'bz;
Quando output_enable é alto, aciona o pino. Quando baixo, libera para alta impedância. Esse é o padrão canônico em pinos de chip onde múltiplos drivers podem compartilhar um wire.
Concorrência: múltiplos assigns não são sequenciais
Um lembrete que não vai parar de ser relevante: múltiplas declarações assign no mesmo module todas rodam em paralelo. Não são uma sequência:
assign y = a & b; // existe em todos os momentos
assign z = a | b; // também existe em todos os momentos, independentemente
A ordem no arquivo é irrelevante. As duas equações são simultaneamente verdadeiras. O sintetizador pode dispor a porta AND à esquerda da porta OR ou à direita; não importa, ambas as portas disparam continuamente.
Se você quer comportamento que pareça sequencial, está recorrendo a um bloco always (e provavelmente a um clock). Esse é outro capítulo.
Múltiplos drivers: o padrão de barramento
Um wire pode ter mais de um assign mirando nele, mas você quase nunca quer isso exceto para barramentos tri-state. Dois drivers brigando por um wire produzem comportamento indefinido:
assign y = a;
assign y = b; // RUIM - dois drivers, simulador escolhe um ou marca como x
O padrão legítimo: cada driver libera para z quando inativo, e no máximo um está ativo a qualquer momento.
assign bus = device_a_active ? data_from_a : 1'bz;
assign bus = device_b_active ? data_from_b : 1'bz;
Isso funciona porque a qualquer momento dado, no máximo um dos dois ternários produz um valor não-z. O valor real do wire é qualquer driver que não esteja liberando.
Em lógica interna - em qualquer lugar que não seja um pino de chip ou um barramento on-chip compartilhado - um driver por wire. Bugs de múltiplos drivers são feios de debugar.
O que assign não pode fazer
Algumas coisas para as quais assign é a ferramenta errada:
- Armazenamento.
assigndescreve relações combinacionais; não pode introduzir um flip-flop. Se você precisa que um valor seja lembrado entre ciclos de clock, isso é um blocoalways @(posedge clk). - Lógica procedural multi-passo. Você não pode escrever
if/elseoucasedentro de umassign. O mais próximo que chega é?:encadeado, que fica feio depois de três ramos. - Conduzir registers de dentro de um bloco procedural. Alvos
regprecisam de atribuição procedural, nãoassign.
Saber os limites é como você decide quando trocar para always.
O que vem a seguir
Você agora viu o lado estrutural completo do Verilog: declarando modules, instanciando-os e conectando lógica combinacional com assign. O próximo capítulo entra em blocos procedurais - as construções initial e always onde tempo e ordenação começam a importar.
Perguntas frequentes
O que é uma continuous assignment em Verilog?
assign target = expression; declara uma relação permanente e contínua: target sempre equivale a expression. Sempre que algum sinal na expressão muda, o simulador reavalia o lado direito e atualiza target. Não há clock, não há evento - a relação é verdadeira em cada ponto no tempo.
O que posso mirar com um assign em Verilog?
assign pode conduzir um wire, mas nunca um reg. O alvo precisa ser um tipo de net. Se você precisa atribuir a algo dentro de um bloco always, declare-o como reg. O compilador vai rejeitar assign x = ... se x é reg, e rejeitar x = ... dentro de um always se x é wire.
Quando devo usar assign vs um bloco always?
Use assign para lógica combinacional simples - uma expressão de entrada, um sinal de saída, sem necessidade de if/else. Use always @(*) quando a lógica precisar de declarações procedurais (um case, uma cadeia if/else if, um loop for). Ambos produzem hardware combinacional; a escolha é sobre legibilidade.
Posso ter múltiplos assigns para o mesmo wire em Verilog?
Só se você estiver modelando um barramento tri-state onde cada driver libera o wire para z quando inativo. Dois assigns tentando conduzir o wire para valores definidos ao mesmo tempo produzem contenção - o simulador pode escolher um, pode marcar o sinal como X, depende da ferramenta. Para lógica combinacional comum, um driver por wire.