भाग 4: QFTASM और Cogol
वास्तुकला अवलोकन
संक्षेप में, हमारे कंप्यूटर में 16-बिट एसिंक्रोनस RISC हार्वर्ड आर्किटेक्चर है। हाथ से प्रोसेसर का निर्माण करते समय, RISC ( कम इंस्ट्रक्शन सेट कंप्यूटर ) आर्किटेक्चर व्यावहारिक रूप से एक आवश्यकता है। हमारे मामले में, इसका मतलब है कि ऑपकोड की संख्या छोटी है और, इससे भी महत्वपूर्ण बात यह है कि सभी निर्देशों को एक समान तरीके से संसाधित किया जाता है।
संदर्भ के लिए, वायरवर्ल्ड कंप्यूटर ने एक परिवहन-ट्रिगर वास्तुकला का उपयोग किया , जिसमें केवल निर्देश थे MOV
और विशेष रजिस्टरों को लिखने / पढ़ने के द्वारा गणना की गई थी। यद्यपि यह प्रतिमान एक बहुत ही आसानी से लागू होने वाली वास्तुकला की ओर ले जाता है, परिणाम भी सीमा-रहित है: सभी अंकगणितीय / तर्क / सशर्त संचालन के लिए तीन निर्देशों की आवश्यकता होती है । यह हमारे लिए स्पष्ट था कि हम बहुत कम गूढ़ वास्तुकला बनाना चाहते थे।
प्रयोज्यता को बढ़ाते हुए हमारे प्रोसेसर को सरल रखने के लिए, हमने कई महत्वपूर्ण डिजाइन निर्णय लिए:
- कोई रजिस्टर नहीं। रैम में हर पते को समान रूप से व्यवहार किया जाता है और किसी भी ऑपरेशन के लिए किसी भी तर्क के रूप में इस्तेमाल किया जा सकता है। एक मायने में, इसका मतलब है कि सभी रैम को रजिस्टरों की तरह माना जा सकता है। इसका मतलब है कि कोई विशेष लोड / स्टोर निर्देश नहीं हैं।
- एक समान नस में, मेमोरी-मैपिंग। सब कुछ जो एक एकीकृत पते योजना के शेयरों से लिखा या पढ़ा जा सकता है। इसका मतलब यह है कि प्रोग्राम काउंटर (पीसी) पता 0 है, और नियमित निर्देशों और नियंत्रण-प्रवाह निर्देशों के बीच एकमात्र अंतर यह है कि नियंत्रण-प्रवाह निर्देश पते 0 का उपयोग करते हैं।
- डेटा ट्रांसमिशन में सीरियल है, स्टोरेज में समानांतर है। हमारे कंप्यूटर के "इलेक्ट्रॉन"-आधारित प्रकृति के कारण, इसके अलावा और घटाव को लागू करने में काफी आसान होता है जब डेटा सीरियल लिटिल-एंडियन (कम से कम महत्वपूर्ण पहले) रूप में प्रसारित होता है। इसके अलावा, धारावाहिक डेटा बोझिल डेटा बसों की आवश्यकता को दूर करता है, जो कि वास्तव में व्यापक और बोझिल दोनों हैं (ठीक से एक साथ रहने के लिए डेटा के लिए, बस के सभी "लेन" को एक ही यात्रा में देरी का अनुभव करना होगा)।
- हार्वर्ड आर्किटेक्चर, जिसका अर्थ प्रोग्राम मेमोरी (ROM) और डेटा मेमोरी (RAM) के बीच विभाजन है। यद्यपि यह प्रोसेसर के लचीलेपन को कम करता है, इससे आकार के अनुकूलन में मदद मिलती है: कार्यक्रम की लंबाई हमें जितनी मात्रा में रैम की आवश्यकता होती है, उससे बहुत बड़ी होती है, इसलिए हम प्रोग्राम को ROM में विभाजित कर सकते हैं और फिर ROM को संपीड़ित करने पर ध्यान केंद्रित कर सकते हैं। , जो बहुत आसान है जब यह केवल पढ़ने के लिए है।
- 16-बिट डेटा चौड़ाई। यह दो की सबसे छोटी शक्ति है जो एक मानक टेट्रिस बोर्ड (10 ब्लॉक) की तुलना में व्यापक है। यह हमें -32768 से +32767 तक डेटा रेंज और 65536 निर्देशों की अधिकतम प्रोग्राम लंबाई देता है। (2 ^ 8 = 256 निर्देश सबसे सरल चीजों के लिए पर्याप्त है जो हम चाहते हैं कि एक खिलौना प्रोसेसर करना चाहिए, लेकिन टेट्रिस नहीं।)
- अतुल्यकालिक डिजाइन। केंद्रीय घड़ी (या, समतुल्य, कई घड़ियों) के बजाय कंप्यूटर के समय को निर्धारित करते हुए, सभी डेटा एक "घड़ी संकेत" के साथ होता है जो डेटा के समानांतर यात्रा करता है क्योंकि यह कंप्यूटर के चारों ओर बहता है। कुछ पथ दूसरों की तुलना में कम हो सकते हैं, और जब यह एक केंद्र-डिज़ाइन किए गए डिज़ाइन के लिए कठिनाइयों का कारण होगा, तो एक अतुल्यकालिक डिज़ाइन आसानी से चर-समय के संचालन से निपट सकता है।
- सभी निर्देश समान आकार के हैं। हमने महसूस किया कि एक आर्किटेक्चर जिसमें प्रत्येक निर्देश में 3 ऑपरेंड (वैल्यू वैल्यू डेस्टिनेशन) के साथ 1 ओपकोड है, सबसे लचीला विकल्प था। इसमें बाइनरी डेटा ऑपरेशन के साथ-साथ सशर्त चाल शामिल हैं।
- सिंपल एड्रेसिंग मोड सिस्टम। विभिन्न प्रकार के एड्रेसिंग मोड होने से एरे या रिकर्स जैसे चीजों का समर्थन करने के लिए बहुत उपयोगी है। हम अपेक्षाकृत सरल प्रणाली के साथ कई महत्वपूर्ण एड्रेसिंग मोड को लागू करने में कामयाब रहे।
हमारी वास्तुकला का एक चित्रण अवलोकन पोस्ट में निहित है।
कार्यक्षमता और ALU संचालन
यहां से, यह निर्धारित करने की बात थी कि हमारे प्रोसेसर में क्या कार्यक्षमता होनी चाहिए। कार्यान्वयन की आसानी के साथ-साथ प्रत्येक कमांड की बहुमुखी प्रतिभा पर विशेष ध्यान दिया गया था।
सशर्त चाल
सशर्त चालें बहुत महत्वपूर्ण हैं और छोटे पैमाने पर और बड़े पैमाने पर नियंत्रण प्रवाह दोनों के रूप में काम करती हैं। "स्मॉल-स्केल" एक विशेष डेटा चाल के निष्पादन को नियंत्रित करने की अपनी क्षमता को संदर्भित करता है, जबकि "बड़े पैमाने पर" कोड के किसी भी मनमाने टुकड़े को नियंत्रण प्रवाह स्थानांतरित करने के लिए सशर्त कूदने के ऑपरेशन के रूप में इसके उपयोग को संदर्भित करता है। कोई समर्पित जंप ऑपरेशन नहीं हैं, क्योंकि मेमोरी मैपिंग के कारण, एक सशर्त चाल दोनों डेटा को नियमित रूप से रैम पर कॉपी कर सकते हैं और पीसी पर एक गंतव्य पते की प्रतिलिपि बना सकते हैं। हमने बिना किसी कारण के बिना शर्त और बिना शर्त कूद दोनों को भी चुनने का फैसला किया है: दोनों को एक शर्त के साथ सशर्त कदम के रूप में लागू किया जा सकता है जो TRUE के लिए हार्डकोड है।
हमने दो अलग-अलग प्रकार की सशर्त चालें चुनीं: "शून्य नहीं तो चाल" ( MNZ
) और "शून्य से कम चलने पर" ( MLZ
)। कार्यात्मक रूप से, MNZ
यह जाँचने के लिए कि क्या डेटा में कोई बिट 1 है, जबकि MLZ
साइन बिट की जाँच करने के लिए मात्रा 1. 1. वे क्रमशः समानता और तुलना के लिए उपयोगी हैं। जिस कारण से हमने इन दोनों को दूसरों के ऊपर चुना है जैसे "कदम अगर शून्य" ( MEZ
) या "चाल अगर शून्य से अधिक है" ( MGZ
) तो यह था कि MEZ
खाली सिग्नल से TRUE सिग्नल बनाने की आवश्यकता होती है, जबकि MGZ
एक अधिक जटिल जांच है, जिसके लिए आवश्यक है साइन बिट 0 होना चाहिए, जबकि कम से कम एक बिट 1 होना चाहिए।
अंकगणित
प्रोसेसर डिजाइन को निर्देशित करने के संदर्भ में अगला-सबसे महत्वपूर्ण निर्देश, बुनियादी अंकगणितीय ऑपरेशन हैं। जैसा कि मैंने पहले उल्लेख किया है, हम छोटे-एंडियन सीरियल डेटा का उपयोग कर रहे हैं, इसके अलावा / घटाव संचालन की आसानी द्वारा निर्धारित एंडियनस की पसंद के साथ। कम से कम महत्वपूर्ण बिट पहले आने से, अंकगणित इकाइयां आसानी से कैरी बिट का ट्रैक रख सकती हैं।
हमने नकारात्मक संख्याओं के लिए 2 के पूरक प्रतिनिधित्व का उपयोग करना चुना, क्योंकि इससे जोड़ और घटाव अधिक सुसंगत हो जाता है। यह ध्यान देने योग्य है कि वायरवर्ल्ड कंप्यूटर ने 1 के पूरक का उपयोग किया।
जोड़ और घटाव हमारे कंप्यूटर की मूल अंकगणित सहायता (बिट शिफ्ट के अलावा जो बाद में चर्चा की जाती है) की सीमा है। गुणा की तरह अन्य ऑपरेशन, हमारी वास्तुकला द्वारा नियंत्रित किए जाने के लिए बहुत जटिल हैं, और सॉफ्टवेयर में लागू किया जाना चाहिए।
बिटवाइज ऑपरेशन
हमारे प्रोसेसर के पास है AND
, OR
और XOR
निर्देश जो आप अपेक्षा करते हैं। एक NOT
निर्देश के बजाय , हमने एक "और नहीं" ( ANT
) निर्देश चुना है। NOT
निर्देश के साथ कठिनाई फिर से है कि इसे सिग्नल की कमी से संकेत बनाना होगा, जो सेलुलर ऑटोमेटा के साथ मुश्किल है। ANT
शिक्षा देता है 1 केवल यदि पहला तर्क बिट 1 है और दूसरा तर्क बिट 0. इस प्रकार है, NOT x
के बराबर है ANT -1 x
(और साथ ही XOR -1 x
)। इसके अलावा, ANT
बहुमुखी है और मास्किंग में इसका मुख्य लाभ है: टेट्रिस कार्यक्रम के मामले में हम इसका उपयोग टेट्रोमिनोइन्स को मिटाने के लिए करते हैं।
बिट शिफ्टिंग
बिट-शिफ्टिंग ऑपरेशन ALU द्वारा संचालित सबसे जटिल ऑपरेशन हैं। वे दो डेटा इनपुट लेते हैं: शिफ्ट करने के लिए एक मूल्य और इसे शिफ्ट करने के लिए एक राशि। उनकी जटिलता (स्थानांतरण की चर राशि के कारण) के बावजूद, ये ऑपरेशन कई महत्वपूर्ण कार्यों के लिए महत्वपूर्ण हैं, जिसमें टेट्रिस में शामिल कई "ग्राफिकल" ऑपरेशन शामिल हैं। बिट शिफ्ट्स कुशल गुणा / भाग एल्गोरिदम की नींव के रूप में भी काम करेंगे।
हमारे प्रोसेसर में तीन बिट शिफ्ट ऑपरेशन हैं, "शिफ्ट लेफ्ट" ( SL
), "शिफ्ट राईट लॉजिकल" ( SRL
), और "शिफ्ट राईट अरिथमेटिक" ( SRA
)। पहले दो बिट्स शिफ्ट ( SL
और SRL
) नए बिट्स को सभी शून्य के साथ भरें (जिसका अर्थ है कि एक नकारात्मक संख्या सही स्थानांतरित अब नकारात्मक नहीं होगी)। यदि शिफ्ट का दूसरा तर्क 0 से 15 की सीमा के बाहर है, तो परिणाम सभी शून्य है, जैसा कि आप उम्मीद कर सकते हैं। अंतिम बिट शिफ्ट के लिए, SRA
बिट शिफ्ट इनपुट के संकेत को संरक्षित करता है, और इसलिए दो द्वारा एक सच्चे विभाजन के रूप में कार्य करता है।
निर्देश पाइपलाइन
अब वास्तुकला के कुछ बारीक विवरणों के बारे में बात करने का समय है। प्रत्येक CPU चक्र में निम्नलिखित पाँच चरण होते हैं:
1. ROM से वर्तमान निर्देश प्राप्त करें
पीसी का वर्तमान मूल्य ROM से संबंधित निर्देश लाने के लिए उपयोग किया जाता है। प्रत्येक निर्देश में एक ओपकोड और तीन ऑपरेंड होते हैं। प्रत्येक ऑपरेंड में एक डेटा शब्द और एक एड्रेसिंग मोड होता है। रोम से पढ़ते ही ये भाग एक दूसरे से अलग हो जाते हैं।
16 अद्वितीय opcodes का समर्थन करने के लिए opcode 4 बिट्स है, जिनमें से 11 को असाइन किया गया है:
0000 MNZ Move if Not Zero
0001 MLZ Move if Less than Zero
0010 ADD ADDition
0011 SUB SUBtraction
0100 AND bitwise AND
0101 OR bitwise OR
0110 XOR bitwise eXclusive OR
0111 ANT bitwise And-NoT
1000 SL Shift Left
1001 SRL Shift Right Logical
1010 SRA Shift Right Arithmetic
1011 unassigned
1100 unassigned
1101 unassigned
1110 unassigned
1111 unassigned
2. रैम को पिछले निर्देश का परिणाम (यदि आवश्यक हो) लिखें
पिछले निर्देश की स्थिति पर निर्भर करता है (जैसे सशर्त चाल के लिए पहले तर्क का मूल्य), एक लेखन किया जाता है। लेखन का पता पिछले अनुदेश के तीसरे ऑपरेंड द्वारा निर्धारित किया जाता है।
यह ध्यान रखना महत्वपूर्ण है कि निर्देश लाने के बाद लेखन होता है। यह एक शाखा विलंब स्लॉट के निर्माण की ओर जाता है जिसमें शाखा निर्देश के तुरंत बाद निर्देश (कोई भी ऑपरेशन जो पीसी को लिखता है) को शाखा लक्ष्य पर पहले निर्देश के बदले निष्पादित किया जाता है।
कुछ मामलों में (बिना शर्त कूद की तरह), शाखा विलंब स्लॉट को दूर अनुकूलित किया जा सकता है। अन्य मामलों में, यह नहीं हो सकता है और एक शाखा के बाद अनुदेश खाली छोड़ दिया जाना चाहिए। इसके अलावा, इस प्रकार की देरी स्लॉट का मतलब है कि शाखाओं को एक शाखा लक्ष्य का उपयोग करना चाहिए जो वास्तविक पीसीआर वृद्धि के लिए 1 पते से कम है, जो कि पीसी वेतन वृद्धि के लिए है।
संक्षेप में, क्योंकि पिछले निर्देश का आउटपुट रैम के लिए लिखा जाता है, अगला निर्देश प्राप्त होने के बाद, सशर्त कूदने के लिए उनके बाद एक खाली निर्देश की आवश्यकता होती है, अन्यथा कूदने के लिए पीसी ठीक से अपडेट नहीं होगा।
3. रैम से वर्तमान अनुदेश के तर्कों के लिए डेटा पढ़ें
जैसा कि पहले उल्लेख किया गया है, तीनों ऑपरेंड्स में से प्रत्येक में डेटा शब्द और एड्रेसिंग मोड दोनों होते हैं। डेटा शब्द 16 बिट्स है, रैम के समान चौड़ाई। एड्रेसिंग मोड 2 बिट्स है।
एड्रेसिंग मोड इस तरह के एक प्रोसेसर के लिए महत्वपूर्ण जटिलता का स्रोत हो सकता है, क्योंकि कई वास्तविक दुनिया के एड्रेसिंग मोड में मल्टी-स्टेप कंप्यूटेशन (जैसे ऑफसेट जोड़ने) शामिल हैं। इसी समय, बहुमुखी संबोधित करने वाले मोड प्रोसेसर की उपयोगिता में महत्वपूर्ण भूमिका निभाते हैं।
हमने ऑपरेंड के रूप में हार्ड-कोडित संख्याओं का उपयोग करने और ऑपरेंड के रूप में डेटा पतों का उपयोग करने की अवधारणाओं को एकजुट करने की मांग की। इससे काउंटर-आधारित एड्रेसिंग मोड का निर्माण हुआ: एक ऑपरेंड का एड्रेसिंग मोड बस एक संख्या है जो यह दर्शाती है कि रैम रीड लूप के आसपास डेटा कितनी बार भेजा जाना चाहिए। इसमें तत्काल, प्रत्यक्ष, अप्रत्यक्ष और दोहरे-अप्रत्यक्ष संबोधन शामिल हैं।
00 Immediate: A hard-coded value. (no RAM reads)
01 Direct: Read data from this RAM address. (one RAM read)
10 Indirect: Read data from the address given at this address. (two RAM reads)
11 Double-indirect: Read data from the address given at the address given by this address. (three RAM reads)
इसके बाद डेरेफ्रेंसिंग की जाती है, निर्देश के तीन ऑपरेंड में अलग-अलग भूमिका होती है। पहला ऑपरेंड आमतौर पर एक बाइनरी ऑपरेटर के लिए पहला तर्क होता है, लेकिन यह भी उस स्थिति के रूप में कार्य करता है जब वर्तमान निर्देश एक सशर्त चाल है। दूसरा ऑपरेंड एक बाइनरी ऑपरेटर के लिए दूसरे तर्क के रूप में कार्य करता है। तीसरा ऑपरेंड निर्देश के परिणाम के लिए गंतव्य पते के रूप में कार्य करता है।
चूंकि पहले दो निर्देश डेटा के रूप में काम करते हैं, जबकि तीसरा एक पते के रूप में कार्य करता है, इसलिए संबोधित करने के तरीकों की थोड़ी अलग व्याख्या होती है कि वे किस स्थिति में उपयोग किए जाते हैं। उदाहरण के लिए, प्रत्यक्ष मोड का उपयोग निश्चित रैम पते से डेटा पढ़ने के लिए किया जाता है (तब से एक रैम रीड की जरूरत है), लेकिन एक निश्चित रैम पते पर डेटा लिखने के लिए तत्काल मोड का उपयोग किया जाता है (क्योंकि कोई रैम रीड आवश्यक नहीं है)।
4. परिणाम की गणना करें
ओपोड और पहले दो ऑपरेंड को बाइनरी ऑपरेशन करने के लिए ALU में भेजा जाता है। अंकगणित, बिटवाइज़ और शिफ्ट ऑपरेशन के लिए, इसका अर्थ है प्रासंगिक ऑपरेशन करना। सशर्त चालों के लिए, इसका मतलब बस दूसरे ऑपरेंड को वापस करना है।
ओपकोड और पहले ऑपरेंड का उपयोग स्थिति की गणना करने के लिए किया जाता है, जो यह निर्धारित करता है कि स्मृति को परिणाम लिखना है या नहीं। सशर्त चालों के मामले में, इसका मतलब या तो यह निर्धारित करना है कि क्या ऑपरेंड में कोई बिट 1 (के लिए MNZ
) है, या यह निर्धारित करने के लिए कि साइन बिट 1 (के लिए MLZ
) है या नहीं। यदि ओपकोड एक सशर्त चाल नहीं है, तो लेखन हमेशा किया जाता है (शर्त हमेशा सच होती है)।
5. कार्यक्रम काउंटर बढ़ाएँ
अंत में, प्रोग्राम काउंटर को पढ़ा जाता है, इंक्रीमेंट किया जाता है, और लिखा जाता है।
निर्देश पढ़ने और अनुदेश लिखने के बीच पीसी वेतन वृद्धि की स्थिति के कारण, इसका मतलब है कि एक अनुदेश जो पीसी द्वारा 1 बढ़ाता है वह एक नो-ऑप है। एक निर्देश जो पीसी को स्वयं कॉपी करता है, अगले निर्देश को लगातार दो बार निष्पादित किया जाता है। लेकिन, चेतावनी दी जा सकती है, एक पंक्ति में कई पीसी निर्देश अनंत लूपिंग सहित जटिल प्रभाव पैदा कर सकते हैं, यदि आप निर्देश पाइपलाइन पर ध्यान नहीं देते हैं।
टेट्रिस असेंबली के लिए क्वेस्ट
हमने अपने प्रोसेसर के लिए QFTASM नाम से एक नई असेंबली भाषा बनाई। यह असेंबली भाषा कंप्यूटर के रोम में मशीन कोड के साथ 1-टू -1 से मेल खाती है।
किसी भी QFTASM कार्यक्रम को निर्देशों की एक श्रृंखला के रूप में लिखा जाता है, प्रति पंक्ति एक। प्रत्येक पंक्ति इस तरह स्वरूपित है:
[line numbering] [opcode] [arg1] [arg2] [arg3]; [optional comment]
ओपकोड सूची
जैसा कि पहले चर्चा की गई थी, कंप्यूटर द्वारा समर्थित ग्यारह ऑपकोड हैं, जिनमें से प्रत्येक में तीन ऑपरेंड हैं:
MNZ [test] [value] [dest] – Move if Not Zero; sets [dest] to [value] if [test] is not zero.
MLZ [test] [value] [dest] – Move if Less than Zero; sets [dest] to [value] if [test] is less than zero.
ADD [val1] [val2] [dest] – ADDition; store [val1] + [val2] in [dest].
SUB [val1] [val2] [dest] – SUBtraction; store [val1] - [val2] in [dest].
AND [val1] [val2] [dest] – bitwise AND; store [val1] & [val2] in [dest].
OR [val1] [val2] [dest] – bitwise OR; store [val1] | [val2] in [dest].
XOR [val1] [val2] [dest] – bitwise XOR; store [val1] ^ [val2] in [dest].
ANT [val1] [val2] [dest] – bitwise And-NoT; store [val1] & (![val2]) in [dest].
SL [val1] [val2] [dest] – Shift Left; store [val1] << [val2] in [dest].
SRL [val1] [val2] [dest] – Shift Right Logical; store [val1] >>> [val2] in [dest]. Doesn't preserve sign.
SRA [val1] [val2] [dest] – Shift Right Arithmetic; store [val1] >> [val2] in [dest], while preserving sign.
मोड्स को संबोधित करते हुए
प्रत्येक ऑपरेंड में डेटा वैल्यू और एड्रेसिंग मूव दोनों होते हैं। डेटा मान को दशमलव संख्या द्वारा -32768 से 32767 में वर्णित किया गया है। डेटा मोड में एक-अक्षर उपसर्ग द्वारा एड्रेसिंग मोड का वर्णन किया गया है।
mode name prefix
0 immediate (none)
1 direct A
2 indirect B
3 double-indirect C
उदाहरण कोड
पांच लाइनों में फाइबोनैचि अनुक्रम:
0. MLZ -1 1 1; initial value
1. MLZ -1 A2 3; start loop, shift data
2. MLZ -1 A1 2; shift data
3. MLZ -1 0 0; end loop
4. ADD A2 A3 1; branch delay slot, compute next term
यह कोड फाइबोनैचि अनुक्रम की गणना करता है, जिसमें रैम पता 1 वर्तमान शब्द होता है। यह 28657 के बाद तेजी से आगे निकल जाता है।
ग्रे कोड:
0. MLZ -1 5 1; initial value for RAM address to write to
1. SUB A1 5 2; start loop, determine what binary number to covert to Gray code
2. SRL A2 1 3; shift right by 1
3. XOR A2 A3 A1; XOR and store Gray code in destination address
4. SUB B1 42 4; take the Gray code and subtract 42 (101010)
5. MNZ A4 0 0; if the result is not zero (Gray code != 101010) repeat loop
6. ADD A1 1 1; branch delay slot, increment destination address
यह कार्यक्रम ग्रे कोड की गणना करता है और पते 5 पर शुरू होने वाले सुसाइड पतों में कोड को स्टोर करता है। यह प्रोग्राम कई महत्वपूर्ण विशेषताओं जैसे अप्रत्यक्ष पते और सशर्त कूद का उपयोग करता है। यह परिणामी ग्रे कोड होने के बाद रुक जाता है 101010
, जो पता 56 पर इनपुट 51 के लिए होता है।
ऑनलाइन दुभाषिया
एल'एंडिया स्ट्रैटन ने यहां एक बहुत उपयोगी ऑनलाइन दुभाषिया बनाया है । आप कोड के माध्यम से कदम बढ़ा सकते हैं, ब्रेकप्वाइंट सेट कर सकते हैं, रैम को मैनुअल लिख सकते हैं और रैम को डिस्प्ले के रूप में देख सकते हैं।
Cogol
एक बार आर्किटेक्चर और असेंबली भाषा को परिभाषित करने के बाद, प्रोजेक्ट के "सॉफ़्टवेयर" पक्ष पर अगला कदम एक उच्च-स्तरीय भाषा का निर्माण था, जो कि टेट्रिस के लिए उपयुक्त है। इस प्रकार मैंने कोगोल बनाया । नाम "COBOL" पर एक वाक्य है और "C of Game of Life" के लिए एक संक्षिप्त नाम है, हालांकि यह ध्यान देने योग्य है कि Cogol C हमारे कंप्यूटर का वास्तविक कंप्यूटर क्या है।
Cogol विधानसभा भाषा के ठीक ऊपर एक स्तर पर मौजूद है। आम तौर पर, एक कोगोल कार्यक्रम में अधिकांश लाइनें प्रत्येक विधानसभा की एक पंक्ति के अनुरूप होती हैं, लेकिन भाषा की कुछ महत्वपूर्ण विशेषताएं हैं:
- मूल विशेषताओं में असाइनमेंट और ऑपरेटरों के साथ नामित चर शामिल हैं जिनमें अधिक पठनीय वाक्यविन्यास है। उदाहरण के लिए, पते पर कंपाइलर मैपिंग चर के साथ ,
ADD A1 A2 3
बन जाता है z = x + y;
।
- जैसे पाशन निर्माणों
if(){}
, while(){}
है, और do{}while();
इसलिए संकलक शाखाओं हैंडल।
- एक आयामी सरणियों (सूचक अंकगणित के साथ), जो कि टेट्रिस बोर्ड के लिए उपयोग किया जाता है।
- सबरूटीन्स और एक कॉल स्टैक। ये कोड के बड़े हिस्से के दोहराव को रोकने के लिए और पुनरावर्तन का समर्थन करने के लिए उपयोगी हैं।
संकलक (जिसे मैंने स्क्रैच से लिखा है) बहुत ही बुनियादी / भोला है, लेकिन मैंने एक छोटे संकलित कार्यक्रम की लंबाई को प्राप्त करने के लिए भाषा के कई निर्माणों को हाथ से अनुकूलित करने का प्रयास किया है।
यहाँ विभिन्न भाषा सुविधाएँ कैसे काम करती हैं, इसके कुछ संक्षिप्त विवरण दिए गए हैं:
tokenization
स्रोत कोड को रेखीय रूप से (एकल-पास) टोकन किया जाता है, सरल नियमों का उपयोग करके जिनके बारे में पात्रों को टोकन के भीतर समीप होने की अनुमति होती है। जब एक वर्ण का सामना किया जाता है, जो वर्तमान टोकन के अंतिम चरित्र के निकट नहीं हो सकता है, तो वर्तमान टोकन को पूर्ण माना जाता है और नया वर्ण एक नया टोकन प्रारंभ करता है। कुछ वर्ण (जैसे कि {
या ,
) किसी भी अन्य वर्ण के समीप नहीं हो सकते हैं और इसलिए वे अपने स्वयं के टोकन हैं। अन्य (जैसे >
या =
) केवल अपने वर्ग के भीतर अन्य पात्रों से सटे होने के लिए अनुमति दी जाती है, और इस तरह की तरह टोकन फार्म कर सकते हैं >>>
, ==
या >=
, लेकिन पसंद नहीं =2
। व्हॉट्सएप के चरित्र टोकन के बीच एक सीमा को बाध्य करते हैं लेकिन परिणाम में खुद को शामिल नहीं करते हैं। सबसे कठिन चरित्र टोकन है-
क्योंकि यह दोनों घटाव और एकात्मक नकार का प्रतिनिधित्व कर सकता है, और इस प्रकार कुछ विशेष आवरण की आवश्यकता होती है।
पदच्छेद
पारसिंग भी एक एकल-पास फैशन में किया जाता है। कंपाइलर के पास विभिन्न भाषा निर्माणों में से प्रत्येक को संभालने के लिए विधियां हैं, और टोकन वैश्विक संकलित सूची से अलग हैं, क्योंकि वे विभिन्न संकलक विधियों द्वारा उपभोग किए जाते हैं। यदि कंपाइलर कभी भी एक टोकन देखता है जो इसकी अपेक्षा नहीं करता है, तो यह एक सिंटैक्स त्रुटि उठाता है।
ग्लोबल मेमोरी आवंटन
संकलक प्रत्येक वैश्विक चर (शब्द या सरणी) को अपना स्वयं का निर्दिष्ट रैम पता (एस) प्रदान करता है। कीवर्ड का उपयोग करके सभी चर घोषित करना आवश्यक है my
ताकि कंपाइलर इसके लिए स्थान आवंटित करना जानता हो। नामित वैश्विक चरों की तुलना में बहुत ठंडा खरोंच पता स्मृति प्रबंधन है। कई निर्देशों (विशेष रूप से सशर्त और कई सरणी एक्सेस) को मध्यवर्ती गणनाओं को संग्रहीत करने के लिए अस्थायी "खरोंच" पते की आवश्यकता होती है। संकलन प्रक्रिया के दौरान संकलक आवश्यक के रूप में खरोंच के पते आवंटित करता है और डी-आवंटित करता है। यदि कंपाइलर को अधिक स्क्रैच पते की आवश्यकता है, तो यह स्क्रैच पतों के रूप में अधिक रैम समर्पित करेगा। मेरा मानना है कि एक कार्यक्रम के लिए यह विशिष्ट है कि केवल कुछ स्क्रैच पते की आवश्यकता है, हालांकि प्रत्येक स्क्रैच पते का उपयोग कई बार किया जाएगा।
IF-ELSE
बयान
if-else
बयानों के लिए वाक्यविन्यास मानक C रूप है:
other code
if (cond) {
first body
} else {
second body
}
other code
QFTASM में परिवर्तित होने पर, कोड को इस तरह व्यवस्थित किया जाता है:
other code
condition test
conditional jump
first body
unconditional jump
second body (conditional jump target)
other code (unconditional jump target)
यदि पहले निकाय को निष्पादित किया जाता है, तो दूसरा शरीर ऊपर छोड़ दिया जाता है। यदि पहले शरीर को छोड़ दिया जाता है, तो दूसरे निकाय को निष्पादित किया जाता है।
विधानसभा में, एक शर्त परीक्षा आमतौर पर सिर्फ एक घटाव होती है, और परिणाम का संकेत यह निर्धारित करता है कि क्या शरीर को कूदना या निष्पादित करना है। एक MLZ
निर्देश का उपयोग असमानताओं को संभालने के लिए किया जाता है जैसे >
या <=
। एक MNZ
निर्देश को संभालने के लिए उपयोग किया जाता है ==
, क्योंकि यह अंतर शून्य होने पर शरीर पर कूदता है (और इसलिए जब तर्क समान नहीं होते हैं)। बहु-अभिव्यक्ति सशर्त वर्तमान में समर्थित नहीं हैं।
यदि else
कथन छोड़ दिया जाता है, तो बिना शर्त कूद भी छोड़ दिया जाता है, और QFTASM कोड इस तरह दिखता है:
other code
condition test
conditional jump
body
other code (conditional jump target)
WHILE
बयान
while
बयानों के लिए वाक्यविन्यास भी मानक C रूप है:
other code
while (cond) {
body
}
other code
QFTASM में परिवर्तित होने पर, कोड को इस तरह व्यवस्थित किया जाता है:
other code
unconditional jump
body (conditional jump target)
condition test (unconditional jump target)
conditional jump
other code
शर्त परीक्षण और सशर्त कूद ब्लॉक के अंत में हैं, जिसका अर्थ है कि ब्लॉक के प्रत्येक निष्पादन के बाद उन्हें फिर से निष्पादित किया जाता है। जब स्थिति वापस आती है तो शरीर को दोहराया नहीं जाता है और लूप समाप्त हो जाता है। लूप निष्पादन की शुरुआत के दौरान, नियंत्रण प्रवाह लूप बॉडी पर स्थिति कोड में कूदता है, इसलिए पहली बार झूठी होने पर बॉडी को कभी भी निष्पादित नहीं किया जाता है।
एक MLZ
निर्देश का उपयोग असमानताओं को संभालने के लिए किया जाता है जैसे >
या <=
। if
बयानों के दौरान इसके विपरीत , एक MNZ
निर्देश को संभालने के लिए उपयोग किया जाता है !=
, क्योंकि यह अंतर शून्य होने पर शरीर में कूदता है (और इसलिए जब तर्क समान नहीं होते हैं)।
DO-WHILE
बयान
के बीच एकमात्र अंतर while
और do-while
यह है कि एक do-while
लूप बॉडी को शुरू में छोड़ नहीं दिया जाता है इसलिए इसे हमेशा कम से कम एक बार निष्पादित किया जाता है। मैं आमतौर पर do-while
असेंबली कोड की कुछ पंक्तियों को सहेजने के लिए बयानों का उपयोग करता हूं जब मुझे पता है कि लूप को पूरी तरह से छोड़ देने की आवश्यकता नहीं होगी।
Arrays
एक आयामी सरणियों को स्मृति के सन्निहित ब्लॉक के रूप में लागू किया जाता है। सभी ऐरे को उनकी घोषणा के आधार पर निर्धारित लंबाई के होते हैं। ऐरे को ऐसे घोषित किया जाता है:
my alpha[3]; # empty array
my beta[11] = {3,2,7,8}; # first four elements are pre-loaded with those values
सरणी के लिए, यह एक संभावित रैम मैपिंग है, जिसमें दिखाया गया है कि सरणी के लिए 15-18 पते कैसे आरक्षित हैं:
15: alpha
16: alpha[0]
17: alpha[1]
18: alpha[2]
लेबल किए गए पते alpha
को एक पॉइंटर के स्थान से भरा जाता है alpha[0]
, इसलिए थाइ केस एड्रेस 15 में मान 16 होता है। alpha
चर का उपयोग कोगोल कोड के अंदर किया जा सकता है, संभवतः स्टैक पॉइंटर के रूप में यदि आप इस सरणी को स्टैक के रूप में उपयोग करना चाहते हैं। ।
किसी सरणी के तत्वों को एक्सेस करना मानक array[index]
संकेतन के साथ किया जाता है । यदि मान index
एक स्थिर है, तो यह संदर्भ स्वचालित रूप से उस तत्व के पूर्ण पते के साथ भर जाता है। अन्यथा यह वांछित निरपेक्ष पता खोजने के लिए कुछ सूचक अंकगणित (सिर्फ जोड़) करता है। घोंसला अनुक्रमण जैसे कि यह भी संभव है alpha[beta[1]]
।
सबरूटीन्स और कॉलिंग
सबरूटीन कोड के ब्लॉक हैं जिन्हें कई संदर्भों से बुलाया जा सकता है, कोड के दोहराव को रोकने और पुनरावर्ती कार्यक्रमों के निर्माण के लिए अनुमति देता है। यहां फाइबोनैचि संख्याओं (मूल रूप से सबसे धीमी एल्गोरिदम) उत्पन्न करने के लिए एक पुनरावर्ती सबरूटीन के साथ एक कार्यक्रम है:
# recursively calculate the 10th Fibonacci number
call display = fib(10).sum;
sub fib(cur,sum) {
if (cur <= 2) {
sum = 1;
return;
}
cur--;
call sum = fib(cur).sum;
cur--;
call sum += fib(cur).sum;
}
एक सबरूटीन को कीवर्ड के साथ घोषित किया जाता है sub
, और एक सबरूटीन को प्रोग्राम के अंदर कहीं भी रखा जा सकता है। प्रत्येक सबरूटीन में कई स्थानीय चर हो सकते हैं, जिन्हें इसके तर्कों की सूची के भाग के रूप में घोषित किया गया है। इन तर्कों को डिफ़ॉल्ट मान भी दिया जा सकता है।
पुनरावर्ती कॉल को संभालने के लिए, सबरूटीन के स्थानीय चर को स्टैक पर संग्रहीत किया जाता है। रैम में अंतिम स्टैटिक वेरिएबल कॉल स्टैक पॉइंटर है, और इसके बाद की सभी मेमोरी कॉल स्टैक के रूप में कार्य करती है। जब एक सबरूटीन कहा जाता है, तो उसने कॉल स्टैक पर एक नया फ्रेम बनाया, जिसमें सभी स्थानीय चर और साथ ही रिटर्न (ROM) पता शामिल है। कार्यक्रम में प्रत्येक सबरूटीन को एक सूचक के रूप में सेवा करने के लिए एक एकल स्थिर रैम पता दिया जाता है। यह पॉइंटर कॉल स्टैक में सबरूटीन के "वर्तमान" कॉल का स्थान देता है। एक स्थानीय चर का संदर्भ इस स्थिर सूचक के मान का उपयोग करके किया जाता है और उस विशेष स्थानीय चर का पता देने के लिए एक ऑफसेट का उपयोग किया जाता है। इसके अलावा कॉल स्टैक में स्थिर सूचक का पिछला मान है। यहाँ'
RAM map:
0: pc
1: display
2: scratch0
3: fib
4: scratch1
5: scratch2
6: scratch3
7: call
fib map:
0: return
1: previous_call
2: cur
3: sum
एक चीज जो सबरूटीन्स के बारे में दिलचस्प है, वह यह है कि वे किसी विशेष मूल्य को वापस नहीं करते हैं। बल्कि, सबरूटीन के सभी स्थानीय वेरिएबल्स को सबरूटीन के प्रदर्शन के बाद पढ़ा जा सकता है, इसलिए सबरूटिन कॉल से विभिन्न प्रकार के डेटा निकाले जा सकते हैं। यह सबरूटीन की उस विशिष्ट कॉल के लिए पॉइंटर को संचय करके पूरा किया जाता है, जिसे तब (हाल ही में-डीलॉलेटेड) स्टैक फ्रेम के भीतर से किसी भी स्थानीय चर को पुनर्प्राप्त करने के लिए उपयोग किया जा सकता है।
सबरूटिन को कॉल करने के कई तरीके हैं, सभी call
कीवर्ड का उपयोग कर रहे हैं :
call fib(10); # subroutine is executed, no return vaue is stored
call pointer = fib(10); # execute subroutine and return a pointer
display = pointer.sum; # access a local variable and assign it to a global variable
call display = fib(10).sum; # immediately store a return value
call display += fib(10).sum; # other types of assignment operators can also be used with a return value
किसी भी संख्या के मान को सबरूटीन कॉल के लिए तर्क के रूप में दिया जा सकता है। प्रदान किया गया कोई भी तर्क उसके डिफ़ॉल्ट मान के साथ भरा जाएगा, यदि कोई हो। ऐसा तर्क जो प्रदान नहीं किया गया है और जिसका कोई डिफ़ॉल्ट मान नहीं है (निर्देशों / समय को बचाने के लिए) को मंजूरी नहीं दी गई है, इसलिए संभवत: सबरूटीन की शुरुआत में किसी भी मूल्य पर ले सकता है।
प्वाइंटर्स सबरूटीन के कई लोकल वैरिएबल को एक्सेस करने का एक तरीका है, हालांकि यह ध्यान रखना जरूरी है कि प्वाइंटर केवल अस्थायी है: एक और सबरूटीन कॉल करने पर प्वाइंटर प्वाइंट तबाह हो जाएगा।
डिबगिंग लेबल
{...}
Cogol प्रोग्राम में कोई भी कोड ब्लॉक बहु-शब्द वर्णनात्मक लेबल से पहले हो सकता है। यह लेबल संकलित असेंबली कोड में एक टिप्पणी के रूप में जुड़ा हुआ है, और डिबगिंग के लिए बहुत उपयोगी हो सकता है क्योंकि इससे कोड के विशिष्ट विखंडनों का पता लगाना आसान हो जाता है।
शाखा विलंब स्लॉट अनुकूलन
संकलित कोड की गति में सुधार करने के लिए, Cogol संकलक QFTASM कोड पर अंतिम पास के रूप में कुछ वास्तव में बुनियादी देरी स्लॉट अनुकूलन करता है। खाली शाखा विलंब स्लॉट के साथ किसी भी बिना शर्त के कूदने के लिए, देरी स्लॉट को जंप गंतव्य पर पहले निर्देश द्वारा भरा जा सकता है, और अगले निर्देश को इंगित करने के लिए जंप गंतव्य एक से बढ़ जाता है। यह आमतौर पर एक चक्र को बचाता है हर बार बिना शर्त कूद किया जाता है।
टोगो में टेट्रिस कोड लिखना
अंतिम टेट्रिस कार्यक्रम कोगोल में लिखा गया था, और स्रोत कोड यहां उपलब्ध है । संकलित QFTASM कोड यहां उपलब्ध है । सुविधा के लिए, एक पर्मलिंक यहां प्रदान किया गया है: QFTASM में टेट्रिस । चूंकि लक्ष्य असेंबली कोड (कोगोल कोड नहीं) को गोल्फ करना था, इसलिए परिणामी कोगोल कोड अनिच्छुक है। कार्यक्रम के कई हिस्से सामान्य रूप से सबरूटीन में स्थित होंगे, लेकिन वे सबरूटीन्स वास्तव में काफी कम थे कि कोड को डुप्लिकेट करने से निर्देशों को बचाया गयाcall
बयान। अंतिम कोड में मुख्य कोड के अलावा केवल एक सबरूटीन होता है। इसके अतिरिक्त, कई सरणियों को हटा दिया गया था और या तो व्यक्तिगत चर की एक समान-लंबी सूची के साथ या कार्यक्रम में बहुत सारे हार्ड-कोडित संख्याओं के साथ बदल दिया गया था। अंतिम संकलित QFTASM कोड 300 निर्देशों के तहत है, हालांकि यह केवल Cogol स्रोत की तुलना में थोड़ा लंबा है।