6、自动操作
先來回憶下我們提到過的加法器:
從圖中可以看出,8位鎖存器利用觸發器來保存8位數據。使用這個設備時,首先需要按下清零開關使鎖存器中的內容全部為0,然后通過開關輸入第一個數。加法器只是簡單的將這個數字和鎖存器輸出的0進行求和,因此相加的結果與原先輸入的數字是一樣的。按下相加開關可以把這個數保存在鎖存器中,最后會點亮某些燈泡以顯示它。現在通過開關輸入第二個數,加法器把它與已經存放在鎖存器的第一個數相加。再次按下相加開關,就可以把相加的結果存入鎖存器中,通過燈泡顯示這個結果。通過這種方式,可以把一串數相加并顯示運行結果。顯然,這種設計方案存在一個缺陷:8個燈泡無法顯示大于255的數。
對于這種電路,我們目前只講到了一種鎖存器,電平觸發。在電平觸發鎖存器中,為了保存數據必須將時鐘端首先置1,然后回置為0.當時鐘輸入端為1時,鎖存器的數據輸入端可以改變,而這些變化將會影響到數據輸出。我們還介紹了邊沿觸發的鎖存器,這種鎖存器在0跳變為1的瞬間保存數據。邊沿觸發器在很多方面更加易于使用,因此假定本章用到的所有觸發器都是邊沿觸發的。
用來累加多個數的鎖存器稱作累加器(accumulator)
上面的加法器存在一個很大的缺陷,假如要把100個二進制數加起來,你必須坐在加法器前,耐心的輸入所有的數并累加起來,如果中間兩個數輸錯了,又要重復開始。
但是,也許并非如此。上一章我們學習了RAM陣列,我們如果可以把100個二進制數輸入到RAM陣列中而不是直接輸入到加法器中,一旦需要修改一些數據,我們工作將會變得容易很多。
因此我們現在面臨的是如何把RAM陣列和累加器連接起來。RAM陣列的輸出信號可以替代加法器的開關。而用一個16位的計數器就可以控制RAM陣列的地址信號。在這個電路中,RAM陣列的數據輸入信號和寫操作端信號可以省去。修改后的電路結構如下圖所示:
當然,這并不是最易于使用的計算設備。要使用它,首先要閉合清零開關,這樣做的目的是,清楚鎖存器中的內容并把16位計數器的輸出置為0000h,然后閉合RAM控制面板的控制端開關。現在你可以從地址0000h開始輸入一組你想要相加的8位數。如果有100個數,那么他們將被存放在0000h-0063h的地址空間中(也應該把RAM陣列中未使用的單元設置為00h)。然后閉合RAM控制面板的控制端開關(這樣控制面板就不再控制RAM陣列了),同時斷開清零開關。做完了這些,我們可以靜靜坐下來觀察燈泡顯示運算結果。
讓我們看下是如何工作的:當清零開關第一次斷開時,RAM陣列的地址輸入是0000h。RAM陣列的該地址中存放了8位數值是加法器的輸入數據。加法器的另一個輸入數據為00h,因為此時鎖存器也已經清零了振蕩器提供的時鐘信號—— 一個可以在0,1之間快速切換的信號。清零開關斷開后,當時鐘信號由0跳變為1時,將有兩件事同時發生:鎖存器保存加法器的計算結果,同時16位計數器增1,指向RAM陣列的下一個地址單元。清零開關斷開后,時鐘信號第一次從0跳變為1時,鎖存器就將第一個數值保存下來,同時計數器增加為0001h;當時鐘發生第二次跳變時,鎖存器保存之前的兩個數的求和結果,同時計數器增加為0002h;按這種方式反復操作。
?要注意的時,這里首先做了一些假設。最重要的一點就是,振蕩器要足夠慢以使電路的其他部分可以工作。每次時鐘震蕩過程中,在加法器輸出有效的結果之前,一些繼電器必須去觸發其他繼電器。
這個電路存在兩個缺陷:我們沒辦法使它停下來、它目前只能做8位加法。
?
現在關注另外一個問題:如果不需要把100個數加一起,只是想用自動加法器把50對數分別相加,得到50個不同的結果。
我們可能想到把運算結果存回到RAM陣列中去,這樣的話,就可以在適當的時候用RAM陣列的控制面板來檢查運算結果。為了這個目的,控制面板上專門設計了燈泡。
這意味著我們可以去掉與鎖存器連接的燈泡,取而代之的是把鎖存器的輸出端連接到RAM陣列的數據輸入端,這樣就可以把結果寫回到RAM陣列中去。如下圖:
上圖略去了自動加法器的其他部分,其中包括振蕩器和清零開關,因為我們現在不需要特別標注計數器和鎖存器的清零及時鐘輸入。此外,既然我們現在已經開始利用RAM的數據輸入,因此需要一種用來控制RAM寫入信號的方法。
我們希望它不僅僅可以對一組數字做累加運算,還希望它能夠自主地確定要累加多少個數字,而且還能記住在RAM中存放了多少個計算結果,這樣就可以簡化查詢工作。
例如,假設我們要先對三個數進行求和,然后對兩個數進行求和,最后再對三個數進行求和。想象一下,我們可以把這些數保存再RAM陣列中以0000h開始的一組空間中,這些數存儲在RAM陣列中的具體形式如下圖所示:
?實際上,我們希望它能做四件事情:
進行加法操作,首先它要把一個字節加(Add)到累加器中,這個操作稱為加載(Load)。
把存儲器中的一個字節加(Add)到累加器的內容中去。
把累加器中的計算結果取出并村放到存儲器中。
需要一個方法另自動加法器停(Halt)下。
?
這一過程如下,上面提到的自動加法器所做的運算為例:
1、把0000h地址處的內容加載到累加器
2、把0001h地址處的內容加到累加器中
3、把0002h地址處的內容加到累加器中
4、把累加器中的內容存儲到0003h地址處
5、把0004h地址處的內容加載到累加器
6、把0005h地址處的內容加到累加器
7、把累加器中的內容存儲到0006h地址處
8、把0007h地址處的內容加載到累加器
9、把0008h地址處的內容加到累加器
10、把0009h地址處的內容加到累加器
11、把累加器中的內容存儲到000Ah地址處
12、令自動加法器停止工作
?為了更靈活的實現我們的需求,我們需要把存儲器的某個值直接加載到累加器中,或者直接把累加器的值保存在存儲器中,還希望它能自動的停下來。便于觀察RAM值。這個時候我們就需要加一些數字代碼來標識加法器要做的每一項工作:加載、相加、保存、終止。
| 操作碼 | 代碼 |
| Load(加載) | 10h |
| Store(保存) | 11h |
| Add(加法) | 20h |
| Halt(停止) | FFh |
?
?
?
?
我們可能想到方便的方法是另外新增一塊RAM,用戶存放每個地址對應的指令(被稱為指令碼或操作碼,指示電路要執行的操作)
之前我們說自動加法器的8位輸出要作為數據RMA陣列的輸入,這就是Save功能。還需要另一個改變,以前8位加法器的輸出是8位鎖存器的輸入,但是現在為了執行Load指令,數據RAM陣列的輸出有時也需要作為8位鎖存器的輸入,這種新變化需要一個2-1選擇器來實現。改進后的自動加法器如下圖所示:
圖中略去了一些組件,但是仍然清晰的描述了各個組件之間的8位數據通路。16位的計數器為兩個RAM陣列提供地址輸入。通常,數據RAM陣列的輸出傳入到8位加法器執行加操作。8位鎖存器的輸入可以是數據RAM陣列的輸出(當執行Load指令),也可以是加法器的輸出(執行Add指令),這種情況下就需要2-1選擇器。通常鎖存器電路的輸出又流回到加法器中,但是當執行Save指令時,它就成為了數據RAM陣列的輸入數據。
上圖缺少的是控制所有這些組件的信號,它們統稱為控制信號。包括16位計數器的“時鐘”輸入和“清零”輸入,8位鎖存器的“時鐘“輸入和”清零“輸入,數據RAM陣列的”寫“(W)輸入,2-1選擇器的"選擇”(S)輸入。其中一些信號明顯是基于代碼RAM陣列的輸出,例如,如果代碼RAM陣列的輸出是Load指令,那么2-1選擇器的“選擇”輸入必須是0(即選擇數據RAM的輸出)。只有當操作碼是指令Store時,數據RAM陣列的“寫”(W)輸出必須是1.這些控制信號可以通過邏輯門的各種組合來實現。
利用最少的附加硬件和一些新增的操作碼,可以讓這個電路從累加器中減去一個數。第一步是向操作碼表增加一些代碼。
| 操作碼 | 代碼 |
| Load | 10h |
| Store | 11h |
| Add | 20h |
| Subtract(減法) | 21h |
| Halt | FFh |
如果是劍法,數據RAM陣列的數據傳入加法器之前要取反,并且加法器進位輸入置1,然后的操作和Add一樣。增加一個反相器,C0信號可以完成這兩項任務。改進后電路結構如下:
假如現在要把56h和2Ah想加,然后再從中減去38h,可以按照下圖中兩個RAM陣列的代碼(操作碼)和數據(操作數)完成該運算。
累加器中的值,先加載56h,和24A相加,即80h.Subtract操作使數據RAM陣列的下一個值(38h)按位取反,得到C7h。當加法器的進位輸入置1時,取反得到C7h,然后跟80h相加。C7h+80h+1h=48h。
如果我們要進行16位的計算呢?一個解決方法是兩個8位連一起,但還有個更小的代價,就是把低8位計算,再計算高8位就可以了,這里就會出現低8位計算的時候,有進位的話怎么辦?我們可以再低字節加法的時候,把進位給保存起來。如何保存呢?1位鎖存器就是最好的選擇了,該鎖存器應該被稱為進位鎖存器(Carry latch)
為了使用進位鎖存器,還需要增加“進位加法”(Add with Carry)操作碼。8位加法操作時,使用Add指令。加法的進位輸入是0,它的進位輸出將會保存到進位鎖存器(盡管根本不會用到)。
如果要對16位的數進行加法運算,我們仍然使用常規的Add指令對兩個低字節數進行加法運算。加法器的進位輸入為0,進位輸出被鎖存到進位鎖存器中。當把兩個高字節數相加時,要用Add with Carry指令。這時,兩個數相加時要用進位鎖存器的輸出作為加法器的進位輸入。
如果要進行16位數的減法運算,則還需要一個新的指令,稱為“借位減法”(Subtract and Borrow)。通常,Subtract指令需要將減數取反并且把加法器的進位輸入置1.進位輸出正常情況下為1,因此應該被忽略。但對16位數進行減法運算時,鎖存器保存的結果應該作為加法器的進位輸入。在加入了Add with Carry和Subtract and Borrow之后,目前我們已經有7個操作碼,如下:
| 操作碼 | 代碼 |
| Load | 10h |
| Store | 11h |
| Add | 20h |
| Subtract | 21h |
| Add with Carry(進位加法) | 22h |
| Subtract with Borrow(借位減法) | 23h |
| Halt | FFh |
?
在執行減法或借位減法運算時,送入加法器中的操作數需要進行取反預處理。加法器的僅為輸出是進位鎖存器的數據輸入。
現在假設我們要對三個8位數求和,再減去一個8位數并保存結果。可能需要一條Load,兩條Add,一條Subtract指令以及一條Store指令。如果想要從原來的求和結果(3個8位數的和)減去另一個數該怎么做?這個求和結果已經不能被訪問了,每次我們使用它的時候都必須重新計算。
產生上面的原因在于我們構造的自動加法器具有如下的特性:它的代碼存儲器和數據存儲器是同步的、順序的,并且都從0000h開始尋址。代碼存儲器中的每一條指令對應數據存儲器中相同地址的存儲單元。一旦執行了一條Store指令,相應的,就會有一個數被保存到數據存儲器中,而這個數將不能重新加載到累加器中。
要解決這個難題,需要對自動加法器的設計做一個根本性的且程度極大的修改。這個想法實現起來似乎非常困難,但是改進的加法器具有更高的靈活性。
每個指令占一個字節,現在我們除Halt之外,其他的都占用3個字節,一個為代碼本身,另外兩個用來存放一個16位的存儲單元地址。如對于Load,后兩個字節保存的地址用來指明數據RAM陣列的一個存儲單元,該單元存放的是需要被加載到累加器中的字節。對于Add,SubStract,Add with Carry,Subtract with borrow指令來說,該地址知名的存儲單元是要從累加器中加上或減去的自己。對于Sotre,該地址指明累加器中的內容將要保存到的存儲單元地址。
如下求和:
改進的自動加法器中,每條指令(除Halt指令)需要3個字節
后面的兩個字節的地址可以是其他的地址。
前面我們講到過的兩個16位76ABh和232Ch相加,運算結果存儲的問題。現在我們有更合理的方式了。
這6個存儲單元不必像途中這樣全連在一起,它們可以分散在整個64KB數據RAM陣列的任意位置。為了把這些地址中的數相加,代碼RAM陣列中的指令必須用以下方式設置。
上圖描述的是兩個16位數相加,然后先計算低字節,然后計算高字節。然后分別存放在4004h 4005h。
實現該設計的關鍵是把代碼RAM陣列的數據輸出到3個8位鎖存器中。每個鎖存器保存該3字節指令的一個字節。第一個鎖存器保存代碼,第二第三個分別保存地址高字節和地址低字節。
?
?從存儲器中取指令的過程 稱為取指令。在我們設計的加法器中,每一條指令的長度是3個字節。因為每次從存儲器取回一個字節,所以取每條指令需要的時間為3個時鐘周期。此外,一個完整的指令需要4個時鐘周期。這些變化使得控制信號更加復雜。
機器響應指令碼做一系列操作的過程稱為執行指令,每一種機器碼用其唯一的方式出發多種控制信號,從而引發機器執行各種操作。
由于我們現在使用的是3字節長度的指令格式,第二和第三個字節用來指明操作數的存儲地址,因此就沒必要再使用兩個獨立的RAM陣列,操作碼和操作數可以存放在同一個RAM陣列了。
我們需要一個2-1選擇器來確定如何對RAM進行尋址。通常,和前面方式相同,我們用一個16位的計數器來計算地址。數據RAM陣列的輸出仍然鏈接到3個鎖存器,分別用來保存指令代碼及其相應操作數的16位地址,其16位的地址輸出是2-1選擇器的第二種輸入。地址被鎖存后,可以通過選擇器將其作為RAM陣列的地址輸入。
現在可以把操作指令和操作數據保存在同一個RAM陣列中,下面演示了如何把兩個8位數相加,然后從結果中再減去一個8位數。
?由于計數器復位后,從0000h開始訪問,所以我們指令從0000h開始存放。我們可以把他們的操作數以及運算結果放在除了指令之外的任何地方,所以我們選擇在從0010h地址開始保存操作數。
如果現在我們需要添加指令,則0010h顯得不夠用。或許我們可以試著將新添加的指令放在0020h,新添加的操作數據地址放在0030h。我們希望自動加法器從0000h開始執行所有指令完成計算任務。
我們當然希望移除000Ch處的Halt指令,但是移除后,后面0010h操作數據都會被當作指令,所以這個時候我們用到Jump指令,跳轉到0020h。
| 操作碼 | 代碼 |
| Load | 10h |
| Store | 11h |
| Add | 20h |
| Subtract | 21h |
| Add With Carry | 22h |
| Subtract With Borrow | 23h |
| Jump(跳轉) | 30h |
| Halt | FFh |
?
?
Jump指令通過作用于16位計數器實現其功能。無論如何,只要自動加法器遇到指令,計數器就會被強制輸出該Jump指令后的16位地址。這可以通過16位計數器的D型邊沿觸發器的預置(Pre)和清零(Clr)輸入來實現:
Pre和Clr端正常輸入都應該是0,當Pre=1,Q=1,當Clr=1,則Q=0.
如果你希望向一個觸發器加載一個新的值(用A代表地址),可以如下圖這樣鏈接:
通常,置位信號為0.此時,觸發器的預置端輸入為0,在復位信號不為1的情況下,清零信號也為0.當置位信號為1時,如果A為1,則清零輸入為0,預置輸入為1;如果A為0,則預置輸入0,清零輸入1。這就意味著Q端將被設置為與A端相同的值。
我們需要為16位計數器的每一位設置這樣一個觸發器。一旦加載了某個特定的值,計數器就會從該值開始計數。
這對電路的改動并不是很大,從RAM陣列得到的16位地址既可以作為2-1選擇器(它允許該地址作為RAM陣列的地址輸入)的輸入,也可以作為16位計數器置位信號的輸入。
我們必須確保只有指令代碼為30h并且其后的16位地址被所存時,置位信號才為1.
跳轉很重要,思考一下:怎樣讓自動加法器進行兩個8位數的乘法運算呢?例如A7h和1Ch相乘。
兩個8位數相乘得到一個16位數,為了方便起見,把該乘法運算中涉及的3個數均表示為16位數。第一步確定要把乘數和乘積保存到什么位置。
其實乘法就是累加的結果。下圖演示如何把A7h加到該地址。
上圖就是A7h乘以1的結果。我們需要把這6條指令再反復執行27次。為了達到這個目的,可以在0012h地址開始把這6條指令連續輸入27次。也可以在0012h處保存一個Halt指令,然后將復位鍵連續按28次得到最終結果。
當然這兩種方法都不是很理想,它們要求你做很多重復繁瑣的事情。但是如果我們在地址0012處放置一條Jump指令會怎樣?這個指令使得計數器再次從0000h處開始計數。
所以我們需要一種jump指令,能夠指定這個過程需要重復執行的次數,這種指令就是條件跳轉指令。要實現它,要做的第一步就是增加一個與進位鎖存器類似的1位鎖存器。該鎖存器被稱為零鎖存器(Zero latch),這是因為只有當8位加法器的輸出全部為0時,它所存的值才是1.
?和僅為鎖存器的是中暑如一樣,只有當Add、Subtract、Add with Carry、Subtract with Borrow這些指令執行時,零鎖存器才所存1個數,該數稱為零標志位。
有了僅為鎖存器和零鎖存器以后,我們可以為指令表新增4條指令。
例如,非零轉移指令(Jump If Not Zero)只有在零鎖存器的輸出為0時才會跳轉到制定的地址,換言之,如果上一步的加法、減法、進位加法、或者借位減法等運算的結果為0時,將不會發生跳轉。為了實現這個設計,只需要在常規跳轉命令的控制信號之上再加一個控制信號:如果指令是Jump If Not Zero,那么只有當零標志位是0時,16位計數器才被觸發。
其實就是在最后加上一個乘數與FFh相加,保存到乘數中去的結果。FFh相加等于減1.當減成0的時候,零標志位就是1了。程序就停止了。
現在可以斷言,我們一直不斷完善的這組硬件構成的機器確實可以被稱為計算機。當然,它很原始,但畢竟是一臺真正的計算機。條件跳轉指令將它與我們以往設計的加法器區別開來,能否控制重復操作或者循環是計算機和計算器的區別。這里演示了乘法,類似的,也可以運行除法,而且不局限于8位.16、24、32更高位都可以。既然它能完成這些運算,那么開平方根,取對數,三角函數等運算也完全可以勝任。
既然我們已經裝配了一臺計算機,因此可以使用計算機相關詞匯了
我們裝配的屬于數字計算機,只處理離散數據。一臺數字計算機主要由4部分構成:處理器,存儲器,至少一個輸入設備和一個輸出設備。我們裝配的計算機中,存儲器是64KB的RAM,輸入和輸出設備分別是RAM陣列控制面板上的開關和燈泡。除了前面的3種設備之外的其他設備都歸類于處理器(CPU)。
我們設計的是8位處理器。累加器的寬度是8位,大部分數據通路都是8位寬度。唯一的16位數據通路是RAM陣列的地址通路。如果該通路也是8位的話,存儲容量就變為256字節而非65536字節。
在我們的設計中,8位反相器和8位加法器一起構成了算術邏輯單元(ALU)。該ALU只能進行算數運算,復雜的可以進行邏輯運算。16位計數器被成為程序計數器。
一直以來,我們都在使用很長的短語來引用機器所執行的指令,如Add with Carry指令。通常而言,機器碼都分配了對應的短助記符,這些助記符都是用大寫字母來表示,包括2個或3個字符。下面是一系列上述計算機大致能夠識別的機器碼的助記符。
這些助記符與另外一對短語結合使用時,其作用更加突出。例如,對于這樣一條長語句“把1003h地址處的字節加載到累加器”,我們可以使用如下簡潔的句子替代:
LOD ? A, ?[1003h]
位于助記符右側的A和[1003h]稱為參數,它們是這個LOD指令的操作對象。參數由兩部分構成,左邊的操作數成為目標操作數(A代表累加器),右邊的操作數成為源操作數。方括號“[]"表明要加載到累加器的不是1003h這個數值而是位于1003h存儲地址的值。
類似的,指令”把001Eh地址的字節加到累加器“,可以簡寫為: ADD A, [0010Eh]
指令”把累加器中的內容保存到1003h地址“,可以簡寫為:STO [1003h],?A
指令”如果零標志位不是1,則跳轉到0000h地址處,可以簡寫為:JNZ 0000h ?(0000h地址就是跳轉指令的操作數)
用縮寫很方便,因為在這種形式下指令以可讀的方式順序列出而不必畫出存儲器的空間分配情況。通過在一個十六進制地址后面加上一個冒號,可以表示某個指令保存在某個特定的地址空間。例如
0000h: ? ? LOD ?A, [1005h]
下列語句表示了數據在特定地址空間的存儲情況。
1000h: ? ? ? 00h, A7h
1002h: ? ? ? 00h, 1Ch
1004h: ? ? ? 00h, 00h
等價于:
1000h: ? ? ? 00h, A7h, 00h, 1Ch, 00h, 00h
因此上面討論的乘法程序可以用如下一系列語句來表示:
0000h: ? LOD ?A, [1005h]
ADD ?A, ?[1001h]
STO ?[1005h], ?A
?
LOD ?A, ?[1004h]
ADC ?A, ?[1000h]
STO ?[1004h], ?A
?
LOD ?A, ?[1003h]
ADD ?A, ?[001Eh]
STO ?[1003h], ?A
?
JNC ?0000h
?
001Eh: HLT
1000h: 00h,A7h
1002h: 00h,1Ch
1004h: 00h,00h
空格空行僅僅為了人們更方便閱讀程序。
在編碼的時候最好不要使用實際的數字地址,因為它們是可變的。用標號(label)來指代存儲器的地址空間是個較好的辦法。或是類似單詞的字符串。上面的代碼可以改寫為:
BEGIN: ?LOD ?A, ?[RESULT ?+ ?1]
ADD ?A, [NUM1 ?+ ?1] :低字節相加
STO ?[RESULT+1], ?A
LOD ?A, ?[RESULT]
ADC ?A, ?[NUM1] :高字節相加
STO ?[RESULT], ?A
?
LOD ?A, ?[NUM2 + 1]
ADD ?A, [NEG1] ?:第二個數減1
STO ?[NUM2+1], ?A
?
JNZ ?BEGIN
?
NEG1: HLT
NUM1: 00h, A7h
NUM2: 00h,1Ch
RESULT: ? ?00h,00h
+1代表了第二個字節, 冒號意思為注釋
這里給出的是一種計算機程序設計語言,稱為匯編語言。它是全數字的機器語言和指令的文字描述的一種結合體。同時它用標號表示存儲地址。每一條匯編語句都對應著機器語言中的某些特定字節。
轉載于:https://www.cnblogs.com/Garin/p/6871568.html
《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀總結