2-4 Digital IC Learning
参考书籍 Digital VLSI Design and Simulation with Verilog
组合逻辑电路
- 与门
- 或门
- 非门
通用门
可以构建任何逻辑的门
- NAND
- NOR
组合逻辑电路
输出只和输入有关,没有可用的记忆元件的电路,并且可能包含多个子系统。
组合逻辑介绍
- 半加法器
- 逻辑表达式:半加器(不包含进位信号)
- $$\begin{align*} \text{Sum} &= \overline{A}B + A\overline{B} \ &= A \oplus B \ \text{Carry} &= AB \end{align*} $$
- 全加法器:这里就是有三个输入了,因为多了一个cin的进位输入信号。
- 逻辑表达式:$$\begin{align*} \text{Sum} &= A \oplus B \oplus \text{Cin} \ \text{Cout} &= AB + BCin + CinA \end{align*} $$
- 半减法器
- 全减法器
- mux多路复用器,根据选择信号,将指定的输入信号给到输出,只有一个输出信号,如果有2的n次方的信号,则需要n个选择信号。
- 电路图
- 可以用来实现具体的逻辑(也是FPGA的LUT的原理来源)
- De-mux,将选择信号选中的输入信号到指定的输出信号输出。
- decoder译码器:将输入的信号译码为独热的输出信号。
- 乘法器multiplier:
-
- 流程图:这是一个两位乘法器的结果,用到两个半加器。如图所示流程。
- 比较器
- 一般是通过写出真值表来完成设计的,两位比较器的真值表和逻辑表达式如下
- Code Converters 编码转换器
- 真值表设计
- 十进制2BCD
时序逻辑电路
触发器
触发器是一种可以存储二进制比特位数据的器件,有两种状态,一种是预置,一种是清零。有两个互补的输出,
- 3个控制信号
- 时钟:用于同步电路一般
- set
- reset
SR触发器
是基本的电路,其他的触发器可以用SR触发器来实现。SR != 1,否则会出现不定状态。时钟边沿触发,此处为上升沿。
- D触发器:其实就是把SR触发器的输入信号连在一起加了一个反相器。输出的下一个状态就是输入,也叫做延迟时钟输出器。
- JK触发器:不存在JK != 1的限制,在这个条件下,输出反转。但是因为输出频繁切换容易导致竞争,因此应该尽量保持时钟周期低于JK触发器的输出延迟。(还有主从JK触发器的电路上的实现)
- T触发器:输入为1是反转信号,否则保持。
寄存器
一组触发器被称之为寄存器,可以用来存储数据,也可以拿来做计数器。时钟是寄存器常用的控制信号。
串行输入和串行输出
寄存器串联的方式存入数据和取出
串行输入和并行输出
串联的方式输入信号,当最后一位信号输入完毕的时候,每一个组成寄存器的触发器将数据输出,构成并行输出。
并行输入和并行输出
数据并行输入又输出,花费一个时钟周期的时间,因此又被称之为,缓冲器。
并行输入和串行输出
这里的电路图给出了外围的驱动,并行输入可以大幅提升工作效率,但是设计的复杂程度也会提高,其中两个块,一个是加载数据,一个是移位控制信号,通过一个2选1的mux来进行加载/移位的模式控制
计数器
同步计数器
同步计数器的时钟被同时应用在了所有的触发器上,一般T和JK触发器经常被用来设计同步计数器,当它们被断言为1时,会反转状态为之前的状态,
一个简单的3位JK同步计数器
D0是2分频,D1是4分频,D2是8分频,按照递增的顺序计数脉冲,
异步计数器
异步计数器的时钟输入来自前一个触发器的输出
环形计数器
环形计数器将D触发器的首尾相连,在这里,计数器必须以任何非零值开始。假设给定的初始值是 0001,则序列将是 1000、0100、0010、0001,并且将再次重复相同的序列。
约翰逊计数器
一般用于检测特定序列
有限状态机
是一种数学模型,任意时间提取一个状态数,包括状态图,状态表,激励表和电路图。
mealy型和moore型
Mealy有限状态机的输出取决于外部的输入和反馈。用于检测的序列位数和状态位数相等。通常要比同样逻辑的Moore有限状态机状态数目少一个。
这是一个典型的Mealy状态机的结构图,
- 下一状态的组合逻辑
- 基于当前的状态和外部的输入计算出下一个状态
- 状态寄存器
- 存储元件用于保持当前的状态信息
- 在市中心好的控制下更新值,输出给输出组合逻辑来确定输出,同时也反馈给下一状态的组合逻辑来计算下一状态
- 输出的组合逻辑
- 基于当前的状态和外部输入来产生输出信号
- 输出可能在输入改变时立即改变,不需要等待到下一个时钟周期(与Moore状态机仅依赖于当前状态不同)
Moore有限状态机与meely型不同的地方就是,Moore型的输出不受输入信号影响,只和当前状态有关
- 下一状态的组合逻辑
- 当前状态和输入来决定下一状态
- 状态寄存器
- 存储元件用于保持当前的状态信息
- 可以直接决定输出组合逻辑的值
- 输出的组合逻辑
- 输出仅依赖于当前状态,与当前的输入无关,只会在状态发生转换的时候输出发生变化,不会因为输入的变化而变化,moore的输出相对于mealy来说有更小的延迟,因为输出的改变仅在状态的变化的时候发生。
模式或序列检测器
通常情况下,mealy要比moore完成同样状态的逻辑任务的状态数少1,如果模式或序列中的比特数为 N,那么在 Mealy 中所需的状态数将为 N,在 Moore 序列检测器中为 N+1。
比如这里要检测011的数字序列,先绘制出状态转换图
然后这里可以根据状态转换图列出状态转换表
然后融入具体的器件,这里是D触发器,在同一个表格中化简,绘制卡诺图,最终化简出D1和D2的表达式,以及Y输出,最后根据公示绘制出我们需要的电路
导论到Verilog HDL
verilog HDL的基础知识
verilog是一种硬件描述语言,,用于描述硬件进行仿真、综合和实现。
verilog 将电路描述为基本的开关到复杂的集成电路,如ROM、RAM、微控制器、微处理器以及其他的组合和时序逻辑电路。
引入VLSI超大规模集成电路
是一种集成的类型,相较于分立的器件,在这一个集成类别下,数百万个晶体管被制造在单一的集成电路中,微处理器和微控制器也属于这一个VLSI类别,由于电路的复杂性,所以不可能进行验证、制造或者面包板的实验,需要使用CAD来进行验证和制造。
模拟和数字VLSI
VLSI也是一种HDL,它用于模拟数字和模拟信号,但是在这里,我们只讨论数字设计。
机器语言和HDLs
我们知道在数字系统中所有的操作都是由机器语言所执行的,机器语言和HDL在数字系统设计中起着非常重要的作用,这两种语言也在设计高频合成器的通信中被广泛使用。
设计方法论
主要有两种设计方法,
- 自上而下设计方法
- 定义顶层设计块,然后是子级块,此外,子块被划分为叶子单元,无法在进一步划分,这种设计方法被称之为自顶向下设计方法
- 自下向上设计方法
- 定义底层设计块,借助叶子单元,构建更大的宏单元,可以用于设计更高级别的块,被称之为自底向上设计方法
设计流程
-
Specifications of Design
- 定义整个设计的要求和目标。
-
Behavioral Description
- 用高层次的硬件描述语言来表达设计的行为。
-
RTL Description
- 描述寄存器传输级别(Register Transfer Level)的设计,用于细化行为描述。
-
Functional Verification
- 通过模拟检查RTL设计是否满足行为规格。
-
Testing
- 对功能验证过程中的设计进行测试,确保没有错误。
-
Logic Synthesis
- 将RTL描述转换成门级网表。
-
Gate-Level Analysis
- 分析门级网表来确保满足时序和功耗要求。
-
Verification and Testing
- 在门级阶段对设计进行验证和测试。
-
Floor Planning
- 对芯片的物理结构进行初步规划,包括模块布局和互连。
-
Physical Layout
- 根据floor planning,创建设计的详细物理布局。
-
Layout Verification
- 使用DRC(Design Rule Check)、LVS(Layout vs. Schematic Check)等工具验证布局。
-
Final Implementation
- 最终实现,准备设计用于制造。
抽象级别和建模的概念
verilog是一种行为、结构描述语言,内部的抽象级别可以分为四种级别
门电路级
模块使用逻辑门来设计,通过网络来相互连接,类似于焖鸡图,电路简单的时候可以用于设计
数据流级
数据抽象层,用特定的数据流来进行设计,我们知道硬件寄存器之间的数据流和在电路中的数据的处理方式
行为级
也称之为算法级,是最高级别的抽象层,可以使用所需的算法设计模块,而不需要担心硬件的细节,类似于使用C语言来编程,但是两者的本质完全不同,不可混淆。
开关级
是最低的抽象,设计在开关、存储节点、互联的线之间实现,可以了解verilog VS的开关级别的实现,但是设计复杂电路很困难,通常用于设计低功耗设备。
基础语法规定
- 注释
- 占位符
- 标志符
- 转义字符
- 关键字(小写)
- 字符串
- 运算符
- 数字
数据类型
- 值
- 线网wire
- 寄存器reg
- 向量
- 多个位宽的可以表示为向量,寄存器或者网络也可以表示为向量,左侧的数字将是向量的最高有效位
- 整数数据类型integer
- 实数数据类型real
- 时间数据类型$time
- 数组
- memory
- 可以在verilog中表示为一组寄存器,每个单元被称之为一个字,一个字可能是1或者很多bits
testbench 概念
也被称之为激发信号,main module用于生成设计的RTL,testbench用于生成波形来验证设计的输出是否达到要求,设计可以根据抽象的程度不同来发生更改,但是测试的建模都是相通的。
Verilog中的编程技术
Verilog描述硬件,C语言描述软件,在Verliog中,语句是并行执行的,而在其他语句中是顺序执行的。基本的结构是在module中构建的,定义了输入和输出,最后被映射到电路的实际的硬件逻辑门。
电路的门级模型
通常直接从Verilog的可用库调用,逻辑门与硬件原理图有一一对应的关系,被认为是设计模型的最低级别。
一个带有与、或、非门的逻辑电路
组合逻辑电路
输出取决于当前输入的组合,而不取决于先前的输入的电路称之为组合逻辑电路,由逻辑门、输入变量和输出变量组成。
加法器和减法器
加法器
半加器
$$S = A \overline{B} + \overline{A} B = A \oplus B \text{ and } C = AB $$ 两个输入A、B,两个输出,S和C,分别为和以及进位标志
module HA(A, B, S, C);
input A, B;
output S, C;
xor X_2(S, A, B); //实例化异或
and A_2(C, A, B);
endmodule
也可以使用NAND来构建半加器。如图所示
代码实现如图
module HA(A, B, S, C);
input A, B;
output S, C;
wire W1, W2, W3;
nand N_1(W1, A, B);
nand N_2(W2, A, W1);
nand N_3(W3, W1, B);
nand N_4(S, W2, W3);
nand N_5(C, W1, W1);
endmodule
也可以使用NOR,如图,
代码实现如下
module HA(A, B, S, C);
input A, B;
output S, C;
wire W1, W2, W3;
nor N_1(W1, A, A);
nor N_2(W2, B, B);
nor N_3(W3, A, B);
nor N_4(C, W1, W2);
nor N_5(S, W3, C);
endmodule
全加器
多了一个进位输入Cin相较于半加器,通过真值表可以得出表达式化简 $$\begin{align*} S &= A\overline{B}C_{\text{in}} + A\overline{B}C_{\text{in}}’ + AB’C_{\text{in}}’ + ABC_{\text{in}} \ &= A \oplus B \oplus C_{\text{in}} \ \text{and } Co &= A\overline{B}C_{\text{in}} + AB’C_{\text{in}}’ + ABC_{\text{in}} \ &= AB + BC_{\text{in}} + AC_{\text{in}} \end{align*} $$
module FA(A, B, Cin, S, Co);
input A, B, Cin;
output S, Co;
wire W1, W2, W3;
xor X_1(S, A, B, Cin);
and A_1(W1, A, B);
and A_2(W2, A, Cin);
and A_3(W3, B, Cin);
or O_1(Co, W1, W2, W3);
endmodule
减法器subtractor
半减器
同上
全减器
根据真值表得出表达式并化简 $$\begin{align*} D &= A\overline{B}C_{\text{in}} + A\overline{B}C_{\text{in}}’ + AB’C_{\text{in}}’ + ABC_{\text{in}} \ &= A \oplus B \oplus C_{\text{in}} \ \text{and } Bo &= A\overline{B}C_{\text{in}} + A\overline{B}C_{\text{in}}’ + ABC_{\text{in}} \ &= A\overline{B} + BC_{\text{in}} + AC_{\text{in}} \end{align*} $$
module FS(A, B, C, D, Bo);
input A, B, C;
output D, Bo;
wire W1, W2, W3, W4;
xor X_1(D, A, B, C);
not n_1(W1, A);
and A_1(W2, W1, C);
and A_2(W3, W1, B);
and A_3(W4, B, C);
or O_1(Bo, W2, W3, W4);
endmodule
多路复用器mux和解多路复用器
多路复用器mux
选择信号将选择唯一的输入信号作为输出信号输出,
一个2选1 mux电路逻辑图
module mux_2x1(Y, I0, I1, S);
input I0, I1, S;
output Y;
wire W1, W2, W3;
not (W1, S);
and (W2, I0, W1);
and (W3, I1, S);
or (Y, W2, W3);
endmodule
4选1 的mux电路图
module mux_4x1(Y, I0, I1, I2, I3, S1, S0);
input I0, I1, I2, I3, S1, S0;
output Y;
wire w1, w2, w3, w4, w5, w6;
not (w1, S1);
not (w2, S0);
and (w3, I0, w1, w2);
and (w4, I1, w1, S0);
and (w5, I2, S1, w2);
and (w6, I3, S1, S0);
or (Y, w3, w4, w5, w6);
endmodule
解多路复用器
1*2 解多路复用器 一个输入,一个选择线,两个输出线,根据选择线,将输入在输出端反映 电路图 verilog代码
解码器和编码器
解码器
也称为译码器,
module Dec_2to4(A, B, D0, D1, D2, D3);
input A, B;
output D0, D1, D2, D3;
wire w1, w2;
not (w1, A);
not (w2, B);
and (D0, w1, w2);
and (D1, w1, B);
and (D2, A, w2);
and (D3, A, B);
endmodule
编码器
电路图 代码表达
比较器
一个1位比较器,有两个输入A,B和三个输出,大于、小、和于等于。 电路图 代码
Verilog 中的编程技术2
verilog中的编程技术2
本章描述电路的数据流模型
电路的数据流模型
数据流模型用运算符描述输出变量的布尔函数,数据从寄存器流向寄存器(所以这个是RTL级?)
可以用更少的设计步骤,一般用多个运算符来产生期望的结果,用assign 来进行连续赋值。
组合电路的数据流类型
加法器和减法器
半加器代码
module Half_Add(X, Y, S, C);
input X, Y;
output S, C;
assign S = X ^ Y; // 异或操作,计算和
assign C = X & Y; // 与操作,计算进位
endmodule
半减器
module Half_Subtractor(X, Y, D, B);
input X, Y;
output D, B;
assign D = X ^ Y; // 异或操作,计算差值
assign B = ~X & Y; // 与非操作,计算借位
endmodule
多路复用器
module mux_2x1_df(I0, I1, S, Y);
input I0, I1, S;
output Y;
assign Y = (~S & I0) | (S & I1);
endmodule
4to1 mux
module mux_4x1(y, i0, i1, i2, i3, s0, s1);
input i0, i1, i2, i3, s0, s1;
output y;
assign y = (~s0 & ~s1 & i0) |
(s0 & ~s1 & i1) |
(~s0 & s1 & i2) |
(s0 & s1 & i3);
endmodule
条件运算符也可以用来描述多路复用器
4to1 mux
译码器
根据公式用verilog写出表达式
比较器
同上
testbench
testbench的目的是仿真,不需要有物理的实现。仿真器把一系列的输入信号应用在verilog编写的电路设计上。testbench被纳入verilog的module块来进行测试,initial来定义输入,模拟的时候,要把中间的值用reg来写符号,输入和输出用wire来定义。仿真结束的时候需要用到$finish 或者 $stop 来决定。
module ckt_tb();
reg A, B, C;
wire x, y;
// 假设模块名称为Module_nameCKT,需要替换成实际使用的模块名
Module_nameCKT inst (A, B, C, x, y);
initial begin
A = 1'b0; B = 1'b0; C = 1'b0;
#100;
A = 1'b1; B = 1'b1; C = 1'b1;
#100 $stop;
end
endmodule
半加器和testbench 的数据流模型
// Testbench for Half adder
module HA_tb();
reg A, B;
wire S, C;
// Instantiate the Half Adder module, assuming it is named Half_Add
Half_Add Half (A, B, S, C);
initial begin
A = 0; B = 0;
#5 A = 0; B = 1;
#5 A = 1; B = 0;
#5 A = 1; B = 1;
#5 $stop;
end
endmodule
输入的A, B 需要在initial块内被赋予新的值来定义输入,所以这里被定义为reg类型,而输出不需要被定义输出,是用来测试模块输出结果的,所以定义为wire型。
半减法器的testbench和数据流
module HS_tb();
reg X, Y;
wire D, B;
Half_S Half(X, Y, D, B);
initial
begin
X = 0; Y = 0;
#5 X = 0; Y = 1;
#5 X = 1; Y = 0;
#5 X = 1; Y = 1;
#5 $stop;
end
endmodule
2*1 mux的数据流模型和testbench
// Testbench of 2 x 1 mux
module mux_tb();
reg I0, I1, S;
wire Y;
// Instantiate the 2x1 multiplexer module, assuming its name is mux_2x1_df
mux_2x1_df Mux(Y, I0, I1, S);
initial begin
// Test sequence
I0 = 1'b1; I1 = 1'b0; // Set inputs
#5 S = 1'b0; // Set select to choose I0
#5 S = 1'b1; // Set select to choose I1
#5 $stop; // Stop simulation
end
endmodule
4*1 mux的数据流模型和testbench
// Testbench of 4 x 1 mux
module mux_tb();
reg I0, I1, I2, I3, S1, S0;
wire Y;
// Instantiate the 4x1 multiplexer module
mux_4x1 Mux(Y, I0, I1, I2, I3, S1, S0);
initial begin
// Initialize inputs
I0 = 1; I1 = 0; I2 = 1; I3 = 0;
#5 S1 = 0; S0 = 0;
#5 S1 = 0; S0 = 1;
#5 S1 = 1; S0 = 0;
#5 S1 = 1; S0 = 1;
#5 $finish; // End simulation
end
endmodule
2-4译码器数据流模型和testbench
// Testbench of 2-to-4 Decoder
module Dec_tb();
reg A, B;
wire D0, D1, D2, D3;
// Instantiate the 2-to-4 Decoder module, assuming it is named Dec_2to4
Dec_2to4 Dec(A, B, D0, D1, D2, D3);
initial begin
A = 0; B = 0;
#5 A = 0; B = 1;
#5 A = 1; B = 0;
#5 A = 1; B = 1;
#5 $finish;
end
endmodule
verilog中的编程技术3
行为级描述可以进行组合和时序电路的设计,每个语句的执行都是通过触发信号来实现的,是通过always或者initial来启动语句执行的,所以又称为过程语句。
initial在仿真期间只执行一次,从时间0开始执行,always也是从一开始执行,但是会重复循环执行直至时间结束。
在always或者initial中的多个语句都是在关键字begin和end之间分组的。
一个时钟的例子, 每5个时间单位一个翻转信号
always @posedgeclk
#5 clk <= ~ clk;
使用 <=
是创建一个非阻塞赋值(Non-Blocking Assignment, NBA),这通常在 always
块中用于模拟时钟或其他时序行为。而连续赋值语句,使用 assign
关键字和 =
操作符,是用于描述组合逻辑的,不应该用于在 always
块中描述时钟信号。
阻塞赋值(=
):
- 在执行时,会立即更新右侧表达式的值到左侧的变量。
- 如果在同一个过程块中连续使用多个阻塞赋值,后一个赋值会看到前一个赋值的结果。
- 这种立即执行的特性使得阻塞赋值像软件编程中的顺序执行。
非阻塞赋值(<=
):
- 在当前时刻计算右侧表达式的值,但是直到当前过程块的所有操作都计算完毕后,才会在过程块结束时同时更新所有左侧的变量。
- 这种延迟更新的特性使得多个非阻塞赋值可以模拟硬件中并行的行为。
组合电路的行为模型
使用if-else的半加器行为模型
module half_adder(S, C, A, B);
input A, B;
output reg S, C;
always @(A or B) begin
if (A == 1 && B == 1) begin
S = 0; C = 1;
end else if (A == 1 || B == 1) begin
S = 1; C = 0;
end else begin
S = 0; C = 0;
end
end
endmodule
用半加器组成的全加器行为建模代码
module fulladder(S, C, x, y, z);
input x, y, z;
output S, C;
wire S1, D1, D2;
// Instantiate the half adders
halfadder HA1(S1, D1, x, y);
halfadder HA2(S, D2, S1, z);
or g1(C, D2, D1);
endmodule
4位全加器的行为建模代码
纹波加法器
module bit_adder(S, C4, A, B, C0);
input [3:0] A, B;
input C0;
output [3:0] S;
output C4;
wire C1, C2, C3;
// Instantiate the full adder modules
fulladder FA0(S[0], C1, A[0], B[0], C0),
FA1(S[1], C2, A[1], B[1], C1),
FA2(S[2], C3, A[2], B[2], C2),
FA3(S[3], C4, A[3], B[3], C3);
endmodule
多路复用器的行为模型
多路复用器也被称为数据选择器
2*\1 多路复用器的行为代码
//if-else
module mux2x1_bh(A, B, select, OUT);
input A, B, select;
output reg OUT;
always @(select or A or B) begin
if (select == 1)
OUT = B;
else
OUT = A;
end
endmodule
//case
module mux2x1(A, B, select, out);
input A, B;
input select;
output reg out;
always @(A or B or select) begin
case (select)
1'b0: out = A;
1'b1: out = B;
endcase
end
endmodule
4*1 mux的行为代码
// instantiate 3 2-1muxs into 1 4to1 mux
module Mux_4X1(S, I, Y);
input [3:0] I;
input [1:0] S;
output reg Y;
wire C1, C2;
// Instantiate 2x1 multiplexers
mux2x1_bh M0(I[0], I[1], S[0], C1),
M1(I[2], I[3], S[0], C2),
M2(C1, C2, S[1], Y);
endmodule
// case
module mux4x1(i0, i1, i2, i3, S, y);
input i0, i1, i2, i3;
input [1:0] S;
output reg y;
always @(i0 or i1 or i2 or i3 or S) begin
case (S)
2'b00: y = i0;
2'b01: y = i1;
2'b10: y = i2;
2'b11: y = i3;
endcase
end
endmodule
2-4译码器的行为模型
分别用if-else和case两种方式表达
4-2编码器的行为模型
分别用if-else和case两种方式表达
时序电路的行为模型
D锁存器的行为建模
// D-latch verilog
module d_latch(d, en, q);
input d, en;
output reg q;
always @(en or d) begin
if (en) begin
q <= d;
end
end
endmodule
// D-latch testbench
module d_tb();
reg d, en;
wire q;
// Instantiate the D-Latch
d_latch D1(d, en, q);
// Initial block for setting initial values and finishing the simulation
initial begin
en = 1'b0;
d = 1'b1;
#40 $finish;
end
// Toggle `en` every 5 time units
always #5 en = ~en;
// Toggle `d` every 7 time units
always #7 d = ~d;
endmodule
这里是在模拟的testbench中,不需要用到" <= " 来写时钟信号变化,描述硬件行为的时候,要模拟硬件的并行行为,所以要用" <= “,生成测试信号的时候,立即改变信号的值,用阻塞赋值的刺激是适当的。
D触发器的行为建模
// D-ff
module d_ff(clk, d, rst, q);
input clk, d, rst;
output reg q;
always @(posedge clk or negedge rst) begin
if (!rst) begin
q <= 1'b0;
end else begin
q <= d;
end
end
endmodule
// D-ff testbench
module d_tb();
reg clk, d, rst;
wire q;
// Instantiate the D-FF
d_ff D1(clk, d, rst, q);
// Initialize signals and run the simulation
initial begin
clk = 1'b0;
rst = 1'b0;
d = 1'b0;
#5 rst = 1'b1;
#7 rst = 1'b0;
#13 rst = 1'b1;
#40 $finish;
end
// Toggle clock signal every 3 time units
always #3 clk = ~clk;
// Toggle data signal every 5 time units
always #5 d = ~d;
endmodule
JK触发器的行为建模
module JK (q, q1, j, k, clk);
input j, k, clk;
output reg q, q1;
initial begin
q = 1'b0;
q1 = 1'b1;
end
always @(posedge clk) begin
case ({j, k})
2'b00: begin q = q; q1 = q1; end
2'b01: begin q = 1'b0; q1 = 1'b1; end
2'b10: begin q = 1'b1; q1 = 1'b0; end
2'b11: begin q = ~q; q1 = ~q1; end
endcase
end
endmodule
// testbench
module JK_tb();
reg clk, j, k;
wire q, q1;
// Instantiate the JK flip-flop
JK J1(q, q1, j, k, clk);
// Initialize signals and run the simulation
initial begin
clk = 1'b0;
j = 1'b1;
k = 1'b1;
#25 $finish;
end
// Toggle clock signal every 2 time units
always #2 clk = ~clk;
endmodule
使用JK触发器来完成D触发器的行为建模
输入 J 和 K 之间使用了一个反相器。
module d_ff(clk, d, q, q_bar);
input clk, d;
output q, q_bar;
reg q, q_bar;
wire w1;
// Inverting D to use as the K input for the JK flip-flop
not N_1(w1, d);
// Instantiating the JK flip-flop with the D input wired to J,
// the inverted D wired to K, and the clock.
// The Q and Q_bar outputs from the JK flip-flop will serve as the
// D-type flip-flop's outputs.
JK JK_d(q, q_bar, d, w1, clk);
endmodule
使用JK触发器来完成T触发器的行为建模
JK短接在一起
module T_ff(clk, T, q, q_bar);
input clk, T;
output q, q_bar;
reg q;
// q_bar is the inverse of q
wire q_bar;
assign q_bar = ~q;
// Instantiate the JK flip-flop with both J and K inputs tied to T
// When J and K are both high, the JK flip-flop toggles - which is the behavior of T flip-flop
JK JK_d(q, q_bar, T, T, clk);
endmodule
使用JK触发器对SR触发器建模
不可以SR同时为1
module SR_ff(clk, S, R, q, q_bar);
input clk, S, R;
output reg q;
output q_bar;
// q_bar is the complement of q
assign q_bar = ~q;
// Instantiate the JK flip-flop
// The J and K inputs are connected to the S and R inputs respectively.
// Note that this JK instantiation assumes that the JK flip-flop has a
// built-in mechanism to handle the invalid state when J and K are both high.
JK JK_SR(q, q_bar, S, R, clk);
initial begin
q = 1'b0;
q_bar = 1'b1;
end
endmodule
使用开关进行数字设计
开关级模型相当复杂,电路是在晶体管级别设计的。设计可以使用开关进行,例如 p-通道金属-氧化物-半导体场效应晶体管 MOSFET(PMOS)、n-通道 MOSFET(NMOS)和传输门(TG)或互补 MOSFET(CMOS)。
开关门建模
出于对低功耗和高密度封装的需求,设计人员探索新型的低漏电电流和工作电压的MOSFET架构。
使用CMOS技术的数字设计
MOSFET MOSFET 是一种四端器件。这四个端子是源极、漏极、栅极和基底,一般基底和源极连接,所以可以看作三端器件。用于开关和放大电子信号。 电压施加在栅极上,控制从源到漏的电流流动。
电子的较高迁移率导致 NMOS 成为比 PMOS 更合适的选择,后者具有较低的空穴迁移率。电路网络可以概括的分为上拉和下拉网络,负责把信号拉到1和0,可以有多个pmos 或者nmos组成。具体取决于公式的实现。
For, $$( F = \overline{A \cdot B \cdot C \ldots} )$$
NMOS will be series connected in a pull-down network and PMOS will, in parallel, be connected in a pull-up network.
For, $$( F = \overline{(A + B + C \ldots)} )$$
NMOS will be connected in parallel in the pull-down network and PMOS will be series connected in a pull-up network.
CMOS 反相器
使用开关设计和实现组合逻辑电路
verilog有一个开关的库文件,包含可以用于实现逻辑的晶体管级开关,这一特性对于电路内部的连接和摆放大有裨益。
开关的种类
原语:此处的实例化名称可选 nmos n1 (drain, source, gate); //syntax for NMOS switch instantiation pmos p1(drain, source, gate); //syntax for PMOS switch instantiation
CMOS开关
cmos c1(Out, In, ngate, pgate); //syntax for CMOS switch instantiation
cmos原语用一个pmos和nmos组成了一个传输门
电阻开关
r为前缀的开关,意味着具有更高阻抗的mos开关,和常规的开关不同。在双向电阻性开关中(例如rtran
,rtranif0
和rtranif1
),电阻性质同样重要,这种开关允许信号在两个方向上以较高阻抗状态传输。
Keywords of resistive switches are:
rnmos //resistive NMOS switch
rpmos //resistive PMOS switch
rcmos //resistive CMOS switch
rtran // bidirectional resistive switch without control.
rtranif1 // bidirectional resistive switch with control
rtranif0 // bidirectional resistive switch with control_bar
供电和接地需求
电源和地端口分别用关键字 supply1 和 supply0 表示。 supply1 VDD; //logic 1 connected to VDD supply0 GND; //logic 0 connected to GND
使用开关来完成逻辑实现
对于2输入NAND门的设计,分别需要两个PMOS和两个NMOS晶体管用于上拉和下拉网络。CMOS实现始终提供补码输出。对于NAND操作,NMOS连接在串联中,而PMOS连接在并联中。
// NAND
module NAND_Switch(F, A, B);
input A, B;
output F;
wire w1;
supply1 Vdd;
supply0 gnd;
// PMOS switches connected to Vdd
pmos P1(F, Vdd, A);
pmos P2(F, Vdd, B);
// NMOS switches connected in series with an intermediate node w1
nmos N1(F, w1, A);
nmos N2(w1, gnd, B);
endmodule
与门需要添加一个CMOS反相器,所以需要用到6个晶体管,包括3个nmos和3个pmos
// Verilog Program for AND using switches
module AND_Switch(F, A, B);
input A, B;
output F;
wire w1, F1;
supply1 Vdd;
supply0 gnd;
// PMOS switches connected to Vdd for pull-up network
pmos P1(F1, Vdd, A);
pmos P2(F1, Vdd, B);
// NMOS switches connected in series for pull-down network
nmos N1(F1, w1, A);
nmos N2(w1, gnd, B);
// Output buffer to drive the signal F
pmos P3(F, Vdd, F1);
nmos N4(F, gnd, F1);
endmodule
对于NOR的设计同理
// Nor
module switch_nor(out, a, b);
output out;
input a, b;
// Internal wires
wire w1;
supply1 VDD; // Power (1) is connected to VDD
supply0 GND; // Ground (0) is connected to GND
// PMOS for pull-up network
pmos (out, VDD, a); // When 'a' is LOW, connect 'out' to VDD
pmos (out, VDD, b); // When 'b' is LOW, connect 'out' to VDD
// NMOS for pull-down network
nmos (out, w1, a); // When 'a' is HIGH, connect 'out' to 'w1'
nmos (w1, GND, b); // When 'b' is HIGH, connect 'w1' to GND
endmodule
XOR, 可以使用 AND、OR 和 NOT 操作来实现
使用双向开关来实现
传输门是一个双向开关的实例,由cmos的pmos和nmos组成,可以表示出布尔表达式的任何逻辑表达式。
使用开关的多路复用器
6个mos组成一个2-1 mux
// 2x1 Multiplexer using CMOS switch
module mux2x1_switch(out, s, i0, i1);
output out;
input s, i0, i1;
wire s1;
// Instantiate the CMOS inverter
snot U1(s1, s);
// CMOS transmission gates for multiplexer functionality
cmos (out, i0, s1, s);
cmos (out, i1, s, s1);
endmodule
// CMOS inverter
module snot(s1, s);
output sbar;
input s;
supply1 vdd;
supply0 gnd;
// CMOS inverter logic
nmos (s1, gnd, s);
pmos (s1, vdd, s);
endmodule
在verilog中不光可以使用常规的mos来组成加法器,还可以使用mux来组成加法器,而这里的mux是由cmos组成的。
// 1-bit Full adder block using 4X1 multiplexer
module fulladder_Mux41_cmos(a, b, cin, sum, carry);
input a, b, cin;
output sum, carry;
supply0 GND;
supply1 Vdd;
wire w1;
not n1(w1, cin);
mux41 x1(a, b, cin, w1, w1, cin, sum);
mux41 x2(a, b, GND, cin, cin, Vdd, carry);
endmodule
// 4x1 Multiplexer block using switches.
module mux41(s0, s1, i0, i1, i2, i3, y);
input s0, s1, i0, i1, i2, i3;
output y;
supply0 GND;
supply1 Vdd;
wire w1, w2, w3, w4;
nmos n1(w1, Vdd, s0);
pmos p1(w1, GND, s0);
nmos n2(w2, Vdd, s1);
pmos p2(w2, GND, s1);
cmos c1(w3, i0, s0, w1);
cmos c2(w3, i1, w1, s0);
cmos c3(w4, i2, s0, w1);
cmos c4(w4, i3, w1, s0);
cmos c5(y, w3, s1, w2);
cmos c6(y, w4, w2, s1);
endmodule
// Verilog test stimulus for 1-bit Full adder
module fulladder_cmos_test;
reg a, b, cin;
wire sum, carry;
fulladder_Mux41_cmos f1(a, b, cin, sum, carry);
initial begin
a = 0; b = 0; cin = 0;
#3 a = 0; b = 0; cin = 1;
#3 a = 0; b = 1; cin = 0;
#3 a = 0; b = 1; cin = 1;
#3 a = 1; b = 0; cin = 0;
#3 a = 1; b = 0; cin = 1;
#3 a = 1; b = 1; cin = 0;
#3 a = 1; b = 1; cin = 1;
#3 $stop;
end
endmodule
采用结构级建模的verilog开关级描述
verilog支持用开关级电路在结构级建模,比如一个4位纹波加法器,可以用nmos和pmos精确描述之后用于子模块调用,结构化的方式写出4位纹波加法器。
带延迟的开关模型
高级verilog主题
延迟建模和编程
实际硬件中存在不同的延迟,广义上可以分为两种类型,也就是门延迟和线路互连延迟。设计硬件的时候需要考虑延迟来匹配实际的硬件时序。随着技术的进步,时序约束变得越来越重要,因为这关系到电路工作频率的提高。
延迟模型
在Verilog中有三种不同类型的延迟建模:(i)分布式延迟建模(ii)集总延迟建模(iii)端到端延迟建模或路径延迟建模。
分布式延迟模型
针对电路的每个元素都有传播延迟。以下是一个电路的延迟模型和数据流延迟模型
// Program P8.1
module distributed_delay (F, A, B, C, D);
output F;
input A, B, C, D;
wire w1, w2;
or #3 x1(w1, A, B);
or #3 x2(w2, C, D);
and #2 x3(F, w1, w2);
endmodule
// Program P8.2
module distributed_delay (F, A, B, C, D);
output F;
input A, B, C, D;
wire w1, w2;
assign #3 w1 = A & B;
assign #3 w2 = C & D;
assign #2 F = w1 & w2;
endmodule
集总延迟模型
是特定电路或模块的总体延迟。在输出门上指定。
// Program P8.3
module lumped_delay (F, A, B, C, D);
output F;
input A, B, C, D;
wire w1, w2;
assign w1 = A & B;
assign w2 = C & D;
assign #5 F = w1 & w2;
endmodule
端到端延迟模型
单个输入到输出的延迟模型,也被称为路径延迟,用关键字specify和endspecify来引入。
// Program P8.4
module path_delay (F, A, B, C, D);
output F;
input A, B, C, D;
wire w1, w2;
specify
(A => F) = 5;
(B => F) = 6;
(C => F) = 7;
(D => F) = 8;
endspecify
assign w1 = A & B;
assign w2 = C & D;
assign #5 F = w1 & w2;
endmodule
用户自定义的原语UDP
Verilog 具有许多内置原语,可以在模块中进行说明,如门类型(AND、NAND、OR、NOR、XOR、XNOR 等)和开关类型(NMOS、PMOS、CMOS 等)。Verilog 使用户能够创建自己的原语,称为用户定义的原语(UDP)。使用真值表来描述任何逻辑。
// 格式
primitive name_of_UDP (output, input1, input2);
// Declaration of ports
output reg output; // optional
// Initial block to set an initial value for the output
initial begin
output = some_initial_value; // optional
end
// Truth table defining the behavior of the UDP
table
// truth table
endtable
endprimitive
组合UDP
区别在于UDP只有一个输出,组合电路可以有多个输出
真值表格式
// Program P8.6
primitive my_and (f, a, b);
output f;
input a, b;
table
// a b : f
0 0 : 0;
0 1 : 0;
1 0 : 0;
1 1 : 1;
endtable
endprimitive
写入值的顺序应与声明输入端口的顺序相同。输入给端口的值用空格隔开,输出的端口用冒号与输入分开。
为输入端口分配的值
x, ?
组合UDP的实例化
时序UDP
- 输出端口需要被声明为reg
- 输出需要被初始化为某值,可以是0或1
- 输入值、当前输出和下一输出之间要用冒号分隔 时序UDP可以分为电平敏感和边沿敏感两种。
电平敏感的时序UDP
// Program P8.10
primitive dff (q, d, clock, rst);
output q;
input d, clock, rst;
reg q;
initial begin
q = 0; // Set initial value of q
end
table
// d clock rst : current q : next q
? ? 1 : ? : 0; // reset condition
0 1 0 : ? : 0; // clk level high, store 0
1 1 0 : ? : 1; // clk level high, store 1
? 0 ? : ? : -; // clk level low, no change
endtable
endprimitive
边沿敏感的时序UDP
// Primitive definition for an edge-triggered D flip-flop
primitive EDff (q, d, clk, rst);
output q; // Output of the flip-flop
input d, clk, rst; // Data, clock, and reset inputs
reg q; // Internal storage for the output
// Set initial value of q
initial q = 0;
// Define the behavior of the flip-flop with a truth table
table
// d clk rst : current q : next q
? ? 1 : ? : 0; // Reset condition
? (0?) 0 : ? : -; // Ignore positive edge of the clock
? (x1) 0 : ? : -; // Ignore unknown positive edge of the clock
? (1x) 0 : ? : -; // Ignore unknown negative edge of the clock
1 (10) 0 : ? : 1; // Output follows d at negative edge of the clock
0 (10) 0 : ? : 0; // Output follows d at negative edge of the clock
(??) 0 0 : ? : -; // No change on d when clk is stable
endtable
endprimitive
?
表示任意值,(0?)
表示0到任意值的正边沿,(x1)
和 (1x)
分别表示未知到1和1到未知的变化,(10)
表示从1到0的负边沿。-
表示输出不变。
速记符号
任务和函数
在verilog 设计项目中,可能在不同的地方实现相同的步骤,可以把特定功能保存在特定位置,主程序需要时调用相同的功能,不用每次写重复的代码,这些程序或者代码通常被称为子程序,并且几乎在所有的编程语言中都得到了实现。verilog中,任务和函数可用于满足子程序的要求。
任务和函数的区别
任务Task被定义为一组指令/语句,用于执行特定的功能,可以在设计中重复使用,比如一个任务把内容从内存上一个位置移动到另一个内存位置。再比如执行算术运算,然后结果保存在不同的内存位置。就有点像计算机指令集里面的指令。
函数Function通常是计算一个表达式或者公式,特定的输入集合给出单一的输出,中途可能涉及计算多次。
任务和函数的语法
任务用task关键字开头,然后用endtask结束,函数以function开始,endfunction结束。
调用任务和函数
任何模块中都可以用这个格式来调用声明的任务和函数。 // Program P8.14 Task invocation Syntax task_name(outputs_from_the_task, inputs_to_the_task);
// Program P8.15 Function Invocation Syntax name_of_function(inputs_to_the_function);
任务声明和调用的示例
// Program P8.16
module operation(ab_and, ab_or, ab_xor, a, b);
input [15:0] a, b;
output [15:0] ab_and, ab_or, ab_xor;
reg [15:0] ab_and, ab_or, ab_xor;
always @(a or b) begin
bitwise_oper(ab_and, ab_or, ab_xor, a, b); // Task Invocation
end
task bitwise_oper;
output [15:0] ab_and, ab_or, ab_xor;
input [15:0] a, b;
begin
ab_and = a & b;
ab_or = a | b;
ab_xor = a ^ b;
end
endtask
endmodule
这里就很像C语言,而且给人的感觉很趋于本质。这里先用bitwise_oper(ab-and, ab-or, ab-xor, a, b);
调用了这个函数,实际上就是把这一部分的代码逻辑转移到文中,后面紧接着使用task bitwise_oper;
来声明描写了这个task,这也对应了C语言里面先写程序主体然后紧接着执行程序的时候需要声明一下函数
函数声明和调用示例
// Program P8.17
module parity(parity, address);
input [31:0] address;
output parity;
reg parity;
always @(address) begin
parity = check_parity(address); // function invocation
$display("Parity calculated = %b", parity); // function invocation
end
function check_parity;
input [31:0] addr;
begin
// Calculate parity by XOR'ing all bits of the address
check_parity = ^addr;
end
endfunction
endmodule
以上都是先调用然后再声明函数本身。
可编程和可配置器件
逻辑综合
复杂功能的设计,往往都是在更高级的抽象层次下面完成的,比如行为或者数据流模型。逻辑综合的工程师,将设计从寄存器RTL级或者更高级的抽象层次转换为晶体管级别的网表。这些更高级的抽象层次可以让设计师更快的设计出符合要求的特定设计规范,而这仅仅借助于数字电路的算法或者真值表。
逻辑综合有助于更高级别的verilog模型下实现最佳的门级网表或者逻辑实现,而不需要低抽象层次需要考虑的内部的连线和逻辑底层实现。逻辑综合是一个高度自动化的过程,可以让高层次抽象转为低层次。
技术映射
技术映射建立了逻辑描述和目标实现(底层物理实现)之间的连贯性,在技术映射之前,设计逻辑描述是可以独立于任何目标技术的。
技术库
可编程逻辑器件的介绍
一般逻辑器件中,硬件元素的连接都是固定的,用于执行特定的功能,例如基本的门或者组合、时序电路。
可编程逻辑器件(PLD)可以在制造后通过编程修改逻辑以获得不同的逻辑功能。可以借助编程设备制造复杂逻辑功能的开发原型硬件。
PROM、PAL、和PLA
PROM是固定的AND平面块,后面紧跟着OR可编程块
可编程阵列逻辑(PAL):可编程的AND连接到固定的OR块
可编程逻辑阵列(PLA):AND和OR都可编程
SPLD和CPLD
分别是simple简单PLD和complex复杂PLD。
- 可编程的AND平面类似于带有与门和非门的译码器电路,与门和非门的数量取决于一个特定PAL块的输入线数量。
- 宏单元:包含一个固定的OR平面,一个异或门、触发器、多路复用器和三态缓冲器。下图展示了带有宏单元的PAL一般视图。异或门的作用是获得实际逻辑函数的补码形式。F/F 用于存储接收到的逻辑信息。通过多路复用器在输出阶段跟随的三态缓冲器,可以在存储状态和当前状态之间进行选择。
可编程门阵列FPGA
FPGA是一种具有更多输入/输出银角和硬件组件以及非常高逻辑容量的可编程器件。FPGA提供可配置的逻辑快CLB来替代CPLD中的AND-OR块。FPGA相较于CPLD装配了大量的触发器和逻辑资源。
以下是一个FPGA的基本视图:
FPGA可以处理更大的电路,例如:
- CLB(可编程逻辑块)
- I/O 块(输入/输出块)
- 互连线和开关
- 提供逻辑功能的CLB
- 用于将CLB连接到I/O引脚和其他FPGA块的互连开关
逻辑单元和可配置互连组成了逻辑电路的基本构建模块。
FPGA架构
主要模块是CLB可配置逻辑块,其实就是2或者3输入的LUT查找表,一些商用的FPGA是4甚至以上的LUT作为单元。它们都包含存储单元可以加载适当的值,**多路复用器mux,multiplexer blocks to implement any desired function of n-variables depend- ing on the type of LUT. **。
香农的扩展和查找表
LUT为什么可以表达逻辑的原理
2输入查找表LUT
2 输入LUT由三个2 * 1多路复用器和四个寄存器(存储值)组成,任何n变量的逻辑都可以用2输入LUT实现。
3输入查找表
3输入查找表由7个2-1多路复用器和8歌寄存器组成,用于存储所需变量的合适值。
假如这里是f= x1x2x3,把00000001写入寄存器,从左到右分别是输入信号,对应着最后的输出被引导到x3 mux作为输出。
FPGA家族
xilinx各种不同的产品线
使用FPGA进行编程
使用FPGA进行开发编程,需要有对应的开发软件,比如xilinx的 ISE或者Vivado开发套件,不同的板卡可能对应着不同的兼容的开发软件。开发的时候需要以下重要的组成:
- HDL源文件
- HDL测试testbench
- 设计的约束文件(UCF或XDC文件) 设计约束文件,是通过软件提供有关板子物理引脚规划的文件。
FPGA是一种让设计者可以在制造后进行配置的集成电路,主要用于数字逻辑块/系统 的快速原型设计。
### 设计规格 (Design Specifications)
设计者根据功耗、面积延迟等标准和要求被提供特定的设计规格。
### 设计分区 (Design Partitioning)
设计师被允许执行设计分区,以增强灵活性和简化设计。
### 设计输入 (Design Entry)
这用于将设计输入到一个ASIC设计系统中,可以通过硬件描述语言(HDL)或原理图输入来完成。
### 功能验证 (Functional Verification)
在这一步中,电路设计被验证,即,系统功能是否按照预期规格运行。如果不是,则在这里更改设计输入。静态时序分析、FPGA上的批处理流程(制造)功能验证规范系统分区(对于大型系统)设计输入功能验证逻辑综合楼层规划放置及设计师的路由回注。
### FPGA上的原型制作 (Prototyping on FPGA)
当后布局仿真正确完成后,可以通过在FPGA上烧录软件代码来设计原型。
### 制造 (Manufacturing)
在设计出原型后,将根据设计的原型制造芯片。
FPGA的应用
FPGA 最常用于航空航天、国防、便携式医疗电子产品、ASIC 原型设计、数字信号处理(DSP)、高分辨率视频、图像处理以及消费类电子产品,如显示器、开关、数码相机、多功能打印机等领域。
ASIC及其应用
在需要设计特定任务或者应用的集成电路时,ASIC大规模制造的低成本相较于FPGA更有优势,与 FPGA 板上类似设计相比,功耗、延迟和集成电路面积也较小。FPGA可以作为原型验证以减少ASIC设计失败所带来的影响。
基于Verilog HDL的项目
这里介绍一个verilog的项目,包含了从开关级到行为级的四个抽象级别 (开关级、门级、数据流RTL、行为级)。每个项目都包含了一个基本的描述,真值表和验证的结果。
基于Verilog HDL的组合逻辑项目
Verilog HDL 的不同抽象级别设计任何数字电路。组合电路,如加法器、减法器、乘法器、比较器等通常是许多数字处理器的一部分。 在本节中,使用开关级、门级、数据流和行为模型介绍了一些组合电路的附加示例。
使用开关在结构化下的全加器
这里用AND、OR、和XOR等实现了一个开关级别的全加器。设计包含四个模块,是顶层的全加器模块和开关实现的子模块。
// Main module
module Switch_fulladd(S, C, u, v, w); // Top module and port declaration
input u, v, w;
output S, C;
wire y1, c1, c2;
sxor u1(y1, u, v);
sxor u2(S, y1, w);
sand u3(c1, u, v);
sand u4(c2, y1, w);
sor u5(C, c2, c1);
endmodule
// Module for OR gate using switches (sor)
module sor(f, x, y); // Module elaboration of OR-gate using switch (sor)
input x, y;
output f;
wire w;
snor x1(w, x, y); // Call of module switch NOR-gate
snot x2(f, w);
endmodule
module snor (Op, x, y);
output Op;
input x, y;
wire w;
supply1 Vdd;
supply0 Gnd;
pmos (w, Vdd, y);
pmos (Op, w, x);
nmos (Op, Gnd, x);
nmos (Op, Gnd, y);
endmodule
module sxor (Op, x, y);
input x, y;
output Op;
wire w1, w2, w3, w4, w5;
supply1 Vdd;
supply0 Gnd;
nmos (w3, Gnd, w2);
nmos (Op, w3, w1);
nmos (w4, Gnd, y);
nmos (Op, w4, x);
pmos (Op, w5, x);
pmos (Op, w5, y);
pmos (w5, Vdd, w1);
pmos (w5, Vdd, w2);
snot nl (w1, x);
snot n2 (w2, y);
endmodule
module sand (Op, x, y);
output Op;
input x, y;
wire w;
snand x1 (w, x, y);
snot x2 (Op, w);
endmodule
module snand (Op, x, y);
output Op;
input x, y;
wire c;
supply1 Vdd;
supply0 Gnd;
pmos (Op, Vdd, y);
pmos (Op, Vdd, x);
nmos (w, Gnd, x);
nmos (Op, w, y);
endmodule
module snot (Op, x);
input x;
output Op;
supply1 Vdd;
supply0 Gnd;
nmos (Op, Gnd, x);
pmos (Op, Vdd, x);
endmodule
纹波全加器ripple-carry full adder
使用结构级模型设计一个由4个1位全加器组成的4位RCFA,变量M和Cout是输出,U、V被认为是4输入变量,Cin被用于初始进位输入。
module RCFA_4_bit (M, Cout, U, V, Cin);
input [3:0] U, V;
input Cin;
output [3:0] M;
output Cout;
wire C0, C1, C2;
// 串联即可
fulladd f1(M[0], C0, U[0], V[0], Cin);
fulladd f2(M[1], C1, U[1], V[1], C0);
fulladd f3(M[2], C2, U[2], V[2], C1);
fulladd f4(M[3], Cout, U[3], V[3], C2);
endmodule
4位进位预查加法器carry look-ahead adder
CLAs属于快速加法器的一种。这个全加器FA无需等待来自前一个FA的进位信号,但是比RCFA需要更多的硬件逻辑资源。
这里CLA的设计是verilog HDL平台上用数据流模型来实现的。
// Design module
module carrylookahead_adder(S, Cout, U, V, Cin);
input [3:0] U, V;
input Cin;
output [3:0] S;
output Cout;
wire h0, k0, h1, k1, h2, k2, h3, k3;
wire C3, C2, C1;
assign k0 = U[0] ^ V[0];
assign k1 = U[1] ^ V[1];
assign k2 = U[2] ^ V[2];
assign k3 = U[3] ^ V[3];
assign h0 = U[0] & V[0];
assign h1 = U[1] & V[1];
assign h2 = U[2] & V[2];
assign h3 = U[3] & V[3];
assign S[0] = U[0] ^ V[0] ^ Cin;
assign S[1] = U[1] ^ V[1] ^ C1;
assign S[2] = U[2] ^ V[2] ^ C2;
assign S[3] = U[3] ^ V[3] ^ C3;
assign C1 = h0 & (k0 & Cin);
assign C2 = h1 & (k1 & C1);
assign C3 = h2 & (k2 & C2);
assign Cout = h3 & (k3 & C3);
endmodule
// Test Stimulus
module carrylookahead_adder_test;
reg [3:0] U, V;
reg Cin;
wire [3:0] S;
wire Cout;
carrylookahead_adder u(S, Cout, U, V, Cin);
initial begin
$monitor($time, "S = %d, Cout = %d, U = %b, V = %b, Cin = %b", S, Cout, U, V, Cin);
U = 4'd1; V = 4'd2; Cin = 0;
#15 U = 4'd2; V = 4'd5; Cin = 0;
#15 U = 4'd4; V = 4'd4; Cin = 1;
#15 U = 4'd3; V = 4'd5; Cin = 0;
#20 $stop;
end
endmodule
4位进位保存加法器carry save adder CSA
在结构级别上,1位全加器的门级模块组成4位加位保存加法器,如图所示。CSA主要用于三个或三个以上的二进制加法,例如乘法器经常执行两个以上的二进制加法,在这里已经调用一个全加器模块来实现CSA,可以使用门级或者数据流级别在单独的模块中声明。
// Main module
module CSA(u, v, w, s, cout);
input [3:0] u, v, w;
output [4:0] s;
output cout;
wire [3:0] c1, s1, c2;
fulladd fa_10(u[0], v[0], w[0], s1[0], c1[0]); // calling of full blocks
fulladd fa_11(u[1], v[1], w[1], s1[1], c1[1]);
fulladd fa_12(u[2], v[2], w[2], s1[2], c1[2]);
fulladd fa_13(u[3], v[3], w[3], s1[3], c1[3]);
fulladd fa_20(s1[1], c1[0], 1'b0, s[1], c2[1]);
fulladd fa_21(s1[2], c1[1], c2[1], s[2], c2[2]);
fulladd fa_22(s1[3], c1[2], c2[2], s[3], c2[3]);
fulladd fa_23(1'b0, c1[3], c2[3], s[4], cout);
assign s[0] = s1[0];
endmodule
2位数组乘法器
module arraymultiplier_2bit(C, X, Y);
input [1:0] X, Y;
output [3:0] C;
wire w1, w2, w3, w4;
and x1(C[0], X[0], Y[0]);
and x2(w1, X[0], Y[1]);
and x3(w2, X[1], Y[0]);
and x4(w4, X[1], Y[1]);
ha h1(C[1], w3, w1, w2);
ha h2(C[2], C[3], w3, w4);
endmodule
module ha(s, c, x, y);
input x, y;
output s, c;
xor r1(s, x, y);
and r2(c, x, y);
endmodule
module arraymultiplier_2bit_test;
reg [1:0] x, y;
wire [3:0] C;
arraymultiplier_2bit u(C, x, y);
initial begin
x = 2'd0; y = 2'd0;
#5 x = 2'd0; y = 2'd1;
#5 x = 2'd1; y = 2'd0;
#5 x = 2'd2; y = 2'd3;
#5 $stop;
end
endmodule
2 * 2 位除法电路设计
利用真值表和卡诺图简化,设计了一个2 * 2 位除法电路设计,遵循了除法的简单规则:0/0 = x;0/1 = 0;1/1=1/0 = x 等
// Main module
module division2x2(Q, A, B);
input [1:0] A, B;
output [1:0] Q;
assign Q[0] = A[1] & ~B[1];
assign Q[1] = (A[1] & A[0]) | (A[1] & B[0]) | (A[0] & ~B[1] & B[0]);
endmodule
2位比较器
// Main module
module comparator_2bit(f1, f2, f3, A, B);
input [1:0] A, B;
output f1, f2, f3;
assign f1 = (~A[1] & B[1]) | (~A[1] & ~A[0] & B[0]) | (~A[0] & B[1] & B[0]);
assign f2 = (A[0] & ~B[1] & ~B[0]) | (A[1] & ~B[1]) | (A[1] & A[0] & ~B[0]);
assign f3 = (~A[1] & ~A[0] & ~B[1] & ~B[0]) | (~A[1] & A[0] & ~B[1] & B[0]) | (A[1] & ~A[0] & B[1] & ~B[0]) | (A[1] & A[0] & B[1] & B[0]);
endmodule
16位算术逻辑单元arithmetic logic unit, ALU
// Design module
module ALU_16bit(out, a, b, s);
input [15:0] a, b;
input [3:0] s;
output reg [31:0] out;
// s is the function select line
always @(s) begin
case(s)
4'd0: out = a + b;
4'd1: out = a * b;
4'd2: out = a & b;
4'd3: out = a ^ b;
4'd4: out = a << 2;
4'd5: out = b >> 2;
4'd6: out = {a, b}; // concatenation operator
4'd7: out = {2{a}}; // replication operator
4'd8: out = a - b;
4'd9: out = ~a;
4'd10: out = |b;
4'd11: out = a >> 1; // right shift 1 bit
4'd12: out = b << 1; // left shift 1 bit
4'd13: out = ~a ^ b;
4'd14: out = &a; // reduction operator
4'b15: out = ~(a | b);
endcase
end
endmodule
使用2-4译码器实现4-16译码器
本例中使用2-4译码器作为子模块,通过结构级和数据流模型,设计了4-16译码器电路。图中展示了具体的实现逻辑图。
// Design module
module decoder_4x16(f, el, x);
output [15:0] f;
input [3:0] x;
input el;
wire [3:0] w;
// 结构级
dec_2x4 r0(w, x[3:2], el);
dec_2x4 r1(f[3:0], x[1:0], w[0]);
dec_2x4 r2(f[7:4], x[1:0], w[1]);
dec_2x4 r3(f[11:8], x[1:0], w[2]);
dec_2x4 r4(f[15:12], x[1:0], w[3]);
endmodule
module dec_2x4(f0, f1, f2, f3, el, a, b);
input a, b, en;
output f0, f1, f2, f3;
// 数据流级
assign f0 = (~a) & (~b) & en;
assign f1 = (~a) & b & en;
assign f2 = a & (~b) & en;
assign f3 = a & b & en;
endmodule
基于verilog HDL设计时序电路
和组合电路类似,时序电路也是用不同层次的抽象来实现的,使用逻辑块、寄存器和时钟信号。行为级别模型是在时序电路中最受欢迎的方式。
4位计数器设计
计数器是一个计算脉冲的时序电路实例。这个例子中用行为级模型设计了一个计数器,可以完成向上和向下两种计数方式。
这里是时钟正边沿触发,一个清楚信号用于清除存储在上下计数器中不同触发器中存储的值,输入的up_down 信号用于选择向上或者向下的计数过程。
// Design module 异步
module asyn_up_down(out, clock, clear, up_down);
input clock, clear, up_down;
output reg [3:0] out;
always @(posedge clock or posedge clear) begin
if (clear == 1'b0) begin
if (up_down == 1'b1)
out = out + 1;
else
out = out - 1;
end else begin
out = 4'd0;
end
end
endmodule
基于LFSR的8 bit测试模式生成器
线性反馈移位寄存器(LFSRs,Linear feedback shift registers) 被用于生成随机的测试输入序列。x1
到x8
代表8个移位寄存器,它们通过一个时钟信号同步移位。每当时钟信号发生一个上升沿时,每个寄存器中的位就会移至下一个寄存器。图中的逻辑门(看起来像是或非门)用于产生反馈,这意味着某些寄存器的输出会以某种方式组合,然后反馈到序列的开始。LFSR的特点是可以生成具有良好统计特性的伪随机位序列,经常被应用于通信系统、密码学、以及作为内建自测试(BIST)电路的一部分来生成测试模式。这种反馈机制决定了生成序列的特性,包括周期性和随机性。
设计适当的反馈函数可以确保LFSR输出一个很长的无重复序列,直到它最终重复,理想情况下,这个周期是 $2^n - 1$,其中 $n$ 是寄存器的位数。在这个8位LFSR的例子中,最长周期是 $2^8 - 1 = 255$。
这里让人想到广为流传的计算机里没有真正的随机数,这里也许就是产生随机数的原理(不过也有一些应用是直接弄了一个随机数的数据库)
// Design module
module lfsr8_xor(out, clk, rst);
output reg [7:0] out;
input clk, rst;
wire xx0, xx1, xx2;
assign xx2 = (out[6] ^ out[7]);
assign xx1 = (out[5] ^ xx0);
assign xx0 = (out[4] ^ xx1);
always @(posedge clk, posedge rst) begin
if (rst)
out = 8'd00000001;
else
out = {out[6:0], xx2};
end
endmodule
// Test Stimulus
module lfsr_tb();
reg clk;
reg rst;
wire [7:0] out;
lfsr8_xor u(out, clk, rst);
initial begin
clk = 0;
rst = 1;
#15 rst = 0;
#200;
end
always #5 clk = ~clk;
endmodule
计数器设计
使用行为级模型的条件或者循环语句可以轻松完成计数器设计,这里提供了实例。
随机计数器,序列为2,4,6,8,2等等
// Design module
module counter_random(count, clk, rst);
input clk, rst;
output reg [3:0] count;
always @(posedge clk) begin
if (rst)
count <= 4'd0;
else if (count < 10)
count <= count + 2;
else
count <= 4'd2;
end
endmodule
module counter_random_test;
reg clk, rst;
wire [3:0] count;
counter_random u(count, clk, rst);
initial begin
clk = 0;
forever #5 clk = ~clk;
end
initial begin
rst = 1;
#10 rst = 0;
#200 $stop;
end
endmodule
// Mod16 up-counter that count from 0-15
module count_mod16(co, clk, rst);
input clk, rst;
output reg [3:0] co;
integer i;
always @(posedge clk) begin
if (rst)
co <= 0;
else begin
for (i = 0; i < 15; i = i + 1) // use of for loop
co <= co + 1;
end
end
endmodule
// Mod10 down counter that count from 0-9
module counter_down(co, clk, rst);
input clk, rst;
output reg [3:0] co;
always @(posedge clk) begin
if (rst) // use of if else
co <= 0;
else if (co == 0)
co <= 4'd9;
else
co <= co - 1;
end
endmodule
在行为级模型中使用task
在这个例子中,已经开发了一个逻辑来使用任务作为行为级模型中的子程序来计算输入的二进制字符串中1的数量。
// Main module
module task_count_1(cout, x);
input [7:0] x;
output reg [3:0] cout;
integer i;
always @(x)
count_1(cout, x);
task count_1;
output [3:0] cout;
input [7:0] x;
begin
cout = 8'd0;
for (i = 0; i < 8; i = i + 1) begin
if (x[i]) // x[i] is true
cout = cout + 1;
else
cout = cout;
end
end
endtask
endmodule
// Test bench
module task_count_1test;
reg [7:0] x;
wire [3:0] cout;
task_count_1 u(cout, x);
initial begin
x = 8'd0;
#5 x = 8'd7;
#5 x = 8'd9;
#5 x = 8'd8;
#5 x = 8'd15;
#5 $stop;
end
endmodule
交通信号灯控制器
// Design module
module traffic_signal_lights;
reg clk, cred, cyellow, cgreen;
parameter on = 1, off = 0, cred_tics = 30, cyellow_tics = 3, cgreen_tics = 20;
initial begin
cred = off;
cyellow = off;
cgreen = off;
always begin
cred = on;
light(cred, cred_tics);
cgreen = on;
light(green, cgreen_tics);
cyellow = on;
light(cyellow, cyellow_tics);
end
end
// Task to wait for positive edge clocks of tic
task light;
output col;
input [31:0] tic;
begin
repeat (tic) @ (posedge clk);
col = off;
end
endtask
always begin
#15 clk = 0;
#1 clk = 1;
end
endmodule
汉明码(h, k) 编码器/解码器
汉明码是检查和纠正输入比特流中错误比特的技术之一。它可分为生成器电路、校验器电路和校正器电路三个部分。
// Encoder
module ham_en(clk, i, co);
input clk;
input [3:0] i;
output reg [6:0] co;
always @(posedge clk) begin
co[6] = i[3];
co[5] = i[2];
co[4] = i[1];
co[3] = i[1] ^ i[2] ^ i[3];
co[2] = i[0];
co[1] = i[0] ^ i[2] ^ i[3];
co[0] = i[0] ^ i[1] ^ i[3];
end
endmodule
// Decoder
module ham_dec(clk, co, w, cout, d);
input clk;
input [6:0] co;
output reg [2:0] w;
output reg [6:0] cout;
output reg [3:0] d;
always @(posedge clk) begin
w[2] = co[0] ^ co[4] ^ co[5] ^ co[6];
w[1] = co[1] ^ co[2] ^ co[5] ^ co[6];
w[0] = co[0] ^ co[2] ^ co[4] ^ co[6];
cout = co;
if (w)
cout[w-1] = ~c[w-1];
end
always @(cout) begin
d[0] = cout[2];
d[1] = cout[4];
d[2] = cout[5];
d[3] = cout[6];
end
endmodule
频率分频电路
频率分频器可以通过f/n来完成分频增加时钟脉冲持续时间,n为整数,下面用代码实现f/2的大于100MHz的分频。
// Design Module
module clockdivide(clk, nclk); // clk and nclk are the clock input and output of divider circuit
input clk;
output reg nclk;
reg [31:0] count = 32'd0;
always @(posedge clk) begin
count = count + 1;
nclk = count[2];
end
endmodule
// Test Stimulus
module clockdivide_test;
reg clk;
wire nclk;
clockdivide uu(clk, nclk);
initial begin
clk = 0;
forever #5 clk = ~clk;
end
initial #1000000000 $stop;
endmodule
System Verilog
介绍
SV相比 Verilog 具有一些额外的特性,这使得它对于快速发展的技术变得更具吸引力,引入了面向对象这一编程思想?
SV的独特特性
SV拥有许多拓展特性,使得成为比verilog更好的硬件设计和验证语言:
- 非阻塞赋值和阻塞赋值可以用于数组
- 输入、输出和inout端口支持更多数据类型,比如实数、结构体和枚举以及多维数组
- 再循环语句内部可以自动声明变量,while循环可以使用do-while
- 增量/减量运算符,例如
i++
,++i
,i--
,--i
。 - 复合赋值运算符,例如
i += x
,i -= x
,i *= x
,i /= x
,i %= x
,i <<= x
,i >>= x
,i &= x
,i ^= x
,i |= x
。 - fork-join 块中的新特性,包括
join_none
和join_any
。 - 在 SystemVerilog 中,函数可以不返回值,声明为
void
。 - 参数可以声明为任何类型,包括用户定义的 typedef。
- SystemVerilog 提供了一种与其他语言(如 C&C++)进行接口的方法,称为 Direct Programming Interface(DPI)。
数据类型
SV在几乎所有类别中引入了新的数据类型,收到了C语言的启发,使得C到verilog更加舒适。
- verilog中有0、1、x、z四种可能变量,SV中只有0、1
- 在reg 或者 wire 的位置换成logic,SV允许自由决定哪个声明为reg或者wire
- Sv只需声明logic,综合工具转换为reg或者wire 下面展示了一个4位加法器的SV代码,可以和verilog代码比较
module adder4bit (
input logic [3:0] a, b,
input logic c,
output logic [3:0] sum,
output logic carry
);
logic [4:0] result;
assign result = a + b + c;
assign sum = result[3:0];
assign carry = result[4];
endmodule:adder4bit
module adder4bit (
input reg [3:0] a, b,
input reg c,
output wire [3:0] sum,
output wire carry
);
wire [4:0] result;
assign result = a + b + c;
assign sum = result[3:0];
assign carry = result[4];
endmodule
模块和 endmodule 的语法有一个区别,即在 endmodule 后面也写上模块名称,用冒号“:”分隔。
另一个区别是声明“logic”而不是“wire”以及“reg.” 如果我们在 SystemVerilog 中将信号声明为“logic”,综合工具将会确定它是“wire”还是“reg.”
数组
在verilog中,虽然允许使用多维数组的net和变量,但是有所限制。SV的多维数组允许更多的操作。
比如:reg [3:0][4:0] register [0:6];
p213