Det handlar väl om "profilering", d.v.s. vad den är optimerad för. Det är lite som att säga "vad skiljer mellan en speldator och en videoredigeringsdator?".
Jag har inte pysslat så mycket med "riktiga" DSP:er, men gjorde mitt exjobb (för 20 år sen) på en TMS 320-C80 som väl kan ses som lite representativ.
Den hade fem processorer inom en och samma kapsel, en Master Processor (RISC) och fyra stycken Parallel Processors (RISC).
Dessa PP:er hade en hel del "intressanta" instruktioner. De jobbade med 32-bitars register men det fanns funktioner för att hantera dessa som fyra stycken bytes, så en PP kunde alltså i princip göra fyra stycken 8-bitars additioner parallellt.
De hade också dubbla ALU:er, en enklare som användes för adressberäkningar och en "huvud-ALU". Varje PP hade eget minne (8 KB) och så fanns det även gemensamt minne (50 KB). Extern minnesaccess gjordes med särskilda instruktioner av en särskild controller och hade en viss latens.
Massa olika register, både dataregister och adressregister (både globala och lokala).
Hittade datablad, den hade visst en videocontroller också (men den använde jag aldrig, jag skrev ett filter).
Jag har sparat ett rätt intressant kodexempel (som man knappast hade fått med en kompilator, men ändå:) ) där man räknar ut kvadratiskt medelvärde på en klockcykel per invärde (i alla fall om det är mer än 4 invärden, eftersom det tar fyra instruktioner per varv i loopen:) ).
Förklarar varje instruktion för sig. (De där || betyder alltså att det är något som görs parallellt med det som står innan)
Först har vi en "splittad" subtraktion. Registren är 32-bitars men är delade så att det bara är bytevis subtraktion. m:et står för multiple och c står för att vi vill att multiple flags (mf-registrets) lägsta fyra bitar skall sättas som carry-flaggorna.
Sen har vi en unsigned halfword (uh) extract av det lägre (0) halfwordet.
Och slutligen en "fuskis". d5 används som ett dummyregister. Vi vill bara räkna upp adressregsitret med indexregistrer. Detta sköts av en av adressenheteerna, och behöver alltså inte ALUn. d5 får visserligen också värdet av a0, men det används alltså inte.
Kod: Markera allt
d4= m 0 + ((d7&@mf)|(-d7&~@mf))
|| x1=uh1 d4
|| d6=*a1++
m:et står även här för multiple, d.v.s. att varje byte hanteras för sig.
@mf betyder Expanded Multiple Flags. De fyra lägsta bitarna i mf sattes till carry. Expansionen innebär att var och en av de bitarna fyller upp en byte.
Alltså, binärt: 0000000000000000000000000000abcd blir aaaaaaaabbbbbbbbccccccccdddddddd
Resultatet blir att absolutbeloppet för varje byte i d7 hamnar som bytes i d4.
Sen har vi en till halfword extract, den här gången det övre halfwordet (uh1=unsigned halfword 1).
Slutligen laddas d6 från adressen som a1 pekar på, och a1 räknas upp (det funkar precis som i C).
Kod: Markera allt
d4= um d4*d4
|| d7=ealu(d3+d4>>u16)
|| x0=uh0 d2
|| d5=&*(a0+=x0)
En unsigned split (m=multiple) multiply. Bytesen i d4 kvadreras alltså och blir i samma veva halfwords. Det är alltså bara de två lägsta bytesen som kvadreras.
ealu (extended ALU) är en lite speciall sak. Operationen anges i d0-registret (den är satt tidigare), och opkoden innehåller alltså bara information om att det är en ealu-operation och vilka register som skall användas. Den används för relativt komplexa operationer, men det går alltså inte att skriva vad som helst inom parantesen, det måste vara nån av de operationer som stöds. MEN man kan välja vilka register man vill.
>>u16 betyder 16 steg unsigned shift right (precis som i C). d3 är laddad med noll! Orsaken förklaras nedan, men resultatet blir i alla fall en halfword extract (övre halvordet från d4 hamnar i d7).
En "vanlig" halfword extract (0 = lower) och en adressaritmetisk beräkning (öka a0 med x0).
Kod: Markera allt
d2= um d7*d7
|| d1=ealu(d1+d2>>u16)
|| d5=&*(a0+=x1)
|| d5=*a8++
En kvadrering till, precis som ovan.
Och ännu en ealu. Nu är grejen att den här ealun har SAMMA operationskod som den förra (det syns på formeln inom parantesen). Vi använder alltså samma d0, men har andra register (de stod ju i opkoden). Det var alltså anledningen till att vi hade d3 som nollregister tidigare. Här används den inte som halfword extract, utan den summerar kvadrerade värden (de blev ju halfwords när de kvadrerades).
Sen har vi en adressaritmetisk pryl igen, och en load från minnet. Nu råkar bägge två ha samma destination, vilket ser lite konstigt ut. Som tur är (jo,
det visste de som skrev det här:) ) så finns det nåt som heter Write Priority. Det fungerar som så att Global Transfer (och det är det här, eftersom a8 är ett
globalt adressregister) har högre prioritet än ALU-operationer. Bägge prylarna utförs alltså, men bara den ena skriver till destinationen.