Now, let's start designing the APB interface in earnest. For an overview of the interface and the BFM for verification, please refer to the previous post.
Register map
First, you need to create a register map for interface design. This determines which signals the interface will send and receive from the IP block. The register map is recorded in the data sheet describing the IP and is also required for developing the software that operates the IP, making it incredibly important.
Now, let's write the register map as follows.
- 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 |
Here, R/W stands for Read/Write access and the types are as follows:
- RW: readable and writable
- RO: read-only
- WO: write-only
- W1C: write 1 clear
APB interface design
Now, let's start designing the interface in earnest. First, let's set up the input and output ports.
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
);There's a bus signal, and the signals set in the register map will be set as output and connected to the IP block. And we need to declare the data type, right? First, I set write_en and read_en.
Data type declaration
//===================================================================
// Internal Signals
//===================================================================
wire write_en = psel & ~penable & pwrite;
wire read_en = psel & ~penable & ~pwrite;The enable signals are turned on when psel is high, that is, when the CPU selects this IP. Then, depending on pwrite, it decides whether to write or read.
APB write
Next is the Address related 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;The address set in the register map has been reflected.
Here's the actual register signal. Compare it with the generated 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];
endWhen the CPU writes to register a (0x0), we_a turns on and specific bits of pwdata are written to a_one, a_two, and a_three. The same goes for register b (0x4). So, we need to connect this register file to the output, right?
//===================================================================
// 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
Now let's set the RD (Read Data) value.
//===================================================================
// 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;Now, let's write a testbench and check whether the register setting is correct through simulation results.
//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;
endmoduleBy performing a write transfer to register a (address 0x0), you can control the a_1, a_2, and a_3 signals, and by performing a write transfer to register b (address 0x4), you can control the b_1, b_2, and b_3 signals.
These signals are input to the IP block, enabling IP control. The CPU controls the IP through the AMBA bus; this is the register setting.
This allows us to design IPs using the APB interface in earnest.
References: ARM® AMBA APB Protocol Specification