根据曼彻斯特码的编解码原理,使用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 参考代码:
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, 接收下一个包。
仿真波形:

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