Files
Hiscores_MiSTer/nvram.v
2021-10-09 21:27:05 +01:00

279 lines
8.8 KiB
Verilog

//============================================================================
// NVRAM-style hiscore autosave support for MiSTer arcade cores.
//
// https://github.com/JimmyStones/Hiscores_MiSTer
//
// Copyright (c) 2021 Jim Gregory
//
// 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 3 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.
//============================================================================
/*
Version history:
0001 - 2021-10-01 - First marked release
0002 - 2021-10-09 - Add change mask support
===========================================================================
*/
module nvram
#(
parameter DUMPWIDTH=8, // Address size of NVRAM for highscore data (default 8 = 256 bytes max)
parameter CONFIGINDEX=3, // ioctl_index for config transfer
parameter DUMPINDEX=4, // ioctl_index for dump transfer
parameter PAUSEPAD=4 // Cycles to wait with paused CPU before and after NVRAM access
)
(
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_index,
output [7:0] ioctl_din,
input [7:0] ioctl_dout,
input OSD_STATUS,
output [DUMPWIDTH-1:0] nvram_address,
input [7:0] nvram_data_out,
output reg pause_cpu // Pause core CPU to prepare for/relax after NVRAM access
);
// Hiscore data tracking
wire downloading_config; // Is hiscore config currently being loaded?
reg downloaded_config = 1'b0; // Has hiscore config been loaded?
wire downloading_dump; // Is hiscore data currently being loaded?
reg downloaded_dump = 1'b0; // Has hiscore data been loaded?
wire uploading_dump; // Is hiscore data currently being saved?
reg extracting_dump = 1'b0; // Is hiscore data currently extracted and checked for change?
assign downloading_config = ioctl_download && (ioctl_index==CONFIGINDEX);
assign downloading_dump = ioctl_download && (ioctl_index==DUMPINDEX);
assign uploading_dump = ioctl_upload && (ioctl_index==DUMPINDEX);
// State machine constants
localparam SM_STATEWIDTH = 3; // Width of state machine net
localparam SM_IDLE = 0;
localparam SM_TIMER = 1;
localparam SM_EXTRACTINIT = 2;
localparam SM_EXTRACTREADY = 3;
localparam SM_EXTRACTNEXT = 4;
localparam SM_EXTRACTSAVE = 5;
localparam SM_EXTRACTCOMPLETE = 6;
// State machine control
reg [(SM_STATEWIDTH-1):0] state = SM_IDLE; // Current state machine index
reg [(SM_STATEWIDTH-1):0] next_state = SM_IDLE; // Next state machine index to move to after wait timer expires
reg [31:0] wait_timer; // Wait timer for inital/read/write delays
// Last cycle signals
reg last_reset = 1'b0; // Last cycle reset
reg last_OSD_STATUS; // Last cycle OSD status
reg [7:0] last_ioctl_index; // Last cycle HPS IO index
reg last_ioctl_download = 0;// Last cycle HPS IO download
// Buffer RAM control signals
reg [DUMPWIDTH-1:0] buffer_addr;
reg [DUMPWIDTH-1:0] buffer_length;
wire [7:0] buffer_data_in;
reg buffer_write = 1'b0;
assign nvram_address = buffer_addr;
// Change detection signals
reg [DUMPWIDTH-1: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 [7:0] check_mask_out /* synthesis keep */;
wire check_mask = check_mask_out[buffer_addr[2:0]] /* synthesis keep */;
// RAM used to store high score check mask
spram_hs #(.aWidth(DUMPWIDTH-3),.dWidth(8))
mask_ram (
.clk(clk),
.addr(downloading_config ? ioctl_addr[DUMPWIDTH-4:0] : buffer_addr[DUMPWIDTH-1:3]),
.we(downloading_config && ioctl_wr),
.d(ioctl_dout),
.q(check_mask_out)
);
// RAM used to store high score data buffer
spram_hs #(.aWidth(DUMPWIDTH),.dWidth(8))
nvram_buffer (
.clk(clk),
.addr((downloading_dump || uploading_dump) ? ioctl_addr[DUMPWIDTH-1:0] : buffer_addr),
.we(downloading_dump ? ioctl_wr : buffer_write),
.d(downloading_dump ? ioctl_dout : nvram_data_out),
.q(ioctl_din)
);
always @(posedge clk)
begin
// Track completion of configuration and dump download
if ((last_ioctl_download != ioctl_download) && (ioctl_download == 1'b0))
begin
if (last_ioctl_index==CONFIGINDEX) downloaded_config <= 1'b1;
if (last_ioctl_index==DUMPINDEX) downloaded_dump <= 1'b1;
end
// Track last cycle values
last_ioctl_download <= ioctl_download;
last_ioctl_index <= ioctl_index;
last_OSD_STATUS <= OSD_STATUS;
last_reset <= reset;
// Check for end of core reset to initialise hiscore system state
if (last_reset == 1'b1 && reset == 1'b0)
begin
next_state <= SM_IDLE;
state <= SM_IDLE;
extracting_dump <= 1'b0;
buffer_length <= (2**DUMPWIDTH) - 1'b1;
end
else
begin
// 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)
begin
extracting_dump <= 1'b1;
state <= SM_EXTRACTINIT;
end
// Extract hiscore data from game RAM and save in hiscore data buffer
if (extracting_dump == 1'b1)
begin
case (state)
SM_EXTRACTINIT: // Initialise state machine for extraction
begin
// Setup addresses and comparison flags
buffer_addr <= 1'b0;
buffer_write <= 1'b0;
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_EXTRACTREADY;
wait_timer <= PAUSEPAD;
ioctl_upload_req <= 1'b0;
end
SM_EXTRACTREADY:
begin
// Schedule write for next cycle when addresses are ready
buffer_write <= 1'b1;
compare_length <= compare_length + 1'b1;
state <= SM_EXTRACTNEXT;
end
SM_EXTRACTNEXT:
begin
if ((nvram_data_out != ioctl_din) && (downloaded_config == 1'b0 || check_mask == 1'b1))
begin
compare_changed <= 1'b1; // Hiscore data changed since last dump
end
if (nvram_data_out != 8'b0)
begin
compare_nonzero <= 1'b1; // Hiscore data is not blank
end
// Always stop writing to hiscore dump ram and increment local address
buffer_write <= 1'b0;
buffer_addr <= buffer_addr + 1'b1;
if (compare_length == buffer_length)
begin
// Finish extract if last address is reached
state <= SM_TIMER;
next_state <= SM_EXTRACTSAVE;
wait_timer <= PAUSEPAD;
end
else
begin
// Otherwise move to next byte
state <= SM_TIMER;
next_state <= SM_EXTRACTREADY;
wait_timer <= 1'b0;
end
end
SM_EXTRACTSAVE:
begin
// If high scores have changed and are not blank, then trigger autosave if enabled
if (compare_changed == 1'b1 && compare_nonzero == 1'b1 && autosave == 1'b1)
begin
ioctl_upload_req <= 1'b1;
end
// Release pause and schedule end of extraction process
pause_cpu <= 1'b0;
state <= SM_TIMER;
next_state <= SM_EXTRACTCOMPLETE;
wait_timer <= 4'd4;
end
SM_EXTRACTCOMPLETE:
begin
// End extract, clear any upload request and move state machine to idle
extracting_dump <= 1'b0;
ioctl_upload_req <= 1'b0;
state <= SM_IDLE;
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
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
endmodule
// Simple single port RAM used by nvram module
module spram_hs #(
parameter dWidth=8,
parameter aWidth=8
)(
input clk,
input [aWidth-1:0] addr,
input [dWidth-1:0] d,
input we,
output reg [dWidth-1:0] q
);
reg [dWidth-1:0] ram [2**aWidth-1:0];
always @(posedge clk) begin
if (we) ram[addr] <= d;
q <= ram[addr];
end
endmodule