自我介绍
问项目
在项目中主要负责哪部分
智力题
天平秤球,有一个质量不一样的,3个球,15个球
OSI七层模型
组合逻辑电路和时序逻辑电路的区别
testbench构yi
C语言和Verilog的本质区别
C描述"执行步骤"';Verilog描述"硬件电路结构"
本质不同:
- C语言:软件编程语言,跑在CPU上,按顺序执行指令
- Verilog:硬件描述语言(HDL),用来描述数字电路(门电路、触发器、连线)
执行方式
- C:串行执行,一行一行跑,有先后顺序
- Verilog:并行执行,所有同时满足条件的逻辑一起跑,和真实电路一致
时间概念
- C:几乎没有"时钟周期"的概念,只关心执行顺序
- Verilog:严格依赖时钟、时序,必须考虑寄存器、延迟、建立保持时间
变量含义
- C的变量=内存里的一块数据
- Veriog的wire/reg=实际硬件的连线/触发器
用途
- C:写应用、驱动、算法、操作系统等
- Verilog:写FPGA/AISC芯片内部逻辑(CPU内核、接口、控制器等)
抽象层级
C 语言 :面向过程/对象 ,最高抽象到算法复杂度。
Verilog :面向结构/数据流 ,最高抽象到时钟域、流水线、面积与速度互换
C 语言是厨师给助手的做菜指令(一张时间表),Verilog 是建筑师给工厂的楼房图纸(一张空间网)
C 语言和 Verilog 虽然语法上有相似之处(如 if、case、赋值符号等),但二者的本质 完全不同:
C 语言描述"如何做" (过程式编程),而 Verilog 描述"是什么"(硬件结构)
执行模型(核心区别)
| 对比项 | C 语言 | Verilog HDL |
|---|---|---|
| 执行方式 | 顺序执行。CPU 一条指令接一条指令运行。 | 并行执行 。所有 always 块、assign 语句、模块实例在硬件层面同时工作。 |
| 时间概念 | 软件时间由 CPU 时钟间接体现,代码运行时间不确定(取决于指令数、流水线、缓存)。 | 硬件时间精确到皮秒 。信号变化严格由时钟边沿或延时(#)控制。 |
| 本质比喻 | 编写菜谱(先洗菜,再切菜,再下锅)。 | 绘制电路原理图(电源接通后,电阻、电容、逻辑门同时工作)。 |
编译与运行机制
| 对比项 | C 语言 | Verilog HDL |
|---|---|---|
| 编译产物 | 生成 机器指令 (.exe / .elf),加载到内存运行。 |
生成 网表 (逻辑门、触发器、连线),映射到物理芯片(FPGA 或 ASIC)。 |
| 运行载体 | 冯·诺依曼架构 CPU(统一内存存指令和数据)。 | 空间展开的硬件电路(每一个操作都有专属的运算单元)。 |
| 调试方式 | 打断点、单步执行、查看变量当前值。 | 绘制波形图,观察信号在纳秒级时间轴上的电压变化。 |
赋值语句的物理含义
| 语句类型 | C 语言含义 | Verilog 含义 |
|---|---|---|
a = b; |
阻塞赋值 :立即更新 a 的值,后续代码使用新值。 |
组合逻辑连线 (assign 或组合 always 块):a 是一条物理导线,b 变化 a 即刻变化。 |
a <= b; |
无此语法。 | 非阻塞赋值 (时序 always 块):推断出一个 D 触发器 ,时钟边沿后 a 才更新为旧时刻的 b。 |
// Verilog 中的并行幻觉
always @(posedge clk) begin
a <= b; // 在时钟上升沿,a 被赋予"旧时刻"b 的值
b <= a; // 在同一个时钟上升沿,b 被赋予"旧时刻"a 的值
// 综合结果:a 和 b 的寄存器交换了值!这在 C 语言中如果不借助临时变量是不可能的。
end
函数与模块的区别
| 对比项 | C 语言函数 | Verilog 模块 |
|---|---|---|
| 调用关系 | 嵌套调用,复用代码段。调 100 次占 1 份代码空间。 | 实例化 ,复制硬件。例化 100 次就生成 100 份独立电路。 |
| 参数传递 | 传值、传指针,数据通过栈 或寄存器流动。 | 端口映射,物理连线连接两个硬件单元。 |
| 递归 | 支持(有限制)。 | 不支持(电路无法无限嵌套自身)。 |
循环语句的差异
| 对比项 | C 语言 for 循环 |
Verilog for 循环 |
|---|---|---|
| 用途 | 指示 CPU 重复执行同一段指令(耗时)。 | 生成多个并行的硬件副本(不耗时,占面积)。 |
| 典型场景 | for(i=0;i<100;i++) sum += a[i]; |
for(i=0;i<8;i++) assign out[i] = in[7-i];(位反转连线)。 |
| 注意 | 代码量小。 | 只有循环次数是常数时才可综合 (for 必须在编译时展开为固定电路)。 |
综合与不可综合
这是 C 语言不具备的概念。
-
C 语言:所有语法在运行时都有对应行为(除未定义行为)。
-
Verilog :分为可综合子集 (能变成电路)和行为级仿真子集(只能用于测试验证)
| 可综合 (Synthesizable) | 不可综合 / 仅仿真 (Non-Synthesizable) |
|---|---|
assign, always @(*), always @(posedge clk) |
initial(初始化),#10(延时),$display(打印),force/release |
| 目的:造芯片 | 目的:写 Testbench 验证模型 |
设计思维对比表(总结)
| 维度 | C 语言思维 | Verilog 思维 |
|---|---|---|
| 核心问题 | 如何用最少的 CPU 周期完成任务? | 如何用最少的逻辑门 / LUT 实现功能? |
| 资源衡量 | 时间复杂度(O(n))、内存占用。 | 面积(Area)、工作频率(Fmax)、功耗。 |
| 代码结构 | 主函数调用子函数。 | 顶层模块例化子模块。 |
| 错误后果 | 段错误(Segmentation Fault)、内存泄漏。 | 竞争冒险(Glitch)、亚稳态(Metastability)、建立时间违例。 |
状态机两段三段的区别,三段的优点
状态机几段式,到底在分什么?
状态机本质三部分:
- 现态 current state:当前在哪个状态(寄存器存的)
- 次态 next state:下一个要跳去哪(组合逻辑)
- 输出 output:根据状态 / 输入产生的控制信号
所谓 "几段式",就是把这三部分写在几个 always 块里。
输出output也可用assign
1)一段式状态机
- 只用 1 个 always
- 把:状态跳转 + 输出逻辑 全部写在一起
- 通常是
always @(posedge clk)里塞一堆 if/else/case
缺点:
- 逻辑混乱,可读性差
- 容易写出锁存器(latch)
- 难维护、难查时序问题
- 大型工程基本不用
2)两段式状态机(最常见)
分成 2 个 always:
时序 always :只负责状态更新
组合 always :负责次态计算 + 输出
特点:
- 结构清晰,新手友好
- 满足大部分简单 / 中型工程
- 输出容易出现组合逻辑毛刺
3)三段式状态机(最规范、最推荐)
严格分成 3 个 always:
- 时序段:只更新现态(和两段一样)
- 组合段 :只算次态 next_state,不碰输出
- 时序输出段 :只用现态,寄存器输出,完全不带组合逻辑
三段式状态机的优点(重点)
1. 输出是寄存器输出,彻底消除毛刺
- 两段式输出常是组合逻辑,容易有毛刺、冒险竞争
- 三段式输出打一拍,干净、稳定、适合跨时钟 / 对外控制
2. 逻辑完全解耦,便于维护和修改
- 状态跳转归跳转
- 输出归输出
- 改输出不影响跳转逻辑,改跳转不影响输出大型工程、多人协作必备
3. 综合器更友好,时序更容易收敛
- 组合逻辑只负责次态,层级浅、延迟小
- 输出全寄存器,更容易达到时序要求(Timing closure)
4. 避免 latch,代码更安全
- 组合块只做 next_state 计算,结构简单
- 输出在时序块里,默认有复位 / 默认值,几乎不会产生意外锁存器
5. 方便插入流水线、优化时序
- 结构规整,很容易再加寄存器打拍
- 对高速设计非常友好
两段式写法将状态机拆分为两个
always块1.时序逻辑块(寄存器块)
- 敏感表:
posedge clk, negedge rst_n- 功能:完成现态寄存器(current_state)的更新,将次态赋值给现态。
2.组合逻辑块
- 敏感表:
*(或全部输入信号 + 现态)- 功能:根据现态 + 输入 ,计算次态(next_state) 和输出逻辑
两段式的特点
结构简单,代码量少。
缺点:组合输出 (如上述的
out)直接由组合逻辑产生,可能引入毛刺,对时序收敛和下游电路不友好。若输出采用寄存器输出(即在第一段时序块中直接赋值),则又退化为类似一段式的写法,输出会延迟一拍。
三段式在两段式基础上,将输出逻辑 单独剥离为一个独立的
always块,形成三个并行部分:第一段:时序逻辑块
- 与两段式相同,完成
state <= next_state。第二段:纯组合逻辑块
- 仅负责计算次态(next_state),不包含任何输出赋值。
- 敏感表为组合逻辑全列表。
第三段:输出逻辑块
- 通常为时序逻辑块 (
posedge clk),根据现态(state) (或现态+输入,即 Mealy 型)生成寄存器输出。- 也可以采用组合输出,但推荐使用寄存器输出以避免毛刺。
| 优点 | 详细说明 |
|---|---|
| 1. 消除输出毛刺 | 输出由寄存器驱动(第三段为时序逻辑),即使输入或状态发生变化,输出仅当时钟边沿到来时才跳变,从根本上杜绝了组合逻辑毛刺传递到下游电路,提升了系统可靠性和时序收敛性。 |
| 2. 结构清晰,易于维护 | 次态逻辑 与输出逻辑 完全解耦: - 修改次态跳转时,不影响输出逻辑。 - 修改输出行为时,不干扰状态跳转。特别适合复杂状态机(几十个状态以上)的团队协作与后期修改。 |
| 3. 灵活支持 Moore 与 Mealy | 第三段若基于 state 描述输出,则为 Moore 型 (输出仅取决于现态);若基于 next_state 或同时参考输入,则为 Mealy 型。两种模型均可清晰实现,且互不干扰。 |
| 4. 时序约束与优化更友好 | 寄存器输出使得输出信号成为同步信号,FPGA/ASIC 综合工具能更准确地分析建立/保持时间,并易于插入 pipeline 寄存器以满足高频需求。组合输出路径上的逻辑深度被限制在次态计算中,有利于关键路径优化。 |
| 5. 减少意外锁存器(latch) | 两段式中,组合逻辑块若对输出信号未在所有分支中完整赋值 ,容易综合出锁存器。三段式将输出剥离为独立的时序块,默认值或 case 全分支覆盖要求更明确,减少了综合出错几率。 |
-
推荐优先使用三段式的场合:
-
中大型状态机(状态数 > 5)。
-
对输出信号质量要求高(如作为其他模块的使能、复位、控制信号)。
-
需要实现 Moore 与 Mealy 混合输出的场景。
-
团队协作或代码需长期维护的项目。
-
-
两段式仍可使用的场合:
-
极简单的状态机(如 3~4 个状态)。
-
对毛刺不敏感的输出(例如仅驱动 LED 指示)。
-
需要刻意实现组合输出立即响应的场合(此时需仔细评估毛刺风险)
-
分频,输入50MHz,输出10MHz,Verilog代码
采用奇数分频(5 分频)且输出占空比为 50% 的实现方案
// ================================================================
// Module: clk_div_5
// Description: 50MHz to 10MHz clock divider (divide by 5, 50% duty cycle)
// ================================================================
module clk_div_5 (
input wire clk_in, // 50MHz input clock
input wire rst_n, // asynchronous active-low reset
output wire clk_out // 10MHz output clock
);
// Parameter: division factor (N = 5)
localparam N = 5;
localparam HALF_CYCLE = (N - 1) / 2; // (5-1)/2 = 2
// Counters and toggle registers
reg [2:0] cnt_pos; // 3-bit counter for posedge
reg [2:0] cnt_neg; // 3-bit counter for negedge
reg clk_div_pos; // posedge triggered intermediate signal
reg clk_div_neg; // negedge triggered intermediate signal
// ------------------------------------------------------------
// Posedge counter and toggle logic
// ------------------------------------------------------------
always @(posedge clk_in or negedge rst_n) begin
if (!rst_n) begin
cnt_pos <= 3'd0;
end else begin
if (cnt_pos == N-1)
cnt_pos <= 3'd0;
else
cnt_pos <= cnt_pos + 1'b1;
end
end
always @(posedge clk_in or negedge rst_n) begin
if (!rst_n) begin
clk_div_pos <= 1'b0;
end else begin
if (cnt_pos == HALF_CYCLE)
clk_div_pos <= 1'b1;
else if (cnt_pos == N-1)
clk_div_pos <= 1'b0;
end
end
// ------------------------------------------------------------
// Negedge counter and toggle logic
// ------------------------------------------------------------
always @(negedge clk_in or negedge rst_n) begin
if (!rst_n) begin
cnt_neg <= 3'd0;
end else begin
if (cnt_neg == N-1)
cnt_neg <= 3'd0;
else
cnt_neg <= cnt_neg + 1'b1;
end
end
always @(negedge clk_in or negedge rst_n) begin
if (!rst_n) begin
clk_div_neg <= 1'b0;
end else begin
if (cnt_neg == HALF_CYCLE)
clk_div_neg <= 1'b1;
else if (cnt_neg == N-1)
clk_div_neg <= 1'b0;
end
end
// ------------------------------------------------------------
// Combine posedge and negedge signals to get 50% duty cycle
// ------------------------------------------------------------
assign clk_out = clk_div_pos | clk_div_neg;
endmodule
1.分频比计算
50MHz / 10MHz = 5,因此需要实现 5 分频。
2.50% 占空比实现思路(奇数分频)
- 在时钟上升沿和下降沿分别使用计数器(模 5)计数。
- 当上升沿计数器值等于
(5-1)/2 = 2时,将clk_div_pos置 1;当计数器等于 4 时置 0。 - 同样,下降沿计数器在值为 2 时置 1,值为 4 时置 0。
- 最终将两个信号进行逻辑或运算,得到占空比为 50% 的 10MHz 时钟。
3.复位处理
采用异步低电平复位 rst_n,确保上电或复位后电路处于确定状态。
偶数分频(如 2、4、6 分频)可以直接在计数到一半时翻转输出,占空比天然 50%。
奇数分频 (如 5 分频)的输入时钟周期数为奇数,输出时钟的一个周期包含 2.5 个输入高电平周期和 2.5 个输入低电平周期------0.5 个周期的宽度无法用单个边沿触发逻辑直接产生。
因此需要利用时钟的双边沿,分别产生两个相位错开的波形,再通过逻辑运算"拼合"出半个周期的偏移。
计数器工作方式
计数器
cnt_pos在clk_in上升沿 递增,计数范围0 → 1 → 2 → 3 → 4 → 0。计数器
cnt_neg在clk_in下降沿 递增,计数范围同样为0 → 1 → 2 → 3 → 4 → 0两个计数器相差半个输入时钟周期启动,因为下降沿相对于上升沿延迟了 180° 相位
中间波形生成规则
对 5 分频(N = 5),设定高电平持续
HALF_CYCLE = (5-1)/2 = 2个输入时钟周期。
上升沿触发波形
clk_div_pos当
cnt_pos == 2时置 1 ,当cnt_pos == 4时置 0 。→ 高电平出现在计数周期中的 2、3、4(即 3 个输入周期?不,仔细分析见后文波形)
下降沿触发波形
clk_div_neg同样逻辑:
cnt_neg == 2时置 1 ,cnt_neg == 4时置 0合成输出
最终输出
clk_out = clk_div_pos | clk_div_neg(逻辑或)。两个波形错开半个输入周期,逻辑或后恰好拼接出一个完整的高电平区间,占空比严格 50%。