【手撕代码】握手机制

为什么要握手

  • 跨时钟域处理:
    在这里插入图片描述

握手与反压

在这里插入图片描述
如图所示,信号从输入端到A,经过模块A处理后,再送入到B模块进行处理。为了防止B错误读取A中的数据,A与B之间添加了信号Valid,只有当Valid信号为真时,A输出的数据才是有效数据,同时,为了防止B出现问题,A与B之间还加入了一个引脚Ready,B拉高该电平时,表明当前B模块可以接收、处理信号。只有当A送给B的信号有效(Valid为真),B此时做好的准备可以接收A的数据了(Ready为真),此时,数据才被顺利的送入B中,这个过程就叫做‘握手’。

如果B没能及时的处理完A送达的数据,就会将Ready拉低,提醒A模块不要再传输数据了。此时A模块接收到B的指令,为了不让数据丢失,在B模块读取当前数据前,A模块不会更新自己输出的Data内容。由于A目前不能更新输出值,因此需要告诉A的上级模块停止数据传输,因此A模块也会拉低自己的Ready输出信号,提醒上一级暂停数据的传输。这种当后面的模块未能及时处理上级模块的输入数据时,通过一个Ready信号告诉自己前面的模块暂停数据传输的方法被称之为‘反压’。

握手信号无非3种可能

  • valid先变高:上游master提供的valid信号随数据一起拉高,但下游slave过一段时间才能准备好ready信号
    在这里插入图片描述
  • ready先变高:
    在这里插入图片描述
  • valid、ready一起变高:
    在这里插入图片描述

怎样实现握手

案例一:数据反压

  • 代码
    关键点在于握手机制的实现、数据的处理,即利用上游的valid_i信号、下游的ready_i信号,判断本模块:
    什么时候该接收上游数据(即什么时候向上游发送ready_o信号)
    什么时候该向下级发送数据(即什么时候向下游发送valid_o、data_out信号)
  • 本代码设计中,ready_o信号是否输出?看的是下级什么时候需要数据,即ready_i信号是否为1
    valid_o信号什么时候有效?该模块功能是数据被打一拍后输出,所以握手成功后(ready_i有效),valid_o就应该输出
`timescale 1ns/1ns

module handshake(
    input   clk,
    input   rst_n,
    
    input           valid_i,
    input   [7:0]   data_i,
    output          ready_o,

    output  [7:0]   data_o,
    input           ready_i,
    output          valid_o
);

reg [7:0]   data_o_r;
reg valid_o_r;

assign ready_o = ready_i;

always@(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        data_o_r <= 8'b0;
    end else if(valid_i && ready_i)begin
        data_o_r <= data_i * 2;
    end
end

always@(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        valid_o_r <= 1'b0;
    end else if(ready_i)begin
        valid_o_r <= valid_i;
    end
end

assign data_o = data_o_r;
assign valid_o = valid_o_r;

endmodule
  • testbench
`timescale 1ns/10ps

module handshake_tb();

reg clk;
reg rst_n;

reg         valid_i;
reg [7:0]   data_i;
wire        ready_o;

wire    data_o;
reg     ready_i;
wire    valid_o;

always #10 clk = ~clk;

initial begin
    clk = 0;
    rst_n = 0;
    data_i = 8'd0;
    valid_i = 1'd0;
    ready_i = 1'd1;
    #15
    rst_n = 1'b1;
    #15
    rst_n   <= 1'b1;
    data_i  <= 8'b0000_1000;
    valid_i <= 1'd0;    //upstream data invalid
    ready_i <= 1'd1;    //downstream ready
    #20
    data_i <= 8'b0111_1000;
    valid_i <= 1'd1;     //upstream data valid
    #20
    data_i  <= 8'b0100_0100;    //upstream data change
    #20
    valid_i <= 1'd0;     //upstream data invalid
    #20
    data_i  <= 8'b0010_0100;
    valid_i <= 1'd1;    //upstream data valid
    ready_i <= 1'd0;    //downstream not ready
    #20
    ready_i <= 1'b1;    //downstream ready
    #20
    valid_i <= 1'b0;    //upstream data invalid
    #500
    $stop();
end

handshake u_handshake(
        .clk            (clk    ),
        .rst_n          (rst_n  ),
                                
        .valid_i        (valid_i),
        .data_i         (data_i ),
        .ready_o        (ready_o),
                                
        .data_o         (data_o ),
        .ready_i        (ready_i),
        .valid_o        (valid_o)    
        );


endmodule

结果
在这里插入图片描述

案例二:数据串转并输出

  • 设计描述
    案例二 :数据串转并电路
    实现串并转换电路,输入端输入单bit数据,每当本模块接收到6个输入数据后,输出端输出拼接后的6bit数据。本模块输入端与上游的采用valid-ready双向握手机制,输出端与下游采用valid-only握手机制。数据拼接时先接收到的数据放到data_b的高位。
    在这里插入图片描述
    在这里插入图片描述
  • 电路的接口如下图所示:
    valid_a用来指示数据输入data_in的有效性;
    valid_b用来指示数据输出data_out的有效性;
    ready_a用来指示本模块是否准备好接收上游数据;
    ready_b表示下游是否准备好接收本模块的输出数据;
    clk是时钟信号;rst_n是异步复位信号。
  • 代码
`timescale 1ns/10ps

module handshake_s_to_p(
    input   clk,
    input   rst_n,
    input   data_in,
    input   valid_a,

    input   ready_b,
    output reg [5:0] data_out,
    output reg  valid_b,
    output  ready_a
);

reg [2:0] counter;
reg [5:0] data_in_r;
assign ready_a = ~valid_b || ready_b;

always@(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        counter <= 3'd0;
//    end else if(counter == 2'd3)begin
//        counter <= 2'd0;
    end else if(valid_a && ready_a )begin
        counter <= (counter == 3'd5) ? 3'd0 : counter + 1'b1;
//        counter <= counter + 1'b1;
    end else begin
        counter <= counter;
    end
end

always@(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        data_in_r <= 5'd0;
    end else if(valid_a && ready_a)begin
        data_in_r <= {data_in_r[4:0],data_in};
    end else begin
        data_in_r <= data_in_r;
    end
end

always@(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        data_out <= 5'd0;
        valid_b <= 1'b0;
    end else if(counter == 3'd5)begin
        data_out<= data_in_r;
        valid_b <= 1'b1;
    end else begin
        data_out<= data_out;
        valid_b <= 1'b0;
    end
end

endmodule
  • testbench
`timescale 1ns/10ps

module handshake_tb();

reg clk;
reg rst_n;
reg [7:0] data_in;
reg valid_a;

reg ready_b; 
wire [9:0] data_out;
wire valid_b; 
wire ready_a; 

always #10 clk <= ~clk;

initial begin
    clk = 1'b0;
    rst_n = 1'b0;
    ready_b = 1'b1;
    #15
    rst_n = 1'b1;
    #15
    data_in  <= 1'b1;
    valid_a <= 1'd1;    //upstream data invalid
    #20
    data_in  <= 1'b0;    //upstream data change
    #20
    data_in <= 1'b1;
    #20
    data_in  <= 1'b0;    //upstream data change
    #20
    data_in  <= 1'b0;    //upstream data change
    #20
    data_in  <= 1'b1;    //upstream data change
    #20
    ready_b <= 1'b1;    //downstream ready
    #500
    $stop();

end

handshake_s_to_p u_handshake_s_to_p(
        .clk         (clk     ),
        .rst_n       (rst_n   ),
        .data_in     (data_in ),
        .valid_a     (valid_a ),

        .ready_b     (ready_b ),
        .data_out    (data_out),
        .valid_b     (valid_b ),
        .ready_a     (ready_a )    
);

endmodule

结果
在这里插入图片描述

案例三:数据累加输出

  • 设计描述
    实现串行输入数据累加输出,输入端输入8bit数据,每当模块接收到4个输入数据后,输出端输出4个接收到数据的累加结果。输入端和输出端与上下游的交互采用valid-ready双向握手机制。要求上下游均能满速传输时,数据传输无气泡,不能由于本模块的设计原因产生额外的性能损失。
    在这里插入图片描述
    在这里插入图片描述
    valid_a和ready_a均为高电平,完成数据in握手,开始接收上游下行数据(应判断ready_a何时拉高)
    本模块接收数据时,valid_b始终保持为低
    接收完4个数据并完成累加后,valid_b拉高(表示本模块已经准备好输出数据),等待ready_b信号到来握手
    ready_b信号到来同时,ready_a立刻拉高表示本模块继续准备接受数据(显然ready_a与ready_b存在assign逻辑),valid_b与ready_b握手成功后,valid_b在下一周期主动拉低,data_out输出数据

  • 电路的接口如下图所示:
    valid_a用来指示数据输入data_in的有效性;
    valid_b用来指示数据输出data_out的有效性;
    ready_a用来指示本模块是否准备好接收上游数据;
    ready_b表示下游是否准备好接收本模块的输出数据;
    clk是时钟信号;rst_n是异步复位信号。

  • 代码

`timescale 1ns/10ps

module handshake_mux(
    input   clk,
    input   rst_n,
    input   [7:0]   data_in,
    input   valid_a,

    input   ready_b,
    output reg  [9:0]   data_out,
    output reg  valid_b,
    output  ready_a
);


reg [1:0] counter;

assign ready_a = ~valid_b || ready_b;
//若本模块数据已经准备好输出,ready_b拉高,此时ready_a拉高立刻允许新的data输入,保证无气泡传输
//ready_a与valid_b互斥,本模块还在接受数据时即数据没有准备好输出

always@(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        data_out <= 8'd0;
    end else if(valid_a && ready_a)begin
        if(counter == 2'd0 && ready_b)begin
            data_out <= data_in;
        end else begin
            data_out <= data_in + data_out;
        end
    end else begin
        data_out <= data_out;
    end
end

always@(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        counter <= 2'd0;
//    end else if(counter == 2'd3)begin  //可以直接注释掉该句,因为计数器溢出后自动清零
//        counter <= 2'd0;
    end else if(valid_a && ready_a )begin
        counter <= counter + 1'b1;
    end else begin
        counter <= counter;
    end
end
//counter <= (counter==2'd3) ? 2'd0 : (counter+1'b1);

always@(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        valid_b <= 1'b0;
    end else if(counter == 2'd3 && valid_a && ready_a)begin
        valid_b <= 1'b1;
    end else if(valid_b && ready_b)begin
        valid_b <= 1'b0;
    end
end

endmodule
  • testbench
`timescale 1ns/10ps

module handshake_tb();

reg clk;
reg rst_n;
reg [7:0] data_in;
reg valid_a;

reg ready_b; 
wire [9:0] data_out;
wire valid_b; 
wire ready_a; 

always #10 clk <= ~clk;

initial begin
    clk = 1'b0;
    rst_n = 1'b0;
    ready_b = 1'b0;
    #15
    rst_n = 1'b1;
    #15
    data_in  <= 8'b0000_0001;
    valid_a <= 1'd1;    //upstream data invalid
    #20
    data_in  <= 8'b0000_0010;    //upstream data change
    #20
    data_in <= 8'b0000_0011;
    #20
    data_in  <= 8'b0000_0100;    //upstream data change
    #20
    data_in  <= 8'b0000_0101;    //upstream data change
    #20
    ready_b <= 1'b1;    //downstream ready
    #500
    $stop();

end

handshake_mux u_handshake_mux(
        .clk         (clk     ),
        .rst_n       (rst_n   ),
        .data_in     (data_in ),
        .valid_a     (valid_a ),

        .ready_b     (ready_b ),
        .data_out    (data_out),
        .valid_b     (valid_b ),
        .ready_a     (ready_a )    
);

endmodule

结果
在这里插入图片描述

参考链接

  1. Verilog手撕代码(8)握手机制
  2. cdc跨时钟域处理-结绳握手法
  3. 数字IC设计中的握手与反压
### Python编程基础代码练习 #### 一、贪心算法实践 通过解决实际问题来掌握贪心算法的核心思想。例如,在学生重新排队的问题中,可以尝试实现一个函数 `rearrange_students` 来模拟这一过程[^1]。 ```python def rearrange_students(students): students.sort(reverse=True) # 按照身高降序排列 result = [] while len(students) > 0: tallest_student = students.pop(0) # 取最高的学生加入队列 result.append(tallest_student) if len(students) > 0: shortest_student = students.pop() # 取最矮的学生加入队列 result.insert(0, shortest_student) return result ``` 此代码片段展示了如何利用贪心策略解决问题。 --- #### 二、字符串重构挑战 针对LeetCode上的题目《767. 重构字符串》,可以通过优先级队列(堆)的方式构建解决方案[^3]。 ```python from collections import Counter import heapq def reorganize_string(s: str) -> str: count = Counter(s) max_heap = [(-freq, char) for char, freq in count.items()] heapq.heapify(max_heap) prev_freq, prev_char = 0, '' result = [] while max_heap: freq, char = heapq.heappop(max_heap) result.append(char) if prev_freq < 0: heapq.heappush(max_heap, (prev_freq, prev_char)) freq += 1 # 减少频率计数 prev_freq, prev_char = freq, char reconstructed_str = ''.join(result) if len(reconstructed_str) != len(s): # 如果无法满足条件返回空串 return "" return reconstructed_str ``` 上述方法基于字符频率调整位置以达到最优解。 --- #### 三、Linux常用命令与跨平台脚本执行 对于工作中常见的Linux命令,如`awk`, `sed`, `vim`等工具的应用场景进行了总结[^2]。当需要在Linux上运行来自Windows环境的脚本时,可能遇到编码格式不兼容的情况。此时可借助Vim编辑器修改文件格式: ```bash vim test.sh :set ff=dos # 查看当前文件格式是否为DOS :set ff=unix # 将文件格式转换为Unix :wq # 保存并退出 ``` 以上操作能够有效处理因操作系统差异引发的问题。 --- #### 四、Python基础知识巩固 为了进一步提升Python编程能力,可以从以下几个方面入- **数据结构**:熟练运用列表推导式、字典解析以及集合运算。 - **控制流语句**:深入理解循环中的`break`和`continue`关键字作用。 - **异常处理机制**:学会定义自定义错误类及其触发逻辑。 示例程序如下所示: ```python try: num = int(input("请输入一个整数: ")) except ValueError as e: print(f"输入有误! {e}") else: factorial = 1 for i in range(1, num + 1): factorial *= i print(f"{num} 的阶乘是 {factorial}.") finally: print("计算完成.") ``` 这段代码演示了基本的异常捕获流程。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值