Useful Snippets
Here are some useful snippets of hdl as used throughout the MiSTer project with sources linked for reference.
Simple Pixel Clock Divider Using Clock Enable Generator
In any MiSTer core you develop you will need to attach a pixel clock to the CE_PIXEL
signal. It's common to divide your video clock by either 4 or 8, it really depends on the core. For simple division like this it is generally best practice to use a clock enable generator, as shown in the Tropical Angel core.
reg ce_pix;
always @(posedge clk_vid) begin // Divide video clock by 8
reg [2:0] div;
div <= div + 1'd1;
ce_pix <= !div;
end
assign CE_PIXEL = ce_pix;
Adjust this simple clock enable generator's div
register width to increase the division factor. [2:0]
divides by 8 because 3 bits counts from 0-7, etc...
Fractional Clock Enable Generator
For clock division generators that do not divide evenly in 2, 4, 8, 16, etc... You may need a more complicated scheme. You could use a module like CEGen.vhd from the MegaDrive core and then you would instantiate it using the parameters in a similar way:
wire ce_flt;
CEGen fltce
(
.CLK(clk),
.RST_N(~reset),
.IN_CLK(53693175),
.OUT_CLK(7056000),
.CE(ce_flt)
);
What this essentially does is take in a 53.693175MHz clock and output a clock enable register that is oscillating at approximately 7.056MHz, on average. Since 7.056MHz doesn't divide evenly into the input clock's frequency, we had to use a fractional clock division which is usually not suggested. Be careful when doing this because it is not a 50% duty cycle, so it should only be used in instances where you know it won't give you a bad result. In this case it was used for audio filtering, so it was not an issue.
Reading Stream Of A ROM
You can read the stream of data from the ROM you load and have conditionals trigger as a result. Here's a snippet from the SMS core that was done to switch behavior when a specific version of a ROM is detected:
reg ysj_quirk = 0;
always @(posedge clk_sys) begin
reg [31:0] cart_id;
reg old_download;
old_download <= cart_download;
if(~old_download && cart_download) {ysj_quirk} <= 0;
if(ioctl_wr & cart_download) begin
if(ioctl_addr == 'h7ffc) cart_id[31:24] <= ioctl_dout[7:0];
if(ioctl_addr == 'h7ffd) cart_id[23:16] <= ioctl_dout[7:0];
if(ioctl_addr == 'h7ffe) cart_id[15:08] <= ioctl_dout[7:0];
if(ioctl_addr == 'h7fff) cart_id[07:00] <= ioctl_dout[7:0];
if(ioctl_addr == 'h8000) begin
if(cart_id == 32'h13_70_01_4F) ysj_quirk <= 1; // Ys (Japan) Graphics Fix, forces VDP Version 1
end
end
end
As the ROM is loaded, when it gets to the certain byte offset (the check for a certain ioctl_addr
), it will then start loading in the data into cart_id
one byte at a time. When it gets past the point where I don't want to detect anymore, it then checks to see if cart_id
matches the hex value I'm looking for. If your core needs special quirks for specific mappers and there are no typical detection schemes (like NES mappers that are added to ROMs), this can be a good way to adjust behavior to match how original hardware would behave given other unique identifiers within the ROM.
Analog Joystick Combinatorial Block with Deadzones
Analog Joysticks like on the Sony DualSense controller are supported on MiSTer. If you have a specific control scheme you want to handle with Analog joysticks, you can specify this like the Inferno core.
hps_io #(.CONF_STR(CONF_STR)) hps_io
(
//skip
.joystick_l_analog_0(joystick_l_analog_0),
.joystick_l_analog_1(joystick_l_analog_1),
.joystick_r_analog_0(joystick_r_analog_0),
.joystick_r_analog_1(joystick_r_analog_1)
);
//skip
logic [3:0] joyal_1, joyal_2, joyar_1, joyar_2;
always_comb begin
joyal_1[3] = ($signed(joystick_l_analog_0[15:8]) < -20); // Up
joyal_1[2] = ($signed(joystick_l_analog_0[15:8]) > 20); // Down
joyal_1[1] = ($signed(joystick_l_analog_0[ 7:0]) < -20); // Left
joyal_1[0] = ($signed(joystick_l_analog_0[ 7:0]) > 20); // Right
joyar_1[3] = ($signed(joystick_r_analog_0[15:8]) < -20);
joyar_1[2] = ($signed(joystick_r_analog_0[15:8]) > 20);
joyar_1[1] = ($signed(joystick_r_analog_0[ 7:0]) < -20);
joyar_1[0] = ($signed(joystick_r_analog_0[ 7:0]) > 20);
joyal_2[3] = ($signed(joystick_l_analog_1[15:8]) < -20);
joyal_2[2] = ($signed(joystick_l_analog_1[15:8]) > 20);
joyal_2[1] = ($signed(joystick_l_analog_1[ 7:0]) < -20);
joyal_2[0] = ($signed(joystick_l_analog_1[ 7:0]) > 20);
joyar_2[3] = ($signed(joystick_r_analog_1[15:8]) < -20);
joyar_2[2] = ($signed(joystick_r_analog_1[15:8]) > 20);
joyar_2[1] = ($signed(joystick_r_analog_1[ 7:0]) < -20);
joyar_2[0] = ($signed(joystick_r_analog_1[ 7:0]) > 20);
end
If the dead zone is incorrect to your feel, you can adjust the + or - integer value at the end of each.