I2C的Verilog实现有几个需要注意的地方:
- SDA是双向口,在Verilog中应声明为inout sda;这里就涉及到了inout口的使用方式
assign sda = (link_write)? sh8out_buf[7] : 1'bz;
一般都是使用三态门控制双向口,需要先声明一个link_write寄存器,用于判断sda是输入还是输出,若link_write为高则sda为输出,输出sh8out_buf[7],否则sda为z,即是高阻态,作为输入口,接受外来数据。 - 根据I2C协议的规定,sda数据线若不是在表示开始或停止位时,就只能在scl的低电平发生数据变化(详情看I2C详解 )。这里有一个非常巧妙的办法。
用CLK的下降沿来触发SCL时钟,用CLK的上升沿来触发SDA,这样SDA与SCL就相差了半个 SCL 周期,这样就满足了I2C总线上的传输协议。always @(negedge CLK) if(!RESET) SCL <= 0; else SCL <= ~SCL;
- Task语句块的使用,任务就是一段封装在“task-endtask”之间的程序。任务是通过调用来执行的,而且只有在调用时才执行,如果定义了任务,但是在整个过程中都没有调用它,那么这个任务是不会执行的。调用某个任务时可能需要它处理某些数据并返回操作结果,所以任务应当有接收数据的输入端和返回数据的输出端。另外,任务可以彼此调用,而任务内还可以调用函数。
task task_id; [declaration] procedural_statement endtask
(1)在第一行“task”语句中不能列出端口名称;
(2)任务的输入、输出端口和双向端口数量不受限制,甚至可以没有输入、输出以及双向端口。
(3)在任务定义的描述语句中,可以使用出现不可综合操作符合语句(使用最为频繁的就是延迟控制语句) ,但这样会造成该任务不可综合。
(4)在任务中可以调用其他的任务或函数,也可以调用自身。
(5)在任务定义结构内不能出现 initial和 always过程块。
(6)在任务定义中可以出现“disable 中止语句” ,将中断正在执行的任务,但其是不可综合的。当任务被中断后,程序流程将返回到调用任务的地方继续向下执行。
如例所示:
task shift8_out;
begin
casex(sh8out_state)
sh8out_bit7: //将 sh8out_buf[7]给 sda
if(!SCL) //scl 为低电平时给 sda 赋值,这是 i2c 总线时序要求的
begin
link_sda <= YES;
link_write <= YES;
sh8out_state <= sh8out_bit6;
end
else
sh8out_state <= sh8out_bit7;
sh8out_bit6: //将 sh8out_buf[6]给 sda
if(!SCL)
begin
link_sda <= YES;
link_write <= YES;
sh8out_state <= sh8out_bit5;
sh8out_buf <= sh8out_buf<<1;
end
else
sh8out_state <= sh8out_bit6;
sh8out_bit5: //将 sh8out_buf[5]给 sda
if(!SCL)
begin
sh8out_state <= sh8out_bit4;
sh8out_buf <= sh8out_buf<<1;
end
else
sh8out_state <= sh8out_bit5;
sh8out_bit4: //将 sh8out_buf[4]给 sda
if(!SCL)
begin
sh8out_state <= sh8out_bit3;
sh8out_buf <= sh8out_buf<<1;
end
else
sh8out_state < = sh8out_bit4;
sh8out_bit3: //将 sh8out_buf[3]给 sda
if(!SCL)
begin
sh8out_state <= sh8out_bit2;
sh8out_buf <= sh8out_buf<<1;
end
else
sh8out_state <= sh8out_bit3;
sh8out_bit2: //将 sh8out_buf[2]给 sda
if(!SCL)
begin
sh8out_state <= sh8out_bit1;
sh8out_buf <= sh8out_buf<<1;
end
else
sh8out_state <= sh8out_bit2;
sh8out_bit1: //将 sh8out_buf[1]给 sda
if(!SCL)
begin
sh8out_state <= sh8out_bit0;
sh8out_buf <= sh8out_buf<<1;
end
else
sh8out_state <= sh8out_bit1;
sh8out_bit0: //将 sh8out_buf[0]给 sda
if(!SCL)
begin
sh8out_state <= sh8out_end;
sh8out_buf <= sh8out_buf<<1;
end
else
sh8out_state <= sh8out_bit0;
sh8out_end: //将 sda 上的数据保持一个 scl 周期,保持数据的完整性
if(!SCL) //到下一次 scl 为低电平时,正好为一个 scl 周期
begin
link_sda <= NO;
link_write <= NO;
FF <= 1;
end
else
sh8out_state <= sh8out_end;
endcase
end
endtask