第三讲:硬件描述语言基础(Verilog)——模块结构、组合逻辑、时序逻辑与状态机
各位同学好,今天我们聊聊Verilog最核心的几个概念。说实话,很多新手一上来就被各种语法吓住了,其实没那么复杂。你想想看,硬件描述语言说白了就是「画电路图的文字版」。我们不是在写软件,而是在描述硬件该长什么样、该怎么动。
一、模块结构——芯片的「积木块」
Verilog里最基本的单元就是模块(module)。每个模块就像一块乐高积木,有接口(端口),有内部逻辑。我习惯把模块想象成一个黑盒子——外面的人只关心你输入什么、输出什么,里面怎么实现的,那是你自己的事。
module counter (
input wire clk, // 时钟
input wire rst_n, // 复位,低有效
input wire en, // 使能
output reg [7:0] count // 8位计数器输出
);
// 内部逻辑写在这里
endmodule
注意看,端口声明有input、output、inout三种。数据类型常用wire和reg。很多初学者搞不清什么时候用wire、什么时候用reg。我给大家一个简单粗暴的判断方法:assign语句左边必须是wire,always块里被赋值的变量必须是reg。记住这个,至少能避开80%的编译错误。
二、组合逻辑——用assign搭电路
组合逻辑,就是输出只取决于当前输入,跟历史状态无关。说白了,就是一堆逻辑门连在一起。Verilog里用assign关键字来描述组合逻辑。
// 一个简单的加法器
module adder (
input wire [3:0] a,
input wire [3:0] b,
output wire [4:0] sum
);
assign sum = a + b;
endmodule
你看,一行assign就描述了一个加法器。但要注意,assign是连续赋值,只要右边信号变化,左边立刻更新。这跟软件里的赋值完全不一样——软件是顺序执行,硬件是并行执行的。
我在项目中遇到过一个问题:有人用assign写了一个多级组合逻辑,结果时序跑不过。为什么呢?因为组合逻辑路径太长了,信号传播需要时间。所以,复杂的组合逻辑一定要加流水线寄存器,否则上板就翻车。
三、时序逻辑——用always块「记住」状态
时序逻辑跟组合逻辑最大的区别是:它有记忆。它依赖时钟边沿来更新状态。Verilog里用always @(posedge clk)来描述时序逻辑。
// 一个带同步复位的D触发器
module dff (
input wire clk,
input wire rst_n,
input wire d,
output reg q
);
always @(posedge clk) begin
if (!rst_n)
q <= 1'b0;
else
q <= d;
end
endmodule
注意这里用的是非阻塞赋值 <=,不是=。这是新手最容易踩的坑。非阻塞赋值的意思是:所有赋值操作在时钟沿到来时同时计算,然后同时更新。而阻塞赋值=是顺序执行的,用在时序逻辑里会出大问题。
我给大家一个铁律:时序逻辑用 <=,组合逻辑用 =。别问为什么,先记住,等你写多了自然就懂了。
四、状态机——让电路「有脑子」
状态机是数字电路设计的灵魂。说白了,就是让电路知道「我现在在干什么,下一步该干什么」。最常见的写法是三段式状态机,我强烈推荐大家用这种写法。
// 三段式状态机示例:一个简单的交通灯控制器
module traffic_light (
input wire clk,
input wire rst_n,
output reg [1:0] light // 00:红, 01:黄, 10:绿
);
// 第一段:状态定义
localparam IDLE = 2'b00;
localparam RED = 2'b01;
localparam YELLOW = 2'b10;
localparam GREEN = 2'b11;
reg [1:0] state, next_state;
// 第二段:状态转移(时序逻辑)
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
state <= IDLE;
else
state <= next_state;
end
// 第三段:次态逻辑和输出逻辑(组合逻辑)
always @(*) begin
next_state = state;
light = 2'b00;
case (state)
IDLE: begin
next_state = RED;
light = 2'b00;
end
RED: begin
next_state = YELLOW;
light = 2'b01;
end
YELLOW: begin
next_state = GREEN;
light = 2'b10;
end
GREEN: begin
next_state = RED;
light = 2'b11;
end
default: begin
next_state = IDLE;
light = 2'b00;
end
endcase
end
endmodule
三段式的优点很明显:状态转移、次态逻辑、输出逻辑分得清清楚楚。调试的时候,看波形一眼就知道问题出在哪一段。我曾经接手过一个项目,前人用两段式写的状态机,代码乱成一锅粥,我花了三天才理清楚。从那以后,我坚持用三段式。
五、知识体系总览
下面这张图是我自己总结的Verilog基础结构关系图,大家看看就明白了:
这张图把今天讲的内容串起来了。你看,一个模块内部无非就是组合逻辑、时序逻辑、状态机这三样东西。组合逻辑用assign搭,时序逻辑用always块写,状态机用三段式组织。搞清楚了这些,Verilog就算入门了。
嗯,今天就讲到这里。记住我强调的那几个关键点:wire和reg的用法、阻塞和非阻塞赋值的区别、三段式状态机的写法。这些都是我踩过坑之后总结出来的,你们少走弯路。
可可精品资料库