4、硬件描述语言入门:Verilog/VHDL基础,用代码“画”出Tick处理电路
说实话,很多刚入行的朋友问我:“硬件工程师是不是天天画原理图?”我通常会笑一笑,然后打开编辑器给他们看一段Verilog代码。你看,这就是我们“画”电路的方式——用代码描述硬件行为。
这一章,咱们不讲虚的。直接上手,看看怎么用Verilog和VHDL把Tick处理电路给“画”出来。你想想看,Tick数据流进来,我们要做解析、对齐、校验,这些在硬件里就是一堆寄存器、状态机和组合逻辑。用代码描述它们,就是我们的基本功。
4.1 为什么是Verilog/VHDL?
我个人习惯用Verilog,因为它在Tick数据处理领域更常见。但VHDL也有它的铁粉,尤其在军工和欧洲项目里。说白了,两种语言都能干活,关键是你得理解它们的核心思想——并行执行。
软件代码是一行一行跑的。硬件代码呢?所有always块、所有process块,理论上同时启动。我第一次从C语言转过来时,被这个“并行”坑得不轻。嗯,这里要注意:你写的每一行Verilog,最终都会变成实实在在的门电路。
4.2 Verilog基础:模块与端口
一个Tick处理电路,首先得有个“壳子”——模块。模块就是芯片里的一个功能块,有输入有输出。
// 一个简单的Tick数据接收模块
module tick_receiver (
input wire clk, // 时钟
input wire rst_n, // 复位,低有效
input wire tick_data_in, // 串行Tick数据输入
output reg [31:0] tick_value // 解析后的32位Tick值
);
// 内部寄存器
reg [3:0] bit_count;
reg [31:0] shift_reg;
// 时序逻辑:每个时钟沿干活
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
bit_count <= 4'd0;
shift_reg <= 32'd0;
tick_value <= 32'd0;
end else begin
// 移位寄存器:把串行数据变成并行
shift_reg <= {shift_reg[30:0], tick_data_in};
if (bit_count == 4'd31) begin
tick_value <= shift_reg; // 收满32位,输出
bit_count <= 4'd0;
end else begin
bit_count <= bit_count + 1'b1;
end
end
end
endmodule
你看,这段代码描述了什么?一个32位的移位寄存器。我在项目中遇到过类似场景:Tick数据流是串行的,时钟频率很高,必须用硬件移位寄存器来收数。软件里用for循环?太慢了,硬件里一个时钟周期就搞定。
wire和reg别搞混。简单记:wire是连线,reg是寄存器。但always块里赋值的变量,必须是reg类型。
4.3 VHDL基础:同样的电路,不同的写法
VHDL的语法更啰嗦,但类型检查更严格。我个人觉得,VHDL适合大型团队协作,不容易出错。来看看同样的移位寄存器用VHDL怎么写:
-- Tick数据接收模块(VHDL版)
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity tick_receiver is
port (
clk : in std_logic;
rst_n : in std_logic;
tick_data_in : in std_logic;
tick_value : out std_logic_vector(31 downto 0)
);
end entity;
architecture rtl of tick_receiver is
signal bit_count : unsigned(3 downto 0);
signal shift_reg : std_logic_vector(31 downto 0);
begin
process(clk, rst_n)
begin
if rst_n = '0' then
bit_count <= (others => '0');
shift_reg <= (others => '0');
tick_value <= (others => '0');
elsif rising_edge(clk) then
shift_reg <= shift_reg(30 downto 0) & tick_data_in;
if bit_count = 31 then
tick_value <= shift_reg;
bit_count <= (others => '0');
else
bit_count <= bit_count + 1;
end if;
end if;
end process;
end architecture;
VHDL里用process代替Verilog的always,用<=做信号赋值。逻辑完全一样,但VHDL要求你明确声明architecture和signal。我曾经在一个军工项目里被迫用VHDL,刚开始觉得烦,后来发现查错确实方便——类型不匹配直接编译报错,不会像Verilog那样悄悄给你个X态。
4.4 用代码“画”出Tick处理核心
Tick处理的核心是什么?说白了就是解析、校验、对齐。咱们用状态机来实现。状态机是硬件设计的灵魂,没有状态机,很多逻辑根本没法组织。
下面这个例子,是一个Tick帧解析状态机。Tick数据帧通常有帧头、数据体、校验和。我们用三个状态来搞定:
// Tick帧解析状态机
module tick_frame_parser (
input wire clk,
input wire rst_n,
input wire [7:0] data_byte, // 字节输入
input wire data_valid, // 数据有效
output reg [63:0] tick_payload,// 解析出的Tick负载
output reg frame_done // 帧完成标志
);
// 状态定义
localparam IDLE = 2'b00;
localparam HEADER = 2'b01;
localparam PAYLOAD = 2'b10;
localparam CHECKSUM = 2'b11;
reg [1:0] state, next_state;
reg [3:0] byte_count;
reg [7:0] checksum_calc;
// 状态寄存器
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
state <= IDLE;
else
state <= next_state;
end
// 状态转移逻辑
always @(*) begin
next_state = state;
case (state)
IDLE: begin
if (data_valid && (data_byte == 8'hAA)) // 检测帧头0xAA
next_state = HEADER;
end
HEADER: begin
if (data_valid && (data_byte == 8'h55)) // 第二个帧头0x55
next_state = PAYLOAD;
else
next_state = IDLE; // 帧头错误,回到空闲
end
PAYLOAD: begin
if (data_valid && (byte_count == 4'd7)) // 收完8字节负载
next_state = CHECKSUM;
end
CHECKSUM: begin
if (data_valid)
next_state = IDLE; // 校验完,回到空闲
end
endcase
end
// 数据路径逻辑(省略具体实现)
// ...
endmodule
default分支,结果综合出来一堆锁存器。锁存器在Tick处理电路里是灾难——时序难收敛,功耗还大。记住:组合逻辑的case一定要写default,时序逻辑的case可以不写,但最好也写上。
4.5 组合逻辑 vs 时序逻辑:Tick电路的分工
在Tick处理电路里,这两种逻辑各有各的活:
| 类型 | Verilog写法 | Tick处理中的用途 | 注意点 |
|---|---|---|---|
| 组合逻辑 | always @(*) 或 assign |
数据选择、校验计算、地址译码 | 小心竞争冒险,加寄存器打拍 |
| 时序逻辑 | always @(posedge clk) |
状态机、移位寄存器、计数器 | 注意复位,异步复位要同步释放 |
举个例子:Tick数据校验。如果你用组合逻辑算CRC,算完直接输出,可能会因为路径延迟产生毛刺。我建议的做法是:组合逻辑算,时序逻辑打一拍再输出。这样干净利落。
4.6 知识体系:一张图看懂
下面这张SVG图,把本章的核心逻辑串起来了。从HDL基础到Tick处理电路,每一步都是环环相扣的。
这张图你看懂了吗?左边是Verilog路线,右边是VHDL路线,最终都汇聚到Tick处理电路。说白了,语言只是工具,核心是并行思维和状态机设计。
4.7 实战建议:从仿真开始
别一上来就想着上板子。我建议你先写个testbench,仿真跑通再说。仿真能看到波形,波形会告诉你代码到底“画”成了什么电路。
举个例子:你写了一个移位寄存器,仿真时看看数据是不是每个时钟沿移动一位。如果发现数据跳着走,那八成是时序逻辑写成了组合逻辑。嗯,这种坑我踩过不止一次。
好了,这一章的内容就到这里。记住:Verilog和VHDL都是“画”电路的语言,不是写软件。把Tick处理电路拆成状态机、移位寄存器、组合逻辑,然后用代码描述出来——这就是你的基本功。多练,多仿真,慢慢就有感觉了。