在B站上看到有大佬做了個8位計算機,非常感興趣,同時想了解一下計算機底層到底是怎么運作的,就跟著做了一個。以下是筆記,寫的比較細。 先show一下代碼 序號 | 指令 | 說明 |
---|
0 | OUT | 顯示 | 1 | ADD 15 | 加上地址15的值 | 2 | JC 4 | 進位跳轉到地址4 | 3 | JMP 0 | 沒有進位跳轉到地址0 | 4 | SUB 15 | 減去地址15的值 | 5 | OUT | 顯示 | 6 | JZ 0 | 為0跳轉到地址0 | 7 | JMP 4 | 不為0跳轉到地址4 |
15地址設置成15; 代碼意思是:值自增15,如果到達進位255就變成自減15,如果自薦到達0就自增。 
基礎知識二極管單項導通器件 1874年,德國科學家發(fā)現(xiàn)晶體的整流功能 由半導體硅材料制成,硅本身是沒有電極的,在做晶體管的時候做了雜化處理,在一段加入了硼,一段加入了磷,硼這端會多出電子空穴,而磷這一端會多出自由電子,有意思的事情就發(fā)生了。 
因為Si是4個電子,P有3個電子,N有5個電子,所以單純的硅會形成4個共價鍵非常穩(wěn)定。 硅: 
磷:N 
硼:P 
當雜化之后,P端就會有很多電子空穴,N端會多出很多自由電子,在PN交界的地方,N端電子會自動移動到P端,形成一個耗盡區(qū),耗盡區(qū)的電壓為0.7V,所以大多5V的芯片低電壓為0.2V,如果超過0.7V則視為高電壓 

如果加入正電壓會使耗盡區(qū)擴大,造成正向偏壓,如果加入反向電壓,大于耗盡區(qū)0.7V電壓的時候,電子從N極向P極移動沒有任何障礙。 
繪出曲線,橫坐標是電源電壓,縱坐標是電流,負向電壓的時候幾乎沒有電流,負向電壓特別大的時候會擊穿,正向電壓大于0.7V的時候會很快獲得很大的電流。 

二極管的這一特性可以做一個橋式整流電路 



三極管三極管就是二極管的升級,例如NPN型三極管 
這樣在NP的交界處就會形成兩個耗盡區(qū), 
可以看成兩個二極管背靠背相連,不管電源處于哪個狀態(tài)總有一個二極管處于反向加壓的狀態(tài),不導通。但是如果中間加一個電源(第二個電源),大量電子會從P端出來,通過電源到達N端形成通路 
形成通路后,大量電子會到P端,形成反向偏壓, 如果整體來看,P端非常的窄,并不會存儲大量電子,大量電子在第一個電源的驅動下回到電源,形成電流,因為第一個電源的電壓比較大,驅動力比較大,第二個電源電壓比較小,驅動力比較小 

這種現(xiàn)象簡而言之就是 一個小電流被放大成一個大電流, 一個斷路變成一個通路
這種晶體管叫雙極結晶體管, 
晶體管有兩種工作方式: 通過電流,將一個小電流放大成大電流,

通過電壓,只要基極和發(fā)射機有電勢差,集電極和發(fā)射極就會產(chǎn)生大電流,這種又叫場效應管



雙極結型晶體管做的放大電路 


門電路晶體管的基本原理已經(jīng)知道了,門電路就是基于三極管構成相關電路 非門電路: 

與門電路: 
或門電路 
異或門 

鎖存器鎖存器用來做寄存器 將或門改造一下就可以就是SR鎖存器 
SR鎖存器 


再次進階D鎖存器,D鎖存器是構建寄存器的基礎,本計算機種所有的寄存器都是由D鎖存器構造 

觸發(fā)器觸發(fā)器是為了獲取極短時間內的上升沿 第一種方法: 


從0變成1的時候,非門需要幾個納秒的時候才能將狀態(tài)轉過來,所以在非常短的時候內會出現(xiàn)都為1,這個時候與門輸出1,然后非門后的狀態(tài)0輸入,導致輸出變?yōu)?,這樣輸出只有幾個納秒是1。 第二種方法: 通過電容來實現(xiàn) 
電容和電阻,當信號來的時候電容充電,獲得輸出1,當幾十納秒后,電容充滿電,信號就變成0了 
計算時間 
D觸發(fā)器,就是將之前的SR鎖存器的Enable改造一下 

SR觸發(fā)器: 


SR觸發(fā)器,在SR都為1的時候,處于一種無效的狀態(tài),沒有任何輸出。當SR變成0的時候,誰慢一點誰就會被觸發(fā)。這是一種隨機狀態(tài)。 為了解決這個問題: 
第一種情況 JK都為0,這是一種隨機狀態(tài),也成為不確定狀態(tài) 

第二種狀態(tài)K=1,J=0的時候,處于reset狀態(tài),Q=0,反Q=1 
第三種狀態(tài)K=0,J=1的時候,處于set狀態(tài)Q=1,反Q=0 
最有意思的是第四種狀態(tài)K=1,J=1的時候,信號會發(fā)生一次對調 
這樣會出現(xiàn)問題 在這個脈沖內做了很多次轉換,也就是只要兩個輸入都是高電平,這個轉換就一直持續(xù)。 這種情況叫做搶先。 所以發(fā)現(xiàn)這個根本原因出現(xiàn)這個脈沖電路上,這個上升沿時間太多了。如果時間控制在100ns的時間內就可以只完成1次轉換。 把1K電阻換成100電阻,已經(jīng)控制了100ns的時間,發(fā)現(xiàn)還是不行 

因為信號有抖動,邊緣探測不銳利 用主從JK觸發(fā)器來解決這個問題 
高電壓的時候使第一個鎖存器工作,在低電壓的時候使第二個鎖存器工作。 這樣就完全可以避免之前的問題 可以看到這有兩個鎖存器,這兩個鎖存器不可能同時工作,clock高電位第一個鎖存器工作,clock低電位第二個鎖存器工作,主從對應的RS正好相反 如果高電壓,主鎖存器是SET,到低電壓的時候從鎖存器就是reset, 如果都是1的時候,那么主鎖存器執(zhí)行的操作是由從鎖存器的狀態(tài)決定的,而從鎖存器的狀態(tài)正好與主鎖存器狀態(tài)相反 這樣當一個脈沖來的時候,set和reset會執(zhí)行一次交換。 基本模塊計算機需要的模塊:1.主脈沖,2.計數(shù)器,3.計數(shù)器寄存器,4.寄存器A,5.寄存器B,6.ROM,7.指令寄存器,9.顯示模塊,9.控制模塊,10.標志位寄存器。 主脈沖模塊
主脈沖主脈沖使用555芯片 


時許分析 開始的時候,沒有上電 
開始上電的時候 

通過電容放電和充電的時間來控制方波的占空比, 外界的電容和電阻決定了方波的長度 
通過公式來計算 總的時間是0.139S 
在5號引腳加入一個0.01uf的電容接地,可以降噪 


當有信號的時候,一堆晶體管需要獲取更多的電量,這個時候就會從電源端拉出更多的電流,就會形成電路中非常常見的過充的現(xiàn)象。 電線也會產(chǎn)生一些阻抗,也會阻止電流的變化,所以這個電壓就會跳上去, 直接的辦法給電路接一個非常短的線路 
給正極和負極加一個電容,在電路需要電流的時候給電路提供更多的電流。 
在四號引腳接入一個5V高電平,防止Reset鎖存器,這樣就不存在誤操作。 
調整時鐘的速度,把100K換成可變電阻 
單步脈沖為了更好的測試電路,需要有一個單步脈沖,類似程序的單步執(zhí)行,按鈕按一下給一個脈沖 單步脈沖的意思是按1下產(chǎn)生1個脈沖,用555芯片來消除按鈕的抖動 555芯片,消除抖動電路,可以控制燈亮的時間 
電阻是1M,電容是2uf,0.1uf,0.1S時間間隔,這邊要注意在電路不同的狀態(tài),6,7的電壓應該是5V, 

