ALU: chọn phép toán và giữ đúng hợp đồng cờ

Khoảng 55 phútMức 2/3Bản nháp: một số khẳng định chưa đối chiếu spec

Sau bài này bạn sẽ

  • Định nghĩa opcode, độ rộng và cờ trước khi kiểm ALU
  • Dùng chung đường cộng cho ADD/SUB và so signed
  • Đối chiếu kết quả lẫn Z/N/C/V bằng mô hình số nguyên

Đúng kết quả mà sai cờ vẫn là sai ALU

Một ALU có thể trả đúng tổng nhưng báo sai carry, làm nhánh điều kiện của bên dùng ra quyết định sai. Cần mô tả cả dữ liệu lẫn cờ, không chỉ liệt kê các phép toán.

Bài này dùng A/B bốn bit, opcode ba bit và kết quả bốn bit. Đây là hợp đồng ví dụ để học cách suy luận/kiểm, không phải đặc tả của một ISA có sẵn.

Bảng opcode chốt mọi mã 0/1

Opcode Phép Kết quả
000 ADD Bốn bit thấp của A+B
001 SUB Bốn bit thấp của A−B
010 AND AND từng bit
011 OR OR từng bit
100 XOR XOR từng bit
101 LSL Dịch trái logic A
110 LSR Dịch phải logic A
111 SLT 1 nếu A nhỏ hơn B theo signed, ngược lại 0

Số bước dịch lấy từ hai bit thấp của B, từ 0 tới 3. Ví dụ raw B bằng 1111 vẫn cho shift amount 3, không phải 15. LSL bỏ bit vượt độ rộng; LSR chèn 0 từ trái.

Cả tám mã binary xác định đều đã được gán ý nghĩa. X/Z ở opcode chưa thuộc miền kiểm trong ví dụ; một default trong source không tự chứng minh mọi tình huống unknown được xử lý đúng yêu cầu.

Một đường cộng, một đường logic và bộ chọn

ADD đưa B nguyên vẹn vào adder, carry-in 0. SUB đảo B rồi dùng carry-in 1. SLT cũng cần hiệu A−B nội bộ. Như vậy ba phép dùng chung một đường số học theo điều khiển opcode.

Các phép AND/OR/XOR và shift tạo các ứng viên khác. Bộ chọn đầu ra lấy đúng ứng viên; các cờ liên quan cũng phải được chọn hoặc đặt lại theo cùng hợp đồng.

Ví dụ 3 trừ 5 đi qua đường số học để tạo raw 1110. Chọn nhầm đường ADD sẽ trả 1000; chỉ kiểm “đã có adder và mux” chưa xác nhận control đúng.

Quy ước cờ của ví dụ

Cờ Định nghĩa ở bài
Z 1 khi kết quả bốn bit bằng 0
N Bằng MSB của kết quả
C ở ADD Carry-out của tổng unsigned
C ở SUB 1 khi không borrow theo unsigned
V ở ADD/SUB Tổng/hiệu signed không vừa bốn bit
C/V ở sáu opcode khác Đặt 0, không giữ cờ của phép trước

Z/N lấy từ kết quả cuối đã chọn, không lấy nhầm tổng nội bộ khi opcode đang chọn logic hay SLT. N chỉ là bit cao; với overflow, nó không biểu diễn dấu của kết quả toán học không giới hạn.

Shift của bài không xuất cờ “bit cuối bị đẩy ra”; C được đặt 0 theo bảng. Một ISA khác có thể quy định khác. Muốn kiểm đúng phải theo đặc tả đang dùng.

SLT cần hiệu chỉnh dấu của phép trừ

Dấu của hiệu hẹp có thể sai do overflow. Với signed bốn bit, +7 trừ −1 có hiệu thật +8 nhưng raw kết quả 1000 có MSB 1.

Gọi N_diff là bit dấu của hiệu nội bộ, V_diff là overflow của phép trừ. Điều kiện signed-less là N_diff XOR V_diff. Ca trên cho 1 XOR 1 bằng 0: +7 không nhỏ hơn −1.

Ngược lại −8 trừ +1 có hiệu thật −9, raw 0111 có bit dấu 0 nhưng overflow 1; 0 XOR 1 bằng 1, nên −8 nhỏ hơn +1.

Đây là cờ của đường trừ nội bộ, không phải lấy N/V của kết quả SLT đã đổi thành 0 hoặc 1. Cờ C/V xuất ra ngoài ở opcode SLT vẫn bằng 0 theo hợp đồng.

Tính cả dữ liệu và cờ

