This guide provides implementation details for adding second mouse support to the ao486 MiSTer core. Unlike the Amiga which used two identical mouse ports, DOS PCs typically used PS/2 for the primary mouse and a serial (COM) port for the secondary mouse. This was the configuration used by games like The Settlers and The Settlers II.
Based on the research:
We'll implement dual mouse support by:
// serial_mouse.v - Serial mouse emulator for ao486
module serial_mouse (
input clk,
input reset,
// Mouse input from HPS
input [15:0] mouse_x,
input [15:0] mouse_y,
input [7:0] mouse_buttons,
input mouse_strobe,
// Configuration
input [1:0] protocol, // 0=Microsoft, 1=Mouse Systems, 2=Auto
input [2:0] baud_select, // 0=1200, 1=2400, 2=4800, 3=9600
input enabled,
// Serial port signals
output txd,
input rxd,
output rts,
input cts,
output dtr,
input dsr
);
// Baud rate generator
reg [15:0] baud_counter;
reg baud_tick;
wire [15:0] baud_divisor;
// Baud rate selection
assign baud_divisor = (baud_select == 3'd0) ? 16'd2500 : // 1200 baud @ 30MHz
(baud_select == 3'd1) ? 16'd1250 : // 2400 baud
(baud_select == 3'd2) ? 16'd625 : // 4800 baud
16'd313; // 9600 baud
always @(posedge clk) begin
if (reset) begin
baud_counter <= 16'd0;
baud_tick <= 1'b0;
end else begin
if (baud_counter >= baud_divisor) begin
baud_counter <= 16'd0;
baud_tick <= 1'b1;
end else begin
baud_counter <= baud_counter + 16'd1;
baud_tick <= 1'b0;
end
end
end
// Mouse state tracking
reg [15:0] last_x, last_y;
reg [7:0] last_buttons;
reg strobe_d;
reg signed [7:0] delta_x, delta_y;
reg data_ready;
// Detect mouse movement
always @(posedge clk) begin
if (reset) begin
last_x <= 16'd0;
last_y <= 16'd0;
last_buttons <= 8'hFF;
strobe_d <= 1'b0;
delta_x <= 8'd0;
delta_y <= 8'd0;
data_ready <= 1'b0;
end else begin
strobe_d <= mouse_strobe;
if (mouse_strobe != strobe_d && enabled) begin
// Calculate deltas (clamp to -128 to +127)
delta_x <= (mouse_x - last_x > 127) ? 8'd127 :
(mouse_x - last_x < -128) ? -8'd128 :
mouse_x - last_x;
delta_y <= (mouse_y - last_y > 127) ? 8'd127 :
(mouse_y - last_y < -128) ? -8'd128 :
mouse_y - last_y;
last_x <= mouse_x;
last_y <= mouse_y;
last_buttons <= mouse_buttons;
// Signal new data available
if ((mouse_x != last_x) || (mouse_y != last_y) ||
(mouse_buttons != last_buttons)) begin
data_ready <= 1'b1;
end
end
end
end
// Microsoft Mouse Protocol State Machine
// Format: 1LRY5Y4Y3Y2 1X5X4X3X2 0Y1Y0X1X0 (3 bytes)
// L=Left button, R=Right button
reg [2:0] ms_state;
reg [7:0] ms_packet[0:2];
reg ms_sending;
always @(posedge clk) begin
if (reset) begin
ms_state <= 3'd0;
ms_sending <= 1'b0;
end else if (protocol == 2'd0 && data_ready && !ms_sending) begin
// Prepare Microsoft protocol packet
ms_packet[0] <= {2'b01, // Sync bits
~last_buttons[0], // Left button (active low)
~last_buttons[1], // Right button
delta_y[5:2]}; // Y movement high bits
ms_packet[1] <= {2'b00, // Sync bits
delta_x[5:0]}; // X movement
ms_packet[2] <= {2'b00, // Sync bits
delta_y[1:0], // Y movement low bits
delta_x[1:0]}; // X movement low bits (unused)
ms_sending <= 1'b1;
data_ready <= 1'b0;
end
end
// UART Transmitter
reg [3:0] tx_state;
reg [7:0] tx_byte;
reg [2:0] tx_bit;
reg tx_busy;
assign txd = (tx_state == 4'd0) ? 1'b1 : // Idle
(tx_state == 4'd1) ? 1'b0 : // Start bit
(tx_state >= 4'd2 && tx_state <= 4'd9) ? tx_byte[tx_bit] : // Data bits
1'b1; // Stop bit
always @(posedge clk) begin
if (reset) begin
tx_state <= 4'd0;
tx_busy <= 1'b0;
ms_state <= 3'd0;
end else if (baud_tick) begin
case (tx_state)
4'd0: begin // Idle
if (ms_sending && ms_state < 3'd3) begin
tx_byte <= ms_packet[ms_state];
tx_state <= 4'd1;
tx_busy <= 1'b1;
tx_bit <= 3'd0;
end
end
4'd1: begin // Start bit
tx_state <= 4'd2;
end
4'd2, 4'd3, 4'd4, 4'd5,
4'd6, 4'd7, 4'd8, 4'd9: begin // Data bits
tx_bit <= tx_bit + 3'd1;
tx_state <= tx_state + 4'd1;
end
4'd10: begin // Stop bit
tx_state <= 4'd0;
tx_busy <= 1'b0;
ms_state <= ms_state + 3'd1;
if (ms_state >= 3'd2) begin
ms_sending <= 1'b0;
ms_state <= 3'd0;
end
end
endcase
end
end
// Control signals
assign rts = 1'b1; // Always ready to send
assign dtr = 1'b1; // Always ready
endmoduleModify the ao486 SoC to include the serial mouse emulator:
// In ao486.sv - Add serial mouse instance
// Second mouse signals from HPS
wire [15:0] mouse1_x, mouse1_y;
wire [7:0] mouse1_buttons;
wire mouse1_strobe;
// Serial mouse configuration
reg [1:0] serial_mouse_protocol = 2'd0; // Microsoft protocol
reg [2:0] serial_mouse_baud = 3'd0; // 1200 baud
reg serial_mouse_enabled = 1'b0;
// COM2 signals (internal)
wire com2_txd_mouse;
wire com2_rxd;
wire com2_rts_mouse;
wire com2_cts;
wire com2_dtr_mouse;
wire com2_dsr;
// Instantiate serial mouse emulator
serial_mouse serial_mouse_inst (
.clk(clk_sys),
.reset(reset),
// Mouse input
.mouse_x(mouse1_x),
.mouse_y(mouse1_y),
.mouse_buttons(mouse1_buttons),
.mouse_strobe(mouse1_strobe),
// Configuration
.protocol(serial_mouse_protocol),
.baud_select(serial_mouse_baud),
.enabled(serial_mouse_enabled),
// Serial port
.txd(com2_txd_mouse),
.rxd(com2_rxd),
.rts(com2_rts_mouse),
.cts(com2_cts),
.dtr(com2_dtr_mouse),
.dsr(com2_dsr)
);
// Mux COM2 between UART and serial mouse
wire com2_txd = serial_mouse_enabled ? com2_txd_mouse : uart_txd;
wire com2_rts = serial_mouse_enabled ? com2_rts_mouse : uart_rts;
wire com2_dtr = serial_mouse_enabled ? com2_dtr_mouse : uart_dtr;
// Add to OSD configuration
localparam CONF_STR = {
// ... existing config ...
"O[35],Serial Mouse,Disabled,Enabled;",
"O[37:36],Serial Mouse Protocol,Microsoft,Mouse Systems,Auto;",
"O[40:38],Serial Mouse Baud,1200,2400,4800,9600;",
// ... rest of config ...
};
// Process configuration
always @(posedge clk_sys) begin
serial_mouse_enabled <= status[35];
serial_mouse_protocol <= status[37:36];
serial_mouse_baud <= status[40:38];
endFor DOS to recognize both mice, you need appropriate drivers:
REM AUTOEXEC.BAT - Load both mouse drivers
REM Load PS/2 mouse driver first
C:\MOUSE\CTMOUSE.EXE /P
REM Load serial mouse driver on COM2
C:\MOUSE\SERMOUSE.EXE COM2; SETTLERS.CFG
[MOUSE]
PLAYER1=PS2
PLAYER2=COM2The game auto-detects serial mice on COM ports during startup.
This is shared with the Minimig implementation but adapted for ao486:
// In hps_io.v - Add second mouse support
module hps_io_ao486 #(parameter STRLEN=0, PS2DIV=2000)
(
input clk_sys,
inout [48:0] HPS_BUS,
// Primary PS/2 mouse (existing)
output reg [2:0] ps2_mouse,
output reg [24:0] ps2_mouse_data,
// Secondary mouse for serial emulation
output reg [15:0] mouse1_x,
output reg [15:0] mouse1_y,
output reg [7:0] mouse1_buttons,
output reg mouse1_strobe,
// ... other signals ...
);
// Process extended mouse command
always @(posedge clk_sys) begin
if (cmd == UIO_MOUSE_EXT && data_cnt == 6) begin
if (data_buf[7:0] == 8'd1) begin // Second mouse
mouse1_x <= {data_buf[23:16], data_buf[15:8]};
mouse1_y <= {data_buf[39:32], data_buf[31:24]};
mouse1_buttons <= data_buf[47:40];
mouse1_strobe <= ~mouse1_strobe;
end
data_cnt <= 0;
end
end
endmoduleCreate a test program to verify both mice are working:
// DUALMOUSE.C - Test dual mouse support
#include <dos.h>
#include <stdio.h>
#include <conio.h>
// Mouse interrupt functions
#define MOUSE_INT 0x33
#define RESET_MOUSE 0x00
#define SHOW_CURSOR 0x01
#define GET_STATUS 0x03
// Serial mouse functions
#define COM2_BASE 0x2F8
typedef struct {
int x, y;
int buttons;
int present;
} MouseInfo;
MouseInfo ps2_mouse = {0};
MouseInfo ser_mouse = {0};
// Check for PS/2 mouse
int check_ps2_mouse() {
union REGS regs;
regs.x.ax = RESET_MOUSE;
int86(MOUSE_INT, ®s, ®s);
return (regs.x.ax == 0xFFFF);
}
// Initialize serial mouse on COM2
int init_serial_mouse() {
// Set COM2 to 1200,N,8,1
outportb(COM2_BASE + 3, 0x80); // Enable DLAB
outportb(COM2_BASE + 0, 0x60); // Divisor low (1200 baud)
outportb(COM2_BASE + 1, 0x00); // Divisor high
outportb(COM2_BASE + 3, 0x03); // 8N1, disable DLAB
outportb(COM2_BASE + 4, 0x0B); // DTR, RTS, OUT2
// Check if mouse responds
delay(100);
if (inportb(COM2_BASE + 5) & 0x01) {
return 1; // Data available = mouse present
}
return 0;
}
// Read PS/2 mouse
void read_ps2_mouse() {
union REGS regs;
regs.x.ax = GET_STATUS;
int86(MOUSE_INT, ®s, ®s);
ps2_mouse.x = regs.x.cx;
ps2_mouse.y = regs.x.dx;
ps2_mouse.buttons = regs.x.bx;
}
// Read serial mouse (Microsoft protocol)
void read_serial_mouse() {
static unsigned char buffer[3];
static int byte_count = 0;
while (inportb(COM2_BASE + 5) & 0x01) { // Data available
unsigned char byte = inportb(COM2_BASE);
// Sync on first byte (bit 6 set)
if (byte_count == 0 && !(byte & 0x40)) continue;
buffer[byte_count++] = byte;
if (byte_count == 3) {
// Decode Microsoft mouse packet
ser_mouse.buttons = ((buffer[0] >> 5) & 0x03) ^ 0x03;
ser_mouse.x += (char)((buffer[0] << 6) | (buffer[1] & 0x3F));
ser_mouse.y += (char)((buffer[0] << 4) | (buffer[2] & 0x3F));
byte_count = 0;
}
}
}
int main() {
clrscr();
printf("Dual Mouse Test for ao486\n");
printf("=========================\n\n");
// Check PS/2 mouse
ps2_mouse.present = check_ps2_mouse();
printf("PS/2 Mouse: %s\n", ps2_mouse.present ? "Found" : "Not found");
// Check serial mouse
ser_mouse.present = init_serial_mouse();
printf("Serial Mouse on COM2: %s\n", ser_mouse.present ? "Found" : "Not found");
if (!ps2_mouse.present && !ser_mouse.present) {
printf("\nNo mice detected!\n");
return 1;
}
printf("\nPress ESC to exit\n\n");
// Show mouse cursor
if (ps2_mouse.present) {
union REGS regs;
regs.x.ax = SHOW_CURSOR;
int86(MOUSE_INT, ®s, ®s);
}
// Main loop
while (!kbhit() || getch() != 27) {
if (ps2_mouse.present) read_ps2_mouse();
if (ser_mouse.present) read_serial_mouse();
gotoxy(1, 10);
printf("PS/2 Mouse - X:%4d Y:%4d Buttons:%02X\n",
ps2_mouse.x, ps2_mouse.y, ps2_mouse.buttons);
printf("Serial Mouse - X:%4d Y:%4d Buttons:%02X\n",
ser_mouse.x, ser_mouse.y, ser_mouse.buttons);
}
return 0;
}REM Compile with Turbo C
TCC -mc DUALMOUSE.C
REM Run the test
DUALMOUSE.EXEAn alternative approach is to use the existing UART support with a USB-to-Serial adapter:
REM In MiSTer OSD
UART: MidiLink/Remote/Serial
REM In DOS
MODE COM2:1200,N,8,1The Main_MiSTer changes are shared with the Minimig implementation (see the Minimig guide), but we need to ensure the ao486 core is included in the multi-mouse support:
// In input_mouse.cpp - Add ao486 core detection
bool is_ao486_core() {
char corename[64];
user_io_get_core_name(corename, sizeof(corename));
return (strcasecmp(corename, "ao486") == 0);
}
// In mouse event handler
void handle_mouse_events() {
// ... existing code ...
if (is_minimig_core() || is_ao486_core()) {
// Enable multi-mouse support for these cores
send_extended_mouse_data();
} else {
// Use legacy single mouse for other cores
send_legacy_mouse_data();
}
}The ao486 core has known timing sensitivities. The serial mouse emulation must:
// Adjust serial timing based on CPU speed
wire [15:0] baud_adjust = (cpu_speed == 2'd0) ? 16'd1 : // 15MHz
(cpu_speed == 2'd1) ? 16'd2 : // 33MHz
(cpu_speed == 2'd2) ? 16'd3 : // 56MHz
16'd3; // 90MHz
wire [15:0] adjusted_divisor = baud_divisor * baud_adjust;This implementation provides dual mouse support for the ao486 core, enabling games like The Settlers to use two mice simultaneously. The approach:
Key differences from Minimig implementation:
The modular design allows for future enhancements like: