عملیات تقسیم در FPGA

پیاده‌سازی عملیات تقسیم در FPGA

آیا می‌دانید برخلاف عمل جمع و ضرب، عملگری که بتواند تقسیم در FPGA را پیاده‌سازی کند وجود ندارد؟!

در عین حال، عمل تقسیم یکی از ملزومات مهم پیاده‌سازی الگوریتم‌های پردازش سیگنال است!

More...

در این برنامه ویدئویی، شما را با نحوه پیاده‌سازی عملیات تقسیم در FPGA به کمک IPی تقسیم کننده آشنا می‌کنم.

برای آشنایی با نحوه استفاده از IPها در نرم‌افزار ISE این برنامه ویدئویی را ببینید…

به کمک IPی تقسیم کننده در نرم‌افزار ISE می‌توانید عملیات تقسیم در FPGA را با توجه به نیازمندی‌های پروژه‌تان پیاده‌سازی کنید.

مثلا می‌توانید تقسیم کننده را با عرض بیت‌های مختلف یا به صورت علامتدار پیاده‌سازی کنید.

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

در این برنامه، یک مثال کامل از نحوه استفاده از IPی تقسیم کننده هم مطرح کردم و آن را به کمک نرم‌افزار ISim شبیه‌سازی کردم.

برای آشنایی با نحوه شبیه‌سازی مدارات دیجیتال به کمک نرم‌افزار ISim این برنامه ویدئویی را ببینید…

ویدئو یا متن؟

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

برای دانلود نسخه با کیفیت این ویدئو، روی دکمه زیر کلیک کنید:

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

از لحاظ پیاده‌سازی، عمل تقسیم نسبت به سه عمل دیگر، تفاوت‌هایی دارد که در این مقاله قصد دارم در مورد آن و نحوه‌‌ی عملی پیاده‌سازی عملیات تقسیم در FPGA صحبت کنم.

تفاوت عملگر تقسیم با سایر عملگرهای اصلی

در زبان VHDL عملگر '+' برای جمع، '-' برای تفریق، '*' برای ضرب و '/' برای تقسیم قابل استفاده است.

اما عملگر تقسیم نسبت به عملگرهای دیگر کمی محدودتر است؛ وقتی شما در کد VHDL از عملگر مثبت، منفی یا ستاره استفاده می‌‌کنید، نرم‌‌افزار سنتز، مدار مناسبی برای عملیات جمع، تفریق و یا ضرب ایجاد می‌کند.

اما در زبان VHDL عملگر '/'، فقط برای پیاده‌سازی عملیات تقسیم یک رجیستر بر یک عدد ثابت قابل استفاده است که عدد ثابت حتما باید توانی از دو باشد. مثلاً شما می‌‌توانید به کمک عملگر تقسیم، ریجستر A را بر یک عدد ثابت مانند 128یا 256 تقسیم کنید.

اما نمی‌‌توانید به کمک این عملگر یک رجیستر را بر رجیستر دیگر تقسیم کنید؛ مثلاً نمی‌توانید عبارت A=B/C (که در آن B و C هر دو رجیستر هستند) را در کدتان بنویسید. برای تقسیم یک رجیستر بر رجیستر دیگر، باید مدار ویژه‌‌ای طراحی و پیاده‌سازی کنید.

روش‌های پیاده‌‌سازی مدار تقسیم‌‌کننده

برای طراحی این مدار، یک روش این است که خودتان یک الگوریتم را شناسایی و سپس آن را به کمک زبان VHDL پیاده‌سازی کنید.

روش بهتر، استفاده از IP تقسیم‌‌کننده است. در نرم‌‌افزار ISE، یک IP‌‌ی آماده برای پیاده‌‌سازی ماجول تقسیم‌‌کننده وجود دارد. این IP، دو ورودی و دو خروجی اصلی دارد؛ پورت‌‌های ورودی Dividend و Divisor، برای مقسوم و مقسوم‌‌علیه، و پورت‌‌های خروجی Quotient و Remainder، برای خارج‌‌قسمت و باقی‌‌مانده هستند. در شکل زیر می‌‌توانید آن‌‌ها را ملاحظه کنید:

