第一次写博客,记录北京交通大学李金城的Verilog语言入门
参考复制了一些代码,其他代码均为自敲
Verilog语言入门——边学边练
前言
入门实践,应具备基础的数电知识
简介:Verilog HDL(Hardware Description Language)
一、基本逻辑门代码设计与仿真
反相器
1.1反相器
//2020-10-7 旧事沟圈
//反相器
`timescale 1ns/10ps
module inv(
A,
Y
);
input A;
output Y;
assign Y=~A;
endmodule
//写好代码之后,要对其进行测试,即例化testbench
//testbench of inv
module inv_tb;//testbench没有端口,所以后面不加括号
//异名例化
reg aa;
wire yy;
inv inv(
.A(aa),
.Y(yy)
);
initial begin//按时间定义各个变量的值,为顶层
aa <= 0;
#10 aa <= 1;//过10时间单位
#10 aa <= 0;
#10 aa <= 1;
#10 $stop;//仿真的系统停止
end
endmodule
八位反相器:
1.2与非门
//2020-10-7 旧事沟圈
//与非门
`timescale 1ns/10ps
module nand_gate (
A,
B,
Y
);
input A ;
input B ;
output Y ;
assign Y = ~ (A & B);
endmodule
// ----testbench of nand_gate
module nand_gate_tb;
reg aa,bb ;//输入
wire yy ;//看到的变量
nand_gate nand_gate (
.A (aa),
.B (bb),
.Y (yy)
);
initial begin
aa <= 0;bb <= 0;
#10 aa <= 0;bb <= 1;
#10 aa <= 0;bb <= 1;
#10 aa <= 1;bb <= 0;
#10 aa <= 1;bb <= 1;
#10 $stop;
end
endmodule
1.3四位与非门
代码:
//2020-10-7 旧事沟圈
//4位与非门
`timescale 1ns/10ps
module nand_gate_4bits (
A,
B,
Y
);
input [4-1:0] A ;
input [4-1:0] B ;
output [4-1:0] Y ;
assign Y = ~ (A & B);
endmodule
// ----testbench of nand_gate
module nand_gate_4bits_tb;
reg [3:0] aa,bb ;
wire [3:0] yy ;
nand_gate_4bits nand_gate_4bits (
.A(aa),
.B(bb),
.Y(yy)
);
initial begin
aa <= 4'b0000;bb <= 4'b1111; //
#10 aa <= 4'b0010;bb <= 4'b0110;
#10 aa <= 4'b0111;bb <= 4'b0100;
#10 aa <= 4'b1001;bb <= 4'b0110;
#10 aa <= 4'b0100;bb <= 4'b1000;
#10 $stop;
end
endmodule
总结:
二、组合逻辑(较简单,代码只有重点部分)
2.1补码运算
代码如下(示例):
//组合逻辑,多用wire
wire[6:0] b;//按位取反的符号位;
wire[7:0] y;//负数的补码;
assign b=~a[6:0];//按位取反;
assign y[6:0]=b+1;按位取反加一;
assign a_comp=a[7]?y:a;//二选一,和c语言一样;
//testbench 模块,连续变化(从0000_0000到1111_1111)
initial begin
a_in<=0;
#3000 $stop;
end
always #10 a_in<=a_in+1;
2.2 7位译码器(数码管)
//case语句块
always (num) begin
case(num)
4'd0: begin a_g<=7'b111_1110; end
4'd1: begin a_g<=7'b011_0000; end
4'd2: begin a_g<=7'b110_1101; end
4'd3: begin a_g<=7'b111_1001; end
4'd4: begin a_g<=7'b011_0011; end
4'd5: begin a_g<=7'b101_1011; end
4'd0: begin a_g<=7'b111_1110; end
4'd6: begin a_g<=7'b101_1111; end
4'd7: begin a_g<=7'b111_0000; end
4'd8: begin a_g<=7'b111_1111; end
4'd9: begin a_g<=7'b111_1011; end
default: begin a g<=7'b000_0001; end//中杠;
end
endcase
三、时序逻辑
3.1 计数器
reg[7:0] y;
wire[7:0] sum;
assign sun=y+1;//组合逻辑部分;
always@(posedge clk or negedge res)
if(~res)begin//如果复位信号到来
y<=0;//对应实际的触发器,时钟一来就复位
end
else begin
y<=sum;
end
一开始复位信号是0,还在复位,需要把它释放
#17 res<=1
initial begin
clk<=0;res<=0;
#17 res<=1;
#6000 $stop;
end
always #5 clk<=~clk;//每过5纳秒翻一次,得到一个10纳秒为周期的时钟信号
3.2 伪随机码发生器
懒得打代码了~
同样,异名例化,用计数器的方法定义时钟
3.3 秒计数器
reg[24:0] con_t;//秒脉冲分频计数器
reg s_pulse;//秒脉冲尖
//24MHz,24000000转化成二进制25位就可以计满
always@(posedge clk or negedge res)
if(~res) begin
con_t<=0;s_pulse<=0;//触发器一定要清零
end
else begin
if(con_t==24000000-1) begin
con_t<=o;
end
else begin
con_t<=con_t+1;
if(con_t==O) begin
s_pulse<=1;
end
else begin
s_pulse<=0;
end
end
3.4 相邻16点相加输出
Add adjacent 16 points
//2022-10-7 旧事沟圈
`timescale 1ns/10ps
module sigma_16p (
data_in,
syn_in,
clk,
res,
data_out,
syn_out
);
input[7:0] data_in; //采样信号
input syn_in; //采样时钟
input clk;
input res;
output syn_out; //累加结果同步脉冲
output[11:0] data_out; //累加结果输出
reg syn_in_n1; //syn_in反向延时
wire syn_pulse; //采样时钟上升沿识别脉冲
assign syn_pulse = syn_in & syn_in_n1;
reg[3:0] con_syn; //采样时钟循环计数器
wire[7:0] comp_8; //补码
wire[11:0] d_12; //升位结果
assign comp_8=data_in[7]?{data_in[7],~data_in[6:0]+1}:data_in; //补码运算,二选一
assign d_12={comp_8[7],comp_8[7],comp_8[7],comp_8[7],comp_8};//升位
reg [11:0] sigma; //累加计算
reg [11:0] data_out;
reg syn_out;
always @(posedge clk or negedge res) begin
if (~res) begin
syn_in_n1<=0;
con_syn<=4'b1111;
sigma<=0;
data_out<=0;
syn_out<=0;
end
else begin
syn_in_n1<=~syn_in;
if (syn_pulse) begin
con_syn<=con_syn+1;
end
if (syn_pulse) begin
if (con_syn==15) begin
data_out<=sigma;
sigma<=d_12;
syn_out<=1;
end
else begin
sigma<=sigma+d_12;
end
end
else begin
syn_out<=0;
end
end
end
endmodule
//----testbench of sigma_16p----
module sigma_16p_tb;
reg clk,res;
reg [7:0] data_in;
reg syn_in;
wire syn_out;
wire [11:0] data_out;
sigma_16p sigma_16p (
.data_in(data_in),
.syn_in(syn_in),
.clk(clk),
.res(res),
.data_out(data_out),
.syn_out(syn_out)
);
initial begin
clk<=0;res<=0;
data_in<=1;//若改为-1,就是:
//data_in<=8'b1000_0001,最高位为符号位
syn_in<=0;
#17 res<=1;
#5000 data_in<=8'b1000_0001;
#25000 $stop;
end
//系统时钟
always #5 clk=~clk;
//采样时钟
always #100 syn_in<=~syn_in;
endmodule
仿真结果:
四、状态机
4.1 最简单的状态机——三角波发生器
//2022-10-7 旧事沟圈
//最简单的状态机——三角波发生器
`timescale 1ns/10ps
module tri_gen(
clk,
res,
d_out
);
input clk,res;
output[8:0] d_out;
reg state;
reg[8:0] d_out;
always@(posedge clk or negedge res)
if(~res) begin
state<=0;d_out<=0;
end
else begin
case(state)
0://上升
begin
d_out<=d_out+1;
if(d_out==299) begin
state<=1;
end
end
1://下降;
begin
d_out<=d_out-1;//在从0跳转的时候+1也完成了,所以进入1状态的时候是以300进入
if(d_out==1) begin
state<=0;
end
end
endcase
end
endmodule
//------testbench of tri_gen----
module tri_gen_tb;
reg clk,res;
wire[8:0] d_out;
tri_gen U1(
.clk(clk),
.res(res),
.d_out(d_out)
);
initial begin
clk<=0;res<=0;
#17 res<=1;
#20000 $stop;
end
always #5 clk<=~clk;
endmodule
仿真的结果
改成平顶,梯形波的时候,多一个状态
注意可能存在跳出的时候,加一个default,全部置零
//2022-10-7
//最简单的状态机——三角波发生器
`timescale 1ns/10ps
module tri_gen(
clk,
res,
d_out
);
input clk,res;
output[8:0] d_out;
reg[1:0] state;
reg[8:0] d_out;
reg[7:0] con;
always@(posedge clk or negedge res)
if(~res) begin
state<=0;d_out<=0;con<=0;
end
else begin
case(state)
0://上升
begin
d_out<=d_out+1;
if(d_out==299) begin
state<=1;
end
end
1://平顶;
begin
if(con==200) begin
state<=2;
con<=0;
end
else begin
con<=con+1;
end
end
2://下降;
begin
d_out<=d_out-1;
if(d_out==1) begin
state<=0;
end
end
default://state的reg定义为两位,多了一位,所以要多一个状态
begin
state<=0;
con<=0;
d_out<=0;
end
endcase
end
endmodule
//------testbench of tri_gen----
module tri_gen_tb;
reg clk,res;
wire[8:0] d_out;
tri_gen U1(
.clk(clk),
.res(res),
.d_out(d_out)
);
initial begin
clk<=0;res<=0;
#17 res<=1;
#20000 $stop;
end
always #5 clk<=~clk;
endmodule
上下都有平段:
//2022-10-7
//最简单的状态机——三角波发生器
`timescale 1ns/10ps
module tri_gen(
clk,
res,
d_out
);
input clk,res;
output[8:0] d_out;
reg[1:0] state;
reg[8:0] d_out;
reg[7:0] con;
always@(posedge clk or negedge res)
if(~res) begin
state<=0;d_out<=0;con<=0;
end
else begin
case(state)
0://上升
begin
d_out<=d_out+1;
if(d_out==299) begin
state<=1;
end
end
1://平顶;
begin
if(con==200) begin
state<=2;
con<=0;
end
else begin
con<=con+1;
end
end
2://下降;
begin
d_out<=d_out-1;
if(d_out==1) begin
state<=3;
end
end
3://平顶,不需要default了,2bite四个状态满了
begin
if(con==200) begin
state<=0;
con<=0;
end
else begin
con<=con+1;
end
end
endcase
end
endmodule
//------testbench of tri_gen----
module tri_gen_tb;
reg clk,res;
wire[8:0] d_out;
tri_gen U1(
.clk(clk),
.res(res),
.d_out(d_out)
);
initial begin
clk<=0;res<=0;
#17 res<=1;
#40000 $stop;
end
always #5 clk<=~clk;
endmodule
4.2 串口数据接收
//2022-10-7 旧事沟圈
`timescale 1ns/10ps//testbench时间单位
module UART_RXer(
clk,
res,
RX,
data_out,
en_data_out
);
input clk;
input res;
input RX;
output[7:0] data_out;//接收字节输出
output en_data_out;//输出使能
reg[7:0] state;//主状态机
reg[12:0] con;//用于计算比特宽度;
//系统时钟频率24兆赫兹(24,000,000),支持4800波特率
//计数24000000/4800=5000(0001 0011 1000 1000),13位
//1.5倍宽度,5000*1.5=7500,算8000(0001 1111 0100 0000),13位
reg[4:0] con_bits;//用于计算比特数,计转了多少圈
reg RX_delay;//RX延时
reg en_data_out;
reg[7:0] data_out;
always@(posedge clk or negedge res)
if(~res)begin
state<=0;con<=0;con_bits<=0;RX_delay<=0;
data_out<=0;en_data_out;<=0;
end
else begin
RX_delay<=RX;//有时钟就在动,不需要条件
case(state)
0://等空闲,10个bit以上连续的1
begin
if(con==5000-1)begin
con<=0;//计数转了一圈
end
else begin
con<=con+1;
end
if(con==0)begin
if(RX)begin
con_bits<=con_bits+1;
end
else begin
con_bits<=0;
end
end
if(con_bits==12)begin
state<=1;
end
end
1://等起始位;
begin
en_data_out<=0;
if(~RX&RX_delay)begin
state<=2;
end
end
2://收最低位b0;
begin
//要等1.5Tbit,5000*1.5=7500
if(con==7500-1)begin
con<=0;
data_out[0]<=RX;
state<=3;
end
else begin
con<=con+1;
end
end
3://收最低位b1;
begin
//要等1Tbit,5000*1=5000
if(con==5000-1)begin
con<=0;
data_out[1]<=RX;
state<=4;
end
else begin
con<=con+1;
end
end
4://收最低位b2
begin
//要等1Tbit,5000*1=5000
if(con==5000-1)begin
con<=0;
data_out[2]<=RX;
state<=5;
end
else begin
con<=con+1;
end
end
5://收最低位b3
begin
//要等1Tbit,5000*1=5000
if(con==5000-1)begin
con<=0;
data_out[3]<=RX;
state<=6;
end
else begin
con<=con+1;
end
end
6://收最低位b4
begin
//要等1Tbit,5000*1=5000
if(con==5000-1)begin
con<=0;
data_out[4]<=RX;
state<=7;
end
else begin
con<=con+1;
end
end
7://收最低位b5
begin
//要等1Tbit,5000*1=5000
if(con==5000-1)begin
con<=0;
data_out[5]<=RX;
state<=8;
end
else begin
con<=con+1;
end
end
8://收最低位b6
begin
//要等1Tbit,5000*1=5000
if(con==5000-1)begin
con<=0;
data_out[6]<=RX;
state<=9;
end
else begin
con<=con+1;
end
end
9://收最低位b7
begin
//要等1Tbit,5000*1=5000
if(con==5000-1)begin
con<=0;
data_out[7]<=RX;
state<=10;
end
else begin
con<=con+1;
end
end
10://产生使能脉冲
begin
en_data_out<=1;
state<=1;
end
default://其他未定义状态
begin
state<=0;
con<=0;
con_bits<=0;
en_data_out<=0;
end
endcase
end
endmodule
//-------testbench-----------
module UART_RXer_tb;
reg clk,res;
wire RX;
wire[7:0] data_out;
wire en_data_out;
//一个字节的数据(8位),里面有起始位和结束位(2位),带有16个1(16位),一共26位
reg[25:0] RX_send;//里面装有串口字节发送数据
assign RX=RX_send[0];//连接RX
reg[12:0] con;
//同名例化
//上面的量和下面的端口一一对应,而且名字完全一样,可以不打.和()
//例化很多的时候,用异名例化
UART_RXer UART_RXer(
clk,
res,
RX,
data_out,
en_data_out
);
initial begin
clk<=0;res<=0;con<=0;
RX_send<={1'b1,8'haa,1'b0,16'hffff};
//1‘b1结束位,8'haa发送的数据,1'b0起始位,16'hffff空闲位接收数据
#17 res<=1;
#4000000 $stop;//仿真结束
end
always #5 clk<=~clk;
//发送数据
//连续右移
always@(posedge clk)begin
if(con==5000-1)begin
con<=0
end
else begin
con<=con+1;
end
if(con==0)begin
RX_send[24:0]<=RX_send[25:1];//低25位等于高25位,错开一项
RX_send[25]<=RX_send[0];//反复右移旋转
end
end
endmodule
接收到的数据
4.3 串口数据发送
// 2022-10-7 旧事沟圈
// 串口数据发送
`timescale 1ns/10ps
module UART_TXer(
clk,
res,
data_in,
en_data_in,
TX,
rdy
);
input clk;
input res;
input[7:0] data_in; // 准备发送的数据
input en_data_in; // 发送使能
output TX; // 不是output[9:0],发送数据是一位一位发的,一位寄存器即可
output rdy; // 空闲标志,0表示空闲
reg[3:0] state; // 主状态机寄存器
reg[9:0] send_buf; // 发送寄存器
assign TX = send_buf[0]; // 连接TX
reg[9:0] send_flag; // 用于判断右移结束
reg[12:0] con; // 用于计算波特周期
reg rdy; // ready=0 空闲
always@(posedge clk or negedge res)
if(~res)begin
state<=0; send_buf<=1; con<=0; rdy<=0; send_flag<=10'b10_0000_0000; // TX在空闲时是1,send_buf
end
else begin
case(state)
0:// 等待使能信号
begin
if(en_data_in)begin
send_buf <= {1'b1, data_in, 1'b0};
send_flag <= 10'b10_0000_0000;
rdy <= 1; // 准备发数据,不空闲
state<=1;
end
end
1:// 串口发送,send_buf寄存器右移
begin
if(con==5000-1)begin // 一个bit占宽0-4999
con<=0;
end
else begin
con<=con+1;
end
if(con==0)begin // 新的bit到来
send_buf[8:0] <= send_buf[9:1]; // 右移,该时刻send_buf[8:0]等于上一时刻send_buf[9:1]
send_flag[8:0] <= send_flag[9:1];
end
if(send_flag[0])begin
rdy <= 0; // 发完数据,空闲
state <= 0;
end
end
endcase
end
endmodule
// testbench
module UART_TXer_tb;
reg clk,res;
reg[7:0] data_in;
reg en_data_in;
wire TX;
wire rdy;
UART_TXer UART_TXer(
clk,
res,
data_in,
en_data_in,
TX,
rdy
); // 同名例化
initial begin
clk<=0; res<=0; data_in<=8'h0a; en_data_in<=0;
#17 res<=1;
#30 en_data_in<=1; // 发送
#10 en_data_in<=0;
#2000000 $stop;
end
always #5 clk = ~clk;
endmodule
4.4 串口指令处理器
//2022-10-7 旧事沟圈
//cmd模块
module cmd_pro(
clk,
res,
din_pro,
en_din_pro,
dout_pro,
en_dout_pro,
rdy
);
input clk;
input res;
input[7:0] din_pro;//指令和数据输入端口;
input en_din_pro;//输入使能;
output[7:0] dout_pro;//指令执行结果
output en_dout_pro;//指令输出使能
output rdy;//串口发送模块空闲标志,0表示空闲
parameter add_ab=8'h0a;
parameter sub_ab=8'h0b;
parameter and_ab=8'h0c;
parameter or_ab =8'h0d;
reg[2:0] state;//主状态机寄存器;不知道几个状态是,多设置几位
reg[7:0] cmd_reg,A_reg,B_reg;//存放指令、A和B;
reg[7:0] dout_pro;
reg en_dout_pro;
always@(posedge clk or negedge res)
if(~res)begin
state<=0;cmd_reg<=0;A_reg<=0;B_reg<=0;dout_pro<=0;
en_dout_pro<=0;
end
else begin
case(state)
0://等指令
begin
en_dout_pro<=0;
if(en_din_pro)begin
cmd_reg<=din_pro;
state<=1;
end
end
1://收A;
begin
if(en_din_pro)begin
A_reg<=din_pro;
state<=2;
end
end
2://收B;
begin
if(en_din_pro)begin
B_reg<=din_pro;
state<=3;
end
end
3://指令译码和执行
begin
state<=4;
case(cmd_reg)
add_ab:begin dout_pro<=A_reg+B_reg;end
sub_ab:begin dout_pro<=A_reg-B_reg;end
and_ab:begin dout_pro<=A_reg&B_reg;end
or_ab: begin dout_pro<=A_reg|B_reg;end//规整性
endcase
end
4://发送指令执行结果;
begin
if(~rdy)begin
en_dout_pro<=1;
state<=0;
end
end
default://
begin
state<=0;
en_dout_pro<=0;
end
endcase
end
endmodule
给串口接收模块发指令,处理之后发给cmd_pro
`timescale 1ns/10ps
module UART_top(
clk,
res,
RX,
TX
);
input clk;
input res;
input RX;
output TX;
//在这里例化三个模块
//需要定义RX,TX模块与cmd模块联系的5个中间信号,信号名以cmd为准
//因为这五个信号都是连接信号,定义为wire类型
wire[7:0] din_pro;
wire en_din_pro;
wire[7:0] dout_pro;
wire en_dout_pro;
wire rdy;
//封装顶层的时候,不管谁是输入,谁是输出,从顶层看都是连线,纯连接wire类型
//做testbench时,会改变模块的输入,所以要定义为reg类型
//异名例化
//串口数据接收
UART_RXer UART_RXer(
.clk(clk),
.res(res),
.RX(RX),
.data_out(din_pro),
.en_data_out(en_din_pro)
);
//串口数据发送
UART_TXer UART_TXer(
.clk(clk),
.res(res),
.data_in(dout_pro),
.en_data_in(en_dout_pro),
.TX(TX),
.rdy(rdy)
);
//指令处理
cmd_pro cmd_pro(
.clk(clk),
.res(res),
.din_pro(din_pro),
.en_din_pro(en_din_pro),
.dout_pro(dout_pro),
.en_dout_pro(en_dout_pro),
.rdy(rdy)
);
endmodule
测试模块
//------testbench----
module UART_top_tb;
reg clk,res;
wire RX;
wire TX;
reg[45:0] RX_send;//里面装有串口字节发送数据
assign RX=RX_send[0];//连接RX
reg[12:0] con;
UART_top UART_top(
clk,
res,
RX,
TX
);
initial begin
clk<=0;res<=0;con<=0;
RX_send<={1'b1,8'h09,1'b0,1'b1,8'h06,1'b0,1'b1,8'h0a,1'b0,16'hffff};
//发送的数据8'h09,8'h06,发送的操作符8'h0a;
#17 res<=1;
#1000 $stop;//仿真结束
end
always #5 clk<=~clk;
//发送数据
//连续右移
always@(posedge clk)begin
if(con==5000-1)begin
con<=0
end
else begin
con<=con+1;
end
if(con==0)begin
RX_send[44:0]<=RX_send[45:1];//错一项
RX_send[45]<=RX_send[0];//反复右移旋转
end
end
endmodule
https://www.bilibili.com/video/BV1hX4y137Ph?p=1&vd_source=9209c4d54797033b141e57a715563102