مقدمة
هل تتذكر لعبة الثعبان منذ الطفولة ، حيث يجري ثعبان على الشاشة محاولاً أكل تفاحة؟ توضح هذه المقالة تنفيذنا للعبة على FPGA 1 .

الشكل 1. الشكل اللعبة
أولاً ، دعونا نقدم أنفسنا وشرح الأساس المنطقي لعملنا في المشروع. يوجد 3 منا: Tymur Lysenko و Daniil Manakovskiy و Sergey Makarov . كطلاب في السنة الأولى من جامعة Innopolis ، كان لدينا دورة في "هندسة الكمبيوتر" ، والتي يتم تدريسها بشكل احترافي وتمكن المتعلم من فهم البنية ذات المستوى المنخفض للكمبيوتر. في مرحلة ما خلال الدورة ، قدم لنا المدربون الفرصة لتطوير مشروع لـ FPGA للحصول على نقاط إضافية في الدورة. لم يكن دافعنا هو الدرجة فحسب ، بل اهتمامنا باكتساب المزيد من الخبرة في تصميم الأجهزة ، ومشاركة النتائج ، وأخيرًا ، الحصول على لعبة ممتعة.
الآن ، دعونا نذهب في تفاصيل عميقة مظلمة.
نظرة عامة على المشروع
بالنسبة لمشروعنا ، اخترنا لعبة ممتعة وسهلة التنفيذ ، وهي "الأفعى". يأتي هيكل التنفيذ على النحو التالي: أولاً ، يتم أخذ الإدخال من ذراع تحكم SPI ، ثم معالجته ، وأخيرًا ، يتم إخراج الصورة إلى شاشة VGA ويتم عرض النتيجة على شاشة من 7 أجزاء (بالسداسي). على الرغم من أن منطق اللعبة بديهي ومباشر ، إلا أن VGA وعصا التحكم كانت تحديات مثيرة للاهتمام وأدى تنفيذها إلى تجربة لعب جيدة.
اللعبة لديها القواعد التالية. يبدأ اللاعب برأس ثعبان واحد. الهدف هو أكل التفاح ، الذي يتم إنشاؤه بشكل عشوائي على الشاشة بعد أن أكلت السابقة. علاوة على ذلك ، يتم تمديد الثعبان بمقدار ذيل واحد بعد إرضاء الجوع. تتحرك ذيول واحد تلو الآخر ، تتبع الرأس. الأفعى تتحرك دائما. إذا تم الوصول إلى حدود الشاشة ، يتم نقل الثعبان إلى جانب آخر من الشاشة. إذا ضرب الرأس الذيل ، تكون اللعبة قد انتهت.
- Altera Cyclone IV (EP4CE6E22C8N) مع 6272 عنصرًا منطقيًا ، ساعة مدمجة 50 ميجاهرتز ، VGA ملون 3 بت ، عرض مكون من 8 أرقام. لا يمكن لـ FPGA إدخال إدخال تمثيلي إلى دبابيسها.
- SPI Joystick (KY-023)
- شاشة VGA تدعم معدل التحديث 60 هرتز
- Quartus Prime Lite Edition 18.0.0 Build 614
- Verilog HDL IEEE 1364-2001
- اللوح
- العناصر الكهربائية:
- 8 موصلات من الذكور والإناث
- 1 موصل أنثى-أنثى
- 1 موصل ذكر-ذكر
- 4 مقاومات (4.7 KΩ)
نظرة عامة على العمارة
تعتبر بنية المشروع عاملاً مهمًا يجب مراعاته. يوضح الشكل 2 هذه البنية من وجهة نظر المستوى الأعلى:

