【芯片前端】延迟一拍出数的握手型ram结构的一次探索

简介: 【芯片前端】延迟一拍出数的握手型ram结构的一次探索

思路和代码并不一定正确,仅仅是记录一次有意思的结构思考过程,如有疏漏错误,请不吝赐教。


一般而言生成的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:


  1. wvalid和wready同时有效时,将wdata写入到ram地址中,写入的数据在下一拍生效;
  2. arvalid和arready同时有效时,读取araddr对应ram数据,数据最早在下一拍读出;
  3. 只有当rvalid和rready同时有效时,rdata会被取出,否则rdata需要一直保持。

需求就是这么简单,那么接下来对实现进行分析。


对于dual_port_ram(实际上叫double_port_ram比较好,我理解的dual_port_ram是两个口都可以独立的读写),读取的逻辑时序图如下:



显然,arvalid&&arready有效时,应该发起renc信号进行读取,下一拍rvalid必然是为1。但问题在于,当下一拍数据被读出后,ready的状态是未知的,如果恰好rready为0,那么数据将会丢失。


由于之前没有写过这块代码,也没有深入研究过,因此只能凭借我自己的感觉规划了三种思路:


  1. 在确保下拍没有反压(即下一拍的ready一定为1)时才置高arready;
  2. 在ram后面跟pipe来保持数据;
  3. 当数据没有被取出时,连续发起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)。正好对着两个场景:


  1. 下游现在可以接,哪怕你现在pipe里有数,下一拍pipe里也一定是空的,至少保证下一拍的rdata能进pipe,或者直接被下游接走;
  2. 下游现在不能接,但是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小时。


相关文章
|
前端开发 vr&ar
【前端验证】被动响应型uvm_model环境搭建——以握手型ram_model为例
【前端验证】被动响应型uvm_model环境搭建——以握手型ram_model为例
|
7月前
|
Web App开发 移动开发 前端开发
初探前端世界:网页基本结构入门指南
初探前端世界:网页基本结构入门指南
108 0
|
7月前
|
应用服务中间件 开发工具 nginx
Mac M1/M2/M3 芯片环境配置以及常用软件安装-前端
Mac M1/M2/M3 芯片环境配置以及常用软件安装-前端 最近换了台新 Mac,所有的配置和软件就重新安装下,顺便写个文章。
678 1
|
4月前
|
前端开发 JavaScript
前端基础(十三)_定时器(间歇定时器、延迟定时器)
本文介绍了JavaScript中定时器的使用,包括`setTimeout`和`setInterval`两种类型。`setTimeout`是实现延迟执行,即等待一定时间后执行一次指定的函数;而`setInterval`是实现间歇执行,即每隔一定时间就执行一次指定的函数。文章还介绍了如何使用`clearTimeout`和`clearInterval`来取消定时器的执行,并通过示例代码展示了定时器的创建和取消。
166 4
前端基础(十三)_定时器(间歇定时器、延迟定时器)
|
4月前
|
前端开发
【前端web入门第五天】01 结构伪类选择器与伪元素选择器
本文介绍了CSS中的结构伪类选择器和伪元素选择器。结构伪类选择器如`nth-child`可根据元素结构关系进行选择,例如将列表中首个`&lt;li&gt;`元素背景设为绿色。伪元素选择器用于创建装饰性内容。
108 8
|
5月前
|
前端开发 芯片
用于生物电测量的低功耗八通道模拟前端芯片 Low-Power, 8-Channel AFE for Biopotential Measurement
此低功耗八通道模拟前端芯片专为生物电测量设计,集成了八个低噪声放大器与24位高精度ADC,支持125SPS至8kSPS数据速率及多种增益设置。芯片配备内置时钟、参考电压源与断线检测等功能,并兼容多种电极类型。适用于心电图、肌电图和个人健康监测设备,采用VQFN与TQFP封装,尺寸紧凑,确保医疗设备兼具性能与便携性。
|
5月前
|
编解码 前端开发 芯片
全国产化用于生物电测量的低功耗双通道模拟前端芯片 Low-Power, 2-Channel AFE for Biopotential Measurement
这款低功耗双通道模拟前端芯片专为生物电测量设计,集成两个低噪声放大器与24位高精度ADC,支持125至8k SPS的数据速率及多种增益设置。工作电压2.7至3.3V,内置RLD、断线检测等功能,并具备SPI接口。适用于穿戴式健康监测设备、运动智能装备及医疗仪器,如心电图监测。提供TQFP(32)与VQFN(32)封装选项,尺寸紧凑,满足便携与小型化需求。
|
8月前
|
前端开发 JavaScript
|
8月前
|
前端开发 开发者
【专栏】BEM(Block-Element-Modifier)是一种前端命名规范和架构方法,旨在创建清晰、可维护的代码结构。
【4月更文挑战第29天】BEM(Block-Element-Modifier)是一种前端命名规范和架构方法,旨在创建清晰、可维护的代码结构。它包括Block(独立功能单元)、Element(Block的子元素)和Modifier(表示状态或变体)。BEM的特点包括命名一致性、模块化设计、清晰结构和可复用性,适用于代码组织、样式管理、组件化开发和团队协作。虽然命名较长和学习成本是其局限性,但BEM在提升代码质量和效率方面具有显著优势,是前端开发的重要工具。
143 0
|
8月前
|
前端开发
开发指南017- 移动前端结构
移动前端采用uniapp架构,主要目录如下:
下一篇
开通oss服务