.. _w02-verilog:


========================================
Wykład 2: Wprowadzenie do języka Verilog
========================================

Data: 15.10.2019

.. toctree::

.. contents::


O języku
========

Verilog jest językiem opisu sprzętu.  Ma następujące zastosowania:

1. Opis zachowania układów logicznych do celów symulacji.
2. Opis zachowania układów logicznych do celów syntezy.
3. Tworzenie testów do układów logicznych.

W tych trzech zastosowaniach używa się różnych podzbiorów języka -- o ile
symulator zaakceptuje dowolny kod Veriloga, w przypadku syntezy możemy używać
tylko ściśle określonych konstrukcji.  Podzbiór Veriloga akceptowany przez
narzędzia do syntezy nazywa się syntezowalnym Verilogiem.

Projektując układy logiczne, używamy Veriloga następująco:

- piszemy naszą logikę w syntezowalnym Verilogu
- uĹźywamy (niekoniecznie syntezowalnego) opisu gotowych blokĂłw logicznych
  dostępnych na układzie FPGA do celów symulacji (ten opis jest dostarczany
  przez producenta)
- piszemy testy do naszej logiki używając pełnego języka Verilog

Verilog jest językiem programowania i może być używany jak zwykły język
programowania, gdy piszemy testy (możemy nawet np. używać funkcji dostępu do
plików) lub model istniejącego układu do celów symulacji.  Gdy jednak piszemy
model, który będziemy syntezować, musimy uważać na używane konstrukcje, aby
nie stworzyć bardzo nieefektywnego sprzętu (na przykład użycie w syntezowalnym
kodzie operatora dzielenia prawie na pewno źle się skończy).


WaĹźna dyrektywa
===============

Pisząc jakikolwiek kod w Verilogu, najlepiej dodać na sam początek
następujący kod::

    `default_nettype none

Bez tej dyrektywy, zapis do dowolnej niezdefiniowanej nazwy automatycznie
utworzy sieć połączeń o danej nazwie, powodując ciężkie w wykryciu błędy.


Moduły
======

Kod w Verilogu składa się z modułów.  Moduł jest opisem bloku logiki
z ustalonym zbiorem wejść i wyjść.  Moduły mogą być parametryzowane
(np. blok mnożący w którym możemy wybrać szerokość liczb w bitach).
Gdy uruchamiamy symulację bądź syntezę, musimy wybrać jeden z modułów
dostępnych w projekcie jako główny moduł (który może rekurencyjnie
instancjonować inne moduły).  Możemy mieć w projekcie wiele instancji
danego modułu (np. dowolny duży układ DSP będzie wymagał podstawowego
układu mnożącego w wielu kopiach).

Przykładowe moduły w Verilogu mogą wyglądać tak::

        `default_nettype none

        // Pół-sumator: dodaje dwa bity, generując bit wyjścia i bit przeniesienia.
        module half_adder(
                input wire a, // Wejście 1
                input wire b, // Wejście 2
                output wire o, // Wyjście główne
                output wire co // Wyjście przeniesienia
        );

        // Instrukcja assign jest syntezowana w układ kombinacyjny obliczający
        // dane wyraĹźenie.
        assign o = a ^ b;
        assign co = a & b;

        endmodule

        // Pełny sumator: dodaje dwa bity i bit przeniesienia, generując bit
        // wyjścia i bit przeniesienia.
        module full_adder(
                input wire a, // Wejście 1
                input wire b, // Wejście 2
                input wire ci, // Wejście przeniesienia
                output wire o, // Wyjście główne
                output wire co // Wyjście przeniesienia
        );

        // Sieci wewnętrzne -- 1-bitowe.
        wire h1o;
        wire h1c;
        wire h2c;

        // Instancjonujemy pod-moduły (dwie kopie pół-sumatora).
        // Możemy też przekazywać wejścia/wyjścia pozycyjnie, ale
        // nie jest to zalecane.
        half_adder h1(.a(a), .b(b), .o(h1o), .co(h1c));
        half_adder h2(.a(h1o), .b(ci), .o(o), .co(h2c));

        // Instancjonujemy wbudowany prymityw (bramkę or) -- możemy tego używać
        // zamiennie z równoważną instrukcją assign.
        or carry_or(co, h1c, h2c);

        endmodule