الشكل 2. عرض المستوى الأعلى للتصميم ( pdf )
كما ترى ، هناك العديد من المدخلات والمخرجات وبعض الوحدات. يصف هذا القسم معنى كل عنصر ويحدد أي دبابيس مستخدمة على السبورة للمنافذ.
المدخلات الرئيسية
المدخلات الرئيسية اللازمة للتنفيذ هي res_x_one و res_x_two و res_y_one و res_y_two ، والتي يتم استخدامها لاستقبال الاتجاه الحالي لعصا التحكم. يوضح الشكل 3 رسم الخرائط بين قيمهم والاتجاهات.
الإدخال | غادر | حق | لأعلى | للأسفل | لا تغيير في الاتجاه |
---|
res_x_one (PIN_30) | 1 | 0 | س | س | 1 |
res_x_two (PIN_52) | 1 | 0 | س | س | 0 |
res_y_one (PIN_39) | س | س | 1 | 0 | 1 |
res_y_two (PIN_44) | س | س | 1 | 0 | 0 |
الشكل 3. رسم تخطيطي لمدخلات عصا التحكم واتجاهاته
- clk - ساعة السبورة (PIN_23)
- إعادة تعيين - إشارة لإعادة تعيين اللعبة وإيقاف الطباعة (PIN_58)
- اللون - عند 1 ، يتم إخراج جميع الألوان الممكنة على الشاشة واستخدامها فقط لأغراض العرض التوضيحي (PIN_68)
الوحدات الرئيسية
يستخدم joystick_input لإنتاج رمز اتجاه بناءً على إدخال من عصا التحكم.
game_logic
game_logic يحتوي على كل المنطق المطلوب للعب لعبة. تقوم الوحدة بتحريك ثعبان في اتجاه معين. بالإضافة إلى ذلك ، فهي مسؤولة عن تناول التفاح واكتشاف الاصطدام. علاوة على ذلك ، فإنه يتلقى إحداثيات س وص الحالية من البكسل على الشاشة ويعيد كيانًا تم وضعه في الموضع.
VGA_Draw
يقوم الدرج بتعيين لون بكسل إلى قيمة معينة بناءً على الموضع الحالي ( iVGA_X و iVGA_Y ) والكيان الحالي ( ent ).
VGA_Ctrl
يولد تدفق البت للتحكم في خرج VGA ( V_Sync ، H_Sync ، R ، G ، B ).
SSEG_Display 2
SSEG_Display هو محرك لإخراج النتيجة الحالية على شاشة عرض 7 أجزاء.
Vga_clk
يستقبل VGA_clk ساعة 50 ميجا هرتز ويقطعها إلى 25.175 ميجا هرتز.
game_upd_clk
game_upd_clk هي وحدة تولد ساعة خاصة تؤدي إلى تحديث حالة اللعبة.
المخرجات
- VGA_B - دبوس أزرق VGA (PIN_144)
- VGA_G - دبوس أخضر VGA (PIN_1)
- VGA_R - دبوس VGA أحمر (PIN_2)
- VGA_HS - التزامن الأفقي VGA (PIN_142)
- VGA_VS - التزامن الرأسي VGA (PIN_143)
- sseg_a_to_dp - يحدد أي من الأجزاء الثمانية المضاءة (PIN_115 و PIN_119 و PIN_120 و PIN_121 و PIN_124 و PIN_125 و PIN_126 و PIN_127)
- sseg_an - يحدد أيًا من شاشات العرض السبعة التي سيتم استخدامها (PIN_128 ، PIN_129 ، PIN_132 ، PIN_133)
التنفيذ

