为了适应不同的速率,如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
- 设计代码:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
`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)实现)。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
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 时钟脉冲。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
//分频计数程序,注意每次分频寄存器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的原理。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
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等嵌入式处理中,该部分程序可由软件实现。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 |
`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,计数,分频系数的写入等,由于上面已做了较详细的分析,这里不再累述。