穩(wěn)態(tài)和單穩(wěn)態(tài) 

切換電路
將兩個狀態(tài)的輸出型號添加到一個開關中,切換開關可以切換2個狀態(tài)、 但是開關會有一個新的問題,當切換的時候有一個延遲的問題,這個時候需要一個新的555芯片來解決這個問題,其實是用到555芯片內的SR鎖存器 
開關有一個特性叫做先斷后連, 
這個電路主要是解決開關彈跳的問題, 將這三個電路合并起來 
這樣就可以在自動和手動切換 
HLT作用是關閉定時器,接入低電平, 74LS04有6個非門 這樣一個電路需要用到三種芯片效率非常低,可以把電路給改一下 
跟之前的效果一樣,只用到了與非門 最終效果 


總線BUS的工作原理: 
這8條線沒有回路,可以跑1bit的數(shù)據(jù)這非常的靈活 Load:表示數(shù)據(jù)可以放到芯片中 Enable:表示數(shù)據(jù)從芯片放到Bus中 
這里面邊上的藍色線就是控制線,可以看到這個控制線就是Clock,所有的部件同步Load 
enable線來控制芯片將數(shù)據(jù)寫到總線中,這需要同時只有1個芯片進行這樣的操作,不然就會造成混亂 三態(tài)門在總線中有一個非常重要的事情,就是同一時間只有一個部件向總線中輸出數(shù)據(jù),每個部件的輸出端其實就是芯片內部門電路的輸出端。 
通常都會用兩個這樣輸出, 
三態(tài)門:,0,1,和斷路三種狀態(tài) 

74LS245 8路三態(tài)門芯片 
每個模塊都接入一個Enable線,每個模塊都接入Bus中, 同1時刻只有一個模塊Enable線為true,就可以保證只有該數(shù)據(jù)寫入到總線中。 當load為高電平的時候,它會在下一個時鐘周期高電平到來的時候將總線中數(shù)據(jù)讀取到模塊中。 所有需要寫入總線的模塊都需要該245模塊 寄存器整個計算機需要8位寄存器A,8位寄存器B,4位計數(shù)器寄存器,8位指令寄存器 寄存器的構造是使用D鎖存器,有高信號就可以保存住高信號 可以通過D觸發(fā)器來構建寄存器,同時加入一個Load控制,下面這種是Load為0的情況,輸出是什么輸入還是什么 
Load為1的情況,輸入什么輸出還是什么 
74LS74內有2個D觸發(fā)器 


通過搭建上面的電路可以實現(xiàn) 
數(shù)據(jù)不可以直接輸出到總線中,需要在輸出中加入74LS245 三態(tài)門 74LS173由4個D觸發(fā)器,包含Load和Enable 

因為需要外接小燈查看寄存器中的值,所以173芯片中的三態(tài)門一直處于打開狀態(tài),外界一個三態(tài)門來控制輸出。 




本計算機種需要用到三個相同原理的寄存器模塊,寄存器A,寄存器B,指令寄存器。 指令寄存器就是與寄存器A的方向相反 
ALU補碼編碼方式: 用最高位表示符號位,這樣-5和5相加得2是不對的 
另一種編碼方式:得1補碼:用反碼表示負數(shù) 

-5和5相加得到都是1,這就是得1補碼的原因 
比正確的結果少1;如果將結果加1就可以得到正確的結果 第三種編碼方式:得2補碼,反碼+1表示負數(shù) 

每一位都有含義 
取反+1; 補碼:取反+1表示負數(shù),上面為解釋為什么取反+1比較好。 全加器1位加法運算,一共就8中情況,前四種不考慮前面的進位,后四種情況考慮一下之前的進位 結果有兩位,第一位表示結算結果,第二位表示是否有進位 
第一位前四種情況可以用異或門來表示 0,0 =》0 0,1=》1 1,0=》1 1,1=》0 
第二位前四種情況可以用與門來表示 0,0=》0 0,1=》0 1,0=》0 1,1=》1 
進位4種情況:可以發(fā)現(xiàn)第一位進位四種情況正好和之前的相反 那么進位的第一位變化的四種情況就可以直接在之前的結果后面加如一個異或門。異或門可以控制結果取反, 


有進位的第二位四種情況,不僅要考慮本身有進位還要考慮第一位出現(xiàn)進位的情況 
將進位情況求和 
這個電路叫做1位全加器 
每個全加器需要2個異或門,2個與門,1一個或門 1個異或門需要2個晶體管 1個與門需要2個晶體管 1個或門需要2個晶體管 那么可以總結出1個全加器需要10個晶體管,也就是10個三極管,也就是10個晶體管可以計算出1位計算器。 4個全加器組合成4位加法器 需要的材料和電路圖 
74LS86內有4個異或門芯片 74LS08內有4個與門芯片 74LS32內有4個或門 2個四位撥叉開關 1個面包板 4個小燈顯示結果1個進位 
ALUArithmetic Logic Unit:算術邏輯單元 該模塊其實完全由全加器構成 用寄存器A和寄存器B,中間加入ALU邏輯電路,這樣該模塊就可以計算出寄存器A和寄存器B的求和或相減。 對寄存器中的數(shù)據(jù)進行操作 
通過之前的全加器來構建邏輯單元 , 如何做減法, 現(xiàn)在全加器可以實現(xiàn)加法,是否可以將被減數(shù)變成負數(shù)然后執(zhí)行加法運算 
通過異或門,當A為1的時候相當于取反,當A為0的時候原樣輸出 通過異或門獲取反碼 
4位加法器有一個進位,將這個1和控制器連接起來,如果如果控制器是減法的話,那正好需要進位 這樣就實現(xiàn)了一個數(shù)補碼加1的操作。 



中間的就是ALU 


先要進行測試,測試是有必要的, 如果出現(xiàn)故障需要先排除故障,先從最簡單的部分入手,然后慢慢縮小范圍。 先設置A寄存器是0,B寄存器是0 
然后讓B存器器是0,然后讓A每一位依次置1,查看是否有問題,發(fā)現(xiàn)問題然后跟蹤這條線, 然后讓A寄存器是0,然后B依次置1; 出現(xiàn)問題需要刨根問底將其找出來。 不要慌,從第一步開始的第一個異常,首先分析可能出現(xiàn)這個現(xiàn)象的原因,大多數(shù)情況下都想不出, 查看接線是否正常,接線正常后查看所有輸出輸入,特定的輸入產(chǎn)生特定的輸出,通過萬用表量輸入和輸出電壓。 
將ALU中產(chǎn)生的數(shù)據(jù)直連到總線中,每當有脈沖的時候,A寄存器從總線中讀取值,ALU從A中讀值,從B中讀值進行加操作,并將操作的結果放到總線中,1個脈沖實現(xiàn)加放到總線中讀取總線數(shù)據(jù)的操作。 ROM本計算機構建了16個字節(jié)的內存; 內存的構建有兩種方式, 1.直接通過D鎖存器構建 2.直接通過一個電容和一個晶體管構建,然后有一個電容不停刷新這個電容的數(shù)據(jù)。 
1word的寄存器,1個字節(jié)寄存器,輸入輸出,寫和讀 16個字節(jié) 
哪個字節(jié)的Enable開,哪個字節(jié)的數(shù)據(jù)就被讀出來, 這樣需要對16個字節(jié)進行編碼 第一步 需要對16個字節(jié)進行編碼,每個字節(jié)有8個D鎖存器,也就是128個D鎖存器 0-16這16個數(shù)字表示地址,也就是4個bit位,這樣一個數(shù)字代表一個字節(jié)。 地址譯碼單元直接輸出這個地址,地址譯碼單元怎么構造,首先需要有4個bit輸入,每個輸入有高低輸出,然后構建一個有5個輸入的與門,1位標識load,然后四位對應地址,那么就有16個5位輸入與門,代表16個地址 
這個地址電路應該在內存電路的前面,4個輸入就可以讓內存電路輸出該地址的數(shù)據(jù)。 74LS189就是一個內存芯片,是一個64bit的存儲器,有4個地址輸入,16個地址位每個地址位4個輸出,其使用的方式就是D寄存器的方式構建的內存 




