此笔记仅作为Verilog临时速成的主观简单记录,对于Verilog的语法和性质等并没有进行完备的记录、对于Verilog更深入的内容也并不记载,同时并不保证笔记的绝对正确
Verilog与C相似,很多语法结构和设计思想都可以参考C
Verilog是一门硬件描述语言,虽然与C有很多相似,但在基本设计思想上与C有本质不同
设计代码时应当时刻谨记我们在描述硬件而非设计程序
项目结构
设计方法
Verilog项目一般采用自顶向下的设计方法,即先定义顶层模块功能,进而分析要构成顶层模块的必要子模块;然后进一步对各个模块进行分解、设计,直到到达无法进一步分解的底层功能块。Verilog与C有许多相似之处,同样地,顶层模块一般可以视作C代码的main函数,编译器将由顶层模块开始组织项目

模块结构
简单而言,Verilog模块内部由模块定义声明、内部信号声明和各种逻辑语句、赋值语句以及其他结构构成

基本语法
基本
Verilog语句以分号结尾,多行语句一般用begin、end包住
代码使用//进行单行注释、用/*、*/进行多行注释
单个模块使用module、endmodule包住
编译指令
`define、 `undef
`define用于编译阶段文本替换,与C的#define相似`undef用来取消之前的宏定义
`include
用于编译阶段的文件包含,与C的#include相似
`timescale
用于定义时延、仿真的单位和精度
`timescale      time_unit / time_precisiontime_unit表示时间单位,time_precision表示时间精度,它们均是由数字以及单位s(秒),ms(毫秒),us(微妙),ns(纳秒),ps(皮秒)和 fs(飞秒)组成
时间精度大小不能超过时间单位大小
可以使用#n来实现n个时间单位的时延
数值表示
Verilog有0、1、x或X(未知)、z或Z(高阻态)四种电平逻辑
Verilog允许使用下划线_来分隔多位数字增加可读性
Verilog在整数声明时有四种基数格式:十进制('d或'D)、十六进制('h或'H)、二进制('b或'B)、八进制('o或'O)
数值可不指明位宽
负数通常在位宽前表示
示例
4'b1011         // 4bit 数值
32'h3022_c0de   // 32bit 的数值
'd100            //一般会根据编译器自动分频位宽,常见的为32bit
-6'd15             //负数Verilog在实数声明时使用十进制或科学计数法表示
Verilog里字符串是由双引号包起来的字符队列
字符串不能多行书写,即字符串中不能包含回车符
字符串本质是一系列的单字节ASCII字符队列
数据类型
interger(整数)
integer var_name ;若将实数值赋值给整数变量,将只保留其整数部分
real(实数)
real var_name ;可用十进制或科学计数法表示
time(时间)
Verilog使用特殊的时间寄存器time型变量,对仿真时间进行保存。其宽度一般为64 bit,通过调用系统函数 $time获取当前仿真时间
示例
time       current_time ;
initial begin
       #100 ;
       current_time = $time ; //current_time 的大小为 100
endparameter/localparam(参数)
parameter    name = data;
localparam    name = data;参数用于表示常量,其只能赋值一次
局部参数用localparam声明,其与parameter不同之处在于其只能在本模块中被调用
数组
type    var_name [n0:m0][n1:m1]… ; //声明
var_name [a][b]… ; //访问Verilog允许声明多维数组
wire(线网)
wire var_name ;wire类型表示硬件单元之间的物理连线,由其连接的器件输出端连续驱动
如果没有驱动元件连接到wire型变量,缺省值一般为高阻态 "Z"
reg(寄存器)
reg    var_name ;寄存器用来表示存储单元,其会在被改写前一直保持旧值integer、real、time本质为寄存器类型
向量
[n:m] //位宽=n-m+1当位宽大于1时wire与reg可以声明为向量形式
n和m可以为表达式
Verilog允许我们指定使用某一位或向量若干相邻位
Verilog允许我们指定当前位前/后若干位的选择
Verilog允许使用大括号将向量合成新的向量
示例
reg [3:0]      counter ;    //声明4bit位宽的寄存器counter
wire [32-1:0]  gpio_data;   //声明32bit位宽的线型变量gpio_data
wire [8:2]     addr ;       //声明7bit位宽的线型变量addr,位宽范围为8:2
reg [0:31]     data ;       //声明32bit位宽的寄存器变量data, 最高有效位为0
//表达式可变向量域
reg [31:0]     data1 ;
reg [7:0]      byte1 [3:0];
integer j ;
always@* begin
    for (j=0; j<=3;j=j+1) begin
        byte1[j] = data1[(j+1)*8-1 : j*8];
        //把data1[7:0]…data1[31:24]依次赋值给byte1[0][7:0]…byte[3][7:0]
    end
end
//下面 2 种赋值是等效的
A = data1[31-: 8] ;
A = data1[31:24] ;
//下面 2 种赋值是等效的
B = data1[0+ : 8] ;
B = data1[0:7] ;
//向量合成
wire [31:0]    temp1, temp2 ;
assign temp1 = {byte1[0][7:0], data1[31:8]};  //数据拼接
assign temp2 = {32{1'b0}};  //赋值32位的数值0  存储器
reg    var_name [n:m] ;存储器本质为寄存器数组,可用于描述RAM、ROM的行为
字符串
reg [0:strlen*8-1] str ;
initial begin
    str = "your_string" ;
end字符串保存在寄存器变量中,每个字符占8 bit
若长度溢出则直接截断,若长度不足则补零
特殊字符需要使用前缀来转义
逻辑操作
Verilog 中提供了大约 9 种操作符,分别是算术、关系、等价、逻辑、按位、归约、移位、拼接、条件操作符,大部分与C相似
- 算术:乘*、除/、加+、减-、求幂**、取模%
- 关系:大于>,小于<,大于等于>=,小于等于<=- 关系操作符的正常结果有2种,真(1)或假(0),如果操作数中有一位为x或z,则关系表达式的结果为x
 
- 等价:逻辑相等==,逻辑不等!=,全等===,非全等!==- 等价操作符的正常结果有2种:为真(1)或假(0)
- 逻辑相等/不等操作符不能比较x或z,当操作数包含一个x或z,则结果为不确定值
- 全等比较时,如果按位比较有相同的x或z,返回结果也可以为1,即全等比较可比较x或z
 
- 逻辑:逻辑与&&,逻辑或||,逻辑非!- 如果一个操作数不为0,它等价于逻辑1
- 如果它任意一位为x或z,它等价于x
- 如果任意一个操作数包含x,逻辑操作符运算结果不一定为x
 
- 按位:取反~,与&,或|,异或^,同或~^
- 归约:归约与&,归约与非~&,归约或|,归约或非~|,归约异或^,归约同或~^- 归约其实意为从最左位开始与每位右一位进行逻辑运算,可用于进行是否全1/全0等多位判断、归约操作
 
- 移位:左移<<,右移>>,算术左移<<<,算术右移>>>- 算术右移会根据最左位决定补1/补0来保证补码的正确性,其余操作符一律补0
 
- 拼接:大括号{、}
- 条件:expression ? true_expression : false_expression- 等价于C的三目运算符
 
过程结构、时序控制
assign语句
assign target = expression ;assign语句称作连续赋值语句
之所以称为连续赋值语句是指其总是处于激活状态,只要表达式中的操作数有变化,立即进行计算和赋值
赋值目标必须是wire型assign语句中没有begin、end
always语句
always @(signal_condition) //星号(*)表示全体信号
    expression ;always语句块又称过程块always语句本身不是单一的有意义的一条语句,而是和下面的语句一起构成一个语句块,称之为过程块,过程块中的赋值语句称过程赋值语句
该语句块不是总处于激活状态,当满足激活条件时才能被执行,否则被挂起,挂起时即使操作数有变化,也不执行赋值,赋值目标值保持不变,激活条件分为边沿敏感(上升沿或下降沿)和电平敏感(目标电平发生变化时激活)
常用边沿敏感条件有上升沿posedge、下降沿negedge,电平敏感条件可用逗号或or并列
赋值目标必须是reg型always语句中还可以使用循环、分支等语句使其功能更加强大
initial语句
initial begin
    statement ;
    …
endinitial语句从0时刻开始执行,只执行一次
多个initial块之间是相互独立的initial语句理论上来讲是不可综合的,多用于初始化、信号检测等
赋值
由于硬件设计的对时序要求的特殊性,Verilog的赋值分为阻塞赋值和非阻塞赋值
阻塞赋值=按语句顺序执行
非阻塞赋值<=则所有语句并行执行
示例
begin
    m = a*b;
    y = m;        //y=a*b
end
begin
    m <= a*b;
    y <= m;        //y=old_m
end设计组合电路时常用阻塞赋值,设计时序电路时常用非阻塞赋值
不建议在一个always块中混合使用阻塞赋值和非阻塞赋值
分支语句
if语句
if (condition1)    true_statement1 ;
else of (condition2) true_statement1 ;
else default_statement ;case语句
case(case_expr)
    condition1 : true_statement1 ;
    condition2 : true_statement2 ;
    …
    default : default_statement ;
endcase循环语句
Verilog 循环语句有4种类型,分别是while,for,repeat和forever循环
循环语句只能在always或initial块中使用,但可以包含延迟表达式
while循环
while (condition) begin
    …
endfor循环
for(initial_assignment; condition ; step_assignment)  begin
    …
endrepeat循环
repeat (loop_times) begin
    …
endrepeat的功能是执行固定次数的循环,循环的次数必须是一个常量、变量或信号
如果循环次数是变量信号,则循环次数是开始执行循环时变量信号的值
即便执行期间,循环次数代表的变量信号值发生了变化,执行次数也不会改变
forever循环
forever begin
    …
endforever表示永久循环,不包含任何条件表达式,一旦执行便无限的执行下去
系统函数$finish可退出循环
函数和任务
函数
function [range-1:0]     function_id ;
input_declaration ;
 other_declaration ;
procedural_statement ;
endfunction
function_id(input1, input2, …);函数在声明时,会隐式的声明一个宽度为range、 名字为function_id的寄存器变量,函数的返回值通过这个变量进行传递
当该寄存器变量没有指定位宽时,默认位宽1
函数通过指明函数名与输入变量进行调用,函数结束时,返回值被传递到调用处
Verilog中,一般函数的局部变量是静态的,若函数发生并发调用则会产生难以预测的结果
可以在function后加上automatic关键字来说明此类函数在调用时自动分配新的内存空间,也可以理解为此类函数是可并发调用、可递归的
任务
task       task_id ;
    port_declaration ;
    procedural_statement ;
endtask
task_id(input1, input2, …,outpu1, output2, …);任务中使用关键字input、output和inout对端口进行声明input、inout型端口将变量从任务外部传递到内部,output、inout型端口将任务执行完毕时的结果传回到外部
进行任务的逻辑设计时,可以把input声明的端口变量看做wire型,把output声明的端口变量看做reg型
但是不需要用reg对output端口再次说明。
对output信号赋值时也不要用关键字assign
为避免时序错乱,建议output信号采用阻塞赋值
函数和任务的异同
和函数一样,任务可以用来描述共同的代码段,并在模块内任意位置被调用,让代码更加的直观易读。函数一般用于组合逻辑的各种转换和计算,而任务更像一个过程,不仅能完成函数的功能,还可以包含时序控制逻辑
下面对任务与函数的区别进行概括:
| 比较点 | 函数 | 任务 | 
|---|---|---|
| 输入 | 函数至少有一个输入,端口声明不能包含 inout 型 | 任务可以没有或者有多个输入,且端口声明可以为 inout 型 | 
| 输出 | 函数没有输出 | 任务可以没有或者有多个输出 | 
| 返回值 | 函数至少有一个返回值 | 任务没有返回值 | 
| 仿真时刻 | 函数总在零时刻就开始执行 | 任务可以在非零时刻执行 | 
| 时序逻辑 | 函数不能包含任何时序控制逻辑 | 任务不能出现 always 语句,但可以包含其他时序控制,如延时语句 | 
| 调用 | 函数只能调用函数,不能调用任务 | 任务可以调用函数和任务 | 
| 书写规范 | 函数不能单独作为一条语句出现,只能放在赋值语言的右端 | 任务可以作为一条单独的语句出现语句块中 | 
模块内子程序出现下面任意一个条件时,则必须使用任务而不能使用函数
- 子程序中包含时序控制逻辑,例如延迟、事件控制等
- 没有输入变量
- 没有输出或输出端的数量大于1
门原语
Verilog语言提供已经设计好的门,称为门原语(共12个)
门原语包括逻辑门(and、or、not、xor、nand、nor、xnor)、缓冲器(buf)、三态门(bufif1、notif1、bufif0、notif0)
门原语的调用类似于模块调用,此处不赘述
模块
模块定义声明
module 模块名 ([端口列表);
             [端口信号声明]; //[输入/输出属性] [数据类型] [位宽] [名称]
             [参数声明]; //[]
             [语句];
endmodule- 模块名应符合命名规则(与C类似),根据代码规范最好与文件名一致(一文件一模块)
- 端口列表指电路的输入/输出信号名称列表,信号间用逗号隔开
- 端口信号声明包括端口信号的属性、数据类型、位宽和信号名
- 属性有input、output、inout
- 类型常用的有wire和reg
- 位宽用[n:m]表示,位宽=n-m+1且默认为1,根据代码规范通常规定n>m
- 数据类型默认wire型
 
- 属性有
- 参数声明需要说明参数名及其初值
- 端口信号声明可以代替端口列表直接写在括号内
示例
module full_adder (A,B,CIN,S,COUT);
    input [3:0] A,B;
    input CIN;
    output reg [3:0] S;
    output COUT;
endmodule模块例化
在一个模块中引用另一个模块,对其端口进行相关连接,叫做模块例化
可以类比理解为C++类的实例化
module_name name (ports_list);例化模块需要进行端口映射,端口映射有命名法和顺序法两种方法
命名法
module_name name (.port_name(signal_name),…,.port_name(signal_name));顺序法
module_name name (signal_name,…,signal_name);    //按照原模块端口定义顺序值得注意的是,Verilog规定,在上层设计中若信号是从子模块输出,则不能使用reg型而应使用wire型
示例
示例1:4bit ALU
其实是没写过其他项目,显然没什么可参考性()
alu_4.v
`timescale 1ns / 1ps
module alu_1(
    input A,
    input B,
    input cin,
    input[2:0] M,
    output S,
    output C
    );
    reg result=1'b0;
    reg result1=1'b0;
    always @*
    begin
       case(M)
        3'b000:
        begin
            result=A&B;
            result1=~result;
        end
        3'b001:
        begin
            result=A|B;
            result1=~result;
        end
        3'b010:
        begin
            result=~A;
            result1=~result;
        end
        3'b011:
        begin
            result=~B;
            result1=~result;
        end
        3'b100:
        begin
            result=A^B;
            result1=~result;
        end
        3'b101:
        begin
//            result=(A^B)^cin;
//            result1=((A&B)|(A^B))&cin;
            {result1,result}=A+B+cin;
        end
        3'b110:
        begin
//            result=(A^B)^cin;
//            result1=(~A&(B^cin))|(B&cin);   
            {result1,result}=A-cin-B;
        end
        default:
        begin
            result=0;
            result1=~result;
        end
        endcase
    end
    assign S=result;
    assign C=result1;
endmodule
module alu_4(
    input[3:0] A,
    input[3:0] B,
    input[2:0] M,
    output[3:0] S,
    output C
);
    wire [2:0] Cn;
    alu_1 a1(A[0],B[0],0,M,S[0],Cn[0]);
    alu_1 a2(A[1],B[1],Cn[0],M,S[1],Cn[1]);
    alu_1 a3(A[2],B[2],Cn[1],M,S[2],Cn[2]);
    alu_1 a4(A[3],B[3],Cn[2],M,S[3],C);
endmodulealu_4_sim.v
`timescale 1ns / 1ps
module alu_4_sim();
    reg [3:0] A;
    reg [3:0] B;
    reg [2:0] M;
    wire [3:0] S;
    wire C;
    integer i;
    integer j;
    integer k;
alu_4 alu_4_sim (
    .A(A),
    .B(B),
    .M(M),
    .S(S),
    .C(C)
);
initial
begin
    for(i=0,A=4'b0000;i<16;i=i+1)
    begin
        for(j=0,B=4'b0000;j<16;j=j+1)
        begin
            for(k=0,M=3'b000;k<8;k=k+1)
            begin
                #1;
                M=M+3'b001;            
            end
            B=B+4'b0001;
        end
        A=A+4'b0001;
    end
end
endmodulevivado仿真结果

示例2:汽车尾灯
要求使用JK触发器和数据选择器实现
通过触发器实现移位寄存器,从而实现汽车尾灯左转、右转和闪烁功能
设计得闭眼可见的烂,但蒟蒻如我实在没活了()
main.v
`timescale 1ns / 1ps
module jk(clk, cr, j, k, q);
    input clk, cr, j, k;
    output q;  
    reg q=1'b0;
