FPGA系列(1)---3-8译码器(decoder3_8)
直接来看一下3-8译码器的代码和测试代码:
decoder3_8:
module decoder3_8(
input a,
input b,
input c,
output reg[7:0] out
);
always@(a,b,c)begin
case({a,b,c})
3'b000:out = 8'b0000_0001;
3'b001:out = 8'b0000_0010;
3'b010:out = 8'b0000_0100;
3'b011:out = 8'b0000_1000;
3'b100:out = 8'b0001_0000;
3'b101:out = 8'b0010_0000;
3'b110:out = 8'b0100_0000;
3'b111:out = 8'b1000_0000;
endcase
end
endmodule
tb_decoder3_8:
`timescale 1ns/1ns
module tb_decoder3_8();
reg a;
reg b;
reg c;
wire [7:0] out;
initial begin
a=0;b=0;c=0;
#200;
a=0;b=0;c=1;
#200;
a=0;b=1;c=0;
#200;
a=0;b=1;c=1;
#200;
a=1;b=0;c=0;
#200;
a=1;b=0;c=1;
#200;
a=1;b=1;c=0;
#200;
a=1;b=1;c=1;
#200;
$stop;
end
decoder3_8 inst(
.a(a),
.b(b),
.c(c),
.out(out)
);
endmodule
在数字电路中可以根据电路功能的不同分为,组合逻辑电路与时序逻辑电路。 组合逻辑电路在逻辑功能上的特点是任意时刻的输出仅仅取决于该时刻的输入, 与电路原来的状态无关。而时序逻辑从电路特征上看来,其特点为任意时刻的输 出不仅取决于该时刻的输入,而且还和电路原来的状态有关。组合逻辑电路在电 路结构上,不涉及对信号跳变沿的处理,无存储电路,也没有反馈电路,通常可 以通过真值表的形式表达出来。时序逻辑电路在电路结构上,不管输入如何变化, 仅当时钟的沿(上升沿或下降沿)到达时,才有可能使输出发生变化。
组合逻辑电路设计之译码器
译码器真值表
在你的3-8译码器代码中使用阻塞赋值(=)而不是非阻塞赋值(<=)是正确的,原因如下:
为什么用 =(阻塞赋值)是正确的?
- 组合逻辑特性
- 3-8译码器是纯组合逻辑
- 组合逻辑的输出应该立即响应输入的变化
- 阻塞赋值
=在当前时间步内立即更新值
一般我们只需要记住:有时钟存在一般用<=,没有时钟存在一般用=
在测试文件中为什么用=来进行initial的时钟信号生成:一句话总结:测试文件是顺序执行环境,用=能确保赋值立即生效并按正确时序生成激励信号,而<=用于模拟硬件并行性,在测试中反而会造成时序不确定性。
`timescale 1ns / 1ns
module decoder3_8_tb();
reg a;
reg b;
reg c;
wire [7:0] out;
initial begin
a = 0;b = 0;c = 0; //在 0 时刻三个输入均为 0#200; //经过 200ns 的延时
#200;
a = 0;b = 0;c = 1; //在 200ns 时输入信号的值#200; //又经过 200ns 的延时
#200;
a = 0;b = 1;c = 0; //在 400ns 时输入信号的值#200; //又经过 200ns 的延时
#200;
a = 0;b = 1;c = 1; //在 600ns 时输入信号的值#200;
#200;
a = 1;b = 0;c = 0;
#200;
a = 1;b = 0;c = 1;
#200;
a = 1;b = 1;c = 0;
#200;
a = 1;b = 1;c = 1;
#200;
$stop;
end
decoder3_8 inst(
.a(a),
.b(b),
.c(c),
.out(out)
);
endmodule
测试代码本身是“软件化”的顺序执行,我们正是要利用阻塞赋值(=)的“阻塞”特性,来精确控制信号产生的顺序和时序,从而确保激励信号按我们期望的步骤生成。
可以这样理解:
- 在硬件设计(RTL)中:我们使用非阻塞赋值(<=)来 避免 阻塞,以模拟真实寄存器同时更新的硬件并行行为。
- 在测试激励(Testbench)中:我们使用阻塞赋值(=)来 利用 阻塞,以创建精确、顺序的激励波形。
所以,测试代码不是“不考虑”阻塞,而是 主动选择并使用阻塞赋值 来实现其目的。这是两种赋值方式在不同场景下的正确应用。
RTL分析是将你的代码翻译成逻辑电路图,而综合是将这张逻辑电路图映射到目标FPGA芯片的特定物理资源(如LUT、触发器)上。
3-8译码器综合后的图:图中的8个LUT3,每个都被配置成了完全不同的逻辑功能。
是的,Vivado会根据你的代码逻辑,自动为每个LUT3计算并配置独一无二的真值表,你只需描述功能(做什么),Vivado负责硬件实现(怎么做)。
虽然它们都是同一种硬件单元(LUT3),但内部存储的"真值表"内容各不相同,就像同样都是空白笔记本,但每个人在上面写下了不同的内容。
详细解释:为什么每个LUT3都不同?
核心原因:每个输出位(out[0]到out[7])的逻辑条件完全不同。
让我们对照你的3-8译码器真值表来看:
| 输入 c b a | out[7] | out[6] | out[5] | out[4] | out[3] | out[2] | out[1] | out[0] |
|---|---|---|---|---|---|---|---|---|
| 000 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
| 001 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
| 010 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 |
| 011 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 |
| 100 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 |
| 101 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 |
| 110 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 |
| 111 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
具体看几个LUT3的配置差异:
- 负责 out[0] 的 LUT3
- 功能:只有当输入为
000时输出1 - 内部真值表:
输入cba | 输出
000 | 1 ← 只有这一行是1
001 | 0
010 | 0
011 | 0
100 | 0
101 | 0
110 | 0
111 | 0
- 负责 out[1] 的 LUT3
- 功能:只有当输入为
001时输出1 - 内部真值表:
输入cba | 输出
000 | 0
001 | 1 ← 只有这一行是1
010 | 0
... | ...
- 负责 out[7] 的 LUT3
- 功能:只有当输入为
111时输出1 - 内部真值表:
输入cba | 输出
... | ...
110 | 0
111 | 1 ← 只有这一行是1
为什么综合工具要这样设计?
这种"一个输出对应一个LUT"的设计是最优化的:
- 最大化并行性:8个LUT可以同时工作,输入变化后所有输出位并行计算,速度极快。
- 资源利用率高:每个3输入逻辑功能正好用一个LUT3实现,没有浪费。
- 布线简单:所有LUT的输入都连接相同的a、b、c信号,布线规整。
- 时序一致:每个输出位的延迟基本相同,时序容易控制。
对比一下低效的设计:
如果用一个巨大的LUT8(8输入,256种组合)来实现整个译码器,虽然理论上可行,但:
- 浪费严重(LUT8比8个LUT3占用更多资源)
- 可能还需要额外的选择逻辑
- 在FPGA中反而不如分布式设计高效
总结
虽然硬件上8个LUT3是相同的物理单元,但通过综合工具的配置,每个都被"编程"成了独一无二的逻辑函数,分别负责检测8种不同的输入组合。这正是FPGA灵活性和并行计算能力的完美体现。
IBUF和OBUF是啥
核心比喻:FPGA的“海关口岸”
- IBUF是进口海关,处理外部信号进入FPGA。
- OBUF是出口海关,处理内部信号驱动外部世界。
一、IBUF(输入缓冲器)是信号进入FPGA的“第一道门”
- 位置:在
input信号后,内部逻辑前。 - 核心作用: 电气转换:将外部电压(如3.3V)安全转换为FPGA内核电压(如1.0V)。 信号保护:缓冲和滤除来自芯片引脚的噪声、静电,保护内部精密逻辑。 时钟同步:对时钟信号尤为重要,确保边沿清晰。
二、OBUF(输出缓冲器)是信号离开FPGA的“最后一道门”
- 位置:在内部逻辑后,
output信号前。 - 核心作用: 功率驱动:将内部微弱的逻辑信号放大,才能驱动外部LED、芯片等负载。 电平转换:将内部电平转换为外部电路所需的标准(如LVCMOS)。 配置特性:可设置输出压摆率、驱动强度等。
三、实际意义总结
- 自动生成:Vivado会自动为你的
input/output端口添加IBUF/OBUF,无需手动编写。 - 硬件桥梁:它们是连接软件代码(逻辑功能)与物理世界(真实电路)不可或缺的硬件接口。
- 时序部分:它们会引入微小延迟,在高速设计时需要考量。
| 元件 | 角色 | 对应代码 | 核心任务 |
|---|---|---|---|
| IBUF | 进口海关 | input a; |
保护、转换外来信号 |
| OBUF | 出口海关 | output b; |
放大、驱动外出信号 |
一句话总结:IBUF和OBUF是保障FPGA内部逻辑电路能安全、高效与外部物理世界进行电气交互的关键硬件接口。

