verilog基础知识学习(菜鸟教程) - minichao9901/TangNano-20k-Zynq-7020 GitHub Wiki

参考

1.1 Verilog 教程 | 菜鸟教程 (runoob.com)
在线编译器
Online VERILOG Compiler - Online VERILOG Editor - Run VERILOG Online - Online VERILOG Runner (jdoodle.com)
代码编写规范
https://code84.com/733564.html

(1)数据类型

module test();
//基础类型
reg a;
wire b
reg [7:0] c;
wire [7:0] d;

//整数和实数
integer i;
real b;

//定义有符号数
reg signed [7:0] mem[0:1023];

//多维数组,存储器
reg membit[0:1023];
reg [7:0] mem[0:1023];
reg [7:0] mem2d[0:1023][0:63];

//多维数组的遍历与print
integer i;
initial begin
    for(i=0; i<1023; i=i+1) begin
        mem[i]=i%256;
    end 
end

initial begin
    for(i=0; i<1023; i=i+1) begin
        $display(mem[i]);
    end 
end

//time类型
time   current_time ;
initial begin
       #100 ;
       current_time = $time ; //current_time 的大小为 100
       $display(current_time);
       $finish
end

//参数定义
parameter      data_width = 10'd32 ;
parameter      i=1, j=2, k=3 ;
parameter      mem_size = data_width * 10 ;

//字符串
reg [0: 14*8-1]  str ;
initial begin
    str = "run.runoob.com";
    $display(str);
end  

endmodule

(2) 表达式

