[Tiny Processor] 邏輯單元(Logical Unit)實作

前面先把算術單元補起來之後,這次要繼續把 logical unit 加進 processor,讓 ALU 的功能更完整。

這一篇會加入 ANDORXORXNORNANDNORNOT,並延續前面的 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

其中 ANDORXORXNOR 都可以直接使用 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_ORR_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

ANDXORXNOR 的處理方式基本上都相同,只需要替換對應的 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 的邏輯運算,分別是 ANDXOR

測試 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 下的 ORNORNOT

ORNOR 使用 GPR4GPR16 作為 source,並將結果寫入 GPR0NOT 則對 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] 的變化:

logical unit wave form

後記

越來越熟悉基本邏輯和流程了,有些地方其實和軟體工程很相似,只是面對的問題和思考方式不太一樣。

不管是先拆功能再一小塊一小塊實作與驗證,還是透過 testbench 去確認每個 operation 是否符合預期,本質上都和軟體開發沒有想像中那麼遠。只是到了硬體,多了一層 timing、signal 與實際電路行為需要考慮。

也因此有時候會更不懂某些硬體工程師那種優越感到底從哪裡來 XD

軟體工程師確實不一定熟悉電路,但每次只要討論到 coding,好像就很容易被預設成「應該不懂吧?」甚至直接被當成什麼都不會。可實際上,兩邊本來就有大量共通的思考方式,領域不同,不代表另一邊就只是把 code 寫一寫而已。(我還被從來沒寫過軟體的人指教過相當多次呢)(性別好像也是一個因素,可以延伸討論)

更有趣的是,我實際碰過的一些硬體工程師,可能對具體的東西可以很清楚,不過一旦問題往 abstraction、interface 或整體討論架構拉高,反而討論會很明顯卡住。更麻煩的是,有時候對方明明理解已經跟不上了,臉皮又很硬拉不下來,往往要真的執行下去之後,才會發現前面的問題其實根本沒有釐清。

想到職場上遇到的討論實況,反而會覺得有點好笑。不同領域擅長處理的抽象層級本來就不一樣,還真的沒必要相輕。自己吃老本不會在私底下進修,不代表別人也不會。

突然有感而發想小抱怨一下哈哈哈哈哈XD


References

讓我知道你在想什麼!