Neste trabalho não se descreve nem perto de toda linguagem Verilog, para isso remete-se o leitor para a documentação. O objectivo é dar ao leitor umas noções básicas de Verilog. O suficiente para criar alguns módulos básicos e procurar a restante informação ao nível das necessidades.

 

            Começamos a nossa incursão em Verilog com um programinha simples. Pelas instruções de atribuição podemos perceber que a linguagem é muito parecida com C. Os comentários têm um sabor de C++, e. g. são delimitados por “//” par uma linha, ou por “/*” e “*/” para diversas linhas. A linguagem Verilog descreve um sistema digital como um conjunto de módulos, mas para este exemplo teremos um único módulo chamado “simple”.

 

//A first digital model in Verilog

 

module simple;

//Exemplo simples do nível de transferência do registo (RTL) em Verilog.

//O registo A é incrementado por um. Depois os primeiros quatro bits de B são

//alterados para a negação do últimos quatro bits de A.

//C é a redução AND dos últimos dois bits de A.

// declaração de registos e flip-flops

reg [0:7] A, B;

reg C;

 

//Os dois "initials" e "always" executam concurrencialmente

initial begin: stop_at

//Para a execução depois de 20 unidades de tempo.

#20; $stop;

end

 

initial begin: Init

//Inicializa o registo A, outros valores ficam como x

A = 0;

 

//Cabeçalho

$display("Time A B C");

 

//Exibe a mensagem, de cada vez que A, B ou C mudam

$monitor(" %0d %b %b %b", $time, A, B, C);

end

 

//main_process repete até a simulação acabar

always begin: main_process

 

// #1 significa esperar uma unidade de tempo

#1 A = A + 1;

#1 B[0:3] = ~A[4:7]; // ~ negação entre-bits

#1 C = &A[6:7]; //redução AND dos dois últimos bits de A

 

end

endmodule

 

 

No módulo simple, declarámos A e B como registos de 8-bit e C com um registo de 1-bit também chamado flip-flop. Dentro do módulo, o always e os dois initial descrevem três threads de controlo, isto é, funcionam ao mesmo tempo ou concurrencialmente. Dentro da construção initial, as instruções são executadas sequencialmente tal como em C ou outras línguas de programação imperativas tradicionais. A construção always comporta-se como a construção initial, mas com diferença que executa durante toda a simulação.

 

A notação #1 significa executar a indicação após esperar de uma unidade do tempo simulado. Assim, a thread de controlo causada pelo primeiro construção initial atrasa 20 unidades de tempo antes de chamar o $stop e parar a simulação.

 

A instrução de sistema $display permite que o programador imprima uma mensagem tal como o printf faz na linguagem C e o println em Java. Para cada unidade de tempo em que as variáveis listadas mudam, a função $monitor exibe uma mensagem. A função de sistema $time retorna o valor actual do tempo simulado.

 

Abaixo temos o output esperado da simulação:

 

Time A B C

0 00000000 xxxxxxxx x

1 00000001 xxxxxxxx x

2 00000001 1110xxxx x

3 00000001 1110xxxx 0

4 00000010 1110xxxx 0

5 00000010 1101xxxx 0

7 00000011 1101xxxx 0

8 00000011 1100xxxx 0

9 00000011 1100xxxx 1

10 00000100 1100xxxx 1

11 00000100 1011xxxx 1

12 00000100 1011xxxx 0

13 00000101 1011xxxx 0

14 00000101 1010xxxx 0

16 00000110 1010xxxx 0

17 00000110 1001xxxx 0

19 00000111 1001xxxx 0

Stop at simulation time 20

 

Recomenda-se que o leitor analise atentamente o código e perceba os outputs antes de continuar, visto que esta é a estrutura típica de um programa em Verilog. Um bloco initial para delimitar o tempo de simulação, outro bloco initial para inicializar os registos e descrever os que devem ser monitorizados e finalmente um bloco always para o sistema digital a modelar. Note que todas as indicações no segundo initial são feitas com o tempo = 0, visto que não há nenhum instrução da forma #<inteiro> para atrasar.

 

Léxico da Linguagem

 

O léxico da linguagem é parecido com C++. Assim, os comentários são precedidos por “//” ou delimitados por “/*” e “*/”. Palavras reservadas, como module, são invariavelmente todas em minúsculas. A linguagem é case-sensitive, o que diferencia as letras minúsculas das maiúsculas. Os espaços são também importantes, porque são estes que delimitam os tokens da linguagem

 