因為這邊189的輸出都是低電位有效,所以需要74LS04非門進行反轉,最后接入一個245三態(tài)門輸出到總線中 地址線需要處理,需求是:實現(xiàn)從總線中讀取,或者手動設置。 通過4Bit寄存器來獲得輸入,地址寄存器。74LS173正好滿足條件 
地址輸入希望這個地址寄存器能切換模式手動模式和自動模式,自動模式是從總線中讀取地址,手動模式用撥碼開關來指定地址。 選擇電路
74LS157可以實現(xiàn)二選一電路 


對撥碼開關的控制,可以獲得1個明確0,1信號 

值輸入希望可以手動向內存中寫入值,同時也可以選擇從總線中讀入值。 又是一個選擇電路,但是這邊又8Bit輸入,所以就用了2塊74LS157芯片 


到這可以控制手動輸入地址和值的ROM就做好了 計數(shù)器一個計算機僅僅只有脈沖是不可能正常運行的,必須還要有可以指示程序運行的計數(shù)器,指示程序運行到 了哪一步。 當我們從計算機中運行程序,這些程序放在內存中,它是一條條指令,為了執(zhí)行這些指令需要從內存中讀取它,在這個8位計算器中需要從地址0開始執(zhí)行。先執(zhí)行地址0的指令,然后執(zhí)行地址1的指令,需要確定當前在哪個地址上執(zhí)行,所以我們需要程序計數(shù)器。 
在上面我們由JK觸發(fā)器構造了一個計數(shù)器,這個程序技術器也是由4位組成 ,指向下一條需要指向的指令,需要能從總線中讀取數(shù)據(jù) ,這樣可以跳轉到別的地址。 程序計數(shù)器的功能: 第一個CO就是程序控制器的輸出,把值放到總線中 第二個J就是jump,從總線中讀取數(shù)據(jù),只獲取4位數(shù)據(jù), 第三個CE就是控制,控制計數(shù)器開始計數(shù)和停止計數(shù)。不一定每個脈沖都需要計數(shù),當CE活動的時候,將計數(shù)器開始計數(shù) 二分電路怎么把脈沖變成明確的計數(shù)信號呢? 這就需要之前的基礎知識:主從觸發(fā)器 主從觸發(fā)器的特性,在一次脈沖來的時候會進行Q和反Q的切換,如果構建多個主從觸發(fā)器,將第一個主從觸發(fā)器的反Q接到下一個主從觸發(fā)器的Q,會發(fā)生什么呢? DM7476就是使用主從觸發(fā)器來構造了JK觸發(fā)器 
可以發(fā)現(xiàn)這個JK觸發(fā)器在下降沿的時候觸發(fā)。 

接了一個JK觸發(fā)器可以看的更清楚一些,在每個脈沖周期,JK觸發(fā)器交換了一次 
當去掉一個顯示的時候,可以發(fā)現(xiàn)這個Q亮到不亮再到亮用了2個脈沖周期 這個電路稱為二分電路,通過JK觸發(fā)器,將原來的主脈沖的周期擴大了一倍。 在原來二分電路的基礎上再加一個二分JK觸發(fā)器,把第一個觸發(fā)器的輸出接到下一個JK觸發(fā)器的輸入 
第二個JK的轉換速度是前一個的一半,是4倍的主脈沖周期 構建4個JK觸發(fā)器,每一個都是前一個的周期的一半 
這樣我們就獲得了一個2進制的計數(shù)器,可以從0計數(shù)到15, 計數(shù)器本計算機的計數(shù)器就是使用了這一原理構建,這邊我們使用74LS161作為計數(shù)器 
其有4個輸入,4個輸出,是否寫入控制線,CLock控制線,Enable輸入輸出控制線,清除控制線 這個芯片非常有用,它的Clock內部加了一個非門,這樣上升沿變成下降沿,我們的JK觸發(fā)器也是下降沿觸發(fā)器 

顯示共陰極和共陽極數(shù)碼管 


構建真值表
通過這個真值表可以獲取a這個值什么時候亮 


如果需要顯示真正的數(shù)據(jù),必須要建立一個真值表,將真值表轉化成電路,這樣的電路就是解析器, EEPROM可以替代計算機中任何的組合邏輯。 組合邏輯:任何一個狀態(tài)的輸入對應一個狀態(tài)的輸出 時序邏輯:寄存器,鎖存器,計數(shù)器,輸出不進取決于當前的狀態(tài)也取決于之前的狀態(tài)。 有許多種ROM芯片,這個芯片是只讀的,還有一種可以變成的只讀芯片的就叫做PROM,提供了一個空白的芯片,只能寫入一次,寫入之后就不能改變了。EPROM可以重復寫入,在紫外線的作用下可以擦除內部的數(shù)據(jù) 
EEPROM是電可擦寫存儲器,用電就可以擦除。 AT28C16可擦寫只讀存儲器,可以存2K個字節(jié) 
有兩種封裝形式,直插和貼片, 8條IO引腳,數(shù)據(jù)引腳 11條地址引線,接地線和電源 反CE,反OE和反WE 
需要給WE 一個100ns-1000ns的時間, 
用一個電容和一個電阻來實現(xiàn)。RC震蕩電路, 1nf,和680歐姆電阻。 

通過EEPROM來實現(xiàn)真值表,左邊是地址,右邊的值。 Arduino寫入數(shù)據(jù)

看以下Arduino Nano的引腳數(shù)根本不夠,因為地址線11根,數(shù)據(jù)線8根 需要另選一個方案來向EPROM中寫入數(shù)據(jù)。 通過一個引腳輸出地址,8根引腳輸出數(shù)據(jù),1根引腳怎么輸出數(shù)據(jù)呢 這邊用到了8個D觸發(fā)器,思路基本和計數(shù)器一樣,只不過計數(shù)的Enable線就是脈沖線,這樣脈沖來一次就+1; 這邊的enable線是通過按鈕輸入,按下為1不按為0 這邊用74LS74來構建,其有兩個D觸發(fā)器 

用4個74芯片的D觸發(fā)器輸出連接到輸入,構建了一個8位寄存器來獲得8個連續(xù)的輸入。 當脈沖來的時候按鈕按下為輸入1,不按為輸入0 
Arduino一根數(shù)據(jù)線輸入數(shù)據(jù)問題解決就可以運用上面的思路,找到74HC595這個芯片 

那么現(xiàn)在只需要3根線來控制數(shù)據(jù)輸入,數(shù)據(jù)輸入線DS,時鐘線SH_CP,和控制輸出線ST_CP 
地址線有11條,所以需要2個595芯片 

