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
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
算术: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 ;
`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,合法
initial和always //initial是不可综合的
#delay //会过滤掉比延时窄的脉冲变化
非阻塞赋值:用于产生时序逻辑(always @clk),所有语句并行执行,只计算右值,不赋给左值)
阻塞赋值:用于产生组合逻辑(initial, always@*),所有语句顺序执行,计算并赋给左值后,才进行下一条)
因此:非阻塞赋值,才是verilog是并行描述语言的核心特征。
#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
begin
end顺序块
fork
join并行块
语句块可以相互嵌套
//命令语句块
initial begin: runoob
always begin : detect_stop
//disable语句可以暂时停止某个块的执行,类似c的continue
disable runoob_d3.clk_gen
disable runoob_d2
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
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 ;
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
1)类型规则
输入端口
模块例化时,从模块外部来讲, input 端口可以连接 wire 或 reg 型变量。这与模块声明是不同的,从模块内部来讲,input 端口必须是 wire 型变量。
输出端口
模块例化时,从模块外部来讲,output 端口必须连接 wire 型变量。这与模块声明是不同的,从模块内部来讲,output 端口可以是 wire 或 reg 型变量。
输入输出端口
模块例化时,从模块外部来讲,inout 端口必须连接 wire 型变量。这与模块声明是相同的。
一般来说,input 端口在例化时不能删除,否则编译报错,output 端口在例化时可以删除或者悬空
2)generate
用 generate 语句进行多个模块的重复例化
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
//方法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
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
函数一般用于组合逻辑的各种转换和计算。
而任务更像一个过程,不仅能完成函数的功能,还可以包含时序控制逻辑,例如显示/初始化等执行动作,显然用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
$fopen,$fclose
$fwrite,$fdisplay,$fmonitor,$fstrobe
$fread,$freadmemb,$freadmemh
$display,$monitor,$strobe
$random
$finish,$stop
$time,$realtime
这里举几个例子演示Verilog部分常用系统函数:
- 文件读写
integer fp;
initial begin
fp = $fopen("data.txt", "w");
$fdisplay(fp,"The data is %d", data);
$fclose(fp);
end
- 打印输出
initial begin
$monitor("Time=%0t Data=%d", $time, data);
end
- 随机数
reg [7:0] rand_no;
initial begin
rand_no = $random; //获得一个随机8位二进制数
end
- 结束仿真
initial begin
#100 $finish; // 100时间单位后结束
end
- 获取仿真时间
always @(posedge clk) begin
$display("Current time = %0t", $time);
end
- $fread - 从文件中按二进制格式读取数据:
integer fp;
reg [7:0] buffer;
initial begin
fp = $fopen("input.dat");
$fread(buffer, fp);
$fclose(fp);
end
- $freadmemb - 以二进制格式从文件读取内存内容
reg [7:0] mem[0:31];
initial begin
$freadmemb("mem.dat", mem);
end
- $freadmemh - 以十六进制格式从文件读取内存内容
reg [7:0] ram[1023:0];
initial begin
$freadmemh("ram.hex", ram);
end
在这几个示例中:
- $fread 用于按原始二进制格式读取文件数据
- $freadmemb 用来初始化二进制 memory 文件
- $freadmemh 用来初始化十六进制 memory 文件
需求:用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