FPGA通常工作在MHz,而串口波特率远远低于该频率(最高标准速率115200),所以我们需要想办法生成一个滴答时钟来尽可能的接近串口波特率。这里我们用串口链路的最高速度来举例说明:
先来啰嗦一段
在典型的串口设计中,RS-232芯片通常使用1.8432MHz的时钟,因为这样比较容易生成串口的标准波特。1.8432MHz除以16得到115200Hz。当然,也有直接使整个系统工作在11.0592MHz的时钟方案。
// 假设FPGA时钟信号是1.8432MHz
// 设计一个4-bit计数器
reg [3:0] BaudDivCnt;
always @(posedge clk)
BaudDivCnt <= BaudDivCnt + 1; // count forever from 0 to 15
// 滴答时钟(每16个时钟滴答一次,即115200次每秒)
wire BaudTick = (BaudDivCnt==15);
是不是很简单?
但是如果你的时钟不是1.8432MHz,比如是2MHz,怎么办?为了从2MHz时钟得到115200Hz, 我们需要除以17.361111111...,然而这并不是一个整数,FPGA也表示不出来。解决的办法是:有时候我们用17来除,有时候用18来除,确保最后的比例是17.361111111即可,这样实现起来就容易多了:
先来看C代码:
while(1) // repeat forever
{
acc += 115200;
if(acc>=2000000)
printf("*");
else
printf(" ");
acc %= 2000000;
}
代码的执行结果是以一个特定的比率打印"*",这个比率就是17.361111111...。
为了在FPGA上得到相同的效果,有一个很重要的前提,那就是:我们的串口是可以接受波特率在一定范围内产生偏差的,即115200Hz ± x%。
如果我们的时钟频率刚好是2的幂次方就最好了,但是显然2MHz不是。为了在FPGA上好做一点,我们需要用一个近似值来代替2000000/115200,这里以1024/59 = 17.356为例。 这已经很接近我们想要的17.361111...了。
在FPGA上可以这样实现:设定一个10-bit的累加器,累加步长为59,把它的溢出信号作为滴答信号即可:
// 假设FPGA工作在2.0000MHz
// 一个11-bit累加器,最高位用作累加器的溢出信号
reg [10:0] acc; // 11 bits
// 每次自加59
always @(posedge clk)
acc <= acc[9:0] + 59;
wire BaudTick = acc[10]; // 溢出信号作为滴答信号
在2MHz时钟频率下,每秒滴答115234次,相比于理想波特率115200,其误差为0.03%,这是可以接受的。
前面的例子我们用到了10 bit的累加器,但是随着时钟频率的增加,显然我们需要使用更多的比特。
我们再以25MHz时钟为例,设置一个16 bit累加器,并且将其参数化
parameter ClkFreq = 25000000; // 25MHz
parameter Baud = 115200;
parameter BaudGenAccWidth = 16;
parameter BaudGenInc = (Baud<<BaudGenAccWidth)/ClkFreq;
reg [BaudGenAccWidth:0] BaudGenAcc;
always @(posedge clk)
BaudGenAcc <= BaudGenAcc[BaudGenAccWidth-1:0] + BaudGenInc;
wire BaudTick = BaudGenAcc[BaudGenAccWidth];
如果你按照上面的方法去做了,细心的同学会发现这里是有问题的。因为verilog使用了32bit的中间结果,而我们在计算BaudGenInc显然溢出了。解决办法如下:
parameter BaudGenInc = ((Baud<<(BaudGenAccWidth-4)) + (ClkFreq>>5)) / (ClkFreq>>4);