根据曼彻斯特码的编解码原理,使用verilog 语言设计曼彻斯特编码的PHY层逻辑。
相关的参考文章:
本工程采用mvb 相关曼码通讯协议,其他曼码工程与之类似。 一般情况下,只需要修改起始定界符,终止定界符就可以应用到其他的曼码工程中去了。
曼码设计框图:

整个曼码模块由 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, 用来防止数据传输时丢失。

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 参考代码:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 |
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, 接收下一个包。
仿真波形:

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