شرح برنامج إم ميكس مع تمارين عملية
28 Mar 2021
برنامج MMIX ويُلفظ إم-مِيكس مُصمَّم من قِبل دونالد نوث (Donald Knuth) وهو معالج افتراضي بلغة أسِّمبلي خاصة به. (يُطلق اسم لغة أسِّمبلي على كل لغة ذات مستوى منخفض Low-Level والتي تكون أقرب للآلة منها للإنسان).
يفيدنا تعلم لغة الأسمبلي هذه في فهم آلية عمل الحاسوب (والتي تستخدم في الـ compilers أيضا) وكذلك تتعلم من خلالها كيفية التعامل المباشر مع الذاكرة.
يمكنك تحميل برنامج MMIX من هنا (يمكن تشغيله على اللينكس باستخدام برنامج wine)
والحقيقة الشرح هنا ليس من الصفر تمامًا، وإنما وضع لمن يعلم الأساسيات (إضافة وطرح عددين وما إلى ذلك)، لذا يمكنك قراءة الشرح الرسمي هنا قبل البدء.
تتألف الذاكرة الرئيسية من خمسة أقسام كل قسم له عنوان (مجال من العناوين) خاص به
يتم تخزين الكود والمتغيرات وقيمهم في الـ code Segment ويبدأ عنوانه من 100#
يتم تخزين المعلومات والنصوص ومُدخلات المستخدم في الـ Data_Segment بحيث عندما نريد أخذ شيئ من المستخدم أو طباعته على الشاشة نستخدم داًئمًا الـ Data_Segment ويبدأ من:
#2000 0000 0000 0000 (عدد الأصفار 15)
والآن لنأتي مباشرة لبعض الأمثلة:
تمرين 1
برنامج لطباعة عبارة “Hello World!” ثلاث مرات
حيث أننا الآن في الداتا سيغمينت، وسنبدأ الكود دائما من هناك. تكون الرسالة التي نريد طباعتها دائما من نوع بايت، ونضع بعدها سطر جديد وبعدها صفر، دائما هكذا
#A تعني سطر جديد
عندما نريد طباعة عبارة على الشاشة (أو أخذ شيئ من المستخدم) نضع دائما عنوان عبارتنا التي نريد طباعتها (أو العنوان المخصص لحفظ مُدخل المستخدم) في الريجستر 255
السطر التالي (المأخوذ من الصورة أعلاه) يقوم بالطباعة:
TRAP 0, Fputs, stdout
وفي السطر التالي من الكود قمنا بإنقاص قيمة 1 مرة تلو الأخرى من القيمة 3 التي أعطيناها في أول الكود، وهكذا ليتم طباعة العبارة ثلاث مرات
SUB times, times, 1
قم بقراءة الكود والشرح أعلاه جيدا قبل الانتقال للأسفل.
والآن كيف يمكننا أخذ قيمة من المستخدم؟ سوف نقوم بتخصيص عنوان في الذاكرة Data_Segment ونقوم بتحميل بداية هذا العنوان المخصص للريجستر 255 ونعطي أمر القراءة من المستخدم
لماذا انتهى عنوان الـ Arg بخمسين؟ لأن 80 بالديسيمال هي 50 بالهيكساديسيمال حيث أن العنوان دائمًا بالهيكساديسيمال
إذًا الـ Arg هنا هي نهاية العنوان المخصص لإدخال المستخدم، وبعدها مباشرة نكتب القيمة التي خصصناها
عنوان الـ Arg إذًًا عرفناه، ولكن القيمة داخله هي بداية العنوان المخصص يعني عند ال Buffer والتي تكون ال Data_Segment لذا دائما عند قراءة شيئ من المستخدم نُحمّل نهاية العنوان للريجستر 255 والذي هو هنا الـ Args والذي يحفظ بداخله قيمة بداية العنوان، ثم يأتي الريجستر 255 على العنوان Args يجد بداخله أنه يدل على العنوان Buffer ويبدأ بحفظ مُدخل المستخدم من عنده.
كيف نكتب ذلك بالكود؟
السطر 4: اذهب للعنوان الذي يلي عنوان الـ Buffer بـ 80 (ديسيمال / 50 هيكساديسيمال) السطر 5: الآن احفظ لي هنا متغير اسمه Arg يحوي بداخله عنوان الـ Buffer السطر6: متغير جديد بلا اسم يحوي القيمة التي خزّنّاها (بالديسيمال)
الصورة أعلاه تنفذ تماما المخطط الذي تكلمنا عنه قبلها، لذلك سأضعهم بجانب بعضهم وتأمل يرعاك الله.
تمرين 2
اكتب برنامج يأخذ اسم من المستخدم ويحسب مربع عدد أحرف ذلك الاسم.
أي إذا أدخل المستخدم abc سيحفظ بعد الإدخال 4 في الريجستر 255،لماذا؟ ﻷنه دائما يكون بعد المُدخل صفر بالنهاية، فإذا أردنا الطول الحقيقي يجب أن نننقص فورا 1
بالنسبة للأمر GO فهو من أجل عمل دالة، حيث تُكتب الدالة قبل الـ main وبعدها عندما نريد استدعاء الدالة نضع الأمر GO، وله شكلين، الأول:
Go $X,Name
هنا يكون لدينا دالة قبل الـ main اسمها Name، سيقوم الأمر أعلاه بالذهاب للدالة ذات الاسم Name وحفظ عنوان السطر الحالي أي سطر الأمر أعلاه في الريجستر X$
الشكل الثاني:
GO $X,$Y,0
ونعني هنا اذهب إلى عنوان السطر المُخزّن في Y واحفظ عنوان السطر الحالي في الريجستر X$ مع العلم أن X و Y قد يكونوا نفس الريجستر، أي
Go $0,$0,0
أي اذهب للسطر المخزن عنوانه في الريجستر 0$ وبعدها عدل قيمة 0$ لتصبح عنوان السطر الحالي.
ولكن ما معنى الصفر في آخر التعليمة؟ Go $0,$0,0
عند التجربة تبين أنه لا معنى لها وأي رقم سيوضع مكان الصفر لن يؤثر بشيئ ويمكن الاستغناء عنها. ولكن لا يمكن إضافة صفر للشكل الأول أعلاه!
ولكن هذه الصفر (القيمة) مفيدة وهامة جدا في تعليمة مثل LDB مثلا، مثال:
LDB h1,k1,cnt
هنا سيتم إضافة قيمة الريجستر cnt إلى k1 قبل تخزينها في h1، وهي الفكرة المطبقة لأخذ حرف حرف من الكلمة. (تابع التمارين أدناه)
الشكل الأول من GO إذًًا يُستخدم عند استدعاء دالة في الـ main والثاني يُستخدم عادة خارج الـ main للذهاب لسطر محدد خارج أو داخل الـ main
ملاحظة: مباشرة قبل كتابة أول دالة اكتب دائمًا:
LOC #100
GREG @
تمرين 3
اكتب برنامج يقرأ عدد من الذاكرة ويحسب مجموع عدد خاناته (مثلا العدد 10 يحوي خانتين).
(الاسمان Sayi و Zahl أدناه يعنيان “عدد” والاسم summe يعني المجموع)
LOC Data_Segment
GREG @
sayi OCTA 156
LOC #100
zahl IS $1
summe IS $2
p IS $3
s IS $4
Main LDO zahl,sayi
DIV summe,zahl,100
DIV p,zahl,10
DIV p,p,10
GET p,rR
DIV s,zahl,10
GET s,rR
ADD summe,summe,p
ADD summe,summe,s
TRAP 0,Halt,0
تمرين 4
اكتب برنامج ينفذ خوارزمية ترتيب الأعداد Bubble sort وقم بتخصيص مساحة WYDE لكل عدد يتم ترتيبه. إليك الكود والصورة التالية لشرح الخوارزمية:
الحل
LOC Data_Segment
GREG @
A1 WYDE 5
A2 WYDE 3
A3 WYDE 4
A4 WYDE 6
A5 WYDE 1
a1 IS $1
a2 IS $2
a3 IS $3
a4 IS $4
a5 IS $5
test IS $6
s IS $7
tmp IS $8
LOC #100
Main LDW a1,A1
LDW a2,A2
LDW a3,A3
LDW a4,A4
LDW a5,A5
Loop SET s,0
CMP test,a1,a2
BP test,swap
JMP t2
swap ADD s,s,1
SET tmp,a1
SET a1,a2
SET a2,tmp
t2 CMP test,a2,a3
BP test,swap1
JMP t3
swap1 ADD s,s,1
SET tmp,a2
SET a2,a3
SET a3,tmp
t3 CMP test,a3,a4
BP test,swap2
JMP t4
swap2 ADD s,s,1
SET tmp,a3
SET a3,a4
SET a4,tmp
t4 CMP test,a4,a5
BP test,swap3
JMP son
swap3 ADD s,s,1
SET tmp,a4
SET a4,a5
SET a5,tmp
son BZ s,END
JMP Loop
END TRAP 0,Halt,0
تمرين 5
اكتب برنامج يقوم بمقارنة عبارتين نصيتين من نوع BYTE. في حال كانت العبارتين متماثلتين فليقم البرنامج بطبع الرسالة “Strings are equal” وإلا فليطبع “Srtings are different”
LOC Data_Segment
GREG @
loc1 BYTE 0
s1 BYTE "Hello",#A,0
loc2 BYTE 0
s2 BYTE "Hella",#A,0
loc3 BYTE 0
yes BYTE "Strings are equal",#A,0
loc4 BYTE 0
no BYTE "Srtings are different",#A,0
char BYTE 0
k1 IS $1
k2 IS $2
lang1 IS $3
lang2 IS $4
test IS $5
h1 IS $6
h2 IS $7
cnt IS $8
LOC #100
Main LDA k1,s1
LDA $255,s1
TRAP 0,Fputs,StdOut
SET lang1,$255
SUB lang1,lang1,1
LDA k2,s2
LDA $255,s2
TRAP 0,Fputs,StdOut
SET lang2,$255
SUB lang2,lang2,1
CMP test,lang1,lang2
BNZ test,not
SET cnt,0
Loop LDB h1,k1,cnt
LDB h2,k2,cnt
CMP test,h1,h2
BNZ test,not
ADD cnt,cnt,1
SUB lang1,lang1,1
BZ lang1,yup
JMP Loop
not LDA $255,no
TRAP 0,Fputs,StdOut
JMP END
yup LDA $255,yes
TRAP 0,Fputs,StdOut
END TRAP 0,Halt,0
تمرين 6
تحوي لغة سي دالة باسم Atoi حيث تحوي بارامتر يأخذ عدد على شكل عبارة نصية String وتقوم الدالة بتحويل العدد من نوع string إلى النوع int. اكتب هذه الدالة في برنامج MMIX
LOC Data_Segment
GREG @
buf BYTE 0
LOC buf+64
Arg OCTA buf,64
char OCTA 0
lang IS $1
all IS $4
a IS $2
cnt IS $3
int IS $5
LOC #100
Main LDA $255,Arg
TRAP 0,Fgets,StdIn
SET lang,$255
SUB lang,lang,1
SET cnt,0
LDA all,buf
SET int,0
Loop LDB a,all,cnt
SUB a,a,48
MUL int,int,10
ADD int,int,a
SUB lang,lang,1
BZ lang,END
ADD cnt,cnt,1
JMP Loop
END TRAP 0,Halt,0
تمرين 7
اكتب برنامج يأخذ من المستخدم عددا بين 1 و 10000. وباستخدام الدالة أعلاه Atoi قم بتحويل الرقم لنوع Integer. واكتب دالة تفحص هذا العدد إذا كان عدد أولي أم لا.
LOC Data_Segment
GREG @
buf BYTE 0
LOC buf+64
Arg OCTA buf,64
char OCTA 0
lang IS $1
all IS $4
a IS $2
cnt IS $3
int IS $5
test IS $7
result IS $8
H IS $10
G IS $11
asama IS $12
LOC #100
GREG @
Atoi LDB a,all,cnt
SUB a,a,48
MUL int,int,10
ADD int,int,a
SUB lang,lang,1
BZ lang,END
ADD cnt,cnt,1
JMP Atoi
END GO $0,$0,0
prim SET G,0
SET asama,0
DIV test,int,2
GET test,rR
BNZ test,pz
continue DIV test,int,3
SET asama,2
GET test,rR
BNZ test,pz
Endp GO $9,$9,0
pz ADD G,G,1
BZ asama,continue
CMP test,G,2
BZ test,pz1
JMP Endp
pz1 SET result,1
JMP Endp
Main LDA $255,Arg
TRAP 0,Fgets,StdIn
SET lang,$255
SUB lang,lang,1
SET cnt,0
LDA all,buf
SET int,0
GO $0,Atoi
CMP test,int,1
BN test,finish
SET H,10000
CMP test,int,H
BP test,finish
GO $9,prim
finish TRAP 0,Halt,0
تمرين 8
احفظ القيم التالية في الذاكرة:
a=3, b=7, c=11, d=0, s=5, t=9, u=13, v=0
اكتب دالة باسم “islem” (إشلم تعني عملية/إجراء بالتركي) وبداخلها قم بعمل العملية التالية:
(x + (y * z)) / (x + y) iş
واحفظ النتيجة في الذاكرة.
للمراجعة: ماهو الفرق بين STB, LDB,LDA..
ملاحظة: يمكننا حفظ موقع بشكل مباشر في متغير في الذاكرة (وليس ريجستر) بكتابة GREG مثلًا:
A GREG #2000000000000050
ملاحظة أخيرة: هناك ريجسترات خاصة منها rR الذي يحوي باقي القسمة ويتم أخذ القيمة منه بعد إجراء عملية قسمة بالأمر:
GET $X,rR
أي حفظ باقي القسمة في الريجستر X
عندما يكون لدينا أي تمرين يتطلب معرفة باقي قسمة ما، أو قانون يحوي العامل مودولو % والذي هو باقي القسمة أيضا، نستخدم الريجستر rR (أي نأخذ قيمة باقي القسمة كما في الأمر أعلاه)