Sieci
=====

Siecią w układzie logicznym nazywamy połączony zbiór wejść/wyjść prymitywów.
Sieci definiuje się słowem ``wire`` (jak widzimy w powyższym przykładzie)
bądź kilkoma podobnymi (których nie używa się we współczesnych FPGA).
Możemy przekazać sieć jako parametr do pod-modułu.

Verilog pozwala na podłączenie kilku wyjść do jednej sieci -- taka konstrukcja
nie jest błędem, jeśli możemy zapewnić, że w danym momencie będzie aktywne
tylko jedno z nich (jest to uĹźywane np. w dwukierunkowych szynach
komunikacyjnych), bądź użyjemy specjalnych typów sieci ze zdefiniowanymi
regułami rozwiązywania konfliktów (``wand``, ``wor`` zamiast ``wire``).
Takie konstrukcje jednak nie są wspierane wewnątrz współczesnych FPGA
(żeby osiągnąć dużą wydajność, każda sieć powinna mieć tylko jedno źródło)
i będą przetłumaczone w nieefektywną logikę przez syntezę.  Nie należy ich
więc używać, z wyjątkiem wyprowadzeń dwukierunkowych (``inout``) na zewnątrz
FPGA.


Typy danych i operatory
=======================

W Verilogu istnieje jeden podstawowy typ danych, którego możemy użyć
w syntezowalnym kodzie -- wektor bitów, być może traktowany jako liczba
bez znaku.  Jeśli chcemy, możemy też wybrać traktowanie go jak liczbę ze
znakiem::

    wire a;             // Jeden bit.
    wire [0:7] d;       // 8 bitów, numerowanie big-endian (najbardziej znaczący bit to 0).
    wire [13:0] d2;     // 14 bitów, numerowanie little-endian (najmniej znaczący bit to 0).
    wire signed [1:10] d3;      // 10 bitów, numerowanie big-endian licząc od 1, ze znakiem.

Stałe zapisujemy następująco::

    wire [0:3] a;
    assign a = 4;   // Stała bez szerokości, automatycznie staje się 4-bitowa przy zapisie.

    assign b = 4'd5;    // 4-bitowa stała o wartości 5 (zapis dziesiętny)
    assign b = 4'b1101; // 4-bitowa stała o wartości 13 (zapis binarny)
    assign b = 4'hd;    // 4-bitowa stała o wartości 13 (zapis szesnastkowy)
    assign b = 27'h123_4567;    // 27-bitowa stała szesnastkowa, znak _ jest ignorowany
    assign c = -5'sha;  // 5-bitowa stała ze znakiem o wartości -10, zapis szesnastkowy.

Operatory są dość podobne do C, ale znajdziemy też wiele przydatnych dodatków:

- ``a[2:3]`` -- wycięcie kilku bitów z wektora
- ``{a, b, c}`` -- konkatenacja kilku wektorĂłw
- ``|a`` -- szeroki OR (wszystkie bity w wektorze są ORowane w jeden bit)

Bit w logice cyfrowej może mieć 4 wartości:

- 0 (nie wymaga tłumaczenia)
- 1 (j/w)
- X: stan nieustalony.  Powstaje sam, gdy dwa różne wyjścia na jednej sieci
  mają stany 0 i 1 (prawdziwy sprzęt nie znosi tego dobrze).  Możemy sami
  przypisać X do jakiejś sieci -- mówimy wtedy syntezie, że nie obchodzi nas
  wartość tego bitu w danej sytuacji, pozwalając na większą dowolność
  w optymalizacji układu.
