思路和代码并不一定正确,仅仅是记录一次有意思的结构思考过程,如有疏漏错误,请不吝赐教。
一般而言生成的ram都是通过wenc/renc信号来控制读写,并且读使能后一拍读数据返回,当然,当ram深度较大时可能会延时。这次根据实际场景,ram本身是双口的一拍读取RAM。
用下面的代码来代替ram内部的代码:
module dual_port_RAM #(parameter DEPTH = 16, parameter WIDTH = 8)( input wclk , input wenc , input [$clog2(DEPTH)-1:0] waddr , input [WIDTH-1:0] wdata , input rclk , input renc , input [$clog2(DEPTH)-1:0] raddr , output reg [WIDTH-1:0] rdata ); reg [WIDTH-1:0] RAM_MEM [0:DEPTH-1]; always @(posedge wclk) begin if(wenc) RAM_MEM[waddr] <= wdata; end always @(posedge rclk) begin if(renc) rdata <= RAM_MEM[raddr]; end endmodule
我的需求是一个握手型的ram,通过握手信号来控制ram的读写,对应的接口如下:
module hand_dp_ram #( parameter DEPTH = 16, parameter WIDTH = 8 )( input clk, input rst_n, input wvalid, output wready, input [$clog2(DEPTH) -1:0]waddr, input [WIDTH -1:0]wdata, input arvalid, output arready, input [$clog2(DEPTH) -1:0]araddr, output rvalid, input rready, output [WIDTH -1:0]rdata );
Feature List:
- wvalid和wready同时有效时,将wdata写入到ram地址中,写入的数据在下一拍生效;
- arvalid和arready同时有效时,读取araddr对应ram数据,数据最早在下一拍读出;
- 只有当rvalid和rready同时有效时,rdata会被取出,否则rdata需要一直保持。
需求就是这么简单,那么接下来对实现进行分析。
对于dual_port_ram(实际上叫double_port_ram比较好,我理解的dual_port_ram是两个口都可以独立的读写),读取的逻辑时序图如下:
显然,arvalid&&arready有效时,应该发起renc信号进行读取,下一拍rvalid必然是为1。但问题在于,当下一拍数据被读出后,ready的状态是未知的,如果恰好rready为0,那么数据将会丢失。
由于之前没有写过这块代码,也没有深入研究过,因此只能凭借我自己的感觉规划了三种思路:
- 在确保下拍没有反压(即下一拍的ready一定为1)时才置高arready;
- 在ram后面跟pipe来保持数据;
- 当数据没有被取出时,连续发起renc,直到该地址的数据被读取;
我不确认有没有更好的办法,所以只能对自己的这三个思路进行分析。
第一个思路呢,如果想获取到下一拍rready的状态,那么分类一想就会发现,只有当上一拍没有renc且当拍的rready==1才能保证下一拍的rready==1。这样就会造成一个读取效率的问题即只能隔一拍读取一次,因此单纯的这样处理一定是有问题的。
第三个思路呢,我举得功能上应该是可以实现的,但是可能会导致rvalid有效但是没有握手过程中,rdata一直在变化,这不符合握手协议要求,因此暂时把这个思路放在了一边。
最后选定的是第二种思路,通过在ram后面增加一个pipe来进行数据保持。那么应该选择什么类型的pipe呢?需求上,希望rdata从ram中读出来了就尽快发出,在无法发出时保持在pipe中,因此必然是选择backword pipe。
于是,整体的结构就定下来了:dual_port_ram —— bw_pipe。
整体结构定下来后,就又回到了开始的问题,如何预知下一拍ram的出口一定可以接数据呢?经过一个很长时间的探索(一晚上+一早上),我发现这个问题就简化为了一句话:下一拍pipe_in_ready为1时,这拍的arready才能为1。
那么怎么预知下一拍pipe_in_ready为1呢?这不就是bw_pipe里的逻辑么?!
【芯片前端】保持代码手感——握手协议ready打拍时序优化
wire out_ready_en = data_in_valid || data_out_ready; wire out_ready_d = data_out_ready; wire out_ready_q; dffse #(.WIDTH(1), .VALUE(1'b1)) u_out_ready_dffse( .clk(clk), .rst_n(rst_n), .d(out_ready_d), .en(out_ready_en), .q(out_ready_q) ); assign data_in_ready = out_ready_q;
最后归纳出最后最后的逻辑,显然就是(data_out_ready == 1)|| (data_in_valid == 0 && data_in_ready == 1)。正好对着两个场景:
- 下游现在可以接,哪怕你现在pipe里有数,下一拍pipe里也一定是空的,至少保证下一拍的rdata能进pipe,或者直接被下游接走;
- 下游现在不能接,但是pipe是空的,且这一拍没有数据要被读到pipe里;
基于这个思路,最后读通道的代码组织形式就出来了,放一下完整的代码:
module hand_dp_ram #( parameter DEPTH = 16, parameter WIDTH = 8 )( input clk, input rst_n, input wvalid, output wready, input [$clog2(DEPTH) -1:0]waddr, input [WIDTH -1:0]wdata, input arvalid, output arready, input [$clog2(DEPTH) -1:0]araddr, output rvalid, input rready, output [WIDTH -1:0]rdata ); //*********************************************** // ram inst //*********************************************** wire ram_wenc; wire [$clog2(DEPTH) -1:0]ram_waddr; wire [WIDTH -1:0]ram_wdata; wire ram_renc; wire [$clog2(DEPTH) -1:0]ram_raddr; wire [WIDTH -1:0]ram_rdata; dual_port_RAM #( .DEPTH(DEPTH), .WIDTH(WIDTH)) u_ram( .wclk(clk), .wenc(ram_wenc), .waddr(ram_waddr), .wdata(ram_wdata), .rclk(clk), .renc(ram_renc), .raddr(ram_raddr), .rdata(ram_rdata) ); //*********************************************** // write path //*********************************************** assign ram_wenc = wvalid && wready; assign ram_waddr = waddr; assign ram_wdata = wdata; //*********************************************** // read path //*********************************************** assign ram_renc = arvalid && arready; assign ram_raddr = araddr; //*********************************************** // read pipe //*********************************************** wire ram_renc_ff; dffr #(.WIDTH(1)) u_renc_ff( .clk(clk), .rst_n(rst_n), .d(ram_renc), .q(ram_renc_ff) ); wire pipe_in_valid = ram_renc_ff; wire pipe_in_ready; bw_pipe #( .WIDTH(WIDTH)) u_pipe( .clk(clk), .rst_n(rst_n), .data_in(ram_rdata), .data_in_valid(pipe_in_valid), .data_in_ready(pipe_in_ready), .data_out(rdata), .data_out_valid(rvalid), .data_out_ready(rready) ); //*********************************************** // out logic //*********************************************** assign wready = 1'b1; assign arready = rready || (~pipe_in_valid && pipe_in_ready); endmodule
进行了简单的仿真:
功能与预期是一致的。
PS.
在组织代码的过程中波形一直和预想的不一样。分析了半天后发现bw_pipe一四有问题于是愤怒的加了一笔逻辑就对了。然后赶紧查自己的博客有没有写错,发现博客写的跟添了一笔的代码一摸一样,gitee上的代码留错了/(ㄒoㄒ)/~~白花了2小时。