المثال العملي 1 : وحدة المعالجة و المنطق

عام 0 geek4arab
Spread the love

منذ فترة قمنا بوضع بعض الدروس حول تصميم الهاردوير بإستخدام لغة الـVHDL .. و الآن لنعد لإستكمالها ..

تحدثنا في الدروس السابقة عن تعريف لغة الـVHDL و لماذا نحتاج إليها و كيف ستفيدنا في تصميم الهاردوير .. و هذه المرة سنقوم بكتابة مثال عملي بتلك اللغة .. سيكون الكلام هذه المرة عن وحدة المعالجة و المنطق المعروفة بالـALU و التي هي إختصار لـArithmetic Logic Unit .. و هي وحدة من أهم الوحدات في المعالج Processor .. و هي التي تقوم بعمليات الجمع و الطرح و الضرب و سائر العمليات الرياضية و العمليات المنطقية .. هذه الوحدة لها مدخلان يكونان هما المدخلان الذان نريد أن نقوم بعمل العملية الرياضية أو المنطقية عليهما .. و هناك مخرج من المخرجات به ناتج العملية .. فمثلاً قد يكون الناتج هو حاصل ضرب المدخلين أو مجموعهما أو الفرق بينهما و غير ذلك من العمليات .. و هناك مدخل ثالث يحدد لنا ما هي العملية التي نريد .. فعلى سبيل المثال إذا كان هذا المدخل بقيمة 000 فهذا معناه أنه سيقوم بعملية جمع .. و إذا كان 001 فهذا معناه أنه سيقوم بعملية طرح و غير ذلك بحسب الذي نريد .. و الآن إنظر إلى فقرة الـVHDL التالية :

entity ALU is Port (

A : in  STD_LOGIC_VECTOR (31 downto 0);

B : in  STD_LOGIC_VECTOR (31 downto 0);

ALU_OUT : out  STD_LOGIC_VECTOR (31 downto 0);

Control : in  STD_LOGIC_VECTOR (2 downto 0));

end ALU;

هنا قمنا بتعريف الوحدة بمداخلها و مخارجها .. هناك مدخلان بطول 32bits و هما A و B و هما المدخلان الذان سنقوم بعمل العملية الرياضية أو المنطقية عليهما .. و المخرج ALU_OUT بنفس الطول أيضاً .. و هناك المدخل Control الذي سيقوم بتحديد العملية التي سنقوم بها .. و طول هذا المدخل يتم تحديده بناءاً على عدد العمليات التي ستقوم وحدة المعالجة و المنطق بها .. فعلى سبيل المثال إذا كانت ستقوم بـعملياتان فقط فيكفي أن يكون الـControl بطول بت واحد .. و إذا كانت ستقوم بأكثر من عمليتين إلى أربع عمليات فسيكون طول الـControl بـ2bits إذ أن الإحتمالات هي 00 و 01 و 10 و 11 فقط .. و هكذا .. و في هذه الحالة سنقول مثلاً أننا نريد أن نقوم بعمل ثمانية عمليات فقط و لهذا جعلنا طول الـControl بـ3bits .. بالطبع فإن وحدة المعالجة و المنطق ليست هذا فقط .. بل يوجد بها في الواقع أكثر من ذلك و لكننا سنكتفي بهذا للتبسيط ..

و بعد أن قمنا بتعريف الوحدة .. سنقوم الآن بتوصيف سلوكها الداخلي .. إنظر إلى الفقرة القادمة :

architecture Behavioral of ALU is

begin

ALU_OUT <= A + B when Control=”000″ else

A – B when Control=”001″ else

A and B when Control=”010″ else

A or B when Control=”011″ else

A xor B when Control=”100″ else

A nor B when Control=”101″ else

A nand B when Control=”110″ else

A xnor B;

end Behavioral;

هنا قمنا بتوصيف حال المخرج ALU_OUT .. و من خلال الكود السابق أعتقد بأنه من الواضح بأنه سيخرج ناتج لعمليةٍ ما على المدخلين A و B و تختلف تلك العملية بإختلاف قيمة الـControl ..

و لكي يكتمل الكود الوصفي لوحدة المعالجة و المنطق علينا أولاً أن نذكر المكتبات التي قمنا بإستخدامها في هذا الكود .. و لهذا عليك أن تضع تلك السطور في البداية :

use IEEE.STD_LOGIC_1164.ALL;

use IEEE.std_logic_unsigned.all;

