// Logic for the QLROMEXT board of the QL-SD interface // Copyright (C) 2011-2018 Adrian Ives, Peter Graf and Marcel Kilgus // // This hardware description is free; you can redistribute it and/or // modify it under the terms of the GNU General Public License // as published by the Free Software Foundation; either version 2 // of the License, or (at your option) any later version. // // This hardware description is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. // Version: 0.091 (SPI version) // Target: CPLD LC4064V // Control Register addresses // The position and ordering of IF_ENABLE and IF_DISABLE are // important because of Minerva's extended memory test (which, for // some unfathomable reason checks the ROM cartridge space for RAM!) // This order ensures that the interface remains disabled during the // check; preventing anything from being placed on the data bus or // any spurious signals from being sent to the SD Card `define IF_ENABLE 16'hFEE0 // 65248 `define IF_DISABLE 16'hFEE1 // 65249 `define IF_RESET 16'hFEE2 // 65250 `define IF_VERSION 16'hFEE3 // 65251 `define SPI_READ 16'hFEE4 // 65252 `define SPI_XFER_FAST 16'hFEE6 // 65254 `define SPI_XFER_SLOW 16'hFEE8 // 65256 `define SPI_XFER_OFF 16'hFEEA // 65258 `define SPI_DESELECT 16'hFEF0 // 65264 `define SPI_SELECT1 16'hFEF1 // 65265 `define SPI_SELECT2 16'hFEF2 // 65266 `define SPI_SELECT3 16'hFEF3 // 65267 `define SPI_CLR_MOSI 16'hFEF4 // 65368 `define SPI_SET_MOSI 16'hFEF5 // 65369 `define SPI_CLR_SCLK 16'hFEF6 // 65370 `define SPI_SET_SCLK 16'hFEF7 // 65371 // SPI background transfer shift register write page // If the interface is enabled and background // transfers are switched on then any write to // this page will load the shift register from // the bottom eight bits of the Address Bus and // transfer the byte over SPI in the background `define SPI_XFER 8'hFF // $FF00,65280 // SPI Background Transfer Finite State Machine codes `define STATE_ROMOE 3'b000 // Wait for ROMOE `define STATE_CHECK 3'b001 // Check address `define STATE_PROLOGUE 3'b010 // Prologue `define STATE_DIVIDE 3'b011 // Dividing `define STATE_SHIFT 3'b100 // Shifting `define STATE_SHIFTED 3'b101 // Shifted `define STATE_EPILOGUE 3'b110 // Epilogue // Clock divider for slow speed background transfers `define SLOW_CLK_DIVIDER 64 // This should give an approximate SPI clock of 25MHZ/64 = 390.625KHZ module qlromext(clk, romoel, d, a, gl, sd_cs1l, sd_cs2l, sd_clk, sd_do, sd_di, io1, io2, io3, io4); input [15:0] a; input clk, romoel, sd_do, io2; output [7:0] d; output gl, sd_cs1l, sd_cs2l, sd_clk, sd_di, io1, io3, io4; wire [15:0] a /* synthesis syn_keep=1 */ ; reg [7:0] d; reg gl; wire clk /* synthesis syn_keep=1 */ ; wire romoel /* synthesis syn_keep=1 */ ; wire sd_do /* synthesis syn_keep=1 */ ; wire io2 /* synthesis syn_keep=1 */ ; // It's a bit of a waste of time setting initial values of registers // to anything other than 0 because the synthesis tool doesn't appear // to translate them into logic. By default, all CPLD registers are // set to 0 at power up. reg interface_enabled = 0; // Determines whether the interface is enabled // ROMOE filter reg filter1, filter2; reg romoel_filtered; // SPI Slave Selects reg ss1 = 1; reg ss2 = 1; reg ss3 = 1; // Foreground SPI bits reg fg_mosi = 1; reg fg_sclk = 1; // Background SPI bits reg bg_mosi = 1; reg bg_sclk = 1; // SPI background transfer control reg spi_bg_enabled = 0; // If true, background transfers are enabled reg [2:0] spi_state = `STATE_ROMOE; // Background transfer current state reg spi_xfer_running = 0; // If true, an SPI background transfer is in progress reg spi_fast = 0; // If true, the maximum SPI clock rate (25MHZ) is used for background SPI transfers reg [7:0] spi_shiftreg = 0; // SPI Shift Register reg [3:0] spi_counter = 0; // 4 bit counter used to control SPI bit shifting reg [6:0] spi_divider = 0; // 7 bit counter used to divide the clock for slow SPI background transfers // Connect the SPI signals assign sd_di = (spi_xfer_running) ? bg_mosi : fg_mosi; assign sd_clk = (spi_xfer_running) ? bg_sclk : fg_sclk; assign io1 = (spi_xfer_running) ? bg_mosi : fg_mosi; assign io3 = (spi_xfer_running) ? bg_sclk : fg_sclk; assign sd_cs1l = (interface_enabled) ? ss1 : 1; assign sd_cs2l = (interface_enabled) ? ss2 : 1; assign io4 = (interface_enabled) ? ss3 : 1; wire miso = (ss3) ? sd_do : io2 /* synthesis syn_keep=1 */ ; // -------------------------------------- // Control the Data Bus // -------------------------------------- always @(*) begin if (romoel) // MR.SCHMITT? begin // ROMOEL is HIGH d = 8'bZZZZZZZZ; gl = 1; end else begin // ROMOEL is LOW if (interface_enabled && (a == `SPI_READ )) begin // Access to the SPI_READ control register d = (spi_bg_enabled) ? spi_shiftreg : { 7'b0000000, miso } ; gl = 1; end else if (interface_enabled && (a == `IF_VERSION )) begin // Access to IF_VERSION register d = { 8'b00000001 } ; // SPI version, V2 gl = 1; end else begin // Any other address enables the ROM d = 8'bZZZZZZZZ; gl = 0; end end end // -------------------------------------- // Filter & delay falling flank of romoel. This helps us a lot with the SGC! // It might even remove the need for the Schmitt trigger, but I haven't tested that. Better save than sorry... // // It also makes sure that changes to romoel_filtered are synchronised to clk. // I think many weird effects happened because it wasn't synchronised before... // -------------------------------------- always @(negedge clk) begin filter1 <= romoel; filter2 <= filter1; romoel_filtered <= romoel || filter1 || filter2; end // -------------------------------------- // Process changes on the Address Bus // -------------------------------------- always @(negedge romoel_filtered) begin case (a) `IF_ENABLE: begin // Enable the interface interface_enabled <= 1; end `IF_DISABLE: begin // Disable the interface interface_enabled <= 0; end `IF_RESET: begin // Reset the interface fg_mosi <= 1; fg_sclk <= 1; ss1 <= 1; ss2 <= 1; ss3 <= 1; spi_fast <= 0; spi_bg_enabled <= 0; end `SPI_XFER_FAST: if (interface_enabled) begin // Enable SPI background transfers at full speed // SPI_READ now gets the SPI Shift Register spi_fast <= 1; spi_bg_enabled <= 1; end `SPI_XFER_SLOW: if (interface_enabled) begin // Enable SPI background transfers at low speed // SPI_READ now gets the SPI Shift Register spi_fast <= 0; spi_bg_enabled <= 1; end `SPI_XFER_OFF: if (interface_enabled) begin // Disable SPI background transfers // SPI_READ now gets foreground MISO spi_bg_enabled <= 0; end `SPI_DESELECT: if (interface_enabled) begin // Clear all slave selects ss1 <= 1; ss2 <= 1; ss3 <= 1; end `SPI_SELECT1: if (interface_enabled) begin // Select SPI Slave #1 ss1 <= 0; ss2 <= 1; ss3 <= 1; end `SPI_SELECT2: if (interface_enabled) begin // Select SPI Slave #2 ss1 <= 1; ss2 <= 0; ss3 <= 1; end `SPI_SELECT3: if (interface_enabled) begin // Select SPI Slave #3 ss1 <= 1; ss2 <= 1; ss3 <= 0; end `SPI_SET_MOSI: if (interface_enabled) begin // Bit-banged SPI; Set MOSI=1 fg_mosi <= 1; end `SPI_CLR_MOSI: if (interface_enabled) begin // Bit-banged SPI; Set MOSI=0 fg_mosi <= 0; end `SPI_SET_SCLK: if (interface_enabled) begin // Bit-banged SPI; Set SCLK=1 fg_sclk <= 1; end `SPI_CLR_SCLK: if (interface_enabled) begin // Bit-banged SPI; Set SCLK=0 fg_sclk <= 0; end endcase end // -------------------------------------- // Handle SPI background transfers // Finite State Machine using spi_state // -------------------------------------- always @(posedge clk) begin case (spi_state) `STATE_ROMOE: // Wait for ROMOE edge // Stay in this state while: // The interface is disabled // Background transfers are disabled // There was no edge on ROMOE begin spi_state <= (interface_enabled && spi_bg_enabled && !romoel_filtered)? `STATE_CHECK: `STATE_ROMOE; end `STATE_CHECK: // Check if the ROM access was for the SPI_XFER address. Otherwise wait for ROMOE to drop begin spi_state <= (a[15:8] == `SPI_XFER)? `STATE_PROLOGUE: `STATE_EPILOGUE; end `STATE_PROLOGUE: // Prologue: initialise registers for the transfer begin spi_shiftreg <= a[7:0]; // Load the SPI shift register from the bottom 8 bits of the Address Bus bg_mosi <= 1; bg_sclk <= 1; // Set background I/O lines to high spi_counter <= 0; // Reset shift counter spi_divider <= `SLOW_CLK_DIVIDER; // Reset clock divider spi_xfer_running <= 1; // Selects background SPI output lines spi_state <= (spi_fast)? `STATE_SHIFT: `STATE_DIVIDE; // Select the next state according to the transfer speed end `STATE_DIVIDE: // Dividing // Enter this state before transitioning SCLK when the transfer speed is set to slow begin spi_xfer_running <= 1; spi_divider <= spi_divider - 1; spi_state <= (spi_divider == 0)? `STATE_SHIFT: `STATE_DIVIDE; // Remain in this state until the clock divider count is satisfied end `STATE_SHIFT: // Transition SCLK and shift the next bit across the SPI bus begin spi_xfer_running <= 1; bg_sclk <= !bg_sclk; spi_counter <= spi_counter + 1; bg_mosi <= spi_shiftreg[7]; if (bg_sclk) // SPI clock went low to high; output the next bit bg_mosi <= spi_shiftreg[7]; else // SPI clock went high to low; input the next bit spi_shiftreg <= { spi_shiftreg[6:0], miso }; spi_divider <= `SLOW_CLK_DIVIDER; // Always reset the clock divider ahead of next SCLK transition if (spi_counter == 15) spi_state <= `STATE_SHIFTED; // If the byte has been shifted move to next state else spi_state <= (spi_fast)? `STATE_SHIFT: `STATE_DIVIDE; // Else next state depends upon transfer speed end `STATE_SHIFTED: // Shift has completed; reset registers begin spi_xfer_running <= 0; bg_mosi <= 1; // Reset MOSI spi_state <= `STATE_EPILOGUE; // Next state is Epilogue end `STATE_EPILOGUE: // Wait for ROMOEH to drop again begin spi_state <= (!romoel_filtered)? `STATE_EPILOGUE: `STATE_ROMOE; end endcase end endmodule