系列文章目录
第一章 Procedures
文章目录
前言
有时用模块和赋值语句组合而成的描述方法不是描述电路的最方便的方式,procedure(过程)提供了一种替代语法。
一、Alwaysblock(combinational)
1.理论
过程语句有两种类型:
- combinational(组合):always @(*)
- Clocked(时钟):always @(posedge clk)
组合always块等效于assign语句,选择使用和哪一个主要是哪种更方便的问题。always块具有更丰富的语句集(例如,if-then,case),不能包含连续赋值。
每当任何输入更改值时,两者都会重新计算输出。
*表示对所有的信号都敏感。
2.练习
使用assign语句和组合always块构建AND门。
// synthesis verilog_input_version verilog_2001
module top_module(
input a,
input b,
output wire out_assign,
output reg out_alwaysblock
);
assign out_assign=a&b; //assign赋值语句
always @(*) out_alwaysblock=a&b; //组合always块
endmodule
二、Alwaysblock(clocked)
1.理论
时钟always块创建组合逻辑块,但也在组合逻辑块的输出处创建一组触发器(或寄存器)。逻辑块的输出不是立即可见,而是仅在下一个(posedge clk)之后立即可见。
2.阻塞赋值和非阻塞赋值
Verilog中有三种类型的赋值:
- 连续赋值(assign x=y),只能在过程语句外面使用(始终阻塞)
- 阻塞赋值(x=y),只能在过程语句中使用
- 非阻塞赋值(x<=y),只能在过程语句中使用。
在组合always块中,使用阻塞赋值;在始终always块中使用非阻塞赋值。不遵循此规则会导致很难发现仿真和综合硬件之间的不确定性和不同的错误。
3.练习
以三种不同的方式构建XOR门,使用连续赋值语句、组合always块和时钟always块。
// synthesis verilog_input_version verilog_2001
module top_module(
input clk,
input a,
input b,
output wire out_assign,
output reg out_always_comb,
output reg out_always_ff );
assign out_assign = a^b; //连续赋值
always @(*) out_always_comb=a^b; //组合always块(阻塞赋值)
always @(posedge clk) out_always_ff<=a^b; //时钟always块(非阻塞赋值)
endmodule
三、always if语句
1.理论
一个if语句通常会产生一个2选1多路选择器。
语句模板:
always @(*) begin
if (condition) begin
out = x;
end
else begin
out = y;
end
end
等效于使用带有条件运算符的连续赋值:
assign out = (condition) ? x : y;
2.练习
构建一个在a和b之间进行选择的2选1多路选择器,选择逻辑如下图所示:
// synthesis verilog_input_version verilog_2001
module top_module(
input a,
input b,
input sel_b1,
input sel_b2,
output wire out_assign,
output reg out_always );
assign out_assign=(sel_b1==1&&sel_b2==1)?b:a; //连续赋值
// always if语句
always @(*) begin
if(sel_b1==1&&sel_b2==1) begin
out_always=b;
end
else begin
out_always=a;
end
end
endmodule
四、if语句引入锁存器
1.理论
常见的错误来源:if语句引入了锁存器。
语法正确的代码不一定会产生合理的电路(组合逻辑+触发器),通常一个原因是在指定的情况之外会发生什么?Veriolog的答案是:保持输出不变(引入锁存器)。
除非锁存器是我们特意引入的,否则它几乎总是错误的来源,组合电路必须在所有条件下为所有输出分配一个值,通常意味着需要else子句或其他语句进行输出赋值。
2.练习
以下代码包含创建锁存器的错误行为。修复错误,以便在计算机确实过热时才关闭计算机,并在到达目的地或者需要加油时停止驾驶。
// synthesis verilog_input_version verilog_2001
module top_module (
input cpu_overheated,
output reg shut_off_computer,
input arrived,
input gas_tank_empty,
output reg keep_driving ); //
always @(*) begin
if (cpu_overheated)
shut_off_computer = 1;
else //对这种情况进行赋值,避免引入锁存器
shut_off_computer=0;
end
always @(*) begin
if (~arrived)
keep_driving = ~gas_tank_empty;
else //对这种情况进行赋值,避免引入锁存器
keep_driving=0;
end
endmodule
五、case语句
1.理论
case语句几乎等同于将一个表达式与其他表达式列表进行比较的一系列if-elseif-else,它的语法和功能与c中的switch语句不同。
模板代码:
always @(*) begin // This is a combinational circuit
case (in)
1'b1: begin
out = 1'b1; // begin-end if >1 statement
end
1'b0: out = 1'b0;
default: out = 1'bx;
endcase
end
- case语句以case开头,每个case item以冒号结尾,没有switch
- 每个case项只能执行一个语句,不必使用break,但是如果需要多个语句,则必须使用begin…end
- 允许重复(和部分重复),运行第一个匹配的。
2.练习
当有大量的情况时,case语句比if语句更方便,本练习中,创建一个6选1的多路选择器,当sel在0到5之间时,选择对应的数据输入,否则输出0.
// synthesis verilog_input_version verilog_2001
module top_module (
input [2:0] sel,
input [3:0] data0,
input [3:0] data1,
input [3:0] data2,
input [3:0] data3,
input [3:0] data4,
input [3:0] data5,
output reg [3:0] out );//
always@(*) begin // This is a combinational circuit
case(sel)
3'b000:out=data0;
3'b001:out=data1;
3'b010:out=data2;
3'b011:out=data3;
3'b100:out=data4;
3'b101:out=data5;
default:out=0; //注意避免锁存器
endcase
end
endmodule
六、优先级译码器
1.理论
优先级编码器是一个组合电路,当给定一个输入向量时,输出第一个1的位置,例如输入8’b10010000的8位优先级译码器将输出3’d4,因为bit[4]是第一个1.
2.练习
构建一个4位优先级译码器,如果没有一个输入位为高电平,则输出为0.
// synthesis verilog_input_version verilog_2001
module top_module (
input [3:0] in,
output reg [1:0] pos );
always @(*) begin
case (in)
4'b0000:pos=0;
4'b0001:pos=0;
4'b0010:pos=1;
4'b0011:pos=0;
4'b0100:pos=2;
4'b0101:pos=0;
4'b0110:pos=1;
4'b0111:pos=0;
4'b1000:pos=3;
4'b1001:pos=0;
4'b1010:pos=1;
4'b1011:pos=0;
4'b1100:pos=2;
4'b1101:pos=0;
4'b1110:pos=1;
4'b1111:pos=0;
endcase
end
endmodule
七、使用casez完成优先级译码器
创建八位优先级译码器时,可以发现有256各情况,casez语句中的case项支持不关心位,可以减少情况,z值位作为不关心的比较位。
代码模板:
always @(*) begin
casez (in[3:0])
4'bzzz1: out = 0; // in[3:1] can be anything
4'bzz1z: out = 1;
4'bz1zz: out = 2;
4'b1zzz: out = 3;
default: out = 0;
endcase
end
注意安排的顺序,以便于匹配多个case项时,选择第一个匹配的项。
- casex和casez都视为不关心的项。
- ?和z是同义词。
2.练习
完成8位优先级译码器
// synthesis verilog_input_version verilog_2001
module top_module (
input [7:0] in,
output reg [2:0] pos );
always @(*) begin
casez(in)
8'bzzzzzzz1:pos=0;
8'bzzzzzz1z:pos=1;
8'bzzzzz1zz:pos=2;
8'bzzzz1zzz:pos=3;
8'bzzz1zzzz:pos=4;
8'bzz1zzzzz:pos=5;
8'bz1zzzzzz:pos=6;
8'b1zzzzzzz:pos=7;
default:pos=0;
endcase
end
endmodule
八、避免锁存器
假设您正在构建一个电路来处理来自游戏的PS/2键盘扫描码,给定接收到的最后两个字节的扫描码,判断是否按下了键盘上的箭头键之一。
对应关系如下:
为了避免创建锁存器,必须在所有可能条件下位所有输出分配一个值,这时仅有一个default是不够的。这可能在编写代码时比较费力,一个简单的解决方法是在case语句之前为输出赋默认值。
always @(*) begin
up = 1'b0; down = 1'b0; left = 1'b0; right = 1'b0;
case (scancode)
... // Set to 1 as necessary.
endcase
end
2.练习
键盘扫描码电路实现。
// synthesis verilog_input_version verilog_2001
module top_module (
input [15:0] scancode,
output reg left,
output reg down,
output reg right,
output reg up );
always @(*) begin
left=0;down=0;right=0;up=0;//赋初值
case(scancode)
16'he06b:left=1;
16'he072:down=1;
16'he074:right=1;
16'he075:up=1;
endcase
end
endmodule
总结
上述内容讲解了Verilog中的过程语句基本内容,主要有组合always块和时钟always块,其中介绍了if和case语句,并且就进一步的应用:如何避免锁存器的引入,提供了比较好的解决方法。