面经、笔试(持续更新中)

自我介绍

问项目

在项目中主要负责哪部分

智力题

天平秤球,有一个质量不一样的,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 虽然语法上有相似之处(如 ifcase、赋值符号等),但二者的本质 完全不同:
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)、建立时间违例。

状态机两段三段的区别,三段的优点

状态机几段式,到底在分什么?

状态机本质三部分:

  1. 现态 current state:当前在哪个状态(寄存器存的)
  2. 次态 next state:下一个要跳去哪(组合逻辑)
  3. 输出 output:根据状态 / 输入产生的控制信号

所谓 "几段式",就是把这三部分写在几个 always 块里

输出output也可用assign

1)一段式状态机

  • 只用 1 个 always
  • 把:状态跳转 + 输出逻辑 全部写在一起
  • 通常是 always @(posedge clk) 里塞一堆 if/else/case

缺点:

  • 逻辑混乱,可读性差
  • 容易写出锁存器(latch)
  • 难维护、难查时序问题
  • 大型工程基本不用

2)两段式状态机(最常见)

分成 2 个 always

时序 always :只负责状态更新

组合 always :负责次态计算 + 输出

特点:

  • 结构清晰,新手友好
  • 满足大部分简单 / 中型工程
  • 输出容易出现组合逻辑毛刺

3)三段式状态机(最规范、最推荐)

严格分成 3 个 always

  1. 时序段:只更新现态(和两段一样)
  2. 组合段 :只算次态 next_state,不碰输出
  3. 时序输出段 :只用现态,寄存器输出,完全不带组合逻辑

三段式状态机的优点(重点)

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% 的实现方案

RTL代码-CSDN博客

复制代码
// ================================================================
// 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_posclk_in 上升沿 递增,计数范围 0 → 1 → 2 → 3 → 4 → 0

  • 计数器 cnt_negclk_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 时置 1cnt_neg == 4 时置 0

合成输出

最终输出 clk_out = clk_div_pos | clk_div_neg(逻辑或)。

两个波形错开半个输入周期,逻辑或后恰好拼接出一个完整的高电平区间,占空比严格 50%。

相关推荐
xixixi777772 小时前
通信领域的“中国速度”:从5G-A到6G,从地面到星空
人工智能·5g·安全·ai·fpga开发·多模态
Nobody334 小时前
Verilog always语句详解:从组合逻辑到时序逻辑
fpga开发
Yuk丶5 小时前
UE4客户端开发技术问题汇总
面试·ue4·图形学·ue4客户端开发
yuki_uix6 小时前
重排、重绘与合成——浏览器渲染性能的底层逻辑
前端·javascript·面试
李嘉图Ricado6 小时前
FPGA 时序约束与分析
fpga开发
何陋轩6 小时前
OpenAI Codex深度解析:终端里的AI代码特工,一个指令重构整个项目
人工智能·面试
yuki_uix6 小时前
虚拟 DOM 与 Diff 算法——React 性能优化的底层逻辑
前端·react.js·面试
yuki_uix6 小时前
从输入 URL 到页面显示——浏览器工作原理全解析
前端·面试
白又白、9 小时前
时序优化和上板调试小结
fpga开发