为了适应不同的速率,如100K,400K,1M,3.4M的收发速度,同时又能适应不同的系统时钟频率,因此需要采用配置寄存器以及参数化设计相配合,做到适用性广,方便集成到CPU,DSP,HDMI 等智能器件及接口中。该部分内容与UART的波特率发生器的设计非常类似,可以参照该部分内容进行设计与理解,后面附有I2C时钟代码。
1.I2C 的时钟频率分频器设计
I2C有四种速度匹配,分别为100K,400K,1M,3.4M四种,由于是同步时钟,从机的位捕获直接使用主机时钟,因此这一点与UART等类型的异步通信有较大区别。首先I2C的从设备速度等级表示从机的在速度方面的解码能力,如速度为100K,是指0-100K的速度范围内都可以运行(这一点与SMbus不同)。其次从机直接使用主机时钟,因此从机对时钟的精确度要求不高,如主机设置100K,实际运行时由于时钟晶振误差,分频系数不准确(非整数倍分频)等带来的误差影响不大。
- 设计要求
- 参数化设计,假设时钟为可以提供133MHz,100MHz,50MHz,25MHz四种,速度满足3.4M,1M,400K, 100K。
- 可编程,虽然参数化设计解决了大部分问题,但依然无法满足众多IC的匹配的需求,如某MCU的主时钟66MHz,或80MHz等,这些都不在上面提供的参数化范围之内。因此提供一套可编程接口接波特率寄存器,可以由应用程序计算分频系数并写入分频寄存器中。还有参数化的设计一旦设计综合成硬件之后便不能修改,可编程接口提供好了在线修改的可能。
- 输出为SCL(I2C时钟)两倍频的系统主时钟脉冲信号。如图2,用于在其它模块中生成SCL时钟。

