复杂的时序逻辑电路设计

序列检测器

检测10010的产生。

  • 第一步:画出状态转移图
  • 第二步:化简状态
    状态示意图

  • 第三步:编写逻辑代码
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
module seqdet (
x,
z,
clk,
rst
);
input x, clk, rst;
output z;
reg [2:0] state; //状态寄存器
wire z;
parameter IDLE = 3'd0, A = 3'd1, B = 3'd2, C = 3'd3, D = 3'd4,
E = 3'd5, F = 3'd6, G = 3'd7;
assign z = (state == D && x == 0) ? 1 :
0; //状态为D时又收到了0,表明收到10010,应有输出Z为高
always @(posedge clk or negedge rst)
if (!rst) begin
state <= IDLE;
end
else
casex (state)
IDLE:
if (x == 1)
state <= A
; //用状态变量记住高电平(x==1)来过
else
state <= IDLE; //输入的是低电平,不符合要求,所以状态保留不变
A:
if (x == 0)
state <= B; //用状态变量记住第2位正确低电平(x==0)来过
else
state <= A; //输入的是高电平,不符合要求,所以状态保留不变
B:
if (x == 0)
state <= C; //用状态变量记住第3位正确低电平(x==0)来过
else
state <= F; //输入的是高电平,不符合要求,记住只有1位曾经对过
C:
if (x == 1)
state <= D; //用状态变量记住第4位正确高电平(x==1)来过
else
state <= G; //输入的是低电平,不符合要求,记住没有1位曾经对过
D:
if (x == 0)
state <= E; //用状态变量记住第五位正确低电平(x==0)来过
else
state <= A; //输入的是高电平,不符合要求,记住只1位对过
//回到状态A
E:
if (x == 0)
state <= C; //用状态变量记住100曾经来过,此状态为C
else
state <= A; //输入的是高电平,只有1位正确,该状态是A
F:
if (x == 1)
state <= A; //输入的是高电平,只有1位正确,该状态是A
else
state <= B; //输入的是低电平,已有2位正确,该状态是B
G:
if (x == 1) state <= F;
//输入的又是高电平,只有1位正确,记该状态F
else
state <= B; //输入的是低电平,已有2位正确,该状态是B
default: state <= IDLE;
endcase
endmodule

这个例子的状态是为了处理序列重叠情况,只要功能正确,不用去管是不是冗余。如果冗余,会有EDA工具处理的。

我的优化

我自己重新写的状态机,省去了两个状态 ( •̀ ω •́ )✧

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
module my_seqdet (
x,
z,
clk,
rst
);
input x, clk, rst;
output z;
reg [2:0] state; //状态寄存器
wire z;
parameter IDLE = 3'd0, A = 3'd1, B = 3'd2, C = 3'd3, D = 3'd4,
E = 3'd5, F = 3'd6, G = 3'd7;
assign z = (state == D && x == 0) ? 1 :
0; //状态为D时又收到了0,表明收到10010,应有输出Z为高
always @(posedge clk or negedge rst)
if (!rst) begin
state <= IDLE;
end
else
casex (state)
IDLE:
if (x == 1)
state <= A
; //用状态变量记住高电平(x==1)来过
else
state <= IDLE; //输入的是低电平,不符合要求,所以状态保留不变

A:
if (x == 0)
state <= B; //用状态变量记住第2位正确低电平(x==0)来过
else
state <= A; //输入的是高电平,不符合要求,所以状态保留不变

B:
if (x == 0)
state <= C; //用状态变量记住第3位正确低电平(x==0)来过
else
state <= A; //输入的是高电平,不符合要求,记住只有1位曾经对过

C:
if (x == 1)
state <= D; //用状态变量记住第4位正确高电平(x==1)来过
else
state <= IDLE; //输入的是低电平,不符合要求,记住没有1位曾经对过

