الجزء
الأول والثاني .
8080 محاكي المعالج
قذيفة المحاكي
يجب أن يكون لديك الآن كل المعرفة اللازمة لبدء إنشاء محاكي معالج 8080.
سأحاول أن أجعل الكود الخاص بي واضحًا قدر الإمكان ، يتم تنفيذ كل كود op بشكل منفصل. عندما تشعر بالراحة تجاهه ، قد ترغب في إعادة كتابته لتحسين الأداء أو إعادة استخدام التعليمات البرمجية.بادئ ذي بدء ، سوف أقوم بإنشاء بنية ذاكرة تحتوي على حقول لكل شيء بدا لي ضروريًا عند كتابة أداة تفكيك. سيكون هناك أيضًا مكان لمخزن الذاكرة المؤقت ، والذي سيكون ذاكرة الوصول العشوائي.
typedef struct ConditionCodes { uint8_t z:1; uint8_t s:1; uint8_t p:1; uint8_t cy:1; uint8_t ac:1; uint8_t pad:3; } ConditionCodes; typedef struct State8080 { uint8_t a; uint8_t b; uint8_t c; uint8_t d; uint8_t e; uint8_t h; uint8_t l; uint16_t sp; uint16_t pc; uint8_t *memory; struct ConditionCodes cc; uint8_t int_enable; } State8080;
الآن قم بإنشاء إجراء باستدعاء خطأ سينهي البرنامج بخطأ. سيبدو شيء من هذا القبيل:
void UnimplementedInstruction(State8080* state) {
دعونا ننفذ بعض الشفرات.
void Emulate8080Op(State8080* state) { unsigned char *opcode = &state->memory[state->pc]; switch(*opcode) { case 0x00: break;
ها أنت ذا. لكل كود تشغيل ، نقوم بتغيير الحالة والذاكرة ، كما يفعل الأمر الذي يتم تنفيذه على 8080 حقيقي.
يحتوي 8080 على حوالي 7 أنواع ، اعتمادًا على كيفية تصنيفهم:
- نقل البيانات
- الحساب
- منطقي
- الفروع
- المكدس
- المدخلات والمخرجات
- خاص
دعونا نلقي نظرة على كل منها على حدة.
المجموعة الحسابية
التعليمات الحسابية هي العديد من 256 شفرة لمعالج 8080 ، والتي تشمل أنواعًا مختلفة من الجمع والطرح. تعمل معظم التعليمات الحسابية مع السجل A وتحفظ النتيجة في A. (يُسمى السجل A أيضًا المجمع).
من المثير للاهتمام ملاحظة أن هذه الأوامر تؤثر على رموز الشروط. يتم تعيين رموز الدولة (تسمى أيضًا الأعلام) اعتمادًا على نتيجة الأمر الذي تم تنفيذه. لا تؤثر جميع الأوامر على الأعلام ، ولا تؤثر جميع الفرق التي تؤثر على الأعلام على جميع الأعلام في وقت واحد.
الأعلام 8080
في معالج 8080 ، تسمى الأعلام Z و S و P و CY و AC.
- Z (صفر ، صفر) تأخذ القيمة 1 عندما تكون النتيجة صفر
- تأخذ S (العلامة) القيمة 1 عندما يتم إعطاء البتة 7 (البتة الأكثر أهمية ، البتة الأكثر أهمية ، MSB) للأمر الرياضي
- يتم تعيين P (التماثل ، التماثل) عندما تكون النتيجة زوجية ، ويتم إعادة ضبطها عندما تكون فردية
- CY (حمل) يأخذ القيمة 1 عندما ، نتيجة للأمر ، يتم تنفيذ التحويل أو الاقتراض في بت عالي الترتيب
- يستخدم AC (حمل مساعد) بشكل رئيسي للرياضيات BCD (عشري مشفر ثنائي). لمزيد من التفاصيل ، انظر الدليل ، في غزاة الفضاء لا يتم استخدام هذه العلامة.
يتم استخدام رموز الولاية في أوامر الفرع الشرطي ، على سبيل المثال ، ينفذ JZ التفرع فقط إذا تم تعيين علامة Z.
تحتوي معظم التعليمات على ثلاثة أشكال: للسجلات والقيم الفورية والذاكرة. دعنا ننفذ بعض التعليمات لفهم أشكالها ونرى كيف يبدو العمل مع رموز الولاية. (لاحظ أني لا أقوم بتطبيق علامة النقل المساعدة لأنها غير مستخدمة. إذا قمت بتطبيقها ، فلن أتمكن من اختبارها.)
نموذج التسجيل
هنا مثال على تنفيذ تعليماتين مع نموذج التسجيل ؛ في الأول ، قمت بنشر الشفرة لتسهيل فهم عملها ، وفي الثانية ، يتم تقديم نموذج أكثر إحكاما يقوم بنفس الشيء.
case 0x80:
أقوم بمحاكاة أوامر الرياضيات ذات 8 بت برقم 16 بت. وهذا يجعل من السهل تتبع الحالات التي تولد فيها الحسابات حملًا.
نموذج للقيم الفورية
نموذج القيم المباشرة هو نفسه تقريبًا ، باستثناء أن البايت بعد الأمر هو مصدر الإضافة. نظرًا لأن "opcode" هو مؤشر للأمر الحالي في الذاكرة ، فسيكون opcode [1] على الفور البايت التالي.
case 0xC6:
شكل للذاكرة
في نموذج الذاكرة ، ستتم إضافة بايت يشير إليه العنوان المخزن في زوج من تسجيلات HL.
case 0x86:
ملاحظات
يتم تنفيذ التعليمات الحسابية المتبقية بطريقة مماثلة. الإضافات:
- في إصدارات مختلفة مع حمل (ADC ، ACI ، SBB ، SUI) ، وفقًا للدليل المرجعي ، نستخدم بتات الحمل في الحسابات.
- يؤثر كل من INX و DCX على أزواج التسجيل ؛ ولا تؤثر هذه الأوامر على العلامات.
- DAD هو أمر آخر لزوجين من السجلات ، فهو يؤثر فقط على علم الحمل
- INR و DCR لا تؤثر على علم الحمل
مجموعة الفرع
بعد التعامل مع رموز الولاية ، ستصبح مجموعة الفروع واضحة بما يكفي بالنسبة لك. هناك نوعان من التفرع - التحولات (JMP) والمكالمات (CALL). يقوم JMP فقط بتعيين الكمبيوتر إلى قيمة وجهة القفز. يستخدم CALL للروتين ، ويكتب عنوان المرسل إلى المكدس ، ثم يعين الكمبيوتر عنوان الوجهة. إرجاع RET من CALL ، تلقي العنوان من المكدس وكتابته إلى الكمبيوتر.
ينتقل كل من JMP و CALL فقط إلى العناوين المطلقة التي يتم ترميزها بالبايت بعد شفرة التشغيل.
Jmp
يتفرع أمر JMP دون قيد أو شرط إلى عنوان الوجهة. هناك أيضًا أوامر فرع شرطية لجميع رموز الحالة (باستثناء AC):
- JNZ و JZ للصفر
- JNC و JC للهجرة
- JPO و JPE من أجل التكافؤ
- JP (زائد) و JM (ناقص) للعلامة
هنا تنفيذ بعضها:
case 0xc2:
اتصل وأعد
يدفع CALL عنوان التعليمات على المكدس بعد المكالمة ، ثم ينتقل إلى عنوان الوجهة. يتلقى RET العنوان من المكدس ويحفظه على جهاز الكمبيوتر. توجد نسخ شرطية من CALL و RET لجميع الدول.
- CZ ، CNZ ، RZ ، RNZ للصفر
- CNC ، CC ، RNC ، RC للتحويل
- CPO ، CPE ، RPO ، RPE من أجل التكافؤ
- CP ، CM ، RP ، RM للتوقيع
case 0xcd:
ملاحظات
- ينتقل الأمر PCHL دون قيد أو شرط إلى عنوان في زوج من تسجيلات HL.
- لم أقوم بتضمين RST الذي تمت مناقشته سابقًا في هذه المجموعة. يكتب عنوان الإرجاع إلى المكدس ، ثم ينتقل إلى العنوان المحدد مسبقًا في الجزء السفلي من الذاكرة.
مجموعة منطقية
تقوم هذه المجموعة بتنفيذ العمليات المنطقية (انظر المنشور
الأول من البرنامج التعليمي). بحكم طبيعتها ، فهي تشبه مجموعة حسابية في أن معظم العمليات تعمل مع السجل A (محرك الأقراص) ، ومعظم العمليات تؤثر على الأعلام. يتم تنفيذ جميع العمليات على قيم 8 بت ، في هذه المجموعة لا توجد أوامر تؤثر على أزواج من السجلات.
العمليات المنطقية
AND ، أو ، NOT (CMP) و "خاص أو" (XOR) تسمى العمليات المنطقية. أو ووضحت في وقت سابق. يغير الأمر NOT (للمعالج 8080 الذي يطلق عليه CMA ، أو المجمع المتراكم) قيم البت ببساطة - تصبح جميع الوحدات أصفارًا ، وتصبح الأصفار منها.
أرى XOR على أنه "أداة التعرف على الاختلافات". جدول الحقيقة الخاص بها يبدو كما يلي:
AND و OR و XOR لها نموذج للسجلات والذاكرة والقيم الفورية. (يحتوي CMP فقط على أمر حساس لحالة الأحرف). هنا تنفيذ زوج من opcodes:
case 0x2F:
أوامر التحول الدوري
تغير هذه الأوامر ترتيب البتات في السجلات. يؤدي النقل إلى اليمين إلى تحريكها يمينًا بمقدار بت واحد ، والتحول إلى اليسار - يسارًا بت واحد:
(0b00010000) = 0b00001000
(0b00000001) = 0b00000010
يبدو أنها لا قيمة لها ، ولكن في الواقع الأمر ليس كذلك. يمكن استخدامها للتكاثر والقسمة بسلطات اثنين. خذ التحول الأيسر كمثال.
0b00000001
هو رقم عشري 1 ،
0b00000001
إلى اليسار يجعله
0b00000010
، أي رقم عشري 2. إذا قمنا بتحول آخر إلى اليسار ، نحصل على
0b00000100
، أي 4. تحول آخر إلى اليسار ،
0b00000100
8. هذا سيعمل مع أي بالأرقام: 5 (
0b00000101
) عند
0b00000101
إلى اليسار يعطي 10 (
0b00001010
). يعطي التحول الأيسر الآخر 20 (
0b00010100
). التحول إلى اليمين يفعل نفس الشيء ، ولكن للانقسام.
لا يحتوي 8080 على أمر ضرب ، ولكن يمكن تنفيذه باستخدام هذه الأوامر. إذا فهمت كيفية القيام بذلك ، فستحصل على نقاط إضافية. بمجرد طرح هذا السؤال علي في مقابلة. (لقد فعلت ذلك ، على الرغم من أنني استغرقت بضع دقائق).
تقوم هذه الأوامر بتدوير محرك الأقراص دوريًا وتؤثر فقط على علم الحمل. فيما يلي أمرين:
case 0x0f:
مقارنة
مهمة CMP و CPI هي فقط تعيين العلامات (للتفرع). يفعلون ذلك عن طريق طرح الأعلام ، ولكن ليس تخزين النتيجة.
- بالتساوي: إذا كان رقمان متساويين ، فسيتم تعيين علامة Z ، حيث أن طرحهما من بعضهما يعطي صفرًا.
- أكبر من: إذا كانت A أكبر من القيمة التي تتم مقارنتها ، فسيتم مسح علامة CY (حيث يمكن إجراء الطرح بدون الاقتراض).
- أصغر: إذا كانت A أقل من القيمة المقارنة ، فسيتم تعيين علامة CY (لأنه يجب على A إكمال الاقتراض لإكمال الطرح).
هناك إصدارات من هذه الأوامر للسجلات والذاكرة والقيم الفورية. التنفيذ هو طرح بسيط بدون حفظ النتيجة:
case 0xfe:
CMC و STC
يكملون المجموعة المنطقية. يتم استخدامها لتعيين ومسح علم الحمل.
مجموعة المدخلات والمخرجات والأوامر الخاصة
لا يمكن تعيين هذه الأوامر لأي فئة أخرى. سأذكرها من أجل الاكتمال ، ولكن يبدو لي أنه سيتعين علينا العودة إليها مرة أخرى عندما نبدأ في محاكاة أجهزة Space Invaders.
- يعمل كل من EI و DI على تمكين أو تعطيل قدرة المعالج على معالجة المقاطعات. أضفت إشارة interrupt_enabled إلى بنية حالة المعالج ، وقمت بتعيين / إعادة تعيينها باستخدام هذه الأوامر.
- يبدو أن RIM و SIM يستخدمان بشكل أساسي في I / O التسلسلي. إذا كنت مهتمًا ، يمكنك قراءة الدليل ، ولكن لا يتم استخدام هذه الأوامر في Space Invaders. لن أقوم بمحاكاة لهم.
- HLT هي محطة. لا أعتقد أننا بحاجة إلى محاكاة ذلك ، ولكن يمكنك استدعاء رمز الإقلاع عن التدخين (أو الخروج (0)) عندما ترى هذا الأمر.
- IN و OUT هما أمران يستخدمهما جهاز المعالج 8080 للتواصل مع المعدات الخارجية. بينما نقوم بتنفيذها ، لكنهم لن يفعلوا أي شيء سوى تخطي بايت البيانات الخاصة بهم. (في وقت لاحق سنعود لهم).
- NOP "لا يوجد عملية". أحد تطبيقات NOP هو التحكم في توقيت اللوحة (يستغرق تنفيذ أربع دورات لوحدة المعالجة المركزية).
تطبيق آخر من NOP هو تعديل التعليمات البرمجية. لنفترض أننا بحاجة إلى تغيير رمز ROM الخاص باللعبة. لا يمكننا فقط حذف الرموز غير الضرورية ، لأننا لا نريد تغيير جميع أوامر CALL و JMP (ستكون غير صحيحة إذا تم نقل جزء واحد على الأقل من التعليمات البرمجية). مع NOP يمكننا التخلص من الرمز.
إضافة رمز أكثر صعوبة بكثير! يمكنك إضافته من خلال إيجاد مساحة في مكان ما في ROM وتغيير الأمر إلى JMP.مجموعة المكدس
لقد أكملنا بالفعل الآليات لمعظم الفرق في مجموعة المكدس. إذا قمت بالعمل معي ، فسيكون من السهل تنفيذ هذه الأوامر.
PUSH و POP
يعمل PUSH و POP فقط مع أزواج التسجيل. يكتب PUSH زوج من السجلات إلى المكدس ، ويأخذ بروتوكول POP 2 بايت من أعلى المكدس ويكتبها إلى زوج من السجلات.
هناك أربعة شفرات opcodes لـ PUSH و POP ، واحد لكل زوج من الأزواج: BC و DE و HL و PSW. PSW هو زوج خاص من سجلات إشارة محرك الأقراص ورموز الحالة. هنا تنفيذي لـ PUSH و POP لـ BC و PSW. لا توجد تعليقات فيه - لا أعتقد أن هناك أي شيء صعب بشكل خاص هنا.
case 0xc1:
SPHL و XTHL
هناك فريقان آخران في مجموعة المكدس - SPHL و XTHL.
SPHL
بنقل HL إلى SP (مما يجبر SP على الحصول على عنوان جديد).XTHL
ما يوجد فوق المكدس بما يوجد في زوج من تسجيلات HL. لماذا تحتاج للقيام بذلك؟ لا اعرف.
المزيد عن الأرقام الثنائية
عند كتابة برنامج كمبيوتر ، فإن أحد القرارات التي تحتاج إلى اتخاذها هو اختيار نوع البيانات المستخدمة للأرقام - ما إذا كنت تريد أن تكون سلبية وما هو الحد الأقصى لحجمها. بالنسبة لمحاكي وحدة المعالجة المركزية ، نحتاج إلى نوع البيانات لمطابقة نوع البيانات لوحدة المعالجة المركزية المستهدفة.
موقعة وغير موقعة
عندما بدأنا في الحديث عن الأرقام السداسية ، اعتبرناها غير موقعة - أي أن كل رقم ثنائي من الرقم السداسي العشري له قيمة موجبة ، وكان كل واحد يعتبر قوة لاثنين (وحدات ، اثنان ، أربعة ، إلخ).
تعاملنا مع قضية تخزين الكمبيوتر للأرقام السالبة. إذا كنت تعلم أن البيانات المعنية تحتوي على علامة ، أي أنها يمكن أن تكون سالبة ، فيمكنك التعرف على رقم سالب بأهم بت من الرقم (أهم بت ، MSB). إذا كان حجم البيانات بايت واحد ، يكون كل رقم بقيمة بت MSB معينة سالبة ، ويكون كل رقم له صفر MSB موجبًا.
يتم تخزين قيمة رقم سالب كرمز إضافي. إذا كان لدينا رقم موقع وكان MSB يساوي واحدًا ، ونريد معرفة ما هو الرقم ، فيمكننا تحويله على النحو التالي: تنفيذ ثنائي "NOT" للأرقام السداسية ، ثم إضافة واحد.
على سبيل المثال ، بالنسبة للرقم السداسي 0x80 ، يتم تعيين بت MSB ، أي أنه سلبي. ثنائي "NOT" للرقم 0x80 هو 0x7f ، أو العشري 127. 127 + 1 = 128. أي أن 0x80 في الرقم العشري هو -128. المثال الثاني: 0xC5. ليس (0xC5) = 0x3A = عشري 58 +1 = عشري 59. أي أن 0xC5 عشري -59.
ما يثير الدهشة في الأرقام التي تحتوي على رمز إضافي هو أنه يمكننا إجراء حسابات معهم كما هو الحال مع الأرقام غير الموقعة ،
وستظل تعمل . لا يحتاج الكمبيوتر لعمل أي شيء خاص بالعلامات. سوف أعرض بضعة أمثلة تثبت ذلك.
مثال 1
ثنائي عشري عشري
-3 0xFD 1111 1101
+ 10 0x0A +0000 1010
----- -----------
7 0x07 1 0000 0111
^ يتم تسجيل هذا في بت الحمل
مثال 2
ثنائي عشري عشري
-59 0xC5 1100 0101
+ 33 0x21 +0010 0001
----- -----------
-26 0xE6 1110 0110
في المثال 1 ، نرى أن إضافة 10 و -3 نتائج في 7. تم نقل نتيجة الإضافة ، بحيث يمكن تعيين علامة C. في المثال 2 ، كانت نتيجة الإضافة سالبة ، لذلك نقوم بفك شفرة هذا: Not (0xE6) = 0x19 = 25 + 1 = 26.
0xE6 = -26
انفجار الدماغ!
إذا كنت تريد ، اقرأ المزيد عن الكود الإضافي على
ويكيبيديا .
أنواع البيانات
في لغة C ، هناك علاقة بين أنواع البيانات وعدد البايتات المستخدمة لهذا النوع. في الواقع ، نحن مهتمون فقط بالأعداد الصحيحة. أنواع البيانات القياسية / القديمة في المدرسة هي char ، int ، و long ، وكذلك أصدقاءهم char غير الموقعة ، int غير الموقعة ، وغير الموقعة طويلة. المشكلة هي أنه على منصات مختلفة وفي مترجمين مختلفين يمكن أن يكون لهذه الأنواع أحجام مختلفة.
لذلك ، من الأفضل تحديد نوع بيانات لمنصتنا التي تعلن عن حجم البيانات بشكل صريح. إذا كان النظام الأساسي الخاص بك يحتوي على stdint.h ، فيمكنك استخدام int8_t و uint8_t وما إلى ذلك.
يحدد حجم العدد الصحيح العدد الأقصى الذي يمكن تخزينه فيه. في حالة الأعداد الصحيحة غير الموقعة ، يمكنك تخزين الأرقام من 0 إلى 255 في 8 بت. إذا قمت بالترجمة إلى ست عشري ، فسيكون من 0x00 إلى 0xFF. نظرًا لأن 0xFF يحتوي على "مجموعة البتات جميعها" ، ويتوافق مع الرقم العشري 255 ، فمن المنطقي تمامًا أن يكون الفاصل بين عدد صحيح بلا إشارة أحادية البايت هو 0-255. تخبرنا الفواصل الزمنية أن جميع أحجام الأعداد الصحيحة ستعمل بنفس الطريقة تمامًا - تتوافق الأرقام مع الرقم الذي يتم الحصول عليه عند تعيين جميع البتات.
اكتب | الفاصل الزمني | عرافة |
---|
8 بت غير موقعة | 0-255 | 0x0-0xFF |
توقيع 8 بت | -128-127 | 0x80-0x7F |
16 بت غير موقعة | 0-65535 | 0x0-0xFFFF |
موقع 16 بت | -32768-32767 | 0x8000-0x7FFF |
32 بت غير موقعة | 0-4294967295 | 0x0-0xFFFFFFFFFF |
توقيع 32 بت | -2147483648-2147483647 | 0x80000000-0x7FFFFFFF |
والأكثر إثارة للاهتمام هو أن -1 في كل نوع بيانات موقعة هو رقم تم تعيين كافة وحدات البت له (0xFF للبايتات الموقعة و 0xFFFF لرقم 16 بت الموقعة و 0xFFFFFFFF لرقم 32 بت الموقَّع). إذا اعتبرت البيانات غير موقعة ، فسيتم الحصول على أقصى عدد ممكن لهذا النوع من البيانات لجميع البتات المحددة.
لمحاكاة تسجيلات المعالج ، نختار نوع البيانات المقابلة لحجم هذا السجل. ربما يكون من المفيد تحديد الأنواع غير الموقعة بشكل افتراضي وتحويلها عندما تحتاج إلى اعتبارها موقعة. على سبيل المثال ، نستخدم نوع بيانات uint8_t لتمثيل تسجيل 8 بت.
تلميح: استخدم المصحح لتحويل أنواع البيانات
إذا تم تثبيت gdb على النظام الأساسي الخاص بك ، فمن المناسب استخدامه للعمل مع الأرقام الثنائية. أدناه سأعرض مثالاً - في الجلسة الموضحة أدناه ، الخطوط التي تبدأ بـ # هي التعليقات التي أضفتها لاحقًا.
# /c, gdb
(gdb) print /c 0xFD
$1 = -3 '?'
# /x, gdb hex
# "p" "print"
(gdb) p /c 0xA
$2 = 10 '\n'
# 2 " "
(gdb) p /c 0xC5
$3 = -59 '?'
(gdb) p /c 0xC5+0x21
$4 = -26 '?'
# print , gdb
(gdb) p 0x21
$9 = 33
# , gdb,
# ,
(gdb) p 0xc5
$5 = 197 #
(gdb) p /c 0xc5
$3 = -59 '?' #
(gdb) p 0xfd
$6 = 253
# ( 32- )
(gdb) p /x -3
$7 = 0xfffffffd
# 1
(gdb) print (char) 0xff
$1 = -1 '?'
# 1
(gdb) print (unsigned char) 0xff
$2 = 255 '?'
عندما أعمل بالأرقام السداسية ، أفعل ذلك دائمًا في gdb - ويحدث ذلك تقريبًا كل يوم. أسهل بكثير من فتح الآلة الحاسبة للمبرمج باستخدام واجهة المستخدم الرسومية. على أجهزة Linux (و Mac OS X) ، لبدء جلسة gdb ، ما عليك سوى فتح الوحدة الطرفية وإدخال "gdb". إذا كنت تستخدم Xcode على OS X ، فبعد بدء البرنامج ، يمكنك استخدام وحدة التحكم داخل Xcode (التي يتم إخراج إخراج printf إليها).
في Windows ، يتوفر مصحح أخطاء gdb من Cygwin.إنهاء محاكي وحدة المعالجة المركزية
بعد تلقي كل هذه المعلومات ، أنت جاهز لرحلة طويلة. يجب أن تقرر كيفية تنفيذ المحاكي - إما إنشاء مضاهاة 8080 كاملة ، أو تنفيذ الأوامر اللازمة لإكمال اللعبة فقط.إذا قررت القيام بمحاكاة كاملة ، فستحتاج إلى عدد قليل من الأدوات. سأتحدث عنها في القسم التالي.طريقة أخرى هي محاكاة التعليمات التي تستخدمها اللعبة فقط. سنستمر في ملء بنية التبديل الضخمة التي أنشأناها في قسم Emulator Shell. سنكرر العملية التالية حتى يكون لدينا أمر واحد غير محقق:- قم بتشغيل المحاكي مع ROM Space Invaders
UnimplementedInstruction()
تنتهي المكالمة إذا لم يكن الأمر جاهزًا- قم بمحاكاة هذه التعليمات
- اذهب 1
أول شيء فعلته عند بدء كتابة محاكي هو إضافة رمز من أداة تفكيكي. لذلك تمكنت من إخراج أمر يجب تنفيذه على النحو التالي: int Emulate8080Op(State8080* state) { unsigned char *opcode = &state->memory[state->pc]; Disassemble8080Op(state->memory, state->pc); switch (*opcode) { case 0x00:
أضفت أيضًا رمزًا في النهاية لعرض جميع السجلات وأعلام الدولة.بشرى سارة: من أجل الخوض في البرنامج لـ 50 ألف فريق ، نحتاج فقط إلى مجموعة فرعية من 8080 شفرة ، وسأعطي قائمة من رموز الشفرة التي يجب تنفيذها:كود التشغيل | الفريق |
---|
0x00 | لا |
0x01 | LXI B ، D16 |
0x05 | DCR B |
0x06 | MVI B ، D8 |
0x09 | أبي ب |
0 × 0 د | DCR C |
0 × 0 هـ | MVI C ، D8 |
0x0f | ص ص |
0x11 | LXI D ، D16 |
0x13 | إنكس د |
0x19 | أبي د |
0x1a | LDAX د |
0x21 | LXI H ، D16 |
0x23 | Inx ح |
0x26 | MVI H ، D8 |
0x29 | أبي ح |
0x31 | LXI SP ، D16 |
0x32 | STA adr |
0x36 | MVI م ، د 8 |
0x3a | Lda adr |
0x3e | MVI A ، D8 |
0x56 | موف د ، م |
0x5e | MOV E ، M. |
0x66 | MOV ح ، م |
0x6f | MOV L ، A |
0x77 | MOV M ، A |
0x7a | موف ، د |
0x7b | MOV A ، E |
0x7c | MOV A ، H |
0x7e | MOV أ ، م |
0xa7 | ANA A |
0xaf | XRA أ |
0xc1 | بوب ب |
0xc2 | جنز ادر |
0xc3 | JMP adr |
0xc5 | PUSH ب |
0xc6 | ADI D8 |
0xc9 | إعادة |
0xcd | اتصل adr |
0xd1 | البوب د |
0xd3 | خارج D8 |
0xd5 | PUSH د |
0xe1 | البوب ح |
0xe5 | PUSH ح |
0xe6 | ANI D8 |
0xeb | Xchg |
0xf1 | POP PSW |
0 xf5 | دفع PSW |
0xfb | إي |
0xfe | CPI D8 |
هذه ليست سوى تعليمات 50 ، و 10 منها هي الحركات التي يتم تنفيذها بشكل تافه.تصحيح الأخطاء
لكن لدي بعض الأخبار السيئة. من شبه المؤكد أن المحاكي الخاص بك لن يعمل بشكل صحيح ، ومن الصعب جدًا العثور على الأخطاء في هذا الرمز. إذا كنت تعرف أي أمر يتصرف بشكل سيء (على سبيل المثال ، انتقال أو مكالمة تذهب إلى رمز لا معنى له) ، فيمكنك محاولة إصلاح الخطأ عن طريق فحص الرمز الخاص بك.بالإضافة إلى فحص الكود ، هناك طريقة أخرى لإصلاح المشكلة - من خلال مقارنة محاكيك بأخرى تعمل بالضبط. نفترض أن محاكيًا آخر يعمل دائمًا بشكل صحيح ، وجميع الاختلافات هي أخطاء في محاكيك. على سبيل المثال ، يمكنك استخدام محاكي الخاص بي. يمكنك تشغيلها يدويًا بالتوازي. يمكنك توفير الوقت إذا قمت بدمج الكود الخاص بي في مشروعك للحصول على العملية التالية:- إنشاء حالة لمحاكيك
- إنشاء دولة لي
- للفريق القادم
- استدعاء المحاكي الخاص بك مع دولتك
- استدعاء منجم مع ثروتي
- قارن بين دولتينا
- تبحث عن أخطاء في أي اختلافات
- اذهب 3
طريقة أخرى هي استخدام هذا الموقع يدويًا . هذا هو محاكي معالج 8080 Javascript حتى يتضمن ROM Space Invaders. هنا العملية:- قم بإعادة تشغيل مضاهاة Space Invaders بالنقر فوق الزر Space Invaders
- اضغط على زر "تشغيل 1" لتنفيذ الأمر.
- نقوم بتنفيذ الأمر التالي في محاكينا
- قارن بين حالة المعالج وحالتك
- إذا تطابقت الشروط ، انتقل إلى 2
- إذا لم تتطابق الشروط ، فإن محاكاة التعليمات الخاصة بك خاطئة. قم بتصحيحها ، ثم ابدأ مرة أخرى من الخطوة 1.
لقد استخدمت هذه الطريقة في البداية لتصحيح محاكي 8080 الخاص بي. لن أكذب - يمكن أن تكون العملية طويلة. ونتيجة لذلك ، تحولت العديد من مشاكلي إلى أخطاء مطبعية ولصق نسخ ، والتي كان من السهل جدًا إصلاحها بعد الكشف.إذا قمت خطوة بخطوة بتنفيذ التعليمات البرمجية الخاصة بك ، ثم يتم تنفيذ معظم الإرشادات الثلاثين ألف الأولى في دورة حول $ 1a5f. إذا نظرت إلى جافا سكريبت في المحاكي ، يمكنك أن ترى أن هذا الرمز ينسخ البيانات إلى الشاشة. أنا متأكد من أن هذا الرمز يسمى في كثير من الأحيان.بعد العرض الأول للشاشة ، بعد 50 ألف أمر ، يتعثر البرنامج في هذه الحلقة اللانهائية: 0ada LDA $20c0 0add ANA A 0ade JNZ $0ada
تنتظر حتى تتغير القيمة في الذاكرة عند $ 20c0 إلى الصفر. نظرًا لأن الرمز في هذه الحلقة لا يغير تمامًا $ 20c0 ، فيجب أن يكون إشارة من مكان آخر. حان الوقت للحديث عن محاكاة "الحديد" لآلة الورق.قبل أن ننتقل إلى القسم التالي ، تأكد من أن محاكي وحدة المعالجة المركزية يقع في هذه الحلقة اللانهائية.كمرجع ، انظر مصادري .مضاهاة 8080 كاملة
درس كلفني الكثير: لا تنفذ فرقًا لا يمكنك اختبارها. هذه قاعدة جيدة لأي برنامج قيد التطوير. إذا لم تتحقق من الفريق ، فسيتم كسرها بالتأكيد. وكلما ابتعدت عن تنفيذه ، زادت صعوبة إيجاد المشاكل.هناك حل آخر إذا كنت ترغب في عمل محاكي 8080 كامل والتأكد من أنه يعمل. اكتشفت رمزًا لـ 8080 يسمى cpudiag.asm ، تم تصميمه لاختبار كل أمر معالج 8080.أقدم لك هذه العملية بعد الأول لعدة أسباب:- كنت أرغب في تكرار وصف هذه العملية لمعالج آخر. لا أعتقد أن نظير cpudiag.asm موجود لجميع المعالجات.
- كما ترون ، فإن العملية شاقة للغاية. أعتقد أن المبتدئ في تصحيح رمز التجميع سيواجه صعوبات كبيرة إذا لم يتم سرد هذه الخطوات.
هكذا استخدمت هذا الاختبار مع محاكي. يمكنك استخدامه أو ابتكار طريقة أفضل لدمجها.تجميع الاختبار
لقد جربت شيئين ، ونتيجة لذلك استقرت على استخدام هذه الصفحة الرائعة . قمت بلصق النص cpudiag.asm في الجزء الأيسر وأكمل البناء دون أي مشاكل. لقد استغرق الأمر مني دقيقة لمعرفة كيفية تنزيل النتيجة ، ولكن بالنقر فوق الزر "إنشاء رمز جميل" في أسفل اليسار ، قمت بتنزيل ملف يسمى test.bin ، وهو رمز برمجي 8080. تمكنت من التحقق من ذلك باستخدام أداة التفكيك الخاصة بي.قم بتنزيل cpudiag.asm من المرآة على موقع الويب الخاص بي.قم بتنزيل cpudiag.bin (الرمز المتراكم 8080) من موقعي.تحميل اختبار لمحاكي الخاص بي
بدلاً من تحميل الغزاة. * الملفات ، أقوم بتحميل هذا الثنائي.تنشأ صعوبات صغيرة هنا. أولاً ، يوجد سطر في التعليمات البرمجية للمجمع المصدر ORG 00100H
، أي أنه يعني أن الملف بأكمله تم تجميعه بافتراض أن السطر الأول من التعليمات البرمجية في 0x100 سداسي عشري. لم أكتب كودًا في المجمّع 8080 من قبل ، لذلك لم أكن أعرف ما يفعله هذا السطر. استغرق الأمر دقيقة واحدة فقط لمعرفة أن جميع عناوين فروع الفروع كانت غير صحيحة وكان من الضروري أن تبدأ الذاكرة عند 0x100.ثانيًا ، نظرًا لأن محاكي يبدأ من الصفر ، يجب أن أقوم أولاً بالانتقال إلى الكود الحقيقي. بعد إدراج القيمة السداسية في الذاكرة عند العنوان صفر JMP $0100
، تعاملت مع هذا. (أو يمكنك فقط تهيئة جهاز الكمبيوتر بقيمة 0x100.)ثالثًا ، وجدت خطأً في الشفرة المترجمة. أعتقد أن السبب هو المعالجة غير الصحيحة للسطر الأخير من الشفرة STACK EQU TEMPP+256
، لكنني لست متأكدًا. على أي حال ، كان المكدس أثناء التجميع موجودًا في $ 6ad ، وبدأ PUSH القليلة الأولى في إعادة كتابة التعليمات البرمجية. اقترحت أن المتغير يجب أن يقابله أيضًا 0x100 ، مثل باقي الكود ، لذلك قمت بإصلاحه عن طريق إدخال "0x7" في سطر الكود الذي يهيئ مؤشر المكدس.أخيرًا ، نظرًا لأنني لم أقوم بتطبيق DAA أو الترحيل الإضافي في محاكي الخاص بي ، فأنا أقوم بتعديل الشفرة لتخطي هذا الاختيار (ببساطة نتخطاها باستخدام JMP). ReadFileIntoMemoryAt(state, "/Users/kpmiller/Desktop/invaders/cpudiag.bin", 0x100);
يحاول الاختبار التوصل إلى نتيجة
من الواضح أن هذا الاختبار يعتمد على المساعدة من CP / M OS. اكتشفت أن CP / M يحتوي على بعض التعليمات البرمجية بمبلغ 0005 دولار يطبع الرسائل إلى وحدة التحكم ، وغيرت مضاهاة CALL للتعامل مع هذا السلوك. لست متأكدًا مما إذا كان كل شيء يسير على ما يرام ، ولكنه نجح مع الرسالتين اللتين يحاول البرنامج طباعتهما. يبدو مضاهاة CALL لتشغيل هذا الاختبار كما يلي: case 0xcd:
مع هذا الاختبار ، وجدت العديد من المشاكل في محاكي الخاص بي. لست متأكدًا من منهم سيشارك في اللعبة ، ولكن إذا كانوا مشاركين ، فسيكون من الصعب جدًا العثور عليهم.تقدمت وقمت بتطبيق جميع رموز opcodes (باستثناء DAA وأصدقائه). استغرق الأمر مني 3-4 ساعات لإصلاح المشكلات في تحدياتي وتنفيذ تحديات جديدة. لقد كانت بالتأكيد أسرع من العملية اليدوية التي وصفتها أعلاه - قبل أن أجد هذا الاختبار ، أمضيت أكثر من 4 ساعات في العملية اليدوية. إذا كان بإمكانك معرفة هذا الشرح ، فإنني أوصي باستخدام هذه الطريقة بدلاً من المقارنة يدويًا. ومع ذلك ، فإن معرفة العملية اليدوية هي أيضًا مهارة رائعة ، وإذا كنت تريد محاكاة معالج آخر ، فيجب عليك العودة إليها.إذا لم تتمكن من إجراء هذه العملية أو كانت تبدو معقدة للغاية ، فمن الجدير بالتأكيد اختيار النهج الموصوف أعلاه مع محاكيين مختلفين يعملان داخل برنامجك. عند ظهور عدة ملايين من الأوامر في البرنامج وإضافة المقاطعات ، سيكون من المستحيل مقارنة محاكيين يدويًا.