本节主要介绍了APB协议的基本传输时序,并以基于简单APB的SRAM读写实验加强理解。
APB协议的基本介绍
Advanced Peripheral Bus(先进外设总线),具备低功耗、接口简单、控制简单的特点,主要用于低速、功耗低的外设寄存器配置
APB协议是一种低成本接口,针对低功耗和降低接口复杂度进行了优化,APB接口不是流水线接口,是一种简单的同步协议。每次传输至少需要两个周期才能完成
不能读写同时传输,因为其读写地址是共用的
不能仲裁,因为是单主多从协议。典型的APB协议包括唯一的APB桥作为Master,而所有的APB模块都是APB slave
APB协议的基本传输时序
- 传输时序建议直接看APB官方手册,实际上APB比较简单,单看时序图也能懂
1.写操作
1.1 无等待
1.2 有等待
2.读操作
2.1 无等待
2.2 有等待
APB时序控制状态机
从APB master角度
退出ACCESS状态取决于slaver的PREADY信号
Transfer这一状态跳转条件其实取决于master的上游模块是否需要发起一次传输活动
基于APB总线的简单SRAM读写实验
设计框图:
1.源代码
apb_master.v
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181/*-------------------------------------------------------------
-- modified by xlinxdu, 2022/05/27
-- pclk 50MHz
-- APB3,No pslverr signal
-- cmd_i:56bit;[55:48]:r/w ,8'b0 -> read,8'b1 -> write
[47:32]:paddr ,
[31:0]:pwdata
-------------------------------------------------------------*/
module apb_master #(
parameter RD_FLAG = 8'b0 ,
parameter WR_FLAG = 8'b1 ,
parameter CMD_RW_WIDTH = 8 ,
parameter CMD_ADDR_WIDTH = 16 ,
parameter CMD_DATA_WIDTH = 32 ,
parameter CMD_WIDTH = CMD_RW_WIDTH + CMD_ADDR_WIDTH + CMD_DATA_WIDTH
)(
//-- system signal
input pclk_i ,
input prst_n_i ,
//-- cmd_in
input [CMD_WIDTH -1 : 0] cmd_i ,
input cmd_vld_i ,
output reg [CMD_DATA_WIDTH - 1 : 0] cmd_rd_data_o,
//-- apb interface
output reg [CMD_ADDR_WIDTH - 1 : 0] paddr_o ,
output reg pwrite_o ,
output reg psel_o ,
output reg penable_o ,
output reg [CMD_DATA_WIDTH - 1 : 0] pwdata_o ,
input [CMD_DATA_WIDTH - 1 : 0] prdata_i ,
input pready_i
);
//-- FSM state
parameter IDLE = 3'b001;
parameter SETUP = 3'b010;
parameter ACCESS = 3'b100;
//-- current state and next state
reg [2 : 0] cur_state;
reg [2 : 0] nxt_state;
//-- data buf
reg start_flag ;
reg [CMD_WIDTH - 1 : 0] cmd_in_buf ;
reg [CMD_DATA_WIDTH - 1 : 0] cmd_rd_data_buf;
/*-----------------------------------------------\
-- update cmd_in_buf --
\-----------------------------------------------*/
always @ (posedge pclk_i or negedge prst_n_i) begin
if (!prst_n_i) begin
cmd_in_buf <= {(CMD_WIDTH){1'b0}};
end
else if (cmd_vld_i) begin
cmd_in_buf <= cmd_i;
end
end
/*-----------------------------------------------\
-- start flag of transfer --
\-----------------------------------------------*/
always @ (posedge pclk_i or negedge prst_n_i) begin
if (!prst_n_i) begin
start_flag <= 1'b0;
end
else if (cmd_vld_i) begin
start_flag <= 1'b1;
end
else begin
start_flag <= 1'b0;
end
end
/*-----------------------------------------------\
-- update current state --
\-----------------------------------------------*/
always @ (posedge pclk_i or negedge prst_n_i) begin
if (!prst_n_i) begin
cur_state <= IDLE;
end
else begin
cur_state <= nxt_state;
end
end
/*-----------------------------------------------\
-- update next state --
\-----------------------------------------------*/
always @ (*) begin
nxt_state = cur_state;
case(cur_state)
IDLE : begin
if(start_flag)begin
nxt_state = SETUP;
end
else begin
nxt_state = IDLE;
end
end
SETUP : begin
nxt_state = ACCESS;
end
ACCESS: begin
if (!pready_i)begin
nxt_state = ACCESS;
end
else if(start_flag)begin
nxt_state = SETUP;
end
else if(pready_i)begin
nxt_state = IDLE;
end
end
endcase
end
/*-----------------------------------------------\
-- update signal of output --
\-----------------------------------------------*/
always @ (posedge pclk_i or negedge prst_n_i) begin
if (!prst_n_i) begin
pwrite_o <= 1'b0;
psel_o <= 1'b0;
penable_o <= 1'b0;
paddr_o <= {(CMD_ADDR_WIDTH){1'b0}};
pwdata_o <= {(CMD_DATA_WIDTH){1'b0}};
end
else begin
if (nxt_state == IDLE) begin
psel_o <= 1'b0;
penable_o <= 1'b0;
end
else if(nxt_state == SETUP)begin
psel_o <= 1'b1;
penable_o <= 1'b0;
paddr_o <= cmd_in_buf[CMD_WIDTH - CMD_RW_WIDTH - 1 : CMD_DATA_WIDTH];
//-- read
if(cmd_in_buf[CMD_WIDTH - 1 : CMD_WIDTH - 8] == RD_FLAG)begin
pwrite_o <= 1'b0;
end
//-- write
else begin
pwrite_o <= 1'b1;
pwdata_o <= cmd_in_buf[CMD_DATA_WIDTH - 1 : 0];
end
end
else if(nxt_state == ACCESS)begin
penable_o <= 1'b1;
end
end
end
/*-----------------------------------------------\
-- update cmd_rd_data_buf --
\-----------------------------------------------*/
always @ (posedge pclk_i or negedge prst_n_i) begin
if (!prst_n_i) begin
cmd_rd_data_buf <= {(CMD_DATA_WIDTH){1'b0}};
end
else if (pready_i && psel_o && penable_o) begin
cmd_rd_data_buf <= prdata_i;
end
end
/*-----------------------------------------------\
-- update cmd_rd_data_o --
\-----------------------------------------------*/
always @ (posedge pclk_i or negedge prst_n_i) begin
if (!prst_n_i) begin
cmd_rd_data_o <= {(CMD_DATA_WIDTH){1'b0}};
end
else begin
cmd_rd_data_o <= cmd_rd_data_buf;
end
end
endmoduleapb_slave_sram.v
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75module apb_slave_sram #(
parameter DATA_WIDTH = 32,
parameter DATA_DEPTH = 16,
parameter ADDR_WIDTH = 16
)(
input PCLK,
input PSEL,
input PENABLE,
input PWRITE,
input [DATA_WIDTH - 1 : 0] PWDATA,
input [ADDR_WIDTH - 1 : 0] PADDR,
input PRESETn,
output wire PREADY,
output reg [DATA_WIDTH - 1 : 0] PRDATA
);
integer i;
reg [DATA_WIDTH - 1 : 0] SRAM [DATA_DEPTH - 1 : 0];
assign PREADY = PSEL && PENABLE;
always@(posedge PCLK or negedge PRESETn)
begin
if(!PRESETn) begin
for(i = 0; i < DATA_DEPTH; i = i + 1) begin
SRAM [i] <= 'd0;
end
PRDATA <= 0;
end
else begin
if(PSEL == 1 && PENABLE == 0 && PWRITE == 1) begin
SRAM[PADDR] <= PWDATA;
end
else if(PSEL == 1 && PENABLE == 0 && PWRITE == 0) begin
PRDATA <= SRAM[PADDR];
end
else begin
PRDATA <= PRDATA;
end
end
end
// integer i;
// reg [DATA_WIDTH - 1 : 0] SRAM [DATA_DEPTH - 1 : 0];
// always @(posedge PCLK or negedge PRESETn) begin
// if(~PRESETn) begin
// PREADY <= 'd0;
// end
// else begin
// PREADY <= PSEL && PENABLE;
// end
// end
// always@(posedge PCLK or negedge PRESETn)
// begin
// if(!PRESETn) begin
// for(i = 0; i < DATA_DEPTH; i = i + 1) begin
// SRAM [i] <= 'd0;
// end
// PRDATA <= 0;
// end
// else begin
// if(PSEL && PENABLE && PWRITE == 1) begin
// SRAM[PADDR] <= PWDATA;
// end
// else if(PSEL && PENABLE && PWRITE == 0) begin
// PRDATA <= SRAM[PADDR];
// end
// else begin
// PRDATA <= PRDATA;
// end
// end
// end
endmodule
2.Testbench
apb_simple_tb.v
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173module apb_simple_tb;
parameter RD_FLAG = 8'b0 ;
parameter WR_FLAG = 8'b1 ;
parameter CMD_RW_WIDTH = 8 ;
parameter CMD_ADDR_WIDTH = 16 ;
parameter CMD_DATA_WIDTH = 32 ;
parameter CMD_WIDTH = CMD_RW_WIDTH + CMD_ADDR_WIDTH + CMD_DATA_WIDTH ;
//-- system signal
reg pclk_i ;
reg prst_n_i ;
//-- cmd_in
reg [CMD_WIDTH -1 : 0] cmd_i ;
reg cmd_vld_i ;
wire [CMD_DATA_WIDTH - 1 : 0] cmd_rd_data_o;
//-- apb interface
wire [CMD_ADDR_WIDTH - 1 : 0] paddr_o ;
wire pwrite_o ;
wire psel_o ;
wire penable_o;
wire [CMD_DATA_WIDTH - 1 : 0] pwdata_o ;
wire [CMD_DATA_WIDTH - 1 : 0] prdata_i ;
wire pready_i ;
apb_master #(
.RD_FLAG (RD_FLAG ),
.WR_FLAG (WR_FLAG ),
.CMD_RW_WIDTH (CMD_RW_WIDTH ),
.CMD_ADDR_WIDTH (CMD_ADDR_WIDTH),
.CMD_DATA_WIDTH (CMD_DATA_WIDTH),
.CMD_WIDTH (CMD_WIDTH )
)apb_master_U1(
//-- system signal
.pclk_i (pclk_i ),
.prst_n_i (prst_n_i),
//-- cmd_in
.cmd_i (cmd_i ),
.cmd_vld_i (cmd_vld_i ),
.cmd_rd_data_o(cmd_rd_data_o),
//-- apb interface
.paddr_o (paddr_o ),
.pwrite_o (pwrite_o ),
.psel_o (psel_o ),
.penable_o (penable_o),
.pwdata_o (pwdata_o ),
.prdata_i (prdata_i ),
.pready_i (pready_i )
);
apb_slave_sram #(
.DATA_WIDTH (CMD_DATA_WIDTH),
.DATA_DEPTH (CMD_ADDR_WIDTH),
.ADDR_WIDTH (CMD_ADDR_WIDTH)
)apb_slave_sram_U1(
.PCLK (pclk_i ),
.PRESETn(prst_n_i),
.PSEL (psel_o),
.PENABLE(penable_o),
.PWRITE (pwrite_o),
.PWDATA (pwdata_o),
.PADDR (paddr_o),
.PREADY (pready_i),
.PRDATA (prdata_i)
);
initial begin
pclk_i = 1;
end
always #10 pclk_i = ~pclk_i;
initial begin
prst_n_i = 0;
cmd_i = 'd0;
cmd_vld_i = 'd0;
#51 prst_n_i = 1;
//--------------------------有等待读写-----------------------------------
//写操作
// cmd_vld_i <= 'd1; cmd_i <= {8'b1, 16'd0, 32'd101};
// #20 cmd_vld_i <= 'd0;
// #40 cmd_vld_i <= 'd1; cmd_i <= {8'b1, 16'd1, 32'd102};
// #20 cmd_vld_i <= 'd0;
// #40 cmd_vld_i <= 'd1; cmd_i <= {8'b1, 16'd2, 32'd103};
// #20 cmd_vld_i <= 'd0;
// #40 cmd_vld_i <= 'd1; cmd_i <= {8'b1, 16'd3, 32'd104};
// #20 cmd_vld_i <= 'd0;
// #60 cmd_vld_i <= 'd1; cmd_i <= {8'b1, 16'd4, 32'd105};
// #20 cmd_vld_i <= 'd0;
// #40 cmd_vld_i <= 'd1; cmd_i <= {8'b1, 16'd5, 32'd106};
// #20 cmd_vld_i <= 'd0;
// #40 cmd_vld_i <= 'd1; cmd_i <= {8'b1, 16'd6, 32'd107};
// #20 cmd_vld_i <= 'd0;
// //读操作
// #100 cmd_vld_i <= 'd1; cmd_i <= {8'b0, 16'd6, 32'd0};
// #20 cmd_vld_i <= 'd0;
// #40 cmd_vld_i <= 'd1; cmd_i <= {8'b0, 16'd5, 32'd0};
// #20 cmd_vld_i <= 'd0;
// #40 cmd_vld_i <= 'd1; cmd_i <= {8'b0, 16'd4, 32'd0};
// #20 cmd_vld_i <= 'd0;
// #80 cmd_vld_i <= 'd1; cmd_i <= {8'b0, 16'd3, 32'd0};
// #20 cmd_vld_i <= 'd0;
// #40 cmd_vld_i <= 'd1; cmd_i <= {8'b0, 16'd2, 32'd0};
// #20 cmd_vld_i <= 'd0;
// #40 cmd_vld_i <= 'd1; cmd_i <= {8'b0, 16'd1, 32'd0};
// #20 cmd_vld_i <= 'd0;
// #40 cmd_vld_i <= 'd1; cmd_i <= {8'b0, 16'd0, 32'd0};
// #20 cmd_vld_i <= 'd0;
//--------------------------无等待读写-----------------------------------
//写操作
cmd_vld_i <= 'd1; cmd_i <= {8'b1, 16'd0, 32'd101};
#20 cmd_vld_i <= 'd0;
#20 cmd_vld_i <= 'd1; cmd_i <= {8'b1, 16'd1, 32'd102};
#20 cmd_vld_i <= 'd0;
#20 cmd_vld_i <= 'd1; cmd_i <= {8'b1, 16'd2, 32'd103};
#20 cmd_vld_i <= 'd0;
#20 cmd_vld_i <= 'd1; cmd_i <= {8'b1, 16'd3, 32'd104};
#20 cmd_vld_i <= 'd0;
#40 cmd_vld_i <= 'd1; cmd_i <= {8'b1, 16'd4, 32'd105};
#20 cmd_vld_i <= 'd0;
#20 cmd_vld_i <= 'd1; cmd_i <= {8'b1, 16'd5, 32'd106};
#20 cmd_vld_i <= 'd0;
#20 cmd_vld_i <= 'd1; cmd_i <= {8'b1, 16'd6, 32'd107};
#20 cmd_vld_i <= 'd0;
//读操作
#80 cmd_vld_i <= 'd1; cmd_i <= {8'b0, 16'd6, 32'd0};
#20 cmd_vld_i <= 'd0;
#20 cmd_vld_i <= 'd1; cmd_i <= {8'b0, 16'd5, 32'd0};
#20 cmd_vld_i <= 'd0;
#20 cmd_vld_i <= 'd1; cmd_i <= {8'b0, 16'd4, 32'd0};
#20 cmd_vld_i <= 'd0;
#60 cmd_vld_i <= 'd1; cmd_i <= {8'b0, 16'd3, 32'd0};
#20 cmd_vld_i <= 'd0;
#20 cmd_vld_i <= 'd1; cmd_i <= {8'b0, 16'd2, 32'd0};
#20 cmd_vld_i <= 'd0;
#20 cmd_vld_i <= 'd1; cmd_i <= {8'b0, 16'd1, 32'd0};
#20 cmd_vld_i <= 'd0;
#20 cmd_vld_i <= 'd1; cmd_i <= {8'b0, 16'd0, 32'd0};
#20 cmd_vld_i <= 'd0;
end
endmodule
3.仿真结果
无等待读写:(这种情况下,将pready一直拉高也可以)
有等待读写:
其实master中的关键就是写出控制状态机,slaver中主要是psel和penable对原模块的控制