نمایش بلوک تقسیم‌کننده

​بلوک تقسیم‌کننده

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

شمایی از عملیات تقسیم

​شمایی از عملیات تقسیم

اکنون اجازه دهید به کمک یک مثال عملی، انجام تنظیمات و نحوه‌‌ی استفاده از IP‌‌ی تقسیم‌‌کننده را بررسی کنیم.

استفاده از IP‌‌ی تقسیم‌‌کننده

برای استفاده از IP‌‌ی تقسیم‌‌کننده، ابتدا باید وارد نرم افزار ISE شوید و در بخش IP‌ها، IP‌‌ی تقسیم‌‌کننده را انتخاب کنید. سپس تنظیمات آن را بر مبنای نیاز‌‌تان انجام دهید.

به شما پیشنهاد می‌‌کنم اگر با موضوع IP‌ها آشنا نیستید، برنامه معرفی IP‌ها در نرم‌‌افزار ISE را در سایت ملاحظه کنید و بعد از آن، مجددا به این برنامه مراجعه کنید.

برای اضافه کردن IP به پروژه، همانند شکل زیر در قسمت Hierarchy کلیک‌‌راست کنید. سپس گزینه New Source را انتخاب کنید.

نمایش نحوه‌ی اضافه کردن IP به پروژه

​نحوه‌ی اضافه کردن IP به پروژه

اکنون گزینه‌‌ی IP را انتخاب و در کادر نام‌‌گذاری، نامی برای IP‌‌تان تایپ کنید؛ برای مثال من نام آن را Div قرار داده‌‌ام. سپس روی دکمه‌‌ی Next کلیک کنید.

​نمایش نحوه‌ی تعیین نام IP

​تعیین نام IP

از میان IPهایی که در پنچره‌‌ی باز شده وجود دارد، فولدر Math Function را باز کنید. سپس وارد فولدر Divider شوید و مطابق شکل زیر، روی IP Divider Generator کلیک کنید.

نمایش نحوه‌ی انتخاب IP تقسیم‌کننده از میان سایر IPها

​انتخاب IP تقسیم‌کننده

حال گزینه‌‌ی Next را انتخاب و در آخر گزینه‌‌ی Finish را کلیک کنید تا پنچره‌‌ی تنظیمات IP ظاهر شود.

تنظیمات IP‌‌ی تقسیم‌‌کننده

در این پنجره شما می‌‌توانید مطابق شکل زیر، IP را بر مبنای نیاز‌‌تان تنظیم کنید.

نمایش پنجره‌ی ​تنظیمات IP تقسیم‌کننده؛ Algorithm Type

پنجره‌ی ​تنظیمات IP تقسیم‌کننده؛ Algorithm Type

مورد اول مربوط به انتخاب الگوریتمی است که برای این Divider یا تقسیم‌‌کننده به کار می‌رود. بر مبنای توصیه‌‌ی دیتاشیت این IP، اگر عرض بیت ورودی‌‌های تقسیم‌‌کننده کمتر از ۱۶ بیت باشد، باید از الگوریتم  Radix2 استفاده کنیم و اگر عرض بیت ورودی‌‌های آن، بزرگتر از ۱۶ بیت باشد، الگوریتم High Radix را انتخاب می‌‌کنیم.

در قسمت بعدی، عبارت Dividend & Quotient Width نوشته شده است که در آن می‌‌توانید عرض بیت مقسوم و خارج‌‌قسمت را مشخص کنید. برای مثال من عدد هشت را انتخاب کرده‌‌ام. در بخش Divisor Width می‌‌توانید عرض بیت مقسوم‌‌علیه را مشخص کنید.

هم‌‌چنین برای قسمت Remainder Type مطابق شکل زیر، دو انتخاب دارید؛

نمایش ​پنجره‌ی ​تنظیمات IP تقسیم‌کننده؛ Remainder Type

پنجره‌ی ​تنظیمات IP تقسیم‌کننده؛ Remainder Type

