Oblig 2 - Design og simulering av CLA-adder i VHDL

Nedlastbar pdf - Merk introduksjonsteksten er noe forskjellig- oppgaven er ellers lik.

VHDL skiller seg fra andre programmeringsspråk ved at det beskriver hardware, ikke software. Det vil si at VHDL koden ikke blir utført på en CPU, men i steden beskriver hvordan en krets med logiske porter kobles fysisk (blir implementert). I denne obligen er målet å få øvelse i å lage tre typer kode i vhdl: Dataflyt-, strukturell- og testbenkkode. Dataflytkode beskriver sammenkobling av logiske porter (AND, OR, XOR, osv). Blokker med logikk kaller vi ofte for moduler. Koden som kobler disse modulene sammen til større systemer kalles strukturell kode. Alle systemer man lager må testes (verifiseres) at fungerer som det skal. Små systemer kan testes direkte i et simuleringsverktøy, men for større system er det alltid gunstig å automatisere testingen med en testbenk. Koden vi skriver testbenken i er VHDL, men den beskriver en sekvens av hendelser (omtrent som annen software*) i tillegg til å beskrive eventuelle kretser vi trenger sammen med den vi skal teste. I enkleste forstand leser man av hvordan signalene inn og ut av kretsen endrer seg i waveform-vinduet til simuleringsverktøyet, etter å ha kjørt testbenken. Vi kan også skrive tesbenkkode som sjekker at utgangene til kretsen gir de resultatene vi forventer underveis og rapporterer til skjerm eller fil (selvsjekkende testbenker).

[* Det finnes verktøy som lar oss skrive testbenker i vanlige programmeringsspråk som for eksempel Python, men det er ikke pensum i dette kurset]

I denne obligen skal vi stegvis implementere en carry-lookahead adder(CLA-adder). Dette vil gi oss erfaring med alle typene kode nevnt ovenfor, i tillegg til øvelse i å bruke et simuleringsverktøy og lese av waveform-vindu. Til sist er det et mål å øke forståelsen for oppbygningen av digital elektronikk generelt, slik at det blir lettere å analysere hvordan de virker.

Oppgave 1 - Halvadder

Målet med denne oppgaven er å bli kjent med hvordan vi benytter simuleringsverktøy til å analysere kretser ved utvikling av hardware. Verktøyet vi bruker heter Questa (tidligere Modelsim) og er et kompilerings- og simuleringsverktøy for hardwarespråk (både VHDL og verilog). Vi skal bli kjent med Questa ved å simulere en halvadder.

Bilde
Figur 1 - Halvadder

library IEEE; -- bibliotek
  use IEEE.STD_LOGIC_1164.all; -- pakke

entity halfadder is
  port(
    a, b : in std_logic; -- inputs
    s : out std_logic; -- sum
    c : out std_logic -- carry
  );
end entity halfadder;

architecture dataflow of halfadder is
begin
  s <= a xor b
  c <= a and b;
end architecture dataflow;

Når du har lagret filen, skal du åpne den i Questa. Questa er installert på linux-systemet til ifi. For å benytte Questa, må linuxmiljøet ditt settes opp slik at lisensiering fungerer og at systemet kjenner stiene som er nødvendige for å kjøre programmet. Hvordan du setter opp dette er beskrevet på siden FPGA_tools på wikien til robin.

Når miljøet er satt opp kan du kjøre vsim i terminalen for å starte Questa.

I questa:

Det er lagt inn en feil i halvadderkoden, som gjør at kompileringen feiler. Dette vises med et rødt kryss ved siden av filnavnet i prosjektvinduet. Du kan se feilmeldingene ved å høyreklikke i prosjektvinduet og velge Compile -> Compile Summary. Fiks alle feil inntil filen kompilerer. Dette vises med en grønn hake ved siden av filnavnet. Når filen er kompilert kan vi begynne simuleringen. Vi skal først se på simulering uten testbenk.

I Wave vinduet, vil du nå se fire røde streker, en for hvert signal, samt at det står U i kolonnen Msgs ved siden av signalnavnene. Det betyr at den gule linja (cursoren) står et sted der signalene er uninitialized (derav U). For å få noe vettugt ut av et digitalt design, må vi sette inputene til en verdi.

På dette tidspunktet bør alle signalene kunne leses av som 0.

