آیا میدانید عملیات ضرب در FPGA را میتوان به روشهای مختلفی پیادهسازی کرد؟
آیا با روشهای انتخاب نوع پیادهسازی عملیات ضرب در FPGA آشنا هستید؟
در این برنامه ویدئویی، شما را با دو روش اصلی پیادهسازی ضرب در FPGA آشنا میکنم و تکنیکهایی را به شما معرفی میکنم که به کمک آن میتوانید به نرمافزار پیادهسازی دستور دهید از کدام روش برای پیادهسازی استفاده کند.
More...
در این برنامه، همچنین بلوک DSP48 را که یکی از منابع سختافزاری موجود در FPGAها است معرفی میکنم. به کمک بلوک DSP48 شما میتوانید عملیات ضرب را به صورت کاملا بهینه از نظر سرعت، حجم و توان مصرفی در FPGA پیادهسازی کنید.
برای آشنایی با ماهیت و ساختار FPGAها این برنامه ویدئویی را ببینید…
به کمک بلوک DSP48 شما نه تنها میتوانید عملیات ضرب در FPGA را پیادهسازی کنید، بلکه انواع ترکیبهای ضرب و جمع را هم میتوانید فقط به کمک یک بلوک DSP48 پیادهسازی کنید.
در انتهای این برنامه، چند مثال کوچک هم از نحوه صحیح استفاده از بلوک DSP48 مطرح میکنم و نشان میدهم چطور میتوان محاسبات ریاضی شامل ضرب و جمعهای مرتبط را به کمک این ماجول پیادهسازی کرد.
ویدئو یا متن؟
محتوای این برنامه آموزشی، به دو صورت ویدئو و متن آماده شده است. اگر علاقمند به یادگیری این مطلب به صورت ویدئویی هستید، ویدئوی زیر را ببینید و اگر ترجیح میدهید آن را به صورت متن مطالعه کنید، ادامه این مطلب را بخوانید.
برای دانلود نسخه با کیفیت این ویدئو، روی دکمه زیر کلیک کنید:
در این مقاله قصد دارم دربارهی نحوهی پیادهسازی عمل ضرب، در FPGA صحبت کنم.
در ادامهی مقاله، با این موضوع مهم آشنا میشویم که وقتی ضرب در FPGA پیادهسازی میشود، در واقع، درون FPGA چه نوع مداری شکل میگیرد.
برای توصیف عملیات ضرب در زبان VHDL، باید در بخشی از کدتان عبارتی مانند زیر را بنویسید.
A <= B * C;
در این عبارت، سیگنال B در C ضرب شده و به سیگنال A منتقل شده است.
نحوهی پیادهسازی عملیات جمع، تفریق و ضرب در زبان VHDL
عملیات جمع، تفریق و ضرب در زبان VHDL تعریف شده هستند. بدین معنی که شما برای پیادهسازی یک مدار جمع، تفریق یا ضرب نیازی ندارید که خودتان مداری را طراحی کنید. بلکه فقط کافی است که در زبان VHDL، از عملگر های + ، - و * استفاده کنید؛ در این صورت، نرمافزار پیادهساز، با دیدن این عملگرها، مدار مناسبی را درون FPGA پیادهسازی میکند. در مورد ضرب، این کار با عملگر ستاره انجام میشود.
قبل از اینکه به طور خاص، دربارهی پیادهسازی عمل ضرب صحبت کنیم، اجازه دهید به طور کلی، دربارهی نحوهی پیادهسازی مدارات دیجیتال در FPGA، یک یادآوری داشته باشیم.
نحوهی پیادهسازی مدارات دیجیتال در FPGA
به صورت کلی، برای پیادهسازی مدارات دیجیتال در FPGA، از یکی از مهمترین منابع سختافزاری داخل FPGA، یعنی Look-Up Tableها (که به اختصار، LUT گفته میشوند)، استفاده میکنیم؛ در واقع، LUTها، حافظههای کوچکی هستند که میتوانید به کمک آنها، هر مدار دیجیتالی را پیادهسازی کنید.
در عمل، برای پیادهسازی یک مدار دیجیتالی بزرگ، آن را به بخشها و توابع کوچکی تقسیم میکنیم. هر کدام از توابع کوچک، به کمک یک LUT پیادهسازی میشود و با به هم متصل کردن این LUTها، میتوانید مدار اصلی را داشته باشید.
مثلاً اگر شما عبارت A=E+C را در بخشی از کد VHDL بنویسید، نرمافزار سنتز با دیدن این عبارت، یک مدار جمعکننده را به کمک یک یا چند LUT، در FPGA پیادهسازی میکند.
اما اگر عمل ضرب را در کدتان به کار ببرید، پیادهسازی ضربکننده، در مدار شما چگونه اتفاق میافتد؟
پیادهسازی ضربکننده
موضوع پیادهسازی عمل ضرب در FPGAها، کمی متفاوت از عمل جمع و تفریق است. عملیات ضرب، یکی از عملگرهای پرکاربرد در پیادهسازی دیجیتال است؛ خصوصاً اگر شما در حال پیادهسازی الگوریتمهای پردازش سیگنال باشید، عملیات ضرب در این الگوریتمها بسیار پرکاربرد است. به همین دلیل، معمولاً در FPGAها، به جز بلوکهای LUT، بلوکهای سختافزاری مخصوص دیگری نیز وجود دارند که فقط به طور خاص، عمل ضرب را انجام میدهند.
در FPGAهای شرکت XILINX، به بلوکهایی که سختافزار از پیش آماده شدهای برای پیادهسازی عملیات ضرب در FPGA دارند، بلوک DSP48 گفته میشود.
به طور پیشفرض، اگر در کد شما، عبارت ضرب استفاده شود، برای مثال، اگر در کد، عبارتی مانند A=B*C وجود داشته باشد، نرمافزار سنتز (Synthesis Tool)، آن را با استفاده از یک بلوک DSP48 (و نه با استفاده از LUTها)، پیادهسازی میکند.
همانطور که میدانیم، به کمک LUTها، میتوانیم هر سختافزار دیجیتالی را (از جمله ضربکنندهها را)، پیادهسازی کنیم.
قبل از اینکه راجع به این موضوع صحبت کنیم که چرا توسط نرمافزار سنتز، به صورت پیشفرض، به جای بلوکهای LUT، از بلوکهای سختافزاری DSP48 برای پیادهسازی ضربکننده استفاده میشود، اجازه دهید با خود این بلوک بیشتر آشنا شویم.
بلوک سختافزاری DSP48
در شکل زیر، یک دیاگرام بسیار ساده از بلوک سختافزاری DSP48 میبینید.
در مرکز این بلوک، عملیات ضرب را میبینید که در واقع همان موضوعی است که میخواهیم در مورد آن صحبت کنیم.
در این بلوک، به جز عمل ضرب، عملیات دیگری هم انجام میشود؛ مثلاً یکی از ورودیهای بلوک ضربکننده، میتواند حاصل جمع یا تفریق دو سیگنال دیگر باشد. خروجی ضربکننده نیز، میتواند با سیگنال دیگری جمع یا تفریق شود.
بنابراین، گرچه ما برای سادگی، به این بلوک، بلوک ضربکننده میگوییم، اما این بلوک در عمل کاری بیش از ضربکنندگی انجام میدهد.
این بلوک میتواند حاصلضرب یک عدد، در حاصلجمع دو عدد دیگر باشد. یا این بلوک میتواند حاصلضرب دو عدد را، با عدد دیگری جمع کند.
قابلیت جالب دیگری که در این بلوک وجود دارد، این است که اگر به جای اینکه یک سیگنال خارجی را به ورودی C این جمعکننده وصل کنیم، آن را به یک رجیستر که در خروجی جمعکننده وجود دارد وصل کنیم، مطابق شکل زیر، جمعکنندهی دوم تبدیل به یک Accumulator میشود.
بنابراین، مجموعهی بلوک جمعکننده و ضربکننده، یک بلوک Multiply & Accumulate خواهد بود؛ یعنی یک بلوک MAC داریم که در پیادهسازی الگوریتمهای پردازش سیگنال بسیار پرکاربرد است و من در انتهای همین مقاله، در این مورد، یک مثال کوچک را نشان خواهم داد.
همانطور که قبلاً گفته شد، ما برای سادگی، نام این بلوک را ضربکننده قرار دادیم، اما همانطور که دیدید، در واقع، این بلوک کارهایی بیش از یک ضربکننده را برای ما انجام میدهد. همچنین دیاگرامی که در شکل قبل دیدید، در واقع یک دیاگرام بسیار ساده شده از بلوک ضربکننده است و این بلوک تنظیمات و قابلیتهایی بیش از این دارد. در شکل زیر، مدار کامل این ضربکننده را میبینید.
در مرکز تصویر، ضربکنندهای که قسمت اصلی این مدار است را میببینید.
در سمت چپ تصویر، جمعکنندهای وجود دارد که قبل از ضربکننده است و در اصطلاح به آن Pre-Adder میگوییم؛ بدین معنی که قبل از اینکه عمل ضرب انجام شود، میتوانیم در صورت تمایل، دو عدد را با هم جمع کنیم و حاصل آن را در یک عدد دیگر ضرب کنیم.
در سمت راست تصویر، جمعکنندهای را که در خروجی ضربکننده قرار دارد، میبینید که در اصطلاح به آن، Post-Adder میگوییم.
این جمعکننده، میتواند به ما کمک کند که حاصلضربمان را با عدد دیگر جمع کنیم.
تعداد و قابلیتهای بلوکهای DSP48 در FPGAهای مختلف متفاوت است. برای مثال، در FPGA SPARTAN6 LX9، تعداد ۱۶ بلوک ضربکننده وجود دارد؛ اما در بعضی از FPGAهای جدیدتر، ممکن است بیش از هزار تا از این بلوک، وجود داشته باشد.
همچنین، با پیشرفت FPGAها، بلوکهای ضربکننده نیز مدام در حال پیشرفت و پیچیدهتر شدن هستند. در FPGA SPARTAN3، اسم این بلوک DSP48A بود. در FPGA SPARTAN6 این بلوک پیشرفتهتر و پیچیدهتر شد و DSP48A1 نام گرفت. به همین نسبت، در FPGAهای جدیدتر، این بلوک پیشرفتهتر و پیچیدهتر شده و پسوند نامش کمی تغییر کرده است.
اما اصلیترین مشخصهی بلوک DSP48، این است که میتواند یک عمل جمع را، با دو ورودی قبل از ضرب، انجام دهد که ما به این عمل جمع، Pre-Adder میگوییم.
یک ضربکننده، در مرکز بلوک DSP48 وجود دارد، که در FPGA SPARTAN 6 و بسیاری از FPGAهای دیگر، ورودیهای این ضربکننده، ۱۸ بیت در ۱۸ بیت است.
در FPGAهای پیشرفتهتر، این ورودی، ۱۸ بیت در ۲۵ بیت، و در یکی از FPGAها، ۱۸ بیت در ۲۷ بیت است. دانستن عرض بیت ورودیهای ضربکننده، برای شما به عنوان طراح بسیار مهم است؛ چون در صورت استفاده از FPGA SPARTAN 6، اگر عرض بیت هر یک از اعدادی که قصد ضرب آنها را دارید، بیش از 18 بیت باشد، باید از دو بلوک DSP48، به جای یک بلوک، استفاده کنید.
برای مثال، اگر شما در کاربردی بخواهید دو عدد به عرض بیتهای ۱۸ بیت و ۱۹ بیت را، درهم ضرب کنید، تنها به دلیل وجود یک بیت اضافه، مجبور هستید که از یک ضربکنندهی اضافه استفاده کنید.
در صورتی که با دانستن این نکته که ضربکنندهی DSP48، دارای ورودیهایی با عرض ۱۸ بیت در ۱۸ بیت است، ممکن است بتوانید آن یک بیت را از یکی از سیگنالها کم کنید و به جای استفاده از دو بلوک DSP48، فقط از یک بلوک DSP48 استفاده کنید.
نکتهی بعدی، مشخصهی مهم بلوکهای DSP48A1 است؛ خروجی این ضربکننده، میتواند با عدد دیگری جمع شود. این جمعکننده (Post-Adder)، ۴۸ بیتی است.
در واقع، به این دلیل اسم این بلوک DSP48 است که عرض بیت جمعکنندهی خروجی آن، ۴۸ بیت است. بنابراین شما میتوانید خروجی جمعکننده (که در واقع ۳۶ بیتی است) را با جمع، با یک عدد حداکثر ۴۸ بیتی، تا ۴۸ بیت گسترش دهید. این موضوع، قابلیت انعطاف زیادی را در پیادهسازی انواع مدارات مختلف به شما میدهد.
مزایای استفاده از بلوک DSP48 به جای LUTها
اما چرا از DSP48 استفاده میکنیم؟ چرا عملیات ضرب را همانند بقیهی مدار، به کمک بلوکهای LUT پیادهسازی نمیکنیم؟
همانطور که گفتم، ضرب، یکی از عملگرهای بسیار پرکاربرد در پیادهسازی دیجیتال است. استفاده از یک بلوک سختافزاری از پیش آماده شده، مانند بلوک DSP48، که مخصوص پیادهسازی ضرب است، مزایای مختلفی نسبت به استفاده از منابع عمومی FPGA (که LUTها هستند)، دارد.
اولین مزیت، این است که بلوک سختافزاری از پیش آماده شده یا همان DSP48 نسبت به LUT سرعت بسیار بیشتری دارد؛ بنابراین، در نهایت، مدار شما میتواند با سرعت بیشتری کار کند.
مزیت دوم، این است که شما به کمک استفاده از بلوک DSP48 میتوانید ابزار کمتری استفاده کنید؛ فرض کنید که در مدار شما، تعداد زیادی ضربکننده وجود داشته باشد و برای پیادهسازی همهی این ضربکنندهها، لازم باشد که از بلوکهایLUT استفاده شود. این مسئله، باعث میشود که تعداد زیادی از بلوکهای LUT موجود در FPGA، که در واقع باید برای پیادهسازی بخشهای دیگر مدار استفاده شوند، هدر شوند و فضای FPGA شما پر شود.
مزیت سوم، این است که بلوکهای DSP48 نسبت به حالتی که شما بخواهید از بلوکهای LUT استفاده کنید، توان مصرفی کمتری خواهند داشت.
به این دلایل، ما معمولاً دوست داریم، از بلوکهای DSP48 به جای بلوک LUT استفاده کنیم.
اگر عبارت A=B*C را در بخشی از کدتان استفاده کنید، نرمافزار سنتز، این کد را به کمک ضربکنندهی DSP48 پیادهسازی میکند.
شما باید فرمولهایی را که قصد پیادهسازی آنها را دارید، به گونهای ساده کنید که تمامی جمع و ضربها را بتوان تنها با یک بلوک DSP48 انجام داد و نیاز نباشد که در کنار این بلوک، از LUTها استفاده شود.
این موضوع، برای بهینهتر شدن کد شما، از نظر سرعت اجرا و توان مصرفی بسیار مفید خواهد بود.
برای اینکه بهتر متوجه شویم که بلوک DSP48 چگونه کد ما را پیادهسازی میکند، در ادامه، چند مثال کوچک را بررسی خواهیم کرد.
چند مثال از پیادهسازی عمل ضرب در FPGA
به شکل زیر توجه کنید؛
اگر بتوانید محاسبات موجود در مدارتان را به صورت شکل بالا درآورید، میتوانید آن را به صورت کامل، به کمک فقط یک بلوک DSP48، پیادهسازی کنید. در ادامه، با ذکر چند مثال این موضوع را تشریح خواهم کرد.
فرض کنید بخواهید عبارت زیر را پیادهسازی کنید:
P <= C + A * B;
بلوک DSP48 این عبارت را مطابق شکل زیر پیادهسازی میکند؛ سیگنال B از یک Pre-Adder عبور میکند اما تنظیماتی در این بلوک وجود دارد، که به صورت اتوماتیک، بلوک Pre-Adder را بایپس میکند و سیگنال B، مستقیماً به ضرب کننده وارد میشود. پس A، مستقیماً در B ضرب میشود و حاصلش با سیگنال C جمع میشود. بنابراین، برای پیادهسازی این عبارت، به جز یک بلوک DSP48، به بلوک دیگری نیاز نداریم.
یا مثلاً در عبارت زیر، یک محاسبهی اکومولاتوری را میبینید:
P <= P + A * B;
چراکه مقدار P برابر است با، مقدار قبلی P به اضافهی حاصل ضرب A*B. یعنی عمل Multiply & Accumulate را داریم. پیادهسازی این عبارت، همانطور که قبلاً به آن اشاره شد، مطابق شکل زیر است:
برای پیادهسازی این عبارت، به طور خودکار، به جای اینکه برای ورودی جمع کنندهی Post-Adder، از ورودی C استفاده شود، از خروجی P استفاده میشود. یعنی از خروجی P، مستقیماً مسیری به این جمعکننده وارد میشود. بنابراین در هر لحظه، یکی از ورودیهای جمع کنندهی Post-Adder، برابر با مقدار خروجی این جمع کننده در کلاک قبلی است.
عبارت بسیار پرکاربرد دیگری که به طور کامل با این بلوک ساخته میشود، به صورت زیر است:
P <= A*(B+D);
در واقع A را در عملوند دیگری (که خود این عملوند حاصل جمع دو سیگنال B و D است.) ضرب میکنیم. در این حالت از بلوک Pre-Adder استفاده میکنیم.
پیادهسازی فرمول (P=A*(B+D به کمک این ویژگی ضرب کنندهها، در پیادهسازی فیلترهای FIR که از خاصیت تقارن استفاده میکنند، بسیار پرکاربرد است. در آنجا، این خاصیت باعث میشود که مقدار منابع دیجیتالی مصرف شده در FPGA بسیار کاهش یابد. همچنین باعث افزایش سرعت کار فیلترها میشود. این موضوع را در دورهی پردازش سیگنال با FPGA به طور کامل توضیح دادهام.
چگونه یک عمل ضرب را بدون بلوک DSP48 محاسبه کنیم؟
همانطور که گفتم، در پیادهسازی عمل ضرب در کد VHDL، اگر از کارکتر * استفاده کنید، به صورت پیش فرض، این ضربکننده به کمک بلوک DSP48 محاسبه میشود.
اما فرض کنید به هر دلیلی تمایل داشته باشید که یک بلوک ضرب یا یک عملیات ضرب را به کمک LUT پیادهسازی کنید؛ برای این کار، باید مقدار قید خاصی به نام Multstyle را در نرمافزار سنتز تغییر دهیم. Multstyle، مخفف عبارت Multiply Style است.
برای تغییر مقدار پیشفرض آن، باید کد زیر را، در کد VHDL و قبل از Begin بخش Architecture وارد کنید.
attribute multstyle : string; attribute multstyle of Product_Int : signal is "logic";
شما باید به جای عبارت Product_Int در خط دوم کد، اسم سیگنالی که قرار است حاصلضرب به آن منتقل شود را بنویسید.
مثلاً اگر در کدتان عبارت زیر را دارید:
A <= B*C;
باید به جای عبارت Product_Int، حرف A را بنویسید.
در انتهای خط دوم کد، عبارت "Logic" نوشته شده است، برای این عبارت، دو انتخاب دارید؛ Logic یا DSP.
اگر عبارت DSP را بنویسید، به این معناست که به نرمافزار سنتز دستور میدهید، این ضربکننده را به کمک DSP48 پیادهسازی کند.
در صورتی که عبارت Logic را بنویسید، به نرمافزار دستور میدهید، این ضربکننده خاص را به کمک بلوکهای LUT پیادهسازی کند.
برای اینکه این موضوع بیشتر مشخص شود، اجازه دهید یک کد نمونه به شما نشان دهم.
در کد زیر، نمونهای از یک عملیات ساده ضرب و جمع را میبینید.
برای نمایش کد، اینجا را کلیک کنید...
در خط 53اُم کد، میبینیم که سیگنال A_Int را در C1_Int ضرب کردهایم و به سیگنال P1_Int منتقل کردهایم.
P1_Int <= A_Int * C1_Int;
در حالت عادی، اگر هیچ تغییری در سیستم انجام ندهید، این عمل ضرب توسط نرمافزار سنتز، به کمک بلوک DSP48 پیادهسازی میشود.
فرض کنید میخواهیم این عمل ضرب را به طور خاص، به کمک LUTها پیادهسازی کنیم. برای این کار، همانطور که گفتم، باید به کمک قید multstyle، به نرمافزار دستور دهیم که برای پیادهسازی، از LUTها استفاده کند.
بنابراین همانند خطهای 29 و 30 کد، قید multstyle را، قبل از Begin بخش Architecture، اضافه میکنیم.
attribute multstyle : string; attribute multstyle of P1_Int : signal is "logic";
اگر از این قید استفاده نکنیم و یا به جای عبارت Logic، عبارت DSP را بنویسیم، این ضربکننده با یک بلوک DSP48 پیادهسازی میشود.
نکتهی دیگر دربارهی قید Multstyle این است که میتوان به نرمافزار سنتز دستور داد که تمامی ضربکنندههای یک Architecture را با LUT یا DSP48 پیادهسازی کند.
برای این کار، همانند خط 32 و 33 کد، عبارات زیر را، قبل از Begin بخش Architecture، مینویسیم:
attribute multstyle : string; attribute multstyle of Behavioral : architecture is "logic";
سطر اول تغییری نمیکند، اما در سطر دوم باید دو تغییر اعمال کنیم؛
اولین تغییر این است که به جای سیگنال P1_Int، عبارت Behavioral که نام Architectureمان است را مینویسیم.
دومین تغییر این است که با نوشتن عبارت Architecture، به جای عبارت signal، به نرمافزار سنتز دستور میدهیم که تمامی ضربکنندههای درون Architecture را با LUT پیادهسازی کند.
امیدوارم از خواندن این مقاله هم لذت برده باشید و بتوانید از نکات یاد گرفته شده، در انجام پروژههایتان استفاده کنید.
آیا برنامه ویدئویی پشت پرده عملیات ضرب در FPGA برای شما مفید بود؟
لطفا نظرتان را در مورد این برنامه در پایین همین پست با دیگران به اشتراک بگذارید. همچنین با کلیک روی هر کدام از دکمههای اشتراک گذاری ابتدای این مطلب و به اشتراکگذاری آن در شبکههای اجتماعی میتوانید افراد بیشتری را در یادگیری این مطالب سهیم کنید.
بسیار عالی تدریس می کنید.
خوشحالم که از این آموزش لذت بردید.
موفق باشید.
آقای ثقفی، نحوه توضیح مطالبتون خیلی خوب و مفیده…تبریک میگم.
سلام. وقت بخیر.
بنده به منظور تست موضوع این برنامه، کد بسیار ساده ای نوشتم به این صورت که سه پورت ورودی A و B و C به صورت هشت بیتی و پورت خروجی F را ۱۶ بیتی تعریف کردم و در قسمت Concurrent نوشتم: F <= (A * B) + C
وقتی قید استفاده از LUT به جای DSP48 را در قسمت Declaration اضافه کردم پیغام زیر در قسمت هشدارها ظاهر میشود و این تغییر قید اثری در قسمت شماتیک حاصل از سنتز مدار ندارد.
WARNING:Xst:37 – Detected unknown constraint/property "multstyle". This constraint/property is not supported by the current software release and will be ignored.
علت این موضوع چیست؟ آیا نکته ای را رعایت نکرده ام؟
ممنون و سپاس
سلام،
قیدی که در ویدئو عنوان شده برای psartan6 قابل استفاده نیست. لطفا از قید زیر استفاده کنید و مقدار آن را برای سیگنال مورد نظر روی NO قرار بدهید:
attribute use_dsp48: string;
attribute use_dsp48 of {signal_name}: {signal} is “{auto|automax|yes|no}”;
موفق باشید.
استاد کلا روی هیچ fpga تغییری حاصل نمیشه با این قید
سلام ممنون از آموزشتان.
استاد اگر بخواهیم با زبان verilog برنامه بنویسیم که ضرب از طریق LUTها پیاده شوند چگونه باید عمل کنیم؟
سلام،
از طریق تنظیم قیدهای مرتبط با آن میتوانید این کار را انجام دهید.
موفق باشید.
خیلی توضیحات خوبی بود در مورد بلوک mac ممنون
ممنون از نظر مثبت شما.