Verilog基础知识

I/O端口类型:

input             wire型

output           wire/reg型

verilog可综合语句 assign,always,其中initial 语句不建议用在可综合语句中。

关于时标:

`timescale 时间单位/时间精度

`timescale 1ns/1ps

#3  延时3ns,精度为1ps,递增时,可以递增0.1ns

如果 `timescale 1ns/1ns,精度为1ns,递增时只能按照1ns开始递增

变量类型:wire ,reg

wire 线型

reg 寄存器

在Verilog中,寄存器要用reg来声明,但是reg声明的变量不一定都是寄存器,有可能是锁存器,或者查找表。

查找表:可以理解为数电中的真值表

对于线型,用assign进行赋值即可。

对于寄存器,包含数据输入D端,时钟输入clk端,使能信号en(可无),数据输出Q端。其中,clk是指同步脉冲,一般是指外部晶振提供的方波时钟,占空比50%,即高低电平各占一个时钟周期的50%,1表示高电平,0表示低电平,高到低为下降沿,低到高为上升沿,一般都为上升沿有效时,触发寄存器(也可以下降沿触发,但是reg型必须在always块内进行编程);当时钟clk上升沿触发D端将数据传输至Q端,如果寄存器没有到达下一个沿,继续保持当前数据;当下一个沿到来时,再由D端传输新的数据至Q端。(不含使能情况,若含有使能,则需要在时钟有效后,使能有效,才进行数据的传输)

FPGA一般接收的是方波时钟,而非正弦波时钟。

RTL电路基本结构:组合逻辑 + 时序逻辑

RTL级描述的就是寄存器级的输出传输

阻塞赋值 = :属于立即发生

组合逻辑用,输出受限于输入信号最长延时路径,

非阻塞赋值 <=:时钟沿触发

时序逻辑用,输出受限于时钟沿的到来

在使用always时触发列表由posedge或negedge触发,使用非阻塞赋值

在使用assign时,用阻塞赋值

生成锁存器一般是电平触发   always @(*)

生成寄存器的一般是沿触发  always @(posedge clk)

在代码中进行数值定义时,如果不声明前面的位宽则默认为32bit,写代码时需要写明位宽和进制;如果数据表述为32位宽,但实际数值不足32位宽,则高位补0。

例如: 255 默认32bit

// reg [7:0]  cnt = 8'd255;  

// reg [3:0]  cnt = 255;    cnt 实际上等于 4'd15, 4hf,4'b1111

// reg [3:0]  cnt = 81;    cnt 实际上等于 4'b0001

//0~255 随机数生成方式    {$random}%256

// %表示求模运算,返回值是除以256后的余数

//always #10   a = {$random}%256;

赋值语句:

assign c1 = a & b; //c1   wire

always @(posedge clk) begin

c2 <= a & b;  //c2   reg

end

//敏感列表不全会生成latch,所以建议直接使用 * 代替所有敏感变量

always @(*)begin

c3 <= a & b; // c3    reg

end

时钟频率计算:

时钟周期 20ns = 50 Mhz = 1 / 20

时钟周期 10ns = 100 Mhz = 1/10

F = 1/T

1s=10^3 ms=10^6 us=10^9 ns

1GHz=1000MHz,1MHz=1000kHz,1kHz=1000Hz

1s=1000ms,1 ms=1000μs,1μs=1000ns

时钟频率

    时钟周期

1Hz      

 1s

1KHz   

1ms

1MHz   

1μs

1GHz   

1ns

定义接口时,output  reg          c2=1'b0, 可给寄存器赋初始值;但是线网不可进行赋初始值。

运算符:

% 求模运算

假设得到0~9之间的随机数:a = {$random}%10;

判断 a 是否在 10 ~ 20 之间写法:

错误写法:if(10<a<20)

正确写法:if(10 < a && a < 20)

逻辑运算符(&&、||、!):结果为 1 或者 0

按位运算符(&、|、~):每一位都进行运算

条件运算符((x)?x:x)

移位运算发:

>>右移; 高位补0

<<左移;低位补0

位拼接运算符:{};   例如:d <= {a,b,c};

位拼接实现循环左移:po_a <= {po_a[6:0],po_a[7]};

位拼接实现循环右移:po_a <= {po_a[0],po_a[7:1]};

移位运算应用于并串转换,SPI接口协议将协议以方波的形式传递出去(实际数据8bit,但SPI输出的数据是在一根线上,所以可以用移位寄存器将输入的8bit并行数据以串行方式以1bit输出方式串行输出)

条件判断语句:if…else     case…endcase

PS:

在编写always块时,if else 叠加不易过多,不然可能造成线路的延时过多。因为,每一个 if else 语句都会生成一个选择器,而每两级选择器之间会存在线路延时,当 if else 过多时,选择器链路总延时就会很长,从而影响电路时序,当时序出问题时,就算是功能仿真正确,下板后电路也是不正确的。

另外:在使用 if else 时需要注意优先级造成的影响。

使用case…endcase语句可生成多路选择器,无延时,并行判断,勿漏default(组合逻辑中),不然会生成 latch:

对于:

else if(cnt == 4'd15)

        cnt <= 4'd0;

如果cnt最大值是cnt位宽所能容纳的最大值时,计数到最大值后自动清零;

反之,如果位宽所能容纳的最大值不是cnt的最大值时,就需要加限制条件。

时钟分频:(在FPGA中,如果不调用PLL,则无法进行倍频)

在FPGA开发板上面只有一个晶振,即只有一种频率的时钟,如果需要用到不同频率的时钟,就需要在这个固定的时钟频率条件下进行分频或者倍频;

得到比固定的时钟频率更慢的时钟,进行分频操作;

得到比固定时钟频率更快的时钟,进行倍频操作;

时钟分频例子:呼吸灯

要求:从亮到灭的时间为 2s,从灭到亮的时间为 2 秒,完成呼吸的过程一共需要 4 秒时间。

解析:2s/1000份 = 2000000ms = 2000us,将2000us再分1000份=2us,

50Mhz=20ns,2us/20ns = 100,最小计数为100,计数1000次,得到一个2/1000 s,

计满1000个2/1000 s 即为2s,每2/1000 s 为1个PWM周期。

波形图:

RTL code:
module breathing_led(
	input  	wire  clk,rst,
	output 	reg 	led
	);

	reg    [6:0]	clk50m_cnt;
	reg    [9:0]	clk50m_cnt_1000;
	reg    [9:0]	pwn_cnt_1000;
	reg 			pwn_flag;

	always @(posedge clk)begin
		if(rst)
			clk50m_cnt <= 7'd0;
		else if(clk50m_cnt == 7'd99)
			clk50m_cnt <= 7'd0;
		else 
			clk50m_cnt <= clk50m_cnt + 1'b1;
	end

	always @(posedge clk)begin
		if(rst)
			clk50m_cnt_1000 <= 10'd0;
		else if(clk50m_cnt == 7'd99 && clk50m_cnt_1000 == 10'd999)
			clk50m_cnt_1000 <= 10'd0;
		else if(clk50m_cnt == 7'd99)
			clk50m_cnt_1000 <= clk50m_cnt_1000 + 1'b1;
	end

	always @(posedge clk)begin
		if(rst)
			pwn_cnt_1000 <= 10'd0;
		else if(clk50m_cnt == 7'd99 && clk50m_cnt_1000 == 10'd999 && pwn_cnt_1000 == 10'd999)
			pwn_cnt_1000 <= 10'd0;
		else if(clk50m_cnt == 7'd99 && clk50m_cnt_1000 == 10'd999)
			pwn_cnt_1000 <= pwn_cnt_1000 + 1'b1;
	end

	always @(posedge clk)begin
		if(rst)
			pwn_flag <= 1'b0;
		else if(clk50m_cnt == 8'd99 && clk50m_cnt_1000 == 10'd999 && pwn_cnt_1000 == 10'd999)
			pwn_flag <= ~pwn_flag;
	end

	assign led = (pwn_flag==1'b1)?((clk50m_cnt_1000 < pwn_cnt_1000)?1'b1:1'b0):((clk50m_cnt_1000 < pwn_cnt_1000)?1'b0:1'b1);

endmodule
TB:
`timescale 1ns/1ns
module tb_breathing_led;
	reg 		clk,rst;
	wire 		led;

	initial begin
		clk=0;
		rst=1;
		#100
		rst=0;
	end

	always #10 clk = ~clk;

	breathing_led inst_breathing_led (
		  .clk(clk)
		, .rst(rst)
		, .led(led)
		);

