Menu Close

曼彻斯特解码-参考代码(Verilog)

根据曼彻斯特码的编解码原理,使用verilog 语言设计曼彻斯特编码的PHY层逻辑。

相关的参考文章:

曼彻斯特编码介绍

曼彻斯特解码介绍

曼彻斯特编码-参考代码(Verilog)

 

本工程采用mvb 相关曼码通讯协议,其他曼码工程与之类似。 一般情况下,只需要修改起始定界符,终止定界符就可以应用到其他的曼码工程中去了。

曼码设计框图:

%title插图%num

整个曼码模块由 phy_mcode.v phy_bt.v mcode_tx_phy.v mcode_rx_phy 四个文件组成。

顶层文件:phy_mcode.v 接口包括

clk : 系统时钟, 当前使用的系统时钟为48M 时钟,mvb 的通讯速率是1.5M, 48M 系统时钟会非常方便分频。

reset: 对整个曼码模块进行复位。

mcode_tx: 曼码发送pin ,可连接到fpga pin 上 ,用于曼码的发送。

mcode_tx_de:曼码发送enable pin, 可连接到fpga pin 上, 用于曼码发送。(有时硬件设计会采用rs485,rs422等通讯方式,这时就需要这个pin 来控制) 不是必须的,根据用户的硬件方案选择使用。 这个信号会包含mcode_tx 信号,宽度(时间轴)会大于 mode_tx, 用来防止数据传输时丢失。

%title插图%num

mcode_rx: 曼码接收到的串行数据。

mcode_rx_de: 曼码接收enable 线。 使用方法和发送类似。

tx_data: 8bit 数据 输入, 由fpga 内容逻辑产生,将这个数据发送给曼码模块, 曼码模块将数据以曼彻斯特编码方式串行输出的fpga pin 上。

tx_data_en: tx_data 的enable, 通知曼码模块,当前的tx_data 可以使用了。(48M单时钟周期)

tx_start_en: 通知曼码模块,发送起始定界符(48M单时钟周期)

tx_stop_en: 通知曼码模块,发送终止定界符(48M单时钟周期)

tx_busy: 在曼码数据发送期间(包括发送 起始定界符,终止定界符,正常的数据发送)一直为高电平,当发送结束,tx_busy 为低电平。这时fpga 系统软件可以发送下一个指令了(起始定界符,终止定界符,数据)。

rx_data: 从曼码phy 层得到的数据(将曼码解码得到的正常数据,可用于fpga 系统使用)

rx_rdy: 表示rx_data 已经正常接收到了(48M单时钟周期)

 

rx_eop: 表示 当前曼码包已经结束了(48M单时钟周期)

rx_data_err: 表示 当前曼码接收到的数据由错误。

rx_start_err: 表述 当前曼码接收的起始定界符错误。  

 

本文主要对mcode_rx_phy.v 进行介绍,其他模块的相关信息,可以参考曼彻斯特编码-参考代码(Verilog)

mcode_rx_phy.v接口介绍:

clk:  系统时钟(48M)

mcode_rx:  与顶层文件:phy_mcode.v 中的介绍相同

mcode_rx_de:  输入信号,当mcode_rx_de = 1 时,mcode_rx_phy.v 才开始接收数据。

rx_data: 将接收到的曼码数据解码后得到正常的接收数据

rx_rdy: 表示rx_data 接收到(48M 时钟下的单沿)

rx_eop: 表示曼码数据包接收完毕(整包接收结束, 48M时钟下的单沿)

rx_data_err: 表示接收到的数码 曼码解码发生错误

rx_start_err: 表示在接收到曼码的起始定界符时,发生错误

reset: 整个模块的复位信号

mcode_rx_phy 参考代码:

module mcode_rx_phy(
    input  clk,
 
    input  mcode_rx,
    input  mcode_rx_de,
 
    output reg [7:0] rx_data = 0,
    output reg rx_rdy = 0,
    output reg rx_eop = 0,
 
    output reg rx_data_err = 0,
    output reg rx_start_err = 0,
     
    input reset
);
 
//-----------------------------------------------------------------
reg [2:0] rx_r = 0;
always @ (posedge clk)
if(mcode_rx_de) rx_r <= {rx_r[1:0], mcode_rx};
 