الشكل 4. عصا التحكم SPI (KY-023)
أثناء تنفيذ وحدة الإدخال ، اكتشفنا أن العصا تنتج إشارة تناظرية. تحتوي عصا التحكم على 3 مواضع لكل محور:
- أعلى - ~ 5V الناتج
- منتصف - خرج 2.5V
- منخفض - خرج 0V
الإدخال مشابه جدًا للنظام الثلاثي: بالنسبة للمحور X ، لدينا حالة true
(يسار) false
(يمين) وحالة undetermined
، حيث لا يكون ذراع التحكم على اليسار ولا على اليمين. تكمن المشكلة في أن لوحة FPGA يمكنها فقط معالجة إدخال رقمي. لذلك لا يمكننا تحويل هذا المنطق الثلاثي إلى ثنائي بمجرد كتابة بعض التعليمات البرمجية. كان الحل المقترح الأول هو العثور على محول رقمي تناظري ، لكننا قررنا بعد ذلك استخدام معرفتنا بالفيزياء في المدرسة وتنفيذ مقسم الجهد 3 . لتحديد الحالات الثلاث ، سنحتاج إلى وحدتين: 00 false
، 01 غير undefined
و 11 true
. بعد بعض القياسات ، اكتشفنا أن الحد بين صفر وواحد على السبورة يبلغ 1.7 فولت تقريبًا. وهكذا ، قمنا ببناء المخطط التالي (الصورة التي تم إنشاؤها باستخدام circlab 4 ):

الرقم 5 الشكل 5. دائرة ل ADC لعصا التحكم
تم تنفيذ التنفيذ المادي باستخدام عناصر مجموعة Arduino ، ويبدو كما يلي:

