一个有趣的异步时序逻辑电路设计实例 ——MFM调制模块设计笔记
本文从本人的163博客搬迁至此。
MFM是改进型频率调制的缩写,其本质是一种非归零码,是用于磁介质硬盘存储的一种调制方式。调制规则有两句话,即两个翻转条件:
1、为1的码元在每个码元的正中进行一次翻转;为0的码元不翻转。
2、对连续两个为0的码元,则在第一个为0的码元结束时翻转一次;单个的0码元不翻转。
设计过程:
若码元的同步时钟为CLK,不失一般性,假设CLK的上升沿开始产生新的码元,下降沿为该码元的正中。则MFM调制信号有可能在时钟的上升沿,也有可能在时钟的下降沿发生电平翻转。由于一个触发器不会在两个边沿都翻转,也就不可能由单个触发器的输出作为MFM调制的输出。一种合理的思路是分别由一个上升沿和一个下降沿触发器分别在上升沿和下降沿翻转,然后用它们的输出相异或的结果作为总输出。特别值得注意的是,异或具有如下的属性:参与异或的两个数,不论其中一个为0或1,只要另一个发生翻转,异或的结果一定会翻转。下面分别来实现MFM调制规则的两个条件,即在满足条件1时翻转的电路——设其输出为Dout1,和满足条件2时翻转的电路——设其输出为Dout2,再将它们异或为最终的MFM调制结果Dout。
条件1要求在为1的码元的正中进行一次翻转,比较容易实现,使用受控的触发器时钟即可。条件2较难实现,原因在于其对应的触发器需要在第二个码元为0的还没有到来之前,先就对是否存在两个连续为0的码元做出判断,并在第二个为0的码元开始出现的上升沿就先翻转。由于我们的电路不可能“未卜先知”地知道上升沿出现后的码元是否为0,只能让判断条件2的电路延迟(潜伏)一个时钟(CLK)周期后再发生翻转。当然由于条件2翻转的触发器电路,需要延迟一个CLK,条件1翻转的触发器电路也必须随之延迟一个时钟周期,以同步于条件2翻转的电路。图1中的DinD就是延迟一个时钟周期后的输入被调制信号。
图1 MFM调制电路及其时序
满足条件1的电路,应该由两部分构成。第一个部分在上升沿动作,完成延迟一个时钟周期。第二部分则在下降沿动作,当码元为1时在下降沿翻转Dout1,为一个T'触发器。
满足条件2的电路,也由两部分组成。第一个部分是一组下降沿移位寄存器,该移位寄存器有两个D触发器构成,负责保存最近两个下降沿时刻码元的值,当两个触发器同时为0时输出允许Dout2翻转的信号。第二个部分是在第一部分允许的条件下,在上升沿翻转的T'触发器,其输出就是Dout2。
从图中可以看到,本电路是一个典型的异步时序逻辑电路,为了在连续的一个/组下降沿和上升沿之间不间断地动作,满足条件1的电路和满足条件2的电路的前后两个部分所使用的时钟边沿都不相同。使用硬件描述语言时,需要分别使用两个always模块来对应上升沿和下降沿电路。由于上升沿电路和下降沿电路交叉出现在两个always模块中,这段VerilogHDL不太容易直接看懂。
1 module MFM( 2 3 input CLK, //产生被调制信号的时钟,其周期等于被调制信号一个码元的宽度 4 5 input Din, //被调制信号 6 7 input rst, //复位信号 8 9 output DinD, //延迟了一个clk周期的被调制信号 10 11 output Dout1, //为1的码元在码元正中翻转的信号 12 13 output Dout2, //连续两个为0的码元,在两个码元之间翻转的信号 14 15 output MFM_Dout //Dout1和Dout2异或的结果,也就是DOUT1和Dout2翻转时都翻转的信号。 16 17 ); 18 19 reg DinD_reg;//这个寄存器的值将输入延迟了一个时钟周期 20 21 reg Dout1_reg;//Dout1对应的寄存器 22 23 reg Dout2_reg;//Dout2对应的寄存器 24 25 reg[1:0] D_reg_n; //在下降沿缓冲两级输入,以判断是否是连续两个0 26 27 assign DinD = DinD_reg; 28 29 assign Dout1 = Dout1_reg; 30 31 assign Dout2 = Dout2_reg; 32 33 assign MFM_Dout = Dout1^Dout2; 34 35 //异或的属性就是不论第一个自变量为0还是1,只要第二个自变量变化,结果都会跟着变化,因此MFM_Dout可以在DOUT1和Dout2翻转时都翻转 36 37 always @(posedge CLK or posedge rst) 38 39 begin 40 41 if(rst) 42 43 begin 44 45 DinD_reg <= 0; 46 47 Dout2_reg <= 0; 48 49 end 50 51 else begin 52 53 DinD_reg <= Din; 54 55 if(~(D_reg_n[0]|D_reg_n[1])) 56 57 //如果D_reg_n[0]和D_reg_n[1]都为0则翻转Dout2 58 59 Dout2_reg <= ~Dout2_reg; 60 61 end 62 63 end 64 65 66 67 always @(negedge CLK or posedge rst) 68 69 begin 70 71 if(rst) 72 73 begin 74 75 Dout1_reg <= 0; 76 77 D_reg_n[1:0] <= 2'b11; 78 79 end 80 81 else begin 82 83 if (DinD == 1) 84 85 Dout1_reg <= ~Dout1_reg; 86 87 D_reg_n[1:0] <= {D_reg_n[0],Din}; 88 89 //缓冲两个下降沿时的输出,如果都为0,则需要在下一个上升沿翻转Dout2 90 91 end 92 93 end 94 95 endmodule
上述代码在Vivado中综合后,得到下图所示的Schematic。
图2 在Vivado中综合后产生的Schematic
这个实例再次印证了用硬件描述语言开发硬件电路的那个准则:在开始描述之前,脑中应该先有电路的大概模型,否则不可能综合出满足要求的硬件电路。