|  | `default_nettype none | 
|  | //------------------------------------- | 
|  | // SPI controller for Caravel (PicoSoC) | 
|  | //------------------------------------- | 
|  | // Written by Tim Edwards | 
|  | // efabless, inc. September 27, 2020 | 
|  | //------------------------------------- | 
|  |  | 
|  | //----------------------------------------------------------- | 
|  | // This is a standalone slave SPI for the caravel chip that is | 
|  | // intended to be independent of the picosoc and independent | 
|  | // of all IP blocks except the power-on-reset.  This SPI has | 
|  | // register outputs controlling the functions that critically | 
|  | // affect operation of the picosoc and so cannot be accessed | 
|  | // from the picosoc itself.  This includes the PLL enables | 
|  | // and trim, and the crystal oscillator enable.  It also has | 
|  | // a general reset for the picosoc, an IRQ input, a bypass for | 
|  | // the entire crystal oscillator and PLL chain, the | 
|  | // manufacturer and product IDs and product revision number. | 
|  | // To be independent of the 1.8V regulator, the slave SPI is | 
|  | // synthesized with the 3V digital library and runs off of | 
|  | // the 3V supply. | 
|  | // | 
|  | // This module is designed to be decoupled from the chip | 
|  | // padframe and redirected to the wishbone bus under | 
|  | // register control from the management SoC, such that the | 
|  | // contents can be accessed from the management core via the | 
|  | // SPI master. | 
|  | // | 
|  | //----------------------------------------------------------- | 
|  |  | 
|  | //------------------------------------------------------------ | 
|  | // Caravel defined registers: | 
|  | // Register 0:  SPI status and control (unused & reserved) | 
|  | // Register 1 and 2:  Manufacturer ID (0x0456) (readonly) | 
|  | // Register 3:  Product ID (= 16) (readonly) | 
|  | // Register 4-7: Mask revision (readonly) --- Externally programmed | 
|  | //	with via programming.  Via programmed with a script to match | 
|  | //	each customer ID. | 
|  | // | 
|  | // Register 8:   PLL enables (2 bits) | 
|  | // Register 9:   PLL bypass (1 bit) | 
|  | // Register 10:  IRQ (1 bit) | 
|  | // Register 11:  reset (1 bit) | 
|  | // Register 12:  trap (1 bit) (readonly) | 
|  | // Register 13-16:  PLL trim (26 bits) | 
|  | // Register 17:	 PLL output divider (3 bits) | 
|  | // Register 18:	 PLL feedback divider (5 bits) | 
|  | //------------------------------------------------------------ | 
|  |  | 
|  | module housekeeping_spi( | 
|  | `ifdef USE_POWER_PINS | 
|  | vdd, vss, | 
|  | `endif | 
|  | RSTB, SCK, SDI, CSB, SDO, sdo_enb, | 
|  | pll_ena, pll_dco_ena, pll_div, pll_sel, | 
|  | pll90_sel, pll_trim, pll_bypass, irq, reset, | 
|  | trap, mask_rev_in, pass_thru_reset, | 
|  | pass_thru_mgmt_sck, pass_thru_mgmt_csb, | 
|  | pass_thru_mgmt_sdi, pass_thru_mgmt_sdo, | 
|  | pass_thru_user_sck, pass_thru_user_csb, | 
|  | pass_thru_user_sdi, pass_thru_user_sdo | 
|  | ); | 
|  |  | 
|  | `ifdef USE_POWER_PINS | 
|  | inout vdd;	    // 3.3V supply | 
|  | inout vss;	    // common ground | 
|  | `endif | 
|  |  | 
|  | input RSTB;	    // from padframe | 
|  |  | 
|  | input SCK;	    // from padframe | 
|  | input SDI;	    // from padframe | 
|  | input CSB;	    // from padframe | 
|  | output SDO;	    // to padframe | 
|  | output sdo_enb; // to padframe | 
|  |  | 
|  | output pll_ena; | 
|  | output pll_dco_ena; | 
|  | output [4:0] pll_div; | 
|  | output [2:0] pll_sel; | 
|  | output [2:0] pll90_sel; | 
|  | output [25:0] pll_trim; | 
|  | output pll_bypass; | 
|  | output irq; | 
|  | output reset; | 
|  | input  trap; | 
|  | input [31:0] mask_rev_in;	// metal programmed;  3.3V domain | 
|  |  | 
|  | // Pass-through programming mode for management area SPI flash | 
|  | output pass_thru_reset; | 
|  | output pass_thru_mgmt_sck; | 
|  | output pass_thru_mgmt_csb; | 
|  | output pass_thru_mgmt_sdi; | 
|  | input  pass_thru_mgmt_sdo; | 
|  |  | 
|  | // Pass-through programming mode for user area SPI flash | 
|  | output pass_thru_user_sck; | 
|  | output pass_thru_user_csb; | 
|  | output pass_thru_user_sdi; | 
|  | input  pass_thru_user_sdo; | 
|  |  | 
|  | reg [25:0] pll_trim; | 
|  | reg [4:0] pll_div; | 
|  | reg [2:0] pll_sel; | 
|  | reg [2:0] pll90_sel; | 
|  | reg pll_dco_ena; | 
|  | reg pll_ena; | 
|  | reg pll_bypass; | 
|  | reg reset_reg; | 
|  | reg irq; | 
|  |  | 
|  | wire [7:0] odata; | 
|  | wire [7:0] idata; | 
|  | wire [7:0] iaddr; | 
|  |  | 
|  | wire trap; | 
|  | wire rdstb; | 
|  | wire wrstb; | 
|  | wire pass_thru_mgmt;		// Mode detected by spi_slave | 
|  | wire pass_thru_mgmt_delay; | 
|  | wire pass_thru_user;		// Mode detected by spi_slave | 
|  | wire pass_thru_user_delay; | 
|  | wire loc_sdo; | 
|  |  | 
|  | // Pass-through mode handling.  Signals may only be applied when the | 
|  | // core processor is in reset. | 
|  |  | 
|  | assign pass_thru_mgmt_csb = reset ? ~pass_thru_mgmt_delay : 1'bz; | 
|  | assign pass_thru_mgmt_sck = reset ? (pass_thru_mgmt ? SCK : 1'b0) : 1'bz; | 
|  | assign pass_thru_mgmt_sdi = reset ? (pass_thru_mgmt ? SDI : 1'b0) : 1'bz; | 
|  |  | 
|  | assign pass_thru_user_csb = reset ? ~pass_thru_user_delay : 1'bz; | 
|  | assign pass_thru_user_sck = reset ? (pass_thru_user ? SCK : 1'b0) : 1'bz; | 
|  | assign pass_thru_user_sdi = reset ? (pass_thru_user ? SDI : 1'b0) : 1'bz; | 
|  |  | 
|  | assign SDO = pass_thru_mgmt ? pass_thru_mgmt_sdo : | 
|  | pass_thru_user ? pass_thru_user_sdo : loc_sdo; | 
|  | assign reset = pass_thru_reset ? 1'b1 : reset_reg; | 
|  |  | 
|  | // Instantiate the SPI slave module | 
|  |  | 
|  | housekeeping_spi_slave U1 ( | 
|  | .reset(~RSTB), | 
|  | .SCK(SCK), | 
|  | .SDI(SDI), | 
|  | .CSB(CSB), | 
|  | .SDO(loc_sdo), | 
|  | .sdoenb(sdo_enb), | 
|  | .idata(odata), | 
|  | .odata(idata), | 
|  | .oaddr(iaddr), | 
|  | .rdstb(rdstb), | 
|  | .wrstb(wrstb), | 
|  | .pass_thru_mgmt(pass_thru_mgmt), | 
|  | .pass_thru_mgmt_delay(pass_thru_mgmt_delay), | 
|  | .pass_thru_user(pass_thru_user), | 
|  | .pass_thru_user_delay(pass_thru_user_delay), | 
|  | .pass_thru_reset(pass_thru_reset) | 
|  | ); | 
|  |  | 
|  | wire [11:0] mfgr_id; | 
|  | wire [7:0]  prod_id; | 
|  | wire [31:0] mask_rev; | 
|  |  | 
|  | assign mfgr_id = 12'h456;		// Hard-coded | 
|  | assign prod_id = 8'h10;		// Hard-coded | 
|  | assign mask_rev = mask_rev_in;	// Copy in to out. | 
|  |  | 
|  | // Send register contents to odata on SPI read command | 
|  | // All values are 1-4 bits and no shadow registers are required. | 
|  |  | 
|  | assign odata = | 
|  | (iaddr == 8'h00) ? 8'h00 :	// SPI status (fixed) | 
|  | (iaddr == 8'h01) ? {4'h0, mfgr_id[11:8]} :	// Manufacturer ID (fixed) | 
|  | (iaddr == 8'h02) ? mfgr_id[7:0] :	// Manufacturer ID (fixed) | 
|  | (iaddr == 8'h03) ? prod_id :	// Product ID (fixed) | 
|  | (iaddr == 8'h04) ? mask_rev[31:24] :	// Mask rev (metal programmed) | 
|  | (iaddr == 8'h05) ? mask_rev[23:16] :	// Mask rev (metal programmed) | 
|  | (iaddr == 8'h06) ? mask_rev[15:8] :		// Mask rev (metal programmed) | 
|  | (iaddr == 8'h07) ? mask_rev[7:0] :		// Mask rev (metal programmed) | 
|  |  | 
|  | (iaddr == 8'h08) ? {6'b000000, pll_dco_ena, pll_ena} : | 
|  | (iaddr == 8'h09) ? {7'b0000000, pll_bypass} : | 
|  | (iaddr == 8'h0a) ? {7'b0000000, irq} : | 
|  | (iaddr == 8'h0b) ? {7'b0000000, reset} : | 
|  | (iaddr == 8'h0c) ? {7'b0000000, trap} : | 
|  | (iaddr == 8'h0d) ? pll_trim[7:0] : | 
|  | (iaddr == 8'h0e) ? pll_trim[15:8] : | 
|  | (iaddr == 8'h0f) ? pll_trim[23:16] : | 
|  | (iaddr == 8'h10) ? {6'b000000, pll_trim[25:24]} : | 
|  | (iaddr == 8'h11) ? {2'b00, pll90_sel, pll_sel} : | 
|  | (iaddr == 8'h12) ? {3'b000, pll_div} : | 
|  | 8'h00;	// Default | 
|  |  | 
|  | // Register mapping and I/O to slave module | 
|  |  | 
|  | always @(posedge SCK or negedge RSTB) begin | 
|  | if (RSTB == 1'b0) begin | 
|  | // Set trim for PLL at (almost) slowest rate (~90MHz).  However, | 
|  | // pll_trim[12] must be set to zero for proper startup. | 
|  | pll_trim <= 26'b11111111111110111111111111; | 
|  | pll_sel <= 3'b010;	// Default output divider divide-by-2 | 
|  | pll90_sel <= 3'b010;	// Default secondary output divider divide-by-2 | 
|  | pll_div <= 5'b00100;	// Default feedback divider divide-by-8 | 
|  | pll_dco_ena <= 1'b1;	// Default free-running PLL | 
|  | pll_ena <= 1'b0;	// Default PLL turned off | 
|  | pll_bypass <= 1'b1;	// Default bypass mode (don't use PLL) | 
|  | irq <= 1'b0; | 
|  | reset_reg <= 1'b0; | 
|  | end else if (wrstb == 1'b1) begin | 
|  | case (iaddr) | 
|  | 8'h08: begin | 
|  | pll_ena <= idata[0]; | 
|  | pll_dco_ena <= idata[1]; | 
|  | end | 
|  | 8'h09: begin | 
|  | pll_bypass <= idata[0]; | 
|  | end | 
|  | 8'h0a: begin | 
|  | irq <= idata[0]; | 
|  | end | 
|  | 8'h0b: begin | 
|  | reset_reg <= idata[0]; | 
|  | end | 
|  | // Register 0xc is read-only | 
|  | 8'h0d: begin | 
|  | pll_trim[7:0] <= idata; | 
|  | end | 
|  | 8'h0e: begin | 
|  | pll_trim[15:8] <= idata; | 
|  | end | 
|  | 8'h0f: begin | 
|  | pll_trim[23:16] <= idata; | 
|  | end | 
|  | 8'h10: begin | 
|  | pll_trim[25:24] <= idata[1:0]; | 
|  | end | 
|  | 8'h11: begin | 
|  | pll_sel <= idata[2:0]; | 
|  | pll90_sel <= idata[5:3]; | 
|  | end | 
|  | 8'h12: begin | 
|  | pll_div <= idata[4:0]; | 
|  | end | 
|  | endcase	// (iaddr) | 
|  | end | 
|  | end | 
|  | endmodule	// housekeeping_spi | 
|  |  | 
|  | //------------------------------------------------------ | 
|  | // housekeeping_spi_slave.v | 
|  | //------------------------------------------------------ | 
|  | // General purpose SPI slave 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 to supply next byte to idata | 
|  | // 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_slave(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_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_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_reset = pass_thru_mgmt_delay | pre_pass_thru_mgmt; | 
|  |  | 
|  | // 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 | 
|  | if (readmode == 1'b1) begin | 
|  | rdstb <= 1'b1; | 
|  | end | 
|  | state <= `DATA; | 
|  | 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 | 
|  | 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_slave | 
|  | `default_nettype wire |