【Verilog】10.10练习题——Verilog语法知识点补充 和 一些注意点
verilog基础练习题
Verilog也是能写for循环的!
①基本方法:
integer i;
for(i=0; i<=SIZE-1; i=i+1)
begin
/* 代码 */
end
②使用generate…for块(生成迭代器):
genvar i; //即generate variable
generate
for(i=0; i<=SIZE-1; i=i+1)
begin
/* 代码 */
end
endgenerate
值得注意的是:
for循环一般写在testbench中做测试用,而不是写在module中。
因为for循环是不能被综合(synthesis)的,不能写在常规module中。
Verilog模块内部也是能写函数的!
【Verilog Function函数语法说明】
function [3:0]FUCTION_NAME;
input [SIZE-1:0] input_data;
input [SIZE-1:0] other_input;
begin
reverse[0] = data[3];
reverse[1] = data[2];
reverse[2] = data[1];
reverse[3] = data[0];
end
endfunction
还有一点需要注意,verilog中函数的书写在module整体偏后的位置,并且在使用之后书写:
调用function在书写function之前。
如下:
`timescale 1ns/1ns
module function_mod(
input [3:0]a,
input [3:0]b,
output [3:0]c,
output [3:0]d
);
assign c = reverse(a);
assign d = reverse(b);
function [3:0]reverse;
input [3:0] data;
begin
reverse[0] = data[3];
reverse[1] = data[2];
reverse[2] = data[1];
reverse[3] = data[0];
end
endfunction
endmodule
就是在function内写for循环会报错(error: Variable declaration in unnamed block requires SystemVerilog.),比如下面这个程序:
function [3:0]reverse;
input [3:0] data;
begin
integer i;
for(i=0; i<4; i=i+1)
begin
assign reverse[i] = data[3-i];
end
end
endfunction
报错的原因其实在上一个部分中就讲过了:for循环是不能被综合的,不能写在常规的module中。
简单的组合逻辑模块实例化也要注意时序
描述
在数字芯片设计中,通常把完成特定功能且相对独立的代码编写成子模块,在需要的时候再在主模块中例化使用,以提高代码的可复用性和设计的层次性,方便后续的修改。
请编写一个子模块,将输入两个8bit位宽的变量data_a,data_b,并输出data_a,data_b之中较小的数。并在主模块中例化,实现输出三个8bit输入信号的最小值的功能。
子模块的信号接口图如下:
主模块的信号接口图如下:
使用Verilog HDL实现以上功能并编写testbench验证。
输入描述:
clk:系统时钟
rst_n:异步复位信号,低电平有效
a,b,c:8bit位宽的无符号数
输出描述:
d:8bit位宽的无符号数,表示a,b,c中的最小值
【VL9 使用子模块实现三输入数的大小比较】
错误代码:
`timescale 1ns/1ns
module main_mod(
input clk,
input rst_n,
input [7:0]a,
input [7:0]b,
input [7:0]c,
output [7:0]d
);
wire [7:0] compareline;
comparator tb_cpt1(
.clk(clk),
.rst_n(rst_n),
.a(a),
.b(b),
.c(compareline)
);
comparator tb_cpt2(
.clk(clk),
.rst_n(rst_n),
.a(compareline),
.b(c),
.c(d)
);
endmodule
module comparator(
input clk,
input rst_n,
input [7:0]a,
input [7:0]b,
output reg [7:0]c
);
always@(posedge clk or negedge rst_n)
begin
if(rst_n == 1'b0) c <= 0;
else begin
c <= (a<b) ? a : b;
end
end
endmodule
错误分析:
该电路为时序逻辑,先选择a、b最小的输出m,然后再将m与c比较输出d,因此需要2拍延迟。如果a、b最小值m直接与c进行比较,则时序错误,c永远比较的都是m的前一拍的值。
正确的代码:
`timescale 1ns/1ns
module main_mod(
input clk,
input rst_n,
input [7:0]a,
input [7:0]b,
input [7:0]c,
output [7:0]d
);
wire [7:0]result1;
wire [7:0]result2;
comparator tb_cpt1(
.clk(clk),
.rst_n(rst_n),
.a(a),
.b(b),
.c(result1)
);
comparator tb_cpt2(
.clk(clk),
.rst_n(rst_n),
.a(a),
.b(c),
.c(result2)
);
comparator tb_cpt3(
.clk(clk),
.rst_n(rst_n),
.a(result1),
.b(result2),
.c(d)
);
endmodule
module comparator(
input clk,
input rst_n,
input [7:0]a,
input [7:0]b,
output reg [7:0]c
);
always@(posedge clk or negedge rst_n)
begin
if(rst_n == 1'b0) c <= 0;
else begin
c <= (a<b) ? a : b;
end
end
endmodule
时序逻辑——非状态机的Verilog代码实现(通过Q0Q1状态真值表)
描述
某同步时序电路转换表如下,请使用D触发器和必要的逻辑门实现此同步时序电路,用Verilog语言描述。
电路的接口如下图所示。
输入描述:
input A ,
input clk ,
input rst_n
输出描述:
output wire Y
画状态真值表,写各个输出(Y,Qn+1)的逻辑表达式:
代码:
`timescale 1ns/1ns
module seq_circuit(
input A ,
input clk ,
input rst_n,
output wire Y
);
reg Q0,Q1;
always@(posedge clk or negedge rst_n)
begin
if(rst_n==1'b0) begin
Q0 <=0; //状态初始化
Q1 <=0;
end
else begin
Q0 <= ~Q0; //下一个状态 <= 当前状态的逻辑表达式
Q1 <= A & (~Q1) & (~Q0) | (~A) & (~Q1) & Q0 | (~A) & Q1 & (~Q0) | A & Q1 & Q0;
end
end
assign Y = Q1 & Q0;
endmodule
时序逻辑——状态机的Verilog代码实现
描述
某同步时序电路的状态转换图如下,→上表示“C/Y”,圆圈内为现态,→指向次态。
请使用D触发器和必要的逻辑门实现此同步时序电路,用Verilog语言描述。
电路的接口如下图所示,C是单bit数据输入端。
输入描述:
input C ,
input clk ,
input rst_n
输出描述:
output wire Y
代码:
`timescale 1ns/1ns
module seq_circuit(
input C ,
input clk ,
input rst_n,
output reg Y
);
reg [1:0]STATE;
parameter AA = 2'b00;
parameter BB = 2'b01;
parameter CC = 2'b10;
parameter DD = 2'b11;
//状态跳转(当前状态----[条件]---->下一个状态)
always@(posedge clk or negedge rst_n)
begin
if(rst_n == 1'b0) STATE <= AA;
else begin
case(STATE)
AA: if(C == 0) STATE <= AA;
else STATE <= BB;
BB: if(C == 1) STATE <= BB;
else STATE <= DD;
CC: if(C == 1) STATE <= CC;
else STATE <= AA;
DD: if(C == 0) STATE <= DD;
else STATE <= CC;
default: STATE <= AA;
endcase
end
end
//不同状态对应的输出[注意:敏感列表必须是全部变量]
always@(*)
begin
case(STATE)
AA: Y <= 0;
BB: Y <= 0;
CC: if(C == 1) Y <= 1;
else Y <= 0;
DD: Y <= 1;
default: Y <= 0;
endcase
end
endmodule
值得注意的是:
控制不同状态时的输出的always块,其敏感列表必须是全体(即*),而不是posedge clk or negedge rst_n !
always中a_temp<=a可锁存上一个时钟时刻a的数据
描述
有一个缓慢变化的1bit信号a,编写一个程序检测a信号的上升沿给出指示信号rise,当a信号出现下降沿时给出指示信号down。
注:rise,down应为单脉冲信号,在相应边沿出现时的下一个时钟为高,之后恢复到0,一直到再一次出现相应的边沿。
使用Verilog HDL实现以上功能并编写testbench验证。
输入描述:
clk:系统时钟信号
rst_n:异步复位信号,低电平有效
a:单比特信号,作为待检测的信号
输出描述:
rise:单比特信号,当输入信号a出现上升沿时为1,其余时刻为0
down:单比特信号,当输入信号a出现下降沿时为1,其余时刻为0
代码:
`timescale 1ns/1ns
module edge_detect(
input clk,
input rst_n,
input a,
output reg rise,
output reg down
);
reg a_before; //记录当前clk^前一个^的数据
always@(posedge clk or negedge rst_n)
begin
if(rst_n == 1'b0) begin
rise <= 0;
down <= 0;
a_before <= 0;
end
else
a_before <= a;
end
always@(posedge clk or negedge rst_n)
begin
if(a_before == 1'b0 && a == 1'b1) begin
rise <= 1;
down <= 0;
end
else if (a_before == 1'b1 && a == 1'b0) begin
rise <= 0;
down <= 1;
end
else begin
rise <= 0;
down <= 0;
end
end
endmodule
结合这段代码,其实很好理解这个原理:
a_before每一个clk^ 都锁存这个clk^ 检测到的a值。
当下一个clk^ 到来时,赋值操作均未开始,因为要先进行if判断。
因此,在进行if判断时,a_before存储的还是a在上一个clk^ 的值,而此时clk^ 又会监测到一个新的a。
由此,我们可以说:
always中a_temp<=a可锁存上一个时钟上升沿检测的a的数据,直到当前时钟上升沿的赋值操作完成为止。
在当前的时钟上升沿,a将监测到一个新的值,a_temp此时即表示了上一个时钟上升沿a的数据。