1. 程式人生 > >編碼的奧祕:自動操作

編碼的奧祕:自動操作

轉自:《編碼的奧祕》      第十七章

 

 

           人類是非常富於創造性而且是十分勤勉的,但是,人類在本質上也是十分懶惰的。非常明顯,人類並不願意去工作,這種對工作的反感導致人們用大量的時間來設計和製造可以把工作日縮短到幾分鐘的裝置。幻想使人感到興奮,甚至遠比我們所看到新奇的事物更令人興奮得多。

            當然不會在這裡介紹自動割草機的設計。本章將通過設計更精密的機器,使加減法運算更加自動化,這聽起來也許有些不可思議。本章最後設計出的機器將具有廣泛的用途,它實際上可以解決任何利用加減法的問題,這些問題的範圍太大了。

           當然,由於精密機器越來越複雜,因此有些部分可能會很粗糙。不過如果你略過了某些困難的細節,沒有人會責備你。有時,你可能會不耐煩並且發誓再也不會因為一個數學問題而去尋求電或機械的幫助。不過請耐心堅持到底,因為本章最後將發明一個叫作計算機的機器。

           我們曾在第 1 4章見過一個加法器。它有一個 8位鎖存器,累加由 8個開關輸入的數的和:

          前面曾講過, 8位鎖存器用觸發器來儲存 8位資料。使用這個裝置時,必須首先按下清零開關使鎖存器的儲存內容清零,然後用開關來輸入第一個數字。加法器簡單地把這個數字與鎖存器輸出的零相加,因此其結果就是你剛輸入的數字。按下相加開關可在鎖存器中儲存該數並且通過燈泡顯示出來。現在從開關上輸入第二個數,加法器把這個數與儲存在鎖存器中的數相加,再按下相加開關把總和儲存在鎖存器中並通過燈泡顯示出來。通過這種方法,你可以加上一串數字並顯示出運算總和。當然,其中存在的一個侷限是 8個燈泡不能顯示總和超過2 5 5的數。

           第1 4章介紹該電路的時候,只講到一種鎖存器,它是電平觸發的。在電平觸發的鎖存器中,時鐘輸入端必須先置 1 然後回到 0,才能使鎖存器儲存資料。當時鍾訊號等於 1 時,鎖存器的資料輸入可以改變,這種改變將會影響所儲存的資料輸出。第 1 4章的後面又介紹了邊沿觸發的鎖存器,這種鎖存器在時鐘輸入從 0變化到 1 的瞬間儲存資料。由於邊沿觸發的鎖存器易於使用,所以假定本章用到的鎖存器為邊沿觸發的鎖存器。

           用於累加數字的鎖存器叫作累加器,本章後面將會看到累加器並非僅僅進行簡單的累加。累加器通常是一個鎖存器,儲存第一個數字,然後該數字又加上或減去另一個數字。

           上面這個加法機存在的最大問題已經相當明顯:如果想把 1 0 0個二進位制數加起來,你就得坐在加法機前耐著性子輸入每一個數字並累加起來。當你完成時,卻發現有兩個數字是錯誤的,你只好又重複全部的工作。

           不過,也可能並非如此。上一章用了差不多 5 0 0萬個繼電器來構造一個 6 4 K B的R A M陣列。另外,我們還連線了一個控制面板,用來閉合接管開關接通線路,並使用開關進行 R A M陣列的寫入和讀出。

          

          如果你向 R A M陣列中輸入 1 0 0個二進位制數字,而不是直接輸入到加法機中,那麼進行資料修改會容易得多。

          現在我們面臨著一個挑戰,即如何將R A M陣列連到累加器上。顯而易見, R A M的資料輸出訊號應該代替累加器的開關組。但是,用一個 1 6位的計數器(正如在第 1 4章構造的)就可以控制R A M陣列的地址訊號。在下面這個電路中,連到 RAM 的資料輸入訊號和寫入訊號可以不要:

         

         當然這並非已經發明的最容易操作的計算裝置。在使用之前,必須先閉合清零開關,以清除鎖存器的內容並把 1 6位計數器的輸出置為 0 0 0 0 h,接著閉合 R A M控制面板上的接管開關。你可以從 R A M地址的 0 0 0 0 h處開始輸入一組想要加的 8位數,如果有 1 0 0個數,則它們儲存在從0 0 0 0 h~ 0 0 6 3 h的地址中(也可以把 R A M陣列中沒有用到的單元都設定為 0 0 h)。然後斷開R A M控制面板上的接管開關(這樣控制面板不會再對 R A M陣列起控制作用了) ,並斷開清零開關。這時,你就只需坐著看燈泡的亮滅變化了。

         其工作情況為:當清零開關第一次斷開時, R A M陣列的地址輸入為 0 0 0 0 h,儲存在 R A M陣列當前地址的 8位數是加法器的輸入。由於鎖存器也清零,所以加法器的另 8位輸入為 0 0 h。

         振盪器提供時鐘訊號—一個在 0和1 之間迅速交替變化的訊號。在清零開關斷開後,當時鍾由 0變為1 時,將同時發生兩個事件:鎖存器儲存來自加法器的結果;同時, 1 6位計數器加 1 ,指向 R A M陣列的下一個地址。在清零開關斷開後,當時鍾第一次由 0變為 1 時,鎖存器儲存第一個數,同時,計數器增加到 0 0 0 1 h;當時鍾第二次由 0變為1 時,鎖存器儲存第一個數與第二個數之和,同時計數器增加到 0 0 0 2 h;依此類推。

         當然,這裡先做了一些假設,首要一點,振盪器需慢到允許電路的其餘部分可以工作。對於每次時鐘振盪,在加法器輸出端顯示有效和之前,許多繼電器必須觸發其他繼電器。

         這種電路有一個問題,即沒有辦法讓它停止。到一定時候,燈泡會停止閃動,因為 R A M陣列中的其餘數都為 0 0 h。這時,你可以看到二進位制和。但當計數器最終到達 F F F F h時,它又會翻到 0 0 0 0 h(就像汽車裡程表),這時自動加法器又會開始把這些數加到已經計算過的和中。

         這種加法機還有一個問題:它只能用於加法,並且只能加 8位數。不僅在 R A M陣列中的每個數不能超過 2 5 5,而且其總和也不能超過 2 5 5。這種加法器也沒有辦法進行減法運算。雖然可以用 2的補碼錶示負數,但是在這種情況下,加法器只能處理- 1 2 8~1 2 7之間的數字。讓它處理更大數字(例如, 1 6位數)的一種顯而易見的方法就是使 R A M陣列、加法器和鎖存器的