這樣我們的Arduino寫入EEPRom模塊就做好了 現(xiàn)在來寫程序吧; //定義好各個引腳的標志
#define SHIFT_DATA 2
#define SHIFT_CLK 3
#define SHIFT_LATCH 4
#define EEPROM_D0 5
#define EEPROM_D7 12
#define WRITE_EN 13
/*
* 使用移位寄存器將地址數(shù)據(jù)輸出
*/
void setAddress(int address, bool outputEnable) {
shiftOut(SHIFT_DATA, SHIFT_CLK, MSBFIRST, (address >> 8) | (outputEnable ? 0x00 : 0x80));//將地址寫入到595中,高8位
shiftOut(SHIFT_DATA, SHIFT_CLK, MSBFIRST, address);//將地址寫入到595中,低8位
//設置595輸出地址
digitalWrite(SHIFT_LATCH, LOW);
digitalWrite(SHIFT_LATCH, HIGH);
digitalWrite(SHIFT_LATCH, LOW);
}
/*
* 從指定地址的EEPROM讀取一個字節(jié)
*/
byte readEEPROM(int address) {
for (int pin = EEPROM_D0; pin <= EEPROM_D7; pin++) {
pinMode(pin, INPUT);
}
setAddress(address, /*outputEnable*/ true);
byte data = 0;
for (int pin = EEPROM_D7; pin >= EEPROM_D0; pin--) {
data = (data << 1) + digitalRead(pin);
}
return data;
}
/*
* 將字節(jié)寫入指定地址的EEPROM。
*/
void writeEEPROM(int address, byte data) {
setAddress(address, /*outputEnable*/ false);//設置地址到595中并輸出地址
for (int pin = EEPROM_D0; pin <= EEPROM_D7; pin++) {
pinMode(pin, OUTPUT);//設置引腳
}
for (int pin = EEPROM_D0; pin <= EEPROM_D7; pin++) {
digitalWrite(pin, data & 1);//將數(shù)據(jù)寫到引腳中,只取最后一位
data = data >> 1;
}
digitalWrite(WRITE_EN, LOW);//寫入EMROM
delayMicroseconds(1);
digitalWrite(WRITE_EN, HIGH);
delay(10);
}
/*
* 讀取EEPROM的內容并將其打印到串行監(jiān)視器。
*/
void printContents() {
for (int base = 0; base <= 255; base += 16) {
byte data[16];
for (int offset = 0; offset <= 15; offset++) {
data[offset] = readEEPROM(base + offset);
}
char buf[80];
sprintf(buf, "%03x: %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x",
base, data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7],
data[8], data[9], data[10], data[11], data[12], data[13], data[14], data[15]);
Serial.println(buf);
}
}
// 用于共陽極7段顯示的4位十六進制解碼器
//byte data[] = { 0x81, 0xcf, 0x92, 0x86, 0xcc, 0xa4, 0xa0, 0x8f, 0x80, 0x84, 0x88, 0xe0, 0xb1, 0xc2, 0xb0, 0xb8 };
// 用于共陰極7段顯示的4位十六進制解碼器
byte data[] = { 0x7e, 0x30, 0x6d, 0x79, 0x33, 0x5b, 0x5f, 0x70, 0x7f, 0x7b, 0x77, 0x1f, 0x4e, 0x3d, 0x4f, 0x47 };
void setup() {
// put your setup code here, to run once:
pinMode(SHIFT_DATA, OUTPUT);
pinMode(SHIFT_CLK, OUTPUT);
pinMode(SHIFT_LATCH, OUTPUT);
digitalWrite(WRITE_EN, HIGH);//寫低電平有效
pinMode(WRITE_EN, OUTPUT);
Serial.begin(57600);
// Erase entire EEPROM
Serial.print("擦除 EEPROM");
for (int address = 0; address <= 2047; address ++) {
writeEEPROM(address, 0x55);
if (address % 64 == 0) {
writeEEPROM(address, 0x55);
Serial.print(".");
}
}
Serial.println(" done");
// 寫入數(shù)據(jù)
Serial.print("編輯 EEPROM");
for (int address = 0; address < sizeof(data); address ++ ) {//sizeof(data)=16
writeEEPROM(address, data[address]);
if (address % 64 == 0) {//數(shù)據(jù)一共64Bit,
writeEEPROM(address, data[address]);
Serial.print(".");
}
}
Serial.println(" 完成");
// 讀EEPROM中的值
Serial.println("讀.... EEPROM");
printContents();
}
void loop() {
// put your main code here, to run repeatedly:
} 


重點看一下 /*
* 使用移位寄存器將地址數(shù)據(jù)輸出
*/
void setAddress(int address, bool outputEnable) {
shiftOut(SHIFT_DATA, SHIFT_CLK, MSBFIRST, (address >> 8) | (outputEnable ? 0x00 : 0x80));
shiftOut(SHIFT_DATA, SHIFT_CLK, MSBFIRST, address);
digitalWrite(SHIFT_LATCH, LOW);
digitalWrite(SHIFT_LATCH, HIGH);
digitalWrite(SHIFT_LATCH, LOW);
} shiftout:一次將數(shù)據(jù)字節(jié)移出一位。從最高(即最左邊)或最低(最右邊)有效位開始。每個位依次寫入數(shù)據(jù)引腳,然后向時鐘引腳脈沖(先變高,然后變低),以指示該位可用。 MSBFIRST:最高位有效在先 至此EEPEOM的真值表寫入完畢,我們只使用了16個地址的數(shù)據(jù),真是極大的浪費呢 如何顯示數(shù)據(jù)第一種方案是用三個EEPROM來表示百,十,個三個位的數(shù)據(jù) 
這種方案顯然造成EEPROM的極大浪費 第二種方案:復雜一點點,將選擇這種方案,就是順序讓每一個數(shù)碼管顯示,當速度非常塊的時候,數(shù)碼管看上去就像一直顯示的一樣,怎么才能讓數(shù)碼管順序顯示 這邊我們就用到了上面計數(shù)器的原理,構建一個單獨的顯示脈沖,然后通過2個JK觸發(fā)器就可以獲得4種不同的編碼狀態(tài),00,01,10,11 這邊用74LS76,其正好有兩個JK觸發(fā)器 
同時需要將00,01,10,11進行解碼,將其變成0001,0010,0100,1000,這樣將這四條線連接到4個數(shù)碼管,數(shù)碼管就會順序顯示,這邊我們用到了74LS139 