Innleveringen i oppgave 1 skal bestå av bildefilen halfadder.png.

Oppgave 2 - Fulladder og testbenk

Skriv en VHDL modul for en fulladder som vist på Figur 2 nedenfor. Symbolet for en fulladder er vist i Figur 3. Modulen skal hete fulladder.vhd, og entiteten skal hete fulladder. Bruk navnene a, b og cin for inputene, og s og cout for outputene. Alle inputs og outputs skal være av typen std_logic.

Bilde
Figur 2 - Fulladder
Bilde
Figur 3 - Fulladder symbol

Sammenhengen mellom input og output for fulladderen skal være som angitt i sannhetsverditabellen:

a b cin cout s
0 0 0 0 0
0 0 1 0 1
0 1 0 0 1
0 1 1 1 0
1 0 0 0 1
1 0 1 1 0
1 1 0 1 0
1 1 1 1 1

For at testingen av disse verdiene skal gå smidig har vi laget en testbenk for fulladderen:


library IEEE;
  use IEEE.STD_LOGIC_1164.all;

entity tb_fulladder is -- testbenkentiteter er normalt tomme.
end entity tb_fulladder;

architecture behavioral of tb_fulladder is
  -- en komponent er en entitet definert i en annen fil, og som vi vil bruke.
  -- komponentdeklarasjonen må matche entiteten.
  component fulladder is
    port(
      a, b : in std_logic;
      cin  : in std_logic;
      s    : out std_logic;
      cout : out std_logic
    );
  end component;
 
  -- Tilordning av startverdi ved deklarasjon gjøres med :=
  signal tb_a, tb_b, tb_cin : std_logic := '0';

  -- outputs bør ikke få en startverdi i testbenken, da det kan maskere feil.
  signal tb_s, tb_cout : std_logic;

begin
  -- instansiering:
  DUT: fulladder     -- Merkelappen DUT betyr «device under test» som er en av mange
  port map(          -- vanlige betegnelser på simuleringsobjektet.
    a    => tb_a,    -- Mappinger gjøres med =>, til forskjell fra tilordninger som
    b    => tb_b,    -- bruker <= eller :=
    cin  => tb_cin,  -- Mappinger kan ses på en ren sammenkobling av ledninger
    s    => tb_s,    -- Vi mapper alltid testenhetens porter til testbenkens signaler
    cout => tb_cout  -- Siste informasjon før parantes-slutt har ikke ',' eller ';'
  );

  -- I testbenker kan vi ha prosesser uten sensitivitetsliste..
  -- i slike prosesser kan vi angi tid med «wait» statements, og
  -- vi kan sette signaler flere ganger etter hverandre uten å gi konflikter.
  -- NB: Prosessen vil trigges om og om igjen om vi ikke hindrer det.
  process
  begin
    wait for 10 ns;
    tb_a <= '1';
    tb_b <= '0';
    tb_cin <= '0';
    wait for 10 ns;

    report("Ferdig!") severity note;
    std.env.stop; -- stopper simuleringen
  end process;
end architecture behavioral;

Når simuleringen stopper vil modelsim automatisk hoppe inn i koden der den stoppet. Vanligvis kommer dette vinduet opp foran wave-vinduet. For å få frem wave-vinduet igjen, kan man enten lukke vinduet med koden, eller trykke på wave-tab’en som dukket opp i underkant av vinduet.

Innleveringen i oppgave 2 skal bestå av filene: fulladder.vhd, tb_fulladder.vhd, fulladertest.png og fulladdertest.vcd.

Oppgave 3 - CLA-blokk med testbenk

Carry-lookahead adder (CLA)

Carry-lookahead addere er beskrevet i kapittel 5.2 i læreboka (s241). I denne oppgaven skal vi lage en 32-bits CLA adder og teste den med simulering. Det er ikke nødvendig å forstå hvorfor CLA adderen vil virke for å fullføre denne oppgaven, men vi håper oppgaven kan hjelpe til å avmystifisere hvordan den er bygget opp.

Innholdet i CLA blokken er vist på to måter i Figur 4 og Figur 5.

Bilde
Figur 4 - CLA adder
Bilde
Figur 5 - CLA adder

