[FPGA 学习记录] 简单组合逻辑——译码器

简单组合逻辑——译码器


在本小节我们继续使用简单组合逻辑,设计并实现一个具有译码器功能的电路。我们这样做的目的是为了继续巩固整个设计流程,以及我们语法的使用。
本小节的主要内容分为两个部分:第一部分是理论学习,在这一部分我们会学习译码器的相关知识;第二部分是实战演练,通过实际操作,设计并实现一个3-8译码器。

下面就是第一部分理论学习的内容。

1 理论学习

译码器

译码是编码的逆过程。在编码时,每一种二进制代码它都被赋予了一个特定的含义,它都表示了一个确定的信号或者对象、或者实现某种功能。把代码状态特定的含义翻译出来的过程叫做译码,实现译码操作的电路就是译码器。你可以理解为译码器就是可以将输入的二进制代码的状态翻译成输出信号,用来表示原来含义的电路。译码器是一种组合逻辑电路器件,它一般是多输入多输出。主要可以分为变量译码和显示译码两大类。
常见的变量译码器就有 n-2ⁿ 译码器,怎么理解呢?

比如说图 1.1.1 这个译码器,它有 n 个输入,它最多可以表示 2ⁿ 种状态。

image-20231023090416772.png

图 1.1.1 n-2ⁿ 译码器

比如说我们常见的3-8译码器,如图 1.1.2 所示

image-20231022213941759

图 1.1.2 3-8 译码器

它有三路输入,就可以表示 2³ 就是 8 种输出,所以说又叫3-8译码器。

显示译码器一般是用来将二进制数转换成对应的七段码,一般是用于驱动 LED 或者驱动 LCD。

那么以上就是译码器的相关内容,下面我们开始进行实战演练。

2 实战演练

2.1 实验目标

首先我们已经明确了我们的设计目标,就是实现一个3-8译码器。

2.2 硬件资源

那么功能怎么实现呢?还是使用按键和我们的 LED 灯。三路输入可以使用三个按键代替,八路输出使用八个 LED 灯代替。但是我们的开发板上只有四个 LED 灯,所以说对于3-8译码器,我们只进行仿真验证,不再进行上板验证,因为我们没有足够的 LED 灯。

2.3 程序设计

那么了解了实验目标和实现功能,我们就开始我们功能的设计。

那么首先找到文件存放位置,新建一个文件夹,用来存放我们的实验工程,文件夹进行命名,然后进行文件体系的构建,这儿就轻车熟路了,建立 4 个文件夹

explorer_K7APQ9wlbl

文件体系构建完成之后,打开 doc 文件夹,建立我们的 Visio 文件。打开 Visio 文件,绘制我们的框图和波形图

explorer_X1fktvFPBA

2.3.1 模块框图

波形工具箱添加完成之后,先来进行框图的绘制。拖拽圆角矩形,然后添加输入信号,输入信号有三路,那么对它们进行一下命名

VISIO_K36zdi3941

然后是输出信号,输出信号我们可以定义为 8 位宽,然后将输出信号进行加粗处理,用来进行位宽的区别。那么这样模块框图就绘制完成

VISIO_1NUc37TeEZ

2.3.2 波形绘制

模块框图绘制完成之后,我们可以进行波形图的绘制。

2.3.2.1 真值表

那么在进行波形图的绘制之前,先画一个真值表,就是我们译码器的真值表。
首先是三路输入,然后还有我们的输出,假如说第一路输入是低电平,第二路输入也是低电平,那么第三路输入也是低电平,我们就可以让它输出什么呢?0000_0001。如果第一路输入为低电平,第二路输入也为低电平,第三路输入为高电平,我们就可以让输出为什么呢?八位二进制的 2。那么如果第一路输入是低电平,第二路输入是高电平,第三路输入是低电平,那么输出就是八位二进制的 4

VISIO_aJ4myw9kHc

那么按照这个规律,我们进行后续的补充

VISIO_WqfiNgMz8X

2.3.2.2 波形图

