구글 안티그래비티 완전 분석 — 모델·요금제·CLI 총정리

🚀 구글 안티그래비티(Antigravity) 완전 분석 구글이 2025년 11월 Gemini 3와 함께 공개한 에이전트 퍼스트(agent-first) IDE 안티그래비티는 Claude·GPT·Gemini를 한 도구에서 골라 쓰는 멀티모델 코딩 환경이다. 이 글에서는 ① 지원 모델과 요금제별 사용량의 실체, ② 실사용자 평가, ③ 구글의 방향성, ④ Claude Code와의 비교·연계, ⑤ CLI( agy )로 직접 쓰는 법까지 다섯 갈래를 차례로 정리한다. 자료 간 충돌이 있는 지점은 한쪽으로 단정하지 않고 양쪽을 모두 살려 표기했다. 📅 기준 시점: 2026년 6월 · 프리뷰 단계 정보로 수치는 변동 가능 1. 안티그래비티란 무엇인가 — 기초 정리 안티그래비티는 2025년 7월 구글이 24억 달러 규모 라이선스 계약 으로 영입한 전 Windsurf 팀이 설계를 주도했다. VSCode를 포크한 위에 자율 에이전트 오케스트레이션 계층을 얹은 구조다. 2026년 5월 Google I/O에서 발표된 안티그래비티 2.0 은 데스크탑 앱과 함께 공식 CLI agy 를 처음 공개하며 기존 Gemini CLI의 공식 후계자 자리를 확정했다. 핵심 정체성은 단순 코드 자동완성이 아니라 병렬 에이전트 오케스트레이션 이다. 여러 에이전트가 동시에 — 하나는 API, 하나는 테스트, 또 하나는 프론트엔드 — 작업을 나눠 진행하고, 각 에이전트는 계획·테스트 결과·스크린샷·영상을 담은 Artifact 를 남긴다. "사람이 한 줄씩 승인"하는 방식이 아니라 "에이전트들이 일을 마치고 사람이 사후 검수"하는 모델이다. flowchart TD A([사용자 작업 지시]) --> B[에이전트 A API 구현] A --> C[에이전트 B 테스트 작성] A --> D[에이전트 C UI 생성] B --> E[Artifact 계획·결과·영상] C --> E D --> E...

SystemVerilog Testbench Essentials: Beyond Verilog's Limits

SystemVerilog Testbench Essentials: Beyond Verilog's Limits

Verilog was a revolutionary step for hardware design and simulation. However, as designs grew more complex, the verification methodologies needed to keep pace. SystemVerilog, an extension of Verilog, introduced a wealth of features specifically designed to streamline and enhance the verification process, especially within testbenches. If you're still working solely with Verilog testbenches, you're missing out on powerful tools that can make your verification more efficient, comprehensive, and maintainable. Let's dive into some of these game-changing features.

1. Concurrent Execution with fork-join

In Verilog, processes (like always blocks) generally execute sequentially. This can make it challenging to model complex, real-time interactions where multiple events or activities need to happen simultaneously. SystemVerilog's fork-join construct is a paradigm shift. It allows you to create multiple, independent threads of execution that run concurrently.

  • How it works: The fork statement initiates parallel execution of the blocks that follow it. The join statement (or join_any, join_none) synchronizes these processes. join waits for all forked processes to complete before the main thread continues. This is crucial for testbenches where you might have a driver sending transactions, a monitor observing them, and a checker verifying them, all operating at the same time.
  • Example:

    ```systemverilog
    module tb;
    initial begin
    $display("Starting testbench...");

    fork
      // Process 1: Stimulus generation
      begin
        $display("Process 1: Starting stimulus generation.");
        #10; // Simulate some delay
        $display("Process 1: Stimulus sent.");
      end
    
      // Process 2: Monitoring
      begin
        $display("Process 2: Starting monitoring.");
        #5; // Simulate some delay
        $display("Process 2: Observed data.");
        #5;
        $display("Process 2: Monitoring complete.");
      end
    join
    // This part only executes after both processes in the fork-join complete.
    $display("All concurrent processes finished. Testbench done.");
    

    end
    endmodule
    ```

    • Explanation: When you run this, you'll see the messages from both processes interleaved as they execute concurrently, demonstrating that they are not waiting for each other unless explicitly told to (via join at the end). This is fundamentally different from Verilog's single-threaded execution model.

2. Object-Oriented Programming (OOP) for Reusability and Structure

