Oblig 2 - Design og simulering av 32 bits carry-lookahead-adder med strukturell-VHDL

En oppgave i å konstruere og analysere digitale kretser på lavnivå.

Frist for H2026: 8. oktober 23:59! Leveres gjennom devilry.ifi.uio.no (se også timeplan).

Av Yngve Hafting, Philipp Häfliger
Publisert 8. sep. 2026

Introduksjon

CLA-adder

CLA er en forkortelse for carry-lookahead-adder. CLA addere vil ha lavere propagerinsforsinkelse enn ripple-carry-addere som er den enkleste formen for multi-bit-addere. I kurset vil dere lære mer om hvordan disse virker, når vi kommer til kapittel 5 i læreboka. Det er ikke nødvendig å forstå alt på forhånd.

Strukturell, og RTL-kode

Strukturell kode beskriver hvordan moduler og porter settes sammen. (For å skille ut bruk av laveste port nivå til struktur vil det brukes betegnelse 'dataflyt kode' i det mer avanserte VHDL kurset IN3160.) Med strukturell kode helt ned til port nivå har man full kontroll og kan optimere critical path og dermed forskinkelse i et modul. RTL kode beskriver hva som skjer fra register til register som for eksempel i en finite state maskin eller pipeline. Moduler definert som strukturell kode kan brukes inne i RTL-kode, men ofte brukes det operatorer fra biblioteker (for eksempel 'use ieee.numeric_std.all;') og man spesifiseres ikke i all detalj hvordan noe skal skje. Da  stoler man på VHDL-kompilatoren å ta smarte valg.  

I dette kurset og denne obligen fokuserer vi på grunnleggende dataflyt og struktur.

Simulering og testbenk

Å få større digitale design til å virke er tilnærmet umulig uten testing, og i en designprosess tar testingen ofte større plass enn designet av kretsen man skal implementere. Testing kan gjøres på mange måter og nivåer, med varierende grad av automasjon. På det enkleste nivået så laster man en modul inn i en simulator og bruker det grafiske grensesnittet i simulatoren til å sette ett signal av gangen før man ber den kjøre en tidsperiode. Denne prosessen kan gjentas til det kjedsommelige for å teste en modul fullstendig, men det er svært tungvint og ta mye tid. For å kjøre en rekke tester automatisk, kan man lage en «testbenk» som genererer inputene for oss. For de fleste praktiske formål, vil det være mer effektivt å benytte en testbenk enn å manipulere signaler enkeltvis ved simulering. Testbenken kan gjenbrukes selv om innmaten i en modul endres på, så lenge portene definert i entiteten er de samme.

Testbenker i VHDL kan lages selvsjekkende, med rapportering til skjerm og fil, samt bruk av testvektorer man kan ha lagret på filer i varierende format. Man kan også lage tester med skriptspråk som TCL, eller ved bruk av andre programmeringsspråk som for eksempel Python, men det vil i stor grad være verktøyavhengig om slike løsninger kan benyttes.

I dette kurset er målet å kunne lage enkle testbenker for å teste moduler laget med strukturell-VHDL. I denne obligen, skal du skrive testbenker som manipulerer input og sjekker om output er riktig. I tillegg skal du benytte det grafiske grensesnittet til simuleringsverktøyet til å sjekke resultatet.

Målet med obligen

I denne obligen er målet å få øvelse i å lage egen strukturell/dataflyt kode i VHDL, og koble den sammen strukturelt til en modul som testes med et simuleringsverktøy. Videre er det et mål å få erfaring med testbenk-kode som kan brukes til å manipulere innganger, og å lese og tolke resultatet av kjøringen fra waveform-vinduet til simuleringsverktøyet. Til sist er det et mål å øke forståelsen for oppbygningen av digital elektronikk, slik som addisjonskretser, slik at det blir lettere å analysere hvordan de virker.

Merk:

Bruk av KI i denne oppgaven

I denne oppgaven er intensjonen at du skal få til og teste koblingene mellom de ulike komponentene selv. Bruker du KI til å lage svaret ditt, så får du lite læring av å utføre oppgaven, og oppgaven kan fortone seg som meningsløs. Vi har observert (2025) at opp mot halvparten av studentene bruker KI verktøy eller kopierer andre som har gjort det.

  • Det kan ses på som fusk, hvis ikke bruken er deklarert og avklart (se krav til innleverte oppgaver).
  • Videre har KI-løsningene en del uheldige bivirkninger i form av dårlig koding, og dårlig kodeteknikk.
    • Et eksempel er at modultilkoblinger ikke benyttes
      • det kan skape uforutsigbar oppførsel av digital elektronikk (støy)
      • dette kan igjen føre til innførsel av dataflytskomponenter som ikke var med i designspesifikasjonen
      • MAO. en løsning som krever større areal, høyere strømforbruk og med fare for feil også selv om testbenk ikke avdekker feil.