可以看到該編碼器完美滿足我們的需求。 
構建公用真值表 
就是用A10,A9,A8,來表示個位十位百位 這樣真值表就比較復雜了 舉個例子321這個值的真值表: 
改進程序 #define SHIFT_DATA 2
#define SHIFT_CLK 3
#define SHIFT_LATCH 4
#define EEPROM_D0 5
#define EEPROM_D7 12
#define WRITE_EN 13
/*
使用移位寄存器輸出地址位和outputEnable信號。
*/
void setAddress(int address, bool outputEnable) {
shiftOut(SHIFT_DATA, SHIFT_CLK, MSBFIRST, (address >> 8) | (outputEnable ? 0x00 : 0x80));
shiftOut(SHIFT_DATA, SHIFT_CLK, MSBFIRST, address);
digitalWrite(SHIFT_LATCH, LOW);
digitalWrite(SHIFT_LATCH, HIGH);
digitalWrite(SHIFT_LATCH, LOW);
}
/*
從指定地址的EEPROM讀取一個字節(jié)。
*/
byte readEEPROM(int address) {
for (int pin = EEPROM_D0; pin <= EEPROM_D7; pin += 1) {
pinMode(pin, INPUT);
}
setAddress(address, /*outputEnable*/ true);
byte data = 0;
for (int pin = EEPROM_D7; pin >= EEPROM_D0; pin -= 1) {
data = (data << 1) + digitalRead(pin);
}
return data;
}
/*
將字節(jié)寫入指定地址的EEPROM。
*/
void writeEEPROM(int address, byte data) {
setAddress(address, /*outputEnable*/ false);
for (int pin = EEPROM_D0; pin <= EEPROM_D7; pin += 1) {
pinMode(pin, OUTPUT);
}
for (int pin = EEPROM_D0; pin <= EEPROM_D7; pin += 1) {
digitalWrite(pin, data & 1);
data = data >> 1;
}
digitalWrite(WRITE_EN, LOW);
delayMicroseconds(1);
digitalWrite(WRITE_EN, HIGH);
delay(10);
}
/*
讀取EEPROM的內容并將其打印到串行監(jiān)視器。
*/
void printContents() {
for (int base = 0; base <= 255; base += 16) {
byte data[16];
for (int offset = 0; offset <= 15; offset += 1) {
data[offset] = readEEPROM(base + offset);
}
char buf[80];
sprintf(buf, "%03x: %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x",
base, data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7],
data[8], data[9], data[10], data[11], data[12], data[13], data[14], data[15]);
Serial.println(buf);
}
}
void setup() {
// put your setup code here, to run once:
pinMode(SHIFT_DATA, OUTPUT);
pinMode(SHIFT_CLK, OUTPUT);
pinMode(SHIFT_LATCH, OUTPUT);
digitalWrite(WRITE_EN, HIGH);
pinMode(WRITE_EN, OUTPUT);
Serial.begin(57600);
// Bit patterns for the digits 0..9
byte digits[] = { 0x7e, 0x30, 0x6d, 0x79, 0x33, 0x5b, 0x5f, 0x70, 0x7f, 0x7b };
writeEEPROM(0,0);
Serial.println("寫入個位 ");
for (int value = 0; value <= 255; value += 1) {
writeEEPROM(value, digits[value % 10]);
}
Serial.println("寫入十位");
for (int value = 0; value <= 255; value += 1) {
writeEEPROM(value + 256, digits[(value / 10) % 10]);
}
Serial.println("寫入百位");
for (int value = 0; value <= 255; value += 1) {
writeEEPROM(value + 512, digits[(value / 100) % 10]);
}
Serial.println("寫入符號位");
for (int value = 0; value <= 255; value += 1) {
writeEEPROM(value + 768, 0);
}
Serial.println("寫入個位 (后半部)");
for (int value = -128; value <= 127; value += 1) {
writeEEPROM((byte)value + 1024, digits[abs(value) % 10]);
}
Serial.println("寫入十位 (后半部)");
for (int value = -128; value <= 127; value += 1) {
writeEEPROM((byte)value + 1280, digits[abs(value / 10) % 10]);
}
Serial.println("寫入百位 (后半部)");
for (int value = -128; value <= 127; value += 1) {
writeEEPROM((byte)value + 1536, digits[abs(value / 100) % 10]);
}
Serial.println("寫入符號位 (后半部)");
for (int value = -128; value <= 127; value += 1) {
if (value < 0) {
writeEEPROM((byte)value + 1792, 0x01);
} else {
writeEEPROM((byte)value + 1792, 0);
}
}
// Read and print out the contents of the EERPROM
Serial.println("讀..... EEPROM");
printContents();
}
void loop() {
// put your main code here, to run repeatedly:
} 控制數(shù)據(jù)顯示現(xiàn)在數(shù)據(jù)顯示的問題已經(jīng)解決了,下面怎么控制其從Bus種讀取數(shù)據(jù)顯示,這邊肯定不能直接顯示總線的數(shù)據(jù),因為總線的數(shù)據(jù)是不斷變化的,所以需要一個8bit寄存器控制讀取總線中的數(shù)據(jù),然后控制其顯示, 這邊使用不同的芯片74LS273 


這邊有8個輸入,8個輸出,一個脈沖引腳,一個重置線 這邊有一個問題,這個芯片沒有IEnable線,如果主脈沖接進來,每次脈沖變化都會讀取值,這個問題可以通過一個與門來解決,通過與門接入脈沖和控制線,控制線為1的時候,脈沖變化才有效 
做個簡單的總結,將已經(jīng)做好的部件連接到總線 

控制器現(xiàn)在這個部件就缺少一個控制邏輯就可以正常工作了,來看看有多少個控制線 
目前有14根控制線,還要做一個HTL停機線,在主脈沖中 
如何控制現(xiàn)在我們寫一個程序,來手動運行這個程序 LDA 14 //將內存地址14中內容讀取到A寄存器
ADD 15 //把內存地址15中內容與A寄存器中值相加放到寄存器
OUT //把A寄存器中的內容放到輸出模塊 這會很奇怪,這些命令是哪里來的,在之前的計算機構造中沒有構造任何與命令有關的內容,實際上這些是我們自己定義的,你可以定義任何想做的命令,這是不是非???。 下面我們來定義 LDA:0001 ADD:0010 OUT:1110 那么程序就被翻譯成機器語言了 LADA 14 // 0001 1110
ADD 15 // 0010 1111
OUT // 1110 xxxx 這個程序一共三行,我們在加上行號 LADA 14 // 0000 0001 1110
ADD 15 // 0001 0010 1111
OUT // 0010 1110 xxxx 所以想要運行這個程序我們需要將值寫到ROM中,進入手動模式輸入ROM值 地址 | 值 |
---|
0000 | 0001 1110 | 0001 | 0010 1111 | 0010 | 1110 0000 |
|
| 1110 | 0001 1100(28) | 1111 | 0000 1110(14) |
這個代碼翻譯成高級語言就是28+14=? 現(xiàn)在我們需要手動控制程序的運行 首先將指令從內存中讀出來放到指令寄存器中,指令寄存器告訴我們數(shù)據(jù)將怎么解析。 取址周期就是將指令從內存中取出來放到指令寄存器中。 計算器中所有的組件都是由程序計數(shù)器來協(xié)調,計數(shù)器記錄了當前執(zhí)行到哪條指令。計數(shù)器是從0開始的。 一開始0000 首先將計數(shù)器的值放到內存地址寄存器中, 
可以看到這邊計數(shù)器和內存地址寄存器都是0, 而0地址上ROM的值就是0001 1110 計數(shù)器輸出+ CO 內存地址寄存器輸入+ MI 給一個脈沖
將內存地址中的值放到指令寄存器中 
可以看到ROM中數(shù)據(jù)給了指令寄存器 將內存輸出打開+ RO 指令寄存器輸入+ II 給一個脈沖
這兩步操作取址的操作就完成了,要執(zhí)行下一個代碼,計數(shù)器加一 計數(shù)器加1 CE+ 
計數(shù)器加一 給一個脈沖,計數(shù)器加一變成0001
執(zhí)行任何的代碼都需要上面的三步,上面三步又稱取址周期,其實就是將計數(shù)器對應的ROM中的值放到指令寄存器中,然后計數(shù)器加1。下面來解析命令和執(zhí)行命令,這才是與命令相關的控制邏輯 LDA指令 LDA 14 ,控制器看到指令寄存器的高四位是0001,就知道這是對應LDA的操作,就會執(zhí)行LDA的控制,這是由控制器完成的,我們稍后構建它,現(xiàn)在還是手動操作,假設自己的控制器 將指令寄存器后4BIt 輸入到內存地址寄存器中 ,以獲得內存地址14中的內容 
因為指令寄存器只有第四位接入到總線中,所以地址寄存器獲取第四位的地址數(shù)據(jù),ROM中顯示了該地址中的值,也就是0001 1100其值為28 指令寄存器輸出 + IO 內存地址寄存器輸入 + MI 給一個脈沖
將內存地址中的值輸出到寄存器A 
可以看到內存中的值給了寄存器A,同時因為寄存器B位0,ALU就顯示了A+0的值, 至此完成了LDA的命令,將地址14中的值放到寄存器A中。下面執(zhí)行第二個命令 內存輸出+ RO 寄存器A輸入+ AI 給一個脈沖
ADD指令解析 ADD 15, 要執(zhí)行到該指令現(xiàn)到取到該指令,跟之前的三部取址周期一樣 
指令計數(shù)器的值給地址寄存器 
內存地址中的值給指令寄存器 
計數(shù)器加1,這個時候控制器通過指令寄存器高四位0010分析出執(zhí)行ADD控制 將指令寄存器后4bit輸入到內存地址寄存器中 
將指令寄存器中的低四位放到地址寄存器中,這個時候ROM顯示該地址中的值 0000 1110 其值位14 指令寄存器輸出+ IO 內存地址寄存器輸入+ MI 給一個脈沖
將內存地址15中的值放到B寄存器中,ALU會自動計算出值 
可以看到ALU自動算出求和的值 內存輸出+ RO 寄存器B輸入+ BI 給一個脈沖
將ALU中的值輸出到寄存器A中 
這邊寄存器A獲得ALU的值,同時ALU更新了,這邊非???,鎖操作只發(fā)生在脈沖的上升沿, ALU的輸出 +EO 寄存器A輸入+AI 給一個脈沖
OUT命令 OUT,前3步是一樣的 