This is arguably the biggest leap. SystemVerilog brings C++ style OOP to hardware verification. You can define classes (blueprints) for your verification components and create objects (instances) from them. This promotes modularity, reusability, and maintainability.

  • Key Concepts:
    • Classes: Blueprints for objects. They define properties (data members) and methods (functions/tasks).
    • Objects: Instances of classes. You can create multiple objects from a single class.
    • Encapsulation: Bundling data and methods within a class, controlling access.
    • Inheritance: Creating new classes that inherit properties and methods from existing ones, promoting code reuse.
  • Example: Creating a transaction class to represent data packets.

    ```systemverilog
    // Define a class for data transactions
    class transaction;
    // Properties (data members)
    typedef enum { READ, WRITE } op_type;
    op_type opcode;
    bit [7:0] address;
    bit [31:0] data;

    // Methods (functions/tasks)
    // Constructor to initialize the object
    function new(op_type op, bit [7:0] addr, bit [31:0] data_val);
    opcode = op;
    address = addr;
    data = data_val;
    endfunction

    // Method to print transaction details
    virtual function void print(); // 'virtual' allows overriding in derived classes
    $display("Transaction: Opcode=%s, Addr=0x%h, Data=0x%h", opcode.name(), address, data);
    endfunction
    endclass

    // Testbench module to use the transaction class
    module tb;
    initial begin
    $display("Creating transactions...");

    // Create objects (instances) of the transaction class
    transaction tr1 = new(transaction::READ, 8'h10, 32'hDEADBEEF);
    transaction tr2 = new(transaction::WRITE, 8'h20, 32'hCAFEFACE);
    
    // Call methods on the objects
    tr1.print();
    tr2.print();
    
    $display("Transactions created and printed.");
    

    end
    endmodule
    ```

    • Explanation: The transaction class acts as a template. We create two transaction objects (tr1, tr2) using the new constructor and then call the print method on each to display their contents. This is far more organized than using raw Verilog regs and wires for complex data structures.

3. Powerful Data Types and Structures

SystemVerilog significantly expands the data types available compared to Verilog.

  • typedef: Define custom type names for clarity and reusability.
  • struct: Group related variables of different types under a single name, much like C structs. This is perfect for representing transactions.
  • enum: Define named integer constants, making code more readable and less error-prone than using raw numbers.
  • Dynamic Arrays: Arrays whose size can be determined at runtime.
  • Associative Arrays: Data structures that map keys to values, similar to dictionaries or hash maps in software languages.

  • Example (struct and enum):

    ```systemverilog
    // Using struct and enum within a class
    class packet;
    typedef enum { PKT_TYPE_A, PKT_TYPE_B, PKT_TYPE_C } packet_type_e;
    typedef struct packed {
    packet_type_e type;
    bit [7:0] id;
    bit [15:0] payload_len;
    } header_t;

    header_t hdr;
    bit [31:0] data []; // Dynamic array for payload

    function new();
    // Initialize dynamic array
    data = new[10]; // Allocate space for 10 elements
    endfunction

    function void set_header(packet_type_e t, bit [7:0] i, bit [15:0] len);
    hdr.type = t;
    hdr.id = i;
    hdr.payload_len = len;
    endfunction

    function void print_header();
    $display("Header: Type=%s, ID=0x%h, Payload Len=%d", hdr.type.name(), hdr.id, hdr.payload_len);
    endfunction
    endclass

    module tb;
    initial begin
    packet p = new();
    p.set_header(packet::PKT_TYPE_B, 8'h42, 256);
    p.print_header();
    end
    endmodule
    ```

    • Explanation: We use enum for packet_type_e and struct packed for header_t. This makes the header_t structure self-describing and easy to manage. The dynamic array data can grow or shrink as needed.

4. Randomization and Constraints for Comprehensive Testing

