1. 使用串口发送5个字节数据到电脑
uart协议规定,发送的数据位只能是6,7,8位,如果数据位不符合,接收者接收不到数据。所以我们需要将40位数据data分为5个字节数据分别发送,那么接收者就能通过uart协议接收到数据了。
2. 第一次使用状态机写设计代码(不够简洁的版本)
为什么要使用状态机:由于在always语句块中,语句是并行执行的,当我想要处理有先后顺序的问题时,就需要用状态机来解决。
针对发送五个字节数据到电脑的目的按,可将状态机的使用分为以下三种情况:
1. 没有开始发送(数据请求trans_go信号没有出现)
2. 数据请求trans_go信号出现
3. 数据请求trans_go信号出现
2.1 设计代码
module uart_tx_data(
clk,
rstn,
trans_go,
data40,
uart_tx,
trans_done
);
input clk;
input rstn;
input trans_go;
input [39:0] data40;
output uart_tx;
output reg trans_done;
reg [7:0] data;
reg send_go;
wire tx_done;
uart_byte_tx uart_byte_tx(
.clk(clk),
.rstn(rstn),
.blaud_set(3'd4),
.data(data),
.send_go(send_go),
.uart_tx(uart_tx),
.tx_done(tx_done)
);
reg [2:0]state;
always@(posedge clk or negedge rstn)
if(!rstn) begin
state <= 0;
send_go <= 0;
data <= 0;
trans_done <= 0;
end
else
case(state)
0: begin
if(trans_go)begin
trans_done <= 0;
data <= data40[7:0];
send_go <= 1;
state <= 1;
end
else begin
data <= data;
send_go <= 0;
state <= 0;
end
end
1:begin
if(tx_done)begin
data <= data40[15:8];
send_go <= 1;
state <= 2;
end
else
send_go <= 0;
end
2:begin
if(tx_done)begin
data <= data40[23:16];
send_go <= 1;
state <= 3;
end
else
send_go <= 0;
end
3:begin
if(tx_done)begin
data <= data40[31:24];
send_go <= 1;
state <= 4;
end
else
send_go <= 0;
end
4:begin
if(tx_done)begin
data <= data40[39:32];
send_go <= 1;
state <= 5;
end
else
send_go <= 0;
end
5:begin
if(tx_done)begin
trans_done <= 1;
state <= 0;
end
else
send_go <= 0;
end
default: begin
data <= data;
send_go <= 0;
state <= 0;
end
endcase
endmodule
module uart_byte_tx(
clk,
rstn,
blaud_set,
data,
send_go,
uart_tx,
tx_done
);
input clk;
input rstn;
input [2:0]blaud_set;
input [7:0]data;
input send_go;
output reg uart_tx;
output tx_done;
//Blaud_set = 0时,波特率 = 9600;
//Blaud_set = 1时,波特率 = 19200;
//Blaud_set = 2时,波特率 = 38400;
//Blaud_set = 3时,波特率 = 57600;
//Blaud_set = 4时,波特率 = 115200;
reg[17:0] bps_dr;
always@(*)
case(blaud_set)
0: bps_dr = 1000000000/9600/20;
1: bps_dr = 1000000000/19200/20;
2: bps_dr = 1000000000/38400/20;
3: bps_dr = 1000000000/57600/20;
4: bps_dr = 1000000000/115200/20;
endcase
reg [7:0] r_data;
always@(posedge clk)
if(send_go)
r_data <= data;
else
r_data <= r_data;
reg send_en;
always@(posedge clk or negedge rstn)
if(!rstn)
send_en <= 0;
else if(send_go)
send_en <= 1;
else if(tx_done)
send_en <= 0;
wire bps_clk;
assign bps_clk = (div_cnt == 1);
reg[17:0] div_cnt;
always@(posedge clk or negedge rstn)
if(!rstn)
div_cnt <= 0;
else if(send_en)begin
if(div_cnt == (bps_dr - 1))
div_cnt <= 0;
else
div_cnt <= div_cnt + 1'd1;
end
else
div_cnt <= 0;
reg[3:0] bps_cnt;
always@(posedge clk or negedge rstn)
if(!rstn)
bps_cnt <= 0;
else if(send_en)begin
if(bps_cnt == 11)
bps_cnt <= 0;
else if(div_cnt == 1)
bps_cnt <= bps_cnt + 4'd1;
end
else
bps_cnt <= 0;
reg tx_done;
always@(posedge clk or negedge rstn)
if(!rstn)
uart_tx <= 1'd1;
else
case(bps_cnt)
0: tx_done <= 0;
1: uart_tx <= 1'd0;
2: uart_tx <= r_data[0];
3: uart_tx <= r_data[1];
4: uart_tx <= r_data[2];
5: uart_tx <= r_data[3];
6: uart_tx <= r_data[4];
7: uart_tx <= r_data[5];
8: uart_tx <= r_data[6];
9: uart_tx <= r_data[7];
10: uart_tx <= 1'd1;
11: begin uart_tx <= 1'd1; tx_done <= 1; end
default: uart_tx <= 1'd1;
endcase
endmodule
2.2 仿真代码(学习trans_go脉冲信号以及数据发送完成信号)
以下两点需要学习:
- 通过控制trans_go信号的产生与结束,来模拟一个周期的脉冲信号
- 通过增加一个输出端口tx_done,来通知我输出何时完成
`timescale 1ns / 1ps
module uart_tx_data_tb();
reg clk;
reg rstn;
reg trans_go;
reg [39:0]data40;
wire trans_done;
wire uart_tx;
uart_tx_data uart_tx_data_inst(
.clk(clk),
.rstn(rstn),
.trans_go(trans_go),
.data40(data40),
.trans_done(trans_done),
.uart_tx(uart_tx)
);
initial clk = 1;
always #10 clk = ~clk;
initial begin
rstn = 0;
trans_go = 0;
data40 = 0;
#201;
rstn = 1;
#200;
data40 = 40'h123456789a;
trans_go = 1; //trans_go脉冲信号的模拟
#20;
trans_go = 0; //trans_go脉冲信号的模拟
@(posedge trans_done) //数据发送完成信号的标识
#200000;
data40 = 40'ha987654321;
trans_go = 1;
#20;
trans_go = 0;
@(posedge trans_done)
#200000;
$stop;
end
endmodule
仿真波形
3. 优化状态机代码
1. 任务:优化状态机,实现只要个或3个状态实现发送的功能,并且易于修改为发送任意个字节的数据
2. 征集不使用状态机的思想来实现本任务的方案
任务1完成如下,对于任务2,我的思路是:由于fpga是并行发送数据的,如果我们想要多字节发送数据的话,肯定需要将多字节串起来发送,所以我们可以将五个字节的数据串起来,每个字节之间相隔起始位和结束位,以此来达到在遵循协议的情况下实现多字节的输出。
3.1 设计代码(三个状态):
三个状态:
状态1.等待发送请求
状态2.等待单字节数据发送完成
状态3.检查所有数据是否发送完成
module uart_tx_data1(
clk,
rstn,
trans_go,
data40,
uart_tx,
trans_done
);
input clk;
input rstn;
input trans_go;
input [39:0] data40;
output uart_tx;
output reg trans_done;
reg [7:0] data;
reg send_go;
wire tx_done;
uart_byte_tx uart_byte_tx(
.clk(clk),
.rstn(rstn),
.blaud_set(3'd4),
.data(data),
.send_go(send_go),
.uart_tx(uart_tx),
.tx_done(tx_done)
);
reg [2:0]state;
reg [2:0]counter;
always@(posedge clk or negedge rstn)
if(!rstn) begin
state <= 0;
send_go <= 0;
data <= 0;
trans_done <= 0;
counter <= 0;
end
else
case(state)
0:begin //等待发送请求
if(trans_go)begin
trans_done <= 0;
send_go <= 1;
data <= (data40>>8*counter);
state <= 1;
end
else begin
data <= data;
send_go <= 0;
state <= 0;
end
end
1:begin //等待单字节数据发送完成
if(tx_done)begin
counter <= counter + 1'd1;
state <= 2;
end
else
send_go <= 0;
end
2:begin //检查所有数据是否发送完成
if(counter == 5) begin
trans_done <= 1;
state <= 0;
counter <= 0;
end
else begin
send_go <= 1;
data <= (data40>>(8*counter));
state <= 1;
end
end
default: begin
data <= data;
send_go <= 0;
state <= 0;
end
endcase
endmodule
module uart_byte_tx(
clk,
rstn,
blaud_set,
data,
send_go,
uart_tx,
tx_done
);
input clk;
input rstn;
input [2:0]blaud_set;
input [7:0]data;
input send_go;
output reg uart_tx;
output tx_done;
//Blaud_set = 0时,波特率 = 9600;
//Blaud_set = 1时,波特率 = 19200;
//Blaud_set = 2时,波特率 = 38400;
//Blaud_set = 3时,波特率 = 57600;
//Blaud_set = 4时,波特率 = 115200;
reg[17:0] bps_dr;
always@(*)
case(blaud_set)
0: bps_dr = 1000000000/9600/20;
1: bps_dr = 1000000000/19200/20;
2: bps_dr = 1000000000/38400/20;
3: bps_dr = 1000000000/57600/20;
4: bps_dr = 1000000000/115200/20;
endcase
reg [7:0] r_data;
always@(posedge clk)
if(send_go)
r_data <= data;
else
r_data <= r_data;
reg send_en;
always@(posedge clk or negedge rstn)
if(!rstn)
send_en <= 0;
else if(send_go)
send_en <= 1;
else if(tx_done)
send_en <= 0;
wire bps_clk;
assign bps_clk = (div_cnt == 1);
reg[17:0] div_cnt;
always@(posedge clk or negedge rstn)
if(!rstn)
div_cnt <= 0;
else if(send_en)begin
if(div_cnt == (bps_dr - 1))
div_cnt <= 0;
else
div_cnt <= div_cnt + 1'd1;
end
else
div_cnt <= 0;
reg[3:0] bps_cnt;
always@(posedge clk or negedge rstn)
if(!rstn)
bps_cnt <= 0;
else if(send_en)begin
if(bps_cnt == 11)
bps_cnt <= 0;
else if(div_cnt == 1)
bps_cnt <= bps_cnt + 4'd1;
end
else
bps_cnt <= 0;
reg tx_done;
always@(posedge clk or negedge rstn)
if(!rstn)
uart_tx <= 1'd1;
else
case(bps_cnt)
0: tx_done <= 0;
1: uart_tx <= 1'd0;
2: uart_tx <= r_data[0];
3: uart_tx <= r_data[1];
4: uart_tx <= r_data[2];
5: uart_tx <= r_data[3];
6: uart_tx <= r_data[4];
7: uart_tx <= r_data[5];
8: uart_tx <= r_data[6];
9: uart_tx <= r_data[7];
10: uart_tx <= 1'd1;
11: begin uart_tx <= 1'd1; tx_done <= 1; end
default: uart_tx <= 1'd1;
endcase
endmodule
仿真代码
`timescale 1ns / 1ps
module uart_tx_data1_tb();
reg clk;
reg rstn;
reg trans_go;
reg [39:0]data40;
wire trans_done;
wire uart_tx;
uart_tx_data1 uart_tx_data_inst1(
.clk(clk),
.rstn(rstn),
.trans_go(trans_go),
.data40(data40),
.trans_done(trans_done),
.uart_tx(uart_tx)
);
initial clk = 1;
always #10 clk = ~clk;
initial begin
rstn = 0;
trans_go = 0;
data40 = 0;
#201;
rstn = 1;
#200;
data40 = 40'h123456789a;
trans_go = 1;
#20;
trans_go = 0;
@(posedge trans_done);
#200000;
data40 = 40'ha987654321;
trans_go = 1;
#20;
trans_go = 0;
@(posedge trans_done);
#200000;
$stop;
end
endmodule
仿真波形
3.2 调试
调试1:counter位宽给错了,counter要记到5,但是只给了[1:0]两位:
调试2:counter记到5后未清零,导致数据多发了三次,且由于data = data40>>8*counter,导致数据为00: