这里我们使用串行连接的最大速度115200波特,其他较慢的波特也很容易由此产生。
FPGA通常运行在远高于115200Hz的时钟频率上(对于今天的标准的来说RS-232真是太慢了),这就意味着我们需要用一个较高的时钟来分频产生尽量接近于115200Hz的时钟信号。
从1.8432MHz的时钟产生
通常RS-232芯片使用1.8432MHz的时钟,以为这个时钟很容易产生标准的波特率,所以我们假设已经拥有了一个这样的时钟源。
只需要将 1.8432MHz 16分频便可得到 115200Hz的时钟,多方便啊!
reg [3:0] BaudDivCnt;
always @(posedge clk) BaudDivCnt <= BaudDivCnt + 1;
wire BaudTick = (BaudDivCnt==15);
所以 "BaudTick" 每16个时钟周期需要置位一次,从而从1.8432MHz的时钟得到115200Hz的时钟。
早期的发生器假设使用1.8432MHz的时钟。但如果我们使用2MHz的时钟怎么办呢?要从2MHz的时钟得到 115200Hz,需要将时钟 "17.361111111..." 分频,并不是一个整数。我的解决办法是有时候17分频,有时候18分频,使得整体的分频比保持在 "17.361111111"。这是很容易做到的。
下面是实现这个想法的C语言代码:
while(1) // 死循环
{
acc += 115200;
if(acc >=2000000) printf("*"); else printf(" ");
acc %= 2000000;
}
这段代码会精确的以平均每 "17.361111111..." 个时钟间隔打印出一个"*"。
为了从FPGA得到同样的效果,考虑到串行接口可以容忍一定的波特率误差,所以即使我们使用17.3或者17.4这样的分频比也是没有关系的。
我们希望2000000是2的整数幂,但很可惜,它不是。所以我们改变分频比,"2000000/115200" 约等于 "1024/59" = 17.356. 这跟我们要求的分频比很接近,并且使得在FPGA上实现起来相当有效。
//10 位的累加器 ([9:0]), 1位进位输出 ([10])
reg [10:0] acc; //一共11位!
always @(posedge clk)
acc <= acc[9:0] + 59; //我们使用上一次结果的低10位,但是保留11位结果
wire BaudTick = acc[10]; //第11位作为进位输出
使用 2MHz 时钟, "BaudTick" 为 115234 波特, 跟理想的115200波特存在 0.03% 的误差。
前面的设计我们使用的是10位的累加器,如果时钟频率提高的话,需要更多的位数。
下面是一个使用 25MHz 时钟和 16 位累加器的设计,该设计是参数化的,所以很容易根据具体情况修改。
parameter ClkFrequency = 25000000; // 25MHz
parameter Baud = 115200;
parameter BaudGeneratorAccWidth = 16;
parameter BaudGeneratorInc = (Baud< reg [BaudGeneratorAccWidth:0] BaudGeneratorAcc; always @(posedge clk) BaudGeneratorAcc <= BaudGeneratorAcc[BaudGeneratorAccWidth-1:0] + BaudGeneratorInc; wire BaudTick = BaudGeneratorAcc[BaudGeneratorAccWidth]; 上面的设计中存在一个错误: "BaudGeneratorInc"的计算是错误的, 因为 Verilog 使用 32 位的默认结果, 但实际计算过程中的某些数据超过了32位,所以改变一种计算方法。 parameter BaudGeneratorInc = ((Baud<<(BaudGeneratorAccWidth-4))+(ClkFrequency>>5))/(ClkFrequency>>4); 这行程序也使得结果成为整数,从而避免截断。 这就是整个的设计方法了。 现在我们已经得到了足够精确的波特率,可以继续设计串行接收和发送模块了。