مورد اول، Remainder است که در صورت انتخاب، در پورت خروجی Remainder، حاصل باقی‌‌مانده‌‌ی صحیح این تقسیم مشاهده خواهد شد؛ مثلاً در تقسیم عدد ۱۰ بر ۳، باقی‌‌مانده صحیح برابر با یک است؛ بنابراین در خروجی Remainder عدد یک دیده می‌‌شود.

در صورتی که گزینه‌‌ی دوم، یعنی Fractional را انتخاب کنید، خروجی کاملاً متفاوت با حالت قبل و برابر با بخش کسری خارج‌‌قسمت خواهد بود؛ مثلاً در تقسیم ۱۰ بر ۳، همان‌‌طور که می‌‌دانید می‌‌توان با اعشاری کردن خارج‌‌قسمت، تقسیم را ادامه داد؛ بنابراین خارج‌‌قسمت برابر با 3/3 می‌‌شود. (البته می‌‌توان آن را تا ارقام اعشاری بیش‌‌تری نیز ادامه داد.) در حالت Fractional، در پورت خروجی Remainder، عدد 3/0 را خواهیم داشت. ما برای این مثال، گزینه Remainder را انتخاب می‌‌کنیم.

گزینه‌‌ی بعدی Operand Sign است. در این قسمت می‌‌توانید از بین دو گزینه‌‌ی Signed و Unsigned یکی را انتخاب کنید. من گزینه‌‌ی  Signedرا انتخاب می‌‌کنم، چون می‌‌خواهم دو عدد علامت‌‌دار را بر هم تقسیم کنم.

گزینه‌‌ی دیگر، Clocks per Division است که می‌‌تواند یکی از مقادیر یک، دو، چهار و هشت را داشته باشد.

نمایش ​پنجره‌ی ​تنظیمات IP تقسیم‌کننده؛ Clock per Division

پنجره‌ی ​تنظیمات IP تقسیم‌کننده؛ Clock per Division

اگر این گزینه برابر یک باشد، ماجول می‌‌تواند یک عملیات تقسیم را در هر کلاک انجام دهد.

اگر شما آن را برابر چهار قرار دهید، هر ​چهار کلاک یکبار، یک تقسیم انجام می‌‌شود؛ یعنی در کلاک اول یک تقسیم انجام می‌‌شود، سپس سه کلاک می‌‌گذرد و در این مدت نمی‌‌توانید ورودی جدید به ماجول بدهید. پس از آن و در کلاک پنجم، دوباره یک تقسیم می‌‌تواند انجام شود. اگر شما این گزینه را روی مقادیر بالاتر تنظیم کنید، از نظر پیاده‌سازی در این تقسیم‌‌کننده تفاوت ایجاد می‌‌شود.

ما معمولا مقدار یک را برای این گزینه انتخاب می‌‌کنیم.

در پنچره‌‌ی تنظیمات، گزینه‌های کنترلی نیز وجود دارد که بر مبنای نیازتان می‌‌توانید از آن‌‌ها استفاده کنید.

نمایش ​​پنجره‌ی ​تنظیمات IP تقسیم‌کننده؛ ​Control Signals

​پنجره‌ی تنظیمات IP تقسیم‌کننده؛ Control Signals

برای مثال، گزینه‌‌های Clock Enable و Clear که به ترتیب با تیک زدن CE و SCLR فعال می‌‌شوند. در بخش شماتیک یا IP Symbol (همان قسمتی که بلوک تقسیم‌‌کننده را نشان می‌دهد) با فعال کردن هر ورودی کنترلی، پایه‌‌ی متناظر با آن پررنگ می‌‌شود؛ این بدین معنی است که این پایه پیاده‌سازی خواهد شد. البته من معمولا این پایه‌‌ها را فعال نمی‌‌کنم.

نمایش ​​​پنجره‌ی ​تنظیمات IP تقسیم‌کننده؛ شماتیک IP

پنجره‌ی ​تنظیمات IP تقسیم‌کننده؛ شماتیک IP