This is a cornerstone of modern constrained-random verification (CRV). Instead of writing every single test case manually, you define the rules (constraints) for your test data, and SystemVerilog's built-in random number generator (RNG) creates valid stimulus.

  • rand and randc: Keywords that declare a variable or class property as subject to randomization. randc ensures that each value is generated only once within a cycle.
  • constraint: A block that defines rules for randomization. You can specify ranges, distributions, relationships between variables, etc.
  • How it works: When you call .randomize() on an object, SystemVerilog solves the constraints to produce a valid set of random values for its rand and randc members.
  • Example:

    ```systemverilog
    class data_packet;
    // Declare variables to be randomized
    rand bit [7:0] header_len;
    rand bit [15:0] data_payload[$]; // Dynamic array for payload
    rand bit [3:0] priority;

    // Define constraints
    constraint c_header {
    header_len inside {8, 16, 32, 64}; // header_len must be one of these values
    }

    constraint c_payload {
    data_payload.size() == header_len / 8; // Payload size depends on header length
    foreach (data_payload[i]) begin
    data_payload[i] < 256; // Each byte in payload is < 256
    end
    }

    constraint c_priority {
    priority dist { 0 :/ 10, 1 :/ 20, 2 :/ 40, 3 :/ 30 }; // Weighted distribution
    }

    function void print();
    $display("Header Length: %0d bytes", header_len);
    $display("Priority: %0d", priority);
    $display("Payload Size: %0d bytes", data_payload.size());
    if (data_payload.size() > 0) begin
    $display("Payload: [ %p ]", data_payload);
    end
    endfunction
    endclass

    module tb;
    initial begin
    data_packet pkt = new();

    // Generate random packets
    repeat (5) begin
      if (!pkt.randomize()) begin
        $error("Randomization failed!");
      end
      pkt.print();
      $display("--------------------");
    end
    

    end
    endmodule
    ```

    • Explanation: Each call to pkt.randomize() generates a new data_packet instance that adheres to all defined constraints. This allows you to explore a vast design space with minimal manual effort, uncovering corner cases that might be missed in directed testing.

5. SystemVerilog Assertions (SVA) for Verification

SVA provides a powerful, declarative way to specify properties and check them during simulation. Instead of writing complex procedural checks in your testbench, you define what should never happen or what must happen under certain conditions.

  • How it works: You define properties that describe sequences of events. These properties can then be instantiated as assertions within your testbench or even your design (if it supports them). The simulator checks these properties, reporting failures when they are violated.
  • Example: Checking that an acknowledge signal ack always follows a request signal req within a certain number of clock cycles.

    ```systemverilog
    // Define a reusable property
    property req_ack_prop;
    @(posedge clk) disable iff (!rst_n) // Clock edge, disable on reset
    (req ##1 ack) || (req ##2 ack) || (req ##3 ack); // req followed by ack within 1-3 cycles
    endproperty

    // Instantiate the assertion within a module, interface, or program
    // Example within a testbench module:
    module tb;
    bit clk, rst_n, req, ack;
    // ... clock generation and reset logic ...

    // Instantiate the assertion
    assert property (req_ack_prop); // Reusable property instantiation

    // Stimulus generation...
    initial begin
    // ... setup ...
    rst_n = 0; #10; rst_n = 1; // Reset
    @(posedge clk);
    req = 1; @(posedge clk); req = 0; ack = 1; // Valid sequence
    @(posedge clk); ack = 0;
    // ... more stimulus ...
    end
    endmodule
    ```

    • Explanation: The property keyword defines a reusable verification rule. The assert property statement checks this rule. If req goes high and ack does not go high within 1-3 clock cycles (after req goes high), the assertion fails, immediately stopping simulation or reporting an error. This is more robust and readable than equivalent checks in Verilog.

6. Interfaces for Simplified Connectivity

As designs grow, the number of ports on a DUT can become enormous. Passing each individual signal from the testbench to the DUT is cumbersome and error-prone. SystemVerilog interfaces bundle related signals into a single, named entity.

  • How it works: An interface is like a conduit that groups signals (wires, regs, etc.). You connect the testbench to the DUT using a single interface instance, rather than many individual signals. Modports within an interface define the directionality of signals for different users (e.g., the DUT, the testbench).
  • Example:

    ```systemverilog
    // Define an interface
    interface simple_bus (input bit clk, rst_n);
    // Signals
    logic [7:0] data_in;
    logic [7:0] data_out;
    logic wr_en;
    logic rd_en;
    logic busy;

    // Modport for the DUT
    modport DUT (input clk, rst_n, data_in, wr_en, rd_en, output data_out, busy);
    // Modport for the testbench
    modport TB (input clk, rst_n, output data_in, wr_en, rd_en, input data_out, busy);
    endinterface

    // DUT using the interface
    module my_dut (simple_bus.DUT bus); // Connect to the DUT's view of the interface
    always @(posedge bus.clk or negedge bus.rst_n) begin
    if (!bus.rst_n) begin
    bus.data_out <= '0;
    bus.busy <= 0;
    end else begin
    if (bus.wr_en) begin
    bus.data_out <= bus.data_in; // Simple write logic
    end
    bus.busy <= 1; // Example: DUT busy for one cycle
    end
    end
    endmodule

    // Testbench using the interface
    module tb;
    bit clk = 0;
    bit rst_n = 0;
    // Instantiate the interface
    simple_bus u_bus (.clk(clk), .rst_n(rst_n));

    // Instantiate the DUT, connecting to its modport view of the interface
    my_dut dut_inst (.bus(u_bus.DUT)); // Pass the DUT's view to the DUT

    // Stimulus generation for the testbench side
    initial begin
    // Clock generation
    forever #5 clk = ~clk;
    end

    initial begin
    // Reset sequence
    #10 rst_n = 1;

    // Use the TB modport to drive signals
    @(posedge clk);
    u_bus.wr_en <= 1;
    u_bus.data_in <= 8'hAB;
    @(posedge clk);
    u_bus.wr_en <= 0;
    // ... more stimulus ...
    

    end
    endmodule
    ```

    • Explanation: The simple_bus interface bundles all bus signals. my_dut instantiates the DUT modport of this interface, gaining access only to the signals it needs with the correct directionality. The testbench tb instantiates the interface and passes the TB modport to its own control logic. This dramatically simplifies port connections and reduces wiring complexity.

