串行和并行哪个速度快?
串口,即串行通信接口,与之对应的是并行接口。在实际时钟频率比较低的情况下,并口因为可以同时传输若干比特,速率确实比串口快。但是,随着技术的发展,时钟频率越来越高,当时钟频率提高到一定的程度时,并行接口因为有多条并行且紧密的导线,导线之间的相互干扰越来越严重。而串口因为导线少,线间干扰容易控制,况且加上差分信号的加持,抗干扰性能大大提升,因此可以通过不断提高时钟频率来提高传输速率,这就是为什么现在高速传输都采用串行方式的原因。例如常见的USB、SATA、PCIe、以太网等。
如果有人问关于串行传输与并行传输谁更好的问题,你也许会脱口而出:串行通信好!但是,串行传输之所以走红,是由于将单端信号传输转变为差分信号传输,并提升了控制器工作频率的原因,而“在相同频率下并行通信速度更高”这个基本道理是永远不会错的,通过增加位宽来提高数据传输率的并行策略仍将发挥重要作用。当然,前提是有更好的措施来解决并行传输过程中的种种问题。
标准串口协议的Verilog实现
基于Verilog实现标准串口协议发送8位数据:起始位 + 8位数据位 + 校验位 + 停止位 = 11位,每1位的时间是16个时钟周期,所以输入时钟应该为:波特率*16,带Busy忙信号输出。实现方法比较简单,数据帧的拼接、计数器计时钟周期,每16个时钟周期输出一位数据即可。
串口发送1个字节实现
/*
串口协议发送:起始位 + 8位数据位 + 校验位 + 停止位 = 11位 * 16 = 176个时钟周期
clk频率 = 波特率 * 16
*/
module uart_tx_8bit(
//input
input clk, //UART时钟=16*波特率
input rst_n,
input [7:0] data_in, //需要发送的数据
input trig, //上升沿发送数据
//output
output busy, //高电平忙:数据正在发送中
output reg tx //发送数据信号
);
reg[7:0] cnt; //计数器
reg trig_buf;
reg trig_posedge_flag;
// reg trig_negedge_flag;
reg send;
reg [10:0] data_in_buf; //trig上升沿读取输入的字节,拼接数据帧
wire odd_bit; //奇校验位 = ~偶校验位
wire even_bit; //偶校验位 = 各位异或
wire POLARITY_BIT = even_bit; //偶校验
// wire POLARITY_BIT = odd_bit; //奇校验
assign even_bit = ^data_in; //一元约简,= data_in[0] ^ data_in[1] ^ 。..。.
assign odd_bit = ~even_bit;
assign busy = send; //输出的忙信号
//起始位+8位数据位+校验位+停止位 = 11位 * 16 = 176个时钟周期
parameter CNT_MAX = 176;
always @(posedge clk)
begin
if(!rst_n)
begin
trig_buf 《= 0;
trig_posedge_flag 《= 0;
// trig_negedge_flag 《= 0;
end
else
begin
trig_buf 《= trig;
trig_posedge_flag 《= (~trig_buf) & trig; //在trig信号上升沿时产生1个时钟周期的高电平
// trig_negedge_flag 《= trig_buf & (~trig); //在trig信号下降沿时产生1个时钟周期的高电平
end
end
always @(posedge clk)
begin
if(!rst_n)
send 《= 0;
else if (trig_posedge_flag & (~busy)) //当发送命令有效且线路为空闲时,启动新的数据发送进程
send 《= 1;
else if(cnt == CNT_MAX) //一帧资料发送结束
send 《= 0;
end
always @ (posedge clk)
begin
if(!rst_n)
data_in_buf 《= 11’b0;
else if(trig_posedge_flag & (~busy)) //只读取一次数据,一帧数据发送过程中,改变输入无效
data_in_buf 《= {1‘b1, POLARITY_BIT, data_in[7:0], 1’b0}; //数据帧拼接
end
always @ (posedge clk)
begin
if(!rst_n)
cnt 《= 0;
else if(!send || cnt 》= CNT_MAX)
cnt 《= 0;
else if(send)
cnt 《= cnt + 1;
end
always @(posedge clk)
begin
if(!rst_n)
tx 《= 1;
else if(send)
begin
case(cnt) //1位占用16个时钟周期
0: tx 《= data_in_buf[0]; //低位在前,高位在后
16: tx 《= data_in_buf[1]; //bit0,占用第16~31个时钟
32: tx 《= data_in_buf[2]; //bit1,占用第47~32个时钟
48: tx 《= data_in_buf[3]; //bit2,占用第63~48个时钟
64: tx 《= data_in_buf[4]; //bit3,占用第79~64个时钟
80: tx 《= data_in_buf[5]; //bit4,占用第95~80个时钟
96: tx 《= data_in_buf[6]; //bit5,占用第111~96个时钟
112: tx 《= data_in_buf[7]; //bit6,占用第127~112个时钟
128: tx 《= data_in_buf[8]; //bit7,占用第143~128个时钟
144: tx 《= data_in_buf[9]; //发送奇偶校验位,占用第159~144个时钟
160: tx 《= data_in_buf[10]; //发送停止位,占用第160~167个时钟
CNT_MAX: tx 《= 1; //无空闲位
default:;
endcase
end
else if(!send)
tx 《= 1;
end
endmodule
仿真波形
串口接收1个字节实现
串口接收部分的实现,涉及到串口数据的采样,对于MCU来说,不同单片机集成外设的处理方式有所不同,具体采样原理可以参考内核的Reference Manual。以传统51内核为例,按照所设置的波特率,每个位时间被分为16个时间片。UART接收器会在第7、8、9三个时间片进行采样,按照三取二的逻辑获得该位时间内的采样结果。其它一些类型的单片机则可能会更加严苛,例如有些工业单片机会五取三甚至七取五(设置成抗干扰模式时)。
本程序中采用的中间值采样,即取16个时钟周期中的中间位作为当前的采样值。
//Verilog实现串口协议接收,带错误指示,校验错误和停止位错误
/*
16个时钟周期接收1位,中间采样
*/
module my_uart_rx(
input clk, //采样时钟
input rst_n,
input rx, //UART数据输入
output reg [7:0] dataout, //接收数据输出
output reg rx_ok, //接收数据有效,高说明接收到一个字节
output reg err_check, //数据出错指示
output reg err_frame //帧出错指示
);
reg [7:0] cnt;
reg [10:0] dataout_buf;
reg rx_buf;
reg rx_negedge_flag;
reg receive;
wire busy;
wire odd_bit; //奇校验位 = ~偶校验位
wire even_bit; //偶校验位 = 各位异或
wire POLARITY_BIT; //本地计算的奇偶校验
// wire polarity_ok;
// assign polarity_ok = (POLARITY_BIT == dataout_buf[9]) ? 1 : 0; //校验正确=1,否则=0
assign busy = rx_ok;
assign even_bit = ^dataout; //一元约简,= data_in[0] ^ data_in[1] ^ 。..。.
assign odd_bit = ~even_bit;
assign POLARITY_BIT = even_bit; //偶校验
// assign POLARITY_BIT = odd_bit; //奇校验
parameter CNT_MAX = 176;
//rx信号下降沿标志位
always @(posedge clk)
begin
if(!rst_n)
begin
rx_buf 《= 0;
rx_negedge_flag 《= 0;
end
else
begin
rx_buf 《= rx;
rx_negedge_flag 《= rx_buf & (~rx);
end
end
//在接收期间,保持高电平
always @(posedge clk)
begin
if(!rst_n)
receive 《= 0;
else if (rx_negedge_flag && (~busy)) //检测到线路的下降沿并且原先线路为空闲,启动接收数据进程
receive 《= 1; //开始接收数据
else if(cnt == CNT_MAX) //接收数据完成
receive 《= 0;
end
//起始位+8位数据位+校验位+停止位 = 11位 * 16 = 176个时钟周期
always @ (posedge clk)
begin
if(!rst_n)
cnt 《= 0;
else if(!receive || cnt 》= CNT_MAX)
cnt 《= 0;
else if(receive)
cnt 《= cnt + 1;
end
//校验错误:奇偶校验不一致
always @ (posedge clk)
begin
if(!rst_n)
err_check 《= 0;
else if(cnt == 152)
begin
// if(POLARITY_BIT == rx)
if(POLARITY_BIT != dataout_buf[9]) //奇偶校验正确
err_check 《= 1; //锁存
// else
// err_check 《= 1;
end
end
//帧错误:停止位不为1
always @ (posedge clk)
begin
if(!rst_n)
err_frame 《= 0;
else if(cnt == CNT_MAX)
begin
if(dataout_buf[10] != 1) //停止位
err_frame 《= 1;
// else
// err_frame 《= 1; //如果没有接收到停止位,表示帧出错
end
end
always @ (posedge clk)
begin
if(!rst_n)
dataout 《= 11‘h00;
else if(receive)
begin
// if(rx_ok)
if(cnt 》= 137)
dataout 《= dataout_buf[8:1]; //数据位:8-1位
// else if(!rx_ok)
// dataout 《= 0;
end
end
always @ (posedge clk)
begin
if(!rst_n)
rx_ok 《= 0;
else if(receive)
begin
if(cnt 》= 137) //137-169
rx_ok 《= 1;
else
rx_ok 《= 0;
end
else
rx_ok 《= 0;
end
//起始位+8位数据+奇偶校验位+停止位 = 11 * 16 = 176位
always @(posedge clk)
begin
if(!rst_n)
dataout_buf 《= 8’h00;
else if(receive)
begin
case (cnt) //中间采样
8‘d8: dataout_buf[0] 《= rx; //起始位=0
8’d24: dataout_buf[1] 《= rx; //LSB低位在前
8‘d40: dataout_buf[2] 《= rx;
8’d56: dataout_buf[3] 《= rx;
8‘d72: dataout_buf[4] 《= rx;
8’d88: dataout_buf[5] 《= rx;
8‘d104: dataout_buf[6] 《= rx;
8’d120: dataout_buf[7] 《= rx;
8‘d136: dataout_buf[8] 《= rx; //MSB高位在后
8’d152: dataout_buf[9] 《= rx; //奇偶校验位
8‘d168: dataout_buf[10] 《= rx; //停止位=1
default:;
endcase
end
end
endmodule
串行和并行哪个速度快?
串口,即串行通信接口,与之对应的是并行接口。在实际时钟频率比较低的情况下,并口因为可以同时传输若干比特,速率确实比串口快。但是,随着技术的发展,时钟频率越来越高,当时钟频率提高到一定的程度时,并行接口因为有多条并行且紧密的导线,导线之间的相互干扰越来越严重。而串口因为导线少,线间干扰容易控制,况且加上差分信号的加持,抗干扰性能大大提升,因此可以通过不断提高时钟频率来提高传输速率,这就是为什么现在高速传输都采用串行方式的原因。例如常见的USB、SATA、PCIe、以太网等。
如果有人问关于串行传输与并行传输谁更好的问题,你也许会脱口而出:串行通信好!但是,串行传输之所以走红,是由于将单端信号传输转变为差分信号传输,并提升了控制器工作频率的原因,而“在相同频率下并行通信速度更高”这个基本道理是永远不会错的,通过增加位宽来提高数据传输率的并行策略仍将发挥重要作用。当然,前提是有更好的措施来解决并行传输过程中的种种问题。
标准串口协议的Verilog实现
基于Verilog实现标准串口协议发送8位数据:起始位 + 8位数据位 + 校验位 + 停止位 = 11位,每1位的时间是16个时钟周期,所以输入时钟应该为:波特率*16,带Busy忙信号输出。实现方法比较简单,数据帧的拼接、计数器计时钟周期,每16个时钟周期输出一位数据即可。
串口发送1个字节实现
/*
串口协议发送:起始位 + 8位数据位 + 校验位 + 停止位 = 11位 * 16 = 176个时钟周期
clk频率 = 波特率 * 16
*/
module uart_tx_8bit(
//input
input clk, //UART时钟=16*波特率
input rst_n,
input [7:0] data_in, //需要发送的数据
input trig, //上升沿发送数据
//output
output busy, //高电平忙:数据正在发送中
output reg tx //发送数据信号
);
reg[7:0] cnt; //计数器
reg trig_buf;
reg trig_posedge_flag;
// reg trig_negedge_flag;
reg send;
reg [10:0] data_in_buf; //trig上升沿读取输入的字节,拼接数据帧
wire odd_bit; //奇校验位 = ~偶校验位
wire even_bit; //偶校验位 = 各位异或
wire POLARITY_BIT = even_bit; //偶校验
// wire POLARITY_BIT = odd_bit; //奇校验
assign even_bit = ^data_in; //一元约简,= data_in[0] ^ data_in[1] ^ 。..。.
assign odd_bit = ~even_bit;
assign busy = send; //输出的忙信号
//起始位+8位数据位+校验位+停止位 = 11位 * 16 = 176个时钟周期
parameter CNT_MAX = 176;
always @(posedge clk)
begin
if(!rst_n)
begin
trig_buf 《= 0;
trig_posedge_flag 《= 0;
// trig_negedge_flag 《= 0;
end
else
begin
trig_buf 《= trig;
trig_posedge_flag 《= (~trig_buf) & trig; //在trig信号上升沿时产生1个时钟周期的高电平
// trig_negedge_flag 《= trig_buf & (~trig); //在trig信号下降沿时产生1个时钟周期的高电平
end
end
always @(posedge clk)
begin
if(!rst_n)
send 《= 0;
else if (trig_posedge_flag & (~busy)) //当发送命令有效且线路为空闲时,启动新的数据发送进程
send 《= 1;
else if(cnt == CNT_MAX) //一帧资料发送结束
send 《= 0;
end
always @ (posedge clk)
begin
if(!rst_n)
data_in_buf 《= 11’b0;
else if(trig_posedge_flag & (~busy)) //只读取一次数据,一帧数据发送过程中,改变输入无效
data_in_buf 《= {1‘b1, POLARITY_BIT, data_in[7:0], 1’b0}; //数据帧拼接
end
always @ (posedge clk)
begin
if(!rst_n)
cnt 《= 0;
else if(!send || cnt 》= CNT_MAX)
cnt 《= 0;
else if(send)
cnt 《= cnt + 1;
end
always @(posedge clk)
begin
if(!rst_n)
tx 《= 1;
else if(send)
begin
case(cnt) //1位占用16个时钟周期
0: tx 《= data_in_buf[0]; //低位在前,高位在后
16: tx 《= data_in_buf[1]; //bit0,占用第16~31个时钟
32: tx 《= data_in_buf[2]; //bit1,占用第47~32个时钟
48: tx 《= data_in_buf[3]; //bit2,占用第63~48个时钟
64: tx 《= data_in_buf[4]; //bit3,占用第79~64个时钟
80: tx 《= data_in_buf[5]; //bit4,占用第95~80个时钟
96: tx 《= data_in_buf[6]; //bit5,占用第111~96个时钟
112: tx 《= data_in_buf[7]; //bit6,占用第127~112个时钟
128: tx 《= data_in_buf[8]; //bit7,占用第143~128个时钟
144: tx 《= data_in_buf[9]; //发送奇偶校验位,占用第159~144个时钟
160: tx 《= data_in_buf[10]; //发送停止位,占用第160~167个时钟
CNT_MAX: tx 《= 1; //无空闲位
default:;
endcase
end
else if(!send)
tx 《= 1;
end
endmodule
仿真波形
串口接收1个字节实现
串口接收部分的实现,涉及到串口数据的采样,对于MCU来说,不同单片机集成外设的处理方式有所不同,具体采样原理可以参考内核的Reference Manual。以传统51内核为例,按照所设置的波特率,每个位时间被分为16个时间片。UART接收器会在第7、8、9三个时间片进行采样,按照三取二的逻辑获得该位时间内的采样结果。其它一些类型的单片机则可能会更加严苛,例如有些工业单片机会五取三甚至七取五(设置成抗干扰模式时)。
本程序中采用的中间值采样,即取16个时钟周期中的中间位作为当前的采样值。
//Verilog实现串口协议接收,带错误指示,校验错误和停止位错误
/*
16个时钟周期接收1位,中间采样
*/
module my_uart_rx(
input clk, //采样时钟
input rst_n,
input rx, //UART数据输入
output reg [7:0] dataout, //接收数据输出
output reg rx_ok, //接收数据有效,高说明接收到一个字节
output reg err_check, //数据出错指示
output reg err_frame //帧出错指示
);
reg [7:0] cnt;
reg [10:0] dataout_buf;
reg rx_buf;
reg rx_negedge_flag;
reg receive;
wire busy;
wire odd_bit; //奇校验位 = ~偶校验位
wire even_bit; //偶校验位 = 各位异或
wire POLARITY_BIT; //本地计算的奇偶校验
// wire polarity_ok;
// assign polarity_ok = (POLARITY_BIT == dataout_buf[9]) ? 1 : 0; //校验正确=1,否则=0
assign busy = rx_ok;
assign even_bit = ^dataout; //一元约简,= data_in[0] ^ data_in[1] ^ 。..。.
assign odd_bit = ~even_bit;
assign POLARITY_BIT = even_bit; //偶校验
// assign POLARITY_BIT = odd_bit; //奇校验
parameter CNT_MAX = 176;
//rx信号下降沿标志位
always @(posedge clk)
begin
if(!rst_n)
begin
rx_buf 《= 0;
rx_negedge_flag 《= 0;
end
else
begin
rx_buf 《= rx;
rx_negedge_flag 《= rx_buf & (~rx);
end
end
//在接收期间,保持高电平
always @(posedge clk)
begin
if(!rst_n)
receive 《= 0;
else if (rx_negedge_flag && (~busy)) //检测到线路的下降沿并且原先线路为空闲,启动接收数据进程
receive 《= 1; //开始接收数据
else if(cnt == CNT_MAX) //接收数据完成
receive 《= 0;
end
//起始位+8位数据位+校验位+停止位 = 11位 * 16 = 176个时钟周期
always @ (posedge clk)
begin
if(!rst_n)
cnt 《= 0;
else if(!receive || cnt 》= CNT_MAX)
cnt 《= 0;
else if(receive)
cnt 《= cnt + 1;
end
//校验错误:奇偶校验不一致
always @ (posedge clk)
begin
if(!rst_n)
err_check 《= 0;
else if(cnt == 152)
begin
// if(POLARITY_BIT == rx)
if(POLARITY_BIT != dataout_buf[9]) //奇偶校验正确
err_check 《= 1; //锁存
// else
// err_check 《= 1;
end
end
//帧错误:停止位不为1
always @ (posedge clk)
begin
if(!rst_n)
err_frame 《= 0;
else if(cnt == CNT_MAX)
begin
if(dataout_buf[10] != 1) //停止位
err_frame 《= 1;
// else
// err_frame 《= 1; //如果没有接收到停止位,表示帧出错
end
end
always @ (posedge clk)
begin
if(!rst_n)
dataout 《= 11‘h00;
else if(receive)
begin
// if(rx_ok)
if(cnt 》= 137)
dataout 《= dataout_buf[8:1]; //数据位:8-1位
// else if(!rx_ok)
// dataout 《= 0;
end
end
always @ (posedge clk)
begin
if(!rst_n)
rx_ok 《= 0;
else if(receive)
begin
if(cnt 》= 137) //137-169
rx_ok 《= 1;
else
rx_ok 《= 0;
end
else
rx_ok 《= 0;
end
//起始位+8位数据+奇偶校验位+停止位 = 11 * 16 = 176位
always @(posedge clk)
begin
if(!rst_n)
dataout_buf 《= 8’h00;
else if(receive)
begin
case (cnt) //中间采样
8‘d8: dataout_buf[0] 《= rx; //起始位=0
8’d24: dataout_buf[1] 《= rx; //LSB低位在前
8‘d40: dataout_buf[2] 《= rx;
8’d56: dataout_buf[3] 《= rx;
8‘d72: dataout_buf[4] 《= rx;
8’d88: dataout_buf[5] 《= rx;
8‘d104: dataout_buf[6] 《= rx;
8’d120: dataout_buf[7] 《= rx;
8‘d136: dataout_buf[8] 《= rx; //MSB高位在后
8’d152: dataout_buf[9] 《= rx; //奇偶校验位
8‘d168: dataout_buf[10] 《= rx; //停止位=1
default:;
endcase
end
end
endmodule
举报