Intro#
RS-232 是一个通信中常见的电气接口标准,现在常以 DE-9 的物理端子相互连接,在其之上几乎总是运行 UART. 但当我们细看其标准 TIA/EIA-232-F 时, 就会发现一些诸如 DTE 与 DCE 奇怪的概念. 这些概念一般就出现在 TIA 发布的规范中.
网上搜一下 DTE 与 DCE,总是能看到有人将 PC 与 MODEM 通过 PSTN 拨号上网的时代搬出来,并断言
PC 是 DTE 而 MODEM 是 DCE.
然而那个时代离现在似乎有点远,就好像在说这些概念是被历史抛弃了吗?是也不是.
即使当今的嵌入式开发者在使用 TTL 电平的 UART 时碰到一些纯软件问题,他们可能还是会在一些设备文档中不经意之间跟这些术语打交道.
本文将探讨区分 DTE 与 DCE 背后的动机,以及这些概念在当今是怎么被弱化的,并展示在 SystemVerilog 数字设计时合理运用这些概念可能带来的好处.
因为我们要在设备之间定义一种接口,所以才创造出了 DTE 与 DCE 的概念.
接口就是一捆有方向的线#
这么说可能会有瑕疵.
以 I2C 为例,其中就有双向的数据总线,但考虑任一时刻,数据线的方向总是确定的,因为规范中对于信号时序是良定义的.
我们可以看一下 SPI 的接口定义.
SPI 接口 |
含义 |
方向 |
CS |
片选 |
主设备 → 从设备 |
SCLK |
串行时钟 |
主设备 → 从设备 |
MOSI |
主设备的输出,从设备的输入 |
主设备 → 从设备 |
MISO |
主设备的输入,从设备的输出 |
从设备 → 主设备 |
这里的关键点就是,为了区分信号的方向,标准必须将设备进行某种分类,然后才能定义它们之间的数据流向.
RS-232 也是这样的,下面是比较常见的一种对于 DTE 与 DCE 的定义.
DTE 自发产生数据而 DCE 负责数据交换与信号转换.
RS-232 接口 |
含义 |
方向 |
DCD |
Data Carrier Detect |
DCE → DTE |
RXD |
Received Data |
DCE → DTE |
TXD |
Transmitted Data |
DTE → DCE |
DTR |
Data Terminal Ready |
DTE → DCE |
GND |
Ground |
|
DSR |
Data Set Ready |
DCE → DTE |
RTS |
Request To Send |
DTE → DCE |
CTS |
Clear To Send |
DCE → DTE |
RI |
Ring Indicator |
DCE → DTE |
为了连接 DTE 与另外一个 DCE,必须要使用直通线.
困境与弱化#
实际场景下迎来了两难,让这些概念变得更加困惑.
RS-232 最常见的应用场景就是点对点的 UART. 例如 PC 连接到 FPGA 上的 DE-9 端子后,就建立了一个全双工的信道,FPGA 收集传感器数据并通过 UART 发送. 问题来了,在这种场景下哪个设备是 DCE 呢?
事实上,它们都是 DTE.
要连接两个 DTE 就必须使用交叉线,也叫零 MODEM. 它甚至不只是一根线,根据定义,它完全有资格作为一个独立的 DCE.
特意区分 DTE 与 DCE 的话,在运行 UART 设备上标注着 TXD
的引脚就不一定表示 “从该设备发出的数据” 了.
然而运行 UART 的 DTE 设备之间的连接占大头,就使得 DTE 与 DCE 的概念显得格格不入,逐渐被现代设备弱化. 所以现在如果有标注着 TXD
的引脚一般都表示 “从该设备发出的数据”,因为它们本质上一般都是 DTE.
Ethernet (略微偏题,可跳过)#
Ethernet 设备 / 端口同样被分类为了 DTE 与 DCE,也就有直通线和交叉线,但它们以另外一种方式被弱化了.
不像 RS-232 之间的 UART 连接,你没法说清楚在 Ethernet 中哪种类型的设备是主体. 为了减轻用户区分不同线缆的心智压力,自动 MDI-X 可以协商链路类型(主要针对 10BASE-T / 100BASE-TX), 使得用户不用关心线缆类型. 更进一步地,PMA 在 1000BASE-T 中直接支持双向信号对,并能识别可能存在的极性翻转.
由于线序可以被 Ethernet 收发器自动解析和识别,现在大多数 Ethernet 的 RJ-45 线缆都是直通线,所以在路由器、交换机、集线器与 PC 之间的连接也就没有什么差别了. 相同的连接也就有相同的端口,相同的端口也就造就了实际用户感受不到差别.
数字设计中的应用#
因为实际生活中的设备以 DTE 居多,这些概念在现实生活中并不实用. 然而在数字设计中,我们可以在内部使用 DCE 来实现复杂的逻辑. 下面我们用 SystemVerilog 演示一些例子.
统一接口#
这是一个含有标准 MODEM 信号集的 UART 接口.
1
2
3
4
5
6
7
8
9
10
|
interface uart_io;
logic dcd;
logic rxd;
logic txd;
logic dtr;
logic dsr;
logic rts;
logic cts;
logic ri;
endinterface: uart_io
|
要让 DTE 与 DCE 有用武之地,我们可以使用 modport
关键字来定义 DTE 与 DCE 变体的信号方向.
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
|
interface uart_io;
logic dcd;
logic rxd;
logic txd;
logic dtr;
logic dsr;
logic rts;
logic cts;
logic ri;
modport dte (
input dcd,
input rxd,
output txd,
output dtr,
input dsr,
output rts,
input cts,
input ri
);
modport dce (
output dcd,
output rxd,
input txd,
input dtr,
output dsr,
input rts,
output cts,
output ri
);
endinterface: uart_io
|
零 MODEM#
这是一个简单的零 MODEM的构造,这里使用了 uart_io.dce
变体是因为零 MODEM本身是一个 DCE,并且对端分别连接的是 DTE.
1
2
3
4
5
6
|
module null_modem (uart_io.dce a, uart_io.dce b);
alias a.rxd = b.txd, b.rxd = a.txd;
alias a.cts = b.rts, b.cts = a.rts;
alias a.dsr = b.dtr, b.dsr = a.dtr;
alias a.dcd = a.dtr, b.dcd = b.dtr;
endmodule: null_modem
|
更进一步,我们可以直接移除变体的使用,如果有信号方向违例的话(比如把一个 DTE 用零 MODEM连接到了 DTE),综合器是会发出错误消息的.
1
2
3
4
5
6
|
module null_modem (uart_io a, uart_io b);
alias a.rxd = b.txd, b.rxd = a.txd;
alias a.cts = b.rts, b.cts = a.rts;
alias a.dsr = b.dtr, b.dsr = a.dtr;
alias a.dcd = a.dtr, b.dcd = b.dtr;
endmodule: null_modem
|
Mux#
Mux 在下面的一个例子中比较重要,它通过 select_i
信号来选择具体是哪一个设备连接到主设备上. 下面的样例代码看上去比较迂腐,主要是因为 SystemVerilog 不支持基于 interface 的 mux,于是开发者必须手动展开并复用信号线.
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
|
interface uart_io;
logic dcd;
logic rxd;
logic txd;
logic dtr;
logic dsr;
logic rts;
logic cts;
logic ri;
modport dte (
input dcd,
input rxd,
output txd,
output dtr,
input dsr,
output rts,
input cts,
input ri
);
modport dce (
output dcd,
output rxd,
input txd,
input dtr,
output dsr,
input rts,
output cts,
output ri,
import terminate
);
function terminate;
dcd = 1'b0;
rxd = 1'b0;
dsr = 1'b0;
cts = 1'b0;
endfunction: terminate
endinterface: uart_io
module uart_mux (
uart_io.dce x,
uart_io.dce a,
uart_io.dce b,
input logic select_i
);
always_comb begin
x.rxd = (select_i == 1'b0) ? a.txd : b.txd;
x.cts = (select_i == 1'b0) ? a.rts : b.rts;
x.dsr = (select_i == 1'b0) ? a.dtr : b.dtr;
x.dcd = (select_i == 1'b0) ? a.dtr : b.dtr;
if (select_i == 1'b0) begin
a.rxd = x.txd;
a.cts = x.rts;
a.dsr = x.dtr;
a.dcd = x.dtr;
b.terminate();
end
else begin
b.rxd = x.txd;
b.cts = x.rts;
b.dsr = x.dtr;
b.dcd = x.dtr;
a.terminate();
end
end
endmodule: uart_mux
|
一个(并不)令人惊讶的事实,先前的 null MODEM 可由 mux 反向定义,如下所示.
1
2
3
4
|
module null_modem (uart_io a, uart_io b);
uart_io dangling ();
uart_mux mux (a, b, dangling, 1'b0);
endmodule: null_modem
|
简单的基于 mux 的交换机#
这里是一个简单的基于 mux 的交换机. 有一台 PC 以及两个额外的 DTE,通过按键可以选择 PC 连接到哪个 DTE 上.
这是我最喜欢的例子,因为这很好的解释了为什么网络极客使用「switch」作为交换机的术语. 这个 UART 交换机虽然非常简单,但它与复杂计算机网络中的 L2 交换机、L3 交换机背后的原理是一样的.
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
|
module uart_switch (
input logic clock_i,
input logic reset_ni,
input logic key_i,
uart_io.dce pc,
uart_io.dce a,
uart_io.dce b
);
logic index = 1'b0;
logic last_key = 1'b0;
always @ (posedge clock_i, negedge reset_ni) begin
if (!reset_ni) begin
index <= 1'b0;
end
else begin
if (key_i && !last_key) begin
index <= !index;
end
last_key <= key_i;
end
end
uart_mux mux (pc, a, b, index);
endmodule: uart_switch
|
完整示例#
可以查看完整示例 alphabet.sv,顶层模块如下所示.
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
|
module top (
input logic clock_i,
input logic reset_ni,
input logic key_i,
uart_io.dce uart
);
uart_io uart_uppercase ();
alphabet_generator #(.BASE("A")) uppercase (
.clock_i(clock_i),
.reset_ni(reset_ni),
.uart(uart_uppercase)
);
uart_io uart_lowercase ();
alphabet_generator #(.BASE("a")) lowercase (
.clock_i(clock_i),
.reset_ni(reset_ni),
.uart(uart_lowercase)
);
uart_switch switch (
.clock_i(clock_i),
.reset_ni(reset_ni),
.key_i(key_i),
.pc(uart),
.a(uart_uppercase),
.b(uart_lowercase)
);
endmodule: top
|
有两个字母生成器,一个大写一个小写,用户可以通过轻触按键切换生成器. 记得把下面的时钟频率给改成合适的值.
1
2
3
4
5
6
7
8
9
10
|
module alphabet_generator #(
parameter byte BASE = "A",
parameter longint unsigned CLOCK = 12_000_000,
parameter longint unsigned DIVISOR = (CLOCK / 9_600)
) (
input logic clock_i,
input logic reset_ni,
uart_io.dte uart
);
...
|
为了明确通信接口之间的信号方向,才会发明 DTE 与 DCE 的概念. 然而 DTE 占多数的今天,TXD
RXD
这些标注只有在 DTE 才有更自然的含义,所以人们不管不顾 DTE 与 DCE,总是使用某种零 MODEM来连接设备.
在数字设计中恰当地使用这些概念,能让声明更加统一,并产出更优雅的 HDL 代码.