Bruker du KI-verktøy er det naturlige spørsmålet om du kan forklare hvordan den kretsen du har laget virker? Kan du ikke forklare din egen krets, bør du lage den på ny. Uten verifiserbarhet og forklarlighet kan vi ikke bruke et design, fordi vi vet ikke når det slutter å virke.

Å bruke KI til å få innspill kan være greit, men skriv koden i oppgaven selv (ikke copy-paste), og prøv å forstå alt du gjør.

Obs: Om koden har stor preg av KI bruk kan vi innkalle til intervju hvor studenten må kunne forklare koden som ble levert!

Oppgave 1, Fulladder og testbenk.

Merk: 

Det anbefales å ha gjort oblig 1 og innledende ukesoppgaver i kapittel 4 (4.1, 4.3, 4,5 og 4.6) før du går løs på denne oppgaven.

  1. Skriv en VHDL modul for en fulladder som vist på figuren nedenfor. Modulen skal hete fulladder.vhd, og entiteten skal hete også 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_ulogic.

    Fulladder-skjematikk
    Fulladder-symbol
    Skjematikk til en fulladderSymbol til en fulladder
  2. Kompiler koden, rett eventuelle feil i kompileringen.

Sammenhengen mellom input og output for fulladderen skal være som angitt som følger:

Sannhetstabell for fulladder
abcincouts
00000
10001
01001
11010
00101
10110
01110
11111

For at testingen av disse verdiene skal gå smidig har vi laget en ramme til en testbenk for fulladderen som skal brukes for å teste at vi får riktig output i waveform vinduet:

library IEEE; 
  use IEEE.STD_LOGIC_1164.all;
  
entity tb_fulladder is   -- Testbenkentiteter skal ikke kobles 
end entity tb_fulladder; -- til noe annet, så de er tomme. 
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_ulogic;
      cin   : in  std_ulogic;
      s     : out std_ulogic; 
      cout  : out std_ulogic
    );
  end component;
  -- Tilordning av startverdi ved deklarasjon gjøres med :=
  signal tb_a, tb_b, tb_cin : std_ulogic := '0'; 
  -- outputs bør ikke få en startverdi i testbenken, 
  -- da det kan maskere feil. 
  signal tb_s, tb_cout : std_ulogic; 
	  
begin  
  -- instansiering: 
  DUT: fulladder    -- Merkelappen DUT betyr «device under test» 
  port map(         -- som er en av mange vanlige betegnelser 
    a    => tb_a,   -- på simuleringsobjektet.    
    b    => tb_b,   -- Mappinger gjøres med =>, til forskjell fra 
    cin  => tb_cin, -- tilordninger som bruker <= eller :=
    s    => tb_s,   -- Mappinger kan ses på en ren sammenkobling
    cout => tb_cout -- av ledninger. Vi mapper alltid testenhetens
  );				-- porter til testbenkens signaler.
					-- 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
    -- wait; -- brukes med GHDL som alternativ til "std.env.stop;"
  end process;
end architecture behavioral;

ppp

  1. Lag en fil tb_fulladder.vhd, der du skriver inn koden som vist over.
  2. Modifiser koden slik at du får testet alle mulighetene angitt i sannhetsverditabellen, med 10 ns mellomrom.
  3. Kompilér og rett opp eventuelle feil
  4. Åpne den automatisk genererte .ghw filen i gtkwave
  5. Zoom ut fullt, og kontroller resultatet.
  6. Eksporter waveformen til en png-fil med navn fulladdertest.png og en ghw fil fulladdertest.ghw

Merk:

Innlevering i oppgave 1 skal bestå av filene: fulladder.vhd, tb_fulladder.vhd, fulladertest.png1 og fulladdertest.ghw2

1: Formålet med å levere bildefil (.png) er at det viser om man klarer å finne frem riktig simuleringsdata, og det kan leses i retteverktøyet.

2: Waveform (.ghw) viser om simuleringen er foretatt korrekt.

Oppgave 2, CLA-blokk med testbenk

Merk: 

Det anbefales å ha gjort ukesoppgaver 4.8, 4.9 og 4.10 før du går løs på denne oppgaven.

Carry-lookahead adder (CLA)