- Z: brak wyjścia.  Przypisując tą wartość do sieci, wyłączamy dany układ
  wyjściowy, pozwalając innemu wyjściu kontrolować jej stan.  We współczesnych
  układach FPGA powinno być używane tylko do dwukierunkowych sygnałów
  zewnętrznych.


Procesy
=======

Proces jest blokiem kodu w Verilogu uruchamianym zawsze, gdy zmieni się stan
danych sieci::

    // Układ kombinacyjny zrealizowany przez proces -- będzie wykonywane
    // zawsze, gdy zmieni się stan któregoś z wejść.
    reg [1:0] d;
    always @(a, b, c) begin
        d = 0;
        if (a)
            d = 1;
        if (b)
            d = 2;
        if (c)
            d = 3;
    end

Głównym zadaniem procesów w syntezowalnym kodzie jest zapis do rejestrów
-- rejestry są definiowane słowem ``reg`` i są efektywnie zmiennymi.  Możemy
je czytać podobnie jak sieci (i przekazywać je do wejść pod-modułów, bądź
do własnych wyjść).

W procesach mamy dwie instrukcje przypisania:

- ``=``: stan rejestru zmienia się natychmiast (nowa wartość jest widoczna w kolejnej instrukcji)
- ``<=``: stan rejestru zmienia się w następnym cyklu symulacji (czytanie rejestru w kolejnej instrukcji da starą wartość)

W syntezowalnym kodzie należy przestrzegać następujących reguł:

- dany rejestr może być zapisywany tylko przez jeden proces
- dany proces może być albo kombinacyjny albo tworzyć przerzutniki
- w przypadku procesu kombinacyjnego, należy w liście ``@`` wypisać wszystkie
  sieci używane wewnątrz procesu do liczenia nowych wartości rejestrów
- w przypadku procesu do przerzutników, należy w liście ``@`` użyć tylko
  jednego zbocza jednego zegara i ew. asynchronicznych sygnałów resetu oraz
  używać *tylko* przypisania ``<=``

Przykład syntezy przerzutnika jest następujący::

    wire [3:0] i;
    // Możemy zdefiniować początkowy stan rejestru.
    reg [3:0] a = 0;
    wire clk, rst;

    // Prosty akumulator -- w każdym zegarze zwiększa licznik o wartość i.
    always @(posedge clk, posedge rst) begin
        if rst
            a <= 0;
        else
            a <= a + i;
    end


Pamięć
======

Rolę tablic w Verilogu pełni tzw. pamięć.  Tworzy się ją używając nawiasów
kwadratowych po nazwie rejestru::

    // Pamięć 128-bajtowa (z 7-bitowymi bajtami).
    reg [6:0] m [127:0];

    always @(posedge clk) begin
        if (write)
            m[addr] <= wrdata;
        rddata <= m[addr];
    end

Aby pamięć była sensownie syntezowalna, należy używać ściśle określonych konstrukcji
(omówionych na późniejszych wykładach).


Case
====

Wewnątrz procesów możemy używać instrukcji ``case`` (równoważnej ``switch``
z języka C)::

    wire [3:0] bits;
    reg err;
    reg [1:0] which;
    always @(bits) begin
        case (bits)
        4'b0001 : begin
            which <= 0;
            err <= 0;
        end
        4'b0010 : begin
            which <= 1;
            err <= 0;
        end
        4'b0100 : begin
            which <= 2;
            err <= 0;
        end
        4'b1000 : begin
            which <= 3;
            err <= 0;
        end
        default : begin
            which <= 4'hx;
            err <= 1;
        end
        endcase
    end

Często przydaje się instrukcja ``casex``, która działa jak instrukcja ``case``,
ale gdy użyjemy bitu ``x`` w stałej, będzie do niego pasować zarówno ``0`` jak
i ``1`` (normalna instrukcja ``case`` dopasowałaby w tym wypadku tylko bit
``x``, co oczywiście nie jest syntezowalne)::

    wire [3:0] bits;
    reg err;
    reg [1:0] maxbit;
    always @(bits) begin
        casex (bits)
        4'b0001 : begin
            maxbit <= 0;
            err <= 0;
        end
        4'b001x : begin
            maxbit <= 1;
            err <= 0;
        end
        4'b01xx : begin
            maxbit <= 2;
            err <= 0;
        end
        4'b1xxx : begin
            maxbit <= 3;
            err <= 0;
        end
        // Uruchomi się tylko dla bits == 0
        default : begin
            maxbit <= 4'hx;
            err <= 1;
        end
        endcase
    end