Os números podem ser especificados de forma normal, como uma série de dígitos com ou sem sinal, ou então na forma:

 

<tamanho><base><número>

 

onde o campo <tamanho>, opcional, contém os dígitos decimais que especificam o tamanho da constante em bits. O campo <base> forma-se com o caracter ' (plica) seguido por um dos seguintes caracteres b, d, o e h, que representam, binário, decimal, octal e hexadecimal, respectivamente. O campo <número> contem os dígitos legais para a <base>. Alguns exemplos:

 

549 // número decimal

'h 8FF // número hexadecimal

'o765 // número octal

4'b11 // número binário de 4-bits: 0011

3'b10x // número binário de 3-bits com último bit desconhecido

5'd3 // número decimal de 3-bits

-4'b11 // complementos de dois de 4-bits de 0011 ou 1011

 

Atenção: o sinal dever colocado apenas antes do campo <tamanho>.

 

Uma string é uma sequência de caracteres separada por caracteres “ (aspas):

 

"isto é uma string"

 

Os operadores são um, dois ou três caracteres utilizados nas expressões e são especificados mais à frente neste documento.

 

Identificadores são especificados por uma letra ou underscore seguido de zero ou mais letras, dígitos, sinais dólar ou underscores até um máximo de 1024 caracteres.

 

 

Estrutura do programa

 

A linguagem Verilog descreve um sistema digital como um conjunto dos módulos. Cada um destes módulos tem uma relação com outros módulos que descreve como estão ligados. Normalmente coloca-se um módulo por ficheiro mas é apenas convenção e não é obrigatório. Os módulos podem funcionar simultaneamente, mas normalmente especificamos um módulo do nível superior que especifica um sistema fechado contendo dados de teste bom como modelos de hardware. A este módulo chamaremos módulo de topo. O módulo de topo invoca instâncias de outros módulos.

 

Os módulos podem representar as partes de hardware que podem ir desde portas simples até sistemas complexos como microprocessadores. Os módulos podem ser especificados por comportamento ou estruturalmente (ou uma combinação dos dois). Uma especificação por comportamento define o comportamento de um sistema digital (módulo) que usa as construções tradicionais duma linguagem de programação, e. g., ifs, instruções de atribuição. Uma especificação estrutural expressa o comportamento de um sistema digital (módulo) como uma interconexão hierárquica dos módulos secundários. No fundo da hierarquia os componentes devem ser primitivas ou especificados por comportamento. As primitivas de Verilog incluem portas, e.g., nand, bem como transístores de passagem (switches).

 

A estrutura de um módulo é a seguinte:

module <nome_módulo> (<lista_portas>);

<declarações>

<elementos_módulo>

endmodule

 

<nome_módulo> é um identificador que descreve univocamente o módulo. <lista_portas> é uma lista das portas de input, de inout e de output que são usados na ligação a outros módulos. A parte <declarações> especifica estruturas de dados como registos ou memórias bem outras construções executáveis como funções.

Os <elementos_módulo> podem ser blocos initial, blocos always ,atribuições contínuas ou instâncias de módulos.

 

A semântica do bloco module em Verilog é muito diferente de funções ou procedimentos noutras linguagens. Ao invés de ser chamado, o módulo é instanciado no início do programa e dura até ao fim do programa. Isto faz sentido, pois o Verilog representa modelos de hardware e faz sentido assumir que ninguém adiciona, remove ou altera qualquer dos módulos depois de serem postos a funcionar. De cada vez que um módulo é instanciado, dá-se à instância um nome. Por exemplo, NAND1 e NAND2 são os nomes das instâncias de nossa porta do NAND no exemplo seguinte.

 

Abaixo estão as especificações para um módulo NAND. A saída out é a negação da conjunção das entradas in1 e in2.:

 

//Modelo comportamental de uma porta NAND

module NAND(in1, in2, out);

 

input in1, in2;

output out;

//atribuição contínua

assign out = ~(in1 & in2);

endmodule

 

As portas in1, in2 e out não são mais que “etiquetas” em fios. A atribuição contínua assign monitoriza constantemente as variáveis na expressão da direita e quando se dão alterações reavalia a expressão e propagas o resultado para o lado esquerdo(out). A instrução assign é utilizada para modelar circuitos combinacionais, onde as saídas mudam quando muda uma das entrada.

 