always@(posedge clk) begin
    if(cr) begin
            q<=1'b0;
        end
    else begin
            case({j,k})
                2'b00: q<=q;
                2'b01: q<=1'b0;
                2'b10: q<=1'b1;
                2'b11: q<=~q;
            endcase
        end
end
endmodule
module right(clk, cr, s0, s1, out);
    input clk, cr, s0, s1;
    output [3:0]out;
    jk j1(clk, cr, s0, s1, out[0]);
    jk j2(clk, cr, out[0], ~out[0], out[1]);
    jk j3(clk, cr, out[1], ~out[1], out[2]);
    jk j4(clk, cr, out[2], ~out[2], out[3]);
endmodule
module left(clk, cr, s0, s1, out);
    input clk, cr, s0, s1;
    output [3:0]out;
    jk j1(clk, cr, s0, s1, out[3]);
    jk j2(clk, cr, out[3], ~out[3], out[2]);
    jk j3(clk, cr, out[2], ~out[2], out[1]);
    jk j4(clk, cr, out[1], ~out[1], out[0]);
endmodule
module stop(clk, out);
    input clk;
    output [3:0]out;
    jk j1(clk, 0, 1, 1, out[3]);
    jk j2(clk, 0, 1, 1, out[2]);
    jk j3(clk, 0, 1, 1, out[1]);
    jk j4(clk, 0, 1, 1, out[0]);
