今天学习了序列发送逻辑的设计,具体目标为:实现模块发送序列数据,每发送一个数据后,延时50us,再继续发送下一个数据,发送的数据可以自定义。
一、数据发送模块
对于控制数据发送的模块,要求具有数据并行输入端,使能端,数据发送端等端口,具体代码参考如下:
module data_send(
Clk,
Rst_n,
en,
data,
tx_done,
tx
);
input Clk; //时钟
input Rst_n; //复位端
input en; //使能端
input [7:0] data; //数据输入端
output reg tx_done; //发送完成标志
output reg tx; //数据发送端
reg [16:0] cnt; //内部计数器
//这个always块用于计数
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
cnt <= 0;
else if(en)begin
if(cnt == 399) //每隔8us计满一次
cnt <= 0;
else
cnt <= cnt + 1'b1;
end
else
cnt <= 0;
//这个always块用于在特定计数值时将数据传出
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
tx <= 0;
else begin
case(cnt)
0: tx <= data[0];//0us、第0位数据
49: tx <= data[1];//1us、第1位数据
99: tx <= data[2];//2us、第2位数据
149:tx <= data[3];//3us、第3位数据
199:tx <= data[4];//4us、第4位数据
249:tx <= data[5];//5us、第5位数据
299:tx <= data[6];//6us、第6位数据
349:tx <= data[7];//7us、第7位数据
default:tx <= tx;
endcase
end
//这个always块用于给tx_done信号赋值
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
tx_done <= 0;
else if(cnt == 399)
tx_done <= 1;
else if(cnt == 0)
tx_done <= 0;
endmodule
1.代码解读
整个模块仅仅用到了3个always语句块,可以说是比较简单的一个模块,但其所蕴含的设计思想是很重要的。
①内部计数器
该模块内部具有一个周期为8us的定时器,对于8位的数据,每1us输出一位数据。
②数据发送
在内部寄存器cnt计数到一定值时,把数据data的各位数据给到输出端tx。
③发送完毕标志
定时器每计满一个周期,将发送完毕标志位tx_done拉高,否则拉低。
2.仿真
编写testbench如下:
`timescale 1ns/1ps
module cnt_test3_tb;
reg Clk;
reg Rst_n;
reg en;
reg [7:0] data;
wire tx;
wire tx_done;
test cnt_test3(
.Clk(Clk),
.Rst_n(Rst_n),
.en(en),
.data(data),
.tx_done(tx_done),
.tx(tx)
);
initial Clk = 1;
always #10 Clk = ~Clk;
initial begin
Rst_n = 0;
data = 0;
en = 0;
#201;
Rst_n = 1;
#200;
en = 1;//开始发送
data = 8'h12;
@(posedge tx_done);//等待信号tx_done的上升沿到来
en = 0;
#50000;
en = 1;//开始发送
data = 8'h56;
@(posedge tx_done);//等待信号tx_done的上升沿到来
en = 0;
#50000;
en = 1;//开始发送
data = 8'haf;
@(posedge tx_done);//等待信号tx_done的上升沿到来
en = 0;
#50000;
$stop;
end
endmodule
仿真波形如图
在这个实验中,延时50us是由testbench控制的,即模块本身不能控制两次数据发送的时间间隔。如果想要由模块本身自动发送并延时,则需要用到下面介绍的步骤。
二、自动发送设计
1.设计思路
在原有的发送模块外部再加上一个控制模块,结合发送模块中的en和tx_done信号,可以实现上电自动发送。设计图如下:
如图所示,数据发送模块的使能信号和数据输入均由控制模块给出,而发送完毕标志信号返回给控制模块,体现了一种“控制、反馈”的思想。
2.控制模块
在进行控制模块编写之前我们要具备一定的数字电路知识储备,最重要的是关于状态机的编写。根据两个模块之间的联系,我们可以画出控制部分的状态转换图。
①状态转换图
图中有下划线的式子表示转换条件,无下划线的表示信号转变。控制模块在三个状态之间跳变,我们人为规定为S0:等待状态、S1:发送状态、S2:发送完毕状态。控制部分代码给出如下:
module ctrl(
Clk,
Rst_n,
En,
Data,
Tx_Done
);
input Clk,Rst_n;
output reg En;
output [7:0] Data;
input Tx_Done;
reg [1:0] state;//用一个寄存器来表示状态
localparam S0 = 2'd0;
localparam S1 = 2'd1;
localparam S2 = 2'd2;//定义三个状态常数,推荐使用格雷码
reg cnt_en;
reg [16:0] cnt;
reg dly_done;
assign Data = 8'ha5;
//这个always块用于控制状态机从S0->S1->S2的切换
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)begin
state <= S0;
En <= 1'd0;
cnt_en <= 0;
end
else begin
case(state)
S0://等待状态
begin
En <= 1'd1;
state <= S1;
end
S1://发送状态
begin
if(Tx_Done)begin
En <= 0;
cnt_en <= 1;
state <= S2;
end
else begin
En <= 1;
cnt_en <= 0;
state <= S1;
end
end
S2://发送完毕等待状态
begin
if(dly_done)begin
state <= S0;
cnt_en <= 0;
end
else begin
state <= S2;
cnt_en <= 1;
end
end
default:begin
state <= S0;
En <= 1'd0;
cnt_en <= 0;
end
endcase
end
//这个always块用于控制数据发送完毕后的50us延时
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)begin
cnt <= 0;
dly_done <= 0;
end
else if(cnt_en)begin
if(cnt == 2497)begin
cnt <= 0;
dly_done <= 1;
end
else begin
cnt <= cnt + 1'b1;
dly_done <= 0;
end
end
else begin
cnt <= 0;
dly_done <= 0;
end
endmodule
控制模块比较复杂,需要根据自己画出的状态转换图来编写,感兴趣可以自行学习相关知识。
3.顶层模块
将两个模块设计好后,需要一个顶层模块来将它们连接起来,顶层模块写起来比较简单,代码如下:
module data_send_top(
Clk,
Rst_n,
Tx
);
input Clk;
input Rst_n;
output Tx;
wire En;
wire [7:0] data;
wire tx_done;
data_send data_send(
.Clk(Clk),
.Rst_n(Rst_n),
.en(En),
.data(data),
.tx_done(tx_done),
.tx(Tx)
);
ctrl ctrl(
.Clk(Clk),
.Rst_n(Rst_n),
.En(En),
.Data(data),
.Tx_Done(tx_done)
);
endmodule
4.仿真
根据顶层模块编写测试激励文件如下:
`timescale 1ns/1ps
module data_send_tb;
reg Clk;
reg Rst_n;
wire tx;
data_send_top data_send_top(
.Clk(Clk),
.Rst_n(Rst_n),
.Tx(tx)
);
initial Clk = 1;
always #10 Clk = ~Clk;
initial begin
Rst_n = 0;
#200;
Rst_n = 1;
#255000;
$stop;
end
endmodule
用Modelsim进行仿真,得到波形如下:
满足设计需求。
三、小结
今天的学习主要是借序列发送逻辑这一任务提高了对状态机书写的熟练程度。后面的学习中会涉及到越来越复杂的设计,掌握状态机的书写是十分有必要的。