تولید PWM سختافزاری بر روی بورد Mojo FPGA
در این بخش میخواهیم یاد بگیریم که چگونه PWM را با کمک وریلاگ به صورت سختافزاری پیادهسازی کنیم. همچنین خواهیم دید که چگونه قابلیت ماژولار بودن زبان وریلاگ، موجب میشود بتوانیم به تعداد دلخواه سیگنال PWM در FPGAها تولید کنیم.
تنها چیزی که برای دنبال کردن این آموزش نیاز دارید یک عدد بورد Mojo V3 Development Board است.
کدی که برای تولید PWM مینویسیم را بر روی همان Mojo Base Project مینویسیم که توسط Embedded Micro ارائه شده و در قسمت اول نیز از آن استفاده کردیم. مزیت این کار این است که مشخصات و تعاریف اولیهی بورد و نیز initializationهای مربوط به ISE در آن آورده شدهاند و نیازی نیست که خودمان از نو آنها را بنویسیم. اما اگر دوست داشته باشید میتوانید نام این Base Project را که فولدری با پسوند xise. در مسیر ذخیرهی پروژه است، تغییر دهید و نام دلخواه خودتان را بگذارید. مثلا ما در اینجا آن را به MojoPWM.xise تغییر دادهایم.
مطابق روالی که در قسمت اول این آموزش توضیح دادیم، ابتدا باید به فایل UCF برویم و در آنجا نام و شماره پین تمام اتصالات ورودی/خروجی که در پروژهمان داریم را تعریف و اضافه کنیم. در این پروژه قرار است از LED موجود بر روی خود Mojo استفاده کنیم که نام و شماره پین آن از قبل در UCF وجود دارد. بنابراین نیازی نیست در این فایل هیچ تغییری ایجاد کنیم و میتوانیم مستقیما به سراغ فایل کد اصلی برویم و به زبان وریلاگ رفتار سختافزاری که قرار است PWM تولید کند را توصیف کرده و به آن اضافه کنیم. اما این بار به جای آن که کد سختافزار تولیدکنندهی PWM را مستقیما در خود ماژول mojo_top وارد کنیم، آن را در یک فایل جداگانه مینویسیم تا بعدا بتوانیم از خاصیت ماژولار بودن وریلاگ استفاده کنیم. به این ترتیب اگر بخواهیم سیگنالهای PWM مختلفی را متناظر با LEDهای مختلفی که روی بورد وجود دارند، تولید کنیم، دیگر نیازی نخواهد بود که کد سختافزار PWM را چندین بار پشت سر هم کپی کنیم و یک کد نامرتب داشته باشیم، بلکه کافی است مانند تمام زبانهای دیگر، از این ماژول که به صورت جداگانه نوشتهایم، instance بگیریم. به این ترتیب کد ما به مراتب کوتاهتر و خواناتر خواهد بود.
در پنجرهی Hierarchy که در سمت چپ صفحه قرار دارد، راست کلیک کنید و گزینهی «…New Source» را انتخاب کنید. در لیستی که برای Source Type به شما پیشنهاد شده است، گزینهی «Verilog Module» را بزنید و نام فایل را PWM.v بگذارید.
حالا یک فایل جدید دارید که میتوانید کد سختافزار تولیدکنندهی سیگنال PWM را به زبان وریلاگ در آن بنویسید.
قبل از نوشتن کد، بهتر است ابتدا قدری در مورد اینکه چگونه میخواهیم این سختافزار را پیادهسازی کنیم، با هم صحبت کنیم.
همانطور که از قبل (در بخش پیشین) در مورد PWM صحبت کردیم، ما با سیگنالی مواجه هستیم که در ذات خود پریودیک است. یعنی مقدار آن وابسته به زمان تغییر میکند. بنابراین واضح است که باید رفتار آن را با کمک سیگنال کلاک توصیف کنیم. این سیگنال در ماژول mojo_top به عنوان یکی از ورودیهای سیستم تعریف شده است و یک موج مربعی است که ظاهری شبیه به شکل زیر دارد.
با این اوصاف عملکرد سختافزاری که میخواهیم طراحی کنیم به این شکل خلاصه میشود.
- در هر سیکل کلاک (که شروع آن همزمان با لبهی بالاروندهی سیگنال کلاک است)، مقدار یک شمارنده (کانتر) که در بازهی ۰ تا ۲۵۵ میتواند بشمارد را یکی اضافه میکنیم.
- پارامتر duty cycle را به عنوان یک ورودی به این ماژول میدهیم. این عدد در بازهی ۰ تا ۲۵۵ قرار دارد. (درست مانند PWM بوردهای آردوینو)
- در هر کلاک، اگر مقدار شمارنده از مقدار duty cycle کمتر باشد، سطح سیگنال خروجی همینطور بالا میماند و در صورتی که از آن بیشتر باشد، سطح سیگنال خروجی به سطح پایین تغییر میکند.
- با هر بار بالا رفتن سطح سیگنال یا ورود سیگنال ریست، مقدار شمارنده را ۰ میکنیم.
ممکن است بپرسید که چرا بالاترین مقدار شمارنده را ۲۵۵ میگیریم؟ علت آن این است که ۲۵۵ بزرگترین عددی است که میتوان با در دست داشتن ۸ بیت نمایش داد (۱۱۱۱۱۱۱۱). زمانی که به این مقدار برسد، دوباره ۰ شده و از نو شروع به اضافه شدن میکند. اگر علاقهمند بودید در مورد محاسبات باینری و نمایش متغیرها با نمایش باینری بیشتر بدانید، در قسمت انتهایی (بخش سوم) این آموزش برای شما یک لینک کاربردی قرار دادهایم که میتوانید به آن مراجعه کنید.
در اینجا یک نمودار زمانی داریم که عملکرد مدار ما را با توجه به سیگنال کلاک نشان میدهد.
دیاگرام زمانی برای یک ماژول PWM سختافزاری ۸ بیتی. Duty cycle: 3/255.
بسیار خب، نوشتن کد PWM را با توصیف و معرفی سیگنالهای ورودی و خروجی به این ماژول آغاز میکنیم.
input clk, input rst, input[7:0] duty, output sig_drv
همانطور که از نام آنها مشخص است، این چهار سیگنال ورودیهای کلاک، ریست و مقدار duty cycle و در نهایت سیگنال خروجی PWM تولید شده هستند.
در این مرحله باید نوع دادهی خروجی یعنی sig_drv را مشخص کنیم. زبان وریلاگ دارای دو نوع داده است، wire و reg. از نظر نوع داده در این پروژه فرقی نمیکند که ما از کدام یک از آنها استفاده کنیم. اما چون میخواهیم از بلوک always استفاده کنیم، طبق قانون وریلاگ تنها مجاز به استفاده از متغیر نوع reg هستیم. در مورد بلوکهای always به طور مختصر در ادامه توضیح خواهیم داد اما همینجا این نکته را هم اضافه کنیم که اگر خودمان نوع متغیری را مشخص نکنیم، وریلاگ به صورت پیشفرض آن را از نوع wire در نظر میگیرد. بنابراین اگر میخواهیم متغیری از نوع register باشد، باید حتما آن را قید کنیم. این کار را میتوانیم با استفاده از دستور زیر و دقیقا بعد از بخش معرفی سیگنالها انجام دهیم.
reg sig_drv;
همچنین قرار است از یک شمارندهی ۸ بیتی هم استفاده کنیم که چون آن هم در بلوک always آورده میشود، نوع آن را نیز reg تنظیم میکنیم.
reg[7:0] counter;
احتمالا دقت کردهاید که وریلاگ هم مانند بسیاری از زبانهای دیگر zero-indexed است. یعنی شمارشهای آن از ۰ شروع میشود. بنابراین اگر ۸ بیت داشته باشیم، ایندکس این بیتها به ترتیب از ۰ تا ۷ خواهد بود نه از ۱ تا ۸.
حالا باید قسمت مربوط به شمارنده و تولید سیگنال خروجی را توصیف کنیم. این کار را با استفاده از بلوک always انجام میدهیم.
بلوک always یکی از ساختارهای زبان وریلاگ است که با استفاده از آن، کاربر میتواند رخداد دستوراتی خاص را منوط به trigger شدن سیگنالهایی که مشخص میشوند کند. این ساختار در حالت اولیهی خود به این شکل است.
always @ (…) begin … end
در پرانتزی که بعد از @ میآید، باید لیست سیگنالهای تریگری قرار داده شود که اجرای محتویات داخل بلوک وابسته به آنهاست.
در این پروژه ما به دو بلوک always احتیاج داریم.
always @(*) begin end
و
always @(posedge clk or posedge rst) begin end
در بلوک اول، همانطور که میبنیید شرایط تریگ شدن به صورت (*) نوشته شده است. منظور از این حالت این است که محتویات داخل این بلوک نسبت به تغییر تمام سیگنالهای ورودی مدار حساس هستند و در پی تغییر هر کدام از آنها، این دستورات دوباره اجرا میشوند. مهندسان طراحی سختافزار به این بلوکها، بلوکهای combinational یا ترکیبی میگویند. یعنی بلوکهایی که همواره خروجی را به عنوان تابعی از ورودیها مشخص و محاسبه میکنند. بنابراین ما هم در این بلوک تابعی را قرار میدهیم که میخواهیم همواره و نه فقط هر کلاک یک بار اجرا شود، تولید سیگنال خروجی.
بالاتر توضیح دادیم که خروجی قرار است به این شکل محاسبه شود که اگر عدد شمارنده از duty cycle بیشتر بود خروجی ۰ و اگر از آن کمتر بود خروجی ۱ شود. این عملکرد را همانند زیر در وریلاگ پیادهسازی میکنیم.
if (duty > counter) begin sig_drv = 1’b1; end else begin sig_drv = 1’b0; end
سیگنال خروجی sig_drv یک سیگنال ۱ بیتی است. معنای پیشوند 1’b همین است. عبارت بالا یک عبارت شرطی است که دقیقا بر همان اساس که گفتیم، مقدار خروجی را ۱ یا ۰ میکند.
اما در بلوک دومیکه باید بنویسیم، لیست سیگنالهای تریگر به این صورت است، posedge clk or posedge rst. معنای این عبارت این است که دستورات داخل این بلوک تنها زمانی اجرا میشوند که لبهی بالاروندهی کلاک و یا لبهی بالاروندهی سیگنال ریست را داشته باشیم. با استفاده از این بلوک، شمارندهای را توصیف میکنیم که قرار است در هر کلاک تنها یک بار بر مقدار آن اضافه شود. خطوط زیر این عملکرد را به زبان وریلاگ بیان میکنند.
if (rst) begin counter <= 8’b0; end else begin counter <= counter + 1; end
در بخش اول این عبارتی شرطی گفته شده است که هر زمان سیگنال reset مدار فعال شود، مقدار شمارنده باید به حالت تماما ۰ برود. در بخش دوم آن که پس از else آمده است، مشخص میکند در صورتی که ریست فعال نباشد، مقدار شمارنده در هر کلاک یکی اضافه شود.
نکتهی دیگری که در این کد وجود دارد این است که مقداری که در هر کلاک به counter تخصیص میدهیم، واضحا به مقدار قبلی آن بستگی دارد. مهندسان طراحی سختافزار به چنین مدارهایی مدارهای sequential یا ترتیبی میگویند. یعنی آنکه مقدار خروجی در هر لحظه به مقادیر ورودی و نیز حالت قبلی مدار بستگی دارد.
آخرین نکتهای که در مورد این کد باید یاد بگیریم، عملگر «=>» است. به این عملگر یک non-blocking assignment گفته میشود که در مقابل عملگر «=» که یک blocking assignment است قرار میگیرد. تفاوت این دو در این است که زمانی که از «=» استفاده میکنیم، در حقیقت به وریلاگ میگوییم کد را از بالا به پایین اجرا کن. یعنی اگر عملوند این عبارت در خطوط قبلی هم استفاده شده و در آنجا تغییراتی میکند، ابتدا آن تغییرات را لحاظ کن و آخرین مقدار این عملوند را وارد این عبارت کن. این مدل تخصیص در مدارها کاربردهای خاص خود را دارد که در اینجا مد نظر ما نیستند.
در مقابل در مدارهای ترتیبی مانند چیزی که ما در اینجا داریم و به یک کلاک با فرکانس بالا وابسته هستند، ترجیح ما این است که تمام تخصیصهای خطوط به صورت موازی و همزمان انجام شوند (البته اگر به هم وابسته نباشند) در این حالت تاخیر مدار در محاسبه خروجیها کمتر خواهد بود.
هرچند که ما در این کد اصلا assignmentsهای متعددی که بخواهند همگی در لبهی کلاک اتفاق بیافتند نداریم، اما اگر داشتیم هم بهتر بود تا جایی که مقادیر سیگنالها به اشتباه محاسبه نشوند، از عملگر «=>» استفاده کنیم.
ماژولی که برای PWM نوشتهایم در نهایت باید چنین شکلی داشته باشد.
منبع: ترجمه از سایت deviceplus.com
منبع: عکس شاخص از سایت enjoy-digital.fr
اگر این نوشته برایتان مفید بود لطفا کامنت بنویسید.
سلام
خدا وکیلی خیلی هم دست دلبازید وهم خیلی سخاوت دارید بخشندگی کار هرکس نیست یکی ازخصلت های خداست بخشندگی شنیده اید که گویند درم داران عالم را کرم نیست؟
و کرم داران عالم را هم درم نیست؟
پس نتیجه هردو مثل هم هستند چرا چون خدا نمی خواهد بالاتراز خودش بخشنده باشد الی کسانی مثل شما که علم بخشش می کنند خدا وند این افراد را در سطر خودش بخشش و برکت به علمش میدهد