FPGA学习笔记-基本RTL电路仿真(1)

一.设计验证与测试方法

基于语言的电路模型必须经过验证以确保其功能符合设计规范的要求。常用的验证方法有两种:逻辑仿真和形式验证。逻辑仿真是通过把激励波形加到电路上,然后监视其仿真特性,来确定电路逻辑是否正确。

验证数字电路功能的基本方法是构造一个测试平台,该平台能将激励模板加到被测电路,并显示测试波形。用户自己(或使用软件)可以验证响应的正确性。

测试平台是一个具有独立结构的 Verilog模块,其基本组成如图4.16所示。它位于一个新的设计层次的顶部,而这个新的设计层次包含激励发生器、响应显示器和被测试单元(UUT)。利用Verilog语句能够定义应用于电路的激励模板。在仿真期间,响应监视器有选择地收集设计中有关信号的数据,并以文本或图表格式显示出来。

仿真器要完成三项主要的任务:(1)检查源代码;(2)报告语法错误;(3)应用测试平台所定义的输入信号对电路行为进行仿真。所有的语法错误必须在进行仿真之前就被消除掉,而且还要明白没有语法错误并不能说明模型的功能是正确的。

二.典例

下面的模块t_module Add_half具有一个测试平台的基本结构,该测试平台模块可用于在一个具有图形用户界面的仿真器中验证Add_half。要注意:它包含了一个测试单元Add half的示例。也就是给出了测试平台模板t_module Add_half,测试的模块是半加器Add half。

应用于测试单元的波形不是由硬件产生的,而是由单通Verlog行为抽象地产生,由关键词imitial定义,并通过填充关键词begin...end之间的语句来完成的。在这个简单的例子中,用户可以通过响应监视器把输出波形与期望值进行比较。

简单理解,也就是输入信号序列是modelsim软件咱们写代码模拟的,抽象产生,用关键词initial和begin and,然后modelsim查看比较波形验证。

参考书籍:俺们老师推荐的这个

Michael D.Ciletti. Verilog HDL 高级数字设计(第二版).电子工业出版社.2010.4

上面这个verilog代码,先是写了一个基本单元半加器模块,然后在测试模块里实例化它,定义应用于电路的激励,仿真验证波形。

下面为符合通常的设计测试分离原则,将设计代码和测试代码写在两个文件中,比如将边沿检测电路作为一个单独的模块,然后在测试平台中实例化它。跟之前文章做法不同就是在stim文件夹创建两个.v文件,添加文件到工程时候添加两个,然后编译全部文件,或者一个一个编译,就选compile selected,方便编译出错了找错,两文件编译都打小绿勾就Ok了。

三.同步边沿检测(双边沿)

理论知识就不解释了哈。还有这里不考虑时钟不同步碰到亚稳态情况,前面加两个D触发器构成的同步电路就好,另外上升沿下降沿检测电路把异或门改成一个端口取非的与门就好。

设计模块

`timescale 1ns/1ns
module edge_detc(
  input clk,
  input rstn,
  input din,
  output reg dout
);
reg dd;
//第一个触发器dff
always@(posedge clk or negedge rstn)
  if(~rstn)
    dd<=0;
  else 
    dd<=din;
//第二个触发器dff,双边沿检测
always@(posedge clk or negedge rstn)
  if(~rstn)
    dout<=0;
  else 
    dout<=dd^din;
endmodule
 

测试模块

`timescale 1ns/1ns
module DUT_edgedetc();
//声明信号
reg clk,rstn,din;
wire dout;
//实例化
edge_detc dong(
    .clk(clk),
    .rstn(rstn),
    .din(din),
    .dout(dout)
);
//时钟生成,T=10ns
always #5 clk=~clk;
//测试激励
initial begin
//初始化
din=0;
rstn=0;
clk=0;
//释放复位
#20 rstn=1;
//边沿变化测试序列
#20 din=1;
#20 din=0;
#20 din=0;
#20 din=1;
#20 din=0;
#20 din=1;
#20 din=0;

#50 $finish;
end
endmodule

错误总结:

1. 端口声明规则

在设计模块中:

verilog

module edge_detector(
    input clk,        // 自动为wire类型
    input rstn,       // 自动为wire类型  
    input din,        // 自动为wire类型
    output reg dout   // 明确声明为reg类型
);

为什么这样声明?

输入端口(input)

  • 自动为wire类型:因为输入信号来自外部,设计模块只是"读取"它们

  • 不需要驱动:输入端口不能被模块内部赋值

  • Verilog规则:所有input端口默认为wire类型

