MCDT是MCDF的缩减版,MCDT比MCDF(multi-channel data formatter)少了寄存器配置模块(Registers)和整型打包数据模块(Formater)。
1. Slave_fifo
此模块是
Slave部分与FIFO部分结合的模块,一共分别有3个Slave和3个FIFO,对应3个发送激励的chnl模块。
module slave_fifo (
input clk_i, // Clock input
input rstn_i, // Reset signal
input [31:0] chx_data_i, // 来自外部的数据输入,这里即是来自Chnl发送的激励数据输入。
input a2sx_ack_i, // Read ack ---->来自Aribiter的反馈输入,Aribiter要响应并从你这里读FIFO中的数据,ack意思是我(Aribiter)是否现在可以行动起来从你FIFO拿数据了。
input chx_valid_i, // Data is valid From outside ---->来自外部的控制信号,告诉Slave,我(Chnl)此次发送的数据data做不做数。
output reg [31:0] slvx_data_o, // Data Output ---->向Arbiter输出的数据,x表示从哪一个slave来都有可能。
output [5:0] slvx_margin_o, // Data margin ---->告诉外部/寄存器模块,我现在还剩余多少空位,你还可以继续发送数据。
output reg chx_ready_o, // Ready to accept data ---->告诉外部(Chnl),我准备好了,有空位,你可以发送数据进来。
output reg slvx_val_o, // Output data is valid ---->告诉下游Arbiter,我发出的数据是否有效,可以类比chx_valid_i
output reg slvx_req_o // Request to send data ---->告诉下游Arbiter,我需要发送数据出去了。
);
//------------------------------Internal variables-------------------//
reg [5:0] wr_pointer_r; // 内部变量,表示FIFO的写操作指针。
reg [5:0] rd_pointer_r; //内部变量,表示FIFO的读操作指针。
reg [31:0] mem [0:31]; // 一个[31:0]宽,32bit深的FIFO。
//-----------------------------Variable assignments------------------//
wire full_s, empty_s, rd_en_s ; // 由我上一篇文章写的,这是代表信号线,可以传递指示状态。
wire [5:0] data_cnt_s; // 一捆线,由6根信号线组成,可以指示FIFO当前的余量状态。
assign full_s = ({~wr_pointer_r[5], wr_pointer_r[4:0]}==rd_pointer_r); // 满状态的标志,逻辑是写指针的最高位取反,写指针最高位达到1了肯定就满了,因为我们的FIFO是32位深的,2^5就能表示32了(`b100_000),在达到最大值32之前,写指针的最高位都是0。因此可以通过最高位取反与读指针做比较来判断当前是否是满状态。
assign empty_s = (wr_pointer_r == rd_pointer_r); // 空状态,其实空状态和满状态,读写指针都相等,但在上一行已经通过最高位可以判断此时是空还是满。
assign data_cnt_s = (6'd32 - (wr_pointer_r - rd_pointer_r)); // 简单的数学计算,剩余空位 = 写入数 - 读出数。
assign slvx_margin_o = data_cnt_s; // 将剩余空位信号,驱动到margin信号上。
assign rd_en_s = a2sx_ack_i; // 将Arbiter反馈的可读信号线,驱动到read_enable信号线上,这根信号线有用。
always @ (*) //ready signal
begin
if (!full_s) chx_ready_o = 1'b1; // 如果满信号为0,说明还没满,把ready信号线拉高,说明准备可以接收数据了。
else chx_ready_o = 1'b0; // 如果满信号为1,拉低ready。
end
always @ (*) //reset signal
begin
if (!rstn_i) slvx_req_o = 1'b0; // 复位信号,低有效,意思rstn_i拉低,说明我们要进行重制,把req请求信号拉低,代表我们不往下游发送数据。
else if (!empty_s) slvx_req_o = 1'b1; // 不复位,且empty信号为0,代表我现在FIFO不为空,拉高req,表明我需要把我FIFO的数据排空到下游Arbiter。
else slvx_req_o = 1'b0; // 不复位,但FIFO为空,发也没得发了,拉低req。
end
// 写指针的运作原理
always @ (posedge clk_i or negedge rstn_i) // 时钟上升沿或重置信号下降沿触发
begin :
if (!rstn_i) begin // 复位,写指针清零,同时也代表着我FIFO里被清零了。
wr_pointer_r <= 6'b0000;
end else
if (chx_valid_i && chx_ready_o) begin // valid信号拉高,且ready信号拉高,不仅我Chnl准备好了,我的Slave和FIFO也准备好了,开始写入数据到FIFO。(具体写操作在下面,此处仅对写指针做操作)
wr_pointer_r <= wr_pointer_r + 6'b0001; // 每写一个数据,指针+1
end
end
// 读指针加法
always @ (posedge clk_i or negedge rstn_i)
begin : READ_POINTER
if (!rstn_i) begin
rd_pointer_r <= 6'b0000;
end else
if (rd_en_s && (!empty_s)) begin // 当下游Arbiter可以从FIFO读数据了,并且我的FIFO不为空的时候,开始读取数据。(具体读操作在下面,此处只对读指针做+1操作)
rd_pointer_r <= rd_pointer_r + 6'b0001;
end
end
// 数据输出有效信号的操作(slave-FIFO给Arbiter的valid信号)
always @ (posedge clk_i or negedge rstn_i)
begin
if (!rstn_i) slvx_val_o <= 1'b0;
else if (rd_en_s && (!empty_s)) // 下游的 Arbiter可以读数据,并且不为空,此处其实与上一块代码重合,可以优化。
slvx_val_o <= 1'b1; // 拉起valid
else slvx_val_o <= 1'b0; // 拉低valid
end
// FIFO读数据
always @ (posedge clk_i )
begin : READ_DATA
if (rstn_i && rd_en_s && (!empty_s)) begin
slvx_data_o <= mem[rd_pointer_r[4:0]]; // 将FIFO中读指针对应位置的数据拿出,赋给slvx_data_out
end
end
// FIFO写数据
always @ (posedge clk_i)
begin : MEM_WRITE
if (rstn_i && chx_valid_i && chx_ready_o) begin
mem[wr_pointer_r[4:0]] <= chx_data_i; // 将此时Chnl传递来的数据,写入到FIFO中写指针对应的位置。
end
end
2. Arbiter
module arbiter(
input clk_i,
input rstn_i,
//connect with slave port
input [31:0] slv0_data_i,
input [31:0] slv1_data_i,
input [31:0] slv2_data_i,
input slv0_req_i,
input slv1_req_i,
input slv2_req_i,
input slv0_val_i,
input slv1_val_i,
input slv2_val_i,
output a2s0_ack_o, // 输出到上游FIFO端
output a2s1_ack_o,
output a2s2_ack_o,
//Output of MCDT
output data_val_o, // 输出到下游外部
output [1:0] arb_id_o,
output [31:0] arb_data_o
);
reg data_val_r;
reg [1:0] arb_id_r;
reg [31:0] arb_data_r;
reg [2:0] c_state;
reg [2:0] n_state;
//--------------------------------use FSM to implete simple Round Robin Arbiter 轮询状态机
parameter IDLE = 3'b000,
GRANT0 = 3'b001,
GRANT1 = 3'b010,
GRANT2 = 3'b100;
always @ (posedge clk_i or negedge rstn_i)
begin
if (!rstn_i) c_state <= IDLE; // 初始状态为IDLE
else c_state <= n_state;
end
always @ (*)
begin
if (!rstn_i) n_state = IDLE; //default priority slv0 > slv1 > slv2
else
case (c_state)
IDLE : if (slv0_req_i) n_state = GRANT0;
else if (slv1_req_i) n_state = GRANT1;
else if (slv2_req_i) n_state = GRANT2;
else n_state = IDLE;
GRANT0 : if (slv1_req_i) n_state = GRANT1;
else if (slv2_req_i) n_state = GRANT2;
else if (slv0_req_i) n_state = GRANT0;
else n_state = IDLE;
GRANT1 : if (slv2_req_i) n_state = GRANT2;
else if (slv0_req_i) n_state = GRANT0;
else if (slv1_req_i) n_state = GRANT1;
else n_state = IDLE;
GRANT2 : if (slv0_req_i) n_state = GRANT0;
else if (slv1_req_i) n_state = GRANT1;
else if (slv2_req_i) n_state = GRANT2;
else n_state = IDLE;
default : n_state = IDLE;
endcase
end
轮询机制描述了状态转换,使得状态机无论进入到哪一种状态,都可以进入到下一个状态,不至于锁死。例如:当前为IDLE状态时,3个req信号都同时拉起了,一定是先进入GRANT0状态,进入GRANT0之后,即使req信号是同时拉起的状态,不会再进入GRANT0状态了,而是会进入下一个GRANT1状态。依次类推。
assign {a2s2_ack_o,a2s1_ack_o,a2s0_ack_o} = c_state; // 状态机映射到信号线
always @ (*)
begin
if (!rstn_i) begin
data_val_r = 1'b0;
arb_id_r = 2'b11;
arb_data_r = 32'hffff_ffff;
end
else
case ({slv2_val_i,slv1_val_i,slv0_val_i})
3'b001 : begin
data_val_r = slv0_val_i;
arb_id_r = 2'b00;
arb_data_r = slv0_data_i;
end
3'b010 : begin
data_val_r = slv1_val_i;
arb_id_r = 2'b01;
arb_data_r = slv1_data_i;
end
3'b100 : begin
data_val_r = slv2_val_i;
arb_id_r = 2'b10;
arb_data_r = slv2_data_i;
end
default : begin
data_val_r = 1'b0;
arb_id_r = 2'b11;
arb_data_r = 32'hffff_ffff;
end
endcase
end
assign data_val_o = data_val_r;
assign arb_data_o = arb_data_r;
assign arb_id_o = arb_id_r;
endmodule