I læreboka i kapittel 5.2 er prinsippet til CLA addere beskrevet (s241). I denne oppgaven skal vi lage en 32-bits CLAadder 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.

Blockdiagram og skjematikk til en 32 bit CLA med åtte 4-bits blocker.
32-bit CLA adder, med detaljer av en 4-bit CLA blokk i blå rektangel (Figur 5.6 i læreboka “Digital design and computer architecture”). I tillegg i 4-bit CLA blokken trengs det: Gi=Ai AND Bi, og Pi=Ai OR Bi

Utgangspunktet for designet er figur 5.6 på side 242 i læreboka. For å løse oppgaven vil vi dele oppgaven i 3 forskjellige designmoduler. Den innerste modulen har du allerede laget, og er fulladderen fra oppgave 2). Videre har vi en modul for CLA-blokken. CLA blokken skal danne logikken beskrevet i figuren over. CLA blokken vil benytte modulen fulladderen. Etterpå setter vi sammen flere CLA blokker i en modul som skal hete CLA_top. Hver designmodul skal ha sin egen testbenk.

Entitetsdefinisjonen til 4-bit CLA-blocken vil da se slik ut:

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

Bruk entitetsbeskrivelsen over, og lag og kompiler CLA-blokk-modulen i fil CLA_block.vhd:

  1. Arkitekturen skal hete «structural».
  2. Inkluder fulladdermodulen som en komponent.
  3. Lag egne signaler for «propagate» (p) og «generate» (g). Disse signalene skal være av typen std_ulogic_vector, og skal ha samme antall bit som a og b.
  4. Tilordne verdier til «p» og «g» slik at vi får p(i) = a(i) OR b(i), og g(i) = a(i) AND b(i).

Dette tilsvarer det læreboka sier i kap 5.2.1, under «Carry-Lookahead Adder». Noen kilder (som wikipedia eller geeksforgeeks) vil fremheve at Propagate lages med XOR, og ikke OR slik læreboka gjør. Hvis man ønsker en streng tolkning av propagate, der man ikke propagerer når man genererer mente, så blir XOR eneste riktige løsning. I praksis kan begge metoder benyttes, siden det er likegyldig om vi propagerer carry når vi lager den. Spesielt interesserte vil også kunne merke seg at avhengig av teknologien som benyttes (typisk CMOS), vil noen port-typer kan lages med raskere, færre, eller mindre transistorer; men dette er utenfor pensum i IN2060.

  1. Lag et carry-signal «c» av typen std_ulogic_vector, la antall bit være 5 (fra 4 ned til 0).
  2. Lag et eget signal p30 og g30 for å beregne P3:0 og G3:0 i henhold til Figur 5.6 fra læreboka (vist høyere opp)
  3. Bruk p30 og g30 til å beregne cout.

Hint:

  • Bruk en reduksjonsoperator for p30
  • Bruk paranteser for å holde orden på g30
  1. Sett c(0) til cin.
  2. Opprett (instansier) de fire fulladderne og koble dem med port maps til signalene a, b, c. Velg én av de to metodene å gjøre det på:

Enten: Gi hver av de fire fulladderne en egen merkelapp og instansier dem, og koble til portene med port-mapping. (Som instansieringen med merkelapp DUT av fulladderen i tb_fulladder).

Eller: (kanskje mer vanskelig men deretter skalerbar): I VHDL kan man bruke løkker med for + loop, eller for + generate. Bruk av løkker gir kompakte løsninger som skalerer bedre enn om man skriver ut løsningen manuelt. Kanskje for-løkker er en god illustrasjon av forskjellen mellom HDL og programmeringsspråk: i HDL er løkker byggeinstruksjoner til en krets, dvs til slut er det (for eksempel) bygget flere instanser av en modul, men disse kan godt stå i parallell i signalprosesseringen/skjematikken eller den siste kan stå først osv. Når man oppretter flere instanser av et modul (jeg tenker på det som en array av instanser) brukes generate (ved bruk av eksisterende komponenter og signaler brukes loop*.). Så for denne oppgaven er for …+ generate det som kan brukes til å lage og koble opp 4 instanser av fulladdere (og mer avansert for ambisjonerte kunne man også bygge opp kombinatorisk logikk som beregner g30).

Her er et eksempel på hvordan man bruker for + generate og det skal vises et eksempel i forelesningen også.

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;

Ved behov, søk etter mer detaljert beskrivelser og flere eksempler på internett.

  1. 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;

