本题链接:
https://hdlbits.01xz.net/wiki/Exams/review2015_fancytimer
这是一系列五个练习中的第五部分,这些练习由几个较小的电路构建一个复杂的计数器。 你可能会需要先做前面的四个练习 (counter、sequence recognizer FSM、 FSM delay和 combined FSM)。
我们想创建带有一个输入的计时器:
当检测到特定输入模式 (1101) 时启动,
再移 4 位以确定延迟的持续时间,
等待计数器完成计数,并且
通知用户并等待用户确认计时器。
串行数据在数据输入引脚上可用。 当接收到模式 1101 时,电路必须移入接下来的 4 位,首先是最高有效位。 这 4 位决定了定时器延迟的持续时间。 我将此称为延迟[3:0]。
之后,状态机断言其计数输出以指示它正在计数。 状态机必须精确计数 (delay[3:0] + 1) * 1000 个时钟周期。 例如,delay=0 表示计数 1000 个周期,delay=5 表示计数 6000 个周期。 同时输出当前剩余时间。 这应该等于delay 个 1000 个周期,然后 delay-1 个 1000 个周期,依此类推,直到 0 再 1000 个周期。 当电路不计数时,count[3:0] 输出是无关紧要的(设置任何值都可实现)。
此时,电路必须断言 done 以通知用户计时器已超时,并等待输入 ack 为 1,然后再复位以查找下一次出现的启动序列 (1101)。
电路应复位为开始搜索输入序列 1101 的状态。
这是预期输入和输出的示例。 'x' 状态看起来可能有点混乱。 它们表明有限状态机不应该关心该周期中的特定输入信号。 例如,一旦读取了 1101 和 delay[3:0],电路就不再查看数据输入,直到在其他所有操作完成后恢复搜索。 在本例中,电路计数 2000 个时钟周期,因为 delay[3:0] 值为 4'b0001。 最后几个周期以 delay[3:0] = 4'b1110 开始另一个计数,它将计数 15000 个周期。


题目
module top_module (
input clk,
input reset, // Synchronous reset
input data,
output [3:0] count,
output counting,
output done,
提示:
硬件应该类似与 Exams/review2015_fsm 中的有限状态机、Exams/review2015_count1k 中的计数器和 Exams/review2015_shiftcount 中的移位寄存器+计数器的组合。 在此基础上你可能需要加入更多的比较器。
如果组件位于它们自己的 always 块中,则可以将所有代码放在单个模块中,只要清楚哪个代码块对应于哪个硬件块即可。 不要将多个 always 块合并在一起,因为这会导致阅读困难并且容易出错。

答案
module top_module (
input clk,
input reset, // Synchronous reset
input data,
output [3:0] count,
output counting,
output done,
input ack );
parameter idle = 0, s1 = 1, s2 = 2, s3 = 3, b0 = 4, b1 = 5;
parameter b2 = 6, b3 = 7, counts = 8, waiting = 9;
reg [3:0] state, next_state;
reg [9:0] counter;
always @(*) begin
case (state)
idle: next_state = data ? s1 : idle;
s1: next_state = data ? s2 : idle;
s2: next_state = data ? s2 : s3;
s3: next_state = data ? b0 : idle;
b0: next_state = b1;
b1: next_state = b2;
b2: next_state = b3;
b3: next_state = counts;
counts: next_state = (count == 0 && counter == 999) ? waiting : counts;
waiting:next_state = ack ? idle : waiting;
endcase
end
always @(posedge clk) begin
if (reset) begin
count <= 0;
counter <= 0;
end
else begin
case (state)
b0: count[3] <= data;
b1: count[2] <= data;
b2: count[1] <= data;
b3: count[0] <= data;
counts: begin
if (count >= 0) begin
if (counter < 999) begin
counter <= counter + 1;
end
else begin
count <= count - 1;
counter <= 0;
end
end
end
default: counter <= 0;
endcase
end
end
always @(posedge clk) begin
if (reset) begin
state <= idle;
end
else begin
state <= next_state;
end
end
assign counting = (state == counts);
assign done = (state == waiting);
endmodule

非阻塞赋值属于并行执行语句,即下一条语句的执行和当前语句的执行是同时进行的,它不会阻塞位于同一个语句块中后面语句的执行。非阻塞赋值语句使用小于等于号 <= 作为赋值符。
条件(if)语句用于控制执行语句要根据条件判断来确定是否执行。条件语句用关键字 if 和 else 来声明,条件表达式必须在圆括号中。如果 if 条件每次执行的语句只有一条,那么可以不使用 begin 与 end 关键字。但如果是 if-if-else 的形式,即便执行语句只有一条,不使用 begin 与 end 关键字也会引起歧义。当然,编译器一般按照就近原则,使 else 与最近的一个 if(例子中第二个 if)相对应。但显然这样的写法是不规范且不安全的。所以条件语句中加入 begin 与 and 关键字就是一个很好的习惯。
case 语句是一种多路条件分支的形式,可以解决 if 语句中有多个条件选项时使用不方便的问题。case 语句支持嵌套使用。case 语句中的条件选项表单式不必都是常量,也可以是 x 值或 z 值。当多个条件选项下需要执行相同的语句时,多个条件选项可以用逗号分开,放在同一个语句块的候选项中。但是 case 语句中的 x 或 z 的比较逻辑是不可综合的,所以一般不建议在 case 语句中使用 x 或 z 作为比较值。
同步复位是指复位信号在时钟有效边沿到来时有效。如果没有时钟,无论复位信号怎样变化,电路也不执行复位操作。
该描述代码常常会被综合成如下电路:

同步复位的优点:信号间是同步的,能滤除复位信号中的毛刺,有利于时序分析。
同步复位的缺点:大多数触发器单元是没有同步复位端的,采用同步复位会多消耗部分逻辑资源。且复位信号的宽度必须大于一个时钟周期,否则可能会漏掉复位信号。
有限状态机(Finite-State Machine,FSM),简称状态机,是表示有限个状态以及在这些状态之间的转移和动作等行为的数学模型。状态机不仅是一种电路的描述工具,而且也是一种思想方法,在电路设计的系统级和 RTL 级有着广泛的应用。
Verilog 中状态机主要用于同步时序逻辑的设计,能够在有限个状态之间按一定要求和规律切换时序电路的状态。状态的切换方向不但取决于各个输入值,还取决于当前所在状态。状态机可分为 2 类:Moore 状态机和 Mealy 状态机。
参考内容:
4.2 Verilog 过程赋值 | 菜鸟教程:
https://www.runoob.com/w3cnote/verilog-process-assign.html
4.5 Verilog 条件语句 | 菜鸟教程:
https://www.runoob.com/w3cnote/verilog-condition-statement.html
4.6 Verilog 多路分支语句 | 菜鸟教程:
https://www.runoob.com/w3cnote/verilog-case.html
5.1 Verilog 复位简介 | 菜鸟教程:
https://www.runoob.com/w3cnote/verilog2-reset.html
6.3 Verilog 状态机 | 菜鸟教程:
https://www.runoob.com/w3cnote/verilog-fsm.html