1. 点对点连接方式的接口说明
在点对点连接方式方式中,分别只有一个主设备和一个从设备。
所有信号都是高电平有效。
- CLK_I:时钟信号,由外部输入
- RST_I:复位信号,由外部输入
- DAT_O / DAT_O:数据总线,一对主从设备间最多存在两条数据总线
- ADR_O / ADR_O:地址总线,地址由主设备传送给从设备
- WE_O / WE_O:写使能信号,代表主设备对从设备当前进行的操作,1为写,0为读
- SEL_O / SEL_O:数据总线选择信号,也是字节选择信号,以Byte为单位,SEL(4’b 1001)代表最高和最低字节有效
- CYC_O / CYC_O:总线周期信号,CYC有效代表一个主设备请求总线使用权或正在使用总线,但不一定正在进行总线操作(是否正在进行总线操作取决于选通信号STB是否有效)。只有在CYC信号有效的前提下,wishbone主从设备之间的其他信号才有意义。CYC信号在一次总线操作过程中必须持续有效。若一次读写操作可能需要多要时钟周期,那么CYC信号得在多个时钟周期内持续有效
- STB_O / STB_O:选通信号,选通信号有效代表主设备发起一次总线操作
- ACK_O / ACK_O:主从设备间的操作成功结束信号
- TANG_O / TANG_O:标签信号,用户可以利用标签信号传递自定义信息
2. 单次读操作
- 在时钟上升沿0,主设备将地址信号ADR_O、SEL_O放到总线上,将WE_O置0代表读操作,STB_O、CYC_O置高表示一次总线操作开始
- 在时钟上升沿1到来之前,从设备检测主设备发起的操作,将数据放到主设备的输入接口DAT_I,同时将主设备ACK_I置高,作为对主设备STB_O的响应。从设备可以在设置ACK_I有效之前,插入任意数量的等待操作
- 在时钟上升沿1,主设备发现ACK_I为高,采样DAT_I信号作为读取到的数据,并将CYC_O和STB_O置低,表示操作完成,从设备检测到STB_O为低,将主设备的ACK_I置低,单次读操作完成
3. 单次写操作
- 在时钟上升沿0,主设备将地址信号ADR_O、DAT_O、SEL_O放到总线上,将WE_O置0代表写操作,STB_O、CYC_O置高表示一次总线操作开始
- 在时钟上升沿1到来之前,从设备检测主设备发起的操作,锁存DAT_O的数据,同时将主设备的输入ACK_I置高,作为对从设备STB_O的响应。从设备可以在设置ACK_I有效之前,插入任意数量的等待操作
- 在时钟上升沿1,主设备发现ACK_I为高,将CYC_O和STB_O置低,表示操作完成,从设备检测到STB_O为低,将主设备的ACK_I置低,单次写操作完成
4. 补充
4.1. SEL字节选择信号的说明
-
类比加载/存储指令的字节选择信号
OpenMIPS数据总线、地址总线都是32bit,是大端模式,使用4个8位存储器代替一个32位存储器。
读操作时,从4个8位存储器各读取一个字节,组合位32位的数据输出;写操作时,依据sel的值,修改其中特定存储器对应的字节。32位地址的最低两位不需要使用。
例如,SW指令是读取地址n处的字,实际就是从4个8位存储器的地址n/4处各读取一个字节,组合起来就是地址n处的字。
-
通过wishbone进行读操作时,主设备通过ADR_O送出32位地址,从设备将ADR_O最低两位置0,作为目标地址,即字节对齐后的首地址,给出对应的32位数据,输入到主设备的DAT_I。
lb $1, 0x6($0) # 字节加载指令,加载[0x6]处的字节并作符号扩展,$1 = 0x0000 0056
OpenMIPS指令集下,该条指令对应的32位指令码为“80010006”
- 在EX模块,mem_addr = reg1 + { {16{inst_i[15]}}, inst_i[15:0] },即0x0000_0006
- 在MEM模块,分析mem_addr[1:0]为2’b10,所以mem_sel_o <= 4’b0010,正好对应数据存储器中的data_mem1,
wdata_o <= { {24{mem_data_i[15]}}, mem_data_i[15:8] },正好是[0x6]地址对应字节的符号扩展
4.2. wishbone_bus总线接口的实现
采用状态机实现总线接口模块
模块端口
`define WB_IDLE 2'b00
`define WB_BUSY 2'b01
`define WB_WAIT_FOR_STALL 2'b11
`include "defines.v"
module wishbone_bus_if (
input wire clk,
input wire rst,
// 来自CTRL模块
input wire stall_i, // 流水线暂停信号
input wire flush_i, // 流水线清除信号
// CPU侧的接口
input wire cpu_ce_i, // 来自处理器的访问请求信号
input wire [`RegBus] cpu_data_i, // 来自处理器的数据
input wire [`RegBus] cpu_addr_i, // 来自处理器的地址信号
input wire cpu_we_i, // 来自处理器的写操作信号
input wire [3:0] cpu_sel_i, // 来自处理器的字节选择信号
output reg [`RegBus] cpu_data_o, // 输出到处理器的数据
// Wishbone侧的接口
input wire [`RegBus] wishbone_data_i, // wishbone总线输入的数据
input wire wishbone_ack_i, // wishbone总线的响应信号
output reg [`RegBus] wishbone_addr_o, // wishbone总线输出的地址
output reg [`RegBus] wishbone_data_o, // wishbone总线输出的数据
output reg wishbone_we_o, // wishbone总线写使能信号
output reg [3:0] wishbone_sel_o, // wishbone总线字节选择信号
output reg wishbone_stb_o, // wishbone总线的选通信号
output reg wishbone_cyc_o, // wishbone总线周期信号
output reg stallreq // 请求流水线暂停信号
);
reg [1:0] wishbone_state; // 保存wishbone总线接口模块的状态
reg [`RegBus] rd_buf; // 寄存通过wishbone总线访问到的数据
// *********************************************************************************************
// ******************** 第一阶段:控制状态转化的时序电路 ***************************************
// *********************************************************************************************
always @ (posedge clk) begin
if (rst == `RstEnable) begin
wishbone_state <= `WB_IDLE; // 状态复位
wishbone_addr_o <= `ZeroWord;
wishbone_data_o <= `ZeroWord;
wishbone_we_o <= `WriteDisable;
wishbone_sel_o <= 4'b0000;
wishbone_stb_o <= 1'b0;
wishbone_cyc_o <= 1'b0;
rd_buf <= `ZeroWord;
end else begin
case (wishbone_state)
`WB_IDLE: begin
// 处理器发出访问请求,流水线不处于清除状态,总线状态进入BUSY,开始访问总线
if ( (cpu_ce_i == 1'b1) && (flush_i == `False_v) ) begin
wishbone_stb_o <= 1'b1;
wishbone_cyc_o <= 1'b1;
wishbone_addr_o <= cpu_addr_i;
wishbone_data_o <= cpu_data_i;
wishbone_we_o <= cpu_we_i;
wishbone_sel_o <= cpu_sel_i;
wishbone_state <= `WB_BUSY;
rd_buf <= `ZeroWord;
end
end
`WB_BUSY: begin
// 收到总线的响应信号
if (wishbone_ack_i == 1'b1) begin
wishbone_stb_o <= 1'b0; // 主从设备间的操作结束信号
wishbone_cyc_o <= 1'b0;
wishbone_addr_o <= `ZeroWord;
wishbone_data_o <= `ZeroWord;
wishbone_we_o <= `WriteDisable;
wishbone_sel_o <= 4'b0000;
wishbone_state <= `WB_IDLE;
if (cpu_we_i == `WriteDisable) begin // 读使能有效
rd_buf <= wishbone_data_i;
end
if (flush_i != 6'b000000) begin
// 暂停流水线,进入WB_WAIT_FOR_STALL
wishbone_state <= `WB_WAIT_FOR_STALL;
end
end else if (flush_i == `True_v) begin
// 发生异常,清除流水线,并撤销此次总线访问
wishbone_stb_o <= 1'b0;
wishbone_cyc_o <= 1'b0;
wishbone_addr_o <= `ZeroWord;
wishbone_data_o <= `ZeroWord;
wishbone_we_o <= `WriteDisable;
wishbone_sel_o <= 4'b0000;
wishbone_state <= `WB_IDLE;
rd_buf <= `ZeroWord;
end
end
`WB_WAIT_FOR_STALL: begin
if (stall_i == 6'b000000) begin
// 流水线暂停取消
wishbone_state <= `WB_IDLE;
end
end
default: begin
end
endcase
end
end
// *********************************************************************************************
// ******************** 第二阶段:给处理器接口信号赋值的组合电路 *******************************
// *********************************************************************************************
always @ (*) begin
if (rst == `RstEnable) begin
stallreq <= `NoStop;
cpu_data_o <= `ZeroWord;
end else begin
stallreq <= `NoStop;
case (wishbone_state)
`WB_IDLE: begin
if ( (cpu_ce_i == 1'b1) && (flush_i == `False_v) ) begin
stallreq <= `Stop; // 流水线开启暂停
cpu_data_o <= `ZeroWord; // 初始状态读数据为0
end
end
`WB_BUSY: begin
if (wishbone_ack_i == 1'b1) begin // 收到总线响应信号ack
stallreq <= `NoStop; // 流水线结束暂停
if(wishbone_we_o == `WriteDisable) begin // 读使能
cpu_data_o <= wishbone_data_i;
end else begin
cpu_data_o <= `ZeroWord;
end
end else begin // 没有收到总线相应信号ack
stallreq <= `Stop; // 流水线保持暂停
cpu_data_o <= `ZeroWord;
end
end
`WB_WAIT_FOR_STALL: begin
stallreq <= `NoStop;
cpu_data_o <= rd_buf;
end
default: begin
end
endcase
end
end
endmodule