Systemverilog에서 interface 사용하기
전통적인 verilog HDL 사용 유저들에게는 interface는 익숙한 소재는 아니다. 하지만 더이상 verilog가 표준이 업데이트 되지 않고, 검증에 특화되었던 systemverilog에 하위호환으로 포함된 이상, 새로운 표준에서 제공하는 다양한 기능들을 똑같이 누릴 수 있게 되었다.
Interface 기능은 SystemVerilog 3.1(2002년) standard에서 처음으로 도입되었다. 이후 IEEE 1800-2005 standard에 통합되면서 공식화되었다.
이전의 전통방식(Verilog-1995/2001)은 모듈간의 연결에 있어서 모든 wire를 일일이 선언하고 매핑해야 하는 번거로움이 있었다. 이는 port의 개수가 적은 모듈에는 괜찮을지 모르지만 ip top이나 bus interconnect와 같은 수십, 수백개의 port가 존재하는 모듈에 연결하는 것은 사람의 손에서 발생하는 오타로 인해서 connectivity error가 발생하기 십상이었다. 물론, 가독성이 떨어지는 것은 덤이고.
그래서 이렇게 표준으로 등장한 interface는 코드의 간소화(Encapsulation)을 목적으로 개발되었다. 즉, AXI와 같이 신호가 많은 프로토콜을 하나의 포트 이름으로 묶어 줄 수 있게 되었다.
또한 Reusability 성이 좋아서 한번 정의된 interface를 다른 모듈에서 재사용 하기도 쉽게 설계되었다. 이때 parameter를 이용해서 구성은 같지만 signal의 bit-width가 다른 경우에도 손쉽게 대응 할 수 있게 설계되었다.
선택적으로 "modport"를 사용해서 master와 slave 간의 신호 방향을 강제 할 수 있어서 port conflict도 방지 할 수 있다.
마지막으로 UVM 등에서 virtual interface를 통해서 TB가 RTL 내부 신호에 쉽게 접근 할 수 있게 해주긴 하지만, interface는 vim 유저의 입장에서는 사실 따라가는 것 자체가 지옥에 가까운 신호다.
verilog를 오래 써온 사람들 입장에서는 한번쯤 "어? 근데 이거 system call function 같이 합성은 안되는거 아니야?" 라고 생각 할 수 있지만, 유감스럽게도 interface는 synthesis를 지원한다. 그렇기 때문에, 디버깅을 힘들게 찾아가는 vim의 노예들을 무시하고 과감하게 사용해도 괜찮다고 할 수 있다.
이제, 예시코드를 통해서 이해를 도와주는 시간을 가져보자.
아래는 AXI4 interface에 대한 예시코드이다.
Step 1: Interface declear (axi_if.sv)
interface axi_if #(
parameter ADDR_WIDTH = 32,
parameter DATA_WIDTH = 32
) (
input logic aclk, // Global Clock
input logic aresetn // Global Reset
);
// --------------------------------------------------------
// AXI4 Signals Definition
// --------------------------------------------------------
// Write Address Channel (AW)
logic [ADDR_WIDTH-1:0] awaddr;
logic awvalid;
logic awready;
// Write Data Channel (W)
logic [DATA_WIDTH-1:0] wdata;
logic [DATA_WIDTH/8-1:0] wstrb;
logic wlast;
logic wvalid;
logic wready;
// Write Response Channel (B)
logic [1:0] bresp;
logic bvalid;
logic bready;
// Read Address Channel (AR)
logic [ADDR_WIDTH-1:0] araddr;
logic arvalid;
logic arready;
// Read Data Channel (R)
logic [DATA_WIDTH-1:0] rdata;
logic [1:0] rresp;
logic rlast;
logic rvalid;
logic rready;
// --------------------------------------------------------
// Modports: 신호의 방향(Input/Output) 정의
// --------------------------------------------------------
// Master 관점: 주소/데이터를 보내고(Output), Ready/Data를 받음(Input)
modport master (
input aclk, aresetn,
output awaddr, awvalid, input awready,
output wdata, wstrb, wlast, wvalid, input wready,
input bresp, bvalid, output bready,
output araddr, arvalid, input arready,
input rdata, rresp, rlast, rvalid, output rready
);
// Slave 관점: 주소/데이터를 받고(Input), Ready/Data를 보냄(Output)
modport slave (
input aclk, aresetn,
input awaddr, awvalid, output awready,
input wdata, wstrb, wlast, wvalid, output wready,
output bresp, bvalid, input bready,
input araddr, arvalid, output arready,
output rdata, rresp, rlast, rvalid, input rready
);
// Monitor 관점: 모든 신호를 관찰만 함 (검증용)
modport monitor (
input aclk, aresetn,
input awaddr, awvalid, awready,
input wdata, wstrb, wlast, wvalid, wready,
input bresp, bvalid, bready,
input araddr, arvalid, arready,
input rdata, rresp, rlast, rvalid, rready
);
endinterface
Step 2: Use case in module(master, slave, top)
// --------------------------------------------------------
// Master Module
// --------------------------------------------------------
module axi_master (
axi_if.master m_if // 인터페이스 사용 (.master modport 지정)
);
// 예시: 간단한 제어 로직
always_ff @(posedge m_if.aclk or negedge m_if.aresetn) begin
if (!m_if.aresetn) begin
m_if.awvalid <= 1'b0;
m_if.awaddr <= '0;
end else begin
// 신호 접근 시 '.' 연산자 사용
if (!m_if.awvalid) begin
m_if.awvalid <= 1'b1;
m_if.awaddr <= 32'h1000_0000;
end
end
end
endmodule
// --------------------------------------------------------
// Slave Module
// --------------------------------------------------------
module axi_slave (
axi_if.slave s_if // 인터페이스 사용 (.slave modport 지정)
);
// 예시: Ready 신호 생성
assign s_if.awready = 1'b1;
assign s_if.wready = 1'b1;
always_ff @(posedge s_if.aclk) begin
if (s_if.awvalid && s_if.awready) begin
// Master가 보낸 주소 읽기
$display("Write Address Received: %h", s_if.awaddr);
end
end
endmodule
// --------------------------------------------------------
// Top Module (Connection)
// --------------------------------------------------------
module top;
logic clk, resetn;
// 1. 인터페이스 인스턴스화 (와이어 다발 생성)
axi_if bus_if (
.aclk(clk),
.aresetn(resetn)
);
// 2. 모듈 연결 (매우 간결함)
axi_master u_master ( .m_if(bus_if.master) );
axi_slave u_slave ( .s_if(bus_if.slave) );
// Test stimulus
initial begin
clk = 0; resetn = 0;
#10 resetn = 1;
#100 $finish;
end
always #5 clk = ~clk;
endmodule
axi_if.sv 를 재활용하는 방법에은 위의 사용 예시와 같이 interface를 instance로 만들어주면 된다. 이때, include를 하는 것도 괜찮긴 하지만 통상적으로는 include path로 file list에 등록을 해두고, 이를 통해서 EDA tool이 자동으로 interface를 찾아서 컴파일 하는 것을 권장하고 있다.
마지막으로, parameter를 이용해서 같은 interface지만 서로 다른 bit-width에 대해서 대응할 수 있는 방법에 대해서 소개하려고 한다. (사실 이건 그냥 일반 모듈 instance 할 때랑 똑같다고 본다)
interface axi_if #(
parameter ADDR_WIDTH = 32, // 기본 주소 폭
parameter DATA_WIDTH = 32 // 기본 데이터 폭
) (
input logic aclk,
input logic aresetn
);
// 파라미터에 따라 비트 폭이 결정됨
logic [ADDR_WIDTH-1:0] awaddr;
logic [DATA_WIDTH-1:0] wdata;
// ... 나머지 신호들 ...
modport master (
input aclk, aresetn,
output awaddr, wdata
);
modport slave (
input aclk, aresetn,
input awaddr, wdata
);
endinterface
module top;
logic clk, rst_n;
// ---------------------------------------------------------
// Case 1: 고성능 메모리용 버스 (64-bit Data, 40-bit Addr)
// ---------------------------------------------------------
axi_if #(
.ADDR_WIDTH(40), // 40비트 주소
.DATA_WIDTH(64) // 64비트 데이터
) axi_main_bus (
.aclk(clk),
.aresetn(rst_n)
);
// ---------------------------------------------------------
// Case 2: 저속 주변장치용 버스 (32-bit Data, 32-bit Addr)
// ---------------------------------------------------------
axi_if #(
.ADDR_WIDTH(32), // 32비트 주소 (기본값이라 생략 가능하지만 명시함)
.DATA_WIDTH(32) // 32비트 데이터
) axi_peri_bus (
.aclk(clk),
.aresetn(rst_n)
);
// ---------------------------------------------------------
// 모듈 연결
// ---------------------------------------------------------
// 64비트 슬레이브 연결
memory_controller u_dram_ctrl (
.bus_if(axi_main_bus.slave)
);
// 32비트 슬레이브 연결
gpio_controller u_gpio_ctrl (
.bus_if(axi_peri_bus.slave)
);
endmodule
이제 interface를 이용해서 맛깔나는 verilog RTL을 작성하고, 틀에 박힌 선임들에게 새로운 신세계를 보여드리자.
댓글
댓글 쓰기