| Phép | A/B theo ý nghĩa đã nêu | Raw kết quả | Z | N | C | V | |---|---|---|---|---|---|---|---| | ADD | +7 và +1 | 1000 | 0 | 1 | 0 | 1 | | SUB | Unsigned 3 và 5 | 1110 | 0 | 1 | 0 | 0 | | SUB | 5 và 5 | 0000 | 1 | 0 | 1 | 0 | | AND | 1010 và 0101 | 0000 | 1 | 0 | 0 | 0 | | SLT | Signed −8 và +7 | 0001 | 0 | 0 | 0 | 0 |

Ở hàng SLT, A âm nhưng N bằng 0 vì N đọc kết quả 0001. Ở hàng AND, một cờ carry của phép trước không được rò sang kết quả mới.

ALU là tổ hợp trong ví dụ, không có clock hoặc cờ giữ trong thanh ghi. Nếu hệ thống cần lưu flags ở một stage, phải xác định thêm enable, reset và latency ở thiết kế chứa ALU.

Các lỗi control và cờ hay gặp

  • Đảo bảng opcode giữa datapath và testbench.
  • Dùng N_diff một mình cho SLT mà bỏ V_diff.
  • Lấy Z/N từ adder dù đang chọn kết quả logic.
  • Không gán mặc định cho C/V nên giữ cờ cũ hoặc suy latch.
  • Dùng toàn bộ B làm số bước dịch trong khi hợp đồng chỉ dùng hai bit thấp.
  • Mặc định mọi ISA có cùng quy tắc carry/borrow hoặc shift flag.

Khi debug, ghi opcode, cách đọc A/B, raw kết quả và từng cờ. Một nhãn “ADD pass” không thay việc đối chiếu các biên về độ rộng và signedness.

Tự viết mô hình trước khi nhìn RTL

  1. Tính ADD raw 1111 với 0001 và cả Z/N/C/V.
  2. Tính SLT signed A=0111, B=1111; vì sao không dùng riêng dấu hiệu?
  3. Với LSR, A=1000, B=1111, kết quả và N là gì?
  4. Nếu sau ADD có carry rồi thực hiện XOR, C cần bằng gì trong hợp đồng bài?
  5. Một ISA yêu cầu shift trả bit cuối bị đẩy ra ở C. Bạn phải sửa phần đặc tả và phép kiểm nào, thay vì chỉ đổi một hằng trong test?

Kiểm tra

Tự kiểm tra: ALU: chọn phép toán và giữ đúng hợp đồng cờ

  1. SLT signed A=0111,B=1111 phải trả kết quả nào?
  2. Sau ADD rồi thực hiện XOR, C/V trong hợp đồng bài phải thế nào?
  3. LSR với A=1000,B=1111 dùng shift amount nào và cho kết quả gì?
  4. SLT với A âm nhưng đầu ra 0001 thì N xuất ra bằng bao nhiêu?
  5. AND nhận A=1010,B=0101 cho result/Z nào?

Bằng chứng kiểm chứng

Expected tính trước chạy: ALU CHECK vectors=2048 opcodes=8 zero=386 negative=800 carry=256 overflow=128 signed_less=120 time_ns=2048.

ALU bốn bit với tám opcode và quy ước cờ tường minh

Kỳ vọng tính trước chạy: ALU CHECK vectors=2048 opcodes=8 zero=386 negative=800 carry=256 overflow=128 signed_less=120 time_ns=2048

