Menu

Continuous Assignment em Verilog: A declaração assign

Como assign funciona - a relação sempre-verdadeira que descreve, o que pode e o que não pode conduzir, e os padrões em que brilha comparado a código procedural.

Esta página tem editores executáveis - edite, execute e veja a saída na hora.

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:

  • y existe como um wire no circuito.
  • y é em todos os momentos o AND bit a bit de a e b. Mude qualquer entrada e y segue.

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ções case multi-way, if/else if aninhados, qualquer coisa que se beneficia de regs 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. assign descreve relações combinacionais; não pode introduzir um flip-flop. Se você precisa que um valor seja lembrado entre ciclos de clock, isso é um bloco always @(posedge clk).
  • Lógica procedural multi-passo. Você não pode escrever if/else ou case dentro de um assign. O mais próximo que chega é ?: encadeado, que fica feio depois de três ramos.
  • Conduzir registers de dentro de um bloco procedural. Alvos reg precisam de atribuição procedural, não assign.

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.

Coddy programming languages illustration

Aprenda a programar com o Coddy

COMEÇAR