이제 Verilog 실전으로 들어가 봅시다. 목표는 APB interface가 무엇인지, 또한 register setting의 개념을 대해 이해하는 것입니다. 그럼, 시작해 볼까요?
Counter RTL design
먼저 간단한 counter를 설계해보겠습니다.
//top.v
`timescale 1ns/10ps
module top();
parameter period_clk = 10;
reg clk;
reg resetn;
wire [7:0] test;
//clk
always #(period_clk*0.5) clk = ~clk;
//instance
counter u_counter (
.clk (clk )
,.resetn (resetn)
,.test (test )
);
initial begin
clk = 1'b0;
resetn = 1'b0;
#(period_clk);
resetn = 1'b1;
#1000 $finish;
end
initial begin
$dumpfile ("test.vcd");
$dumpvars();
end
endmodule
//counter.v
module counter (
input wire clk
,input wire resetn
,output wire [ 7:0] test
);
reg [ 7:0] cnt;
always @(posedge clk or negedge resetn) begin
if (!resetn) cnt <= 8'h0;
else if (cnt == 8'd10) cnt <= 8'h0;
else cnt <= (cnt + 1'b1);
end
assign test = cnt;
endmoduleTestbench에서는 clock을 생성해 주고 reset을 simulation 시작 후 일정 시간 뒤에 풀어줍니다.
Counter module에서는 reset이 풀리면 cnt 값이 clock에 동기화되어 증가합니다. 그리고 0x10이 되면 0x0으로 초기화됩니다. Simulation 결과를 확인해 볼까요?
Top.v의 signal waveform입니다. Counter의 cnt에 assign 된 test 값이 원하는 데로 달라지는 것을 확인할 수 있습니다.
Counter 수정
그런데 cnt를 초기화해 주는 값(위에서는 0x10)을 유저가 마음대로 바꿀 수 없을까요?? 코드를 다음과 같이 수정해 봅시다.
//top.v
`timescale 1ns/10ps
module top();
parameter period_clk = 10;
reg clk;
reg resetn;
reg [7:0] value; //추가
wire [7:0] test;
//clk
always #(period_clk*0.5) clk = ~clk;
//instance
counter u_counter (
.clk (clk )
,.resetn (resetn)
,.value (value ) //추가
,.test (test )
);
initial begin
clk = 1'b0;
resetn = 1'b0;
value = 8'h5; //추가
#(period_clk);
resetn = 1'b1;
#100 //추가
value = 8'hA; //추가
#100 $finish;
end
initial begin
$dumpfile ("test.vcd");
$dumpvars();
end
endmodule
//counter.v
module counter (
input wire clk
,input wire resetn
,input wire [ 7:0] value //추가
,output wire [ 7:0] test
);
reg [ 7:0] cnt;
always @(posedge clk or negedge resetn) begin
if (!resetn) cnt <= 8'h0;
else if (cnt == value) cnt <= 8'h0; //수정
else cnt <= (cnt + 1'b1);
end
assign test = cnt;
endmodule이제 cnt가 고정된 값이 아닌 input 되는 value에 의해 초기화됩니다. Simulation 결과를 확인해 봅시다.
차이가 보이시나요? Value 값을 조정함으로써 module의 작동 방식을 바꿀 수 있습니다.
Testbench가 아닌 CPU control
위의 예시에서 module에 필요한 signal을 testbench에서 바꿔주면서 작동 방식을 바꿨는데요, 실제 chip에서는 CPU가 이러한 역할을 하게 됩니다. CPU는 bus를 통해 각 module(IP)에 명령을 내리며, bus 중에서도 표준화된 bus인 AMBA bus를 통해 명령을 내림으로써 IP를 control 합니다.
하지만 IP마다 필요한 signal은 다릅니다. 위에서 설계한 counter는 cnt를 초기화해 주는 value signal이 필요하지만, 예를 들어 UART는 baud rate 관련된 signal이 필요할 겁니다.
이렇듯, IP마다 필요한 signal이 다르지만, CPU와 IP가 연결된 Bus는 표준화된 AMBA Bus를 쓰기 때문에 IP에 맞게 signal을 변환해 줄 필요가 있습니다.
APB interface
그래서 앞으로 공부할 APB interface가 필요한 겁니다. IP가 APB bus 기반이면 APB Interface, AHB bus 기반이면 AHB Interface, AXI bus 기반이면 AXI Interface가 있어야 합니다. 저희는 가장 간단한 APB bus를 기반으로 한 Interface에 대해 알아보겠습니다.
APB BFM (Bus functional Model)
Chip에서 IP에 명령을 내리는 것은 CPU입니다. 그래서 IP에서 사용할 interface를 설계하고 이를 검증하기 위해서는 명령을 내릴 CPU가 필요한데요, 검증만을 위해 CPU를 구하기에는 비효율적입니다. 그래서 CPU 역할을 할 수 있는 BFM 모듈을 통해 interface를 검증할 수 있습니다.
module apb_bfm (
input wire pclk
,input wire presetn
,output reg psel
,output reg penable
,output reg [31:0] paddr
,output reg pwrite
,output reg [31:0] pwdata
,input wire pready
,input wire [31:0] prdata
,input wire pslverr
);
parameter delay = 1;
initial begin
psel = 1'b0;
penable = 1'b0;
paddr = 32'b0;
pwrite = 1'b0;
pwdata = 32'b0;
end
//--------------------------------------------------
// task : APB single write
//--------------------------------------------------
task apb_write;
input [31: 0] addr;
input [31: 0] data;
begin
wait (pready == 1'b1);
@(posedge pclk);
psel <= #(delay) 1'b1 ;
penable <= #(delay) 1'b0 ;
paddr <= #(delay) addr ;
pwrite <= #(delay) 1'b1 ; // WRITE
pwdata <= #(delay) data ;
@(posedge pclk);
psel <= #(delay) 1'b1 ;
penable <= #(delay) 1'b1 ;
paddr <= #(delay) addr ;
pwrite <= #(delay) 1'b1 ; // WRITE
pwdata <= #(delay) data ;
wait (pready == 1'b1);
@(posedge pclk);
psel <= #(delay) 1'b0 ;
penable <= #(delay) 1'b0 ;
paddr <= #(delay) 32'h0 ;
pwrite <= #(delay) 1'b0 ;
pwdata <= #(delay) 32'h0 ;
end
endtask
//--------------------------------------------------
// task : APB single read
//--------------------------------------------------
task apb_read;
input [31: 0] addr;
output [31: 0] result_data;
begin
wait (pready == 1'b1);
@(posedge pclk);
psel <= #(delay) 1'b1 ;
penable <= #(delay) 1'b0 ;
paddr <= #(delay) addr ;
pwrite <= #(delay) 1'b0 ; // READ
pwdata <= #(delay) 32'h0 ;
@(posedge pclk);
psel <= #(delay) 1'b1 ;
penable <= #(delay) 1'b1 ;
paddr <= #(delay) addr ;
pwrite <= #(delay) 1'b0 ; // READ
pwdata <= #(delay) 32'h0 ;
wait (pready == 1'b1);
@(posedge pclk);
paddr <= #(delay) 32'h0 ;
pwrite <= #(delay) 1'b0 ; // READ
psel <= #(delay) 1'b0 ;
penable <= #(delay) 1'b0 ;
pwdata <= #(delay) 32'h0 ;
result_data = prdata;
end
endtask
endmodule위 코드를 보면 모듈에 2가지 task가 있는데요, apb_write와 apb_read입니다. APB Bus에 관해 설명할 때 매우 간단한 버스라고 한 것을 기억하시나요? 이 간단한 모듈로도 CPU가 APB bus에 내리는 명령을 수행할 수 있습니다. 그러면 testbench도 작성해 볼까요?
`timescale 1ns/10ps
module top();
parameter period_pclk = 10;
reg pclk;
reg presetn;
wire psel;
wire penable;
wire [31:0] paddr;
wire pwrite;
wire [31:0] pwdata;
wire pready;
wire [31:0] prdata;
wire pslverr;
//clk
always #(period_pclk*0.5) pclk = ~pclk;
assign pready = 1'b1;
assign pslverr = 1'b0;
//instance
apb_bfm u_apb (
.pclk (pclk )
,.presetn (presetn )
,.psel (psel )
,.penable (penable )
,.paddr (paddr )
,.pwrite (pwrite )
,.pwdata (pwdata )
,.pready (pready )
,.prdata (prdata )
,.pslverr (pslverr )
);
initial begin
pclk = 1'b0;
presetn = 1'b0;
#(period_pclk);
presetn = 1'b1;
//u_apb(apb_bfm)의 apb_write task 실행
#(10*period_pclk);
u_apb.apb_write(32'h1 ,32'hAA);
#(10*period_pclk);
u_apb.apb_write(32'h10,32'h55);
#100 $finish;
end
initial begin
$dumpfile ("test.vcd");
$dumpvars();
end
endmoduleTestbench를 보면 assign 문을 통해 pready가 1’b1로, pslverr가 1’b0으로 tie(고정) 되어 있는 것을 알 수 있습니다. pslverr는 tie 하지 않아도 되지만 pready는 apb_bfm에서 사용하기 때문에 반드시 tie 시켜줘야 합니다. 그리고 실무에서도 tie 시킵니다.
task apb_write;
input [31: 0] addr;
input [31: 0] data;
begin
wait (pready == 1'b1);Simulation을 실행하면 reset을 풀고 apb_write task를 두 번 실행합니다. 그럼, 위 testbench를 실행해 볼까요??
APB bus Specification과 testbench sim. 결과를 비교하면 같은 결과인 것을 알 수 있습니다. 0x1 address에 0xAA data를 입력한 simulation입니다.
이로써 interface를 검증할 환경을 잡았습니다. 이제 직접 module을 설계해 보겠습니다.