Abaixo estão a especificação de um  módulo AND à custa de dois módulos NAND. Note que se liga a saída de um dos módulos a ambas as entradas do outro:

 

module AND(in1, in2, out);

//Modelo estrutural de uma porta AND a partir de duas NANDs

input in1, in2;

output out;

wire w1;

//duas instâncias de NAND

NAND NAND1(in1, in2, w1);

NAND NAND2(w1, w1, out);

endmodule

 

Este módulo tem duas instâncias do módulo NAND: NAND1 e NAND2, ligados internamente por um fio interno, w1.

 

A fórmula geral para instanciar um módulo é:

 

<nome_módulo> <lista_parâmetros> <nome_instância> (<lista_portas>);

 

onde <lista_parâmetros> são valores para os parâmetros a passar à instância.

Abaixo está um módulo de alto nível que inicializa algumas variáveis e começa a monitorização de variáveis.

 

module test_AND;

// High level module to test the two other modules

reg a, b;

wire out1, out2;

 

initial begin //Valores de teste

a = 0; b = 0;

#1 a = 1;

#1 b = 1;

#1 a = 0;

end

 

initial begin //Começa a monitorizar entradas e saídas

$monitor("Time=%0d a=%b b=%b out1=%b out2=%b",

$time, a, b, out1, out2);

end

//Instâncias de AND e NAND

AND gate1(a, b, out2);

NAND gate2(a, b, out1);

endmodule

 

Note que é necessário guardar os valores a e b ao longo do tempo, para isso usam-se registo de 1-bit. As variáveis reg, guardam o último valor que lhes foi procedimentalmente atribuído, tal como noutras linguagens imperativas. Linhas no entanto não têm capacidade de armazenamento. Assim, têm que ter um valor constantemente associado, e. g., a uma instrução assign ou a saída de um módulo.

Atenção: se as linhas não forem correctamente conectadas, assumem o valor especial x (desconhecido).

 

Atribuições contínuas usam a instrução assign ao passo que atribuições procedimentais têm a forma <variavel_reg> = <expressão> sendo que<variavel_reg> deve ser um registo ou memória. Este tipo de atribuições(procedimentais) só pode aparecer em blocos initial ou always.

 

As instruções no bloco do primeiro initial são executadas sequencialmente, sendo que algumas são atrasadas por #1, isto é, uma unidade do tempo simulado. O bloco always comporta-se tal como o bloco initial mas, repete-se indefinidamente até a simulação terminar. Os blocos initial e always são usados para modelar lógica sequencial (isto é, autómatos de estados finitos).

 

Verilog faz uma distinção relevante entre a atribuição procedimental e a atribuição contínua assign. A atribuição processual muda o estado de um registo, isto é, lógica sequencial, ao contrário da atribuição contínua que é para usada modelar a lógica combinacional. As atribuições contínuas mantém variáveis tipo linha e alteram-nas quando existe uma modificação nas entradas.

 

Ao correr os três módulos no simulador produz-se a saída seguinte:

 

Time=0 a=0 b=0 out1=1 out2=0

Time=1 a=1 b=0 out1=1 out2=0

Time=2 a=1 b=1 out1=0 out2=1

Time=3 a=0 b=1 out1=1 out2=0

 

Como o simulador não tem mais nada para executar, não é necessário terminar a simulação explicitamente.

 

Tipos de dados

 

Tipos de dados físicos

 

Como o propósito do Verilog é modelar hardware, os principais tipos de dados são registos(reg) e linhas (wire). As variáveis reg armazenam o último valor que lhes foi procedimentalmente atribuído, ao contrário das linhas que representam ligações físicas entre entidades estruturais tais como portas. Uma variável wire representa apenas uma “etiqueta” num fio e não armazena valores. (Existem outros tipos de dados relacionados com wire, mas excedem o espectro desta leitura.)

 

 

As variáveis reg e wire podem ter os seguintes valores:

0 zero lógico ou falso

1 um lógico ou verdadeiro

x valor lógico desconhecido

z alta impedância em portas tri-estado

As variáveis reg são inicializadas a x e qualquer linha não conectada tem também o valor x.

 

O tamanho deste tipo de variáveis pode ser especificado na declaração. Por exemplo, as declarações seguintes:

reg [0:7] A, B;

