يمكن تقسيم التقرير إلى ثلاثة أجزاء:
- كيف يعمل معالج 6502 وكيفية محاكاته باستخدام JavaScript ،
- كيف يعمل جهاز إخراج الرسومات وكيف تخزن الألعاب مواردها ،
- كيف يتم توليف الصوت باستخدام صوت الويب وكيف يتم موازنته في دفقين باستخدام مخرج صوتي.
حاولت تقديم نصائح حول التحسين. لا يزال ، المحاكاة هي الشيء ، عند 60 إطارًا في الثانية ، لم يتبق سوى القليل من الوقت لتنفيذ الكود.
- مرحباً بالجميع ، اسمي زينيا. الآن سيكون هناك حديث غير عادي ، السبت ، حول المشروع في أيام السبت. لنتحدث عن محاكاة أنظمة الكمبيوتر ، والتي يمكن تنفيذها فوق تقنيات الويب الحالية. في الواقع ، الويب غني بالفعل بالأدوات ، ويمكنك القيام بأشياء مذهلة للغاية. بشكل أكثر تحديدًا ، سنتحدث عن المحاكي للجميع ، على الأرجح ، وحدة تحكم Dandy الشهيرة من التسعينيات ، والتي تسمى في الواقع نظام Nintendo Entertainment System.
دعونا نتذكر القليل من التاريخ. بدأ في عام 1983 عندما ظهرت Famicom في اليابان. تم إصداره بواسطة Nintendo. في عام 1985 ، تم إصدار النسخة الأمريكية ، والتي كانت تسمى Nintendo Entertainment System. في التسعينيات كان لدينا نفس المنطقة التايوانية المسماة Dandy ، ولكن سراً ، هذه بادئة غير رسمية. وكانت آخر هدية حديدية من Nintendo في عام 2016 ، عندما ظهر NES mini. لسوء الحظ ، ليس لدي جهاز NES صغير. هناك SNES mini ، Super Nintendo. انظر إلى ما هو صغير ، وفي هذه الشريحة مباشرة يمكنك رؤية قانون مور بكل مجده.
إذا نظرنا إلى عام 1985 ونسبة وحدة التحكم إلى عصا التحكم ، وفي عام 2016 ، يمكننا أن نرى مدى صغر كل شيء ، لأن أيدي الناس لا تتغير ، ولا يمكن تصغير عصا التحكم ، لكن وحدة التحكم نفسها أصبحت صغيرة.
كما لاحظنا بالفعل ، هناك العديد من المحاكيات. لم نقول ذلك ، لكن مسؤول واحد على الأقل لاحظ ذلك. هذا الشيء - SNES mini أو NES mini - ليس في الحقيقة جهاز فك تشفير حقيقي. هذه قطعة من الأجهزة تحاكي وحدة التحكم. هذا ، في الواقع ، هذا محاكي رسمي ، لكنه يأتي في شكل حديدي مضحك.
ولكن كما نعلم ، منذ العقد الأول من القرن الحادي والعشرين ، هناك برامج تحاكي NES ، وبفضلها لا يزال بإمكاننا الاستمتاع بالألعاب من تلك الحقبة. وهناك العديد من المحاكيات. لماذا آخر ، وخاصة في JavaScript ، تسألني؟ عندما فعلت هذا الشيء ، وجدت ثلاث إجابات على هذا السؤال بنفسي.
- , . - , . . , - , - . . . , , . , -.
- , , . , , , , NES — , , NTSC, 60 . 16 , . .
- . , . , , . , , — , . . , , .
لقد شاهدت أيضًا العرض التقديمي الذي قدمه مات جودبولد ، الذي تحدث أيضًا عن محاكاة المعالج الذي يدير NES. قال إنه من المضحك أننا نحاكي مثل هذا الشيء المنخفض المستوى بلغة عالية المستوى. ليس لدينا وصول إلى الأجهزة ، نحن نعمل بشكل غير مباشر.
دعنا ننتقل إلى النظر في ما سنحاكيه ، وكيف سنحاكيه ، وما إلى ذلك. سنبدأ بالمعالج. NES نفسها مبدعة. بالنسبة لروسيا ، من المفهوم أن هذه ظاهرة ثقافية. لكن في الغرب ، وفي الشرق ، في اليابان ، كانت أيضًا ظاهرة ثقافية ، لأن وحدة التحكم ، في الواقع ، أنقذت صناعة ألعاب الفيديو المنزلية بأكملها.
تم تثبيت المعالج أيضًا في MOS6502 الأيقوني. ما هو مغزاها؟ في الوقت الذي ظهرت فيه ، كان سعر منافسيها 180 دولارًا وكان سعر MOS6502 25 دولارًا. أي أن هذا المعالج أطلق ثورة الكمبيوتر الشخصي. وهنا لدي جهازي كمبيوتر. الأول هو Apple II ، نعلم جميعًا ونتخيل مدى أهمية هذا الحدث لعالم أجهزة الكمبيوتر الشخصية.
يوجد أيضًا جهاز كمبيوتر BBC Micro. كان أكثر شعبية في بريطانيا ، بي بي سي هي شركة تلفزيونية بريطانية. وهذا يعني أن هذا المعالج جلب أجهزة الكمبيوتر إلى الجماهير ، وبفضله أصبحنا الآن مبرمجين ومطوري الواجهة الأمامية.
دعنا نلقي نظرة على الحد الأدنى من البرنامج. ماذا نحتاج لصنع نظام حوسبة؟
وحدة المعالجة المركزية نفسها هي جهاز عديم الفائدة إلى حد ما. كما نعلم ، تقوم وحدة المعالجة المركزية بتنفيذ البرنامج. ولكن على الأقل لكي يتم تخزين هذا البرنامج في مكان ما ، هناك حاجة إلى ذاكرة. وبالطبع يتم تضمينه في الحد الأدنى من البرنامج. وتتكون ذاكرتنا من خلايا مكونة من ثمانية بتات تسمى بايت.
في JavaScript ، يمكننا استخدام مصفوفات Uint8Array المكتوبة لمحاكاة هذه الذاكرة ، أي يمكننا تخصيص مصفوفة.
لكي تتفاعل الذاكرة مع المعالج ، هناك ناقل. يسمح الناقل للمعالج بمعالجة الذاكرة عبر العناوين. لم تعد العناوين تتكون من ثمانية بتات ، مثل البيانات ، بل تتكون من 16 ، مما يسمح لنا بمعالجة 64 كيلوبايت من الذاكرة.
هناك حالة معينة في المعالج ، وهناك ثلاثة سجلات - A ، X ، Y. السجل يشبه تخزين القيم الوسيطة. حجم السجل هو بايت واحد أو ثمانية بتات. هذا يخبرنا أن المعالج ثماني بتات ، ويعمل على بيانات ثمانية بت.
مثال على استخدام السجل. نريد إضافة رقمين ، لكن لا يوجد سوى ناقل واحد في الذاكرة. اتضح أنك بحاجة إلى تخزين الرقم الأول في مكان ما بينهما. نقوم بحفظها في السجل A ، ويمكننا أخذ القيمة الثانية من الذاكرة ، وإضافتها ، ويتم وضع النتيجة مرة أخرى في السجل A.
وظيفيًا ، هذه السجلات مستقلة تمامًا - يمكن استخدامها كسجلات عامة. لكن لها معنى ، مثل الجمع ، يتم الحصول على النتيجة في السجل A ويتم أخذ قيمة المعامل الأول.
أو ، على سبيل المثال ، نعالج البيانات. سنتحدث عن هذا بعد قليل. يمكننا تحديد وضع عنونة الإزاحة واستخدام سجل X للحصول على القيمة النهائية.
ماذا يتم تضمينه في حالة المعالج؟ يوجد سجل للكمبيوتر الشخصي يشير إلى عنوان الأمر الحالي ، لأن العنوان هو 2 بايت.
لدينا أيضًا سجل الحالة ، والذي يشير إلى أعلام الحالة. على سبيل المثال ، إذا طرحنا قيمتين وحصلنا على قيمة سالبة ، فسيتم إضاءة جزء معين في سجل العلم.
أخيرًا ، هناك SP ، مؤشر إلى المكدس. المكدس هو مجرد ذاكرة عادية ، ولا يتم فصله عن أي شيء آخر ، عن جميع البرامج الأخرى. هناك ببساطة تعليمات من المعالج تتحكم في مؤشر SP هذا. هذه هي الطريقة التي يتم بها تنفيذ المكدس. ثم سنلقي نظرة على فكرة كمبيوتر رائعة تؤدي إلى مثل هذه الحلول المثيرة للاهتمام.
الآن نحن نعلم أن هناك معالج ، ذاكرة ، حالة في المعالج. دعونا نرى ما هو برنامجنا. هذا هو تسلسل بايت. لا يجب أن تكون متسقة. يمكن وضع البرنامج نفسه في أجزاء مختلفة من الذاكرة.
يمكننا تخيل برنامج ، لدي جزء من الكود هنا - 1 ، 2 ، 3 ، 4 ، 5 ، 6 ، 7 ، 8 ، 9 ، 10. هذا برنامج حقيقي على 6502. كل بايت من هذا البرنامج ، كل رقم في هذه المصفوفة هو مثل كيان ككود تشغيل. كود التشغيل - كود العملية. ثم ، مرة أخرى ، رقم عادي.
على سبيل المثال ، هناك كود تشغيل 169. يشفر شيئين في حد ذاته - أولاً ، تعليمات. عند التنفيذ ، فإن التعليمات تغير حالة المعالج والذاكرة وما إلى ذلك ، أي حالة النظام. على سبيل المثال ، نضيف رقمين ، تظهر النتيجة في السجل A. هذا مثال على التعليمات. لدينا أيضًا تعليمات LDA ، والتي سننظر فيها بمزيد من التفصيل. يقوم بتحميل قيمة من الذاكرة إلى السجل A.
الشيء الثاني الذي يشفره كود التشغيل هو وضع العنونة. يعطي تعليمات حول مكان الحصول على بياناتها. على سبيل المثال ، إذا كان هذا هو وضع عنونة IMM ، فإنه يقول: خذ البيانات الموجودة في الخلية بجوار عداد البرنامج الحالي. سنرى أيضًا كيف يعمل هذا الوضع وكيف يمكن تنفيذه في JavaScript.
هذا هو البرنامج. بصرف النظر عن هذه البايتات ، كل شيء مشابه جدًا لجافا سكريبت ، ولكن بمستوى أقل فقط.
إذا كنت تتذكر ما كنت أتحدث عنه ، فقد يكون هناك تناقض مضحك. لقد اتضح أننا نخزن البرنامج في الذاكرة والبيانات أيضًا. قد يسأل المرء هذا السؤال: هل يمكن أن يعمل البرنامج كبيانات؟ الجواب نعم. يمكننا التغيير من البرنامج نفسه في وقت تنفيذ هذا البرنامج.
أو سؤال آخر: هل يمكن أن تكون البيانات برنامجًا؟ نعم أيضا. المعالج لا يهم. إنه فقط ، مثل المطحنة ، يطحن البايتات التي يتم تغذيتها له ويتبع التعليمات. شيء متناقض. إذا فكرت في الأمر ، فهو غير آمن للغاية. يمكنك البدء في تنفيذ برنامج ما هو إلا بيانات على مكدس ، وما إلى ذلك. ولكن الميزة أنه سهل للغاية. لا حاجة لعمل دوائر معقدة.
هذه أول فكرة رائعة نطرحها اليوم. يطلق عليه عمارة فون نيومان. ولكن كان هناك في الواقع العديد من المؤلفين المشاركين.
يتضح هنا. يوجد البرنامج 1 ، كود التشغيل 169 ، يليه 10 ، بعض البيانات. حسنا. يمكن أيضًا عرض هذا البرنامج على النحو التالي: 169 عبارة عن بيانات ، و 10 عبارة عن كود تشغيل. سيكون هذا برنامجًا قانونيًا لـ 6502. هذا البرنامج بأكمله ، مرة أخرى ، يمكن اعتباره بيانات.
إذا كان لدينا مترجم ، فيمكننا بناء شيء ما ، ووضعه في هذه القطعة من الذاكرة ، وسيكون شيئًا مضحكًا.
دعنا نلقي نظرة على الجزء الأول من برنامجنا - التعليمات.
يوفر 6502 الوصول إلى 73 تعليمة ، بما في ذلك الحساب: الجمع والطرح. لا الضرب والقسمة ، آسف. هناك عمليات بت ، تتعلق بمعالجة البتات في كلمات ذات ثماني بتات.
هناك قفزات محظورة في الواجهة الأمامية: عبارة القفز ، والتي تنقل ببساطة عداد البرنامج إلى جزء من الكود. هذا ممنوع في البرمجة ، ولكن إذا كنت تتعامل مع مستوى منخفض ، فهذه هي الطريقة الوحيدة للقيام بالتفريع. توجد عمليات للمكدس ، إلخ. يتم تجميعها. نعم ، لدينا 73 تعليمات ، ولكن إذا نظرت إلى المجموعات وما تفعله ، فليس هناك الكثير منها بالفعل وكلها متشابهة جدًا.
دعنا نعود إلى تعليمات LDA. كما قلنا ، هذا هو "تحميل القيمة من الذاكرة في السجل A". هذا هو مدى بساطة الأمر في JavaScript. عند المدخل يوجد العنوان الذي يزودنا به وضع العنونة. نغير الحالة في الداخل ، نقول أن هذا ._a يساوي قيمة القراءة من الذاكرة.
ما زلنا بحاجة إلى تعيين هذين الحقلين في سجل الحالة - علامة صفرية وعلم سالب. هناك الكثير من الأشياء هنا. ولكن إذا قمت بعمل محاكي ، فسيصبح من الطبيعي أن تقوم بـ ORs هذه ، السلبيات ، إلخ. الشيء المضحك الوحيد هنا هو أن هناك 256٪ في الفرع الثاني. إنه يحيلنا ، مرة أخرى ، إلى طبيعة لغتنا المحبوبة JavaScript ، إلى حقيقة أنها لا تحتوي على قيم مكتوبة. يمكن أن تتجاوز القيمة التي نضعها في الحالة 256 ، والتي تتناسب مع بايت واحد. علينا التعامل مع مثل هذه الحيل.
الآن دعونا نلقي نظرة على الجزء الأخير من كود التشغيل لدينا ، وضع العنونة.
لدينا 12 طريقة عنونة. كما قلنا من قبل ، فهي تسمح لنا بالحصول على التعليمات والإشارة إليها من أين نحصل على البيانات.
دعنا نلقي نظرة على ثلاثة أشياء. آخرها هو ABS ، وضع العنونة المطلق ، فلنبدأ به ، أعتذر عن القليل من الإحراج. يفعل شيئا مثل هذا. نعطيه العنوان الكامل ، 16 بت ، كمدخلات. هو يحصلنا على القيمة من خلية الذاكرة هذه. في المجمع ، في العمود الثاني ، يمكنك أن ترى كيف يبدو: LDA $ ccbb. ccbb هو رقم سداسي عشري ، رقم عادي ، مكتوب ببساطة بترميز مختلف. إذا كنت تشعر بعدم الارتياح هنا ، فتذكر أن هذا مجرد رقم.
في العمود الثالث ، يمكنك أن ترى كيف يبدو في كود الجهاز. أمامنا رمز التشغيل - 173 ، المميز باللون الأزرق. و 187 و 204 بيانات العنوان بالفعل. ولكن نظرًا لأننا نعمل بقيم ثمانية بت ، فإننا نحتاج إلى موقعين للذاكرة لكتابة العنوان.
لقد نسيت أيضًا أن أقول إن كود التشغيل يتم تنفيذه لبعض الوقت على وحدة المعالجة المركزية ، وله تكلفة معينة. يستغرق LDA مع العنونة المطلقة أربع دورات CPU.
هنا يمكنك بالفعل فهم سبب الحاجة إلى العديد من أوضاع العنونة. ضع في اعتبارك وضع العنونة التالي ، ZP0. هذا هو وضع عنونة الصفحة صفر. والصفحة صفر هي أول 256 بايت مخصصة في الذاكرة. هذه عناوين من صفر إلى 255.
في المجمع ، مرة أخرى ، LDA * 10. ماذا يفعل وضع العنونة هذا؟ يقول: اذهب إلى الصفحة صفر ، هنا في أول 256 بايت ، مع كذا وكذا الإزاحة. في هذه الحالة 10 ، واتخاذ القيمة من هناك. هنا نلاحظ بالفعل فرقًا كبيرًا بين أوضاع العنونة.
في حالة العنونة المطلقة ، نحتاج أولاً إلى ثلاثة بايت لكتابة مثل هذا البرنامج. ثانيًا ، احتجنا إلى أربع دورات لوحدة المعالجة المركزية. وفي وضع العنونة ZP0 ، استغرق الأمر ثلاث دورات وحدة المعالجة المركزية وحدتي بايت فقط. لكن نعم ، فقدنا المرونة. أي أنه يمكننا فقط وضع بياناتنا على الصفحة الأولى ، هذه الصفحة.
يقول وضع العنونة النهائي IMM: أخذ البيانات من الخلية المجاورة لكود التشغيل. هذا المجمع LDA # 10 يفعل ذلك. واتضح أن البرنامج يشبه [169 ، 10]. يتطلب بالفعل دورتين من وحدة المعالجة المركزية. ولكن من الواضح هنا أننا نفقد المرونة أيضًا ، ونحتاج إلى أن يكون كود التشغيل بجوار البيانات.
تنفيذ هذا في JavaScript سهل. إليك بعض التعليمات البرمجية النموذجية. هناك عنوان. هذا هو عنونة IMM ، والتي تأخذ البيانات من عداد البرنامج. نقول ببساطة أن عنواننا عبارة عن عداد برنامج ونزيده بواحد بحيث في المرة التالية التي يتم فيها تنفيذ البرنامج ، سينتقل إلى التعليمات التالية.
هذا شيء مضحك. يمكننا الآن قراءة كود الآلة مثل مطوري الواجهة الأمامية. ونعرف أيضًا كيف نرى ما هو مكتوب هناك في المجمع.
نحن نعلم بالفعل كل ما نحتاجه من حيث المبدأ. يوجد برنامج يتكون من بايت. كل بايت عبارة عن كود تشغيل ، وكل شفرة تشغيل عبارة عن تعليمات ، وهكذا. دعونا نرى كيف يتم تنفيذ برنامجنا. ويتم تنفيذه فقط في دورات وحدة المعالجة المركزية هذه.
كيف يمكن عمل مثل هذا الرمز؟ مثال. نحتاج إلى قراءة كود التشغيل من عداد البرنامج ، ثم زيادته بواحد. نحتاج الآن إلى فك شفرة التشغيل هذا إلى تعليمات وفي وضع العنونة. فكر في الأمر ، كود التشغيل هو رقم أولي ، 169. وفي البايت لدينا 256 رقمًا فقط. يمكننا إنشاء مصفوفة من 256 قيمة. سيرسلنا كل عنصر من هذه المصفوفة ببساطة إلى التعليمات التي يجب استخدامها ، ووضع العنونة المطلوب وعدد الدورات التي سيستغرقها. هذا هو ، الأمر بسيط للغاية. والمصفوفة التي أمتلكها هي فقط في حالة المعالج.
بعد ذلك ، نقوم فقط بوظيفة وضع العنونة في السطر 36 ، والذي يعطينا العنوان ، ونغذيه بالإرشادات.
آخر شيء علينا القيام به هو التعامل مع الحلقات. يقوم opcodeResolver بإرجاع عدد الدورات ، ونكتبها إلى متغير الدورات المتبقية. ننظر إلى كل دورة معالج: إذا كانت هناك دورات صفرية متبقية ، فيمكننا تنفيذ الأمر التالي ، وإذا كان أكبر من الصفر ، فإننا ببساطة نخفضه بمقدار واحد. وهذا كل شيء ، بسيط للغاية. هذه هي الطريقة التي يتم بها تنفيذ البرنامج على 6502.
ولكن كما قلنا بالفعل ، يمكن أن يكون البرنامج في أجزاء مختلفة من الذاكرة ، بنسب مختلفة ، إلخ. كيف يمكن للمعالج أن يفهم من أين يبدأ تنفيذ هذا البرنامج؟ نحن بحاجة إلى int main مثل هذا من العالم C.
في الحقيقة ، كل شيء بسيط. المعالج لديه إجراء لإعادة ضبط حالته. في هذا الإجراء ، نأخذ عنوان الأمر الأولي من العنوان 0xfffxc. 0xfffxc هو مرة أخرى رقم سداسي عشري. إذا كنت تشعر بعدم الارتياح ، فقم بتسجيل هذا الرقم المعتاد. هذه هي الطريقة التي تمت كتابتها بها في JavaScript ، من خلال 0x.
نحتاج لقراءة وحدتي بايت من العنوان ، العنوان 16 بت. نقرأ وحدات البايت المنخفضة من هذا العنوان ، وحدات البايت الكبيرة من العنوان التالي. ثم نضيف هذه الحالة بمثل هذا السحر لعمليات البت. بالإضافة إلى ذلك ، تؤدي إعادة تعيين حالة المعالج أيضًا إلى إعادة تعيين القيمة في السجلات - السجل A ، X ، Y ، المؤشر إلى المكدس ، الحالة. تستغرق إعادة الضبط ثماني دورات. هذا هو الشيء.
نحن نعلم بالفعل كل شيء الآن. بصراحة ، كان من الصعب علي كتابة كل هذا ، لأنني لم أفهم على الإطلاق كيفية اختباره. نحن نكتب جهاز كمبيوتر بالكامل يمكنه تشغيل أي برنامج تم إنشاؤه له. كيف نفهم أننا نتحرك بشكل صحيح؟
هناك طريقة رائعة ورائعة! نأخذ وحدتي CPU. الأول هو الذي نصنعه ، والثاني هو وحدة المعالجة المركزية المرجعية ، ونحن نعلم بالتأكيد أنها تعمل بشكل جيد. على سبيل المثال ، هناك محاكي لـ NES ، nintendulator ، والذي يعتبر بمثابة معيار معياري لوحدات المعالجة المركزية.
نأخذ برنامج اختبار معين ، ونقوم بتنفيذه على وحدة المعالجة المركزية المرجعية ونكتب حالة المعالج إلى سجل الحالة لكل أمر. ثم نأخذ هذا البرنامج ونقوم بتنفيذه على وحدة المعالجة المركزية الخاصة بنا. ويتم مقارنة كل حالة بعد كل أمر بهذا السجل. فكرة خارقة!
بالطبع ، لا نحتاج إلى مرجع وحدة المعالجة المركزية. نحن فقط بحاجة إلى سجل تنفيذ البرنامج. يمكن العثور على هذا السجل على Nesdev. في الواقع ، يمكن كتابة محاكي معالج ، لا أعرف ، في غضون يومين من عطلة نهاية الأسبوع - إنه أمر رائع!
و هذا كل شيء. نأخذ السجل ونقارن الحالة ولدينا اختبار تفاعلي. نقوم بتنفيذ الأمر الأول ، ولم يتم تنفيذه في المعالج الذي نقوم بتطويره. ننفذها ، ننتقل إلى السطر التالي من السجل ونطبقها مرة أخرى. سريع جدا! يسمح لك بالتحرك بسرعة.
هندسة NES
لدينا الآن وحدة المعالجة المركزية ، والتي تعد أساسًا قلب جهاز الكمبيوتر الخاص بنا. ويمكننا أن نرى ما هي بنية NES نفسها وكيف يتم صنع أنظمة الكمبيوتر المركبة المعقدة. لأنه إذا فكرت في الأمر ، حسنًا ، هناك وحدة معالجة مركزية ، وهناك ذاكرة. يمكننا استلام القيم والتسجيل وما إلى ذلك.
ولكن في NES ، في أي جهاز فك التشفير ، توجد أيضًا شاشة وأجهزة صوت وما إلى ذلك. نحتاج إلى تعلم كيفية العمل مع الأجهزة الطرفية. لا تحتاج حتى إلى تعلم أي شيء جديد لهذا ، فإن مفهوم الحافلة الخاص بنا كافٍ. ربما تكون هذه هي الفكرة الرائعة الثانية من نوعها ، اكتشاف رائع صنعته لنفسي أثناء كتابة المحاكي.
لنتخيل أننا نأخذ ذاكرتنا ، التي كانت 64 كيلو بايت ، ونقسمها إلى نطاقين 32 كيلو بايت. في النطاق الأدنى سيكون هناك جهاز معين ، وهو عبارة عن مجموعة من المصابيح الكهربائية ، كما في الصورة مع هذه اللوحة.
لنفترض أنه عند الكتابة إلى هذا النطاق الصغير 32 كيلو بايت ، سيتم تشغيل الضوء أو إيقاف تشغيله. إذا كتبنا هناك القيمة 1 ، يضيء الضوء ، إذا كان 0 - اخرج. في الوقت نفسه ، يمكننا قراءة القيمة وفهم حالة النظام ، وفهم الصورة التي يتم عرضها على هذه الشاشة.
مرة أخرى ، في النطاق العلوي من العناوين ، نضع الذاكرة العادية التي يوجد بها البرنامج ، لأننا نحتاج إلى عنوان في النطاق العلوي أثناء إجراء إعادة التعيين.
هذه في الواقع فكرة عبقرية خارقة. للتفاعل مع الأجهزة الطرفية ، ليست هناك حاجة لأوامر إضافية ، إلخ. نحن نكتب فقط إلى الذاكرة القديمة الجيدة ، كما كان من قبل. لكن في الوقت نفسه ، يمكن أن تكون الذاكرة بالفعل أجهزة إضافية.
نحن الآن على استعداد تام لإلقاء نظرة على بنية NES. لدينا وحدة المعالجة المركزية والحافلة الخاصة بها ، كالعادة. هناك نوعان من كيلوبايت إضافية من الذاكرة. يوجد APU - جهاز إخراج الصوت. لسوء الحظ ، لن نفكر في ذلك الآن ، ولكن كل شيء رائع هناك أيضًا. وهناك خرطوشة. يتم وضعها في النطاق العالي وتزويد بيانات البرنامج. قدم أيضًا هذه الرسوم البيانية ، والآن سننظر فيها. آخر شيء في ناقل وحدة المعالجة المركزية هو PPU ، وحدة معالجة الصور ، مثل بطاقة الفيديو الأولية. إذا كنت ترغب في معرفة كيفية العمل باستخدام بطاقات الفيديو ، فسوف نتعلم الآن كيفية تنفيذ واحدة.
تحتوي PPU أيضًا على ناقلها الخاص ، حيث يتم تبديل جداول الأسماء واللوحات وبيانات الرسوم. لكن البيانات الرسومية يتم توفيرها بواسطة الخرطوشة. ثم هناك ذاكرة الكائن. هذه هي العمارة.
دعونا نرى ما هي الخرطوشة. هذه فكرة أكثر روعة من القرص المضغوط عندما تعتبر أنها من الماضي.
لماذا هي رائعة؟ على اليسار يمكننا رؤية خرطوشة المنطقة الأمريكية ، لعبة Zelda الشهيرة ، إذا لم يلعبها أي شخص - العب ، رائع. وإذا فككنا هذه الخرطوشة ، فسنجد فيها دوائر كهربائية دقيقة. لا يوجد قرص ليزر ، إلخ. عادة ما تحتوي هذه الرقائق على بعض البيانات فقط. أيضا ، الخرطوشة تقطع مباشرة إلى نظام الكمبيوتر لدينا ، في وحدة المعالجة المركزية وناقل PPU. يتيح لك القيام بأشياء مذهلة وتحسين تجربة المستخدم.
يوجد رسام خرائط على الخرطوشة ، تملأ ترجمة العناوين. لنفترض أن لدينا مباراة كبيرة. لكن NES لديها 32 كيلوبايت فقط من الذاكرة التي يمكنها معالجتها للبرنامج. اللعبة ، دعنا نقول ، هي 128 كيلو بايت. يمكن لمخطط الخرائط ، أثناء تنفيذ البرنامج ، استبدال نطاق معين من الذاكرة ببيانات جديدة تمامًا. يمكننا القول في البرنامج: قم بتحميل المستوى 2 ، وسيتم استبدال الذاكرة مباشرة ، على الفور تقريبًا.
بالإضافة إلى أن هناك أشياء مضحكة. على سبيل المثال ، يمكن لرسام الخرائط توفير شرائح تعمل على توسيع المسارات الصوتية ، وإضافة شرائح جديدة ، وما إلى ذلك. إذا كنت قد لعبت لعبة Castlevania ، فاستمع إلى صوت Castlevania في المنطقة اليابانية. هناك صوت إضافي ، يبدو مختلفًا تمامًا. في هذه الحالة ، يتم تنفيذ كل شيء على نفس الجهاز. وهذا يعني أن هذه الفكرة أقرب إلى الفكرة عندما اشتريت بطاقة فيديو ، وقمت بتوصيلها بجهاز كمبيوتر ، ولديك وظائف إضافية. نفس الشيء هنا. ذلك رائع. لكننا عالقون مع الأقراص المدمجة.
دعنا ننتقل إلى الجزء الأخير - دعنا نلقي نظرة على كيفية عمل جهاز إخراج الصور هذا. لأنه إذا كنت تريد إنشاء محاكي ، فإن الحد الأدنى من البرامج هو إنشاء معالج وهذا الشيء لمشاهدة كيف تبدو الصور وألعاب الفيديو.
لنبدأ بكيان المستوى الأعلى - الصورة نفسها. لديها خطتين. هناك مقدمة حيث يتم وضع المزيد من الكيانات الديناميكية وخلفية حيث يتم وضع المزيد من الكيانات الثابتة مثل المشهد.
يمكنك رؤية الانقسام هنا. على اليسار توجد نفس لعبة Castlevania الشهيرة ، لذا فإن رحلتنا بأكملها إلى PPU ستحدث مع Simon Belmont. جنبا إلى جنب معه ، سننظر في كيفية عمل كل شيء.
توجد خلفية وأعمدة وما إلى ذلك. نرى أنها مرسومة في الخلفية ، ولكن في نفس الوقت ، تم رسم جميع الشخصيات - سيمون نفسه (على اليسار ، بني) والأشباح - بالفعل في المقدمة. أي أن المقدمة موجودة لكيانات أكثر ديناميكية ، والخلفية موجودة للكيانات الأكثر ثباتًا.
تتكون الصورة المعروضة على شاشة نقطية من وحدات بكسل. البكسل هي مجرد نقاط ملونة. على أقل تقدير ، نحتاج إلى ألوان. يحتوي NES على لوحة نظام. يتكون من 64 لونًا ، وهي للأسف جميع الألوان التي تستطيع NES إعادة إنتاجها. لكن لا يمكننا أخذ أي لون من اللوحة. بالنسبة للوحات الألوان المخصصة ، هناك نطاق محدد في الذاكرة ، والذي بدوره ينقسم أيضًا إلى نطاقين فرعيين من هذا القبيل.
هناك مجموعة من الخلفية والمقدمة. كل مجموعة مقسمة إلى أربع لوحات من أربعة ألوان. على سبيل المثال ، الخلفية ، لوحة الصفر تتكون من الأبيض والأزرق والأحمر. واللون الرابع في كل لوحة يشير دائمًا إلى لون شفاف ، مما يسمح لنا بعمل بكسل شفاف.
لم يعد هذا النطاق مع اللوحات موجودًا في ناقل وحدة المعالجة المركزية ، ولكن في ناقل PPU. دعونا نرى كيف يمكننا كتابة البيانات هناك ، لأننا لا نستطيع الوصول إلى ناقل PPU عبر ناقل وحدة المعالجة المركزية.
هنا نعود مرة أخرى إلى فكرة تعيين الذاكرة I / O. هناك عناوين 0x2006 و 0x2007 ، هذه عناوين سداسية عشرية ، لكنها مجرد أرقام. ونكتب مثل هذا. نظرًا لأن عنواننا هو 16 بت ، فإننا نكتب العنوان في سجل عنوان ox2006 بطريقتين من ثماني بتات ومن ثم يمكننا كتابة بياناتنا من خلال العنوان 0x2007. هذا شيء مضحك. وهذا يعني ، في الواقع ، أننا بحاجة إلى إجراء ثلاث عمليات من أجل كتابة شيء ما على اللوحة على الأقل.
ممتاز. لدينا لوحة ، لكننا بحاجة إلى هياكل. الألوان جيدة دائمًا ، لكن الصور النقطية منظمة.
للرسومات ، يوجد جدولان كل منهما أربعة كيلوبايت يحتوي كل منهما على مربعات. وكل هذه الذاكرة هي نوع من الأطلس. في السابق ، عندما استخدم الجميع صورة نقطية ، كانوا يصنعون أطلسًا كبيرًا ، ثم يختارون منه الصور الضرورية من خلال صورة الخلفية بواسطة الإحداثيات. ها هي نفس الفكرة.
كل طاولة بها 256 قطعة. مرة أخرى ، علم الأعداد المضحك: 256 بالضبط تسمح لك بتحديد بايت واحد ، 256 قيمة مختلفة. وهذا يعني أنه في بايت واحد يمكننا تحديد أي بلاطة نحتاجها. اتضح طاولتين. جدول للخلفيات وآخر للمقدمة.
دعونا نرى كيف يتم تخزين هذه المربعات. إنه شيء مضحك هنا أيضًا. لنتذكر أن لدينا أربعة ألوان في لوحتنا. الأعداد مرة أخرى: للبايت ثمانية بتات ، والبلاط ثمانية في ثمانية. اتضح أنه باستخدام بايت واحد يمكننا تمثيل شريط من البلاط ، حيث سيكون كل بت مسؤولاً عن بعض الألوان. وباستخدام ثمانية بايت ، يمكننا تمثيل بلاطة كاملة مكونة من ثمانية في ثمانية.
لكن هناك مشكلة واحدة هنا. كما قلنا ، البتة الواحدة مسؤولة عن اللون ، لكنها يمكن أن تمثل قيمتين فقط. يتم تخزين البلاط في طائرتين. هناك مستوى من أهم وأقل بت. للحصول على اللون النهائي ، نقوم بدمج البيانات من كلا المستويين.
يمكنك التفكير - هنا ، على سبيل المثال ، الحرف "I" ، الجزء السفلي ، هناك الرقم "3" ، والذي يتضح على النحو التالي: نأخذ مستوى البتات الأقل أهمية والأكثر أهمية ونحصل على الرقم الثنائي 11 ، والذي سيكون مساويًا للعلامة العشرية 3. هذه بنية بيانات مضحكة.
خلفية
الآن يمكننا أخيرًا تقديم الخلفية!
يوجد جدول أسماء له. لدينا اثنان منهم ، كل منهما 960 بايت ، كل بايت يحيلنا إلى بلاطة معينة. أي أن معرّف التجانب مُشار إليه في الجدول السابق. إذا قمنا بتمثيل هذه الـ 960 بايت كمصفوفة ، فسنحصل على شاشة مربعة بحجم 32 × 30. ستكون دقة NES 256 بكسل × 240 بكسل.
ممتاز. يمكننا كتابة البلاط هناك. ولكن كما لاحظت ، لا تشير المربعات إلى اللوحة التي ينبغي عرضها بها. يمكننا عرض مربعات مختلفة بألواح مختلفة ، ونحتاج أيضًا إلى تخزين هذه المعلومات في مكان ما. لسوء الحظ ، لدينا 64 بايت فقط لكل جدول اسم لتخزين معلومات اللوحة.
وهنا تبرز المشكلة. إذا قمنا بتقسيم الجدول أكثر بحيث يكون هناك 64 قيمة فقط ، فسنحصل على مربعات من أربعة مربعات في أربعة مربعات ، والتي تبدو مثل مربع أحمر. إنها مجرد جزء كبير من الشاشة. ستكون خاضعة للوحة واحدة ، إن لم يكن لواحد ولكن.
كما نتذكر ، هناك أربع لوحات في اللوحة الفرعية ، ونحتاج فقط إلى بتين للإشارة إلى القطعة التي نحتاجها. كل من هذه الـ 64 بايت تنسخ معلومات لوح الألوان لشبكة بأربعة في أربعة. لكن هذه الشبكة لا تزال مقسمة إلى مثل هذه الشبكات الفرعية اثنين في اثنين. بالطبع ، هناك قيود: شبكة ثنائية في اثنين مرتبطة بلوحة واحدة. هذه هي القيود في عالم عرض الخلفيات على Nintendo. حقيقة ممتعة ، لكنها بشكل عام لا تتعارض مع الألعاب.
هناك أيضا التمرير. إذا تذكرنا ، على سبيل المثال ، "ماريو" أو Castlevania ، فإننا نعلم: إذا تحرك البطل في هذه الألعاب إلى اليمين ، فعندئذ يبدو أن العالم يتكشف على طول الشاشة. يتم ذلك عن طريق التمرير.
تذكر أن لدينا جدولا أسماء يشفران بالفعل شاشتين. وعندما يتحرك بطلنا ، نضيف نوعًا ما البيانات إلى جدول الأسماء التالية. مباشرة ، عندما يتحرك بطلنا ، نملأ جدول الأسماء. اتضح أنه يمكننا الإشارة إلى المربع الموجود في جدول الأسماء الذي نحتاجه لبدء عرض البيانات ، وسنقوم بتوسيعه في شرائح. الحيلة الكاملة للتمرير هي القراءة من جدولي الأسماء.
أي ، إذا تجاوزنا جدول أسماء واحدًا أفقيًا ، فإننا نبدأ في القراءة تلقائيًا من جدول آخر ، وما إلى ذلك ، ولا تنسَ ، مرة أخرى ، ملء البيانات.
بالمناسبة ، كان التمرير شيئًا كبيرًا جدًا في ذلك الوقت. كانت إنجازات جون كارماك الأولى في مجال التمرير. تحقق من هذه القصة ، إنها مضحكة جدًا.
المقدمة
والمقدمة. في المقدمة ، كما قلنا ، هناك كيانات ديناميكية ، ويتم تخزينها في ذاكرة الأشياء والسمات.
هناك 256 بايت يمكننا كتابة 64 عنصرًا إليها ، أربعة بايت لكل كائن. يقوم كل كائن بترميز X و Y ، وهو إزاحة البكسل على الشاشة. بالإضافة إلى عنوان البلاط والسمات. يمكننا تحديد أولويات الخلفية ، انظر الصورة أدناه؟ يمكننا تحديد اللوحة. الأولوية على الخلفية تخبر PPU أنه يجب رسم الخلفية أعلى الكائن. هذا يسمح لنا بوضع سيمون خلف التمثال.
يمكننا أيضًا جعل الاتجاه ، ولفه بعد أي محور ، على سبيل المثال ، أفقيًا ، رأسيًا ، مثل الحرف "I" في الصورة. نكتب بنفس طريقة لوحة الألوان تقريبًا: من خلال العنوان 0x2003 ، 0x2004.
أخيرًا ، النهائي. كيف نقدم الأشياء الأمامية؟
تتكشف الصورة على طول خطوط تسمى scanlines ، وهذا مصطلح تلفزيوني. قبل كل سطر مسح ، نأخذ ببساطة ثمانية نقوش متحركة من ذاكرة الكائن والسمة. لا يزيد عن ثمانية ، يتم دعم ثمانية فقط. هناك أيضا مثل هذا القيد. نحن فقط نعرضها سطراً بسطر ، كما هو الحال هنا ، على سبيل المثال. على خط المسح الحالي ، باللون الأصفر ، نعرض سحابة وشمس وقلب في شريط. ولا يتم عرض الابتسامة. لكنه لا يزال سعيدا.
تحقق من قناة One Lone Coder الفائقة . هناك عملية البرمجة نفسها ، على وجه الخصوص - برمجة محاكي NES. و Nesdev يحتوي على جميع المعلومات عن مضاهاة - ما يتكون منها، وما إلى ذلك الحلقه الاخيره هو رمز بلادي المحاكي . ألق نظرة إذا كنت مهتمًا. مكتوب في TypeScript.
شكر. أتمنى انك استمتعت به.