真值表编写完成之后,我们就可以参照我们的真值表进行波形图的绘制。

首先是输入信号的命名,输入信号填充为绿色,然后适当调整一下距离;然后是输出信号,输出信号位宽是 8bit,填充的是红色

VISIO_NMAYbF9bOx

下面就是波形的绘制,我们使用数据保持波形,使用这个符号表示数据的变化,我们使用 X 来表示初始值是未知,然后可以添加一条参考线,方便我们波形的对齐以及绘制

VISIO_EiMD7JtoQN

接下来就参照真值表进行绘制。第一行,三路输入信号都是低电平,得到的输出信号是八位二进制的 1

VISIO_T5BmAQme6y

那么真值表的第二行,输入信号前两路是 0,第三路是 1,输出信号是八位二进制的 2

VISIO_4v8WMSpkX8

接下来就可以按照顺序,参照我们的真值表来绘制我们的波形图

VISIO_lSNsemHpRD

到了这里我们的波形图绘制完成。

2.4 代码编写

接下来就可以进行代码的编写。找到我们的 rtl 文件夹,新建一个 .v 文件,然后双击打开,参照我们的波形图进行代码的编写

VISIO_V5xj60bqdy

我们这儿继续使用我们的组合逻辑来实现我们的3-8译码器。

那么代码的编写首先是 模块开始、模块名,然后括号,存放输入、输出信号,输入信号是 input,wire 型,然后是名称,逗号结尾;然后是第二路的输入信号,然后是第三路的输入信号,逗号结尾

jAEXIOGy3s

然后是我们的输出信号。输出信号可以使用 wire 型也可以使用 reg 型,那么如果你使用always 赋值就一定是 reg 型,使用 assign 语句赋值一定是 wire 型,我们儿将会使用 always 语句进行赋值,所以说它的类型就是 reg 型,位宽是 8bit。最后一路信号不要使用逗号

cjpE0cSEBJ

输入输出信号列表就编写完成,进行赋值

参照我们的波形图,当我们的三路输入信号全是 0 时,那我们就输出我们的八位二进制的 1,条件就是对三路输入信号进行一个判断,怎么进行判断呢?我们可以将它们三路使用拼接符号
就是这个形式 {in_1, in_2, in_3} 然后就实现了输入信号的拼接

0Y4FEJnMMb

接下来对它们进行判断,是否等于三位二进制的 000,那么如果等于就执行下面这条语句,给输出信号进行一个赋值,使用的是阻塞赋值,因为是组合逻辑。那么参照这个波形图,全为 0 时,给它赋值为八位二进制的 1,那么就是 8’b0000_0001,下划线是增强可读性,没有任何意义

Ba29YxWu8D

然后接下来是 else if,如果第一个条件不满足,进入下一句 else if 语句,对第二个条件进行一个判断,我们来看一下波形图,当三路输入信号为 001 时,给 out 赋值为八位二进制的 2,我们继续使用位拼接 来实现我们的条件,然后 满足条件给输出信号进行一个赋值

Raim4DJMvl

那么后面就可以参照我们的波形图,按照这个顺序进行代码的编写

uQa38m3Mmz

那么参照波形图对所有的条件都进行列举、编写之后,我们还要添加一个 else 语句

VZgaZkqcRg

这样是为了避免产生 latch,当上述所有的条件都不满足时候,执行这条语句,当然你也可以输出别的。

那么赋值语句完成之后,编写完成。

decoder.v

module decoder
(
    input   wire        in_1,
    input   wire        in_2,
    input   wire        in_3,
    
    output  reg  [7:0]  out
);