输出端口(output)

  • 需要指定类型

    • output dout → 默认为wire类型(用于连续赋值)

    • output reg dout → 明确声明为reg类型(用于过程赋值)

2. 测试模块的声明差异

在测试模块中:

verilog

module test_edge_detector();
    // 必须显式声明所有信号
    reg clk, rstn, din;  // 这些信号会在initial/always块中被赋值
    wire dout;           // 这个信号只被读取,不被测试模块赋值

为什么测试模块要全部声明?

  • 测试模块是顶层模块,没有输入输出端口

  • 所有信号都是内部信号,必须显式声明

  • clkrstndin 会被测试逻辑赋值 → 声明为reg

  • dout 只被监视读取 → 声明为wire

Verilog数据类型规则总结

设计模块中的信号类型:

信号位置需要声明为reg的情况
input端口永远不能声明为reg
output端口如果在always块中赋值
inout端口永远不能声明为reg
内部信号根据赋值方式决定

赋值方式决定类型:

// 连续赋值 → 必须用wire
wire signal1;
assign signal1 = a & b;

// 过程赋值 → 必须用reg  
reg signal2;
always @(posedge clk) 
    signal2 <= a & b;

四.8位移位寄存器

设计模块

`timescale 1ns/1ns
module shift_reg8(
  input din,
  input clk,
  input rstn,
  output reg[7:0] sr8
);
always@(posedge clk or negedge rstn)
  if(~rstn)
    sr8<=0;
  else 
    sr8<={sr8[6:0],din};
endmodule

测试模块

`timescale 1ns/1ns
module DUT_shiftreg();
//信号声明
reg din,clk,rstn;
wire [7:0] sr8;

//实例化
shift_reg8 dong(
      .din(din),
      .clk(clk),
      .rstn(rstn),
      .sr8(sr8)
);
//时钟生成
always #5 clk=~clk; 
//测试激励
initial begin
//初始化
din=0;
rstn=0;
clk=0;
#20 rstn=1;
//测试序列
#20 din=1;
#20 din=0;
#20 din=1;
#20 din=0;
#20 din=1;
#20 din=0;
#20 din=1;
#20 din=0;
#20 din=1;


#50 $finish;
end
endmodule

错误总结:

问题一,wire [7:0] sr8; // 输出信号应该用wire,不是reg

原因如下:

1. 在设计模块中(shift_reg8)

verilog

module shift_reg8(
  input din,
  input clk, 
  input rstn,
  output reg[7:0] sr8  // ← 这里用reg是正确的!
);

为什么这里用reg?

  • 因为 sr8 在这个模块内部被赋值(在always块中)

  • 它代表的是实际的硬件寄存器(8个D触发器)

  • 这个reg声明表示:"我要在这里创建8个物理寄存器"

2. 在测试模块中(DUT_shiftreg)

verilog

module DUT_shiftreg();
    reg din, clk, rstn;     // 这些信号会被测试逻辑驱动
    wire [7:0] sr8;         // ← 这里必须用wire!

为什么这里用wire?

  • 因为 sr8 在测试模块中只是被观察,不被驱动

  • 连接到设计模块的输出,但不是测试模块自己产生的

  • 这个wire声明表示:"我只是在连接一个已有的信号"

问题二,编译错误

** Error: D:/FPGA/8 bit shift reg/stim/shift_reg.v(6): (vlog-2730) Undefined variable: 'clk'. ** Error: D:/FPGA/8 bit shift reg/stim/shift_reg.v(6): (vlog-2730) Undefined variable: 'rstn'.

但是其实声明了,然后修改文件路径,应该是我给这个工程之前起名字带空格了,把文件移动到没有空格的路径

另外,也可能是对新语法不支持,传统端口声明:

`timescale 1ns/1ns

module shift_reg8(din, clk, rstn, sr8);
  // 端口声明
  input din;
  input clk;
  input rstn;
  output [7:0] sr8;
  
  // 寄存器声明
  reg [7:0] sr8;
  
  // 逻辑
  always@(posedge clk or negedge rstn)
。。。

五.仿真结果

边沿检测,输入信号din出现上升沿或者下降沿后,下一时钟周期dout输出高电平。

8bit移位寄存器,红色竖线是clk上升沿,上升沿采集din后,下一时钟周期移入reg[0],其余位也依次往前移,超出8位丢弃,如照片中sr8[7:0]数据变化。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值