本题带奇偶校验且有数据输出的序列接收器是HDLBits中这个小系列的最后一题,相当于在第一题的基础上加入了数据输出和奇偶校验的功能,所以本篇博客实际上涵盖了三道题的内容。
代码中,奇校验复位标志位的设置和done输出参考了博主“Julio吼”的这篇文章HDL Bits代码记录-Fsm serialdp
首先分析状态机的三要素:
- 输入:时钟(clk),串行数据输入(in),同步复位信号(reset)
- 输出:传输正确信号(done)、正确数据输出([7:0] out_byte)
- 状态:
- 默认状态(IDLE,若未接收到开始标志位则保持这个状态)
- 起始状态(S,接收到开始标志位in = 1’b0后进入这个状态)
- 接收到数据位状态(bit1~bit8,在起始状态之后,根据接下来的8个in输入依次进入)
- 接收到校验位状态(PAR,完成接收8个数据状态之后进入)
- 结束状态(E,表示接收正常结束,即在校验位之后是高电平)
- 错误状态(ERR,在校验位之后未收到in = 1’b0进入)
此外由于需要检查输入的9-bits是否通过奇校验(即要求数据位+校验位中1的个数为奇数),应当借助系统给出的校验模块parity
进行判断。
module parity (
input clk,
input reset,
input in,
output reg odd);
always @(posedge clk)
if (reset) odd <= 0;
else if (in) odd <= ~odd;
endmodule
校验模块parity
的逻辑是这样的:模块复位后输出为odd = 1’b0,当接收到输入in = 1’b1时,对输出信号进行翻转,例如:串行输入数据中有3个1’b1,该模块输出翻转3次,最终输出为1’b1,表示数据中有奇数个1,通过奇校验;若串行输入数据中有2个1’b1输入,该模块输出翻转2次,最终输出为1’b0,表示数据串中有偶数个1,不通过奇校验。
这里就引申出两个问题:
- 如何保证参与奇校验的位恰好是“8个数据位+1个校验位”而不受其他位影响?
- 如何在恰当的时候输出奇校验结果?
对于第1点,其关键是设置复位标志位,在“进入需要进行校验的位”以及“离开需要校验的位”时对校验模块进行复位,其他情况下则不复位,让校验模块保持启用。(这里由于起始状态标志位固定为0,不会导致校验模块输出翻转,并不对校验产生影响,所以也把它算进去了,这样看来校验模块总共判断了10bit输入)
实际上,校验模块复位只发生在进入起始状态之前的状态(体现在状态图中),因为在只在结束状态(E)时需要校验结果参与done
信号的输出判断(参见下面关于第2点的解释),之后直到下次数据传送开始之前并不关心校验模块输出值。
对于第2点,其关键是将校验模块的输出再延迟一个时钟周期,可将判断结果与结束状态(E)对齐,即在数据串进入结束状态(E)时判断结果也同时出现。
这么做的原因是:校验模块的输出本就odd延后输入in一个时钟周期(模块内采用时序逻辑),此时最终的判断结果是与“接收到校验位状态”对齐的,之后如果输入in = 1’b1则进入“结束状态”。此时若将输出odd再延迟一个时钟周期,则最终输出结果与“结束状态”对齐。
当然,由于在判断done
是否输出1’b1时是状态与校验结果一起判断,即使校验结果正确但是当前状态不为结束状态(E),done
信号仍然输出1’b0。
明确了系统的输入、输出以及状态后,可以画出如下状态图,图中还对应当将复位标志位置1’b1的状态做了标注:
设计中每一位数据比特均设置了状态,这里出于简化作图的目的,用一个“DATA”状态代替,这个状态共接收了8次in输入,在第9次接收in输入后跳出
其中有效数据输出逻辑和之前的题目Fsm ps2 / Fsm ps2data中获取输出其实是一个思路,由于先输入的是LSB,所以每个数据比特状态对应的数据输出寄存器应当先从最低位开始赋值。这里数据比特寄存采用次态的原因是可以将数据寄存位置与输入对齐,这样在done
信号输出为1的时候,有效数据恰好同时输出,其余位置则不关心输出情况。
最后给出本题Verilog HDL:
module top_module(
input clk,
input in,
input reset, // Synchronous reset
output [7:0] out_byte,
output done
); //
// Modify FSM and datapath from Fsm_serialdata
localparam IDLE = 1, S = 2, E = 3,
bit1 = 4, bit2 = 5, bit3 = 6, bit4 = 7,
bit5 = 8, bit6 = 9, bit7 = 10, bit8 = 11,
bit9 = 12, ERR = 13, parity =14;
reg [3:0] state;
reg [3:0] next_state;
wire odd, parity_reset;// 用来输出校验结果以及给校验模块复位
reg check_1; // 将odd输出再延后一个时钟周期
always @(posedge clk)
if(reset)
state <= IDLE;
else
state <= next_state;
always @(*) begin
parity_reset = 1'b0;
case(state)
IDLE : begin
next_state = in ? IDLE : S;
parity_reset = 1'b1;
end
S : next_state = bit1;
bit1 : next_state = bit2;
bit2 : next_state = bit3;
bit3 : next_state = bit4;
bit4 : next_state = bit5;
bit5 : next_state = bit6;
bit6 : next_state = bit7;
bit7 : next_state = bit8;
bit8 : next_state = bit9;
bit9 : next_state = in ? E : ERR;
E : begin
next_state = in ? IDLE : S;
parity_reset = 1'b1;
end
ERR : next_state = in ? IDLE : ERR;
default : next_state = IDLE;
endcase
end
// New: Datapath to latch input bits.
reg [8:1] regbyte;
always @(posedge clk)
if(reset)
regbyte <= 8'd0;
else
case(next_state)
bit1 : regbyte[1] <= in;
bit2 : regbyte[2] <= in;
bit3 : regbyte[3] <= in;
bit4 : regbyte[4] <= in;
bit5 : regbyte[5] <= in;
bit6 : regbyte[6] <= in;
bit7 : regbyte[7] <= in;
bit8 : regbyte[8] <= in;
default : regbyte <= regbyte;
endcase
// New: Add parity checking.
parity parity_inst(.clk(clk), .reset(reset | parity_reset), .in(in), .odd(odd));
always @(posedge clk)
if(reset)
check_1 <= 1'b0;
else
check_1 <= odd;
assign done = (state == E) & check_1;
assign out_byte = regbyte;
endmodule
其实只用语言描述会让整个状态机的逻辑看起来不是很清晰……大家可以结合自己手绘时序图对题目逻辑与代码进行分析,理解起来并不困难。