將A寄存器中的值顯示出來 
將A寄存器輸出+ AO output寄存器輸入 OI 給一個脈沖
到這程序執(zhí)行完了 總結一下 
這些小的指令稱為微指令,這些微指令的前三步都是相同的,之后的操作是不同的, 所以需要控制位對每個指令構造控制邏輯 反正我控制位按照一定的順序排序 每一種微指令對應一種控制序列。 真正的微指令會占用余下的時間片,實際上我們需要一個獨立的計數(shù)器,所以需要一個獨立的計數(shù)器 上面通過手動的方式設置控制位,然后手動發(fā)送一次主脈沖,在兩個主脈沖之間改變它的控制位,,所以我們實際上還需要另一個脈沖來控制 ,這邊可以用主脈沖的倒轉,通過非門開獲得另一個脈沖 這邊還要將各個指令分步,才能夠讓控制器知道執(zhí)行到了哪一步,可以看到每個指令最多5步,有些步數(shù)可以合并就合并了。從T0-T4,而有些指令用不到4步,那么多余的步數(shù)計算機什么也不做就浪費了。這是無法避免的 
現(xiàn)在脈沖有了,步數(shù)分解有了,需要將脈沖變成步數(shù),這和程序計數(shù)器是一樣的,使用74LS161,這是一個四位的計數(shù)器, 


計數(shù)器有了,現(xiàn)在要將計數(shù)器解碼,這邊用到了74LS138芯片, 

可以看到其轉換成明確信號,這邊和顯示部分用到的139解碼是一樣的邏輯 

這邊我們可以可以清晰的看到程序走到了哪個時間片,哪一步 下面我們構建非??岬氖虑椋簿褪强刂破鞯恼嬷当?/p> 
第一個取址,可以看到前兩步, 第二個LDA用了剩余的三步,最后一步什么也沒做。 第三個ADD也是三部 
用兩個28C16就可以完成其組合邏輯,其有11條地址線,8個輸出線。 
將真值表輸入到28C16中就可以完成控制 Reset
這邊如果程序執(zhí)行完成,需要將所有的寄存器清空,這邊我們構建這樣一個reset電路用來一個74LS00來構建 
將reset和~reset接到所有的寄存器 
到目前為止,計算機的主體部分就做好了 Arduino寫入指令Arduino的接線方式和之前的顯示解碼器的方式相同,這邊就不過多說了。 直接上程序 #define SHIFT_DATA 2
#define SHIFT_CLK 3
#define SHIFT_LATCH 4
#define EEPROM_D0 5
#define EEPROM_D7 12
#define WRITE_EN 13
#define HLT 0b1000000000000000 // Halt clock HLT信號
#define MI 0b0100000000000000 // Memory address register in 內存地址輸入
#define RI 0b0010000000000000 // RAM data in 內存數(shù)據(jù)輸入
#define RO 0b0001000000000000 // RAM data out 內存數(shù)據(jù)輸出
#define IO 0b0000100000000000 // Instruction register out 指令寄存器輸出
#define II 0b0000010000000000 // Instruction register in 指令寄存器輸入
#define AI 0b0000001000000000 // A register in A寄存器輸入
#define AO 0b0000000100000000 // A register out A寄存器輸出
#define EO 0b0000000010000000 // ALU out ALU輸出
#define SU 0b0000000001000000 // ALU subtract 減法
#define BI 0b0000000000100000 // B register in B寄存器輸入
#define OI 0b0000000000010000 // Output register in 輸出寄存器輸入
#define CE 0b0000000000001000 // Program counter enable 程序計數(shù)允許
#define CO 0b0000000000000100 // Program counter out 程序計數(shù)器輸出
#define J 0b0000000000000010 // Jump (program counter in) 程序計數(shù)器輸入(JUMP)
uint16_t data[] = { // 列是步數(shù),行是不同的指令
MI|CO, RO|II|CE, 0, 0, 0, 0, 0, 0, // 0000 - NOP
MI|CO, RO|II|CE, IO|MI, RO|AI, 0, 0, 0, 0, // 0001 - LDA 加載
MI|CO, RO|II|CE, IO|MI, RO|BI, EO|AI, 0, 0, 0, // 0010 - ADD 加法
MI|CO, RO|II|CE, IO|MI, RO|BI, EO|AI|SU, 0, 0, 0, // 0011 - SUB 減法
MI|CO, RO|II|CE, IO|MI, AO|RI, 0, 0, 0, 0, // 0100 - STA 將寄存器A中值寫入ROM中
MI|CO, RO|II|CE, IO|AI, 0, 0, 0, 0, 0, // 0101 - LDI 將指令寄存器中值寫入寄存器A
MI|CO, RO|II|CE, IO|J, 0, 0, 0, 0, 0, // 0110 - JMP 跳轉到指令寄存器第四位的計數(shù)
MI|CO, RO|II|CE, 0, 0, 0, 0, 0, 0, // 0111
MI|CO, RO|II|CE, 0, 0, 0, 0, 0, 0, // 1000
MI|CO, RO|II|CE, 0, 0, 0, 0, 0, 0, // 1001
MI|CO, RO|II|CE, 0, 0, 0, 0, 0, 0, // 1010
MI|CO, RO|II|CE, 0, 0, 0, 0, 0, 0, // 1011
MI|CO, RO|II|CE, 0, 0, 0, 0, 0, 0, // 1100
MI|CO, RO|II|CE, 0, 0, 0, 0, 0, 0, // 1101
MI|CO, RO|II|CE, AO|OI, 0, 0, 0, 0, 0, // 1110 - OUT 輸出
MI|CO, RO|II|CE, HLT, 0, 0, 0, 0, 0, // 1111 - HLT 停機
};
/*
*使用移位寄存器輸出地址位和outputEnable信號。
*/
void setAddress(int address, bool outputEnable) {
shiftOut(SHIFT_DATA, SHIFT_CLK, MSBFIRST, (address >> 8) | (outputEnable ? 0x00 : 0x80));
shiftOut(SHIFT_DATA, SHIFT_CLK, MSBFIRST, address);
digitalWrite(SHIFT_LATCH, LOW);
digitalWrite(SHIFT_LATCH, HIGH);
digitalWrite(SHIFT_LATCH, LOW);
}
/*
* 從指定地址的EEPROM讀取一個字節(jié)。
*/
byte readEEPROM(int address) {
for (int pin = EEPROM_D0; pin <= EEPROM_D7; pin += 1) {
pinMode(pin, INPUT);
}
setAddress(address, /*outputEnable*/ true);
byte data = 0;
for (int pin = EEPROM_D7; pin >= EEPROM_D0; pin -= 1) {
data = (data << 1) + digitalRead(pin);
}
return data;
}
/*
* 將字節(jié)寫入指定地址的EEPROM。
*/
void writeEEPROM(int address, byte data) {
setAddress(address, /*outputEnable*/ false);//設置地址
for (int pin = EEPROM_D0; pin <= EEPROM_D7; pin += 1) {
pinMode(pin, OUTPUT);//設置數(shù)據(jù)輸出引腳
}
for (int pin = EEPROM_D0; pin <= EEPROM_D7; pin += 1) {
digitalWrite(pin, data & 1);//每個數(shù)據(jù)引腳賦值
data = data >> 1;
}
digitalWrite(WRITE_EN, LOW);//設置脈沖
delayMicroseconds(1);
digitalWrite(WRITE_EN, HIGH);
delay(10);
}
/*
* 讀取EEPROM的內容并將其打印到串行監(jiān)視器。
*/
void printContents() {
for (int base = 0; base <= 255; base += 16) {
byte data[16];
for (int offset = 0; offset <= 15; offset += 1) {
data[offset] = readEEPROM(base + offset);
}
char buf[80];
sprintf(buf, "%03x: %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x",
base, data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7],
data[8], data[9], data[10], data[11], data[12], data[13], data[14], data[15]);
Serial.println(buf);
}
}
void setup() {
// put your setup code here, to run once:
pinMode(SHIFT_DATA, OUTPUT);
pinMode(SHIFT_CLK, OUTPUT);
pinMode(SHIFT_LATCH, OUTPUT);
digitalWrite(WRITE_EN, HIGH);
pinMode(WRITE_EN, OUTPUT);
Serial.begin(57600);
// 寫數(shù)據(jù)
Serial.print("寫 EEPROM");
writeEEPROM(0, 0);
// 將微碼的8個高位寫到EEPROM的前128個字節(jié)中
for (int address = 0; address < sizeof(data)/sizeof(data[0]); address += 1) {
writeEEPROM(address, data[address] >> 8);
if (address % 64 == 0) {
writeEEPROM(address, data[address] >> 8);
Serial.print(".");
}
}
// 將微碼的8個低位寫到EEPROM的前128個字節(jié)中
for (int address = 0; address < sizeof(data)/sizeof(data[0]); address += 1) {
writeEEPROM(address + 128, data[address]);
if (address % 64 == 0) {
writeEEPROM(address + 128, data[address]);
Serial.print(".");
}
}
Serial.println(" done");
// 讀并打印出EERPROM的內容
Serial.println("讀 EEPROM");
printContents();
}
void loop() {
// put your main code here, to run repeatedly:
} 添加了更多的指令 ,SUB,STA,LDI,JMP 這時候計算機可以做更多的功能了。 標志跳轉現(xiàn)在討論一個問題: 這是不是計算機這是不是計算機,還只是一個計算器 這個計算機的頻率只有300HZ左右 是否需要乘法,指數(shù),對數(shù),三角函數(shù)等指令,這些指令肯定是做不出來的,那么問題就回來了我們真正需要什么樣的指令,什么樣的指令才能稱為計算機,計算機是什么? 計算機: 可以完成任何的指令 可以完成任何的可計算的問題 什么是可計算的什么是不可計算的 這不是計算機性能的問題,是通過算法能完成的問題 那么問題就變成了我們需要完成什么樣的算法。 
這個問題在計算機早期圖靈就進行研究過 1936年 他寫了關于這個問題的一篇論文。這篇論文得出的結論是,他可以發(fā)明一種機器,可以完成任何計算序列 他是這樣描述的: 
有一個無限長的紙帶,上面有方格,有1和0兩種狀態(tài),有一個小旗子可以指向這些方格,小旗子有一個狀態(tài)A,一次只能移動一個。 有一個小旗子和其狀態(tài)的真值表 現(xiàn)在這個狀態(tài),A ,瀏覽狀態(tài)是1,就將1寫到袋子上,然后向左移動一格,自身的狀態(tài)變成C,就變成了下面的狀態(tài) 
根據(jù)這個真值表進行一直不停的循環(huán)做,一旦停止到Halt,紙帶上就是結果, 這個機器就能完成任何的數(shù)學算法。只需要設置好這個指令表就好了 實際上圖靈還提高一個更好的計算機,稱為通用計算機,這個機器上有一個指令表,是一個最基本的狀態(tài),其他計算機可以通過編碼的方法將算法映射到這個指令表上 
到這邊就知道了任何可計算的問題都可以變成一個可計算的序列 在同一個時期邱奇也思考了相同的問題 
他寫了一篇論文關于什么是計算能力的定義,從完全不同的角度切入這個問題,他提出新的數(shù)學系統(tǒng)稱為論的演算。 
這便有一些變量,有一些函數(shù),還有一些函數(shù)的結果 
在論文的后面,他定義了一些函數(shù),他用這個方法表達計算機,有點像現(xiàn)在的Lambda表達式 這篇論文的結論是:不是所有的問題都可以通過計算解決,有些可以,有些不可以, 在1936年兩個人從兩種不同的角度思考了這個問題 當圖靈在8月份讀到邱奇的論文,將邱奇的論文放到了附錄中,任何問題可以轉換成論的計算的問題都可以轉化成一個可計算的問題 
我們計算機和圖靈機比較還缺少什么呢,圖靈機有一個操作我們做不到,同一個指令可以有不同的操作 
如果紙帶是空格向右移動如果紙帶為1向左移動, 有一種指令叫做有條件跳轉指令可以做到這一點,它和我們的跳轉指令有一點像,現(xiàn)在的跳轉指令只能跳轉到固定的地址 