المكتبة الأولى نستخدمها كي نتعامل مع الـSTD_LOGIC_VECTOR و المكتبة الثانية نستخدمها هنا كي نتعامل مع الإشارات + و-  بدلاً من عمل وحدة الجمع و الطرح بالجداول و نضع كل الإحتمالات إذ أن تلك المكتبات بها تلك الوحدات و ستضعها عندما تجد تلك الإشارات .. و هاتان المكتبتان موجودتان في المكتبة الرئيسية IEEE و لهذا أضف هذا السطر قبل ذكر المكتبتين السابقتين :

library IEEE;

و بهذا نكون قد إنتهينا من وحدة المعالجة و المنطق البسيطة .. و الآن إذهب إلى  قائمة Processes التي في الغالب تكون في أيسر الشاشة و قم بتشغيل Synthesize ثم Implement Design ..

في هذه المرحلة يبدأ البرنامج في إكتشاف الأخطاء موجودة في الكود .. و بإختصار شديد فإن مراحل تحويل الكود إلى دائرة منطقية يمر بعدة مراحل .. فأول تلك المراحل هي إكتشاف أخطاء الكود و التأكد من أنه سليم و هي مرحلة Check Syntax .. تليها مرحلة الـSynthesize هي مرحلة تكوين و إستنتاج و إختصار الدائرة المنطقية بناءاً على ما جاء في الكود من وصف لها .. و الشئ الذي نصل إليه بعد عملية الـSynthesize هو الدائرة المنطقية و بناءاً على هذا فإنك تستطيع ان ترى الدائرة المنطقية التي وصل البرنامج إليها عن طريق View RTL Schematic أو View Technology Schematic و قد بيننا الفرق بينهما في الدروس السابقة .. و أما مرحلة الـImplement فهي مرتبطة بالرقاقة القابلة للبرمجة Programmable Chip التي سوف نستخدمها لتجربة التصميم .. فيوجد العديد من الموديلات تختلف كل منها على حسب السرعة في الأداء و الحجم الأقصى للتصاميم التي تستطيع إستيعابها و غير ذلك .. و علاقة ذلك بمرحلة الـImplement هو أن في تلك المرحلة يقوم البرنامج بتحديد مكان كل دائرة منطقية داخل الرقاقة Chip و يقوم بتحديد الروابط و التوصيلات بين الدوائر و بعضها و بين الدوائر و المخرجات و غير ذلك .. و بالطبع فبإختلاف موديل الرقاقة ستختلف هذه المرحلة ..

من القائمة Project إختر Design Summary/Reports .. ستجد بها تقارير عن التصميم الذي قمت به مثل النسبة المستخدمة من الرقاقة .. فمثلاً إذا كانت أقل من 100% فهذا يعني أن الرقاقة تسع هذا التصميم و إذا كانت أكثر فهذا معناه أن التصميم كبير جداً و عليك أن تقوم بتجربته على رقاقة أكبر من التي إخترتها في البداية .. ستجد كذلك في بعض التقارير ما يتعلق بالوقت المستغرق أو التأخير Propagation Delay لكي تخرج المخرجات بالشكل الصحيح .. هذا مثلاً ستجده في  Post-PAR Static Timing Report في جدول يسمى Pad To Pad حيث ستجد علاقات بين كل مدخل و كل مخرج ..  على سبيل المثال إنظر إلى هذه المجموعة :

A<0>           |ALU_OUT<0>     |   11.034|
A<0>           |ALU_OUT<1>     |   11.711|
A<0>           |ALU_OUT<2>     |   11.409|
A<0>           |ALU_OUT<3>     |   12.226|
A<0>           |ALU_OUT<4>     |   12.059|
A<0>           |ALU_OUT<5>     |   12.509|
A<0>           |ALU_OUT<6>     |   11.218|
A<0>           |ALU_OUT<7>     |   11.411|
A<0>           |ALU_OUT<8>     |   12.278|
A<0>           |ALU_OUT<9>     |   12.717|
A<0>           |ALU_OUT<10>    |   12.608|

