`timescale 1ns / 1ps

// Testbench for 8-bit ALU
// Tests all opcodes with directed and randomized test cases
module tb_alu_8bit;
    // Inputs
    reg  [7:0] a;
    reg  [7:0] b;
    reg  [3:0] opcode;
    
    // Outputs
    wire [7:0] result;
    wire       zero;
    wire       negative;
    wire       carry;
    wire       overflow;
    
    // Test control
    integer errors = 0;
    integer i;
    
    // Golden model outputs
    reg [7:0] expected_result;
    reg       expected_zero;
    reg       expected_negative;
    reg       expected_carry;
    reg       expected_overflow;
    reg [8:0] temp_result;  // 9 bits for carry detection
    
    // Opcode definitions
    localparam OP_ADD  = 4'b0000;
    localparam OP_SUB  = 4'b0001;
    localparam OP_AND  = 4'b0010;
    localparam OP_OR   = 4'b0011;
    localparam OP_XOR  = 4'b0100;
    localparam OP_NOT  = 4'b0101;
    localparam OP_SHL  = 4'b0110;
    localparam OP_SHR  = 4'b0111;
    localparam OP_INC  = 4'b1000;
    localparam OP_DEC  = 4'b1001;
    localparam OP_CMP  = 4'b1010;
    localparam OP_PASS = 4'b1011;
    
    // Instantiate DUT
    alu_8bit uut (
        .a(a),
        .b(b),
        .opcode(opcode),
        .result(result),
        .zero(zero),
        .negative(negative),
        .carry(carry),
        .overflow(overflow)
    );
    
    // Golden model function
    task compute_expected;
        input [7:0] in_a;
        input [7:0] in_b;
        input [3:0] in_op;
        begin
            expected_carry = 1'b0;
            expected_overflow = 1'b0;
            
            case (in_op)
                OP_ADD: begin
                    temp_result = {1'b0, in_a} + {1'b0, in_b};
                    expected_result = temp_result[7:0];
                    expected_carry = temp_result[8];
                    // Overflow: same sign inputs, different sign result
                    expected_overflow = (in_a[7] == in_b[7]) && (expected_result[7] != in_a[7]);
                end
                OP_SUB: begin
                    temp_result = {1'b0, in_a} - {1'b0, in_b};
                    expected_result = temp_result[7:0];
                    expected_carry = temp_result[8];  // Borrow
                    // Overflow: different sign inputs, result sign differs from A
                    expected_overflow = (in_a[7] != in_b[7]) && (expected_result[7] != in_a[7]);
                end
                OP_AND: begin
                    expected_result = in_a & in_b;
                end
                OP_OR: begin
                    expected_result = in_a | in_b;
                end
                OP_XOR: begin
                    expected_result = in_a ^ in_b;
                end
                OP_NOT: begin
                    expected_result = ~in_a;
                end
                OP_SHL: begin
                    expected_carry = in_a[7];  // Bit shifted out
                    expected_result = in_a << 1;
                end
                OP_SHR: begin
                    expected_carry = in_a[0];  // Bit shifted out
                    expected_result = in_a >> 1;
                end
                OP_INC: begin
                    temp_result = {1'b0, in_a} + 1;
                    expected_result = temp_result[7:0];
                    expected_carry = temp_result[8];
                end
                OP_DEC: begin
                    temp_result = {1'b0, in_a} - 1;
                    expected_result = temp_result[7:0];
                    expected_carry = temp_result[8];  // Borrow
                end
                OP_CMP: begin
                    expected_result = (in_a == in_b) ? 8'b0 : 8'hFF;
                end
                OP_PASS: begin
                    expected_result = in_a;
                end
                default: begin
                    expected_result = 8'b0;
                end
            endcase
            
            expected_zero = (expected_result == 8'b0);
            expected_negative = expected_result[7];
        end
    endtask
    
    // Check outputs against golden model
    task check_result;
        input [255:0] test_name;
        begin
            compute_expected(a, b, opcode);
            
            if (result !== expected_result) begin
                $display("ERROR [%s]: result mismatch. a=%h, b=%h, op=%b. Expected %h, got %h",
                         test_name, a, b, opcode, expected_result, result);
                errors = errors + 1;
            end
            if (zero !== expected_zero) begin
                $display("ERROR [%s]: zero flag mismatch. a=%h, b=%h, op=%b, result=%h. Expected %b, got %b",
                         test_name, a, b, opcode, result, expected_zero, zero);
                errors = errors + 1;
            end
            if (negative !== expected_negative) begin
                $display("ERROR [%s]: negative flag mismatch. a=%h, b=%h, op=%b, result=%h. Expected %b, got %b",
                         test_name, a, b, opcode, result, expected_negative, negative);
                errors = errors + 1;
            end
            if (carry !== expected_carry) begin
                $display("ERROR [%s]: carry flag mismatch. a=%h, b=%h, op=%b. Expected %b, got %b",
                         test_name, a, b, opcode, expected_carry, carry);
                errors = errors + 1;
            end
            if (overflow !== expected_overflow) begin
                $display("ERROR [%s]: overflow flag mismatch. a=%h, b=%h, op=%b. Expected %b, got %b",
                         test_name, a, b, opcode, expected_overflow, overflow);
                errors = errors + 1;
            end
        end
    endtask
    
    // Apply test vector
    task apply_test;
        input [255:0] test_name;
        input [7:0]   in_a;
        input [7:0]   in_b;
        input [3:0]   in_op;
        begin
            a = in_a;
            b = in_b;
            opcode = in_op;
            #10;
            check_result(test_name);
        end
    endtask
    
    // Main test procedure
    initial begin
        $display("===========================================");
        $display("  8-bit ALU Testbench");
        $display("===========================================");
        
        // Initialize
        a = 0;
        b = 0;
        opcode = 0;
        
        // ========================================
        // Test 1: ADD operations
        // ========================================
        $display("\n--- Test 1: ADD ---");
        apply_test("ADD_zero", 8'h00, 8'h00, OP_ADD);
        apply_test("ADD_simple", 8'h12, 8'h34, OP_ADD);
        apply_test("ADD_carry", 8'hFF, 8'h01, OP_ADD);
        apply_test("ADD_max", 8'hFF, 8'hFF, OP_ADD);
        apply_test("ADD_overflow_pos", 8'h7F, 8'h01, OP_ADD);  // 127 + 1 = -128 (overflow)
        apply_test("ADD_no_overflow", 8'h80, 8'h01, OP_ADD);   // -128 + 1 = -127 (no overflow)
        if (errors == 0) $display("  PASS: ADD operations");
        
        // ========================================
        // Test 2: SUB operations
        // ========================================
        $display("\n--- Test 2: SUB ---");
        apply_test("SUB_zero", 8'h34, 8'h34, OP_SUB);
        apply_test("SUB_simple", 8'h34, 8'h12, OP_SUB);
        apply_test("SUB_borrow", 8'h00, 8'h01, OP_SUB);
        apply_test("SUB_negative", 8'h12, 8'h34, OP_SUB);
        apply_test("SUB_overflow", 8'h80, 8'h01, OP_SUB);      // -128 - 1 = 127 (overflow)
        apply_test("SUB_no_overflow", 8'h7F, 8'h01, OP_SUB);   // 127 - 1 = 126 (no overflow)
        if (errors == 0) $display("  PASS: SUB operations");
        
        // ========================================
        // Test 3: AND operations
        // ========================================
        $display("\n--- Test 3: AND ---");
        apply_test("AND_zero", 8'hAA, 8'h55, OP_AND);
        apply_test("AND_all", 8'hFF, 8'hFF, OP_AND);
        apply_test("AND_mask", 8'hAB, 8'h0F, OP_AND);
        if (errors == 0) $display("  PASS: AND operations");
        
        // ========================================
        // Test 4: OR operations
        // ========================================
        $display("\n--- Test 4: OR ---");
        apply_test("OR_simple", 8'hAA, 8'h55, OP_OR);
        apply_test("OR_zero", 8'h00, 8'h00, OP_OR);
        apply_test("OR_partial", 8'h0F, 8'hF0, OP_OR);
        if (errors == 0) $display("  PASS: OR operations");
        
        // ========================================
        // Test 5: XOR operations
        // ========================================
        $display("\n--- Test 5: XOR ---");
        apply_test("XOR_simple", 8'hAA, 8'h55, OP_XOR);
        apply_test("XOR_same", 8'h5A, 8'h5A, OP_XOR);
        apply_test("XOR_zero", 8'h00, 8'hFF, OP_XOR);
        if (errors == 0) $display("  PASS: XOR operations");
        
        // ========================================
        // Test 6: NOT operations
        // ========================================
        $display("\n--- Test 6: NOT ---");
        apply_test("NOT_zero", 8'h00, 8'hXX, OP_NOT);
        apply_test("NOT_all", 8'hFF, 8'hXX, OP_NOT);
        apply_test("NOT_pattern", 8'hA5, 8'hXX, OP_NOT);
        if (errors == 0) $display("  PASS: NOT operations");
        
        // ========================================
        // Test 7: SHL operations
        // ========================================
        $display("\n--- Test 7: SHL ---");
        apply_test("SHL_simple", 8'h01, 8'hXX, OP_SHL);
        apply_test("SHL_carry", 8'h80, 8'hXX, OP_SHL);
        apply_test("SHL_pattern", 8'hA5, 8'hXX, OP_SHL);
        if (errors == 0) $display("  PASS: SHL operations");
        
        // ========================================
        // Test 8: SHR operations
        // ========================================
        $display("\n--- Test 8: SHR ---");
        apply_test("SHR_simple", 8'h02, 8'hXX, OP_SHR);
        apply_test("SHR_carry", 8'h01, 8'hXX, OP_SHR);
        apply_test("SHR_pattern", 8'hA5, 8'hXX, OP_SHR);
        if (errors == 0) $display("  PASS: SHR operations");
        
        // ========================================
        // Test 9: INC operations
        // ========================================
        $display("\n--- Test 9: INC ---");
        apply_test("INC_simple", 8'h00, 8'hXX, OP_INC);
        apply_test("INC_carry", 8'hFF, 8'hXX, OP_INC);
        apply_test("INC_mid", 8'h7F, 8'hXX, OP_INC);
        if (errors == 0) $display("  PASS: INC operations");
        
        // ========================================
        // Test 10: DEC operations
        // ========================================
        $display("\n--- Test 10: DEC ---");
        apply_test("DEC_simple", 8'h01, 8'hXX, OP_DEC);
        apply_test("DEC_borrow", 8'h00, 8'hXX, OP_DEC);
        apply_test("DEC_mid", 8'h80, 8'hXX, OP_DEC);
        if (errors == 0) $display("  PASS: DEC operations");
        
        // ========================================
        // Test 11: CMP operations
        // ========================================
        $display("\n--- Test 11: CMP ---");
        apply_test("CMP_equal", 8'h55, 8'h55, OP_CMP);
        apply_test("CMP_notequal", 8'h55, 8'hAA, OP_CMP);
        apply_test("CMP_zero", 8'h00, 8'h00, OP_CMP);
        if (errors == 0) $display("  PASS: CMP operations");
        
        // ========================================
        // Test 12: PASS operations
        // ========================================
        $display("\n--- Test 12: PASS ---");
        apply_test("PASS_zero", 8'h00, 8'hXX, OP_PASS);
        apply_test("PASS_max", 8'hFF, 8'hXX, OP_PASS);
        apply_test("PASS_pattern", 8'h5A, 8'hXX, OP_PASS);
        if (errors == 0) $display("  PASS: PASS operations");
        
        // ========================================
        // Test 13: Reserved opcodes
        // ========================================
        $display("\n--- Test 13: Reserved Opcodes ---");
        apply_test("RES_1100", 8'hFF, 8'hFF, 4'b1100);
        apply_test("RES_1101", 8'hFF, 8'hFF, 4'b1101);
        apply_test("RES_1110", 8'hFF, 8'hFF, 4'b1110);
        apply_test("RES_1111", 8'hFF, 8'hFF, 4'b1111);
        if (errors == 0) $display("  PASS: Reserved opcodes return zero");
        
        // ========================================
        // Test 14: Flag edge cases
        // ========================================
        $display("\n--- Test 14: Flag Edge Cases ---");
        apply_test("FLAG_zero_result", 8'hFF, 8'hFF, OP_AND);   // Result 0xFF, negative flag
        apply_test("FLAG_neg_result", 8'h80, 8'h00, OP_ADD);    // Result 0x80, negative flag
        apply_test("FLAG_all_zero", 8'h00, 8'h00, OP_AND);       // Result 0, zero flag
        if (errors == 0) $display("  PASS: Flag edge cases");
        
        // ========================================
        // Test 15: Randomized tests
        // ========================================
        $display("\n--- Test 15: Randomized Tests ---");
        for (i = 0; i < 200; i = i + 1) begin
            a = $urandom;
            b = $urandom;
            opcode = $urandom_range(0, 11);  // Only valid opcodes
            #10;
            check_result("RANDOM");
        end
        if (errors == 0) $display("  PASS: Randomized tests");
        
        // ========================================
        // Final Results
        // ========================================
        $display("\n===========================================");
        if (errors == 0) begin
            $display("TEST_RESULT: PASS");
        end else begin
            $display("TEST_RESULT: FAIL (%0d errors)", errors);
        end
        $display("===========================================");
        
        $finish;
    end

endmodule
