Foundationপ্রথম নীতি থেকে
LEVEL 2কঠিন~১০ ঘণ্টাLogisimVerilog

8-bit ALU

8-bit ALU

একটা ৮-বিট ALU — ADD, SUB, AND, OR, XOR, শিফট, আর zero/carry/overflow/negative ফ্ল্যাগ সহ — একবার Logisim-এ গেট দিয়ে, একবার Verilog-এ, তারপর একই test vector টেবিল দিয়ে দুইটাকেই hardware-verification-এর মতো যাচাই করা।

মাইলস্টোন

আগে যা পড়া দরকার

কেন এই প্রজেক্ট

ALU Design লেসনে আমরা দেখেছি কীভাবে adder, logic gate, আর multiplexer জোড়া লাগিয়ে একটা op-select সিগন্যাল দিয়ে একগুচ্ছ অপারেশনের মধ্যে বেছে নেওয়া যায়। এই প্রজেক্টে সেটা দুইবার বানাব — একবার Logisim/Digital-এ, তার-দিয়ে-জোড়া-লাগানো গেট হিসেবে (চোখে দেখা যায় বিদ্যুৎ কোন পথে যাচ্ছে), আর একবার Verilog-এ, টেক্সট দিয়ে বর্ণনা করে (একটা synthesis tool যেটাকে গেটে রূপান্তর করবে)।

দুইবার বানানোর আসল কারণ হলো — এটাই প্রথমবার আপনি দেখবেন একই ডিজাইন দুইটা সম্পূর্ণ ভিন্ন abstraction level-এ কীভাবে প্রকাশ পায়। Logisim-এ আপনি প্রতিটা তার নিজে টানছেন। Verilog-এ আপনি বলছেন “op যদি SUB হয়, ফলাফল A বিয়োগ B” — আর টুলচেইন বাকিটা বের করে নেয়। Level 2-এর বাকি অংশে (আর Level 3-এ পুরোপুরি) আমরা ধীরে ধীরে দ্বিতীয় পদ্ধতিতে সরে যাব, কিন্তু প্রথমটা না বুঝলে দ্বিতীয়টা জাদুবিদ্যা মনে হবে।

শেষে যে ধাপটা সবচেয়ে গুরুত্বপূর্ণ — verification। “দেখতে ঠিক লাগছে” আর “স্পেসিফিকেশন মেনে চলছে” এক জিনিস না। বাস্তব হার্ডওয়্যার ডিজাইনাররা একটা test vector টেবিল বানান (নির্দিষ্ট ইনপুট → প্রত্যাশিত আউটপুট) আর তার বিপরীতে ডিজাইন যাচাই করেন। এখানে আমরা ঠিক সেটাই করব — দুইটা আলাদা implementation-কে একই স্পেসিফিকেশনের বিপরীতে পরীক্ষা করে দেখব তারা একমত কি না।

ALU স্পেসিফিকেশন

৩-বিট op-select কোড (৭টা ব্যবহৃত, একটা reserved):

opঅপারেশনফলাফলcarryoverflow
000ADDA + Bcarry-outsigned overflow
001SUBA − BNOT borrowsigned overflow
010ANDA & B00
011ORA | B00
100XORA ^ B00
101SHLA-কে ১ বিট বামে শিফট (LSB-তে 0 ঢোকে)shift-out বিট0
110SHRA-কে ১ বিট ডানে শিফট (logical, MSB-তে 0 ঢোকে)shift-out বিট0
111সংরক্ষিত (ভবিষ্যতের জন্য ফাঁকা রাখা)

সব ক্ষেত্রে zero = (result == 0) আর negative = result[7] (ফলাফলকে two’s complement signed ধরলে তার sign bit)।

ধাপে ধাপে — Logisim / Digital

১. Adder/Subtractor — একই সার্কিট দুই কাজে

একটা ৮-বিট adder/subtractor বানানো যায় মাত্র একটা ট্রিক দিয়ে — B-এর প্রতিটা বিট একটা XOR গেট দিয়ে একটা SUB কন্ট্রোল লাইনের সাথে XOR করুন, আর সেই একই SUB লাইনটা প্রথম full adder-এর carry-in-এ দিন:

B'_i = B_i XOR SUB          (SUB=0 হলে B'_i = B_i;  SUB=1 হলে B'_i = NOT(B_i))
Cin_0 = SUB
FullAdder_i(A_i, B'_i, Cin_i) → Sum_i, Cout_i
Cin_(i+1) = Cout_i           (ripple carry — একটার carry-out পরেরটার carry-in)

কেন কাজ করে: two’s complement-এ A − B = A + (NOT B) + 1SUB=1 হলে B ইনভার্ট হয়ে যায় (NOT B) আর +1 আসে carry-in থেকে — একই adder চেইন তখন বিয়োগ করছে, কোনো আলাদা সার্কিট ছাড়াই। SUB=0 হলে B'=B, Cin_0=0 — সাধারণ যোগ।

আটটা full adder চেইন করে R[7:0] আর চূড়ান্ত Cout_7 পাবেন।

২. Overflow — carry-based সূত্র

Logisim-এ ripple-carry adder থেকে সরাসরি পাওয়া যায় এমন একটা সূত্র ব্যবহার করা সবচেয়ে সহজ:

overflow = Cout_6 XOR Cout_7

অর্থাৎ, sign bit (বিট ৭)-এ ঢোকা carry আর সেখান থেকে বেরোনো carry যদি আলাদা হয়, তাহলে overflow। এটা মাত্র একটা XOR গেট — অতিরিক্ত কোনো sign-comparison লজিক লাগে না, কারণ Cout_6 আর Cout_7 দুইটাই আগে থেকেই adder চেইনে বিদ্যমান।

৩. AND/OR/XOR — bitwise গেট অ্যারে

আটটা 2-input AND গেট পাশাপাশি (A_i AND B_i), আটটা OR, আটটা XOR — প্রতিটা bit-position-এ স্বাধীনভাবে। কোনো carry propagate করার দরকার নেই, তাই এগুলো adder-এর চেয়ে অনেক সহজ আর দ্রুত (কোনো ripple delay নেই)।

৪. শিফটার — এক-বিট, শুধু ওয়্যারিং

একটা fixed ১-বিট শিফট আসলে কোনো গেট লাগে না — শুধু ওয়্যার পুনর্বিন্যাস:

  • SHL: R[7:1] = A[6:0], R[0] = 0, shift-out বিট (carry) = A[7]
  • SHR: R[6:0] = A[7:1], R[7] = 0, shift-out বিট (carry) = A[0]

৫. আউটপুট মাল্টিপ্লেক্সার

৭টা (adder/subtractor একটাই সার্কিট, তাই কার্যত ৬টা ভিন্ন ফলাফল উৎস) ৮-বিট ফলাফল একটা ৮-টু-১ মাল্টিপ্লেক্সারে ঢুকিয়ে op-এর ৩ বিট দিয়ে সিলেক্ট করুন। carry আর overflow আউটপুটও একইভাবে op অনুযায়ী মাল্টিপ্লেক্স করতে হবে (AND/OR/XOR/শিফটে carry=overflow=0 হার্ডওয়্যার্ড)। zero আর negative চূড়ান্ত R[7:0] থেকে সরাসরি বের করা যায় — ওগুলোর জন্য আলাদা মাক্সের দরকার নেই।

ধাপে ধাপে — Verilog

৬. কম্বিনেশনাল ALU মডিউল

module alu8 (
    input      [7:0] a,
    input      [7:0] b,
    input      [2:0] op,
    output reg [7:0] result,
    output            zero,
    output reg        carry,
    output reg        overflow,
    output            negative
);
    wire [8:0] add_ext = {1'b0, a} + {1'b0, b};
    wire [8:0] sub_ext = {1'b0, a} + {1'b0, ~b} + 9'b1;

    always @(*) begin
        carry    = 1'b0;
        overflow = 1'b0;
        case (op)
            3'b000: begin                              // ADD
                result   = add_ext[7:0];
                carry    = add_ext[8];
                overflow = (a[7] == b[7]) && (result[7] != a[7]);
            end
            3'b001: begin                              // SUB (a - b)
                result   = sub_ext[7:0];
                carry    = sub_ext[8];                  // 1 মানে borrow লাগেনি (a >= b, unsigned)
                overflow = (a[7] != b[7]) && (result[7] != a[7]);
            end
            3'b010: result = a & b;                     // AND
            3'b011: result = a | b;                      // OR
            3'b100: result = a ^ b;                      // XOR
            3'b101: begin                                // SHL
                result = {a[6:0], 1'b0};
                carry  = a[7];
            end
            3'b110: begin                                // SHR (logical)
                result = {1'b0, a[7:1]};
                carry  = a[0];
            end
            default: result = 8'b0;                      // 111 — সংরক্ষিত
        endcase
    end

    assign zero     = (result == 8'b0);
    assign negative = result[7];
endmodule

লক্ষ্য করুন add_ext/sub_ext দুইটাই ৯-বিট — a, b-কে একটা অতিরিক্ত শূন্য বিট দিয়ে zero-extend করে যোগ করা হয়েছে, যাতে ৯ম বিট ([8]) সরাসরি carry-out হয়ে যায়। sub_ext = a + ~b + 1 হলো two’s complement বিয়োগের ঠিক সেই সূত্র যা Logisim-এর adder/subtractor ট্রিক-এ হার্ডওয়্যারে হয় — এখানে টেক্সটে লেখা।

Overflow-এর জন্য এখানে sign-comparison সূত্র ব্যবহার করলাম (a[7]==b[7] হয়ে result[7] আলাদা হলে overflow) — Logisim-এর carry-based সূত্রের (Cout_6 XOR Cout_7) সমতুল্য, কিন্তু বিহেভিয়ারাল কোডে বেশি readable। দুইটা সূত্রই সবসময় একই উত্তর দেয় — সেটা নিজে যাচাই করাও একটা ভালো অনুশীলন।

যাচাই — ১০টা টেস্ট ভেক্টর

ABopR (প্রত্যাশিত)ZCVNমন্তব্য
0x050x03ADD0x080000সাধারণ যোগ
0xFF0x01ADD0x001100unsigned carry হলো, কিন্তু signed overflow নয় (−1 + 1 = 0, সঠিক)
0x500x50ADD0xA00011দুইটা positive যোগ হয়ে negative — signed overflow
0x030x05SUB0xFE00013−5 = −2, borrow হয়েছে তাই C=0
0x800x01SUB0x7F0110−128−1 wrap করে +127; unsigned-এ borrow লাগেনি (C=1) তবু signed overflow (V=1)
0xF00x0FAND0x001000কোনো বিট মেলেনি
0xF00x0FOR0xFF0001সব বিট সেট
0xAA0x55XOR0xFF0001পরিপূরক প্যাটার্ন
0x81SHL0x020100MSB (1) শিফট-আউট হয়ে carry=1
0x01SHR0x001100LSB (1) শিফট-আউট হয়ে carry=1, ফলাফল শূন্য

চতুর্থ ও পঞ্চম সারি বিশেষভাবে গুরুত্বপূর্ণ — এরা দেখায় carry আর overflow সম্পূর্ণ আলাদা জিনিস বলছে0x80 − 0x01-এ unsigned দৃষ্টিতে কোনো borrow লাগেনি (128 ≥ 1) অথচ signed দৃষ্টিতে ফলাফল রেঞ্জের বাইরে চলে গেছে। একটা মাত্র ফ্ল্যাগ দিয়ে এই দুইটা প্রশ্নের উত্তর একসাথে দেওয়া অসম্ভব — এই জন্যই ALU-তে দুইটা আলাদা ফ্ল্যাগ রাখা হয়।

Logisim-এ প্রতিটা ভেক্টর ম্যানুয়ালি ইনপুট দিয়ে আউটপুট মিলিয়ে দেখুন। Verilog-এ একই কাজ একটা testbench দিয়ে স্বয়ংক্রিয় করা যায়:

`timescale 1ns/1ps

module alu8_tb;
    reg  [7:0] a, b;
    reg  [2:0] op;
    wire [7:0] result;
    wire zero, carry, overflow, negative;
    integer errors = 0;

    alu8 dut (.a(a), .b(b), .op(op), .result(result),
              .zero(zero), .carry(carry), .overflow(overflow), .negative(negative));

    task check(input [7:0] ta, input [7:0] tb, input [2:0] top,
               input [7:0] exp_r, input exp_z, input exp_c, input exp_v, input exp_n,
               input [127:0] label);
        begin
            a = ta; b = tb; op = top;
            #1;
            if (result !== exp_r || zero !== exp_z || carry !== exp_c ||
                overflow !== exp_v || negative !== exp_n) begin
                errors = errors + 1;
                $display("FAIL %0s : R=%h(exp %h) Z=%b(exp %b) C=%b(exp %b) V=%b(exp %b) N=%b(exp %b)",
                          label, result, exp_r, zero, exp_z, carry, exp_c, overflow, exp_v, negative, exp_n);
            end else begin
                $display("PASS %0s", label);
            end
        end
    endtask

    initial begin
        check(8'h05, 8'h03, 3'b000, 8'h08, 0,0,0,0, "ADD normal");
        check(8'hFF, 8'h01, 3'b000, 8'h00, 1,1,0,0, "ADD carry, no signed overflow");
        check(8'h50, 8'h50, 3'b000, 8'hA0, 0,0,1,1, "ADD signed overflow");
        check(8'h03, 8'h05, 3'b001, 8'hFE, 0,0,0,1, "SUB borrow");
        check(8'h80, 8'h01, 3'b001, 8'h7F, 0,1,1,0, "SUB signed overflow");
        check(8'hF0, 8'h0F, 3'b010, 8'h00, 1,0,0,0, "AND");
        check(8'hF0, 8'h0F, 3'b011, 8'hFF, 0,0,0,1, "OR");
        check(8'hAA, 8'h55, 3'b100, 8'hFF, 0,0,0,1, "XOR");
        check(8'h81, 8'h00, 3'b101, 8'h02, 0,1,0,0, "SHL boundary");
        check(8'h01, 8'h00, 3'b110, 8'h00, 1,1,0,0, "SHR boundary");

        if (errors == 0) $display("সব ১০টা টেস্ট PASS");
        else $display("%0d টা টেস্ট FAIL", errors);
        $finish;
    end
endmodule

iverilog -o alu_sim alu8.v alu8_tb.v && vvp alu_sim চালিয়ে দেখুন — সবগুলো লাইন PASS দেখানো উচিত। কোনোটা FAIL দিলে সেই একটা লাইনই বলে দেবে ঠিক কোন ফ্ল্যাগ বা কোন বিট মিলছে না, পুরো ডিজাইন নতুন করে দেখতে হবে না।

নিজেকে চ্যালেঞ্জ করুন

  1. Variable-amount শিফটop=101/110-এ B-এর নিচের ৩ বিটকে শিফট-অ্যামাউন্ট হিসেবে ব্যবহার করে barrel shifter বানান (log-shifter — ৩টা ধাপে ১/২/৪ বিট শিফটের মাক্স চেইন)
  2. Arithmetic shift right — logical SHR-এর পাশে একটা arithmetic SHR (sign bit repeat) যোগ করুন, op=111 ব্যবহার করে
  3. Multiply — ৮×৮ = ১৬-বিট গুণ, shift-and-add পদ্ধতিতে (এখনো কম্বিনেশনাল না রেখে multi-cycle করলে পরের প্রজেক্টের FSM ধারণার সাথে মিলবে)
  4. Fuzz testing — হাজার হাজার র‌্যান্ডম (A, B, op) জেনারেট করে Verilog সিমুলেশনের ফলাফল Python-এ লেখা একটা reference model-এর সাথে মিলিয়ে দেখুন
  5. Gate count তুলনা — Logisim ডিজাইনে মোট কয়টা গেট লাগলো গুনুন, তারপর একই ALU শুধু NAND দিয়ে বানালে (Gate Universality-এর নিয়মে) কয়টা লাগত আন্দাজ করুন

এটা যেখানে গিয়ে মিশবে

এখানে যা শিখলেনপরে কোথায় লাগবে
Adder/subtractor + MUX-ভিত্তিক ALU গঠনLevel 3 — CPU Architecture-এর execute stage, ALU integration
Zero/Carry/Overflow/Negative ফ্ল্যাগLevel 3 — condition codes, branch instruction-এর শর্ত
Logisim বনাম Verilog-এ একই ডিজাইনLevel 11 — RTL design workflow, synthesis
Test vector দিয়ে যাচাইএই মডিউলেরই Single-cycle CPU প্রজেক্ট, ও Level 3-এর CPU Emulator প্রজেক্ট
Shift/logical অপারেশনLevel 3 (Assembly module) — shift/rotate instruction