wire data_inout; reg data_reg; reg link; #xx; //延时
force data_inout=1'bx; //强制作为输入端口 ............... #xx;
release data_inout; //释放输入端口 endmodule
很多读者反映仿真双向端口的时候遇到困难,这里介绍一下双向端口的仿真方法。一个典型的双向端口如图1所示。
其中inner_port与芯片内部其他逻辑相连,outer_port为芯片外部管脚,out_en用于控制双向端口的方向,out_en为1时,端口为输出方向,out_en为0时,端口为输入方向。
用Verilog语言描述如下:
module bidirection_io(inner_port,out_en,outer_port); input out_en; inout[7:0] inner_port; inout[7:0] outer_port;
assign outer_port=(out_en==1)?inner_port:8'hzz; assign inner_port=(out_en==0)?outer_port:8'hzz; endmodule
用VHDL语言描述双向端口如下: library ieee;
use IEEE.STD_LOGIC_1164.ALL; entity bidirection_io is
port ( inner_port : inout std_logic_vector(7 downto 0); out_en : in std_logic;
outer_port : inout std_logic_vector(7 downto 0) ); end bidirection_io;
architecture behavioral of bidirection_io is begin
outer_port<=inner_port when out_en='1' else (OTHERS=>'Z'); inner_port<=outer_port when out_en='0' else (OTHERS=>'Z'); end behavioral;
仿真时需要验证双向端口能正确输出数据,以及正确读入数据,因此需要驱动out_en端口,当out_en端口为1时,testbench驱动inner_port端口,然后检查outer_port端口输出的数据是否正确;当out_en端口为0时,testbench驱动outer_port端口,然后检查inner_port端口读入的数据是否正确。由于inner_port和outer_port端口都是双向端口(在VHDL和Verilog语言中都用inout定义),因此驱动方法与单向端口有所不同。
验证该双向端口的testbench结构如图2所示。
这是一个self-checking testbench,可以自动检查仿真结果是否正确,并在Modelsim控制台上打印出提示信息。图中Monitor完成信号采样、结果自动比较的功能。
testbench的工作过程为
1)out_en=1时,双向端口处于输出状态,testbench给inner_port_tb_reg信号赋值,然后读取outer_port_tb_wire的值,如果两者一致,双向端口工作正常。
2)out_en=0时,双向端口处于输如状态,testbench给outer_port_tb_reg信号赋值,然后读取inner_port_tb_wire的值,如果两者一致,双向端口工作正常。
用Verilog代码编写的testbench如下,其中使用了自动结果比较,随机化激励产生等技术。 `timescale 1ns/10ps module tb();
reg[7:0] inner_port_tb_reg; wire[7:0] inner_port_tb_wire; reg[7:0] outer_port_tb_reg; wire[7:0] outer_port_tb_wire; reg out_en_tb; integer i; initial begin out_en_tb=0; inner_port_tb_reg=0; outer_port_tb_reg=0; i=0; repeat(20) begin #50 i=$random;
out_en_tb=i[0]; //randomize out_en_tb inner_port_tb_reg=$random; //randomize data outer_port_tb_reg=$random;
end end
//**** drive the ports connecting to bidirction_io
assign inner_port_tb_wire=(out_en_tb==1)?inner_port_tb_reg:8'hzz; assign outer_port_tb_wire=(out_en_tb==0)?outer_port_tb_reg:8'hzz; //instatiate the bidirction_io module
bidirection_io bidirection_io_inst(.inner_port(inner_port_tb_wire), .out_en(out_en_tb),
.outer_port(outer_port_tb_wire)); //***** monitor ******
always@(out_en_tb,inner_port_tb_wire,outer_port_tb_wire) begin #1;
if(outer_port_tb_wire===inner_port_tb_wire) begin
$display(\$display(\
$display(\outer_port_tb_wire,inner_port_tb_wire); end else begin
$display(\$display(\
$display(\$display(\outer_port_tb_wire,inner_port_tb_wire); end end endmodule
在写组合逻辑电路的代码时,我发现书上例子大都用的\=\;而在写时序逻辑电路代码时,我发现书上例子大都用的\<=\。之前就知道在Verilog HDL中阻塞赋值\和非阻塞赋值\有着很大的不同,但一直没有搞清楚究竟有什么不同,现在来慢慢的琢磨它。
对于我这样的初学者而言,首先要掌握可综合风格的Verilog模块编程的8个原则,并且牢记,才能在综合布局布线的仿真中避免出现竞争冒险现象。 (1) 时序电路建模时,用非阻塞赋值。 (2) 锁存器电路建模时,用非阻塞赋值。
(3) 用always块建立组合逻辑模型时,用阻塞赋值。
(4) 在同一个always块中建立时序和组合逻辑电路时,用非阻塞赋值。 (5) 在同一个always块中不要既用非阻塞赋值又用阻塞赋值。 (6) 不要在一个以上的always块中为同一个变量赋值。 (7) 用$strobe系统任务来显示用非阻塞赋值的变量值。 (8) 在赋值时不要使用 #0延时。
这样做的目的是为了使综合前仿真和综合后仿真一致。在很多时候,用\或者是\实际上对应的是不同的硬件电路,这点一定要十分清楚。
阻塞赋值(=):
我们先做下面定义:RHS—赋值等号右边的表达式,LHS—赋值等号左边的表达式。在串行语句块中,阻塞赋值语句按照它们在块中的排列顺序依次执行,即前一条语句没有完成赋值之前,后面的语句不可能被执行,换言之,后面的语句被阻塞了。阻塞赋值的执行可以认为只有一个步骤的操作,即计算RHS并更新LHS,此时不允许有来自任何其他Verilog语句的干扰。所谓阻塞的概念是指在同一个always块中,其后面的赋值语句从概念上是在前一条赋值语句结束后开始赋值的。有句话我一直没读懂:从理论上讲,它与后面的赋值语句只有概念上的先后,而无实质上的延时。 例如: begin B = A; C = B + 1; end
首先第一条语句执行,将A的值赋给B,接着执行第二条语句,将B+1(即A加1),并赋给C。也就是说C = A + 1。
非阻塞赋值(<=):
非阻塞语句的执行过程是:首先计算语句块内部所有右边表达式(RHS)的值,然后完成对左边寄存器变量的赋值操作,例如,下面两条非阻塞赋值语句的执行过程是:先计算右边表达式的值并暂存在一个暂存器中,A的值被保存在一个寄存器中,而B+1的值被保存在另一个寄