diff --git a/Arcade-Pacman.qsf b/Arcade-Pacman.qsf index f717bd5..d2701c6 100644 --- a/Arcade-Pacman.qsf +++ b/Arcade-Pacman.qsf @@ -13,7 +13,7 @@ set_global_assignment -name PARTITION_NETLIST_TYPE SOURCE -section_id Top set_global_assignment -name PARTITION_FITTER_PRESERVATION_LEVEL PLACEMENT_AND_ROUTING -section_id Top set_global_assignment -name PARTITION_COLOR 16764057 -section_id Top -set_global_assignment -name LAST_QUARTUS_VERSION "17.0.2 Standard Edition" +set_global_assignment -name LAST_QUARTUS_VERSION "17.0.2 Lite Edition" set_global_assignment -name GENERATE_RBF_FILE ON set_global_assignment -name PROJECT_OUTPUT_DIRECTORY output_files diff --git a/Arcade-Pacman.sv b/Arcade-Pacman.sv index ff4db74..930d77e 100644 --- a/Arcade-Pacman.sv +++ b/Arcade-Pacman.sv @@ -1,387 +1,390 @@ -//============================================================================ -// Arcade: Pacman -// -// Port to MiSTer -// Copyright (C) 2017 Sorgelig -// -// This program is free software; 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 program 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. -//============================================================================ - -module emu -( - //Master input clock - input CLK_50M, - - //Async reset from top-level module. - //Can be used as initial reset. - input RESET, - - //Must be passed to hps_io module - inout [45:0] HPS_BUS, - - //Base video clock. Usually equals to CLK_SYS. - output CLK_VIDEO, - - //Multiple resolutions are supported using different CE_PIXEL rates. - //Must be based on CLK_VIDEO - output CE_PIXEL, - - //Video aspect ratio for HDMI. Most retro systems have ratio 4:3. - //if VIDEO_ARX[12] or VIDEO_ARY[12] is set then [11:0] contains scaled size instead of aspect ratio. - output [12:0] VIDEO_ARX, - output [12:0] VIDEO_ARY, - - output [7:0] VGA_R, - output [7:0] VGA_G, - output [7:0] VGA_B, - output VGA_HS, - output VGA_VS, - output VGA_DE, // = ~(VBlank | HBlank) - output VGA_F1, - output [1:0] VGA_SL, - output VGA_SCALER, // Force VGA scaler - - input [11:0] HDMI_WIDTH, - input [11:0] HDMI_HEIGHT, +//============================================================================ +// Arcade: Pacman +// +// Port to MiSTer +// Copyright (C) 2017 Sorgelig +// +// This program is free software; 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 program 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. +//============================================================================ + +module emu +( + //Master input clock + input CLK_50M, + + //Async reset from top-level module. + //Can be used as initial reset. + input RESET, + + //Must be passed to hps_io module + inout [45:0] HPS_BUS, + + //Base video clock. Usually equals to CLK_SYS. + output CLK_VIDEO, + + //Multiple resolutions are supported using different CE_PIXEL rates. + //Must be based on CLK_VIDEO + output CE_PIXEL, + + //Video aspect ratio for HDMI. Most retro systems have ratio 4:3. + //if VIDEO_ARX[12] or VIDEO_ARY[12] is set then [11:0] contains scaled size instead of aspect ratio. + output [12:0] VIDEO_ARX, + output [12:0] VIDEO_ARY, + + output [7:0] VGA_R, + output [7:0] VGA_G, + output [7:0] VGA_B, + output VGA_HS, + output VGA_VS, + output VGA_DE, // = ~(VBlank | HBlank) + output VGA_F1, + output [1:0] VGA_SL, + output VGA_SCALER, // Force VGA scaler + + input [11:0] HDMI_WIDTH, + input [11:0] HDMI_HEIGHT, output HDMI_FREEZE, - -`ifdef MISTER_FB - // Use framebuffer in DDRAM (USE_FB=1 in qsf) - // FB_FORMAT: - // [2:0] : 011=8bpp(palette) 100=16bpp 101=24bpp 110=32bpp - // [3] : 0=16bits 565 1=16bits 1555 - // [4] : 0=RGB 1=BGR (for 16/24/32 modes) - // - // FB_STRIDE either 0 (rounded to 256 bytes) or multiple of pixel size (in bytes) - output FB_EN, - output [4:0] FB_FORMAT, - output [11:0] FB_WIDTH, - output [11:0] FB_HEIGHT, - output [31:0] FB_BASE, - output [13:0] FB_STRIDE, - input FB_VBL, - input FB_LL, - output FB_FORCE_BLANK, - -`ifdef MISTER_FB_PALETTE - // Palette control for 8bit modes. - // Ignored for other video modes. - output FB_PAL_CLK, - output [7:0] FB_PAL_ADDR, - output [23:0] FB_PAL_DOUT, - input [23:0] FB_PAL_DIN, - output FB_PAL_WR, -`endif -`endif - - output LED_USER, // 1 - ON, 0 - OFF. - - // b[1]: 0 - LED status is system status OR'd with b[0] - // 1 - LED status is controled solely by b[0] - // hint: supply 2'b00 to let the system control the LED. - output [1:0] LED_POWER, - output [1:0] LED_DISK, - - // I/O board button press simulation (active high) - // b[1]: user button - // b[0]: osd button - output [1:0] BUTTONS, - - input CLK_AUDIO, // 24.576 MHz - output [15:0] AUDIO_L, - output [15:0] AUDIO_R, - output AUDIO_S, // 1 - signed audio samples, 0 - unsigned - output [1:0] AUDIO_MIX, // 0 - no mix, 1 - 25%, 2 - 50%, 3 - 100% (mono) - - //ADC - inout [3:0] ADC_BUS, - - //SD-SPI - output SD_SCK, - output SD_MOSI, - input SD_MISO, - output SD_CS, - input SD_CD, - - //High latency DDR3 RAM interface - //Use for non-critical time purposes - output DDRAM_CLK, - input DDRAM_BUSY, - output [7:0] DDRAM_BURSTCNT, - output [28:0] DDRAM_ADDR, - input [63:0] DDRAM_DOUT, - input DDRAM_DOUT_READY, - output DDRAM_RD, - output [63:0] DDRAM_DIN, - output [7:0] DDRAM_BE, - output DDRAM_WE, - - //SDRAM interface with lower latency - output SDRAM_CLK, - output SDRAM_CKE, - output [12:0] SDRAM_A, - output [1:0] SDRAM_BA, - inout [15:0] SDRAM_DQ, - output SDRAM_DQML, - output SDRAM_DQMH, - output SDRAM_nCS, - output SDRAM_nCAS, - output SDRAM_nRAS, - output SDRAM_nWE, - -`ifdef MISTER_DUAL_SDRAM - //Secondary SDRAM - //Set all output SDRAM_* signals to Z ASAP if SDRAM2_EN is 0 - input SDRAM2_EN, - output SDRAM2_CLK, - output [12:0] SDRAM2_A, - output [1:0] SDRAM2_BA, - inout [15:0] SDRAM2_DQ, - output SDRAM2_nCS, - output SDRAM2_nCAS, - output SDRAM2_nRAS, - output SDRAM2_nWE, -`endif - - input UART_CTS, - output UART_RTS, - input UART_RXD, - output UART_TXD, - output UART_DTR, - input UART_DSR, - - // Open-drain User port. - // 0 - D+/RX - // 1 - D-/TX - // 2..6 - USR2..USR6 - // Set USER_OUT to 1 to read from USER_IN. - input [6:0] USER_IN, - output [6:0] USER_OUT, - - input OSD_STATUS -); + +`ifdef MISTER_FB + // Use framebuffer in DDRAM (USE_FB=1 in qsf) + // FB_FORMAT: + // [2:0] : 011=8bpp(palette) 100=16bpp 101=24bpp 110=32bpp + // [3] : 0=16bits 565 1=16bits 1555 + // [4] : 0=RGB 1=BGR (for 16/24/32 modes) + // + // FB_STRIDE either 0 (rounded to 256 bytes) or multiple of pixel size (in bytes) + output FB_EN, + output [4:0] FB_FORMAT, + output [11:0] FB_WIDTH, + output [11:0] FB_HEIGHT, + output [31:0] FB_BASE, + output [13:0] FB_STRIDE, + input FB_VBL, + input FB_LL, + output FB_FORCE_BLANK, + +`ifdef MISTER_FB_PALETTE + // Palette control for 8bit modes. + // Ignored for other video modes. + output FB_PAL_CLK, + output [7:0] FB_PAL_ADDR, + output [23:0] FB_PAL_DOUT, + input [23:0] FB_PAL_DIN, + output FB_PAL_WR, +`endif +`endif + + output LED_USER, // 1 - ON, 0 - OFF. + + // b[1]: 0 - LED status is system status OR'd with b[0] + // 1 - LED status is controled solely by b[0] + // hint: supply 2'b00 to let the system control the LED. + output [1:0] LED_POWER, + output [1:0] LED_DISK, + + // I/O board button press simulation (active high) + // b[1]: user button + // b[0]: osd button + output [1:0] BUTTONS, + + input CLK_AUDIO, // 24.576 MHz + output [15:0] AUDIO_L, + output [15:0] AUDIO_R, + output AUDIO_S, // 1 - signed audio samples, 0 - unsigned + output [1:0] AUDIO_MIX, // 0 - no mix, 1 - 25%, 2 - 50%, 3 - 100% (mono) + + //ADC + inout [3:0] ADC_BUS, + + //SD-SPI + output SD_SCK, + output SD_MOSI, + input SD_MISO, + output SD_CS, + input SD_CD, + + //High latency DDR3 RAM interface + //Use for non-critical time purposes + output DDRAM_CLK, + input DDRAM_BUSY, + output [7:0] DDRAM_BURSTCNT, + output [28:0] DDRAM_ADDR, + input [63:0] DDRAM_DOUT, + input DDRAM_DOUT_READY, + output DDRAM_RD, + output [63:0] DDRAM_DIN, + output [7:0] DDRAM_BE, + output DDRAM_WE, + + //SDRAM interface with lower latency + output SDRAM_CLK, + output SDRAM_CKE, + output [12:0] SDRAM_A, + output [1:0] SDRAM_BA, + inout [15:0] SDRAM_DQ, + output SDRAM_DQML, + output SDRAM_DQMH, + output SDRAM_nCS, + output SDRAM_nCAS, + output SDRAM_nRAS, + output SDRAM_nWE, + +`ifdef MISTER_DUAL_SDRAM + //Secondary SDRAM + //Set all output SDRAM_* signals to Z ASAP if SDRAM2_EN is 0 + input SDRAM2_EN, + output SDRAM2_CLK, + output [12:0] SDRAM2_A, + output [1:0] SDRAM2_BA, + inout [15:0] SDRAM2_DQ, + output SDRAM2_nCS, + output SDRAM2_nCAS, + output SDRAM2_nRAS, + output SDRAM2_nWE, +`endif + + input UART_CTS, + output UART_RTS, + input UART_RXD, + output UART_TXD, + output UART_DTR, + input UART_DSR, + + // Open-drain User port. + // 0 - D+/RX + // 1 - D-/TX + // 2..6 - USR2..USR6 + // Set USER_OUT to 1 to read from USER_IN. + input [6:0] USER_IN, + output [6:0] USER_OUT, + + input OSD_STATUS +); assign {SD_SCK, SD_MOSI, SD_CS} = 'Z; assign {UART_RTS, UART_TXD, UART_DTR} = 0; assign {SDRAM_DQ, SDRAM_A, SDRAM_BA, SDRAM_CLK, SDRAM_CKE, SDRAM_DQML, SDRAM_DQMH, SDRAM_nWE, SDRAM_nCAS, SDRAM_nRAS, SDRAM_nCS} = 'Z; - -assign VGA_F1 = 0; -assign VGA_SCALER= 0; -assign USER_OUT = '1; -assign LED_USER = ioctl_download; -assign LED_DISK = 0; -assign LED_POWER = 0; -assign BUTTONS = 0; + +assign VGA_F1 = 0; +assign VGA_SCALER= 0; +assign USER_OUT = '1; +assign LED_USER = ioctl_download; +assign LED_DISK = 0; +assign LED_POWER = 0; +assign BUTTONS = 0; assign AUDIO_MIX = 0; -assign FB_FORCE_BLANK = 0; -assign HDMI_FREEZE = 0; - -wire [1:0] ar = status[15:14]; - -assign VIDEO_ARX = (!ar) ? ((status[2] | mod_ponp) ? 8'd4 : 8'd3) : (ar - 1'd1); -assign VIDEO_ARY = (!ar) ? ((status[2] | mod_ponp) ? 8'd3 : 8'd4) : 12'd0; - -`include "build_id.v" -localparam CONF_STR = { - "A.PACMAN;;", - "H0OEF,Aspect ratio,Original,Full Screen,[ARC1],[ARC2];", - "H1H0O2,Orientation,Vert,Horz;", - "O35,Scandoubler Fx,None,HQ2x,CRT 25%,CRT 50%,CRT 75%;", - "O7,Flip Screen,Off,On;", - "OQS,CRT H-sync Adjust,0,1,2,3,4,5,6,7;", - "OTV,CRT V-sync Adjust,0,1,2,3,4,5,6,7;", +assign FB_FORCE_BLANK = 0; +assign HDMI_FREEZE = 0; + +wire [1:0] ar = status[15:14]; + +assign VIDEO_ARX = (!ar) ? ((status[2] | mod_ponp) ? 8'd4 : 8'd3) : (ar - 1'd1); +assign VIDEO_ARY = (!ar) ? ((status[2] | mod_ponp) ? 8'd3 : 8'd4) : 12'd0; + +`include "build_id.v" +localparam CONF_STR = { + "A.PACMAN;;", + "H0OEF,Aspect ratio,Original,Full Screen,[ARC1],[ARC2];", + "H1H0O2,Orientation,Vert,Horz;", + "O35,Scandoubler Fx,None,HQ2x,CRT 25%,CRT 50%,CRT 75%;", + "O7,Flip Screen,Off,On;", + "OQS,CRT H-sync Adjust,0,1,2,3,4,5,6,7;", + "OTV,CRT V-sync Adjust,0,1,2,3,4,5,6,7;", "-;", + "H2OP,Autosave Hiscores,Off,On;", "P1,Pause options;", - "P1OP,Pause when OSD is open,On,Off;", - "P1OQ,Dim video after 10s,On,Off;", - "-;", - "DIP;", - "-;", - "R0,Reset;", - "J1,Fire,Start 1P,Start 2P,Coin,Cheat,Pause;", - "jn,A,Start,Select,R,L,X;", - "DEFMRA,Puck Man (Japan set 1).mra;", // default MRA to be used when core is uploaded by USB blaster (debug) - "V,v",`BUILD_DATE -}; - -//////////////////// CLOCKS /////////////////// - -wire clk_sys, clk_vid; -wire pll_locked; - -pll pll -( - .refclk(CLK_50M), - .rst(0), - .outclk_0(clk_vid), - .outclk_1(clk_sys), - .locked(pll_locked) -); - -reg ce_6m; -always @(posedge clk_sys) begin - reg [1:0] div; - - div <= div + 1'd1; - ce_6m <= !div; -end - -reg ce_4m; -always @(posedge clk_sys) begin - reg [2:0] div; - - div <= div + 1'd1; - if(div == 5) div <= 0; - ce_4m <= !div; -end - -reg ce_1m79; -always @(posedge clk_sys) begin - reg [3:0] div; - - div <= div + 1'd1; - if(div == 12) div <= 0; - ce_1m79 <= !div; -end - -/////////////////////////////////////////////////// - -wire [31:0] status; -wire [1:0] buttons; -wire forced_scandoubler; -wire direct_video; - -wire ioctl_download; -wire ioctl_upload; -wire [7:0] ioctl_index; -wire ioctl_wr; -wire [24:0] ioctl_addr; -wire [7:0] ioctl_dout; -wire [7:0] ioctl_din; - -wire [15:0] joy1 = (mod_club | mod_jmpst) ? joy1a : (joy1a | joy2a); -wire [15:0] joy2 = (mod_club | mod_jmpst) ? joy2a : (joy1a | joy2a); -wire [15:0] joy1a; -wire [15:0] joy2a; - -wire [21:0] gamma_bus; - -hps_io #(.CONF_STR(CONF_STR)) hps_io -( - .clk_sys(clk_sys), - .HPS_BUS(HPS_BUS), - .EXT_BUS(), - - .buttons(buttons), - .status(status), - .status_menumask({mod_ponp,direct_video}), - .forced_scandoubler(forced_scandoubler), - .gamma_bus(gamma_bus), - .direct_video(direct_video), - - .ioctl_download(ioctl_download), - .ioctl_upload(ioctl_upload), - .ioctl_wr(ioctl_wr), - .ioctl_addr(ioctl_addr), - .ioctl_dout(ioctl_dout), - .ioctl_din(ioctl_din), - .ioctl_index(ioctl_index), - - .joystick_0(joy1a), - .joystick_1(joy2a) -); - -reg mod_plus = 0; -reg mod_jmpst= 0; -reg mod_club = 0; -reg mod_orig = 0; -//reg mod_crush= 0; -reg mod_bird = 0; -reg mod_ms = 0; -reg mod_gork = 0; -reg mod_mrtnt= 0; -reg mod_woodp= 0; -reg mod_eeek = 0; -reg mod_alib = 0; -reg mod_ponp = 0; -reg mod_van = 0; -reg mod_pmm = 0; -reg mod_dshop= 0; -reg mod_glob = 0; - -wire mod_gm = mod_gork | mod_mrtnt; - -always @(posedge clk_sys) begin - reg [7:0] mod = 0; - if (ioctl_wr & (ioctl_index==1)) mod <= ioctl_dout; - - mod_orig <= (mod == 0); - mod_plus <= (mod == 1); - mod_club <= (mod == 2); - //mod_crush<= (mod == 3); - mod_bird <= (mod == 4); - mod_ms <= (mod == 5); - mod_gork <= (mod == 6); - mod_mrtnt<= (mod == 7); - mod_woodp<= (mod == 8); - mod_eeek <= (mod == 9); - mod_alib <= (mod == 10); - mod_ponp <= (mod == 11); - mod_van <= (mod == 12); - mod_pmm <= (mod == 13); - mod_dshop<= (mod == 14); - mod_glob <= (mod == 15); - mod_jmpst<= (mod == 16); -end - -reg [7:0] sw[8]; -always @(posedge clk_sys) if (ioctl_wr && (ioctl_index==254) && !ioctl_addr[24:3]) sw[ioctl_addr[2:0]] <= ioctl_dout; - -wire m_up,m_down,m_left,m_right; -joyonedir jod -( - clk_sys, - mod_bird, - { - joy1[3], - joy1[2], - joy1[1], - joy1[0] - }, - {m_up,m_down,m_left,m_right} -); - -wire m_up_2,m_down_2,m_left_2,m_right_2; -joyonedir jod_2 -( - clk_sys, - mod_bird, - { - joy2[3], - joy2[2], - joy2[1], - joy2[0] - }, - {m_up_2,m_down_2,m_left_2,m_right_2} -); - -wire m_fire = joy1[4]; -wire m_fire_2 = joy2[4]; -wire m_start = joy1[5] | joy2[5]; -wire m_start_2 = joy1[6] | joy2[6]; -wire m_coin = joy1[7] | joy2[7]; + "P1ON,Pause when OSD is open,On,Off;", + "P1OO,Dim video after 10s,On,Off;", + "-;", + "DIP;", + "-;", + "R0,Reset;", + "J1,Fire,Start 1P,Start 2P,Coin,Cheat,Pause;", + "jn,A,Start,Select,R,L,X;", + "DEFMRA,Puck Man (Japan set 1).mra;", // default MRA to be used when core is uploaded by USB blaster (debug) + "V,v",`BUILD_DATE +}; + +//////////////////// CLOCKS /////////////////// + +wire clk_sys, clk_vid; +wire pll_locked; + +pll pll +( + .refclk(CLK_50M), + .rst(0), + .outclk_0(clk_vid), + .outclk_1(clk_sys), + .locked(pll_locked) +); + +reg ce_6m; +always @(posedge clk_sys) begin + reg [1:0] div; + + div <= div + 1'd1; + ce_6m <= !div; +end + +reg ce_4m; +always @(posedge clk_sys) begin + reg [2:0] div; + + div <= div + 1'd1; + if(div == 5) div <= 0; + ce_4m <= !div; +end + +reg ce_1m79; +always @(posedge clk_sys) begin + reg [3:0] div; + + div <= div + 1'd1; + if(div == 12) div <= 0; + ce_1m79 <= !div; +end + +/////////////////////////////////////////////////// + +wire [31:0] status; +wire [1:0] buttons; +wire forced_scandoubler; +wire direct_video; + +wire ioctl_download; +wire ioctl_upload; +wire ioctl_upload_req; +wire [7:0] ioctl_index; +wire ioctl_wr; +wire [24:0] ioctl_addr; +wire [7:0] ioctl_dout; +wire [7:0] ioctl_din; + +wire [15:0] joy1 = (mod_club | mod_jmpst) ? joy1a : (joy1a | joy2a); +wire [15:0] joy2 = (mod_club | mod_jmpst) ? joy2a : (joy1a | joy2a); +wire [15:0] joy1a; +wire [15:0] joy2a; + +wire [21:0] gamma_bus; + +hps_io #(.CONF_STR(CONF_STR)) hps_io +( + .clk_sys(clk_sys), + .HPS_BUS(HPS_BUS), + .EXT_BUS(), + + .buttons(buttons), + .status(status), + .status_menumask({~hs_configured,mod_ponp,direct_video}), + .forced_scandoubler(forced_scandoubler), + .gamma_bus(gamma_bus), + .direct_video(direct_video), + + .ioctl_download(ioctl_download), + .ioctl_upload(ioctl_upload), + .ioctl_upload_req(ioctl_upload_req), + .ioctl_wr(ioctl_wr), + .ioctl_addr(ioctl_addr), + .ioctl_dout(ioctl_dout), + .ioctl_din(ioctl_din), + .ioctl_index(ioctl_index), + + .joystick_0(joy1a), + .joystick_1(joy2a) +); + +reg mod_plus = 0; +reg mod_jmpst= 0; +reg mod_club = 0; +reg mod_orig = 0; +//reg mod_crush= 0; +reg mod_bird = 0; +reg mod_ms = 0; +reg mod_gork = 0; +reg mod_mrtnt= 0; +reg mod_woodp= 0; +reg mod_eeek = 0; +reg mod_alib = 0; +reg mod_ponp = 0; +reg mod_van = 0; +reg mod_pmm = 0; +reg mod_dshop= 0; +reg mod_glob = 0; + +wire mod_gm = mod_gork | mod_mrtnt; + +always @(posedge clk_sys) begin + reg [7:0] mod = 0; + if (ioctl_wr & (ioctl_index==1)) mod <= ioctl_dout; + + mod_orig <= (mod == 0); + mod_plus <= (mod == 1); + mod_club <= (mod == 2); + //mod_crush<= (mod == 3); + mod_bird <= (mod == 4); + mod_ms <= (mod == 5); + mod_gork <= (mod == 6); + mod_mrtnt<= (mod == 7); + mod_woodp<= (mod == 8); + mod_eeek <= (mod == 9); + mod_alib <= (mod == 10); + mod_ponp <= (mod == 11); + mod_van <= (mod == 12); + mod_pmm <= (mod == 13); + mod_dshop<= (mod == 14); + mod_glob <= (mod == 15); + mod_jmpst<= (mod == 16); +end + +reg [7:0] sw[8]; +always @(posedge clk_sys) if (ioctl_wr && (ioctl_index==254) && !ioctl_addr[24:3]) sw[ioctl_addr[2:0]] <= ioctl_dout; + +wire m_up,m_down,m_left,m_right; +joyonedir jod +( + clk_sys, + mod_bird, + { + joy1[3], + joy1[2], + joy1[1], + joy1[0] + }, + {m_up,m_down,m_left,m_right} +); + +wire m_up_2,m_down_2,m_left_2,m_right_2; +joyonedir jod_2 +( + clk_sys, + mod_bird, + { + joy2[3], + joy2[2], + joy2[1], + joy2[0] + }, + {m_up_2,m_down_2,m_left_2,m_right_2} +); + +wire m_fire = joy1[4]; +wire m_fire_2 = joy2[4]; +wire m_start = joy1[5] | joy2[5]; +wire m_start_2 = joy1[6] | joy2[6]; +wire m_coin = joy1[7] | joy2[7]; wire m_cheat = joy1[8] | joy2[8]; wire m_pause = joy1[9] | joy2[9]; @@ -392,177 +395,180 @@ pause #(3,3,2,24) pause ( .*, .user_button(m_pause), .pause_request(hs_pause), - .options(~status[26:25]) -); - -wire hblank, vblank; -wire ce_vid = ce_6m; -wire hs, vs; -wire [2:0] r,g; -wire [1:0] b; - -arcade_video #(288,8) arcade_video -( - .*, - - .clk_video(clk_vid), - .ce_pix(ce_vid), - - .RGB_in(rgb_out), - .HBlank(hblank), - .VBlank(vblank), - .HSync(hs), - .VSync(vs), - - .fx(status[5:3]) -); - -wire no_rotate = status[2] | direct_video | mod_ponp; -wire rotate_ccw = 0; -screen_rotate screen_rotate (.*); - -wire [9:0] audio; -assign AUDIO_L = {audio, 6'd0}; -assign AUDIO_R = AUDIO_L; -assign AUDIO_S = mod_van; - -wire [7:0] in0xor = mod_ponp ? 8'hE0 : 8'hFF; -wire [7:0] in1xor = mod_ponp ? 8'h00 : 8'hFF; - -wire reset; -assign reset = RESET | status[0] | buttons[1]; - -pacman pacman -( - .O_VIDEO_R(r), - .O_VIDEO_G(g), - .O_VIDEO_B(b), - .O_HSYNC(hs), - .O_VSYNC(vs), - .O_HBLANK(hblank), - .O_VBLANK(vblank), - - .dn_addr(ioctl_addr[15:0]), - .dn_data(ioctl_dout), - .dn_wr(ioctl_wr && !ioctl_index), - - .O_AUDIO(audio), - - .in0(sw[0] & (in0xor ^ { - mod_eeek & m_fire_2, - mod_alib & m_fire, - m_coin, - ((mod_orig | mod_plus | mod_ms | mod_bird | mod_alib | mod_woodp) & m_cheat) | ((mod_ponp | mod_van | mod_dshop) & m_fire), - m_down, - m_right, - m_left, - m_up - })), - - .in1(sw[1] & (in1xor ^ { - mod_gm & m_fire_2, - m_start_2 | (mod_eeek & m_fire) | (mod_jmpst & m_fire_2), - m_start | (mod_jmpst & m_fire), - (mod_gm & m_fire) | ((mod_alib | mod_ponp | mod_van | mod_dshop) & m_fire_2), - ~mod_pmm & m_down_2, - mod_pmm ? m_fire : m_right_2, - ~mod_pmm & m_left_2, - ~mod_pmm & m_up_2 - })), - .dipsw1(sw[2]), - .dipsw2((mod_ponp | mod_van | mod_dshop) ? sw[3] : 8'hFF), - - .mod_plus(mod_plus), - .mod_jmpst(mod_jmpst), - .mod_bird(mod_bird), - .mod_ms(mod_ms), - .mod_mrtnt(mod_mrtnt), - .mod_woodp(mod_woodp), - .mod_eeek(mod_eeek), - .mod_alib(mod_alib), - .mod_ponp(mod_ponp | mod_van | mod_dshop), - .mod_van(mod_van | mod_dshop), - .mod_dshop(mod_dshop), - .mod_glob(mod_glob), - .mod_club(mod_club), - .flip_screen(status[7]), - .h_offset(status[28:26]), - .v_offset(status[31:29]), - - .RESET(reset), - .CLK(clk_sys), - .ENA_6(ce_6m), - .ENA_4(ce_4m), + .options(~status[24:23]) +); + +wire hblank, vblank; +wire ce_vid = ce_6m; +wire hs, vs; +wire [2:0] r,g; +wire [1:0] b; + +arcade_video #(288,8) arcade_video +( + .*, + + .clk_video(clk_vid), + .ce_pix(ce_vid), + + .RGB_in(rgb_out), + .HBlank(hblank), + .VBlank(vblank), + .HSync(hs), + .VSync(vs), + + .fx(status[5:3]) +); + +wire no_rotate = status[2] | direct_video | mod_ponp; +wire rotate_ccw = 0; +screen_rotate screen_rotate (.*); + +wire [9:0] audio; +assign AUDIO_L = {audio, 6'd0}; +assign AUDIO_R = AUDIO_L; +assign AUDIO_S = mod_van; + +wire [7:0] in0xor = mod_ponp ? 8'hE0 : 8'hFF; +wire [7:0] in1xor = mod_ponp ? 8'h00 : 8'hFF; + +wire reset; +assign reset = RESET | status[0] | buttons[1]; + +pacman pacman +( + .O_VIDEO_R(r), + .O_VIDEO_G(g), + .O_VIDEO_B(b), + .O_HSYNC(hs), + .O_VSYNC(vs), + .O_HBLANK(hblank), + .O_VBLANK(vblank), + + .dn_addr(ioctl_addr[15:0]), + .dn_data(ioctl_dout), + .dn_wr(ioctl_wr && !ioctl_index), + + .O_AUDIO(audio), + + .in0(sw[0] & (in0xor ^ { + mod_eeek & m_fire_2, + mod_alib & m_fire, + m_coin, + ((mod_orig | mod_plus | mod_ms | mod_bird | mod_alib | mod_woodp) & m_cheat) | ((mod_ponp | mod_van | mod_dshop) & m_fire), + m_down, + m_right, + m_left, + m_up + })), + + .in1(sw[1] & (in1xor ^ { + mod_gm & m_fire_2, + m_start_2 | (mod_eeek & m_fire) | (mod_jmpst & m_fire_2), + m_start | (mod_jmpst & m_fire), + (mod_gm & m_fire) | ((mod_alib | mod_ponp | mod_van | mod_dshop) & m_fire_2), + ~mod_pmm & m_down_2, + mod_pmm ? m_fire : m_right_2, + ~mod_pmm & m_left_2, + ~mod_pmm & m_up_2 + })), + .dipsw1(sw[2]), + .dipsw2((mod_ponp | mod_van | mod_dshop) ? sw[3] : 8'hFF), + + .mod_plus(mod_plus), + .mod_jmpst(mod_jmpst), + .mod_bird(mod_bird), + .mod_ms(mod_ms), + .mod_mrtnt(mod_mrtnt), + .mod_woodp(mod_woodp), + .mod_eeek(mod_eeek), + .mod_alib(mod_alib), + .mod_ponp(mod_ponp | mod_van | mod_dshop), + .mod_van(mod_van | mod_dshop), + .mod_dshop(mod_dshop), + .mod_glob(mod_glob), + .mod_club(mod_club), + .flip_screen(status[7]), + .h_offset(status[28:26]), + .v_offset(status[31:29]), + + .RESET(reset), + .CLK(clk_sys), + .ENA_6(ce_6m), + .ENA_4(ce_4m), .ENA_1M79(ce_1m79), - .pause(pause_cpu), - - .hs_address(hs_address), + .pause(pause_cpu), + + .hs_address(hs_address), + .hs_data_out(hs_data_out), .hs_data_in(hs_data_in), - .hs_data_out(ioctl_din), - .hs_write(hs_write), - .hs_access(hs_access) -); - -// HISCORE SYSTEM -// -------------- - -wire [11:0]hs_address; -wire [7:0]hs_data_in; -wire hs_write; -wire hs_access; -wire hs_pause; + .hs_write_enable(hs_write_enable), + .hs_access_read(hs_access_read), + .hs_access_write(hs_access_write) +); + +// HISCORE SYSTEM +// -------------- + +wire [11:0]hs_address; +wire [7:0] hs_data_in; +wire [7:0] hs_data_out; +wire hs_write_enable; +wire hs_access_read; +wire hs_access_write; +wire hs_pause; +wire hs_configured; hiscore #( .HS_ADDRESSWIDTH(12), .CFG_ADDRESSWIDTH(3), .CFG_LENGTHWIDTH(2) -) hi ( - .clk(clk_sys), - .reset(reset), - .ioctl_upload(ioctl_upload), - .ioctl_download(ioctl_download), - .ioctl_wr(ioctl_wr), - .ioctl_addr(ioctl_addr), - .ioctl_dout(ioctl_dout), - .ioctl_din(ioctl_din), - .ioctl_index(ioctl_index), - - .ram_address(hs_address), - .data_to_ram(hs_data_in), - .ram_write(hs_write), - .ram_access(hs_access), - .pause_cpu(hs_pause) -); - - -endmodule - -module joyonedir -( - input clk, - input dis, - input [3:0] indir, - output [3:0] outdir -); - -reg [3:0] mask = 0; -reg [3:0] in1,in2; -wire [3:0] innew = in1 & ~in2; - -assign outdir = in1 & mask; - -always @(posedge clk) begin - - in1 <= indir; - in2 <= in1; - - if(innew[0]) mask <= 1; - if(innew[1]) mask <= 2; - if(innew[2]) mask <= 4; - if(innew[3]) mask <= 8; - - if(!(indir & mask) || dis) mask <= '1; -end - -endmodule +) hi ( + .*, + .clk(clk_sys), + .paused(pause_cpu), + .autosave(status[25]), + .ram_address(hs_address), + .data_from_ram(hs_data_out), + .data_to_ram(hs_data_in), + .data_from_hps(ioctl_dout), + .data_to_hps(ioctl_din), + .ram_write(hs_write_enable), + .ram_intent_read(hs_access_read), + .ram_intent_write(hs_access_write), + .pause_cpu(hs_pause), + .configured(hs_configured) +); + + +endmodule + +module joyonedir +( + input clk, + input dis, + input [3:0] indir, + output [3:0] outdir +); + +reg [3:0] mask = 0; +reg [3:0] in1,in2; +wire [3:0] innew = in1 & ~in2; + +assign outdir = in1 & mask; + +always @(posedge clk) begin + + in1 <= indir; + in2 <= in1; + + if(innew[0]) mask <= 1; + if(innew[1]) mask <= 2; + if(innew[2]) mask <= 4; + if(innew[3]) mask <= 8; + + if(!(indir & mask) || dis) mask <= '1; +end + +endmodule diff --git a/README.txt b/README.txt index 7003986..9d8a2b0 100644 --- a/README.txt +++ b/README.txt @@ -30,6 +30,32 @@ -- -- Joystick support. -- +---------------------------------------------- + +Hiscore save/load: + +Save and load of hiscores is supported for the following games on this core: + - Alibaba 40 Thieves + - Crush Roller + - Dream Shopper + - Eggor + - Gorkans + - Lizard Wizard + - Mr. TNT + - Ms. Pacman + - Pac-Man (Midway) + - Pacman Club + - Pacman Plus + - Ponpoko + - Puck Man + - Woodpecker + +To save your hiscores manually, press the 'Save Settings' option in the OSD. Hiscores will be automatically loaded when the core is started. + +To enable automatic saving of hiscores, turn on the 'Autosave Hiscores' option, press the 'Save Settings' option in the OSD, and reload the core. Hiscores will then be automatically saved (if they have changed) any time the OSD is opened. + +Hiscore data is stored in /media/fat/config/nvram/ as ```.nvm``` + --------------------------------------------------------------------------------- *** Attention *** diff --git a/releases/Ali Baba and 40 Thieves.mra b/releases/Ali Baba and 40 Thieves.mra index e751b9c..f825cbe 100644 --- a/releases/Ali Baba and 40 Thieves.mra +++ b/releases/Ali Baba and 40 Thieves.mra @@ -65,7 +65,7 @@ - 00 00 00 00 00 FF 00 02 00 02 00 01 00 FF 00 00 + 00 00 00 00 00 FF 00 02 00 02 00 01 00 FF 04 00 00 00 4E 88 00 04 00 00 00 00 43 ED 00 06 40 40 00 00 43 D1 00 01 48 48 diff --git a/releases/Arcade-Pacman_20210915.rbf b/releases/Arcade-Pacman_20210915.rbf new file mode 100644 index 0000000..d98c382 Binary files /dev/null and b/releases/Arcade-Pacman_20210915.rbf differ diff --git a/releases/Mr. TNT.mra b/releases/Mr. TNT.mra index a290e27..384e782 100644 --- a/releases/Mr. TNT.mra +++ b/releases/Mr. TNT.mra @@ -59,7 +59,7 @@ - 00 00 00 00 00 FF 00 02 00 02 00 01 00 FF 00 00 + 00 00 00 00 00 FF 00 02 00 02 00 01 00 FF 02 00 00 00 4C B3 00 3C 4C 01 00 00 43 ED 00 06 00 40 diff --git a/releases/Ms. Pac-Man.mra b/releases/Ms. Pac-Man.mra index a955400..9452cdd 100644 --- a/releases/Ms. Pac-Man.mra +++ b/releases/Ms. Pac-Man.mra @@ -60,7 +60,7 @@ - 00 00 00 00 00 FF 00 02 00 02 00 01 00 FF 00 00 + 00 00 00 00 00 FF 00 02 00 02 00 01 00 FF 02 00 00 00 4E 88 00 04 00 00 00 00 43 ED 00 06 40 40 00 00 43 D1 00 01 48 48 diff --git a/releases/Pac-Man (Midway).mra b/releases/Pac-Man (Midway).mra index 7fcb2d0..647fb00 100644 --- a/releases/Pac-Man (Midway).mra +++ b/releases/Pac-Man (Midway).mra @@ -58,7 +58,7 @@ - 00 00 00 00 00 FF 00 02 00 02 00 01 00 FF 00 00 + 00 00 00 00 00 FF 00 02 00 02 00 01 00 FF 02 00 00 00 4E 88 00 04 00 00 00 00 43 ED 00 06 40 40 00 00 43 D1 00 01 48 48 diff --git a/releases/Pac-Man Club- Club Lambada (AR).mra b/releases/Pac-Man Club- Club Lambada (AR).mra index b13d866..27660ef 100644 --- a/releases/Pac-Man Club- Club Lambada (AR).mra +++ b/releases/Pac-Man Club- Club Lambada (AR).mra @@ -57,7 +57,7 @@ - 00 00 00 00 00 FF 00 02 00 02 00 01 00 FF 00 00 + 00 00 00 00 00 FF 00 02 00 02 00 01 00 FF 02 00 00 00 4E 88 00 04 00 00 00 00 43 ED 00 06 40 40 00 00 43 D1 00 01 59 59 diff --git a/releases/Pac-Man Plus.mra b/releases/Pac-Man Plus.mra index 5f7e964..d714b77 100644 --- a/releases/Pac-Man Plus.mra +++ b/releases/Pac-Man Plus.mra @@ -58,7 +58,7 @@ - 00 00 00 00 00 FF 00 02 00 02 00 01 00 FF 00 00 + 00 00 00 00 00 FF 00 02 00 02 00 01 00 FF 02 00 00 00 4E 88 00 04 00 00 00 00 43 ED 00 06 40 40 00 00 43 D1 00 01 48 48 diff --git a/releases/Ponpoko.mra b/releases/Ponpoko.mra index 281b514..2b5350c 100644 --- a/releases/Ponpoko.mra +++ b/releases/Ponpoko.mra @@ -59,7 +59,8 @@ - 00 00 00 00 00 FF 00 02 00 02 00 01 00 FF 00 00 + 00 00 00 00 00 FF 00 02 00 02 00 01 00 08 00 FF + FF FF 00 00 00 00 00 00 00 00 4C 40 00 03 00 00 00 00 4E 5A 00 13 00 00 00 00 40 6C 00 06 0F 00 diff --git a/releases/Puck Man (JP, Set 1).mra b/releases/Puck Man (JP, Set 1).mra index f7566b0..e05d135 100644 --- a/releases/Puck Man (JP, Set 1).mra +++ b/releases/Puck Man (JP, Set 1).mra @@ -72,7 +72,7 @@ - 00 00 00 00 00 FF 00 02 00 02 00 01 00 FF 00 00 + 00 00 00 00 00 FF 00 02 00 02 00 01 00 FF 02 00 00 00 4E 88 00 04 00 00 00 00 43 ED 00 06 40 40 00 00 43 D1 00 01 48 48 diff --git a/releases/Woodpecker (Set 1).mra b/releases/Woodpecker (Set 1).mra index d4e4f28..f77a13c 100644 --- a/releases/Woodpecker (Set 1).mra +++ b/releases/Woodpecker (Set 1).mra @@ -63,7 +63,8 @@ - 00 00 00 00 00 FF 00 02 00 02 00 01 00 FF 00 00 + 00 00 00 00 00 FF 00 02 00 02 00 01 00 08 00 FF + FF FD 00 00 00 00 00 00 00 00 4E 88 00 03 00 00 00 00 43 ED 00 06 40 40 00 00 4D DA 00 01 03 03 diff --git a/rtl/hiscore.v b/rtl/hiscore.v index fcd5464..aa365e3 100644 --- a/rtl/hiscore.v +++ b/rtl/hiscore.v @@ -30,6 +30,13 @@ 0005 - 2021-03-18 - Add configurable score table width, clean up some stupid mistakes 0006 - 2021-03-27 - Move 'tweakable' parameters into MRA data header 0007 - 2021-04-15 - Improve state machine maintainability, add new 'pause padding' states + 0008 - 2021-05-12 - Feed back core-level pause to halt startup timer + 0009 - 2021-07-31 - Split hiscore extraction from upload (updates hiscore buffer on OSD open) + 0010 - 2021-08-03 - Add hiscore buffer and change detection (ready for autosave!) + 0011 - 2021-08-07 - Optional auto-save on OSD open + 0012 - 2021-08-17 - Add variable length change detection mask + 0013 - 2021-09-01 - Output configured signal for autosave option menu masking + 0014 - 2021-09-09 - Fix turning on autosave w/o core reload ============================================================================ */ @@ -44,21 +51,28 @@ module hiscore ) ( input clk, + input paused, // Signal from core confirming CPU is paused input reset, + input autosave, // Auto-save enabled (active high) input ioctl_upload, + output reg ioctl_upload_req, input ioctl_download, input ioctl_wr, input [24:0] ioctl_addr, - input [7:0] ioctl_dout, - input [7:0] ioctl_din, input [7:0] ioctl_index, + input OSD_STATUS, - output [HS_ADDRESSWIDTH-1:0] ram_address, // Address in game RAM to read/write score data - output [7:0] data_to_ram, // Data to write to game RAM - output reg ram_write, // Write to game RAM (active high) - output ram_access, // RAM read or write required (active high) - output reg pause_cpu // Pause core CPU to prepare for/relax after RAM access + input [7:0] data_from_hps, // Incoming data from HPS ioctl_dout + input [7:0] data_from_ram, // Incoming data from game RAM + output [HS_ADDRESSWIDTH-1:0] ram_address, // Address in game RAM to read/write score data + output [7:0] data_to_hps, // Data to send to HPS ioctl_din + output [7:0] data_to_ram, // Data to send to game RAM + output reg ram_write, // Write to game RAM (active high) + output ram_intent_read, // RAM read required (active high) + output ram_intent_write, // RAM write required (active high) + output reg pause_cpu, // Pause core CPU to prepare for/relax after RAM access + output configured // Hiscore module has valid configuration (active high) ); // Parameters read from config header @@ -68,13 +82,13 @@ reg [15:0] CHECK_HOLD =16'd2; // Hold time for start/end check reads reg [15:0] WRITE_HOLD =16'd2; // Hold time for game RAM writes reg [15:0] WRITE_REPEATCOUNT =16'b1; // Number of times to write score to game RAM reg [15:0] WRITE_REPEATWAIT =16'b1111; // Delay between subsequent write attempts to game RAM -reg [7:0] ACCESS_PAUSEPAD =8'd4; // Cycles to wait with paused CPU before and after RAM access - +reg [7:0] ACCESS_PAUSEPAD =8'd4; // Cycles to wait with paused CPU before and after RAM access +reg [7:0] CHANGEMASK =1'b0; // Length of change mask // State machine constants -localparam SM_STATEWIDTH = 4; // Width of state machine net +localparam SM_STATEWIDTH = 5; // Width of state machine net -localparam SM_INIT = 0; +localparam SM_INIT_RESTORE = 0; localparam SM_TIMER = 1; localparam SM_CHECKPREP = 2; @@ -90,7 +104,19 @@ localparam SM_WRITEDONE = 10; localparam SM_WRITECOMPLETE = 11; localparam SM_WRITERETRY = 12; -localparam SM_STOPPED = 13; +localparam SM_COMPAREINIT = 16; +localparam SM_COMPAREBEGIN = 17; +localparam SM_COMPAREREADY = 18; +localparam SM_COMPAREREAD = 19; +localparam SM_COMPAREDONE = 20; +localparam SM_COMPARECOMPLETE = 21; + +localparam SM_EXTRACTINIT = 22; +localparam SM_EXTRACT = 23; +localparam SM_EXTRACTSAVE = 24; +localparam SM_EXTRACTCOMPLETE = 25; + +localparam SM_STOPPED = 30; /* Hiscore config data structure (version 1) @@ -107,7 +133,8 @@ Hiscore config data structure (version 1) 2 byte WRITE_HOLD 2 byte WRITE_REPEATCOUNT 2 byte WRITE_REPEATWAIT -2 byte (padding/future use) +1 byte ACCESS_PAUSEPAD +1 byte CHANGEMASK - Entry format (when CFG_LENGTHWIDTH=1) 00 00 43 0b 0f 10 01 00 @@ -132,77 +159,100 @@ Hiscore config data structure (version 1) */ -localparam HS_VERSION =7; // Version identifier for module +localparam HS_VERSION =14; // Version identifier for module localparam HS_DUMPFORMAT =1; // Version identifier for dump format localparam HS_HEADERLENGTH =16; // Size of header chunk (default=16 bytes) // HS_DUMPFORMAT = 1 --> No header, just the extracted hiscore data -// Hiscore config and dump status -wire downloading_config; -wire parsing_header; -wire downloading_dump; -wire uploading_dump; -reg downloaded_config = 1'b0; -reg downloaded_dump = 1'b0; -reg uploaded_dump = 1'b0; -reg [3:0] initialised; -reg writing_scores = 1'b0; -reg checking_scores = 1'b0; +// Hiscore config tracking +wire downloading_config; // Is hiscore configuration currently being loaded from HPS? +reg downloaded_config = 1'b0; // Has hiscore configuration been loaded successfully +wire parsing_header; // Is hiscore configuration header currently being parsed? +wire parsing_mask; // Is hiscore configuration change mask currently being parsed? (optional 2nd line of config) +// Hiscore data tracking +wire downloading_dump; // Is hiscore data currently being loaded from HPS? +reg downloaded_dump = 1'b0; // Has hiscore data been loaded successfully +wire uploading_dump; // Is hiscore data currently being sent to HPS? +reg extracting_dump = 1'b0; // Is hiscore data currently being extracted from game RAM? +reg restoring_dump = 1'b0; // Is hiscore data currently being (or waiting to) restore to game RAM + +reg checking_scores = 1'b0; // Is state machine currently checking game RAM for highscore restore readiness +reg reading_scores = 1'b0; // Is state machine currently reading game RAM for highscore dump +reg writing_scores = 1'b0; // Is state machine currently restoring hiscore data to game RAM + +reg [3:0] initialised; // Number of times state machine has been initialised (debug only) + +assign configured = downloaded_config; assign downloading_config = ioctl_download && (ioctl_index==HS_CONFIGINDEX); -assign parsing_header = downloading_config && (ioctl_addr<=HS_HEADERLENGTH); +assign parsing_header = downloading_config && (ioctl_addr 8'b0) && (ioctl_addr < HS_HEADERLENGTH + CHANGEMASK); assign downloading_dump = ioctl_download && (ioctl_index==HS_DUMPINDEX); assign uploading_dump = ioctl_upload && (ioctl_index==HS_DUMPINDEX); -assign ram_access = uploading_dump | writing_scores | checking_scores; +assign ram_intent_read = reading_scores | checking_scores; +assign ram_intent_write = writing_scores; assign ram_address = ram_addr[HS_ADDRESSWIDTH-1:0]; -reg [(SM_STATEWIDTH-1):0] state = SM_INIT; // Current state machine index -reg [(SM_STATEWIDTH-1):0] next_state = SM_INIT; // Next state machine index to move to after wait timer expires -reg [31:0] wait_timer; // Wait timer for inital/read/write delays +reg [(SM_STATEWIDTH-1):0] state = SM_INIT_RESTORE; // Current state machine index +reg [(SM_STATEWIDTH-1):0] next_state = SM_INIT_RESTORE; // Next state machine index to move to after wait timer expires +reg [31:0] wait_timer; // Wait timer for inital/read/write delays reg [CFG_ADDRESSWIDTH-1:0] counter = 1'b0; // Index for current config table entry reg [CFG_ADDRESSWIDTH-1:0] total_entries = 1'b0; // Total count of config table entries reg reset_last = 1'b0; // Last cycle reset reg [7:0] write_counter = 1'b0; // Index of current game RAM write attempt +reg [255:0] change_mask; // Bit mask for dump change check + reg [7:0] last_ioctl_index; // Last cycle HPS IO index reg last_ioctl_download = 0;// Last cycle HPS IO download -reg last_ioctl_upload = 0; // Last cycle HPS IO upload -reg [7:0] last_ioctl_dout; // Last cycle HPS IO data out -reg [7:0] last_ioctl_dout2; // Last cycle +1 HPS IO data out -reg [7:0] last_ioctl_dout3; // Last cycle +2 HPS IO data out +reg [7:0] last_data_from_hps; // Last cycle HPS IO data out +reg [7:0] last_data_from_hps2; // Last cycle +1 HPS IO data out +reg [7:0] last_data_from_hps3; // Last cycle +2 HPS IO data out +reg last_OSD_STATUS; // Last cycle OSD status reg [24:0] ram_addr; // Target RAM address for hiscore read/write -reg [24:0] old_io_addr; reg [24:0] base_io_addr; -wire [23:0] addr_base; +wire [23:0] addr_base /* synthesis keep */; wire [(CFG_LENGTHWIDTH*8)-1:0] length; wire [24:0] end_addr = (addr_base + length - 1'b1); -reg [HS_SCOREWIDTH-1:0] local_addr; -wire [7:0] start_val; -wire [7:0] end_val; +reg [HS_SCOREWIDTH-1:0] data_addr; +reg [HS_SCOREWIDTH-1:0] buffer_addr; +wire [7:0] start_val /* synthesis keep */; +wire [7:0] end_val /* synthesis keep */; + +wire [7:0] hiscore_data_out /* synthesis keep */; +reg dump_write = 1'b0; +wire [7:0] hiscore_buffer_out /* synthesis keep */; +reg buffer_write = 1'b0; +reg [19:0] compare_length = 1'b0; +reg compare_nonzero = 1'b1; // High after extract and compare if any byte returned is non-zero +reg compare_changed = 1'b1; // High after extract and compare if any byte is different to current hiscore data +wire check_mask = change_mask[compare_length]/* synthesis keep */; +reg dump_dirty = 1'b0; // High if dump has changed since last save (or first load if no save has occurred) wire [23:0] address_data_in; wire [(CFG_LENGTHWIDTH*8)-1:0] length_data_in; -assign address_data_in = {last_ioctl_dout2, last_ioctl_dout, ioctl_dout}; -assign length_data_in = (CFG_LENGTHWIDTH == 1'b1) ? ioctl_dout : {last_ioctl_dout, ioctl_dout}; +assign address_data_in = {last_data_from_hps2, last_data_from_hps, data_from_hps}; +assign length_data_in = (CFG_LENGTHWIDTH == 1'b1) ? data_from_hps : {last_data_from_hps, data_from_hps}; -wire address_we = downloading_config & ~parsing_header & (ioctl_addr[2:0] == 3'd3); -wire length_we = downloading_config & ~parsing_header & (ioctl_addr[2:0] == 3'd3 + CFG_LENGTHWIDTH); -wire startdata_we = downloading_config & ~parsing_header & (ioctl_addr[2:0] == 3'd4 + CFG_LENGTHWIDTH); -wire enddata_we = downloading_config & ~parsing_header & (ioctl_addr[2:0] == 3'd5 + CFG_LENGTHWIDTH); +wire parsing_config = ~(parsing_header | parsing_mask); // Hiscore config lines are being parsed + +wire [CFG_ADDRESSWIDTH-1:0] config_upload_addr = ioctl_addr[CFG_ADDRESSWIDTH+2:3] - (9'd2 + CHANGEMASK[7:3]) /* synthesis keep */; + +wire address_we = downloading_config & parsing_config & (ioctl_addr[2:0] == 3'd3); +wire length_we = downloading_config & parsing_config & (ioctl_addr[2:0] == 3'd3 + CFG_LENGTHWIDTH); +wire startdata_we = downloading_config & parsing_config & (ioctl_addr[2:0] == 3'd4 + CFG_LENGTHWIDTH); +wire enddata_we = downloading_config & parsing_config & (ioctl_addr[2:0] == 3'd5 + CFG_LENGTHWIDTH); // RAM chunks used to store configuration data -// - address_table -// - length_table -// - startdata_table -// - enddata_table +// - Address table dpram_hs #(.aWidth(CFG_ADDRESSWIDTH),.dWidth(24)) address_table( .clk(clk), - .addr_a(ioctl_addr[CFG_ADDRESSWIDTH+2:3] - 2'd2), + .addr_a(config_upload_addr), .we_a(address_we & ioctl_wr), .d_a(address_data_in), .addr_b(counter), @@ -212,45 +262,61 @@ address_table( dpram_hs #(.aWidth(CFG_ADDRESSWIDTH),.dWidth(CFG_LENGTHWIDTH*8)) length_table( .clk(clk), - .addr_a(ioctl_addr[CFG_ADDRESSWIDTH+2:3] - 2'd2), + .addr_a(config_upload_addr), .we_a(length_we & ioctl_wr), .d_a(length_data_in), .addr_b(counter), .q_b(length) ); +// - Start data table dpram_hs #(.aWidth(CFG_ADDRESSWIDTH),.dWidth(8)) startdata_table( .clk(clk), - .addr_a(ioctl_addr[CFG_ADDRESSWIDTH+2:3] - 2'd2), + .addr_a(config_upload_addr), .we_a(startdata_we & ioctl_wr), - .d_a(ioctl_dout), + .d_a(data_from_hps), .addr_b(counter), .q_b(start_val) ); +// - End data table dpram_hs #(.aWidth(CFG_ADDRESSWIDTH),.dWidth(8)) enddata_table( .clk(clk), - .addr_a(ioctl_addr[CFG_ADDRESSWIDTH+2:3] - 2'd2), + .addr_a(config_upload_addr), .we_a(enddata_we & ioctl_wr), - .d_a(ioctl_dout), + .d_a(data_from_hps), .addr_b(counter), .q_b(end_val) ); -// RAM chunk used to store hiscore data +// RAM chunk used to store valid hiscore data dpram_hs #(.aWidth(HS_SCOREWIDTH),.dWidth(8)) -hiscoredata ( +hiscore_data ( .clk(clk), .addr_a(ioctl_addr[(HS_SCOREWIDTH-1):0]), .we_a(downloading_dump), - .d_a(ioctl_dout), - .addr_b(local_addr), - .we_b(ioctl_upload), - .d_b(ioctl_din), - .q_b(data_to_ram) + .d_a(data_from_hps), + .addr_b(data_addr), + .we_b(dump_write), + .d_b(hiscore_buffer_out), + .q_b(hiscore_data_out) +); +// RAM chunk used to store temporary high score data +dpram_hs #(.aWidth(HS_SCOREWIDTH),.dWidth(8)) +hiscore_buffer ( + .clk(clk), + .addr_a(buffer_addr), + .we_a(buffer_write), + .d_a(data_from_ram), + .q_a(hiscore_buffer_out) ); +assign data_to_ram = hiscore_data_out; +assign data_to_hps = hiscore_data_out; + wire [3:0] header_chunk = ioctl_addr[3:0]; +wire [7:0] mask_chunk = ioctl_addr[7:0] - 5'd16; +wire [255:0] mask_load_index = mask_chunk * 8; always @(posedge clk) begin @@ -262,19 +328,25 @@ begin begin if(ioctl_wr) begin - if(header_chunk == 4'd3) START_WAIT <= { last_ioctl_dout3, last_ioctl_dout2, last_ioctl_dout, ioctl_dout }; - if(header_chunk == 4'd5) CHECK_WAIT <= { last_ioctl_dout, ioctl_dout }; - if(header_chunk == 4'd7) CHECK_HOLD <= { last_ioctl_dout, ioctl_dout }; - if(header_chunk == 4'd9) WRITE_HOLD <= { last_ioctl_dout, ioctl_dout }; - if(header_chunk == 4'd11) WRITE_REPEATCOUNT <= { last_ioctl_dout, ioctl_dout }; - if(header_chunk == 4'd13) WRITE_REPEATWAIT <= { last_ioctl_dout, ioctl_dout }; - if(header_chunk == 4'd14) ACCESS_PAUSEPAD <= ioctl_dout; + if(header_chunk == 4'd3) START_WAIT <= { last_data_from_hps3, last_data_from_hps2, last_data_from_hps, data_from_hps }; + if(header_chunk == 4'd5) CHECK_WAIT <= { last_data_from_hps, data_from_hps }; + if(header_chunk == 4'd7) CHECK_HOLD <= { last_data_from_hps, data_from_hps }; + if(header_chunk == 4'd9) WRITE_HOLD <= { last_data_from_hps, data_from_hps }; + if(header_chunk == 4'd11) WRITE_REPEATCOUNT <= { last_data_from_hps, data_from_hps }; + if(header_chunk == 4'd13) WRITE_REPEATWAIT <= { last_data_from_hps, data_from_hps }; + if(header_chunk == 4'd14) ACCESS_PAUSEPAD <= data_from_hps; + if(header_chunk == 4'd15) CHANGEMASK <= data_from_hps; end end else + if(parsing_mask) + begin + if(ioctl_wr == 1'b1) change_mask[mask_load_index +: 8] <= data_from_hps; + end + else begin // Keep track of the largest entry during config download - total_entries <= ioctl_addr[CFG_ADDRESSWIDTH+2:3] - 2'd2; + total_entries <= config_upload_addr; end end @@ -285,71 +357,214 @@ begin if (last_ioctl_index==HS_DUMPINDEX) downloaded_dump <= 1'b1; end - // Track completion of dump upload - if ((last_ioctl_upload != ioctl_upload) && (ioctl_upload == 1'b0)) - begin - if (last_ioctl_index==HS_DUMPINDEX) - begin - uploaded_dump <= 1'b1; - // Mark uploaded dump as readable in case of reset - downloaded_dump <= 1'b1; - end - end - - // Track last ioctl values + // Track last cycle values last_ioctl_download <= ioctl_download; - last_ioctl_upload <= ioctl_upload; last_ioctl_index <= ioctl_index; + last_OSD_STATUS <= OSD_STATUS; + + // Cascade incoming data bytes from HPS if(ioctl_download && ioctl_wr) begin - last_ioctl_dout3 = last_ioctl_dout2; - last_ioctl_dout2 = last_ioctl_dout; - last_ioctl_dout = ioctl_dout; + last_data_from_hps3 = last_data_from_hps2; + last_data_from_hps2 = last_data_from_hps; + last_data_from_hps = data_from_hps; end + // If we have a valid configuration then enable the hiscore system if(downloaded_config) begin - // Check for end of state machine reset to initialise state machine + + // Check for end of core reset to initialise state machine for restore reset_last <= reset; - if (reset_last == 1'b1 && reset == 1'b0) + if (downloaded_dump == 1'b1 && reset_last == 1'b1 && reset == 1'b0) begin wait_timer <= START_WAIT; - next_state <= SM_INIT; + next_state <= SM_INIT_RESTORE; state <= SM_TIMER; counter <= 1'b0; initialised <= initialised + 1'b1; + restoring_dump <= 1'b1; end else begin - // Upload scores to HPS + // Upload scores if requested by HPS + // - Data is now sent from the hiscore data buffer rather than game RAM as in previous versions if (uploading_dump == 1'b1) begin - // generate addresses to read high score from game memory. Base addresses off ioctl_address - if (ioctl_addr == 25'b0) begin - local_addr <= 0; - base_io_addr <= 25'b0; - counter <= 1'b0000; - end - // Move to next entry when last address is reached - if (old_io_addr!=ioctl_addr && ram_addr==end_addr[24:0]) - begin - counter <= counter + 1'b1; - base_io_addr <= ioctl_addr; - end - // Set game ram address for reading back to HPS - ram_addr <= addr_base + (ioctl_addr - base_io_addr); - // Set local addresses to update cached dump in case of reset - local_addr <= ioctl_addr[HS_SCOREWIDTH-1:0]; + // Set local address to read from hiscore data based on ioctl_address + data_addr <= ioctl_addr[HS_SCOREWIDTH-1:0]; + // Clear dump dirty flag + dump_dirty <= 1'b0; + end + + // Trigger hiscore extraction when OSD is opened + if(last_OSD_STATUS==1'b0 && OSD_STATUS==1'b1 && extracting_dump==1'b0 && uploading_dump==1'b0 && restoring_dump==1'b0) + begin + extracting_dump <= 1'b1; + state <= SM_COMPAREINIT; + end + + // Extract hiscore data from game RAM and save in hiscore data buffer + if (extracting_dump == 1'b1) + begin + case (state) + // Compare process states + SM_COMPAREINIT: // Initialise state machine for comparison + begin + // Setup addresses and comparison flags + buffer_addr <= 0; + data_addr <= 0; + counter <= 0; + compare_nonzero <= 1'b0; + compare_changed <= 1'b0; + compare_length <= 1'b0; + // Pause cpu and wait for next state + pause_cpu <= 1'b1; + state <= SM_TIMER; + next_state <= SM_COMPAREBEGIN; + wait_timer <= ACCESS_PAUSEPAD; + end + SM_COMPAREBEGIN: + begin + // Get ready to read next line (wait until addr_base is updated) + reading_scores <= 1'b1; + state <= SM_COMPAREREADY; + end + SM_COMPAREREADY: + begin + // Set ram address and wait for it to return correctly + ram_addr <= addr_base; + if(ram_addr == addr_base) + begin + state <= SM_COMPAREREAD; + end + end + SM_COMPAREREAD: + begin + // Setup next address and signal write enable to hiscore buffer + buffer_write <= 1'b1; + state <= SM_COMPAREDONE; + end + SM_COMPAREDONE: + begin + // If RAM data has changed since last dump and there is either no mask or a 1 in the mask for this address + if (data_from_ram != hiscore_data_out && (CHANGEMASK==8'b0 || check_mask==1)) + begin + // Hiscore data changed + compare_changed <= 1'b1; + end + if (data_from_ram != 8'b0) + begin + // Hiscore data is not blank + compare_nonzero <= 1'b1; + end + compare_length <= compare_length + 20'b1; + // Move to next entry when last address is reached + if (ram_addr == end_addr) + begin + // If this was the last entry then we are done + if (counter == total_entries) + begin + state <= SM_TIMER; + reading_scores <= 1'b0; + next_state <= SM_COMPARECOMPLETE; + wait_timer <= ACCESS_PAUSEPAD; + end + else + begin + // Next config line + counter <= counter + 1'b1; + state <= SM_COMPAREBEGIN; + end + end + else + begin + // Keep extracting this section + state <= SM_COMPAREREAD; + ram_addr <= ram_addr + 1'b1; + end + // Always stop writing to hiscore dump ram and increment local address + buffer_addr <= buffer_addr + 1'b1; + data_addr <= data_addr + 1'b1; + buffer_write <= 1'b0; + end + SM_COMPARECOMPLETE: + begin + pause_cpu <= 1'b0; + reading_scores <= 1'b0; + if (compare_changed == 1'b1 && compare_nonzero == 1'b1) + begin + // If high scores have changed and are not blank, update the hiscore data from extract buffer + dump_dirty <= 1'b1; + state <= SM_EXTRACTINIT; + end + else + begin + // If no change or scores are invalid leave the existing hiscore data in place + if(dump_dirty == 1'b1 && autosave == 1'b1) + begin + state <= SM_EXTRACTSAVE; + end + else + begin + extracting_dump <= 1'b0; + state <= SM_STOPPED; + end + end + end + SM_EXTRACTINIT: + begin + // Setup address and counter + data_addr <= 0; + buffer_addr <= 0; + state <= SM_EXTRACT; + dump_write <= 1'b1; + end + SM_EXTRACT: + begin + // Keep writing until end of buffer + if (buffer_addr == compare_length) + begin + dump_write <= 1'b0; + state <= SM_EXTRACTSAVE; + end + // Increment buffer address and set data address to one behind + data_addr <= buffer_addr; + buffer_addr <= buffer_addr + 1'b1; + end + SM_EXTRACTSAVE: + begin + if(autosave == 1'b1) + begin + ioctl_upload_req <= 1'b1; + state <= SM_TIMER; + next_state <= SM_EXTRACTCOMPLETE; + wait_timer <= 4'd4; + end + else + begin + extracting_dump <= 1'b0; + state <= SM_STOPPED; + end + end + SM_EXTRACTCOMPLETE: + begin + ioctl_upload_req <= 1'b0; + extracting_dump <= 1'b0; + state <= SM_STOPPED; + end + endcase end - if (ioctl_upload == 1'b0 && downloaded_dump == 1'b1 && reset == 1'b0) + // If we are not uploading or resetting and valid hiscore data is available then start the state machine to write data to game RAM + if (uploading_dump == 1'b0 && downloaded_dump == 1'b1 && reset == 1'b0) begin // State machine to write data to game RAM case (state) - SM_INIT: // Start state machine + SM_INIT_RESTORE: // Start state machine begin // Setup base addresses - local_addr <= 0; + data_addr <= 0; base_io_addr <= 25'b0; // Reset entry counter and states counter <= 0; @@ -380,7 +595,7 @@ begin SM_CHECKSTARTVAL: // Start check begin // Check for matching start value - if(wait_timer != CHECK_HOLD & ioctl_din == start_val) + if(wait_timer != CHECK_HOLD && data_from_ram == start_val) begin // Prepare end check ram_addr <= end_addr; @@ -408,7 +623,7 @@ begin SM_CHECKENDVAL: // End check begin // Check for matching end value - if (wait_timer != CHECK_HOLD & ioctl_din == end_val) + if (wait_timer != CHECK_HOLD & data_from_ram == end_val) begin if (counter == total_entries) begin @@ -448,7 +663,7 @@ begin SM_CHECKCANCEL: // Cancel start/end check run - disable RAM access and keep CPU paused begin pause_cpu <= 1'b0; - next_state <= SM_INIT; + next_state <= SM_INIT_RESTORE; state <= SM_TIMER; wait_timer <= CHECK_WAIT; end @@ -472,7 +687,7 @@ begin SM_WRITEREADY: // local ram should be correct, start write to game RAM begin - ram_addr <= addr_base + (local_addr - base_io_addr); + ram_addr <= addr_base + (data_addr - base_io_addr); state <= SM_TIMER; next_state <= SM_WRITEDONE; wait_timer <= WRITE_HOLD; @@ -481,7 +696,7 @@ begin SM_WRITEDONE: begin - local_addr <= local_addr + 1'b1; // Increment to next byte of entry + data_addr <= data_addr + 1'b1; // Increment to next byte of entry if (ram_addr == end_addr) begin // End of entry reached @@ -494,7 +709,7 @@ begin // Move to next entry counter <= counter + 1'b1; write_counter <= 1'b0; - base_io_addr <= local_addr + 1'b1; + base_io_addr <= data_addr + 1'b1; state <= SM_WRITEBEGIN; end end @@ -509,12 +724,13 @@ begin begin ram_write <= 1'b0; writing_scores <= 1'b0; + restoring_dump <= 1'b0; state <= SM_TIMER; if(write_counter < WRITE_REPEATCOUNT) begin // Schedule next write next_state <= SM_WRITERETRY; - local_addr <= 0; + data_addr <= 0; wait_timer <= WRITE_REPEATWAIT; end else @@ -536,23 +752,28 @@ begin begin pause_cpu <= 1'b0; end - - SM_TIMER: // timer wait state - begin - if (wait_timer > 1'b0) - wait_timer <= wait_timer - 1'b1; - else - state <= next_state; - end endcase end + + if(state == SM_TIMER) // timer wait state + begin + // Do not progress timer if CPU is paused by source other than this module + // - Stops initial hiscore load delay being foiled by user pausing/entering OSD + if (paused == 1'b0 || pause_cpu == 1'b1) + begin + if (wait_timer > 1'b0) + wait_timer <= wait_timer - 1'b1; + else + state <= next_state; + end + end end end - old_io_addr<=ioctl_addr; end endmodule +// Simple dual-port RAM module used by hiscore module module dpram_hs #( parameter dWidth=8, parameter aWidth=8 @@ -592,4 +813,4 @@ always @(posedge clk) begin end end -endmodule \ No newline at end of file +endmodule diff --git a/rtl/pacman.vhd b/rtl/pacman.vhd index e01dad5..194e1ab 100644 --- a/rtl/pacman.vhd +++ b/rtl/pacman.vhd @@ -96,11 +96,12 @@ port pause : in std_logic; -- high score - hs_address : in std_logic_vector(11 downto 0); - hs_data_in : in std_logic_vector(7 downto 0); - hs_data_out : out std_logic_vector(7 downto 0); - hs_write : in std_logic; - hs_access : in std_logic; + hs_address : in std_logic_vector(11 downto 0); + hs_data_in : in std_logic_vector(7 downto 0); + hs_data_out : out std_logic_vector(7 downto 0); + hs_write_enable : in std_logic; + hs_access_read : in std_logic; + hs_access_write : in std_logic; -- RESET : in std_logic; @@ -556,20 +557,19 @@ cpu_data_in <= cpu_vec_reg when cpu_iorq_l = '0' and cpu_m1_l = '0 x"BF" when iodec_nop_l = '0' else ram_data; - u_rams : work.dpram generic map (12,8) port map ( clock_a => clk, -- enable_a => ena_6, - wren_a => not sync_bus_r_w_l and not vram_l and ena_6, + wren_a => not sync_bus_r_w_l and not vram_l and ena_6 and not (hs_access_read or hs_access_write), address_a => ab(11 downto 0), data_a => cpu_data_out, -- cpu only source of ram data q_a => ram_data, clock_b => clk, address_b => hs_address, - enable_b => hs_access, - wren_b => hs_write, + enable_b => hs_access_read or hs_access_write, + wren_b => hs_write_enable, data_b => hs_data_in, q_b => hs_data_out diff --git a/rtl/pause.v b/rtl/pause.v index 4f21042..586b73b 100644 --- a/rtl/pause.v +++ b/rtl/pause.v @@ -27,6 +27,7 @@ Version history: 0001 - 2021-03-15 - First marked release + 0002 - 2021-08-28 - Add optional output of dim_video signal (currently used by Galaga) ============================================================================ */ module pause #( @@ -49,19 +50,26 @@ module pause #( input [(BW-1):0] b, // Blue channel output pause_cpu, // Pause signal to CPU (active-high) +`ifdef PAUSE_OUTPUT_DIM + output dim_video, // Dim video requested (active-high) +`endif output [(RW+GW+BW-1):0] rgb_out // RGB output to arcade_video module ); // Option constants localparam pause_in_osd = 1'b0; -localparam dim_video = 1'b1; +localparam dim_video_timer= 1'b1; reg pause_toggle = 1'b0; // User paused (active-high) reg [31:0] pause_timer = 1'b0; // Time since pause reg [31:0] dim_timeout = (CLKSPD*10000000); // Time until video output dim (10 seconds @ CLKSPD Mhz) +`ifndef PAUSE_OUTPUT_DIM +wire dim_video; // Dim video requested (active-high) +`endif assign pause_cpu = (pause_request | pause_toggle | (OSD_STATUS & options[pause_in_osd])) & !reset; +assign dim_video = (pause_timer >= dim_timeout); always @(posedge clk_sys) begin @@ -73,7 +81,7 @@ always @(posedge clk_sys) begin // Clear user pause on reset if(pause_toggle & reset) pause_toggle <= 0; - if(pause_cpu & options[dim_video]) + if(pause_cpu & options[dim_video_timer]) begin // Track pause duration for video dim if((pause_timer= dim_timeout) ? {r >> 1,g >> 1, b >> 1} : {r,g,b}; +assign rgb_out = dim_video ? {r >> 1,g >> 1, b >> 1} : {r,g,b}; endmodule \ No newline at end of file