مقدمه
در قسمت چهارم از مجموعه آموزشی FPGA با مدارات ترکیبی و ترتیبی آشناشدیم. درنهایت توابع یک، نیمجمعکننده را استخراج و آنها را به کد VHDL تبدیلکردیم. شاید سوالی که ذهنتان را درگیر کردهباشد، این است که آیا قرار است ما تا ابد، اگر خواستیم مداری را در FPGA پیادهسازی کنیم، باید مشقت استخراج توابع منطقی، جسممان را عذاب بدهد و روحمان را مثل خوره بخورد و بتراشد و تازه پساز این همه عذاب، توابع منطقی استخراجشده را تبدیل به کد VHDL کنیم؟ اگر حافظهی خوبی شما را یاری میکند و ما را نیز بهخوبی دنبالکردهباشید، پس حتما بهیاد دارید که در اواخر قسمت اول یادآوریکردیم که برای پیادهسازی مدار یا عملکرد موردنظرتان در FPGA نیازیبه اشراف کامل بر مدارات دیجیتال نیست، و فقط درک منطق مداردیجیتال کافیست، بقیه کارها را خود نرمافزار با الگوریتمهایی که دارد انجام میدهد. البته منکر این موضوع نیستیم که اشراف هرچه بیشتر بر مسئلهای، آن مسئله را قابل فهمتر میکند. حقیقت این است که در مدارات ساده همچون جمعکننده و امثالهم این کار راحت خواهد بود ولی در مدارات پیچیده، استخراج تکتک توابع و بررسی سطح پایین مدار، کاری بسیاردشوار و زمانبر است. حتی افراد حرفهای نیز چنین کاری نخواهند کرد، مگر در موارد خیلیخاصی که الگو یا رمزنگاری خاصی مدنظر باشد.
اگر موافق باشید اجالتا اجازه بدهید یکبار دیگه، فقط یکبار دیگه، مدار دیگری را بهصورت سطح پایین و بیتبه بیت بررسیکنیم. برای اینکار، توابع مدار تمام جمعکنندهی ۴بیتی را استخراج و سپس آنها را به کد VHDL تبدیل میکنیم. در این حین نیز نکات زیادی را به شما آموزش خواهیمداد. پساز اینکه مدار موردنظر را بهصورت سطح پایین و بیت به بیت تحلیل و پیادهسازی کردیم، اینکار را بهعنوان هنر میبوسیم و میزاریم کنار و با خیال راحت از روشی که درانتهای همین مقاله به شما معرفی خواهیم کرد استفاده میکنیم.
قبلاز اینکه بخواهیم مدار تمامجمعکننده چندبیتی را طراحیکنیم باید مقدماتی را بیانکرد.
انواع پورتها
پورتها از دو جهت موردبررسی قرار میگیرند:
- مسیر یا جهت
- نوع
مسیر یا جهت مشخص میکند که جهت دیتا در کدام سمت خواهد بود. در کل ۳جهت برای دیتا وجود خواهد داشت:
- In
- Out
- Inout
وقتی قرار باشد دیتا وارد FPGA بشود از حالت In و هنگام خروج دیتا از FPGA از حالت Out استفاده میکنیم. احتمالا حدس خواهید زد وقتیکه بخواهیم بهنحوی مجموعی از این دوحالت را داشتهباشیم، از حالت Inout استفاده میکنیم. بحث مربوطبه Inout کمی پیچیدگی دارد، و درقسمتی جداگانه به این موضوع خواهیمپرداخت.
نوع پورتها نیز باتوجهبه کاربردها، دارای انواع مختلفی است که ما در زیر به مهمترین آنها اشارهخواهیمکرد:
- std_logic
- std_logic_vector
- signed
- unsigned
- integer
درطول این مجموعه آموزشی با هرکدام از مواردبالا آشنا خواهیمشد.
سیگنالها
بهصورت خیلیساده سیگنالها یکسری عواملداخلی هستند، که ما برای ارتباط برقرارکردن بین اجزای مختلف در مدار، از آنها استفاده میکنیم. سیگنالها در پیادهسازی یا تبدیل به سیم میشوند یا رجیستر. فعلا بدون دلیل بپذیرید که در این پروژه سیگنال مورداستفاده تبدیل به سیم میشود، تا در جلسات پیشرو خدمتتان عرضکنیم که سیگنالها طبق چهشرایطی میتوانند تبدیل به رجیستر شوند و طبق چه شرایطی به سیم تبدیل میشوند. همچنین نوع سیگنالها نیز مشابه با پورتها میباشد.
تمام جمعکنندهی چهاربیتی
یک تمامجمعکنندهی ۴بیتی همانند تصویر بالا، از چهار، تمام جمعکنندهی تکبیتی تشکیل میشود. یک روش مناسب برای پیادهسازی این است که توابع منطقی یک، تمامجمعکنندهی تکبیتی را استخراجکنیم و در یک ماژول بنویسیم و سپس از این ماژول، بهنحوی، ۴بار استفادهکنیم. البته روشهای مناسبتر و بدتری نیز وجود دارند که ما روشهای بدتر را توضیح نخواهیمداد، اما در آخر همین مقاله به تشریح روش بهتری خواهیم پرداخت.
برای اینکه یک ماژول را به ماژول دیگر اضافهکنیم باید اقداماتی صورتگیرد، که درحین انجام پروژه به توضیح آن خواهیمپرداخت.
میتوان گفت توابع منطقی تمام جمعکننده، تعمیم یافته، همان توابع منطقی نیمجمعکننده میباشد. این توابع را میتوانید در زیر مشاهدهکنید:
S = x ⊕ y ⊕ cin
(C = (x . y) + (x . cin) + (y . cin
ما توابع منطقی ذکرشده در بالا، که مربوطبه یک، تمام جمعکنندهی تک بیتی میباشد را به کد VHDL تبدیل میکنیم و در یک زیرماژول قرار میدهیم. سپس این زیرماژول را، ۴بار به ماژول اصلی اضافهخواهیمکرد.
همانطورکه در کد زیر مشاهده میکنید، زیرماژول ما سه ورودی و دو خروجی دارد، که همه از نوع std_logic هستند. یعنی همهی ورودی-خروجیها تک بیتی هستند.
library IEEE; use IEEE.STD_LOGIC_1164.ALL; entity Sub_Module is Port ( x : in STD_LOGIC; y : in STD_LOGIC; Cin : in STD_LOGIC; Sum : out STD_LOGIC; Cout : out STD_LOGIC ); end Sub_Module; architecture Behavioral of Sub_Module is begin Sum <= x xor y xor Cin; Cout <= (x and y) or (x and Cin) or (y and Cin); end Behavioral;
حال ما باید بهنحوی این زیرماژول را به ماژولاصلی اضافهکنیم. پس بادقت به گفتههای زیر توجهکنید که یکبار برای همیشه این موضوع را فرابگیرید.
منطقا ماژول اصلی اونقدری عاقل نخواهد بود که بخواهد زیرماژول خود را تشخیصدهد. پس ما باید بهطریقی به ماژول اصلی بفهمانیم که آقای ماژول اصلی، فلانی زیرماژول شماست. ما برای این کار، قبلاز begin مربوطبه architecture، زیرماژول را به ماژول اصلی، مانند کد زیر معرفی میکنیم (component port). پساز این کار، ماژول اصلی همهی اختیارات را برعهده خواهد گرفت و میگوید که چگونه میخواهد از این زیر ماژول استفادهکند. و شما چگونگی استفادهاز زیر ماژول را در کد زیر بعداز begin مربوطبه architecture مشاهده میکنید (port map). درمورد چگونگی استفاده، باید یک سر برگردید به عقب و نیمنگاهی به دیاگرام مدار تمامجمعکنندهی ۴بیتی، بیندازید و همزمان با کد VHDL مقایسهکنید. باورکنید ماهم برای نوشتن این کد، کاری جز این نکردیم.
همانطورکه در کد مشخص است، ما ۴بار، به منزلهی ۴بیتیبودن تمامجمعکننده، زیرماژول را به ماژولاصلی اضافه میکنیم.
برای اینکه بتوانیم بین زیرماژولها ارتباط برقرارکنیم یا بیت سرریز خروجی هر تمامجمعکننده را، به سرریز ورودی تمامجمعکنندهی بعدی انتقال بدهیم، یک سیگنال میانی ۳بیتی نیز تعریف میکنیم.
library IEEE; use IEEE.STD_LOGIC_1164.ALL; entity Top_Module is Port ( x : in STD_LOGIC_VECTOR (3 downto 0); y : in STD_LOGIC_VECTOR (3 downto 0); Cin : in STD_LOGIC; Sum : out STD_LOGIC_VECTOR (3 downto 0); Cout : out STD_LOGIC ); end Top_Module; architecture Behavioral of Top_Module is signal C_Int : std_logic_vector (2 downto 0) := (others=>'0'); component Sub_Module port ( x : in std_logic; y : in std_logic; Cin : in std_logic; Sum : out std_logic; Cout : out std_logic ); end component; begin Add1: Sub_Module port map ( x => x(0), y => y(0), Cin => Cin, Sum => Sum(0), Cout => C_Int(0) ); Add2: Sub_Module port map ( x => x(1), y => y(1), Cin => C_Int(0), Sum => Sum(1), Cout => C_Int(1) ); Add3: Sub_Module port map ( x => x(2), y => y(2), Cin => C_Int(1), Sum => Sum(2), Cout => C_Int(2) ); Add4: Sub_Module port map ( x => x(3), y => y(3), Cin => C_Int(2), Sum => Sum(3), Cout => Cout ); end Behavioral;
خب همانطورکه گفتیم، معمولا اول یک فایل برای زیرماژول ایجاد میکنیم و بعداز اینکه، توصیف جزئی از مدار را، در این زیرماژول نوشتیم، ماژول اصلی را ایجاد میکنیم. و طبق مراحلی که گفتیم زیر ماژول را هم به کد معرفی، و هم از آن استفاده میکنیم.
تا این مرحله هنوز زیرماژول، به ماژول اصلی اضافهنشدهاست ولی با اقداماتی که ما انجامدادیم، قراراست که این اتفاق تنها با یک دابلکلیک صورتگیرد. اما قبلاز دابلکلیککردن باید یک کار کوچک دیگر انجامبدهیم. چون ما اول زیرماژول، سپس ماژول اصلی را به پروژه اضافهکردیم، پروژه، زیرماژول را بهعنوان ماژول اصلی میشناسد. برای رفع این مشکل باید روی ماژول اصلی راستکلیک کرده و گزینهی Set as Top Module را انتخابکنیم تا علامت انحصاری تاپماژول مانند شکلزیر کنار همان ماژولی که بهعنوان ماژولاصلی مدنظر ماست فعالشود.
اکنون اول ماژول اصلی را با ماوس انتخاب (Select) میکنیم، سپس با دابل کلیک کردن روی گزینه Check Syntax، زیرماژول، مانند تصویر زیر به ماژول اصلی اضافه خواهد شد. پس از طی این مراحل، زیرماژولها با کمی تورفتگی نسبت به ماژول اصلی قرار خواهند گرفت.
ما فقط یک زیرماژول داشتیم، پس چرا ۴زیرماژول به ماژول اصلی اضافهشدهاست؟ در جواب باید بگوییم که درواقع ما یک زیرماژول را چهاربار به ماژولاصلی اضافهکردیم. احتمالا مزیت زیرماژول نوشتن را نیز متوجهشدهباشید. اگر ما در پروژهای نیازداشتهباشیم بخشیاز کد را چندینبار تکرارکنیم، برای راحتی میتوانیم همان بخش کد را بهصورت زیرماژول در بیاوریم و چندینبار از آن استفاده بکنیم. با اینکار کد نیز خواناتر خواهد شد.
اگر کمی خستهشدید نفسیعمیق بکشید که قراراست همهی این کارهایی که در بالا انجامدادیم را فقط در یک خط خلاصهکنیم.
از این زمان بهبعد ما در تمامی پروژهها، فقط عملکرد و منطق مدار را بررسی و آنرا به کد VHDL تبدیل میکنیم. در کد زیر ما دو پورت ورودی و یک پورت خروجی ۴بیتی تعریف میکنیم و سپس در محیط Concurrent، ورودیها را باهم جمع کرده و به خروجی ارجاع میدهیم. به کد زیر توجهکنید:
library IEEE; use IEEE.STD_LOGIC_1164.ALL; use ieee.numeric_std.all; entity Full_Adder_4bit is Port ( x : in signed (3 downto 0); y : in signed (3 downto 0); Sum : out signed (3 downto 0) ); end Full_Adder_4bit; architecture Behavioral of Full_Adder_4bit is begin Sum <= x + y; end Behavioral;
در کد بالا ورودی-خروجیها را از نوع علامتدار تعریفکردیم، و مطابق با این کار پکیج ieee.numeric_std.all را نیز اضافهکردیم. پس یادتان باشد هروقت خواستید از اعداد علامتدار استفادهکنید، پکیج مربوطبه این اعداد را نیز اضافهکنید تا با خطا مواجهنشوید.
چرا وقتی با همین یک خط کد توانستیم مدار را توصیفکنیم، اون همه سختی را به جان خریدیم و یه عالمه کد نوشتیم؟ باورکنید ما نه بیمار هستیم و نه از اختلالات روانی خاصی رنج میبریم، هدف ما فقط و فقط آموزش FPGA بهصورت کاملا دقیق و از پایه به شما عزیزان میباشد.
در آخر خدمتتان یادآور شویم که در آخرین کد ایراداتی همچون پوشش ندادن سرریز و بقیه موارد وجود دارد، اما چون حجم این مقاله کمی از حدمعمول قسمتهای قبل بیشترشدهاست و همچنین مقدمات مربوطبه این موضوع را هنوز یادنگرفتهایم، دیگر قصد پرداختن به این موضوع را نداریم و قراراست در سری مقالاتی با عنوان “اهمیت تئوری و ریاضیات در برنامهنویسی” که هماکنون در دست تالیف قرار دارد و قسمت اول آن نیز منتشرشدهاست، مفصل درباب این موضوع صحبتکنیم.
امیدواریم پساز چندقسمتی که از این مجموعه آموزشی میگذرد، توانستهباشیم در این مقاله، حسخوب شما نسبتبه FPGA را ارضاکردهباشیم و شما نیز لذت و بهره کافی را بردهباشید.
در قسمت ششم به شما آموزش خواهیمداد که چگونه مدار خود را در نرمافزار شبیهسازی کنید و نتایج را مشاهدهکنید، پس با ما همراه باشید.
منبع: سیسوگ