همان‌‌طور که گفته شد، می‌‌توان پایه‌‌های فعال را در شماتیک مشاهده کرد؛ مثلا پایه RFD (مخفف Ready For Data)، یک پایه‌‌ی خروجی تک‌‌بیتی است؛ هرگاه خروجی آن منطق ۱ باشد، شما اجازه اعمال ورودی جدید برای انجام عملیات تقسیم را دارید.

اگرClocks per Division را برابر یک قرار داده باشید، در هر کلاک اجازه دارید به ماجول تقسیم‌‌کننده ورودی جدید اعمال کنید؛ بنابراین پایه‌‌ی RFD همیشه برابر 1 خواهد بود.

اما اگر Clocks per Division را برابر ۴ قرار دهید، پایه RFD، هر ۴ کلاک یکبار و به‌‌مدت یک کلاک، ۱ است و شما دقیقاً در همان کلاک اجازه دارید که به این ماجول ورودی جدید اعمال کنید.

نکته آخر درمورد تنظیمات این است که همانند تمامی IP‌های دیگر، یک گزینه‌‌ی دیتاشیت در پایین پنجره وجود دارد؛ با کلیک بر آن، می‌‌توانید دیتاشیت این ماجول را در قالب یک فایل PDF ببینید.

نمایش ​پنجره‌ی ​تنظیمات IP تقسیم‌کننده؛ Datasheet

پنجره‌ی ​تنظیمات IP تقسیم‌کننده؛ Datasheet

در این فایل، درباره‌‌ی تمامی تنظیمات ماجول، از جمله الگوریتم‌های به کار رفته در آن و پایه‌های ورودی-خروجی توضیح داده شده است. شما می‌‌توانید در صورت داشتن سوال یا ابهام درباره‌‌ی تنظیمات IP، دیتاشیت آن را مطالعه کنید.

پس از انجام تنظیمات، با فشردن دکمه Generate، این IP ساخته شده و به پروژه‌‌ی شما اضافه می‌‌شود.

نمایش نحوه‌ی ساخت IP تقسیم‌کننده

نحوه‌ی ساخت IP تقسیم‌کننده

شبیه‌‌سازی IP‌‌ی تقسیم‌‌کننده

برای تست و شبیه‌‌سازی IP، باید یک تاپ ماجول بسازیم و IP را در آن Instant کنیم.

من یک کد بسیار ساده به عنوان تاپ ماجول آماده کرده‌‌ام که در واقع در آن چیزی به جز خود IP وجود ندارد.

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

entity Example_02_IP_Core_Divider is
    Port ( 
					Dividend_Top	 	: in  signed (7 downto 0);
					Divisor_Top 		: in  signed (7 downto 0);
					Quotient_Top 		: out signed (7 downto 0);
					Remainder_Top 		: out signed (7 downto 0);
					Clock 				: in	STD_LOGIC
			  );
end Example_02_IP_Core_Divider;

architecture Behavioral of Example_02_IP_Core_Divider is

	signal	Quotient_Int		:	std_logic_vector(7 downto 0)	:=	(others=>'0');	
	signal	Fractional_Int		:	std_logic_vector(7 downto 0)	:=	(others=>'0');	
	
	component Example_02_IP_Divider
		port (
		clk: in std_logic;
		rfd: out std_logic;
		dividend: in std_logic_vector(7 downto 0);
		divisor: in std_logic_vector(7 downto 0);
		quotient: out std_logic_vector(7 downto 0);
		fractional: out std_logic_vector(7 downto 0));
	end component;

begin

	Quotient_Top		<=	signed(Quotient_Int);
	Remainder_Top		<=	signed(Fractional_Int);

	IP_Core_Divider_Inst : Example_02_IP_Divider
			port map (
				clk 			=> Clock,
				rfd 			=> open,
				dividend 	=> std_logic_vector(Dividend_Top),
				divisor 		=> std_logic_vector(Divisor_Top),
				quotient 	=> Quotient_Int,
				fractional 	=> Fractional_Int
				);
			
end Behavioral;