endmodule
module choose(s0, s1 , s2, i0, i1, i2, i3, i4, i5, i6, i7, out);
    input s0, s1, s2, i0, i1, i2, i3 , i4, i5, i6, i7;
    output out;
    reg t=1'b0;
    always@* begin
        case({s0,s1,s2})
            3'b000: t=i0;
            3'b010: t=i1;
            3'b100: t=i2;
            3'b110: t=i3;
            3'b111: t=i4;
            3'b101: t=i5;
            3'b001: t=i6;
            3'b011: t=i7;
        endcase
    end
    assign out=t;
endmodule
module light(    
    input clk,
    input s0,
    input s1,
    input s2,
    output [3:0] lo,
    output [3:0] ro,
    output [3:0] so,
    output [7:0] t
);
    left l0(clk, &lo, 1&s0, 0, lo);
    right r0(clk, &ro, 1&s0, 0, ro);
    stop st0(clk, so);
    choose m0(s0, s1, s2, 0, 0, ro[0], lo[0], 0, 0, so[0], so[0], t[0]);
    choose m1(s0, s1, s2, 0, 0, ro[1], lo[1], 0, 0, so[1], so[1], t[1]);
    choose m2(s0, s1, s2, 0, 0, ro[2], lo[2], 0, 0, so[2], so[2], t[2]);
    choose m3(s0, s1, s2, 0, 0, ro[3], lo[3], 0, 0, so[3], so[3], t[3]);
    choose m4(s0, s1, s2, 0, 0, ro[0], lo[0], 0, 0, so[0], so[0], t[4]);
    choose m5(s0, s1, s2, 0, 0, ro[1], lo[1], 0, 0, so[1], so[1], t[5]);
    choose m6(s0, s1, s2, 0, 0, ro[2], lo[2], 0, 0, so[2], so[2], t[6]);
    choose m7(s0, s1, s2, 0, 0, ro[3], lo[3], 0, 0, so[3], so[3], t[7]);