يظهر لنا هذا التقرير الوقت المستغرق لكي تخرج المخرجات بالشكل الصحيح إذا تغير مدخل واحد فقط .. فهنا على سبيل المثال إفترضنا في هذا الجدول أن المدخل الذي تغير هو أول بت في المدخل A .. إذا تغير هذا المدخل فإن أول بت في المخرج ALU_OUT سيتغير بعد 11.034 نانوثانية nanosecond و سيتغير البت الذي بعده بعد 11.711 .. بالطبع جاء البت الثاني متأخراً عن البت الأول إذ أن نتيجته في عملية الجمع مثلاً معتمدة عليه .. فأنت حينما تقوم بجمع رقمين تبدأ بأول خانة ولا تستطيع أن تبدأ في الخانة التي تليها إلا إذا أنهيت الخانة الأولى لإن الخانة الثانية قد تعتمد على نتيجة جمع الخانة الأولى عن طريق الفيض Carry و لهذا فنتيجتها ستأتي متأخرة عنها .. و بنفس المنطق فإن البت التي تليها سوف تكون أكثر تأخراً .. فإذا كانت البت الأولى ستتغير بعد 11.034 نانوثانية فإن البت الأخيرة ستتغير بعد 13.429 نانوثانية .. و لهذا السبب فإن جمع الرقم المكون من 8bits أسرع من جمع الرقم المكون من 32bits .. هذه الأرقام تم حسابها بالطبع بناءاً على أن أول بت في المدخل A هو الذي تغير .. أما إذا تغير البت الثاني في المدخل A فإن البت الأول في المخرج ALU_OUT لن يتغير أصلاً .. (تخيل مثلاً أنك تجمع الرقم 100 و 001 ستكون النتيجة هي 101 .. فإذا غيرت قيمة البت الثاني لتكون مثلاً 110+001 فإن قيمة البت الأول في الناتج لن يتغير و سيبدأ التغيير من البت الثاني ليكون 111) و لهذا فإذا نظرت في الجدول ستجد أن التغيرات المبنية على تغيير قمية البت الثاني بدأت من البت الثاني في الناتج و ليس الأول :

A<0>           |ALU_OUT<30>    |   14.106|
A<0>           |ALU_OUT<31>    |   13.429|
A<1>           |ALU_OUT<1>     |   10.793|
A<1>           |ALU_OUT<2>     |   10.877|
A<1>           |ALU_OUT<3>     |   11.694|
A<1>           |ALU_OUT<4>     |   11.527|
A<1>           |ALU_OUT<5>     |   11.977|
A<1>           |ALU_OUT<6>     |   10.686|
A<1>           |ALU_OUT<7>     |   10.879|
A<1>           |ALU_OUT<8>     |   11.746|
A<1>           |ALU_OUT<9>     |   12.185|

و بالطبع فإن الوقت المستغرق هنا سيكون أقل .. إذا أن تغيير البت الأول قد يتسبب في تغيير الرقم و الـ32bits كلهم .. و لكن التغيير في البت الثاني يغير فقط في الـ31bits العليا من الرقم .. و لهذا سيكون التأخير أقل .. إذا تصفحت الجدول أكثر ستجد بأن التغيير في المدخل Control الذي يحدد العملية الحسابية التي ستقوم وحدة المعالجة و المنطق بها يتسبب في تأخير كبير جداً .. فعلى سبيل المثال إنظر إلى هذا الجزء من الجدول :

Control<0>     |ALU_OUT<0>     |   15.034|
Control<0>     |ALU_OUT<1>     |   15.172|
Control<0>     |ALU_OUT<2>     |   14.870|
Control<0>     |ALU_OUT<3>     |   15.687|
Control<0>     |ALU_OUT<4>     |   15.520|
Control<0>     |ALU_OUT<5>     |   15.970|
Control<0>     |ALU_OUT<6>     |   14.679|

حيث يبدوا أن  هذا المدخل ذو حساسية كبيرة إذ أن التغيير فيه قد يعني أحياناً أن وحدة المعالجة  و المنطق ستقوم ببدأ بعملية حسابية أخرى من جديد .. على سبيل المثال قد تكون قيمة مدخل الـControl تقول بأننا نريد أن نقوم بعملية جمع و فجأة تغيرت لتقول بأننا نريد أن نقوم بعملية طرح فمعنى ذلك أننا سنلغي كل ما سبق حسابه و نقوم ببدأ عملية الطرح من جديد ..

إننا كمصممين دائماً ما نأخذ في إعتبارنا أسوأ الإحتمالات تحت أسوأ الظروف .. و لهذا السبب إذا سألتني مثلاً كم تستغرق وحدة المعالجة و المنطق لكي تقوم بعملية حسابية أو منطقية و تريد مني أن أعطيك رقماً واحداً فإنني سأعطيك قيمة التأخير التي ستحدث عند أسوأ الظروف .. و كثيراً من الأحيان تكون هي قيمة أكبر تأخير في هذا الجدول تحسباً لأسوأ الظروف ..

و هكذا نكون قد إنتهينا من هذا الدرس .. و لندع المحاكاة Simulator و كيفية عملها بلغة الـVHDL في الدرس القادم إن شاء الله ..

الكاتب geek4arab

geek4arab

مواضيع متعلقة

التعليقات مغلقة