在 Verilog 中,wire 和 reg 是两种不同类型的变量,它们有着不同的特性和用途
wire 变量用于连接模块中的输入、输出以及内部信号线。
它主要用于表示连续赋值的逻辑连接,类似于硬件电路中的导线。
wire 变量不能在 always 块或 initial 块中赋值,它们只能通过连续赋值“assign”语句连接到其他信号,
因此,wire 变量和 reg 变量的本质区别在于它们的用途和赋值方式。wire 主要用于连接信号,而 reg 主要用于存储时序逻辑的状态值。
module ExampleModule(input a, input b, output c);
// 使用 wire 连接输入和输出
wire w1;
assign w1 = a & b; // 连接输入信号 a 和 b
assign c = w1; // 连接输出信号 c
// 使用 reg 存储状态值
reg [3:0] counter; // 定义一个 4 位寄存器
always @(posedge clk) begin
if (reset) begin
counter <= 4'b0000; // 在上升沿时重置寄存器的值
else
counter <= counter + 1; // 在上升沿时递增寄存器的值
end
end
endmodule
在这个示例中,wire 变量 w1 用于连接输入信号 a 和 b 到输出信号 c,而 reg 变量 counter 被用作一个 4 位的寄存器,用于存储时序逻辑中的状态值。wire 用于连接逻辑,而 reg 用于存储状态。
·阻塞赋值使用“=”符号进行赋值。
·在 always 块中使用阻塞赋值时,会按照代码的顺序依次执行赋值语句。
·每次遇到阻塞赋值语句时,会立即计算并更新被赋值的变量。
·阻塞赋值会导致并发赋值,可能会引起意外的结果,特别是在多个赋值操作的情况下。
·非阻塞赋值使用“<=”符号进行赋值。
·在 always 块中使用非阻塞赋值时,所有赋值语句会同时执行,但在时钟信号的边沿才会生效更新被赋值的变量。
·非阻塞赋值可以用于描述时序逻辑中的寄存器更新操作,确保时序逻辑的正确性。
·非阻塞赋值消除了并发赋值问题,避免了多个赋值操作之间的相互影响,提高了代码的可读性和可维护性。
(1)阻塞赋值的使用情况:
·适用于组合逻辑电路或者需要立即更新变量值的情况。
·在同一个 always 块内多个赋值语句的情况下,可能会导致意外结果。
module blocking_example (
input wire a,
input wire b,
output reg c
);
always @ (a, b)
begin
// 阻塞赋值,立即更新变量值
c = a & b;
end
endmodule
(2)非阻塞赋值的使用情况:
·适用于时序逻辑电路中需要保持状态稳定性的情况。
·用于在时钟信号的边沿更新寄存器值,避免并发赋值问题。
module non_blocking_example (
input wire clk,
input wire rst,
input wire data,
output reg reg_data
);
always @(posedge clk or posedge rst)
begin
if (rst)
begin
// 重置寄存器值
reg_data <= 0;
end
else
begin
// 非阻塞赋值,在时钟信号的边沿更新寄存器值
reg_data <= data;
end
end
endmodule
通过以上示例,可以看到阻塞赋值适用于需要立即更新变量值的情况,而非阻塞赋值适用于时序逻辑中需要保持状态稳定性的情况。在实际设计中,根据逻辑需求选择合适的赋值方式可以确保设计的正确性和性能。
在Verilog中,常见的条件语句包括if语句、case语句和unique case语句。以下是它们的区别和用法:
if语句用于在条件满足时执行特定的操作。
可以包含多个else if和else部分。
在每个时钟周期只能选择一个分支执行。
module if_statement_example (
input wire a,
output reg b
);
always @ (a)
begin
if (a == 1)
b = 1;
else if (a == 0)
b = 0;
else
b = 0; // 默认情况
end
endmodule
case语句根据输入信号的不同值执行相应的操作。
可以使用case、casex或casez,分别表示精确匹配、模糊匹配X值、模糊匹配Z值。
每个case分支可以有多个值或范围。
module case_statement_example (
input wire [1:0] opcode,
output reg result
);
always @ (*)
begin
case (opcode)
2'b00: result = 4'b0000;
2'b01: result = 4'b0011;
2'b10: result = 4'b1111;
default: result = 4'bXXXX; // 默认情况
endcase
end
endmodule
unique case语句用于确保每个分支值都唯一,不会有重复的情况。
如果出现多个分支值相同的情况,编译器会报错。
module unique_case_statement_example (
input wire [1:0] sel,
output reg out
);
always @ (*)
begin
unique case (sel)
2'b00, 2'b01: out = 1;
2'b10: out = 0;
default: out = 0; // 默认情况
endcase
end
endmodule
与(&):两个输入都为高时输出高,否则输出低。
或(|):两个输入至少有一个高时输出高。
非(~):对输入取反,即高变低,低变高。
异或(^):两个输入不同时为高或者不同时为低时输出高,否则输出低。
加法(+):对两个数进行加法运算。
减法(-):对两个数进行减法运算。
乘法(*):对两个数进行乘法运算。
除法(/):对两个数进行除法运算,通常需要使用特殊的算法实现。
等于(==):判断两个操作数是否相等。
不等于(!=):判断两个操作数是否不相等。
大于(>):判断左操作数是否大于右操作数。
小于(<):判断左操作数是否小于右操作数。
大于等于(>=):判断左操作数是否大于等于右操作数。
小于等于(<=):判断左操作数是否小于等于右操作数。
与‘==’不同,全等将x和z都当成确定的值进行比较,当表述完全相同时输出1;此外,在做全等比较时,对于两个比较位数不等的情况,不会像处理操作符‘==’那样作高位补0操作,而会直接判断两数据不等。
左移(<<):将操作数向左移动指定的位数,右侧空出的位用0填充。
右移(>>):将操作数向右移动指定的位数,左侧空出的位根据有符号或无符号右移来填充。
与(&):按位与操作。
或(|):按位或操作。
非(~):按位取反操作。
异或(^):按位异或操作。