آموزش FPGA : قسمت پنجم

آموزش FPGA : قسمت پنجم

در قسمت چهارم از مجموعه آموزشی 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 مقایسه کنید. باور کنید ما هم برای نوشتن این کد، کاری جز این نکردیم.

همانطور که در کد مشخص است، ما چهار بار، به منزله‌ی چهار بیتی بودن تمام جمع‌کننده، زیرماژول را به ماژول اصلی اضافه می‌کنیم.

برای اینکه بتوانیم بین زیرماژول‌ها ارتباط برقرار کنیم یا بیت سرریز خروجی هر تمام جمع‌کننده را، به سرریز ورودی تمام جمع‌کننده‌ی بعدی انتقال بدهیم، یک سیگنال میانی 3 بیتی نیز تعریف می‌کنیم.

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، زیرماژول‌، مانند تصویر زیر به ماژول اصلی اضافه خواهد شد. پس از طی این مراحل، زیرماژول‌ها با کمی تورفتگی نسبت به ماژول اصلی قرار خواهند گرفت.

ما فقط یک زیرماژول داشتیم، پس چرا 4 زیرماژول به ماژول اصلی اضافه شده است؟ در جواب باید بگوییم که در واقع ما یک زیرماژول را چهار بار به ماژول اصلی اضافه کردیم. احتمالا مزیت زیرماژول نوشتن را نیز متوجه شده باشید. اگر ما در پروژه‌ای نیاز داشته باشیم بخشی از کد را چندین بار تکرار کنیم، برای راحتی می‌توانیم همان بخش کد را به صورت زیرماژول در بیاوریم و چندین بار از آن استفاده بکنیم. با این کار کد نیز خواناتر خواهد شد.

اگر کمی خسته شدید نفسی عمیق بکشید که قرار است همه‌ی این کارهایی که در بالا انجام دادیم را فقط در یک خط خلاصه کنیم.

از این زمان به بعد ما در تمامی پروژه‌ها، فقط عملکرد و منطق مدار را بررسی و آن را به کد 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;

چرا وقتی با همین یک خط کد توانستیم مدار را توصیف کنیم، اون همه سختی را به جان خریدیم و یه عالمه کد نوشتیم؟ باور کنید ما نه بیمار هستیم و نه از اختلالات روانی خاصی رنج می‌بریم، هدف ما فقط و فقط آموزش FPGA به صورت کاملا دقیق و از پایه به شما عزیزان می‌باشد.

در آخر خدمتتان یادآور شویم که در آخرین کد ایراداتی همچون پوشش ندادن سرریز و بقیه موارد وجود دارد، اما چون حجم این مقاله کمی از حد معمول قسمت‌های قبل بیشتر شده است و همچنین مقدمات مربوط به این موضوع را هنوز یاد نگرفته‌ایم، دیگر قصد پرداختن به این موضوع را نداریم و قرار است در سری مقالاتی با عنوان “اهمیت تئوری و ریاضیات در برنامه‌نویسی” که هم‌اکنون در دست تالیف قرار دارد و قسمت اول آن نیز منتشر شده است، مفصل در باب این موضوع صحبت کنیم.

امیدواریم پس از چند قسمتی که از این مجموعه آموزشی می‌گذرد، توانسته باشیم در این مقاله، حس خوب شما نسبت به FPGA را ارضا کرده باشیم و شما نیز لذت و بهره کافی را برده باشید.

در قسمت ششم به شما آموزش خواهیم داد که چگونه مدار خود را در نرم‌افزار شبیه‌سازی کنید و نتایج را مشاهده کنید .

درباره نویسنده

نویسنده و طراح الکترونیکا هستم . سوالی داشتید در کامنت ها یا پیج های اینستاگرام و تلگرام سایت بپرسید .