{"task_id": "spi_controller_simple", "task_number": 1, "description": "Simple SPI master controller with 4-state FSM. Supports CPOL=0, CPHA=0 mode only. On start, latch data_in and shift out MSB-first on mosi while shifting in miso. After 8 bits, output data and return to IDLE.", "header": "module spi_controller_simple (\n input clk,\n input rst_n,\n input [7:0] data_in,\n input start,\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_simple (\n input clk,\n input rst_n,\n input [7:0] data_in,\n input start,\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 // Simplified FSM: IDLE -> SEND -> SAMPLE -> DONE -> IDLE\n localparam IDLE = 2'd0;\n localparam SEND = 2'd1;\n localparam SAMPLE = 2'd2;\n localparam DONE = 2'd3;\n\n reg [1:0] state;\n reg [2:0] bit_count;\n reg [7:0] shift_reg;\n reg spi_clk_en;\n\n // SPI clock generation: derived from main FSM state\n // spi_clk toggles when in SEND state\n wire spi_clk_next = spi_clk_en ? ~spi_clk : 1'b0;\n\n always @(posedge clk or negedge rst_n) begin\n if (!rst_n) begin\n spi_clk <= 1'b0;\n end else begin\n spi_clk <= spi_clk_next;\n end\n end\n\n // Main FSM - single always block drives all registers\n always @(posedge clk or negedge rst_n) begin\n if (!rst_n) begin\n state <= IDLE;\n bit_count <= 3'd0;\n shift_reg <= 8'd0;\n data_out <= 8'd0;\n busy <= 1'b0;\n mosi <= 1'b0;\n spi_clk_en <= 1'b0;\n end else begin\n case (state)\n IDLE: begin\n busy <= 1'b0;\n mosi <= 1'b0;\n spi_clk_en <= 1'b0;\n if (start) begin\n busy <= 1'b1;\n shift_reg <= data_in;\n bit_count <= 3'd7;\n state <= SEND;\n spi_clk_en <= 1'b1;\n end\n end\n\n SEND: begin\n busy <= 1'b1;\n mosi <= shift_reg[7];\n // Check if spi_clk just transitioned (rising edge detection)\n if (spi_clk == 1'b0) begin\n state <= SAMPLE;\n end\n end\n\n SAMPLE: begin\n busy <= 1'b1;\n // Shift in miso\n shift_reg <= {shift_reg[6:0], miso};\n if (bit_count == 3'd0) begin\n data_out <= {shift_reg[6:0], miso};\n state <= DONE;\n spi_clk_en <= 1'b0;\n end else begin\n bit_count <= bit_count - 1'b1;\n state <= SEND;\n end\n end\n\n DONE: begin\n busy <= 1'b0;\n mosi <= 1'b0;\n state <= IDLE;\n spi_clk_en <= 1'b0;\n end\n\n default: begin\n state <= IDLE;\n spi_clk_en <= 1'b0;\n end\n endcase\n end\n end\n\nendmodule\n", "testbench": "`timescale 1ns/1ps\n\nmodule tb_spi_controller_simple;\n reg clk;\n reg rst_n;\n reg start;\n reg [7:0] data_in;\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\n localparam IDLE = 2'd0;\n localparam SEND = 2'd1;\n localparam SAMPLE = 2'd2;\n localparam DONE = 2'd3;\n\n spi_controller_simple dut (\n .clk(clk),\n .rst_n(rst_n),\n .data_in(data_in),\n .start(start),\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 // Simple MISO: shift through pattern\n always @(*) begin\n if (dut.state == SAMPLE) begin\n miso = data_in[dut.bit_count];\n end else begin\n miso = 1'b1;\n end\n end\n\n task check;\n input cond;\n input [127:0] message;\n begin\n checks = checks + 1;\n if (!cond) begin\n errors = errors + 1;\n $display(\"CHECK FAILED: %s\", message);\n end\n end\n endtask\n\n task reset_dut;\n begin\n rst_n = 1'b0;\n start = 1'b0;\n data_in = 8'h00;\n repeat (3) @(posedge clk);\n rst_n = 1'b1;\n @(posedge clk);\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 < 100) begin\n @(posedge clk);\n timeout = timeout + 1;\n end\n check(timeout < 100, \"SPI transfer timed out\");\n end\n endtask\n\n task launch_transfer;\n input [7:0] tx_data;\n begin\n data_in = tx_data;\n start = 1'b1;\n @(posedge clk);\n start = 1'b0;\n end\n endtask\n\n initial begin\n clk = 1'b0;\n rst_n = 1'b0;\n start = 1'b0;\n data_in = 8'h00;\n miso = 1'b1;\n errors = 0;\n checks = 0;\n\n reset_dut();\n\n // Test 1: Simple write 0xA5\n launch_transfer(8'hA5);\n wait_for_idle();\n check(data_out == 8'hA5, \"Simple write should echo back 0xA5\");\n check(busy == 1'b0, \"busy should deassert after transfer\");\n\n // Test 2: Another transfer\n launch_transfer(8'h5A);\n wait_for_idle();\n check(data_out == 8'h5A, \"Transfer 0x5A should echo back\");\n\n // Test 3: All ones\n launch_transfer(8'hFF);\n wait_for_idle();\n check(data_out == 8'hFF, \"Transfer 0xFF should echo back\");\n\n // Test 4: All zeros\n launch_transfer(8'h00);\n wait_for_idle();\n check(data_out == 8'h00, \"Transfer 0x00 should echo back\");\n\n if (errors == 0) begin\n $display(\"All %0d checks passed!\", checks);\n end else begin\n $display(\"%0d of %0d checks failed\", errors, checks);\n end\n\n $finish;\n end\n\nendmodule\n"}