{"task_id": "spi_controller", "task_number": 1, "description": "Single-byte SPI master controller. On start, latch data_in, assert busy, idle spi_clk at cpol, shift out MSB-first on mosi, and shift in miso into data_out. When cpha=0, sample on the first active edge; when cpha=1, sample on the second edge. After 8 bits, deassert busy and return to IDLE with spi_clk back at cpol. If start is asserted while a transfer is already in progress, enter a recoverable ERROR state and then return to IDLE.", "header": "module spi_controller (\n input clk,\n input rst_n,\n input [7:0] data_in,\n input start,\n input cpol,\n input cpha,\n output reg [7:0] data_out,\n output reg busy,\n output reg spi_clk,\n output reg mosi,\n input miso\n);", "module_code": "`timescale 1ns/1ps\n\nmodule spi_controller (\n input clk,\n input rst_n,\n input [7:0] data_in,\n input start,\n input cpol,\n input cpha,\n output reg [7:0] data_out,\n output reg busy,\n output reg spi_clk,\n output reg mosi,\n input miso\n);\n localparam IDLE = 3'd0;\n localparam LOAD = 3'd1;\n localparam LEAD = 3'd2;\n localparam SAMPLE = 3'd3;\n localparam SHIFT = 3'd4;\n localparam DONE = 3'd5;\n localparam ERROR = 3'd6;\n\n reg [2:0] state;\n reg [2:0] bit_count;\n reg [7:0] tx_shift;\n reg [7:0] rx_shift;\n\n always @(posedge clk or negedge rst_n) begin\n if (!rst_n) begin\n state <= IDLE;\n bit_count <= 3'd0;\n tx_shift <= 8'd0;\n rx_shift <= 8'd0;\n data_out <= 8'd0;\n busy <= 1'b0;\n spi_clk <= 1'b0;\n mosi <= 1'b0;\n end else begin\n case (state)\n IDLE: begin\n busy <= 1'b0;\n spi_clk <= cpol;\n mosi <= 1'b0;\n if (start) begin\n busy <= 1'b1;\n bit_count <= 3'd7;\n tx_shift <= data_in;\n rx_shift <= 8'd0;\n state <= LOAD;\n end\n end\n\n LOAD: begin\n busy <= 1'b1;\n spi_clk <= cpol;\n mosi <= tx_shift[7];\n if (start) begin\n state <= ERROR;\n end else begin\n state <= LEAD;\n end\n end\n\n LEAD: begin\n busy <= 1'b1;\n spi_clk <= ~cpol;\n if (start) begin\n state <= ERROR;\n end else if (!cpha) begin\n rx_shift <= {rx_shift[6:0], miso};\n if (bit_count == 3'd0) begin\n data_out <= {rx_shift[6:0], miso};\n state <= DONE;\n end else begin\n state <= SHIFT;\n end\n end else begin\n state <= SAMPLE;\n end\n end\n\n SAMPLE: begin\n busy <= 1'b1;\n spi_clk <= ~cpol;\n if (start) begin\n state <= ERROR;\n end else begin\n rx_shift <= {rx_shift[6:0], miso};\n if (bit_count == 3'd0) begin\n data_out <= {rx_shift[6:0], miso};\n state <= DONE;\n end else begin\n state <= SHIFT;\n end\n end\n end\n\n SHIFT: begin\n busy <= 1'b1;\n spi_clk <= cpol;\n tx_shift <= {tx_shift[6:0], 1'b0};\n bit_count <= bit_count - 1'b1;\n mosi <= tx_shift[6];\n if (start) begin\n state <= ERROR;\n end else begin\n state <= LEAD;\n end\n end\n\n DONE: begin\n busy <= 1'b0;\n spi_clk <= cpol;\n mosi <= 1'b0;\n if (start) begin\n state <= ERROR;\n end else begin\n state <= IDLE;\n end\n end\n\n default: begin\n busy <= 1'b0;\n spi_clk <= cpol;\n mosi <= 1'b0;\n if (!start) begin\n state <= IDLE;\n end\n end\n endcase\n end\n end\n\nendmodule\n", "testbench": "`timescale 1ns/1ps\n\nmodule tb_spi_controller;\n reg clk;\n reg rst_n;\n reg start;\n reg cpol;\n reg cpha;\n reg [7:0] data_in;\n reg [7:0] current_rx;\n reg miso;\n\n wire [7:0] data_out;\n wire busy;\n wire spi_clk;\n wire mosi;\n\n integer errors;\n integer checks;\n reg saw_error;\n\n localparam IDLE = 3'd0;\n localparam LEAD = 3'd2;\n localparam SAMPLE = 3'd3;\n localparam ERROR = 3'd6;\n\n spi_controller dut (\n .clk(clk),\n .rst_n(rst_n),\n .data_in(data_in),\n .start(start),\n .cpol(cpol),\n .cpha(cpha),\n .data_out(data_out),\n .busy(busy),\n .spi_clk(spi_clk),\n .mosi(mosi),\n .miso(miso)\n );\n\n always #5 clk = ~clk;\n\n always @(*) begin\n miso = 1'b1;\n if (dut.state == LEAD || dut.state == SAMPLE) begin\n miso = current_rx[dut.bit_count];\n end\n end\n\n always @(posedge clk) begin\n if (dut.state == ERROR) begin\n saw_error <= 1'b1;\n end\n end\n\n task check;\n input cond;\n input string message;\n begin\n checks = checks + 1;\n if (!cond) begin\n errors = errors + 1;\n $display(\"CHECK FAILED: %0s\", message);\n end\n end\n endtask\n\n task reset_dut;\n begin\n rst_n = 1'b0;\n start = 1'b0;\n cpol = 1'b0;\n cpha = 1'b0;\n data_in = 8'h00;\n current_rx = 8'h00;\n repeat (3) @(posedge clk);\n rst_n = 1'b1;\n @(posedge clk);\n end\n endtask\n\n task launch_transfer;\n input [7:0] tx_byte;\n input [7:0] rx_byte;\n input mode_cpol;\n input mode_cpha;\n begin\n current_rx = rx_byte;\n data_in = tx_byte;\n cpol = mode_cpol;\n cpha = mode_cpha;\n start = 1'b1;\n @(posedge clk);\n start = 1'b0;\n end\n endtask\n\n task wait_for_idle;\n integer timeout;\n begin\n timeout = 0;\n while ((busy || dut.state != IDLE) && timeout < 200) begin\n @(posedge clk);\n timeout = timeout + 1;\n end\n check(timeout < 200, \"SPI transfer timed out\");\n end\n endtask\n\n initial begin\n clk = 1'b0;\n rst_n = 1'b0;\n start = 1'b0;\n cpol = 1'b0;\n cpha = 1'b0;\n data_in = 8'h00;\n current_rx = 8'h00;\n errors = 0;\n checks = 0;\n saw_error = 1'b0;\n\n reset_dut();\n\n launch_transfer(8'hA5, 8'h3C, 1'b0, 1'b0);\n wait_for_idle();\n check(data_out == 8'h3C, \"CPOL=0 CPHA=0 readback mismatch\");\n check(spi_clk == 1'b0, \"spi_clk should idle low when cpol=0\");\n check(busy == 1'b0, \"busy should deassert after transfer\");\n\n launch_transfer(8'h5A, 8'hC3, 1'b1, 1'b1);\n wait_for_idle();\n check(data_out == 8'hC3, \"CPOL=1 CPHA=1 readback mismatch\");\n check(spi_clk == 1'b1, \"spi_clk should idle high when cpol=1\");\n\n saw_error = 1'b0;\n launch_transfer(8'h96, 8'hF0, 1'b0, 1'b1);\n @(posedge clk);\n start = 1'b1;\n @(posedge clk);\n start = 1'b0;\n wait_for_idle();\n check(saw_error, \"start during busy should reach ERROR recovery\");\n check(busy == 1'b0, \"busy should clear after error recovery\");\n\n if (errors == 0) begin\n $display(\"Hint: Total mismatched samples is 0 out of %0d samples\", checks);\n end else begin\n $display(\"Hint: Total mismatched samples is %0d out of %0d samples\", errors, checks);\n end\n $display(\"Mismatches: %0d in %0d samples\", errors, checks);\n $finish;\n end\n\nendmodule\n"} {"task_id": "i2c_controller", "task_number": 2, "description": "Single-byte I2C master controller with an explicit FSM and open-drain scl/sda behavior. On start_tx, if the bus is idle high, generate START, send {addr,rw}, check ACK1, then either write one byte from data_in or read one byte from sda into data_out. For write mode, sample ACK2 after the data byte. During STOP, if scl is externally held low, stay in CLOCK_STRETCH until the line is released. If the controller releases sda for a logic-1 bit but observes sda low during address transmission, treat this as arbitration loss and set ack_err. Any NACK or illegal START condition should also set ack_err before returning to IDLE.", "header": "module i2c_master (\n input clk,\n input rst_n,\n input [6:0] addr,\n input rw,\n input [7:0] data_in,\n input start_tx,\n output reg [7:0] data_out,\n output reg busy,\n output reg ack_err,\n inout scl,\n inout sda\n);", "module_code": "`timescale 1ns/1ps\n\nmodule i2c_master (\n input clk,\n input rst_n,\n input [6:0] addr,\n input rw,\n input [7:0] data_in,\n input start_tx,\n output reg [7:0] data_out,\n output reg busy,\n output reg ack_err,\n inout scl,\n inout sda\n);\n localparam IDLE = 4'd0;\n localparam START_A = 4'd1;\n localparam START_B = 4'd2;\n localparam ADDR_DRIVE = 4'd3;\n localparam ADDR_SAMPLE = 4'd4;\n localparam ACK1 = 4'd5;\n localparam WRITE_DRIVE = 4'd6;\n localparam WRITE_SAMPLE = 4'd7;\n localparam READ = 4'd8;\n localparam ACK2 = 4'd9;\n localparam STOP_A = 4'd10;\n localparam STOP_B = 4'd11;\n localparam CLOCK_STRETCH = 4'd12;\n localparam ARBITRATION_LOST = 4'd13;\n localparam ERROR = 4'd14;\n\n reg [3:0] state;\n reg [2:0] bit_count;\n reg [7:0] tx_shift;\n reg [7:0] rx_shift;\n reg scl_drive_low;\n reg sda_drive_low;\n reg latched_rw;\n\n wire scl_in;\n wire sda_in;\n\n assign scl = scl_drive_low ? 1'b0 : 1'bz;\n assign sda = sda_drive_low ? 1'b0 : 1'bz;\n assign scl_in = scl;\n assign sda_in = sda;\n\n always @(posedge clk or negedge rst_n) begin\n if (!rst_n) begin\n state <= IDLE;\n bit_count <= 3'd0;\n tx_shift <= 8'd0;\n rx_shift <= 8'd0;\n scl_drive_low <= 1'b0;\n sda_drive_low <= 1'b0;\n latched_rw <= 1'b0;\n data_out <= 8'd0;\n busy <= 1'b0;\n ack_err <= 1'b0;\n end else begin\n case (state)\n IDLE: begin\n busy <= 1'b0;\n scl_drive_low <= 1'b0;\n sda_drive_low <= 1'b0;\n bit_count <= 3'd7;\n rx_shift <= 8'd0;\n if (start_tx) begin\n busy <= 1'b1;\n ack_err <= 1'b0;\n tx_shift <= {addr, rw};\n latched_rw <= rw;\n state <= START_A;\n end\n end\n\n START_A: begin\n busy <= 1'b1;\n if (scl_in && sda_in) begin\n sda_drive_low <= 1'b1;\n scl_drive_low <= 1'b0;\n state <= START_B;\n end else begin\n ack_err <= 1'b1;\n state <= ERROR;\n end\n end\n\n START_B: begin\n busy <= 1'b1;\n scl_drive_low <= 1'b1;\n bit_count <= 3'd7;\n sda_drive_low <= ~tx_shift[7];\n state <= ADDR_DRIVE;\n end\n\n ADDR_DRIVE: begin\n busy <= 1'b1;\n scl_drive_low <= 1'b0;\n sda_drive_low <= ~tx_shift[7];\n state <= ADDR_SAMPLE;\n end\n\n ADDR_SAMPLE: begin\n busy <= 1'b1;\n scl_drive_low <= 1'b0;\n if (tx_shift[7] && !sda_in) begin\n ack_err <= 1'b1;\n state <= ARBITRATION_LOST;\n end else if (bit_count == 3'd0) begin\n sda_drive_low <= 1'b0;\n state <= ACK1;\n end else begin\n tx_shift <= {tx_shift[6:0], 1'b0};\n bit_count <= bit_count - 1'b1;\n scl_drive_low <= 1'b1;\n sda_drive_low <= ~tx_shift[6];\n state <= ADDR_DRIVE;\n end\n end\n\n ACK1: begin\n busy <= 1'b1;\n scl_drive_low <= 1'b0;\n sda_drive_low <= 1'b0;\n bit_count <= 3'd7;\n if (!sda_in) begin\n if (latched_rw) begin\n rx_shift <= 8'd0;\n state <= READ;\n end else begin\n tx_shift <= data_in;\n sda_drive_low <= ~data_in[7];\n state <= WRITE_DRIVE;\n end\n end else begin\n ack_err <= 1'b1;\n state <= ERROR;\n end\n end\n\n WRITE_DRIVE: begin\n busy <= 1'b1;\n scl_drive_low <= 1'b0;\n sda_drive_low <= ~tx_shift[7];\n state <= WRITE_SAMPLE;\n end\n\n WRITE_SAMPLE: begin\n busy <= 1'b1;\n scl_drive_low <= 1'b0;\n if (tx_shift[7] && !sda_in) begin\n ack_err <= 1'b1;\n state <= ARBITRATION_LOST;\n end else if (bit_count == 3'd0) begin\n sda_drive_low <= 1'b0;\n state <= ACK2;\n end else begin\n tx_shift <= {tx_shift[6:0], 1'b0};\n bit_count <= bit_count - 1'b1;\n scl_drive_low <= 1'b1;\n sda_drive_low <= ~tx_shift[6];\n state <= WRITE_DRIVE;\n end\n end\n\n READ: begin\n busy <= 1'b1;\n scl_drive_low <= 1'b0;\n sda_drive_low <= 1'b0;\n rx_shift <= {rx_shift[6:0], sda_in};\n if (bit_count == 3'd0) begin\n data_out <= {rx_shift[6:0], sda_in};\n state <= ACK2;\n end else begin\n bit_count <= bit_count - 1'b1;\n end\n end\n\n ACK2: begin\n busy <= 1'b1;\n scl_drive_low <= 1'b0;\n sda_drive_low <= 1'b0;\n if (!latched_rw && sda_in) begin\n ack_err <= 1'b1;\n end\n state <= STOP_A;\n end\n\n STOP_A: begin\n busy <= 1'b1;\n scl_drive_low <= 1'b0;\n sda_drive_low <= 1'b1;\n if (!scl_in) begin\n state <= CLOCK_STRETCH;\n end else begin\n state <= STOP_B;\n end\n end\n\n CLOCK_STRETCH: begin\n busy <= 1'b1;\n scl_drive_low <= 1'b0;\n sda_drive_low <= 1'b1;\n if (scl_in) begin\n state <= STOP_B;\n end\n end\n\n STOP_B: begin\n busy <= 1'b0;\n scl_drive_low <= 1'b0;\n sda_drive_low <= 1'b0;\n state <= IDLE;\n end\n\n ARBITRATION_LOST: begin\n busy <= 1'b0;\n scl_drive_low <= 1'b0;\n sda_drive_low <= 1'b0;\n ack_err <= 1'b1;\n state <= IDLE;\n end\n\n default: begin\n busy <= 1'b0;\n scl_drive_low <= 1'b0;\n sda_drive_low <= 1'b0;\n ack_err <= 1'b1;\n state <= IDLE;\n end\n endcase\n end\n end\n\nendmodule\n", "testbench": "`timescale 1ns/1ps\n\nmodule tb_i2c_master;\n reg clk;\n reg rst_n;\n reg [6:0] addr;\n reg rw;\n reg [7:0] data_in;\n reg start_tx;\n\n tri1 scl;\n tri1 sda;\n\n reg slave_scl_low;\n reg slave_sda_low;\n reg nack_first_ack;\n reg arbitration_request;\n reg [7:0] read_value;\n reg saw_arbitration_lost;\n\n wire [7:0] data_out;\n wire busy;\n wire ack_err;\n\n integer errors;\n integer checks;\n\n localparam IDLE = 4'd0;\n localparam ADDR_DRIVE = 4'd3;\n localparam ADDR_SAMPLE = 4'd4;\n localparam ACK1 = 4'd5;\n localparam READ = 4'd8;\n localparam ACK2 = 4'd9;\n localparam STOP_A = 4'd10;\n localparam CLOCK_STRETCH = 4'd12;\n localparam ARBITRATION_LOST = 4'd13;\n\n assign scl = slave_scl_low ? 1'b0 : 1'bz;\n assign sda = slave_sda_low ? 1'b0 : 1'bz;\n\n i2c_master dut (\n .clk(clk),\n .rst_n(rst_n),\n .addr(addr),\n .rw(rw),\n .data_in(data_in),\n .start_tx(start_tx),\n .data_out(data_out),\n .busy(busy),\n .ack_err(ack_err),\n .scl(scl),\n .sda(sda)\n );\n\n always #5 clk = ~clk;\n\n always @(*) begin\n slave_sda_low = 1'b0;\n\n if (dut.state == ACK1) begin\n if (!nack_first_ack) begin\n slave_sda_low = 1'b1;\n end\n end else if (dut.state == ACK2 && !dut.latched_rw) begin\n slave_sda_low = 1'b1;\n end else if (dut.state == READ) begin\n slave_sda_low = ~read_value[dut.bit_count];\n end\n\n if (arbitration_request &&\n (dut.state == ADDR_DRIVE || dut.state == ADDR_SAMPLE) &&\n dut.tx_shift[7]) begin\n slave_sda_low = 1'b1;\n end\n end\n\n always @(posedge clk) begin\n if (dut.state == ARBITRATION_LOST) begin\n saw_arbitration_lost <= 1'b1;\n end\n end\n\n task check;\n input cond;\n input string message;\n begin\n checks = checks + 1;\n if (!cond) begin\n errors = errors + 1;\n $display(\"CHECK FAILED: %0s\", message);\n end\n end\n endtask\n\n task reset_dut;\n begin\n rst_n = 1'b0;\n start_tx = 1'b0;\n addr = 7'h00;\n rw = 1'b0;\n data_in = 8'h00;\n nack_first_ack = 1'b0;\n arbitration_request = 1'b0;\n slave_scl_low = 1'b0;\n read_value = 8'h00;\n repeat (3) @(posedge clk);\n rst_n = 1'b1;\n @(posedge clk);\n end\n endtask\n\n task launch_transfer;\n input [6:0] tx_addr;\n input tx_rw;\n input [7:0] tx_data;\n begin\n addr = tx_addr;\n rw = tx_rw;\n data_in = tx_data;\n start_tx = 1'b1;\n @(posedge clk);\n start_tx = 1'b0;\n end\n endtask\n\n task wait_for_idle;\n integer timeout;\n begin\n timeout = 0;\n while ((busy || dut.state != IDLE) && timeout < 200) begin\n @(posedge clk);\n timeout = timeout + 1;\n end\n check(timeout < 200, \"I2C transfer timed out\");\n end\n endtask\n\n initial begin\n clk = 1'b0;\n rst_n = 1'b0;\n addr = 7'h00;\n rw = 1'b0;\n data_in = 8'h00;\n start_tx = 1'b0;\n slave_scl_low = 1'b0;\n nack_first_ack = 1'b0;\n arbitration_request = 1'b0;\n read_value = 8'h00;\n saw_arbitration_lost = 1'b0;\n errors = 0;\n checks = 0;\n\n reset_dut();\n\n launch_transfer(7'h42, 1'b0, 8'hA5);\n wait_for_idle();\n check(ack_err == 1'b0, \"write transaction should complete without ack_err\");\n check(busy == 1'b0, \"busy should deassert after write\");\n check(scl === 1'b1 && sda === 1'b1, \"bus should return high after STOP\");\n\n read_value = 8'h3C;\n launch_transfer(7'h42, 1'b1, 8'h00);\n wait_for_idle();\n check(data_out == 8'h3C, \"read transaction should capture slave data\");\n check(ack_err == 1'b0, \"read transaction should complete without ack_err\");\n\n nack_first_ack = 1'b1;\n launch_transfer(7'h42, 1'b0, 8'h55);\n wait_for_idle();\n check(ack_err == 1'b1, \"NACK on first ACK should set ack_err\");\n nack_first_ack = 1'b0;\n\n fork\n begin\n wait (dut.state == STOP_A);\n slave_scl_low = 1'b1;\n repeat (3) @(posedge clk);\n slave_scl_low = 1'b0;\n end\n join_none\n launch_transfer(7'h42, 1'b0, 8'hC3);\n wait_for_idle();\n check(ack_err == 1'b0, \"clock stretch should not force ack_err\");\n\n saw_arbitration_lost = 1'b0;\n fork\n begin\n wait (dut.state == ADDR_SAMPLE && dut.tx_shift[7]);\n arbitration_request = 1'b1;\n repeat (2) @(posedge clk);\n arbitration_request = 1'b0;\n end\n join_none\n launch_transfer(7'h60, 1'b0, 8'hF0);\n wait_for_idle();\n check(saw_arbitration_lost, \"arbitration loss path should be reachable\");\n check(ack_err == 1'b1, \"arbitration loss should set ack_err\");\n\n if (errors == 0) begin\n $display(\"Hint: Total mismatched samples is 0 out of %0d samples\", checks);\n end else begin\n $display(\"Hint: Total mismatched samples is %0d out of %0d samples\", errors, checks);\n end\n $display(\"Mismatches: %0d in %0d samples\", errors, checks);\n $finish;\n end\n\nendmodule\n"}