wire [0:3] Dataout;

reg [7:0] C;

especificam os registos A e B com 8-bits cada e o bit mais significativo é o bit 0, ao passo que no registo C é o bit 7. A linha Dataout tem 4-bits.

 

Os bits num registo ou linha podem ser endereçados pela notação [<bit-inicialt>:<bit-final>].

Por exemplo, na segunda linha do código abaixo:

initial begin: int1

A = 8'b01011010;

B = {A[0:3] | A[4:7], 4'b0000};

end

o valor de B é alterado para a disjunção dos quatro primeiros bits de A com os quatro últimos concatenada com 0000. B fica com o valor 11110000. As {} chavetas representam a concatenação.

 

Qualquer argumento pode ser replicado especificando um número na fórmula:

{numero_repetições{exp1, exp2, ... , expn}}

 

Eis alguns exemplos:

C = {2{4’b1011}}; //C é alterado para  8’b10111011

C = {{4{A[4]}}, AA[4:7]}; // primeiros 4 bit são extensões de sinal

 

Os limites destas referências numa expressão têm que ser valores constantes. No entanto, um bit isolado pode ser endereçado com uma variável. Tomemos o exemplo:

reg [0:7] A, B;

B = 3;

A[0: B] = 3'b111; // ILEGAL – os indiques devem ser constantes!!

A[B] = 1'b1; // A referência um bit isolado é LEGAL

Este tipo exótico de limitação justifica-se de novo com o objectivo do Verilog: descrever hardware, por isso necessitamos apenas de expressões realizáveis por hardware.

 

Em Verilog as memórias são declaradas como vectores de registos. Para obter uma memória com 1K de Words cada uma a 32 bits tem-se:

 

reg [31:0] Mem [0:1023];

 

A notação Mem[0] endereça a primeira word da memória e o índice para este tipo de operações pode ser um registo.

Atenção: Verilog HDL não suporta referências à memória bit-a-bit. Assim, para utilizar uma série de bits em memória é necessário carregar primeiro a word inteira para um registo temporário.

 

Tipos de dados abstractos

 

Para além da modelação de hardware, existem outros usos para as variáveis num modelo da hardware. Por exemplo, o programador pode precisar de usar um inteiro para contar o número de vezes que executa um ciclo ou executar outras operações de debug. Para maior comodidade, o Verilog HDL inclui alguns tipos de variáveis sem representação no hardware. Esses tipos são real, integer e time e são semelhantes aos correspondentes noutras linguagens.

Atenção: uma variável reg não tem sinal e um integer de 32 bits tem. Isto tem consequências não negligenciáveis ao subtrair.

 

Variáveis do tipo time armazenam valores de 64-bits e usam-se em conjunto com a função de sistema $time. Arrays unidimensionais de  variáveis integer e time podem ser utilizados.

Atenção: arrays do tipo real e arrays multi dimensionais são ilegais.

 

Alguns exemplos:

integer Count; // inteiro simples de 32-bits com sinal

integer K[1:64]; // array de 64 inteiros

time Start, Stop; // duas variáveis time de 64-bits

 

Operadores

 

Operadores binários aritméticos

 

Operadores binários aritméticos operam sobre dois operandos. Registos e linhas são tratados como sendo sem sinal. No entanto, reais e inteiros podem ter sinal. Se qualquer um dos bits de qualquer operando for desconhecido, então o resultado é esse mesmo: x.

 

Operador Nome Detalhes
+ Adição
- Subtracção
* Multiplicação
/ Divisão Divisão por 0 resulta em x
% Modulus

 

Operadores unários aritméticos

 

Operador Nome Detalhes
- Simétrico Muda o sinal do operador

 

Operadores relacionais

 

Operadores relacionais comparam dois operando e devolvem TRUE(1) ou FALSE(0). Se qualquer dos bits é desconhecido, o resultado é também desconhecido (x).

 

Operador Nome Detalhes
> Maior
>= Maior ou igual
< Menor
<= Menor ou igual
== Igual
!0 Diferente

 

Operadores lógicos

 

Operadores lógicos operam sobre operandos lógicos e devolvem TRUE(1) ou FALSE(0).

Atenção: Não confundir operadores lógicos com operadores booleanos entre-bits. Por exemplo, ! é a negação lógica e ~ é a negação entre-bits. O primeiro nega, e. g., !(5==6) é TRUE. O segundo complementa os bits, e. g., ~{1,0,1,1} é 0100.

 

Operador Nome Detalhes
! Negação lógica
&& Conjunção lógica
|| Disjunção lógica

 

Operadores entre-bits

 

Operadores operam sobre os bits dos operandos. Por exemplo, o resultado de A&B é o AND dos bits correspondentes de A em B. A operação sobre um bit desconhecido(x) resulta no valor esperado. Ou seja, (x AND FALSE) devolve x e (x OR TRUE) retorna TRUE.

 

Operador Nome Detalhes
~ Negação entre-bits
& Conjunção entre-bits
| Disjunção inclusiva entre-bits
^ Disjunção exclusiva entre-bits
~& NAND entre-bits
~| NOR entre-bits
~^ ou ^~ Equivalência entre-bits

 

Operadores unários de redução

 

Estes operadores produzem um único bit, resultado da aplicação do operador a todos os bits do intervalo. Assim, &A fará a operação AND sobre todos os bits de A.

 

Operador             Nome                          Detalhes

Operador Nome Detalhes
& Redução AND
| Redução Or
^ Redução XOR
~& Redução NAND
~| Redução NOR
~^ Redução XNOR

 

Outros operadores

 

Outros operadores um pouco mais exóticos.            

 

Operador Nome Detalhes
===  Igualdade Comparação entre bits, incluindo x e z. Todos os bits têm que coincidir para haver igualdade. Retorna True ou False.
!==  Desigualdade Comparação entre bits, incluindo x e z. Se um bit difere produz desigualdade. Retorna True ou False.
{ , } Conatenação Concatena os bits de 2 ou mais expressões separadas por vírgulas.
<< Shift à esquerda

Espaços de bits vazios enchem-se com 0s.

>> Shift à Direita Espaços de bits vazios enchem-se com 0s.
?: Condicional Devolve um de dois valores de acordo com uma expressão.

E. g., A = C>D ? B+3 : B-2 significa que se o valor de C é maior que D, então A toma o valor B+3, senão A toma o valor B-2.

 

Precedência de operadores

 

A precedência de operadores está esquematizada na tabela seguinte. Operadores no topo da tabela têm a precedência mais alta e operadores na mesma linha têm a mesma precedência em qualquer expressão, executando-se primeiro os mais à esquerda. Parênteses podem e DEVEM ser utilizados para clarificar a situação e aumentar a legibilidade do código.

 

operadores unários: ! & ~& | ~| ^ ~^ + - (precedência mais alta)

* / %

+ -

<< >>

< <= > >+

== != === ~==

& ~& ^ ~^

| ~|

&&

||

?:

 

Blocos de controlo

 

Verilog HDL tem diversas instruções de controlo que podem ser usadas nas partes procedimentais do código, ou seja, nos blocos initial e always. A maior parte destes blocos são comuns a quase todas as linguagens procedimentais. Ao contrário de linguagens como C e Java, que usam chavetas, Verilog usa begin e end para delimitar os blocos tal como Pascal.

 

Selecção

 

Um if apresenta a seguinte forma:

 

if (A == 4)

begin

B = 2;

end

else

begin

B = 4;

end

 

Ao contrário de C e Java, a instrução case em Verilog não necessita de break. Ao invés disso, assim que <expressão> igualar a <valor>, as <instruções> correspondentes são executadas e a execução prossegue a partir do endcase:

 

case (<expressão>)

<value1>: <instruções>

<value2>: <instruções>

default: <instruções>

endcase

 

O exemplo seguinte verifica o valor de uma linha de 1-bit:

 

case (sig)

1'bz: $display("O sinal está a flutuar");

1'bx: $display("O sinal é desconhecido");

default: $display("Signal is %b", sig);

endcase

 

Repetição

 

De novo, verifica-se a proximidade da expressão for entre Verilog e Java. No entanto, os operadores ++ e –- não existem, devendo por isso utilizar-se i = i + (ou -) 1;

 

for(i = 0; i < 10; i = i + 1)

begin

$display("i= %0d", i);

end

 

O bloco while tem a forma mais comum:

 

i = 0;

while(i < 10)

begin

$display("i= %0d", i);

i = i + 1;

end

 

A instrução repeat repete o bloco associado um número determinado de vezes.

 

repeat (5)

begin

$display("i= %0d", i);

i = i + 1;

end