ماژولها
یادگرفتن وریلاگ به خودی خود کار دشواری نیست؛ اما اینکه بتوانیم یک مدار را با وریلاگ خوب طراحی کنیم، بعضا ممکن است کار دشواری باشد. اما نگران نباشید؛ ما در اینجا با تمرکز بر یک طراحی ساده جلو میرویم و میکوشیم تا به سادهترین حالت ممکن همهچیز را به شما توضیح دهیم.
اگر پیش از این با زبانهای برنامهنویسی پروسیژرال یا همان رویهای (procedural languages) مانند C و ++C کار کرده باشید، حالا باید کاملا ذهنتان را آماده و هوشیار نگه دارید که قرار نیست در در دنیای دیجیتال هم همهچیز به همان صورت رویهای و ترتیبی باشد. بلکه ممکن است اتفاقات زیادی را داشته باشیم که به صورت موازی با هم رخ بدهند. زمانی که خود من شروع به آموختن وریلاگ کردم، به دلیل اینکه همین نکتهی ساده را نمیدانستم، اوایل تمام کد ها را به صورت ترتیبی (sequentially) مینوشتم؛ انگار که مشغول نوشتن کد C باشم.
برنامههای به زبان C، بر روی میکروپروسسور اجرا میشوند که دستورات را تک به تک و ترتیبی اجرا میکند. بنابراین شاید در ذهن آوردن و نوشتن چنین برنامهای سادهتر به نظر برسد که شما اتفاقات را دقیقا به همان ترتیبی که میخواهید رقم بخورند، کد میزنید. اما همین نکتهی به نوعی مثبت؛ از منظر دیگری نقطهی ظعف میکروکنترلرها و میکروپروسسورها نیز محسوب میشود. آنها در هر لحظه فقط و فقط یک کار را میتوانند انجام دهند. (پر واضح است که منظورمان زمانیست که از ابزارهای تک هستهای استفاده کنیم)
اما برخلاف میکروپروسسورها؛ مدارهای دیجیتال مانند FPGAها، CPLDها و ASICها، در آن واحد و در هر لحظه میتوانند چندین کار را انجام دهند. بنابراین اولین چیزی که باید بیاموزید این است که بتوانید تصور کنید چندین و چند اتفاق در یک زمان قرار است رخ بدهند. برخلاف برنامهنویسی رویهای که اتفاقات مختلف در زمانهای مختلف رخ میدادند. ( یا به عبارت دیگر در هر زمان فقط یک دستور اجرا میشود)
ماژولهای وریلاگ
ابتدا باید توضیح دهیم که در زبان Verilog به چه چیزی ماژول گفته میشود؟ به هر واحد مجزای طراحی شده با عملکرد خاص و مشخص، یک ماژول میگوییم. چیزی شبیه یک black-box. این عملکرد و هدف خاص، در مرحلهی طراحی RTL در نظر گرفته و دنبال شده است. تعدادی ورودی و تعدادی خروجی دارد و مطابق عملکردی که برای آن مهندسی شده است، ورودیها را به خروجیها میبرد. یکی از سادهترین ماژولهای وریلاگ میتواند گیت NOT باشد. (در سومین تصویری که در ادامه داریم میتوانید آن را ببینید) کار این گیت این است که سیگنالهای ورودی را بگیرد، آنها را معکوس کند و به خروجی بفرستد.
چنین ماژولی شاید بسیار ساده به نظر برسد و همینطور است. اما حد بالای پیچیدگی ماژولهای وریلاگ تا کجا میتواند باشد؟ در واقع میتوان گفت که هیچ حد بالایی وجود ندارد و تا دلتان بخواهد میتوانید یک ماژول را پیچیده کنید. در حدی که حتی میتوانید هستههای یک پروسسور را به طور کامل در قالب یک ماژول وریلاگ پیادهسازی کنید.
اما همانطور که گفتیم و میدانید، زبان وریلاگ مختص مدارهای دیجیتال است و زمانی که در قلمرو آن هستیم، میتوانیم ماژولهایش را به اجزاء یک مدار دیجیتال تشبیه کنیم و آنها را معادل هم در نظر بگیریم. مثلا یک ماژول میتواند معادل یک گیت ساده یا معادل واحدهای پیچیدهتری مانند ALU، کانتر (شمارنده) و … باشد.
از منظری دیگر، میتوان گفت که ماژولها مشابه کلاسها در ++C هستند؛ به گونهای که آنچه برای انجام عملکردشان نیاز دارند در درون خودشان هست و تنها به تعداد محدودی درگاه برای ارتباط با جهان خارج نیاز دارند و دقیقا همانطور که در برنامهنویسی شیئگرایی مانند ++C از روی کلاس ها Objectها نمونهسازی میشوند (instantiate)، ماژولهای وریلاگ را نیز میتوان از روی طراحی سطح RTL نمونه سازی کرد و البته دقت کنیم، این که میگوییم شبیه هستند بیشتر به منظور تقریب ذهن شماست و منظورمان این نیست که 100 درصد فرآیند یکسانی دارند.
برای سادهسازی و فهم منظور؛ یک ماژول وریلاگ را -پس از ساخت- از نظر گرافیکی به صورت یک جعبهی دربسته درنظر بگیرید که تعدادی پورت ورودی/ خروجی دارد. (پورت ها میتوانند فقط ورودی، فقط خروجی و یا دوطرفه باشند) دیتای مبادله شده توسط این پورتها میتواند تک بیتی یا چندبیتی باشد. مثلا تصویر زیر را ببینید که یک ماژول را با تعدادی ورودی و خروجی نشان میدهد. اینکه عرض باند (تعداد بیت مبادله شده) آنها چقدر است و یا یک طرفه هستند یا دوطرفه چیزی است که بستگی به عملکرد تعریف شده در داخل این جعبه سیاه دارد.
میتوان گفت که اساسا مسئلهی اصلی در وریلاگ (و البته تقریبا تمام زبانهای HDL)، مسئلهی ساخت و تولید ماژول است؛ سپس اتصال درست ماژولها به هم و مدیریت زمانبندیها.
بسیار خب، تئوری بس است! تا بحال هنوز یک خط کد هم ننوشتهایم، حتی یک Hello World ساده! اما از کجا باید شروع کنیم؟
از طراحی مدار همان NOT گیت ساده. ابتدا مدار آن را به زبان وریلاگ بنویسیم، سپس آن را سیموله کنیم و در نهایت بر روی سختافزار واقعی تست کنیم.
NOT گیت یا همان اینورتر (معکوسکننده) خودمان، تقریبا سادهترین گیت منطقی موجود است. خروجی این گیت چیزی نیست جز معکوس منطقی ورودی، یعنی: B=!A. (اگر A ورودی و B خروجی باشد)
به طور خلاصه؛ عملکرد این گیت را در جدول ارزش (truth table) که در تصویر زیر آمده است میبینید.
بنابراین تا اینجای کار فهمیدیم که گیت معکوس کننده، یک ماژول است با یک ورودی و یک خروجی که عملکرد داخلی آن به صورت: B=!A است. پس شمای گرافیکی آن را نیز میتوانیم به صورت تصویر زیر داشته باشیم.
حالا بیایید ببینیم چطور این ماژول را با زبان وریلاگ توصیف کنیم.
module myModule(A, B); input wire A; output wire B; assign B = !A; endmodule
چقدر ساده و کوتاه! نه؟! حالا بیابید تا خط به خط این کد کوچک را با هم بررسی کنیم تا ببینیم چه خبر است.
اسم این ماژول را myModule گذاشتهایم و آن را با استفاده از دستور یک کلمهای module معرفی (declare) کردهایم. در واقع module در زبان وریلاگ یک کلمهی کلیدی محسوب میشود که هر وقت بخواهیم یک ماژول مشخص (مثلا myModule در اینجا) را معرفی کنیم، از آن استفاده میکنیم. در ادامه، پورت های این ماژول مشخص را نیز در داخل پرانتزی مقابل نام آن، تعریف میکنیم. مثلا در اینجا A و B.
یک قانون کلی هم وجود دارد از این قرار که هر اتفاقی که قرار است در درون این ماژول خاص و توسط آن انجام بگیرد (عملکرد) و یا دربارهی آن باشد (مثلا نام آن یا ورودی/خروجیهای آن)، بین دو کلمهی کلیدی module و end module قرار میگیرد.
توجه کنیم که تا این لحظه، هرچند نام پورتها و تعداد آنها مشخص شده است، اما جهت آنها (ورودی یا خروجی بودن) و پهنای باند آنها هنوز مشخص نشده است. بنابراین در دو خط بعدی، جهت پورتها را مشخص میکنیم. مثلا در اینجا میبینیم که پورت A و پورت B، به ترتیب به عنوان ورودی و خروجی تعریف شدهاند. خب، احتمالا فهمیده باشید و منتظر هستید که من هم تایید کنم که wire هم یک کلمهی کلیدی زبان وریلاگ محسوب میشود، بله همینطور است. و اگر بخواهیم جزئیتر بگوییم، در زبان وریلاگ، دادهها میتوانند بر دو نوع تعریف شوند؛ یا wire باشند یا reg. (البته انواع دیگری نیز هستند مثلا int، real و … اما دلیل اینکه ما فقط بر دو نوع wire و reg تاکید داریم این است که این دو از همه مهمتر و پرکاربردتر هستند و اگر آنها را ندانیم، تقریبا نخواهیم توانست در وریلاگ هیچ پیشرفتی کنیم) اما برای اینکه بدانیم این دو نوع بیانگر چه هستند، همانطور که در قسمت قبل هم اشاره کردیم؛ لازم است گریزی بزنیم به اطلاعاتی که از الکترونیک دیجیتال به یاد داریم. وقتی میگوییم نوع یک متغیر از نوع wire است، منظورمان تقریبا شبیه یک wire (سیم) فیزیکی است که دو نقطه مختلف یک مدار را از نظر الکتریکی به هم وصل میکند. اگر ما به یک سر یک سیم مسی ولتاژی اعمال کنیم، تا زمانی که این ولتاژ برقرار باشد، در سر دیگر سیم نیز همین ولتاژ را میتوانیم داشته باشیم. اما به محض اینکه آن را قطع کنیم، سر دیگر نیز عاری از هر گونه ولتاژ خواهد شد.
در وریلاگ هم متناظرا به همین شکل است. دادهی از نوع wire، انگار سر دوم همان سیم فیزیکال است که سر اول آن به دست یک داده/ انتیتی دیگر است. تا زمانی که تحت کنترل آن انتیتی دیگر است، مقدار آن دقیقا از مقدار آن انتیتی (entity) تبعیت میکند و هر وضعیتی که آن از نظر منطقی داشته باشد، این نیز خواهد داشت. به محض اینکه سر اول آن رها شود و کسی مقداری به آن ندهد، از نظر منطقی در وضعیت نامعلوم (unknown state) خواهد رفت.
کاربرد این نوع متغیر در وریلاگ زمانیست که بخواهیم دو چیز را درون یک ماژول یا حتی بین ماژولهای مختلف به هم وصل کنیم.
از طرف دیگر، متغیر reg، متغیری است که میتواند یک وضعیت منطقی را در خود ذخیره کند و تا زمانی که کسی آن را تغییر ندهد، همچنان آن را حفظ کند. (بله دقیقا شبیه به همان کاری است که یک رجیستر در میکروکنترلر انجام میدهد) این همان عملکردی است که فیلپفلاپها دارند. اگر شما یک فیلپفلاپ را در وضعیتی مشخص قرار دهید، تا زمانی که کسی آن وضعیت را تغییر ندهد، خود فلیپفلاپ آن را تغییر نداده و حفظ خواهد کرد.
با این اوصاف، میتوان نتیجه گرفت که از متغیرهای نوع wire هم به عنوان ورودی و هم به عنوان خروجی یک ماژول میتوان استفاده کرد واز متغیرهای نوع reg هم به عنوان خروجی ماژولها.
فقط دقت کنید که در حالتی که خروجی را از نوع wire انتخاب میکنیم، حتما باید در درون ماژول یک متغیر از نوع reg وجود داشته باشد که بتواند این wire را تحت کنترل بگیرد. (سر اول سیم را بدست بگیرد) در غیر این صورت خود متغیر wire به تنهایی معنایی نخواهد داشت. اما زمانی که از متغیر نوع reg در خروجی استفاده میکنیم، دیگر نیازی نیست که چنین نکتهای را در ضمن طراحی پروسهی داخلی ماژول مدنظر قرار دهیم. متغیر reg خودش به تنها میتواند مقادیر را نگه دارد و معنا داشته باشد.
احتمالا از خودتان میپرسید که اگر چنین است، پس چرا در قطعه کد بالا هم ورودی و هم خروجی هر دو wire هستند و هیچ متغیر regی هم در آن وجود ندارد؟ سوال بسیار خوبیست و پاسخ آن هم بسیار مهم است.
کدی که در بالا نوشتیم ، برای یک اینورتر یا به عبارت دیگر برای یک گیت NOT بود. گیتها اصلا قرار نیست که هیچ وضعیتی را در خود نگه دارند. مدارهای آنها مدارهای ترکیبی (combinational) محض است به این معنا که خروجی در هر لحظه فقط به ورودی در همان لحظه وابسته است. یعنی در هر لحظهای که وضعیت منطقی متفاوتی به ورودی آن اعمال شود، خروجی آن نیز متناظر با آن وضعیت ورودی و تابع درونی، تغییر میکند. (مثلا در این مورد وضعیت هرچه که باشد فقط معکوس شده و به خروجی میرود) یا حتی اگر در لحظهای هیچ ورودی مشخصی به ورودی داده نشود و اصطلاحا در وضعیت نامعلوم باشد، خروجی نیز به تبع در وضعیت نامعلوم خواهد بود.
اما معنای این حرفها به طور ضمنی چیست؟ وقتی به علت بالا خروجی باید از نوع wire باشد و از طرفی ورودی نیز wire است یعنی خود نیز باید توسط یک انتیتی دیگر کنترل شود؛ به طور ضمنی به این معناست که اگر قرار باشد ما یک خروجی خوب و معنی دار از این ماژول بگیریم، باید ورودی را نیز توسط چیز دیگری تحت کنترل بگیریم. این را در ذهنتان نگه دارید تا در قسمتهای بعدی که در مورد test bech صحبت میکنیم، باز هم به آن بازگردیم.
نکتهی بعدی در مورد کد بالا، کلمهی کلیدی assign است. این دستوری است که از آن برای ساخت مدارهای ترکیبی استفاده میکنیم. هر متغیری که در سمت راست علامت مساوی قرار داده شود، دائما رصد شده و مقدار و وضعیت آن هرچه باشد طبق دستوری که وجود دارد، پردازش شده (مثلا معکوس میشود یا …) و به متغیر سمت چپ تساوی تحویل داده میشود. و این کار به صورت سنکرون انجام میشود. به این ترتیب به محض اینکه در وضعیت متغیر سمت راست تغییری رخ بدهد، بازتاب آن در متغیر سمت چپ نیز نمود خواهد یافت.
اگر هنوز فکر میکنید آن طور که باید مطلب برایتان جا نیفتاده است، احتمالا بد نیست که سری به منابع در مورد مدارهای ترکیبی بزنید و یک بار دیگر آنها را مرور کنید.
بسیار خب، تا اینجا ما تصویری از ماژولی که میخواستیم داشته باشیم را با هم درآوردیم، کد آن را هم به زبان وریلاگ نوشتیم. حالا نوبت این است که این کد را شبیهسازی (سیموله) کنیم تا ببینیم واقعا همان کاری را انجام میدهد که ما انتظار داریم؟
اما پروسهی سیمولیشن به -معنای تست مداری عملکرد- به این ترتیب است که ما تعدادی ورودی معلوم و مشخص که خروجی آنها را هم میدانیم یا میتوانیم محاسبه کنیم را به مدار طراحی شده میدهیم و خروجی متناظر با هر کدام را از مدار دریافت میکنیم. از طرفی میدانیم که خروجیای که مدار تولید میکند هم به ورودی دریافت شدهاش وابسته است و هم به صحت یا عدم صحت عملکرد آن. اگر خروجی دریافت شده برای یک ورودی همان چیزی باشد که میدانیم یا محاسبه کرده بودیم؛ به این ترتیب عملکرد ماژول تایید میشود. به این پروسه verification نیز گفته میشود. برای انجام دادن این پروسهی سیمولیشن و وریفیکیشن، ابزارهای مختلفی وجود دارند که این کار را برای ما و بدون نیاز به محاسبات دستی و انسانی انجام دهند. ما در اینجا برای این کار و بررسی شکل موجهای خروجی، از ابزار iSim استفاده میکنیم که جزئی از نرمافزار Xilinx ISE Webpack است.
- منبع: ترجمه از سایت numato.com
- منبع: عکس شاخص از سایت alamy.com
قبل از اتمام جلسه دوم توصیه میکنم از نوشته دانلود فایل های آموزش FPGA فایلهای آموزش Xilinx ISE را دانلود و مطالعه کنید و یا بهتر است به ورژن جدید این نرم افزار که به اسم VIVADO عرضه شده مهاجرت کنید. برای این منظور مقاله آموزش نرم افزار Vivado میتواند برای شما مفید باشد. خب در جلسه بعدی آموزش Verilog با ما همراه باشید. و اگر این آموزش براتون مفید واقع شده ما را نیز دعا کنید و اگر خواستین میتوانید از محتوای رایگان آموزشی حمایت مالی کنید.
اگر این نوشته برایتان مفید بود لطفا کامنت بنویسید.
در مطلب فوق کلمه آسنکرون باید به سنکرون تغییر یابد و اشتباه شده است.
ممنون از زحماتتون بسیار عالی بود