أجزاء من
الأول والثاني والثالث .
ما تبقى من الجهاز
الشفرة التي كتبناها لمحاكاة المعالج 8080 عامة جدًا ويمكن تكييفها بسهولة للتشغيل على أي جهاز باستخدام المترجم C. ولكن من أجل لعب اللعبة نفسها ، نحتاج إلى القيام بالمزيد. سيكون علينا محاكاة معدات آلة الممرات بأكملها وكتابة التعليمات البرمجية التي تلصق الميزات المحددة لبيئة الحوسبة الخاصة بنا إلى المحاكي.
(قد تكون مهتمًا بالنظر إلى
مخطط الدائرة الخاص بالجهاز.)
توقيت
يتم تشغيل اللعبة على 280 MHz 8080. جهاز الكمبيوتر الخاص بك أسرع بكثير. لأخذ ذلك في الاعتبار ، سيتعين علينا التوصل إلى نوع من الآلية.
الانقطاعات
تم تصميم المقاطعات بحيث يمكن للمعالج معالجة المهام بأوقات تنفيذ دقيقة ، مثل الإدخال / الإخراج. يمكن للمعالج تنفيذ البرنامج ، وعندما يتم تشغيل دبوس المقاطعة ، يتوقف عن تنفيذ البرنامج الحالي ويفعل شيئًا آخر.
نحن بحاجة إلى محاكاة الطريقة التي تولد بها آلة الممرات المقاطعات.
الرسومات
يرسم Space Invaders الرسومات إلى ذاكرته في نطاق عناوين 0x2400. وحدة تحكم فيديو الأجهزة الحقيقية ستقرأ ذاكرة الوصول العشوائي وتتحكم في عرض CRT. سيتعين على برنامجنا محاكاة هذا السلوك من خلال عرض صورة للعبة في نافذة.
الأزرار
تحتوي اللعبة على أزرار مادية يقرأها البرنامج باستخدام الأمر IN للمعالج 8080. سيحتاج محاكينا إلى ربط إدخال لوحة المفاتيح بأوامر IN هذه.
ROM وذاكرة الوصول العشوائي
يجب أن أعترف: "قطعنا الزاوية" من خلال إنشاء ذاكرة تخزين مؤقت سعة 16 كيلوبايت ، والتي تتضمن 16 كيلوبايت أقل من تخصيص ذاكرة المعالج. في الواقع ، أول 2 كيلو بايت من تخصيص الذاكرة هي ذاكرة حقيقية للقراءة فقط (ROM). سوف نحتاج إلى وضع عمليات الكتابة في الذاكرة في وظيفة بحيث لا يمكن الكتابة إلى ROM.
الصوت
حتى الآن لم نقل أي شيء عن الصوت. لدى Space Invaders نظام صوت تناظري لطيف يعيد إنتاج واحد من 8 أصوات يتم التحكم فيها بواسطة أمر OUT ، والذي يتم إرساله إلى أحد المنافذ. سيتعين علينا تحويل أوامر OUT هذه لتشغيل عينات صوتية على منصتنا.
قد يبدو هذا كثيرًا من العمل ، ولكنه ليس سيئًا للغاية ، ويمكننا التحرك تدريجيًا. أول شيء نريد القيام به هو رؤية الشاشة ، التي نحتاج إلى مقاطعات ورسومات وجزء من معالجة أوامر IN و OUT.
يعرض وتحديث
الأساسيات
ربما تكون على دراية بمكونات نظام عرض الفيديو. يوجد في مكان ما في النظام نوع من ذاكرة الوصول العشوائي ، والذي يحتوي على صورة للعرض على الشاشة. في حالة الأجهزة التناظرية ، هناك معدات تقرأ ذاكرة الوصول العشوائي هذه وتحول وحدات البايت إلى جهد تناظري ينتقل إلى الشاشة.
سيساعدنا فهم أعمق للنظام عندما يتعلق الأمر بتحليل الغرض من تخصيص الذاكرة ووظائف التعليمات البرمجية.
الشاشات التناظرية لها متطلبات لمعدلات التحديث والتوقيت. تحتوي الشاشة في أي وقت على بكسل محدد محدث. تمتلئ الصورة المرسلة إلى الشاشة نقطة بنقطة ، بدءًا من الزاوية اليسرى العليا وأعلى اليمين ، ثم النقطة الأولى من السطر الثاني ، والنقطة الأخيرة من السطر الثاني ، إلخ. بعد رسم السطر الأخير على الشاشة ، يمكن لوحدة التحكم بالفيديو إنشاء مقاطعة فارغة رأسية (تُعرف أيضًا باسم VBI أو VBL).
لضمان حركة سلسة ، لا يمكن تغيير الصورة في ذاكرة الوصول العشوائي التي تتم معالجتها بواسطة وحدة تحكم الفيديو. إذا حدث تحديث ذاكرة الوصول العشوائي (RAM) في منتصف الإطار ، فسيرى العارض أجزاء من صورتين. وينتج عن ذلك تأثير "تمزق" عندما يتم عرض إطار مختلف عن الإطار الموجود في الجزء السفلي من الشاشة. إذا رأيت فاصل أسطر ، فأنت تعرف كيف يبدو.
لتجنب الثغرات ، يجب على البرنامج أن يفعل شيئًا لتجنب نقل موقع تحديث الشاشة. وهناك طريقة واحدة فقط للقيام بذلك.
يتم إنشاء VBL بعد نهاية السطر الأخير ، وعادة ما يكون هناك قدر معين من الوقت قبل إعادة رسم السطر الأول. (هذا هو الوقت العمودي الفارغ ، ويمكن أن يكون حوالي 1 مللي ثانية.)
عند تلقي VBL ، يبدأ البرنامج في عرض الشاشة من الأعلى.
يتم رسم كل خط قبل عملية عكس مسح الإطار.
تكون وحدة المعالجة المركزية دائمًا قبل عودة التشغيل السريع ، وبالتالي تتجنب فواصل الأسطر.
نظام فيديو غزاة الفضاء
تخبرنا
صفحة غنية بالمعلومات أن لدى غزاة الفضاء مقاطعتين للفيديو. أحدهما لنهاية الإطار ، ولكنه يولد أيضًا مقاطعة في منتصف الشاشة. تصف الصفحة نظام تحديث الشاشة - تقوم اللعبة برسم الرسومات في النصف العلوي من الشاشة عندما تتلقى انقطاعًا في منتصف الشاشة ، وترسم الرسومات في الجزء السفلي من الشاشة عندما تتلقى انقطاعًا في نهاية الإطار. هذه طريقة ذكية جدًا لإزالة فواصل الأسطر ، ومثال جيد على ما يمكن تحقيقه عند تطوير الأجهزة والبرامج في نفس الوقت.
يجب أن نجبر مضاهاة آلة لدينا لتوليد مثل هذه المقاطعات. إذا قمنا بتوليدها بتردد 60 هرتز ، بالإضافة إلى جهاز Space Invaders ، فسيتم رسم اللعبة بالتردد الصحيح.
في القسم التالي ، سنتحدث عن آليات المقاطعات ونفكر في كيفية محاكاتها.
الأزرار والمنافذ
يطبق 8080 الإدخال / الإخراج باستخدام تعليمات IN و OUT. يحتوي على 8 منافذ IN و OUT منفصلة - يتم تحديد المنفذ بواسطة بايت بيانات الأمر. على سبيل المثال ، سيضع
IN 3
قيمة المنفذ 3 في التسجيل A ، و
OUT 2
سيرسل A إلى المنفذ 2.
أخذت معلومات عن الغرض من كل منفذ من موقع
علم آثار الكمبيوتر . إذا لم تكن هذه المعلومات متاحة ، فسنضطر إلى الحصول عليها من خلال دراسة مخطط الدائرة ، وكذلك القراءة وتنفيذ التعليمات البرمجية خطوة بخطوة.
:
1
0 (0, )
1 Start
2 Start
3 ?
4
5
6
7 ?
2
0,1 DIP- (0:3,1:4,2:5,3:6)
2 ""
3 DIP- , 1:1000,0:1500
4
5
6
7 DIP-, 1:,0:
3
2 ( 0,1,2)
3
4
5
6 "" ? , ,
(0=a,1=b,2=c ..)
( 3,5,6 1=$01 2=$00
, (attract mode))
هناك ثلاث طرق لتنفيذ الإدخال / الإخراج في مجموعة برامجنا (والتي تتكون من محاكي 8080 ورمز الجهاز ورمز النظام الأساسي).
- قم بتضمين معرفة الآلة في محاكي 8080
- قم بتضمين 8080 معرفة محاكي في كود الجهاز
- ابتكار واجهة رسمية بين الأجزاء الثلاثة من التعليمات البرمجية لتمكين تبادل المعلومات من خلال API
لقد استبعدت الخيار الأول - من الواضح جدًا أن المحاكي يقع في أسفل سلسلة المكالمات هذه ويجب أن يظل منفصلاً. (تخيل أنك بحاجة إلى إعادة استخدام المحاكي للعبة أخرى ، وسوف تفهم ما أعنيه.) بشكل عام ، يعد نقل هياكل البيانات عالية المستوى إلى مستويات أقل حلًا معماريًا رديئًا.
اخترت الخيار 2. دعوني أظهر الكود أولاً:
while (!done) { uint8_t opcode = state->memory[state->pc]; if (*opcode == 0xdb)
يعيد هذا الكود تنفيذ معالجة رموز opc لـ IN و OUT في نفس الطبقة ، والتي تستدعي المحاكي لبقية الأوامر. في رأيي ، هذا يجعل الشفرة أنظف. يشبه هذا تجاوز أو فئة فرعية للأمرين ، والتي تشير إلى طبقة الأوتوماتون.
العيب هو أننا ننقل مضاهاة opcodes في مكانين. لن ألومك على اختيار الخيار الثالث. في الخيار الثاني ، يلزم رمز أقل ، ولكن الخيار 3 أكثر "نظافة" ، ولكن السعر هو زيادة في التعقيد. هذه مسألة اختيار أسلوب.
سجل التحول
تحتوي آلة Space Invaders على حل جهاز مثير للاهتمام يقوم بتنفيذ أمر shift قليلا. يحتوي 8080 على أوامر لتغيير 1-بت ، ولكن ستكون هناك حاجة لعشرات من 8080 فريقًا لتنفيذ التحول متعدد البت / متعدد البايت.تسمح الأجهزة الخاصة للعبة بتنفيذ هذه العمليات في بضع تعليمات فقط. بمساعدته ، يتم رسم كل إطار في مجال اللعبة ، أي أنه يتم استخدامه عدة مرات لكل إطار.
لا أعتقد أنه يمكنني شرحها أفضل من
التحليل الممتاز لعلم آثار الكمبيوتر:
; 16- :
; f 0
; xxxxxxxxyyyyyyyy
;
; 4 x y, x, :
; $0000,
; write $aa -> $aa00,
; write $ff -> $ffaa,
; write $12 -> $12ff, ..
;
; 2 ( 0,1,2) 8- , :
; offset 0:
; rrrrrrrr result=xxxxxxxx
; xxxxxxxxyyyyyyyy
;
; offset 2:
; rrrrrrrr result=xxxxxxyy
; xxxxxxxxyyyyyyyy
;
; offset 7:
; rrrrrrrr result=xyyyyyyy
; xxxxxxxxyyyyyyyy
;
; 3 .
بالنسبة لأمر OUT ، تؤدي الكتابة إلى المنفذ 2 إلى تعيين مقدار النقل ، وتؤدي الكتابة إلى المنفذ 4 إلى تعيين البيانات في سجلات النقل. تؤدي القراءة باستخدام IN 3 إلى إرجاع البيانات التي تم تحويلها حسب مقدار التغيير. في جهازي ، يتم تنفيذ هذا على النحو التالي:
-(uint8_t) MachineIN(uint8_t port) { uint8_t a; switch(port) { case 3: { uint16_t v = (shift1<<8) | shift0; a = ((v >> (8-shift_offset)) & 0xff); } break; } return a; } -(void) MachineOUT(uint8_t port, uint8_t value) { switch(port) { case 2: shift_offset = value & 0x7; break; case 4: shift0 = shift1; shift1 = value; break; } }
لوحة المفاتيح
للحصول على استجابة الجهاز ، نحتاج إلى ربط إدخال لوحة المفاتيح به. تحتوي معظم الأنظمة الأساسية على طريقة لتلقي ضغطات المفاتيح وأحداث الإصدار. سيبدو رمز النظام الأساسي للأزرار كما يلي:
if(PeekMessage(&msg,NULL,0,0,PM_REMOVE)) { if (msg.message==WM_KEYDOWN ) { if ( msg.wParam == VK_LEFT ) MachineKeyDown(LEFT); } else if (msg.message==WM_KEYUP ) { if ( msg.wParam == VK_LEFT ) MachineKeyUp(LEFT); } }
سيبدو رمز الجهاز الذي يلتصق برمز النظام الأساسي برمز المحاكي على النحو التالي:
MachineKeyDown(char key) { switch(key) { case LEFT: port[1] |= 0x20;
إذا كنت ترغب في ذلك ، يمكنك الجمع بين رمز الجهاز والمنصة كما تريد - وهذا هو خيار التنفيذ. لن أفعل ذلك لأنني سأقوم بتوصيل الجهاز إلى عدة منصات مختلفة.
الانقطاعات
بعد دراسة الدليل ، أدركت أن 8080 يعالج المقاطعات على النحو التالي:
- يقوم مصدر المقاطعة (الخارجي لوحدة المعالجة المركزية) بتعيين دبوس مقاطعة CPU.
- عندما تؤكد وحدة المعالجة المركزية استلام المقاطعة ، يمكن لمصدر المقاطعة إرسال أي رمز تشغيل إلى الناقل ويراها المعالج. (غالبًا ما يستخدمون الأمر RST.)
- تنفذ وحدة المعالجة المركزية هذا الأمر. إذا كان RST ، فهذا هو تناظري لأمر CALL لعنوان ثابت في الجزء السفلي من الذاكرة. يدفع الكمبيوتر الحالي إلى المكدس.
- يعالج الرمز الموجود في عنوان الذاكرة السفلي ما تريده المقاطعة من إخبار البرنامج. بعد اكتمال المعالجة ، ينتهي RST باستدعاء RET.
تولد معدات الفيديو للعبة مقاطعتين يجب علينا محاكاهما برمجيًا: نهاية الإطار ووسط الإطار. يتم تنفيذ كليهما عند 60 هرتز (60 مرة في الثانية). 1/60 من الثانية هي 16.6667 مللي ثانية.
لتبسيط العمل مع المقاطعات ، سأضيف وظيفة إلى محاكي 8080:
void GenerateInterrupt(State8080* state, int interrupt_num) {
يجب أن يطبق رمز النظام الأساسي مؤقتًا يمكننا الاتصال به (في الوقت الحالي ، أسميه الوقت ()). سيستخدمه رمز الجهاز لتمرير مقاطعة إلى محاكي 8080. في رمز الجهاز ، عندما تنتهي صلاحية الموقت ، سأتصل GenerateInterrupt:
while (!done) { Emulate8080Op(state); if ( time() - lastInterrupt > 1.0/60.0)
هناك بعض التفاصيل حول كيفية تعامل 8080 مع المقاطعات ، والتي لن نحاكيها. أعتقد أن هذه المعالجة ستكون كافية لأغراضنا.