We współczesnych mikroarchitekturach ze zmianą nazw rejestrów koszt wdrożenia flag lub nie flag jest dość podobny. Główną różnicą, o której mogę myśleć, jest to, że niektóre flagi wskazują cechy wartości (czy wartość jest ujemna? Czy wartość wynosi zero? Czy wartość ma parzystą czy nieparzystą parzystość?), Podczas gdy niektóre reprezentują zdarzenie, które wystąpiło podczas poprzedniej operacji (czy instrukcja add miała wykonanie lub przepełnienie?) Doprowadziło to do niezbyt idealnej sytuacji w MIPS, gdy chcieliśmy symulować 64-bitowe dodawanie w architekturze 32-bitowej (lub 128-bitowe dodawanie w Architektura 64-bitowa.) Na większości architektur z flagą carry jest coś specjalnegoadd-with-carry
instrukcja, która zawiera flagę carry z poprzedniej instrukcji add. To sprawia, że symulowanie arytmetyki wieloprecyzyjnej jest stosunkowo niedrogie na wielu architekturach z rejestrami flag.
Z drugiej strony, testowanie rejestru N-bitowego na zero lub niezerowe jest w rzeczywistości zaskakująco drogie. Aby przetestować N-bitowy rejestr na zero, należy wykonać N-bitową operację NOR, która wymaga obliczenia poziomów logiki . W architekturach z rejestrami flag dodatkowa logika do obliczania wartości zerowej / niezerowej na końcu etapu ALU może spowodować wolniejsze działanie zegara (lub zmusić ALU do wykonania dwóch operacji cyklu). Myślę, że z tego powodu niektóre architektury, podobnie jak SPARC, miały dwie wersje każdej operacji arytmetycznej, jedną, która ustawiała flagi, a drugą nie.O ( logN.)
Ale MIPS niczego tu nie zapisuje. Właśnie przenieśli problem gdzieś indziej. Na MIPS jest branch-on-equal
instrukcja. Oznacza to, że instrukcja rozgałęzienia musi faktycznie posiadać etap ALU (w tym coś w rodzaju xor
operacji bitowej , po której następuje nor
redukcja do pojedynczego bitu równego / nierównego) przed ustaleniem, w którą stronę idzie gałąź.
Architektura DEC Alpha próbowała rozdzielić różnicę za pomocą lewy. DEC Alpha nie ma rejestrów flag, ale także nie ma branch-on-equal
instrukcji. Zamiast tego wszystkie instrukcje oddziału patrzą na stan jednego rejestru ogólnego przeznaczenia. Tam jest branch-on-zero
, branch-on-not-zero
, branch-on-less-than-zero
, itd. Sztuką jest to, że można dać każdy ogólnego przeznaczenia zarejestrować dodatkowy bit 65. informujący, czy pozostałe 64 bity są zerowe lub nie. To bardziej przypomina rejestrowanie flag: wszystkie instrukcje rozgałęzienia patrzą na pojedynczy bit (który jest już obliczony), aby podjąć decyzję, ale teraz wróciłeś do zastanawiania się, jak obliczyć ten dodatkowy bit wskaźnika zero podczas normalnego ALU cykl. (I nadal nie można wykonywać arytmetyki wieloprecyzyjnej, patrząc tylko na flagę carry z poprzedniej operacji).
1 Z perspektywy ISA
Posiadanie instrukcji testowych, które ustawiają tylko flagi, jest tylko sposobem na zmniejszenie ciśnienia rejestrowego w architekturach głodujących rejestr. Jeśli masz wystarczającą liczbę rejestrów, po prostu zmodyfikuj jeden z nich i zignoruj wynik. Sztuczka polegająca na tym, że rejestr 0 z wartością wejściową 0 jest po prostu sztuczką kodującą wygodną, gdy masz wystarczającą liczbę rejestrów, że ustalenie jednego z nich na 0 jest lepsze niż zwiększenie liczby instrukcji. Wygodne jest także użycie go jako celu (zmniejsza liczbę fałszywych zależności).
Kodowanie ponownie. Jeśli kodujesz warunek w skokach, będziesz miał skoki z 3 operandami (dwoma do porównania i celem skoku), z których dwa chcesz być natychmiastowymi wartościami, jednym, który chcesz być tak duży, jak możliwe (skoki mają często swój własny format kodowania, dzięki czemu cel może użyć jak największej liczby bitów). Lub upuszczasz możliwości.
Korzystanie z flag daje więcej możliwości ich ustawienia. Nie tylko operacje porównania mogą ustawiać flagi, ale cokolwiek chcesz. (Z zastrzeżeniem, że im więcej operacji ustawiasz flagi, tym bardziej musisz uważać, aby ostatnia operacja, która ustawiła flagi, była tą, której potrzebujesz). Jeśli masz flagi, jesteś w stanie przetestować liczbę warunków (często 16) pomnożoną przez liczbę instrukcji, które są w stanie ustawić flagi (jeśli nie używasz flag, kończy się tak wiele skoków warunkowych, jak mieć rzeczy do przetestowania lub są rzeczy, których nie pozwala się tak łatwo testować (na przykład nosić lub przepełniać).
2 Z perspektywy implementatora
Testowanie flag jest łatwe i może być wykonane szybko. Im bardziej złożony jest Twój test, tym większy będzie on miał wpływ na czas cyklu (lub strukturę rurociągu, jeśli jesteś w trybie potokowym). Jest to szczególnie prawdziwe w przypadku prostszych implementacji, gdy dojdziesz do wysokiej klasy procesora wykorzystującego wszystkie sztuczki z książki, efekt jest dość minimalny.
Posiadanie flag oznacza, że wiele instrukcji ma wiele wyników (naturalny wynik i każda ze zmodyfikowanych flag). A w przypadku POV z mikroarchitekturą wiele wyników jest złych (musisz śledzić ich powiązanie). Kiedy masz tylko jeden zestaw flag, które wprowadzają zależności (niepotrzebne, jeśli flaga nie jest następnie używana), musisz poradzić sobie w ten czy inny sposób. Ponownie jest to szczególnie prawdziwe w przypadku prostszych implementacji, gdy dojdziesz do wysokiej klasy procesora wykorzystującego wszystkie sztuczki z książki, dodatkowe trudności są zmniejszone przez resztę procesora.
źródło
Na maszynie 32-bitowej instrukcja „add-with-carry” stosowana jako część sekwencji precyzji addycji z wieloma precyzjami musi zaakceptować argumenty o wartości 65 bitów i obliczyć sumę 33 bitów. Specyfikacje rejestru źródłowego określą, skąd powinny pochodzić 64 bity operandu, a specyfikacja rejestru docelowego powie, gdzie powinny pójść dolne 32 bity wyniku, ale co zrobić z operandem „dodaj jeden dodatkowy” lub górnym bitem wyniku? Umożliwienie określenia w ramach instrukcji, skąd powinien pochodzić dodatkowy argument i gdzie powinien przejść dodatkowy bit wyniku, byłoby umiarkowanie użyteczne, ale ogólnie nie byłoby tak przydatne, aby uzasadnić dodatkowe pole w kodzie operacji. Posiadanie stałej „lokalizacji” do obsługi flagi przenoszenia może być nieco niewygodne z perspektywy planowania instrukcji, ale „
Gdyby ktoś próbował zaprojektować zestaw instrukcji, aby umożliwić arytmetykę z wieloma precyzjami, ale każda instrukcja była ograniczona do dwóch 32-bitowych operandów i jednego 32-bitowego operandu docelowego, można zaimplementować 64-bitowe „dodawanie” w czterech instrukcjach: „set r5 do 1, jeśli r0 + r2 niesie lub zero w przeciwnym razie; oblicz r4 = r1 + r3; oblicz r5 = r4 + r5; oblicz r4 = r0 + r2 ", ale przekroczenie tego wymagałoby trzech instrukcji dla każdego dodatkowego słowa. Posiadanie flagi przeniesienia dostępnej jako dodatkowe źródło i miejsce docelowe zmniejsza koszt do jednej instrukcji na słowo.
Zwróć uwagę, że posiadanie bitu instrukcji kontrolującego, czy instrukcja aktualizuje rejestr flagi, może ułatwiać wykonywanie poza kolejnością, ponieważ instrukcje, które używają lub modyfikują bity flagi, muszą utrzymywać swoją sekwencję względem siebie, ale instrukcje, które nie mogą swobodnie się przestawiać. Biorąc pod uwagę sekwencję:
jednostka wykonawcza mogłaby dość łatwo rozpoznać, że trzecia instrukcja mogła zostać wykonana bez konieczności oczekiwania na odczyt danych
[r1]
, ale gdyby druga instrukcja byłaadds r0,r0,r2
możliwa, byłoby to możliwe tylko wtedy, gdyby jednostka wykonawcza mogła zapewnić, że do tego czasu cokolwiek spróbuje użyć flagi, flaga zerowa utrzyma wartość ustaloną w trzeciej instrukcji, ale flaga przenoszenia zatrzyma wartość w drugiej instrukcji.źródło
Prosta odpowiedź ... szybka, tania operacja pamięci, która nie wymaga absolutnie żadnego użycia wewnętrznej magistrali oprócz samej instrukcji. Może być używany jako bool stosu bez stosu lub bitu procesu, bez pamięci.
źródło