endmodule

 Top-down设计

Top-down 设计即自顶向下的设计

2502535898dc4b7e9e75c870b204f1d8.png

在模块例化时:

如果模块内部接口是输入,连接的接口类型可以是 wire/reg;

如果模块内部接口是输出,连接的接口类型可以是 wire;

有限状态机

一段式指的是在一个 always块内使用时序逻辑既描述状态的转移,同时也描述数据的输出;

二段式指一个always 块使用时序逻辑描述状态转移,另外一个 always 块使用时序逻辑描述数据输出;

三段式指使用三个 always 块,一个 always 模块采用时序逻辑描述状态转移,一个 always 块采用组合逻辑判断状态转移条件,描述状态转移规律,另一个 always 块描述状态输出(可以用组合电路输出,也可以时序电路输出)。

//状态机模板
module 模块名(
端口 1,
端口 2,
...
端口 N,
);

/时序逻辑描述的状态转移
 always@(posedge clk or negedge rst_n)
 状态转移;

 //时序逻辑描述的数据输出
 always@(posedge clk or negedge rst_n)
 数据输出;

endmodule

 对于modelsim自动化仿真脚本的建立:

run.tcl

#退出仿真
quit -sim   
#清除临时保存的仿真文件
.main clear  

#建立库
vlib work 
#编译 .v
vlog ./tb_test.v     
#编译所有 .v 到work库中
vlog ./../src/*.v  	 
#启动仿真,-voptargs+=acc(等于手动点simulation),选择work库中的tb顶层文件,进行仿真
vsim -voptargs+=acc work.tb_test 


#添加wave
add wave /tb_test/test_init/*
#执行1us
run 1us

状态机例子:

一个自动售货机中的商品 2.5 元一件,每次投币既能投入 1 元, 也能投入 0.5 元,当投入 3 元时,找零0.5元,使用状态机描述。

RTL code

modele fsm(
	input 	wire 	clk,
	input 	wire 	rst,
	input 	wire 	pi_money,
	output  reg 	po_money,
	output  reg 	po_water
	);
	reg [4:0] state;

	parameter  idle = 5'b00001;
	parameter  half = 5'b00010;
	parameter  one  = 5'b00100;
	parameter  one_half = 5'b01000;
	parameter  two	= 5'b10000;

	always @(posedge clk)begin
		if(rst==1'b1)begin
			state <= idle;
		end
		else begin
			case(state)
				idle : if(pi_money == 1'b1)	state <= one;
					else if(pi_money == 1'b0) state <= half;
				half : if(pi_money == 1'b1)	state <=one_half;
					else if(pi_money == 1'b0) state <= one;
				one : if(pi_money == 1'b1)	state <= two;
					else if(pi_money == 1'b0) state <= one_half;
				one_half: if(pi_money == 1'b1)	state <= idle;
					else if(pi_money == 1'b0) state <= two;
				two : if(pi_money == 1'b1)	state <= idle;
					else if(pi_money == 1'b0) state <= idle;
				default : state <= idle;
			endcase
		end
	end

	always @(posedge clk)begin
		if(rst==1'b1)begin
			po_water <= 1'b0;
			po_money <= 1'b0;
		end
		else if(state == one_half && pi_money == 1'b1) || (state == two && pi_money == 1'b0)begin
			po_water <= 1'b1;
			po_money <= 1'b0;
		end
		else if (state == two && pi_money == 1'b1) begin
			po_water <= 1'b1;
			po_money <= 1'b1;
		end
	end

endmodule
tb:
`timescale 1ns/1ns
module tb_fsm;
	reg 	clk,rst,pi_money;
	wire    po_money,po_water;

	initial begin
		rst = 1;
		clk = 0;
		pi_money = 0;
		#100
		rst = 0;
	end

	always #10 clk = ~clk;
	always #20 pi_money = {$random};

	fsm fsm_inst(
		 .clk(clk)
		,.rst(rst)
		,.pi_money(pi_money)
		,.po_money(po_money)
		,.po_water(po_water)
		);

endmodule
run.tcl:

quit -sim   
.main clear  

vlib work 
vlog ./tb_fsm.v     
vlog ./../src/*.v  	 
vsim -voptargs+=acc work.tb_fsm 

#定义结构体进行匹配
virtual type{
	{5'b00001 idle}
	{5'b00010 half}
	{5'b00100 one}
	{5'b01000 one_half}
	{5'b10000 two}
}abc;

#创建新信号,(abc)强制转换
virt function{(abc)/tb_test/test_init/state}new_state
add wave /tb_fsm/fsm_init/*
run 1us
  • 6
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值