그럼, 이제 본격적으로 APB interface를 설계해 봅시다. Interface의 개요와 검증을 위한 BFM은 이전 글을 참고해 주세요.
Register map
먼저 interface 설계를 위한 register map을 작성해야 합니다. 이는 interface에서 IP block에 어떤 신호를 주고받을지 정하는 겁니다. Register map은 IP를 설명하는 data sheet에 기록되고, IP를 운용하는 소프트웨어를 짜는 데도 필요하기 때문에 굉~~~장히 중요합니다.
그럼, register map을 아래와 같이 작성해 보겠습니다.
- Address 0x0: register a
| Signal name | R/W | Default value | Bit |
|---|---|---|---|
| Reserved | – | – | 31:11 |
| a_3 | RW | 0x0 | 10:8 |
| Reserved | – | – | 7:6 |
| a_2 | RW | 0x0 | 5:4 |
| Reserved | – | – | 3:1 |
| a_1 | RW | 0x0 | 0 |
- Address 0x4: register b
| Signal name | R/W | Default value | Bit |
|---|---|---|---|
| Reserved | – | – | 31:11 |
| b_3 | RW | 0x0 | 10:8 |
| Reserved | – | – | 7:6 |
| b_2 | RW | 0x0 | 5:4 |
| Reserved | – | – | 3:1 |
| b_1 | RW | 0x0 | 0 |
여기서 R/W는 Read/Write access이며 종류는 아래와 같습니다.
- RW: readable and writable
- RO: read-only
- WO: write-only
- W1C: write 1 clear
APB interface 설계
그럼, 본격적으로 interface를 설계해 볼까요? 우선 input output port를 설정해 봅시다.
module apb_if (
//APB Interface
input wire pclk
,input wire presetn
,input wire penable
,input wire psel
,input wire [31:0] paddr
,input wire pwrite
,input wire [31:0] pwdata
,output wire [31:0] prdata
//Register Interface
,output wire a_1
,output wire [ 1:0] a_2
,output wire [ 2:0] a_3
,output wire b_1
,output wire [ 1:0] b_2
,output wire [ 2:0] b_3
);Bus signal이 있고, register map에서 설정한 signal 들이 output으로 설정되어 IP block에 연결될 겁니다. 그리고 자료형 선언을 해야겠죠? 우선 write_en과 read_en을 설정했습니다.
자료형 선언
//===================================================================
// Internal Signals
//===================================================================
wire write_en = psel & ~penable & pwrite;
wire read_en = psel & ~penable & ~pwrite;Enable signal 들은 psel이 high일 때, 그러니까 CPU가 이 IP를 선택할 때 켜집니다. 그리고 pwrite에 따라 write를 할지, read를 할지 정합니다.
APB write
다음으로 Address 관련된 signal입니다.
//===================================================================
// Address Decode
//===================================================================
wire a = (paddr == 32'h0);
wire b = (paddr == 32'h4);
//===================================================================
// Write Enable
//===================================================================
wire we_a = write_en & a;
wire we_b = write_en & b;Register map에서 설정한 address를 반영했습니다.
다음은 실제 register signal입니다. 작성된 register map과 비교해 보시길 바랍니다.
//===================================================================
// Register Setting
//===================================================================
reg a_one;
always @(posedge pclk or negedge presetn) begin
if(!presetn) a_one <= 1'b0;
else if (we_a) a_one <= pwdata[0];
end
reg [ 1:0] a_two;
always @(posedge pclk or negedge presetn) begin
if(!presetn) a_two <= 1'b0;
else if (we_a) a_two <= pwdata[5:4];
end
reg [ 2:0] a_three;
always @(posedge pclk or negedge presetn) begin
if(!presetn) a_three <= 1'b0;
else if (we_a) a_three <= pwdata[10:8];
end
reg b_one;
always @(posedge pclk or negedge presetn) begin
if(!presetn) b_one <= 1'b0;
else if (we_b) b_one <= pwdata[0];
end
reg [ 1:0] b_two;
always @(posedge pclk or negedge presetn) begin
if(!presetn) b_two <= 1'b0;
else if (we_b) b_two <= pwdata[5:4];
end
reg [ 2:0] b_three;
always @(posedge pclk or negedge presetn) begin
if(!presetn) b_three <= 1'b0;
else if (we_b) b_three <= pwdata[10:8];
endCPU가 register a(0x0)에 write를 하면 we_a가 켜지면서 pwdata의 특정 bit 들이 a_one, a_two, a_three에 입력됩니다. Register b(0x4)도 마찬가지로 입력됩니다. 그럼, 이 register file을 output에 연결해야겠죠?
//===================================================================
// Output Assign
//===================================================================
assign a_1 = a_one ;
assign a_2 = a_two ;
assign a_3 = a_three;
assign b_1 = b_one ;
assign b_2 = b_two ;
assign b_3 = b_three;APB Read
이제 RD(Read Data) 값을 설정해 봅시다.
//===================================================================
// Local Parameters
//===================================================================
localparam INVALID_DATA = 32'hDEAD_DEAD;
//===================================================================
// Read Decode
//===================================================================
reg [31:0] RD;
always @(*) begin
if(read_en) begin
case (paddr)
32'h0 : RD = {21'h0, a_three, 2'h0, a_two, 3'h0, a_one};
32'h4 : RD = {21'h0, b_three, 2'h0, b_two, 3'h0, b_one};
default : RD = INVALID_DATA;
endcase
end
else RD = INVALID_DATA;
end
assign prdata = RD;그럼, testbench를 작성하고 simulation 결과를 통해 register setting이 제대로 되는지 확인해 봅시다.
//top.v
`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;
wire a_1;
wire [ 1:0] a_2;
wire [ 2:0] a_3;
wire b_1;
wire [ 1:0] b_2;
wire [ 2:0] b_3;
//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 )
);
apb_if u_apb_if (
.pclk (pclk )
,.presetn (presetn )
,.psel (psel )
,.penable (penable )
,.paddr (paddr )
,.pwrite (pwrite )
,.pwdata (pwdata )
,.prdata (prdata )
,.a_1 (a_1 )
,.a_2 (a_2 )
,.a_3 (a_3 )
,.b_1 (b_1 )
,.b_2 (b_2 )
,.b_3 (b_3 )
);
initial begin
pclk = 1'b0;
presetn = 1'b0;
#(period_pclk);
presetn = 1'b1;
//Register a write
#(10*period_pclk);
u_apb.apb_write(32'h0,32'haaa);
#(10*period_pclk);
u_apb.apb_write(32'h0,32'h555);
//Register b write
#(10*period_pclk);
u_apb.apb_write(32'h4,32'haaa);
#(10*period_pclk);
u_apb.apb_write(32'h4,32'h555);
#100 $finish;
end
initial begin
$dumpfile ("test.vcd");
$dumpvars();
end
endmodule
//apb_if.v
module apb_if (
//APB Interface
input wire pclk
,input wire presetn
,input wire penable
,input wire psel
,input wire [31:0] paddr
,input wire pwrite
,input wire [31:0] pwdata
,output wire [31:0] prdata
//Register Interface
,output wire a_1
,output wire [ 1:0] a_2
,output wire [ 2:0] a_3
,output wire b_1
,output wire [ 1:0] b_2
,output wire [ 2:0] b_3
);
//===================================================================
// Local Parameters
//===================================================================
localparam INVALID_DATA = 32'hDEAD_DEAD;
//===================================================================
// Internal Signals
//===================================================================
wire write_en = psel & ~penable & pwrite;
wire read_en = psel & ~penable & ~pwrite;
reg [31:0] RD ;
//===================================================================
// Address Decode
//===================================================================
wire a = (paddr == 32'h0);
wire b = (paddr == 32'h4);
//===================================================================
// Write Enable
//===================================================================
wire we_a = write_en & a;
wire we_b = write_en & b;
//===================================================================
// Register Files
//===================================================================
reg a_one;
always @(posedge pclk or negedge presetn) begin
if(!presetn) a_one <= 1'b0;
else if (we_a) a_one <= pwdata[0];
end
reg [ 1:0] a_two;
always @(posedge pclk or negedge presetn) begin
if(!presetn) a_two <= 1'b0;
else if (we_a) a_two <= pwdata[5:4];
end
reg [ 2:0] a_three;
always @(posedge pclk or negedge presetn) begin
if(!presetn) a_three <= 1'b0;
else if (we_a) a_three <= pwdata[10:8];
end
reg b_one;
always @(posedge pclk or negedge presetn) begin
if(!presetn) b_one <= 1'b0;
else if (we_b) b_one <= pwdata[0];
end
reg [ 1:0] b_two;
always @(posedge pclk or negedge presetn) begin
if(!presetn) b_two <= 1'b0;
else if (we_b) b_two <= pwdata[5:4];
end
reg [ 2:0] b_three;
always @(posedge pclk or negedge presetn) begin
if(!presetn) b_three <= 1'b0;
else if (we_b) b_three <= pwdata[10:8];
end
//===================================================================
// Read Decode
//===================================================================
always @(*) begin
if(read_en) begin
case (paddr)
32'h0 : RD = {21'h0, a_three, 2'h0, a_two, 3'h0, a_one};
32'h4 : RD = {21'h0, b_three, 2'h0, b_two, 3'h0, b_one};
default : RD = INVALID_DATA;
endcase
end
else RD = INVALID_DATA;
end
assign prdata = RD;
//===================================================================
// Output Assign
//===================================================================
assign a_1 = a_one ;
assign a_2 = a_two ;
assign a_3 = a_three;
assign b_1 = b_one ;
assign b_2 = b_two ;
assign b_3 = b_three;
endmoduleRegister a(address 0x0)에 write transfer를 함으로써 a_1, a_2, a_3 signal을 컨트롤하고 register b(address 0x4)에 write transfer를 함으로써 b_1, b_2, b_3 signal을 컨트롤할 수 있습니다.
이 signal 들이 IP의 block에 input 되어 IP control이 가능한 것입니다. CPU가 AMBA bus를 통해 IP를 컨트롤하는 것, 이것이 register setting입니다.
이로써 본격적으로 APB interface를 사용한 IP를 설계할 수 있게 되었습니다.