module SPI_FRAM_Module(
  input wire SI,
  output wire SO,
  input reg SCK,
  input reg nCS,
  output reg [7:0] opcode,		//contains the command which controls the FRAM
  output reg [23:0] addr,		//contains current address that the memory is reading/writing
  reg [7:0] mem_data [1023:0],	//contains the memory data
  reg [7:0] stat_reg,			//stat_reg Bit 0 is 1 while waking up from Hibernate
  reg hibernate);				//if true, memory is in hibernation

  reg [2:0] bitcnt_rcv;      	//counts the bits of the current byte when reading from SPI
  reg [2:0] bitcnt_snd;      	//counts sent bits for the current sent byte when writing to SPI
  reg [2:0] bitcnt_mem_write;	//counts bits written to memory for the current received byte
  reg byte_received;          	//gets high when a full byte is received
  reg [7:0] byte_data_received;	//contains the data of last received byte
  reg [3:0] byte_count; 		//counts the bytes of one message; is reset when a new message starts
  reg [7:0] byte_data_sent; 	//contains the sent byte after transmission
  reg send_data; 				//is set by opcode commands Read status register and Read memory when writing to SPI
  reg write_to_memory; 			////is set by opcode WRITE, when high, incoming Bits from SI are written to the memory at a specific address.
  integer i; 					//countdown variable for status register read
 

  initial begin //values are set to startup values of FRAM
    opcode = 8'h00;						
    stat_reg = 8'b00100000; 			
    addr = 24'h000000;					
    byte_count = 4'b0000;				
    byte_data_sent = 8'h00;				
    bitcnt_rcv = 3'b000; 				
    bitcnt_snd = 3'b111; 				
    bitcnt_mem_write = 3'b111; 			
    byte_received = 1'b0; 				
    byte_data_received = 8'b00000000;
    send_data = 0; 						
    write_to_memory = 0; 				
    i = 8; 							
    hibernate = 0;					
    $readmemh("memory.txt", mem_data); //initializes the memory with the contents of memory.txt
  end

  //receive incoming Bits and organize them bytewise
  always @(posedge SCK) begin
    if (bitcnt_rcv == 3'b111) begin
      byte_count <= byte_count + 4'b0001;
    end
    bitcnt_rcv <= bitcnt_rcv + 3'b001;
    byte_data_received <= {byte_data_received[6:0], SI};

    //when opcode WRÍTE is executed, the incoming bytes are written to memory
    if (write_to_memory == 1 && nCS == 0) begin
      mem_data[addr][bitcnt_mem_write+3'b001] = byte_data_received[0];

      if(bitcnt_mem_write == 3'b000) begin
        addr <= addr + 1;
        bitcnt_mem_write <= 3'b111;
      end
      bitcnt_mem_write <= bitcnt_mem_write - 1;
    end
  end
  always @(posedge SCK) byte_received <= (nCS == 0) && (bitcnt_rcv==3'b111);

  //TRANSMISSION
  //Read out memory and write to SPI, starts at addr
  always @(negedge SCK) begin
    if(send_data == 1 && nCS == 0)
      begin
        byte_data_sent <= {byte_data_sent[6:0], mem_data[addr][bitcnt_snd]};
        bitcnt_snd <= bitcnt_snd - 1;
        if (bitcnt_snd == 3'b000) begin
          addr <= addr + 1;
          bitcnt_snd <= 3'b111;
        end
      end
    //write status register to SO when opcode RDSR is sent
    else if (opcode == 8'h05 && nCS == 0 && i > 0) begin
      byte_data_sent <= {byte_data_sent[6:0], stat_reg[i-1]};
      i = i - 1;
    end
  end
  assign SO = byte_data_sent[0];  // MSB of the transmission is the lsb of byte_data_sent

  

//the following block resets counters when a message has finished
  always @ (posedge nCS) begin 
    if (opcode == 8'h06) begin 	//When WLEN opcode is executed, nCS needs to be reset.
      							//Since the message is not finished, no counters should be reset when executing WLEN
    end
    else if (opcode == 8'hb9 && nCS == 1) hibernate = 1; //When hibernation opcode 8'hb9 is sent, the device goes into hibernation
    else begin
      byte_count = 8'h00;
      bitcnt_rcv = 3'b000; 
      bitcnt_snd = 3'b111;
      bitcnt_mem_write = 3'b111;
      byte_data_received = 8'h00;
    end
    send_data = 0; 		//disables sending data
    write_to_memory = 0; // disables writing to memory
    stat_reg[1] = 0; //reset WEL when writing to memory has finished
  end

  //reset hibernate
  always @ (negedge nCS) hibernate = 0;
//when a byte is received the FRAM-model reacts dependent on the number of bytes received in the current nCS low state, i. e. in one message.
  
  always @ (posedge byte_received) begin
    case (byte_count) 
      //Byte 1 of message
      4'h1: begin //counting starts at 1, not 0.
        case (byte_data_received)
          8'h03: //READ Op-code
            opcode = 8'h03;
          8'h06: begin //WREN Op-Code
            opcode = 8'h06;
            #25; //wait one clock for nCS to get low
            if (nCS == 1) stat_reg [1] = 1; //Set WEL Bit in Status Register after one clock cycle
          end
          //READ STATUS REGISTER Op-Code
          8'h05: opcode = 8'h05;
          //HIBERNATE Op-Code
          8'hb9: begin
            opcode = 8'hb9;
          end
        endcase
      end
      //Byte 2 of message
      4'h2: begin
        case (byte_data_received)
          //WRITE
          8'h02: //WRITE Op-code, only if WREN op-code was executed, WRITE Op-code is permitted.
            if (opcode == 8'h06) opcode = 8'h02;   
          default: 
            //READ - get highest address byte
            if (opcode == 8'h03) //upper four bits are not used and are always 0
              //the address is shifted in from right to left. Byte_data_received is the highest byte of the address
              addr <= {4'b0000, 12'h000, byte_data_received}; 
        endcase
      end
      //Byte 3 of message
      4'h3: begin
        case (byte_data_received)
          default:
            //READ - get middle address byte
            if (opcode == 8'h03) //if opcode is read, the byte_data_received
              					 //is the next byte of the address, followed by 1 byte
              addr <= {4'b0000, 4'b0000, addr[7:0], byte_data_received};
          //WRITE - get highest address byte
          else if (opcode == 8'h02 && stat_reg[1] == 1'b1)
            addr <= {4'b0000, 12'h000, byte_data_received}; 
        endcase
      end
      //Byte 4 of message
      4'h4: begin
        case (byte_data_received)
          default:
            //READ - get the lowest byte of the address
            if (opcode == 8'h03) begin
              addr <= {addr[15:0], byte_data_received};
              send_data = 1; //sets the flag which starts sending every bit out of SO at memory address "addr".
            end
          //WRITE - get middle address byte
          else if (opcode == 8'h02 && stat_reg[1] == 1'b1)
            addr <= {4'b000, 4'b0000, addr[7:0], byte_data_received};
        endcase
      end
      //Byte 5 of message
      4'h5: begin
        case (byte_data_received)
          default:
            //WRITE - get lowest address byte and enable write_to_memory, the following bytes are data.
            if (opcode == 8'h02 && stat_reg[1] == 1'b1) begin
              addr <= {addr[15:0], byte_data_received};
              write_to_memory = 1; //set write to memory and wait one clock 
            end
        endcase
      end
    endcase
  end
endmodule