---
image: https://i.imgur.com/ceox66b.jpg
---
# 單指令CPU, OISC, one-instuction set computer
[TOC]
目前規劃實作一款硬體版本
我挑選的是:subleq 這個版本
主要指令功能如下
首先指令帶有三個運算元,分別是A,B,C
指令格式:
subleq a b c
動作:
把b-a之後結果放到b,如果結果小於等於零(~~或者說「不為正~~」)則跳到c位址,否則執行下一條指令
這個指令有個特殊例子
如果指令不指定c運算元,則指令只會運算但不判斷。
我本來想說這不就是另一個指令,怎麼說是單指令呢?
後來仔細想想
其實就是c指向下一條指令
這樣無論運算結果如何,都是下一條指令
## 相關資源
https://esolangs.org/wiki/Subleq
這個網址有大量相關資料,其中包括一個python寫的組譯器與模擬器
甚至可以看到c編譯器
## 學術論文
https://web.ece.ucsb.edu/~parhami/pubs_folder/parh88-ijeee-ultimate-risc.pdf
![image](https://hackmd.io/_uploads/SyMvP-1Np.png)
## 用logisim模擬subleq
![](https://i.imgur.com/U0Hz6Qq.png)
## ttl 74 serials lib
https://github.com/r0the/logi7400
先是用 logisim 原版
並用 7400 lib
只是這個零件庫是依據IC腳位設計的包裝
在模擬的時候不方便使用
後來找到更完整的7400 lib
同時具有邏輯包裝
https://github.com/r0the/logi7400
再後來 找到 logisim 的替代版本:
logisim evolution
功能比較完整,但是跟原版有些些不相容
目前遇到:
RAM的外觀不一樣
D正反器的內部設計不一樣
遇到「五輸入的邏輯閘」不支援
例如加法器、計數器等內部
會用到「五輸入邏輯閘」的地方
需要打開零件庫
進去裡面把邏輯閘搞對
正反器的部分因為包裝換了
需要重新指定
20220415
74161 CLK 極性顛倒,但是原作者github已經封存,無法發送issue
## 關於這個lib的74系列包裝
在這個零件庫裡的74系列
是可以指定不同外觀的
其中這有個外觀我覺得很合用
它把輸出入至於兩邊
而且標有文字
大小也適中
**這裡要注意!!**
logisim如果像這樣要換零件庫中的零件外觀
必須回到零件庫的circ檔案裡面
從源頭修改外觀
這樣設定才會保住
從專案檔案那邊設定是不會記住的
原理猜想是:
每次專案檔案開啟後
會重新讀取零件庫
![](https://i.imgur.com/1iqVXZl.png)
## 使用logisim、74系列零件庫以及即時時序模擬來驗證
會發現在資料匯流排出現許多EEEE情況,經查,原來是狀態產生器區的組合邏輯信號發生信號競走現象,導致匯流排出現多個來源而產生衝突
其中因為位址匯流排有一個多工器,而資料匯流排上的記憶體會有輸出入衝突
## 思維筆記
### cpu定義:
輸出入單元、運算邏輯單元、記憶單元
### cpu都有一套指令集
指令集有各種指令
指令有各種型態:0~3個不等數量的運算元
指令有各種定址法
### 舉例:
假設有道組合語言指令長這樣:
label: mov a,b
它所描述的功能:把b暫存器的值複製到暫存器a
其中:
label是標記,代表目前這行指令的所在位址,實際在記憶體的位址要等到組譯之後才能得知
mov是指令碼,因為一般cpu有多個指令,因此需要一組文字來代表指令,通常是三個英文字母
a與b是運算元,用來給指令碼作為指示資料處理的來源與目的
### subleq
這個特別的cpu
只有一個指令
但是卻能與其他cpu圖靈等效
換句話說,一個指令能透過操作而能做到其他指令的動作
因為只有一個指令
所以沒有運算碼,只有運算元
這一個指令名為subleq
意指「將兩個來源位址的數值將減後,若小於等於零則跳往指定的位址」
假設在記憶體中放著3個位元組
代表第一組指令的三個運算元
(因為指令集只有一個指令,因此沒有運算碼)
那麼硬體電路應該如何運作
才能執行這一組指令呢?
首先解析這三個運算元
其實都是位址值
而且前兩個位址的內容值
必須相加
所以CPU的順序控制就必須能夠依序擷取前兩個位址的內容值
而資料匯流排只有一組
因此必須採取分時多工策略
並且要有暫存器用來存放這些內容值資料
## 邏輯閘
74系列
當年半導體大廠製作了一系列數位IC,並且以架構命名為電晶體電晶體邏輯(Transistor-Transistor Logic, TTL)
將常用的邏輯閘以常見包裝來封裝,例如DIP-14、DIP16等。
這個系列的商用化是德州儀器發展出來的,因此直到今日,大家都還是以德州儀器的命名編號作為系列名稱,叫他做:74系列
德州儀器在命名數位邏輯IC的時候,會把硬體實作的技術以英文字作為區別,這個英文字串會放在「74」與「編號」之間,例如LS=低功率蕭特基、HC=高速CMOS
https://en.wikipedia.org/wiki/7400-series_integrated_circuits#Families
LS是早期的製程技術
後期多以HC製成製作
LS的工作電壓在5V上下
HC可以2~5V
## 使用現成邏輯IC來建構這個CPU
一但我們明白這個CPU的功能後
就可以開始構思如何實作
因為邏輯電路模擬器:logisim有人為它設計了74系列的零件庫,因此可以利用這個來模擬我們的CPU
首先,用類似思想實驗的方式先在腦中建構這個CPU
先剖析一下這個指令:subleq
他有使用到減法,因此最基本的就要先一個「減法器」
而從「數位邏輯」的學習中有了解到,減法器其實就是一數加上另一數的補數
所以在74系列,可以找到一款4位元加法器
把它在被減數的地方串入一組反相器
這樣就可以把加法器變成減法器了
![使用74283加法器搭配減數串接反相器](https://i.imgur.com/Lv7QPY4.png)
## 目前使用到的74系列
7400 四個二輸入反及閘, 14pin, x3
https://www.ti.com/lit/ds/symlink/sn74hc00.pdf
7410 三個三輸入反及閘, 14pin, x1
https://www.ti.com/lit/ds/symlink/sn54hc10.pdf
~~7404 六個反相器
https://www.ti.com/lit/ds/symlink/sn74hc04.pdf~~
7414 六個舒密特反相器, 14pin, x3
https://www.ti.com/lit/ds/symlink/sn54hc14.pdf
~~7408 四個二輸入及閘
ti.com/lit/ds/symlink/sn74hc08.pdf~~
7432 四個二輸入或閘, 14pin, x2
https://www.ti.com/lit/ds/symlink/sn54hc32-sp.pdf
~~7474 兩個正緣觸發D型正反器帶清除及預設, 14pin, x2
https://www.ti.com/lit/ds/symlink/sn74hc74.pdf~~
7476 兩個帶預設及清除JK正反器, 16pin, x2
ti.com/lit/ds/symlink/sn5476.pdf
74138 三對八線解多工器, 16pin, x2
https://www.ti.com/lit/ds/symlink/sn54hc138-sp.pdf
~~74157 四個二對一線資料多工器
https://www.ti.com/lit/ds/symlink/sn54hc157-sp.pdf~~
74161 四位元同步二進位計數器, 16pin, x5
https://www.ti.com/lit/ds/symlink/sn54hc161-sp.pdf
74244 八個輸出三態緩衝器帶驅動, 20pin, x6
https://www.ti.com/lit/ds/symlink/sn74hc244.pdf
74273 八個D型正反器帶清除, 20pin, x4
https://www.ti.com/lit/ds/symlink/sn54hc273-sp.pdf
74283 4位元全加器, 16pin, x2
https://www.ti.com/lit/ds/symlink/cd54hc283.pdf
~~74595 8位元串入並出移位暫存器帶三態輸出
https://www.ti.com/lit/ds/symlink/cd74hc595.pdf~~
## 一個想法 關於如何使用這個CPU
利用32u4作一個模組
用來搭建CPU與電腦之間的橋樑
同時引出一個32u4的構想:製作一個萬用serial monitor/plotter
在這裏
32u4可作為CPU程式記憶體的發佈器
從電腦來的程式碼
利用32u4載入到CPU的SRAM裡面
或許這樣想:32u4與SRAM是一組的
等到程式下載完成
32u4可控制CPU開始跑
同時32u4也是CPU的serial monitor/plotter
可能還沒有serial
而是透過一個IO port
就像目前模擬的這樣
有一個tty透過一個IO port接收程式送過來的文字並顯示
## 關於外部載入
一個想法。SRAM應該為內部使用。外部要輸入的程式需透過IO Port。因此需要設計一個載入器。
可能的功能:
暫停cpu
輸入位址與資料
重置並同時啟動cpu
利用74595串入並出,實現bootloader
## 影片大綱
cpu簡介:輸出入單元、控制單元、運算邏輯單元
組合語言簡介:搬移指令、定址法、運算指令、跳躍指令、條件判斷
單一指令集CPU
規格(16位元寬)
架構解析與方塊圖
正反器
RS,clock,D,JK
暫存器與栓鎖
計數器
74系列IC簡介
74系列實現程式計數器
**不夠有趣**
## 什麼是CPU
在電腦概論課程裡面會介紹到CPU,常見一種方塊圖解釋:輸出入單元、運算邏輯單元、記憶單元
接下來必須使用想像力,把這些方塊圖的單元想像成一個一個的工廠,而這些工廠處理的材料就是「資料」!
現代CPU結構繁複,功能強大,但是否可以用歸納法找出CPU功能最精簡的樣態?
後來在網路上看到一款名為「單指令集CPU」號稱這個CPU只有一道指令就可以滿足圖靈完備性。
如果撇開理論,最簡單的說法就是,這個CPU可以做到任何一種市面上的CPU可做到的所有事情。
那麼,市面上的CPU基本上可以做到哪些事情呢?可以從他們的指令集開始看起。
以前學組合語言的時候理解到一個CPU擁有許多指令,這些指令的集合稱作「指令集」。CPU指令會分有許多種類,例如搬移指令、運算邏輯指令、條件分支指令等,然後再搭配各類「定址法」於是這些CPU指令就可以組合出各式各樣的功能出來。
這些指令分類以及「定址法」分別是什麼意思呢?
首先說到「搬移指令」。
這是指令集中最常見的指令。因為CPU與周邊之間會有許多的資料搬移的動作,例如電腦鍵盤會偵測哪一個案件被按下,然後鍵盤的微控器就會把按鍵的掃描碼打包起來透過介面傳給電腦,這時CPU就必須利用「搬移指令」將資料搬進來存放,以便後續處理。又或者有數字要相加,CPU就必須利用「搬移指令」把須要相加兩個數字放到「算數邏輯單元」去做相加的動作。
接著說「算數邏輯指令」。一般因為處理加減乘除等「算數」指令與「AND」、「OR」、「NOT」等指令是在同一個區域處理的,因此那個區域的以及動作就會取名為「算數邏輯單元」或是「算數邏輯指令」
然後是「條件分支指令」。如果從Arduino的程式去理解,就是IF跟迴圈指令。其中迴圈指令的WHILE是FOR的一個特例。迴圈指令的一個直覺式類比就是馬克一號的打孔紙帶頭尾相連!
## 折衷計畫
如果說為了減少硬體複雜度
那麼可以把資料匯流排減少為8位元
但位址匯流排依然維持16位元
然後為了保持時序的簡單化
SRAM部分維持兩顆的配置
(兩顆8位元資料匯流排合併為16位元)
也就是說
指令的運算元寬度將會是16位元
但是定址後取出的數值是8位元寬度
計算的資料寬度也是8位元
## 從哪裡開始?
中央處理器的抽象結構
為什麼會有中央處理器?
一開始的「電腦」只有「硬接線」方式
一次解決一件事情
回想學「算術」的過程
怎麼做加法的?
減乘除?
把步驟寫下來
然後試試看用「數位電路」實現?
如果只有加法器,如何?
## 反向思考
因為現在學的都是過往的人歸納後再演繹出來的知識
但是「分久必合」
總是會想是否可以歸納出單一道理?
舉例
while迴圈是for迴圈的特例
中央處理器也可以
有人找到一道指令:subleq可以組合所有其他的中央處理器指令
這個指令類似8051的djnz
## 關聯樹
要明白CPU的運作,需要先知道一些基本知識,如果向前推展,可以發現邏輯閘及其數學是首先要知道的事情
由開關組合起來的電路,基本上遵循二進位數字系統的規範,首先定義,0代表斷路,1代表短路。
數位邏輯延伸出基本數位元件,例如
邏輯閘、正反器、計數器、栓鎖器、暫存器、緩衝器、編解碼器
## 如果是一本書,這本書要叫做?
從數位邏輯、數位元件再到CPLD來解釋與實作CPU
透過Arudino學數位邏輯
## 從學習的角度
用當今最為普及的Arduino作為介面
來學習數位邏輯與CPU
這個CPU有程式載入的困難點
以及記憶體取得的困難點
CPU在運作時必須有地方存放程式
這個程式存放的地方還必須有雙埠性質以供除錯與觀察
另外還有包括RAM的需求
## 用Arduino來模擬RAM?
這個CPU的架構中,有一塊記憶體的需求
用來放程式碼與資料(普林斯頓架構?)
一開始想到的是Arduino
可是IO腳不夠(16位元記憶體)
至少要12bit address + 8bit data
=20腳
加上控制信號 R/W 至少+1
32u4則有26 IOs
或許用裸的32u4
這樣也可以不用稍早前構思的74595載入器
![](https://i.imgur.com/jfVEREc.png)
![](https://i.imgur.com/ckNRlCy.jpg)
## 內容再一次整理
先講電腦歷史?
機械式 機電式 電子式
數位邏輯
布林與夏農
電子式可儲存電腦的架構
程式與資料
用紙上模擬一遍CPU方塊圖的運作方式
插圖?
問:如何用實際元件達到上述效果
數位邏輯元件介紹
邏輯閘、加法器、互斥或
正反器、暫存器(PIPO)、移位器(SIPO、PISO)
計數器
## 數位電路
布林函數 與 夏儂的論文
如何串起這些故事
探詢背後的脈絡
織布機?分析機?
## 邏輯閘
這是AND 兩個輸入,一個輸出
當所有輸入都是Hi的時候,輸出才為Hi
這是OR 兩個輸入,一個輸出
當其中一個輸入為Hi,輸出就為Hi
這是NOT 一個輸入,一個輸出
輸出與輸入為相反邏輯的關係
## 正反器
![](https://i.imgur.com/4Jq74Oy.png)
http://www.falstad.com/circuit/circuitjs.html?ctz=CQAgjCAMB0l3BWcMBMcUHYMGZIA4UA2ATmIxAUgoqoQFMBaMMAKAHcRMVwAWHkbHn4oeVSCwCyAoeBR4QPbNzByonaAknThohUs66qKDSwAynDMpWd84a2JAAXAE4BXOtXHmunBIRvyKH5qVC7unixgCNgWyqqC-CryRtSaUTE+IlQJBg7ctOyxnCLassksAB7IMczE4Bh1Ktw6IABKAKIAyu0AKpXg2BBEyhj+REgt3X1VPIT+Kv48Mip1LQCK-bNIIhBLGXwgY-wbQA
當邏輯閘這樣連接的時候
會形成一組RS正反器
這一支輸入為S(SET)
這一支輸入為R(RESET)
這一支是輸出Q
這一支與Q互為相反邏輯的關係
當S腳致能(ENABLE)且R腳禁致(DISABLE)的時候,輸出Q為Hi
相反地
當R腳致能且S腳禁致的時候,輸出為Low
## 正反器的幾個種類
- RS
- D
- JK
RS正反器為正反器最基本的一種形式
其特點為「記憶」
透過SET輸入腳的觸發
輸出Q會致能
即使SET腳的觸發信號消失
Q狀態依然維持不變
這就是一種RS正反器的記憶能力
有了RS正反器的記憶能力
如果在RS正反器的輸入SET與RESET之間
串接一個反向閘
RS正反器的兩個輸出融合成一個輸入
讓輸入為1的時候,輸出就為1
輸入為0的時候,輸出就為0
這樣就會形成一組D型正反器
搭配「時脈」信號
這裡定義在時脈信號觸發時
RS的SET及RESET輸入信號才能動作
這就形成一種時脈觸發型D型正反器
如果將多組D型正反器並排
並且將時脈信號接在一起
當時脈觸發時
這些並排的D型正反器就會同時將輸入狀態「鎖」在內部
於是形成「栓鎖器」
JK正反器提供了「反轉」的能力
當JK兩隻輸入腳都是1的時候
JK正反器進入「反轉」狀態
每當時脈信號觸發的時候
如果輸出為0則會變成1
如果輸出為1則會變成0
如此「反轉」
如果將多組JK正反器串起來
JK正反器的反轉能力
恰恰提供了二進位計數功能
此為「計數器」的原理
## 三態
有了栓鎖器與計數器就可以開始搭建CPU
在CPu裡面很多時候是資料的搬移
這些資料行走的路線我們叫它「匯流排」(BUS)
為了節省成本
CPU內的匯流排需要在不同時間給不同單元使用
例如來自外部記憶體匯流排的資料
先傳送到內部的暫存器
然後下一個時序則是暫存器送到運算邏輯單元
最後再下一個時序時將運算完成的結果送到外部的記憶體匯流排
這些操作雖然都是牽扯不同的來源與目的
但是因為它們的使用時間錯開
因此都可以共用同一組內部匯流排
但是在其他單元使用匯流排的時候
沒有參與動作的單元該如何處置自己的狀態?
這時候它們與匯流排連接的腳位必須處在「高阻抗」狀態
以避免自己的腳位電壓位準影響到正在動作的單元們
在實體電路中 如果要做到兩路選擇一路
一般可以用閘刀開關
在一路導通時 另一路斷開
反之亦然
應用半導體技術的數位電路
無法做到像實體開關那樣完全斷開
因為都是用電晶體代替開關
而電晶體是利用半導體特性使得通道在導體與絕緣體之間變化
所以只能利用絕緣體狀態下的高阻抗來使得線路不導通
因此稱作「高阻抗」
![](https://i.imgur.com/3IFSj0M.png)
## 電腦原理
回到電腦原理
解釋指令的原理
從記憶裝置讀入、解碼、執行、回存
分析這個單指令的定義:
會將第一運算元記載之位址中的值減去第二運算元記載之位址中的值,後將其結果存到第二運算元記載之位址。如果結果小於等於零,則將程式跳往第三運算元記載之位址,否則執行下一道指令。
其中核心功能為「減法」
因此先討論減法器的設計。在數位邏輯中,減法也是透過加法來完成的。所以這裡會先畫出一個八位元減法器。
為了實現「減法器」,首先要有一組「加法器」
然後在被加數那一側插入反向器,接著上一級進位的地方永遠設為1
這樣就形成一組減法器了
## 不同邏輯單元之間的互動
有了減法器
這時候要思考,如何將兩組暫存器的數值相減,最後把結果放入第三組暫存器?
![](https://i.imgur.com/wrpal3L.png)
http://www.falstad.com/circuit/circuitjs.html?ctz=CQAgjCAMB0l3BWcMBMcUHYMGZIA4UA2ATmIxAUgoqoQFMBaMMAKDELxEIBZwVPihPpxQheVWhTYcuvbGnBhR8qqPFQNCNoJDZsQpZ25xhINdKM6wxUQn7gbZsRsmQWAdzPGzKOfp-iHrr+1qK+VKFQQeHghFR6BnFRngmmMYbJYvJmeJZC-Jxuntw6KAhC3NllQkVZYb5ipQ21JQaOlaKRtanVXqrlmTG9MSqZPblmAwWDUxO92HZj-qOYygq1q7oKm9h4gSnLi9MLohsTJzlG7FEAsnWm3HgR9qrQWgAywUK7fhU8GhAAGYAQwANgBnOjUDbeFANdiqZpBBEOMLeLrRdFJFEo2o4+zpF5Bf7zMIDbpk75qR5jamcf7cGluT7ZRmcbIIJQAkAgiFQpDMxpUqitXR7bm8yHQliff4ZJgGF7gHlgqUCmWyWJUBVaiWq-lRWW8SI6roaSUGwUkho6uHqKgW6VGrba66jCIqvlOzUXHUXCSetWGn3iv3igOO9XO7jXHUxmrm-XSu4IRyRQgDM0oN4sFOOO1cKbNMw5vOUwv5cklrRly4VuuvLQASV1K287qgbykLZRF3miwkXebrfFO3DnaQw5R8brM8Hk5YAA8QKQQBMwLgVxBeDSAIJLle0ax8Wxw5w0gBCB4Qm-YtjlGfPnAAwgfcLwyGJCAYTDvOLuABEAIAUQAJRYIA
## 加入32u4的
32u4用來模擬記憶體,記憶體內容將包括:程式以及周邊IO
其中IO例如:模擬器中的LCD顯示將由arduino的串列埠視窗來顯示
根據手冊,32u4有34條IO
因此考量IC利用最大化
這裡採取16條位址線,8條資料線的架構
16+8=24
32u4剩下兩條
一條會是PE2用來開啟bootloader功能
另一條會是RW控制線
目前32u4板子已經完成
可以載入程式與連接麵包板
接下來要考慮第一步驟
應該會是先建立循序控制以及程式計數器
這樣就可以產生位址信號
用來測試32u4
反向解釋
為了建立循序控制單元
就要解釋指令集以及執行週期
為了解釋執行週期
就要解釋暫存器轉移及其由來
藉由分析subleq的行為
建立循序控制的真值表
然後就可以說明如何安排環形計數器以及解碼邏輯閘
這裡是安排
74161四位元計數器
以及
74138三對八解碼器,兩顆形成4對16解碼器
來作為循序控制信號產生器
## 目前在修改架構 改成 A16D8
因為32u4記憶體模擬器已經完成
32u4的IO腳數目只夠模擬A16D8
因此CPU的架構必須改成八位元資料寬度
造成每個16位元寬的運算元必須存取兩次
使得CPU的控制線路複雜化
現在正處在零件數目與控制邏輯之間的拉扯
即使使用兩顆74HC4017總共20隻控制腳
也不夠
## 使用位元組對齊的版本
![](https://i.imgur.com/M2lxrJq.png)
緣由:
記憶體單元的設計都是以八位元為資料匯流排寬度
因此如果要以16位元為資料寬度
則必須實作位元組對齊的機制
也就是說 記憶體內部的資料排列
是以兩個位元組為一個單位
所以運算元指定的位址值
到了現實世界就必須轉換成記憶體單元的實際位址
這裡的實作方法很簡單
就是把運算元的值左移一位元
讓運算元的位址值自動乘以2來作為實際記憶體單元的位址
然後再由內部時序產生低位元組與高位元組的信號
用來驅動T型正反器 進而產生A0的輸出
## 控制單元的改進
這一次的控制單元也做了一個改進
原本有些控制信號必須在連續的機械週期中維持一個狀態
原本是利用OR閘將這些機械週期集合起來
現在改為使用T型正反器
在連續狀態開始的時候觸發正反器使之轉態
然後在連續狀態結束時再一次轉態
## EA是什麼?
EA就是「有效位址」
舉例來說
subleq的三個運算元都是位址值
而CPU只有一組位址匯流排
因此當CPU使用位址匯流排定址到記憶體單元讀入指令後
位址匯流排必須根據指令的運算元切換到該位址取值
而這個運算元所描述的位址就是一種EA
他在CPU裡面會有一組暫存器存放
以備時序到達的時後取用或者是結果回存時的目標位址
## 位元組對齊版 RTL
![](https://i.imgur.com/CNJiP4J.jpg)
``` text
default
MUX <- PC
A0 <- 0
EDGE RISING FALLING
1
2 EAL <- 1 A0 <- 1
3 EAH <- 1 A0 <- 0, MUX <- EA
4 PCINC <- 1
5 ALOAD <- 1 MUX <- PC
6 EAL <- 1 A0 <- 1
7 EAH <- 1 A0 <- 0, MUX <- EA
8 PCINC <- 1
9 BLOAD <- 1 RAM_OE <- WR
10 RAM_OE <- RD
11 MUX <- PC
12 EAL <- 1 A0 <- 1
13 EAH <- 1, EALOAD <- LEQZ A0 <- 0
14 PCINC <- 1
```
20220420 修改MUX<-EA的時序,修正ADDR輸出短暫錯亂問題
## logisim的模擬
一開始用預設元件進行模擬
順利地完成
後來開始替換成TTL
就遇到實際時序的調整
首先在138解碼器部份
是負邏輯輸出
接著244三態緩衝器是負邏輯
但273與161的時脈輸入是正邏輯
所以控制單元需要仔細推敲
接著發現原本的RTL設計
有時序是相疊的(),調整後好多了
接著發現第一次以及最後一次的MUX是多的!
這樣控制單元狀態可以再少一階
接著觀察A0與EAL有關聯
因此將A0透過EAL連接到JK的J達成
又再節省一顆IC(7432)
## 關於IO
本來這個架構中有一個tty的輸出
程式中文字輸出的地方
而這個架構的資料路徑是獨立的匯流排
但是32u4這邊已經沒有多餘的IO可以接收
所以再修改了這部分
將原本的IO獨立資料匯流排
併入資料匯流排
將原本算術單元的輸出74244
再併一顆74244
這顆74244的來源是原本的IO資料匯流排
然後透過邏輯閘將算術單元的74244
與IO的74244根據控制時序的不同來分開控制
原本是時序到了之後開通74244讓算術單元的結果
回存回去到RAM裡面
但是如果目標位址是IO位址
則時序會被切換成IO資料匯流排
也就是暫存器A的值
這樣就可以對外只有一組資料匯流排
所以對外的控制信號除了原本的RAM WR 以及 RAM RD外
還多了一條IO WR的信號線
## 嘗試在kicad上佈局
![](https://i.imgur.com/pFzVh4F.png)
## logisim TTL模擬
![](https://i.imgur.com/noF99P0.png)
## 關於IO解碼
這個架構中有一個TTY的輸出
原本是從暫存器A取出之後
獨立輸出到一個輸出埠
目前規劃實際作法是
將tty輸出到一個特定位址
然後在arduino那邊接收到這個特定位址存值進來
搭配IO WRITE信號
然後就輸出到arduino的串列埠觀察器那邊去
所以
這個IO的輸出目前是併入資料匯流排
然後用解碼電路
將時序切出一個IO輸出時序
當特定位址出現時
資料匯流排改變來源
從暫存器A取值輸出
原本設想一個動用多個IC的解碼電路
後來念頭一轉
目前只需要一顆7400就好了
只需要解碼A15,A14就可以了
## 時序修改
關於RAM的寫回以及IO的輸出
本來是在同一個時序下
但是經過推敲發現
RAM是非同步操作,時脈是高低動作
IO是時脈同步操作,資料在時脈邊緣時才承認
因此修改如下:
周邊裝置的寫入控制信號獨立為
IOCLK
原本這組信號是來自RAMWR,可是擔心會造成輸出暫存器剛剛打開就去閃控周邊讀入,怕會讀不到,因此把這組信號延遲發送以確保。
IODATASTROBE是IO的輸出暫存器時脈
RAMDATASTROBE是記憶體寫回資料暫存器的控制信號
以下是IO輸出時的時序,當資料備齊後才觸發IOCLK,讓周邊讀入
![](https://i.imgur.com/hXo8V3Z.png)
以下是記憶體寫回時序
![](https://i.imgur.com/pBHzSCN.png)
修改後的記憶體與IO資料匯流排線路
其中IOEN來自A14,A15
此一記憶體區段用來定址給IO使用
當IOEN致能,下面那組74244將會隨之致能
因此這一指令週期的對象就是IO(資料源自暫存器A)
反之,IOEN禁制時,此週期為ALU回存
![](https://i.imgur.com/MmfOJjx.png)
## 麵包板完成主要佈線
接著會連接32u4模擬的記憶單元進行實測
![](https://i.imgur.com/ceox66b.jpg)
## CPU動作流程(流程圖練習)
```mermaid
graph TD;
載入第一個位元組--->程式計數器加1--->載入第二個位元組--->程式計數器再加1--->組成有效位址--->載入資料放到暫存器A
載入第三個位元組--->程式計數器再再加1--->載入第四個位元組--->程式計數器再再再加1--->組成下一組有效位址--->載入資料放到暫存器B
```
減法器會在A、B暫存器資料到位的同時計算完成
```mermaid
graph TD;
計算結果是否小於等於零--->小於等於零則把有效位址載入程式計數器
計算結果是否小於等於零
計算結果是否小於等於零--->沒有小於等於零則程式計數器加1--->結束這一回合
小於等於零則把有效位址載入程式計數器--->結束這一回合
```
## 32u4是用來模擬記憶單元
運行的程式將會放在32u4內
利用gpio模擬記憶單元的位址匯流排以及資料匯流排
在相對應的情況下,
存入來自CPU的資料或是吐出資料給CPU
![](https://i.imgur.com/8UPvgun.png)
![](https://i.imgur.com/TIpHbO7.png)
```
Port B : 8 lines PB0 to PB7
Port C : 2 lines PC6 and PC7
Port D : 8 lines PD0 to PD7
Port E : 2 lines PE2 and PE6
Port F : 6 lines PF0, PF1 and PF4 to PF7
```
## 一個logisim 開源零件庫logi7400 的bug
這個模擬有使用一個開源的TTL零件庫。今天發現一個bug。
![](https://i.imgur.com/C53xczc.png)
左邊是74161的資料表,右邊是零件庫中的設計
其中74161的時脈(CLK)極性是顛倒的!
## 因此,修改了logisim TTL零件庫中的74161,以及CPU控制邏輯
![](https://i.imgur.com/qjXjkpY.png)
把PCINC原本的NOT拿掉
把CLK給正反器路徑上的NOT拿掉
另外
發現EALOAD信號沒有跨越CLK觸發
這樣會有疑慮
因此把EALOAD來源擴展為兩個機器週期
## 20220414 麵包板CPU跑起來了
https://www.facebook.com/DaHaiDeCPU/videos/509237627404809
## 關於用arduino模擬RAM
目前模擬了位址線與資料線各8位元
一隻RW腳 0=R 1=W
## 紀錄
![](https://i.imgur.com/MWWoKf8.png)
## 方塊圖(以hackmd繪製)
```mermaid
graph TD;
程式計數器PC --> 位址匯流排ADDR
有效位址暫存器EA --> 位址匯流排ADDR
有效位址暫存器EA --> 程式計數器PC
資料匯流排DATA --> 暫存器A
資料匯流排DATA --> 暫存器B
資料匯流排DATA --> 有效位址暫存器EA
暫存器B --> 減法器
暫存器A --> 減法器
減法器 --> 資料匯流排DATA
暫存器A --> 資料匯流排DATA
減法器 --> 比較器-是否小於等於零
位址匯流排ADDR --> 記憶體MEM --> 資料匯流排DATA
資料匯流排DATA --> 記憶體MEM
```
## 方塊圖動畫(PPT繪製)
## 思考第一動
```mermaid
graph TD;
記憶體-->暫存器A
記憶體-->暫存器B
暫存器A-->減法器
暫存器B-->減法器
```
```mermaid
graph TD;
程式計數器PC-->位址匯流排ADDR
位址匯流排ADDR-->記憶體MEM
記憶體MEM-->資料匯流排DATA
資料匯流排DATA-->暫存器A
資料匯流排DATA-->暫存器B
暫存器A-->減法器
暫存器B-->減法器
```
``` TEXT
MOV ax,#02
```
## 觀察到ADDR輸出時會有半週期的混亂
原因是控制時序MUX延後,將MUX提前就可以避免
ADDR輸出時資料短暫錯亂
![](https://i.imgur.com/IzGdNir.png)
修改後ADDR輸出穩定
![](https://i.imgur.com/S7iKQrt.png)
## 組譯後轉換檔案
組譯後成為二進位檔
然後進行高低位元組交換,使其成為小端序
``` bash
cat helloworld.txt | xargs -n1 echo | xxd -r -p - > helloworld.bin
dd conv=swab < helloworld.bin > helloworld.bbin
```
組語語法:
``` text
LABEL:OP1 OP2 OP3
```
## 組合語言逐行解釋
``` bash
這是一段網路上人家寫好的一段subleq組合語言
搭配作者寫好的一個組譯器程式可產生機器碼
它的功能是在位址-1(0xffff)的地方依序輸出字串hello world!
本來程式只會執行一次
我稍微改寫程式尾端:
「當一行執行完畢後
清除指標變數
並起跳回0位址,重新再來一次」
於是就會將字串不停重複輸出
每行都有三個運算元,且沒有運算碼(因為每行指令都會是subleq故省略)
第一第二運算元位置會出現下列變數
a、Z、p、m1、E
都是代表位址
各個變數實際位址就在程式後半段有冒號的地方
而冒號後面跟的就是那個位址的初始值
每行指令都只有一種動作
「第二運算元所指的位址的內容值
減去第一運算元所指的位址的內容值
減法結果放回第二運算元位址
若結果小於等於零則程式跳往第三運算元所指位址
否則執行下一道指令」
下面程式在第三運算元欄位則有
?+1,0,restart三個位址
?+1表示下一行位址,換句話說無論結果為何,一律執行下一行
0就是表示位址0
restart表示標記為restart的位址,在下面restart:那個位址
標記在三個運算元位置都可以加
標記代表的是該標記所在的位址
以下逐行解釋
# Output the character pointed to by p.
a a ?+1 清除a位址內含值為0
p Z ?+1
Z a ?+1 這行與上一行聯合起來就是:p的資料搬到a
Z Z ?+1 把Z清為零,每次Z用完就要清零
a:0 -1 ?+1 把a的內容搬到0xffff位址,表示輸出
# Increment p.
m1 p ?+1 把p加一,要知道,p就是指向字串
# Check if p < E.
a a ?+1 清除a
E Z ?+1
Z a ?+1 E搬到a,注意看,E就是字串尾巴
Z Z ?+1 清除Z
p a restart 如果a==p則跳到restart:
Z Z 0 無條件跳往位址0
restart:p p ?+1 清除p
Hp Z ?+1
Z p ?+1 Hp搬到p,讓p重新指向字串開頭
Z Z 0 無條件跳到位址0
p:H Z:0 m1:-1 變數p預設放的資料是字串的開頭位址
變數Z就是要放零
Z變數用在資料搬移,原理如下
先把目的位址清零,這裡的案例就都是a
可用零減去來源值得到該資料的負值
且這時這個負值結果是放在Z這個地方
然後再用目的位址,也就是目前為0的a
減去Z,就會因為負負得正,使得資料出現在目的位址
變數m1就是要放負一,用來將變數加一用的
因為:0-(-1)=+1
Hp:H 0 0 Hp:用來存放字串開頭位址,以便要重頭開始的時候,
重置p變數的值
後兩個填零只是因為組譯器每次都強制解釋三個欄位,
為了不讓組譯器出錯故意填的
# Our text "Hello, world!\n" in ASCII codes
H:72 101 108 H:就是字串開頭
108 111 44 中間都是字串的ASCII碼十進位數字
32 87 111
114 108 100
33 10 E:E E:就是字串結尾
```
## 一個簡介網站(提到巨集組譯器)
https://techtinkering.com/articles/subleq-a-one-instruction-set-computer/
## 巨集組譯器使用方法
```bash
/opt/homebrew/Cellar/tcl-tk/8.6.13/bin/tclsh ../main.tcl io.test.asq | ./conv.sh > io_test.h
```
## bash下轉換組譯器輸出的有號數數字字串成為無號數記憶體排列內容
```bash
cat helloworld.txt | {
for N in $(cat); do
if [[ $N -lt "0" ]]; then
a=$((4294967296+$N))
else
a=$N
fi
a=$(($a % 65536))
echo $(($a % 256))
echo $(($a / 256))
done | xargs
}
```
## 電路板版本
遇到兩個注意點
1. 74LS76A 為負緣觸發
2. 74LS00的輸出可能不夠高,會導致JKFF的輸出異常彈跳,要改為74HC00才路正常
奇怪點:
用32u4做模擬時 jkff輸出異常(mux信號彈跳)
因此調換兩個jkff(把ls跟hc調換)
但是在mega模擬時
卻出現另一組錯誤(mux信號的jkff在第二次時不動作)
把兩個jkff對調回來就又正常了
![](https://i.imgur.com/pNcCzFy.jpg)
## 關於模擬RAM
本來使用arduino但是arduino內建RAM不足
現在改用stm32f103 bluepill
## 使用stm32duino
參考:
https://maker.pro/arduino/tutorial/how-to-program-the-stm32-blue-pill-with-arduino-ide
## stm32f103的pb3 pb4預設會是JTAG功能
要用這個API解除,換成GPIO功能
disableDebugPorts();
## 巨集組譯器有一個範例時做了呼叫副程式功能
可是它是假設把運算寬度等於有效位址寬度
它把呼叫這個動作寫成一個巨集
分為副程式定義、呼叫與返回三個步驟
呼叫副程式
```
main: call helloWorld
```
副程式定義
```
helloWorld: retInst
outputString hello
jump helloWorld ; Return
```
呼叫巨集
```
.macro call addr
sble ret addr+2
jump: sble z z addr+3
ret: .word 0-($+1)
.endm
```
返回巨集
```
.macro retInst
sble $+2 $+1 0
.endm
```
跳躍巨集
```
.macro jump addr
sble z z addr
.endm
```
呼叫時,副程式開頭位址作為巨集參數帶入
在副程式定義的第一行負責儲存返回位址
## 八位元運算無法產出目標位址
而我的硬體目前資料寬度八位元 有效位址十六位元
因此無法實現這個副程式呼叫
要想想另一種方法
## 因此要改成16位元運算核心
把資料匯流排改成16位元
因此減法器也要改成16位元寬
控制時序不用更改
還有周邊讀回機制的實現
基本上是把周邊寫出的邏輯複製一份給讀回機制用
只是動作觸發時序是顛倒的
例如WR/nRD在為1的時候
周邊寫出機制要動作
讓A暫存器的栓鎖打開
以便讓資料直接寫出到周邊
如果WR/nRD為零
則是讓讀入栓鎖器動作
並且同時RAM的讀入禁制
這樣就可以讀回周邊暫存器的資料
## 這個版本稱作A16D16
已經完成logisim模擬
有找到logisim holy-cross版本
模擬速度大幅提升
![](https://hackmd.io/_uploads/SJGJ7Lqb6.png)
對於資料輸入機制也設計完備通過模擬
可以正確遊玩剪刀石頭布範例程式
於是我購買了EISA的萬孔板以及八色鍍銀線
要來實現繞線版本做實體驗證
至於模擬RAM的部分
原本的arduino已經無法應付A16D16
所以會改成STM32-L476RG實驗板來模擬RAM的部分
![](https://hackmd.io/_uploads/ryAzAr9-p.png)
## 經過分區著色的模擬電路
![d16a16ttl功能說明方塊圖](https://hackmd.io/_uploads/r1ILwe1V6.jpg)
## 完成的繞線版
![IMG_2964](https://hackmd.io/_uploads/BJx_vx1Na.jpg)
## 使用STM32L476RG作為模擬記憶體
| FUNC|PORT A|FUNC|PORT B|FUNC|PORT C|FUNC|OTHERS|
| --- | ---- | -- | - | - | - | - | - |
| D0 | PA0 | |(PB0) | A0 | PC0 | | PD2 |
| | PA1 | D1 | PB1 | A1 | PC1 | | PH0 |
| TX* | PA2 | D2 | PB2 | A2 | PC2 | | PH1 |
| RX* | (PA3)| D3 | PB3 | A3 | PC3 | | |
| | (PA4)| D4 | PB4 | A4 | PC4 | | |
| | (PA5)| D5 | PB5 | A5 | PC5 | | |
|IOREN| PA6 | D6 | PB6 | A6 | PC6 | | |
| MEMW| PA7 | D7 | PB7 | A7 | PC7 | | |
| CLR | PA8 | D8 | PB8 | A8 | PC8 | | |
| MEMR| PA9 | D9 | PB9 | A9 | PC9 | | |
| A13 | PA10 | D10| PB10 | A10| PC10 | | |
| A14 | PA11 | D11| PB11 | A11| PC11 | | |
| A15 | PA12 | D12| PB12 | A12| PC12 | | |
| TMS*| PA13 | D13| PB13 | | PC13 | | |
| TCK*| PA14 | D14| PB14 | | PC14 | | |
| IOR | PA15 | D15| PB15 | | PC15 | | |
*:STM32-476RG板子上的功能
![IMG_2960](https://hackmd.io/_uploads/B1OFDeJVT.jpg)
## 終於可以跟我的CPU玩剪刀石頭布
![IMG_9EAFFD4811B1-1](https://hackmd.io/_uploads/H1lwuekEa.jpg)
## mac上的vt100終端機
在stm32上使用aarduino IDE規劃成VT100輸出
將模擬記憶體顯示出來 以及接受使用者使用鍵盤輸入控制信號
F1:單步 F2:RESET F3:RUN F4:STOP
terminal APP: WindTerm
## 油土伯講稿
標題:只有一個指令的CPU!?
繼電器電腦
###### tags: `cpu`