Skip to content

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.