电子说
大家不要以为APB的master和slave很简单,不需要了解。这是大错特错,为什么呢?
不过设计什么模块,你都要让它挂在标准总线上,比如你设计DMA,你就同时需要了解AMBA的master和slave设计。又比如你是设计算法计算模块,你的数据肯定要放到sram,你当然也要了解AMBA的master设计,将数据传输到crossbar上,进而放到指定memory。又比如SOC设计,肯定需要各种bridge,假设一个AHB2APB,你就同时需要了解AHB slave和APB master。
以APB为例,还是因为APB简单,但是我们可以从它学到设计的方法和思路。
既然是设计就需要spec和状态机。
设计spec如下
1、模块规划

模块diagram
2、接口描述

接口描述
3、时序描述
读时序

读时序
写时序

写时序
4、FSM
就是之前讲的APB协议状态机。如下图

APB FSM
模块规划有了,接口有了,时序有了,状态机有了,就可以开始设计coding了,代码如下:
module apb
#(
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
)(
//-- clkrst 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 ,
input pslverr_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 && pready_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 && pready_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
case(cur_state)
IDLE :if(start_flag)begin
nxt_state = SETUP;
end
else begin
nxt_state = IDLE;
end
SETUP :nxt_state = ACCESS;
ACCESS:if (!pready_i)begin
nxt_state = ACCESS;
end
else if(start_flag)begin
nxt_state = SETUP;
end
else if(!cmd_vld_i && pready_i)begin
nxt_state = IDLE;
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 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
/*-----------------------------------------------
-- 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
endmodule
模块设计的比较简单,只是实现APB的基本功能。下面讲一下设计重点:
·一定要做好功课在开始coding。
·Flow control,APB的上级模块,需要给到流控信号,告知APB master什么时候开始传输,什么时候结束。
·FSM,必须完全遵循AMBA的datasheet。
·时序对齐,和FSM一样,接口时序要和APB协议对齐。
·重点中的重点,pready的反压一定要逐级反压,不能直接送到APB master的上次模块,这样会丢数据。
testbench如下
`timescale 1ns/1ns
module tb_apb;
reg pclk_i ;
reg prst_n_i ;
reg [55:0] cmd_i ;
reg cmd_vld_i ;
wire [31:0] cmd_rd_data_o;
wire [15:0] paddr_o ;
wire pwrite_o ;
wire psel_o ;
wire penable_o ;
wire [31:0] pwdata_o ;
reg [31:0] prdata_i ;
reg pready_i ;
reg pslverr_i ;
initial begin
// rst;
pclk_i = 0;
prst_n_i = 1;
pslverr_i = 0;
cmd_i = 56'b0;
cmd_vld_i = 0;
prdata_i = 32'b0;
pready_i = 1;
#20 prst_n_i = 0;
#20 prst_n_i = 1;
// cmd_in_wr(cmd_i,56'h01_FF_EE_DD_CC_BB_AA);
cmd_i = 56'h01_FF_EE_DD_CC_BB_AA;
cmd_vld_i = 1 ;
#20 cmd_vld_i = 0;
#31 pready_i = 0;
#80 pready_i = 1;
#90;
//cmd_in_rd(cmd_i,56'h00_AA_BB_CC_DD_EE_FF,prdata_i,32'h12_34_56_78);
cmd_i = 56'h00_AA_BB_CC_DD_EE_FF;
cmd_vld_i = 1;
#20 cmd_vld_i = 0;
#30 pready_i = 0;
#60 pready_i = 1;
prdata_i = 32'h12_34_56_78;
cmd_i = 56'h00_AA_BB_CC_DD_EE_FF;
cmd_vld_i = 1;
#20 cmd_vld_i = 0;
#30 pready_i = 0;
#50 pready_i = 1;
prdata_i = 32'h11_22_33_44;
end
always #10 pclk_i = ~pclk_i;
//-- RST
task rst;
begin
pclk_i = 1;
prst_n_i = 1;
pslverr_i = 0;
cmd_i = 56'b0;
cmd_vld_i = 0;
prdata_i = 32'b0;
pready_i = 1;
#20 prst_n_i = 0;
#10 prst_n_i = 1;
//cmd_i = 56'h01_FF_EE_DD_CC_BB_Ab;
end
endtask
//-- write
task cmd_in_wr;
output [55:0] cmd;
input [55:0] data;
begin
cmd = data;
cmd_vld_i = 1 ;
#20 cmd_vld_i = 0;
#20 pready_i = 0;
#40 pready_i = 1;
end
endtask
//-- read
task cmd_in_rd;
output [55:0] cmd;
input [55:0] data ;
output [31:0] prdata;
input [31:0] rd_data;
begin
cmd = data;
cmd_vld_i = 1;
#20 cmd_vld_i = 0;
#20 pready_i = 0;
#40 pready_i = 1;
prdata = rd_data;
end
endtask
initial begin
#1000 $finish;
end
apb tb_apb(
.pclk_i (pclk_i ),
.prst_n_i (prst_n_i ),
.cmd_i (cmd_i ),
.cmd_vld_i (cmd_vld_i ),
.cmd_rd_data_o(cmd_rd_data_o),
.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 ),
.pslverr_i (pslverr_i )
);
initial begin
$fsdbDumpfile("apb.fsdb");
$fsdbDumpvars ;
$fsdbDumpMDA ;
end
endmodule
makefile如下:
LAB_DIR = /home/*/apb
DFILES = $(LAB_DIR)/*.v
all:clean elab rung
elab:
vcs -full64 -LDFLAGS -Wl,-no-as-needed -debug_acc+all -timescale=1ns/1ns
-fsdb -sverilog -l comp.log
${DFILES}
run:
./simv -l run.log
rung:
./simv -gui -l run.log
verdi:
verdi ${DFILES}
-ssf ./*.fsdb &
clean:
rm -rf AN.DB
rm -rf DVEfiles
rm -rf csrc
rm -rf simv.*
rm -rf *simv
rm -rf inter.vpd
rm -rf ucli.key
rm -rf *.log
rm -rf verdiLog
rm -rf novas*
rm -rf *.fsdb
下面是仿真结果

好了,今天讲的主要就这么多,这个是基础,但也是干货,对以后设计AHB,AXI乃至NOC都非常有帮助。
审核编辑:黄飞
全部0条评论
快来发表一下你的评论吧 !