D:
if (x == 0)
state <= E; //用状态变量记住第五位正确低电平(x==0)来过
else
state <= A; //输入的是高电平,不符合要求,记住只1位对过
//回到状态A

E:
if (x == 0)
state <= C; //用状态变量记住100曾经来过,此状态为C
else
state <= A; //输入的是高电平,只有1位正确,该状态是A

default: state <= IDLE;
endcase
endmodule

并行数据流

什么这根本就是我们I2C

关于I2C通信,请参阅下一篇笔记:数字系统设计复习笔记:第十一篇

并行数据流转换为一种特殊串行数据流模块的设计。

设计两个可综合的电路模块:

  • 第一个模块(M1)能把4位的并行数据转换为符合以下协议的串行数据流,数据流用scl和sda两条线传输,sclk 为输入的时钟信号,data[3:0]为输入数据,ack为M1请求M0发新数据信号。
  • 第二个模块(M2)能把串行数据流内的信息接收到,并转换为相应16条信号线的高电平,即若数据为 1,则第一条线路为高电平,数据为 n,则第 N条线路为高电平。
  • M0为测试用信号模块。该模块接收M1发出的 ack 信号,并产生新的测试数据data[3:0]

M1

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
/************************************************************************
*** 模块功能:按照设计要求把输入的4位平行数据转换为协议要求的串行数据流
*** 由scl和sda配合输出本模块为RTL可综合模块,已通过综合后门级网表仿真
************************************************************************/
module ptosda (
rst,
sclk,
ack,
scl,
sda,
data
);
input sclk, rst;
input [3:0] data; //并行数据输入
output ack;
output scl;
output sda;
//定义sda为单向的串行总线
reg scl, link_sda, ack, sdabuf;
reg [3:0] databuf;
reg [7:0] state;
assign sda = link_sda ? sdabuf :
1'b0; //link_sda 控制 sdabuf输出到串行总线上
parameter ready = 8'b0000_0000,
start = 8'b0000_0001, bit1 = 8'b0000_0010, bit2 = 8'b0000_0100,
bit3 = 8'b0000_1000, bit4 = 8'b0001_0000, bit5 = 8'b0010_0000,
stop = 8'b0100_0000, IDLE = 8'b1000_0000;
always @(posedge sclk or negedge rst
) //由输入的sclk时钟信号产生串行输出时钟scl
begin
if (!rst) scl <= 1;
else scl <= ~scl;
end
always @(posedge ack
) //请求新数据时存入并行总线上要转换的数据
begin
databuf <= data;
end
//----主状态机:产生控制信号,根据databuf中保存的数据,按照协议产生sda串行信号
always @(negedge sclk or negedge rst) begin
if (!rst) begin
link_sda <= 0; // 把sdabuf与sda串行总线连接
state <= ready;
sdabuf <= 1;
ack <= 0;
end
else begin
case (state)
ready:
if (ack) //并行数据已经到达
begin
link_sda <= 1; // 把sdabuf与sda串行总线连接
state <= start;
end
else //并行数据尚未到达
begin
link_sda <= 0; //把sda总线让出
state <= ready;
ack <= 1; //请求新数据信号置1
end
start:
if (scl && ack) //产生sda的开始信号
begin
sdabuf <= 0
; //在sda 连接的前提下,输出开始信号
state <= bit1;
end
else state <= start;
bit1:
if (!scl) //在scl为低电平时送出最高位databuf[3]
begin
sdabuf <= databuf[3];
state <= bit2;
ack <= 0;
end
else state <= bit1;
bit2:
if (!scl) //在scl为低电平时送出次高位databuf[2]
begin
sdabuf <= databuf[2];
state <= bit3;
end
else state <= bit2;
bit3:
if (!scl) //在scl为低电平时送出次低位databuf[1]
begin
sdabuf <= databuf[1];
state <= bit4;
end
else state <= bit3;
bit4:
if (!scl) //在scl为低电平时送出最低位databuf[0]
begin
sdabuf <= databuf[0];
state <= bit5;
end
else state <= bit4;
bit5:
if (!scl) //为产生结束信号做准备,先把sda变为低
begin
sdabuf <= 0;
state <= stop;
end
else state <= bit5;
stop:
if (scl) //在scl为高时把sda由低变高产生结束信号
begin
sdabuf <= 1;
state <= IDLE;
end
else state <= stop;
IDLE: begin
link_sda <= 0; // 把sdabuf与sda串行总线脱开
state <= ready;
end
default: begin
link_sda <= 0;
sdabuf <= 1;
state <= ready;
end
endcase
end
end
endmodule

