// SPDX-FileCopyrightText: 2020 Efabless Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// SPDX-License-Identifier: Apache-2.0

`default_nettype none

//-----------------------------------------------------------
// SPI controller for Caravel
//-----------------------------------------------------------
// housekeeping_spi.v
//------------------------------------------------------
// General purpose SPI module for the Caravel chip
//------------------------------------------------------
// Written by Tim Edwards
// efabless, inc., September 28, 2020
//------------------------------------------------
// This file is distributed free and open source
//------------------------------------------------

// SCK ---   Clock input
// SDI ---   Data  input
// SDO ---   Data  output
// CSB ---   Chip  select (sense negative)
// idata --- Data from chip to transmit out, in 8 bits
// odata --- Input data to chip, in 8 bits
// addr  --- Decoded address to upstream circuits
// rdstb --- Read strobe, tells upstream circuit that data will be latched.
// wrstb --- Write strobe, tells upstream circuit to latch odata.

// Data format (general purpose):
// 8 bit format
// 1st byte:   Command word (see below)
// 2nd byte:   Address word (register 0 to 255)
// 3rd byte:   Data word    (value 0 to 255)

// Command format:
// 00000000  No operation
// 10000000  Write until CSB raised
// 01000000  Read  until CSB raised
// 11000000  Simultaneous read/write until CSB raised
// 11000100  Pass-through read/write to management area flash SPI until CSB raised
// 11000010  Pass-through read/write to user area flash SPI until CSB raised
// wrnnn000  Read/write as above, for nnn = 1 to 7 bytes, then terminate

// Lower three bits are reserved for future use.
// All serial bytes are read and written msb first.

// Fixed control and status registers

// Address 0 is reserved and contains flags for SPI mode.  This is
// currently undefined and is always value 0.
// Address 1 is reserved and contains manufacturer ID low 8 bits.
// Address 2 is reserved and contains manufacturer ID high 4 bits.
// Address 3 is reserved and contains product ID (8 bits).
// Addresses 4 to 7 are reserved and contain the mask ID (32 bits).
// Addresses 8 to 255 are available for general purpose use.

`define COMMAND  3'b000
`define ADDRESS  3'b001
`define DATA     3'b010
`define USERPASS 3'b100
`define MGMTPASS 3'b101

