I²C · Module 3
Master and Slave Roles (Controller and Target)
Roles on an I²C bus describe responsibility, not data direction. A controller initiates and times a transfer; a target participates once selected. Either may transmit or receive the payload — and collapsing those two axes produces a design that writes correctly and cannot read.
Module 2 finished with a conductor that several devices can share safely: any participant may pull a line LOW or release it, a pull-up restores HIGH when everyone releases, and the resolved level is something every participant reads rather than assumes. That is a working medium.
A medium is not yet a system. Nothing so far says who is allowed to start a conversation, who decides how long it lasts, or how a device knows the traffic on the wire concerns it. Those are questions about responsibility, and this module answers them — starting here, with the two roles the bus defines and the one distinction that causes more broken I\u00B2C designs than any other.
1. Terminology — Two Names for Each Role
The specification\u0027s vocabulary changed, and both sets are in active use, so it is worth settling before anything else.
| Historical term | Current term | What it names |
|---|---|---|
| master | controller | the participant that initiates and times a transfer |
| slave | target | the participant that responds when selected |
NXP\u0027s UM10204 now uses controller and target, and this curriculum follows it. The older words are not obsolete in practice: existing datasheets, register-field names, driver source, signal names and a great deal of production RTL use master and slave, and you will read those documents for years. The useful skill is the mapping, not the preference — when a datasheet says "slave address", it means the target\u0027s address, and nothing about the hardware differs.
One deliberate exception: this chapter\u0027s title carries both terms because the curriculum entry does. From §2 onward the prose says controller and target consistently, because mixing vocabulary mid-argument is how a reader loses track of which axis is being discussed — and this chapter is entirely about keeping two axes apart.
2. What a Controller Owns
A controller is the participant that decides a transfer happens. Concretely it owns four responsibilities, and owning them is what makes it the controller:
It initiates. A transfer exists because a controller started one. Nothing begins on an idle bus without a controller acting.
It provides the timing. The controller drives SCL, which is what paces every bit of the exchange. Module 2 established that SCL is a shared node rather than a plain output — a target is permitted to influence it, and Module 12 develops how — but the controller is the participant that generates the clock in the ordinary case.
It selects the participant. The controller names which target the transfer concerns. How that name is encoded and transmitted is Module 6 and Module 7; here it matters only that the choice is the controller\u0027s.
It sets the direction and ends the transfer. The controller decides whether this transfer moves data toward the target or back from it, and the controller terminates it.
3. What a Target May and May Not Do
A target is the participant that waits. It has no way to start anything, and that single limitation defines it.
What a target does: watches the bus, recognises when it has been named, participates for the duration of the transfer, and — depending on the direction the controller chose — either accepts bytes or supplies them.
What a target does not do: begin a transfer, choose the direction, or decide when the exchange ends. A target with data ready and nothing to do about it must wait until a controller asks.
4. Role Is Not Direction — The Distinction That Breaks Designs
Here is the most valuable paragraph in the chapter.
It is extremely tempting to read controller as the one sending data and target as the one receiving it. That mapping is wrong, it is wrong in a way that survives casual testing, and it produces RTL that cannot perform half of the transfers the bus supports.
Role and data direction are independent axes.
| Bus role | Initiates a transfer? | Can transmit payload? | Can receive payload? |
|---|---|---|---|
| Controller | Yes — always | Yes, on a write | Yes, on a read |
| Target | No | Yes, on a read | Yes, on a write |
Read the two right-hand columns: both roles can do both things. What differs is the left column, and only the left column.
Work the two cases concretely:
A controller writes to a target. The controller initiated, times the bus and chose the direction; it also supplies the bytes. Controller transmits, target receives.
A controller reads from a target. The controller still initiated, still times the bus, still chose the direction, and still ends the transfer. But the bytes come from the target. Target transmits, controller receives — and the roles have not changed at all.
The second case is where the confusion bites. A target supplying data has not become a controller. It is a target doing exactly what a target does when the controller selected the read direction. The controller is still in charge of a transfer whose payload happens to be flowing toward it.
5. RTL Connection — Responsibility Expressed as an Interface
The cleanest way to see that role and direction are separate is to write the two sides\u0027 interfaces and notice what each one has.
This model is architectural. It carries no SDA or SCL, no framing, no addressing and no bit-level timing — those belong to Modules 5 through 8 and to Module 17. What it does carry is the responsibility structure, and it carries it in a form a simulator can check: the controller side has a request port and the target side does not.
One deliberate detail makes the point testable. The target side has a tgt_req input that the design ignores completely. It exists so a testbench can assert it and prove that nothing happens — a target asking for a transfer is not a mechanism the bus has.
5a. SystemVerilog
module i2c_role_model #(
parameter int ID_WIDTH = 3
)(
input logic clk,
input logic rst_n,
// --- controller side: the ONLY side with a request port -----------------
input logic ctrl_req,
input logic [ID_WIDTH-1:0] ctrl_target_id,
input logic ctrl_is_read,
input logic [7:0] ctrl_wdata,
output logic ctrl_busy,
output logic ctrl_done,
output logic [7:0] ctrl_rdata,
// --- target side: participates only when selected -----------------------
input logic tgt_req, // DELIBERATELY IGNORED
output logic tgt_selected,
output logic tgt_is_read,
output logic tgt_rx_valid,
output logic [7:0] tgt_rx_data,
input logic [7:0] tgt_tx_data
);
typedef enum logic [1:0] { IDLE, XFER, FINISH } state_e;
state_e state;
logic [ID_WIDTH-1:0] id_q;
logic rd_q;
logic [7:0] wd_q;
always_ff @(posedge clk) begin
if (!rst_n) begin
state <= IDLE; ctrl_busy <= 1'b0; ctrl_done <= 1'b0;
ctrl_rdata <= '0; tgt_selected <= 1'b0; tgt_is_read <= 1'b0;
tgt_rx_valid <= 1'b0; tgt_rx_data <= '0;
id_q <= '0; rd_q <= 1'b0; wd_q <= '0;
end else begin
ctrl_done <= 1'b0;
tgt_rx_valid <= 1'b0;
case (state)
IDLE: begin
// tgt_req is NOT examined. A target cannot start a transfer.
if (ctrl_req) begin
id_q <= ctrl_target_id; rd_q <= ctrl_is_read; wd_q <= ctrl_wdata;
ctrl_busy <= 1'b1; tgt_selected <= 1'b1; tgt_is_read <= ctrl_is_read;
state <= XFER;
end
end
XFER: begin
if (rd_q) ctrl_rdata <= tgt_tx_data; // target TRANSMITS
else begin tgt_rx_valid <= 1'b1; tgt_rx_data <= wd_q; end
state <= FINISH;
end
FINISH: begin
ctrl_busy <= 1'b0; ctrl_done <= 1'b1; tgt_selected <= 1'b0;
state <= IDLE;
end
default: state <= IDLE;
endcase
end
end
endmoduleThe testbench is where the chapter\u0027s thesis becomes a check rather than a claim. It holds tgt_req high for four cycles and requires that nothing starts; then it runs a write and a read and requires that the role outputs are identical in both, while only the payload moves the other way.
module i2c_role_model_tb;
localparam int ID_WIDTH = 3;
logic clk = 1'b0, rst_n, ctrl_req, ctrl_is_read, tgt_req;
logic [ID_WIDTH-1:0] ctrl_target_id;
logic [7:0] ctrl_wdata, ctrl_rdata, tgt_rx_data, tgt_tx_data;
logic ctrl_busy, ctrl_done, tgt_selected, tgt_is_read, tgt_rx_valid;
int errors = 0;
i2c_role_model #(.ID_WIDTH(ID_WIDTH)) dut (.*);
always #5 clk = ~clk;
task automatic issue(input logic rd, input logic [7:0] d, input logic [ID_WIDTH-1:0] id);
ctrl_target_id = id; ctrl_is_read = rd; ctrl_wdata = d; ctrl_req = 1'b1;
@(negedge clk); ctrl_req = 1'b0;
endtask
initial begin
rst_n=0; ctrl_req=0; tgt_req=0; ctrl_is_read=0; ctrl_target_id='0;
ctrl_wdata='0; tgt_tx_data=8'h5A;
repeat (2) @(negedge clk);
if (ctrl_busy!==1'b0 || tgt_selected!==1'b0) begin $error("not idle in reset"); errors++; end
rst_n=1; @(negedge clk);
// 1 -- A TARGET CANNOT INITIATE. Hold tgt_req high for several cycles.
tgt_req = 1'b1;
repeat (4) begin
@(negedge clk);
if (ctrl_busy!==1'b0 || tgt_selected!==1'b0) begin
$error("tgt_req started a transfer -- a target must not initiate"); errors++;
end
end
tgt_req = 1'b0;
// 2 -- WRITE: controller transmits, target receives. Role = controller.
issue(1'b0, 8'h3C, 3'd2);
@(negedge clk); // transfer cycle
if (ctrl_busy!==1'b1) begin $error("busy not asserted after request"); errors++; end
if (tgt_selected!==1'b1) begin $error("target not selected"); errors++; end
if (tgt_is_read!==1'b0) begin $error("direction wrong on write"); errors++; end
if (tgt_rx_valid!==1'b1 || tgt_rx_data!==8'h3C) begin
$error("target did not receive the written byte"); errors++; end
@(negedge clk); // completion cycle
if (ctrl_done!==1'b1) begin $error("done not pulsed"); errors++; end
if (ctrl_busy!==1'b0) begin $error("busy not cleared"); errors++; end
// 3 -- READ: target transmits, controller receives. ROLE IS UNCHANGED.
@(negedge clk);
tgt_tx_data = 8'hA5;
issue(1'b1, 8'h00, 3'd5);
@(negedge clk); // transfer cycle
if (ctrl_busy!==1'b1) begin $error("busy not asserted on read"); errors++; end
if (tgt_selected!==1'b1) begin $error("target not selected on read"); errors++; end
if (tgt_is_read!==1'b1) begin $error("direction wrong on read"); errors++; end
@(negedge clk); // completion cycle
if (ctrl_rdata!==8'hA5) begin
$error("controller did not receive target data (got %h)", ctrl_rdata); errors++; end
// THE KEY CHECK: the target supplied the payload, and the controller is
// STILL the controller -- it requested, it timed, it finished the transfer.
if (ctrl_done!==1'b1) begin $error("controller did not terminate the read"); errors++; end
if (errors==0) $display("PASS: role is independent of data direction; a target never initiates");
else $display("FAIL: %0d error(s)", errors);
$finish;
end
endmodule5b. Verilog
Same architecture; reg/wire typing, a localparam state encoding instead of an enum, and $display reporting.
module i2c_role_model #(
parameter ID_WIDTH = 3
)(
input wire clk,
input wire rst_n,
input wire ctrl_req,
input wire [ID_WIDTH-1:0] ctrl_target_id,
input wire ctrl_is_read,
input wire [7:0] ctrl_wdata,
output reg ctrl_busy,
output reg ctrl_done,
output reg [7:0] ctrl_rdata,
input wire tgt_req, // DELIBERATELY IGNORED
output reg tgt_selected,
output reg tgt_is_read,
output reg tgt_rx_valid,
output reg [7:0] tgt_rx_data,
input wire [7:0] tgt_tx_data
);
localparam IDLE = 2'd0, XFER = 2'd1, FINISH = 2'd2;
reg [1:0] state;
reg [ID_WIDTH-1:0] id_q;
reg rd_q;
reg [7:0] wd_q;
always @(posedge clk) begin
if (!rst_n) begin
state <= IDLE; ctrl_busy <= 1'b0; ctrl_done <= 1'b0;
ctrl_rdata <= 8'h00; tgt_selected <= 1'b0; tgt_is_read <= 1'b0;
tgt_rx_valid <= 1'b0; tgt_rx_data <= 8'h00;
id_q <= {ID_WIDTH{1'b0}}; rd_q <= 1'b0; wd_q <= 8'h00;
end else begin
ctrl_done <= 1'b0;
tgt_rx_valid <= 1'b0;
case (state)
IDLE: if (ctrl_req) begin // tgt_req is not examined
id_q <= ctrl_target_id; rd_q <= ctrl_is_read; wd_q <= ctrl_wdata;
ctrl_busy <= 1'b1; tgt_selected <= 1'b1; tgt_is_read <= ctrl_is_read;
state <= XFER;
end
XFER: begin
if (rd_q) ctrl_rdata <= tgt_tx_data;
else begin tgt_rx_valid <= 1'b1; tgt_rx_data <= wd_q; end
state <= FINISH;
end
FINISH: begin
ctrl_busy <= 1'b0; ctrl_done <= 1'b1; tgt_selected <= 1'b0;
state <= IDLE;
end
default: state <= IDLE;
endcase
end
end
endmodule module i2c_role_model_tb;
parameter ID_WIDTH = 3;
reg clk, rst_n, ctrl_req, ctrl_is_read, tgt_req;
reg [ID_WIDTH-1:0] ctrl_target_id;
reg [7:0] ctrl_wdata, tgt_tx_data;
wire [7:0] ctrl_rdata, tgt_rx_data;
wire ctrl_busy, ctrl_done, tgt_selected, tgt_is_read, tgt_rx_valid;
integer errors, i;
i2c_role_model #(.ID_WIDTH(ID_WIDTH)) dut (
.clk(clk), .rst_n(rst_n), .ctrl_req(ctrl_req), .ctrl_target_id(ctrl_target_id),
.ctrl_is_read(ctrl_is_read), .ctrl_wdata(ctrl_wdata), .ctrl_busy(ctrl_busy),
.ctrl_done(ctrl_done), .ctrl_rdata(ctrl_rdata), .tgt_req(tgt_req),
.tgt_selected(tgt_selected), .tgt_is_read(tgt_is_read),
.tgt_rx_valid(tgt_rx_valid), .tgt_rx_data(tgt_rx_data), .tgt_tx_data(tgt_tx_data));
initial clk = 1'b0;
always #5 clk = ~clk;
task issue;
input rd; input [7:0] d; input [ID_WIDTH-1:0] id;
begin
ctrl_target_id = id; ctrl_is_read = rd; ctrl_wdata = d; ctrl_req = 1'b1;
@(negedge clk); ctrl_req = 1'b0;
end
endtask
initial begin
errors=0; rst_n=0; ctrl_req=0; tgt_req=0; ctrl_is_read=0;
ctrl_target_id=0; ctrl_wdata=0; tgt_tx_data=8'h5A;
repeat (2) @(negedge clk);
if (ctrl_busy!==1'b0 || tgt_selected!==1'b0) begin
$display("FAIL: not idle in reset"); errors=errors+1; end
rst_n=1; @(negedge clk);
tgt_req = 1'b1; // a target cannot initiate
for (i=0; i<4; i=i+1) begin
@(negedge clk);
if (ctrl_busy!==1'b0 || tgt_selected!==1'b0) begin
$display("FAIL: tgt_req started a transfer"); errors=errors+1; end
end
tgt_req = 1'b0;
issue(1'b0, 8'h3C, 3'd2); // WRITE
@(negedge clk);
if (ctrl_busy!==1'b1) begin $display("FAIL: busy not asserted"); errors=errors+1; end
if (tgt_selected!==1'b1) begin $display("FAIL: target not selected"); errors=errors+1; end
if (tgt_is_read!==1'b0) begin $display("FAIL: direction wrong on write"); errors=errors+1; end
if (tgt_rx_valid!==1'b1 || tgt_rx_data!==8'h3C) begin
$display("FAIL: target did not receive the byte"); errors=errors+1; end
@(negedge clk);
if (ctrl_done!==1'b1) begin $display("FAIL: done not pulsed"); errors=errors+1; end
if (ctrl_busy!==1'b0) begin $display("FAIL: busy not cleared"); errors=errors+1; end
@(negedge clk);
tgt_tx_data = 8'hA5;
issue(1'b1, 8'h00, 3'd5); // READ
@(negedge clk);
if (ctrl_busy!==1'b1) begin $display("FAIL: busy not asserted on read"); errors=errors+1; end
if (tgt_selected!==1'b1) begin $display("FAIL: target not selected on read"); errors=errors+1; end
if (tgt_is_read!==1'b1) begin $display("FAIL: direction wrong on read"); errors=errors+1; end
@(negedge clk);
if (ctrl_rdata!==8'hA5) begin
$display("FAIL: controller did not receive target data"); errors=errors+1; end
if (ctrl_done!==1'b1) begin
$display("FAIL: controller did not terminate the read"); errors=errors+1; end
if (errors==0) $display("PASS: role is independent of data direction; a target never initiates");
else $display("FAIL: %0d error(s)", errors);
$finish;
end
endmodule5c. VHDL
VHDL contributes the clearest statement of the state machine, because an enumerated type names the states rather than encoding them — type state_t is (IDLE, XFER, FINISH) is the design intent written down, and the tool chooses the encoding.
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity i2c_role_model is
generic ( ID_WIDTH : positive := 3 );
port (
clk : in std_logic;
rst_n : in std_logic;
ctrl_req : in std_logic;
ctrl_target_id : in std_logic_vector(ID_WIDTH-1 downto 0);
ctrl_is_read : in std_logic;
ctrl_wdata : in std_logic_vector(7 downto 0);
ctrl_busy : out std_logic;
ctrl_done : out std_logic;
ctrl_rdata : out std_logic_vector(7 downto 0);
tgt_req : in std_logic; -- DELIBERATELY IGNORED
tgt_selected : out std_logic;
tgt_is_read : out std_logic;
tgt_rx_valid : out std_logic;
tgt_rx_data : out std_logic_vector(7 downto 0);
tgt_tx_data : in std_logic_vector(7 downto 0)
);
end entity;
architecture rtl of i2c_role_model is
type state_t is (IDLE, XFER, FINISH);
signal state : state_t := IDLE;
signal id_q : std_logic_vector(ID_WIDTH-1 downto 0) := (others => '0');
signal rd_q : std_logic := '0';
signal wd_q : std_logic_vector(7 downto 0) := (others => '0');
begin
process (clk)
begin
if rising_edge(clk) then
if rst_n = '0' then
state <= IDLE; ctrl_busy <= '0'; ctrl_done <= '0';
ctrl_rdata <= (others => '0'); tgt_selected <= '0'; tgt_is_read <= '0';
tgt_rx_valid <= '0'; tgt_rx_data <= (others => '0');
id_q <= (others => '0'); rd_q <= '0'; wd_q <= (others => '0');
else
ctrl_done <= '0';
tgt_rx_valid <= '0';
case state is
when IDLE =>
-- tgt_req is not examined: a target cannot initiate.
if ctrl_req = '1' then
id_q <= ctrl_target_id; rd_q <= ctrl_is_read; wd_q <= ctrl_wdata;
ctrl_busy <= '1'; tgt_selected <= '1'; tgt_is_read <= ctrl_is_read;
state <= XFER;
end if;
when XFER =>
if rd_q = '1' then
ctrl_rdata <= tgt_tx_data; -- target TRANSMITS
else
tgt_rx_valid <= '1'; tgt_rx_data <= wd_q;
end if;
state <= FINISH;
when FINISH =>
ctrl_busy <= '0'; ctrl_done <= '1'; tgt_selected <= '0';
state <= IDLE;
end case;
end if;
end if;
end process;
end architecture; library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity i2c_role_model_tb is
end entity;
architecture sim of i2c_role_model_tb is
constant ID_WIDTH : positive := 3;
signal clk : std_logic := '0';
signal rst_n : std_logic := '0';
signal ctrl_req : std_logic := '0';
signal ctrl_target_id : std_logic_vector(ID_WIDTH-1 downto 0) := (others => '0');
signal ctrl_is_read : std_logic := '0';
signal ctrl_wdata : std_logic_vector(7 downto 0) := (others => '0');
signal ctrl_busy : std_logic;
signal ctrl_done : std_logic;
signal ctrl_rdata : std_logic_vector(7 downto 0);
signal tgt_req : std_logic := '0';
signal tgt_selected : std_logic;
signal tgt_is_read : std_logic;
signal tgt_rx_valid : std_logic;
signal tgt_rx_data : std_logic_vector(7 downto 0);
signal tgt_tx_data : std_logic_vector(7 downto 0) := x"5A";
begin
dut : entity work.i2c_role_model
generic map (ID_WIDTH => ID_WIDTH)
port map (clk => clk, rst_n => rst_n, ctrl_req => ctrl_req,
ctrl_target_id => ctrl_target_id, ctrl_is_read => ctrl_is_read,
ctrl_wdata => ctrl_wdata, ctrl_busy => ctrl_busy, ctrl_done => ctrl_done,
ctrl_rdata => ctrl_rdata, tgt_req => tgt_req, tgt_selected => tgt_selected,
tgt_is_read => tgt_is_read, tgt_rx_valid => tgt_rx_valid,
tgt_rx_data => tgt_rx_data, tgt_tx_data => tgt_tx_data);
clk <= not clk after 5 ns;
stim : process
procedure issue (rd : in std_logic;
d : in std_logic_vector(7 downto 0);
id : in std_logic_vector(ID_WIDTH-1 downto 0)) is
begin
ctrl_target_id <= id; ctrl_is_read <= rd; ctrl_wdata <= d; ctrl_req <= '1';
wait until falling_edge(clk);
ctrl_req <= '0';
end procedure;
begin
wait until falling_edge(clk);
wait until falling_edge(clk);
assert ctrl_busy = '0' and tgt_selected = '0'
report "not idle during reset" severity error;
rst_n <= '1';
wait until falling_edge(clk);
-- A TARGET CANNOT INITIATE.
tgt_req <= '1';
for i in 0 to 3 loop
wait until falling_edge(clk);
assert ctrl_busy = '0' and tgt_selected = '0'
report "tgt_req started a transfer -- a target must not initiate" severity error;
end loop;
tgt_req <= '0';
-- WRITE: controller transmits, target receives.
issue('0', x"3C", "010");
wait until falling_edge(clk);
assert ctrl_busy = '1' report "busy not asserted" severity error;
assert tgt_selected = '1' report "target not selected" severity error;
assert tgt_is_read = '0' report "direction wrong on write" severity error;
assert tgt_rx_valid = '1' and tgt_rx_data = x"3C"
report "target did not receive the written byte" severity error;
wait until falling_edge(clk);
assert ctrl_done = '1' report "done not pulsed" severity error;
assert ctrl_busy = '0' report "busy not cleared" severity error;
-- READ: target transmits, controller receives. ROLE IS UNCHANGED.
wait until falling_edge(clk);
tgt_tx_data <= x"A5";
issue('1', x"00", "101");
wait until falling_edge(clk);
assert ctrl_busy = '1' report "busy not asserted on read" severity error;
assert tgt_selected = '1' report "target not selected on read" severity error;
assert tgt_is_read = '1' report "direction wrong on read" severity error;
wait until falling_edge(clk);
assert ctrl_rdata = x"A5"
report "controller did not receive target data" severity error;
assert ctrl_done = '1'
report "controller did not terminate the read" severity error;
report "i2c_role_model self-check complete" severity note;
wait;
end process;
end architecture;5d. What the Three Models Share
Ports, widths, the synchronous active-low reset, the three-state sequence, the stimulus and the expected results are identical, and all three testbenches complete at the same simulated time. The only language-level differences are the state encoding (enum / localparam / enumerated type) and the failure-reporting idiom.
What the model abstracts away, stated plainly so nothing here is mistaken for a controller: there is no SDA or SCL, no START or STOP, no address encoding, no acknowledgement, no bit-level timing, and a transfer completes in a fixed two cycles rather than taking as long as a real bus takes. The bit-level engine is deliberately absent — it is taught in Module 17, after framing, timing, acknowledgement, stretching and arbitration have been established. Copying this as a controller would produce something that compiles and cannot talk to a device.
What it does establish is worth keeping: the controller side owns a request port, the target side owns none, and both directions of payload leave the role outputs untouched.
6. Verification Connection — Roles Shape the Environment
The role split has an immediate consequence for how a testbench is organised, and it follows directly from Module 2\u0027s driver/monitor distinction.
A component that plays a controller needs to generate activity: it decides when transfers happen, which target they name and which direction they take. A component that plays a target needs to react: it waits to be named and then responds within the timing somebody else set. Those are different jobs, and an environment normally has both — one controller agent producing stimulus, and one or more target models behaving like real devices.
The asymmetry matters because it decides where interesting bugs are found. A target model is where you make a device behave badly — respond late, refuse, hold the clock, supply the wrong byte count — and a controller that only ever met well-behaved targets has not been verified. That is why a serious I\u00B2C environment invests more in its target models than the word "model" suggests. Modules 19 through 21 build them.
Nothing here requires UVM yet. The architectural point is the split itself: generate on the controller side, react on the target side, and observe the resolved bus separately from either.
7. Debugging — The Read That Was Blamed on the Wrong Device
The controller that could write and not read \u2014 role confused with direction
Pitfall \u2014 treating a read as though the target takes over the bus
// A first I2C controller design. Writes work perfectly against every device on
// the board. Reads return nothing useful. The designer reasons about the read
// like this:
//
// "On a write I am the transmitter, so I drive the data.
// On a read the TARGET is the transmitter, so the target is driving --
// therefore on a read the target is in charge of the transfer."
//
// and builds the read path to match: after selecting the target, the controller
// stops generating the clock and waits for the data to arrive.
//
// It is a coherent-sounding argument. Every step follows from the one before.
// The premise is wrong.Writes succeed against every device. Reads hang or return a fixed value. On a capture the transfer starts correctly -- the target is named, the direction is requested -- and then the bus simply stops: SCL never advances past the point where the read payload should begin. Nothing is electrically wrong. No device is holding a line. The bus is idle-but-not-free, waiting for a clock that is never coming. Because writes work, the designer concludes the controller is basically correct and starts suspecting the target: reading its datasheet again, trying a different part, suspecting the address. All of that is the wrong layer.
Role was confused with data direction. A read changes WHICH PARTICIPANT SUPPLIES THE PAYLOAD; it changes nothing about who is responsible for the transfer. The controller still initiates, still provides the clock for every bit including the bits the target is supplying, still chose the direction, and still terminates the exchange. A target cannot generate the clock -- that was never one of its capabilities -- so a controller that stops clocking during a read has removed the only thing advancing the transfer. The deeper error is architectural rather than a coding slip: the design has two different notions of "who is in charge" depending on payload direction, when the bus has exactly one, fixed for the whole transfer.
// Keep the roles fixed and change only the direction of the payload:
//
// write: controller initiates, clocks, and SUPPLIES bytes
// read: controller initiates, clocks, and ACCEPTS bytes
//
// In both cases the controller generates every clock and terminates the transfer.
// In the architectural model in this chapter that is exactly why ctrl_busy,
// tgt_selected and the completion handshake are IDENTICAL in the write and read
// tests -- only tgt_rx_valid versus ctrl_rdata differs.
//
// The verification that catches it: run a read and a write and compare the ROLE
// outputs, not just the data. A testbench that only checks payload passes a design
// with this bug on writes and never exercises the claim that roles are invariant.
// The chapter testbench checks ctrl_busy, tgt_selected and completion in both
// directions for precisely this reason.The engineering lesson: when a bus works in one direction and not the other, suspect a role error before a device error. Direction-specific failure is the signature of a design that has encoded "who is in charge" as a function of payload direction — and the fastest confirmation is to check whether the timing is still being generated when the payload reverses.
8. Common Misconceptions
9. Reason It Through
Work these through before reading the answers.
A temperature sensor supplies four bytes to a processor during a read transfer. A colleague says the sensor was acting as the controller for those four bytes because it was the one putting data on SDA.
Is the colleague right? No. The sensor supplied a payload; it did not initiate the transfer, did not generate the clock that moved those four bytes, did not choose the direction, and cannot end the exchange. Every responsibility that defines a controller stayed with the processor.
What would have to be true for the sensor to be a controller? It would have to start a transfer on an idle bus of its own accord and generate the timing for it. Some devices can do that — a device capable of acting as a controller — but supplying read data is not that, and no amount of data flowing outward makes it that.
A design must read a register from a target. The engineer asks: during the bytes the target is supplying, who is driving SCL?
Answer, and why it matters. The controller, for every bit, including the bits it is receiving. This is the question whose wrong answer produces the bug in §7, and it is worth being able to answer instantly: the clock belongs to the transfer, and the transfer belongs to the controller.
Sketch the interface a block needs if it must act as a controller and as a target at different times.
Reasoning. It needs both structures: a request path so it can initiate, and a selection-detection path so it can notice when somebody names it. Notice what that implies — the two are not variations of one interface, they are genuinely separate responsibilities that happen to share a physical bus. That is why devices capable of both are meaningfully more complex, and why Chapter 3.2 treats the multi-controller case as an architectural step rather than a configuration option.
10. Understanding Check
11. Summary
The bus defines two roles, and the current specification calls them controller and target where older documents say master and slave. The mapping is worth knowing because both vocabularies remain in use.
A controller initiates a transfer, generates the timing on SCL, selects which target is involved, sets the direction and terminates the exchange. A target waits, recognises when it has been named, and participates for the duration — it cannot start anything, choose a direction or end a transfer.
The distinction to carry forward is that role and payload direction are independent axes. Both roles can transmit and both can receive; only the controller initiates. A target supplying read data is a target doing its job, on a clock the controller is still generating, in a transfer the controller will end. Collapsing the two axes produces a design that writes correctly and cannot read, and the failure looks like a device problem while being an architectural one.
Two behaviours that appear to contradict this do not: a target may hold SCL to buy time inside somebody else\u0027s transfer, and a device may drive a separate interrupt pin to ask a controller to come and read it. Neither is initiation. The electrical layer is symmetric — Module 2 made every participant able to pull any line LOW — while the responsibility layer deliberately is not.
12. What Comes Next
One controller and one target is the simplest arrangement and not the interesting one. Chapter 3.2 puts many targets on the same pair of conductors and confronts the question that makes I\u00B2C what it is: if every device is physically connected to the same wires and sees everything, how does exactly one of them participate? That is the gap between physical broadcast and logical selection, and it also opens the case where more than one participant is capable of being a controller.
Browse the full path on the I\u00B2C tutorials index. For the electrical foundation this chapter assumes, see Wired-AND and SDA and SCL.
Continue learning
Related tutorials
- Related topic
PHY Responsibilities
The controller decides which DDR operation should happen. The PHY makes it real at a pin boundary whose timing the controller cannot meet — and the line between them is not the same in any two implementations.
- Related topic
Masters and Slaves
Master and slave are transaction roles, not a statement about importance or hierarchy. The role determines exactly which information each side owns: the initiator supplies address, direction and write data; the target supplies read data, completion and any error. Getting that ownership wrong is the source of an entire family of integration bugs.
- Related topic
Open-Drain Outputs — Drive Low, Release High
The architectural move the whole bus rests on, and it is a subtraction: delete every device's ability to drive HIGH. What remains is one switch to ground, so the two states are pull LOW and release — and two devices can never impose opposite levels because only one level can be imposed at all.
- Related topic
SCL Generation and the Bit Period
A legal I²C clock is not a frequency. LOW and HIGH are separately constrained phases, the controller pulls SCL low and releases it rather than driving it high, and a naive integer divider satisfies none of that. Build a parameterised phase generator in three languages and measure it.
