目录
这是本人在实际工作中遇到的设计任务,借此研究状态机设计的优化策略
4. 逻辑优化——rx_fifo_rd_en与tx_fifo_wr_en的 流水协同
读使能和写使能的本质上不是流水,其实还是在GET_HEAD、GET_DATA和GET_DATA_NUM中的小状态机,即先读出再写入。
如下图所示,红色部分表示读出,绿色部分表示写入

可以借鉴AHB的原理,address phase给出控制信息,下一拍data phase给出数据,同时给出下次写的address phase的控制信息。

对应本文内容其实就是向TX_FIFO写入数据的同时向RX_FIFO读出新的数据,思路就变成如下图。

所以说读写流水协同本质上还是状态机,只不过是
1. 任意时刻不同信号从属的状态机不同。2. 不同状态机内部的状态转移条件相互影响。
例如本文中读相关信号和写相关信号做的事情就不同,属于不同的状态机。而流水协同就需要保证旧数据成功写入并且新数据成功读出,才能继续写or读
下面详细介绍如何实现rx_fifo_rd_en与tx_fifo_wr_en的 流水协同
首先还是状态机的思路,上图中已经给出读信号和写信号的状态机
4.1. GET_HEAD
如何开始流水呢?我们需要先读出一包要被写入的数据,那就是帧头,最快的时序如下

这里有一个细节,就是读出帧头之后rx_fifo_rd_en还是高,也就是说GET_DATA_NUM的第一拍就可能会读出数据个数,因此更新流水状态机

4.2. GET_DATA_NUM
在该状态下确定能够读出数据之后rx_fifo_rd_en拉低,同时在GET_HEAD的最后一拍也拉高tx_fifo_wr_en写入数据,只有当帧头成功写入且数据长度成功读出才转移到下一个状态。

注意读出数据有效标志是rx_fifo_rdata_val_dly会一直拉高,直到在下一个状态给到tx_fifo_wdata。从时序图中可以看到,当写入成功后tx_fifo_wr_en会拉低,所以读出新数据、写完旧数据的标志是rx_fifo_rdata_val_dly && !tx_fifo_wr_en

4.3. GET_DATA
之后就可以参照GET_DATA_NUM的时序流水起来,如下图读写同时进行

注意流水状态机的状态转移条件是
rx_fifo_rdata_val_dly && !tx_fifo_wr_en,所以必须待tx_fifo_wr_en拉低才能开始新一轮流水。
如果要更快一点,在写恰好完成or读恰好完成的那一拍开始新一轮流水,那么就要考虑是写先完成等待读、还是读先完成等待写还是同时完成,三个条件相或就是流水状态转移条件,即就为(!tx_fifo_wr_en && rx_fifo_rdata_val_dly) || (tx_fifo_wr_en && !tx_fifo_full &&rx_fifo_rdata_val_dly)
流水状态机就为

RD_CHECK_SUM与WR_CHECK_SUM
这里要讨论的就是边界条件了
如下图所示,即使满足流水状态转移条件当data_num == 3'd0时rx_fifo_rd_en也不再拉高了。

那么GET_DATA转回IDLE的条件是data_num == 3'd0 && tx_fifo_wr_en && !tx_fifo_full吗?如下图所示,如果tx_fifo_wr_en一直有效延长至data_num == 3'd0的时刻,那么就会直接跳至IDLE状态,而此时校验和还未写入!rx_fifo_rdata_val_dly将一直为高!
所以转回IDLE的条件应该是RX_FIFO没读、TX_FIFO写完,即data_num == 3'd0 && !rx_fifo_rdata_val_dly && !tx_fifo_wr_en

因此最终得到的流水状态机就为

4.4. 代码
逻辑设计优化完就可以写代码了,注意第一遍写代码的逻辑要足够清晰严谨,if、elseif要写全保证功能正确,然后仿真。确定没问题了,再去优化,然后再仿真确定优化的没毛病。
module frame_combination(
input clk,
input rstn,
input [15:0] rx_fifo_rdata,
input rx_fifo_rdata_val,
input rx_fifo_empty,
output rx_fifo_rd_en,
output [15:0] tx_fifo_wdata,
output tx_fifo_wr_en,
input tx_fifo_full,
input en_trans,
input [15:0] work_mode,
input [15:0] compensation_num
);
localparam IDLE = 2'b00;
localparam GET_HEAD = 2'b01;
localparam GET_DATA_NUM = 2'b10;
localparam GET_DATA = 2'b11;
reg [1:0] cur_state;
reg [1:0] nxt_state;
reg [15:0] data_num;
reg [7:0] check_sum;
reg rx_fifo_rd_en_r;
reg rx_fifo_rdata_val_dly;
reg [15:0] tx_fifo_wdata_r;
reg tx_fifo_wr_en_r;
//wire tx_fifo_wr_done;
wire data_num_zero;
//assign tx_fifo_wr_done = tx_fifo_wr_en && !tx_fifo_full;
assign data_num_zero = ~(|data_num);
always@(posedge clk or negedge rstn) begin
if(!rstn)
cur_state <= IDLE;
else
cur_state <= nxt_state;
end
always@(*) begin
case(cur_state)
IDLE:
if(en_trans)
nxt_state = GET_HEAD;
else
nxt_state = IDLE;
GET_HEAD:
if(rx_fifo_rdata == 16'hABCD && rx_fifo_rdata_val)
nxt_state = GET_DATA_NUM;
else
nxt_state = GET_HEAD;
GET_DATA_NUM:
if(rx_fifo_rdata_val_dly && !tx_fifo_wr_en)
nxt_state = GET_DATA;
else
nxt_state = GET_DATA_NUM;
GET_DATA:
if(data_num_zero && tx_fifo_wr_en && !tx_fifo_full)
nxt_state = IDLE;
else
nxt_state = GET_DATA;
default: nxt_state = IDLE;
endcase
end
always@(posedge clk or negedge rstn) begin
if(!rstn)
data_num <= 16'd0;
else if(cur_state == GET_DATA_NUM) begin
if(rx_fifo_rdata_val_dly && !tx_fifo_wr_en)
data_num <= rx_fifo_rdata + 16'd1;
end
else if(cur_state == GET_DATA) begin
if(rx_fifo_rd_en_r && !rx_fifo_empty)
data_num <= data_num - 16'd1;
end
end
always@(posedge clk or negedge rstn) begin
if(!rstn)
check_sum <= 8'd0;
else if(cur_state == GET_HEAD)
check_sum <= 8'd0;
else if(cur_state == GET_DATA_NUM) begin
if(rx_fifo_rdata_val_dly && !tx_fifo_wr_en)
check_sum <= check_sum + rx_fifo_rdata[7:0];
end
else if(cur_state == GET_DATA) begin
if(rx_fifo_rdata_val_dly && !tx_fifo_wr_en) begin
if(data_num == 16'd4)
check_sum <= check_sum + work_mode;
else if(data_num == 16'd2)
check_sum <= check_sum + compensation_num;
else if(data_num_zero)
check_sum <= check_sum;
else
check_sum <=

本文详细探讨了在FIFO读写操作中实现读写流水协同的策略,通过状态机设计优化,确保数据正确传输。在GET_HEAD、GET_DATA_NUM和GET_DATA状态下,实现了读写同步,并对代码进行了优化,减少了资源消耗。此外,针对可能出现的时序违规问题,提出了插入触发器的解决方案,以保证设计的正确性和稳定性。
最低0.47元/天 解锁文章
1092

被折叠的 条评论
为什么被折叠?



