转载自:知乎
作者:ljgibbs
首先附上传送门:
Exams/review2015 fsmhdlbits.01xz.net
Problem 155 FSM:The complete FSM
牛刀小试
本题实现复杂计数器的第四个组件。
在此题之前,我们已经分别实现了 FSM:Enable shift register 以及 FSM:1101 序列检测器。接下来我们继续前进,实现这个复杂计数器的完整 FSM。
复杂计数器需要如下这些功能特性:
- 在数据流中检测到特定序列后启动计数器,该序列为: 1101
- 将 4bits 数据输入移位寄存器,作为计数器的初值
- 等待计数器结束计数
- 告知上层应用计数完成,并等待用户通过 ack 信号确认
在本题练习中,只需要实现控制状态机,不需要实现数据通路,比如计数器本身以及数据比较器等。
数据流从模块的 data 信号输入,当检测到 1101 序列后,状态机需要置高输出信号 shft\_ena 并保持 4 个周期(用于将接下来 4bit 数据输入移位寄存器)。
之后,状态机置高 counting 信号,表示其正在等待计数器完成计数,当计数器完成计数输出 done\_counting 信号后,counting 信号置低。
再此后,状态机置高 done 信号通知上层应用计数器计数完成,等待 ack 信号置高后,状态机清除 done 信号,返回空闲状态等待捕获下一个 1101 序列。
本题给出了一个期望输入输出的例子。图中的斜线代表当前信号为 'X', 表示状态机不关心该信号当前的值。比如图例中,一旦 FSM 检测到 1101 序列后,在此次计数器事件完成前,对于当前的数据流不再关心。
module top_module (
input clk,
input reset, // Synchronous reset
input data,
output shift_ena,
output counting,
input done_counting,
output done,
input ack );
localparam FSM_W = 8;
localparam FSM_W1 = FSM_W - 1'b1;
reg [FSM_W1:0] state;
reg [FSM_W1:0] nxt_state;
localparam IDLE = 0;
localparam S_0 = 1;
localparam S_1 = 2;
localparam S_11 = 3;
localparam S_110 = 4;
localparam S_SHFT_ENA = 5;
localparam WAT_CNT_FIN = 6;
localparam WAT_ACK = 7;
reg [1:0] asrt_cntr;
wire asrt_cntr_add;
wire asrt_cntr_clr;
//assert signal cntr
always @(posedge clk) begin
if(reset) begin
asrt_cntr <= 'b0;
end else if(asrt_cntr_add)begin
asrt_cntr <= asrt_cntr + 1'b1;
end else if(asrt_cntr_clr)begin
asrt_cntr <= 'b0;
end
end
assign asrt_cntr_add = state[S_SHFT_ENA];
assign asrt_cntr_clr = 1'b0; // cntr clear itself
// State transition logic (combinational)
always @(*) begin
nxt_state[IDLE ] = 1'b0; // never reach for nxt_state
nxt_state[S_0 ] = (state[IDLE ] && ~data) || (state[S_1 ] && ~data) || (state[S_0 ] && ~data) || (state[S_110 ] && ~data) || (state[WAT_ACK ] && ack);
nxt_state[S_1 ] = (state[IDLE ] && data) || (state[S_0 ] && data);
nxt_state[S_11 ] = (state[S_1 ] && data) || (state[S_11 ] && data);
nxt_state[S_110 ] = (state[S_11 ] && ~data);
nxt_state[S_SHFT_ENA ] = (state[S_110 ] && data) || (state[S_SHFT_ENA ] && ~(asrt_cntr == 2'd3));
nxt_state[WAT_CNT_FIN ] = (state[S_SHFT_ENA] && asrt_cntr == 2'd3)
|| (state[WAT_CNT_FIN ] && ~done_counting);
nxt_state[WAT_ACK ] = (state[WAT_CNT_FIN ] && done_counting)
|| (state[WAT_ACK ] && ~ack);
end
// State flip-flops (sequential)
always @(posedge clk) begin
if(reset)
state <= 'b10; //SEQ_RCGN
else begin
state <= nxt_state;
end
end
//output logic
assign done = state[WAT_ACK] ;
assign counting = state[WAT_CNT_FIN];
assign shift_ena = state[S_SHFT_ENA];
endmodule
看到题目的时候,我第一时间的想法是可以利用前两个 FSM 作为底层模块来实现本题。
嗯,但我发现走了弯路,因为前两题的逻辑并不适合作为底层模块,最后只能通过疯狂凑时序实现。如果读者第一想法和我一样,或许可以一试,锻炼下凑时序的能力。(工程中很实用的 /狗头)
所以后来笔者放弃了最初的想法,改为利用先前 2 个 FSM 的逻辑,在同一个模块层次中重新实现一个 FSM。状态可以分为 4 类:序列检测/输出使能/等待 cntr\_done/等待 ack。
本题的状态机还算是比较复杂了,此处建议读者可以读一下英文原版的题目,学习一下原作者对状态机状态的描述,十分简洁明了。
Problem 156 The complete timer
牛刀小试
终于到了完整构建复杂计数器的时候,整体功能已经在上题中讨论,这里不再赘述。
在数据流中检测到序列 1101 后,电路需要将接下来的 4bit 数据移入移位寄存器。4bit 数据决定了计数器的计数周期,称为 delay[3:0]。首先到达的比特作为数据的高位。
之后,状态机置高 counting 信号,表示其正在等待计数器完成计数。在 FSM 中增加计数器状态,计数周期为 (delay[3:0] + 1 )* 1000 个时钟周期。比如 delay = 0 时,计数值为 1000 个周期。delay = 5 代表 6000 个周期。同时输出 count 当前剩余的计数周期,输出当前剩余计数周期的千位(比如,还剩1000个周期输出 1,还剩 999 个周期时输出 0)。当计数停止后,count 的输出可以为任意数。
当计数完成后,电路置高 done 信号通知上层应用计数器计数完成,等待 ack 信号置高后,状态机清除 done 信号,返回空闲状态等待捕获下一个 1101 序列。
本题给出了一个期望输入输出的例子。图中的斜线代表当前信号为 'X', 表示状态机不关心该信号当前的值。比如图例中,一旦 FSM 检测到 1101 序列并读取 delay[3:0] 后,在此次计数器事件完成前,对于当前的数据流不再关心。
在图例中,电路计数周期为 2000 ,因为 delay[3:0] 数值为 4'b0001 。在后续的第二个计数周期中,因为 delay[3:0] = 4‘b1110,所以计数周期为 15000。
解答与分析
module shft_reg_cntr (
input clk,
input shift_ena,
input count_ena,
input data,
output reg [3:0] q);
always @(posedge clk ) begin
if(shift_ena)
q <= {q[2:0],data};
else if(count_ena) begin
q <= q - 4'd1;
end
end
endmodule
module cntr_1k (
input clk,
input reset,
output reg [9:0] q);
always @(posedge clk ) begin
if(reset | q == 10'd999)
q <= 10'd0;
else begin
q <= q + 10'd1;
end
end
endmodule
module top_module (
input clk,
input reset, // Synchronous reset
input data,
output [3:0] count,
output counting,
output done,
input ack );
localparam FSM_W = 8;
localparam FSM_W1 = FSM_W - 1'b1;
reg [FSM_W1:0] state;
reg [FSM_W1:0] nxt_state;
localparam IDLE = 0;
localparam S_0 = 1;
localparam S_1 = 2;
localparam S_11 = 3;
localparam S_110 = 4;
localparam S_SHFT_ENA = 5;
localparam WAT_CNT_FIN = 6;
localparam WAT_ACK = 7;
wire done_counting;
//assert signal cntr
reg [1:0] asrt_cntr;
wire asrt_cntr_add;
wire asrt_cntr_clr;
//delay val
wire[3:0] dly_val;
wire dly_val_shft_ena;
wire dly_val_dec;
//delay cntr
wire [9:0] dly_cntr;
wire dly_cntr_ena;
//assert signal cntr
always @(posedge clk) begin
if(reset) begin
asrt_cntr <= 'b0;
end else if(asrt_cntr_add)begin
asrt_cntr <= asrt_cntr + 1'b1;
end else if(asrt_cntr_clr)begin
asrt_cntr <= 'b0;
end
end
assign asrt_cntr_add = state[S_SHFT_ENA];
assign asrt_cntr_clr = 1'b0; // cntr clear itself
//delay cntr
cntr_1k U_cntr_1k(
.clk (clk),
.reset (~dly_cntr_ena),
.q (dly_cntr)
);
assign dly_cntr_ena = state[WAT_CNT_FIN ];
//delay val
shft_reg_cntr U_shft_reg_cntr
(
.clk (clk),
.shift_ena (dly_val_shft_ena),
.count_ena (dly_val_dec),
.data (data),
.q (dly_val)
);
assign dly_val_shft_ena = state[S_SHFT_ENA ];
assign dly_val_dec = (dly_cntr == 16'd999 ) && ~(dly_val == 4'd0);
assign done_counting = dly_cntr == 16'd999 && dly_val == 4'd0;
// State transition logic (combinational)
always @(*) begin
nxt_state[IDLE ] = 1'b0; // never reach for nxt_state
nxt_state[S_0 ] = (state[IDLE ] && ~data) || (state[S_1 ] && ~data) || (state[S_0 ] && ~data)
|| (state[S_110 ] && ~data) || (state[WAT_ACK ] && ack);
nxt_state[S_1 ] = (state[IDLE ] && data) || (state[S_0 ] && data);
nxt_state[S_11 ] = (state[S_1 ] && data) || (state[S_11 ] && data);
nxt_state[S_110 ] = (state[S_11 ] && ~data);
nxt_state[S_SHFT_ENA ] = (state[S_110 ] && data) || (state[S_SHFT_ENA ] && ~(asrt_cntr == 2'd3));
nxt_state[WAT_CNT_FIN ] = (state[S_SHFT_ENA] && asrt_cntr == 2'd3)
|| (state[WAT_CNT_FIN ] && ~done_counting);
nxt_state[WAT_ACK ] = (state[WAT_CNT_FIN ] && done_counting)
|| (state[WAT_ACK ] && ~ack);
end
// State flip-flops (sequential)
always @(posedge clk) begin
if(reset)
state <= 'b10; //SEQ_RCGN
else begin
state <= nxt_state;
end
end
//output logic
assign done = state[WAT_ACK] ;
assign counting = state[WAT_CNT_FIN];
assign count = dly_val;
endmodule
相比上一题,本题增加了实际的计数逻辑以取代输入信号 done\_counting,但状态以及转移关系与上一题相同。
设计 dly\_val [3:0] ,在 shft\_ena 移位使能的情况下,从数据流中读取 4bit 延迟值。
计数逻辑为一个周期为 1000 的计数器,当计数器输出 16’d999 时,将 dly\_val 减 1。
功能是不是听上去很熟悉,没错,我们可以分别使用之前实现的 移位寄存器 以及 周期为 1000 的计数器。(笔者一开始忘了来着-\_-||),并在相应状态产生使能信号控制这两个下层模块。
Problem 157 FSM:One-hot logic equations
牛刀小试
本题给出了一个具有 3 输入,3 输出以及 10 个状态的 FSM 的状态转移图。
仅需要实现状态转移和输出逻辑的组合逻辑,tb 会检测是否按照要求使用了独热码。
PS:其实这就是上一题中的状态机
图中的状态依次进行了独热码编码,S 为 10b'1,S1、S11、S110、B0、B1、B2、B3、Count、Wait 以此类推。
本题仅要求产生以下状态的状态转移信号:
- B3\_next ,B2 状态的次态(原题写的 B1,应该为笔误)
- S\_next
- S1\_next
- Count\_next
以及下列输出信号
- done
- counting
- shift\_ena
解答与分析
module top_module(
input d,
input done_counting,
input ack,
input [9:0] state, // 10-bit one-hot current state
output B3_next,
output S_next,
output S1_next,
output Count_next,
output Wait_next,
output done,
output counting,
output shift_ena
);
localparam FSM_W = 10;
localparam FSM_W1 = FSM_W - 1'b1;
reg [FSM_W1:0] nxt_state;
localparam S_0 = 0;
localparam S_1 = 1;
localparam S_11 = 2;
localparam S_110 = 3;
localparam B0 = 4;
localparam B1 = 5;
localparam B2 = 6;
localparam B3 = 7;
localparam WAT_CNT_FIN = 8;
localparam WAT_ACK = 9;
always @(*) begin
nxt_state[S_0 ] = (state[S_1 ] && ~d) || (state[S_0 ] && ~d)
|| (state[S_110 ] && ~d) || (state[WAT_ACK ] && ack);
nxt_state[S_1 ] = (state[S_0 ] && d);
nxt_state[S_11 ] = (state[S_1 ] && d) || (state[S_11 ] && d);
nxt_state[S_110 ] = (state[S_11 ] && ~d);
nxt_state[B0 ] = (state[S_110 ] && d);
nxt_state[B1 ] = (state[B0 ]);
nxt_state[B2 ] = (state[B1 ]);
nxt_state[B3 ] = (state[B2 ]);
nxt_state[WAT_CNT_FIN ] = state[B3]
|| (state[WAT_CNT_FIN ] && ~done_counting);
nxt_state[WAT_ACK ] = (state[WAT_CNT_FIN ] && done_counting)
|| (state[WAT_ACK ] && ~ack);
end
assign B3_next = nxt_state[B3];
assign S_next = nxt_state[S_0];
assign S1_next = nxt_state[S_1];
assign Count_next = nxt_state[WAT_CNT_FIN];
assign Wait_next = nxt_state[WAT_ACK];
assign done = state[WAT_ACK] ;
assign counting = state[WAT_CNT_FIN];
assign shift_ena = state[B0]
|| state[B1]
|| state[B2]
|| state[B3];
endmodule
Problem 158 Mux
牛刀小试
接下来的几题中,请从题目给出的 Verilog 中找出并修正 BUG。
本题为 8bit 位宽的 2 选 1 选择器。
解答与分析
module top_module (
input sel,
input [7:0] a,
input [7:0] b,
output [7:0] out );
//assign out = (~sel & a) | (sel & b);
assign out = ({8{sel}} & a) | ({8{~sel}} & b);
endmodule
此题的问题在于,sel 是一个 1bit 信号,sel & a 相当于 {7'b0,sel} & a,实现正确的逻辑需要将 sel 复制延展为 8bit。
Problem 159 NAND
牛刀小试
本题中的三输入与非门不工作了,找出并修正 BUG。
读者必须使用提供的 5 输入与门来实现这个与非门。
解答与分析
module top_module (input a, input b, input c, output out);//
wire out_rev;
andgate inst1 (out_rev, a, b, c,1'b1,1'b1);
assign out = ~out_rev;
endmodule
这里实际用了一个非门,笔者想了想,与门本身肯定不能搭出与非门吧。
推荐阅读
- HDLBits:在线学习 Verilog (三十一 · Problem 150-154)
- HDLBits:在线学习 Verilog (三十 · Problem 145-149)
- HDLBits:在线学习 Verilog (二十九 · Problem 140-144)
- HDLBits:在线学习 Verilog (二十八 · Problem 135-139)
- HDLBits:在线学习 Verilog (二十七 · Problem 130-134)
关注此系列,请关注专栏FPGA的逻辑