Bruk av assert er ment for testbenk, dvs for å teste resultater i en sekvens av inngangssignaler. Husk at bare test benk som bruker process uten sensitivitetsliste interpreteres som et programeringsspråk hvor inngangssignaler og reultater genereres i en tidssekvens.

assert statement:

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å benyttes alene for å alltid gi beskjed.

Så for å lage testbenken velg:

Enten: Lag den nye testbenken ved å kopiere og modifisere testbenken i 1b), slik at den passer for CLA blokken. Velg ut minst 6 tallpar som a og b som du legger sammen med CLA blokken. Sørg for at noen vil lage mente (carry)- og noen ikke gjør det. Bruk assert statement til å teste om resultatene fra CLA-blokken er riktig. Bruk «severity failure» ved feil. Etter alle testene er gjennomført skal testbenken gi en tekstbeskjed om testen foregikk feilfritt.

Eller (kanskje mer vanskelig men igjen skalerbar og trenger antageligvis færre linjer med kode): Test at CLA modulen oppfører seg slik den skal ved å lage en testbenk som går igjennom alle muligheter for a, b og cin.

  • Bruk for …+ loop for å lage alle verdiene for a, b og cin.
  • Bruk assert for å sjekke at verdiene stemmer, bruk «severity failure» ved feil.
  • Bruk report for å rapportere når testbenken er ferdig.

Hint (for begge alternativene):

  • CLA blokken tar inn to fire bits signaler (a og b), samt carry (cin). Testbenken må sette disse signalene og sjekke verdiene på summen (s) og carry out (cout).
  • For at simuleringen skal vise forskjellene, må du vente mellom hver gang du setter og tester noe. Du bør vente noenlunde like lenge hver gang, slik at du kan sjekke resultatet på waveformen.

Merk:

Innlevering i oppgave 2 skal bestå av VHDL filen CLA_block.vhd til CLA blokken , testbenken tb_CLA_block.vhd og en tekstfil med kopi av outputen i konsollvinduet for siste kjøring.

Oppgave 3, CLA-toppmodul med strukturell kode og testbenk

Entiteten til top-modulen til CLA adderen:

entity CLA_top is   
  generic(
    width : positive := 32
  );
  port(
    a, b  : in  std_ulogic_vector(width-1 downto 0);
    cin   : in  std_ulogic;
    sum   : out std_ulogic_vector(width-1 downto 0);
    cout  : out std_ulogic
  );
end entity CLA_top;
  1. Bruk entitetsbeskrivelsen over og lag og kompiler toppmodulen (CLA_top.vhd) slik at den kan utføre 32 bits addisjon. CLA_top skal benytte åtte CLA-blokker til å utføre beregningen. Du velger selv hvordan du instansierer komponentene.
  2. Lag en testbenk (tb_CLA_top.vhd) som tester ut 10 forskjellige tallkombinasjoner etter tur og rapporterer eventuelle feil. Tallkombinasjonen bør inneholde noen tall som bruker, og noen som ikke bruker de mest signifikante bitene i a og b. Eksporter waveformen som en ghw fil tb_CLA_top.ghw (Obs: tidliger sto det 'vcd-fil' her som er utdatert men også et mulig ekvivalent filformat som ghdl kan lage og gtkwave kan lese, så det er også gyldig) som viser kjøringen med alle input og output.

Hint (valgfritt å benytte):

I VHDL kan man ikke uten videre oversette mellom tall (integer) til andre typer, slik som std_logic, men det finnes biblioteker som har funksjoner 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_vector'er, 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_ulogic_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));

Merk: 

Bruk av resolusjonsfunksjon (std_logic vs std_ulogic) spiller ingen rolle ved typekonvertering.

Vi anbefaler deklarering med std_ulogic fordi det lar kompilatoren finne koblingsfeil tidlig.

Merk:

Lever da filene CLA_top.vhd, tb_CLA_top.vhd, og tb_CLA_top.ghw (eller tb_CLA_top.vcd)

Minimumskrav for å bestå denne obligen er

  • Kompilerbar kode (som angitt i oppgaven)
  • Gjennomføre egne simuleringer
  • Relevante tidsdiagram (waveform)
  • Vurdere om simuleringsresultatet er rimelig (spesielt hvis det er noe uventet)
  • Å kunne forstå noenlunde oppbygningen til en CLA adder

Besvarer du oppgavene som angitt i hver oppgave og resultatene er som forventet, så er det ikke nødvendig å skrive eller dokumentere noe ekstra i forhold til dette.

Publisert 8. sep. 2026 11:40 - Sist endret 2. okt. 2026 14:12