assign@(*)
    if ({in_1, in_2, in_3} == 3'b000)
        out = 8'b0000_0001;
    else if ({in_1, in_2, in_3} == 3'b001)
        out = 8'b0000_0010;
    else if ({in_1, in_2, in_3} == 3'b010)
        out = 8'b0000_0100;
    else if ({in_1, in_2, in_3} == 3'b011)
        out = 8'b0000_1000;
    else if ({in_1, in_2, in_3} == 3'b100)
        out = 8'b0001_0000;
    else if ({in_1, in_2, in_3} == 3'b101)
        out = 8'b0010_0000;
    else if ({in_1, in_2, in_3} == 3'b110)
        out = 8'b0100_0000;
    else if ({in_1, in_2, in_3} == 3'b111)
        out = 8'b1000_0000;
    else
        out = 8'b0000_0001;

endmodule

2.5 代码编译

那么接下来就新建一个工程,对我们的代码进行一个编译,来查找我们的语法错误。

我们回到我们的桌面,双击快捷方式,打开开发软件,然后建立一个新工程 Next 选择我们的文件存放位置,找到我们的 quartus_prj 文件夹,选择文件夹,然后对工程进行命名。点击 下一步,接下来是文件的添加,点击 下一步,然后是器件的选择,这儿选择我们的 Cyclone IV E 系列,封装方式是 FBGA,然后我们的引脚数是 256,速度等级是 8,大家要注意,这个速度等级表示的是我们 FPGA 芯片正常工作时它的速度的快慢,在 Altera 系列芯片当中,数字越小它的速度是越快的,那赛灵思公司的器件,它的数字越大,它的速度是越快的。我们使用的 FPGA 芯片的速度等级是 8,我们选择 8,那么经过筛选,我们选择我们的 FPGA 型号 EP4CE10F17C8,点击 下一步,我们进行仿真软件的选择 ModelSim、Verilog HDL,点击下一步,然后 Finish

Co2LIo5wl4

点击 Files 添加我们的 .v 文件,点击 添加 应用 OK,进行一个编译,查找我们的语法错误

JAszyvyk7H

出现报错提示,我们查看代码后发现 always 错误的编写成了 assign,修正后

decoder.v

module decoder
(
    input   wire        in_1,
    input   wire        in_2,
    input   wire        in_3,
    
    output  reg  [7:0]  out
);

always@(*)
    if ({in_1, in_2, in_3} == 3'b000)
        out = 8'b0000_0001;
    else if ({in_1, in_2, in_3} == 3'b001)
        out = 8'b0000_0010;
    else if ({in_1, in_2, in_3} == 3'b010)
        out = 8'b0000_0100;
    else if ({in_1, in_2, in_3} == 3'b011)
        out = 8'b0000_1000;
    else if ({in_1, in_2, in_3} == 3'b100)
        out = 8'b0001_0000;
    else if ({in_1, in_2, in_3} == 3'b101)
        out = 8'b0010_0000;
    else if ({in_1, in_2, in_3} == 3'b110)
        out = 8'b0100_0000;
    else if ({in_1, in_2, in_3} == 3'b111)
        out = 8'b1000_0000;
    else
        out = 8'b0000_0001;

endmodule

重新编译,这儿有 8 个警告。我们看一下这儿是 3 个严重警告,5 个普通警告,没有错误,我们点击这个,3 个严重警告,这儿提示我们是引脚还没有绑定;下面两条提示我们缺少综合设计约束文件 SDC,因为我们当前不做时序分析,所以说 SDC 暂时不用写,所以说这 3 个严重警告暂时可以忽略

quartus_FQF6EYmDaN

2.5.1 查看 RTL 视图

下面我们在这个列表中,找到 RTL Viewer 查看一下我们的 RTL 视图,双击,那么这个视图就是根据我们的 .v 文件生成的一个 RTL 视图

quartus_iOZplcxBfT

我们编写了 8 个 if 条件,这儿就有 8 个比较器 1 2 3 4 5 6 7 8。那么后面这几个器件大家应该比较熟悉,就是我们上一讲学到的多路选择器

image-20231023075326522

那么我们的代码就通过编译了。
下面我们就对我们的代码进行一个修改。之前我们使用的是 if-else 语句,下面我们把它改成 case 条件分支语句。

首先,先将 if-else 条件分支语句进行一个区块注释。我们可以点击右键,然后 区块注释 ,就可以对这一块进行一个注释

notepadPlusPlus_cIPg2SPciR

下面我们参照我们的波形图,进行一个 case 条件分支语句的编写。首先是 always@,然后,依然是敏感列表,使用星号;然后是 case,然后是括号,同样使用位拼接作为条件

20231023080407_8mbBXZdGVs

下面就是情况的列举。那么当三路输入信号都是 0 时给 out 赋值的是八位二进制的 1,然后是以分号结尾;参照我们的波形图,继续我们的代码编写

20231023082126_sDVASic38l

所有的情况都列举完成之后,可以加一个 default 然后冒号,它的意思就是:如果上述情况都不满足,就执行这条语句,我们可以对它进行一个任意的赋值,这条语句的编写也是为了避免 latch。最后是结束我们的 case 语句

20231023082440_H5NIfKfMm5

那么代码编写完成之后,保存。回到我们的实验工程,对它进行一个重新的编译,有 8 个警告,我们点击 OK,那么再一次查看我们的 RTL 视图

xU1rAJ8xLT

在这儿就可以发现,我们的代码被综合成了一个译码器
就是我们将要实现的功能:3-8译码器

image-20231023082805326

if-else 条件分支语句和 case 条件分支语句,我们前面也已经讲过了,这儿只是复习。

使用上面两种不同的代码编写方式,虽然它能够实现同样的功能,但是它得到的 RTL 视图差别是比较大的。我们 if-else 条件分支语句这种写法它是存在优先级的,什么意思呢?就是说,它首先判断的是第一个 if 条件 if ({in_1, in_2, in_3} == 3'b000),如果它的条件满足,就会执行这条语句 out = 8'b0000_0001;;如果它的条件不满足,就会继续向下进行判断,直到某个条件满足,执行该条件下的语句,那么执行完成之后就会跳出。如果所有列举的条件都不满足,就会执行一个 else 语句。而我们的 case 语句,在任何时候都不存在优先级的问题,它是通过判断 case 中的条件 {in_1,in_2,in_3},这个条件与下面列表中的某一个条件如果是吻合的,就执行后面的语句。这是 if-else 和 case 语句的区别

2.6 逻辑仿真

那么通过代码编译之后我们就可以开始进行仿真验证。接下来就是仿真代码的编写。
我们找到我们的 sim 文件夹,新建一个 .v 文件,双击打开,开始我们的仿真文件的编写

20231023083311_GZz7inY4VR

首先是我们的时间尺度,然后是时间单位,然后是时间精度,然后是模块开始,模块(文件)名,然后是信号列表,这儿是空

20231023083605_8t6RZsAwbq

下面就是模拟输入信号的定义。只要使用 always、initial 语句进行赋值的,一律是 reg 型,使用 assign 的一律是 wire 型,分号结尾

20231023090546_2J4eleUtiu

然后要将输出信号引出。那么输出信号的引出使用的是 wire 型,它的位宽是 8 位宽,然后以分号结尾

20231023090706_dR78hdTEQF

那么接下来就是输入信号的初始化,使用 initial 语句,加一个 begin end

20231023090924_DoBVSwXO8H

因为输入信号是模拟输入,接下来就使用 always 语句对它们进行一个随机数的赋值。井号表示时间延时,10 表示延迟 10 个时间单位,也就是说 10ns,然后使用我们的系统函数对 2 进行求余,这样就保证赋值给输入信号 1 的不是 0 就是 1,是 0 和 1 之间的随机,那么对输入信号 2 和输入信号 3 进行同样的处理

20231023091202_0pL0QDdWzV

那么输入信号的随机赋值完成之后,下面就是实例化。
回到我们编写的 .v 文件,对我们的模块名和信号列表进行复制。我们的信号列表为什么要编写的这么整齐呢?一是为了美观,第二也是为了我们的实例化。回到我们的测试文件,粘贴我们复制的内容,首先在模块名称之后补上实例化名称。接下来就进行列操作,然后是信号的连接,添加一个括号,然后将仿真模块中的 in_1 和我们的功能模块的 in_1 相连接;那么 in_2 与 in_2 相连接,in_3 与 in_3 相连接,输出与我们的输出相连接。这样我们的模块实例化就已经完成了

20231023091513_MHQeVMxGCZ

那么这个实例化究竟是什么意思呢?那么有的学员可能还不是很理解,那么我们就结合一个图形来看一下

image-20231023095859543

中间 decoder 部分模块就相当于我们实例化的模块,.in_1(in_1),前面这个 in_1
就表示我们实例化的模块的名称,就是上图中的 i n _ 1 \textcolor{#92d050}{in\_1} in_1.in_2(in_2), 中的 in_2 就是上图中的 i n _ 2 \textcolor{#92d050}{in\_2} in_2;而 .in_3(in_3), 中这个 in_3 就是上图中的 i n _ 3 \textcolor{#92d050}{in\_3} in_3。而括号里面这个 in_1 就是我们仿真文件模拟生成的 t b _ i n _ 1 \textcolor{#00b050}{tb\_in\_1} tb_in_1,这个名称是可以更改的,你仿真模块里写的是什么名称,这儿就写的是什么名称。那么这个连线就相当于我们这个点,这个点就表示将两个信号进行一个连接,我们按照下图所示修改一下 tb_decoder.v

image-20231023102821593

20231023102409_xPWZZWng0E

那么实例化编写完成了。为了方便我们模块的仿真,我们再添加几个系统函数。同样使用 initial 语句,首先 是时间格式的设置,我们使用的是 timeformat,它的格式是关键词加上括号,以分号结尾;括号内首先是对时间单位的设置,我们使用 -9 就是 ns 级别,然后是逗号,然后是小数位的设置,我们这儿不打印小数位,使用 0,然后逗号,然后是时间单位名称,因为前面 -9 是 ns 级别,我们这儿必须使用纳秒 ns,然后是打印数据位宽,我们使用 6,这儿就是时间格式的设置。接下来使用监测函数,那么监测函数语句编写完成,首先是 监测系统函数关键字,然后引号里面是要打印的内容,后面是变量。

20231023103427_3D0DXIYJye

那这样,仿真文件已经编写完成,对仿真文件进行保存

tb_decoder.v

`timescale 1ns/1ns

module tb_decoder();

reg     tb_in_1;
reg     tb_in_2;
reg     tb_in_3;

wire[7:0]tb_out;

initial
    begin
        tb_in_1 <= 1'b0;
        tb_in_2 <= 1'b0;
        tb_in_3 <= 1'b0;
    end

always #10 tb_in_1 <= {$random} % 2;
always #10 tb_in_2 <= {$random} % 2;
always #10 tb_in_3 <= {$random} % 2;

initial
    begin
        $timeformat(-9,0,"ns",6);
        $monitor("@time %t:in_1=%b in_2=%b in_3=%b out=%b",
                        $time, tb_in_1,tb_in_2,tb_in_3,tb_out);
    end

decoder decoder_inst
(
    .in_1(tb_in_1),
    .in_2(tb_in_2),
    .in_3(tb_in_3),
    
    .out (tb_out )
);

endmodule

然后回到我们的实验工程,添加我们的仿真文件

20231023103708_3VWz9PEJuo

然后是仿真的设置。点击 Assignments 选项卡,选择设置,找到我们的仿真 Simulation,仿真软件部分在新建工程部分已经设置完成;然后是编译我们的 test bench,勾选这个位置
点击 Test Benches… 新建,然后名称,然后时间停止参数的设置,添加我们的仿真文件添加 OK OK 应用 OK

20231023103910_fYg2nZDjaX

然后点击 RTL Simulation 开始仿真

20231023104155_jW1lR6gm0i

首先我们先来查看一下波形界面。那么点击波形界面,那么波形界默认会加载我们仿真文件的各个波形,然后你使用这个,就可以把它单独提出来,就相当于一个单独窗口的打开;然后再次点击这个位置就可以将它与 ModelSim 进行绑定

20231023104534_UJezCCn2kx

然后你使用 Ctrl+A 就可以对所有的波形进行一个选择,可以使用 delete(键) 将它们全部删除

20231023104746_6pr7eBlmXL

那么删除的波形怎么添加呢?

我们找到 sim 这个选项卡,选中我们的仿真模块,可以点击右键,添加我们的波形 Add Wave ,你也可以使用快捷键,选中它之后使用 Ctrl+W 然后回到波形界面,就可以发现我们的仿真模块的波形已经被添加了

20231023104953_L2OdDshsbS

回到 sim 选项卡,使用同样的方法可以进行模块波形的添加,点击右键 Add Wave,也可以使用快捷键,那么回到我们的波形窗口,我们可以看到,模块的各个波形已经加载过来了。我们可以使用 Ctrl+A 进行一个全选,使用 Ctrl+G 快捷键进行一个分组,这样方便我们波形的查看

20231023105220_eqLxwQJFzS

大家可以看到,波形前面有很多的前缀,我们看起来是比较麻烦的。我们可以点击左下角这个位置删除我们的路径,这样就只有文件的名称

20231023105341_OlC4bTsFLS

那么波形显示的默认进制是十六进制,我们可以进行进制的更改。比如说我们选中 in_1 我们可以点击右键,找到这个选项卡,在这儿就可以进行二进制、八进制、十进制、无符号型、十六进制等各种进制的更改。比如说我们要改成二进制,那这儿就改成了一个二进制

20231023105705_dF8ah3Ng1Y

这些都是 ModelSim 仿真的一些小技巧,我们这儿只讲一部分,后面还会有讲解。
接下来就是波形的查看。我们点击重新开始按钮,然后点击 OK 删除已经生成的波形,然后将时间参数设置为 500us,然后点击运行一次按钮,那么波形仿真完成

20231023110140_hOOrOOiIFX

我们点击全局视图,然后添加一个参考线,局部放大,然后将这个仿真波形与我们的绘制的波形一同查看

20231023111904_s30T8UB7WZ

比如说我们找到这个位置。那么输入信号 tb_in_1 为 1,输入信号 tb_in_2tb_in_3 为0,那么输出信号八位十六进制的 10。我们可以将输出信号的进制改成二进制,选中它 点击右键。这样,它的数制与我们绘制的波形图数制是一样的,便于观察

20231023112152_jhfIpEsj35

然后再查看这个位置。当输入信号全为 1,那么输出信号是 10000000,我们来看一下 1000_0000 与我们绘制波形图是吻合的

20231023112530_PB1ZJlwQe3

那么经过波形的对比,我们可以发现:我们的仿真波形与我们绘制的波形是完全一致的。
那么下面我们可以查看一下打印信息。点击这个位置 可以进行打印信息的查看。我们将打印信息与我们的真值表进行对比查看,可以更加的直观

20231023112829_xcNX6gNt8o

我们任选一个时刻:当输入信号 1 为 0,输入信号 2 为 1,输入信号 3 为 0,就是 010 时,它输出的是 00000100;我们来对比一下 010,找到这个位置 00000100 0000_0100 是对应的

20231023113026_l60wg5L5GF

再随机选择一组数据:当三路输入信号都为 1 时,输出的信号是 10000000。我们来对比一下,当输入信号全为 1 输出信号也是 1000_0000

20231023113154_Azv9LUZQHO

那么经过对比,打印信息和真值表也是完全匹配的,所以说仿真验证通过。
那么以上就是实战演练的全部内容。在本讲视频当中,我们主要讲解了经典组合逻辑3-8译码器如何用 Verilog 代码去实现,而且对我们的 if-else 语句、case 语句所表达的逻辑的异同也进行了一个比较。希望大家在以后的应用中能够合理的、熟练的使用这两种语法


参考资料:

08-第七讲-简单组合逻辑——译码器

5. 简单组合逻辑 — 译码器

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值