[하만]세미콘 아카데미/verilog
0619 uvm_ram
내머리속은이런걸로
2024. 7. 10. 12:20

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을 판단한다.