endmodulesim.v
`timescale 1ns / 1ps
module light_sim();    
    reg clk;
    reg s0;
    reg s1;
    reg s2;
//    wire [7:0] out;
    wire [3:0] ro;
    wire [3:0] lo;
    wire [3:0] so;
    wire [7:0] t;
//    integer i;
light light_sim(
    .clk(clk),
    .s0(s0),
    .s1(s1),
    .s2(s2),
//    .out(out)
    .lo(lo),
    .ro(ro),
    .so(so),
    .t(t)
);
initial begin
    for(clk=1'b0;1;) begin
        #10;
        clk=~clk;
    end
end
initial begin
    for({s0,s1,s2}=3'b000;1;) begin
        {s0,s1,s2}=3'b000;
        #200;
        {s0,s1,s2}=3'b100;
        #200;
        {s0,s1,s2}=3'b000;
        #200;
        {s0,s1,s2}=3'b001;
        #200;
        {s0,s1,s2}=3'b000;
        #200;
        {s0,s1,s2}=3'b110;
        #200;
        {s0,s1,s2}=3'b000;
        #200;
        {s0,s1,s2}=3'b011;
        #200;
    end
end
endmodulevivado仿真结果:

踩过的坑:
模块只能在单独的语句块中调用,而不能在其他语句中调用
上层模块从子模块接收的输入变量只接受wire型,不接受reg型
Verilog的for循环的初始化和判断不允许留空
寄存器最好声明一个初值,否则仿真伊始会出现x值影响触发器

 







 
Comments NOTHING