目录
一、多路选择器
学生实验
二、交叉开关
学生实验:
三、优先编码器
四、多路译码器
五、加法器
1.无符号加法器
学生实验:
2.补码加法器
3.带流水线的加法器
学生实验:
六、乘法器
学生实验
七、计数器
学生实验:
八、状态机
学生实验:
九、移位寄存器
学生实验:
一、多路选择器
2选1mux电路结构
学生实验
使用case语句写的4选1mux
使用输入端口更多,选择信号的宽度改变,使得使用的资源变多,电路变得复杂很多(电路截图只截取了一部分)
module mux41(
IN0 , // input 1
IN1 , // input 2
IN2 ,
IN3 ,
SEL , // select
OUT ); // out data
//parameter WL = 16; // 输入输出数据信号位宽
input [16-1:0] IN0, IN1,IN2,IN3;// 选择器的两个输入数据信号
input [2-1:0] SEL; // 通道选通的控制信号,SEL信号改为宽度为2的信号
output[16-1:0] OUT; // 选择器的输入数据信号
reg [16-1:0] OUT;
// 生成组合逻辑的代码
always @ (IN0 or IN1 or IN2 or IN3 or SEL) begin
case(SEL)
2'b00: OUT = IN0;
2'b01: OUT = IN1;
2'b10: OUT = IN2;
2'b11: OUT = IN3;
endcase
end
endmodule
只有total pins项发生改变
二、交叉开关
从代码和电路可以看出:交叉开关实质是多路选择器的组合,每一个输出端都有对应的选通信号
学生实验:
4×4交叉开关,相当于4个4选1的选择器集合,代码使用了if-else语句
module top (IN0,IN1,IN2,IN3,
SEL0,SEL1,SEL2,SEL3,
OUT0,OUT1,OUT2,OUT3);
parameter WL = 16;
input [WL-1:0] IN0,IN1,IN2,IN3;
input SEL0,SEL1,SEL2,SEL3;
output [WL-1:0]OUT0,OUT1,OUT2,OUT3;
reg [WL-1:0]OUT0,OUT1,OUT2,OUT3;
// GEI THE OUT0
always @ (IN0 or IN1 or IN2 or IN3 or SEL0 or SEL1)
begin
if ((SEL0)&&(SEL1))
OUT0=IN3;
else if ((!SEL0)&&(SEL1))
OUT0=IN2;
else if((SEL0)&&(!SEL1))
OUT0=IN1;
else
OUT0 = IN0;
end
always @ (IN0 or IN1 or IN2 or IN3 or SEL1 or SEL2)
begin
if ((SEL1)&&(SEL2))
OUT1=IN3;
else if ((!SEL1)&&(SEL2))
OUT1=IN2;
else if((SEL1)&&(!SEL2))
OUT1=IN1;
else
OUT1= IN0;
end
always @ (IN0 or IN1 or IN2 or IN3 or SEL2 or SEL3)
begin
if ((SEL2)&&(SEL3))
OUT2=IN3;
else if ((!SEL2)&&(SEL3))
OUT2=IN2;
else if((SEL2)&&(!SEL3))
OUT2=IN1;
else
OUT2= IN0;
end
always @ (IN0 or IN1 or IN2 or IN3 or SEL3 or SEL0)
begin
if ((SEL3)&&(SEL0))
OUT3=IN3;
else if ((!SEL3)&&(SEL0))
OUT3=IN2;
else if((SEL3)&&(!SEL0))
OUT3=IN1;
else
OUT3 = IN0;
end
endmodule
下面是两个电路仿真的RTL视图和资源对比,端口增加使得消耗资源增加
2×2交叉开关
4×4交叉开关
三、优先编码器
- 常用于状态检测
- 优先的含义是,如果多个条件同时成立,则按照优先级高的条件输出
- 例如,CPU的中断编码电路就是一个优先编码器
没有很多特别之处,学生实验的代码也只是做类似的扩展即可(左侧是4比特输入,右侧是8比特输入)
四、多路译码器
多路译码器用处非常广泛,代码中使用了case语句,需要注意格式,4-16译码器比起3-8译码器多管脚,消耗的资源也更多了
因为系统有DECODER模块,RTL视图比较简洁
五、加法器
加法器是很常用的电路,有无符号加法器和补码加法器两种形式。
1.无符号加法器
输入和输出数据都是无符号的整数,常用于计数器累加和计算地址序号
波形仿真图中输入数据和对应结果在时间上是有延迟的,这是组合逻辑门的延迟造成的。
输出结果有毛刺,这是因为输入信号不止1比特,无法做到同时翻转。
本例中输出比输入多一个比特,这保证了进位不会丢失,保证了结果是正确的。(截图中第一个输入是15+15,结果中30是正确的,这是需要输出多1比特才能保证正确)
学生实验:
输出信号改成4比特位宽会使结果中的最高位进位丢失,结果可能是不正确的。如15+15的结果是14,发生了错误。
输入信号改成8比特位宽后,输出延时依然在10ns左右,没有明显变化,说明延迟与管脚数量没有关系。
2.补码加法器
输入和输出的数据都是2补码形式的有符号数,常用于数字信号处理电路
截图的上面是无符号加法器波形,下面是补码加法器波形,可以看出,两个波形的延迟和毛刺肉眼看来没有太大差别
把进制改成无符号十进制发现计算结果是不正确的,补码加法器是不可以作为输入多出1比特时的无符号加法器使用的。
波形图上的数据是带负号的,这是通过选择信号-属性-Radix为 有符号的十进制 来观察的结果 请思考,对于同样的二进制比特数据,我们可以用不同的Radix观察它,但注意信号的Radix设置必须符合其本质,才能显示正确的波形。
把加法器输出改4比特位宽,可以看到同样出现了错误
把输入改8比特位宽,没有发现延时和毛刺有大的变化
3.带流水线的加法器
带流水线的加法器即在加法器的输入与输出都连接了D触发器,D触发器可以通过时钟同步,有效的减少了组合逻辑的竞争与冒险,从而减少毛刺。从波形仿真可以看出来,毛刺的长度明显减少,但输入与对应的结果是不在一个周期的。
8比特带宽加法器加流水线后也减少了毛刺的长度,但延时没有大的变化。
学生实验:
对无符号加法器加两次流水线后,RTL视图中可以看到多出一层D触发器,波形仿真中对应结果的延时比一层流水线时的延时更长
module top(
IN1 ,
IN2 ,
CLK ,
OUT );
input [3:0] IN1, IN2;
input CLK;
output [4:0] OUT;
reg [3:0] in01_d1R, in02_d1R;
reg [3:0] in11_d1R, in12_d1R;
reg [4:0] adder1_out,adder0_out, OUT;
always@(posedge CLK) begin // 生成D触发器的always块
in01_d1R <= IN1;
in02_d1R <= IN2;
adder1_out<= adder0_out;
end
always@(posedge CLK) begin // 生成D触发器的always块
in11_d1R<= in01_d1R;
in12_d1R<= in02_d1R;
OUT <= adder1_out;
end
always@(in11_d1R or in12_d1R) begin // 生成组合逻辑的always 块
adder0_out = in11_d1R + in12_d1R;
end
endmodule
六、乘法器
乘法器是一种奢侈品会消耗大量的组合电路逻辑资源,一定要慎重使用
乘法器的代码和加法器类似,有无符号,有符号的乘法器,另外还可以添加流水线。
1.符号位
对于补码乘法器,计算结果会扩展出1比特额外的符号位,从波形图中可以看到,最高2个比特总是一样的。
那么为什么代码不使用更少的比特呢?我把输出减少1比特后,却发现-8×-8出现错误
然后使用同样的输入,输出8比特结果,发现,当输入是-8×-8时,输出的最高两位的确是不同的
但是在实际使用中会避免产生-8这样的数字。
2.资源消耗
使用默认的芯片(Stratix II)是有乘法器的,改用芯片Cyclone EP1C6F256C7,对比8比特的乘法器和加法器两者编译之后的资源开销,可以看出乘法器的资源使用是比加法器多的。(左侧是乘法器)
3.输入改为8比特带宽后,波形仿真的毛刺没有太大变化
学生实验
对乘法器加入流水线后,会发现毛刺变少
module top(
IN1 ,
IN2 ,
CLK ,
OUT );
input signed [7:0] IN1, IN2;
input CLK;
output signed [15:0] OUT;
reg signed [15:0] muler_out,OUT;
reg signed [7:0] in1_dir,in2_dir;
always@(posedge CLK) begin // 生成组合逻辑的always 块
in1_dir<=IN1;
in2_dir<=IN2;
OUT<=muler_out;
end
always @ (in1_dir or in2_dir) begin
muler_out=in1_dir * in2_dir;
end
endmodule
没有流水线
有流水线,毛刺变少
七、计数器
计数器是最为常用的时序电路之一
最简单的计数器只有一个CLK信号和一个计数值Q信号,在实际应用中的计数器可能会出现较多的变化,例如下图所示的各种端口,完成不同的逻辑功能,列举如下:
- 计数溢出功能,当计数到某个最大值MAX的时候,OV(OVerflow)信号输出1,否则输出0
- 计数使能功能,当输入使能EN信号为1的周期,计数器的Q值会有更新的动作,否则保持不动
- 计数同步清零功能,当输入的CLR信号为1的周期,在下一个周期Q端清零。
- 计数同步加载功能,当输入的LOAD信号为1的周期,DATA信号被选通至触发器的D端,在下一个周期传递至Q端。
- 以上的功能是有重叠的,根据实际应用不同,EN信号、CLR信号、LOAD信号的优先级可能会不同,例如某个电路需要在EN无效的时候D触发器彻底被锁死,清零信号和同步加载信号的值对D触发器没有影响,而另一个电路可能会要求清零信号的优先级更高。实际应用中,需要根据不同的需求用不同的代码来描述(主要是用if-else的嵌套,参考上文中的优先编码器例子)。这正是利用代码来描述电路的优势所在。
这个Verilog程序的功能定义中:组合逻辑是定义各个功能及其优先级,生成OV;时序逻辑是更新下一时钟周期的计数值,会编译为D触发器。
老师所给的代码中有严谨的使用很多begin-end,我尝试去掉begin-end,发现在if-else语句只有一句时是可以直接写begin-end不会出现错误的。
计数器代码 /
module top(
RST , // 异步复位, 高有效
CLK , // 时钟,上升沿有效
EN , // 输入的计数使能,高有效
CLR , // 输入的清零信号,高有效
LOAD , // 输入的数据加载使能信号,高有效
DATA , // 输入的加载数据信号
CNTVAL, // 输出的计数值信号
OV );// 计数溢出信号,计数值为最大值时该信号为1
input RST , CLK , EN , CLR , LOAD ;
input [3:0] DATA ;
output [3:0] CNTVAL;
output OV;
reg [3:0] CNTVAL, cnt_next;
reg OV;
// 电路编译参数,最大计数值
parameter CNT_MAX_VAL = 9;
// 组合逻辑,生成cnt_next
// 计数使能最优先,清零第二优先,加载第三优先
always @(EN or CLR or LOAD or DATA or CNTVAL) begin
if(EN) begin // 使能有效
if(CLR) begin // 清零有效
cnt_next = 0;
end
else begin // 清零无效
if(LOAD) begin // 加载有效
cnt_next = DATA;
end
else begin // 加载无效,正常计数
// 使能有效,清零和加载都无效,根据当前计数值计算下一值
if(CNTVAL < CNT_MAX_VAL) begin // 未计数到最大值, 下一值加1
cnt_next = CNTVAL + 1'b1;
end
else begin // 计数到最大值,下一计数值为0
cnt_next = 0;
end
end // else LOAD
end // else CLR
end // if EN
else begin // 使能无效,计数值保持不动
cnt_next = CNTVAL;
end // else EN
end
// 时序逻辑 更新下一时钟周期的计数值
// CNTVAL 会被编译为D触发器
always @ (posedge CLK or posedge RST) begin
if(RST)
CNTVAL <= 0;
else
CNTVAL <= cnt_next;
end
// 组合逻辑,生成OV
always @ (CNTVAL) begin
if(CNTVAL == CNT_MAX_VAL)
OV = 1;
else
OV = 0;
end
endmodule
学生实验:
简单版只需要有输入时钟信号,输出溢出信号
复杂版:调整优先级也就是调整if-else语句。其中同步清零CLR的优先级最高,使能EN次之,LOAD最低。代码中if的条件判断顺序就是CLR,EN,LOAD,从RTL视图可以看出来这三个功能控制的选择器位置是相对应的(与原来的RTL视图对比也可以看出改变)。
八、状态机
有限状态机(Finite State Machine)同样是数字电路设计中非常常用的模块,其在EDA设计中的地位等同于C语言中的If-else语句。
设计要点:把要解决的问题映射到状态空间
包括:
- 定义哪些状态
- 状态之间的跳转逻辑是怎样的
- 使用表格和状态图来描述状态机的跳转逻辑
- 使用parameter定义状态
- 尽量使用三段式的标准写法
RTL视图并没有得到参考图中的样子
学生实验:
画出状态图后改写参考代码中状态转换的部分,把使能en放在状态转换的语句附近。
网上查找到的答案是不正确的,从1010的第二个‘0’就转移回了状态0,即从状态3之间回到状态0,这样在遇到‘101011’时无法识别出1011。
更改为在状态3下输入0转移到状态2,才可以完全识别到1011.
module teller1011(clk,rst,in,en,out);
input clk;
input rst;
input in;
input en;
output out;
parameter ST_0= 0;
parameter ST_1= 1;
parameter ST_2= 2;
parameter ST_3= 3;
parameter ST_4= 4;
reg [2:0] stateR;
reg [2:0] next_state;
reg out;
always @(in or stateR or en)
begin
if(en)
begin
case(stateR)
ST_0 :begin if(in) next_state = ST_1 ; else next_state = ST_0; end
ST_1 :begin if(!in) next_state = ST_2 ; else next_state = ST_0 ; end
ST_2 :begin if(in) next_state = ST_3 ; else next_state = ST_0 ; end
ST_3 :begin if(in) next_state = ST_4 ; else next_state = ST_2 ; end
ST_4 :begin next_state = ST_0 ; end
endcase
end
else
next_state<=ST_0;
end
// calc output
always @ (stateR) begin
if(stateR == ST_4)
out = 1'b1;
else
out = 1'b0;
end
// state DFF
always @ (posedge clk or posedge rst)begin
if(rst)
stateR <= ST_0;
else
stateR <= next_state;
end
endmodule
图中有4个红框,前两个是识别出1011序列的高电平输出;第三个框:en为0时,输入的信号无效,第三个框里面无法识别1011;第四个框:rst为1,状态清0,本来的1011无法识别 。
九、移位寄存器
串行数据和并行数据之间的相互转换是在接口设计中很常见的功能,一般而言,数据在FPGA内部都是并行传递的,当通过串行接口协议(例如SPI,I2C,I2S等)把数据从FPGA内部传送到一个外部芯片(例如一片EEPROM存储器或是一片音频DAC)时就需要用到串并转换了,其核心的电路是移位寄存器。
参考代码可以成功实现串入并出
串入并出移位寄存器 /
module top(
RST , // 异步复位, 高有效
CLK , // 时钟,上升沿有效
EN , // 输入数据串行移位使能
IN , // 输入串行数据
OUT ); // 并行输出数据
input RST, CLK, EN;
input IN;
output[3:0] OUT;
reg [3:0] shift_R;
assign OUT[3:0] = shift_R[3:0];
// 时序逻辑 根据输入使能进行串行移位
// shift_R 会被编译为D触发器
always @ (posedge CLK or posedge RST) begin
if(RST)
shift_R[3:0] <= 0;
else
if(EN) begin // 串行移位的使能有效
shift_R[3:1] <= shift_R[2:0];
shift_R[0] <= IN;
end
else begin // 使能无效保持不动
shift_R[3:0] <= shift_R[3:0];
end
end // always
endmodule
学生实验:
并入串出寄存器
input en;
input [3:0] in;
input load;
output out;
reg [3:0] shift;
//reg dout;
always @(posedge clk)
begin
if(!en)
shift<=shift;
else if(!load)
shift={shift[2:0],1'b0};
else
shift<=in;
end
assign out=shift[3];
endmodule
更多推荐
作业01——Verilog RTL代码新手上路教程
发布评论