diff --git a/.gitignore b/.gitignore index e8aa144..3f6e877 100644 --- a/.gitignore +++ b/.gitignore @@ -53,3 +53,4 @@ src/os.sym .vscode/settings.json verilator/imgui.ini verilator/imgui.ini +docs/* diff --git a/InputTest.sv b/InputTest.sv index efc973a..c40106f 100644 --- a/InputTest.sv +++ b/InputTest.sv @@ -1,9 +1,9 @@ /*============================================================================ - MiSTer test harness - emu module + Input Test - emu module Author: Jim Gregory - https://github.com/JimmyStones/ Version: 1.0 - Date: 2021-07-03 + Date: 2021-07-12 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 @@ -209,7 +209,6 @@ localparam CONF_STR = { "O89,Aspect ratio,Original,Full Screen,[ARC1],[ARC2];", "-;", "F0,BIN,Load BIOS;", - "F1,PF,Load Font;", "-;", "J1,A,B,X,Y,L,R,Select,Start,C,Z;", "V,v",`BUILD_DATE @@ -254,6 +253,7 @@ wire [8:0] spinner_5; wire [10:0] ps2_key; wire [24:0] ps2_mouse; wire [15:0] ps2_mouse_ext; +wire [32:0] timestamp; hps_io #(.CONF_STR(CONF_STR)) hps_io ( @@ -301,7 +301,9 @@ hps_io #(.CONF_STR(CONF_STR)) hps_io .ps2_key(ps2_key), .ps2_mouse(ps2_mouse), - .ps2_mouse_ext(ps2_mouse_ext) + .ps2_mouse_ext(ps2_mouse_ext), + + .TIMESTAMP(timestamp) ); @@ -363,7 +365,8 @@ system system( .paddle({paddle_5,paddle_4,paddle_3,paddle_2,paddle_1,paddle_0}), .spinner({7'b0,spinner_5,7'b0,spinner_4,7'b0,spinner_3,7'b0,spinner_2,7'b0,spinner_1,7'b0,spinner_0}), .ps2_key(ps2_key), - .ps2_mouse({ps2_mouse_ext,7'b0,ps2_mouse}) + .ps2_mouse({ps2_mouse_ext,7'b0,ps2_mouse}), + .timestamp(timestamp) ); endmodule diff --git a/PETSCII.pf b/PETSCII.pf new file mode 100644 index 0000000..95d608e Binary files /dev/null and b/PETSCII.pf differ diff --git a/README.MD b/README.MD index b4a0b3a..f92c7aa 100644 --- a/README.MD +++ b/README.MD @@ -2,12 +2,13 @@ ## Overview -A custom mini-system running an input test utility. 3 test screens available: +A custom mini-system running an input test utility. There are 4 test screens available: - Digital - shows joypad buttons for first two input devices - Analog - shows analog axes for first two input devices - Advanced - shows all standard HPS sourced inputs (joystick, analog, paddle, spinner) simultaneously as well as a ps2 keyboard console - -Mode switching instructions are shown at the bottom of the screen. + - Button test - guided button test to help detect bounce / missed inputs and test man+machine lag + +Hold (select) on the first joypad to enter the menu ## Acknowledgements @@ -16,9 +17,13 @@ Created by JimmyStones (http://github.com/jimmystones), with thanks to: - Jotego (https://github.com/jotego) for his very tidy JTFRAME modules - Porkchop Express (https://misteraddons.com/) for testing a bunch of devices I couldn't +## History +- v1.0 - 2021-07-03 - First release with digital, analog and advanced tests +- v1.1 - 2021-07-18 - PETSCII UI update, add button test mode + ## Hardware -A simple bespoke (i.e. I made it up with no real plan) 8-bit system with a 40x30 character display, each character can be set to one of 256 colours. No sound capabilities. All relevant MiSTer inputs are mapped into memory regions. +A simple bespoke (i.e. I made it up with no real plan) 8-bit system with a 40x30 character display, each character foreground and background colour can be set independently to one of 256 colours. No sound capabilities whatsoever. All relevant MiSTer inputs are mapped into memory regions for the CPU to access directly. There is also a dedicated millisecond timer (runs 0-32677, resettable by writing to any bit of the address). ### Components - Z80 CPU (tv80 by Guy Hutchison, based on VHDL T80 core by Daniel Wallner) @@ -29,14 +34,16 @@ A simple bespoke (i.e. I made it up with no real plan) 8-bit system with a 40x30 - 2Kb character ROM (port 1 cpu, port 2 download) - 2Kb character RAM (port 1 cpu, port 2 graphics) - 2Kb colour RAM (port 1 cpu, port 2 graphics) - - 7 Memory-mapped IO regions (all read-only). _Yes I know about MREQ but SFRs turned out to be annoying in sdcc so :)_ - - Hardware state (H/V Sync, H/V Blank etc) (8 bytes) - - joystick_5->0 from HPS (192 bytes) - - joystick_analog_5->0 from HPS (96 bytes) - - paddle_5->0 from HPS (48 bytes) - - spinner_5->0 from HPS (96 bytes) - - ps2_key from HPS (11 bytes) + - 8 Memory-mapped IO regions. _Yes I know about MREQ but SFRs turned out to be annoying in sdcc so :)_ + - Hardware state (H/V Sync, H/V Blank etc) (8 bytes, read-only) + - joystick_5->0 from HPS (192 bytes, read-only) + - joystick_analog_5->0 from HPS (96 bytes, read-only) + - paddle_5->0 from HPS (48 bytes, read-only) + - spinner_5->0 from HPS (96 bytes, read-only) + - ps2_key from HPS (11 bytes, read-only) - ps2_mouse from HPS (48 bytes, combination of ps2_mouse and ps2_mouseext with some padding to align bytes) - currently unused + - timestamp from HPS (33 bytes, read-only) + - Hardware millisecond resolution timer (16 bytes read/write) ### Memory Map Start|End|Length|Name @@ -50,6 +57,8 @@ Start|End|Length|Name 0x7300|0x735F|0x0060|Spinner inputs 0x7400|0x740B|0x000C|PS2 keyboard input 0x7500|0x752F|0x0030|PS2 mouse input +0x7600|0x7020|0x0021|Timestamp +0x7700|0x7010|0x0011|Timer 0x8000|0x87FF|0x0800|Char RAM 0x8800|0x8FFF|0x0800|Colour RAM 0xC000|0xFFFF|0x4000|Work RAM diff --git a/build.sh b/build.sh index e7cab8b..bcd756e 100755 --- a/build.sh +++ b/build.sh @@ -12,6 +12,6 @@ cd .. # Hexify roms od -An -t x1 -v src/os.bin > verilator/rom.hex od -An -t x1 -v src/os.bin > rtl/rom.hex -od -An -t x1 -v MiSTer.pf > verilator/font.hex -od -An -t x1 -v MiSTer.pf > rtl/font.hex +od -An -t x1 -v PETSCII.pf > verilator/font.hex +od -An -t x1 -v PETSCII.pf > rtl/font.hex diff --git a/rtl/system.v b/rtl/system.v index 1aee1d8..b558cf6 100644 --- a/rtl/system.v +++ b/rtl/system.v @@ -1,6 +1,6 @@ `timescale 1ns / 1ps /*============================================================================ - MiSTer test harness - System module + Aznable (custom 8-bit computer system) - System module Author: Jim Gregory - https://github.com/JimmyStones/ Version: 1.0 @@ -48,6 +48,9 @@ module system ( // [0-23] mouse data, [24] - toggles with every event, [25-31] - padding, // [32-39] - wheel movements, [40-47] - reserved(additional buttons) input [47:0] ps2_mouse, + + // [31:0] - seconds since 1970-01-01 00:00:00, [32] - toggle with every change + input [32:0] timestamp, output VGA_HS, output VGA_VS, @@ -87,20 +90,49 @@ jtframe_vtimer #( .VS(VGA_VS) ); +// Millisecond timer +reg [15:0] timer; +reg [14:0] timer_divider = 15'd0; + +always @(posedge clk_sys) +begin + if(timer_cs == 1'b1 && cpu_wr_n == 1'b0) + begin + timer <= 16'd0; + timer_divider <= 15'd0; + end + else + begin + if(timer_divider==15'd24000) + begin + timer <= timer + 16'd1; + timer_divider <= 15'd0; + end + else + begin + timer_divider <= timer_divider + 15'd1; + end + end +end + // Character map wire [3:0] chpos_x = 4'd7 - hcnt[2:0]; wire [2:0] chpos_y = vcnt[2:0]; wire [5:0] chram_x = hcnt[8:3]; wire [5:0] chram_y = vcnt[8:3]; wire [11:0] chram_addr = {chram_y, chram_x}; -wire [11:0] colram_addr = chram_addr; wire [11:0] chrom_addr = {1'b0, chmap_data_out[7:0], chpos_y}; wire chpixel = chrom_data_out[chpos_x[2:0]]; -// RGB output -assign VGA_R = chpixel ? {{2{colram_data_out[2:0]}},2'b0} : 8'b0; -assign VGA_G = chpixel ? {{2{colram_data_out[5:3]}},2'b0} : 8'b0; -assign VGA_B = chpixel ? {{3{colram_data_out[7:6]}},2'b0} : 8'b0; +// RGB mixer +wire [2:0] r_temp = chpixel ? fgcolram_data_out[2:0] : bgcolram_data_out[2:0]; +wire [2:0] g_temp = chpixel ? fgcolram_data_out[5:3] : bgcolram_data_out[5:3]; +wire [1:0] b_temp = chpixel ? fgcolram_data_out[7:6] : bgcolram_data_out[7:6]; + +// Convert RGb to 24bpp +assign VGA_R = {{2{r_temp}},2'b0}; +assign VGA_G = {{2{g_temp}},2'b0}; +assign VGA_B = {{3{b_temp}},2'b0}; // CPU control signals wire [15:0] cpu_addr; @@ -136,25 +168,29 @@ wire [7:0] pgrom_data_out; wire [7:0] chrom_data_out; wire [7:0] wkram_data_out; wire [7:0] chram_data_out; -wire [7:0] colram_data_out; +wire [7:0] fgcolram_data_out; +wire [7:0] bgcolram_data_out; // RAM data to GFX wire [7:0] chmap_data_out; // Hardware inputs -wire [7:0] in0_data_out = {VGA_HS, VGA_VS, 6'b101000}; +wire [7:0] in0_data_out = {VGA_HS, VGA_VS,VGA_HB, VGA_VB, 4'b1000}; wire [7:0] joystick_data_out = joystick[cpu_addr[7:0] +: 8]; wire [7:0] analog_data_out = analog[cpu_addr[6:0] +: 8]; wire [7:0] paddle_data_out = paddle[cpu_addr[5:0] +: 8]; wire [7:0] spinner_data_out = spinner[cpu_addr[6:0] +: 8]; wire [7:0] ps2_key_data_out = ps2_key[cpu_addr[3:0] +: 8]; wire [7:0] ps2_mouse_data_out = ps2_mouse[cpu_addr[5:0] +: 8]; +wire [7:0] timestamp_data_out = timestamp[cpu_addr[5:0] +: 8]; +wire [7:0] timer_data_out = timer[cpu_addr[3:0] +: 8]; // CPU address decodes wire pgrom_cs = cpu_addr[15:14] == 2'b00; //wire chrom_cs = cpu_addr[15:12] == 4'b0100; // CPU never accesses the character ROM data directly wire chram_cs = cpu_addr[15:11] == 5'b10000; -wire colram_cs = cpu_addr[15:11] == 5'b10001; +wire fgcolram_cs = cpu_addr[15:11] == 5'b10001; +wire bgcolram_cs = cpu_addr[15:11] == 5'b10010; wire wkram_cs = cpu_addr[15:14] == 2'b11; wire in0_cs = cpu_addr == 16'h6000; wire joystick_cs = cpu_addr[15:8] == 8'b01110000; @@ -163,25 +199,31 @@ wire paddle_cs = cpu_addr[15:8] == 8'b01110010; wire spinner_cs = cpu_addr[15:8] == 8'b01110011; wire ps2_key_cs = cpu_addr[15:8] == 8'b01110100; wire ps2_mouse_cs = cpu_addr[15:8] == 8'b01110101; +wire timestamp_cs = cpu_addr[15:8] == 8'b01110110; +wire timer_cs = cpu_addr[15:8] == 8'b01110111; -// always @(posedge clk_sys) begin +// always @(posedge timestamp[32]) begin +// $display("%b", timestamp); +// end +//always @(posedge clk_sys) begin // if(pgrom_cs) $display("%x pgrom o %x", cpu_addr, pgrom_data_out); // if(wkram_cs) $display("%x wkram i %x o %x w %b", cpu_addr, cpu_dout, wkram_data_out, wkram_wr); // if(chram_cs) $display("%x chram i %x o %x w %b", cpu_addr, cpu_dout, chram_data_out, chram_wr); -// if(colram_cs) $display("%x colram i %x o %x w %b", cpu_addr, cpu_dout, colram_data_out, colram_wr); +// if(fgcolram_cs) $display("%x fgcolram i %x o %x w %b", cpu_addr, cpu_dout, fgcolram_data_out, fgcolram_wr); // if(in0_cs) $display("%x in0 i %x o %x", cpu_addr, cpu_dout, in0_data_out); // if(joystick_cs) $display("joystick %b %b", joystick_bit, joystick_data_out); // if(analog_cs) $display("analog %b %b", analog_bit, analog_data_out); // if(paddle_cs) $display("paddle %b", paddle_data_out); // if(ps2_key_cs) $display("ps2_key %b %x", ps2_key_data_out, cpu_addr[3:0]); // $display("%x", cpu_addr); -// end +//end // CPU data mux assign cpu_din = pgrom_cs ? pgrom_data_out : wkram_cs ? wkram_data_out : chram_cs ? chram_data_out : - colram_cs ? colram_data_out : + fgcolram_cs ? fgcolram_data_out : + bgcolram_cs ? bgcolram_data_out : in0_cs ? in0_data_out : joystick_cs ? joystick_data_out : analog_cs ? analog_data_out : @@ -189,6 +231,8 @@ assign cpu_din = pgrom_cs ? pgrom_data_out : spinner_cs ? spinner_data_out : ps2_key_cs ? ps2_key_data_out : ps2_mouse_cs ? ps2_mouse_data_out : + timestamp_cs ? timestamp_data_out : + timer_cs ? timer_data_out : 8'b00000000; // Rom upload write enables @@ -198,7 +242,8 @@ wire chrom_wr = dn_wr && dn_index == 8'b1; // Ram write enables wire wkram_wr = !cpu_wr_n && wkram_cs; wire chram_wr = !cpu_wr_n && chram_cs; -wire colram_wr = !cpu_wr_n && colram_cs; +wire fgcolram_wr = !cpu_wr_n && fgcolram_cs; +wire bgcolram_wr = !cpu_wr_n && bgcolram_cs; // MEMORY @@ -253,20 +298,36 @@ dpram #(11,8) chram .q_b(chmap_data_out) ); -// Char color RAM - 0x8800 - 0x8FFF (0x0800 / 2048 bytes) -dpram #(11,8) colram +// Char foreground color RAM - 0x8800 - 0x8FFF (0x0800 / 2048 bytes) +dpram #(11,8) fgcolram ( .clock_a(clk_sys), .address_a(cpu_addr[10:0]), - .wren_a(colram_wr), + .wren_a(fgcolram_wr), .data_a(cpu_dout), .q_a(), .clock_b(clk_sys), - .address_b(colram_addr[10:0]), + .address_b(chram_addr[10:0]), .wren_b(1'b0), .data_b(), - .q_b(colram_data_out) + .q_b(fgcolram_data_out) +); + +// Char background color RAM - 0x9000 - 0x97FF (0x0800 / 2048 bytes) +dpram #(11,8) bgcolram +( + .clock_a(clk_sys), + .address_a(cpu_addr[10:0]), + .wren_a(bgcolram_wr), + .data_a(cpu_dout), + .q_a(), + + .clock_b(clk_sys), + .address_b(chram_addr[10:0]), + .wren_b(1'b0), + .data_b(), + .q_b(bgcolram_data_out) ); // Work RAM - 0xC000 - 0xFFFF (0x4000 / 16384 bytes) diff --git a/src/fader.c b/src/fader.c new file mode 100644 index 0000000..060ae69 --- /dev/null +++ b/src/fader.c @@ -0,0 +1,84 @@ +/*============================================================================ + Input Test - Fader + + Author: Jim Gregory - https://github.com/JimmyStones/ + Version: 1.0 + Date: 2021-07-12 + + 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, see . +===========================================================================*/ + +#pragma once + +#include "sys.c" + +// Fade in/out constants +#define fadefreq 4 +// Fade in/out variables +unsigned char fade = 0; +unsigned char fadetimer = 0; + +// Initialise fadeout state +void start_fadeout() +{ + state = STATE_FADEOUT; + fadetimer = fadefreq; + fade = 0; +} + +// Initialise fadein state +void start_fadein() +{ + state = STATE_FADEIN; + fadetimer = fadefreq; + fade = 15; +} + +// Fade out state +void fadeout() +{ + if (VBLANK_RISING) + { + fadetimer--; + if (fadetimer == 0) + { + box(fade, fade, 39 - fade, 29 - fade, 127, 0b0000111); + fadetimer = fadefreq; + fade++; + if (fade == 16) + { + start_fadein(); + } + } + } +} + +// Fade in state +void fadein() +{ + if (VBLANK_RISING) + { + fadetimer--; + if (fadetimer == 0) + { + box(fade, fade, 39 - fade, 29 - fade, 0, 0b0000000); + fadetimer = fadefreq; + fade--; + if (fade == 0) + { + state = nextstate; + } + } + } +} diff --git a/src/inputtester.c b/src/inputtester.c new file mode 100644 index 0000000..8b691d3 --- /dev/null +++ b/src/inputtester.c @@ -0,0 +1,837 @@ +/*============================================================================ + Input Test - Input test state handlers + + Author: Jim Gregory - https://github.com/JimmyStones/ + Version: 1.0 + Date: 2021-07-13 + + 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, see . +===========================================================================*/ + +#pragma once + +#include "sys.c" +#include "fader.c" +#include "menu.c" + +// Input tester variables +unsigned char joystick_last[12]; +signed char ax_last[6]; +signed char ay_last[6]; +unsigned char px_last[6]; +signed char sx_toggle_last[6]; +signed char sx_last[6]; +unsigned long sx_pos[6]; + +// Console constants +#define con_cursorfreq 30 +// Console variables +unsigned char con_x; // Console cursor X position +unsigned char con_y; // Console cursor X position +unsigned char con_l = 2; // Console left edge X +unsigned char con_t = 22; // Console top edge Y +unsigned char con_r = 37; // Console right edge X +unsigned char con_b = 36; // Console bottom edge Y +bool con_cursor; +unsigned char con_cursortimer = 1; + +// Mode switcher variables +char modeswitchtimer_select = 0; +char modeswitchtimer_start = 0; + +#define HISTORY_LENGTH 6 +char history[HISTORY_LENGTH]; + +#define PAD_COUNT 2 +#define BUTTON_COUNT 12 + +char pad_offset_x[PAD_COUNT] = {7, 7}; +char pad_offset_y[PAD_COUNT] = {5, 16}; +char button_symbol[BUTTON_COUNT][6] = { + "R", + "L", + "D", + "U", + "A", + "B", + "X", + "Y", + "L", + "R", + "Sel", + "Start"}; +char button_name[BUTTON_COUNT][12] = { + "DPAD Right", + "DPAD Left", + "DPAD Down", + "DPAD Up", + "A", + "B", + "X", + "Y", + "L", + "R", + "Select", + "Start"}; +char button_x[BUTTON_COUNT] = {6, 2, 4, 4, 24, 22, 22, 20, 3, 23, 9, 13}; +char button_y[BUTTON_COUNT] = {5, 5, 6, 4, 5, 6, 4, 5, 1, 1, 5, 5}; +#define color_button_active 0xFF +#define color_button_inactive 0b10100100 + +char analog_offset_x[PAD_COUNT] = {1, 20}; +char analog_offset_y[PAD_COUNT] = {5, 5}; +signed char analog_x[PAD_COUNT]; +signed char analog_y[PAD_COUNT]; +#define analog_size 18 +#define analog_ratio 15 // 256 / 17; + +#define timestamp_clock_index 32 +#define timestamp_index_1 8 +#define timestamp_index_2 16 +#define timestamp_index_3 24 +bool last_timestamp_clock = 0; +// Button test constants +#define btntest_mode_select 0 +#define btntest_mode_ready 1 +#define btntest_mode_test 2 +#define btntest_mode_results 3 +#define btntest_timer_start 60 // vblanks to wait before first prompt +#define btntest_timer_interval 90 // vblanks to wait between prompts +#define btntest_timer_visible 30 // vblanks to wait between prompts +#define btntest_counter_max 5 // Number of button presses required +#define btntest_max 255 +#define btntest_highlight_start 2 +#define btntest_highlight_end 35 + +// Button test variables +char btntest_mode = 0; +char btntest_buttonbank = 0; +char btntest_buttonindex = 0; +char btntest_buttonlast = 1; +char btntest_timer = 0; +char btntest_counter = 0; +unsigned short btntest_presses[btntest_max]; +unsigned short btntest_prompts[btntest_counter_max]; +char btntest_pos = 0; +char btntest_buttondownlast = 0; +bool btntest_results_refresh = 0; +char btntest_results_offset = 0; +char btntest_highlight = 0; +char btntest_aftertimer = 0; + + +// Draw static elements for digital input test page +void page_inputtester_digital() +{ + page_frame(true, false); + // Draw pads + for (char j = 0; j < PAD_COUNT; j++) + { + write_stringf("JOY %d", 0xFF, pad_offset_x[j] - 5, pad_offset_y[j] + 5, j + 1); + draw_pad(pad_offset_x[j], pad_offset_y[j]); + } +} + +// Draw static elements for analog input test page +void page_inputtester_analog() +{ + page_frame(true, false); + // Draw analog grids + for (char j = 0; j < PAD_COUNT; j++) + { + write_stringf("ANALOG %d", 0xFF, analog_offset_x[j] + 5, analog_offset_y[j] - 1, j + 1); + draw_analog(analog_offset_x[j], analog_offset_y[j], analog_size, analog_size); + write_char('X', 0xFF, analog_offset_x[j] + 5, analog_offset_y[j] + analog_size + 1); + write_char('Y', 0xFF, analog_offset_x[j] + 13, analog_offset_y[j] + analog_size + 1); + } +} + +// Draw static elements for advanced input test page +void page_inputtester_advanced() +{ + page_frame(true, false); + + write_string("RLDUABXYLRsS", 0xFF, 7, 5); + write_string("AX", 0xFF, 22, 5); + write_string("AY", 0xFF, 27, 5); + + write_string("POS", 0xFF, 7, 13); + write_string("SPD POS", 0xFF, 18, 13); + + char label[5]; + for (unsigned char j = 0; j < 6; j++) + { + sprintf(label, "JOY%d", j + 1); + write_string(label, 0xFF - (j * 2), 2, 6 + j); + + sprintf(label, "PAD%d", j + 1); + write_string(label, 0xFF - (j * 2), 2, 14 + j); + + sprintf(label, "SPN%d", j + 1); + write_string(label, 0xFF - (j * 2), 14, 14 + j); + } + write_string("CON", 0xFF, 2, 21); +} + +// Draw static elements for button test page +void page_btntest(bool showMenuButton, bool showContinueButton) +{ + page_frame(showMenuButton, showContinueButton); + write_string("BUTTON TEST", 0xAA, 15, 4); +} + +void reset_inputstates() +{ + modeswitchtimer_select = 0; + modeswitchtimer_start = 0; + for (char i = 0; i < 12; i++) + { + joystick_last[i] = 1; + } + for (char i = 0; i < 6; i++) + { + ax_last[i] = 1; + ay_last[i] = 1; + px_last[i] = 1; + sx_toggle_last[i] = 1; + sx_last[i] = 1; + sx_pos[i] = 0; + } +} + +// Initialise digital inputtester state and draw static elements +void start_inputtester_digital() +{ + state = STATE_INPUTTESTER; + + // Draw page + page_inputtester_digital(); + + // Reset last states for inputs + reset_inputstates(); +} + +// Initialise analog inputtester state and draw static elements +void start_inputtester_analog() +{ + state = STATE_INPUTTESTERANALOG; + + // Draw page + page_inputtester_analog(); + + // Reset last states for inputs + reset_inputstates(); +} + +// Initialise advanced inputtester state and draw static elements +void start_inputtester_advanced() +{ + state = STATE_INPUTTESTERADVANCED; + + // Draw page + page_inputtester_advanced(); + + // Reset console cursor + con_x = con_l; + con_y = con_t; + + // Reset last states for inputs + reset_inputstates(); +} + +// Initialise button test state and draw static elements +void start_btntest() +{ + state = STATE_BTNTEST; + btntest_mode = btntest_mode_select; + btntest_buttonbank = 0; + btntest_buttonindex = 0; + btntest_buttonlast = 1; + btntest_timer = 0; + btntest_counter = 0; + btntest_pos = 0; + + // Draw page + page_btntest(true, false); + write_string("Press the button you want to test", 0xFF, 3, 14); + write_string("Remember to enable fast USB polling!", 0xEE, 2, 25); +} + +// Rotate DPAD direction history and push new entry +void pushhistory(char new) +{ + for (char h = 1; h < HISTORY_LENGTH; h++) + { + history[h - 1] = history[h]; + } + history[HISTORY_LENGTH - 1] = new; +} + +// Track input history of P1 DPAD for secret codes! +void handle_codes() +{ + if (!input_up && input_up_last) + { + pushhistory(1); + } + if (!input_down && input_down_last) + { + pushhistory(2); + } + if (!input_left && input_left_last) + { + pushhistory(3); + } + if (!input_right && input_right_last) + { + pushhistory(4); + } + // Check for SNEK code + if (history[0] == 1 && history[1] == 1 && history[2] == 2 && history[3] == 2 && history[4] == 3 && history[5] == 4) + { + nextstate = STATE_START_ATTRACT; + pushhistory(0); + start_fadeout(); + return; + } +} + +// Menu opening handler +bool modeswitcher() +{ + // Open menu if select is held + if (input_select) + { + modeswitchtimer_select++; + if (modeswitchtimer_select == menu_openholdtime) + { + modeswitchtimer_select = 0; + start_menu(); + return 1; + } + } + else + { + modeswitchtimer_select = 0; + } + return 0; +} + +// Digital input tester state +void inputtester_digital() +{ + + // Handle PS/2 inputs whenever possible to improve latency + handle_ps2(); + + // Handle secret code detection (joypad 1 directions) + if (HBLANK_RISING) + { + basic_input(); + handle_codes(); + } + + // As soon as vsync is detected start drawing screen updates + if (VBLANK_RISING) + { + // Handle test mode switch + if (modeswitcher()) + { + return; + } + + // Draw control pad buttons + for (char joy = 0; joy < PAD_COUNT; joy++) + { + char index = joy * 32; + for (char button = 0; button < BUTTON_COUNT; button++) + { + char color = (button < 8 ? CHECK_BIT(joystick[index], button) : CHECK_BIT(joystick[index + 8], button - 8)) ? color_button_active : color_button_inactive; + write_string(button_symbol[button], color, pad_offset_x[joy] + button_x[button], pad_offset_y[joy] + button_y[button]); + } + } + } +} + +// Analog input tester state +void inputtester_analog() +{ + + // Handle PS/2 inputs whenever possible to improve latency + handle_ps2(); + + // Handle secret code detection (joypad 1 directions) + if (HBLANK_RISING) + { + basic_input(); + handle_codes(); + } + + // As soon as vsync is detected start drawing screen updates + if (VBLANK_RISING) + { + // Handle test mode switch + if (modeswitcher()) + { + return; + } + + // Draw analog point + for (char j = 0; j < PAD_COUNT; j++) + { + + char mx = analog_offset_x[j] + (analog_size / 2); + char my = analog_offset_y[j] + (analog_size / 2); + + // Reset previous color + set_fgcolour(color_analog_grid, analog_x[j] + mx, analog_y[j] + my); + + signed char ax = analog[(j * 16)]; + signed char ay = analog[(j * 16) + 8]; + + analog_x[j] = ax / analog_ratio; + analog_y[j] = ay / analog_ratio; + + // Set new color + set_fgcolour(0xFF, analog_x[j] + mx, analog_y[j] + my); + + write_stringfs("%4d", 0xFF, analog_offset_x[j] + 2, analog_offset_y[j] + analog_size + 2, ax); + write_stringfs("%4d", 0xFF, analog_offset_x[j] + 10, analog_offset_y[j] + analog_size + 2, ay); + } + } +} + +// Advanced input tester state +void inputtester_advanced() +{ + + // Handle PS/2 inputs whenever possible to improve latency + handle_ps2(); + + // Handle secret code detection (joypad 1 directions) + if (HBLANK_RISING) + { + basic_input(); + handle_codes(); + } + + // As soon as vsync is detected start drawing screen updates + if (VBLANK_RISING) + { + // Handle test mode switch + if (modeswitcher()) + { + return; + } + + // Draw joystick inputs (only update each byte if value has changed) + for (char inputindex = 0; inputindex < 6; inputindex++) + { + char m = 0b00000001; + char x = 6; + char y = 6 + inputindex; + char inputoffset = (inputindex * 32); + char lastoffset = (inputindex * 2); + for (char b = 0; b < 2; b++) + { + char index = (b * 8) + inputoffset; + char lastindex = b + lastoffset; + char joy = joystick[index]; + if (joy != joystick_last[lastindex]) + { + m = 0b00000001; + char bytes = (b == 0 ? 8 : 4); + for (char i = 0; i < bytes; i++) + { + x++; + write_char((joy & m) ? asc_1 : asc_0, 0xFF, x, y); + m <<= 1; + } + } + else + { + x += 8; + } + joystick_last[lastindex] = joy; + } + + // Draw analog inputs (only update if value has changed) + signed char ax = analog[(inputindex * 16)]; + signed char ay = analog[(inputindex * 16) + 10]; + if (ax != ax_last[inputindex] || ay != ay_last[inputindex]) + { + char stra[10]; + sprintf(stra, "%4d %4d", ax, ay); + write_string(stra, 0xFF, 20, 6 + inputindex); + } + ax_last[inputindex] = ax; + ay_last[inputindex] = ay; + + // Draw paddle inputs (only update if value has changed) + unsigned char px = paddle[(inputindex * 8)]; + if (px != px_last[inputindex]) + { + char strp[5]; + sprintf(strp, "%4d", px); + write_string(strp, 0xFF, 6, 14 + inputindex); + } + px_last[inputindex] = px; + + // Draw spinner inputs (only update when update clock changes) + bool sx_toggle = CHECK_BIT(spinner[(inputindex * 16) + 8], 0); + signed char sx = spinner[(inputindex * 16)]; + if (sx_toggle != sx_toggle_last[inputindex]) + { + sx_pos[inputindex] += sx; + write_stringf("%4d", 0xFF, 22, 14 + inputindex, sx_pos[inputindex] / 8); + } + else + { + if (sx == 1 || sx == -1) + { + sx = 0; + } + } + if (sx_last[inputindex] != sx) + { + write_stringfs("%4d", 0xFF, 17, 14 + inputindex, sx); + } + sx_last[inputindex] = sx; + sx_toggle_last[inputindex] = sx_toggle; + } + + // Keyboard test console + if (kbd_buffer_len > 0) + { + // Clear existing cursor if visible + if (con_cursor) + { + write_char(' ', 0xFF, con_x, con_y); + } + // Write characters in buffer + for (char k = 0; k < kbd_buffer_len; k++) + { + if (kbd_buffer[k] == '\n') + { + // New line + con_x = con_l; + con_y++; + if (con_y > con_b) + { + // Wrap to top + con_y = con_t; + } + } + else if (kbd_buffer[k] == '\b') + { + // Backspace - only if not at beginning of line + if (con_x > con_l) + { + con_x--; + // Clear existing character + write_char(' ', 0xFF, con_x, con_y); + } + } + else + { + // Write character + write_char(kbd_buffer[k], 0xFF, con_x, con_y); + // Move cursor right + con_x++; + if (con_x > con_r) + { + // New line + con_x = con_l; + con_y++; + if (con_y > con_b) + { + // Wrap to top + con_y = con_t; + } + } + } + } + // Clear buffer and enable cursor + kbd_buffer_len = 0; + con_cursor = 0; + con_cursortimer = 1; + } + + // Cursor blink timer + con_cursortimer--; + if (con_cursortimer <= 0) + { + con_cursor = !con_cursor; + con_cursortimer = con_cursorfreq; + write_char(con_cursor ? '|' : ' ', 0xFF, con_x, con_y); + } + } +} + +void btntest_starttest() +{ + btntest_mode = btntest_mode_test; + page_btntest(false, false); // reset screen + + // Prepare variables + btntest_timer = btntest_timer_start; + + // Reset hardware timer + timer[0] = 0; + + write_string("Press here", 0xDD, 14, 14); + write_char(19, 0xDD, 19, 15); + write_string("---\x3\x3\x3\x2\x2\x2\x1\x1\x1\xA6\xA6\xA6\x7f\x7f\x7f\xA6\xA6\xA6\x1\x1\x1\x2\x2\x2\x3\x3\x3---", 0xDD, 3, 16); +} + +// Button test - button select state +void btntest_select() +{ + if (HBLANK_RISING) + { + + // If any of 1st 8 buttons is pressed + if (joystick[0] != 0) + { + // Find which button + for (char b = 0; b < 8; b++) + { + if (CHECK_BIT(joystick[0], b)) + { + btntest_buttonindex = b; + break; + } + } + btntest_mode = btntest_mode_ready; + page_btntest(false, false); // reset screen + write_string("Hit the button at each prompt", 0b11111000, 5, 14); + write_string("Selected button is: ", 0xFF, 6, 16); + char i = btntest_buttonindex; + write_string(button_name[i], 0b00000111, 26, 16); + write_string("Press again to start test", 0b00111000, 7, 18); + btntest_timer = 10; + return; + } + } + if (VBLANK_RISING) + { + // Check inputs for select hold to enter menu + basic_input(); + modeswitcher(); + } +} + +// Button test - ready state +void btntest_ready() +{ + if (HBLANK_RISING) + { + char button = CHECK_BIT(joystick[0], btntest_buttonindex); + if (btntest_timer > 0) + { + if (joystick[0] == 0) + { + btntest_timer--; + } + } + else + { + // Check for selected button to be pressed again + if (!button && btntest_buttonlast) + { + btntest_starttest(); + } + } + btntest_buttonlast = button; + } +} + +// Button test - begin test state +void btntest_test() +{ + + // Track button presses + bool down = CHECK_BIT(joystick[0], btntest_buttonindex); + if (down && !btntest_buttondownlast) + { + btntest_presses[btntest_pos] = GET_TIMER; + btntest_pos++; + } + btntest_buttondownlast = down; + + if (VBLANK_RISING) + { + // Count down to next prompt on vblanks + btntest_timer--; + + set_bgcolour(0x0, btntest_highlight, 16); + if (btntest_timer == 30) + { + btntest_highlight = btntest_highlight_start; + } + if (btntest_highlight > 0) + { + if ((btntest_timer % 2) == 0) + { + btntest_highlight++; + if (btntest_highlight > btntest_highlight_end) + { + btntest_highlight = 0; + } + } + } + if (btntest_highlight > 0) + { + set_bgcolour(0xFF, btntest_highlight, 16); + } + + if (btntest_aftertimer > 0) + { + btntest_aftertimer--; + if (btntest_aftertimer == 0) + { + // End test here + if (btntest_counter == btntest_counter_max) + { + btntest_mode = btntest_mode_results; + btntest_results_refresh = true; + page_btntest(true, true); // reset screen + return; + } + } + } + + if (btntest_timer == 0) + { + // Capture timer + btntest_prompts[btntest_counter] = GET_TIMER; + + // Display test counter + write_stringf("%2d", 0xFF, 18, 6, btntest_counter + 1); + + btntest_timer = btntest_timer_interval; + btntest_aftertimer = 33; + btntest_counter++; + } + } +} + +unsigned short abs(signed short value) +{ + if (value < 0) + { + value = value * -1; + } + return value; +} +// Button test - results view +void btntest_results() +{ + + if (btntest_results_refresh) + { + write_string("Prompts", 0xFF, 2, 6); +// write_string("-------", 0xFF, 2, 7); + write_string("Presses", 0xFF, 11, 6); +// write_string("-------", 0xFF, 11, 7); + + char y = 7; + char press = 0; + char prompt = 0; + char done = false; + + unsigned short prompt_last = 0; + unsigned short press_last = 0; + + while (!done) + { + + unsigned short prompt_time = prompt < btntest_counter_max ? btntest_prompts[prompt] : 65535; + unsigned short press_time = press < btntest_pos ? btntest_presses[press] : 65535; + + if (prompt_time < press_time) + { + write_stringf_ushort("%6dms", 0xFF, 2, y, btntest_prompts[prompt]); + y++; + prompt++; + prompt_last = prompt_time; + } + else if (press_time < prompt_time) + { + write_stringf_ushort("%6dms", 0xFF, 11, y, btntest_presses[press]); + + // Is this early or late or just nowhere near?! + unsigned short prompt_next = prompt + 1 <= btntest_counter_max ? btntest_prompts[prompt] : 65535; + + unsigned short diff_last = abs(press_time - prompt_last); + unsigned short diff_next = abs(prompt_next - press_time); + + if (diff_next < 500) + { + write_stringf_short("%3dms before", 0b11111000, 20, y, diff_next); + } + else if (diff_last <= 500) + { + write_stringf_short("%3dms after", 0b00111111, 20, y, diff_last); + } + else + { + write_stringf_short("????", 0b00000111, 20, y, diff_next); + } + y++; + press++; + } + else + { + done = true; + } + } + + btntest_results_refresh = 0; + } + + if (VBLANK_RISING) + { + + // Check inputs for select hold to enter menu + basic_input(); + modeswitcher(); + + if (input_start && !input_start_last) + { + start_btntest(); + } + } +} + +// Button test - mode handler +void btntest() +{ + + switch (btntest_mode) + { + case btntest_mode_select: + btntest_select(); + break; + case btntest_mode_ready: + btntest_ready(); + break; + case btntest_mode_test: + btntest_test(); + break; + case btntest_mode_results: + btntest_results(); + break; + } +} diff --git a/src/menu.c b/src/menu.c new file mode 100644 index 0000000..aeb1ac7 --- /dev/null +++ b/src/menu.c @@ -0,0 +1,139 @@ +/*============================================================================ + Aznable OS - Popup menu functions + + Author: Jim Gregory - https://github.com/JimmyStones/ + Version: 1.0 + Date: 2021-07-13 + + 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, see . +===========================================================================*/ +#pragma once + +// Menu constants +#define menu_tx 12 +#define menu_bx 28 +#define menu_my 15 +#define menu_openholdtime 60 +#define menu_count 4 +// Menu variables +char menu_timer = 0; +char menu_index; +bool menu_dirty = 0; +bool menu_ready = 0; +char *menu_string[] = { + "Digital", + "Analog", + "Advanced", + "Button test"}; + +// Initialise menu state +void start_menu() +{ + state = STATE_MENU; + menu_timer = 0; + menu_ready = 0; +} + +// Menu state +void menu() +{ + // Check inputs at end of each scanline. Is this too much?! + if (HBLANK_RISING) + { + basic_input(); + if (input_up && !input_up_last) // up + { + menu_index--; + if (menu_index > 128) + { + menu_index = menu_count - 1; + } + menu_dirty = true; + } + if (input_down && !input_down_last) // down + { + menu_index++; + if (menu_index == menu_count) + { + menu_index = 0; + } + menu_dirty = true; + } + if (input_start && input_start_last) + { + switch (menu_index) + { + case 0: + state = STATE_START_INPUTTESTER; + break; + case 1: + state = STATE_START_INPUTTESTERANALOG; + break; + case 2: + state = STATE_START_INPUTTESTERADVANCED; + break; + case 3: + state = STATE_START_BTNTEST; + break; + } + } + } + + // As soon as vsync is detected start drawing screen updates + if (VBLANK_RISING) + { + char maxsize = (menu_count * 3) + 1; + + if (menu_timer < maxsize) + { + char oy1 = ((menu_timer) / 2); + char oy2 = oy1 + ((menu_count % 2) ? 0 : 1); + char my = menu_my; + panel_shaded(menu_tx, my - oy1, menu_bx, my + oy2, 0xFF, 0b00110110, 0b00100100); + if (oy1 > 1) + { + fill(menu_tx + 1, my - (oy1 - 1), menu_bx - 1, my + (oy2 - 1), 0, 0); + fill_bgcolor(menu_tx + 1, my - (oy1 - 1), menu_bx - 1, my + (oy2 - 1), 0b00001001); + } + menu_timer++; + if (menu_timer == maxsize) + { + menu_dirty = true; + } + } + else + { + if (menu_dirty) + { + char ty = menu_my - ((menu_count * 3) / 2) + 1; + for (char m = 0; m < menu_count; m++) + { + if (menu_index == m) + { + panel_shaded(menu_tx + 1, ty, menu_bx - 1, ty + 2, 0b11111111, 0b10111111, 0b10110110); + write_string(menu_string[m], 0b11111111, menu_tx + 2, ty + 1); + } + else + { + panel_shaded(menu_tx + 1, ty, menu_bx - 1, ty + 2, 0b10111111, 0b10110110, 0b01100100); + write_string(menu_string[m], 0b10110110, menu_tx + 2, ty + 1); + } + ty += 3; + } + menu_dirty = false; + menu_ready = true; + } + } + } +} diff --git a/src/os.c b/src/os.c index 1e92d36..3682a71 100644 --- a/src/os.c +++ b/src/os.c @@ -3,7 +3,7 @@ Author: Jim Gregory - https://github.com/JimmyStones/ Version: 1.0 - Date: 2021-07-03 + Date: 2021-07-12 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 @@ -22,61 +22,22 @@ #include #include #include + #include "sys.c" -#include "ui.c" +#include "sys_custom.c" #include "ps2.c" +#include "ui.c" +#include "ui_custom.c" +#include "menu.c" +#include "inputtester.c" +#include "snek.c" +#include "fader.c" bool hsync; bool hsync_last; bool vsync; bool vsync_last; -// Application state -#define STATE_START_INPUTTESTER 0 -#define STATE_INPUTTESTER 1 - -#define STATE_START_INPUTTESTERADVANCED 2 -#define STATE_INPUTTESTERADVANCED 3 - -#define STATE_START_INPUTTESTERANALOG 4 -#define STATE_INPUTTESTERANALOG 5 - -#define STATE_FADEOUT 8 -#define STATE_START_FADEIN 9 -#define STATE_FADEIN 10 - -#define STATE_START_ATTRACT 16 -#define STATE_ATTRACT 17 - -#define STATE_START_GAME 24 -#define STATE_GAME 25 - -char state = STATE_START_INPUTTESTER; -char nextstate = 0; - -// SNEK variables -unsigned char movefreqinit = 14; -unsigned char movefreqdecfreq = 200; -unsigned char movefreqdectimer = 0; -unsigned char movefreq = 0; -unsigned char movetimer = 0; -signed int x = 20; -signed int y = 15; -signed char xd = 0; -signed char yd = 1; -signed char nxd = 0; -signed char nyd = 1; -unsigned int length = 0; -unsigned char playerchar = 83; - -// Fade in/out variables -unsigned char fade = 0; -unsigned char fadetimer = 0; -unsigned char fadefreq = 4; - -// Attract mode variables -unsigned char attractstate = 0; - // Input tester variables unsigned char joystick_last[12]; signed char ax_last[6]; @@ -86,19 +47,6 @@ signed char sx_toggle_last[6]; signed char sx_last[6]; unsigned long sx_pos[6]; -unsigned char con_x; // Console cursor X position -unsigned char con_y; // Console cursor X position -unsigned char con_l = 2; // Console left edge X -unsigned char con_t = 20; // Console top edge Y -unsigned char con_r = 37; // Console right edge X -unsigned char con_b = 37; // Console bottom edge Y -bool con_cursor; -unsigned char con_cursortimer = 1; -unsigned char con_cursorfreq = 30; - -char modeswitchtimer_select = 0; -char modeswitchtimer_start = 0; - // DPAD tracker bool bdown_left = 0; bool bdown_left_last = 0; @@ -109,696 +57,6 @@ bool bdown_up_last = 0; bool bdown_down = 0; bool bdown_down_last = 0; -#define HISTORY_LENGTH 6 -char history[HISTORY_LENGTH]; - -#define PAD_COUNT 2 -#define BUTTON_COUNT 12 - -char pad_offset_x[PAD_COUNT] = {7, 7}; -char pad_offset_y[PAD_COUNT] = {7, 16}; -char button_name[BUTTON_COUNT][6] = { - "R", - "L", - "D", - "U", - "A", - "B", - "X", - "Y", - "L", - "R", - "Sel", - "Start"}; -char button_x[BUTTON_COUNT] = {6, 2, 4, 4, 24, 22, 22, 20, 3, 23, 9, 13}; -char button_y[BUTTON_COUNT] = {3, 3, 4, 2, 3, 4, 2, 3, 0, 0, 3, 3}; - -char analog_offset_x[PAD_COUNT] = {1, 20}; -char analog_offset_y[PAD_COUNT] = {5, 5}; -char analog_size = 18; -signed char analog_x[PAD_COUNT]; -signed char analog_y[PAD_COUNT]; -char analog_ratio = 256 / 17; - -// Draw static elements for digital input test page -void page_inputtester_digital() -{ - clear_chars(0); - page_border(0b00000111); - - write_string("- MiSTer Input Tester -", 0b11100011, 8, 1); - write_string("Hold: Select=analog Start=advanced", 0b11100011, 3, 29); - - // Draw pads - for (char j = 0; j < PAD_COUNT; j++) - { - write_stringf("JOY %d", 0xFF, pad_offset_x[j] - 5, pad_offset_y[j] + 3, j + 1); - draw_pad(pad_offset_x[j], pad_offset_y[j]); - } -} - -// Draw static elements for analog input test page -void page_inputtester_analog() -{ - clear_chars(0); - page_border(0b00000111); - - write_string("- MiSTer Input Tester -", 0b11100011, 8, 1); - write_string("Hold: Select=digital Start=advanced", 0b11100011, 3, 29); - - for (char j = 0; j < PAD_COUNT; j++) - { - write_stringf("ANALOG %d", 0xFF, analog_offset_x[j] + 5, analog_offset_y[j] - 1, j + 1); - draw_analog(analog_offset_x[j], analog_offset_y[j], analog_size, analog_size); - } -} - -// Draw static elements for advanced input test page -void page_inputtester_advanced() -{ - clear_chars(0); - page_border(0b00000111); - - write_string("- MiSTer Input Tester -", 0b11100011, 8, 1); - write_string("Hold: Select=digital Start=analog", 0b11100011, 3, 29); - - write_string("RLDUABXYLRsS", 0xFF, 7, 3); - write_string("AX", 0xFF, 22, 3); - write_string("AY", 0xFF, 27, 3); - - write_string("POS", 0xFF, 7, 11); - write_string("SPD POS", 0xFF, 18, 11); - - char label[5]; - for (unsigned char j = 0; j < 6; j++) - { - sprintf(label, "JOY%d", j + 1); - write_string(label, 0xFF - (j * 2), 2, 4 + j); - - sprintf(label, "PAD%d", j + 1); - write_string(label, 0xFF - (j * 2), 2, 12 + j); - - sprintf(label, "SPN%d", j + 1); - write_string(label, 0xFF - (j * 2), 14, 12 + j); - } - write_string("CON", 0xFF, 2, 19); -} - -void reset_inputstates() -{ - modeswitchtimer_select = 0; - modeswitchtimer_start = 0; - for (char i = 0; i < 12; i++) - { - joystick_last[i] = 1; - } - for (char i = 0; i < 6; i++) - { - ax_last[i] = 1; - ay_last[i] = 1; - px_last[i] = 1; - sx_toggle_last[i] = 1; - sx_last[i] = 1; - sx_pos[i] = 0; - } -} - -// Initialise digital inputtester state and draw static elements -void start_inputtester_digital() -{ - state = STATE_INPUTTESTER; - - // Draw page - page_inputtester_digital(); - - // Reset last states for inputs - reset_inputstates(); -} - -// Initialise analog inputtester state and draw static elements -void start_inputtester_analog() -{ - state = STATE_INPUTTESTERANALOG; - - // Draw page - page_inputtester_analog(); - - // Reset last states for inputs - reset_inputstates(); -} - -// Initialise advanced inputtester state and draw static elements -void start_inputtester_advanced() -{ - state = STATE_INPUTTESTERADVANCED; - - // Draw page - page_inputtester_advanced(); - - // Reset console cursor - con_x = con_l; - con_y = con_t; - - // Reset last states for inputs - reset_inputstates(); -} - -// Initialise fadeout state -void start_fadeout() -{ - state = STATE_FADEOUT; - fadetimer = fadefreq; - fade = 0; -} - -// Initialise fadein state -void start_fadein() -{ - state = STATE_FADEIN; - fadetimer = fadefreq; - fade = 15; -} - -// Initialise attract state and draw static elements -void start_attract() -{ - state = STATE_ATTRACT; - attractstate = 0; - clear_chars(0); - page_border(0b00000111); - write_string("SNEK", 0b00000111, 18, 0); - movefreq = 5; - movetimer = 1; -} - -// Initialise attract state and draw static elements -void start_gameplay() -{ - state = STATE_GAME; - length = 0; - x = 20; - y = 15; - xd = 0; - yd = 1; - nxd = 0; - nyd = 1; - clear_chars(0); - page_border(0b00000111); - write_string("SNEK", 0b00000111, 18, 0); - write_char(playerchar, 0xFF, x, y); - - movefreq = movefreqinit; - movefreqdectimer = movefreqdecfreq; - movetimer = movefreq; -} - -// Fade out state -void fadeout() -{ - if (vsync && !vsync_last) - { - fadetimer--; - if (fadetimer == 0) - { - box(fade, fade, 39 - fade, 29 - fade, 127, 0b0000111); - fadetimer = fadefreq; - fade++; - if (fade == 16) - { - start_fadein(); - } - } - } -} - -// Fade in state -void fadein() -{ - if (vsync && !vsync_last) - { - fadetimer--; - if (fadetimer == 0) - { - box(fade, fade, 39 - fade, 29 - fade, 0, 0b0000000); - fadetimer = fadefreq; - fade--; - if (fade == 0) - { - state = nextstate; - } - } - } -} - -// Rotate DPAD direction history and push new entry -void pushhistory(char new) -{ - for (char h = 1; h < HISTORY_LENGTH; h++) - { - history[h - 1] = history[h]; - } - history[HISTORY_LENGTH - 1] = new; -} - -// Track input history of P1 DPAD for secret codes! -void handle_codes() -{ - bdown_up_last = bdown_up; - bdown_down_last = bdown_down; - bdown_left_last = bdown_left; - bdown_right_last = bdown_right; - bdown_up = CHECK_BIT(joystick[0], 3); - bdown_down = CHECK_BIT(joystick[0], 2); - bdown_left = CHECK_BIT(joystick[0], 1); - bdown_right = CHECK_BIT(joystick[0], 0); - if (!bdown_up && bdown_up_last) - { - pushhistory(1); - } - if (!bdown_down && bdown_down_last) - { - pushhistory(2); - } - if (!bdown_left && bdown_left_last) - { - pushhistory(3); - } - if (!bdown_right && bdown_right_last) - { - pushhistory(4); - } - // Check for SNEK code - if (history[0] == 1 && history[1] == 1 && history[2] == 2 && history[3] == 2 && history[4] == 3 && history[5] == 4) - { - nextstate = STATE_START_ATTRACT; - pushhistory(0); - start_fadeout(); - return; - } -} - -bool modeswitcher() -{ - // Switch to advanced mode if start is held for 1 second - if (CHECK_BIT(joystick[8], 3)) - { - modeswitchtimer_start++; - if (modeswitchtimer_start == 60) - { - if (state == STATE_INPUTTESTERADVANCED) - { - start_inputtester_analog(); - } - else - { - start_inputtester_advanced(); - } - return 1; - } - } - else - { - modeswitchtimer_start = 0; - } - // Switch between digital/analog mode if select is held for 1 second - if (CHECK_BIT(joystick[8], 2)) - { - modeswitchtimer_select++; - if (modeswitchtimer_select == 60) - { - if (state == STATE_INPUTTESTER) - { - start_inputtester_analog(); - } - else - { - start_inputtester_digital(); - } - return 1; - } - } - else - { - modeswitchtimer_select = 0; - } - return 0; -} - -// Digital input tester state -void inputtester_digital() -{ - - // Handle PS/2 inputs whenever possible to improve latency - handle_ps2(); - - // Handle secret code detection (joypad 1 directions) - if (hsync && !hsync_last) - { - handle_codes(); - } - - // As soon as vsync is detected start drawing screen updates - if (vsync && !vsync_last) - { - - // Handle test mode switch - if (modeswitcher()) - { - return; - } - - // Draw control pad buttons - for (char joy = 0; joy < PAD_COUNT; joy++) - { - char index = joy * 32; - for (char button = 0; button < BUTTON_COUNT; button++) - { - char color = (button < 8 ? CHECK_BIT(joystick[index], button) : CHECK_BIT(joystick[index + 8], button - 8)) ? 0xFF : 0b10010010; - write_string(button_name[button], color, pad_offset_x[joy] + button_x[button], pad_offset_y[joy] + button_y[button]); - } - } - } -} - -// Analog input tester state -void inputtester_analog() -{ - - // Handle PS/2 inputs whenever possible to improve latency - handle_ps2(); - - // Handle secret code detection (joypad 1 directions) - if (hsync && !hsync_last) - { - handle_codes(); - } - - // As soon as vsync is detected start drawing screen updates - if (vsync && !vsync_last) - { - - // Handle test mode switch - if (modeswitcher()) - { - return; - } - - // Draw analog point - for (char j = 0; j < PAD_COUNT; j++) - { - - char mx = analog_offset_x[j] + (analog_size / 2); - char my = analog_offset_y[j] + (analog_size / 2); - - // Reset previous color - set_colour(color_analog_grid, analog_x[j] + mx, analog_y[j] + my); - - signed char ax = analog[(j * 16)]; - signed char ay = analog[(j * 16) + 8]; - - analog_x[j] = ax / analog_ratio; - analog_y[j] = ay / analog_ratio; - - // Set new color - set_colour(0xFF, analog_x[j] + mx, analog_y[j] + my); - - write_stringfs("%4d", 0xFF, analog_offset_x[j], analog_offset_y[j] + analog_size + 1, ax); - write_stringfs("%4d", 0xFF, analog_offset_x[j] + 5, analog_offset_y[j] + analog_size + 1, ay); - } - } -} - -// Advanced input tester state -void inputtester_advanced() -{ - - // Handle PS/2 inputs whenever possible to improve latency - handle_ps2(); - - // Handle secret code detection (joypad 1 directions) - if (hsync && !hsync_last) - { - handle_codes(); - } - - // As soon as vsync is detected start drawing screen updates - if (vsync && !vsync_last) - { - - // Handle test mode switch - if (modeswitcher()) - { - return; - } - - // Draw joystick inputs (only update each byte if value has changed) - for (char inputindex = 0; inputindex < 6; inputindex++) - { - char m = 0b00000001; - char x = 6; - char y = 4 + inputindex; - char inputoffset = (inputindex * 32); - char lastoffset = (inputindex * 2); - for (char b = 0; b < 2; b++) - { - char index = (b * 8) + inputoffset; - char lastindex = b + lastoffset; - char joy = joystick[index]; - if (joy != joystick_last[lastindex]) - { - m = 0b00000001; - char bytes = (b == 0 ? 8 : 4); - for (char i = 0; i < bytes; i++) - { - x++; - write_char((joy & m) ? asc_1 : asc_0, 0xFF, x, y); - m <<= 1; - } - } - else - { - x += 8; - } - joystick_last[lastindex] = joy; - } - - // Draw analog inputs (only update if value has changed) - signed char ax = analog[(inputindex * 16)]; - signed char ay = analog[(inputindex * 16) + 8]; - if (ax != ax_last[inputindex] || ay != ay_last[inputindex]) - { - char stra[10]; - sprintf(stra, "%4d %4d", ax, ay); - write_string(stra, 0xFF, 20, 4 + inputindex); - } - ax_last[inputindex] = ax; - ay_last[inputindex] = ay; - - // Draw paddle inputs (only update if value has changed) - unsigned char px = paddle[(inputindex * 8)]; - if (px != px_last[inputindex]) - { - char strp[5]; - sprintf(strp, "%4d", px); - write_string(strp, 0xFF, 6, 12 + inputindex); - } - px_last[inputindex] = px; - - // Draw spinner inputs (only update when update clock changes) - bool sx_toggle = CHECK_BIT(spinner[(inputindex * 16) + 8], 0); - signed char sx = spinner[(inputindex * 16)]; - if (sx_toggle != sx_toggle_last[inputindex]) - { - sx_pos[inputindex] += sx; - write_stringf("%4d", 0xFF, 22, 12 + inputindex, sx_pos[inputindex] / 8); - } - else - { - if (sx == 1 || sx == -1) - { - sx = 0; - } - } - if (sx_last[inputindex] != sx) - { - write_stringfs("%4d", 0xFF, 17, 12 + inputindex, sx); - } - sx_last[inputindex] = sx; - sx_toggle_last[inputindex] = sx_toggle; - } - - // Keyboard test console - if (kbd_buffer_len > 0) - { - // Clear existing cursor if visible - if (con_cursor) - { - write_char(' ', 0xFF, con_x, con_y); - } - // Write characters in buffer - for (char k = 0; k < kbd_buffer_len; k++) - { - if (kbd_buffer[k] == '\n') - { - // New line - con_x = con_l; - con_y++; - if (con_y > con_b) - { - // Wrap to top - con_y = con_t; - } - } - else if (kbd_buffer[k] == '\b') - { - // Backspace - only if not at beginning of line - if (con_x > con_l) - { - con_x--; - // Clear existing character - write_char(' ', 0xFF, con_x, con_y); - } - } - else - { - // Write character - write_char(kbd_buffer[k], 0xFF, con_x, con_y); - // Move cursor right - con_x++; - if (con_x > con_r) - { - // New line - con_x = con_l; - con_y++; - if (con_y > con_b) - { - // Wrap to top - con_y = con_t; - } - } - } - } - // Clear buffer and enable cursor - kbd_buffer_len = 0; - con_cursor = 0; - con_cursortimer = 1; - } - - // Cursor blink timer - con_cursortimer--; - if (con_cursortimer <= 0) - { - con_cursor = !con_cursor; - con_cursortimer = con_cursorfreq; - write_char(con_cursor ? '|' : ' ', 0xFF, con_x, con_y); - } - } -} - -// SNEK - gameplay state -void gameplay() -{ - - if (hsync && !hsync_last) - { - if (yd != 1 && joystick[0] & 0b00001000) // up - { - nyd = -1; - nxd = 0; - } - if (yd != -1 && joystick[0] & 0b00000100) // down - { - nyd = 1; - nxd = 0; - } - if (xd != 1 && joystick[0] & 0b00000010) // left - { - nxd = -1; - nyd = 0; - } - if (xd != -1 && joystick[0] & 0b00000001) //right - { - nxd = 1; - nyd = 0; - } - - if (CHECK_BIT(joystick[8], 2)) // select to quit - { - start_inputtester_advanced(); - return; - } - } - - if (vsync && !vsync_last) - { - movetimer--; - if (movetimer == 0) - { - write_char(127, 0x66, x, y); - xd = nxd; - yd = nyd; - x += xd; - y += yd; - unsigned int p = (y * chram_cols) + x; - if (chram[p] > 0) - { - nextstate = STATE_START_ATTRACT; - start_fadeout(); - return; - } - length++; - write_char(playerchar, 0xFF, x, y); - movetimer = movefreq; - char score[5]; - sprintf(score, "%4d", length); - write_string(score, 0xFF, 35, 0); - } - - movefreqdectimer--; - if (movefreqdectimer == 0) - { - movefreqdectimer = movefreqdecfreq; - if (movefreq > 3) - { - movefreq--; - } - char str_movefreq[3]; - sprintf(str_movefreq, "%4d", movefreq); - write_string(str_movefreq, 0xFF, 35, 29); - } - } -} - -// SNEK - attract state -void attract() -{ - - if (hsync && !hsync_last) - { - if (CHECK_BIT(joystick[8], 3)) // start to start - { - start_gameplay(); - return; - } - if (CHECK_BIT(joystick[8], 2)) // select to quit - { - start_inputtester_advanced(); - return; - } - } - - if (vsync && !vsync_last) - { - movetimer--; - if (movetimer == 0) - { - attractstate = !attractstate; - write_string("PRESS START", attractstate == 0 ? 0x00 : 0xFF, 16, 15); - movetimer = movefreq; - } - } -} - // Main entry and state machine void main() { @@ -807,6 +65,8 @@ void main() { hsync = input0 & 0x80; vsync = input0 & 0x40; + hblank = input0 & 0x20; + vblank = input0 & 0x10; switch (state) { case STATE_START_INPUTTESTER: @@ -830,6 +90,20 @@ void main() inputtester_analog(); break; + case STATE_START_BTNTEST: + start_btntest(); + break; + case STATE_BTNTEST: + btntest(); + break; + + case STATE_START_MENU: + start_menu(); + break; + case STATE_MENU: + menu(); + break; + case STATE_FADEOUT: fadeout(); break; @@ -841,20 +115,25 @@ void main() start_attract(); break; case STATE_ATTRACT: - attract(); + snek_attract(); break; - case STATE_START_GAME: + case STATE_START_GAME_SNEK: start_gameplay(); break; - case STATE_GAME: - gameplay(); + case STATE_GAME_SNEK: + snek_gameplay(); break; default: + // Start default state + start_inputtester_digital(); + //start_btntest(); break; } hsync_last = hsync; vsync_last = vsync; + hblank_last = hblank; + vblank_last = vblank; } } diff --git a/src/ps2.c b/src/ps2.c index 6ed9312..41653c5 100644 --- a/src/ps2.c +++ b/src/ps2.c @@ -1,5 +1,5 @@ /*============================================================================ - MiSTer test harness OS - PS/2 interface functions + Aznable OS - PS/2 interface functions Author: Jim Gregory - https://github.com/JimmyStones/ Version: 1.0 diff --git a/src/snek.c b/src/snek.c new file mode 100644 index 0000000..e745ffc --- /dev/null +++ b/src/snek.c @@ -0,0 +1,181 @@ +/*============================================================================ + Input Test - Snek mini-game + + Author: Jim Gregory - https://github.com/JimmyStones/ + Version: 1.0 + Date: 2021-07-12 + + 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, see . +===========================================================================*/ + +#pragma once + +#include "sys.c" +#include "fader.c" + +// SNEK constants +#define movefreqinit 14 +#define movefreqdecfreq 200 +#define playerchar 83 +// SNEK variables +unsigned char movefreqdectimer = 0; +unsigned char movefreq = 0; +unsigned char movetimer = 0; +signed int x = 20; +signed int y = 15; +signed char xd = 0; +signed char yd = 1; +signed char nxd = 0; +signed char nyd = 1; +unsigned int length = 0; + +// Attract mode variables +unsigned char attractstate = 0; + +// Initialise attract state and draw static elements +void start_attract() +{ + state = STATE_ATTRACT; + attractstate = 0; + clear_chars(0); + page_border(0b00000111); + write_string("SNEK", 0b00000111, 18, 0); + movefreq = 5; + movetimer = 1; +} + +// Initialise attract state and draw static elements +void start_gameplay() +{ + state = STATE_GAME_SNEK; + length = 0; + x = 20; + y = 15; + xd = 0; + yd = 1; + nxd = 0; + nyd = 1; + clear_chars(0); + page_border(0b00000111); + write_string("SNEK", 0b00000111, 18, 0); + write_char(playerchar, 0xFF, x, y); + + movefreq = movefreqinit; + movefreqdectimer = movefreqdecfreq; + movetimer = movefreq; +} + +// SNEK - gameplay state +void snek_gameplay() +{ + + if (HBLANK_RISING) + { + if (yd != 1 && joystick[0] & 0b00001000) // up + { + nyd = -1; + nxd = 0; + } + if (yd != -1 && joystick[0] & 0b00000100) // down + { + nyd = 1; + nxd = 0; + } + if (xd != 1 && joystick[0] & 0b00000010) // left + { + nxd = -1; + nyd = 0; + } + if (xd != -1 && joystick[0] & 0b00000001) //right + { + nxd = 1; + nyd = 0; + } + + if (CHECK_BIT(joystick[8], 2)) // select to quit + { + state = 0; + return; + } + } + + if (VBLANK_RISING) + { + movetimer--; + if (movetimer == 0) + { + write_char(127, 0x66, x, y); + xd = nxd; + yd = nyd; + x += xd; + y += yd; + unsigned int p = (y * chram_cols) + x; + if (chram[p] > 0) + { + nextstate = STATE_START_ATTRACT; + start_fadeout(); + return; + } + length++; + write_char(playerchar, 0xFF, x, y); + movetimer = movefreq; + char score[5]; + sprintf(score, "%4d", length); + write_string(score, 0xFF, 35, 0); + } + + movefreqdectimer--; + if (movefreqdectimer == 0) + { + movefreqdectimer = movefreqdecfreq; + if (movefreq > 3) + { + movefreq--; + } + char str_movefreq[3]; + sprintf(str_movefreq, "%4d", movefreq); + write_string(str_movefreq, 0xFF, 35, 29); + } + } +} + +// SNEK - attract state +void snek_attract() +{ + + if (HBLANK_RISING) + { + if (CHECK_BIT(joystick[8], 3)) // start to start + { + start_gameplay(); + return; + } + if (CHECK_BIT(joystick[8], 2)) // select to quit + { + state = 0; + return; + } + } + + if (VBLANK_RISING) + { + movetimer--; + if (movetimer == 0) + { + attractstate = !attractstate; + write_string("PRESS START", attractstate == 0 ? 0x00 : 0xFF, 16, 15); + movetimer = movefreq; + } + } +} diff --git a/src/sys.c b/src/sys.c index 1b710c1..93c6201 100644 --- a/src/sys.c +++ b/src/sys.c @@ -1,9 +1,9 @@ /*============================================================================ - MiSTer test harness OS - System interface functions + Aznable OS - System interface functions Author: Jim Gregory - https://github.com/JimmyStones/ Version: 1.0 - Date: 2021-07-03 + Date: 2021-07-12 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 @@ -25,7 +25,8 @@ #include #include -// Memory maps +// Memory mapped IO +// - Inputs unsigned char __at(0x6000) input0; unsigned char __at(0x7000) joystick[24]; unsigned char __at(0x7100) analog[12]; @@ -33,13 +34,37 @@ unsigned char __at(0x7200) paddle[6]; unsigned char __at(0x7300) spinner[12]; unsigned char __at(0x7400) ps2_key[2]; unsigned char __at(0x7500) ps2_mouse[6]; +unsigned char __at(0x7600) timestamp[5]; +unsigned char __at(0x7700) timer[2]; +// - Graphics RAM unsigned char __at(0x8000) chram[2048]; -unsigned char __at(0x8800) colram[2048]; +unsigned char __at(0x8800) fgcolram[2048]; +unsigned char __at(0x9000) bgcolram[2048]; // Character map const unsigned char chram_cols = 64; const unsigned char chram_rows = 32; unsigned int chram_size; +// Hardware inputs +bool hsync; +bool hsync_last; +bool vsync; +bool vsync_last; +bool hblank; +bool hblank_last; +bool vblank; +bool vblank_last; + // Macros -#define CHECK_BIT(var, pos) ((var) & (1 << (pos))) \ No newline at end of file +#define CHECK_BIT(var, pos) ((var) & (1 << (pos))) +#define SET_BIT(var,pos) ((var) |= (1 << (pos))) +#define CLEAR_BIT(var,pos) ((var) &= ~(1 << (pos))) +#define VBLANK_RISING (vblank && !vblank_last) +#define VSYNC_RISING (vsync && !vsync_last) +#define HBLANK_RISING (hblank && !hblank_last) +#define HSYNC_RISING (hsync && !hsync_last) + +// Application state +char state = 0; +char nextstate = 0; diff --git a/src/sys_custom.c b/src/sys_custom.c new file mode 100644 index 0000000..ff331c1 --- /dev/null +++ b/src/sys_custom.c @@ -0,0 +1,83 @@ +/*============================================================================ + Input Test - Custom system functions + + Author: Jim Gregory - https://github.com/JimmyStones/ + Version: 1.0 + Date: 2021-07-12 + + 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, see . +===========================================================================*/ + +#pragma once +#include "sys.c" + +// Application states +#define STATE_START_INPUTTESTER 1 +#define STATE_INPUTTESTER 2 + +#define STATE_START_INPUTTESTERADVANCED 3 +#define STATE_INPUTTESTERADVANCED 4 + +#define STATE_START_INPUTTESTERANALOG 5 +#define STATE_INPUTTESTERANALOG 6 + +#define STATE_START_BTNTEST 7 +#define STATE_BTNTEST 8 + +#define STATE_START_MENU 9 +#define STATE_MENU 10 + +#define STATE_FADEOUT 20 +#define STATE_START_FADEIN 21 +#define STATE_FADEIN 22 + +#define STATE_START_ATTRACT 30 +#define STATE_ATTRACT 31 + +#define STATE_START_GAME_SNEK 40 +#define STATE_GAME_SNEK 41 + +#define GET_TIMER ((unsigned short)timer[8] << 8) | (unsigned char)timer[0] + +// DPAD tracker +bool input_left = 0; +bool input_left_last = 0; +bool input_right = 0; +bool input_right_last = 0; +bool input_up = 0; +bool input_up_last = 0; +bool input_down = 0; +bool input_down_last = 0; +bool input_start; +bool input_start_last = 0; +bool input_select; +bool input_select_last = 0; + +// Track joypad 1 directions and start for menu control +void basic_input() +{ + input_up_last = input_up; + input_down_last = input_down; + input_left_last = input_left; + input_right_last = input_right; + input_start_last = input_start; + input_select_last = input_select; + input_up = CHECK_BIT(joystick[0], 3); + input_down = CHECK_BIT(joystick[0], 2); + input_left = CHECK_BIT(joystick[0], 1); + input_right = CHECK_BIT(joystick[0], 0); + input_start = CHECK_BIT(joystick[8], 3); + input_select = CHECK_BIT(joystick[8], 2); +} + diff --git a/src/ui.c b/src/ui.c index b5aeff2..2653030 100644 --- a/src/ui.c +++ b/src/ui.c @@ -1,9 +1,9 @@ /*============================================================================ - MiSTer test harness OS - User interface functions + Aznable OS - User interface functions Author: Jim Gregory - https://github.com/JimmyStones/ - Version: 1.0 - Date: 2021-07-03 + Version: 1.1 + Date: 2021-07-15 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 @@ -25,30 +25,51 @@ char asc_0 = 48; char asc_1 = 49; +#define char_corner_round_tl 149 +#define char_corner_round_tr 137 +#define char_corner_round_bl 138 +#define char_corner_round_br 139 +#define char_line_h 131 +#define char_line_v 130 +#define char_t_up 177 +#define char_dot 27 +#define char_cross 155 + // Set all character RAM to specified character void clear_chars(char c) { for (unsigned int p = 0; p < chram_size; p++) { chram[p] = c; + fgcolram[p] = c; + bgcolram[p] = c; + } +} + +// Set all character background colours to specified +void clear_bgcolor(char color) +{ + for (unsigned int p = 0; p < chram_size; p++) + { + bgcolram[p] = color; } } // Write string to character RAM -void write_string(const char *string, char color, unsigned int x, unsigned int y) +void write_string(const char *string, char color, unsigned char x, unsigned char y) { unsigned int p = (y * chram_cols) + x; unsigned char l = strlen(string); for (char c = 0; c < l; c++) { chram[p] = string[c]; - colram[p] = color; + fgcolram[p] = color; p++; } } // Write formatted string to character RAM (signed char data) -void write_stringfs(const char *format, char color, unsigned int x, unsigned int y, signed char data) +void write_stringfs(const char *format, char color, unsigned char x, unsigned char y, signed char data) { unsigned int p = (y * chram_cols) + x; char temp[30]; @@ -61,13 +82,13 @@ void write_stringfs(const char *format, char color, unsigned int x, unsigned int return; } chram[p] = temp[c]; - colram[p] = color; + fgcolram[p] = color; p++; } } // Write formatted string to character RAM (unsigned char data) -void write_stringf(const char *format, char color, unsigned int x, unsigned int y, char data) +void write_stringf(const char *format, char color, unsigned char x, unsigned char y, char data) { unsigned int p = (y * chram_cols) + x; char temp[30]; @@ -80,28 +101,115 @@ void write_stringf(const char *format, char color, unsigned int x, unsigned int return; } chram[p] = temp[c]; - colram[p] = color; + fgcolram[p] = color; + p++; + } +} + +// Write formatted string to character RAM (unsigned short data) +void write_stringf_ushort(const char *format, char color, unsigned char x, unsigned char y, unsigned short data) +{ + unsigned int p = (y * chram_cols) + x; + char temp[40]; + sprintf(temp, format, data); + unsigned char l = strlen(temp); + for (char c = 0; c < l; c++) + { + if (temp[c] == 0) + { + return; + } + chram[p] = temp[c]; + fgcolram[p] = color; + p++; + } +} + +// Write formatted string to character RAM (signed short data) +void write_stringf_short(const char *format, char color, unsigned char x, unsigned char y, short data) +{ + unsigned int p = (y * chram_cols) + x; + char temp[40]; + sprintf(temp, format, data); + unsigned char l = strlen(temp); + for (char c = 0; c < l; c++) + { + if (temp[c] == 0) + { + return; + } + chram[p] = temp[c]; + fgcolram[p] = color; + p++; + } +} + +// Write formatted string to character RAM (unsigned long data) +void write_stringf_ulong(const char *format, char color, unsigned char x, unsigned char y, unsigned long data) +{ + unsigned int p = (y * chram_cols) + x; + char temp[40]; + sprintf(temp, format, data); + unsigned char l = strlen(temp); + for (char c = 0; c < l; c++) + { + if (temp[c] == 0) + { + return; + } + chram[p] = temp[c]; + fgcolram[p] = color; p++; } } // Write single char to character RAM and colour RAM -void write_char(unsigned char c, char color, unsigned int x, unsigned int y) +void write_char(unsigned char c, char color, unsigned char x, unsigned char y) { unsigned int p = (y * chram_cols) + x; chram[p] = c; - colram[p] = color; + fgcolram[p] = color; +} + +// Write row of consecutive chars to character RAM and colour RAM +void write_char_row(unsigned char c, char color, unsigned char x, unsigned char y, unsigned char count) +{ + unsigned int p = (y * chram_cols) + x; + for (char b = 0; b < count; b++) + { + chram[p] = c; + fgcolram[p] = color; + p++; + } } // Set colour of single char -void set_colour(char color, unsigned int x, unsigned int y) +void set_fgcolour(char color, char x, char y) { unsigned int p = (y * chram_cols) + x; - colram[p] = color; + fgcolram[p] = color; +} + +// Set background colour of single char +void set_bgcolour(char color, char x, char y) +{ + unsigned int p = (y * chram_cols) + x; + bgcolram[p] = color; +} + +// Write row of consecutive chars to character RAM and colour RAM +void write_bgcol_row(char color, unsigned char x, unsigned char y, unsigned char count) +{ + unsigned int p = (y * chram_cols) + x; + for (char b = 0; b < count; b++) + { + bgcolram[p] = color; + p++; + } } // Write grouped bits to character RAM -void write_bits(char bits[], char multi, unsigned char first, unsigned char length, char color, unsigned int x, unsigned int y) +void write_bits(char bits[], char multi, unsigned char first, unsigned char length, char color, unsigned char x, unsigned char y) { for (char b = first; b < first + length; b++) { @@ -118,14 +226,14 @@ void write_bits(char bits[], char multi, unsigned char first, unsigned char leng } // Draw box outline with specified character -void box(unsigned int tx, unsigned int ty, unsigned int bx, unsigned int by, char c, char color) +void box(unsigned char tx, unsigned char ty, unsigned char bx, unsigned char by, char c, char color) { - for (unsigned int x = tx; x <= bx; x++) + for (unsigned char x = tx; x <= bx; x++) { write_char(c, color, x, ty); write_char(c, color, x, by); } - for (unsigned int y = ty + 1; y < by; y++) + for (unsigned char y = ty + 1; y < by; y++) { write_char(c, color, tx, y); write_char(c, color, bx, y); @@ -135,30 +243,60 @@ void box(unsigned int tx, unsigned int ty, unsigned int bx, unsigned int by, cha // Draw UI panel void panel(char tx, char ty, char bx, char by, char color) { - write_char(128, color, tx, ty); - write_char(130, color, bx, ty); - write_char(133, color, tx, by); - write_char(132, color, bx, by); + write_char(char_corner_round_tl, color, tx, ty); + write_char(char_corner_round_tr, color, bx, ty); + write_char(char_corner_round_bl, color, tx, by); + write_char(char_corner_round_br, color, bx, by); for (char x = tx + 1; x < bx; x++) { - write_char(129, color, x, ty); - write_char(129, color, x, by); + write_char(char_line_h, color, x, ty); + write_char(char_line_h, color, x, by); } for (char y = ty + 1; y < by; y++) { - write_char(131, color, tx, y); - write_char(131, color, bx, y); + write_char(char_line_v, color, tx, y); + write_char(char_line_v, color, bx, y); + } +} + +// Shaded panel +void panel_shaded(char tx, char ty, char bx, char by, char color_high, char color1, char color2) +{ + write_char(char_corner_round_tl, color_high, tx, ty); + write_char(char_corner_round_tr, color1, bx, ty); + write_char(char_corner_round_bl, color1, tx, by); + write_char(char_corner_round_br, color2, bx, by); + for (char x = tx + 1; x < bx; x++) + { + write_char(char_line_h, color1, x, ty); + write_char(char_line_h, color2, x, by); + } + for (char y = ty + 1; y < by; y++) + { + write_char(char_line_v, color1, tx, y); + write_char(char_line_v, color2, bx, y); } } void fill(char tx, char ty, char bx, char by, char c, char color) { - for (char x = tx; x <= bx; x++) + for (char y = ty; y <= by; y++) { - for (char y = ty; y <= by; y++) - { - write_char(c, color, x, y); - } + write_char_row(c, color, tx, y, (bx - tx) + 1); + } +} +void fill_bgcolor(char tx, char ty, char bx, char by, char bgcolor) +{ + for (char y = ty; y <= by; y++) + { + write_bgcol_row(bgcolor, tx, y, (bx - tx) + 1); + } +} +void fill_bgcol(char tx, char ty, char bx, char by, char color) +{ + for (char y = ty; y <= by; y++) + { + write_bgcol_row(color, tx, y, bx - tx); } } @@ -168,65 +306,16 @@ void page_border(char color) panel(0, 0, 39, 29, color); } -char color_pad_outline = 0xFE; - -// Draw game pad outline -void draw_pad(char xo, char yo) +void draw_charactermap() { - // Outline - write_char(134, color_pad_outline, xo, yo + 1); - for (char x = 1; x < 26; x++) + char c = 94; + for (char y = 0; y < 29; y += 2) { - write_char(135, color_pad_outline, xo + x, yo + 1); + for (char x = 0; x < 39; x += 4) + { + write_stringf("%d", 0xFF, x, y, c); + write_char(c, 0, x, y + 1); + c++; + } } - write_char(136, color_pad_outline, xo + 26, yo + 1); - for (char y = 2; y < 5; y++) - { - write_char(137, color_pad_outline, xo, yo + y); - write_char(137, color_pad_outline, xo + 26, yo + y); - } - write_char(139, color_pad_outline, xo, yo + 5); - write_char(138, color_pad_outline, xo + 26, yo + 5); - - write_char(138, color_pad_outline, xo + 8, yo + 5); - write_char(139, color_pad_outline, xo + 18, yo + 5); - write_char(134, color_pad_outline, xo + 8, yo + 4); - write_char(136, color_pad_outline, xo + 18, yo + 4); - for (char x = 1; x < 8; x++) - { - write_char(135, color_pad_outline, xo + x, yo + 5); - } - for (char x = 9; x < 18; x++) - { - write_char(135, color_pad_outline, xo + x, yo + 4); - } - for (char x = 19; x < 26; x++) - { - write_char(135, color_pad_outline, xo + x, yo + 5); - } - // Shoulders - write_char(134, color_pad_outline, xo + 1, yo); - write_char(136, color_pad_outline, xo + 5, yo); - write_char(134, color_pad_outline, xo + 21, yo); - write_char(136, color_pad_outline, xo + 25, yo); -} - -char color_analog_grid = 0x23; - -// Draw game pad outline -void draw_analog(char xo, char yo, char xs, char ys) -{ - panel(xo, yo, xo + xs, yo + ys, 0xFF); - fill(xo + 1, yo + 1, xo + xs - 1, yo + ys - 1, 27, color_analog_grid); - char mx = xo + (xs / 2); - char my = yo + (ys / 2); - for (char x = xo + 1; x < xo + xs; x++) - { - write_char(129, color_analog_grid, x, my); - } - for (char y = yo + 1; y < yo + ys; y++) - { - write_char(131, color_analog_grid, mx, y); - } - write_char('+', color_analog_grid, mx, my); } diff --git a/src/ui_custom.c b/src/ui_custom.c new file mode 100644 index 0000000..d165c80 --- /dev/null +++ b/src/ui_custom.c @@ -0,0 +1,111 @@ +/*============================================================================ + Input Test - Custom UI functions + + Author: Jim Gregory - https://github.com/JimmyStones/ + Version: 1.0 + Date: 2021-07-12 + + 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, see . +===========================================================================*/ +#pragma once +#include + +#define color_pad_outline 0xFE + +void page_frame(bool showMenuButton, bool showContinueButton) +{ + bool footer = showMenuButton || showContinueButton; + clear_chars(0); + write_string("MiSTer Input Tester", 0b11111111, 11, 1); + panel_shaded(0, 0, 39, 2, 0b00000111, 0b00000110, 0b00000100); + panel_shaded(0, 3, 39, footer ? 26 : 29, 0b10100100, 0b10100100, 0b01010010); + if (footer) + { + char buttons = showMenuButton && showContinueButton ? 2 : 1; + char x1 = buttons == 1 ? 39 : 19; + char m1 = buttons == 1 ? 20 : 10; + panel_shaded(0, 27, x1, 29, 0b11000000, 0b10000000, 0b01000000); + if (buttons == 2) + { + panel_shaded(x1 + 1, 27, 39, 29, 0b11000000, 0b10000000, 0b01000000); + } + if (showMenuButton) + { + write_string("Hold Select: Menu", 0b11011011, m1 - 9, 28); + m1 += 20; + } + if (showContinueButton) + { + write_string("Start: Continue", 0b11011011, m1 - 8, 28); + } + } +} + +// Draw game pad outline +void draw_pad(char xo, char yo) +{ + // Outline + write_char(char_corner_round_tl, color_pad_outline, xo, yo + 2); + write_char_row(char_line_h, color_pad_outline, xo + 1, yo + 2, 25); + write_char(char_corner_round_tr, color_pad_outline, xo + 26, yo + 2); + for (char y = 3; y < 8; y++) + { + write_char(char_line_v, color_pad_outline, xo, yo + y); + write_char(char_line_v, color_pad_outline, xo + 26, yo + y); + } + write_char(char_corner_round_bl, color_pad_outline, xo, yo + 8); + write_char(char_corner_round_br, color_pad_outline, xo + 26, yo + 8); + + write_char(char_corner_round_br, color_pad_outline, xo + 8, yo + 8); + write_char(char_corner_round_bl, color_pad_outline, xo + 18, yo + 8); + write_char(char_corner_round_tl, color_pad_outline, xo + 8, yo + 7); + write_char(char_corner_round_tr, color_pad_outline, xo + 18, yo + 7); + + write_char_row(char_line_h, color_pad_outline, xo + 1, yo + 8, 7); + write_char_row(char_line_h, color_pad_outline, xo + 9, yo + 7, 9); + write_char_row(char_line_h, color_pad_outline, xo + 19, yo + 8, 7); + + // Shoulders + write_char(char_line_v, color_pad_outline, xo + 1, yo + 1); + write_char(char_t_up, color_pad_outline, xo + 1, yo + 2); + write_char(char_corner_round_tl, color_pad_outline, xo + 1, yo); + write_char_row(char_line_h, color_pad_outline, xo + 2, yo, 3); + write_char(char_line_v, color_pad_outline, xo + 5, yo + 1); + write_char(char_t_up, color_pad_outline, xo + 5, yo + 2); + write_char(char_corner_round_tr, color_pad_outline, xo + 5, yo); + write_char(char_line_v, color_pad_outline, xo + 21, yo + 1); + write_char(char_t_up, color_pad_outline, xo + 21, yo + 2); + write_char(char_corner_round_tl, color_pad_outline, xo + 21, yo); + write_char_row(char_line_h, color_pad_outline, xo + 22, yo, 3); + write_char(char_line_v, color_pad_outline, xo + 25, yo + 1); + write_char(char_t_up, color_pad_outline, xo + 25, yo + 2); + write_char(char_corner_round_tr, color_pad_outline, xo + 25, yo); +} + +#define color_analog_grid 0x23 + +// Draw game pad outline +void draw_analog(char xo, char yo, char xs, char ys) +{ + panel(xo, yo, xo + xs, yo + ys, 0xFF); + fill(xo + 1, yo + 1, xo + xs - 1, yo + ys - 1, char_dot, color_analog_grid); + char mx = xo + (xs / 2); + char my = yo + (ys / 2); + write_char_row(char_line_h, color_analog_grid, xo + 1, my, xs - 1); + for (char y = yo + 1; y < yo + ys; y++) + { + write_char(char_line_v, color_analog_grid, mx, y); + } + write_char(char_cross, color_analog_grid, mx, my); +} diff --git a/verilator/sim.v b/verilator/sim.v index e3bfd11..9f5568d 100644 --- a/verilator/sim.v +++ b/verilator/sim.v @@ -1,10 +1,10 @@ `timescale 1ns / 1ps /*============================================================================ - MiSTer test harness - Verilator emu module + Input Test - Verilator emu module Author: Jim Gregory - https://github.com/JimmyStones/ - Version: 0.1 - Date: 2021-06-29 + Version: 1.0 + Date: 2021-07-12 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 @@ -61,6 +61,9 @@ module emu ( input [24:0] ps2_mouse, input [15:0] ps2_mouse_ext, // 15:8 - reserved(additional buttons), 7:0 - wheel movements + // [31:0] - seconds since 1970-01-01 00:00:00, [32] - toggle with every change + input [32:0] timestamp, + output [7:0] VGA_R, output [7:0] VGA_G, output [7:0] VGA_B, @@ -84,8 +87,8 @@ wire ce_pix; jtframe_cen24 divider ( .clk(clk_sys), - //.cen12(ce_pix), // <-- dodgy video speed for faster simulation, will cause bearable char map corruption - .cen4(ce_pix) // <-- correct video speed + .cen12(ce_pix), // <-- dodgy video speed for faster simulation, will cause bearable char map corruption + //.cen4(ce_pix) // <-- correct video speed ); /* verilator lint_on PINMISSING */ @@ -110,7 +113,8 @@ system system( .paddle({paddle_5,paddle_4,paddle_3,paddle_2,paddle_1,paddle_0}), .spinner({7'b0,spinner_5,7'b0,spinner_4,7'b0,spinner_3,7'b0,spinner_2,7'b0,spinner_1,7'b0,spinner_0}), .ps2_key(ps2_key), - .ps2_mouse({ps2_mouse_ext,7'b0,ps2_mouse}) + .ps2_mouse({ps2_mouse_ext,7'b0,ps2_mouse}), + .timestamp(timestamp) ); endmodule diff --git a/verilator/sim_main.cpp b/verilator/sim_main.cpp index 4c6d35a..1e42f08 100644 --- a/verilator/sim_main.cpp +++ b/verilator/sim_main.cpp @@ -10,6 +10,7 @@ #define WIN32 #include #endif +#include #include "sim_console.h" #include "sim_bus.h" @@ -45,7 +46,7 @@ const int input_b = 5; const int input_x = 6; const int input_y = 7; const int input_l = 8; -const int input_r= 9; +const int input_r = 9; const int input_select = 10; const int input_start = 11; @@ -69,15 +70,30 @@ int multi_step_amount = 1024; // -------------- Vemu* top = NULL; -vluint64_t main_time = 0; // Current simulation time. -double sc_time_stamp() { // Called by $time in Verilog. +vluint64_t main_time = 0; // Current simulation time. +vluint32_t timestamp = 0; // Simulated Unix timestamp. +vluint16_t timestamp_ticksperms = 24000; // Number of simulation ticks per simulated millisecond +vluint16_t timestamp_ticks = 0; // Ticks left until next ms +unsigned short timestamp_updatefreq = 5000; // Only update sim every 5 seconds +unsigned short timestamp_update = 0; // Ms to next update. +bool timestamp_clock = 1; // Timestamp update clock + +double sc_time_stamp() { // Called by $time in Verilog. return main_time; } SimClock clk_sys(1); + + + +long GetTime() { + return std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); +} + void resetSim() { main_time = 0; + timestamp = GetTime(); top->reset = 1; clk_sys.Reset(); } @@ -94,6 +110,24 @@ int verilate() { // Clock dividers clk_sys.Tick(); + // Increment timestamp + timestamp_ticks ++; + if (timestamp_ticks >= timestamp_ticksperms) { + timestamp_ticks -= timestamp_ticksperms; + timestamp++; + + top->timestamp = timestamp; + timestamp_clock = !timestamp_clock; + + if (timestamp_clock) { + timestamp_update--; + if (timestamp_update <= 0) { + top->timestamp |= ((uint64_t)1) << 32; + timestamp_update = timestamp_updatefreq; + } + } + } + // Set system clock in core top->clk_sys = clk_sys.clk; @@ -167,7 +201,7 @@ int main(int argc, char** argv, char** env) { input.SetMapping(input_r, DIK_W); // R input.SetMapping(input_select, DIK_1); // Select input.SetMapping(input_start, DIK_2); // Start - + #else input.SetMapping(input_up, SDL_SCANCODE_UP); input.SetMapping(input_right, SDL_SCANCODE_RIGHT); @@ -185,6 +219,9 @@ int main(int argc, char** argv, char** env) { // Setup video output if (video.Initialise(windowTitle) == 1) { return 1; } + // Initial reset + resetSim(); + #ifdef WIN32 MSG msg; ZeroMemory(&msg, sizeof(msg)); @@ -243,6 +280,7 @@ int main(int argc, char** argv, char** env) { ImGui::Checkbox("Flip V", &video.output_vflip); ImGui::Text("main_time: %d frame_count: %d sim FPS: %f", main_time, video.count_frame, video.stats_fps); + ImGui::Text("timestamp: %d actual ms: %d frame_ms: %d ", timestamp, timestamp/1000, video.count_frame * 1000); ImGui::Text("minx: %d maxx: %d miny: %d maxy: %d", video.stats_xMin, video.stats_xMax, video.stats_yMin, video.stats_yMax); // Draw VGA output @@ -328,7 +366,8 @@ int main(int argc, char** argv, char** env) { for (char b = 8; b < 16; b++) { top->spinner_0 &= ~(1UL << b); } - if (spinner_toggle) { top->spinner_0 |= 1UL << 8; } } + if (spinner_toggle) { top->spinner_0 |= 1UL << 8; } + } //top->spinner_1 -= 1; //top->spinner_2 += 1; //top->spinner_3 -= 1; diff --git a/verilator/verilate.sh b/verilator/verilate.sh index 7e1b3dd..4ab75f3 100755 --- a/verilator/verilate.sh +++ b/verilator/verilate.sh @@ -1,25 +1,16 @@ set -e if grep -qEi "(Microsoft|WSL)" /proc/version &> /dev/null ; then -$VERILATOR_ROOT/bin/verilator \ --cc \ --exe \ ---public \ +verilator \ +--cc \ --compiler msvc +define+SIMULATION=1 \ -O3 --x-assign fast --x-initial fast --noassert \ +-Wno-TIMESCALEMOD \ --converge-limit 6000 \ --top-module emu sim.v \ -../rtl/dpram.v \ -../rtl/spram.v \ -../rtl/JTFRAME/jtframe_vtimer.v \ -../rtl/JTFRAME/jtframe_cen24.v \ -../rtl/system.v \ -../rtl/tv80/tv80_core.v \ -../rtl/tv80/tv80_alu.v \ -../rtl/tv80/tv80_mcode.v \ -../rtl/tv80/tv80_reg.v \ -../rtl/tv80/tv80n.v \ -../rtl/tv80/tv80s.v +-I../rtl \ +-I../rtl/JTFRAME \ +-I../rtl/tv80 else echo "not running on windows" fi