7. Program-Module Construct for Testbench/Design Separation

SystemVerilog's program block provides a dedicated construct for writing testbench code, separate from the DUT's module. This separation helps manage simulation timing and avoid race conditions.

  • How it works: program blocks are scheduled to run in a different time slot than module blocks during a simulation timestep. Generally, modules execute first (e.g., combinational logic), then programs (e.g., stimulus generation), and finally, updates are visible to the other block. This ensures that stimulus generated in a program block for a given timestep doesn't affect the DUT's output within that same timestep, preventing unintended race conditions.
  • Example:

    ```systemverilog
    // DUT Module
    module my_module (input bit clk, reset, enable, data_in, output reg data_out);
    always @(posedge clk or posedge reset) begin
    if (reset)
    data_out <= '0;
    else if (enable)
    data_out <= data_in;
    end
    endmodule

    // Testbench Program
    program my_program (input bit clk, reset, input bit data_in);
    output bit enable;
    reg [7:0] data_to_send;

    task send_data(input bit [7:0] data);
    data_to_send = data;
    enable = 1; // Assert enable
    @(posedge clk); // Wait for clock edge
    enable = 0; // De-assert enable
    $display("Program: Sent data %h", data_to_send);
    endtask

    initial begin
    $display("Program: Starting stimulus...");
    reset = 1;
    enable = 0;
    @(posedge clk); // Wait for first clock
    reset = 0;
    @(posedge clk); // Wait for reset to de-assert

    send_data(8'hAA);
    send_data(8'hBB);
    $display("Program: Stimulus complete.");
    

    end
    endprogram

    // Top-level module connecting Program and Module
    module tb;
    bit clk;
    bit reset;
    bit enable;
    bit [7:0] data_in;
    reg [7:0] data_out;

    // Clock generator
    initial forever #5 clk = ~clk;

    // Instantiate the DUT module
    my_module dut_inst (
    .clk(clk),
    .reset(reset),
    .enable(enable),
    .data_in(data_in),
    .data_out(data_out)
    );

    // Instantiate the Testbench Program
    my_program prog_inst (
    .clk(clk),
    .reset(reset),
    .data_in(data_in),
    .enable(enable) // Connect enable from program to module
    );

    // Assign data_in from program to module
    assign data_in = prog_inst.data_to_send;

    // Monitor data_out
    always @(posedge clk) begin
    if (prog_inst.enable) begin // Only display when enable is asserted
    $display("TB: Received data %h at DUT output.", data_out);
    end
    end
    endmodule
    ```

    • Explanation: The my_program block generates stimulus, driving enable and data_in (via the assign in tb). The my_module block responds to these inputs. The separation ensures that the enable signal asserted by the program at time T correctly causes data_out to update on the next clock edge, and this updated data_out is then visible to the testbench program at time T+delta (after the module executes but before the next timestep's program execution).

Conclusion

SystemVerilog testbenches offer a significant upgrade over traditional Verilog. Features like fork-join for concurrency, OOP for structure, randomization for coverage, SVA for robust checks, interfaces for clean connectivity, and the program block for timing management empower verification engineers to tackle modern hardware complexity. By embracing these SystemVerilog constructs, you can build more effective, maintainable, and high-coverage verification environments.

References

댓글

이 블로그의 인기 게시물

Vim 9.2 릴리즈 총정리: 더 빠르고 강력해진 텍스트 편집의 제왕

폐쇄망 SoC 설계자를 위한 가볍고 빠른 Vim 최적화 가이드

에이전트 시대를 위한 터미널 cmux 가이드: 설치부터 AI 활용까지