算术:a+b, a-b, a*b, a/b, a%b
逻辑:A&&B, A||B, !A
比较:a>b, a>=b, a<b, a<=b, a==b, a!=b
位操作:a&b, a|b, a^b, ~a
算术移位(右移补符号位,左移补0):a<<<3, a>>>3
逻辑移位(左右均补0):a<<3, a>>3
拼接:b={a[3:0],a[7:4],8{1'b0}}
条件表达式:cond? a:b
assign   hsel = (addr[9:8] == 2'b00) ? hsel_p1 :
                (addr[9:8] == 2'b01) ? hsel_p2 :
                (addr[9:8] == 2'b10) ? hsel_p3 :
                (addr[9:8] == 2'b11) ? hsel_p4 ;

(3)编译指令

`ifdef       MCU51
    parameter DATA_DW = 8   ;
`elsif       WINDOW
    parameter DATA_DW = 64  ;
`else
    parameter DATA_DW = 32  ;
`endif

`include         "../../param.v"
`include         "header.v"

`timescale 1ns/100ps    //时间单位为1ns,精度为100ps,合法

(4)过程与赋值

initialalways   //initial是不可综合的
#delay  //会过滤掉比延时窄的脉冲变化
非阻塞赋值:用于产生时序逻辑(always @clk),所有语句并行执行,只计算右值,不赋给左值)
阻塞赋值:用于产生组合逻辑(initial, always@*),所有语句顺序执行,计算并赋给左值后,才进行下一条)
因此:非阻塞赋值,才是verilog是并行描述语言的核心特征。

(5)触发控制

#10  value_general    = value_test ;  
#10 ; value_ single   = value_test ;
//以上2种延时模型相同,不会漏掉脉冲

value_embed        = #10 value_test ;
//内嵌延时,具有“惯性赋值”的特性,可能会漏掉脉冲

//always写时序逻辑,典型写法
always @(posedge clk or negedge rstn)    begin 
    if! rstn)begin
        q <= 1'b ;      
    end
    else begin
        q <= d ;
    end
end

//always写组合逻辑,敏感信号表要完全
always @(*) begin
//always @(a, b, c, d, e, f, g, h, i, j, k, l, m) begin
//两种写法等价
    assign s = a? b+c : d ? e+f : g ? h+i : j ? k+l : m ;
end

(6)语句块

begin
end顺序块

fork
join并行块

语句块可以相互嵌套

//命令语句块
initial begin: runoob
always begin : detect_stop

//disable语句可以暂时停止某个块的执行,类似c的continue
disable runoob_d3.clk_gen
disable runoob_d2

(6)控制流

if (condition1)       true_statement1 ;
else if (condition2)        true_statement2 ;
else if (condition3)        true_statement3 ;
else                      default_statement ;


case(sel)
    2'b00:   begin      
            sout_t = p0 ;
        end
    2'b01:       sout_t = p1 ;
    2'b10:       sout_t = p2 ;
    default:     sout_t = p3 ;
endcase

while (counter<=10) begin
    #10 ;
    counter = counter + 1'b1 ;
end

counter2 = 'b0 ;
for (i=0; i<=10; i=i+1) begin
    #10 ;
    counter2 = counter2 + 1'b1 ;
end

repeat (11) begin  //重复11次
    #10 ;
    counter3 = counter3 + 1'b1 ;
end

(7)force/release

wait (test.u_counter.cnt_temp == 4'd4) ;
@(negedge clk) ;
force     test.u_counter.cnt_temp = 4'd6 ;
force     test.u_counter.cout     = 1'b1 ;
#40 ;
@(negedge clk) ;
release   test.u_counter.cnt_temp ;
release   test.u_counter.cout ;

(8) module定义

module module_name 
#(parameter_list)
(port_list) ;
              Declarations_and_Statements ;
endmodule

//例子,需要注意:1)参数之间,用逗号。2)端口之间,用逗号。3)端口定义括号末尾,用分号。这意味着module定义也是一条语句,这与C语言不同之处。
module adder
#(
    parameter  LEN=10
)
(
    input [LEN-1:0] A,
    input [LEN-1:0] B,
    output [LEN:0] S
);

assign S=A+B;
endmodule

module test
#(
    parameter LEN=10
)
();
    reg [LEN-1:0] A=17;
    reg [LEN-1:0] B=33;
    wire [LEN:0] C;
    adder u_adder(
        .A(A),
        .B(B),
        .S(C)
    );
    
    initial begin
        #100;
        $display(C);
    end
endmodule

(9)模块例化

1)类型规则
输入端口
模块例化时,从模块外部来讲, input 端口可以连接 wirereg 型变量。这与模块声明是不同的,从模块内部来讲,input 端口必须是 wire 型变量。

输出端口
模块例化时,从模块外部来讲,output 端口必须连接 wire 型变量。这与模块声明是不同的,从模块内部来讲,output 端口可以是 wirereg 型变量。

输入输出端口
模块例化时,从模块外部来讲,inout 端口必须连接 wire 型变量。这与模块声明是相同的。

一般来说,input 端口在例化时不能删除,否则编译报错,output 端口在例化时可以删除或者悬空

2generategenerate 语句进行多个模块的重复例化
module adder(
    input a,
    input b,
    output s,
    output c
);
    assign {c,s}=a+b;

endmodule

module adders();
    wire [10:0] a;
    wire [10:0] b;
    wire [10:0] c;
    wire [10:0] s;
    
    genvar i;
    generate
        for(i=0; i<10; i=i+1) begin
            adder u_adder(
                .a(a[i]),
                .b(b[i]),
                .c(c[i]),
                .s(s[i])
            );
        end
    endgenerate

endmodule

(10) 参数定义与传递

//方法1
module  ram
    #(  parameter       AW = 2 ,
        parameter       DW = 3 )
    (
        input                   CLK ,
        input [AW-1:0]          A ,
        input [DW-1:0]          D ,
        input                   EN ,
        input                   WR ,    //1 for write and 0 for read
        output reg [DW-1:0]     Q    );
endmodule

module test;
parameter AW=4;
parameter DW=4;
reg [AW-1:0] a,d;
wire[DW-1:0] q;

ram #(.AW(4), .DW(4))
    u_ram
    (
        .CLK    (clk),
        .A      (a[AW-1:0]),
        .D      (d),
        .EN     (en),
        .WR     (wr),    //1 for write and 0 for read
        .Q      (q)
     );
endmodule

//方法2
module  ram
    (
        input                   CLK ,
        input [AW-1:0]          A ,
        input [DW-1:0]          D ,
        input                   EN ,
        input                   WR ,    //1 for write and 0 for read
        output reg [DW-1:0]     Q    );

        parameter       AW = 2 ;
        parameter       DW = 3 ;
endmodule

module test;
parameter AW=4;
parameter DW=4;
reg [AW-1:0] a,d;
wire[DW-1:0] q;
defparam     u_ram.AW = 4 ;
defparam     u_ram.DW = 4 ;
ram  u_ram
    (
        .CLK    (clk),
        .A      (a[AW-1:0]),
        .D      (d),
        .EN     (en),
        .WR     (wr),    //1 for write and 0 for read
        .Q      (q)
     );

endmodule

(11)函数

1)不含有任何延迟、时序或时序控制逻辑
2)至少有一个输入变量
3)只有一个返回值,且没有输出
4)不含有非阻塞赋值语句
5)函数可以调用其他函数,但是不能调用任务

从以上描述看,函数的限制很大,只能做运算和逻辑转换工作。不能有任何记忆性的语句。

1)普通函数,函数的局部变量是静态变量。函数是不可重入的,不同的调用可能会彼此冲突。
2)automatic函数,函数的局部变量是临时变量。函数是可重入的,不同的调用不会冲突。因此支持递归函数。

wire [31:0]          results3 = factorial(4);
function automatic   integer         factorial ;
    input integer     data ;
    integer           i ;
    begin
        factorial = (data>=2)? data * factorial(data-1) : 1 ;
    end
endfunction // factorial

(12)任务块

函数一般用于组合逻辑的各种转换和计算。
而任务更像一个过程,不仅能完成函数的功能,还可以包含时序控制逻辑,例如显示/初始化等执行动作,显然用task合适。task比较像没有返回值的函数。

模块内子程序出现下面任意一个条件时,则必须使用任务而不能使用函数。 1)子程序中包含时序控制逻辑,例如延迟,事件控制等 2)没有输入变量 3)没有输出或输出端的数量大于 1

task xor_oper_iner(
    input [N-1:0]   numa,
    input [N-1:0]   numb,
    output [N-1:0]  numco  ) ;
    #3  numco       = numa ^ numb ;
endtask

always @(*) begin          //任务调用
    xor_oper_iner(a, b, co_t);
end

(13) 系统函数

$fopen,$fclose
$fwrite,$fdisplay,$fmonitor,$fstrobe
$fread,$freadmemb,$freadmemh
$display,$monitor,$strobe
$random
$finish,$stop
$time,$realtime

这里举几个例子演示Verilog部分常用系统函数:

  1. 文件读写
integer fp;

initial begin
  fp = $fopen("data.txt", "w"); 
  $fdisplay(fp,"The data is %d", data);
  $fclose(fp);
end
  1. 打印输出
initial begin
  $monitor("Time=%0t Data=%d", $time, data); 
end
  1. 随机数
reg [7:0] rand_no;
initial begin
  rand_no = $random; //获得一个随机8位二进制数  
end
  1. 结束仿真
initial begin
  #100 $finish; // 100时间单位后结束
end
  1. 获取仿真时间
always @(posedge clk) begin
  $display("Current time = %0t", $time); 
end  
  1. $fread - 从文件中按二进制格式读取数据:
integer fp;
reg [7:0] buffer;

initial begin
  fp = $fopen("input.dat");
  $fread(buffer, fp); 
  $fclose(fp); 
end
  1. $freadmemb - 以二进制格式从文件读取内存内容
reg [7:0] mem[0:31];  

initial begin
  $freadmemb("mem.dat", mem); 
end
  1. $freadmemh - 以十六进制格式从文件读取内存内容
reg [7:0] ram[1023:0];

initial begin 
  $freadmemh("ram.hex", ram);
end

在这几个示例中:

  • $fread 用于按原始二进制格式读取文件数据
  • $freadmemb 用来初始化二进制 memory 文件
  • $freadmemh 用来初始化十六进制 memory 文件

(14) 状态控制法去写单片机的顺序执行程序

需求:用verilog实现如下行为: delay_ms(100); task1(); delay_ms(200); task2(); delay_ms(100); task3(); delay_ms(200); task4();

module delay_tasks(
    input clk,
    output [2:0] led
);

parameter S_IDLE=2'b00;
parameter S_TASK1=2'b01;
parameter S_TASK2=2'b10;
parameter S_TASK3=2'b11;
parameter FCLK=27e6;

reg [1:0] state=S_IDLE;
reg [31:0] delay_cnt;
reg [2:0] led=3'b111;

always @(posedge clk) begin
    delay_cnt<=delay_cnt+1;

    case(state)
        S_IDLE: begin
            if(delay_cnt==FCLK*0.1) begin
                state<=S_TASK1;
                delay_cnt<=0;
            end
        end
        S_TASK1: begin
            task1();
            if(delay_cnt==FCLK*0.2) begin
                state<=S_TASK2;
                delay_cnt<=0;
            end
        end
        S_TASK2: begin
            task2();
            if(delay_cnt==FCLK*0.1) begin
                state<=S_TASK3;
                delay_cnt<=0;
            end
        end
        S_TASK3: begin
            task3();
            if(delay_cnt==FCLK*0.2) begin
                state<=S_IDLE;
                delay_cnt<=0;
            end
        end
    endcase
end

task task1;
        led[0]=~led[0];
endtask

task task2;
    led[1]=~led[1];
endtask

task task3;
    led[2]=~led[2];
endtask

endmodule
⚠️ **GitHub.com Fallback** ⚠️