@@ -0,0 +1,180 @@ | |||
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 |
@@ -0,0 +1,223 @@ | |||
//This testbench verifies all implemented functions of the Module "SPI-FRAM-Module". That contains Read/Write to memory, Status register read and Hibernation. | |||
`timescale 1us/1ns | |||
module SPI_FRAM_tb; | |||
reg SI, SO; //init all registers that are connected to the identical named ports of the FRAM-Module. | |||
reg SCK, nCS; | |||
reg [7:0] opcode; | |||
reg [7:0] mem_data [1023:0] ; | |||
reg [23:0] addr; //3 Byte Memory Address for test only the lower 13 are used (2^13 = 8192) | |||
reg [7:0] stat_reg; | |||
reg hibernate; | |||
SPI_FRAM_Module dut(.nCS(nCS), .SCK(SCK), .SI(SI), .SO(SO), .opcode(opcode), .addr(addr), .mem_data(mem_data),. stat_reg(stat_reg), .hibernate(hibernate)); | |||
initial begin //values are set to startup values of FRAM | |||
nCS = 1'b1; | |||
SCK = 1'b0; | |||
end | |||
//generate 40MHz clock | |||
always @(nCS) begin | |||
while (nCS == 0) #12.5 SCK = ~SCK; | |||
if (nCS == 1) SCK = 0; | |||
end | |||
initial begin | |||
$dumpfile("dump.vcd"); | |||
$dumpvars; | |||
SI = 0; | |||
nCS = 0; | |||
//TEST READ MEMORY | |||
// Sends 8'b00000011 as Read Opcode | |||
SI = 0; | |||
#25 SI = 0; | |||
#25 SI = 0; | |||
#25 SI = 0; | |||
#25 SI = 0; | |||
#25 SI = 0; | |||
#25 SI = 1; | |||
#25 SI = 1; | |||
#25; assert (opcode == 8'h03); | |||
//first byte (only the highest 4 bits are used) of 20-Bit address | |||
SI = 0; | |||
#25 SI = 0; | |||
#25 SI = 0; | |||
#25 SI = 0; | |||
#25 SI = 0; | |||
#25 SI = 0; | |||
#25 SI = 0; | |||
#25 SI = 0; | |||
//second Byte of 20-Bit address | |||
#25 SI = 0; | |||
#25 SI = 0; | |||
#25 SI = 0; | |||
#25 SI = 0; | |||
#25 SI = 0; | |||
#25 SI = 0; | |||
#25 SI = 0; | |||
#25 SI = 0; | |||
//third byte of address | |||
#25 SI = 0; | |||
#25 SI = 0; | |||
#25 SI = 0; | |||
#25 SI = 0; | |||
#25 SI = 0; | |||
#25 SI = 0; | |||
#25 SI = 1; | |||
#25 SI = 1; | |||
//read one Byte (200clocks/25 clocks per bit = 8 bit) | |||
#25 assert (addr == 24'h000003); //check address | |||
//check for correct writing to SPI out of memory | |||
#25 assert (SO == 0); | |||
#25 assert (SO == 0); | |||
#25 assert (SO == 1); | |||
#25 assert (SO == 1); | |||
#25 assert (SO == 0); | |||
#25 assert (SO == 0); | |||
#25 assert (SO == 1); | |||
#25 assert (SO == 1); | |||
//Message is finished, so nCS is not active | |||
nCS = 1; | |||
#50 nCS = 0; //enable nCS after 50 clock cycles for next test | |||
//TEST WRITE MEMORY | |||
//send WREN opcode to set WEL bit | |||
SI = 0; | |||
#25 SI = 0; | |||
#25 SI = 0; | |||
#25 SI = 0; | |||
#25 SI = 0; | |||
#25 SI = 1; | |||
#25 SI = 1; | |||
#25 SI = 0; | |||
//to set WEL-bit, nCS needs to be high (inactive) | |||
#25 nCS = 1; | |||
#25 assert (opcode == 8'h06); //check if the WREN-opcode was recognized | |||
#25 nCS = 0; | |||
assert (stat_reg[1] == 1'b1); //check if WEL-Bit is set | |||
//after stat_reg is set, the next opcode WRITE can be received | |||
SI = 0; | |||
#25 SI = 0; | |||
#25 SI = 0; | |||
#25 SI = 0; | |||
#25 SI = 0; | |||
#25 SI = 0; | |||
#25 SI = 1; | |||
#25 SI = 0; | |||
#25 assert (opcode == 8'h02); | |||
//the next 3 following Bytes are the address. the upper 4 Bits are cut off. | |||
//Highest Byte 1 | |||
SI = 0; | |||
#25 SI = 0; | |||
#25 SI = 0; | |||
#25 SI = 0; | |||
#25 SI = 0; | |||
#25 SI = 0; | |||
#25 SI = 0; | |||
#25 SI = 0; | |||
//second address Byte | |||
#25 SI = 0; | |||
#25 SI = 0; | |||
#25 SI = 0; | |||
#25 SI = 0; | |||
#25 SI = 0; | |||
#25 SI = 0; | |||
#25 SI = 0; | |||
#25 SI = 0; | |||
//third address Byte | |||
#25 SI = 0; | |||
#25 SI = 0; | |||
#25 SI = 0; | |||
#25 SI = 0; | |||
#25 SI = 0; | |||
#25 SI = 0; | |||
#25 SI = 1; | |||
#25 SI = 0; | |||
#25 assert (addr == 24'h000002); | |||
//the following SI is written to the memory at the address "addr" | |||
//one Byte is written | |||
assert (mem_data[24'h000002] == 8'hFF); //check data at addr before to see difference after writing to it | |||
SI = 0; | |||
#25 SI = 1; | |||
#25 SI = 1; | |||
#25 SI = 1; | |||
#25 SI = 0; | |||
#25 SI = 0; | |||
#25 SI = 1; | |||
#25 SI = 0; | |||
//Message is finished, so nCS is not active | |||
#12.5 nCS = 1; | |||
#50 nCS = 0; //enable nCS after 50 clock cycles for next test | |||
assert (mem_data[24'h000002] == 8'h72); //check if the operation wrote the correct data to the correct address; 8'h72 is used because it is not symmetrical and the first and last bit are 0. Since the memory (see memory.txt) has 8'hFF written to all other bytes it is easily recognized if a 0 was accidentally written elsewhere. | |||
assert (mem_data[24'h000001] == 8'hFF); | |||
//test to see if write accidentally wrote in the next memory byte | |||
assert (mem_data[24'h000003] == 8'h33); | |||
//test opcode read status register | |||
SI = 0; | |||
#25 SI = 0; | |||
#25 SI = 0; | |||
#25 SI = 0; | |||
#25 SI = 0; | |||
#25 SI = 1; | |||
#25 SI = 0; | |||
#25 SI = 1; | |||
#25 assert (opcode == 8'h05); | |||
//Test correct writing to SPI of status register (stat_reg = 8'h20) | |||
#25 assert (SO == 0); | |||
#25 assert (SO == 0); | |||
#25 assert (SO == 1); | |||
#25 assert (SO == 0); | |||
#25 assert (SO == 0); | |||
#25 assert (SO == 0); | |||
#25 assert (SO == 0); | |||
#25 assert (SO == 0); | |||
assert (stat_reg == 8'h20); | |||
//Message is finished, so nCS is not active | |||
#50 nCS = 1; | |||
#25 nCS = 0; //enable nCS and SCK after 50 clock cycles for next test | |||
//TEST HIBERNATE MODE | |||
//send opcode | |||
SI = 1; | |||
#25 SI = 0; | |||
#25 SI = 1; | |||
#25 SI = 1; | |||
#25 SI = 1; | |||
#25 SI = 0; | |||
#25 SI = 0; | |||
#25 SI = 1; | |||
#25 assert (opcode == 8'hb9); | |||
//Message is finished, so nCS is not active | |||
//hibernate is set on the rising edge of nCS and reset at the falling edge of nCS | |||
nCS = 1; | |||
#25 assert (hibernate == 1); | |||
#500 nCS = 0; //enable nCS and SCK after 50 clock cycles for next test | |||
#25 assert (hibernate == 0); | |||
end | |||
endmodule |
@@ -0,0 +1,199 @@ | |||
`include "SPI.sv" | |||
module FRAM( | |||
input i_clk, //Module (Module CLock = SPI Clock) | |||
input i_nreset, | |||
input logic [19:0] i_adr, //Memorycell adress in FRAM | |||
input logic [7:0] i_data, //data to write | |||
output logic [7:0] o_data, //data to read | |||
input logic i_rw, //Read = 1, Write = 0 | |||
input logic i_status, //If 1 Read Staut register | |||
input logic i_hbn, //If 1 FRAM will enter Hibernation Mode | |||
input logic i_cready, //Starts transmission | |||
output logic o_busy, //Indicates FRAM Busy | |||
// SPI Interface | |||
output o_SPI_Clk, | |||
input i_SPI_MISO, | |||
output o_SPI_MOSI, | |||
output o_SPI_CS_n | |||
); | |||
//FRAM SPI OP Codes | |||
//Write Enable Control | |||
localparam WREN = 8'h06; //Set Write enable latch | |||
localparam WRDI = 8'h04; //Reset write enable latch | |||
//Register Access | |||
localparam RDSR = 8'h05; //Read Status Register | |||
localparam WRSR = 8'h01; //Write Status Register | |||
//Memory Write | |||
localparam WRITE = 8'h02; //Write Memory Data | |||
//Memory Read | |||
localparam READ = 8'h03; //Read Memory Data | |||
localparam FSTRT = 8'h0B; //Fast read memory Data | |||
//Special Sector Memory Access | |||
localparam SSWR = 8'h42; //Spcial Sector Write | |||
localparam SSRD = 8'h4B; //Special Sector Read | |||
//Identification and serial Number | |||
localparam RDID = 8'h9F; //Read Device ID | |||
localparam RUID = 8'h4C; //Read Unique ID | |||
localparam WRSN = 8'hC2; //Write Serial Number | |||
localparam RDSN = 8'hC3; //Read Serial Number | |||
//Low Power Modes | |||
localparam DPD = 8'hBA; // Enter Deep Power-Down | |||
localparam HBN = 8'hB9; // Enter Hibernate Mode | |||
//end FRAM SPI OP Codes | |||
//Controller Specific | |||
logic [3:0] state; | |||
// SPI Specific | |||
parameter SPI_MODE = 0; // CPOL = 0, CPHA = 0 | |||
parameter CLKS_PER_HALF_BIT = 2; // 25MHz | |||
parameter MAX_BYTES_PER_CS = 5; // 5 bytes max per chip select cycle | |||
parameter CS_INACTIVE_CLKS = 1; // Adds delay (1clk) between cycles | |||
logic [7:0] r_Master_TX_Byte = 0; | |||
logic r_Master_TX_DV = 1'b0; | |||
logic w_Master_TX_Ready; | |||
logic w_Master_RX_DV; | |||
logic [7:0] w_Master_RX_Byte; | |||
logic [$clog2(MAX_BYTES_PER_CS+1)-1:0] w_Master_RX_Count, r_Master_TX_Count = 3'h1; //Standard 1 Byte pro CS Cycle | |||
SPI_Master_With_Single_CS | |||
#(.SPI_MODE(SPI_MODE), //SPI Mode 0-3 | |||
.CLKS_PER_HALF_BIT(CLKS_PER_HALF_BIT), //sets Frequency of SPI_CLK | |||
.MAX_BYTES_PER_CS(MAX_BYTES_PER_CS), //Maximum Bytes per CS Cycle | |||
.CS_INACTIVE_CLKS(CS_INACTIVE_CLKS) //Amount of Time holding CS Low befor next command | |||
) SPI | |||
( | |||
// Control/Data Signals, | |||
.i_Rst_L(i_nreset), // FPGA Reset | |||
.i_Clk(i_clk), // FPGA Clock | |||
// TX (MOSI) Signals | |||
.i_TX_Count(r_Master_TX_Count), // Number of bytes per CS | |||
.i_TX_Byte(r_Master_TX_Byte), // Byte to transmit on MOSI | |||
.i_TX_DV(r_Master_TX_DV), // Data Valid Pulse with i_TX_Byte | |||
.o_TX_Ready(w_Master_TX_Ready), // Transmit Ready for Byte | |||
// RX (MISO) Signals | |||
.o_RX_Count(w_Master_RX_Count), // Index of RX'd byte | |||
.o_RX_DV(w_Master_RX_DV), // Data Valid pulse (1 clock cycle) | |||
.o_RX_Byte(w_Master_RX_Byte), // Byte received on MISO | |||
// SPI Interface | |||
.o_SPI_Clk(o_SPI_Clk), | |||
.i_SPI_MISO(i_SPI_MISO), | |||
.o_SPI_MOSI(o_SPI_MOSI), | |||
.o_SPI_CS_n(o_SPI_CS_n) | |||
); | |||
//end SPI Specific | |||
task SPI_SendByte(input [7:0] data); | |||
@(posedge i_clk); | |||
r_Master_TX_Byte <= data; | |||
r_Master_TX_DV <= 1'b1; | |||
@(posedge i_clk); | |||
r_Master_TX_DV <= 1'b0; | |||
@(posedge i_clk); | |||
@(posedge w_Master_TX_Ready); | |||
endtask //end SPI_SendByte | |||
//FRAM Tasks | |||
task FRAM_Write(input [19:0] adr, input [7:0] data); //vgl. Fig.11 | |||
logic [7:0] value; | |||
value <= 8'h0; | |||
//Set Write Enable | |||
r_Master_TX_Count <= 3'b1; //1Byte Transaction | |||
SPI_SendByte(WREN); | |||
//Write to fram | |||
r_Master_TX_Count <= 3'h5; //5 Byte Transaction | |||
SPI_SendByte(WRITE); //OPCode | |||
SPI_SendByte({4'hF,adr[19:16]}); //Adress [23-16] | |||
SPI_SendByte(adr[15:8]); //Adress [15-8] | |||
SPI_SendByte(adr[7:0]); //Adress [7-0] | |||
SPI_SendByte(data); //Data [7:0] | |||
//Reset Write Disable and Verify | |||
do begin | |||
r_Master_TX_Count <= 3'b1; //1Byte Transaction | |||
SPI_SendByte(WRDI); //Set Write Disable | |||
FRAM_Read_Status(value); //Lese Status Register | |||
end while(((value & 8'h2) >> 1) != 0); | |||
endtask //end FRAM_Write | |||
task FRAM_Read(input [19:0] adr, output [7:0] data); //vgl. Fig12 | |||
r_Master_TX_Count <= 3'h5; //5 Byte Transaction | |||
SPI_SendByte(READ); //Opcode | |||
SPI_SendByte({4'hF,adr[19:16]}); //Adress [23-16] | |||
SPI_SendByte(adr[15:8]); //Adress [15-8] | |||
SPI_SendByte(adr[7:0]); //Adress [7-0] | |||
SPI_SendByte(8'hAA); //Dummy Bits, read byte in w_Master_RX_Byte | |||
data = w_Master_RX_Byte; | |||
endtask //end FRAM_READ | |||
task FRAM_Read_Status(output [7:0] data); //vgl. Fig9 | |||
r_Master_TX_Count <= 3'h2; //2 Byte Transaction | |||
SPI_SendByte(RDSR); //OpCode | |||
SPI_SendByte(8'hFD); //Dummy Bits, read byte in w_Master_RX_Byte | |||
data = w_Master_RX_Byte; | |||
endtask //FRAM_Read_Status | |||
task FRAM_Hibernation(); //vgl. Fig22 | |||
r_Master_TX_Count <= 3'h1; //1 Byte Transaction | |||
SPI_SendByte(HBN); | |||
endtask //FRAM_Hibernation | |||
//end FRAM Tasks | |||
always @(posedge i_clk or negedge i_nreset) begin | |||
state[0] = i_cready; | |||
state[1] = i_hbn; | |||
state[2] = i_status; | |||
state[3] = i_rw; | |||
if(~i_nreset) begin //Modul Reset | |||
o_data <= 8'h00; | |||
end //end if | |||
if(w_Master_TX_Ready) begin | |||
case(state) inside | |||
4'b??11: FRAM_Hibernation(); | |||
4'b?101: FRAM_Read_Status(o_data); | |||
4'b1001: FRAM_Read(i_adr, o_data); | |||
4'b0001: FRAM_Write(i_adr, i_data); | |||
default:; | |||
endcase //endcase | |||
end //endif | |||
end //end always | |||
assign o_busy = w_Master_TX_Ready; | |||
endmodule |
@@ -0,0 +1,188 @@ | |||
/////////////////////////////////////////////////////////////////////////////// | |||
//Source: https://github.com/nandland/spi-master/tree/master/Verilog/source | |||
//Description: SPI (Serial Peripheral Interface) Master | |||
// With single chip-select (AKA Slave Select) capability | |||
// | |||
// Supports arbitrary length byte transfers. | |||
// | |||
// Instantiates a SPI Master and adds single CS. | |||
// If multiple CS signals are needed, will need to use different | |||
// module, OR multiplex the CS from this at a higher level. | |||
// | |||
// Note: i_Clk must be at least 2x faster than i_SPI_Clk | |||
// | |||
// Parameters: SPI_MODE, can be 0, 1, 2, or 3. See above. | |||
// Can be configured in one of 4 modes: | |||
// Mode | Clock Polarity (CPOL/CKP) | Clock Phase (CPHA) | |||
// 0 | 0 | 0 | |||
// 1 | 0 | 1 | |||
// 2 | 1 | 0 | |||
// 3 | 1 | 1 | |||
// | |||
// CLKS_PER_HALF_BIT - Sets frequency of o_SPI_Clk. o_SPI_Clk is | |||
// derived from i_Clk. Set to integer number of clocks for each | |||
// half-bit of SPI data. E.g. 100 MHz i_Clk, CLKS_PER_HALF_BIT = 2 | |||
// would create o_SPI_CLK of 25 MHz. Must be >= 2 | |||
// | |||
// MAX_BYTES_PER_CS - Set to the maximum number of bytes that | |||
// will be sent during a single CS-low pulse. | |||
// | |||
// CS_INACTIVE_CLKS - Sets the amount of time in clock cycles to | |||
// hold the state of Chip-Selct high (inactive) before next | |||
// command is allowed on the line. Useful if chip requires some | |||
// time when CS is high between trasnfers. | |||
/////////////////////////////////////////////////////////////////////////////// | |||
`include "iSPI.sv" | |||
module SPI_Master_With_Single_CS | |||
#(parameter SPI_MODE = 0, | |||
parameter CLKS_PER_HALF_BIT = 2, | |||
parameter MAX_BYTES_PER_CS = 1, | |||
parameter CS_INACTIVE_CLKS = 1) | |||
( | |||
// Control/Data Signals, | |||
input i_Rst_L, // FPGA Reset | |||
input i_Clk, // FPGA Clock | |||
// TX (MOSI) Signals | |||
input [$clog2(MAX_BYTES_PER_CS+1)-1:0] i_TX_Count, // # bytes per CS low | |||
input [7:0] i_TX_Byte, // Byte to transmit on MOSI | |||
input i_TX_DV, // Data Valid Pulse with i_TX_Byte | |||
output o_TX_Ready, // Transmit Ready for next byte | |||
// RX (MISO) Signals | |||
output reg [$clog2(MAX_BYTES_PER_CS+1)-1:0] o_RX_Count, // Index RX byte | |||
output o_RX_DV, // Data Valid pulse (1 clock cycle) | |||
output [7:0] o_RX_Byte, // Byte received on MISO | |||
// SPI Interface | |||
output o_SPI_Clk, | |||
input i_SPI_MISO, | |||
output o_SPI_MOSI, | |||
output o_SPI_CS_n | |||
); | |||
localparam IDLE = 2'b00; | |||
localparam TRANSFER = 2'b01; | |||
localparam CS_INACTIVE = 2'b10; | |||
reg [1:0] r_SM_CS; | |||
reg r_CS_n; | |||
reg [$clog2(CS_INACTIVE_CLKS)-1:0] r_CS_Inactive_Count; | |||
reg [$clog2(MAX_BYTES_PER_CS+1)-1:0] r_TX_Count; | |||
wire w_Master_Ready; | |||
// Instantiate Master | |||
SPI_Master | |||
#(.SPI_MODE(SPI_MODE), | |||
.CLKS_PER_HALF_BIT(CLKS_PER_HALF_BIT) | |||
) SPI_Master_Inst | |||
( | |||
// Control/Data Signals, | |||
.i_Rst_L(i_Rst_L), // FPGA Reset | |||
.i_Clk(i_Clk), // FPGA Clock | |||
// TX (MOSI) Signals | |||
.i_TX_Byte(i_TX_Byte), // Byte to transmit | |||
.i_TX_DV(i_TX_DV), // Data Valid Pulse | |||
.o_TX_Ready(w_Master_Ready), // Transmit Ready for Byte | |||
// RX (MISO) Signals | |||
.o_RX_DV(o_RX_DV), // Data Valid pulse (1 clock cycle) | |||
.o_RX_Byte(o_RX_Byte), // Byte received on MISO | |||
// SPI Interface | |||
.o_SPI_Clk(o_SPI_Clk), | |||
.i_SPI_MISO(i_SPI_MISO), | |||
.o_SPI_MOSI(o_SPI_MOSI) | |||
); | |||
// Purpose: Control CS line using State Machine | |||
always @(posedge i_Clk or negedge i_Rst_L) | |||
begin | |||
if (~i_Rst_L) | |||
begin | |||
r_SM_CS <= IDLE; | |||
r_CS_n <= 1'b1; // Resets to high | |||
r_TX_Count <= 0; | |||
r_CS_Inactive_Count <= CS_INACTIVE_CLKS; | |||
end | |||
else | |||
begin | |||
case (r_SM_CS) | |||
IDLE: | |||
begin | |||
if (r_CS_n & i_TX_DV) // Start of transmission | |||
begin | |||
r_TX_Count <= i_TX_Count - 1; // Register TX Count | |||
r_CS_n <= 1'b0; // Drive CS low | |||
r_SM_CS <= TRANSFER; // Transfer bytes | |||
end | |||
end | |||
TRANSFER: | |||
begin | |||
// Wait until SPI is done transferring do next thing | |||
if (w_Master_Ready) | |||
begin | |||
if (r_TX_Count > 0) | |||
begin | |||
if (i_TX_DV) | |||
begin | |||
r_TX_Count <= r_TX_Count - 1; | |||
end | |||
end | |||
else | |||
begin | |||
r_CS_n <= 1'b1; // we done, so set CS high | |||
r_CS_Inactive_Count <= CS_INACTIVE_CLKS; | |||
r_SM_CS <= CS_INACTIVE; | |||
end // else: !if(r_TX_Count > 0) | |||
end // if (w_Master_Ready) | |||
end // case: TRANSFER | |||
CS_INACTIVE: | |||
begin | |||
if (r_CS_Inactive_Count > 0) | |||
begin | |||
r_CS_Inactive_Count <= r_CS_Inactive_Count - 1'b1; | |||
end | |||
else | |||
begin | |||
r_SM_CS <= IDLE; | |||
end | |||
end | |||
default: | |||
begin | |||
r_CS_n <= 1'b1; // we done, so set CS high | |||
r_SM_CS <= IDLE; | |||
end | |||
endcase // case (r_SM_CS) | |||
end | |||
end // always @ (posedge i_Clk or negedge i_Rst_L) | |||
// Purpose: Keep track of RX_Count | |||
always @(posedge i_Clk) | |||
begin | |||
begin | |||
if (r_CS_n) | |||
begin | |||
o_RX_Count <= 0; | |||
end | |||
else if (o_RX_DV) | |||
begin | |||
o_RX_Count <= o_RX_Count + 1'b1; | |||
end | |||
end | |||
end | |||
assign o_SPI_CS_n = r_CS_n; | |||
assign o_TX_Ready = ((r_SM_CS == IDLE) | (r_SM_CS == TRANSFER && w_Master_Ready == 1'b1 && r_TX_Count > 0)) & ~i_TX_DV; | |||
endmodule // SPI_Master_With_Single_CS |
@@ -0,0 +1,240 @@ | |||
/////////////////////////////////////////////////////////////////////////////// | |||
//Source: https://github.com/nandland/spi-master/tree/master/Verilog/source | |||
// Description: SPI (Serial Peripheral Interface) Master | |||
// Creates master based on input configuration. | |||
// Sends a byte one bit at a time on MOSI | |||
// Will also receive byte data one bit at a time on MISO. | |||
// Any data on input byte will be shipped out on MOSI. | |||
// | |||
// To kick-off transaction, user must pulse i_TX_DV. | |||
// This module supports multi-byte transmissions by pulsing | |||
// i_TX_DV and loading up i_TX_Byte when o_TX_Ready is high. | |||
// | |||
// This module is only responsible for controlling Clk, MOSI, | |||
// and MISO. If the SPI peripheral requires a chip-select, | |||
// this must be done at a higher level. | |||
// | |||
// Note: i_Clk must be at least 2x faster than i_SPI_Clk | |||
// | |||
// Parameters: SPI_MODE, can be 0, 1, 2, or 3. See above. | |||
// Can be configured in one of 4 modes: | |||
// Mode | Clock Polarity (CPOL/CKP) | Clock Phase (CPHA) | |||
// 0 | 0 | 0 | |||
// 1 | 0 | 1 | |||
// 2 | 1 | 0 | |||
// 3 | 1 | 1 | |||
// More: https://en.wikipedia.org/wiki/Serial_Peripheral_Interface_Bus#Mode_numbers | |||
// CLKS_PER_HALF_BIT - Sets frequency of o_SPI_Clk. o_SPI_Clk is | |||
// derived from i_Clk. Set to integer number of clocks for each | |||
// half-bit of SPI data. E.g. 100 MHz i_Clk, CLKS_PER_HALF_BIT = 2 | |||
// would create o_SPI_CLK of 25 MHz. Must be >= 2 | |||
// | |||
/////////////////////////////////////////////////////////////////////////////// | |||
module SPI_Master | |||
#(parameter SPI_MODE = 0, | |||
parameter CLKS_PER_HALF_BIT = 2) | |||
( | |||
// Control/Data Signals, | |||
input i_Rst_L, // FPGA Reset | |||
input i_Clk, // FPGA Clock | |||
// TX (MOSI) Signals | |||
input [7:0] i_TX_Byte, // Byte to transmit on MOSI | |||
input i_TX_DV, // Data Valid Pulse with i_TX_Byte | |||
output reg o_TX_Ready, // Transmit Ready for next byte | |||
// RX (MISO) Signals | |||
output reg o_RX_DV, // Data Valid pulse (1 clock cycle) | |||
output reg [7:0] o_RX_Byte, // Byte received on MISO | |||
// SPI Interface | |||
output reg o_SPI_Clk, | |||
input i_SPI_MISO, | |||
output reg o_SPI_MOSI | |||
); | |||
// SPI Interface (All Runs at SPI Clock Domain) | |||
wire w_CPOL; // Clock polarity | |||
wire w_CPHA; // Clock phase | |||
reg [$clog2(CLKS_PER_HALF_BIT*2)-1:0] r_SPI_Clk_Count; | |||
reg r_SPI_Clk; | |||
reg [4:0] r_SPI_Clk_Edges; | |||
reg r_Leading_Edge; | |||
reg r_Trailing_Edge; | |||
reg r_TX_DV; | |||
reg [7:0] r_TX_Byte; | |||
reg [2:0] r_RX_Bit_Count; | |||
reg [2:0] r_TX_Bit_Count; | |||
// CPOL: Clock Polarity | |||
// CPOL=0 means clock idles at 0, leading edge is rising edge. | |||
// CPOL=1 means clock idles at 1, leading edge is falling edge. | |||
assign w_CPOL = (SPI_MODE == 2) | (SPI_MODE == 3); | |||
// CPHA: Clock Phase | |||
// CPHA=0 means the "out" side changes the data on trailing edge of clock | |||
// the "in" side captures data on leading edge of clock | |||
// CPHA=1 means the "out" side changes the data on leading edge of clock | |||
// the "in" side captures data on the trailing edge of clock | |||
assign w_CPHA = (SPI_MODE == 1) | (SPI_MODE == 3); | |||
// Purpose: Generate SPI Clock correct number of times when DV pulse comes | |||
always @(posedge i_Clk or negedge i_Rst_L) | |||
begin | |||
if (~i_Rst_L) | |||
begin | |||
o_TX_Ready <= 1'b0; | |||
r_SPI_Clk_Edges <= 0; | |||
r_Leading_Edge <= 1'b0; | |||
r_Trailing_Edge <= 1'b0; | |||
r_SPI_Clk <= w_CPOL; // assign default state to idle state | |||
r_SPI_Clk_Count <= 0; | |||
end | |||
else | |||
begin | |||
// Default assignments | |||
r_Leading_Edge <= 1'b0; | |||
r_Trailing_Edge <= 1'b0; | |||
if (i_TX_DV) | |||
begin | |||
o_TX_Ready <= 1'b0; | |||
r_SPI_Clk_Edges <= 16; // Total # edges in one byte ALWAYS 16 | |||
end | |||
else if (r_SPI_Clk_Edges > 0) | |||
begin | |||
o_TX_Ready <= 1'b0; | |||
if (r_SPI_Clk_Count == CLKS_PER_HALF_BIT*2-1) | |||
begin | |||
r_SPI_Clk_Edges <= r_SPI_Clk_Edges - 1; | |||
r_Trailing_Edge <= 1'b1; | |||
r_SPI_Clk_Count <= 0; | |||
r_SPI_Clk <= ~r_SPI_Clk; | |||
end | |||
else if (r_SPI_Clk_Count == CLKS_PER_HALF_BIT-1) | |||
begin | |||
r_SPI_Clk_Edges <= r_SPI_Clk_Edges - 1; | |||
r_Leading_Edge <= 1'b1; | |||
r_SPI_Clk_Count <= r_SPI_Clk_Count + 1; | |||
r_SPI_Clk <= ~r_SPI_Clk; | |||
end | |||
else | |||
begin | |||
r_SPI_Clk_Count <= r_SPI_Clk_Count + 1; | |||
end | |||
end | |||
else | |||
begin | |||
o_TX_Ready <= 1'b1; | |||
end | |||
end // else: !if(~i_Rst_L) | |||
end // always @ (posedge i_Clk or negedge i_Rst_L) | |||
// Purpose: Register i_TX_Byte when Data Valid is pulsed. | |||
// Keeps local storage of byte in case higher level module changes the data | |||
always @(posedge i_Clk or negedge i_Rst_L) | |||
begin | |||
if (~i_Rst_L) | |||
begin | |||
r_TX_Byte <= 8'h00; | |||
r_TX_DV <= 1'b0; | |||
end | |||
else | |||
begin | |||
r_TX_DV <= i_TX_DV; // 1 clock cycle delay | |||
if (i_TX_DV) | |||
begin | |||
r_TX_Byte <= i_TX_Byte; | |||
end | |||
end // else: !if(~i_Rst_L) | |||
end // always @ (posedge i_Clk or negedge i_Rst_L) | |||
// Purpose: Generate MOSI data | |||
// Works with both CPHA=0 and CPHA=1 | |||
always @(posedge i_Clk or negedge i_Rst_L) | |||
begin | |||
if (~i_Rst_L) | |||
begin | |||
o_SPI_MOSI <= 1'b0; | |||
r_TX_Bit_Count <= 3'b111; // send MSb first | |||
end | |||
else | |||
begin | |||
// If ready is high, reset bit counts to default | |||
if (o_TX_Ready) | |||
begin | |||
r_TX_Bit_Count <= 3'b111; | |||
end | |||
// Catch the case where we start transaction and CPHA = 0 | |||
else if (r_TX_DV & ~w_CPHA) | |||
begin | |||
o_SPI_MOSI <= r_TX_Byte[3'b111]; | |||
r_TX_Bit_Count <= 3'b110; | |||
end | |||
else if ((r_Leading_Edge & w_CPHA) | (r_Trailing_Edge & ~w_CPHA)) | |||
begin | |||
r_TX_Bit_Count <= r_TX_Bit_Count - 1; | |||
o_SPI_MOSI <= r_TX_Byte[r_TX_Bit_Count]; | |||
end | |||
end | |||
end | |||
// Purpose: Read in MISO data. | |||
always @(posedge i_Clk or negedge i_Rst_L) | |||
begin | |||
if (~i_Rst_L) | |||
begin | |||
o_RX_Byte <= 8'h00; | |||
o_RX_DV <= 1'b0; | |||
r_RX_Bit_Count <= 3'b111; | |||
end | |||
else | |||
begin | |||
// Default Assignments | |||
o_RX_DV <= 1'b0; | |||
if (o_TX_Ready) // Check if ready is high, if so reset bit count to default | |||
begin | |||
r_RX_Bit_Count <= 3'b111; | |||
end | |||
else if ((r_Leading_Edge & ~w_CPHA) | (r_Trailing_Edge & w_CPHA)) | |||
begin | |||
o_RX_Byte[r_RX_Bit_Count] <= i_SPI_MISO; // Sample data | |||
r_RX_Bit_Count <= r_RX_Bit_Count - 1; | |||
if (r_RX_Bit_Count == 3'b000) | |||
begin | |||
o_RX_DV <= 1'b1; // Byte done, pulse Data Valid | |||
end | |||
end | |||
end | |||
end | |||
// Purpose: Add clock delay to signals for alignment. | |||
always @(posedge i_Clk or negedge i_Rst_L) | |||
begin | |||
if (~i_Rst_L) | |||
begin | |||
o_SPI_Clk <= w_CPOL; | |||
end | |||
else | |||
begin | |||
o_SPI_Clk <= r_SPI_Clk; | |||
end // else: !if(~i_Rst_L) | |||
end // always @ (posedge i_Clk or negedge i_Rst_L) | |||
endmodule // SPI_Master |
@@ -0,0 +1,168 @@ | |||
module testbench(); | |||
logic clk; | |||
logic nReset; | |||
logic [19:0] FRAM_Adr; | |||
logic [7:0] FRAM_DATA_OUT; | |||
logic [7:0] FRAM_DATA_IN; | |||
logic FRAM_RW; | |||
logic FRAM_RSTATUS; | |||
logic FRAM_hbn; | |||
logic FRAM_go; | |||
logic FRAM_busy; | |||
logic SPI_CLK; | |||
logic SPI_MISO; | |||
logic SPI_MOSI; | |||
logic SPI_CS; | |||
logic [2:0]test; | |||
logic test_running; | |||
logic starttesting; | |||
localparam TESTS_cnt = 5; | |||
initial begin | |||
// Required for EDA Playground | |||
$dumpfile("dump.vcd"); | |||
$dumpvars; | |||
clk = 1'h0; | |||
nReset = 1'h0; | |||
FRAM_Adr <= 20'h0; | |||
FRAM_DATA_IN <= 8'h0; | |||
FRAM_RW = 0; | |||
FRAM_RSTATUS = 0; | |||
FRAM_hbn = 0; | |||
FRAM_go = 0; | |||
test <= 2'h0; | |||
repeat(10) @(posedge clk); | |||
nReset = 1'h1; | |||
starttesting <= 1'h1; | |||
test_running <= 1'h0; | |||
end //initial end | |||
// Clock Generation: | |||
always #(5) clk = ~clk; //clk 100MHz | |||
// end Clock Generation | |||
always @ (posedge starttesting or posedge FRAM_busy) begin | |||
repeat(10) @(posedge clk); | |||
if(test_running == 1'h0 & FRAM_busy == 1'h1) begin | |||
if(test == TESTS_cnt+1) begin | |||
test_running <= 1'h0; | |||
$display("Tests Finished"); | |||
$finish; | |||
end | |||
case(test) inside | |||
3'b000: begin Test1(); test <= test + 1'h1; end | |||
3'b001: begin Test2(); test <= test + 1'h1; end | |||
3'b010: begin Test3(); test <= test + 1'h1; end | |||
3'b011: begin Test4(); test <= test + 1'h1; end | |||
endcase | |||
end // endif | |||
end // end always | |||
task Test1(); | |||
test_running <= 1'h1; | |||
$display("DEBUG: %0tns: Test_1_Hibernation",$realtime); | |||
FRAM_hbn <= 1'h1; //Enter Hibernation | |||
FRAM_go <= 1'h1; | |||
#10; | |||
FRAM_hbn <= 1'h0; //Reset Hibernation Flag | |||
FRAM_go <= 1'h0; | |||
$display("DEBUG: %0tns: Test_1_Hibernation__-END",$realtime); | |||
test_running <= 1'h0; | |||
endtask | |||
task Test2(); | |||
test_running <= 1'h1; | |||
$display("DEBUG: %0tns: Test_2_ReadStatus",$realtime); | |||
FRAM_RSTATUS <= 1'h1; //Read Status | |||
FRAM_go <= 1'h1; //Go | |||
#10; | |||
FRAM_RSTATUS <= 1'h0; //Read Status | |||
FRAM_go <= 1'h0; //reset Go | |||
$display("DEBUG: %0tns: Test_2_ReadStatus__-END",$realtime); | |||
test_running <= 1'h0; | |||
endtask | |||
task Test3(); | |||
test_running <= 1'h1; | |||
$display("DEBUG: %0tns: Test_3_FRAM_WRITE",$realtime); | |||
FRAM_Adr <= 20'h8FFF1; //Load 8FFF1 as adress | |||
FRAM_DATA_IN <= 8'hAA; //Load AA as Data to Write into FRAM | |||
FRAM_RW <= 1'h0; //Write Operation | |||
FRAM_go <= 1'h1; //Go | |||
#10; | |||
FRAM_go <= 1'h0; //resetGo | |||
$display("DEBUG: %0tns: Test_3_FRAM_WRITE__-END",$realtime); | |||
test_running <= 1'h0; | |||
endtask | |||
task Test4(); | |||
test_running <= 1'h1; | |||
$display("DEBUG: %0tns: Test_4_FRAM_READ",$realtime); | |||
FRAM_Adr <= 20'h8FFF1; //Load 8FFF1 as adress | |||
FRAM_RW <= 1'h1; //Read | |||
FRAM_go <= 1'h1; //Go | |||
#10; | |||
FRAM_go <= 1'h0; //resetGo | |||
FRAM_RW <= 1'h0; //Read | |||
$display("DEBUG: %0tns: Test_4_FRAM_READ__-END",$realtime); | |||
test_running <= 1'h0; | |||
endtask | |||
FRAM FRAM_ut( | |||
.i_clk(clk), | |||
.i_nreset(nReset), | |||
.i_adr(FRAM_Adr), | |||
.i_data(FRAM_DATA_IN), | |||
.o_data(FRAM_DATA_OUT), | |||
.i_rw(FRAM_RW), | |||
.i_status(FRAM_RSTATUS), | |||
.i_hbn(FRAM_hbn), | |||
.i_cready(FRAM_go), | |||
.o_busy(FRAM_busy), | |||
.o_SPI_Clk(SPI_CLK), | |||
.i_SPI_MISO(SPI_MOSI), // !!! only for Testing!!! | |||
.o_SPI_MOSI(SPI_MOSI), // | |||
.o_SPI_CS_n(SPI_CS) | |||
); | |||
endmodule | |||