最后的引脚约束文件xdc:
set_property PACKAGE_PIN M22 [get_ports {out[7]}]
set_property PACKAGE_PIN N22 [get_ports {out[6]}]
set_property PACKAGE_PIN L21 [get_ports {out[5]}]
set_property PACKAGE_PIN K21 [get_ports {out[4]}]
set_property PACKAGE_PIN K22 [get_ports {out[3]}]
set_property PACKAGE_PIN J22 [get_ports {out[2]}]
set_property PACKAGE_PIN H22 [get_ports {out[1]}]
set_property PACKAGE_PIN M21 [get_ports {out[0]}]
set_property PACKAGE_PIN E22 [get_ports a]
set_property PACKAGE_PIN D22 [get_ports b]
set_property PACKAGE_PIN G22 [get_ports c]
set_property IOSTANDARD LVCMOS33 [get_ports {out[6]}]
set_property IOSTANDARD LVCMOS33 [get_ports {out[5]}]
set_property IOSTANDARD LVCMOS33 [get_ports {out[4]}]
set_property IOSTANDARD LVCMOS33 [get_ports {out[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports {out[2]}]
set_property IOSTANDARD LVCMOS33 [get_ports {out[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {out[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {out[7]}]
set_property IOSTANDARD LVCMOS33 [get_ports a]
set_property IOSTANDARD LVCMOS33 [get_ports b]
set_property IOSTANDARD LVCMOS33 [get_ports c]
好的,我用更通俗的方式解释这两个概念:
set_property PACKAGE_PIN M22- 物理位置约束
- 相当于"门牌号分配"
- 告诉工具:"把这个信号线连接到芯片的第M22号引脚上"
具体含义:
M22 → 芯片封装上的具体物理位置
┌─────────────┐
│ 芯片 │
│ │
M22 ●│ │● N22
│ │
└─────────────┘
实际作用:
- 就像给每个信号分配具体的"房间号"
- 确保设计中的逻辑信号能正确连接到开发板的实际物理接口
set_property IOSTANDARD LVCMOS33- 电气特性约束
- 相当于"电压和通信协议规范"
- 告诉芯片:"这个引脚使用3.3V的CMOS电平标准"
具体含义:
LVCMOS33 = Low Voltage CMOS 3.3V
- 高电平:3.3V
- 低电平:0V
- 最大电压:不能超过3.3V
为什么重要?
| 电平标准 | 电压范围 | 应用场景 |
|---|---|---|
| LVCMOS33 | 0-3.3V | 常用外设、传感器 |
| LVCMOS18 | 0-1.8V | 低功耗设备 |
| LVDS | 差分信号 | 高速传输 |

浙公网安备 33010602011771号