前面先把算術單元補起來之後,這次要繼續把 logical unit 加進 processor,讓 ALU 的功能更完整。
這一篇會加入 AND、OR、XOR、XNOR、NAND、NOR 與 NOT,並延續前面的 instruction format 與 addressing mode,最後再用 testbench 和 waveform 驗證結果。
支援的 Logical Operations
接下來要替 processor 加入 logical unit。前面已經完成 arithmetic unit,負責加法、減法與乘法等算術運算;加入 logical unit 之後,就組成了完整的 ALU(Arithmetic Logic Unit)。
Logical unit 和 arithmetic unit 一樣,運算所需要的資料會從 GPR 取得。
不過 logical operation 和 multiplication 有一個重要差別:邏輯運算的輸出 bit 數通常和輸入相同。
例如兩個 16-bit 資料做 AND,就是 16-bit AND 16-bit -> 16-bit result,不會像 multiplication,需要額外使用 SGPR 保存高 16 bits,logical unit 的結果可以直接存回一般的 GPR。
Verilog 的 Bitwise Operators
這個 processor 預計支援以下 logical operations:
AND
OR
XOR
XNOR
NAND
NOR
NOT
其中 AND、OR、XOR 與 XNOR 都可以直接使用 Verilog 的 bitwise operators:
& // AND
| // OR
^ // XOR
^~ // XNOR
例如:
Rdest = Rsrc1 & Rsrc2;
會針對兩個 register 的每一個 bit 分別進行 AND。
假設 Rsrc1 = 1010 以及 Rsrc2 = 1100,則:
1010
1100
----
1000
因此 Rdest = 1000
NAND 與 NOR
Verilog 沒有獨立的 binary bitwise NAND、NOR operator,因此可以利用現有的 bitwise operation 再搭配 NOT。
NAND 可以寫成:
~(Rsrc1 & Rsrc2)
NOR 則是:
~(Rsrc1 | Rsrc2)
另外,Verilog 確實有 ~&、~| 這類 reduction operators,但那是把一整組 bits reduction 成單一 bit,和這裡要做的逐 bit NAND / NOR 不同。
NOT
NOT 只需要一個 source。
Verilog 使用 ~
例如:
Rdest = ~Rsrc1;
假設 Rsrc1 = 1010,則 Rdest = 0101。
Logical Instructions
除了 NOT 之外,其餘 logical instructions 都需要 2 個 Source 和 1 個 Destination。
例如 OR:
OR R2, R1, R0
代表:
R2 = R1 | R0
同樣的形式也適用於其他邏輯。而且和前面的 arithmetic instructions 一樣,這些 operation 也會支援 immediate addressing mode。
因此第二個 source 可以是 register:
Rdest = Rsrc1 OP Rsrc2
也可以是 immediate data:
Rdest = Rsrc1 OP Immediate
至於 NOT,因為只需要對一筆資料取反,因此只需要 1 個 Source 和 1 個 Destination
例如:
NOT R2, R1
代表:
R2 = ~R1
下一步就是把這些 logical instructions 加入目前既有的 RTL,並和前面的 instruction format、GPR 與 addressing mode 整合起來。
邏輯指令的 Opcode 與 RTL 實作
和前面的 arithmetic operations 一樣,logical operations 也會透過 `define 為每一種 operation 配置唯一的 code,讓後續撰寫 RTL 時不需要直接使用數值判斷。
由於 or 本身是 Verilog 的 keyword,因此不能直接拿來當作 `define 的名稱,所以這裡改用 R_OR、R_AND 等名稱。
目前 logical operations 的 code 依序為:
`define R_OR 5'b00101 `define R_AND 5'b00110 `define R_XOR 5'b00111 `define R_XNOR 5'b01000 `define R_NAND 5'b01001 `define R_NOR 5'b01010 `define R_NOT 5'b01011
也就是:
OR -> 5
AND -> 6
XOR -> 7
XNOR -> 8
NAND -> 9
NOR -> 10
NOT -> 11
每一個 code 都必須保持唯一,processor 才能根據 OP_TYPE 判斷目前要執行哪一種 operation。
OR
OR 同樣支援 register addressing mode 與 immediate addressing mode。
RTL 可以寫成:
`R_OR: begin
if (`IMM_MODE)
GPR[`RDST] = GPR[`RSRC1] | `IMM_DATA;
else
GPR[`RDST] = GPR[`RSRC1] | GPR[`RSRC2];
end
AND、XOR 與 XNOR
AND、XOR、XNOR 的處理方式基本上都相同,只需要替換對應的 bitwise operator。
`R_AND: begin
if (`IMM_MODE)
GPR[`RDST] = GPR[`RSRC1] & `IMM_DATA;
else
GPR[`RDST] = GPR[`RSRC1] & GPR[`RSRC2];
end
`R_XOR: begin
if (`IMM_MODE)
GPR[`RDST] = GPR[`RSRC1] ^ `IMM_DATA;
else
GPR[`RDST] = GPR[`RSRC1] ^ GPR[`RSRC2];
end
XNOR 則可以使用 ^~:
`R_XNOR: begin
if (`IMM_MODE)
GPR[`RDST] = GPR[`RSRC1] ^~ `IMM_DATA;
else
GPR[`RDST] = GPR[`RSRC1] ^~ GPR[`RSRC2];
end
NAND 與 NOR
NAND 與 NOR 沒有對應的 binary bitwise operator,因此需要組合其他 operator。
NAND 是先做 AND,再將結果取反:
Rdest = ~(Rsrc1 & Source2)
RTL:
`R_NAND: begin
if (`IMM_MODE)
GPR[`RDST] = ~(GPR[`RSRC1] & `IMM_DATA);
else
GPR[`RDST] = ~(GPR[`RSRC1] & GPR[`RSRC2]);
end
NOR 則是先做 OR,再將結果取反:
Rdest = ~(Rsrc1 | Source2)
RTL:
`R_NOR: begin
if (`IMM_MODE)
GPR[`RDST] = ~(GPR[`RSRC1] | `IMM_DATA);
else
GPR[`RDST] = ~(GPR[`RSRC1] | GPR[`RSRC2]);
end
NOT
NOT 和前面幾個 logical operations 不同,它只需要一個 operand。
如果使用 immediate addressing mode:Rdest = ~Immediate
如果使用 register addressing mode:Rdest = ~Rsrc1
RTL 可以寫成:
`R_NOT: begin
if (`IMM_MODE)
GPR[`RDST] = ~`IMM_DATA;
else
GPR[`RDST] = ~GPR[`RSRC1];
end
除了 NOT,其餘 operation 都可以使用兩個 source,並透過 IMM_MODE 決定第二個 operand 是:
IMM_MODE = 0 -> GPR[`RSRC2]
IMM_MODE = 1 -> IMM_DATA
接下來就可以在 testbench 中挑幾個 logical operations 進行驗證,其餘則可以用相同方式補上測試。
測試邏輯運算
接下來會在 testbench 中驗證 logical operation。
這裡先測試兩種 immediate mode 的邏輯運算,分別是 AND 與 XOR。
測試 AND 與 XOR
首先測試 AND。
因為使用 immediate addressing mode,所以先將 instruction register 清為 0,並將 IMM_MODE 設定為 1,這次設定:
Destination -> R4
Source 1 -> R7
Immediate -> 56
因此概念上的 operation 是:
R4 = R7 & 56
testbench 可以寫成:
// ------------------------------------------------
// AND with immediate addressing mode
// R4 = R7 & 56
// ------------------------------------------------
dut.IR = 32'd0;
dut.`IMM_MODE = 1'b1;
dut.`OP_TYPE = `R_AND;
dut.`RDST = 5'd4;
dut.`RSRC1 = 5'd7;
dut.`IMM_DATA = 16'd56;
#10;
$display("AND Immediate");
$display("Source = %8b", dut.GPR[7]);
$display("Immediate = %8b", dut.`IMM_DATA);
$display("Destination = %8b", dut.GPR[4]);
這裡使用 binary 顯示數值,方便直接觀察 bitwise operation 的結果。
接著測試 XOR。
同樣使用 immediate addressing mode,因此這次執行的是:
R4 = R7 ^ 56
Testbench 可以寫成:
// ------------------------------------------------
// XOR with immediate addressing mode
// R4 = R7 ^ 56
// ------------------------------------------------
dut.IR = 32'd0;
dut.`IMM_MODE = 1'b1;
dut.`OP_TYPE = `R_XOR;
dut.`RDST = 5'd4;
dut.`RSRC1 = 5'd7;
dut.`IMM_DATA = 16'd56;
#10;
$display("XOR Immediate");
$display("Source = %8b", dut.GPR[7]);
$display("Immediate = %8b", dut.`IMM_DATA);
$display("Destination = %8b", dut.GPR[4]);
這裡主要是在確認 logical unit 在 immediate addressing mode 下是否能正確取得 Rsrc1 與 immediate data,並將 bitwise operation 的結果寫入 destination register。
register addressing mode 的測試方式則相同,只需要將 IMM_MODE 改為 0,並改由 RSRC2 指定第二個 source register。
ok 來看看目前為止的 output 吧:
AND Immediate
Source = 00000010
Immediate = 00111000
Destination = 00000000
XOR Immediate
Source = 00000010
Immediate = 00111000
Destination = 00111010
看起來是沒什麼問題,可以繼續。
測試 OR、NOR 與 NOT
接著測試 register addressing mode 下的 OR、NOR 與 NOT。
OR 與 NOR 使用 GPR4 和 GPR16 作為 source,並將結果寫入 GPR0;NOT 則對 GPR6 取反,同樣將結果寫入 GPR0。
原本所有 GPR 都初始化為 2,這樣雖然可以驗證功能,但從 console 或 waveform 上不太容易看出 logical operation 的變化。因此這裡另外指定幾組較明顯的 bit pattern:
// Temporarily use an undefined operation code dut.IR = 32'hF800_0000; #1; // Set distinct values for register-mode logical tests dut.GPR[4] = 16'h0F0F; dut.GPR[16] = 16'h3333; dut.GPR[6] = 16'hAA55;
接著測試 OR:
// ------------------------------------------------
// OR with register addressing mode
// R0 = R4 | R16
// ------------------------------------------------
dut.IR = 32'd0;
dut.`IMM_MODE = 1'b0;
dut.`OP_TYPE = `R_OR;
dut.`RDST = 5'd0;
dut.`RSRC1 = 5'd4;
dut.`RSRC2 = 5'd16;
#10;
$display("OR Register");
$display("Source 1 = %16b", dut.GPR[4]);
$display("Source 2 = %16b", dut.GPR[16]);
$display("Destination = %16b", dut.GPR[0]);
NOR:
// ------------------------------------------------
// NOR with register addressing mode
// R0 = ~(R4 | R16)
// ------------------------------------------------
dut.IR = 32'd0;
dut.`IMM_MODE = 1'b0;
dut.`OP_TYPE = `R_NOR;
dut.`RDST = 5'd0;
dut.`RSRC1 = 5'd4;
dut.`RSRC2 = 5'd16;
#10;
$display("NOR Register");
$display("Source 1 = %16b", dut.GPR[4]);
$display("Source 2 = %16b", dut.GPR[16]);
$display("Destination = %16b", dut.GPR[0]);
最後是 NOT:
// ------------------------------------------------
// NOT with register addressing mode
// R0 = ~R6
// ------------------------------------------------
dut.IR = 32'd0;
dut.`IMM_MODE = 1'b0;
dut.`OP_TYPE = `R_NOT;
dut.`RDST = 5'd0;
dut.`RSRC1 = 5'd6;
#10;
$display("NOT Register");
$display("Source = %16b", dut.GPR[6]);
$display("Destination = %16b", dut.GPR[0]);
這組資料的預期結果會是:
OR
0F0F | 3333 -> 3F3F
NOR
~(0F0F | 3333) -> C0C0
NOT
~AA55 -> 55AA
ok 來看看目前為止的 output 吧:
AND Immediate
Source = 0000000000000010
Immediate = 0000000000111000
Destination = 0000000000000000
XOR Immediate
Source = 0000000000000010
Immediate = 0000000000111000
Destination = 0000000000111010
OR Register
Source 1 = 0000111100001111
Source 2 = 0011001100110011
Destination = 0011111100111111
NOR Register
Source 1 = 0000111100001111
Source 2 = 0011001100110011
Destination = 1100000011000000
NOT Register
Source = 1010101001010101
Destination = 0101010110101010
wave form 看個 GPR[0] 的變化:

後記
越來越熟悉基本邏輯和流程了,有些地方其實和軟體工程很相似,只是面對的問題和思考方式不太一樣。
不管是先拆功能再一小塊一小塊實作與驗證,還是透過 testbench 去確認每個 operation 是否符合預期,本質上都和軟體開發沒有想像中那麼遠。只是到了硬體,多了一層 timing、signal 與實際電路行為需要考慮。
也因此有時候會更不懂某些硬體工程師那種優越感到底從哪裡來 XD
軟體工程師確實不一定熟悉電路,但每次只要討論到 coding,好像就很容易被預設成「應該不懂吧?」甚至直接被當成什麼都不會。可實際上,兩邊本來就有大量共通的思考方式,領域不同,不代表另一邊就只是把 code 寫一寫而已。(我還被從來沒寫過軟體的人指教過相當多次呢)(性別好像也是一個因素,可以延伸討論)
更有趣的是,我實際碰過的一些硬體工程師,可能對具體的東西可以很清楚,不過一旦問題往 abstraction、interface 或整體討論架構拉高,反而討論會很明顯卡住。更麻煩的是,有時候對方明明理解已經跟不上了,臉皮又很硬拉不下來,往往要真的執行下去之後,才會發現前面的問題其實根本沒有釐清。
想到職場上遇到的討論實況,反而會覺得有點好笑。不同領域擅長處理的抽象層級本來就不一樣,還真的沒必要相輕。自己吃老本不會在私底下進修,不代表別人也不會。
突然有感而發想小抱怨一下哈哈哈哈哈XD
References
- 封面圖由 DIvan Chumak 拍攝





