注释:这是一个将并行数据转换为串行数据的一个代码,经过多次修改、仿真,虽然功能仿真正确,但是在进行时序仿真的时候还是不对,可是自己在分模块仿真的时候没有错误,希望哪位高手哈哪里编写不好指点哈。
并行数据流转换成串行数据流
设计概述:
如图:该设计分为三个模块,模块M1的作用是把四位的并行数据转换为符合以下协议的串行数据流,数据流用scl和sda两条传输线,sclk作为输入的时钟信号,data[3:0]为输入 数据,ack为M1请求M0发新的数据信号。模块M2能把串行数据流内的信息接收到,并转换为相应16条信号线的高电平,相当于一个4—16译码电路,即如果收到的串行数据是0010,那么输出的16条信号线中的outhigh[2]为高电平,其余为低电平。M0是发送数据信号模块,它接收到M1的数据请求,就像M1发送数据,测试M1和M2的功能是否正确。
通信协议:scl为不断输出的时钟信号,由M1模块产生,如果scl为高电平时,sda由高变低的时候表示串行数据流开始;如果scl为高电平时,sda由低变高,串行数据结束。sda信号的串行数据位必须在scl为低电平的时候变化,也就是说我们的M1模块要有效的发送数据必须在scl为低电平才能发送。
设计分析:
其实这个设计的难点是如何来写这个通信协议,例如,M1什么时候开始向M0请求数据,什么时候开始将并行数据转换为串行数据,怎么转换,什么时候结束,以及M2模块什么时候开始接收数据?只有正确理解了这个传输协议,才能让我们的M1和M2模块正常工作。那么我们就来具体分析:
我的分析思路是从各个模块的功能在到协议,首先是M1模块,它扮演个中间角色,它本身的功能是将接收到的并行数据转换成串行数据,与上一个模块有关的信号有三个,最重要的是ack,即请求数据信号,它决定是否从上一个模块接收数据;与下一个模块有关的有2个信号,scl和sda,scl是时钟信号,由它产生,提供给下一个模块,sda是串行数据总线,它的作用是传输数据,而这个数据的传输必须符合我们的通信协议,协议分析,三个点:开始位、数据传输位、结束位,由协议知,开始为满足的条件是scl为高,sda由高变低,数据传输的时间实在scl为低的时候,结束位是在scl为高,sda由低变高的时候,所以我们在写数据传输的时候一定要按照这个协议来写。但是,我们在何时请求数据呢?得到数据后什么时候开始转换?这些都必须考虑进去。
看代码具体分析:
- always @ (posedge ack) //请求新数据存入到并行总线上要转换的数据
databuf <= data; //存入要转换的数据
第一段代码是接收上一个模块的数据,它触发的条件是在ack的上升沿的时候,而我们知道,ack对于M1是输出信号,而不是输入时钟,所以这个信号的触发不是每时每刻的。那么我们怎么来提供这个触发条件呢?这个触发是在下面状态机模块产生的。
//------------主状态机:产生控制信号,根据databuf中保存的数据,按照协议产生sda串行信号--------------
always @ (negedge sclk or negedge rst_n)
if(!rst_n)//开始一进来我们就将ack拉低,状态进入ready状态,目的是通过这个配合获得ack的一个上升沿,请求并存储上一个模块的数据。
begin
link_sda <= 0;
state <= ready;
sdabuf <= 1;
ack <= 0;
end
else begin
case(state)
ready : if(ack)
begin
link_sda <= 1;
state <= start;
end
else
begin
link_sda <= 0;
state <= ready;
ack <= 1;
end
start : if(scl && ack) //产生起始信号
begin
sdabuf <= 0;
state <= bit1;
end
else state <= start;
bit1 : if(!scl) //开始传输四位的并行数据
begin
sdabuf <= databuf[3];
state <= bit2;
ack <= 0; //ack拉低,取消请求数据,与上一个模块断开
end
这段代码的巧妙之处在于很好的利用了ack,通过状态机的方式获得了上一个模块的数据,后面的代码都按照协议写,没什么特别的。
然后是M2模块,对于这个模块需要注意的就是什么时候开始接收数据。其实M1已经分析得很清楚了,我们按照协议写就可以了,首先是收到起始信号,即在sda的下降沿的时候,然后开始接收数据,接收完之后开始停止并把接收到的四位数据保存,将其译码输出。相关部分代码:
//-------sda由高电平变成低电平时,表示有数据输入了----------------------------------------------
- always @ (negedge sda)
if(scl)
StartFlag <= 1; //由传输协议知,在sda拉低后,scl为高电平数据起始标志位置1
else if(EndFlag)
StartFlag <= 0;
接收到起始信号,获得信号标志位,告诉后面相关模块开始工作
//-------sda由低电平变成高电平时,表示数据接收完毕----------------------------------------------
- always @ (posedge sda)
if(scl)
begin
EndFlag <= 1; //由传输协议知,在sda拉低后,scl为高电平数据结束标志位置1
pdatabuf <= pdata; //把接收到的四位数据存入寄存器中
end
else
EndFlag <= 0;
数据接收完毕,保存。
最后是模块M0,它就是收到M1的有效命令后就发送数据给M1。相关代码:
always @ (negedge rst_n or posedge ack)
if(!rst_n)
data_r <= 0;
else if(ack)
data_r <= data_r+1;
else if(data==15)
data_r <= 4'b0000;
assign data = data_r;//每次给下一个模块一次数据
assign data = data_r;收到命令,发送一个数据。
always @ (posedge clk or negedge rst_n)
if(!rst_n)
sclk <= 0;
else
sclk <= ~sclk;
产生下一个模块的时钟。
在仿真的时候,我先是一个模块一个模块进行仿真的,没有问题,可是在我把所有模块连起来仿真的时候就出现问题了。问题是这样的,M0高三位的数据没有发送出去,只发送了最后一位,但是数据是并行输出的,不可能只有最后一位发送出去了啊,如果我发送1111,译码结果为最低位为1,如果发送1110,译码结果为初始状态,一位一位的检查,只有在最后一位为1的时候,译码才发生变化,并且是最低位为1.但是我对后两个模块进行仿真的时候,先仿真M1,输出没有问题,让后在仿真M2的时候scl和sda的时序是按照M1的仿真结果来弄的,结果正确,意思就是将这两个模块连在一起是正确的,那么问题就出在M1向M0请求数据这里了,要是没有请求到数据,那么输出始终不变才对,但是data=4‘b0001的时候译码发生变化,而却只要最低位为1,译码就是最低位为1,即1111,,1101等的输出结果是一样的,这让我百思不得其解啊!!后来经一再检查才发现顶层模块中的data位没定义对,本来该是4位自己弄成一位去了。但是我仿真的时候QUARTUS自带的和Modelsim仿真不一样,后面的严格些!经过一再仿真修改,虽然功能仿真是正确的,但是时序仿真不正确,估计是经过多次分频照成的影响吧。还是不明白为什么。
小结:
这个练习主要是让自己熟悉如何来写通信协议,我觉得代码中最好的地方就是ack信号的写法了,它在每次请求数据都只有一次高脉冲,保证每次转换都只获得一次数据,其次就是M1写协议、M2何时开始接收数据这些。由于自己出现了一个小错误,就是data位宽,在顶层模块出错了,导致仿真的时候结果不对,浪费了自己很多的时间,对于输出变量一定要赋初值。
所有代码:
1. 顶层模块:
module BingToCuan(
clk,
rst_n,
outhigh
);
input clk;
input rst_n;
output [15:0] outhigh;
wire ack;
wire sclk;
wire [3:0]data;
wire sda;
wire scl;
sigdata sigdata_M0(
.clk(clk),
.rst_n(rst_n),
.ack(ack),
.sclk(sclk),
.data(data)
);
ptosda ptosda_M1(
.sclk(sclk),
.rst_n(rst_n),
.ack(ack),
.scl(scl),
.sda(sda),
.data(data)
);
out16hi out16hi_M2(
.scl(scl),
.sda(sda),
.outhigh(outhigh)
);
endmodule
模块M0:
module sigdata(
clk,
rst_n,
ack,
sclk,
data
);
input clk;
input rst_n;
input ack;
output sclk;
output [3:0] data;
reg [3:0] data_r;
reg sclk;
always @ (negedge rst_n or posedge ack)
if(!rst_n)
data_r <= 0;
else if(ack)
data_r <= data_r+1;
else if(data==15)
data_r <= 4'b0000;
assign data = data_r;
always @ (posedge clk or negedge rst_n)
if(!rst_n)
sclk <= 0;
else
sclk <= ~sclk;
endmodule
//---------------------------------------------------------------------------------------------
模块M1:
//**************************************************
/*****模块功能:将接收到的并行数据转换成串行数据输出*****/
//**************************************************
module ptosda(
sclk,
rst_n,
data,
scl,
ack,
sda
);
input sclk; //输入时钟信号
input rst_n; //复位信号
input [3:0] data; //四位并行数据输入
output scl; //输出时钟信号,为下一个模块提供时钟
output sda; //输出串行数据位
output ack;
reg scl;
reg link_sda; //控制数据输出到总线上
reg ack; //向上一个模块寻求新的数据信号寄存器
reg sdabuf; //串行数据输出寄存器
reg [3:0] databuf; //并行数据输入寄存器
reg [7:0] state;
assign sda = link_sda ? sdabuf : 1'b0;//通过link_sda很好的控制了数据在总线上的传输
parameter ready = 8'b0000_0000,
start = 8'b0000_0001,
bit1 = 8'b0000_0010,
bit2 = 8'b0000_0100,
bit3 = 8'b0000_1000,
bit4 = 8'b0001_0000,
bit5 = 8'b0010_0000,
stop = 8'b0100_0000,
IDLE = 8'b1000_0000;
//-------------为下一个模块产生时钟信号------------------------------------------------------------
always @ (posedge sclk or negedge rst_n)
if(!rst_n)
scl <= 1;
else
scl <= ~scl;
//-------------将并行数据转换成串行数据-------------------------------------------------------------
always @ (posedge ack) //请求新数据存入到并行总线上要转换的数据
databuf <= data; //存入要转换的数据
//------------主状态机:产生控制信号,根据databuf中保存的数据,按照协议产生sda串行信号--------------
always @ (negedge sclk or negedge rst_n)
if(!rst_n)
begin
link_sda <= 0;
state <= ready;
sdabuf <= 1;
ack <= 0;
end
else begin
case(state)
ready : if(ack)
begin
link_sda <= 1;
state <= start;
end
else
begin
link_sda <= 0;
state <= ready;
ack <= 1;
end
start : if(scl && ack) //产生起始信号,由通信协议在scl为高的时候sda为0表示开始发送数据
begin
sdabuf <= 0;
state <= bit1;
end
else state <= start;
bit1 : if(!scl) //由通信协议,数据只能在SCL为低的时候变化
begin
sdabuf <= databuf[3];
state <= bit2;
ack <= 0;
end
else state <= bit1;
bit2 : if(!scl)
begin
sdabuf <= databuf[2];
state <= bit3;
end
else state <= bit2;
bit3 : if(!scl)
begin
sdabuf <= databuf[1];
state <= bit4;
end
else state <= bit3;
bit4 : if(!scl)
begin
sdabuf <= databuf[0];
state <= bit5;
end
else state <= bit4;
bit5 : if(!scl) //为产生结束信号做准备,先把sda拉低
begin
sdabuf <= 0;
state <= stop;
end
else state <= bit5;
stop : if(scl) //在scl为高的时候,把sda由低变高,产生结束信号
begin
sdabuf <= 1;
state <= IDLE;
end
else state <= stop;
IDLE : begin
link_sda <= 0; //把sdabuf与sda串行总线断开,结束数据转换完成。
state <= ready;
end
default : begin
link_sda <= 0;
sdabuf <= 1;
state <= ready;
end
endcase
end
endmodule
//--------------------------------------------------------------------------------------------------------------
模块M2:
//*******************************************************************************
/****模块功能:把串行数据流内的信号接收到,并转换为相应的16条信号的高电平,4—16译码****/
//*******************************************************************************
module out16hi(
scl,
sda,
outhigh
);
input scl;
input sda; //串行数据输入
output [15:0] outhigh; //根据输入的串行数据设置该电平位
reg [5:0] mstate; //状态寄存器
reg [3:0] pdata,pdatabuf; //记录串行数据位时,用寄存器和最终数据寄存器
reg [15:0] outhigh; //输出寄存器
reg StartFlag,EndFlag; //数据开始和结束标志位
//-------sda由高电平变成低电平时,表示有数据输入了----------------------------------------------
always @ (negedge sda)
if(scl)
StartFlag <= 1; //由传输协议知,在sda拉低后,scl为高电平数据起始标志位置1
else if(EndFlag)
StartFlag <= 0;
//-------sda由低电平变成高电平时,表示数据接收完毕----------------------------------------------
always @ (posedge sda)
if(scl)
begin
EndFlag <= 1; //由传输协议知,在sda拉低后,scl为高电平数据结束标志位置1
pdatabuf <= pdata; //把接收到的四位数据存入寄存器中
end
else
EndFlag <= 0;
parameter ready = 6'b00_0000,
sbit0 = 6'b00_0010,
sbit1 = 6'b00_0100,
sbit2 = 6'b00_1000,
sbit3 = 6'b01_0000,
sbit4 = 6'b10_0000;
//---------------把接收到的数据译码输出相应的高电平----------------------------------------------
always @ (pdatabuf)
begin
case(pdatabuf)
4'b0001 : outhigh = 16'b0000_0000_0000_0001;
4'b0010 : outhigh = 16'b0000_0000_0000_0010;
4'b0011 : outhigh = 16'b0000_0000_0000_0100;
4'b0100 : outhigh = 16'b0000_0000_0000_1000;
4'b0101 : outhigh = 16'b0000_0000_0001_0000;
4'b0110 : outhigh = 16'b0000_0000_0010_0000;
4'b0111 : outhigh = 16'b0000_0000_0100_0000;
4'b1000 : outhigh = 16'b0000_0000_1000_0000;
4'b1001 : outhigh = 16'b0000_0001_0000_0000;
4'b1010 : outhigh = 16'b0000_0010_0000_0000;
4'b1011 : outhigh = 16'b0000_0100_0000_0000;
4'b1100 : outhigh = 16'b0000_1000_0000_0000;
4'b1101 : outhigh = 16'b0001_0000_0000_0000;
4'b1110 : outhigh = 16'b0010_0000_0000_0000;
4'b1111 : outhigh = 16'b0100_0000_0000_0000;
4'b0000 : outhigh = 16'b1000_0000_0000_0000;
endcase
end
//---------------------在检测到开始标志位的时候,每次scl正跳变沿接收数据,共四位-----------------------------------
always @ (posedge scl)
if(StartFlag)
case(mstate)
sbit0 : begin
mstate <= sbit1;
pdata[3] <= sda;
//$display("I am in sdabit0");
end
sbit1 : begin
mstate <= sbit2;
pdata[2] <= sda;
//$display("I am in sdabit1");
end
sbit2 : begin
mstate <= sbit3;
pdata[1] <= sda;
//$display("I am in sdabit2");
end
sbit3 : begin
mstate <= sbit4;
pdata[0] <= sda;
//$display("I am in sdabit3");
end
sbit4 : begin
mstate <= sbit0;
//$display("I am in sdabit4");
end
default : mstate <= sbit0;
endcase
else mstate <= sbit0;
endmodule