module housekeeping_spi(reset, SCK, SDI, CSB, SDO,
	sdoenb, idata, odata, oaddr, rdstb, wrstb,
	pass_thru_mgmt, pass_thru_mgmt_delay,
	pass_thru_user, pass_thru_user_delay,
	pass_thru_mgmt_reset, pass_thru_user_reset);

    input reset;
    input SCK;
    input SDI;
    input CSB;
    output SDO;
    output sdoenb;
    input [7:0] idata;
    output [7:0] odata;
    output [7:0] oaddr;
    output rdstb;
    output wrstb; 
    output pass_thru_mgmt;
    output pass_thru_mgmt_delay;
    output pass_thru_user;
    output pass_thru_user_delay;
    output pass_thru_mgmt_reset;
    output pass_thru_user_reset;

    reg  [7:0]  addr;
    reg		wrstb;
    reg		rdstb;
    reg		sdoenb;
    reg  [2:0]  state;
    reg  [2:0]  count;
    reg		writemode;
    reg		readmode;
    reg  [2:0]	fixed;
    wire [7:0]  odata;
    reg  [6:0]  predata;
    wire [7:0]  oaddr;
    reg  [7:0]  ldata;
    reg		pass_thru_mgmt;
    reg		pass_thru_mgmt_delay;
    reg		pre_pass_thru_mgmt;
    reg		pass_thru_user;
    reg		pass_thru_user_delay;
    reg		pre_pass_thru_user;
    wire	csb_reset;

    assign odata = {predata, SDI};
    assign oaddr = (state == `ADDRESS) ? {addr[6:0], SDI} : addr;
    assign SDO = ldata[7];
    assign csb_reset = CSB | reset;
    assign pass_thru_mgmt_reset = pass_thru_mgmt_delay | pre_pass_thru_mgmt;
    assign pass_thru_user_reset = pass_thru_user_delay | pre_pass_thru_user;

    // Readback data is captured on the falling edge of SCK so that
    // it is guaranteed valid at the next rising edge.
    always @(negedge SCK or posedge csb_reset) begin
        if (csb_reset == 1'b1) begin
            wrstb <= 1'b0;
            ldata  <= 8'b00000000;
            sdoenb <= 1'b1;
        end else begin

            // After CSB low, 1st SCK starts command

            if (state == `DATA) begin
            	if (readmode == 1'b1) begin
                    sdoenb <= 1'b0;
                    if (count == 3'b000) begin
                	ldata <= idata;
                    end else begin
                	ldata <= {ldata[6:0], 1'b0};	// Shift out
                    end
                end else begin
                    sdoenb <= 1'b1;
                end

                // Apply write strobe on SCK negative edge on the next-to-last
                // data bit so that it updates data on the rising edge of SCK
                // on the last data bit.
 
                if (count == 3'b111) begin
                    if (writemode == 1'b1) begin
                        wrstb <= 1'b1;
                    end
                end else begin
                    wrstb <= 1'b0;
                end

	    end else if (state == `MGMTPASS || state == `USERPASS) begin
		wrstb <= 1'b0;
		sdoenb <= 1'b0;
            end else begin
                wrstb <= 1'b0;
                sdoenb <= 1'b1;
            end		// ! state `DATA
        end		// ! csb_reset
    end			// always @ ~SCK

    always @(posedge SCK or posedge csb_reset) begin
        if (csb_reset == 1'b1) begin
            // Default state on reset
            addr <= 8'h00;
	    rdstb <= 1'b0;
            predata <= 7'b0000000;
            state  <= `COMMAND;
            count  <= 3'b000;
            readmode <= 1'b0;
            writemode <= 1'b0;
            fixed <= 3'b000;
	    pass_thru_mgmt <= 1'b0;
	    pass_thru_mgmt_delay <= 1'b0;
	    pre_pass_thru_mgmt <= 1'b0;
	    pass_thru_user = 1'b0;
	    pass_thru_user_delay <= 1'b0;
	    pre_pass_thru_user <= 1'b0;
        end else begin
            // After csb_reset low, 1st SCK starts command
            if (state == `COMMAND) begin
		rdstb <= 1'b0;
                count <= count + 1;
        	if (count == 3'b000) begin
	            writemode <= SDI;
	        end else if (count == 3'b001) begin
	            readmode <= SDI;
	        end else if (count < 3'b101) begin
	            fixed <= {fixed[1:0], SDI}; 
	        end else if (count == 3'b101) begin
		    pre_pass_thru_mgmt <= SDI;
	        end else if (count == 3'b110) begin
		    pre_pass_thru_user <= SDI;
		    pass_thru_mgmt_delay <= pre_pass_thru_mgmt;
	        end else if (count == 3'b111) begin
		    pass_thru_user_delay <= pre_pass_thru_user;
		    if (pre_pass_thru_mgmt == 1'b1) begin
			state <= `MGMTPASS;
			pre_pass_thru_mgmt <= 1'b0;
		    end else if (pre_pass_thru_user == 1'b1) begin
			state <= `USERPASS;
			pre_pass_thru_user <= 1'b0;
		    end else begin
	                state <= `ADDRESS;
		    end
	        end
            end else if (state == `ADDRESS) begin
	        count <= count + 1;
	        addr <= {addr[6:0], SDI};
	        if (count == 3'b111) begin
	            state <= `DATA;
		    if (readmode == 1'b1) begin
			rdstb <= 1'b1;
		    end
	        end else begin
		    rdstb <= 1'b0;
		end

            end else if (state == `DATA) begin
	        predata <= {predata[6:0], SDI};
	        count <= count + 1;
	        if (count == 3'b111) begin
	            if (fixed == 3'b001) begin
	                state <= `COMMAND;
	            end else if (fixed != 3'b000) begin
	                fixed <= fixed - 1;
	                addr <= addr + 1;	// Auto increment address (fixed)
	            end else begin	
	                addr <= addr + 1;	// Auto increment address (streaming)
	            end
		    if (readmode == 1'b1) begin
			rdstb <= 1'b1;
		    end
	        end else begin
		    rdstb <= 1'b0;
		end
	    end else if (state == `MGMTPASS) begin
		pass_thru_mgmt <= 1'b1;
	    end else if (state == `USERPASS) begin
		pass_thru_user <= 1'b1;
            end		// ! state `DATA | `MGMTPASS | `USERPASS
        end		// ! csb_reset 
    end			// always @ SCK

endmodule // housekeeping_spi
`default_nettype wire