W wielu aplikacjach procesor, którego wykonywanie instrukcji ma znaną zależność czasową z oczekiwanymi bodźcami wejściowymi, może obsłużyć zadania wymagające znacznie szybszego procesora, gdyby związek był nieznany. Na przykład w projekcie, w którym użyłem PSOC do wygenerowania wideo, użyłem kodu do wyprowadzenia jednego bajtu danych wideo co 16 taktów procesora. Ponieważ testowanie, czy urządzenie SPI jest gotowe i rozgałęzienie, jeśli nie, IIRC zajmie 13 zegarów, a ładowanie i przechowywanie danych wyjściowych zajmie 11, nie było możliwości przetestowania urządzenia pod kątem gotowości między bajtami; zamiast tego po prostu ustawiłem, aby procesor wykonał dokładnie kod o wartości 16 cykli dla każdego bajtu po pierwszym (wydaje mi się, że użyłem rzeczywistego obciążenia indeksowanego, sztucznego obciążenia indeksowanego i magazynu). Pierwszy zapis SPI każdej linii miał miejsce przed rozpoczęciem wideo, a dla każdego kolejnego zapisu było 16-cyklowe okno, w którym zapis mógł wystąpić bez przepełnienia lub niedopełnienia bufora. Pętla rozgałęziająca wygenerowała 13-cyklowe okno niepewności, ale przewidywalne wykonanie 16-cyklowe oznaczało, że niepewność dla wszystkich kolejnych bajtów mieściłaby się w tym samym oknie 13-cyklowym (które z kolei mieszczą się w 16-cyklowym oknie, w którym zapis może być akceptowalny pojawić się).
W przypadku starszych procesorów informacje o taktowaniu instrukcji były jasne, dostępne i jednoznaczne. W przypadku nowszych układów ARM informacje o taktowaniu wydają się znacznie bardziej niejasne. Rozumiem, że kiedy kod jest wykonywany z pamięci flash, zachowanie buforowania może znacznie utrudnić przewidywanie, więc spodziewałbym się, że każdy kod liczony w cyklu powinien być wykonywany z pamięci RAM. Jednak nawet podczas wykonywania kodu z pamięci RAM specyfikacje wydają się nieco niejasne. Czy stosowanie kodu liczonego w cyklu jest nadal dobrym pomysłem? Jeśli tak, jakie są najlepsze techniki, aby działał niezawodnie? W jakim stopniu można bezpiecznie założyć, że sprzedawca mikroukładów nie zamierza po cichu wsunąć „nowego ulepszonego” układu, który w niektórych przypadkach odcina cykl wykonywania niektórych instrukcji?
Zakładając, że następująca pętla zaczyna się na granicy słów, jak określić na podstawie specyfikacji dokładnie, ile to zajmie (załóżmy, że Cortex-M3 z pamięcią stanu zerowego oczekiwania; nic innego o systemie nie powinno mieć znaczenia dla tego przykładu).
myloop: mov r0, r0; Krótkie proste instrukcje, aby umożliwić pobranie większej liczby instrukcji mov r0, r0; Krótkie proste instrukcje, aby umożliwić pobranie większej liczby instrukcji mov r0, r0; Krótkie proste instrukcje, aby umożliwić pobranie większej liczby instrukcji mov r0, r0; Krótkie proste instrukcje, aby umożliwić pobranie większej liczby instrukcji mov r0, r0; Krótkie proste instrukcje, aby umożliwić pobranie większej liczby instrukcji mov r0, r0; Krótkie proste instrukcje, aby umożliwić pobranie większej liczby instrukcji dodaje r2, r1, # 0x12000000; Instrukcja 2-słowowa ; Powtórz następujące czynności, prawdopodobnie z innymi operandami ; Będzie dodawał wartości, dopóki nie pojawi się przeniesienie itcc addscc r2, r2, # 0x12000000; 2-wyrazowa instrukcja plus dodatkowe „słowo” dla itcc itcc addscc r2, r2, # 0x12000000; 2-wyrazowa instrukcja plus dodatkowe „słowo” dla itcc itcc addscc r2, r2, # 0x12000000; 2-wyrazowa instrukcja plus dodatkowe „słowo” dla itcc itcc addscc r2, r2, # 0x12000000; 2-wyrazowa instrukcja plus dodatkowe „słowo” dla itcc ; ... itd., z bardziej warunkowymi instrukcjami składającymi się z dwóch słów sub r8, r8, # 1 bpl myloop
Podczas wykonywania pierwszych sześciu instrukcji rdzeń będzie miał czas na pobranie sześciu słów, z których trzy zostaną wykonane, aby mogły zostać pobrane maksymalnie trzy słowa. Kolejne instrukcje składają się z trzech słów, więc rdzeń nie będzie mógł pobrać instrukcji tak szybko, jak są one wykonywane. Spodziewałbym się, że niektóre instrukcje „it” zajmą cykl, ale nie wiem, jak przewidzieć, które z nich.
Byłoby miło, gdyby ARM mógł określić pewne warunki, w których czas rozkazu „it” byłby deterministyczny (np. Jeśli nie ma stanów oczekiwania lub rywalizacji o magistralę kodową, a poprzednie dwie instrukcje są instrukcjami rejestru 16-bitowego itp.) ale nie widziałem żadnej takiej specyfikacji.
Przykładowa aplikacja
Załóżmy, że ktoś próbuje zaprojektować płytę główną dla Atari 2600 do generowania komponentowego wyjścia wideo w rozdzielczości 480P. 2600 ma zegar pikseli 3,579 MHz i zegar procesora 1,19 MHz (zegar punktowy / 3). W przypadku komponentowego wideo 480P, każda linia musi być wyprowadzona dwukrotnie, co oznacza wyjście z zegarem kropkowym 7,168 MHz. Ponieważ układ wideo Atari (TIA) generuje jeden z 128 kolorów, wykorzystując jako 3-bitowy sygnał luma plus sygnał fazowy o rozdzielczości około 18ns, trudno byłoby dokładnie określić kolor, patrząc tylko na wyjścia. Lepszym rozwiązaniem byłoby przechwytywanie zapisów do rejestrów kolorów, obserwowanie zapisanych wartości i wprowadzanie do każdego rejestru wartości luminancji TIA odpowiadającej numerowi rejestru.
Wszystko to można zrobić za pomocą FPGA, ale niektóre dość szybkie urządzenia ARM mogą być znacznie tańsze niż FPGA z wystarczającą ilością pamięci RAM, aby obsłużyć niezbędne buforowanie (tak, wiem, że w przypadku woluminów taka rzecz mogłaby zostać wyprodukowana koszt nie jest prawdziwy czynnik). Wymaganie od ARM monitorowania przychodzącego sygnału zegarowego znacznie zwiększy jednak wymaganą szybkość procesora. Przewidywalne liczby cykli mogłyby uczynić rzeczy czystszymi.
Stosunkowo proste podejście polegałoby na tym, aby CPLD obserwował procesor i TIA i generował 13-bitowy sygnał synchronizacji RGB +, a następnie kazałby ARM DMA pobierać 16-bitowe wartości z jednego portu i zapisywać je na drugim z odpowiednim taktowaniem. Ciekawym wyzwaniem projektowym byłoby sprawdzenie, czy tani ARM mógłby zrobić wszystko. DMA może być użytecznym aspektem podejścia typu „wszystko w jednym”, jeśli można przewidzieć jego wpływ na liczbę cykli procesora (szczególnie jeśli cykle DMA mogą się zdarzyć w cyklach, gdy szyna pamięci jest w przeciwnym razie bezczynna), ale w pewnym momencie procesu ARM musiałby wykonywać funkcje wyszukiwania tabeli i oglądania magistrali. Zauważ, że w przeciwieństwie do wielu architektur wideo, w których rejestry kolorów są zapisywane w odstępach czasu wygaszania, Atari 2600 często zapisuje rejestry kolorów podczas wyświetlanej części ramki,
Być może najlepszym rozwiązaniem byłoby użycie kilku dyskretnych układów logicznych do identyfikacji zapisów kolorów i wymuszenie niższych bitów rejestrów kolorów do odpowiednich wartości, a następnie użycie dwóch kanałów DMA do próbkowania danych wejściowych magistrali procesora i danych wyjściowych TIA oraz trzeci kanał DMA do generowania danych wyjściowych. Procesor będzie wtedy mógł przetwarzać wszystkie dane z obu źródeł dla każdej linii skanowania, wykonać niezbędne tłumaczenie i buforować je w celu uzyskania danych wyjściowych. Jedynym aspektem obowiązków adaptera, które musiałyby się zdarzyć w „czasie rzeczywistym”, byłoby zastąpienie danych zapisanych w COLUxx, i które można by załatwić za pomocą dwóch wspólnych układów logicznych.
źródło
Informacje o czasie są dostępne, ale, jak zauważyłeś, czasami mogą być niejasne. W sekcji 18.2 i tabeli 18.1 Technicznej instrukcji obsługi Cortex-M3 znajduje się wiele informacji na temat czasu , na przykład ( tutaj pdf ), a fragment tutaj:
które podają listę warunków dla maksymalnego czasu. Czas dla wielu instrukcji zależy od czynników zewnętrznych, z których niektóre pozostawiają niejednoznaczności. Podkreśliłem każdą dwuznaczność, którą znalazłem w następującym fragmencie z tej sekcji:
Dla wszystkich przypadków użycia będzie bardziej złożony niż „Ta instrukcja to jeden cykl, ta instrukcja to dwa cykle, to jest jeden cykl ...” licząc możliwe w prostszych, wolniejszych, starszych procesorach. W niektórych przypadkach użycia nie napotkasz żadnych dwuznaczności. Jeśli napotkasz dwuznaczności, sugeruję:
Wymagania te prawdopodobnie stanowią odpowiedź na twoje pytanie: „Nie, to nie jest dobry pomysł, chyba że napotkane trudności są warte swojej ceny” - ale już o tym wiesz.
źródło
Jednym ze sposobów obejścia tego problemu jest użycie urządzeń o deterministycznych lub przewidywalnych czasach, takich jak śmigło Parallax i układy XMOS:
http://www.parallaxsemiconductor.com/multicoreconcept
http://www.xmos.com/
Liczenie cykli działa bardzo dobrze w przypadku śmigła (należy użyć języka asemblera), podczas gdy urządzenia XMOS mają bardzo potężne narzędzie programowe, XMOS Timing Analyzer, który działa z aplikacjami napisanymi w języku programowania XC:
https://www.xmos.com/download/public/XMOS-Timing-Analyzer-Whitepaper%281%29.pdf
źródło
Liczenie cykli staje się coraz bardziej problematyczne, gdy uciekasz od mikrokontrolerów niskiego poziomu i przechodzisz do bardziej ogólnych procesorów obliczeniowych. Pierwsze zwykle mają dobrze określone terminy instrukcji, częściowo z powodów, dla których umieszczasz witrynę. Jest tak również dlatego, że ich architektura jest dość prosta, więc czasy instrukcji są stałe i można je poznać.
Dobrym przykładem tego jest większość PIC Microchip. Serie 10, 12, 16 i 18 mają bardzo dobrze udokumentowane i przewidywalne czasy instrukcji. Może to być przydatna funkcja w małych aplikacjach kontrolnych, do których przeznaczone są te układy.
Kiedy unikasz bardzo niskich kosztów, a projektant może w związku z tym poświęcić nieco więcej miejsca na chipy, aby uzyskać większą prędkość z bardziej egzotycznej architektury, możesz także uniknąć przewidywalności. Spójrz na nowoczesne warianty x86 jako ekstremalne przykłady tego. Istnieje kilka poziomów pamięci podręcznej, ożywienia pamięci, pobierania z wyprzedzeniem, potokowania itp., Co sprawia, że liczenie cykli instrukcji jest prawie niemożliwe. W tej aplikacji nie ma to jednak znaczenia, ponieważ klient jest zainteresowany dużą szybkością, a nie przewidywalnością czasu instrukcji.
Możesz nawet zobaczyć ten efekt w pracy w wyższych modelach Microchip. 24-bitowy rdzeń (serie 24, 30 i 33) ma w dużej mierze przewidywalne taktowanie instrukcji, z wyjątkiem kilku wyjątków, gdy istnieją treści magistrali rejestrów. Na przykład w niektórych przypadkach maszyna wstawia przeciągnięcie, gdy następna instrukcja wykorzystuje rejestr z niektórymi trybami adresowania pośredniego, których wartość została zmieniona w poprzedniej instrukcji. Ten rodzaj przeciągnięcia jest niezwykły na dsPIC i przez większość czasu można go zignorować, ale pokazuje, jak te rzeczy się wkradają, ponieważ projektanci starają się zapewnić ci szybszy i bardziej wydajny procesor.
Więc podstawowa odpowiedź jest taka, że jest to część kompromisu przy wyborze procesora. W przypadku małych aplikacji sterujących możesz wybrać coś małego, taniego, o małej mocy i przewidywalnym czasie instrukcji. Gdy potrzebujesz większej mocy obliczeniowej, zmienia się architektura, więc musisz zrezygnować z przewidywalnego czasu instrukcji. Na szczęście nie stanowi to większego problemu w przypadku aplikacji wymagających większej mocy obliczeniowej i zastosowań ogólnych, więc uważam, że kompromisy działają całkiem dobrze.
źródło
Tak, nadal możesz to zrobić, nawet na ARM. Największy problem z ARM polega na tym, że ARM sprzedaje rdzenie, a nie układy scalone, a taktowanie rdzenia jest znane, ale to, co owija go dostawca układów, różni się od dostawcy do dostawcy, a czasem od rodziny układów do innego w obrębie dostawcy. Tak więc konkretny układ od konkretnego dostawcy może być dość deterministyczny (jeśli na przykład nie używasz pamięci podręcznych), ale trudniej go przenieść. W przypadku 5 zegarów tutaj i 11 zegarów przy użyciu timerów jest problematyczne, ponieważ liczba instrukcji potrzebnych do próbkowania timera i ustalenia, czy upłynął limit czasu. Z dźwięków z poprzednich doświadczeń programistycznych jestem skłonny założyć się, że prawdopodobnie debuguję za pomocą oscyloskopu, tak jak ja, więc możesz wypróbować ciasną pętlę na chipie z częstotliwością zegara, spojrzeć na spi lub i2c lub jakikolwiek kształt fali, dodać lub usuń nops, zmień liczbę razy w pętli i po prostu dostrój. Jak w przypadku każdej platformy, nieużywanie przerw znacznie pomaga deterministyczny charakter wykonywania instrukcji.
Nie, nie jest to tak proste jak PIC, ale wciąż całkiem wykonalne, szczególnie jeśli opóźnienie / taktowanie zbliża się do częstotliwości taktowania procesora. Wielu dostawców opartych na ARM pozwala zwielokrotnić częstotliwość zegara i uzyskać powiedzmy 60 MHz z odniesienia 8 MHz, więc jeśli potrzebujesz jakiegoś interfejsu 2 MHz zamiast robienia czegoś co 4 instrukcje, możesz zwiększyć zegar (jeśli masz budżet energetyczny), a następnie użyj timera i daj sobie mnóstwo zegarów, aby robić również inne rzeczy.
źródło