在线咨询 切换到宽版
eetop公众号 创芯大讲堂 创芯人才网

 找回密码
 注册

手机号码,快捷登录

手机号码,快捷登录

搜帖子
查看: 82|回复: 1

[讨论] 推荐一下夏宇闻老师的设计书《Verilog数字系统设计教程》

[复制链接]
发表于 6 小时前 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?注册

×
本帖最后由 卡卡布 于 2025-9-6 14:42 编辑

推荐一下夏宇闻老师的设计书《Verilog数字系统设计教程》,尤其是复杂逻辑设计这一章节。注:该书部分章节存在抄写错误、设计冗余等问题,但不影响理解设计思路。
大家都知道Verilog语言非常难学,并行化、思想要细化到每一个时钟。以至于大部分人都走向了一个误区---以信号为单位去思考问题。
倘若以信号为单位思考问题,会引入大量的控制信号,通过拼接的方式来决定各个阶段的数据处理方式。要知道,Verilog语言中的控制信号、输入输出信号、数据处理信号.....,在语法定义方面是不做任何区分的。如果没有事先的约定与规范,遇到了不负责任的设计者,就只能自认倒霉了。
如下例所示,简单的串口发数:

module uart_tx1 #(
    parameter BAUD = 115200,
    parameter FREQUENCY = 100_000_000, // unit Hz
    parameter PARITY = 0    // 0-none,1-odd,2-even
)(
    input wire clk,rst_n,
    input wire [7:0] tx_data,
    input wire tx_data_vld,
    output logic busy,txd
);
    localparam [3:0] TX_BIT_WIDTH = PARITY ? 4'd10:4'd9;
    logic [$clog2(FREQUENCY/BAUD):0] tx_baud_cnt;   // essentially used as ctrl signal
    logic [3:0] tx_bit_cnt; // essentially used as ctrl signal
    logic [10:0] shift_reg;

    always @(posedge clk)   // used as ctrl signal
        if(!rst_n)
            busy <= 1'b0;
        else if(tx_data_vld)
            busy <= 1'b1;
        else if(tx_bit_cnt == 4'd11 && tx_baud_cnt == FREQUENCY/BAUD+1'b1)
            busy <= 1'b0;
        else
            busy <= busy;

    always @(posedge clk)
        if(!rst_n)
            tx_baud_cnt <= 'd0;
        else if(busy && tx_baud_cnt <=FREQUENCY/BAUD)
            tx_baud_cnt <= tx_baud_cnt + 1'b1;
        else   
            tx_baud_cnt <= 'd0;

    always @(posedge clk)
        if(!rst_n)
            tx_bit_cnt <= 4'd0;
        else if(busy)
            if(tx_baud_cnt ==FREQUENCY/BAUD)
                tx_bit_cnt <= tx_bit_cnt + 1'b1;
            else
                tx_bit_cnt <= tx_bit_cnt;
        else
            tx_bit_cnt <= 4'd0;

    always @(posedge clk)
        if(!rst_n)
            shift_reg <= {11{1'b1}};
        else if(tx_data_vld)
            case (PARITY)
                1: shift_reg <= {1'b1,^tx_data,tx_data,1'b0};   // reverse
                2: shift_reg <= {1'b1,~^tx_data,tx_data,1'b0};
                default: shift_reg <= {1'b1,1'b1,tx_data,1'b0};
            endcase
        else if(tx_baud_cnt == FREQUENCY/BAUD)
//            shift_reg <= shift_reg >> 1;
            shift_reg <= {1'b1,shift_reg[10:1]};
        else   
            shift_reg <= shift_reg;

    assign txd = !busy ? 1'b1:shift_reg[0];

endmodule




因串口发数较为简单,理解起来并不困难,但busy信号作为控制信号绑定计数器的操作,若不提前说明仍然需要花费一段时间进行理解。
那么如何解决上述问题,规范控制流呢?
其实大家都能想到并且熟练运用,就是状态机。
状态机本质上就是控制信号,但却极大的简化了控制信号的判断条件。使得数据信号只要根据状态机状态决定输出内容就好了。
module uart_tx2 #(
    parameter BAUD = 115200,
    parameter FREQUENCY = 100_000_000, // unit Hz
    parameter PARITY = 0    // 0-none,1-odd,2-even
)(
    input wire clk,rst_n,
    input wire [7:0] tx_data,
    input wire tx_data_vld,
    output logic busy,txd
);
    localparam [3:0] TX_BIT_WIDTH = PARITY ? 4'd10:4'd9;
    logic [$clog2(FREQUENCY/BAUD):0] tx_baud_cnt;   // essentially used as ctrl signal
    logic [3:0] tx_bit_cnt; // essentially used as ctrl signal
    logic [10:0] shift_reg;

    logic [2:0] main_state;
    localparam [2:0] IDLE = 3'd0;
    localparam [2:0] REG_DATA = 3'd1;
    localparam [2:0] BAUD_CNT = 3'd2;
    localparam [2:0] SHIFT_DATA = 3'd3;
    localparam [2:0] TX_END = 3'd4;

    always @(posedge clk)
        if(!rst_n)
            main_state <= IDLE;
        else
            case(main_state)
                IDLE:
                    if(tx_data_vld)
                        main_state <= REG_DATA;
                    else   
                        main_state <= IDLE;
                REG_DATA:
                    main_state <= BAUD_CNT;
                BAUD_CNT:
                    if(tx_baud_cnt ==FREQUENCY/BAUD)
                        main_state <= SHIFT_DATA;
                    else
                        main_state <= BAUD_CNT;
                SHIFT_DATA:
                    if(tx_bit_cnt == 10)
                        main_state <= TX_END;
                    else
                        main_state <= BAUD_CNT;
                TX_END:
                    main_state <= IDLE;
                default:
                    main_state <= IDLE;
            endcase

    always @(posedge clk)   // used as ctrl signal
        if(!rst_n)
            busy <= 1'b0;
        else if(main_state == REG_DATA)
            busy <= 1'b1;
        else if(main_state == TX_END)
            busy <= 1'b0;
        else
            busy <= busy;

    always @(posedge clk)
        if(!rst_n)
            tx_baud_cnt <= 'd0;
        else if(main_state == BAUD_CNT)
            tx_baud_cnt <= tx_baud_cnt + 1'b1;
        else   
            tx_baud_cnt <= 'd0;

    always @(posedge clk)
        if(!rst_n)
            tx_bit_cnt <= 4'd0;
        else if(main_state == SHIFT_DATA)
                tx_bit_cnt <= tx_bit_cnt + 1'b1;
        else if(main_state == TX_END)
            tx_bit_cnt <= 4'd0;
        else   
            tx_bit_cnt <= tx_bit_cnt;

    always @(posedge clk)
        if(!rst_n)
            shift_reg <= {11{1'b1}};
        else if(main_state == REG_DATA)
            case (main_state == PARITY)
                1: shift_reg <= {1'b1,^tx_data,tx_data,1'b0};   // reverse
                2: shift_reg <= {1'b1,~^tx_data,tx_data,1'b0};
                default: shift_reg <= {1'b1,1'b1,tx_data,1'b0};
            endcase
        else if(main_state == SHIFT_DATA)
//            shift_reg <= shift_reg >> 1;
            shift_reg <= {1'b1,shift_reg[10:1]};
        else   
            shift_reg <= shift_reg;

    assign txd = !busy ? 1'b1:shift_reg[0];

endmodule



注意,该部分代码若波特率较快,可能出现问题,波特率计数与发送bit之间相差了一个时钟周期,需微调。
上述代码从状态机中已经能看出各个状态的作用,但各个信号之间仍然较为独立。若想知道每一个状态涉及哪些信号,还需要考验一番眼力。
何不将每一个状态与信号写在一起呢?

module uart_tx3 #(
    parameter BAUD = 115200,
    parameter FREQUENCY = 100_000_000, // unit Hz
    parameter PARITY = 0    // 0-none,1-odd,2-even
)(
    input wire clk,rst_n,
    input wire [7:0] tx_data,
    input wire tx_data_vld,
    output logic busy,txd
);
    localparam [3:0] TX_BIT_WIDTH = PARITY ? 4'd10:4'd9;
    logic [$clog2(FREQUENCY/BAUD):0] tx_baud_cnt;   // essentially used as ctrl signal
    logic [3:0] tx_bit_cnt; // essentially used as ctrl signal
    logic [10:0] shift_reg;

    logic [2:0] main_state;
    localparam [2:0] IDLE = 3'd0;
    localparam [2:0] REG_DATA = 3'd1;
    localparam [2:0] BAUD_CNT = 3'd2;
    localparam [2:0] SHIFT_DATA = 3'd3;
    localparam [2:0] TX_END = 3'd4;

    always @(posedge clk)
        if(!rst_n)
            main_state <= IDLE;
        else
            case(main_state)
                IDLE: begin
                        busy <= 1'b0;
                        tx_baud_cnt <= 'd0;
                        tx_bit_cnt <= 4'd0;
                        shift_reg <= {11{1'b1}};
                        if(tx_data_vld)
                            main_state <= REG_DATA;
                        else   
                            main_state <= IDLE;
                end
                REG_DATA: begin
                    busy <= 1'b1;
                    case (main_state == PARITY)
                        1: shift_reg <= {1'b1,^tx_data,tx_data,1'b0};   // reverse
                        2: shift_reg <= {1'b1,~^tx_data,tx_data,1'b0};
                        default: shift_reg <= {1'b1,1'b1,tx_data,1'b0};
                    endcase
                    main_state <= BAUD_CNT;
                end
                BAUD_CNT: begin
                    tx_baud_cnt <= tx_baud_cnt + 1'b1;
                    if(tx_baud_cnt ==FREQUENCY/BAUD)
                        main_state <= SHIFT_DATA;
                    else
                        main_state <= BAUD_CNT;
                end
                SHIFT_DATA: begin
                    tx_baud_cnt <= 'd0;
                    tx_bit_cnt <= tx_bit_cnt + 1'b1;
                    shift_reg <= {1'b1,shift_reg[10:1]};
                    if(tx_bit_cnt == 10)
                        main_state <= TX_END;
                    else
                        main_state <= BAUD_CNT;
                end
                TX_END:
                    main_state <= IDLE;
                default:
                    main_state <= IDLE;
            endcase
/*
    always @(posedge clk)   // used as ctrl signal
        if(!rst_n)
            busy <= 1'b0;
        else if(main_state == REG_DATA)
            busy <= 1'b1;
        else if(main_state == TX_END)
            busy <= 1'b0;
        else
            busy <= busy;

    always @(posedge clk)
        if(!rst_n)
            tx_baud_cnt <= 'd0;
        else if(main_state == BAUD_CNT)
            tx_baud_cnt <= tx_baud_cnt + 1'b1;
        else   
            tx_baud_cnt <= 'd0;

    always @(posedge clk)
        if(!rst_n)
            tx_bit_cnt <= 4'd0;
        else if(main_state == SHIFT_DATA)
                tx_bit_cnt <= tx_bit_cnt + 1'b1;
        else if(main_state == TX_END)
            tx_bit_cnt <= 4'd0;
        else   
            tx_bit_cnt <= tx_bit_cnt;

    always @(posedge clk)
        if(!rst_n)
            shift_reg <= {11{1'b1}};
        else if(main_state == REG_DATA)
            case (main_state == PARITY)
                1: shift_reg <= {1'b1,^tx_data,tx_data,1'b0};   // reverse
                2: shift_reg <= {1'b1,~^tx_data,tx_data,1'b0};
                default: shift_reg <= {1'b1,1'b1,tx_data,1'b0};
            endcase
        else if(main_state == SHIFT_DATA)
//            shift_reg <= shift_reg >> 1;
            shift_reg <= {1'b1,shift_reg[10:1]};
        else   
            shift_reg <= shift_reg;
*/
    assign txd = !busy ? 1'b1:shift_reg[0];

endmodule



当然了,上述思路只是个人比较推荐的一些习惯。具体如何编写还是要看公司的代码规范,只不过个人更加推荐3,可以更加直观的看到各个状态下每个信号的变化。更符合顺序的思维方式。





 楼主| 发表于 4 小时前 | 显示全部楼层
附:
rx代码
module uart_rx #(
    parameter BAUD = 115200,
    parameter FREQUENCY = 100_000_000,
    parameter PARITY = 0
)(
    input wire clk,rst_n,rxd,
    output logic [7:0] rx_data,
    output logic rx_data_vld,parerr,busy
);
    wire rx_falling;
    reg rxd_reg;

    always@(posedge clk)
        if(!rst_n)
            rxd_reg <= 1'b0;
        else
            rxd_reg <= rxd;

    assign rx_falling = !rxd & rxd_reg;

    logic [$clog2(FREQUENCY/BAUD):0] rx_baud_cnt;
    logic [3:0] rx_bit_cnt;
    localparam [3:0] RX_BIT_WIDTH = PARITY ? 4'd10:4'd9;

    reg [2:0] main_state;
    localparam [2:0] IDLE = 3'd0;
    localparam [2:0] BAUD_CNT = 3'd1;
    localparam [2:0] SHIFT_DATA = 3'd2;
    localparam [2:0] BAUD_CNT2 = 3'd3;
    localparam [2:0] RX_END = 3'd4;

    always@(posedge clk)
        if(!rst_n) begin
            rx_baud_cnt <= 'd0;
            rx_bit_cnt <= 4'd0;
            rx_data <= 8'd0;
            rx_data_vld <= 1'b0;
            parerr <= 1'b0;
            busy <= 1'b0;
            main_state <= IDLE;
        end
        else case(main_state)
            IDLE: begin
                rx_baud_cnt <= 'd0;
                rx_bit_cnt <= 4'd0;
                rx_data <= 8'd0;
                rx_data_vld <= 1'b0;
                parerr <= 1'b0;
                busy <= 1'b0;
                if(rx_falling) begin
                    busy <= 1'b1;
                    main_state <= BAUD_CNT;
                end
                else
                    main_state <= IDLE;
            end
            BAUD_CNT: begin
                rx_baud_cnt <= rx_baud_cnt + 1'b1;
                if(rx_baud_cnt == (FREQUENCY/BAUD)/2)
                    main_state <= SHIFT_DATA;
                else
                    main_state <= BAUD_CNT;
            end
            SHIFT_DATA: begin
                rx_bit_cnt <= rx_bit_cnt + 1'b1;
                rx_data <= {rxd_reg,rx_data[7:1]};
                main_state <= BAUD_CNT2;
            end
             BAUD_CNT2: begin
                rx_baud_cnt <= rx_baud_cnt + 1'b1;
                if(rx_bit_cnt == RX_BIT_WIDTH)
                    main_state <= RX_END;
                else if(rx_baud_cnt == (FREQUENCY/BAUD)) begin
                    rx_baud_cnt <= 'd0;
                    main_state <= BAUD_CNT;
                end
                else
                    main_state <= BAUD_CNT2;
             end
            RX_END: begin
                rx_data_vld <= 1'b1;
                busy <= 1'b0;
                case (PARITY)
                    1: parerr <= ^rx_data;
                    2: parerr <= ~^rx_data;
                    default:parerr <= 1'b0;
                endcase
                main_state <= IDLE;
            end
        endcase



endmodule
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

关闭

站长推荐 上一条 /3 下一条


手机版| 小黑屋| 关于我们| 联系我们| 隐私声明| EETOP 创芯网
( 京ICP备:10050787号 京公网安备:11010502037710 )

GMT+8, 2025-9-6 20:53 , Processed in 0.011452 second(s), 3 queries , Gzip On, Redis On.

eetop公众号 创芯大讲堂 创芯人才网
快速回复 返回顶部 返回列表