diff --git a/cores/arbalest/cfg/files.yaml b/cores/arbalest/cfg/files.yaml
new file mode 100644
index 000000000..5b68969ab
--- /dev/null
+++ b/cores/arbalest/cfg/files.yaml
@@ -0,0 +1,35 @@
+arbalest:
+ - get: [ "*.v" ]
+cal50:
+ - get:
+ - jtcal50_video.v
+ - jtcal50_colmix.v
+ - jtx1012.v
+kiwi:
+ - get:
+ - jtkiwi_gfx.v
+ - jtkiwi_obj.v
+ - jtkiwi_draw.v
+ - jtkiwi_tilemap.v
+jtframe:
+ - get:
+ - jtframe_ff.v
+ - jtframe_8bit_reg.v
+ - jtframe_edge.v
+ - from: ram
+ get:
+ - jtframe_ram.v
+ - jtframe_dual_ram16.v
+ - jtframe_obj_buffer.v
+ - jtframe_prom.v
+ - from: video
+ get:
+ - jtframe_vtimer.v
+ - jtframe_scroll.yaml
+ - from: cpu
+ get:
+ - jtframe_m68k.yaml
+jt680x:
+ - get:
+ - jt65c02.yaml # I/O / protection sub-CPU (not sound, unlike cal50)
+jtx1010: # X1-010 PCM — driven by the main 68000 here
diff --git a/cores/arbalest/cfg/macros.def b/cores/arbalest/cfg/macros.def
new file mode 100644
index 000000000..1a5f230b0
--- /dev/null
+++ b/cores/arbalest/cfg/macros.def
@@ -0,0 +1,33 @@
+CORENAME=JTARBALEST
+JTFRAME_RATE=57.45
+
+JTFRAME_JOY_RLDU
+JTFRAME_BUTTONS=2
+
+# Video
+JTFRAME_COLORW=5
+JTFRAME_PXLCLK=8
+JTFRAME_WIDTH=384
+JTFRAME_HEIGHT=224
+JTFRAME_VERTICAL
+
+# MRA
+# Header byte 0 selects the game (0=metafox, 1=arbalest) for the shared bitstream
+JTFRAME_HEADER=16
+JTFRAME_BA1_START=0xA0000
+PCM_START=0xE0000
+JTFRAME_BA2_START=0x1E0000
+JTFRAME_BA3_START=0x3E0000
+
+# Saves video RAM for debug compilations
+debug JTFRAME_IOCTL_RD+=12288
+debug JTFRAME_IOCTL_RD+=8
+# X1-001 configuration registers
+debug JTFRAME_IOCTL_RD+=4
+debug JTFRAME_IOCTL_RD+=1024
+debug JTFRAME_IOCTL_RD+=1024
+debug JTFRAME_IOCTL_RD+=8192
+
+[mist|sidi]
+# not enough BRAM for M68K RAM
+JTFRAME_SKIP
diff --git a/cores/arbalest/cfg/mame2mra.toml b/cores/arbalest/cfg/mame2mra.toml
new file mode 100644
index 000000000..6681550a2
--- /dev/null
+++ b/cores/arbalest/cfg/mame2mra.toml
@@ -0,0 +1,46 @@
+[global]
+author=["jotego"]
+
+[parse]
+sourcefile=[ "downtown.cpp" ]
+# This core ships the metafox-class members of downtown.cpp (sub = I/O CPU,
+# X1-010 on the main 68000 bus). calibr50 is the cal50 core; the others differ.
+skip.Machines=[
+ "tndrcade","tndrcadej",
+ "twineagl","twineagl1",
+ "downtown","downtown2","downtownj","downtownp",
+ "usclssic",
+ "calibr50",
+]
+
+[header]
+info="""Seta downtown.cpp (metafox-class) family header, decoded in jtarbalest_game.v.
+ byte 0 = game_id (0=metafox, 1=arbalest). Bytes 1..15 reserved."""
+data=[
+ { setnames=["metafox"], data="00" },
+ { setnames=["arbalest"], data="01" },
+]
+
+[buttons]
+names = [
+ { names="Shot,Bomb" },
+]
+
+[dipsw]
+delete= [
+ { names=["Copyright / License", "Licensed To", "Licensor Option", "Coinage Type"] },
+]
+
+[ROM]
+regions = [
+ { name="maincpu", width=16, reverse=true, no_offset=true },
+
+ { name="sub", start="JTFRAME_BA1_START", no_offset=true },
+ { name="x1snd", start="PCM_START" },
+
+ { name="tiles", width=16, start="JTFRAME_BA2_START", no_offset=true },
+ { name="sprites", width=32, start="JTFRAME_BA3_START", no_offset=true },
+ { name="proms", skip=true }
+]
+
+order=["maincpu","sub","x1snd","tiles","sprites"]
diff --git a/cores/arbalest/cfg/mem.yaml b/cores/arbalest/cfg/mem.yaml
new file mode 100644
index 000000000..b96932c6f
--- /dev/null
+++ b/cores/arbalest/cfg/mem.yaml
@@ -0,0 +1,116 @@
+params:
+ - { name: PCM_OFFSET, value: "(`PCM_START-`JTFRAME_BA1_START)>>1" }
+clocks:
+ clk48:
+ - freq: 8 MHz
+ gate: [ snd ]
+ outputs:
+ - cen8
+ - freq: 16 MHz
+ outputs:
+ - cen_pcm
+audio:
+ mute: true
+ channels:
+ # Same dual 4k/8k anti-alias filtering as cal50 (X1-010 left/right mono mix)
+ - { name: pcm_4k, data_width: 16, rsum: 47k, fir: fir_192k_4k.csv }
+ - { name: pcm_8k, data_width: 16, rsum: 47k, fir: fir_192k_8k.csv }
+sdram:
+ banks:
+ - buses:
+ - name: main
+ addr_width: 20
+ data_width: 16
+ - buses:
+ - name: snd # 65C02 I/O sub-CPU program ROM (region "sub")
+ addr_width: 18
+ data_width: 8
+ - name: pcm # X1-010 samples (region "x1snd")
+ addr_width: 20
+ data_width: 8
+ offset: PCM_OFFSET
+ - buses:
+ - name: tile
+ addr_width: 21
+ data_width: 32
+ gfx_sort: hvvvx
+ - buses:
+ - name: scr
+ addr_width: 21
+ data_width: 32
+ - name: obj
+ addr_width: 21
+ data_width: 32
+bram:
+ - name: ram # M68K work RAM (0xf00000-)
+ addr_width: 16
+ data_width: 16
+ din: cpu_dout
+ rw: true
+ # Shared RAM between the main 68000 (0xb00000, byte) and the I/O 65C02 (0x5000)
+ - name: shram
+ addr_width: 11
+ data_width: 8
+ din: cpu_dout[7:0]
+ addr: main_addr[11:1]
+ rw: true
+ dual_port:
+ name: subsh
+ din: subsh_din
+ dout: subsh_dout
+ rw: true
+ we: subsh_we
+ # X1-012 Video RAM
+ - name: tlv
+ addr_width: 14
+ data_width: 16
+ din: cpu_dout
+ addr: main_addr[13:1]
+ rw: true
+ ioctl:
+ save: true
+ order: 0
+ unless: [ JTFRAME_RELEASE ]
+ dual_port:
+ name: tlrd
+ dout: tlrd_data
+ # Palette RAM
+ - name: pal
+ addr_width: 10
+ data_width: 16
+ din: cpu_dout
+ addr: main_addr[9:1]
+ rw: true
+ ioctl:
+ save: true
+ order: 1
+ unless: [ JTFRAME_RELEASE ]
+ dual_port:
+ name: palrd
+ dout: pal_data
+ # X1-001 internal RAM
+ - name: yram
+ addr_width: 10
+ data_width: 8
+ din: cpu_dout[7:0]
+ addr: main_addr[10:1]
+ rw: true
+ ioctl:
+ save: true
+ order: 2
+ unless: [ JTFRAME_RELEASE ]
+ dual_port:
+ name: col
+ dout: col_data
+ # X1-001 external video RAM. metafox is 16 KB (0xe00000-0xe03fff): two 8 KB sprite buffers
+ - name: dma
+ addr_width: 14
+ data_width: 16
+ rw: true
+ ioctl:
+ save: true
+ order: 3
+ unless: [ JTFRAME_RELEASE ]
+ dual_port:
+ name: code
+ dout: code_dout
diff --git a/cores/arbalest/cfg/mmr.yaml b/cores/arbalest/cfg/mmr.yaml
new file mode 100644
index 000000000..b23d8867c
--- /dev/null
+++ b/cores/arbalest/cfg/mmr.yaml
@@ -0,0 +1,15 @@
+- name: x1012
+ no_core_name: true
+ size: 8
+ dw: 16
+ regs:
+ - name: hpos
+ dw: 16
+ at: "1,0"
+ - name: vpos
+ dw: 16
+ at: "3,2"
+ # vctrl[2]: bit3 (0x08)=tilemap bank (vram 0 / 0x1000), bit4 (0x10)=color/gfx mode (MAME x1_012.cpp)
+ - name: bank
+ dw: 1
+ at: "4[3]"
diff --git a/cores/arbalest/cfg/msg b/cores/arbalest/cfg/msg
new file mode 100644
index 000000000..c5f8e0bcf
--- /dev/null
+++ b/cores/arbalest/cfg/msg
@@ -0,0 +1,7 @@
+\BArbalester - Metafox\W FPGA core
+
+ by \GAndr\Wea Bog\Razzi\W
+ on the \RJTCORES\W framework
+ by JOTEGO + community
+
+ Reviewed by Jose Tejada
\ No newline at end of file
diff --git a/cores/arbalest/hdl/jtarbalest_game.v b/cores/arbalest/hdl/jtarbalest_game.v
new file mode 100644
index 000000000..262d5c18d
--- /dev/null
+++ b/cores/arbalest/hdl/jtarbalest_game.v
@@ -0,0 +1,230 @@
+/* This file is part of JTCORES.
+ JTCORES 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.
+
+ JTCORES 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 JTCORES. If not, see .
+
+ Author: Andrea Bogazzi. andreabogazzi79@gmail.com
+ Version: 1.0
+ Date: 17-06-2026 */
+
+// Seta downtown.cpp (metafox-class) top.
+// Reuses the cal50 video pipeline (jtcal50_video = single X1-012 layer + X1-001
+// sprites + direct xRGB-555 colmix) IMPORTED via cfg/files.yaml. Only the bus
+// topology differs, so main/sub/sound are local to this core.
+module jtarbalest_game(
+ `include "jtframe_game_ports.inc" // see $JTFRAME/hdl/inc/jtframe_game_ports.inc
+);
+
+wire [13:1] cpu_addr;
+wire [ 1:0] cpu_dsn;
+wire [ 8:0] hdump;
+wire [ 7:0] st_main, st_sub, st_video, x1_dout, slatch0, slatch1;
+wire [15:0] vram_dout;
+wire flip, cpu_rnw, sub_rst,
+ vram_cs, vctrl_cs, vflag_cs, tctrl_cs,
+ x1_cs, shram_cs;
+
+// MRA header byte 0 = game_id (0=metafox, 1=arbalest)
+`ifdef JTFRAME_SIM_GAMEID
+reg [3:0] game_id = `JTFRAME_SIM_GAMEID;
+`else
+reg [3:0] game_id = 4'd0;
+`endif
+always @(posedge clk) if( prog_we && header ) case( prog_addr[3:0] )
+ 4'd0: game_id <= prog_data[3:0];
+ default:;
+endcase
+
+assign debug_view = st_video;
+assign dip_flip = ~flip;
+assign mute = 0;
+
+/* verilator tracing_on */
+jtarbalest_main u_main(
+ .rst ( rst ),
+ .clk ( clk ),
+ .cen8 ( cen8 ),
+ .lvbl ( LVBL ),
+
+ .rom_addr ( main_addr ),
+ .ram_addr ( ram_addr ),
+ .cpu_addr ( cpu_addr ),
+ .cpu_dsn ( cpu_dsn ),
+ .ram_we ( ram_we ),
+ .cpu_dout ( cpu_dout ),
+ .cpu_rnw ( cpu_rnw ),
+
+ .rom_cs ( main_cs ),
+ .rom_data ( main_data ),
+ .rom_ok ( main_ok ),
+ .ram_dout ( ram_dout ),
+
+ // X1-010 sound (main bus)
+ .x1_cs ( x1_cs ),
+ .x1_dout ( x1_dout ),
+
+ // I/O sub-CPU (sub_ctrl_w decoded in main)
+ .slatch0 ( slatch0 ),
+ .slatch1 ( slatch1 ),
+ .sub_rst ( sub_rst ),
+ .shram_cs ( shram_cs ),
+ .shram_we ( shram_we ),
+ .shram_dout ( shram_dout ),
+
+ // video
+ .pal_we ( pal_we ),
+ .pal_dout ( pal_dout ),
+ .tctrl_cs ( tctrl_cs ),
+ .tlv_we ( tlv_we ),
+ .tlv_dout ( tlv_dout ),
+ .vram_cs ( vram_cs ),
+ .vflag_cs ( vflag_cs ),
+ .vctrl_cs ( vctrl_cs ),
+ .vram_dout ( vram_dout ),
+
+ // cabinet
+ .game_id ( game_id ),
+ .dipsw ( dipsw[15:0] ),
+ .dip_pause ( dip_pause ),
+ .st_dout ( st_main ),
+ .debug_bus ( debug_bus )
+);
+
+/* verilator tracing_on */
+jtarbalest_sub u_sub(
+ .rst ( sub_rst ),
+ .clk ( clk ),
+ .cen ( cen8 ), // 8 MHz crystal cen -> ~2 MHz E (jt65c02 /4)
+ .joystick1 ( joystick1[5:0]),
+ .joystick2 ( joystick2[5:0]),
+ .cab_1p ( cab_1p[1:0] ),
+ .coin ( coin[1:0] ),
+ .service ( service ),
+ .tilt ( tilt ),
+ .slatch0 ( slatch0 ),
+ .slatch1 ( slatch1 ),
+
+ .rom_addr ( snd_addr ),
+ .rom_cs ( snd_cs ),
+ .rom_data ( snd_data ),
+ .rom_ok ( snd_ok ),
+
+ .subsh_addr ( subsh_addr ),
+ .subsh_din ( subsh_din ),
+ .subsh_dout ( subsh_dout ),
+ .subsh_we ( subsh_we ),
+
+ .hs ( HS ),
+ .lvbl ( LVBL ),
+ .st_dout ( st_sub )
+);
+
+/* verilator tracing_on */
+jtarbalest_sound u_sound(
+ .rst ( rst ),
+ .clk ( clk ),
+ .cen_pcm ( cen_pcm ),
+
+ .cs ( x1_cs ),
+ .addr ( cpu_addr ),
+ .din ( cpu_dout[7:0] ),
+ .dout ( x1_dout ),
+ .we (~cpu_rnw ),
+
+ .pcm_addr ( pcm_addr ),
+ .pcm_data ( pcm_data ),
+ .pcm_cs ( pcm_cs ),
+
+ .snd_left ( pcm_8k ),
+ .snd_right ( pcm_4k )
+);
+
+/* verilator tracing_on */
+jtcal50_video #(
+ .OBJAW ( 13 ), // 16kB sprite RAM + setac bank
+ .SCR_EN( 1 ), // X1-001 background layer (draw_background) draws the attract scenery
+ .OBJ_LIMIT( 9'h1ff ),
+ // MAME visarea rows 16..239 (224 lines) -> vdump 8..231, VS stays centred
+ .VB_END ( 9'd8 ),
+ .VB_START( 9'd232 ),
+ // set_fg_xoffsets noflip: calibr50 -1, metafox/arbalest 0 -> one count right
+ .OBJ_XOFF( 9'h1ff )
+) u_video(
+ .rst ( rst ),
+ // MAME x1_012 set_xoffsets noflip: metafox 16 -> 0x00, arbalest -2 -> 0x12
+ .thoffs ( game_id==4'd1 ? 16'h1f : 16'h0d ),
+ .clk ( clk ),
+ .clk_cpu ( clk ),
+ .cen244 ( ),
+ .pxl2_cen ( pxl2_cen ),
+ .pxl_cen ( pxl_cen ),
+ .LHBL ( LHBL ),
+ .LVBL ( LVBL ),
+ .HS ( HS ),
+ .VS ( VS ),
+ .hdump ( hdump ),
+ .flip ( flip ),
+
+ .cpu_rnw ( cpu_rnw ),
+ .cpu_dsn ( cpu_dsn ),
+ .cpu_addr ( cpu_addr ),
+ .cpu_dout ( cpu_dout ),
+ .vram_cs ( vram_cs ),
+ .vctrl_cs ( vctrl_cs ),
+ .vflag_cs ( vflag_cs ),
+ .vram_dout ( vram_dout ),
+
+ .col_addr ( col_addr ),
+ .col_data ( col_data ),
+ .yram_dout ( yram_dout ),
+ .yram_we ( yram_we ),
+
+ .dma_addr ( dma_addr ),
+ .dma_din ( dma_din ),
+ .dma_we ( dma_we ),
+ .dma_dout ( dma_dout ),
+ .code_dout ( code_dout ),
+ .code_addr ( code_addr ),
+
+ .tctrl_cs ( tctrl_cs ),
+ .tvram_addr ( tlrd_addr ),
+ .tvram_dout ( tlrd_data ),
+ .tile_addr ( tile_addr ),
+ .tile_data ( tile_data ),
+ .tile_cs ( tile_cs ),
+ .tile_ok ( tile_ok ),
+
+ .pal_addr ( palrd_addr ),
+ .pal_data ( pal_data ),
+
+ .scr_addr ( scr_addr ),
+ .scr_data ( scr_data ),
+ .scr_ok ( scr_ok ),
+ .scr_cs ( scr_cs ),
+
+ .obj_addr ( obj_addr ),
+ .obj_data ( obj_data ),
+ .obj_ok ( obj_ok ),
+ .obj_cs ( obj_cs ),
+
+ .red ( red ),
+ .green ( green ),
+ .blue ( blue ),
+
+ .ioctl_addr (ioctl_addr[3:0]),
+ .ioctl_din ( ioctl_din ),
+ .gfx_en ( gfx_en ),
+ .debug_bus ( debug_bus ),
+ .st_dout ( st_video )
+);
+
+endmodule
diff --git a/cores/arbalest/hdl/jtarbalest_main.v b/cores/arbalest/hdl/jtarbalest_main.v
new file mode 100644
index 000000000..dfbd7345c
--- /dev/null
+++ b/cores/arbalest/hdl/jtarbalest_main.v
@@ -0,0 +1,257 @@
+/* This file is part of JTCORES.
+ JTCORES 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.
+
+ JTCORES 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 JTCORES. If not, see .
+
+ Author: Andrea Bogazzi. andreabogazzi79@gmail.com
+ Version: 1.0
+ Date: 17-06-2026 */
+
+// Seta downtown.cpp (metafox-class) main 68000.
+// Unlike calibr50 (cal50): the X1-010 lives on THIS bus (0x100000) and the
+// 65C02 is an I/O / protection coprocessor reached through shared RAM (0xb00000).
+//
+// downtown_map nibble decode (A[23:20]):
+// 0 ROM 1 X1-010 sound 2 protection 3 ipl1-ack 4 (unused: twineagl-only)
+// 5 ctrl/coin 6 DSW 7 palette 8 X1-012 ctrl 9 X1-012 VRAM
+// A sub-ctrl B shared RAM C X1-001 bg-flag D X1-001 spr-ctrl
+// E X1-001 spr-code F work RAM
+module jtarbalest_main(
+ input rst, clk, cen8,
+ input lvbl,
+
+ output [19:1] rom_addr,
+ output [15:1] ram_addr,
+ output [12:0] cpu_addr,
+ output [ 1:0] cpu_dsn,
+ output [ 1:0] ram_we,
+ output [15:0] cpu_dout,
+ output cpu_rnw,
+
+ output reg rom_cs,
+ input [15:0] rom_data,
+ input rom_ok,
+ input [15:0] ram_dout,
+
+ // X1-010 sound (on the main bus)
+ output reg x1_cs,
+ input [ 7:0] x1_dout,
+
+ // I/O / protection sub-CPU (65C02). sub_ctrl_w (0xa00000) is decoded here:
+ output reg [ 7:0] slatch0, slatch1, // soundlatches, read by the sub @0x0800/1
+ output sub_rst, // 0xa00000 bit0 rising -> stretched sub reset
+ output reg shram_cs, // 0xb00000 shared RAM
+ output shram_we,
+ input [ 7:0] shram_dout,
+
+ // X1-012 tilemap + palette
+ output [ 1:0] pal_we,
+ input [15:0] pal_dout,
+ output reg tctrl_cs, // X1-012 control
+ output [ 1:0] tlv_we,
+ input [15:0] tlv_dout,
+
+ // X1-001 sprites (same regions as cal50)
+ output reg vram_cs, vflag_cs, vctrl_cs,
+ input [15:0] vram_dout,
+
+ // Game select (0=metafox, 1=arbalest)
+ input [ 3:0] game_id,
+ // Cabinet (DSW read by main; joysticks/coins are read by the sub)
+ input [15:0] dipsw,
+ input dip_pause,
+ output [ 7:0] st_dout,
+ input [ 7:0] debug_bus
+);
+`ifndef NOMAIN
+wire [23:1] A;
+reg [15:0] cpu_din;
+reg [ 2:0] IPLn;
+reg ram_cs, prot_cs, ipl1_cs, dips_cs, tlv_cs, pal_cs, subctrl_cs;
+wire int16ms,
+ cpu_cen, cpu_cenb, dtackn, VPAn, vgfx_cs,
+ UDSn, LDSn, RnW, ASn, BUSn, bus_busy, bus_cs;
+reg HALTn;
+
+assign cpu_addr = A[13:1];
+assign rom_addr = A[19:1];
+assign ram_addr = A[15:1];
+// VPAn (6800-style sync) asserted on the upper half so the level-3 vblank IRQ
+// autovectors: the IACK address is all 1s -> A[23]=1. Same idiom as cal50.
+// MAME: screen_vblank -> set_inputline(maincpu,3) with no vector cb = autovector.
+assign VPAn = ~&{A[23],~ASn};
+assign cpu_dsn = {UDSn, LDSn};
+assign bus_cs = rom_cs | vgfx_cs;
+// X1-001/X1-012 RAM is dual-port BRAM (CPU + engine on separate ports), so the
+// CPU needs NO access-arbitration stall. cal50's hdump-gated vgfx_bsy wait, when
+// imported here, desynced the fx68k dtack on the POST's back-to-back sprite-RAM
+// writes and corrupted an ALU result (e1c7+0f0f -> 1289 instead of f0d6),
+// failing the sprite-RAM self-test. Dropping the stall fixes it.
+assign bus_busy = rom_cs & ~rom_ok;
+assign BUSn = ASn | (LDSn & UDSn);
+assign cpu_rnw = RnW;
+assign ram_we = ~cpu_dsn & {2{ram_cs & ~RnW}};
+assign pal_we = ~cpu_dsn & {2{pal_cs & ~RnW}};
+assign tlv_we = ~cpu_dsn & {2{tlv_cs & ~RnW}};
+assign shram_we = shram_cs & ~RnW & ~LDSn; // 8-bit shared RAM, low byte
+assign st_dout = 0;
+assign vgfx_cs = vram_cs | vflag_cs | vctrl_cs;
+
+always @* begin
+ rom_cs = !BUSn && A[23:20]==0;
+ x1_cs = !ASn && A[23:20]==1; // X1-010
+ prot_cs = !ASn && A[23:20]==2; // protection / debug stub
+ ipl1_cs = !ASn && A[23:20]==3;
+// ctrl/coin = !ASn && A[23:20]==5;
+ dips_cs = !ASn && A[23:20]==6;
+ pal_cs = !ASn && A[23:20]==7;
+ tctrl_cs = !ASn && A[23:20]==8 && !RnW;
+ tlv_cs = !ASn && A[23:20]==9 && !A[14];
+ subctrl_cs = !ASn && A[23:20]==4'hA;
+ shram_cs = !BUSn && A[23:20]==4'hB;
+ // SETA X1-001 chip
+ vflag_cs = !ASn && A[23:20]==4'hC;
+ vctrl_cs = !ASn && A[23:20]==4'hD && (A[9:8]!=3 || !LDSn);
+ vram_cs = !ASn && A[23:20]==4'hE;
+ ram_cs = !BUSn && A[23:20]==4'hF;
+end
+
+reg [7:0] dipsw_mx;
+always @* dipsw_mx = A[1] ? dipsw[7:0] : dipsw[15:8];
+
+// metafox X1-017 protection (game_id==0). MAME metafox_protection_r returns
+// offset*0x1f (word offset within 0x21c000-0x21ffff) with three special words.
+// The boot self-tests this readback sequence; arbalest has no 0x21c000 protection.
+wire prot_meta = prot_cs && game_id==4'd0 && A[19:14]==6'd7;
+wire [12:0] prot_off = A[13:1];
+reg [15:0] prot_dout;
+always @* begin
+ case( prot_off )
+ 13'h000: prot_dout = 16'h003d;
+ 13'h800: prot_dout = 16'h0076;
+ 13'h1000: prot_dout = 16'h0010;
+ default: prot_dout = prot_off * 16'd31; // offset*0x1f
+ endcase
+end
+
+// metafox main = a single level-3 IRQ on vblank (downtown.cpp:
+// screen_vblank().set_inputline(maincpu, 3)). Acked by the 0x300000 write
+// (ipl1_ack_w). NO timer/scanline IRQ on the main (that is calibr50-only).
+always @* begin
+ IPLn = 3'b111; // level 0 (idle)
+ if( int16ms ) IPLn = 3'b100; // level 3 vblank IRQ
+end
+
+always @(posedge clk) begin
+ HALTn <= dip_pause & ~rst;
+ cpu_din <= rom_cs ? rom_data :
+ ram_cs ? ram_dout :
+ (vram_cs | vctrl_cs) ? vram_dout :
+ pal_cs ? pal_dout :
+ tlv_cs ? tlv_dout :
+ x1_cs ? {8'd0, x1_dout} :
+ shram_cs ? {8'd0, shram_dout}:
+ dips_cs ? {8'hff, dipsw_mx} :
+ prot_meta? prot_dout :
+ prot_cs ? 16'hffff : 16'h0;
+end
+
+// sub_ctrl_w (0xa00000-7, umask 0x00ff): off0 bit0 rising -> stretched sub reset;
+// off4/off6 -> soundlatch0/1 (read by the sub at 0x0800/1). A[2:1] = sub_ctrl offset.
+reg subctrl0_b0;
+reg [ 4:0] subrst_cnt; // stretch the reset pulse over a few cen8 ticks
+wire subctrl_we = subctrl_cs & ~RnW & ~LDSn;
+assign sub_rst = rst | (subrst_cnt!=0);
+always @(posedge clk) begin
+ if( rst ) begin
+ slatch0 <= 0; slatch1 <= 0; subctrl0_b0 <= 0; subrst_cnt <= 0;
+ end else begin
+ if( subctrl_we ) case( A[2:1] )
+ 2'd0: begin
+ subctrl0_b0 <= cpu_dout[0];
+ if( cpu_dout[0] & ~subctrl0_b0 ) subrst_cnt <= 5'h1f; // bit0 rising
+ end
+ 2'd2: slatch0 <= cpu_dout[7:0];
+ 2'd3: slatch1 <= cpu_dout[7:0];
+ default:;
+ endcase
+ if( subrst_cnt!=0 && cen8 ) subrst_cnt <= subrst_cnt - 5'd1;
+ end
+end
+
+jtframe_edge u_16ms(
+ .rst ( rst ),
+ .clk ( clk ),
+ .edgeof (~lvbl ), // vblank (LVBL falling) -> level-3 IRQ
+ .clr ( ipl1_cs ), // acked by the 0x300000 write
+ .q ( int16ms )
+);
+
+jtframe_68kdtack_cen #(.W(6),.RECOVERY(1)) u_bus_dtack(
+ .rst ( rst ),
+ .clk ( clk ),
+ .cpu_cen ( cpu_cen ),
+ .cpu_cenb ( cpu_cenb ),
+ .bus_cs ( bus_cs ),
+ .bus_busy ( bus_busy ),
+ .bus_legit ( 1'b0 ),
+ .bus_ack ( 1'b0 ),
+ .ASn ( ASn ),
+ .DSn ({UDSn,LDSn}),
+ .num ( 5'd1 ),
+ .den ( 6'd6 ),
+ .DTACKn ( dtackn ),
+ .wait2 ( 1'b0 ),
+ .wait3 ( 1'b0 ),
+ .fave ( ),
+ .fworst ( )
+);
+
+jtframe_m68k u_cpu(
+ .clk ( clk ),
+ .rst ( rst ),
+ .RESETn ( ),
+ .cpu_cen ( cpu_cen ),
+ .cpu_cenb ( cpu_cenb ),
+
+ .eab ( A ),
+ .iEdb ( cpu_din ),
+ .oEdb ( cpu_dout ),
+
+ .eRWn ( RnW ),
+ .LDSn ( LDSn ),
+ .UDSn ( UDSn ),
+ .ASn ( ASn ),
+ .VPAn ( VPAn ),
+ .FC ( ),
+
+ .BERRn ( 1'b1 ),
+ .HALTn ( HALTn ),
+ .BRn ( 1'b1 ),
+ .BGACKn ( 1'b1 ),
+ .BGn ( ),
+
+ .DTACKn ( dtackn ),
+ .IPLn ( IPLn )
+);
+`else
+ initial rom_cs = 0;
+ assign rom_addr = 0, ram_addr = 0, cpu_addr = 0, cpu_dsn = 3,
+ ram_we = 0, cpu_dout = 0, cpu_rnw = 1,
+ pal_we = 0, tlv_we = 0, shram_we = 0, sub_rst = rst,
+ st_dout = 0;
+ initial begin
+ x1_cs=0; subctrl_cs=0; shram_cs=0; tctrl_cs=0;
+ vram_cs=0; vflag_cs=0; vctrl_cs=0; slatch0=0; slatch1=0;
+ end
+`endif
+endmodule
diff --git a/cores/arbalest/hdl/jtarbalest_sound.v b/cores/arbalest/hdl/jtarbalest_sound.v
new file mode 100644
index 000000000..a96f01bd2
--- /dev/null
+++ b/cores/arbalest/hdl/jtarbalest_sound.v
@@ -0,0 +1,62 @@
+/* This file is part of JTCORES.
+ JTCORES 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.
+
+ JTCORES 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 JTCORES. If not, see .
+
+ Author: Andrea Bogazzi. andreabogazzi79@gmail.com
+ Version: 1.0
+ Date: 17-06-2026 */
+
+module jtarbalest_sound(
+ input clk,
+ input rst,
+ input cen_pcm,
+
+ // Main-bus CPU interface
+ input cs,
+ input [12:0] addr, // main cpu_addr = A[13:1]
+ input [ 7:0] din,
+ output [ 7:0] dout,
+ input we,
+
+ // PCM sample ROM (SDRAM pcm bank)
+ output [19:0] pcm_addr,
+ input [ 7:0] pcm_data,
+ output pcm_cs,
+
+ // Sound output: left = music (8kHz AA), right = bass/sfx (4kHz AA)
+ output signed [15:0] snd_left, snd_right
+);
+`ifndef NOSOUND
+jtx1010 u_pcm(
+ .rst ( rst ),
+ .clk ( clk ),
+ .cen ( cen_pcm ),
+
+ .addr ( addr ),
+ .din ( din ),
+ .dout ( dout ),
+ .we ( we & cs ),
+ .cs ( cs ),
+
+ .rom_addr ( pcm_addr ),
+ .rom_data ( pcm_data ),
+ .rom_cs ( pcm_cs ),
+
+ .left ( snd_left ),
+ .right ( snd_right ),
+ .sample ( )
+);
+`else
+ assign dout=0, pcm_addr=0, pcm_cs=0, snd_left=0, snd_right=0;
+`endif
+endmodule
diff --git a/cores/arbalest/hdl/jtarbalest_sub.v b/cores/arbalest/hdl/jtarbalest_sub.v
new file mode 100644
index 000000000..c720fa207
--- /dev/null
+++ b/cores/arbalest/hdl/jtarbalest_sub.v
@@ -0,0 +1,157 @@
+/* This file is part of JTCORES.
+ JTCORES 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.
+
+ JTCORES 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 JTCORES. If not, see .
+
+ Author: Andrea Bogazzi. andreabogazzi79@gmail.com
+ Version: 1.0
+ Date: 17-06-2026 */
+
+// metafox / arbalest I/O - protection sub-CPU (65C02 @ 2 MHz).
+// It reads the cabinet inputs and exchanges them with the main 68000 through
+// the shared RAM, plus does bankswitch + coin lockout.
+//
+// metafox_sub_map:
+// 0x0000-0x01ff internal RAM 0x0800/0x0801 soundlatch 0/1 (main comm)
+// 0x1000 COINS r / bank+lockout w 0x1002 P1 0x1006 P2
+// 0x5000-0x57ff shared RAM 0x7000-0x7fff ROM
+// 0x8000-0xbfff banked ROM 0xc000-0xffff ROM
+module jtarbalest_sub(
+ input rst, clk,
+ input cen, // 2 MHz enable
+
+ // Cabinet inputs (read here, shared to main via shram)
+ input [ 5:0] joystick1, joystick2,
+ input [ 1:0] cab_1p, // start buttons
+ input [ 1:0] coin,
+ input service, tilt,
+
+ // main->sub command latches (sub_ctrl_w 0xa00004/0xa00006), read at 0x0800/0x0801
+ input [ 7:0] slatch0, slatch1,
+
+ // Program ROM (snd SDRAM bank)
+ output [17:0] rom_addr,
+ output reg rom_cs,
+ input [ 7:0] rom_data,
+ input rom_ok,
+
+ // Shared RAM (subsh side of the shram dual-port BRAM)
+ output [10:0] subsh_addr,
+ output [ 7:0] subsh_din,
+ input [ 7:0] subsh_dout,
+ output subsh_we,
+
+ // Screen timing — the metafox sub_interrupt is generated from it below.
+ input hs,
+ input lvbl,
+ output [ 7:0] st_dout
+);
+`ifndef NOSUB
+wire [15:0] A;
+wire [ 7:0] cpu_dout, ram_dout;
+reg [ 7:0] cpu_din, cab_dout;
+wire cpu_wr;
+reg ram_cs, sh_cs, in_cs, lat_cs;
+
+assign st_dout = 0;
+// metafox sub ROM is an 8 KB EPROM that MAME ROM_RELOADs across 0x6000-0xffff.
+// mame2mra packs it once at region offset 0 and drops the reloads, so mirror it
+// here: index the 8 KB image by A[12:0]. (Reset vector 0xfffc -> ROM[0x1ffc].)
+assign rom_addr = {5'd0, A[12:0]};
+assign subsh_addr = A[10:0];
+assign subsh_din = cpu_dout;
+assign subsh_we = sh_cs & cpu_wr; // byte writes from the sub
+
+always @* begin
+ ram_cs = A[15:9]==0; // 0x0000-0x01ff
+ in_cs = A[15:12]==4'h1; // 0x1000 region (inputs / bank)
+ sh_cs = A[15:12]==4'h5; // 0x5000-0x57ff shared RAM
+ rom_cs = A[15] | (A[15:13]==3'b011); // 0x6000-0xffff (8KB ROM, mirrored)
+ lat_cs = A[15:11]==5'h01; // 0x0800 soundlatch (stub)
+end
+
+always @* begin
+ case(A[2:1])
+ 2'd0: cab_dout = {coin[0],coin[1],service,tilt,4'hf}; // 0x1000 COINS
+ 2'd1: cab_dout = {cab_1p[0],1'b1,joystick1}; // 0x1002 P1
+ 2'd2: cab_dout = 8'h00; // 0x1004 status port: reads 0
+ // (arbalest sub boot checks 0x1004==0; MAME nopr=0)
+ 2'd3: cab_dout = {cab_1p[1],1'b1,joystick2}; // 0x1006 P2
+ default: cab_dout = 8'hff;
+ endcase
+end
+
+always @* begin
+ cpu_din = ram_cs ? ram_dout :
+ sh_cs ? subsh_dout :
+ in_cs ? cab_dout :
+ lat_cs ? (A[0] ? slatch1 : slatch0) : // soundlatch 0x0800/0x0801 (main->sub)
+ rom_cs ? rom_data : 8'hff;
+end
+
+// The 0x1000 write is the ROM-bank + coin-lockout register. Banking is not
+// implemented (the 8 KB sub ROM is mirrored across 0x6000-0xffff, see rom_addr)
+// and coin-lockout is a no-op here, so the latch is omitted. The write's only
+// live effect is acking the sub IRQ below (in_cs & cpu_wr -> u_irq.clr).
+
+jtframe_ram #(.AW(9)) u_ram( // 0x0000-0x01ff internal RAM
+ .clk ( clk ),
+ .cen ( 1'b1 ),
+ .addr ( A[8:0] ),
+ .data ( cpu_dout ),
+ .we ( ram_cs & cpu_wr ),
+ .q ( ram_dout )
+);
+
+// metafox sub_interrupt from the screen scanline (MAME line 0 = LVBL rising):
+// IRQ (level) asserted entering line 112, NMI (edge) high during vblank (line>=240).
+reg [ 8:0] vline;
+reg hs_q, lvbl_q, irq_set, nmi;
+always @(posedge clk) begin
+ hs_q <= hs;
+ lvbl_q <= lvbl;
+ irq_set <= 0;
+ if( lvbl & ~lvbl_q ) vline <= 0;
+ else if( hs & ~hs_q ) begin
+ vline <= vline + 9'd1;
+ if( vline==9'd111 ) irq_set <= 1;
+ end
+ nmi <= vline >= 9'd240;
+end
+
+// IRQ latch: set at scanline 112, cleared by the 0x1000 bank/lockout write.
+wire irq;
+jtframe_edge u_irq(
+ .rst ( rst ),
+ .clk ( clk ),
+ .edgeof ( irq_set ),
+ .clr ( in_cs & cpu_wr ), // sub_bankswitch_lockout_w acks the IRQ
+ .q ( irq )
+);
+
+jt65c02 u_cpu(
+ .rst ( rst ),
+ .clk ( clk ),
+ .cen ( cen ),
+ .irq ( irq ),
+ .nmi ( nmi ),
+ .rd ( ),
+ .wr ( cpu_wr ),
+ .addr ( A ),
+ .din ( cpu_din ),
+ .dout ( cpu_dout )
+);
+`else
+ initial rom_cs = 0;
+ assign rom_addr=0, subsh_addr=0, subsh_din=0, subsh_we=0, st_dout=0;
+`endif
+endmodule
diff --git a/cores/cal50/hdl/jtcal50_colmix.v b/cores/cal50/hdl/jtcal50_colmix.v
index 2e9d75420..ac1590361 100644
--- a/cores/cal50/hdl/jtcal50_colmix.v
+++ b/cores/cal50/hdl/jtcal50_colmix.v
@@ -16,14 +16,16 @@
Version: 1.0
Date: 15-11-2025 */
-module jtcal50_colmix(
+module jtcal50_colmix #(
+ parameter SCR_EN = 0 // 1: blend the X1-001 background layer (metafox/arbalest)
+)(
input clk,
input clk_cpu,
input pxl_cen,
input LHBL,
input LVBL,
- input [ 8:0] scr_pxl, // unused ?
+ input [ 8:0] scr_pxl, // X1-001 draw_background (column-scrolled sprite BG)
input [ 8:0] obj_pxl, tiles_pxl,
output reg [ 9:1] pal_addr,
@@ -38,21 +40,28 @@ module jtcal50_colmix(
reg [ 8:0] col_addr;
reg [14:0] rgb;
-wire [ 8:0] obj_srt;
-wire blank;
+wire [ 8:0] obj_srt, scr_srt, bg;
+wire blank, scr_sel;
reg obj_sel;
assign blank = ~(LVBL & LHBL);
assign {red,green,blue} = blank ? 15'd0 : rgb;
+// X1-001 pen-bit order (same for foreground sprites and the background layer)
assign obj_srt = {obj_pxl[8:4],obj_pxl[1],obj_pxl[3],obj_pxl[0],obj_pxl[2]};
+assign scr_srt = {scr_pxl[8:4],scr_pxl[1],scr_pxl[3],scr_pxl[0],scr_pxl[2]};
+// X1-001 background (draw_background) masks the X1-012 tiles where opaque. Only
+// for SCR_EN cores; calibr50 (SCR_EN=0) keeps bg==tiles_pxl, so the mux below is
+// byte-identical to before.
+assign scr_sel = SCR_EN & gfx_en[1] & (scr_srt[3:0]!=4'h0);
+assign bg = scr_sel ? scr_srt : tiles_pxl;
always @* begin
obj_sel = obj_srt[3:0] != 4'h0;
case( {gfx_en[3],gfx_en[0]})
2'b00: col_addr = 0;
- 2'b01: col_addr = tiles_pxl;
+ 2'b01: col_addr = bg;
2'b10: col_addr = obj_srt;
- 2'b11: col_addr = obj_sel ? obj_srt : tiles_pxl; // simple priority for now.
+ 2'b11: col_addr = obj_sel ? obj_srt : bg;
endcase
end
diff --git a/cores/cal50/hdl/jtcal50_game.v b/cores/cal50/hdl/jtcal50_game.v
index 086487f3d..67f0899a0 100644
--- a/cores/cal50/hdl/jtcal50_game.v
+++ b/cores/cal50/hdl/jtcal50_game.v
@@ -122,6 +122,7 @@ jtcal50_sound u_sound(
/* verilator tracing_on */
jtcal50_video u_video(
.rst ( rst ),
+ .thoffs ( 16'h20 ),
.clk ( clk ),
.clk_cpu ( clk ),
.cen244 ( cen244 ),
diff --git a/cores/cal50/hdl/jtcal50_video.v b/cores/cal50/hdl/jtcal50_video.v
index 7c5d51d3f..a5742de72 100644
--- a/cores/cal50/hdl/jtcal50_video.v
+++ b/cores/cal50/hdl/jtcal50_video.v
@@ -16,7 +16,18 @@
Version: 1.0
Date: 15-11-2025 */
-module jtcal50_video(
+module jtcal50_video #(
+ // 12 = 8kB sprite RAM (calibr50), 13 = 16kB + setac bank (metafox/arbalest)
+ parameter OBJAW = 12,
+ // 1: blend the X1-001 background layer (metafox/arbalest)
+ parameter SCR_EN = 0,
+ // Caliber 50 only populates object entries 0-200; a shorter scan leaves
+ // line time for the sprites that wrap through Y=0
+ parameter [ 8:0] OBJ_LIMIT = 9'd200,
+ parameter [ 8:0] VB_END = 9'd0,
+ parameter [ 8:0] VB_START = 9'd240,
+ parameter [ 8:0] OBJ_XOFF = 9'h1fe
+)(
input rst,
input clk,
input clk_cpu,
@@ -30,6 +41,10 @@ module jtcal50_video(
output VS,
output flip,
output [ 8:0] hdump,
+ // X1-012 tilemap scroll origin. Per game, so it cannot be a parameter on a
+ // shared bitstream. MAME x1_012 update_scroll: x += 0x10-xoffsets[0], and our
+ // origin sits 0x0d above MAME's -> calibr50 0x20, metafox 0x0d, arbalest 0x1f
+ input [15:0] thoffs,
// Palette
output [ 9:1] pal_addr,
input [15:0] pal_data,
@@ -56,11 +71,11 @@ module jtcal50_video(
input [ 7:0] col_data, yram_dout,
output yram_we,
// X1-001 External VRAM (defined in mem.yaml)
- output [12:1] dma_addr,
+ output [OBJAW:1] dma_addr,
output [15:0] dma_din,
output [ 1:0] dma_we,
input [15:0] dma_dout, code_dout,
- output [11:0] code_addr,
+ output [OBJAW-1:0] code_addr,
// SDRAM interface
output [20:2] scr_addr,
input [31:0] scr_data,
@@ -95,6 +110,7 @@ wire [6:0] nx_244 = {1'b0,cnt244} + 6'd1;
wire [8:0] hdump_gfx = hdump - 9'd7;
wire [8:0] vdump_gfx = vdump - 9'd1,
vrender_gfx = vrender - 9'd1;
+wire [8:0] vdump_tile = vdump_gfx - VB_END;
assign ioctl_din = ioctl_addr[3] ? x1001_ioctl_din : x1012_ioctl_din;
@@ -120,8 +136,8 @@ jtframe_vtimer #(
.V_START ( 9'd000 ),
.VS_START( 9'd253 ),
.VS_END ( 9'd261 ),
- .VB_START( 9'd240 ),
- .VB_END ( 9'd000 ),
+ .VB_START( VB_START ),
+ .VB_END ( VB_END ),
.VCNT_END( 9'd271 )
) u_timer(
.clk ( clk ),
@@ -140,6 +156,7 @@ jtframe_vtimer #(
/* verilator tracing_off */
jtx1012 u_tiles(
.rst ( rst ),
+ .hoffs ( thoffs ),
.clk ( clk ),
.pxl_cen ( pxl_cen ),
@@ -151,7 +168,7 @@ jtx1012 u_tiles(
.hs ( HS ),
.flip ( flip ),
- .vdump ( vdump_gfx ),
+ .vdump ( vdump_tile ),
.hdump ( hdump_gfx ),
// Video RAM
.vram_addr ( tvram_addr ),
@@ -183,10 +200,11 @@ wire [8:0] vdump_adj = vdump_gfx + VADJ,
// range leaves enough line time for sprites that wrap through Y=0.
jtkiwi_gfx #(
.CPUW ( 16 ),
- .OBJ_XOFF( 9'h1fe ),
+ .OBJ_XOFF( OBJ_XOFF ),
.OBJ_YOFF( 8'hf5 ),
.OBJ_YWRAP( 1'b1 ),
- .OBJ_LIMIT( 9'd200 )
+ .OBJ_LIMIT( OBJ_LIMIT ),
+ .OBJAW ( OBJAW )
) u_gfx(
.rst ( rst ),
.clk ( clk ),
@@ -245,7 +263,7 @@ jtkiwi_gfx #(
.st_dout ( st_kiwi )
);
/* verilator tracing_on */
-jtcal50_colmix u_colmix(
+jtcal50_colmix #(.SCR_EN(SCR_EN)) u_colmix(
.clk ( clk ),
.clk_cpu ( clk_cpu ),
.pxl_cen ( pxl_cen ),
diff --git a/cores/cal50/hdl/jtx1012.v b/cores/cal50/hdl/jtx1012.v
index 3d55e80ca..20c61648d 100644
--- a/cores/cal50/hdl/jtx1012.v
+++ b/cores/cal50/hdl/jtx1012.v
@@ -33,6 +33,8 @@ module jtx1012(
input hs, flip,
input [8:0] vdump,
input [8:0] hdump,
+ // tilemap scroll origin, per game (jtframe_scroll: scrx left)
+ input [15:0] hoffs,
// Video RAM
output [13:1] vram_addr,
input [15:0] vram_dout,
@@ -69,9 +71,10 @@ assign rom_addr = {pre_addr[20:7],pre_addr[5],~flip^pre_addr[6],
};
always @(posedge clk) begin
- hpos <= pre_hpos+16'h20;
+ hpos <= pre_hpos+hoffs;
end
+
always @(posedge clk) begin
attr <= ~attr;
if(attr) begin
diff --git a/cores/kiwi/hdl/jtkiwi_gfx.v b/cores/kiwi/hdl/jtkiwi_gfx.v
index 47d47a67f..f6675bd05 100644
--- a/cores/kiwi/hdl/jtkiwi_gfx.v
+++ b/cores/kiwi/hdl/jtkiwi_gfx.v
@@ -28,7 +28,10 @@ module jtkiwi_gfx #(
parameter [8:0] OBJ_XOFF=0,
parameter [7:0] OBJ_YOFF=0,
parameter OBJ_YWRAP=0,
- parameter [8:0] OBJ_LIMIT=9'h1ff
+ parameter [8:0] OBJ_LIMIT=9'h1ff,
+ // 12 = 8kB sprite RAM, page selects the 0x800 half (tnzs/calibr50)
+ // 13 = 16kB, page selects the 0x1000 buffer (metafox/arbalest, MAME seta001 setac)
+ parameter OBJAW=12
)(
input rst,
input clk,
@@ -65,11 +68,11 @@ module jtkiwi_gfx #(
input [ 7:0] col_data, yram_dout,
output yram_we,
// External VRAM (defined in mem.yaml)
- output [12:1] dma_addr,
+ output [OBJAW:1] dma_addr,
output [15:0] dma_din,
output [ 1:0] dma_we,
input [15:0] dma_dout, code_dout,
- output reg [12:1] code_addr,
+ output reg [OBJAW:1] code_addr,
// SDRAM interface
output [20:2] scr_addr,
input [31:0] scr_data,
@@ -87,10 +90,15 @@ module jtkiwi_gfx #(
output reg [ 7:0] st_dout
);
+localparam SETAC = OBJAW==13;
+
wire video_en;
wire [ 1:0] vram_we;
wire [11:0] tm_addr;
-wire [12:1] lut_addr, dma_txa;
+wire [OBJAW:1] lut_addr;
+wire [12:1] dma_txa;
+wire [13:1] dma_txa_pad, tm_addr_pad;
+wire setac_bank;
wire [ 7:0] scol_addr;
reg [ 7:0] attr, xpos, ypos;
reg [ 7:0] cfg[0:3], flag;
@@ -125,8 +133,12 @@ assign obj_page = obj_pg_en ? tm_page ^ ~obj_bufb : 1'b1;
assign dma_src = obj_pg_en ? tm_page ^ dma_tm : 1'b0;
assign col_cfg = cfg[1][3:0];
assign col_xmsb = { cfg[3], cfg[2] };
-assign dma_addr = dma_bsy ? dma_txa : cpu_addr[11:0];
+assign dma_addr = dma_bsy ? dma_txa_pad[OBJAW:1] : cpu_addr[OBJAW-1:0];
assign dma_txa ={dma_src ^ dma_st, dma_tm, dma_cnt};
+assign dma_txa_pad = { 1'b0, dma_txa };
+// 16kB sprite RAM: cfg[1] carries the buffer bank (MAME seta001 setac_eof)
+assign setac_bank = ((cfg[1] ^ (~cfg[1]<<1)) & 8'h40)!=0;
+assign tm_addr_pad = { setac_bank, tm_addr };
assign dma_din = dma_bsy ? dma_data : vram_d16;
assign dma_we = dma_bsy ? dma_bsy_we : vram_we;
@@ -211,7 +223,7 @@ always @* begin
case( cen_cnt )
0,1: begin
col_addr = { 2'b10, scol_addr };
- code_addr = tm_addr;
+ code_addr = tm_addr_pad[OBJAW:1];
end
2,3: begin // objects
col_addr = { 1'b0, y_addr };
@@ -228,7 +240,8 @@ jtkiwi_tilemap u_tilemap(
.hs ( hs ),
.flip ( flip ),
- .page ( tm_page ),
+ // SETAC: no 0x800 page split, the bank is the code_addr MSB
+ .page ( SETAC ? 1'b0 : tm_page ),
.drtoppel ( drtoppel ),
.col_xmsb ( col_xmsb ),
@@ -257,7 +270,8 @@ jtkiwi_obj #(
.XOFF( OBJ_XOFF ),
.YOFF( OBJ_YOFF ),
.YWRAP( OBJ_YWRAP ),
- .LIMIT( OBJ_LIMIT )
+ .LIMIT( OBJ_LIMIT ),
+ .OBJAW( OBJAW )
) u_obj(
.rst ( rst ),
.clk ( clk ),
@@ -266,7 +280,7 @@ jtkiwi_obj #(
.hs ( hs ),
.flip ( flip ),
- .page ( obj_page ),
+ .page ( SETAC ? setac_bank : obj_page ),
.lut_addr ( lut_addr ),
.lut_data ( code_dout ),
diff --git a/cores/kiwi/hdl/jtkiwi_obj.v b/cores/kiwi/hdl/jtkiwi_obj.v
index 4025aa062..62d3fc050 100644
--- a/cores/kiwi/hdl/jtkiwi_obj.v
+++ b/cores/kiwi/hdl/jtkiwi_obj.v
@@ -22,7 +22,10 @@ module jtkiwi_obj #(
parameter [8:0] XOFF=0,
parameter [7:0] YOFF=0,
parameter YWRAP=0,
- parameter [8:0] LIMIT=9'h1ff
+ parameter [8:0] LIMIT=9'h1ff,
+ // 12 = 8kB sprite RAM, page selects the 0x800 half (tnzs/calibr50)
+ // 13 = 16kB, page selects the 0x1000 buffer (metafox/arbalest, MAME seta001 setac)
+ parameter OBJAW=12
)(
input rst,
input clk,
@@ -33,7 +36,7 @@ module jtkiwi_obj #(
input flip,
input page,
- output [12:1] lut_addr,
+ output [OBJAW:1] lut_addr,
input [15:0] lut_data,
output [ 8:0] y_addr,
@@ -68,7 +71,10 @@ wire dr_busy;
wire [ 8:0] buf_din, buf_addr;
wire buf_we;
-assign lut_addr = { page, 1'b0, ~st[1], objcnt }; // 1 + 1 + 1 + 9 = 12
+generate
+ if( OBJAW==13 ) assign lut_addr = { page, 2'b00, ~st[1], objcnt }; // 1 + 2 + 1 + 9 = 13
+ else assign lut_addr = { page, 1'b0, ~st[1], objcnt }; // 1 + 1 + 1 + 9 = 12
+endgenerate
assign y_addr = objcnt;
assign vf = {9{flip}} ^ (vdump-9'd1);
assign ypos = y_data + YOFF;