wire rx_pos = (rx_r[2:1] == 2'b01) ? 1'b1 : 1'b0;
wire rx_neg = (rx_r[2:1] == 2'b10) ? 1'b1 : 1'b0;
 
wire rx_value = rx_r[2];
//-----------------------------------------------------------------
localparam RX_SAMP_POS = 7;
reg [3:0] bt_cnt = 0;
always @ (posedge clk)
if(^rx_r[2:1]) bt_cnt <= 0;
else 
begin
    bt_cnt <= bt_cnt + 1;
end
 
(* mark_debug = "true" *)wire bt_trigger = (bt_cnt == RX_SAMP_POS) ? 1'b1 : 1'b0;
//-----------------------------------------------------------------
wire [17:0] start_flag = {2'b10, 2'b10, 2'b10, 2'b10, 2'b00, 2'b11, 2'b10, 2'b00, 2'b11};
wire [3:0]  stop_flag = {2'b00, 2'b11};
 
reg  [17:0] temp_data = 0;
wire [7:0] temp_odd  = {temp_data[15], temp_data[13], temp_data[11], temp_data[9], temp_data[7], temp_data[5], temp_data[3], temp_data[1]}; 
wire [7:0] temp_even = {temp_data[14], temp_data[12], temp_data[10], temp_data[8], temp_data[6], temp_data[4], temp_data[2], temp_data[0]}; 
 
reg  [7:0] rx_cnt = 0;
(* mark_debug = "true" *)reg [2:0] rx_st = 0;
always @ (posedge clk)
if(reset)
begin
    rx_st <= 0;
end
else case(rx_st)
0:
begin
    rx_eop <= 0;
    rx_start_err <= 0;
    rx_data_err <= 0;
    rx_cnt <= 0;
    temp_data <= 0;
    if(rx_neg)
    begin
        temp_data[0] <= 1'b1;
        rx_st <= 1;
    end
end
1:
begin
    if(bt_trigger)
    begin
        temp_data <= {temp_data[16:0], rx_value};
        rx_cnt <= rx_cnt + 1;
    end
     
    if(rx_cnt == 17)
        rx_st <= 2;
end
2:
begin
    rx_cnt <= 0;
    if(temp_data == start_flag) 
    begin
        temp_data <= 0;
        rx_st <= 3;
    end
    else
    begin
        rx_start_err <= 1;
        rx_st <= 7;
    end
end
3:
begin
    rx_rdy <= 0;
     
    if(bt_trigger)
    begin
        temp_data <= {temp_data[16:0], rx_value};
        rx_cnt <= rx_cnt + 1;
    end
    else  if(rx_cnt == 16)
        rx_st <= 4;
     
    if( (temp_data[3:0] == 4'b0011) && (rx_cnt == 4))
    begin
        rx_st <= 5;
    end
end
4:
begin
    rx_cnt <= 0;
    if( temp_odd == ~temp_even )
    begin
        temp_data <= 0;
        rx_data <= temp_odd;
        rx_rdy <= 1;
        rx_st <= 3;
    end
    else
    begin
        rx_data_err <= 1;
        rx_st <= 7;
    end
end
5:
begin
    if(rx_cnt == 4) 
    begin
        rx_eop <= 1;
        rx_st <= 6;
    end
    else
    begin
        rx_data_err <= 1;
        rx_st <= 7;
    end
end
6:
begin
    rx_eop <= 0;
    rx_st <= 0;
end
7:
begin
    if(bt_trigger)
        temp_data <= {temp_data[16:0], rx_value};
 
    if ( (~|temp_data) || (&temp_data) )
    begin
        rx_eop <= 1;
        rx_st <= 0;
    end
end
default: rx_st <= 0;
endcase
 
//-----------------------------------------------------------------
 
 
 
endmodule

 


输入信号采样部分:

reg [2:0] rx_r = 0;
always @ (posedge clk)
if(mcode_rx_de) rx_r <= {rx_r[1:0], mcode_rx};

wire rx_pos = (rx_r[2:1] == 2’b01) ? 1’b1 : 1’b0;
wire rx_neg = (rx_r[2:1] == 2’b10) ? 1’b1 : 1’b0;

wire rx_value = rx_r[2];

将fpga 的输入pin (曼码的接收线)锁存三次,同时提取输入信号的(曼码信号)的上升沿, 下降沿。在接下来的曼码采样中会使用rx_value ,这样可以和采样的边沿同步。

采样点定位:

localparam RX_SAMP_POS = 7;
reg [3:0] bt_cnt = 0;
always @ (posedge clk)
if(^rx_r[2:1]) bt_cnt <= 0;
else
begin
     bt_cnt <= bt_cnt + 1;
end

(* mark_debug = “true” *)wire bt_trigger = (bt_cnt == RX_SAMP_POS) ? 1’b1 : 1’b0;

^rx_r[2:1] 表示上升沿或者下降沿(边沿), bt_cnt 分频为3M 的采样率

bt_trigger 在边沿过后7个48M时钟 产生第一个 trigger, 之后如果没有边沿,则每16个bt_cnt 产生一次trigger.

localparam RX_SAMP_POS = 7;  这里指示采样点(0.25BT)的位置, 用户也可以根据自己的需求更改(原则:尽量采样点的位置是数据的中心点,最稳定的地方)

定界符:

wire [17:0] start_flag = {2’b10, 2’b10, 2’b10, 2’b10, 2’b00, 2’b11, 2’b10, 2’b00, 2’b11};
wire [3:0] stop_flag = {2’b00, 2’b11};

接收到的的数据:(曼码数据)

reg [17:0] temp_data = 0;
wire [7:0] temp_odd = {temp_data[15], temp_data[13], temp_data[11], temp_data[9], temp_data[7], temp_data[5], temp_data[3], temp_data[1]};
wire [7:0] temp_even = {temp_data[14], temp_data[12], temp_data[10], temp_data[8], temp_data[6], temp_data[4], temp_data[2], temp_data[0]};

对于定界符,由start_flag, stop_flag来判断; 对于数据, 分为奇偶两种数据 。8bit 数据在编码时产生8对16bit的数据, 所以解码时会接收到16个数据,

将这16个数据分为奇偶两个部分 ,在解码(数据)中,我们发现 temp_odd = ~temp_even; 或者说temp_even = ~temp_odd 时, 解码是正确的, 否则解码错误,产生rx_data_err 信号。

同时我们会发现:temp_odd 正好就是解码后的数据,(rx_data)

 

状态机讲解:

状态0:等待下降沿,在我们观察mvb 协议中, 起始位都是以下降沿时发生的,当检测到下降沿,就得到了起始定界符中的第一个1

wire [17:0] start_flag = {2’b10, 2’b10, 2’b10, 2’b10, 2’b00, 2’b11, 2’b10, 2’b00, 2’b11};

状态1:每次采样,temp_data 左移一位,当收到了18个数据时,起始定界符完全收到了

状态2:检查收到的起始定界符是否正确,如果不正确,输出rx_start_err 信号; 如果正确,跳转到状态3 

状态3:每当bt_trigger 时,接收一个数据,当收到16个数据后就可以跳转到状态4; 如果收到的数据是4’b0011 ,代表当前接收的不是数据,而是终止定界符,跳转到状态5

状态4:数据已经收到,检查数据是否错误(曼码解码),如果解码正确,跳转到状态3; 如果错误,输出rx_data_err = 1, 跳转到状态7。

状态5:如果刚好接收4个数据,同时数据为4’b0011,表示正常的终止定界符,输出rx_eop = 1, 跳转到状态6; 如果不满足之前的条件,输出rx_data_err = 1 信号,跳转状态7

状态6:跳转状态0,表示一个完整的没有错误的曼码包解码接收结束。

状态7:异常状态下,当接收到全0,或者全1的信号时,表示整包结束了,跳转到状态0, 接收下一个包。

 

仿真波形:

%title插图%num

 

到此,一个完整的曼码包解码完成。 用户可以根据这个例子为参考, 修改相关的代码,以满足用户当前的需求。

Posted in FPGA, IC, IP, IP开发, SoC, SoC硬件开发, Verilog, 开发语言, 教材与教案, 文章
0 0 投票数
Article Rating
订阅评论
提醒
guest
0 评论
内联反馈
查看所有评论

相关链接