در Entityی این تاپ ماجول، همان پورت‌های ورودی-خروجی IP را تعریف می‌‌کنیم؛ بنابراین پورت‌های Quotient ،Remainder ،Divisor ،Dividend و Clock را تعریف می‌‌کنیم.

اکنون باید IP تقسیم‌‌کننده را در تاپ ماجول Instant کنیم.

کافی است مطابق شکل زیر، روی IP کلیک کنید و گزینه Core Generator را از منوی پایین صفحه باز کنید.

نمایش محل گزینه‌ی View HDL Instantiation Template در نرم‌افزار ISE

محل گزینه‌ی View HDL Instantiation Template، در نرم‌افزار ISE

حال روی گزینه View HDL Instantiation Template، دبل‌‌کلیک کنید.

در نتیجه، نمونه کد Instant کردن IP ساخته می‌‌شود. اکنون باید مطابق شکل زیر، بخش Component Declaration را از کد ساخته شده انتخاب کنید و آن را در تاپ ماجول و قبل از Begin Architecture، کپی کنیم.

نمایش بخش Component Declaration

کد نمونه‌ی بخش Component Declaration جهت فرآیند Instant کردن IP

حالا مطابق شکل زیر، از کد نمونه قسمت Instant کردن این IP را انتخاب و آن را در تاپ ماجول و بعد از Begin مربوط به بخش Architecture، کپی می‌‌کنیم.

نمایش کد نمونه‌ی بخش ​I​nstantiation جهت فرآیند Instant کردن ​IP

کد نمونه‌ی بخش ​I​nstantiation جهت فرآیند Instant کردن ​IP

تنها کار باقی مانده، عملیات Port Map کردن است.

نکته‌‌ای که باید در Port Map کردن به آن دقت کنید این است که ما برای شفافیت بیش‌‌تر کد و راحتی کار، تمام پورت‌‌های برداری‌ را به صورت signed یا unsigned تعریف می‌‌کنیم.

اما پورت‌‌های برداری IP‌ها به صورت پیش‌‌فرض، STD_Logic_Vector تعریف شده‌‌اند.

تمامی پورت‌هایی که من برای پروژه تعریف کرده‌‌ام، از نوع signed هستند (خط 6 تا 11)، اما پورت‌های IPی تقسیم‌‌کننده همگی STD_Logic_Vector هستند.

بنابراین زمانی که می‌‌خواهیم IP را در کد Port Map کنیم، باید تبدیل تایپ انجام دهیم.

پورت‌‌های Dividend و Divisor که ورودی‌های IP هستند را در لحظه‌‌ی Port Map کردن و به‌ کمک عبارت ()STD_Logic_Vector تبدیل تایپ می‌‌کنیم (خط 39 و 40 از کد)؛

در واقع پورت Dividend (متعلق به تاپ ماجول)، از نوع signed است که به STD_Logic_Vector تبدیل می‌‌کنیم. سپس آن را به پورت ورودی Dividend (متعلق به IP)، اعمال می‌‌کنیم.

اما برای دو پورت خروجی quotient و fractional نمی‌‌توانیم مانند پورت‌‌های ورودی عمل کنیم.

برای پورت‌‌های خروجی، روش کار به این صورت است که دو سیگنال میانی به‌‌نام‌‌های Quotient_Int و Fractional_Int را از نوع STD_Logic_Vector تعریف می‌‌کنیم و برای Port Map کردن به IP تقسیم‌‌کننده، از آن‌‌ها (به‌‌جای پورت‌های خروجی Quotient و Fractional که تایپ آن‌ها signed است) استفاده می‌‌کنیم.

در واقع، مقدار دو خروجی quotient و fractional (متعلق به IP) را به سیگنال‌‌های Quotient_Int و Fractional_Int منتقل می‌کنیم.

همچنین همان‌‌طور که در کد می‌‌بینید، باید در محیط Concurrent، نوع دو سیگنال Quotient_Int و Fractional_Int را به signed تبدیل کرده و به خروجی‌های تاپ ماجول که Quotient و Remainder هستند اعمال کنیم.

این روش را باید در کد‌هایی با نوع داده‌‌های برداری signed وunsigned استفاده کنید تا بتوانید آن‌‌ها راPort Map  کنید.

به این ترتیب، کد ما کامل شده است و می‌‌توانیم آن را شبیه‌سازی کنیم.

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

همان‌‌طور که در کد بالا می‌‌بینید، ورودی‌‌هایی را برای شبیه‌سازی به ماجول تقسیم‌‌کننده اعمال کرده‌‌ام.

به Dividend و Divisor (یعنی مقسوم و مقسوم‌‌علیه)، ورودی‌‌های ۴۶ و ۹ اعمال شده است؛ در واقع می‌‌خواهیم عدد ۴۶ را  بر ۹ تقسیم کنیم.

اکنون مطابق شکل زیر، برای انجام شبیه‌سازی از منوی سمت چپ، گزینه‌‌ی Simulation را انتخاب و سپس روی تست‌‌بنچ کلیک کنید.

نمایش نحوه‌ی ​انتخاب گزینه‌ی شبیه‌سازی در نرم‌افزار ISE

​انتخاب گزینه‌ی شبیه‌سازی، در نرم‌افزار ISE

حال از منوی پایین صفحه، روی گزینه Simulate Behavioral Model دبل‌‌کلیک کنید تا نرم‌‌افزار ISim از دل نر‌‌افزار ISE اجرا و عمل شبیه‌سازی انجام شود.

نرم‌‌افزار ISim به صورت پیش‌‌فرض به مدت یک میکروثانیه شبیه‌‌سازی را انجام می‌‌دهد.

نمایش ​​شبیه‌سازی در نرم‌‌افزار ISim به مدت یک میکروثانیه

​​شبیه‌سازی در نرم‌‌افزار ISim، به مدت یک میکروثانیه

در سمت چپ تصویر، ورودی‌‌ها و خروجی‌‌ها نوشته‌‌شده است؛ ولی نمایش آن‌‌ها به صورت باینری است که خواندن آن‌‌ها را کمی سخت می‌‌کند.

برای تبدیل آن‌‌ها به عددهای دسیمال signed، می‌‌توانید پس از انتخاب سیگنال‌‌های موردنظر، روی آن‌‌ها کلیک‌‌راست کرده و سپس با انتخاب گزینه‌‌ی Radix، انواع داده را مشاهده کنید. من نوع signed decimal را انتخاب می‌‌کنم.

نمایش نحوه‌ی تغییر مبنای داده‌ها در  نرم‌‌افزار ISim

نحوه‌ی تغییر مبنای داده‌ها در  نرم‌‌افزار ISim

همان‌‌طور که می‌‌بینید، ۴۶ بر ۹ تقسیم شده است و عدد ۵ را در خروجی خارج قسمت، و عدد ۱ را در خروجی باقی‌‌مانده‌‌ی صحیح داریم. بنابراین عملیات تقسیم به‌ درستی انجام شده است.

نمایش ​​نتیجه‌ی شبیه‌سازی در نرم‌‌افزار ISim

​​​نتیجه‌ی شبیه‌سازی در نرم‌‌افزار ISim

امیدوارم​ از خواندن این مقاله هم لذت برده باشید و بتوانید از نکات یاد گرفته شده، در انجام پروژه‌‌هایتان استفاده کنید.

آیا برنامه ویدئویی پیاده‌سازی عملیات تقسیم در FPGA برای شما مفید بود؟

لطفا نظرتان را در مورد این برنامه در پایین همین پست با دیگران به اشتراک بگذارید. همچنین با کلیک روی هر کدام از دکمه‌های اشتراک گذاری در ستون سمت چپ این صفحه و اشتراک‌گذاری این مطلب در شبکه‌های اجتماعی می‌توانید افراد بیشتری را در یادگیری این مطالب سهیم کنید.

کانال تلگرام آموزش FPGA از صفر

برای عضویت در کانال تلگرام و دسترسی به آموزش‌های بیشتر و اطلاع سریع از زمان انتشار آموزش‌ها و تخفیف‌های ویژه، روی دکمه زیر کلیک کنید: