|
马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?注册
×
本帖最后由 卡卡布 于 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,可以更加直观的看到各个状态下每个信号的变化。更符合顺序的思维方式。
|
|