开坑:Verilog HDL学习 【4】

萌新小白,为做FPGA大创,开始学习Verilog HDL


门级建模在电路简单且已知时还好,一旦复杂起来编译者就会欲哭无泪,所以有了数据流建模和行为级建模

数据流建模

顾名思义,从数据的角度出发描述电路,再由逻辑综合工具完成电路的编译

  • 连续赋值语句

    常规写法

    assign addr[15:0]=addr_bits[15:0]^addr2_bits[15:0];右侧接表达式

    assign {c_out,sum[3:0]}=a[3:0]+b[3:0]+c_in;

    隐式连续赋值和隐式线网声明

    wire out =i1&i2;没有写assign

    wire i1,i2;

    assign out=i1&i2;没有写wire out

  • 延迟声明

    assign #10 out=in1&in2;

    wire #10 out=in1&in2;等效为先定义out,再延迟赋值out

    wire #10 out;

    assign out=in1&in2; 声明时延迟

  • 操作符

操作类型

操作符

功能

算术

*

/

+

-

%

去模

**

求幂

逻辑

逻辑求反

&&

逻辑与

||

逻辑或

关系

>

大于

<

小于

>=

大于等于

<=

小于等于

等价

==

相等(若有xz,则结果为x)

!=

不等(若有xz,则结果为x)

===

case等(xz也要考虑进去)

!==

case不等(xz也要考虑进去)

按位

按位求反

&

按位与

按位或

^

按位异或

^~

按位同或

缩减

&

缩减与

~&

缩减与非

缩减或

~|

缩减或非

移位

>>

右移

<<

左移

>>>

算术右移(专用于负整数右移,空缺用1来填补)

<<<

算术左移

拼接

{}

拼起来

复制

{{}}

可以设定复制几次

条件

?:

和c里用法一样

例子:

  1. 4位全加器(和以前比变得异常简单)

    module fulladd4(sum,c_out,a,b,c_in);
    output sum,c_out;
    input [3:0]a,b;
    input c_in;
    assign {c_out,sum}=a+b+c_in;
    endmodule
  2. 脉动计数器

    module counter(Q,clock,clear);
    output [3:0]Q;
    input clock,clear;
    T_FF tff0(Q[0],clock,clear);
    T_FF tff1(Q[1],Q[0],clear);
    T_FF tff2(Q[2],Q[1],clear);
    T_FF tff3(Q[3],Q[2],clear);//四个T触发器
    endmodule
    
    module T_FF (q,clk,clear);
    output q;
    input clk,clear;
    edge_dff ff1(q,~q,clk,clear);
    endmodule
    
    module edge_dff (q,qbar,d,clk,clear);
    output q,qbar;
    input d,clk,clear;
    wire s,sbar,r,rbar,cbar;
    assign cbar=~clear,
    s=~(sbar&cbar&~clk),
    r=~(rbar&~clk&s),
    rabr=~(r&cbar&d);
    assign q=~(s&qbar),qbar=~(q&r&cbar);
    endmodule//维持阻塞D触发器

行为级建模

从电路外部的行为进行描述

  • initial语句

整个仿真期间仅执行一次,用于数据初始化等,直接在需要的地方协商initial即可,一个initial里的多条语句可以用begin end框起来。

  • always语句

从0时刻起开始执行always,会循环下去。

  • 过程赋值语句

过程赋值语句和之前数据流的数据赋值本质的不同是数据流的数据一旦改变,其结果会立即发生变化,但是过程赋值语句只有在执行到这里时才会改变结果,过程赋值不需要写assign

阻塞赋值:=;非阻塞赋值:<=

非阻塞赋值在运行时会让下一条语句同时运行,而阻塞赋值需要先给当前值赋完值再运行下一条,这两个最好不出现在同一个always语句中。非阻塞赋值在交换值时相比阻塞赋值有明显的优势。

  • 时序控制

延迟控制

  1. 基于延迟的时序控制

    和数据流控制的延迟控制一样,在语句前用#标明延迟的时间

  2. 内嵌赋值延迟控制

    y=#5 x+z;在0时刻求x+z,在5时刻给y

  3. 零延迟控制

    #0将会让该语句在最后执行,但是如果有多个#0将会导致不知道执行哪个

