APB总线计数器
基本知识
整体结构
从设备的地址空间
每个从设备的地址空间如下:
Slave0: 0x0000_0000 ~ 0x0000_00ff;
Slave1: 0x0000_0100 ~ 0x0000_01ff;
Slave2: 0x0000_0200 ~ 0x0000_02ff;
Slave3: 0x0000_0300 ~ 0x0000_03ff;
一次性传播多个数据
令从设备地址空间的下边界为其地址的基址,假设每个从设备中有可访问APB寄存器16个,位宽均为32比特,16个寄存器的访问地址计算方式为 基址 + 寄存器编号左移2位(byte 偏移)
从设备得读写时序
程序编写
计数器部分
//时钟信号定义
reg[7:0] data_H;//时信号
reg[7:0] data_M;//分信号
reg[7:0] data_S;//秒信号
//计数
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)begin //异步复位
data_S<=8'd0;
data_H<=8'd0;
data_M<=8'd0;
end
else if(softreset==1'd1)begin //软件高电平复位
data_S<=8'd0;
data_H<=8'd0;
data_M<=8'd0;
end
else if(pause==1'd0)begin //暂停
data_S<=data_S;
data_H<=data_H;
data_M<=data_M;
end
else if(pause==1'd1)
begin
data_S<=data_S+8'd1;
if(data_S==8'd59)begin //计数逢60进1
data_S<=8'd0;
data_M<=data_M+1;
end
if(data_M==8'd59 && data_S==8'd59 )begin //计数逢60进1 到59‘59时候,分清0 时加1
data_M<=8'd0;
data_H<=data_H+1;
end
if (data_H==8'd23 && data_M==8'd59 && data_S==8'd59) //计数到23‘59’59 全体清0
data_H<=8'd0;
end
end
///数据组合输出
always@(posedge clk or negedge rst_n) //和计数输出相差一个时钟
begin
if(!rst_n)
time_all<=32'd0;
else
time_all<={8'h00,data_H,data_M,data_S};
end
APB桥部分
从设备选择
根据主机发送的地址来选择从设备,总共有四个
每个从设备的地址空间如下:
Slave0: 0x0000_0000 ~ 0x0000_00ff;
Slave1: 0x0000_0100 ~ 0x0000_01ff;
Slave2: 0x0000_0200 ~ 0x0000_02ff;
Slave3: 0x0000_0300 ~ 0x0000_03ff;
从地址中我们可以看出只需要判断地址的高24位,就可以看出选择了那个从设备。
/* 从设备的地址
S0: 0x0000_0000 ~ 0x0000_00ff;
S1: 0x0000_0100 ~ 0x0000_01ff;
S2: 0x0000_0200 ~ 0x0000_02ff;
S3: 0x0000_0300 ~ 0x0000_03ff;
*/
reg [1:0]APB_State;
reg Msel0;
reg Msel1;
reg Msel2;
reg Msel3;
reg [23:0]addr_d;
always @(*) begin
addr_d = Paddr[31:8];
Msel0 = 1'b0;
Msel1 = 1'b0;
Msel2 = 1'b0;
Msel3 = 1'b0;
case (addr_d)
24'h0000_00:Msel0 = 1'b1;
24'h0000_01:Msel1 = 1'b1;
24'h0000_02:Msel2 = 1'b1;
24'h0000_03:Msel3 = 1'b1;
default: begin
Msel0 = 1'b0;
Msel1 = 1'b0;
Msel2 = 1'b0;
Msel3 = 1'b0;
end
endcase
end
状态机部分
桥部分得功能:主机通过桥向从机传输数据
在桥部分我根据APB总线的读写时序写了一个状态机器。
总线状态 | Pwrite(写/读) | Psel | Pen |
---|---|---|---|
IDLE | 0 | 0 | 0 |
Setup | 1/0 | 1 | 0 |
Enable | 1/0 | 1 | 1 |
从状态图中可以看出,当有传输的时候,只会在2,3状态间跳转,此时就需要主机给一个传输使能信号,即下面的master_en信号,当为1时候在2,3状态跳转。
/* APB FSM */
always @(posedge PCLK or negedge Prst) begin
if (!Prst) begin
//APB_Slave
APB_State <= `Idle;
Pen <= 1'b0;
Psel0 <= 1'd0;
Psel1 <= 1'd0;
Psel2 <= 1'd0;
Psel3 <= 1'd0;
end
else begin
case (APB_State)
`Idle: begin
APB_State <= `Setup;
Pen <= 1'b0;
Psel0 <= Msel0;//通过地址选择从设备
Psel1 <= Msel1;
Psel2 <= Msel2;
Psel3 <= Msel3;
end
`Setup:
begin
APB_State <= `Access;
Pen <= 1'b0;
end
`Access:
begin
if(master_en==1)begin //1表示一直在传输,
APB_State <= `Setup;
Pen <= 1'b1;
end
else
APB_State <= `Idle;
end
default: APB_State <= `Idle;
endcase
end
end
APB从机部分
在从机部分,我们只需要关注对从机的读写。根据实验要求,首先是将暂停信号和高电平复位信号写入,并且将计数信号读出。
为什么要分频
首先我们从APB的时序中可以看出,APB写入一个数据需要2周期,读出一个数据也需要2周期,所以我们将每个计数信号完整的读出,在一个计数时钟周期中,我们就需要对APB进行一次读和写,所以我写了一个4分频,其中4分频信号传给计数器。
module apb_Slave(PSELx,Pen,Pwrite,Prst,PCLK,Pwdata,Prdata);
input PSELx; //从设备选择
input Pen; //使能
input Pwrite; //0为读,1为写
input Prst; //Reset,0为复位
input PCLK; //时钟,为HCLK的二分频
input [31:0] Pwdata; //写数据
output reg [31:0] Prdata; //读数据
reg [31:0] Slave_reg; //定义16个32位寄存器
wire [31:0]time_all;
/* 从设备的地址
S0: 0x0000_0000 ~ 0x0000_00ff;
S1: 0x0000_0100 ~ 0x0000_01ff;
S2: 0x0000_0200 ~ 0x0000_02ff;
S3: 0x0000_0300 ~ 0x0000_03ff;
*/
wire pause=Slave_reg[0];
wire softreset=(Slave_reg[1]==1)?1'd1:1'd0;
always @(posedge PCLK or negedge Prst ) begin
if (!Prst)begin
Prdata <= 32'h0000_0000;
Slave_reg<=32'd0;
end
else begin
if (PSELx & Pen) begin
if (Pwrite) Slave_reg <= Pwdata; //此部分是根据读写时序写的 写入的是暂停和高电平复位信号,在tb文件中体现
end
if (PSELx & (~Pen)) begin //为了让信号提前传输
if (!Pwrite) Prdata <= time_all;//读出的是时钟计数信号
end
end
end
reg Sclk;
reg cnt;
//4分频
always@(posedge PCLK or negedge Prst)
begin
if(!Prst)
begin
cnt<=1'd0;
Sclk<=1'd0;
end
else if(cnt==1)begin
Sclk<=~Sclk;
cnt<=1'd0;
end
else begin
cnt<=cnt+1'd1;
end
end
cnt cnt_inst(//将计数器例化
.clk(Sclk) ,
.rst_n(Prst) ,
.pause(pause) ,
.softreset(softreset) ,//高电平复位
.time_all(time_all) //时信号
);
endmodule
测试文件
module apb_top_tb();
reg clk;
reg rst_n;
reg Pwrite;
reg [31:0] Paddr;
wire [31:0]Prdata;
reg [31:0]Pwdata1;
reg master_en;
//reg Pwdata;
//时钟、复位
initial
begin
clk = 1'b1;
rst_n <= 1'b0;
master_en<=1'd1; //表示一直传输
Pwdata1<=32'd1; //写入的暂停复位信号
Paddr<=32'h0000_0100; //选择从设备
#200
rst_n <= 1'b1;
master_en<=1'd1;
Pwdata1<=32'd1;
Paddr<=32'h0000_0000;//选择的是设备0
#17280000
master_en<=1'd1;
Pwdata1<=32'd0;
#20000000
Pwdata1<=32'd3;
master_en<=1'd1;
end
always //2周期转换一次读写
begin
Pwrite<=0;
#40
Pwrite<=1;
#40
Pwrite<=0;
end
always #10 clk = ~clk ; //50MHz系统时钟
APB_top APB_top_inst(
.clk(clk) ,
.rst_n(rst_n) ,
.master_en(master_en) ,
.Pwrite(Pwrite), //0为读,1为写
.Paddr(Paddr), //地址
.Pwdata(Pwdata1),
.Prdata(Prdata)
);
endmodule
最后说明
代码可以自行扩充,目前只使用了一个从设备,APB总线最多可以挂4个从设备。 但是需要注意每个时钟周期只能激活其中一个从设备。
文件云盘地址
链接:https://pan.baidu.com/s/1I7j4qVXuoUxG063UT6VUnQ
提取码:fr46
–来自百度网盘超级会员V6的分享