1 前言
- 本文所用EDA虚拟机来自博主【芯王国】的分享,版本是EDA_lite,对于没精力折腾环境的小白来说非常友好,强烈推荐!
- 本文的例程改编于知乎博主【四人独行】的博客《soc设计入门7-APB master设计》。
(没错,本文只是个学习笔记,只是把实验过程记录一下,并非原创~)
2 代码设计
【四人独行】的原版代码也是跑得通的,但是我感觉有些冗余,而且不能像手册一样连续不断的2拍传1个数据,所以我稍微改变了下。
2.1 APB设计
module apb
//========================< parameter >=====================================
#(
parameter WR = 1'b1 ,
parameter RD = 1'b0 ,
//---------------------------------------------------
parameter wRW = 1 ,
parameter wADDR = 16 ,
parameter wDATA = 32 ,
parameter wCMD = wRW + wADDR + wDATA
)
//========================< port >==========================================
(
//-- clk rst ----------------------------------------
input pclk_i ,
input prst_n_i ,
//-- cmd_in -----------------------------------------
input [wCMD-1:0] cmd_i ,
input cmd_vld_i ,
//-- apb --------------------------------------------
output reg [wADDR-1:0] paddr_o ,
output reg pwrite_o ,
output reg psel_o ,
output reg penable_o ,
output reg [wDATA-1:0] pwdata_o ,
input [wDATA-1:0] prdata_i ,
input pready_i ,
input pslverr_i
);
//========================< parameter >=====================================
parameter IDLE = 3'b001 ;
parameter SETUP = 3'b010 ;
parameter ACCESS = 3'b100 ;
//========================< signal >========================================
reg [2:0] cur_state ;
reg [2:0] nxt_state ;
wire start_flag = cmd_vld_i && pready_i;
//==========================================================================
//== FSM
//==========================================================================
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
always @ (*) begin
case(cur_state)
IDLE : begin
if(start_flag)
nxt_state = SETUP;
else
nxt_state = IDLE;
end
SETUP : begin
nxt_state = ACCESS;
end
ACCESS: begin
if (!pready_i)
nxt_state = ACCESS;
else if(start_flag)
nxt_state = SETUP;
else if(!cmd_vld_i && pready_i)
nxt_state = IDLE;
end
endcase
end
//==========================================================================
//== update 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 <= {(wADDR){1'b0}};
pwdata_o <= {(wDATA){1'b0}};
end
else if (cur_state == IDLE) begin
psel_o <= 1'b0;
penable_o <= 1'b0;
end
else if(cur_state == SETUP)begin
psel_o <= 1'b1;
penable_o <= 1'b0;
paddr_o <= cmd_i[wCMD-wRW-1:wDATA];
//-- read
if(cmd_i[wCMD-1] == RD)begin
pwrite_o <= 1'b0;
end
//-- write
else if(cmd_i[wCMD-1] == WR) begin
pwrite_o <= 1'b1;
pwdata_o <= cmd_i[wDATA-1:0];
end
end
else if(cur_state == ACCESS)begin
penable_o <= 1'b1;
end
end
endmodule
2.2 Testbench设计
testbench里用上了原文本来注释掉的task,使得设计更为简单,task里小小修改了 pready_i 的激励,使得只需2个周期就能发送一个数据。整个testbench发送了2读2写,然后空闲2个周期,再发送2读2写。
`timescale 1ns/1ps //时间精度
`define Clock 20 //时钟周期
module apb_tb;
//========================< parameter >=====================================
parameter WR = 1'b1 ;
parameter RD = 1'b0 ;
//---------------------------------------------------
parameter wRW = 1 ;
parameter wADDR = 16 ;
parameter wDATA = 32 ;
parameter wCMD = wRW + wADDR + wDATA ;
//========================< signal >========================================
reg pclk_i ;
reg prst_n_i ;
//---------------------------------------------------
reg [wCMD-1:0] cmd_i ;
reg cmd_vld_i ;
//---------------------------------------------------
wire [wADDR-1:0] paddr_o ;
wire pwrite_o ;
wire psel_o ;
wire penable_o ;
wire [wDATA-1:0] pwdata_o ;
reg [wDATA-1:0] prdata_i ;
reg pready_i ;
reg pslverr_i ;
//==========================================================================
//== instantiation
//==========================================================================
apb
#(
.WR (WR ),
.RD (RD ),
.wRW (wRW ),
.wADDR (wADDR ),
.wDATA (wDATA ),
.wCMD (wCMD )
)
u_apb
(
.pclk_i (pclk_i ),
.prst_n_i (prst_n_i ),
.cmd_i (cmd_i ),
.cmd_vld_i (cmd_vld_i ),
.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 )
);
//==========================================================================
//== clock
//==========================================================================
initial begin
pclk_i = 1;
forever
#(`Clock/2) pclk_i = ~pclk_i;
end
//==========================================================================
//== simulation
//==========================================================================
initial begin
cmd_rst;
cmd_wr(49'h1_1122_11223344);
cmd_wr(49'h1_3344_55667788);
cmd_rd(49'h0_5566_a1a2a3a4);
cmd_rd(49'h0_7788_b5b6b7b8);
#(`Clock*2);
cmd_wr(49'h1_1122_11223344);
cmd_wr(49'h1_3344_55667788);
cmd_rd(49'h0_5566_a1a2a3a4);
cmd_rd(49'h0_7788_b5b6b7b8);
#(`Clock*2);
$finish;
end
//==========================================================================
//== task
//==========================================================================
task cmd_rst;
begin
cmd_i = 0;
cmd_vld_i = 0;
prdata_i = 0;
pready_i = 1;
pslverr_i = 0;
prst_n_i = 0; #(`Clock);
prst_n_i = 1;
end
endtask
task cmd_wr;
input [wCMD-1:0] data;
begin
cmd_i = data;
cmd_vld_i = 1;
#(`Clock) cmd_vld_i = 0;
pready_i = 0;
#(`Clock) pready_i = 1;
end
endtask
task cmd_rd;
input [wCMD-1:0] data;
begin
cmd_i = data;
cmd_vld_i = 1;
#(`Clock) cmd_vld_i = 0;
pready_i = 0;
#(`Clock) pready_i = 1;
prdata_i = cmd_i[wDATA-1:0];
end
endtask
//==========================================================================
//== fsdb
//==========================================================================
initial begin
$fsdbDumpfile("tb_top.fsdb");
$fsdbDumpvars;
$fsdbDumpMDA;
end
endmodule
3 EDA环境
3.1 EDA环境设置
EDA虚拟机直接用的【芯王国】的 IC_EDA_lite虚拟机(精简版本),安装后直接能用,出处:https://blog.csdn.net/weixin_40377195/article/details/124899571
根据自己习惯,我对.bashrc文件进行了小小修改,增加了terminal颜色和一些alias文件,如下所示:
最终实现的效果如下所示:
3.2 Win与Linux文件共享
有些代码是在windows下写的,需要传到Linux里。或者Linux里写好的,需要传到Windows下,可以用这个方法:
(1)windows下找个喜欢的位置新建文件夹,命名按自己喜好即可。
(2)点击VMware的菜单栏:虚拟机 -- 设置。
(3)点击选项--共享文件夹,设置为总是启用,修改文件夹位置,选至刚刚创建的文件夹即可。
(4)Linux中该文件夹的路径如下所示。我在设置时选择的文件夹名称为ExShare,如果你选的是别的名称,那这里会相应的改变。
3.3 makefile
makefile文件没有用博主【四人独行】的版本,因为没跑通......于是拿着EDA虚拟机自带的例程里的makefile改了下。由于这次实验比较简单,就2个.v文件,我不想新增一个filelist文件,于是直接在makefile第一行指明文件路径。如下所示:
filelist = ./*.v
#----------------------------------------------------------------------------------------------------
all: clean vcs verdi
#----------------------------------------------------------------------------------------------------
vcs:
vcs \
${filelist} \
-timescale=1ns/1ps \
-full64 -R +vc +v2k -sverilog -debug_access+all \
|tee vcs.log
#----------------------------------------------------------------------------------------------------
verdi:
verdi ${filelist} -ssf ./*.fsdb &
clean:
rm -rf *~ core csrc simv* vc_hdrs.h ucli.key urg* *.log novas.* *.fsdb* verdiLog 64* DVEfiles *.vpd
该makefile文件在执行 make 后,会自动执行 clean、vcs、verdi。如果不想自动执行verdi,可以把 all:后的verdi字去掉,想执行了就再在terminal敲 make verdi。
3.4 EDA仿真
整个实验就用到了如下3个文件:
然后在terminal执行 make,根据上面的makefile文件,系统就自动调用vcs编译和仿真,之后自动打开verdi并加载fsdb文件了。如果编译仿真出错,terminal中会显示错误原因,有vcs的log文件可以查看。同时因为没有生成fsdb文件,verdi打开也无法正常加载波形,也会报错。如果一切正常,verdi的波形显示如下:
蓝色部分是输入的激励,黄色部分是用来看写传输,粉色部分是用来看读传输。从波形中可以看出,两次的两写两读和APB数据手册中描述的一致。
整个实验结束后,文件夹中多了很多相关文件,看起来很乱,可以执行一下 make clean ,将这些文件删除,回到最初的样子。也可以不管,反正下次执行 make 时,第一步就会自动执行一下 clean。
参考资料:
[1] 芯王国:https://blog.csdn.net/weixin_40377195/article/details/124899571
[2] 四人独行:https://zhuanlan.zhihu.com/p/607964532
标签:pready,cmd,总线,vld,reg,wDATA,APB,parameter,AMBA From: https://www.cnblogs.com/xianyufpga/p/17279235.html