根据曼彻斯特码的编解码原理,使用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: 表述 当前曼码接收的起始定界符错误。
顶层文件phy_mcode.v 代码:
module phy_mcode(
input clk,
// 485 pin
output mcode_tx,
output mcode_tx_de,
input mcode_rx,
output mcode_rx_de,
// send port
input [7:0] tx_data,
input tx_data_en,
input tx_start_en,
input tx_stop_en,
output tx_busy,
// rev port
output [7:0] rx_data,
output rx_rdy,
output rx_eop,
output rx_data_err,
output rx_start_err,
input reset
);
wire bt_3m_tick;
phy_bt phy_bt_inst
(
.clk (clk), //48m
.clk_div (15), // divide 16
.bt_3m_tick (bt_3m_tick),
.reset (reset)
);
mcode_tx_phy mcode_tx_phy_inst
(
.clk (clk),
.bt_tick (bt_3m_tick),
.mcode_tx (mcode_tx),
.mcode_tx_de(mcode_tx_de),
.tx_data (tx_data),
.tx_data_en (tx_data_en),
.tx_start_en(tx_start_en),
.tx_stop_en (tx_stop_en),
.tx_busy (tx_busy),
.reset (reset)
);
//assign mcode_rx_de = ~mcode_tx_de;
assign mcode_rx_de = mcode_tx_de;
mcode_rx_phy mcode_rx_phy_inst
(
.clk (clk),
.mcode_rx (mcode_rx),
.mcode_rx_de(mcode_rx_de),
.rx_data (rx_data),
.rx_rdy (rx_rdy),
.rx_eop (rx_eop),
.rx_data_err(rx_data_err),
.rx_start_err(rx_start_err),
.reset (reset)
);
endmodule
[/code]
注意:
//assign mcode_rx_de = ~mcode_tx_de;
assign mcode_rx_de = mcode_tx_de;
当前的工程是一个测试代码,也就是说发送的数据会被接收模块(mcode_rx_phy.v)立即接收到。对于mcode_rx_phy来说,
mcode_rx_de 是一个input 信号。这样方便在一个板子上做测试。但是在实际的工程中, 由于外部接口(硬件设计)有所不同,用户可以根据情况修改这个信号。
举例:在串行异步半双工通讯过程中, 往往是发送完数据后, 转为接收状态,接收完数据后(完整的接收包)后再次转为发送状态。(一般情况下的异步通讯)。
但是也不排除用户使用的是全双工的通讯方式, 用户在具体的使用(用户自己的项目中),可以根据情况,修改mcode_rx_de 的状态, 甚至可以修改input ,output等
状态来满足用户自己的需求。本文中mcode_rx_phy.v 只有在mcode_rx_de = 1 时,才接收曼码数据。
phy_bt.v 文件:
根据系统时钟分频,产生0.5BT 的波特率,用于曼码发送使用。 当前文件依照mvb 协议 产生3M (0.5BT) 波特率,其他的曼码协议,用户可以自行更改。
clk: 系统时钟 (48M)
clk_div: 分频系数, 本文中选择15. 3M = 48M / (15 + 1)
bt_3m_tick: 0.5BT 信号, 这时一个与48M 时钟同步的单沿信号,频率是3M
reset: 系统复位信号
用户可以根据自己协议的需要,更改相关的系统时钟和分频系数来满足客户的需求,本文是以mvb 为例的。
phy_bt.v 代码
module phy_bt(
input clk,
input [7:0] clk_div,
output reg bt_3m_tick = 0,
input reset
);
reg [7:0] cnt = 0;
always @ (posedge clk) // 48m
if(reset)
begin
bt_3m_tick <= 0;
cnt <= 0;
end
else
begin
if(cnt == clk_div)
begin
cnt <= 0;
bt_3m_tick <= 1;
end
else
begin
bt_3m_tick <= 0;
cnt <= cnt + 1;
end
end
endmodule
mcode_tx_phy.v 文件:
接口信号
clk: 系统时钟 (48M)
bt_tick: 0.5BT 波特率(3M 频率, 48M时钟下的单沿信号)
mcode_tx: 定义与phy_mcode.v(上文中)的定义相同
mcode_tx_de: 定义与phy_mcode.v(上文中)的定义相同
tx_data: 定义与phy_mcode.v(上文中)的定义相同
tx_data_en: 定义与phy_mcode.v(上文中)的定义相同
tx_start_en: 定义与phy_mcode.v(上文中)的定义相同
tx_stop_en: 定义与phy_mcode.v(上文中)的定义相同
tx_busy: 定义与phy_mcode.v(上文中)的定义相同
reset: 模块复位信号
mcode_tx_phy.v 功能的使用流程:
- fpga 系统 给出 tx_start_en (单沿信号) ,通知tx phy(mcode_tx_phy.v) 产生起始定界符
- 等待tx phy (mcode_tx_phy.v) tx_busy 信号 为一
- 等待tx_busy 信号为零, 发送tx_data 数据,同时产生tx_data_en(单沿信号)
- 等待tx phy (mcode_tx_phy.v) tx_busy 信号 为一
- 如果还有要发送的数据,跳转到3; 如果没有数据需要发送,等待tx_busy信号为零, 发送tx_stop_en(单沿信号)
- 等待tx phy (mcode_tx_phy.v) tx_busy 信号 为一
- 等待tx_busy 信号为零, 跳转到1
到此一个完整的曼码包发送完毕。
mcode_tx_phy.v 代码:
module mcode_tx_phy(
input clk,
input bt_tick,
output reg mcode_tx = 1,
output reg mcode_tx_de = 0,
input [7:0] tx_data,
input tx_data_en,
input tx_start_en,
input tx_stop_en,
output reg tx_busy = 0,
input reset
);
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 [15:0] temp_data = 0;
reg [2:0] tx_st = 0;
reg [7:0] tx_cnt = 0;
always @ (posedge clk)
if(reset)
begin
mcode_tx_de <= 0;
mcode_tx <= 1;
tx_st <= 0;
end
else case(tx_st)
0:
begin
tx_cnt <= 0;
tx_busy <= 0;
if(tx_start_en)
begin
tx_busy <= 1;
mcode_tx_de <= 1;
tx_st <= 1;
end
else
mcode_tx_de <= 0;
end
1:
begin
if(&tx_cnt)
begin
tx_cnt <= 17;
tx_st <= 2;
end
else tx_cnt <= tx_cnt + 1;
end
2: // start delimiter
begin
if(bt_tick)
begin
mcode_tx <= start_flag[tx_cnt];
tx_cnt <= tx_cnt - 1;
if(tx_cnt == 0)
begin
tx_busy <= 0;
tx_cnt <= 15;
tx_st <= 3;
end
end
end
3:
begin
tx_busy <= 0;
if(tx_stop_en)
begin
tx_busy <= 1;
tx_cnt <= 3;
tx_st <= 5;
end
else if(tx_data_en)
begin
temp_data[15:8] <= {tx_data[7],~tx_data[7], tx_data[6],~tx_data[6], tx_data[5],~tx_data[5], tx_data[4],~tx_data[4] };
temp_data[7:0] <= {tx_data[3],~tx_data[3], tx_data[2],~tx_data[2], tx_data[1],~tx_data[1], tx_data[0],~tx_data[0] };
tx_busy <= 1;
tx_cnt <= 15;
tx_st <= 4;
end
end
4:
begin
if(bt_tick)
begin
mcode_tx <= temp_data[tx_cnt];
if(tx_cnt == 0) tx_st <= 3;
else tx_cnt <= tx_cnt - 1;
end
end
5:
begin
if(bt_tick)
begin
mcode_tx <= stop_flag[tx_cnt];
if(tx_cnt == 0) tx_st <= 6;
else tx_cnt <= tx_cnt - 1;
end
end
6:
begin
if(&tx_cnt)
begin
tx_busy <= 0;
tx_cnt <= 0;
tx_st <= 0;
end
else tx_cnt <= tx_cnt + 1;
end
default:
begin
tx_st <= 0;
end
endcase
//-----------------------------------------------------------------
endmodule
其中:
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}; //终止定界符
在tx_st 状态机中 :
状态0: 等待tx_start_en 信号,得到tx_start_en 信号后, 跳转到状态1
状态1: 产生mcode_tx_de = 1 , 但没有发送起始定界符,让长线传输中,线路上信号稳定一段时间(&cnt == 1) 256 个系统时钟周期,这里是测试例子,实际需要多少时间,用户可以根据协议修改。如果用户的协议中不需要mcode_tx_de 这个信号, 状态1 可以之间跳转到状态2
状态2: 发送起始定界符18bit (9对 数据 )
状态3: 根据情况准备发送数据还是发送终止定界符。 发送数据,将数据编码为曼码格式 ,跳转到状态4; 发送终止定界符,跳转到状态5
状态4: 发送数据,发送完成后跳转到状态3
状态5: 发送终止定界符,发送完成后,跳转到状态6
状态6: 延迟一段时间后,mcode_tx_de = 0; 返回状态0
仿真波形为:

1: 发送tx_start_en
2: 发送数据1
3: 发送数据2
4: 发送数据3
5: 发送 tx_stop_en
mcode_tx 上显示的是完整一包的曼码波形。