يوم جيد.
منذ زمن بعيد كانت هناك رغبة في كتابة محاكي لبعض المعالجات.
وماذا يمكن أن يكون أفضل من اختراع دراجة؟
اسم الدراجة هو V16 ، من الإلتصاق بكلمة Virtual ، وفي الواقع ، عمق البت.
من أين تبدأ؟
وتحتاج إلى البدء ، بالطبع ، مع وصف المعالج.
في البداية ، خططت لكتابة محاكي DCPU-16 ، ولكن هناك أكثر من كافية من هذه المعجزات على الإنترنت ، لذلك قررت التركيز فقط على "لعق" أبسط مع DCPU-16 1.1.
هندسة معمارية
الذاكرة والموانئ
- يعالج V16 128 كيلو بايت (65536 كلمة) من ذاكرة الوصول العشوائي ، والتي يمكن أيضًا استخدامها كمخازن مؤقتة للجهاز والمكدس.
- يبدأ المكدس بعنوان FFFF ، لذلك ، لدى RSP قيمة قياسية 0xFFFF
- تحتوي منافذ الإدخال / الإخراج V16 على 256 منفذًا ، طولها 16 بت. تتم القراءة والكتابة منها من خلال التعليمات
IN b, a
و OUT b, a
.
سجلات
يحتوي الإصدار V16 على مجموعتين من سجلات الأغراض العامة: أولية وبديلة.
يمكن للمعالج العمل مع مجموعة واحدة فقط ، لذلك يمكنك التبديل بين المجموعات باستخدام تعليمات XCR
.
تعليمات
يبلغ طول كل التعليمات ثلاث كلمات ويتم تعريفها بالكامل أولاً
تقسم الكلمة الأولى إلى ثلاث قيم: البايت المنخفض هو شفرة التشغيل ، والبايتة العالية في شكل قيمتين 4 بت هي وصف المعاملات.
المقاطعات
لا تمثل المقاطعات هنا سوى جدول يحتوي على عناوين يكرر فيها المعالج تعليمة CALL
. إذا كانت قيمة العنوان صفرية ، فإن المقاطعة لا تفعل شيئًا ، فهي ببساطة تعيد تعيين إشارة HF.
مثال على الكود الكاذب والكلمات التي يجب أن تترجم كل هذا:
MOV RAX, 0xABCD ; 350D ABCD MOV [RAX], 0x1234 ; 354D 1234
دورات (دورات)
يمكن لـ V16 تنفيذ تعليمة واحدة في 1 أو 2 أو 3 تدابير. كل وصول الذاكرة هو دورة واحدة على مدار الساعة منفصلة. التعليمات ليست براعة!
لنبدأ الكتابة!
تنفيذ هياكل المعالج الأساسية
مجموعة من السجلات. لا يوجد سوى أربعة سجلات ، لكن الموقف يتحسن بوجود مجموعتين من هذه المجموعات في المعالج. يحدث التبديل باستخدام تعليمات XCR
.
typedef struct Regs { uint16_t rax, rbx;
الأعلام. على عكس DCPU-16 ، يحتوي V16 على قفزات مشروطة ومكالمات روتين فرعي ويعود من نفسه. في الوقت الحالي ، يحتوي المعالج على 8 أعلام ، 5 منها أعلام شرطية.
في الواقع ، المعالج نفسه. كما يصف جدول عناوين المقاطعة ، والذي يمكن أن يسمى واصفات وإيجاد مرجع آخر إلى x86.
typedef struct CPU {
المعامل. عند الحصول على القيم ، نحتاج أولاً إلى القراءة ، ثم التغيير ، ثم كتابة القيمة مرة أخرى إلى حيث حصلنا عليها.
typedef struct Opd { uint8_t code : 4; uint16_t value; uint16_t nextw; } opd_t;
وظائف للعمل مع الهياكل
عندما يتم وصف جميع الهياكل ، تنشأ الحاجة إلى الوظائف التي ستمنح هذه الهياكل القوة السحرية للرمز المخمد.
cpu_t * cpu_create(void);
أيضًا ، لم أذكر تعدادًا كبيرًا برموز التشغيل ، لكن هذا ليس ضروريًا وضروريًا فقط لفهم ما يحدث في كل هذه الفوضى.
القراد () وظيفة
أيضًا ، هناك استدعاءات لوظائف ثابتة مخصصة فقط للاتصال من tick()
.
void cpu_tick(cpu_t *cpu) {
ماذا تفعل بعد ذلك؟
في محاولة للعثور على إجابة لهذا السؤال ، أعدت كتابة المحاكي خمس مرات من C إلى C ++ ، والعكس بالعكس.
ومع ذلك ، يمكن تحديد الأهداف الرئيسية الآن:
- ربط المقاطعات العادية (بدلاً من مجرد استدعاء وظيفة وحظر تلقي المقاطعات الأخرى ، قم بإجراء استدعاء دالة وإضافة مقاطعات جديدة إلى قائمة الانتظار).
- الأجهزة اللولبية ، وكذلك طرق التواصل معها ، يمكن أن تكون فائدة الشفرات 256.
- للتدريس
لا تكتب لنفسك أي بدعة على هبر يعمل المعالج بسرعة محددة تبلغ 200 ميجا هرتز.
استنتاج
آمل أن تكون هذه "المقالة" مفيدة لشخص ما ، شخص ما سيحثهم على كتابة شيء مماثل.
فطائر بلدي يمكن أن ينظر إليها على جيثب .
أيضًا ، حول الرعب ، لدي أداة تجميع للإصدار القديم من هذا المحاكي (لا ، لا تحاول حتى ، فإن المحاكي سوف يشكو على الأقل من تنسيق ROM الخاطئ)