Utgangspunktet for designet er figur 5.6 på side 242 i læreboka. Vi deler CLA adderen i 3 forskjellige designmoduler. Den innerste modulen har du allerede laget. Det er fulladderen fra oppgave 2. Videre lager vi i denne oppgaven en modul for CLA-blokken. CLA blokken skal danne logikken beskrevet i den blå stiplede firkanten i Figur 4/5, og vil da legge sammen to 4-bit tall. CLA blokken vil benytte fulladderen, og vil derfor ha både strukturell- og dataflytkode. Etterpå, i neste oppgave, setter vi sammen flere CLA blokker i en modul som skal hete CLA_top, slik at vi kan legge sammen tall som er større enn 4 bit. Hver designmodul skal ha sin egen testbenk.


entity CLA_block is
  port(
    a, b : in std_logic_vector(3 downto 0);
    cin  : in std_logic;
    s    : out std_logic_vector(3 downto 0);
    cout : out std_logic
  );
end entity CLA_block;

Bruk entitetsbeskrivelsen for CLA_block ovenfor, og skriv og kompiler CLA-blokk-modulen:


min_løkke: for i in 1 to 5 generate ny_komponent: min_komponent
  port map(
    epler => eplesignal(i),
    pærer => pæresignal(i)
  );
end generate;

Lag en testbenk for å teste CLA_blokken. Testbenken skal hete tb_cla_block.vhd. Benytt assert slik at testbenken stopper ved feil.

Eksempel på assert-statement:

assert(i = j) report ("i er ulik j") severity failure;

assert rapporterer når det boolske uttrykket inni parantesen er usant. Alvorlighetsgraden (severity) har fire nivåer: note, warning, error, failure. Bare failure stopper simuleringen. Report kan også brukes alene, uten assert, og vil da alltid skrive ut beskjeden.

Velg én av metodene under:

Innlevering i oppgave 3 skal bestå av VHDL filene cla_block.vhd, tb_cla_block.vhd og en tekstfil med kopi av outputen i konsollvinduet for siste kjøring.

Oppgave 4 - CLA-toppmodul med strukturell kode og testbenk


entity CLA_top is
  generic(
    width : positive := 32;
  );
  port(
    a, b : in  std_logic_vector(width-1 downto 0);
    cin  : in  std_logic;
    sum  : out std_logic_vector(width-1 downto 0);
    cout : out std_logic
  );
end entity CLA_top;

Bruk entitetsbeskrivelsen ovenfor og lag og kompiler toppmodulen slik at den kan utføre 32 bits addisjon. Filnavnet på toppmodulen skav være cla_top.vhd. CLA_top skal benytte åtte CLA-blokker til å utføre beregningen. Du velger selv hvordan du instansierer komponentene.

Lag en testbenk som tester ut 10 forskjellige tallkombinasjoner etter tur og rapporterer eventuelle feil. Tallkombinasjonene bør inneholde noen tall som bruker, og noen som ikke bruker, de mest signifikante bitene i a og b. Eksporter waveformen som en vcd fil CLA_top.vcd som viser kjøringen med alle input og output.

Innleveringen i oppgave 4 skal bestå av toppmodulen cla_top.vhd, testbenken tb_cla_top.vhd og en waveform CLA_top.vcd som viser kjøringen med alle verdiene for input og output.

Hint (valgfritt å benytte):

I VHDL kan man ikke uten videre oversette mellom tall (integer) og andre typer slik som std_logic, men det finnes biblioteker som kan gjøre det.

For tallkonverteringer benytter vi biblioteket IEEE.numeric_std

Her er et eksempel på deklarering av biblioteket:


library IEEE;
  use IEEE.STD_LOGIC_1164.all;
  use IEEE.numeric_std.all;

Hvis vi skal konvertere et heltall til en std_logic_vector, må vi først bestemme oss om vi vil ha med fortegnsbit eller ikke. For å benytte fortegn har biblioteket en type som heter signed, mens uten fortegn har vi unsigned. Disse typene består av std_logic_vectors, og man må angi bitbredden på samme vis. Når vi konverterer et tall til f.eks unsigned, så må funksjonen vi bruker ha beskjed om hvor mange bit vi skal ha. Her følger eksempel på konvertering fra integer til std_logic_vector og fra std_logic_vector til integer uten fortegn:


signal my_sig : std_logic_vector(31 downto 0);
signal my_int : integer;

...

my_sig <= std_logic_vector(to_unsigned(my_int,32));

my_int <= to_integer(unsigned(my_sig));