https://insoobaik.tistory.com/672
위 I2C 이론을 기반으로 RTL 코드를 설계할 것이다.
- Simulation
- Master Write / Slave Read 동작
(현재 Master와 Slave 신호를 둘 다 보기 때문에 둘 사이에 동일하게 동작하는 신호의 경우 위와 같이 동일한 2개의 신호가 나오는 것 처럼 보일 것이다.)
Master에서 Address 포함 Write 동작에 대한 전체 Simulation은 위와 같다.
1. Start Condition
SCL이 1인 상태에서 SDA가 0으로 떨어지는 구간은 Start Condition을 나타내고 I2C 프로토콜이 동작하기 시작하는 구간에 해당한다.
bitcount, state, pulse의 값이 동작하기 시작한다.
2. Address & (Master) Data write / Ack
State를 통해서도 확인할 수 있지만 위 동작을 보게되면 Start Condition 이후 처음 8bit(첫번째 파란색 선까지) {add(2)(8bit 중 상위 7bit), op(0)(마지막 bit)} => 00000010이 전달된다.
sda_en(Master, Slave가 Data를 전달할 때 사용하는 Flag)가 각자 전달 타이밍에 맞게 변하는 것을 확인할 수 있다.
처음 Addr 8bit를 전달하기 위해서는 Master가 sda를 사용하고, Addr에 대한 ACK 신호를 전달하기 위해서는 Slave가 sda를 점유해야 한다.
또한 Master에서 전달할 Data 값이 1이기 때문에 처음 8bit를 통해 Addr 및 Read, Write를 결정하고 Slave로부터 응답 신호를 전달 받으면 다음 8bit에 Data 1을 전달하게 된다.
3. State, ACK, STOP
State(파란색 네모)를 보게 되면 Start Condition, Read or Write(8bit), ACK, Read or Write(8bit), ACK, Stop 순서로 변하는 것을 확인할 수 있다.
이는 I2C 프로토콜의 통신 방식과 동일하게 흘러가는 것을 확인할 수 있다.
각자 Data를 전송할 때는 sda_en(빨간색) 신호가 High를 Data를 전달 받을 때는 sda_en이 Low 신호를 전달하는 것을 볼 수 있다. 이는 sda라는 Data bus 하나를 가지고 Data를 주고 받기 때문이다.
8bit Addr, ACK1, 8bit Data, ACK2를 마치게 되면 마지막으로 scl이 1인 상태가 유지되고, sda가 0에서 1로 변하는 구간을 볼 수 있다. 이 구간이 Data I2C 프로토콜의 한 주기가 끝나는 Stop Condition(노란색) 부분에 해당하게 된다.
4. Data Write
w_mem(memory에 값을 입력하는 구간) High 신호일 때 Master에서 보낸 Addr 자리에 전달된 Data가 저장된 것을 확인할 수 있다.
- Master Read / Slave Send 동작
1. Start Condition
Start Condition의 경우 Master Write와 동일하게 신호를 전달한다.
2. Address & (Master) Data Read / Ack
Addr로 4의 주소를 전달하고 OP code는 Master Read이기 때문에 High 신호를 받아 처음 8bit는 {addr(0000100), op(1)} => 9의 값을 전달한다.
현재 Memory의 4번째 자리에는 04의 값이 저장되어 있고, 처음 Addr를 전달하는 8bit 이후 ACK 신호를 통해 Slave가 확인되면 4번째 자리에 있는 04의 값을 Master로 전달한다.
두번째 Data를 전달하는 8bit를 보게되면 00000100(4)의 값을 전달하고 있는 것을 확인할 수 있다.
3. State, ACK, STOP
기본적으로 Start, 8bit(Addr), ACK1, 8bit(Data), ACK2, Stop은 동일하지만 Master Read의 경우 State는 물론 Flag 신호와 8bit(Data) 이후 ACK2 신호에 있어서 변화가 발생한다.
Master Read의 경우 Slave에서 sda 버스에 데이터를 Master로 전달하기 때문에 Slave의 sda_en Flag가 High 신호를 가지게 된다.
처음 ACK 신호의 경우 ADDR을 구분하기 위한 8bit이기 때문에 Slave에서 ACK신호를 전달하지만 Master Read의 경우 Slave에서 Data를 전달하기 때문에 ACK 신호를 Master에서 전달해야 한다.
ACK2는 Master에서 Slave로 전달하게 되고, 기존의 Slave에서 Master로 전달하는 ACK 신호는 Low(0)을 전달하면 응답 신호로 보지만 Master가 Slave에게 전달하는 ACK 신호의 경우 NACK로 1의 신호를 전달하게 된다.
4. Data Read
Data Read 의 경우 처음 Simulation에서 보았듯이 Memory에 ADDR에 해당하는 Data가 8bit를 통해 Master로 전달된 것을 확인할 수 있었다.
- Verilog Code
- Master Code
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company:
// Engineer:
//
// Create Date: 2024/08/16 15:16:09
// Design Name:
// Module Name: i2c_master
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//////////////////////////////////////////////////////////////////////////////////
module i2c_master
// #(parameter idle = 0, parameter start = 1, parameter write_addr = 2,
// parameter ack_1 = 3, parameter write_data = 4, parameter read_data = 5,
// parameter stop = 6, parameter ack_2 = 7, parameter master_ack = 8)
(
input clk, rst, newd,
input [6:0] addr,
input op,
inout sda,
output scl,
input [7:0] din,
output [7:0] dout,
output reg busy, ack_err, done
);
// temporary
reg scl_t = 0;
reg sda_t = 0;
parameter sys_freq = 40000000; // 40MHz
parameter i2c_freq = 100000; // 100k
// 클럭 주기를 의미 4등분 할 것이기 때문에 4로 지정
parameter clk_count4 = {sys_freq/i2c_freq}; // 400
parameter clk_count1 = clk_count4/4; // 100
integer count1 = 0;
reg i2c_clk = 0;
// i2c 동기화 주기를 4로 나누고 각 구간을 의미
reg [1:0] pulse = 0;
always @(posedge clk)
begin
if(rst)
begin
pulse <= 0;
count1 <= 0;
end
// busy == 0이면 동작중이 아니다
else if(busy == 1'b0)
begin
pulse <= 0;
count1 <= 0;
end
// i2c 주기의 pulse 동작 관련 코드
else if(count1 == clk_count1 - 1)
begin
pulse <= 1;
count1 <= count1 + 1;
end
else if(count1 == clk_count1*2 -1)
begin
pulse <= 2;
count1 <= count1 + 1;
end
else if(count1 == clk_count1*3 -1)
begin
pulse <= 3;
count1 <= count1 + 1;
end
else if(count1 == clk_count1*4 -1)
begin
pulse <= 0;
count1 <= 0;
end
else
begin
count1 <= count1 + 1;
end
end
//----------
reg [3:0] bitcount = 0;
reg [7:0] data_addr = 0, data_tx = 0;
reg r_ack = 0;
reg [7:0] rx_data = 0;
reg sda_en = 0;
// reg [3:0] state;
typedef enum logic [3:0] {idle = 0, start = 1, write_addr = 2, ack_1 = 3, write_data = 4, read_data = 5, stop = 6, ack_2 =7, master_ack = 8} state_type;
state_type state = idle;
always @(posedge clk)
begin
if(rst)
begin
bitcount <= 0;
data_addr <= 0;
data_tx <= 0;
scl_t <= 1;
sda_t <= 1;
state <= idle;
busy <= 1'b0;
ack_err <= 1'b0;
done <= 1'b0;
end
else
begin
case(state)
// --- idle
idle :
begin
done <= 1'b0;
if(newd == 1'b1)
begin
data_addr <= {addr, op};
data_tx <= din;
busy <= 1'b1;
state <= start;
ack_err <= 1'b0;
end
else
begin
data_addr <= 0;
data_tx <= 0;
busy <= 1'b0;
state <= idle;
ack_err <= 1'b0;
end
end
// --- start
start :
begin
sda_en <= 1'b1; // send start to slave
// start condition scl == 1 / sda == 1 -> 0
case(pulse)
0 : begin scl_t <= 1'b1; sda_t <= 1'b1; end
1 : begin scl_t <= 1'b1; sda_t <= 1'b1; end
2 : begin scl_t <= 1'b1; sda_t <= 1'b0; end
3 : begin scl_t <= 1'b1; sda_t <= 1'b0; end
endcase
if(count1 == clk_count1 * 4 -1)
begin
state <= write_addr;
scl_t <= 1'b0;
end
else state <= start;
end
// --- write_addr
write_addr :
begin
sda_en <= 1'b1;
if(bitcount <= 7)
begin
// data bit가 전송될 때, SDA는 SCL이 Low 상태일 때 값을 설정하고,
// SCL이 High 상태일 때 값이 읽힌다.
case(pulse)
0 : begin scl_t <= 1'b0; sda_t <= 1'b0; end
1 : begin scl_t <= 1'b0; sda_t <= data_addr[7 - bitcount]; end
2 : begin scl_t <= 1'b1; end
3 : begin scl_t <= 1'b1; end
endcase
if(count1 == clk_count1*4 -1)
begin
state <= write_addr;
scl_t <= 1'b0;
bitcount <= bitcount + 1;
end
else
begin
state <= write_addr;
end
end
else
begin
state <= ack_1;
bitcount <= 0;
// Slave에서 Master로 보내는 것이기 떄문에 en = 0
sda_en <= 1'b0;
end
end
//--- write_data
write_data :
begin
if(bitcount <= 7)
begin
case(pulse)
0 : begin scl_t <= 1'b0; end
1 : begin scl_t <= 1'b0; sda_en <= 1'b1; sda_t <= data_tx[7-bitcount]; end
2 : begin scl_t <= 1'b1; end
3 : begin scl_t <= 1'b1; end
endcase
if(count1 == clk_count1*4 - 1)
begin
state <= write_data;
scl_t <= 1'b0;
bitcount <= bitcount + 1;
end
else
begin
state <= write_data;
end
end
else
begin
state <= ack_2;
bitcount <= 0;
sda_en <= 1'b0;
end
end
//---read data
read_data :
begin
sda_en <= 1'b0;
if(bitcount <= 7)
begin
case(pulse)
0 : begin scl_t <= 1'b0; sda_t <= 1'b0; end
1 : begin scl_t <= 1'b0; sda_t <= 1'b0; end
2 : begin scl_t <= 1'b1; rx_data[7:0] <= (count1 == 200) ? {rx_data[6:0], sda} : rx_data; end
3 : begin scl_t <= 1'b1; end
endcase
if(count1 == clk_count1*4 - 1)
begin
state <= read_data;
scl_t <= 1'b0;
bitcount <= bitcount + 1;
end
else
begin
state <= read_data;
end
end
else
begin
state <= master_ack;
bitcount <= 0;
sda_en <= 1'b1;
end
end
//---master_ack
master_ack :
begin
sda_en <= 1'b1;
case(pulse)
0 : begin scl_t <= 1'b0; sda_t <= 1'b1; end
1 : begin scl_t <= 1'b0; sda_t <= 1'b1; end
2 : begin scl_t <= 1'b1; sda_t <= 1'b1; end
3 : begin scl_t <= 1'b1; sda_t <= 1'b1; end
endcase
if(count1 == clk_count1*4 - 1)
begin
sda_t <= 1'b0;
state <= stop;
sda_en <= 1'b1;
end
else
begin
state <= master_ack;
end
end
//---ack_2
ack_2 :
begin
sda_en <= 1'b0;
case(pulse)
0 : begin scl_t <= 1'b0; sda_t <= 1'b0; end
1 : begin scl_t <= 1'b0; sda_t <= 1'b0; end
2 : begin scl_t <= 1'b1; sda_t <= 1'b0; r_ack <= 1'b0; end
3 : begin scl_t <= 1'b1; end
endcase
if(count1 == clk_count1*4 - 1)
begin
sda_t <= 1'b0;
sda_en <= 1'b1;
if(r_ack == 1'b0)
begin
state <= stop;
ack_err <= 1'b0;
end
else
begin
state <= stop;
ack_err <= 1'b1;
end
end
else
begin
state <= ack_2;
end
end
//---stop
stop :
begin
sda_en <= 1'b1;
case(pulse)
0 : begin scl_t <= 1'b1; sda_t <= 1'b0; end
1 : begin scl_t <= 1'b1; sda_t <= 1'b0; end
2 : begin scl_t <= 1'b1; sda_t <= 1'b1; end
3 : begin scl_t <= 1'b1; sda_t <= 1'b1; end
endcase
if(count1 == clk_count1*4 - 1)
begin
state <= idle;
scl_t <= 1'b0;
busy <= 1'b0;
sda_en <= 1'b1;
done <= 1'b1;
end
else state <= stop;
end
//---default
default : state <= idle;
endcase
end
end
assign sda = (sda_en == 1) ? (sda_t == 0) ? 1'b0 : 1'b1 : 1'bz;
assign scl = scl_t;
assign dout = rx_data;
endmodule
- Slave Code
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company:
// Engineer:
//
// Create Date: 2024/08/17 12:26:10
// Design Name:
// Module Name: i2c_slave
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//////////////////////////////////////////////////////////////////////////////////
module i2c_slave(
input scl, clk, rst,
inout sda,
output reg ack_err, done
);
typedef enum logic [3:0] {idle = 0, read_addr = 1, send_ack1 = 2, send_data = 3, master_ack = 4, read_data = 5, send_ack2 = 6, wait_p = 7, detect_stop = 8} state_type;
state_type state = idle;
reg [7:0] mem[0:127];
reg [7:0] r_addr;
reg [6:0] addr;
reg r_mem = 0;
reg w_mem = 0;
reg [7:0] dout;
reg [7:0] din;
reg sda_t;
reg sda_en;
reg [3:0] bitcnt = 0;
// mem 초기화
always @(posedge clk)
begin
if(rst)
begin
for(int i = 0; i < 128; i++)
begin
mem[i] = i;
end
dout <= 8'h0;
end
else if(r_mem == 1'b1)
begin
dout <= mem[addr];
end
else if(w_mem == 1'b1)
begin
mem[addr] <= din;
end
end
//--- pulse
parameter sys_freq = 40000000; // 40MHz
parameter i2c_freq = 100000; // 100k
// 클럭 주기를 의미 4등분 할 것이기 때문에 4로 지정
parameter clk_count4 = {sys_freq/i2c_freq}; // 400
parameter clk_count1 = clk_count4/4; // 100
integer count1 = 0;
reg i2c_clk = 0;
// i2c 동기화 주기를 4로 나누고 각 구간을 의미
reg [1:0] pulse = 0;
reg busy;
always @(posedge clk)
begin
if(rst)
begin
pulse <= 0;
count1 <= 0;
end
// busy == 0이면 동작중이 아니다
else if(busy == 1'b0)
begin
// busy == 1'b0일 때 master, slave가 최초 연결하는 타이밍이다.
// master에서 4개의 pulse로 나눴고, start condition이 3번째 pulse에서 동작하기 때문이다.
pulse <= 2;
count1 <= 202;
end
// i2c 주기의 pulse 동작 관련 코드
else if(count1 == clk_count1 - 1)
begin
pulse <= 1;
count1 <= count1 + 1;
end
else if(count1 == clk_count1*2 -1)
begin
pulse <= 2;
count1 <= count1 + 1;
end
else if(count1 == clk_count1*3 -1)
begin
pulse <= 3;
count1 <= count1 + 1;
end
else if(count1 == clk_count1*4 -1)
begin
pulse <= 0;
count1 <= 0;
end
else
begin
count1 <= count1 + 1;
end
end
reg scl_t;
wire start;
// 동기화를 위해 사용한다.
always @(posedge clk)
begin
scl_t <= scl;
end
//scl이 0에서 1로 변할때 start
assign start = ~scl & scl_t;
reg r_ack;
always @(posedge clk)
begin
if(rst)
begin
bitcnt <= 0;
state <= idle;
r_addr <= 7'b0000000;
sda_en <= 1'b0;
sda_t <= 1'b0;
addr <= 0;
r_mem <= 0;
din <= 8'h00;
ack_err <= 0;
done <= 1'b0;
busy <= 1'b0;
end
else
begin
case(state)
//---idle
idle :
begin
// start condition을 만족하는지 확인
if(scl == 1'b1 && sda == 1'b0)
begin
busy <= 1'b1;
state <= wait_p;
end
else
begin
state <= idle;
end
end
//---wait_p
wait_p :
begin
// start condition이 끝나는 지점
if(pulse == 2'b11 && count1 == 399)
begin
state <= read_addr;
end
else
begin
state <= wait_p;
end
end
//---read_addr
// 처음 slave는 우선 master가 전달하는 address를 필수로 확인해야 한다.
read_addr :
begin
sda_en <= 1'b0; //read addr to slave
if(bitcnt <= 7)
begin
case(pulse)
0 : begin end
1 : begin end
// 3번째 pulse scl High에서 값을 읽기 때문이다.
2 : begin r_addr <= (count1 == 200) ? {r_addr[6:0], sda} : r_addr; end
3 : begin end
endcase
if(count1 == clk_count1*4 - 1)
begin
state <= read_addr;
bitcnt <= bitcnt + 1;
end
else
begin
state <= read_addr;
end
end
else
begin
state <= send_ack1;
bitcnt <= 0;
// ack 신호는 slave가 master에게 보내는 신호이기 때문에 sda_en을 1로 변경 ack 신호를 보내야 하기 때문이다.
sda_en <= 1'b1;
//master로부터 전달받은 8비트 중 앞에 6비트는 address bit
addr <= r_addr[7:1];
end
end
//---read_data
read_data :
begin
sda_en <= 1'b0;
if(bitcnt <= 7)
begin
case(pulse)
0 : begin end
1 : begin end
// SCL이 High일 때 sda를 통해 전달된 데이터를 Shift register로 데이터를 임시 저장소에 저장한다.
2 : begin din <= (count1 == 200) ? {din[6:0], sda} : din; end
3 : begin end
endcase
if(count1 == clk_count1*4 - 1)
begin
state <= read_data;
bitcnt <= bitcnt + 1;
end
else
begin
state <= read_data;
end
end
else
begin
state <= send_ack2;
bitcnt <= 0;
//master로부터 data를 읽고 ack 신호를 보내기 위해 sda_en 상태를 변경한다.
sda_en <= 1'b1;
//w_mem 상태에 따라 memory에 값을 읽거나 쓴다.
w_mem <= 1'b1;
end
end
//---send_ack2
send_ack2 :
begin
case(pulse)
//SCL Low일 때 값을 변경한다.
//master에서 scl low일 때 sda를 통해 slave에서 0의 값이 들어오면 ack라는 것을 알 수 잇다.
0 : begin sda_t <= 1'b0; end
//scl low일 때 master, slave에서 값을 읽지 않기 때문에 값 전달과 관련된 상태를 변경한다.
1 : begin w_mem <= 1'b0; end
2 : begin end
3 : begin end
endcase
if(count1 == clk_count1*4 - 1)
begin
//Stop Condition을 만들기 위한 상태
state <= detect_stop;
sda_en <= 1'b0;
end
else
begin
state <= send_ack2;
end
end
//---send_data
send_data :
begin
sda_en <= 1'b1;
if(bitcnt <= 7)
begin
r_mem <= 1'b0;
case(pulse)
0 : begin end
1 : begin sda_t <= (count1 == 100) ? dout[7-bitcnt] : sda_t; end
2 : begin end
3 : begin end
endcase
if(count1 == clk_count1*4 - 1)
begin
state <= send_data;
bitcnt <= bitcnt + 1;
end
else
begin
state <= send_data;
end
end
else
begin
state <= master_ack;
bitcnt <= 0;
sda_en <= 1'b0;
end
end
//---master_ack
master_ack :
begin
case(pulse)
0 : begin end
1 : begin end
2 : begin r_ack <= (count1 == 200) ? sda : r_ack; end
3 : begin end
endcase
if(count1 == clk_count1*4 - 1)
begin
if(r_ack == 1'b1)
begin
ack_err <= 1'b0;
state <= detect_stop;
sda_en <= 1'b0;
end
else
begin
ack_err <= 1'b1;
state <= detect_stop;
sda_en <= 1'b0;
end
end
else
begin
state <= master_ack;
end
end
//---detect_stop;
detect_stop :
begin
if(pulse == 2'b11 && count1 == 399)
begin
state <= idle;
busy <= 1'b0;
done <= 1'b1;
end
else state <= detect_stop;
end
//---default
default :
state <= idle;
endcase
end
end
//verilog에서 z high impedance 상태는 pin이 외부로부터 들어오는 신호를 받는 경우나 외부로 신호를 내보내지 않는 경우에 사용된다.
assign sda = (sda_en == 1'b1) ? sda_t : 1'bz;
endmodule
- Top Code
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company:
// Engineer:
//
// Create Date: 2024/08/17 13:41:12
// Design Name:
// Module Name: i2c_top
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//////////////////////////////////////////////////////////////////////////////////
module i2c_top(
input clk, rst, newd, op,
input [6:0] addr,
input [7:0] din,
output [7:0] dout,
output busy, ack_err,
output done
);
wire sda, scl;
wire ack_errm, ack_errs;
i2c_master master(clk, rst, newd, addr, op, sda, scl, din, dout, busy, ack_errm, done);
i2c_slave slave(scl, clk, rst, sda, ack_errs,);
assign ack_err = ack_errs | ack_errm;
endmodule
- Test Bench Code
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company:
// Engineer:
//
// Create Date: 2024/08/17 13:44:22
// Design Name:
// Module Name: i2c_top_tb
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//////////////////////////////////////////////////////////////////////////////////
module i2c_top_tb;
reg clk = 0, rst = 0, newd = 0, op;
reg [6:0] addr;
reg [7:0] din;
wire [7:0] dout;
wire busy, ack_err;
wire done;
i2c_top dut (clk,rst, newd, op, addr, din, dout, busy, ack_err, done);
always #5 clk = ~clk;
initial
begin
rst = 1;
repeat(5) @(posedge clk);
rst = 0;
repeat(40) @(posedge clk);
//////////// write operation
for(int i = 0; i < 2 ; i++)
begin
newd = 1;
op = 0;
addr = $urandom_range(1,4);
din = $urandom_range(1,5);
repeat(5) @(posedge clk);
newd <= 1'b0;
@(posedge done);
$display("[WR] din : %0d addr: %0d",din, addr);
@(posedge clk);
end
////////////read operation
for(int i = 0; i < 2 ; i++)
begin
newd = 1;
op = 1;
addr = $urandom_range(1,4);
din = 0;
repeat(5) @(posedge clk);
newd <= 1'b0;
@(posedge done);
$display("[RD] dout : %0d addr: %0d",dout, addr);
@(posedge clk);
end
repeat(10) @(posedge clk);
$stop;
end
endmodule
'Semiconductor > 0. RTL, Simulation' 카테고리의 다른 글
컴퓨터 구조 및 CPU 동작 원리 (4) - 32 Bit RISC CPU(Pipeline) Simulation 분석 (0) | 2024.10.17 |
---|---|
컴퓨터 구조 및 CPU 동작 원리 (3) - 32 Bit RISC CPU(Pipeline) RTL 설계 (0) | 2024.10.14 |
RTL - Two Port SRAM / Dual Port SRAM 구조 및 설계 (0) | 2024.08.07 |
RTL - CACHE(Two Port SRAM) 동작 과정 및 설명 (0) | 2024.08.07 |
RTL - SRAM (`ifdef를 이용한 FPGA, ASIC 코드 분리) (0) | 2024.08.06 |