整体过程
- 目的:接收其他CPU(here用.vt 文件给)传过来的并行数据,并转为串行数据输出(之后有接收串行数据并转化为并行数据的例子);
- 流程:.vt 给一个启动脉冲,在.v文件中接收此异步信号并处理为同步信号(点here跳转);对sys_clk分频,产生4个分频脉冲(点here跳转),进而模拟产生SCL控制时钟信号,并控制并行数据一位位输出为串行SDA数据。
程序说明
- 并行数据接收
像单片机、DSP那样,通过好多个输出引脚来输出一个数据的高位到低位,接收端接收到的就是并行数据,即一个data的各位都一起接收到了。
here不管并行数据什么时候来,都每个时钟来把它锁存一下,赋给reg r_cpu_data保存一下。
仿真信号直接在某个时间给 i_cpu_data赋值就行://.v always @(posedge i_sys_clk or negedge i_rst_n) begin if(!i_rst_n) begin r_cpu_data <= 32'd0; end else begin r_cpu_data <= i_cpu_data; end end
#10 i_cpu_data = 32'h55aa55aa;
想法:程序中我是直接判断启动脉冲来了就去状态跳转、转换数据,所以仿真限号得设置i_cpu_data 的在启动脉冲前赋值。 觉得状态的切换可以再设置一点东西,判断一下这是的r_cpu_data 是不是被赋值了(可能数据i_cpu_data 还没来,r_cpu_data 还全是零)。 得看实际情况怎么用,收发应该沟通好了就没问题。
- 判断启动脉冲 (点here跳回去)
- 启动脉冲来到时间提前不知道,相较系统时钟而言是异步信号,一般需要处理成同步信号来使用这个启动脉冲。这里一开始疑惑一个脉冲只是用一位数据的高低电平表示,又不是一串数据,那它怎么会通过取反、与操作来对齐波形呢?其实不需要是一串数据也可以就行取反、与操作等,assign是阻塞赋值,所以等式右边码值一变,等式左边码值就变,所以不是理解为一串数据与一串数据的操作,而是一位与一位的操作。
- 当以上升沿的脉冲做启动脉冲时,沿同步的结果应为:(下降沿的脉冲做启动脉冲不同,另一个取反,具体见串并转换时)
assign startpulse_align = i_startpulse &&(~startpulse_dl);
延迟锁存如下:
波形示意如下:always @(posedge i_sys_clk or negedge i_rst_n) begin if(!i_rst_n) begin startpulse_dl <= 1'd0; end else begin startpulse_dl <= i_startpulse; end end
(其中需要startpulse的持续时间不能太短,否则sys_clk还没检测到;) - 在状态切换程序块中判断startpulse_align是不是为高来判断是否切换到下一状态,这里注意,状态切换程序块也是在sys_clk上升沿进行的判断,而由于非阻塞赋值,这里的startpulse_dl还为低(所有always块完了再一起更新值),所以startpulse_align还为高,所以判断若startpulse_align为高则跳转状态可以正常进行。
- 时钟分频,设置4个phase脉冲 (点here跳回去)
- 通过sys_clk设置了4个分频脉冲,四个脉冲走完相当于SCL一个周期走完(对应SCL的上升沿、高电平中间、下降沿、低电平中间),所以可以认为它们分别对应相位0/1/2/3。在各个脉冲到来时干特定的任务(觉得有点像时间片轮转法),而不是直接去判断SCL、SDA的沿(觉得这里考虑到传输协议,直接判断沿可能不好做,第一次接触到这种用法,不知道适用性怎么样);
-
always @(posedge i_sys_clk or negedge i_rst_n) begin if(!i_rst_n) begin //都初始化为0 end else begin if(phase_cnt == 10'd39) begin//这里判断是不是40个sys_clk过去了 phase_cnt <= 10'b0; end else begin phase_cnt <= phase_cnt + 10'b1; end if(phase_cnt == 10'd39) begin//这里判断phase0是不是可以到了,并在一个sys_clk后置会0; phase0 <= 1'd1; end else begin phase0 <= 1'd0; end //另几个phase同理 end end
- 这里判断条件的值还是注意非阻塞赋值;
ps:程序设置的4个phase共走40个sys_clk,这只是随便取的,差不多保证对应上面提到的SCL、SDA的四个特殊位置就行,怎么方便清楚怎么来。 - pahse0的判断条件不是用的phase_cnt =0,而是phase_cnt =尾,其他三个phase的判断仍按顺序就行。
- 根据四个分频脉冲,产生SCL信号时钟
在phase0~1之间(指phase0来后到phase2来前)设置SCL信号为高电平;phase2-3(指phase2来后到phase0来前)之间为低电平;
ps:同样由于非阻塞赋值,所以后面仿真可以看到波形上SCL是变沿是phase脉冲的尾部;if(phase0 == 1'b1) begin o_scl_clk <= 1'b1; end else if(phase2 == 1'b1) begin o_scl_clk <= 1'b0; end else o_scl_clk <= o_scl_clk;
- 串行传输协议
- 这里用的是I2C:模拟产生SCL控制时钟线、SDA数据线;
SCL为高时,SDA由高到低:数据传输开始;(1)
SCL为低时,SDA由高到低 或 由低到高:传输码值;
两个SCL低电平之间SDA码值不变;(2)
SCL为高时,SDA由低到高:数据传输结束;
即SCL为低时,SDA变的是数据码值;SCL为高时,SDA变是开始或结束传输。如下:
对于接收方来说,则以(1)做为启动脉冲,在(2)时读取稳定数据,这就只需要在SCL的上升沿读数据就行,也就不需要phase信号 - 结合4个分频脉冲,有:
phase1为高 且 SCL为高时:若SDA由高到低,则开始传输数据,跳到TRANS状态;若 SDA由低到高,则结束传输,跳到IDLE;
ps:这里得加一个“且SCL为高”,因为实际程序是先产生的phase1,之后才出现的phase0,所以防止SCL还没变高就开始传输数据了;
phase3为高 (即SCL为低)时:接收数据并存储;
- 状态机切换----结合传输协议
-
IDLE:变量初始化;判断startpulse_align来没,来了去START;
-
START:判断 SCL为高 且 phase1为高 不,是的去TRANS;
-
TRANS:判断phase3为高不,为再判断数据个数够没有(一般收发双方会说好),够了去DELAY,不够继续接收;
r_data_cnt <= r_data_cnt + 10'b1;//非阻塞赋值,so这两行位置欧克 o_sda_data_com <= r_cpu_data[31 - r_data_cnt];
这里并行数据 r_cpu_data 一开始就保存下来了,这里把它一位位的输出成串行数据;
-
DELAY:延迟几个时钟单元(确保上个输出数据输出完整之类的),再把SDA线拉低(因为之后结束传输SDA需要由低到高);再去STOP;
o_sda_data_com <= 1'b0;
-
STOP:判断phase1为高不,为则把SDA拉高,并去IDLE;否则等phase1来;
- 仿真结果