虚拟机:VMware -14.0.0.24051
环 境:ubuntu 18.04.1
脚 本:makefile(点击直达)
应用工具:vcs 和 verdi
文章目录
一、Overview
(1)Theory
虽然使用了SCL、SDA和ACK,但这真的不是IIC协议呀,这个模块功能是实现并行数据流转换为一种特殊串行数据流模块的设计。
- 模块框图:
- 通信协议:
- 模块时序:
(2)Demand
- 设计两个可综合的电路模块:
第一个模块(M1)能把4位的并行数据转换为符合以下协议的串行数据流,数据流用scl和sda两条线传输,sclk为输入的时钟信号,data[3:0]为输入数据,ack为数据输入的使能信号。
第二个模块(M1)能把串行数据流内的信息接收到,并转换为相应16条信号线的高电平,即若数据为1,则第一条线路为高电平,数据为n,则第N条线路为高电平。
M0为测试用信号模块。该模块接收M1发出的ack信号,产生新的测试数据data[3::0]。 - 通信协议:
scl为不断输出的时钟信号,如果scl为高电平时,sda由高变低时刻,串行数据流开始;如果scl为高电平时,sda由低变高,申行数据结束。sda信号的串行数据位必须在scl为低电平时变化,若变为高则为1,否则为零。
二、Interface Description
Signal Name | Width | Direction | Description |
---|---|---|---|
sclk | 1 | input | System clk signal, xxMhz |
rst_n | 1 | input | System reset signal |
data_i | 4 | input | Sent data bus |
ack | 1 | output | Request send signal |
scl | 1 | output | Two divided-frequency signal |
sda | 1 | output | Data signal |
outhigh | 16 | output | Out signal |
三、Timeing
(1)M1-Timing
(2)M2-Timing
四、Design and Functional Verification
(1)M1-RTL
//-- modified by xlinxdu, 2022/05/06
module ptosda (
input sclk_i ,
input rst_n_i,
input [3:0] data_i ,
output reg ack_o ,
output reg scl_o ,
output reg sda_o
);
parameter IDLE = 7'b000_0001;
parameter START = 7'b000_0010;
parameter BIT1 = 7'b000_0100;
parameter BIT2 = 7'b000_1000;
parameter BIT3 = 7'b001_0000;
parameter BIT4 = 7'b010_0000;
parameter STOP = 7'b100_0000;
reg sda_en;
reg [3:0] data_buf;
reg [6:0] cur_state;
reg [6:0] nxt_state;
/*-----------------------------------------------\
------------ set flag --> sda_en ---------------
\-----------------------------------------------*/
always @ (posedge sclk_i or negedge rst_n_i) begin
if (!rst_n_i) begin
sda_en <= 1'b0;
end
else if (ack_o) begin
sda_en <= 1'b1;
end
else if((cur_state == STOP) && (ack_o == 1'b0)) begin
sda_en <= 1'b0;
end
end
/*-----------------------------------------------\
---------------- updata data_buf -------------
\-----------------------------------------------*/
always @ (posedge sclk_i or negedge rst_n_i) begin
if (!rst_n_i) begin
data_buf <= 4'b0;
end
else if (ack_o) begin
data_buf <= data_i;
end
end
/*-----------------------------------------------\
----------------- sclk --> scl -----------------
\-----------------------------------------------*/
always @ (posedge sclk_i or negedge rst_n_i) begin
if (!rst_n_i) begin
scl_o <= 1'b0;
end
else begin
scl_o <= ~scl_o;
end
end
/*-----------------------------------------------\
-------------------- ack signal ----------------
\-----------------------------------------------*/
always @ (posedge sclk_i or negedge rst_n_i) begin
if (!rst_n_i) begin
ack_o <= 1'b0;
end
else if ((cur_state == IDLE) && (sda_en == 1'b0)) begin
ack_o <= 1'b1;
end
else begin
ack_o <= 1'b0;
end
end
/*-----------------------------------------------\
--------- updata current state of FSM ----------
\-----------------------------------------------*/
always @ (posedge sclk_i or negedge rst_n_i) begin
if (!rst_n_i) begin
cur_state <= IDLE;
end
else begin
cur_state <= nxt_state;
end
end
always @ (*) begin
case(cur_state)
IDLE: if(sda_en && (scl_o == 1'b1))begin
sda_o = 1'b0;
nxt_state = START;
end
else begin
sda_o = 1'b1;
nxt_state = IDLE;
end
START:if(sda_en && (scl_o == 1'b0))begin
sda_o = data_buf[0];
nxt_state = BIT1;
end
else begin
sda_o = 1'b0;
nxt_state = START;
end
BIT1: if(sda_en && (scl_o == 1'b0))begin
sda_o = data_buf[1];
nxt_state = BIT2;
end
else begin
sda_o = data_buf[0];
nxt_state = BIT1;
end
BIT2: if(sda_en && (scl_o == 1'b0))begin
sda_o = data_buf[2];
nxt_state = BIT3;
end
else begin
sda_o = data_buf[1];
nxt_state = BIT2;
end
BIT3: if(sda_en && (scl_o == 1'b0))begin
sda_o = data_buf[3];
nxt_state = BIT4;
end
else begin
sda_o = data_buf[2];
nxt_state = BIT3;
end
BIT4: if(sda_en && (scl_o == 1'b0))begin
sda_o = 1'b0;
nxt_state = STOP;
end
else begin
sda_o = data_buf[3];
nxt_state = BIT4;
end
STOP: if(!sda_en && (scl_o == 1'b1))begin
sda_o = 1'b1;
nxt_state = IDLE;
end
else begin
sda_o = 1'b0;
nxt_state = STOP;
end
default:begin
sda_o = 1'b1;
nxt_state = IDLE;
end
endcase
end
endmodule
(2)M1-Test Bench
module tb_ptosda;
reg sclk_i ;
reg rst_n_i;
reg [3:0] data_i ;
wire ack_o ;
wire scl_o ;
wire sda_o ;
initial begin
sclk_i = 0 ;
rst_n_i = 1;
data_i = 4'b0;
#10 rst_n_i = 0;
#10 rst_n_i = 1;
data_i = 4'b1011;
end
always begin
#5 sclk_i = ~sclk_i;
end
ptosda tb_ptosda(
.sclk_i (sclk_i ),
.rst_n_i(rst_n_i),
.data_i (data_i ),
.ack_o (ack_o ),
.scl_o (scl_o ),
.sda_o (sda_o )
);
initial begin
#1000 $finish;
$fsdbDumpfile("ptosda.fsdb");
$fsdbDumpvars ;
$fsdbDumpMDA ;
end
endmodule
(3)M2-RTL
//-- modified by xlinxdu, 2022/05/06
module out16hi
(
input clk_i ,
input rst_n_i ,
input data_i ,
output reg [15:0] outhigh_o
);
reg [1:0] dly_data;
wire negedge_data_i;
reg [3:0] data_buf;
reg cnt_en;
reg [2:0] cnt;
always @ (posedge clk_i or negedge rst_n_i) begin
if (!rst_n_i) begin
dly_data <= 2'b1;
end
else begin
dly_data <= {dly_data[0],data_i};
end
end
assign negedge_data_i = (dly_data == 2'b10)?1'b1:1'b0;
always @ (posedge clk_i or negedge rst_n_i) begin
if (!rst_n_i) begin
cnt_en <= 1'b0;
end
else if (negedge_data_i) begin
cnt_en <= 1'b1;
end
else if(cnt == 3'd4)begin
cnt_en <= 1'b0;
end
end
always @ (posedge clk_i or negedge rst_n_i) begin
if (!rst_n_i) begin
cnt <= 3'b0;
end
else if (cnt == 3'd4) begin
cnt <= 3'b0;
end
else if (cnt_en) begin
cnt <= cnt + 1'b1;
end
end
always @ (posedge clk_i or negedge rst_n_i) begin
if (!rst_n_i) begin
data_buf <= 4'b0;
end
else if(cnt == 3'd4) begin
data_buf <= 4'b0;
end
else if (cnt_en) begin
data_buf <= {data_buf[2:0],data_i};
end
end
always @ (*)begin
case(data_buf)
4'b0001: outhigh_o = 16'b0000_0000_0000_0001;
4'b0010: outhigh_o = 16'b0000_0000_0000_0010;
4'b0011: outhigh_o = 16'b0000_0000_0000_0100;
4'b0100: outhigh_o = 16'b0000_0000_0000_1000;
4'b0101: outhigh_o = 16'b0000_0000_0001_0000;
4'b0110: outhigh_o = 16'b0000_0000_0010_0000;
4'b0111: outhigh_o = 16'b0000_0000_0100_0000;
4'b1000: outhigh_o = 16'b0000_0000_1000_0000;
4'b1001: outhigh_o = 16'b0000_0001_0000_0000;
4'b1010: outhigh_o = 16'b0000_0010_0000_0000;
4'b1011: outhigh_o = 16'b0000_0100_0000_0000;
4'b1100: outhigh_o = 16'b0000_1000_0000_0000;
4'b1101: outhigh_o = 16'b0001_0000_0000_0000;
4'b1110: outhigh_o = 16'b0010_0000_0000_0000;
4'b1111: outhigh_o = 16'b0100_0000_0000_0000;
4'b0000: outhigh_o = 16'b1000_0000_0000_0000;
default: begin
outhigh_o = 16'b0000_0000_0000_0000;
end
endcase
end
(4)M2-Test Bench
module tb_ptosda;
reg sclk_i ;
reg rst_n_i;
reg [3:0] data_i ;
wire ack_o ;
wire scl_o ;
wire sda_o ;
wire [15:0]outhigh_o;
initial begin
sclk_i = 0 ;
rst_n_i = 1;
data_i = 4'b0;
#10 rst_n_i = 0;
#10 rst_n_i = 1;
data_i = 4'b1011;
end
always begin
#5 sclk_i = ~sclk_i;
end
ptosda tb_ptosda(
.sclk_i (sclk_i ),
.rst_n_i(rst_n_i),
.data_i (data_i ),
.ack_o (ack_o ),
.scl_o (scl_o ),
.sda_o (sda_o )
);
out16hi tb_out16hi(
.clk_i (scl_o ),
.rst_n_i (rst_n_i ),
.data_i (sda_o ),
.outhigh_o(outhigh_o)
);
initial begin
#1000 $finish;
$fsdbDumpfile("ptosda.fsdb");
$fsdbDumpvars ;
$fsdbDumpMDA ;
end
endmodule
五、Result
(1)M1
bug1:由仿真波形可以看出,请求信号占了时钟两个周期,与实际时序(一个周期)不符,定位到ack部分代码,分析是由sda_en使能信号延迟了一拍导致。
/*-----------------------------------------------\
-------------------- ack signal ----------------
\-----------------------------------------------*/
always @ (posedge sclk_i or negedge rst_n_i) begin
if (!rst_n_i) begin
ack_o <= 1'b0;
end
else if ((cur_state == IDLE) && (sda_en == 1'b0)) begin
ack_o <= 1'b1;
end
else begin
ack_o <= 1'b0;
end
end
继续定位到sda_en代码块,发现sda_en是由ack_o控制拉高的,所以在ack_o在第一次被拉高时,使能信号是无法立刻被拉高的,依据实际功能,这里sda_en应该和请求信号ack同一拍产生。
/*-----------------------------------------------\
------------ set flag --> sda_en ---------------
\-----------------------------------------------*/
always @ (posedge sclk_i or negedge rst_n_i) begin
if (!rst_n_i) begin
sda_en <= 1'b0;
end
else if (ack_o) begin
sda_en <= 1'b1;
end
else if((cur_state == STOP) && (ack_o == 1'b0)) begin
sda_en <= 1'b0;
end
end
更改:把产生高电平的条件改一下
/*-----------------------------------------------\
------------ set flag --> sda_en ---------------
\-----------------------------------------------*/
always @ (posedge sclk_i or negedge rst_n_i) begin
if (!rst_n_i) begin
sda_en <= 1'b0;
end
else if ((cur_state == IDLE) && (ack_o == 1'b0)) begin
sda_en <= 1'b1;
end
else if((cur_state == STOP) && (ack_o == 1'b0)) begin
sda_en <= 1'b0;
end
end
结果:在更改后,请求信号和使能信号均正常
bug2:由通信协议可知道,scl为高电平时,sda由低变高时刻,串行数据结束。由仿真波形可以看到,红框是已经发送完4bit的数据,产生stop信号时刻。在红框内,scl第一次为高电平时,stop信号没有产生,而是在scl的下一拍产生,多了一拍,时序错误。由仿真波形可以看到,产生这个bug的原因是sda_en拉低的时刻晚了一拍。定位到sda_en代码块。
/*-----------------------------------------------\
------------ set flag --> sda_en ---------------
\-----------------------------------------------*/
always @ (posedge sclk_i or negedge rst_n_i) begin
if (!rst_n_i) begin
sda_en <= 1'b0;
end
else if ((cur_state == IDLE) && (ack_o == 1'b0)) begin
sda_en <= 1'b1;
end
else if((cur_state == STOP) && (ack_o == 1'b0)) begin
sda_en <= 1'b0;
end
end
//第二处
BIT4: if(sda_en && (scl_o == 1'b0))begin
sda_o = 1'b0;
nxt_state = STOP;
end
else begin
sda_o = data_buf[3];
nxt_state = BIT4;
end
更改:把sda_en在发送完第四个bit数据时产生,同时把状态机里面的BIT4条件改一下。因为sda_en发生了改变。
/*-----------------------------------------------\
------------ set flag --> sda_en ---------------
\-----------------------------------------------*/
always @ (posedge sclk_i or negedge rst_n_i) begin
if (!rst_n_i) begin
sda_en <= 1'b0;
end
else if ((cur_state == IDLE) && (ack_o == 1'b0)) begin
sda_en <= 1'b1;
end
else if((cur_state == BIT4) && (ack_o == 1'b0)) begin
sda_en <= 1'b0;
end
end
//第二处
BIT4: if(!sda_en && (scl_o == 1'b0))begin
sda_o = 1'b0;
nxt_state = STOP;
end
else begin
sda_o = data_buf[3];
nxt_state = BIT4;
end
结果:更改后,仿真波形与时序一致。
(2)M2
bug1:由仿真波形可以看到,传输的数据结束之后,data_buf,依然在缓冲数据,所以输出一直在变,不符合时序要求,问题定位到data_buf更新代码块。
always @ (posedge clk_i or negedge rst_n_i) begin
if (!rst_n_i) begin
data_buf <= 4'b0;
end
else if (cnt_en) begin
data_buf <= {data_buf[2:0],data_i};
end
else begin
data_buf <= 4'b0;
end
end
更正:在一次数据传输结束之后,data_buf清零,可解决上述bug。
always @ (posedge clk_i or negedge rst_n_i) begin
if (!rst_n_i) begin
data_buf <= 4'b0;
end
else if(cnt == 3'd4) begin
data_buf <= 4'b0;
end
else if (cnt_en) begin
data_buf <= {data_buf[2:0],data_i};
end
end
bug2:在解决bug1后,发现清零会带来一个问题,就是改数据被误认为是M1传输过来的,定位到输出代码块
always @ (*)begin
case(data_buf)
4'b0001: outhigh_o = 16'b0000_0000_0000_0001;
4'b0010: outhigh_o = 16'b0000_0000_0000_0010;
4'b0011: outhigh_o = 16'b0000_0000_0000_0100;
4'b0100: outhigh_o = 16'b0000_0000_0000_1000;
4'b0101: outhigh_o = 16'b0000_0000_0001_0000;
4'b0110: outhigh_o = 16'b0000_0000_0010_0000;
4'b0111: outhigh_o = 16'b0000_0000_0100_0000;
4'b1000: outhigh_o = 16'b0000_0000_1000_0000;
4'b1001: outhigh_o = 16'b0000_0001_0000_0000;
4'b1010: outhigh_o = 16'b0000_0010_0000_0000;
4'b1011: outhigh_o = 16'b0000_0100_0000_0000;
4'b1100: outhigh_o = 16'b0000_1000_0000_0000;
4'b1101: outhigh_o = 16'b0001_0000_0000_0000;
4'b1110: outhigh_o = 16'b0010_0000_0000_0000;
4'b1111: outhigh_o = 16'b0100_0000_0000_0000;
4'b0000: outhigh_o = 16'b1000_0000_0000_0000;
default: begin
outhigh_o = 16'b0000_0000_0000_0000;
end
endcase
end
更正:加入计数控制条件,只有在数据传输过程(cnt=1~4)时,data_buf的0才有效,否则输出16‘b0。
always @ (*)begin
case(data_buf)
4'b0001: if(cnt !== 3'd0) begin outhigh_o = 16'b0000_0000_0000_0001;end
else begin outhigh_o = 16'b0000_0000_0000_0000;end
4'b0010: if(cnt !== 3'd0) begin outhigh_o = 16'b0000_0000_0000_0010;end
else begin outhigh_o = 16'b0000_0000_0000_0000;end
4'b0011: if(cnt !== 3'd0) begin outhigh_o = 16'b0000_0000_0000_0100;end
else begin outhigh_o = 16'b0000_0000_0000_0000;end
4'b0100: if(cnt !== 3'd0) begin outhigh_o = 16'b0000_0000_0000_1000;end
else begin outhigh_o = 16'b0000_0000_0000_0000;end
4'b0101: if(cnt !== 3'd0) begin outhigh_o = 16'b0000_0000_0001_0000;end
else begin outhigh_o = 16'b0000_0000_0000_0000;end
4'b0110: if(cnt !== 3'd0) begin outhigh_o = 16'b0000_0000_0010_0000;end
else begin outhigh_o = 16'b0000_0000_0000_0000;end
4'b0111: if(cnt !== 3'd0) begin outhigh_o = 16'b0000_0000_0100_0000;end
else begin outhigh_o = 16'b0000_0000_0000_0000;end
4'b1000: if(cnt !== 3'd0) begin outhigh_o = 16'b0000_0000_1000_0000;end
else begin outhigh_o = 16'b0000_0000_0000_0000;end
4'b1001: if(cnt !== 3'd0) begin outhigh_o = 16'b0000_0001_0000_0000;end
else begin outhigh_o = 16'b0000_0000_0000_0000;end
4'b1010: if(cnt !== 3'd0) begin outhigh_o = 16'b0000_0010_0000_0000;end
else begin outhigh_o = 16'b0000_0000_0000_0000;end
4'b1011: if(cnt !== 3'd0) begin outhigh_o = 16'b0000_0100_0000_0000;end
else begin outhigh_o = 16'b0000_0000_0000_0000;end
4'b1100: if(cnt !== 3'd0) begin outhigh_o = 16'b0000_1000_0000_0000;end
else begin outhigh_o = 16'b0000_0000_0000_0000;end
4'b1101: if(cnt !== 3'd0) begin outhigh_o = 16'b0001_0000_0000_0000;end
else begin outhigh_o = 16'b0000_0000_0000_0000;end
4'b1110: if(cnt !== 3'd0) begin outhigh_o = 16'b0010_0000_0000_0000;end
else begin outhigh_o = 16'b0000_0000_0000_0000;end
4'b1111: if(cnt !== 3'd0) begin outhigh_o = 16'b0100_0000_0000_0000;end
else begin outhigh_o = 16'b0000_0000_0000_0000;end
4'b0000: if(cnt !== 3'd0) begin outhigh_o = 16'b1000_0000_0000_0000;end
else begin outhigh_o = 16'b0000_0000_0000_0000;end
default: begin
outhigh_o = 16'b0000_0000_0000_0000;
end
endcase
end
(3)Top
在进行M1-M2-M0联调时,波形如上,输出符合预期时序要求,前仿真功能正常。
作者:xlinxdu
版权:本文是作者原创,版权归作者所有。
转载:未经作者允许,禁止转载,转载必须保留此段声明,必须在文章中给出原文连接。