Virtuelt skrivebord med programvare til obligene
Obligene vil vanligvis kreve at man jobber på et virtuelt skrivebord på en server (Virtual Desktop Infrastructure, VDI). VDIet som skal brukes heter 'IFI Digital Electronics' og er et Linux skrivebord. Man åpner dette i programmet 'Omnissa'. Følg lenkene for å lære mer og å installere Omnissa!
Del 1 - Bli kjent med simuleringsverktøy for digital design
Målet med denne oppgaven er å bli kjent med hvordan vi benytter simuleringsverktøy ved utvikling av hardware. Verktøyet vi benytter heter GHDL og er et kompilerings- og simuleringsverktøy for VHDL. Skal man kunne analysere og konstruere digitale kretser må man kjenne til hvordan slike verktøy virker og bruke minst ett. De fleste verktøy har noenlunde likhet i den funksjonaliten som benyttes i IN2060.
Om man er spesielt interessert, kan GHDL og Gtkwave (som er 'open source') installeres på egen maskin. I forbindelse med et annet kurs, har vi utviklet en kokebok med instruksjon i hvordan installere GHDL og hvordan installere gtkwave. Til det samme kurset har vi også laget en instruksjon i hvordan bruke GHDL og GtkWave for å gjøre VHDL-simuleringer. Hele kokeboka er her, om du vil gå dypere inn i materien.
I korte trekk består oppgaven her av å kompilere og simulere en halvadder. Halvadderen har en feil som skal rettes. Etter den er rettet skal den simuleres med en testbenk. Etter at simuleringen er gjort skal resultatet vises frem og lagres som en .png bildefil. Bildefilen skal leveres som en del av besvarelsen.
- Før vi kommer til simuleringen, så må vi få laget en modul som kan kompileres og simuleres.
- Lag en egen folder på UIO-hjemmeområdet ditt til bruk for denne oppgaven
I den første oppgaven har vi kode som skal lagres som en fil med navn halfadder.vhd. Skriv inn programmet nedenfor med et redigeringsverktøy og lagre det i den nye folderen. Det anbefales å skrive teksten manuelt inn i starten, for at oppbyggingen, nøkkelord og syntaksen skal sette seg i fingrene.
Et hvilket som helst tekstredigeringsverktøy kan benyttes, bare det kan lagre rene tekstfiler. Det anbefales å bruke verktøy som holder styr på linjenummer, har monospace font, og kan highlighte nøkkelord i VHDL med forskjellige farger. Eksempler kan være Notepad++, VScode, Gedit, Emacs eller Vim.
library IEEE; -- bibliotek
use IEEE.STD_LOGIC_1164.all; -- pakke
entity halfadder is
port(
a, b : in std_ulogic; -- inputs
s : out std_ulogic; -- sum
c : out std_ulogic -- 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 kompilere filen og rette eventuelle feil før vi starter en simulering.
For å kompilere filen med GHDL, kjører du følgende kommando i terminalen:
ghdl -a --std=08 halfadder.vhdDette starter kompilering av filen. Parameteren --std=08 forteller kompilatoren at vi bruker VHDL2008. Dersom det er feil i koden vil du få beskjed om det. Det er normalt at det blir en del feil ved første kompilering. Å skrive kode for hånd kan lett gi flere feil. I koden til halvadderen mangler det ett semikolon på den tredje siste linjen. Kompilatoren bør finne dette og rapportere hvilken linje feilen er på. Rettingen gjør du i din favoritteditor. Hvis du rettet dette før kompilering, så prøv å legg inn feilen og kompiler med en feil før du retter opp igjen, for å bli kjent med hvordan det ser ut.
- Etter å ha rettet feilen, så kompilér igjen.
I mange tilfeller vil rekompilering føre til at kompilatoren finner nye feil som ikke ble rapportert første gang. Det er helt vanlig fordi kompileringen skjer trinnvis, og senere trinn startes ikke før det første er i orden. For eksempel vil kompilatoren finne syntaksfeil før den sjekker om noe er koblet riktig. Er syntaksfeil vil ikke semantikken (om det man ber om er mulig) sjekkes. Noen ganger kan én feil gjøre at noe annet også fremkommer som feil, også selv om det ikke er det. I starten kan det derfor være lurt å rette én feil av gangen, og heller kompilere mange ganger.
Når feilene er rettet, og fila er kompilert, så vil GHDL lagre en arbeidsfil "work-obj-08.cf". Den inneholder informasjon om alle filene du har kompilert fra folderen. GHDL lagrer denne fila dette uten å gi noen ny beskjed ved kompilering.
For å kunne simulere bruk av halvadderen, så må vi ha en måte å beskrive hva slags input den skal testes med. Det kan gjøres på mange ulike måter, og i dette kurset skal vi bruke en VHDL testbenk til dette. En vhdl testbenk beskriver hvordan den kobler seg opp mot en modul (her: halfadder) og hvordan inngangene skal manipuleres over tid. En vhdl testbenk kan også teste at utgangsverdiene til modulen får den verdien vi forventer, men det er ikke en del av denne oppgaven.
- Lag en ny testbenkfil i samme folder som halvadderen, med navn tb_halfadder.vhd.
- Kopier inn tekstbenkkoden under i testbenkfila.
library ieee;
use ieee.std_logic_1164.all;
entity tb_halfadder is
end entity tb_halfadder;
architecture sim of tb_halfadder is
signal a : std_ulogic := '0';
signal b : std_ulogic := '0';
signal s : std_ulogic;
signal c : std_ulogic;
begin
uut: entity work.halfadder
port map (
a => a,
b => b,
s => s,
c => c
);
stimulus: process
begin
a <= '0'; b <= '0';
wait for 10 ns;
a <= '0'; b <= '1';
wait for 10 ns;
a <= '1'; b <= '0';
wait for 10 ns;
a <= '1'; b <= '1';
wait for 10 ns;
a <= '0'; b <= '0';
wait for 10 ns;
std.env.stop;
end process stimulus;
end architecture sim;Tar vi et blikk på koden til testbenken, så ser vi at den kobler opp "work.halfadder" som er en referanse til navnet til halvaddermodulen vi nettop har kompilert. For at testbenken skal gi stimuli til inngangene til halvadderen, må den ha signaler som kan kobles til halvadderen. Siden vi kan koble opp mange instanser1 av en modul i en testbenk gir vi modulen et navn. For moduler som skal testes i en testbenk er det vanlig å bruke forkortelsene UUT for "Unit under test" og DUT for "Device under test". Signalene i testbenken kobles (mappes) til signalene i testbenken der det står port map. Etter mappinga har vi én prosess der det settes verdier til signalene som er koblet til halvadderen. Etter at verdier er satt så venter man litt før man setter nye verdier. Til sist så har vi en stoppkommando for å hindre at prosessen starter på ny. Koden i testbenken kan vi regne som et program, til forskjell fra halvadderen som er en kretsbeskrivelse. Kretsbeskrivelser kan syntetiseres og brukes til å lage en krets. Testbenker kan bare kjøres på en datamaskin.
1:En instans av en modul er en kopi. Vi sier instans fordi hardwarespråk beskriver krester som kan lages fysisk (instansieres). Hvis vi trenger to kopier av en modul, trenger disse hver sine oppkoblinger, selv om modulene er like. Vi kan ikke gjenbruke en instans til å gjøre to ulike ting i parallell, da det vil kortslutte kretsene. Derfor trenger hver instans en egen referanse (navn) som vi kan bruke når vi gjør oppkoblinger enten det er for simulering eller syntese. ↩
For at testbenken skal kompilere, så må filen som skal testes (halfadder.vhd) være kompilert, siden testbenken refererer til halfadder modulen.
- Kompiler testbenkfila på samme måte som halvadderen
ghdl -a --std=08 tb_halfadder.vhdEtter at testbenken er kompilert, kan vi kjøre simuleringen som testbenken beskriver. For at det skal lages et diagram og ikke bare tekstlig output, må vi be GHDL om å lage et waveform-diagram som vi kan vise frem med gtkwave senere.
- Simuler med diagram ved hjelp av følgende kommando:
ghdl -r --std=08 tb_halfadder --wave=wave.ghwMerk at simuleringen refererer til navnet til entiteten i testbenken, og ikke filnavnet. Det er fordi den bruker den kompilerte arbeidsfila og ikke vhdl-filen.
Når simuleringen har kjørt skal du se en beskjed "simulation stopped @50ns". Videre vil det ha blitt generert en fil "wave.ghw". Denne måten å generere waveform på, tar med alle verdier for alle signalene til både testbenken og modulen som testes.
- Åpne waveformen med gtkwave:
gtkwave wave.ghwI utgangspunktet vil vinduet være nokså tomt, fordi vi ikke har bestemt hvilke signaler vi skal se på. Til venstre kan vi se en fane med "top" og et lite + tegn ved siden av. Denne fanen viser hierarkiet til testen vår.
- Brett ut hierarkiet og velg signalene i halvadderen:
- Trykk på '+' ved siden av top (Da kommer tb_halfadder til syne.)
- Trykk på '+' ved siden av tb_halfadder ("uut" vises)
- Trykk på uut
I vinduet under hierarkiet er en oversikt over tilgjengelige signaler på det nivået vi har valgt (uut).
- Velg alle signalene og dra dem over i signalkolonnen i midten.
Signalene kan dras over enkeltvis eller samlet. Man kan også velge alle fra det nivået man står i og nedover ved å bruke høyreklikk i hierarkiet. Da vil man få et valg recurse import. Trykker man på det får man valgene append, insert, eller replace. Alle valgene kan benyttes, men man får en advarsel "Really import 4 facilities?", der man må svare Yes. Kokeboka har en grafisk fremstilling av dette.
Etter dette vil verdiene til alle signalene komme frem i "waves" fanen, men i utgangspunktet vises bare de første 3 femtosekundene av simuleringen. Siden simuleringen vi kjørte brukte fire steg på 10 nanosekunder, så må vi zoome ut ganske mye for å vise hele forløpet.
- Zoom ut ved å trykke på forstørrelsesglasset med firkant inni.
Alternativt kan menyvalget Time->Zoom->Zoom Full benyttes.
- Tilpass størrelsen på vinduet slik at alle signalene og hele signalforløpet vises og lagre waveformen som en .png fil halfadder.png
- File->Grab To file->halfadder.png
Alternativt kan du bruke skjermdump, eller lignende fangstmetode fra skjerm (Snipping tool osv).
Important
Innleveringen i denne oppgavedelen skal bestå av bildefilen halfadder.png.
Etterord til Del 1
Det er mange måter å gjøre simuleringer på. Noen verktøy lar deg sette signaler i grafiske grensesnitt, noe som er enkelt, men også svært tidkrevende. De fleste verktøy tillater ulike former for skripting, og man kan også lage testbenker i andre programmeringsspråk. I dette kurset benytter vi VHDL testbenker fordi det krever minimalt oppsett når vi bruker VHDL til å beskrive hardware. Metoden kan benyttes for å teste i ukesoppgaver og i den neste obligen i kurset. Prosessen med å gjøre kompilering og simulering kan skriptes slik at man automatisk kjører begge deler hvis kompileringen fullfører. Kokeboksiden beskriver hvordan det kan gjøres.
Del 2 - Bli kjent med ARM assembler kompilering
Når vi koder et program, som skal kjøres av en datamaskin, skriver vi det ofte i et høynivå programmeringsspråk. Høynivå programmeringsspråk er laget for å være enkle å lese og forstå for mennesker. Dette kan for eksempel være Python, Java eller C. Datamaskinen kan ikke direkte forstå og utføre disse programmene. Det er fordi prosessoren(CPU) kun kan utføre et sett instruksjoner som ble definert i hardware da den ble designet. Vi kaller disse instruksjonene for maskinkode. Dere vil lære mer om hva maskinkode er senere i kurset, men for nå holder det å vite all kode vi skriver må oversettes til maskinkode for at datamaskinen skal kunne lese og utføre den.
For noen programmeringsspråk (f.eks. Python) vil koden oversettes til maskinkode av en interpreter underveis mens den kjører, men for mange progammeringsspråk må vi først oversette hele koden til maskinkode før vi kjører den. Programmet som oversetter høynivåkode til maskinkode kalles en kompilator. I denne obligen skal vi kompilere og kjøre et C program og et Assembler program.
Hvilket sett av maskinkodeinstruksjoner en prosessor støtter er avhengig av arkitekturen til prosessoren. De to mest vanlige arkitekturene er X86 og ARM. Linux maskinene på ifi har X86 arkitektur, men i dette kurset skal dere kjøre ARM maskinkode. Da må vi enten kjøre koden på en datamaskin som har ARM arkitektur, f.eks. en Raspberry Pi, eller bruke en emulator. ARM emulatoren later som at den utfører ARM maskinkode, men i bakgrunnen må den kjøre maskinkode som samsvarer med arkitekturen til datamaskinen den kjører på.
Denne obligen tar som utgangspunkt at emulatoren blir brukt for å kjøre ARM kode. (Dersom du har en Raspberry Pi du vil bruke i steden kan du følge vår oppsettsguide, og så gå videre til steg 4.
Vi skal nå bli kjent med emuleringsverktøyet ved å kompilere og kjøre et C program:
- For å få tilgang til emulatoren må du enten være logget inn på en av linux maskinene på ifi, logge deg inn på IFIs ssh login cluster, eller logge deg inn på vdi ressursen IFI Digital Electronics. Du kan logge inn på VDI ressursen med Omnissa Horizon klienten, eller i nettleser. Dersom du er på en egen mac eller linux maskin kan du følge denne guiden for å logge deg inn med ssh på IFIs ssh login cluster. Dersom du er på en egen windows maskin kan du f.eks. bruke X-Win som beskrevet i denne guiden.
Når du har kommet inn på en av maskinene kan du kjøre følgende kommando for å åpne et miljø der der du har tilgang på emulatoren:
in2060_armNår du vil lukke miljøet kan du gjøre det med følgende kommando:
exitVi skal nå kompilere og kjøre C kode. Lag en mappe med navn in2060_oblig1, og naviger til mappen i terminalen. I mappen lagrer du koden nedenfor i en fil med navn helloworld.c (Inne i in2060_arm miljøet finnes kun editorene vim, nano og emacs, og du vil ikke ha tilgang på grafisk brukergrensesnitt. Det kan derfor lønne seg å lagre koden i en fil først, og så åpne miljøet etterpå.)
#include <stdio.h> int main(int argc, char** argv) { printf("Hello World!\n"); return 0; }- Vi skal nå kompilere koden. Bruk kommandoen in2060_arm for å åpne miljøet som inneholder emulatoren og kompilatoren. Kompilatoren du skal bruke heter arm-linux-gnueabihf-g++. Denne kompilatoren kompilerer til ARM maskinkode.
- Kompiler programmet med følgende kommando:
arm-linux-gnueabihf-g++ -static -o helloworld helloworld.c- Vi inkluderer flagget -static til kompilatoren når vi kompilerer koden som skal emuleres. Dette er fordi ARM emulatoren vi bruker er statisk bygget.
Dersom du kompilerer på en RPi eller en annen maskin med ARM arkitektur kan du i steden bruke gcc til å kompilere:
gcc -o helloworld helloworld.cDu vil nå ha en eksekverbar fil som heter helloworld. Dersom du er på en maskin med ARM arkitektur (en RPi) kan du kjøre programmet med:
./helloworldNår du prøver å kjøre programmet på denne måten på en maskin med x86 arkitektur, slik som på en ifi maskin, vil du få en Exec format error. Denne feilmeldingen forteller deg at programmet du prøver å kjøre er bygget for en annen arkitektur enn den maskinen du kjører på har. Vi kjører derfor koden med emulatoren i steden, ved å bruke følgende kommando:
qemu-arm-static helloworld- Koden skal nå kjøre! Ta et skjermblide av terminalen etter at koden har kjørt og gi det navnet helloworld_c.png. Denne bildefilen er en del av innleveringen til Del.
Assembler er et lavnivå programmeringsspråk. Dette programmeringsspråket ligger veldig nært maskinkode. Vi kan nesten se på det som en menneskeleselig versjon av maskinkode. Når vi programmerer i Assembler har vi veldig god kontroll på hvilke instruksjoner prosessoren utfører. Dette kan være nyttig hvis man skal lage en kompilator, en driver eller et operativsystem.
Vi går nå videre til neste steg hvor vi skal kompilere og kjøre et Assembler program:
Lagre koden nedenfor i en fil med navn helloworld_assembler.s. Vi bruker endingen .s på filer som inneholder Assembler kode.
.text .global main main: push {lr} ldr r0, =string bl printf mov r0, $0 pop {lr} bx lr .data string: .asciz "Hello World!\n"For å kompilere Assembler koden til maskinkode bruker du følgende kommando (vi har med opsjonen -g for å legge til debuginformasjon til en debugger vi straks skal bruke. Husk å åpne miljøet med emulatoren med kommandoen "in2060_arm" før du kompilerer!)
arm-linux-gnueabihf-g++ -static -g -o helloworld_assembler helloworld_assembler.sDersom du bruker en RPi kan du igjen bruke gcc i steden for kompilatoren vi bruker i emuleringsmiljøet:
gcc -g -o helloworld_assembler helloworld_assembler.sDu skal nå ha en eksekverbar fil med navn helloworld_assembler. Kjør den med en av de følgende kommandoene:
1. Dersom du bruker emulatoren kjør med:
qemu-arm-static helloworld_assembler2. Dersom du er på en RPi kjør med:
./helloworld_assemblerDersom man får en feil når man programmerer i Assembler kan det være vanskelig å finne den ettersom koden ikke er like lett å forstå som høynivå programmeringsspråk. Vi bruker derfor en debugger når vi skal finne feil i Assembler kode. Miljøet med emulatoren har en debugger som heter gdb. Følg disse stegene for å åpne koden din i gdb med et terminalbrukergrensesnitt (tui): (Dersom du er på RPi hopp videre til Bokmerke 1)
- Først emulerer vi koden. Vi inkluderer -g opsjonen for å videresende debuginformasjon til en port (her port 1234). Dette gjør at gdb kan få tak i informasjon om kjøringen av programmet vårt fra emulatoren. Vi inkluderer & slik at programmet kjører i bakgrunnen. (Merk at du må starte programmet ditt med emulatoren, slik som dette, på nytt for hver gang du vil kjøre det i debuggeren)
qemu-arm-static -g 1234 ./helloworld_assembler &- Så åpner vi gdb med følgende kommando: (Noen ganger må man trykke på enter en ekstra gang for at gdb skal komme i gang skikkelig. Pass på at det står (gdb) på nederste linje i terminalen før du går videre)
gdb-multiarch -tui ./helloworld_assembler- Deretter må vi fortelle gdb hvilken port den skal lese fra (pass på at du bruker samme portnummer som du brukte før med "qemu-arm-static -g"):
target remote localhost:1234- Nå kan vi se hva som ligger i registerene med følgende kommando (Hvis registerne ikke vises etter komandoen, følg skrittene videre så vil registerne dukke opp):
tui reg generalRegisterene er minneelementer der prosessoren har dataene den jobber på (Du vil lære mer om dette senere i kurset). Det er veldig nyttig å se på disse dataene, så prøv å huske på denne kommandoen til oblig 3 :)
Bokmerke 1
- Dette steget beskriver hvordan vi kjører gdb på RPi. Dersom du bruker emulatoren gå videre til Bokmerke 2.
Vi åpner gdb med følgende kommando: (Noen ganger må man trykke på enter en ekstra gang for at gdb skal komme i gang skikkelig. Pass på at det står (gdb) på nederste linje i terminalen før du går videre)
gdb -tui ./helloworld_assemblerBokmerke 2
- Nå kan vi se hva som ligger i registerene med følgende kommando:
tui reg general- Ta et skjermbilde av gdb der man kan se hva som ligger i registerene og lagre det i en fil med navn gdb_tui.png. Denne bildefilen er en del av innleveringen til Del 2.
Du kan nå undersøke hvordan gdb fungerer. Test følgende kommandoer:
1. `r `Kjører programmet (ps. legg inn et stoppested (breakpoint) før du kjører programmet) (pps. hvis du bruker emulatoren kjører allerede programmet når du åpner gdbtui, på RPi må du starte det selv) 2. `s `Kjører neste instruksjon 3. `break main `Legger inn et stoppested ved *main:* i koden 4. `c `Kjører til neste stoppested, eller til slutten av koden dersom det ikke er noen stoppesteder 5. `q `Avslutter debuggingsprogrammet- Når du kjører koden kan du se at verdiene i registerene endrer seg.
Vi skal nå gjøre en liten endring i programmet helloworld_assembler.s, før vi kjører det på nytt. Mot slutten av assembler koden finner du følgende instruksjon:
mov r0, $0- Denne instruksjonen flytter tallet 0 til registeret r0. Legg til ny kode rett før instruksjonen "mov r0, $0" som i steden flytter tallet 5 til r0 og tallet 3 til r1.
Når tallene er flyttet til r0 og r1 skal vi legge dem sammen, vi kan gjøre det med instruksjonen:
add r0, r1- Resultatet av addisjonen vil du kunne se i registeret r0.
- Kompiler den modifiserte koden, og kjør den med debuggeren gdb-tui. Gå steg for steg igjennom koden, og ta et skjermbilde av debuggeren rett etter at "add" instruksjonen har blitt utført. (Slik at man kan se resultatet i r0)
- Den modifiserte assemblerfilen helloworld_assembler.s og bildefilen gdb_tui_add.png er en del av innleveringen til Del 2.
Important
Innlevering oblig 1:
Fra Del 1:
- halfadder.png
Fra Del 2:
- helloworld_c.png
- gdb_tui.png
- helloworld_assembler.s
- gdb_tui_add.png