————来源北京交大李金城老师《Verilog零基础入门》状态机代码设计与仿真讲座整理
第10讲 状态机代码设计与仿真(串口指令处理器)_哔哩哔哩_bilibili
一、串口指令处理器功能框图
电路图:
二、代码实现
1、串口接收模块(UART_RXer)
/****串口协议简介***************
1、串口发送端口空闲时为高电平;
2、发送端口拉低表示数据传送即将开始;
3、字节数据低位先发;
4、字节发送后拉高,表示字节传送结束;
5、字节位宽可以不为8;
6、常用波特率有4800、9600、115200等。
*********************************/
//状态规划:
//状态1: 空闲识别
//状态2: 等起始位
//状态3: 收b0
//状态4: 收b1
//状态5: 收b2
//状态6: 收b3
//状态7: 收b4
//状态8: 收b5
//状态9: 收b6
//状态10:收b7
//......
//串口数据接收(波特率为4800,系统时钟频率24MHz)
module UART_RXer(
clk,
rst,
RX,
data_out,
en_data_out
);
input clk; //时钟
input rst; //复位
input RX; //串口输入
output[7:0] data_out; //接收字节输出(8位)
output en_data_out; //输出使能,每接收完成一个字节,产生一个同步脉冲
reg[7:0] data_out;//字节输出
reg[7:0] state;//主状态机
reg[12:0] con;//用于计录比特宽度,有效值不超过7500
reg[3:0] con_bits;//用于记录比特数
reg RX_delay;//RX的延时
reg en_data_out;//输出使能
always @(posedge clk or negedge rst)
if(~rst)begin
state<=0;con<=0;con_bits<=0;RX_delay<=0;
data_out<=0;en_data_out<=0;
end
else begin
RX_delay<=RX; //这里赋值得到:RX_delay为RX延时一个时钟周期的副本
case (state)
0://等空闲
begin
//5000:传一个比特位的时钟周期数(T/bit),
//5000=时钟24000000/波特率4800
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; //能连续12位高电平,确认为空闲状态
end
end
1://等起始位
begin
//如果RX的反向和其延时一个时钟周期后相与后还是高电平,
//则确认到了起始位
en_data_out<=0;//复位使能
if(~RX&RX_delay) begin //找到下降沿
state<=2;
end
end
2://收最低位b0
begin
if(con==7500-1) begin
//1.5*(T/bit),即1.5*5000=7500
con<=0;
data_out[0]<=RX;
state<=3;
end
else begin
con<=con+1;
end
end
3://收b1
begin
if(con==5000-1) begin
//1(T/bit),即1*5000=5000,下同
con<=0;
data_out[1]<=RX;
state<=4;
end
else begin
con<=con+1;
end
end
4://收b2
begin
if(con==5000-1) begin
con<=0;
data_out[2]<=RX;
state<=5;
end
else begin
con<=con+1;
end
end
5://收b3
begin
if(con==5000-1) begin
con<=0;
data_out[3]<=RX;
state<=6;
end
else begin
con<=con+1;
end
end
6://收b4
begin
if(con==5000-1) begin
con<=0;
data_out[4]<=RX;
state<=7;
end
else begin
con<=con+1;
end
end
7://收b5
begin
if(con==5000-1) begin
con<=0;
data_out[5]<=RX;
state<=8;
end
else begin
con<=con+1;
end
end
8://收b6
begin
if(con==5000-1) begin
con<=0;
data_out[6]<=RX;
state<=9;
end
else begin
con<=con+1;
end
end
9://收b7
begin
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 of UART_RXer ----
`timescale 1ns/10ps
module UART_RXer_tb;
reg clk,rst;
wire RX;
wire[7:0] data_out;
wire en_data_out;
reg[25:0] RX_send;//里面装有串口字节发送数据
assign RX=RX_send[0];//连接RX
reg[12:0] con;
UART_RXer UART_RXer( //同名例化
clk,
rst,
RX,
data_out,
en_data_out
);
initial begin
clk<=0;rst<=0;con<=0;
RX_send<={1'b1,8'haa,1'b0,16'hffff};//位拼接构造一个1+8+1+16=26位串口数据
#17 rst<=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向右循环移位,模拟16+1+8+1串口数据
RX_send[24:0]<=RX_send[25:1];
RX_send[25]<=RX_send[0];
end
end
endmodule
2、串口发送模块(UART_TXer)
//8位串口数据发送,先低位后高位(波特率为4800,系统时钟频率24MHz)
//TX为串口输出端口
//rdy为空闲标志,字节发送时rdy为高
//data_in为准备发送的字节
//en_data_in为字节发送使能端口,高使能
//
//状态规划:
//状态1: 等待发送使能,如果en_data_in为1,填充发送寄存器。
//状态2: 发送寄存器右移,逐个位发送,完成后跳回等待发送使能
// (位拼接发送寄存器{1,data_in,0},右移发送寄存器,最低位为TX)
//
module UART_TXer(
clk,
rst,
data_in,
en_data_in,
TX,
rdy
);
input clk;
input rst;
input[7:0] data_in; //准备发送的8位数据
input en_data_in; //发送使能
output TX; //输出
output rdy; //空闲标志 0表示空闲
reg[1:0] state; //主状态机寄存器
reg[9:0] send_buf; //发送寄存器,相当于机枪弹夹
reg[12:0] con; //用于计算波特周期;
assign TX=send_buf[0]; //连接TX,相当于弹夹当前子弹,(组合逻辑,TX一直指向send_buf[0])
reg[9:0] send_flag; //判断右移结束标记,用于跟踪发送进度;
reg rdy;
always@(posedge clk or negedge rst )
if (~rst )begin
state<=0;send_buf<=1;con<=0;rdy<=0;
send_flag<=10'b10_0000_0000; //最高位为1,其他位为0
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://串口发送,寄存器右移
begin
if (con==5000-1) begin
con<=0;
end
else begin
con<=con+1;
end
//0-4999期间TX保持低位0,相当于发送一个起始位
if (con==5000-1) begin
send_buf[8:0]<=send_buf[9:1]; //右移,子弹上膛
send_flag[8:0]<=send_flag[9:1];//同步send_buf右移
end
if (send_flag[0]) begin
//右移到剩最后一位,数据发送完毕,此时send_buf[0]=1,即TX=1,置空闲状态
rdy<=0;
state<=0;
end
end
endcase
end
endmodule
//仿真模块-----testbench of UART_TXer-----
`timescale 1ns/10ps
module UART_TXer_tb();
reg clk,rst;
reg[7:0] data_in;
reg en_data_in;
wire TX;
wire rdy;
UART_TXer UART_TXer(
.clk(clk),
.rst(rst),
.data_in(data_in),
.en_data_in(en_data_in),
.TX(TX),
.rdy(rdy)
);
initial begin
clk<=0;rst<=0;en_data_in<=0;
data_in<=8'h5a; //0101 1010
#17 rst<=1;
#30 en_data_in<=1;
#10 en_data_in<=0;
#2000000 $stop;
end
always #5 clk<=~clk;
endmodule
3、指令处理器模块(cmd_pro)
//串口指令处理器///
//cmd_pro指令集格式:
//每次连续接收三个字节,第一字节为指令CMD,第二字节为操作数A,第三字节为操作数B
//指令集如下:
//CMD 操作
//8'h0a A+B
//8'h0b A-B
//8'h0c A&B
//8'h0d A|B
//
//状态规划:
//状态0: 接收指令,遇见en_din_pro为1,则接收数
//状态1: 接收操作数A
//状态2: 接收操作数B
//状态3: 指令译码,根据指令计算
//状态4: 输出使能,返回指令执行结果
//
//串口指令处理模块
module cmd_pro(
clk,
rst,
din_pro,
en_din_pro,
dout_pro,
en_dout_pro,
rdy
);
input clk;
input rst;
input[7:0] din_pro; //指令和数据输入端口
input en_din_pro; //输入使能
output[7:0] dout_pro; //指令执行结果
output en_dout_pro; //指令输出使能
input rdy; //串口发送空闲模块标志,0表示空闲
reg en_dout_pro;
reg[3:0] state; //主状态机寄存器
reg[7:0] cmd_reg,A_reg,B_reg; //存放指令,操作数A和B
reg[7:0] dout_pro;
parameter add_ab=8'h0a; //加操作
parameter sub_ab=8'h0b; //减操作
parameter and_ab=8'h0c; //与操作
parameter or_ab =8'h0d; //或操作
always@(posedge clk or negedge rst )
if(~rst )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; //收操作数A
state<=2;
end
end
2://收B
begin
if(en_din_pro)begin
B_reg<=din_pro; //收操作数B
state<=3;
end
end
3://指令译码和执行
begin
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
state<=4;
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
4、串口收发顶层模块(UART_top)
//串口指令处理器
//中间连接信号以cmd_pro模块为参考
//
module UART_top(
clk,
rst,
RX,
TX
);
input clk; //时钟
input rst; //复位
input RX; //串口输入
output TX; //串口输出
wire[7:0] din_pro; //指令和数据输入端口
wire en_din_pro; //输入使能
wire[7:0] dout_pro; //指令执行结果
wire en_dout_pro; //指令输出使能
wire rdy; //空闲标志 0表示空闲
UART_RXer UART_RXer(
.clk(clk),
.rst(rst),
.RX(RX),
.data_out(din_pro),
.en_data_out(en_din_pro)
);
UART_TXer UART_TXer(
.clk(clk),
.rst(rst),
.data_in(dout_pro),
.en_data_in(en_dout_pro),
.TX(TX),
.rdy(rdy)
);
cmd_pro cmd_pro(
.clk(clk),
.rst(rst),
.din_pro(din_pro),
.en_din_pro(en_din_pro),
.dout_pro(dout_pro),
.en_dout_pro(en_dout_pro),
.rdy(rdy)
);
endmodule
//仿真模块---testbench of UART_top---
//模拟接收数据RX包含3个字节数据,其中,第一个为操作码,另两个为操作数
//观察发送输出结果是否正确
//
`timescale 1ns/10ps
module UART_top_tb;
reg clk,rst;
wire RX;
wire TX;
reg[45:0] RX_send; //里面装有串口字节发送数据
assign RX=RX_send[0]; //连接RX
reg[12:0] con;
UART_top UART_top(
clk,
rst,
RX,
TX
);
initial begin
clk<=0;rst<=0;
//位拼接构造一个3*(1+8+1)+16=46位串口数据
//例如:RX接收的是'加操作'指令h0a,操作数为h50、h22,
//结果:TX应是h72='b0111_0010
RX_send<={1'b1,8'h22,1'b0,1'b1,8'h50,1'b0,1'b1,8'h0a,1'b0,16'hffff};
con<=0;
#17 rst<=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向右循环移位,模拟串口进来的数据
RX_send[44:0]<=RX_send[45:1];
RX_send[45]<=RX_send[0];
end
end
endmodule
三、ModelSim仿真(UART_top_tb)