M2

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
/****************************************************************
*** 模块功能:按照协议接收串行数据,进行处理并按照数据值在相应位输出高电平
*** 本模块为RTL可综合模块,已通过综合后门级网表仿真
****************************************************************/
module out16hi (
scl,
sda,
outhigh
);
input scl, sda;
//串行数据输入
output [15:0] outhigh; //根据输入的串行数据设置高电平位
reg [5:0] mstate;
//本模块的主状态
reg [3:0] pdata, pdatabuf; //记录串行数据位用寄存器
//和最终数据寄存器
reg [15:0] outhigh;
//输出位寄存器
reg StartFlag, EndFlag;
//数据开始和结束标志
always @(negedge sda)
if (scl) StartFlag <= 1;
// 串行数据开始标志
else if (EndFlag) StartFlag <= 0;
always @(posedge sda)
if (scl) begin
EndFlag <= 1;
// 串行数据结束标志
pdatabuf <= pdata; // 把收到的四位数据存入寄存器
end
else EndFlag <= 0;
// 数据接收还没有结束
parameter ready = 6'b00_0000, sbit0 = 6'b00_0001, sbit1 = 6'b00_0010,
sbit2 = 6'b00_0100, sbit3 = 6'b00_1000, sbit4 = 6'b01_0000;
always @(pdatabuf) //把收到的数据变为相应位的高电平
begin
case (pdatabuf)
4'b0001: outhigh = 16'b0000_0000_0000_0001;
4'b0010: outhigh = 16'b0000_0000_0000_0010;
4'b0011: outhigh = 16'b0000_0000_0000_0100;
4'b0100: outhigh = 16'b0000_0000_0000_1000;
4'b0101: outhigh = 16'b0000_0000_0001_0000;
4'b0110: outhigh = 16'b0000_0000_0010_0000;
4'b0111: outhigh = 16'b0000_0000_0100_0000;
4'b1000: outhigh = 16'b0000_0000_1000_0000;
4'b1001: outhigh = 16'b0000_0001_0000_0000;
4'b1010: outhigh = 16'b0000_0010_0000_0000;
4'b1011: outhigh = 16'b0000_0100_0000_0000;
4'b1100: outhigh = 16'b0000_1000_0000_0000;
4'b1101: outhigh = 16'b0001_0000_0000_0000;
4'b1110: outhigh = 16'b0010_0000_0000_0000;
4'b1111: outhigh = 16'b0100_0000_0000_0000;
4'b0000: outhigh = 16'b1000_0000_0000_0000;
endcase
end
always @(posedge scl) //在检测到开始标志后,每次scl正跳变沿时接收数据,共四位
if (StartFlag)
case (mstate)
sbit0: begin
mstate <= sbit1;
pdata[3] <= sda;
$display("I am in sdabit0");
end
sbit1: begin
mstate <= sbit2;
pdata[2] <= sda;
$display("I am in sdabit1");
end
sbit2: begin
mstate <= sbit3;
pdata[1] <= sda;
$display("I am in sdabit2");
end
sbit3: begin
mstate <= sbit4;
pdata[0] <= sda;
$display("I am in sdabit3");
end
sbit4: begin
mstate <= sbit0;
$display("I am in sdasstop");
end
default: mstate <= sbit0;
endcase
else mstate <= sbit0;
endmodule