내머리속은이런걸로 2024. 7. 10. 12:20

from 다영 언니

 

UVM Phase 순서

: phase  구간, 시간 → 각 구간의 타이밍 조절하며 순서를 정한다.

  • 각 phase마다 위의 순서대로 실행이 되며, phase이 존재하지 않을 경우 무시하고 다음 phase을 진행한다.
  • task로 구현 : run, reset, configure, main, shutdown - 시뮬레이션 시간동안 실행되는 복잡한 동작을 수행한다. 시간 경과에 영향을 미친다.
  • function으로 구현 : 그 외 - 주로 짧고 빠르게 실행되는 작업을 수행한다. uvm 환경에서 설정, 상태 확인, 간단한 데이터 처리를 한다.

AXI_Memory.sv

`timescale 1ns / 1ps

module AXI_Slave_Memory (
    // Global Signal
    input  logic        ACLK,
    input  logic        ARESETn,
    // AW Channel (Write Address)
    input  logic [31:0] AWADDR,
    input  logic        AWVALID,
    output logic        AWREADY,
    // AR Channel
    input  logic [31:0] ARADDR,
    input  logic        ARVALID,
    output logic        ARREADY,
    // W Channel
    input  logic [31:0] WDATA,
    input  logic        WVALID,
    input  logic [ 3:0] WSTRB,
    output logic        WREADY,
    // R Channel
    output logic [31:0] RDATA,
    output logic        RVALID,
    input  logic        RREADY,
    // B Channel
    output logic [ 1:0] BRESP,
    output logic        BVALID,
    input  logic        BREADY,
    output logic        ready
);
    logic [7:0] slave_mem[0:15];

    // AW Channel
    enum bit {
        AW_IDLE_S,
        AW_READY_S
    }
        aw_state, aw_state_next;

    logic [31:0] aw_addr_reg, aw_addr_next;  // latch data

    always_ff @(posedge ACLK, negedge ARESETn) begin
        if (!ARESETn) begin
            aw_state    <= AW_IDLE_S;
            aw_addr_reg <= 0;
        end else begin
            aw_state    <= aw_state_next;
            aw_addr_reg <= aw_addr_next;
        end
    end

    // next, output logic
    always_comb begin
        aw_state_next = aw_state;
        aw_addr_next  = aw_addr_reg;
        AWREADY       = 1'b0;
        case (aw_state)
            AW_IDLE_S: begin
                AWREADY = 1'b0;
                if (AWVALID) begin
                    aw_state_next = AW_READY_S;
                    aw_addr_next  = AWADDR;  // address latch
                end
            end
            AW_READY_S: begin
                AWREADY = 1'b1;
                aw_addr_next = AWADDR;
                if (AWVALID && AWREADY) begin
                    aw_state_next = AW_IDLE_S;
                end
            end
        endcase
    end

    // AR Channel
    enum bit {
        AR_IDLE_S,
        AR_READY_S
    }
        ar_state, ar_state_next;

    logic [31:0] ar_addr_reg, ar_addr_next;

    always_ff @(posedge ACLK, negedge ARESETn) begin
        if (!ARESETn) begin
            ar_state    <= AR_IDLE_S;
            ar_addr_reg <= 0;
        end else begin
            ar_state    <= ar_state_next;
            ar_addr_reg <= ar_addr_next;
        end
    end

    // next, output logic
    always_comb begin
        ar_state_next = ar_state;
        ar_addr_next  = ar_addr_reg;
        ARREADY       = 1'b0;
        case (ar_state)
            AR_IDLE_S: begin
                ARREADY = 1'b0;
                if (ARVALID) begin  // triggering time decision
                    ar_state_next = AR_READY_S;
                    ar_addr_next  = ARADDR;  // address latch
                end
            end
            AR_READY_S: begin
                ARREADY      = 1'b1;
                ar_addr_next = ARADDR;
                if (ARVALID && ARREADY) begin
                    ar_state_next = AR_IDLE_S;
                end
            end
        endcase
    end

    // W Channel
    enum bit {
        W_IDLE_S,
        W_READY_S
    }
        w_state, w_state_next;

    logic [31:0] w_data_reg, w_data_next;
    logic [3:0] w_strb_reg, w_strb_next;

    always_ff @(posedge ACLK, negedge ARESETn) begin
        if (!ARESETn) begin
            w_state    <= W_IDLE_S;
            w_data_reg <= 0;
            w_strb_reg <= 0;
        end else begin
            w_state    <= w_state_next;
            w_data_reg <= w_data_next;
            w_strb_reg <= w_strb_next;
        end
    end

    // next, output logic
    always_comb begin
        w_state_next = w_state;
        w_data_next  = w_data_reg;
        w_strb_next  = w_strb_reg;
        WREADY       = 1'b0;
        case (w_state)
            W_IDLE_S: begin
                WREADY = 1'b0;
                if (AWREADY && WVALID) begin  // get address && wdata in 
                    w_state_next = W_READY_S;
                    w_data_next  = WDATA;  // data latch
                    w_strb_next  = WSTRB;  // strobe latch
                end
            end
            W_READY_S: begin
                WREADY = 1'b1;
                //slave_mem[aw_addr_reg] = w_data_reg;  // write
                case (w_strb_reg)
                    4'b0001: begin
                        slave_mem[aw_addr_reg] = w_data_reg[7:0];
                    end
                    4'b0010: begin
                        slave_mem[aw_addr_reg+1] = w_data_reg[15:8];
                    end
                    4'b0100: begin
                        slave_mem[aw_addr_reg+2] = w_data_reg[23:16];
                    end
                    4'b1000: begin
                        slave_mem[aw_addr_reg+3] = w_data_reg[31:24];
                    end
                    4'b0011: begin
                        slave_mem[aw_addr_reg]   = w_data_reg[7:0];
                        slave_mem[aw_addr_reg+1] = w_data_reg[15:8];
                    end
                    4'b0101: begin
                        slave_mem[aw_addr_reg]   = w_data_reg[7:0];
                        slave_mem[aw_addr_reg+2] = w_data_reg[23:16];
                    end
                    4'b1001: begin
                        slave_mem[aw_addr_reg]   = w_data_reg[7:0];
                        slave_mem[aw_addr_reg+3] = w_data_reg[31:24];
                    end
                    4'b0110: begin
                        slave_mem[aw_addr_reg+1] = w_data_reg[15:8];
                        slave_mem[aw_addr_reg+2] = w_data_reg[23:16];
                    end
                    4'b1010: begin
                        slave_mem[aw_addr_reg+1] = w_data_reg[15:8];
                        slave_mem[aw_addr_reg+3] = w_data_reg[31:24];
                    end
                    4'b1100: begin
                        slave_mem[aw_addr_reg+2] = w_data_reg[23:16];
                        slave_mem[aw_addr_reg+3] = w_data_reg[31:24];
                    end
                    4'b0111: begin
                        slave_mem[aw_addr_reg]   = w_data_reg[7:0];
                        slave_mem[aw_addr_reg+1] = w_data_reg[15:8];
                        slave_mem[aw_addr_reg+2] = w_data_reg[23:16];
                    end
                    4'b1011: begin
                        slave_mem[aw_addr_reg]   = w_data_reg[7:0];
                        slave_mem[aw_addr_reg+1] = w_data_reg[15:8];
                        slave_mem[aw_addr_reg+3] = w_data_reg[31:24];
                    end
                    4'b1101: begin
                        slave_mem[aw_addr_reg]   = w_data_reg[7:0];
                        slave_mem[aw_addr_reg+2] = w_data_reg[23:16];
                        slave_mem[aw_addr_reg+3] = w_data_reg[31:24];
                    end
                    4'b1110: begin
                        slave_mem[aw_addr_reg+1] = w_data_reg[15:8];
                        slave_mem[aw_addr_reg+2] = w_data_reg[23:16];
                        slave_mem[aw_addr_reg+3] = w_data_reg[31:24];
                    end
                    4'b1111: begin
                        slave_mem[aw_addr_reg]   = w_data_reg[7:0];
                        slave_mem[aw_addr_reg+1] = w_data_reg[15:8];
                        slave_mem[aw_addr_reg+2] = w_data_reg[23:16];
                        slave_mem[aw_addr_reg+3] = w_data_reg[31:24];
                    end
                    default: begin
                    end
                endcase
                if (WVALID && WREADY) begin
                    w_state_next = W_IDLE_S;
                end
            end
        endcase
    end

    // R Channel
    enum bit {
        R_IDLE_S,
        R_VALID_S
    }
        r_state, r_state_next;

    logic [31:0] r_data_reg, r_data_next;

    always_ff @(posedge ACLK, negedge ARESETn) begin
        if (!ARESETn) begin
            r_state    <= R_IDLE_S;
            r_data_reg <= 0;
        end else begin
            r_state    <= r_state_next;
            r_data_reg <= r_data_next;
        end
    end

    // next, output logic
    always_comb begin
        r_state_next = r_state;
        r_data_next  = r_data_reg;
        RVALID       = 1'b0;
        case (r_state)
            R_IDLE_S: begin
                RVALID = 1'b0;
                if (ARREADY && ARVALID) begin
                    r_state_next       = R_VALID_S;
                    r_data_next[7:0]   = slave_mem[ar_addr_reg+0];
                    r_data_next[15:8]  = slave_mem[ar_addr_reg+1];
                    r_data_next[23:16] = slave_mem[ar_addr_reg+2];
                    r_data_next[31:24] = slave_mem[ar_addr_reg+3];
                end
            end
            R_VALID_S: begin
                RVALID = 1'b1;
                RDATA  = r_data_reg;
                if (RVALID && RREADY) begin
                    r_state_next = R_IDLE_S;
                end
            end
        endcase
    end

    // B Channel
    logic [31:0] b_resp_reg, b_resp_next;  // response

    enum bit {
        B_IDLE_S,
        B_VALID_S
    }
        b_state, b_state_next;

    always_ff @(posedge ACLK, negedge ARESETn) begin
        if (!ARESETn) begin
            b_state    <= B_IDLE_S;
            b_resp_reg <= 0;
        end else begin
            b_state    <= b_state_next;
            b_resp_reg <= b_resp_next;
        end
    end

    // next, output logic
    always_comb begin
        b_state_next = b_state;
        b_resp_next  = b_resp_reg;
        BVALID       = 1'b0;
        ready        = 1'b0;
        case (b_state)
            B_IDLE_S: begin
                BVALID = 1'b0;
                if (WVALID && WREADY) begin  // WREADY : Write done
                    b_state_next = B_VALID_S;
                    b_resp_next  = 2'b00;  // OKAY Response
                end
            end
            B_VALID_S: begin
                BVALID = 1'b1;
                BRESP  = b_resp_reg;
                if (BVALID && BREADY) begin
                    b_state_next = B_IDLE_S;
                    b_resp_next = BRESP;
                    ready = 1'b1;
                end
            end
        endcase
    end
endmodule

 

tb_AXI_Slave_memory.xv

`timescale 1ns / 1ps
`include "uvm_macros.svh"
import uvm_pkg::*;

interface axi_interface (
    input logic ACLK,
    input logic ARESETn
);
    // AW Channel (Write Address)
    logic [31:0] AWADDR;
    logic        AWVALID;
    logic        AWREADY;
    // AR Channel
    logic [31:0] ARADDR;
    logic        ARVALID;
    logic        ARREADY;
    // W Channel
    logic [31:0] WDATA;
    logic        WVALID;
    logic [ 3:0] WSTRB;
    logic        WREADY;
    // R Channel
    logic [31:0] RDATA;
    logic        RVALID;
    logic        RREADY;
    // B Channel
    logic [ 1:0] BRESP;
    logic        BVALID;
    logic        BREADY;
endinterface  //axi_interface

class seq_item extends uvm_sequence_item;
    rand logic [31:0] wdata;
    logic [31:0] rdata;
    logic [31:0] address;
    logic write;  // 1'b1:write, 1'b0:read

    constraint wdata_c {wdata < 256;}
    ;

    function new(input string name = "seq_item");
        super.new(name);
    endfunction  //new()

    `uvm_object_utils_begin(seq_item)
        `uvm_field_int(wdata, UVM_DEFAULT)
        `uvm_field_int(rdata, UVM_DEFAULT)
        `uvm_field_int(address, UVM_DEFAULT)
        `uvm_field_int(write, UVM_DEFAULT)
    `uvm_object_utils_end
endclass  //seq_item extends uvm_sequence_item

class axi_sequence extends uvm_sequence;
    `uvm_object_utils(axi_sequence)
    seq_item axi_seq_item;

    function new(input string name = "axi_sequence");
        super.new(name);
    endfunction  //new()

    task write_item();
        for (int i = 0; i < 16; i++) begin
            start_item(axi_seq_item);
            axi_seq_item.write   = 1'b1;  // write
            axi_seq_item.address = i;
            axi_seq_item.randomize();
            `uvm_info("SEQ", "Write data send to driver", UVM_NONE);
            finish_item(axi_seq_item);
        end
    endtask

    task read_item();
        for (int i = 0; i < 16; i++) begin
            start_item(axi_seq_item);
            axi_seq_item.write   = 1'b0;  // read
            axi_seq_item.address = i;
            `uvm_info("SEQ", "Read Data send to driver", UVM_NONE);
            finish_item(axi_seq_item);
        end
    endtask

    virtual task body();
        axi_seq_item = seq_item::type_id::create("SEQ_ITEM");
        repeat (100) begin
            write_item();
            read_item();
        end
    endtask