事件控制

  1. 常规事件控制
    @(clock);@(posedge clock);@(negedge clock);在语句前可以表明语句执行的时间

    q=@(posedge clock) d;立即计算d,在clock正向跳变时把d给q。

  2. 命名事件控制

    event received_data;->received_data;@(received_data);需要声明event

  3. OR控制

    多个事件发生其一就执行时可以在事件间加or

电平控制

wait(received_data); 用wait语句

  • 条件语句

和c中if语句说些一致

  • 多路分支语句

和c中case语句基本一致
case({s1,s0})

2'd0:out=i0; 2'd1:out=i1; 2'd2 :out=i2; 2'd3:out=i3; default:$display("no signals");

casex和casez的含义是表达式中的xz为无关值

  • 循环语句

  1. while循环

    和c一样

  2. for循环
    和c一样

  3. repeat循环

    repeat()里填入循环次数,即可循环这么多次

  4. forever循环

    一直执行下去,直到遇到$finish或者disable语句

  • 块语句

顺序块(begin end)除非遇到非阻塞赋值,按照顺序一条一条执行
并行块(fork join)里面的语句同时进行

可以进行嵌套,例如begin end中间加入fork join,可以进行命名在begin或者fork的后面加入:然后命名即可。禁用时用disable 模块名即可

  • 生成块

模块可以被动态的生成,从而减少代码量,用generate和endgenerate实现

例如按位求异或

genvar j;
genertat for(j=0;j<N;j=j+1) begin :xor_loop
xor g1(out[j],i0[j],i1[j]);
end
endgenerate

条件语句生成块

generate
if((a0_width<8)||(a1_width<8))
cla_mutilpier #(a0_width,a1_width) m0(product,a0,a1);//两个乘数小于8位用超前进位乘法器
else
tree_mutilpier #(a0_width,a1_width) m0(product,a0,a1);//大于八位用树形乘法器
endgenerate

书上例子:交通信号灯

、define TRUE 1'b1
、define FALSE 1'b0
、define Y2RDELAY 3
、define R2GDELAY 2//定义延迟
module sig_control(hxy,cntry,X,clock,clear);
output [1:0]hwy,cntry;
reg[1:0]hwy,cntry;
input X;
input clock,clear;
parameter RED=2'd0,YELLOW=2'd1;GREEN=2'd2;
parameter S0=3'd0;S1=3'd1;S2=3'd2;S3=3'd3;S4=3'd4;
reg [2:0]state;
reg [2:0]next_state;

always@(posedge clock)
if(clear)
state<=S0;
else
state<=next_state;//定义状态之间的改变

always@(state)
begin
hwy=GREEN;
cntry=RED;
case(state)
S0:;
S1:hwy=YELLOW;
S2:hwy=RED;
s3:begin
hwy=RED;cntry=GREEN;
end
S4:begin
hwy=RED;cntry=`YELLOW;
end
endcase
end//定义每个状态时两个灯的颜色变化

always@(state or X)
begin 
case(state)
S0:if(X)
next_state=S1;
else next_state=S0;
S1:begin
repeat(`Y2RDELAY)@(posedge clock);
next_state=S2;
end
S2:begin
repeat(`R2GDELAY)@(posedge clock);
next_state=S3;
end
S3:if(X)
nexe_state=S3;
else next_state=S4;
S4:begin
repeat(`(posedge clock);
next_state=S0
end
default:next_state=S0;
endcase
end//定义每个形态下一个形态的变化方向,用repeat来进行延迟,用if来描述下一个状态的方向
endmodule

可以看出,整个功能的实现有如下几个模块:状态变化模块、状态描述模块、状态变化方向模块,然后分别实现各自的功能。在前面的定义部分定义出了绝大部分状态对应的英文名称,为之后的编写提供了极大的方便。


书目参考:Verilog HDL数字设计与综合(第二版)[美] Samir Palnitkar