.. warning::

   Używając instrukcji case do syntezy logiki kombinacyjnej, należy pamiętać,
   by w każdej gałęzi przypisać wartości do tego samego zbioru rejestrów --
   w przeciwnym wypadku, zsyntezujemy logikę z zatrzaskami, a to rzadko jest
   pożądane.  Jeśli nie obchodzi nas wartość przypisana do niektórych rejestrów
   w pewnych gałęziach, możemy tam przypisać X (jak w powyższym przykładzie).
   Pozwoli to uprościć zsyntezowanny układ.

.. warning::

   Symulatory traktują x jako osobną wartość od 0 i 1.  Jeśli ``bit == 1'bx``,
   w poniższym kodzie nie wykona się *żadna* gałąź ``case``::

        wire bit;
        always @(bit) begin
            case (bit)
                1'b0: [...]
                1'b1: [...]
            endcase
        end

   Z tego powodu, dobrym pomysłem jest używanie zawsze gałęzi default, nawet
   w przypadku case, ktĂłry pokrywa "wszystkie" przypadki.


Parametry i generate
====================

Moduły mogą być parametryzowane::

    module avg (
        input wire [BITS-1:0] a,
        input wire [BITS-1:0] b,
        input wire rounddir,
        output wire [BITS-1:0] res
    );

    // Domyślnie piszemy 24-bitowy procesor.
    parameter BITS = 24;

    // Konkatenujemy jeden bit zerowy do a, żeby otrzymać (BITS+1)-bitową
    // liczbę (pozostałe składniki się same rozszerzą).
    assign res = ({1'b0,a} + b + rounddir) >> 1;

    endmodule

    [...]

    // Użycie 24-bitowego modułu.
    avg avg1(.a(a1), .b(b1), .rounddir(rounddir1), .res(res1));
    // Użycie 8-bitowego modułu.
    avg #(.BITS(8)) avg2(.a(a2), .b(b2), .rounddir(rounddir2), .res(res2));

Ze względu na brak enumów w Verilogu (są one dostępne dopiero w SystemVerilogu,
którego ISE niestety nie wspiera), parametrów używa się również do definiowania
stałych.

Czasem chcemy wygenerować wiele kopii danego układu.  Pomoże nam w tym
konstrukcja generate::

        module multi_adder(a, b, ci, o, co);

        parameter BITS = 8;

        // Nie możemy użyć parametru przed deklaracją -- stąd definicje typów
        // po deklaracji modułu.
        input wire [BITS-1:0] a,
        input wire [BITS-1:0] b,
        input wire ci,
        output wire [BITS-1:0] o,
        output wire co

        wire [BITS:0] carry;

        assign co = carry[BITS];
        assign carry[0] = ci;

        genvar i;
        generate
                // Pętlę generate-for trzeba nazwać.
                for (i = 0; i < BITS; i = i + 1) begin : gen_fa
                        full_adder fa(
                                .a(a[i]),
                                .b(b[i]),
                                .ci(carry[i]),
                                .o(o[i]),
                                .co(carry[i+1])
                        );
                end
        endgenerate

        endmodule

Analogicznie do pętli ``for`` można używać konstrukcji ``if`` oraz ``case``.


Więcej informacji
=================

Szybki kurs Veriloga: http://www.asic-world.com/verilog/veritut.html (uwaga: w wielu miejscach używa starej składni Verilog 95).

Do wydrukowania i powieszenia na ścianie: http://www.ece.uvic.ca/~fayez/courses/ceng465/vlogref.pdf .