PPM即Pulse Position Modulation(脉冲位置调制),利用脉冲的相对位置来传递信息的一种调制方式。在这种调制方式中,数据能够高速的传递。本文就来详细介绍一下PPM解码器。
1、PPM的功能描述
输入信号
- clk,时钟周期为0.59us
- rst,异步复位信号,低电平有效
- din,输入的PPM编码后的数据
输出信号
- [7:0] dout,PPM解码后的8位数据
- d_en,输出数据有效标志,高电平有效,持续一个时钟周期
- f_en,帧头检测有效标志,高电平有效,持续一个时钟周期
2、PPM的功能分析
计数器用来控制时序,移位寄存器用来暂存数据,状态机用来进行状态转换。
2.1计数器
时钟的周期是0.59us,而输入的每一位数据宽度为9.44us=0.59us※16,解码2bit的数据需要的时间为75.52us=0.59us※128,解码一个完整的8位数据,需要302.08us=75.52us※4。基于以上分析,我们可以设置3个计数器来控制数据的采样。
- count0,0~15,每16个时钟周期采一位din信号。
- count1,0~7,解码2bit需要采到8位din信号。
- count2,0~3,完成一个完整的8位信号,需要解码2bit数据4次。
2.2移位寄存器
我们要对输入数据的8位数据进行判读,就要求我们对数据进行暂存。这里我们采用移位寄存器对输入数据进行暂存。与此同时,输出的8bit数据是2bit数据输出累加到8bit,所以我们也需要移位寄存器对输出数据进行暂存。
- [7:0] reg1,对输入的数据进行移位操作,{reg1[6:0],din}。
- [7:0] reg2,对输出的数据进行暂存,等待8bit移满,就进行数据的输出,{2'b11,reg2[7:2]},{2'b00,reg2[7:2]},{2'b10,reg2[7:2]},{2'b01,reg2[7:2]}。
2.3状态机
在传送数据的时候,主要有两个状态。要么是收到帧头解码数据,要么是没有收到帧头不进行解码。
- S0,表示没有收到帧头,处于未工作状态。
- S1,表示收到帧头,开始进行解码。
注:以上电路图和状态转移图的判断条件有所简化。
具体代码如下:
module PPM( clk, rst, din, dout, d_en, f_en); input clk,rst; input din; output [7:0] dout; output d_en,f_en; reg [7:0] dout; reg d_en,f_en; reg [3:0] count0; reg [2:0] count1; reg [2:0] count2; reg [7:0] reg1; reg [7:0] reg2; reg cs,ns; parameter SOF=8'b01111011, EOF=4'b1101, d_00=8'b10111111, d_01=8'b11101111, d_10=8'b11111011, d_11=8'b11111110, S0=1'b0, S1=1'b1; always@(posedge clk or negedge rst) begin if(!rst) cs<=S0; else cs<=ns; end always@(cs or count0 or count1 or reg1) begin case(cs) S0:begin if((count0==15)&&(reg1==SOF)) begin ns=S1; f_en=1'b1; end else begin ns=S0; f_en=1'b0; end end S1:begin if((count0==15)&&(count1==3)&&(reg1[3:0]==EOF)) begin ns=S0; f_en=1'b0; end else begin ns=S1; f_en=1'b0; end end default:begin ns=ns; f_en=1'b0; end endcase end always@(posedge clk or negedge rst) begin if(!rst) begin count0<=0; reg1<=8'b00000000; end else begin if(count0==15) begin reg1<={reg1[6:0],din}; count0<=0; end else count0<=count0+1; endendalways@(posedge clk or negedge rst) begin if(!rst) count1<=0; else begin if(cs==S1) if(count0==15) if(count1==7) count1<=0; else count1<=count1+1; else count1<=count1;else count1<=0; endend always@(posedge clk or negedge rst)begin if(!rst) count2<=0;elsebegin if((count0==15)&&(cs==S1)&&(count1==7)) if(count2==3) count2<=0; else count2<=count2+1;endend always@(posedge clk or negedge rst) begin if(!rst) d_en<=0; else begin if((count0==15)&&(count1==7)&&(cs==S1)&&(count2==3)) d_en<=1; else d_en<=0; end end always@(posedge clk or negedge rst) beginif(!rst) reg2<=8'b00000000;elsebegin if(cs==S1) if((count0==15)&&(count1==7)&&(count2<=3)) begin case(reg1) d_00:reg2<={2'b00,reg2[7:2]}; d_01:reg2<={2'b01,reg2[7:2]}; d_10:reg2<={2'b10,reg2[7:2]}; d_11:reg2<={2'b11,reg2[7:2]}; default:reg2<=reg2; endcase end else reg2<=reg2; else reg2<=0; endend always@(posedge clk or negedge rst) begin if(!rst)dout<=0; else begin if((d_en)&&(cs==S1))dout<=reg2; else if (cs==S0)dout<=0; endend endmodule
对以上代码做如下说明:
用拼接符号{}实现了移位寄存器,在使用拼接符号时一定要指定每一个元素的位宽。在位拼接表达式中不允许存在没有指明位数的信号。3、testbench的编写
我们下面举一个例子来说明用文件读入的方法对存储器赋值。
先定义一个有256个地址的字节存贮器reg [7:0] mem[40:0]; 地址为0~40,一个地址上存放着8bit的数据 利用文件读入的方法对men赋值- initial $readmemb("mem.txt",mem); 以二进制的方式读取mem.txt中的数据到mem中。
- initial $readmemh("mem.txt",mem,16); 以十六进制的方式读取mem.txt中的数据到mem[16]-mem[40]。
- initial $readmemh("mem.data",mem,23,1); 以十六进制的方式读取mem.txt中的数据到mem[23]-mem[1]。
对读入文件做几点说明
- 不同地址的数据以空格键或者回车键结束,从mem[0]开始读入数据。
- 对一个地址的数据读入是从高位开始的,即从mem[0][7]开始读入mem.txt中的第一个数据,第一位地址的第7个元素为mem[1][7]=0 。
- 对读入文件的命名规则,比如.txt文件名为mem,那么读入文件名应该为“mem.txt”。
- 在Verilog中支持的文件路径格式为
C:/Users/XQ/Desktop/mem.txt
,而不是传统Windows底下的C:\Users\XQ\Desktop\mem.txt
。 必须在run xx ns以后才能对存储器进行复制,初始的存储器的值都为xx。
下面是一个名为mem的txt文件:00000000 //无效输入 01111011 //帧头 10111111 //"00" 11101111 //"01" 11111011 //"10" 11111110 //"11"输出数据e4 11111011 //"10" 11111110 //"11" 10111111 //"00" 11101111 //"01"输出数据4e 11010000 //帧尾 00001100 01111011 //帧头 11111011 //"10" 11111110 //"11" 10111111 //"00" 11101111 //"01"输出数据4e 11111110 //"11" 11101111 //"01" 11111011 //"10" 11111110 //"11"输出数据e7 10111111 //"00" 11101111 //"01" 11111110 //"11" 11101111 //"01"输出数据74 11010010 //帧尾 11010010 01111011 //帧头 10111111 //"00" 11101111 //"01" 11111011 //"10" 11111110 //"11"输出数据e4 10111111 //"00" 11101111 //"01" 11111011 //"10" 11111110 //"11"输出数据e4 11111011 //"10" 11111110 //"11" 10111111 //"00" 11101111 //"01"输出数据4e 11010000 //帧尾
tb文件代码如下:
`timescale 1ns/1ns module PPM_top; reg clk,rst; reg din; wire [7:0]dout; wire d_en,f_en; reg [7:0] mem[40:0]; integer i,j; initial begin $readmemb("C:/Users/XQ/Desktop/mem.txt",mem); for(i=0;i<41;i=i+1) for(j=7;j>=0;j=j-1) begin #9440 din=mem[i][j]; $display("mem[%0d][%0d]=%b",i,j,mem[i][j]); end end always #295 clk=~clk; initial begin clk=0; rst=1; #200 rst=0; #259 rst=1; end PPM ut1( .clk(clk), .rst(rst), .din(din), .d_en(d_en), .f_en(f_en), .dout(dout) ); endmodule
注:reg类型的数据默认为是无符号的数,若reg [7:0] j
那么在j=j-1中就不会出现负数,而是0-1=8'b11111111=255;若integer [7:0] j
那么就会出现0-1=-1,正好符合我们的本意。采用integer配合FOR语句,行数比较少,但是integer不能综合,只能用来仿真。