寬度加倍,同時再提供 8個燈泡。不過你可能不太願意做這種投資。

         當然,要不是我們最終要去解決這些問題,這兒是不會提到這些問題的。不過我們首先想談的卻是另外一個問題。如果不是要把 1 0 0個數加成一個數,會怎麼樣?如果只想用自動加法器把 5 0對數字加成 5 0個不同的結果又會怎麼樣?也許你希望有一個萬能的機器來累加多對數字、 1 0個數字或 1 0 0個數字,並且希望所有的結果都可方便地使用。

          前面提到的自動加法器在與鎖存器相連線的一組燈泡上顯示出其相加結果。對於把 5 0對數字加成 5 0個不同的和來說,這種方法並不好。你可能希望把結果存回 R A M陣列中,然後,在方便的時候用 R A M控制面板來檢查結果。控制面板上有專門為此目的而設計的燈泡。

          這意味著連線在鎖存器上的燈泡可以去掉。不過,鎖存器的輸出端必須連線到 R A M陣列的資料輸入端上,以便於和可以寫入到 R A M中:

        上圖中省略了自動加法器的其餘部分,特別是振盪器和清零開關,因為不再需要顯著標出計數器和鎖存器的清零和時鐘輸入來源。此外,既然我們已經充分利用了 R A M的資料輸入端,就需要有一種方法來控制 R A M的寫入訊號。

        我們不去考慮這個電路能否工作,而把重點放在需要解決的問題上。當前需要解決的問題是能配置一個自動加法器,它不會僅用來累加一串數字。我們希望能隨心所欲地確定累加多少數字、在 R A M中儲存多少不同的結果以供日後檢查。

       例如,假設我們希望先把三個數字加在一起,然後把另兩個數字加在一起,最後再把另外三個數加在一起。我們可能會將這些數字儲存在從地址 0 0 0 0 h開始的 R A M陣列中,儲存器的內容如下所示:

       這是本書第 1 6章所說明的內容。方格里是儲存單元中的內容,儲存器的每一個位元組在一個方格中。方格的地址在方格左面,並非每一個地址都要表示出來,儲存器的地址是連續的,因而可以算出某個方格的地址。方格的右側是關於這個儲存單元的註釋,它們表示出我們希望自動加法器在這些空格中儲存三個結果。 (雖然這些方格是空的,但儲存單元並非空的。儲存單元中總有一些東西,即使只是隨機數,但此時它不是有用的數。 )

        現在可以試一下十六進位制算術運算並且把結果存到方格中,但這並不是此項試驗的要點,我們想讓自動加法器來做一些額外的工作。

         不是讓自動加法器只做一件事情—在最初的加法器中,只是把 R A M地址中的內容加到稱為累加器的 8位鎖存器中—實際上是讓它做四件不同的事。要做加法,需先從儲存器中傳送一個位元組到累加器中,這個操作叫作 L o a d(裝載) 。第二項所要執行的操作是把儲存器中的一個位元組加 ( A d d )到累加器中。第三項是從累加器中取出結果,儲存 ( S t o r e )到儲存器中。最後,需要有一些方法使自動加法器停止 ( H a l t )工作。

         詳細說來,讓自動加法器所做的工作如下所示:

         • 把地址0 0 0 0 h中的數裝載到累加器中
         • 把地址0 0 0 1 h中的數加到累加器中
         • 把地址0 0 0 2 h中的數加到累加器中
         • 把累加器中的數儲存到地址 0 0 0 3 h中
         • 把地址0 0 0 4 h中的數裝載到累加器中
         • 把地址0 0 0 5 h中的數加到累加器中
         • 把累加器中的數儲存到地址 0 0 0 6 h中
         • 把地址0 0 0 7 h中的數裝載到累加器中
         • 把地址0 0 0 8 h中的數加到累加器中
         • 把地址0 0 0 9 h中的數加到累加器中
         • 把累加器中的數儲存到地址 0 0 0 A h中
         • 停止自動加法器的工作

         注意,同最初的自動加法器一樣,儲存器的每個位元組的地址是連續的,開始處為 0 0 0 0 h。以前自動加法器只是簡單地把儲存器中相應地址的數加到累加器中。某些情況下,現在仍然需要這樣做,但有時我們也想直接把儲存器中的數裝載到累加器中或者把累加器中的數儲存到儲存器中。在所有事情都完成以後,我們還想讓自動加法器停下來以便檢查 R A M陣列中的內容。

          怎樣完成這些工作呢?只是簡單地鍵入一組數到 R A M中並期望自動加法器來正確操作是不可能的。對於 R A M中的每個數字,我們還需要一個數字程式碼來表示自動加法器所要做的工作:裝載,加,儲存或停止。

          也許最容易(但肯定不是最便宜)的方法是把這些程式碼儲存在一個完全獨立的 R A M陣列中。這第二個 RAM 陣列與最初的 R A M陣列同時被訪問,但它存放的不是要加的數,而是用來表明自動加法器將要對最初的 R A M陣列的相應地址進行某種操作的程式碼。這兩個 R A M陣列可以分別標為資料(最初的 R A M陣列)和程式碼(新的 R A M陣列):

           

          已經確認新的自動加法器能夠把“和”寫入到最初的 R A M陣列(標為資料),而要寫入新的R A M陣列(標為程式碼)則只能通過控制面板來進行。

          我們用 4個程式碼來表示自動加法器希望能實現的 4個操作。 4個程式碼可任意指定,下面為可能的一組程式碼:

          為了執行以上例子中提到的三組加法,需要用控制面板把下面這些數儲存到程式碼R A M陣列中:

           你可能想把這個 R A M陣列中的內容與存放累加資料的 R A M陣列的內容作一比較。你會發現程式碼 R A M中的每個程式碼或者對應於資料 R A M中一個要裝入或加到累加器的數,或者表示一個要存回到儲存器中的數。這樣的數字程式碼通常稱作指令碼或操作碼,它們“指示”電路執行某種“操作”。

           前面談到過,早期自動加法器的 8位鎖存器的輸出需要作為資料 R A M陣列的輸入,這就是“儲存”指令的功能。另外還需要一個改變:以前, 8位加法器的輸出是作為鎖存器的輸入,但現在為了實現“裝載”指令,資料 R A M的輸出有時候也要作為 8位鎖存器的輸入,這種改變需要 2 - 1 資料選擇器。改進的自動加法器如下圖:

           上圖中少了一些東西,但它顯示了各種元件間的 8 位資料通路,一個 1 6位計數器為 2個R A M陣列提供地址。通常,資料 RAM 陣列輸出到 8位加法器上執行加法指令。 8位鎖存器的輸入可能是資料 R A M陣列的輸出也可能是加法器的輸出,這需要 2 - 1 選擇器來選擇。通常,鎖存器的輸出又流回到加法器,但對“儲存”指令而言,它又作為資料 R A M陣列的輸入。

            上圖中缺少的是控制這些元件的訊號,統稱為控制訊號。它們包括1 6位計數器的時鐘(C l k)和清零( C l r )輸入, 8位鎖存器的 C l k和C l r輸入,資料 R A M陣列的寫入 ( W )輸入以及 2 - 1 選擇器的選擇( S )輸入。其中有一些訊號明顯基於程式碼 RAM 陣列的輸出,例如,若程式碼 RAM 陣列的輸出表示裝載指令,則 2 - 1選擇器的 S輸入必須為 0(選擇資料 R A M陣列的輸出) 。僅當操作碼為儲存指令時,資料R A M陣列的W輸入才為1。這些控制訊號可以由邏輯閘的各種組合來產生。

             利用最小數量的附加硬體和新增的操作碼,也能讓這個電路從累加器中減去一個數。第 1步是擴充操作碼錶:

             加法和減法只通過操作碼的最低有效位來區分。若操作碼為 2 1 h,除了在資料 R A M陣列的輸出資料輸入到加法器之前取反並且加法器的進位輸入置 1 外,電路所做的幾乎與電路執行加法指令所做的完全相同。在下面這個改進的有一個反相器的自動加法器裡, C0訊號可以完成這兩項任務:

          現在假設把 5 6 h和 2 A h相加再減去 3 8 h,可以按下圖所顯示的儲存在兩個 R A M陣列中的操作碼和資料進行計算:

 

           裝載操作完成後,累加器中的數為 5 6 h。加法操作完成後,累加器中的數為 5 6 h加上2 A h的和,即 8 0 h。減法操作使資料 R A M陣列的下一個數( 3 8 h)按位取反,變為 C 7 h。加法器的進位輸入置為 1 時,取反的數 C 7 h與8 0 h相加:

           其結果為 4 8 h。 (按十進位制, 8 6加4 2減5 6等於7 2。 )

           還有一個未找到適當解決方法的問題是加法器及連到其上的所有部件的寬度只有 8位。以往唯一的解決方法就是連線兩個 8位加法器(或其他的兩個部件),形成1 6位的裝置。

            但也有更便宜的解決方法。假設要加兩個 1 6位數,例如:


           這種1 6位加法同單獨加最右邊的位元組(通常稱作低位元組):

           然後再加最左邊的位元組,即高位元組

            得到的結果一樣,為 9 9 D 7 h。因此,如果像這樣把兩個 1 6位數儲存在儲存器中:

            結果中的 D 7 h存在地址 0 0 0 2 h中, 9 9 h存在地址 0 0 0 5 h中。

           當然,並非所有的數都這樣計算,對上例中的數是這樣計算。若兩個 1 6位數7 6 A B h和2 3 6 C h相加會怎麼樣呢?在這種情況下, 2個低位元組數相加的結果將產生一個進位:


          這個進位必須加到 2個高位元組數的和中:
          最後的結果為 9 A 1 7 h。

          可以增強自動加法器的電路功能以正確進行 1 6位數的加法嗎?當然可以。需要做的就是儲存低位元組數相加結果的進位,然後把該進位作為高位元組數相加的進位輸入。如何儲存 1 位呢?當然是用 1 位鎖存器。這時,該鎖存器稱為進位鎖存器。

          為了使用進位鎖存器,需要有另一個操作碼,稱作“進位加”( Add with Carry)。在進行8位數加法運算時,使用的是常規“加法”指令。加法器的進位輸入為 0,加法器的進位輸出鎖存在進位鎖存器中(儘管根本不必用到)。

           在進行 1 6位數加法運算時,仍然使用常規“加法”指令來進行低位元組加法運算。加法器的進位輸入為 0,其進位輸出鎖存到進位鎖存器中。要進行高位元組加法運算就要使用新的“進位加”指令。這時,兩個數相加使用進位鎖存器的輸出作為加法器的進位輸入。如果低位元組加法有進位,則其進位可用在第二次運算中;如果無進位,則進位鎖存器的輸出為 0。

           如果進行 1 6位數減法運算,還需要一個新指令,稱為“借位減”( Subtract with Borrow)。通常,減法操作需要使減數取反且把加法器的進位輸入置為 1 。因為進位通常不是 1 ,所以往往被忽略。在進行 1 6位數減法運算時,進位輸出應儲存在進位鎖存器中。高位元組相減時,進位鎖存器的結果應作為加法器的進位輸入。

            加上新的“進位加”和“借位減”操作,共有 7個操作碼:
         在減法和借位減法運算中,需要把送往加法器的數取反。加法器的進位輸出作為進位鎖存器的輸入。無論何時執行加法、減法、進位加法和借位減法操作,進位鎖存器都被同步。當進行減法操作,或進位鎖存器的資料輸出為 1 並且執行進位加法或者借位減法指令時, 8位加法器的進位輸入被置為 1 。

         記住,只有上一次的加法或者進位加法指令產生進位輸出時,進位加法操作才會使 8位加法器的進位輸入為 1 。任何時候進行多位元組數加法運算時,不管是否必要,都應該用進位加法指令計算。為正確編碼前面列出的 1 6位加法,可用如下所示方法:
        不管是什麼樣的數,該方法都能正確工作。

        有了這兩個新的操作碼,極大地擴充套件了機器處理的範圍,使其不再只侷限於進行 8位數加法。重複使用進位加法指令,能進行 1 6位數、 2 4位數、 3 2位數、 4 0位數等更多位數的加法運算。假設要把 3 2位數7 A 8 9 2 B C D h與 6 5 A 8 7 2 F F h相加,則需要一個加法指令及三個進位加法指令:

         
         當然,把這些數存放到儲存器中並非真的很好。這不僅要用開關來表示二進位制數,而且數在儲存器中的地址也並不連續。例如, 7 A 8 9 2 B C D h從最低有效位元組開始,每個位元組分別存入儲存器地址 0 0 0 0 h、 0 0 0 3 h、 0 0 0 6 h及0 0 0 9 h中。為了得到最終結果,還必須檢查地址 0 0 0 2 h、0 0 0 5 h、 0 0 0 8 h及0 0 0 B h中的數。

         此外,當前設計的自動加法器不允許在隨後的計算中重複利用計算結果。假設要把 3 個8位數加起來,然後再在和中減去一個 8位數,並且儲存結果。這需要一次裝載操作、兩次加法操作、一次減法和一次儲存操作。但如果想從原先的和中減去另外一個數會怎麼樣呢?那個和是不能訪問的,每次用到它時都要重新計算。

          原因在於我們已經建造了一個自動加法器,其中的程式碼 R A M和資料 R A M陣列同時、順序地從0 0 0 0 h開始定址。程式碼 R A M中的每條指令對應於資料 R A M中相同地址的儲存單元。一旦“儲存”指令使某個資料儲存在資料 R A M中,這個數就不能再被裝載到累加器中。

          為了解決這個問題,要對自動加法器做一個基本的及大的改變。雖說剛開始看上去會異常複雜,但很快你就會看到一扇通向靈活性的大門打開了。

          讓我們開始吧,目前我們已經有了 7個操作碼:

          每個操作碼在儲存器中佔 1 個位元組。除了“停止”程式碼外,現在希望每條指令在儲存器中佔 3個位元組,其中第一個位元組為程式碼本身,後兩個位元組存放一個 1 6位的儲存器單元地址。對於裝載指令來說,其地址指明資料在資料 R A M陣列中的儲存單元,該儲存單元存放要裝載到累加器中的位元組;對於加法、減法、進位加法和借位減法指令來說,地址指明要從累加器中加上或者減去的位元組的儲存單元;對於儲存指令來說,地址指明累加器中的內容將要儲存的儲存單元。

           例如,當前自動加法器所能做的最簡單的工作就是加兩個數。要完成這項工作,可以按照下面的方法來設定程式碼 R A M陣列 和資料 R A M陣列:

           在改進的自動加法器中,每條指令(除了“停止”)需要3個位元組:
          每條指令(除了“停止”)後跟2個位元組,用來表示在資料 R A M陣列中的 1 6位地址。這三個地址碰巧為 0 0 0 0 h、 0 0 0 1 h和0 0 0 2 h,它們可以是任何其他地址。

           前面說明了如何使用加法和進位加法指令來相加一對 1 6位數—比如7 6 A B h和2 3 2 C h。必須把2個數的低位元組儲存在儲存器單元 0 0 0 0 h和0 0 0 1 h中,把2個高位元組儲存在 0 0 0 3 h和0 0 0 4 h中,其相加結果儲存在 0 0 0 2 h和0 0 0 5 h中。

          這樣,我們可以用更合理的方式來儲存兩個加數及其結果,這可能會儲存在以前從未用過的儲存區域:
         這6個儲存單元不必像圖中這樣連在一起,它們可分散在整個 64KB 資料 R A M陣列中的任何地方。為了把這些地址中的數相加,必須在程式碼 R A M陣列中按如下所示設定指令:

        可以看到儲存在地址 4 0 0 1 h和 4 0 0 3 h中的兩個低位元組首先相加,並把結果儲存在地址 4 0 0 5 h中。兩個高位元組(在地址 4 0 0 0 h和4 0 0 2 h中)利用進位加法進行相加,其結果儲存在地址 4 0 0 4 h中。如果去掉“停止”指令並向程式碼 R A M中加入更多指令,隨後的計算就可以簡單地通過儲存器地址來利用原先的數及它們的和。

         實現這種設計的關鍵就是把程式碼 R A M陣列中的資料輸出到3個8位鎖存器中,每個鎖存器儲存3位元組指令的一個位元組。第一個鎖存器儲存指令程式碼,第二個鎖存器儲存地址的高位元組,第三個鎖存器儲存地址的低位元組。第二和第三個鎖存器的輸出組成了資料 RAM 陣列的1 6位地址:

         從儲存器中取出指令的過程叫作取指令。在上述加法機中,每個指令長 3個位元組。因每次只能從儲存器中取出一個位元組,因此每次取指令需要 3個時鐘週期。此外,一個完整的指令週期需要四個時鐘週期。所有這些變化使得控制訊號變得更為複雜。

         機器響應指令程式碼執行一系列操作稱為執行指令,但這並不是說機器是有生命的東西,它也不是通過分析機器碼來決定做什麼。每一個機器碼用唯一的方式觸發各種控制訊號,使機器產生各種操作。

         注意,為了使上述加法機更為有用,我們已經放慢了它的速度。利用同樣的振盪器,它進行數字加法運算的速度只是本章列出的第一個自動加法器的 1 / 4 。這符合一個叫作TA N S TA A F L的工程原理, TA N S TA A F L的意思是“世界上沒有免費的午餐”。通常,機器在某一方面好一點兒,在另一些方面必然會差一些。

        如果不用繼電器來建造這樣一個機器,電路的大部分顯然只是兩個 64KB RAM陣列。確實,早就該省去這些元件,並且一開始就決定只用 1 K B 的儲存器。如果能保證儲存的所有東西都在地址 0 0 0 0 h~0 3 F F h之間,那麼用少於 6 4 k B的儲存器也能很好地解決問題。

         然而,你可能也不會太在意用到了兩個 R A M陣列。事實上,也確實不用。前面介紹過的兩個 R A M陣列—一個儲存程式碼,一個儲存資料—使得自動加法器的體系結構變得儘可能清晰、簡單。但既然已經決定每條指令佔 3個位元組—用第二和第三個位元組來表示資料的儲存地址—就不再需要有兩個獨立的 R A M陣列,程式碼和資料可儲存在同一個 R A M陣列中。

         為了實現這個目標,需要一個 2 - 1 選擇器來確定如何定址 R A M陣列。通常,像前面一樣,其地址來自 1 6位計數器。 R A M資料輸出仍然連線到用來鎖存指令程式碼及其 2位元組地址的三個鎖存器上,但它們的 1 6位地址是 2 - 1 選擇器的第二個輸入。在地址被鎖存後,選擇器允許被鎖存的地址作為 R A M陣列的地址輸入:

         我們已經取得了很大的進步。現在把指令和資料輸入到一個 R A M陣列中已成為可能。例如,下圖顯示出怎樣把兩個 8位數相加再減去第三個數:


          通常,指令開始於 0 0 0 0 h,這是因為復位後計數器從 0 0 0 0 h處開始訪問 R A M陣列。最後的停止指令儲存在地址0 0 0 C h處。可以把這3個數及其運算結果儲存在 R A M陣列中的任何位置(除了開始的1 3個位元組,因為這些儲存單元已經被指令佔用),因而我們選擇從0 0 1 0 h處開始儲存資料。

          現在假設你需要再加兩個數到結果中,你可以輸入一些新的指令來替換你剛輸入的所有指令,不過可能你並不想這樣做。你也許更願意在那些已有的指令末尾接著新的指令,但首先得用一個新的裝載指令來替換地址 000Ch 中的停止指令。此外,還需要兩個新的加法指令、一個儲存指令和一個新的停止指令。唯一的問題在於有一些資料儲存在地址 0010h 中,必須把這些資料移到更高的儲存地址中,並且修改那些涉及到這些儲存器地址的指令。

           想一想,把程式碼和資料混放在一個 R A M陣列中也許並不是一個迫切的問題,但可以肯定,這樣的問題遲早會到來,因此必須解決它。在這種情況下,可能你更願意做的就是從地址0 0 2 0 h處開始輸入新指令,從地址 0 0 3 0 h處開始輸入新資料:

          注意第一條裝載指令指向儲存單元 0 0 1 3 h,即第一次運算結果儲存的位置。

          因此現在有開始於0 0 0 0 h的一些指令、開始於0 0 1 0 h的一些資料、開始於0 0 2 0 h的另外一些指令以及開始於0 0 3 0 h的另外一些資料。我們想讓自動加法器從0 0 0 0 h處開始並執行所有的指令。

          我們必須從 0 0 0 C h處去掉停止指令,並用其他一些東西來替換它,但這樣就足夠了嗎?問題在於無論用什麼來替換停止指令都會被解釋為一個指令位元組,並且至此後儲存器中每隔 3個位元組—在0 0 0 F h、 0 0 1 2 h、 0 0 1 5 h、 0 0 1 8 h、 0 0 1 B h和0 0 1 E h處,位元組也會被解釋為一個指令位元組。如果其中一個正好是 11 h會怎樣呢?這是一個儲存指令。如果儲存指令後的兩個位元組剛好指向地址 0 0 2 3 h又會怎樣呢?機器會把累加器的內容寫入該地址中,但是該地址中已經包含有一些重要的東西。即使沒有諸如此類的事情發生,加法器從儲存器地址 0 0 1 E h的下一個地址中取得的指令位元組將在地址 0 0 2 1 h中,而不是 0 0 2 0 h中,而0 0 2 0 h卻正好是下一個指令的真實所在。

           我們是否都同意不把停止指令從 0 0 0 C h處移走,而期待最佳方案呢?

           不過,我們可用一個叫作 J u m p(轉移)的新指令替換它。現在把它加入到指令表中。
         通常,自動加法器順序定址 R A M陣列。轉移指令改變其定址模式,而從 R A M陣列的某個特定地址開始定址。這樣的命令有時也叫分支( b r a n c h)指令或者 g o t o指令,即“轉到另外一個地方”的意思。

         在前面的例子中,可用轉移指令來替換 0 0 0 C h中的停止指令:
        30h 就是轉移指令的程式碼,其下的 1 6位地址表示自動加法器要讀的下條指令的地址。

        因此,在前面的例子中,自動加法器仍從地址 0 0 0 0 h處開始,執行一條裝載、一條加法、一條減法和一條儲存指令,然後執行轉移指令,接著繼續從 0 0 2 0 h處執行一條裝載、兩條加法和一條儲存指令,最後執行停止指令。

         轉移指令影響 1 6位計數器。當自動加法器遇到轉移指令時,計數器被強制輸入緊隨轉移指令程式碼的新地址,這可以通過組成 1 6位計數器的邊沿觸發的 D型觸發器的預置( P r e)和清零( C l r )輸入來實現:

          
         前面曾講過,正常操作下,預置和清零輸入都應該為 0。但如果 Pre = 1 ,則 Q = 1 ;如果Clr = 1 ,則 Q = 0 。

         如果想裝載一個新值(稱作 A,代表地址)到單個觸發器中,可這樣連線:
         通常,置位訊號為 0。這時,觸發器的預置端為 0。除非復位訊號為 1 ,否則清零端也為 0。這樣觸發器可以不通過置位訊號就可以清零。當置位訊號為 1 時,若 A = 1 ,則 Pre = 1 且Clr =0;若A = 0,則 Pre = 0且Clr = 1 。這意味著 Q端的值設定為 A端的值。

          1 6位計數器的每一位都需要一個這樣的觸發器。一旦裝載一個特定的值,計數器將從那個值開始繼續計數。

           然而,這些變化並不大。從 R A M陣列中鎖存的 1 6位地址既可作為 2 - 1選擇器(它允許該地址作為 R A M陣列的地址輸入)的輸入也可作為 1 6位計數器的輸入並由置位訊號設定:

  

        顯而易見,只有當指令程式碼為 3 0 h且其後面的地址被鎖存,我們才必須保證置位訊號為 1 。

        轉移指令當然很有用,但它並非和一條只有時跳轉而並非時刻跳轉的指令一樣有用,這樣的一個指令叫作條件轉移。為了顯示該命令如何有用,可提出這樣一個問題:怎樣才能讓自動加法器完成兩個 8位數的相乘?例如,怎樣才能得到像 A 7 h乘以1 C h這樣簡單運算的結果?

         很容易,不是嗎?兩個 8位數相乘的結果是一個 1 6位數。為了方便起見,乘法中的 3 個數都用 1 6位數來表示。首要的工作是決定把乘數和乘積放在何處:


        每個人都知道 A 7 h和1 C h(即十進位制的 2 8)相乘的結果與 A 7 h相加 2 8次的結果相同。因此,在1 0 0 4 h和1 0 0 5 h處的 1 6位數就是累加結果。下圖顯示的是把 A 7 h加一次到那個位置的程式碼:

         在這6條指令執行完後,儲存單元 1 0 0 4 h和1 0 0 5 h處的 1 6位數等於 A 7 h乘以 1 。因此,為了使這1 6位數等於 A 7 h乘以1 C h,這6個指令必須重複執行 2 7次。可以通過在地址 0012h 處接著輸入2 7次這6個指令來實現;也可以在 0 0 1 2 h處輸入停止指令,然後按 2 8次復位鍵來得到最終結果。

          當然,這兩個方案都不理想。它們需要你做某些事情 (輸入大批指令或者按復位鍵 )的次數和乘數相當。當然你不願意這樣去進行 1 6位數的乘法運算。

          但是如果在 0012h 處輸入轉移指令會怎麼樣呢?這個指令使計數器從 0 0 0 0 h重新開始計數:

        這當然是一個技巧。第一次執行指令後,儲存單元 1 0 0 4 h和1 0 0 5 h處的 1 6位數等於 A 7 h乘1 ,然後轉移指令使其返回到儲存器頂部。第二次執行指令後,此 1 6位數等於 A 7 h乘2。終於,其結果將等於 A 7 h乘1 C h。不過這樣的過程並不會停止,它將不斷地執行、執行、執行。

        我們想讓轉移指令做的是使迴圈過程只重複所需的次數,這就是條件轉移,它實施起來並不困難。我們要做的第一件事情就是增加一個與進位鎖存器類似的 1 位鎖存器。因為只有 8位加法器的輸出全為 0時它才鎖存 1 ,所以叫它零鎖存器:

          只有當或非門的 8個輸入全為 0時,其輸出才為 1 。同進位鎖存器的時鐘輸入一樣,只有當加法、減法、進位加法或借位減法指令執行時,零鎖存器的時鐘輸入才鎖存一個數,這個被鎖存的數值叫作零標誌位。注意它,是因為它似乎行為相反:如果加法器輸出全為 0,則零標誌位為 1 ;若加法器輸出不全為 0,則零標誌位為 0。

           利用進位鎖存器和零鎖存器,可以在指令表中再新增四條指令:
         例如,只有當零鎖存器輸出為 0時,非零轉移指令才轉移到指定地址。換句話說,如果上一次加法、減法、進位加法和進位減法指令計算結果為 0,則沒有轉移發生。實現這個設計只需在實現常規轉移命令的控制訊號上再加上一個控制訊號:如果為非零轉移指令,則只有當零標誌位為 0時, 1 6位計數器的置位訊號才被觸發。

         利用上述程式碼實現兩個數的乘法所需的操作可由如下開始於地址 0 0 1 2 h處的指令完成:

         正如我們所設計的,迴圈一次後,位於 1 0 0 4 h和1 0 0 5 h處的 1 6位數等於 A 7 h乘以 1 。上圖中的這些指令把位元組從 1 0 0 3 h處裝載到加法器中,此位元組為 1 C h。再把這個位元組與 0 0 1 E h處的資料相加,此處資料正好是停止指令,但當然也是有效數字。把 F F h同 1 C h相加與從 1 C h減去1 的結果相同,都等於 1 B h。這個值不為 0,所以零標誌位為 0,位元組 1 B h存回到地址 1 0 0 3 h處。接下來是一條非零轉移指令,零標誌位沒有置為 1 ,所以轉移發生。下一條指令位於地址 0 0 0 0 h處。

         記住,儲存指令不會影響零標誌位。零標誌位只能被加法、減法、進位加法、借位減法指令所影響,因此它同這些指令中最近一個執行時所設定的值相同。

         迴圈兩次後,位於 1 0 0 4 h和 1 0 0 5 h處的 1 6位數將等於 A 7 h乘以 2。而 1 B h加上 F F h等於 1 A h,不是0,因此又返回到儲存器頂部。

         迴圈到第 2 8次時,位於 1004h 和1 0 0 5 h處的 1 6位數等於 A 7 h乘以 1 C h。位於 1 0 0 3 h處的值等於1 ,它將加上 F F h結果等於 0,因此零標誌位被置位。非零轉移指令不再轉移到儲存器地址0 0 0 0 h處,相反,下一條指令為停止指令。至此,我們完成了全部工作。

         現在可以肯定,很長一段時間以來我們已經裝配了一組硬體,同時可以把它叫作計算機。當然,它只是一臺原始的計算機,但它畢竟是一臺計算機。它與我們以前設計的計算器的不同之處在於條件轉移指令,控制重複或迴圈是計算機和計算器的區別。這裡已經演示了條件轉移指令是如何使得這臺機器進行兩個數的乘法運算的。用類似的方法,它也能計算兩個數的除法。而且,還不侷限於 8位數。它能加、減、乘、除 1 6位、 2 4位、 3 2位甚至更多位的數,而且如果它能實現這些操作,也就能計算平方根,對數和三角函式。

         既然已裝配了一臺計算機,就可以開始使用一些計算機方面的詞彙。

         我們裝配的計算機歸類為數字計算機,因為它採用的是離散值。曾經有過模擬計算機,但它們正逐漸消失。 (數字資料是離散資料,是具體的確定的值;而模擬資訊是連續的、在整個範圍內變化的值。 )

         數字計算機有 4個主要部分:處理器、儲存器、至少一個輸入裝置和一個輸出裝置。上述機器中,儲存器是一個 6 4 K B的 R A M陣列。輸入和輸出裝置分別是 R A M陣列控制面板上的幾行開關和燈泡。這些開關和燈泡使人們可以輸入資料到儲存器並檢查結果。

        處理器是計算機中除儲存器、輸入 /輸出裝置以外的一切東西。處理器也叫中央處理器單元或 C P U。再通俗一點兒,處理器有時也稱作計算機的大腦。但儘量避免用這樣的術語,這是因為在本章中我們所設計的東西根本不像大腦。 (今天,微處理器這個詞用得非常普及。微處理器只是一個很小的處理器,通過採用第 1 8章將要講到的技術而實現。但此刻我們用繼電器所建造的東西則很難用 “ 微”來定義。 )

        我們所建造的處理器是一個 8位處理器。累加器寬度為 8位,並且許多資料通路的寬度都是8位,只有 R A M陣列的地址的資料通路是 1 6位的。如果用 8位的地址通路,則儲存器容量只能限於 2 5 6位元組而非 65 536位元組,那樣處理器則有太大的侷限性。

        處理器有一些元件。已經確定的一個是累加器,它是一個簡單的鎖存器,用來在處理器內部儲存資料。我們所設計的計算機中, 8位反向器和 8位加法器一起稱作算術邏輯單元或 A L U。A L U只能進行算術運算,主要是加法和減法。在稍微複雜一點兒的計算機中(我們將會看到),A L U也可進行邏輯運算,如“與”、 “或”、 “異或”。 1 6位計數器叫作程式計數器 P C。

       我們的計算機是用繼電器、電線、開關和燈泡建造的,所有這些都是硬體。與之對應,指令和輸入儲存器中的其他資料叫作軟體,之所以叫“軟體”是因為它們比硬體更容易改變。

       當談論計算機時, “軟體”和“計算機程式”,更簡單地講“程式”是同義的,編寫軟體也稱作計算機程式設計。當採用一系列計算機指令使計算機進行兩個數的乘法時,我們所做的工作就是計算機程式設計。

       通常,在計算機程式中,可以區分程式碼(即指令)和供程式碼使用的資料。有時這種區分並不明顯,如停止指令還可作為數- 1 執行雙重功能。

      計算機程式設計有時也叫編寫程式碼或編碼。有時計算機程式設計師也叫編碼員,儘管一些人可能認為這是一個貶義的名詞。程式設計師更願意被稱作“軟體工程師”。

      處理器可以響應的操作碼(如指裝載和儲存的 1 0 h和 11 h)叫作機器碼,或機器語言。之所以用“語言”這個術語是因為機器碼類似於可讀 /寫的人類語言可被機器理解和響應。

      我們要用很長的短語表示機器所執行的指令,如:進位加法( Add with Carry)。通常,機器碼都分配指定了用大寫字母表示的短的助記符,這些助記符有 2或3個字元。下面是一系列可能的上述計算機所能識別的機器碼的助記符:

      
       這些助記符特別適於和另外一對簡潔短語結合使用。例如,不說像“把 1 0 0 3 h處的值裝載到累加器中”這樣羅嗦的話,而是用下面語句來代替:


       位於助記符 L O D右邊的 A和[ 1 0 0 3 ]叫作運算元,它們是特定的裝載( L o a d)指令的操作物件。左邊的運算元為目的運算元( A代表累加器) ,右邊的為源運算元,方括號表示要裝載到累加器中的值不是 1 0 0 3 h,而是儲存在儲存器地址 1 0 0 3 h中的值。

       同樣,指令“把 0 0 1 E h處的位元組加到累加器中”可簡寫為:

       而“把累加器中的內容儲存到地址 1 0 0 3 h處”記作:


       注意,目的運算元(儲存指令的儲存單元)仍然在左邊,源運算元在右邊。累加器的內容儲存在地址 1 0 0 3 h處。指令“若零標誌位不為 1 則轉移到 0 0 0 0 h處”可簡潔地記作:

       該指令中沒有使用方括號,這是因為該指令是轉移到地址 0 0 0 0 h處而不是轉移到地址0 0 0 0 h中儲存的值所表示的位置處。

       用縮寫指令的形式來表示很方便,因為指令能以可讀的方式連續列出來而不需畫出儲存器的分配圖。為了表示某一指令儲存在某一地址,可以用一個十六進位制地址後加冒號來表示,如下所示:

       

       下面表示了一些儲存在某一地址的資料:


       用逗號隔開的兩個位元組表示第一個位元組儲存在左邊的地址中,第二個位元組儲存在緊接著該地址的下一個地址中。上述三行相當於:

       因此,整個乘法程式可寫成如下一系列語句:

      使用空格和空行只是為了使程式具有更好的可讀性,以方便人們閱讀程式。

      寫程式碼時最好不要用真實的數字地址,因為它們是會變的。例如,如果要把數字儲存到地址 2 0 0 0 h~2 0 0 5 h處,需要重寫許多語句。較好的方法是使用標號來指定儲存單元,這些標號是簡單的單詞,或類似於單詞的東西,如:

          注意,標號 N U M 1 、 N U M 2和 R E S U LT都表示儲存兩個位元組的儲存單元。在這些語句中,標號 N U M 1 + 1 、 N U M 2 + 1 和 R E S U LT + 1 都指向特定標號後的第二個位元組。注意, N E G 1( negative one)用來標記 H LT指令。

         此外,為了不忘記這些語句的意思,可以加上一些註釋,它們與語句之間用分號隔開:

        以上表示的是一種計算機程式設計語言,稱作組合語言。它是全數字的機器程式碼和指令描述性語言的綜合,且儲存器地址用符號表示。人們有時會把機器語言和組合語言弄混淆,因為它們是表示同種事情的兩種不同的方法。組合語言的每條語句都對應於機器程式碼的特定位元組。

        如果你想為本章所建立的計算機編寫程式,你可能首先想用匯編語言寫出來(在紙上) 。然後,在認為它正確並準備測試它時,可以對它進行手工彙編:這意味著用手工的方法把每一個彙編語句轉換成機器程式碼,仍然寫在紙上。接著,你可以用開關把機器碼輸入到 R A M陣列並執行該程式,即讓機器執行指令。

        學習計算機程式設計的概念時,不可能很快就能正確知道程式的毛病所在。編寫程式碼時—特別是用機器程式碼—很容易產生錯誤。輸入一個錯誤的數字已經很不好了,但如果輸入一條錯誤的指令會怎麼樣呢?本想輸入 1 0 h (裝載指令 ),但卻輸入了 11 h(儲存指令) ,不但機器不會把期望的資料裝載,而且該處的資料還會被累加器中的內容覆蓋。

         一些錯誤可以導致難以預料的結果。假設使用無條件轉移指令轉移到沒有有效指令程式碼的位置,或者偶然使用儲存指令覆蓋了一些指令,任何事情都可能發生(經常如此)。

        上述乘法程式中也有一些毛病。如果你執行它兩次,則第二次將會是 A 7 h乘以 2 5 6,並且結果將加到原來計算的結果中。這是因為程式執行一次後,地址 1 0 0 3 h處的值為 0。當程式第二次執行時, F F h將加到那個值中,其結果不為 0,程式將繼續執行直到它為 0。

        我們已看到上述機器可以進行乘法運算,同樣,它也可以進行除法運算。此外,它可利用這些基本功能進行平方根、對數和三角函式的計算。機器所需要的只是用來進行加法、減法的硬體及利用條件轉移指令來執行適當程式碼的一些方法。正如一個程式設計師所說: “我可以用軟體完成其餘功能”。

        當然,軟體可能相當複雜。許多書中都描述了一些演算法供程式設計師解決專門的問題,本書還沒準備這樣做。我們一直在考慮自然數而沒有考慮如何在計算機中表示十進位制小數,我們將在第 2 3章介紹它。

         前面已說過幾次,建造這些裝置的所有硬體在 1 0 0多年前就有了。但本章中出現的計算機在那時卻沒有建造出來。在 2 0世紀 3 0年代中期,最早的繼電器計算機制造出來時,包含在設計中的許多概念還未形成,直到 1 9 4 5年左右人們才開始意識到。例如,直到那時候,人們仍然設法在計算機內部使用十進位制數而不是二進位制數;計算機程式也並非總是儲存在儲存器中,而是有時把它存在紙帶上。特別是早期計算機的儲存器非常昂貴且體積龐大。不管是在 1 0 0年前還是在現在,用 5 0 0萬個電報繼電器來建造 6 4 K B的R A M陣列都是荒唐的。

         當我們展望和回顧計算器和計算裝置的歷史時,可能會發現根本沒必要建造這樣精緻的繼電器計算機。就像在第 1 2章提到的,繼電器最終會被真空管和電晶體這樣的電子裝置所取代。或許我們也會發現他人制造的相當於我們設計的處理器和儲存器的東西能小到放在手掌中。