////////////////////////////////////////////////////////////////////////////////// // Company : NPK TAIR // Engineer : Yuri Donskoy // // Create Date (dd/mm/yyyy) : // Design Name : // Module Name : // Project Name : // Target Devices : // Tool versions : // Description : // // Dependencies : // // Revision : 1.0 - It only send data (no miso port) // Additional Comments : MISO port need to be add. What about multiple slave select? // ////////////////////////////////////////////////////////////////////////////////// module SpiMaster ( clk_i, rst_i, data_i, valid_i, ready_o, mosi_o, sck_o, ss_o ); //================================================================================ // // FUNCTIONS // //================================================================================ function integer bit_num; input integer value; begin bit_num = 0; while (value > 0) begin value = value >> 1; bit_num = bit_num + 1; end end endfunction //================================================================================ // // PARAMETER/LOCALPARAM // //================================================================================ parameter CLK_DIVISOR_POWER = 4; //WAS 2 !! DONT FORGET TO CHANGE! parameter DATA_WIDTH = 24; parameter CPOL = 0; parameter CPHA = 0; parameter DATA_DIRECTION = "MSBT"; // MSB or LSB parameter EN_START_DELAY = "NO"; // YES or NO localparam BIT_CNT_W = bit_num(DATA_WIDTH); //================================================================================ // // STATE MACHINE STATES // //================================================================================ localparam SM_IDLE_S = 2'b00; localparam SM_START_S = 2'b01; localparam SM_DATA_S = 2'b10; localparam SM_STOP_S = 2'b11; //================================================================================ // // PORTS // //================================================================================ input clk_i; input rst_i; input [DATA_WIDTH-1:0] data_i; input valid_i; output ready_o; output reg mosi_o; output reg sck_o; output reg ss_o; //================================================================================ // // REG/WIRE // //================================================================================ reg [1:0] sm_curr_state; reg [1:0] sm_next_state; reg sm_clk_div_en; // Clock divider outputs wire clk_divider_redge; wire clk_divider_fedge; // Bits counter reg [BIT_CNT_W-1:0] bit_cnt_r; reg [BIT_CNT_W-1:0] bit_cnt_next; // Data buffers reg [DATA_WIDTH-1:0] tx_buffer_r; reg [DATA_WIDTH-1:0] tx_buffer_next; wire [DATA_WIDTH-1:0] tx_buffer_shifted; wire tx_curr_bit; // Output data next reg mosi_next; reg sck_next; reg ss_next; // Edges wire mosi_shift_edge; wire ss_start_edge; wire ss_stop_edge; wire sck_leading_edge; wire sck_trailing_edge; //================================================================================ // // INTEGER/GENVAR // //================================================================================ //================================================================================ // // ASSIGN // //================================================================================ assign mosi_shift_edge = (CPHA[0] == 1'b1) && (EN_START_DELAY != "YES") || (CPHA[0] == 1'b0) && (EN_START_DELAY == "YES") ? clk_divider_fedge : clk_divider_redge; assign ss_start_edge = (EN_START_DELAY == "YES") ? clk_divider_fedge : clk_divider_redge; assign ss_stop_edge = (EN_START_DELAY == "YES") ? clk_divider_redge : clk_divider_fedge; assign sck_leading_edge = (EN_START_DELAY == "YES") ? clk_divider_redge : clk_divider_fedge; assign sck_trailing_edge = (EN_START_DELAY == "YES") ? clk_divider_fedge : clk_divider_redge; assign tx_buffer_shifted = (DATA_DIRECTION == "MSB") ? tx_buffer_r << 1 : tx_buffer_r >> 1; assign tx_curr_bit = (DATA_DIRECTION == "MSB") ? tx_buffer_r[DATA_WIDTH-1] : tx_buffer_r[0]; assign ready_o = sm_curr_state == SM_IDLE_S; //================================================================================ // // CODING // //================================================================================ // Sequential logic always @(posedge clk_i or posedge rst_i) begin if (rst_i) begin sm_curr_state <= 0; tx_buffer_r <= {DATA_WIDTH{1'b0}}; bit_cnt_r <= {BIT_CNT_W{1'b0}}; mosi_o <= 1'b0; sck_o <= CPOL[0]; ss_o <= 1'b1; end else begin sm_curr_state <= sm_next_state; tx_buffer_r <= tx_buffer_next; bit_cnt_r <= bit_cnt_next; mosi_o <= mosi_next; sck_o <= sck_next; ss_o <= ss_next; end end // Combinational logic always @(*) begin sm_next_state = SM_IDLE_S; tx_buffer_next = tx_buffer_r; mosi_next = mosi_o; sck_next = sck_o; ss_next = ss_o; sm_clk_div_en = 1'b1; bit_cnt_next = bit_cnt_r; case(sm_curr_state) SM_IDLE_S : begin if (valid_i) begin sm_next_state = SM_START_S; end else begin sm_next_state = SM_IDLE_S; end tx_buffer_next = data_i; sm_clk_div_en = 1'b0; bit_cnt_next = {BIT_CNT_W{1'b0}}; end SM_START_S : begin if (ss_start_edge) begin sm_next_state = SM_DATA_S; ss_next = 1'b0; if (!CPHA[0]) begin mosi_next = tx_curr_bit; tx_buffer_next = tx_buffer_shifted; bit_cnt_next = bit_cnt_r + {{(BIT_CNT_W-1){1'b0}}, 1'b1}; end end else begin sm_next_state = SM_START_S; end end SM_DATA_S : begin sm_next_state = SM_DATA_S; if (sck_leading_edge) begin sck_next = ~CPOL[0]; end if (sck_trailing_edge) begin sck_next = CPOL[0]; if (bit_cnt_r == DATA_WIDTH[BIT_CNT_W-1:0]) begin sm_next_state = SM_STOP_S; end end if (mosi_shift_edge) begin mosi_next = tx_curr_bit; tx_buffer_next = tx_buffer_shifted; bit_cnt_next = bit_cnt_r + {{(BIT_CNT_W-1){1'b0}}, 1'b1}; end end SM_STOP_S : begin if (ss_stop_edge) begin if (CPHA[0]) begin mosi_next = tx_curr_bit; end sm_next_state = SM_IDLE_S; ss_next = 1'b1; end else begin sm_next_state = SM_STOP_S; end end endcase end // Clock divider Power2ClkDivider #( .DIVISOR_POWER (CLK_DIVISOR_POWER) ) ClkDividerInst ( .clk_i (clk_i), .rst_i (rst_i), .valid_i (sm_clk_div_en), .signal_o (), .rising_edge_o (clk_divider_redge), .falling_edge_o (clk_divider_fedge) ); endmodule