endclass  //axi_sequence extends uvm_sequence

class axi_driver extends uvm_driver #(seq_item);
    `uvm_component_utils(axi_driver)

    virtual axi_interface axi_mem_intf;
    seq_item axi_seq_item;

    function new(input string name = "axi_driver", uvm_component c);
        super.new(name, c);
    endfunction  //new()

    virtual function void build_phase(uvm_phase phase);
        super.build_phase(phase);
        axi_seq_item = seq_item::type_id::create("SEQ_ITEM", this);
        if (!uvm_config_db#(virtual axi_interface)::get(
                this, "", "axi_mem_intf", axi_mem_intf
            )) begin
            `uvm_fatal(get_name(), "Unable to access axi interface");
        end
    endfunction

    virtual function void start_of_simulation_phase(uvm_phase phase);
        $display("display start of simulation phase");
    endfunction

    virtual task run_phase(uvm_phase phase);
        $display("Display run phase!");
        forever begin
            seq_item_port.get_next_item(axi_seq_item);
            @(posedge axi_mem_intf.ACLK);

            if (axi_seq_item.write == 1'b1) begin  // for write transaction
                axi_mem_intf.ARADDR  <= 0;
                axi_mem_intf.ARVALID <= 1'b0;
                axi_mem_intf.RREADY  <= 1'b0;
                // AW Channel (Write Address)
                axi_mem_intf.AWADDR  <= axi_seq_item.address;
                axi_mem_intf.AWVALID <= 1'b1;
                // W Channel
                axi_mem_intf.WDATA   <= axi_seq_item.wdata;
                axi_mem_intf.WVALID  <= 1'b1;
                axi_mem_intf.WSTRB   <= 4'b0001;
                // B Channel
                axi_mem_intf.BREADY <= 1'b1;
                @(posedge axi_mem_intf.BVALID);
            end else begin  // for read transaction
                axi_mem_intf.AWADDR  <= 0;
                axi_mem_intf.AWVALID <= 1'b0;
                axi_mem_intf.WDATA   <= 0;
                axi_mem_intf.WVALID  <= 1'b0;
                axi_mem_intf.BREADY  <= 1'b0;
                // AR Channel
                axi_mem_intf.ARADDR  <= axi_seq_item.address;
                axi_mem_intf.ARVALID <= 1'b1;
                // R Channel
                axi_mem_intf.RREADY  <= 1'b1;
                @(posedge axi_mem_intf.RVALID);
                axi_mem_intf.ARVALID <= 1'b0;
                axi_mem_intf.RREADY <= 1'b0;
            end
            seq_item_port.item_done(); // sequencer에게 item_done 신호를 보낸다 
        end
    endtask  //run_phase
endclass  //axi_driver extends uvm_driver

class axi_monitor extends uvm_monitor;
    `uvm_component_utils(axi_monitor)

    uvm_analysis_port #(seq_item) send;
    virtual axi_interface axi_mem_intf;
    seq_item axi_seq_item;

    function new(input string name = "axi_monitor", uvm_component c);
        super.new(name, c);
        send = new("WRITE", this);
    endfunction  //new()

    virtual function void build_phase(uvm_phase phase);
        super.build_phase(phase);
        axi_seq_item = seq_item::type_id::create("SEQ_ITEM", this);
        if (!uvm_config_db#(virtual axi_interface)::get(
                this, "", "axi_mem_intf", axi_mem_intf
            )) begin
            `uvm_fatal(get_name(), "Unable to access axi interface");
        end
    endfunction

    virtual task run_phase(uvm_phase phase);  // phase는 task로 실행
        forever begin
            @(posedge axi_mem_intf.ACLK);
            #2;
            if (axi_mem_intf.AWVALID == 1'b1) begin  // for write transaction
                axi_seq_item.write   = 1'b1;
                axi_seq_item.address = axi_mem_intf.AWADDR;
                axi_seq_item.wdata   = axi_mem_intf.WDATA[7:0];
                axi_seq_item.rdata  = 0;
                @(posedge axi_mem_intf.BVALID);
            end else begin  // for read transaction
                axi_seq_item.write   = 1'b0;
                axi_seq_item.address = axi_mem_intf.ARADDR;
                axi_seq_item.wdata = 0;
                @(posedge axi_mem_intf.RVALID);  // monitor에서 data 받는다
                axi_seq_item.rdata = axi_mem_intf.RDATA[7:0];
            end
            send.write(
                axi_seq_item);  // monitor로 axi_seq_item을 전달한다
        end
    endtask
endclass  //axi_monitor extends uvm_monitor

class axi_scoreboard extends uvm_scoreboard;
    `uvm_component_utils(axi_scoreboard)

    uvm_analysis_imp #(seq_item, axi_scoreboard) recv;

    logic [7:0] scb_mem[0:15];

    function new(input string name = "axi_scoreboard", uvm_component c);
        super.new(name, c);
        recv = new("READ", this);
    endfunction  //new()

    virtual function void write(seq_item data);
        `uvm_info("SCB", "Data received from Monitor",
                  UVM_NONE);  // display, UVM_NONE : 나타낼 정보량 옵션
        //data.print(uvm_default_line_printer);
        data.print();


        if (data.write == 1'b1) begin  // write transaction
            scb_mem[data.address] = data.wdata[7:0];
            `uvm_info("SCB", $sformatf("Data Write Memory[%d]: %0h",
                                       data.address, data.wdata), UVM_NONE);
        end else begin
            if (data.rdata === scb_mem[data.address]) begin
                `uvm_info("SCB", "Pass!", UVM_NONE);
            end else begin
                `uvm_info("SCB", "Fail!", UVM_NONE);
            end
            `uvm_info("SCB", $sformatf("Read Data: %0h, scb_mem[%0d]: %0h",
                                       data.rdata[7:0], data.address,
                                       scb_mem[data.address]), UVM_NONE);
        end

    endfunction
endclass  //axi_scoreboard extends uvm_scoreboard

class axi_agent extends uvm_agent;
    `uvm_component_utils(axi_agent)

    function new(input string name = "axi_agent", uvm_component c);
        super.new(name, c);
    endfunction  //new()

    axi_monitor axiMonitor;
    axi_driver axiDriver;
    uvm_sequencer #(seq_item) axiSequencer;  // 흐름 제어

    virtual function void build_phase(uvm_phase phase);
        super.build_phase(phase);
        axiMonitor = axi_monitor::type_id::create("MON", this);
        axiDriver = axi_driver::type_id::create("DRV", this);
        axiSequencer = uvm_sequencer#(seq_item)::type_id::create("SQR", this);
    endfunction

    virtual function void connect_phase(uvm_phase phase);
        super.connect_phase(phase);
        axiDriver.seq_item_port.connect(axiSequencer.seq_item_export);
    endfunction
endclass  //axi_agent extends uvm_agent

class axi_env extends uvm_env;
    `uvm_component_utils(axi_env)

    function new(input string name = "axi_env", uvm_component c);
        super.new(name, c);
    endfunction  //new()

    axi_scoreboard axiScoreboard;  // handler
    axi_agent axiAgent;

    virtual function void build_phase(uvm_phase phase);
        super.build_phase(phase);
        axiScoreboard = axi_scoreboard::type_id::create("SCB", this);
        axiAgent = axi_agent::type_id::create("AGENT", this);
    endfunction

    virtual function void connect_phase(uvm_phase phase);
        super.connect_phase(phase);
        axiAgent.axiMonitor.send.connect(axiScoreboard.recv);
    endfunction
endclass  //axi_env extends uvm_env

class axi_test extends uvm_test; // axi_test에서 만들어놓은 uvm_test를 상속받겠다
    `uvm_component_utils(axi_test);

    function new(input string name = "axi_test", uvm_component c);
        super.new(name,
                  c);    // 부모 생성자에 자식 클래스의 이름을 등록한다.
    endfunction  //new()

    axi_sequence axiSequence;
    axi_env axiEnv;

    virtual function void build_phase(uvm_phase phase);  // void : not return
        super.build_phase(phase);
        axiSequence =
            axi_sequence::type_id::create("SEQ", this);  // this : this class
        axiEnv = axi_env::type_id::create("ENV", this);
    endfunction

    virtual task run_phase(uvm_phase phase);
        phase.raise_objection(this);
        axiSequence.start(
            axiEnv.axiAgent.axiSequencer);  // axiSequencer와 연결
        phase.drop_objection(this);
    endtask  //run_phase

endclass  //axi_test extends uvm_test

module tb_AXI_Slave_memory ();

    logic ACLK;
    logic ARESETn;

    always #5 ACLK = ~ACLK;

    initial begin
        ACLK = 1'b0;
        ARESETn = 1'b0;
        #10 ARESETn = 1'b1;
    end

    axi_interface axi_mem_intf (
        ACLK,
        ARESETn
    );

    axi_test axiTest;  // handler

    AXI_Slave_Memory dut (
        // Global Signal
        .ACLK(axi_mem_intf.ACLK),
        .ARESETn(axi_mem_intf.ARESETn),
        // AW Channel (Write Address)
        .AWADDR(axi_mem_intf.AWADDR),
        .AWVALID(axi_mem_intf.AWVALID),
        .AWREADY(axi_mem_intf.AWREADY),
        // AR Channel
        .ARADDR(axi_mem_intf.ARADDR),
        .ARVALID(axi_mem_intf.ARVALID),
        .ARREADY(axi_mem_intf.ARREADY),
        // W Channel
        .WDATA(axi_mem_intf.WDATA),
        .WVALID(axi_mem_intf.WVALID),
        .WSTRB(axi_mem_intf.WSTRB),
        .WREADY(axi_mem_intf.WREADY),
        // R Channel
        .RDATA(axi_mem_intf.RDATA),
        .RVALID(axi_mem_intf.RVALID),
        .RREADY(axi_mem_intf.RREADY),
        // B Channel
        .BRESP(axi_mem_intf.BRESP),
        .BVALID(axi_mem_intf.BVALID),
        .BREADY(axi_mem_intf.BREADY)
    );

    initial begin
        axiTest = new("AXI Slave UVM Verification",
                      null);  // 실체화, make instance, 이름 할당
        uvm_config_db#(virtual axi_interface)::set(
            null, "*", "axi_mem_intf",
            axi_mem_intf);  // storage, virtual : data type 

        run_test();
    end

endmodule

 

코드 분석

Driver과 Slave 관계

  • Driver와 Slave에서 랜덤 값을 만들어 Monitor에게 전달한다.
  • Monitor은 addr, wdata, rdata 값을 scoreboard에게 전달한다.
  • scoreboard에서 별도의 메모리를 만들어 값을 저장한다. 메모리에 저장된 값과 uvm 실행된 값을 비교하여 Pass / Fail을 판단한다.