从节点
仿真程序需要模拟从主节点接收数据,并发出应答信号,代码如下:
- `include "timescale.v"
- //模块定义
- module i2c_slave_model (scl, sda);
- // 参数
- // 地址
- parameter I2C_ADR = 7'b001_0000;
- // 输入、输出
- input scl;
- inout sda;
- // 变量申明
- wire debug = 1'b1;
- reg [7:0] mem [3:0]; // 初始化内存
- reg [7:0] mem_adr; // 内存地址
- reg [7:0] mem_do; // 内存数据输出
- reg sta, d_sta;
- reg sto, d_sto;
- reg [7:0] sr; // 8 位移位寄存器
- reg rw; // 读写方向
- wire my_adr; // 地址
- wire i2c_reset; // RESET 信号
- reg [2:0] bit_cnt;
- wire acc_done; // 传输完成
- reg ld;
- reg sda_o;
- wire sda_dly;
- // 状态机的状态定义
- parameter idle = 3'b000;
- parameter slave_ack = 3'b001;
- parameter get_mem_adr = 3'b010;
- parameter gma_ack = 3'b011;
- parameter data = 3'b100;
- parameter data_ack = 3'b101;
- reg [2:0] state;
- // 模块主体
- //初始化
- initial
- begin
- sda_o = 1'b1;
- state = idle;
- end
- // 产生移位寄存器
- always @(posedge scl)
- sr <= #1 {sr[6:0],sda};
- //检测到访问地址与从节点一致
- assign my_adr = (sr[7:1] == I2C_ADR);
- //产生位寄存器
- always @(posedge scl)
- if(ld)
- bit_cnt <= #1 3'b111;
- else
- bit_cnt <= #1 bit_cnt - 3'h1;
- //产生访问结束标志
- assign acc_done = !(|bit_cnt);
- // sda 延迟
- assign #1 sda_dly = sda;
- //检测到开始状态
- always @(negedge sda)
- if(scl)
- begin
- sta <= #1 1'b1;
- if(debug)
- $display("DEBUG i2c_slave; start condition detected at %t", $time);
- end
- else
- sta <= #1 1'b0;
- always @(posedge scl)
- d_sta <= #1 sta;
- // 检测到停止状态信号
- always @(posedge sda)
- if(scl)
- begin
- sto <= #1 1'b1;
- if(debug)
- $display("DEBUG i2c_slave; stop condition detected at %t", $time);
- end
- else
- sto <= #1 1'b0;
- //产生 I2C 的 RESET 信号
- assign i2c_reset = sta || sto;
- // 状态机
- always @(negedge scl or posedge sto)
- if (sto || (sta && !d_sta) )
- begin
- state <= #1 idle; // reset 状态机
- sda_o <= #1 1'b1;
- ld <= #1 1'b1;
- end
- else
- begin
- // 初始化
- sda_o <= #1 1'b1;
- ld <= #1 1'b0;
- case(state)
- idle: // idle 状态
- if (acc_done && my_adr)
- begin
- state <= #1 slave_ack;
- rw <= #1 sr[0];
- sda_o <= #1 1'b0; // 产生应答信号
- #2;
- if(debug && rw)
- $display("DEBUG i2c_slave; command byte received (read) at %t",
- $time);
- if(debug && !rw)
- $display("DEBUG i2c_slave; command byte received (write) at %t",
- $time);
- if(rw)
- begin
- mem_do <= #1 mem[mem_adr];
- if(debug)
- begin
- #2 $display("DEBUG i2c_slave; data block read %x from
- address %x (1)", mem_do, mem_adr);
- #2 $display("DEBUG i2c_slave; memcheck [0]=%x, [1]=%x,
- [2]=%x", mem[4'h0], mem[4'h1], mem[4'h2]);
- end
- end
- end
- slave_ack:
- begin
- if(rw)
- begin
- state <= #1 data;
- sda_o <= #1 mem_do[7];
- end
- else
- state <= #1 get_mem_adr;
- ld <= #1 1'b1;
- end
- get_mem_adr: // 等待内存地址
- if(acc_done)
- begin
- state <= #1 gma_ack;
- mem_adr <= #1 sr; // 保存内存地址
- sda_o <= #1 !(sr <= 15); // 收到合法地址信号后发出应答信号
- if(debug)
- #1 $display("DEBUG i2c_slave; address received. adr=%x, ack=%b",
- sr, sda_o);
- end
- gma_ack:
- begin
- state <= #1 data;
- ld <= #1 1'b1;
- end
- data: // 接收数据
- begin
- if(rw)
- sda_o <= #1 mem_do[7];
- if(acc_done)
- begin
- state <= #1 data_ack;
- mem_adr <= #2 mem_adr + 8'h1;
- sda_o <= #1 (rw && (mem_adr <= 15) );
- if(rw)
- begin
- #3 mem_do <= mem[mem_adr];
- if(debug)
- #5 $display("DEBUG i2c_slave; data block read %x from
- address %x (2)", mem_do, mem_adr);
- end
- if(!rw)
- begin
- mem[ mem_adr[3:0] ] <= #1 sr; // store data in memory
- if(debug)
- #2 $display("DEBUG i2c_slave; data block write %x to
- address %x", sr, mem_adr);
- end
- end
- end
- data_ack:
- begin
- ld <= #1 1'b1;
- if(rw)
- if(sda) //
- begin
- state <= #1 idle;
- sda_o <= #1 1'b1;
- end
- else
- begin
- state <= #1 data;
- sda_o <= #1 mem_do[7];
- end
- else
- begin
- state <= #1 data;
- sda_o <= #1 1'b1;
- end
- end
- endcase
- end
- // 从内存读数据
- always @(posedge scl)
- if(!acc_done && rw)
- mem_do <= #1 {mem_do[6:0], 1'b1};
- // 产生三态
- assign sda = sda_o ? 1'bz : 1'b0;
- // 检查时序
- wire tst_sto = sto;
- wire tst_sta = sta;
- wire tst_scl = scl;
- //指定各个信号的上升沿和下降沿
- specify
- specparam normal_scl_low = 4700,
- normal_scl_high = 4000,
- normal_tsu_sta = 4700,
- normal_tsu_sto = 4000,
- normal_sta_sto = 4700,
- fast_scl_low = 1300,
- fast_scl_high = 600,
- fast_tsu_sta = 1300,
- fast_tsu_sto = 600,
- fast_sta_sto = 1300;
- $width(negedge scl, normal_scl_low);
- $width(posedge scl, normal_scl_high);
- $setup(negedge sda &&& scl, negedge scl, normal_tsu_sta); // 开始状态信号
- $setup(posedge scl, posedge sda &&& scl, normal_tsu_sto); // 停止状态信号
- $setup(posedge tst_sta, posedge tst_scl, normal_sta_sto);
- endspecify
- endmodule