图1
- 设计代码:
`timescale 1ns / 1ps
module SCl_en
#(
parameter CLK_F = 100_000_000 , //默认的输入频率值,可以由参数传递修改
parameter SPEED = 100_000 //默认的通信速率
)
(
input sys_clk,
input [ 31: 0 ] div_addr,
input [ 31: 0 ] div_in,
input div_en,
output reg clk_en,
output reg clk_en_half,
input rst
);
localparam DIV_COE = CLK_F / ( SPEED * 2 ) - 1; //计算分频系数
reg [ 31: 0 ] div_f_r;
reg [ 31: 0 ] count_r;
reg rst_r; //可编程写时,复位分频计数程序
//下面程序实现在特定地址选通的情况下,由wr_en将输入分频值div_in写入到分频寄存器div_f_r中,并提供复位信号。
always@( posedge sys_clk or posedge rst )
if ( rst )
begin
div_f_r <= DIV_COE;
rst_r <= 1'b1;
end
else
begin
rst_r <= 1'b0;
if ( div_en && ( div_addr == 32'hFFFF_0001 ) )
begin
div_f_r <= div_in;
rst_r <= 1'b1;
end
end
//分频计数程序,注意每次分频寄存器div_f_r的值修改时,应复位计数器,即从0开始计数
always@( posedge sys_clk or posedge rst_r )
if ( rst_r )
begin
count_r <= 0;
clk_en <= 0;
clk_en_half <= 0;
end
else
begin
clk_en <= 1'b0;
clk_en_half <= 0;
if ( count_r == div_f_r[31:1] )
clk_en_half <= 1;
if ( count_r == div_f_r )
begin
count_r <= 0;
clk_en <= 1'b1;
end
else
count_r <= count_r + 1;
end
endmodule
上面的程序分为两个部分,第一部分为写分频寄存器,在复位状态下由参数传递提供默认值(DIV_COE),在没有写信号的情况下div_f_r将会一直保持该值。在地址选通,写使能wr_en的驱动下将由应用程序提供的div_in的值写入到分频寄存器div_f_r中。并为后面的分频计数程序产生复位信号rst_r。rst_r的作用防止计数器count_r在计数的某个值后,突然写入一个比count_r小的值,使得计数32位计数器不能及时回归(当然也可以用if(count_r>=div_f_r)替代if(count_r==div_f_r)实现)。
always@( posedge sys_clk or posedge rst )
if ( rst )
begin
div_f_r <= DIV_COE;
rst_r <= 1'b1;
end
else
begin
rst_r <= 1'b0;
if ( wr_en && ( div_addr == 32'hFFFF_0001 ) )
begin
div_f_r <= div_in;
rst_r <= 1'b1;
end
end
第二部分程序提供分频的作用,在分频寄存器的值被修改时,分频程序复位,计数器从0开始计数。clk_en将作为时钟使能使用,每个SCL周期对应两个clk_en时钟脉冲。clk_en_half 作为辅助时钟脉冲, 主要是处理I2C START,STOP, 以及主控制发送数据使能等。 每个SCL对应4个clk_en_half 时钟脉冲。
//分频计数程序,注意每次分频寄存器div_f_r的值修改时,应复位计数器,即从0开始计数
always@( posedge sys_clk or posedge rst_r )
if ( rst_r )
begin
count_r <= 0;
clk_en <= 0;
clk_en_half <= 0;
end
else
begin
clk_en <= 1'b0;
clk_en_half <= 0;
if ( count_r == div_f_r[31:1] )
clk_en_half <= 1;
if ( count_r == div_f_r )
begin
count_r <= 0;
clk_en <= 1'b1;
end
else
count_r <= count_r + 1;
end
由于分频程序是整体I2C程序实现的一个部分,因此由I2C_engine.v程序实现组装,后续课程将会详细介绍I2C_engine的原理。
module I2C_engine
#
(
parameter CLK_F = 100000000 ,
parameter SPEED = 100000
)
(
input rst,
input inclk,
input wr_en,
input [ 31: 0 ] div_addr,
input [ 31: 0 ] div_in,
output clk_en
);
SCl_en
#(
.CLK_F ( CLK_F ) ,
.SPEED ( SPEED )
)
SCl_en_inst
(
.rst ( rst ),
.sys_clk ( inclk ),
.wr_en ( wr_en ),
.div_addr ( div_addr ),
.div_in ( div_in ),
.clk_en ( clk_en ),
.clk_en_half(clk_en_half)
);
endmodule
2. 测试程序:
本测试程序为了体现测试的通用性,尽量模拟应用程序在时钟频率,通信速率等方面的需求进行多样化测试,在tb_clk_en.v中设置了4种频率,4种速率。通过延时提供了各种频率和速率组合进行测试,同时应注意时钟切换时的异步时钟域的处理。在MCU,DSP等嵌入式处理中,该部分程序可由软件实现。
`timescale 1 ns / 1ps
module tb_clk_en ( );
parameter PERIOD = 10 ; //基本周期
//声明4种频率参数,用于计算分频系数
parameter FREQUANCY0 = 133000000; //133M
parameter FREQUANCY1 = 100000000; //100M
parameter FREQUANCY2 = 50000000; //50M
parameter FREQUANCY3 = 25000000; //25M
//声明4种速率用于计算分频系数
parameter SPEED0 = 100000; // 100K
parameter SPEED1 = 400000; // 400K
parameter SPEED2 = 1000000; // 1M
parameter SPEED3 = 3400000; // 3.4M
reg inclk0, inclk1, inclk2, inclk3;
//产生4个不同频率的时钟
always #( PERIOD / 3 ) inclk0 = ~inclk0; //133M
always #( PERIOD / 2 ) inclk1 = ~inclk1; //100M
always #( PERIOD ) inclk2 = ~inclk2; //50M
always #( PERIOD * 2 ) inclk3 = ~inclk3; //25M
reg rst; //
wire clk_en;
reg wr_en; //write enable signal ,synchronized with sys_clk;
reg [ 31: 0 ] div_in; //for baudrate register
wire [ 31: 0 ] div_addr = 32'hFFFF_0001; //分频寄存器对应的地址
reg [ 19: 0 ] delay;
//延时后的四种状态,用于不同频率的时钟选择。
reg [ 1: 0 ] dly_done, dly_done_r, dly_done_rr;
reg [ 3: 0 ] st;
//频率选择
wire sys_clk = ( dly_done == 0 ? inclk0 :
( dly_done == 1 ? inclk1 :
( dly_done == 2 ? inclk2 : inclk3 ) ) );
initial
begin
rst = 1'b1;
inclk0 = 0;
inclk1 = 0;
inclk2 = 0;
inclk3 = 0;
#50 rst = 1'b0;
end
always@( posedge inclk0 or posedge rst )
if ( rst )
begin
delay <= 0;
dly_done <= 0;
end
else
begin
if ( delay == 299999 )
begin
dly_done <= dly_done + 1'b1;
delay <= 0;
end
else
begin
delay <= delay + 1;
end
end
//实现异步时钟处理,消除亚稳态
always@( posedge sys_clk or posedge rst )
if ( rst )
begin
dly_done_r <= 0;
dly_done_rr <= 0;
end
else
begin
dly_done_r <= dly_done;
dly_done_rr <= dly_done_r;
end
//实现不同频率,速率的组合写入到分频寄存器中
always@( posedge sys_clk or posedge rst )
if ( rst )
begin
st <= 0;
div_in <= 0;
wr_en <= 1'b0;
end
else
begin
case ( st )
0:
begin
div_in <= ( FREQUANCY0 / SPEED0 * 2 ) - 1;
wr_en <= 1'b0;
if ( dly_done_rr == 0 )
begin
wr_en <= 1'b1;
st <= 1;
end
end
1:
begin
div_in <= ( FREQUANCY1 / SPEED0 * 2 ) - 1;
wr_en <= 1'b0;
if ( dly_done_rr == 1 )
begin
wr_en <= 1'b1;
st <= 2;
end
end
2:
begin
div_in <= ( FREQUANCY2 / SPEED0 * 2 ) - 1;
wr_en <= 1'b0;
if ( dly_done_rr == 2 )
begin
wr_en <= 1'b1;
st <= 3;
end
end
3:
begin
div_in <= ( FREQUANCY3 / SPEED0 * 2 ) - 1;
wr_en <= 1'b0;
if ( dly_done_rr == 3 )
begin
wr_en <= 1'b1;
st <= 4;
end
end
4:
begin
div_in <= ( FREQUANCY1 / SPEED0 * 2 ) - 1;
wr_en <= 1'b0;
if ( dly_done_rr == 0 )
begin
wr_en <= 1'b1;
st <= 5;
end
end
5:
begin
div_in <= ( FREQUANCY1 / SPEED1 * 2 ) - 1;
wr_en <= 1'b0;
if ( dly_done_rr == 1 )
begin
wr_en <= 1'b1;
st <= 6;
end
end
6:
begin
div_in <= ( FREQUANCY1 / SPEED2 * 2 ) - 1;
wr_en <= 1'b0;
if ( dly_done_rr == 2 )
begin
wr_en <= 1'b1;
st <= 7;
end
end
7:
begin
div_in <= ( FREQUANCY1 / SPEED3 * 2 ) - 1;
wr_en <= 1'b0;
if ( dly_done_rr == 3 )
begin
wr_en <= 1'b1;
st <= 8;
end
end
8:
begin
div_in <= ( FREQUANCY2 / SPEED0 * 2 ) - 1;
wr_en <= 1'b0;
if ( dly_done_rr == 0 )
begin
wr_en <= 1'b1;
st <= 9;
end
end
9:
begin
div_in <= ( FREQUANCY2 / SPEED1 * 2 ) - 1;
wr_en <= 0;
if ( dly_done_rr == 1 )
begin
wr_en <= 1'b1;
st <= 10;
end
end
10:
begin
div_in <= ( FREQUANCY2 / SPEED2 * 2 ) - 1;
wr_en <= 1'b0;
if ( dly_done_rr == 2 )
begin
wr_en <= 1'b1;
st <= 11;
end
end
11:
begin
div_in <= ( FREQUANCY2 / SPEED3 * 2 ) - 1;
wr_en <= 1'b0;
if ( dly_done_rr == 3 )
begin
wr_en <= 1'b1;
st <= 12;
end
end
12:
begin
div_in <= ( FREQUANCY3 / SPEED0 * 2 ) - 1;
wr_en <= 1'b0;
if ( dly_done_rr == 0 )
begin
wr_en <= 1'b1;
st <= 13;
end
end
13:
begin
div_in <= ( FREQUANCY3 / SPEED1 * 2 ) - 1;
wr_en <= 1'b0;
if ( dly_done_rr == 1 )
begin
wr_en <= 1'b1;
st <= 14;
end
end
14:
begin
div_in <= ( FREQUANCY3 / SPEED2 * 2 ) - 1;
wr_en <= 1'b0;
if ( dly_done_rr == 2 )
begin
wr_en <= 1'b1;
st <= 15;
end
end
15:
begin
div_in <= ( FREQUANCY3 / SPEED3 * 2 ) - 1;
wr_en <= 1'b0;
if ( dly_done_rr == 3 )
begin
wr_en <= 1'b1;
st <= 0;
end
end
default:
st <= 0;
endcase
end
//I2C_engine.v 例化
I2C_engine
#(
.CLK_F( FREQUANCY3 ) , //default with 100Mhz
.SPEED( SPEED3 ) //default speed 100k
)
I2C_engine_inst
(
.rst ( rst ),
.inclk ( sys_clk ),
.wr_en ( wr_en ),
.div_addr ( div_addr ),
.div_in ( div_in ),
.clk_en ( clk_en ),
.clk_en_half(clk_en_half)
);
endmodule
仿真波形如下:

图2
从图2中可以看出,写入不同的分频系数值,clk_en的输出的频率随之变化,也可以追踪到SCL_en.v内部观察rst_r,计数,分频系数的写入等,由于上面已做了较详细的分析,这里不再累述。
老师,如果clk_en需要在SCL半个时钟的时候输出一个脉冲,1.设计代码 中的 DIV_COE = (CLK_F/SPEED) * 2 -1 括号的位置我算出来的结果需要不对,需要改为下面这个:DIV_COE = CLK_F / (SPEED * 2) -1,我理解的对么?
谢谢你的回复,已经改正!