Symulacje czasowe i ograniczenia czasowe
W ramach ćwiczenia należy obowiązkowo użyć gotowego bloku kontrolera testującego pamięć statyczną RAM umieszczoną na płytce Spartan-3 Starter Kit Board (opis na str. 11 instrukcji). Użyta pamięć to układ IS61LV25616AL. Na płytce dostępne są dwa układy scalone SRAM połączone równolegle i tworzące w ten sposób pamięć o organizacji 262144 słów 32 bitowych. Kontroler pamięci, który dostępny jest pod tym linkiem, działa następująco:
- zapis do każdej komórki tego samego słowa 32 bitowego „01_01_01_..._01” ,
- odczyt każdej komórki pamięci i sprawdzenie czy odczytana wartość jest równa uprzednio wgranej,
- zapis do każdej komórki tego samego słowa 32 bitowego „10_10_10_..._10” ,
- odczyt każdej komórki pamięci i sprawdzenie czy odczytana wartość jest równa uprzednio wgranej,
- wykonywanie powyższych czynności odbywa się cyklicznie w nieskończonej pętli,
- sygnały wyjściowe kontrolera: good_count oraz bad_count zawierają liczbę cykli testowania wykonanych odpowiednio bez błędu i z błędem, są to wektory zawierające liczby w kodzie dwójkowym,
- stała test_end deklaruje ostatni testowany adres pamięci, po jego osiągnięciu test rozpoczynany jest od adresu zerowego, dla symulacji należy testować 8 pierwszych adresów, dla implementacji całą przestrzeń adresową.
Blok testera pamięci należy osadzić w taki sposób aby:
- na wyświetlaczu LED płytki Spartan-3 były umieszczone: na 3 lewych pozycjach liczba prawidłowo wykonanych, na prawej pozycji liczba nieprawidłowych cykli, dla uproszczenia projektu należy wyświetlać cyfry w formacie szesnastkowym,
- dodatkowo przełącznik SW7 (port sw7_i) powinien przełączać taktowanie bloku testera pamięci z częstotliwością równą 50MHz lub 120MHz, zegar 120MHz należy uzyskać poprzez wstawienie bloku DCM i wykorzystanie wyjścia CLKFX, jest to wyjście z syntezera częstotliwości zawartego w bloku DCM.
Po wstępnym wykonaniu opisu układu należy wykonać symulację funkcjonalną. Następnie należy wykonać implementację i kolejną symulację po implementacji obserwując sygnały dochodzące do pamięci RAM. Należy wgrać układ na płytkę FPGA i zaobserwować czy występują błędy pamięci RAM dla obu położeń przełącznika SW7.
Kolejnym krokiem jest powtórzenie poszczególnych symulacji oraz testowania płytki po dodaniu do pliku UCF ograniczeń czasowych.
W czasie obu implementacji należy zapisać wartość otrzymanego najgorszego opóźnienia sieci (Raport Asynchronus Delay Raport).
Dodatek - opis jak wykonać poszczególne kroki:
A) Dodanie bloku DCM. Pełna dokumentacja dotycząca bloku DCM znajduje się w dokumencie katalogowym układu SPARTAN3 (str. 65). Aby nie zagłębiać się w szczegóły i szybko dodać taki blok należy wykonać kolejne kroki:
- dodać deklarację bibliotek nad blokiem ENTITY w którym chcemy osadzić DCM:
library
UNISIM;
use
UNISIM.VComponents.all;
- osadzić komponent poprzez PORT MAP () podobnie jak w załączonym pliku, wartości parametrów generic definiują właściwości wstawianego bloku,
plik z przykładowym osadzeniem
- aby blok DCM w symulacji prawidłowo generował sygnały wyjściowe, dołączony do niego reset asynchroniczny powinien być wystarczająco długi (przynajmniej 3 okresy zegara wejściowego).
B) Model pamięci. Jako model pamięci do symulacji projektu należy pobrać kod z tego linku i osadzić odpowiednio w pliku testbench. Jest to prosty model funkcjonalny zawierający tylko jedno opóźnienie – czas dostępu w czasie odczytu. W celu uniknięcia długiego czasu symulacji zaleca się zmodyfikować stałą test_end (tylko do symulacji). Liczbę słów dostępnych w modelu pamięci deklaruje się poprzez generic words i domyślnie jest on ustawiony na 8.
C) Symulacja czasowa po implementacji. Symulację czasową po implementacji wykonuje się podobnie jak symulację funkcjonalną. Różnicą jest to że:
- przed symulacją w oknie Sources należy wybrać Post-Route Simulation
- następnie w oknie Processes należy kliknąć w przycisk Simulate Post Place & Route Model
- po chwili uruchomi się symulator i dalej należy postępować jak dla symulacji funkcjonalnej, w stosunku do symulacji funkcjonalnej na wykresie czasowym pojawią się opóźnienia sygnałowe, również nazwy sygnałów wewnętrznych tracą hierarchię i są nieco trudniejsze do odnalezienia.
D) Ograniczenia (wymagania) czasowe. Jest kilka metod zadawania w środowisku ISE wymagań czasowych jakie projektant układu chciałby uzyskać po implementacji. Jedną z nich jest umieszczenie ograniczeń w pliku UCF. Szczegóły można znaleźć w dokumencie Constraints Guide lub Timing Constraints Guide. W ramach niniejszego ćwiczenia należy umieścić ograniczenia czasowe przygotowane wcześniej i obejmujące sygnały łączące układ FPGA z pamięcią RAM. Plik UCF ograniczeń czasowych zawarty jest na końcu niniejszego opisu. Po implementacji projektu można przeglądać raporty związane z opóźnieniami układu.
W przypadku gdy projekt zawiera ograniczenia czasowe, w czasie implementacji w konsoli ISE wyświetlane są komunikaty ogólne dotyczące tych ograniczeń. W przypadku braku spełnienia wymagań czasowych można prześledzić ścieżki krytyczne z niespełnionymi czasami. Wykonuje się to poprzez wywołanie programu Timing Analyzer klikając jak na załączonym rysunku (program też można uruchomić poza środowiskiem ISE):
Następnie w zakładce Sources/Timing na czerwono wyróżniane są niespełnione wymagania czasowe. Kliknięcie na ścieżce podświetlonej na czerwono wyświetla w oknie głównym podsumowanie założonych wymagań oraz szczegóły przejść sygnałów. Można następnie kliknąć w niebieskie łącza w celu uzyskania szczegółów dotyczących poszczególnych opóźnień.
Plik UCF:
# Clock:
NET
"clk_i" LOC = "T9" ; # 50 MHz clock
#
Push-buttons:
NET
"rst_i" LOC = "L14" ; # pressed high BTN3
# SW7:
NET
"sw7_i" LOC = "K13" ; # slider sw7
#
Seven-segment LED display:
NET
"led7_an_o<3>" LOC = "E13" ; # leftmost digit, active
low
NET
"led7_an_o<2>" LOC = "F14" ; # active low
NET
"led7_an_o<1>" LOC = "G14" ; # active low
NET
"led7_an_o<0>" LOC = "D14" ; # rightmost digit,
active low
#
NET
"led7_seg_o<7>" LOC = "E14" ; # segment 'a', active
low
NET
"led7_seg_o<6>" LOC = "G13" ; # segment 'b', active
low
NET
"led7_seg_o<5>" LOC = "N15" ; # segment 'c', active
low
NET
"led7_seg_o<4>" LOC = "P15" ; # segment 'd', active
low
NET
"led7_seg_o<3>" LOC = "R16" ; # segment 'e', active
low
NET
"led7_seg_o<2>" LOC = "F13" ; # segment 'f', active
low
NET
"led7_seg_o<1>" LOC = "N16" ; # segment 'g', active
low
NET
"led7_seg_o<0>" LOC = "P16" ; # segment 'dp', active
low
# SRAM pinout
# Address bits
common for both chips
NET
"addr_o<17>" LOC = "L3" ;
NET
"addr_o<16>" LOC = "K5" ;
NET
"addr_o<15>" LOC = "K3" ;
NET
"addr_o<14>" LOC = "J3" ;
NET
"addr_o<13>" LOC = "J4" ;
NET
"addr_o<12>" LOC = "H4" ;
NET
"addr_o<11>" LOC = "H3" ;
NET
"addr_o<10>" LOC = "G5" ;
NET
"addr_o<9>" LOC = "E4" ;
NET
"addr_o<8>" LOC = "E3" ;
NET
"addr_o<7>" LOC = "F4" ;
NET
"addr_o<6>" LOC = "F3" ;
NET
"addr_o<5>" LOC = "G4" ;
NET "addr_o<4>"
LOC = "L4" ;
NET
"addr_o<3>" LOC = "M3" ;
NET
"addr_o<2>" LOC = "M4" ;
NET
"addr_o<1>" LOC = "N3" ;
NET
"addr_o<0>" LOC = "L5" ;
# Data and
control bits
# IC10
NET
"data_io<31>" LOC = "R1" ; # IC 10 IO15
NET
"data_io<30>" LOC = "P1" ; # IC 10 IO14
NET
"data_io<29>" LOC = "L2" ; # IC 10 IO13
NET
"data_io<28>" LOC = "J2" ; # IC 10 IO12
NET
"data_io<27>" LOC = "H1" ; # IC 10 IO11
NET
"data_io<26>" LOC = "F2" ; # IC 10 IO10
NET
"data_io<25>" LOC = "P8" ; # IC 10 IO9
NET
"data_io<24>" LOC = "D3" ; # IC 10 IO8
NET
"data_io<23>" LOC = "B1" ; # IC 10 IO7
NET
"data_io<22>" LOC = "C1" ; # IC 10 IO6
NET
"data_io<21>" LOC = "C2" ; # IC 10 IO5
NET
"data_io<20>" LOC = "R5" ; # IC 10 IO4
NET
"data_io<19>" LOC = "T5" ; # IC 10 IO3
NET
"data_io<18>" LOC = "R6" ; # IC 10 IO2
NET
"data_io<17>" LOC = "T8" ; # IC 10 IO1
NET
"data_io<16>" LOC = "N7" ; # IC 10 IO0
NET
"ce10_o" LOC = "P7" ; # chip enable of IC10
NET
"ub10_o" LOC = "T4" ; # upper byte enable of IC10
NET
"lb10_o" LOC = "P6" ; # lower byte enable of IC10
# IC11
NET
"data_io<15>" LOC = "N1" ; # IC 11 IO15
NET
"data_io<14>" LOC = "M1" ; # IC 11 IO14
NET
"data_io<13>" LOC = "K2" ; # IC 11 IO13
NET
"data_io<12>" LOC = "C3" ; # IC 11 IO12
NET
"data_io<11>" LOC = "F5" ; # IC 11 IO11
NET
"data_io<10>" LOC = "G1" ; # IC 11 IO10
NET
"data_io<9>" LOC =
"E2" ; # IC 11 IO9
NET
"data_io<8>" LOC =
"D2" ; # IC 11 IO8
NET
"data_io<7>" LOC =
"D1" ; # IC 11 IO7
NET
"data_io<6>" LOC =
"E1" ; # IC 11 IO6
NET
"data_io<5>" LOC =
"G2" ; # IC 11 IO5
NET
"data_io<4>" LOC =
"J1" ; # IC 11 IO4
NET
"data_io<3>" LOC =
"K1" ; # IC 11 IO3
NET
"data_io<2>" LOC =
"M2" ; # IC 11 IO2
NET
"data_io<1>" LOC =
"N2" ; # IC 11 IO1
NET
"data_io<0>" LOC =
"P2" ; # IC 11 IO0
NET
"ce11_o" LOC = "N5" ; # chip enable of IC11
NET
"ub11_o" LOC = "R4" ; # upper byte enable of IC11
NET
"lb11_o" LOC = "P5" ; # lower byte enable of IC11
# common
control bits
NET
"oe_o" LOC = "K4" ; # output enable
NET
"we_o" LOC = "G3" ; # write enable
#
Uzupełnienie pliku UCF o ograniczenia czasowe:
# deklaracja nazwy specyfikacji jako TNM_NET = clk_in
NET "clk_i" TNM_NET = "clk_in" ;
# deklaracja zegara jako 20ns o wypełnieniu 50%
# oznacza to, że układ będzie tak implementowany, że zachowane
zostaną ograniczenia
# czasowe wewnątrz rdzenia tak aby przerzutniki dołączone do clk_i
# miały spełnione czasy setup i hold
# dla 20ns zegara
TIMESPEC "TS_clk_i" = PERIOD "clk_in" 20ns
HIGH 50%;
# ograniczenie na zegar CLKFX po wstawieniu DCM jest automatycznie
dodawane przez implementator
# opóźnienie globalne - ustalenie, że wszystkie sygnały generowane
na zewnątrz mają
# być opóźnione o 30ns w stosunku do zegara clk_i
OFFSET = OUT 30ns AFTER "clk_i";
# opóźnienie globalne - ustalenie, że wszystkie sygnały pobierane
z zewnątrz
# są o 30ns szybciej niż zegar clk_i
OFFSET = IN 30ns BEFORE "clk_i";
# sprecyzowanie opóźnień dla wyprowadzeń asynchronicznych
# najpierw sygnały zostały zgrupowane w grupy o nazwach SIG_OUT
SIG_IN a następnie utworzono
# ograniczenie czasowe dla tej grupy
TIMEGRP "SIG_OUT" = PADs ("addr_*"
"data_*" "we_o" "oe_o");
TIMESPEC TS01 = from FFS to SIG_OUT 8ns;
TIMEGRP "SIG_IN" = PADs ("data_*");
TIMESPEC TS02 = from SIG_IN to FFS 3ns;