alu_core.sv RTL
`timescale 1ns/1psmodule alu_core(input logic [3:0] a,b,input logic [2:0] opcode, output logic [3:0] result,output wire zero,negative,output logic carry,overflow); wire subtract=(opcode==3'd1)||(opcode==3'd7); wire [3:0] effective_b=b^{4{subtract}}; wire [4:0] arithmetic={1'b0,a}+{1'b0,effective_b}+{4'b0000,subtract}; wire arithmetic_overflow=(a[3]==effective_b[3])&&(arithmetic[3]!=a[3]); always_comb begin  result=4'b0000;carry=0;overflow=0;  case(opcode)   3'd0,3'd1:begin result=arithmetic[3:0];carry=arithmetic[4];overflow=arithmetic_overflow;end   3'd2:result=a&b;   3'd3:result=a|b;   3'd4:result=a^b;   3'd5:result=a<<b[1:0];   3'd6:result=a>>b[1:0];   3'd7:result={3'b000,arithmetic[3]^arithmetic_overflow};   default:begin result=0;carry=0;overflow=0;end  endcase end assign zero=(result==0); assign negative=result[3];endmodule
tb.sv testbench
`timescale 1ns/1psmodule tb_a3_alu; logic [3:0] a,b;logic [2:0] opcode; wire [3:0] result;wire zero,negative,carry,overflow;alu_core dut(.*); int vectors=0,seen_mask=0,z_count=0,n_count=0,c_count=0,v_count=0,less_count=0; int reference,ai,bi,exact,ab,bb;bit ref_c,ref_v; initial begin  for(int op=0;op<8;op++)for(int ia=0;ia<16;ia++)for(int ib=0;ib<16;ib++)begin   a=4'(ia);b=4'(ib);opcode=3'(op);#1;vectors++;seen_mask|=1<<op;   ai=ia<8?ia:ia-16;bi=ib<8?ib:ib-16;reference=0;ref_c=0;ref_v=0;   case(op)    0,1:begin     exact=op==0?ai+bi:ai-bi;reference=op==0?ia+ib:ia-ib;reference=(reference%16+16)%16;     ref_c=op==0?(ia+ib>=16):(ia>=ib);ref_v=exact < -8 || exact > 7;    end    2,3,4:for(int k=0;k<4;k++)begin     ab=(ia/(2**k))%2;bb=(ib/(2**k))%2;     if((op==2&&ab+bb==2)||(op==3&&ab+bb>=1)||(op==4&&ab!=bb))reference+=2**k;    end    5:reference=(ia*(2**(ib%4)))%16;    6:reference=ia/(2**(ib%4));    7:reference=ai<bi?1:0;    default:$fatal(1,"Unexpected reference opcode");   endcase   if(result!==4'(reference)||zero!==(reference==0)||negative!==(reference>=8)||carry!==ref_c||overflow!==ref_v)    $fatal(1,"ALU result/flags mismatch op=%0d a=%0d b=%0d",op,ia,ib);   if(zero)z_count++;if(negative)n_count++;if(carry)c_count++;if(overflow)v_count++;if(op==7&&reference==1)less_count++;  end  if(vectors!=2048||seen_mask!=255||z_count!=386||n_count!=800||c_count!=256||v_count!=128||less_count!=120||$time!=2048)$fatal(1,"ALU experiment contract");  $display("ALU CHECK vectors=%0d opcodes=%0d zero=%0d negative=%0d carry=%0d overflow=%0d signed_less=%0d time_ns=%0d",vectors,$countones(seen_mask),z_count,n_count,c_count,v_count,less_count,$time);  $finish; endendmodule

Đã kiểm chứng thế nào

  • Phân tích và elaborate theo LRM (pyslang 12.1.0)
  • Lint -Wall (Verilator 5.050)
  • Mô phỏng, exit 0 và output khớp kỳ vọng tính tay (Verilator 5.050, 2-state)
  • Tổng hợp, số cell khớp kỳ vọng (Yosys 0.70)

Output phải khớp: ALU CHECK vectors=2048 opcodes=8 zero=386 negative=800 carry=256 overflow=128 signed_less=120 time_ns=2048

Kỳ vọng sau tổng hợp: alu_core: $_*DFF* = 0alu_core: $_*LATCH* = 0

Verilator 5.050 two-state and finite four-bit domains; no all-width/XZ conformance. Generic combinational synthesis, not physical timing/PPA or exact cell mapping.

Các kiểm tra chạy lúc xây dựng nội dung (2026-11-11), không chạy trong trình duyệt. Kỳ vọng được tính tay trước khi chạy.

Duyệt tám opcode × 256 cặp A/B, kiểm kết quả và mọi cờ ở từng vector. Mô hình bitwise dùng đếm bit số nguyên; shift dùng nhân/chia lũy thừa hai rồi modulo. Signed-less so trực tiếp hai số nguyên đã giải mã, tách khỏi cách hiệu chỉnh dấu của DUT.

Expected Z theo opcode là 16/16/81/1/16/60/60/136, tổng 386; N là 128/128/64/192/128/128/32/0, tổng 800. C chỉ đếm ADD/SUB, tổng 256; V tổng 128; SLT có 120 hàng đúng.

Parser/lint/simulation/synthesis đạt; không có FF/latch trong generic netlist. Không claim timing, cell sharing cụ thể sau mapping, ISA compliance hoặc X/Z. Bảng opcode/cờ là hợp đồng riêng; thay hợp đồng cần sửa mô hình và tính lại kỳ vọng trước chạy.

[1] [2] [3]

Nguồn và mức độ đối chiếu

  1. Comparators & ALUs — bản chụp resources.zip, Inside an ALU; A Worked 8-Bit ALU; What Hardware Each Part Needs. Trích dẫn; nguồn đã đọc các mục liên quan.
  2. Thí nghiệm nguyên bản A3.alu, All eight four-bit opcodes, independent integer/bit-count result model and complete Z/N/C/V checks. Trích dẫn; nguồn đã đọc toàn văn.
  3. IEEE 1800-2023 SystemVerilog LRM, Combinational case selection, expression sizing, shifts, signed comparison correction and flag defaults — chưa đối chiếu LRM. Chưa đối chiếu với văn bản nguồn (chưa mở).