左右等價 根據(jù)不同的值來進行不同的行為 所以我們可以說如果實現(xiàn)條件跳轉指令我們就可以模擬任何圖靈機 
條件跳轉準備實現(xiàn)兩個條件跳轉指令,為0跳轉和進位跳轉0 為0跳轉,這個跳轉需要計算ALU中所有的值是否為0 , 

使用這個電路我們就可以判斷是否為0 74LS08有4個與門和74LS02有4個Nor門 

進位跳轉 ALU中高4位芯片有一個進位引腳,我們很容易就可以判斷出是否進位了。 

這邊就搭建好了2個標識,但是有一個問題, 
在獲得這個標識后,加命令還有一步就是將ALU中的值放到寄存器A中,這樣在進行跳轉指令的時候標識就沒有了, 所以這邊需要將進位標識存起來,這邊我們需要一個173芯片 其實Internal x86也有進位標識計數(shù)器 
一共32位 
這樣就多了一個控制線,F(xiàn)I:標識Flag的輸入, 
這是新的真值表,用了10個地址位,非常棒 直接用Arduino寫入真值表 #define SHIFT_DATA 2
#define SHIFT_CLK 3
#define SHIFT_LATCH 4
#define EEPROM_D0 5
#define EEPROM_D7 12
#define WRITE_EN 13
#define HLT 0b1000000000000000 // Halt clock HLT信號
#define MI 0b0100000000000000 // Memory address register in 內存地址輸入
#define RI 0b0010000000000000 // RAM data in 內存數(shù)據(jù)輸入
#define RO 0b0001000000000000 // RAM data out 內存數(shù)據(jù)輸出
#define IO 0b0000100000000000 // Instruction register out 指令寄存器輸出
#define II 0b0000010000000000 // Instruction register in 指令寄存器輸入
#define AI 0b0000001000000000 // A register in A寄存器輸入
#define AO 0b0000000100000000 // A register out A寄存器輸出
#define EO 0b0000000010000000 // ALU out ALU輸出
#define SU 0b0000000001000000 // ALU subtract 減法
#define BI 0b0000000000100000 // B register in B寄存器輸入
#define OI 0b0000000000010000 // Output register in 輸出寄存器輸入
#define CE 0b0000000000001000 // Program counter enable 程序計數(shù)允許
#define CO 0b0000000000000100 // Program counter out 程序計數(shù)器輸出
#define J 0b0000000000000010 // Jump (program counter in) 程序計數(shù)器輸入(JUMP)
#define FI 0b0000000000000001 // Flags in Flags 標志位輸入
#define FLAGS_Z0C0 0
#define FLAGS_Z0C1 1
#define FLAGS_Z1C0 2
#define FLAGS_Z1C1 3
#define JC 0b0111
#define JZ 0b1000
uint16_t UCODE_TEMPLATE[16][8] = {
{ MI|CO, RO|II|CE, 0, 0, 0, 0, 0, 0 }, // 0000 - NOP
{ MI|CO, RO|II|CE, IO|MI, RO|AI, 0, 0, 0, 0 }, // 0001 - LDA
{ MI|CO, RO|II|CE, IO|MI, RO|BI, EO|AI|FI, 0, 0, 0 }, // 0010 - ADD
{ MI|CO, RO|II|CE, IO|MI, RO|BI, EO|AI|SU|FI, 0, 0, 0 }, // 0011 - SUB
{ MI|CO, RO|II|CE, IO|MI, AO|RI, 0, 0, 0, 0 }, // 0100 - STA
{ MI|CO, RO|II|CE, IO|AI, 0, 0, 0, 0, 0 }, // 0101 - LDI
{ MI|CO, RO|II|CE, IO|J, 0, 0, 0, 0, 0 }, // 0110 - JMP
{ MI|CO, RO|II|CE, 0, 0, 0, 0, 0, 0 }, // 0111 - JC
{ MI|CO, RO|II|CE, 0, 0, 0, 0, 0, 0 }, // 1000 - JZ
{ MI|CO, RO|II|CE, 0, 0, 0, 0, 0, 0 }, // 1001
{ MI|CO, RO|II|CE, 0, 0, 0, 0, 0, 0 }, // 1010
{ MI|CO, RO|II|CE, 0, 0, 0, 0, 0, 0 }, // 1011
{ MI|CO, RO|II|CE, 0, 0, 0, 0, 0, 0 }, // 1100
{ MI|CO, RO|II|CE, 0, 0, 0, 0, 0, 0 }, // 1101
{ MI|CO, RO|II|CE, AO|OI, 0, 0, 0, 0, 0 }, // 1110 - OUT
{ MI|CO, RO|II|CE, HLT, 0, 0, 0, 0, 0 }, // 1111 - HLT
};
uint16_t ucode[4][16][8];//主要把指令根據(jù)進位劃分一下
void initUCode() {
// ZF = 0, CF = 0
memcpy(ucode[FLAGS_Z0C0], UCODE_TEMPLATE, sizeof(UCODE_TEMPLATE));
// ZF = 0, CF = 1
memcpy(ucode[FLAGS_Z0C1], UCODE_TEMPLATE, sizeof(UCODE_TEMPLATE));
ucode[FLAGS_Z0C1][JC][2] = IO|J;
// ZF = 1, CF = 0
memcpy(ucode[FLAGS_Z1C0], UCODE_TEMPLATE, sizeof(UCODE_TEMPLATE));
ucode[FLAGS_Z1C0][JZ][2] = IO|J;
// ZF = 1, CF = 1
memcpy(ucode[FLAGS_Z1C1], UCODE_TEMPLATE, sizeof(UCODE_TEMPLATE));
ucode[FLAGS_Z1C1][JC][2] = IO|J;
ucode[FLAGS_Z1C1][JZ][2] = IO|J;
}
/*
* 使用移位寄存器輸出地址位和outputEnable信號。
*/
void setAddress(int address, bool outputEnable) {
shiftOut(SHIFT_DATA, SHIFT_CLK, MSBFIRST, (address >> 8) | (outputEnable ? 0x00 : 0x80));
shiftOut(SHIFT_DATA, SHIFT_CLK, MSBFIRST, address);
digitalWrite(SHIFT_LATCH, LOW);
digitalWrite(SHIFT_LATCH, HIGH);
digitalWrite(SHIFT_LATCH, LOW);
}
/*
* 從指定地址的EEPROM讀取一個字節(jié)。
*/
byte readEEPROM(int address) {
for (int pin = EEPROM_D0; pin <= EEPROM_D7; pin += 1) {
pinMode(pin, INPUT);
}
setAddress(address, /*outputEnable*/ true);
byte data = 0;
for (int pin = EEPROM_D7; pin >= EEPROM_D0; pin -= 1) {
data = (data << 1) + digitalRead(pin);
}
return data;
}
/*
* 將字節(jié)寫入指定地址的EEPROM。
*/
void writeEEPROM(int address, byte data) {
setAddress(address, /*outputEnable*/ false);
for (int pin = EEPROM_D0; pin <= EEPROM_D7; pin += 1) {
pinMode(pin, OUTPUT);
}
for (int pin = EEPROM_D0; pin <= EEPROM_D7; pin += 1) {
digitalWrite(pin, data & 1);
data = data >> 1;
}
digitalWrite(WRITE_EN, LOW);
delayMicroseconds(1);
digitalWrite(WRITE_EN, HIGH);
delay(10);
}
/*
*讀取EEPROM的內容并將其打印到串行監(jiān)視器。
*/
void printContents(int start, int length) {
for (int base = start; base < length; base += 16) {
byte data[16];
for (int offset = 0; offset <= 15; offset += 1) {
data[offset] = readEEPROM(base + offset);
}
char buf[80];
sprintf(buf, "%03x: %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x",
base, data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7],
data[8], data[9], data[10], data[11], data[12], data[13], data[14], data[15]);
Serial.println(buf);
}
}
void setup() {
// put your setup code here, to run once:
initUCode();
pinMode(SHIFT_DATA, OUTPUT);
pinMode(SHIFT_CLK, OUTPUT);
pinMode(SHIFT_LATCH, OUTPUT);
digitalWrite(WRITE_EN, HIGH);
pinMode(WRITE_EN, OUTPUT);
Serial.begin(57600);
// Program data bytes
Serial.print("寫 EEPROM");
// 將微碼的8個高位寫到EEPROM的前128個字節(jié)中
writeEEPROM(0,0);
for (int address = 0; address < 1024; address += 1) {
int flags = (address & 0b1100000000) >> 8;//flag標識
int byte_sel = (address & 0b0010000000) >> 7;//高低位標識
int instruction = (address & 0b0001111000) >> 3;//指令
int step = (address & 0b0000000111);//步數(shù)
if (byte_sel) {//高低位
writeEEPROM(address, ucode[flags][instruction][step]);
} else {
writeEEPROM(address, ucode[flags][instruction][step] >> 8);
}
if (address % 64 == 0) {
if (byte_sel) {
writeEEPROM(address, ucode[flags][instruction][step]);
} else {
writeEEPROM(address, ucode[flags][instruction][step] >> 8);
}
Serial.print(".");
}
}
Serial.println(" done");
// Read and print out the contents of the EERPROM
Serial.println("讀 EEPROM");
printContents(0, 1024);
}
void loop() {
// put your main code here, to run repeatedly:
} 到這就做好了。 總結我收獲了什么: 計算機底層是怎么運行,控制器是怎么控制 調試的時候也遇到一些坑 寄存器沒有正常工作指令計數(shù)器工作正常,寄存器A和寄存器B工作不正常,這三個模塊是同一個脈沖線接過來的,先接入指令計數(shù)器,再接入寄存器A和寄存器B, 一開始并沒有懷疑脈沖線的問題,因為指令計數(shù)器正常工作,寄存器沒有正常工作,檢查了寄存器的接線發(fā)現(xiàn)沒有問題,量了電壓發(fā)現(xiàn)脈沖電壓非常小0.02V波動,這也太不正常了,量了下指令計數(shù)器的電壓是正常的,這就很奇怪了,后來發(fā)現(xiàn)最后寄存器脈沖線短路接地了,導致一直沒有脈沖, 控制器沒有正常工作發(fā)現(xiàn)控制器是輸出不正常,做了個簡單的測試電路,手動檢查控制器的eprom內存的值,發(fā)現(xiàn)確實沒有輸出正確的值,檢查Arduino nano的寫入接線和視頻中接線不同,導致寫入數(shù)據(jù)地址也不相同,調整Arduino nano和控制線,輸出正常, 經(jīng)驗每個模塊先用跳線接一下再進行測試,如果發(fā)現(xiàn)測試沒有問題再用標準接線將其接通, 正常調試需要一步步執(zhí)行,當出現(xiàn)異常了先解決出現(xiàn)的第一個異常,然后再解決剩余的異常,遇到異常不要慌,一步步解決,不要跳過問題進行下一個問題。
引用大佬的視頻教程,截圖基本都源自于該大佬,并稍加改動
|