用復(fù)古電腦程序 Toy CPU 學(xué)習(xí)低級編程
我兼職教授大學(xué)課程,包括一個對所有專業(yè)開放的一般計算機(jī)主題的課程。這是一門入門課程,向?qū)W生講授技術(shù)是如何運(yùn)作的,以消除圍繞計算的神秘感。
雖然不是計算機(jī)科學(xué)課程,但這門課的一個部分涉及計算機(jī)編程。我通常用非常抽象的術(shù)語談?wù)摼幊蹋圆粫屄牨娐牪欢5墙衲辏蚁胱屛业膶W(xué)生以 “老派” 的方式做一些需要 “動手” 的編程。同時,我又想保持簡單,以便讓每個人都能跟上。
我喜歡將我的課程結(jié)構(gòu)化,以顯示你是如何從 “那里” 到 “這里” 的。理想情況下,我會讓我的學(xué)生學(xué)習(xí)如何編寫一個簡單的程序。然后,我將從這里開始,展示現(xiàn)代編程是如何讓開發(fā)人員創(chuàng)建更復(fù)雜的程序的。我決定嘗試一種非常規(guī)的方法 —— 教學(xué)生學(xué)習(xí)終極的低級別編程語言:機(jī)器語言。
機(jī)器語言編程
早期的個人電腦如 Apple II(1977 年)、TRS-80(1977 年)和 IBM PC(1981 年)讓用戶用鍵盤輸入程序,并在屏幕上顯示結(jié)果。但計算機(jī)并不總是帶有屏幕和鍵盤。
Altair 8800 和 IMSAI 8080(均為 1975 年制造)要求用戶使用面板上的 “開關(guān)和燈” 輸入程序。你可以用機(jī)器語言輸入指令,使用一組開關(guān),機(jī)器會點(diǎn)亮 LED 燈以代表每個二進(jìn)制指令的 1 和 0。
Altair 8800 計算機(jī)的圖片
對這些早期機(jī)器進(jìn)行編程,需要了解被稱為 “操作碼opcode” (操作代碼的簡稱)的機(jī)器語言指令,以執(zhí)行基本操作,如將兩個數(shù)字相加或?qū)⒁粋€值存儲到計算機(jī)的存儲器中。我想向我的學(xué)生展示程序員是如何通過開關(guān)和燈,手工輸入一系列指令和內(nèi)存地址的。
然而,在這門課上,使用實際的 Altair 8800 就有點(diǎn)太復(fù)雜了。我需要一些簡單的、任何初級水平的學(xué)生都能掌握的東西。理想情況下,我希望能找到一個簡單的 “業(yè)余” 復(fù)古計算機(jī),其工作原理與 Altair 8800 相似,但我無法找到一個價格低于 100 美元的合適的 “類似 Altair” 的設(shè)備。我找到了幾個 “Altair” 軟件模擬器,但它們忠實地再現(xiàn)了 Altair 8800 的操作碼,這對我的需求來說太過沉重。
我決定編寫我自己的 “教育” 復(fù)古計算機(jī)。我稱它為 “Toy CPU”。你可以在我的 ??GitHub 代碼庫?? 上找到它,包括幾個可以運(yùn)行的版本。第一版是一個實驗性的原型,運(yùn)行在 ??FreeDOS?? 上。第二版是一個更新的原型,在 Linux 上用 ??ncurses?? 運(yùn)行。版本 3 是一個 FreeDOS 程序,在圖形模式下運(yùn)行。
Toy CPU 的編程
Toy CPU 是一個非常簡單的復(fù)古計算機(jī)。它只有 256 字節(jié)的內(nèi)存和一個最小化的指令集,其目的是在復(fù)制 “開關(guān)和燈” 編程模式的同時保持簡單化。它的界面模仿 Altair 8800,有一系列 8 個 LED 燈,分別代表計數(shù)器(程序的 “行號”)、指令、累積器(用于臨時數(shù)據(jù)的內(nèi)部存儲器)和狀態(tài)。
當(dāng)你啟動 Toy CPU 時,它通過清除內(nèi)存來模擬 “啟動”。當(dāng)它啟動時,它也會在屏幕右下方的狀態(tài)燈中顯示 “INI”(初始化)。“PWR”(電源)燈亮表示 Toy CPU 已被打開。
Toy CPU 的啟動屏幕
當(dāng) Toy CPU 準(zhǔn)備好讓你進(jìn)入一個程序時,它通過狀態(tài)燈指示 “INP”(“輸入”模式),并讓你從程序的計數(shù)器 0 開始。Toy CPU 的程序總是從計數(shù)器 0 開始。
在 “輸入” 模式下,用上下方向鍵顯示不同的程序計數(shù)器,按回車鍵編輯當(dāng)前計數(shù)器上的指令。當(dāng)你進(jìn)入 “編輯” 模式時,Toy CPU 的狀態(tài)燈上會顯示 “EDT”(“編輯” 模式)。
Toy CPU 編輯屏幕
Toy CPU 有一張速查表,被 “貼” 在顯示屏的前面。它列出了 Toy CPU 可以處理的不同操作碼。
- ?
?00000000?
?(??STOP?
?):停止程序執(zhí)行。 - ?
?00000001?
?(??RIGHT?
?):將累加器中的位向右移動一個位置。值??00000010?
? 變成??00000001?
?,??00000001?
? 變成??00000000?
?。 - ?
?00000010?
?(??LEFT?
?):將累加器中的位向左移動一個位置。值??01000000?
? 變成??10000000?
?,??10000000?
? 變成??00000000?
?。 - ?
?00001111?
?(??NOT?
?):對累加器進(jìn)行二進(jìn)制非操作。例如,值??10001000?
? 變成??01110111?
?。 - ?
?00010001?
?(??AND?
?):對累加器用存儲在某一地址的值進(jìn)行二進(jìn)制與操作。該地址被存儲在下一個計數(shù)器中。 - ?
?00010010?
?(??OR?
?):對累積器用存儲在某一地址的值進(jìn)行二進(jìn)制或運(yùn)算。 - ?
?00010011?
?(??XOR?
?):對累加器用存儲在某一地址的值進(jìn)行二進(jìn)制異或運(yùn)算。 - ?
?00010100?
?(??LOAD?
?):將一個地址的值加載(復(fù)制)到累加器中。 - ?
?00010101?
?(??STORE?
?): 存儲(復(fù)制)累加器中的值到一個地址。 - ?
?00010110?
?(??ADD?
?):將存儲在某一地址的數(shù)值加入到累加器中。 - ?
?00010111?
?(??SUB?
?):從累積器中減去儲存在某一地址的數(shù)值。 - ?
?00011000?
?(??GOTO?
?):轉(zhuǎn)到(跳到)一個計數(shù)器地址。 - ?
?00011001?
?(??IFZERO?
?):如果累加器為零,轉(zhuǎn)到(跳到)一個計數(shù)器地址。 - ?
?10000000?
?(??NOP?
?):空操作,可以安全地忽略。
當(dāng)處于 “編輯” 模式時,使用左右方向鍵選擇操作碼中的一個位,然后按空格鍵在關(guān)閉(0)和開啟(1)之間翻轉(zhuǎn)數(shù)值。當(dāng)你完成編輯后,按回車鍵回到 “輸入” 模式。
Toy CPU 輸入模式屏幕
一個示例程序
我想通過輸入一個簡短的程序來探索 Toy CPU,將兩個數(shù)值相加,并將結(jié)果存儲在 Toy CPU 的內(nèi)存中。實際上,這執(zhí)行的是算術(shù)運(yùn)算 ??A+B=C?
?。要創(chuàng)建這個程序,你只需要幾個操作碼:
- ?
?00010100?
?(??LOAD?
?) - ?
?00010110?
?(??ADD?
?) - ?
?00010101?
?(??STORE?
?) - ?
?00000000?
?(??STOP?
?)
??LOAD?
?、??ADD?
? 和 ??STORE?
? 指令需要一個內(nèi)存地址,這個地址總是在下一個計數(shù)器的位置。例如,程序的前兩條指令是:
計數(shù)器 0 中的指令是 ??LOAD?
? 操作,計數(shù)器 1 中的值是你存儲某個值的內(nèi)存地址。這兩條指令一起將內(nèi)存中的數(shù)值復(fù)制到 Toy CPU 的累加器中,在那里你可以對該數(shù)值進(jìn)行操作。
將一個數(shù)字 ??A?
? 裝入累加器后,你需要將數(shù)值 ??B?
? 加到它上面。你可以用這兩條指令來做:
假設(shè)你把值 ??1?
?(??A?
?)裝入累加器,然后把值 ??3?
?(??B?
?)加到它上面。現(xiàn)在累加器的值是 ??4?
?。現(xiàn)在你需要用這兩條指令把數(shù)值 ??4?
? 復(fù)制到另一個內(nèi)存地址(??C?
?):
把這兩個值加在一起后,現(xiàn)在可以用這條指令結(jié)束程序:
計數(shù)器 6 之后的任何指令都可以供程序作為存儲內(nèi)存使用。這意味著你可以用計數(shù)器 7 的內(nèi)存來儲存值 ??A?
?,計數(shù)器 8 的內(nèi)存來儲存值 ??B?
? ,計數(shù)器 9 的內(nèi)存來儲存值 ??C?
?。你需要將這些分別輸入到 Toy CPU 中:
在弄清了所有指令和 ??A?
?、??B?
? 和 ??C?
? 的內(nèi)存位置后,現(xiàn)在可以將完整的程序輸入到 Toy CPU 中。這個程序?qū)?shù)值 1 和 3 相加,得到 4:
要運(yùn)行程序,在 “輸入” 模式下按下 ??R?
? 鍵。Toy CPU 將在狀態(tài)燈中顯示 “RUN”(“運(yùn)行” 模式),并從計數(shù)器 0 開始執(zhí)行你的程序。
Toy CPU 有一個明顯的延遲,所以你可以看到它執(zhí)行程序中的每一步。隨著程序的進(jìn)行,你應(yīng)該看到計數(shù)器從 ??00000000?
?(0)移動到 ??00000110?
?(6)。在計數(shù)器 1 之后,程序從內(nèi)存位置 7 加載數(shù)值 ??1?
?,累積器更新為 ??00000001?
?(1)。在計數(shù)器 3 之后,程序?qū)⒓訑?shù)值 ??3?
?,并更新累加器顯示 ??00000100?
?(4)。累加器將保持這種狀態(tài),直到程序在計數(shù)器 5 之后將數(shù)值存入內(nèi)存位置 9,然后在計數(shù)器 6 結(jié)束。
在運(yùn)行模式下的 Toy CPU
探索機(jī)器語言編程
你可以使用 Toy CPU 來創(chuàng)建其他程序,并進(jìn)一步探索機(jī)器語言編程。通過用機(jī)器語言編寫這些程序來測試你的創(chuàng)造力。
一個在累積器上閃燈的程序
你能點(diǎn)亮累加器上的右四位,然后是左四位,然后是所有的位嗎?你可以用兩種方法之一來寫這個程序。
一種直接的方法是,從不同的內(nèi)存地址加載三個數(shù)值,像這樣:
寫這個程序的另一種方法是嘗試使用 ??NOT?
?? 和 ??OR?
? 二進(jìn)制操作。這樣可以得到一個更小的程序:
從一個數(shù)字開始倒數(shù)
你可以把 Toy CPU 作為一個倒數(shù)計時器。這個程序行使 ??IFZERO?
? 測試,只有當(dāng)累加器為零時,程序才會跳轉(zhuǎn)到一個新的計數(shù)器:
Toy CPU 是學(xué)習(xí)機(jī)器語言的一個好方法。我在入門課程中使用了 Toy CPU,學(xué)生們說他們發(fā)現(xiàn)寫第一個程序很困難,但寫下一個程序就容易多了。學(xué)生們還表示,用這種方式編寫程序其實很有趣,他們學(xué)到了很多關(guān)于計算機(jī)實際工作的知識。Toy CPU 既具有教育性,也很有趣味性!