الشكل 6. تنفيذ ADC
تأخذ دارتنا مدخلًا واحدًا لكل محور وتنتج نواتج: الأول يأتي مباشرة من العصا ويصبح صفرًا فقط إذا خرج عصا التحكم zero
. والثاني هو 0 في حالة undetermined
، ولكن لا يزال 1 في true
. هذه هي النتيجة الدقيقة التي توقعناها.
منطق وحدة الإدخال هو:
- نترجم منطقنا الثلاثي إلى أسلاك ثنائية بسيطة لكل اتجاه.
- في كل دورة ساعة ، نتحقق مما إذا كان اتجاه واحد فقط
true
(لا يمكن للثعبان الانتقال قطريًا) ؛ - قارنا اتجاهنا الجديد بالاتجاه السابق لمنع الأفعى من أكل نفسها من خلال عدم السماح للاعب بتغيير الاتجاه إلى الاتجاه المعاكس.
جزء من كود وحدة الإدخال reg left, right, up, down; initial begin direction = `TOP_DIR; end always @(posedge clk) begin //1 left = two_resistors_x; right = ~one_resistor_x; up = two_resistors_y; down = ~one_resistor_y; if (left + right + up + down == 3'b001) //2 begin if (left && (direction != `RIGHT_DIR)) //3 begin direction = `LEFT_DIR; end //same code for other directions end end
الإخراج إلى VGA
قررنا إجراء إخراج بدقة 640 × 480 على شاشة 60 هرتز تعمل بسرعة 60 إطارًا في الثانية.
تتكون وحدة VGA من جزأين رئيسيين: سائق ودرج . يقوم برنامج التشغيل بتوليد تيار بت يتكون من إشارات تزامن رأسية وأفقية ولون يتم إعطاؤه لمخرجات VGA. تصف مقالة 5 كتبها SlavikMIPT المبادئ الأساسية للعمل مع VGA. لقد قمنا بتعديل السائق من المقال إلى مجلسنا.
قررنا تقسيم الشاشة إلى شبكة عناصر 40x30 ، تتكون من مربعات 16x16 بكسل. يمثل كل عنصر كيان لعبة واحد: إما تفاحة أو رأس ثعبان أو ذيل أو لا شيء.
كانت الخطوة التالية في تنفيذنا هي إنشاء النقوش المتحركة للكيانات.
يحتوي Cyclone IV على 3 بتات فقط لتمثيل لون على VGA (1 لـ Red و 1 لـ Green و 1 لـ Blue). بسبب هذا القيد ، كنا بحاجة إلى تنفيذ محول لتناسب ألوان الصور مع تلك المتاحة. لهذا الغرض ، أنشأنا نصًا بيثونًا يقسم قيمة RGB لكل بكسل إلى 128.
نص بايثون from PIL import Image, ImageDraw filename = "snake_head" index = 1 im = Image.open(filename + ".png") n = Image.new('RGB', (16, 16)) d = ImageDraw.Draw(n) pix = im.load() size = im.size data = [] code = "sp[" + str(index) + "][{i}][{j}] = 3'b{RGB};\\\n" with open("code_" + filename + ".txt", 'w') as f: for i in range(size[0]): tmp = [] for j in range(size[1]): clr = im.getpixel((i, j)) vg = "{0}{1}{2}".format(int(clr[0] / 128),
أصلي | بعد النص |

| 
|
الشكل 7. مقارنة بين المدخلات والمخرجات
الغرض الرئيسي من الدرج هو إرسال لون بكسل إلى VGA بناءً على الموضع الحالي ( iVGA_X و iVGA_Y ) والكيان الحالي ( ent ). جميع الرموز المتحركة مضمنة ولكن يمكن تغييرها بسهولة من خلال إنشاء رمز جديد باستخدام البرنامج النصي أعلاه.
منطق الدرج always @(posedge iVGA_CLK or posedge reset) begin if(reset) begin oRed <= 0; oGreen <= 0; oBlue <= 0; end else begin // DRAW CURRENT STATE if (ent == `ENT_NOTHING) begin oRed <= 1; oGreen <= 1; oBlue <= 1; end else begin // Drawing a particular pixel from sprite oRed <= sp[ent][iVGA_X % `H_SQUARE][iVGA_Y % `V_SQUARE][0]; oGreen <= sp[ent][iVGA_X % `H_SQUARE][iVGA_Y % `V_SQUARE][1]; oBlue <= sp[ent][iVGA_X % `H_SQUARE][iVGA_Y % `V_SQUARE][2]; end end end
الإخراج إلى عرض 7-قطعة
من أجل تمكين اللاعب من رؤية نتيجته ، قررنا إخراج نتيجة لعبة إلى عرض 7 أجزاء. نظرًا لضيق الوقت ، استخدمنا الرمز من وثائق لوحة المبتدئين EP4CE6 2 . تقوم هذه الوحدة بإخراج رقم سداسي عشري على الشاشة.
منطق اللعبة
أثناء التطوير ، جربنا العديد من الأساليب ، ومع ذلك ، انتهى بنا الأمر إلى النهج الذي يتطلب الحد الأدنى من الذاكرة ، وهو سهل التنفيذ في الأجهزة ، ويمكن أن يستفيد من الحسابات المتوازية.
تقوم الوحدة بالعديد من الوظائف. نظرًا لأن VGA يرسم بكسل في كل دورة ساعة بدءًا من الجزء العلوي الأيسر الذي ينتقل إلى الجزء السفلي الأيمن ، فإن وحدة VGA_Draw ، المسؤولة عن إنتاج لون للبكسل ، تحتاج إلى تحديد اللون الذي تستخدمه للإحداثيات الحالية. هذا ما يجب أن تخرجه وحدة منطق اللعبة - رمز كيان للإحداثيات المحددة.
علاوة على ذلك ، يجب عليه تحديث حالة اللعبة فقط بعد رسم الشاشة الكاملة. يتم استخدام الإشارة التي تنتجها وحدة game_upd_clk لتحديد وقت التحديث.
حالة اللعبة
تتكون حالة اللعبة من:
- إحداثيات رأس الأفعى
- مجموعة من إحداثيات ذيل الثعبان. الصفيف محدود بـ 128 عنصر في تطبيقنا
- عدد ذيول
- إحداثيات تفاحة
- اللعبة فوق العلم
- فازت اللعبة بالعلم
يتضمن تحديث حالة اللعبة عدة مراحل:
- انقل رأس الثعبان إلى إحداثيات جديدة بناءً على اتجاه معين. إذا اتضح أن الإحداثيات على حافتها وتحتاج إلى مزيد من التغيير ، فيجب على الرأس القفز إلى حافة أخرى من الشاشة. على سبيل المثال ، يتم تعيين اتجاه إلى اليسار ، وإحداثيات X الحالية هي 0. لذلك ، يجب أن تصبح إحداثيات X الجديدة مساوية لآخر عنوان أفقي.
- يتم اختبار الإحداثيات الجديدة لرأس الثعبان مقابل إحداثيات التفاح:
2.1. إذا كانت متساوية ولم يكن المصفوفة ممتلئة ، أضف ذيلًا جديدًا إلى المصفوفة وقم بزيادة عداد الذيل. عندما يصل العداد إلى أعلى قيمته (128 في حالتنا) ، يتم إعداد العلم الذي ربحته وهذا يعني أن الثعبان لا يمكن أن ينمو بعد الآن ، ولا تزال اللعبة مستمرة. يتم وضع الذيل الجديد على الإحداثيات السابقة لرأس الثعبان. يجب أخذ الإحداثيات العشوائية لـ X و Y لوضع تفاحة هناك.
2.2. في حالة عدم تساويها ، قم بتبديل إحداثيات ذيول ذيول متسلسلة. (n + 1) - يجب أن يتلقى الذيل إحداثيات n-th ، في حالة إضافة الذيل n-th قبل (n + 1) -th. يتلقى الذيل الأول الإحداثيات القديمة للرأس. - تحقق ، إذا كانت الإحداثيات الجديدة لرأس الثعبان تتزامن مع إحداثيات أي ذيل. إذا كان هذا هو الحال ، يتم رفع اللعبة فوق العلم وتتوقف اللعبة.
إنشاء إحداثيات عشوائية
أرقام عشوائية تم إنتاجها بأخذ بتات عشوائية تم إنشاؤها بواسطة مسجلات إزاحة التغذية الراجعة الخطية 6 بت (LFSR) 6 . لتناسب الأرقام في شاشة ، يتم تقسيمها حسب أبعاد شبكة اللعبة ويتم أخذ الباقي.
الخاتمة
بعد 8 أسابيع من العمل ، تم تنفيذ المشروع بنجاح. لقد كان لدينا بعض الخبرة في تطوير اللعبة وانتهى بنا الأمر بنسخة ممتعة من لعبة "Snake" لـ FPGA. اللعبة قابلة للعب ، وتحسنت مهاراتنا في البرمجة وتصميم الهندسة المعمارية والمهارات الناعمة.
الشرائح المعترف بها
نود أن نعرب عن شكرنا الخاص وامتناننا لأستاذينا محمد فهيم وألكسندر تورماسوف لمنحنا المعرفة العميقة والفرصة لوضعها موضع التنفيذ. نشكر بحرارة فلاديسلاف أوستانكوفيتش لتزويدنا بالأجهزة الأساسية المستخدمة في المشروع وتيمور كولماتوف للمساعدة في تصحيح الأخطاء. لن ننسى أن نتذكر Anastassiya Boiko رسم العفاريت الجميلة للعبة. كما نود أن نعرب عن تقديرنا الصادق لرباب معروف لتدقيقه وتحرير هذا المقال.
شكرا لجميع الذين ساعدونا في اختبار اللعبة وحاولوا تسجيل رقم قياسي. آمل أن تستمتع باللعب!
المراجع
[1]: مشروع على جيثب
[2]: [FPGA] EP4CE6 Starter Board Documentation
[3]: مقسم الجهد
[4]: أداة لنمذجة الدوائر
[5]: محول VGA لـ FPGA Altera Cyclone III
[6]: سجل نوبات التغذية المرتدة الخطية (LFSR) على ويكيبيديا
LFSR في FPGA - VHDL و Verilog Code
نسيج تفاح
فكرة لتوليد أرقام عشوائية
بالنيتكار ، س. (2003). Verilog HDL: دليل التصميم الرقمي والتوليف ، الطبعة الثانية.