Files
CDi_MiSTer/rtl/mcd212.sv
Andre Zeps 594bae844e FMV: Fixed timing of frame events and parameters
- Decoupled SEQ event from GOP event.
  Now behaves like real VMPEG hardware
- Fixes Lost Ride gameplay after vehicle charge intro
- Fixes timing accuracy of temp ref and time code
  Measurable with mv_status()
2026-02-22 14:12:37 +01:00

1766 lines
63 KiB
Systemverilog

`include "bus.svh"
`include "videotypes.svh"
// MCD 212 - DRAM and Video
`define dp_vsr(statement) `ifdef DEBUG_VSR $display``statement `endif
`define dp_dcaptr(statement) `ifdef DEBUG_DCA $display``statement `endif
`define dp_raster(statement) `ifdef DEBUG_RASTER $display``statement `endif
`ifdef VERILATOR
function string coding_method_name(bit [3:0] coding, bit plane_b);
if (plane_b) begin
case (coding)
4'b0000: coding_method_name = "OFF";
4'b0001: coding_method_name = "RGB555";
4'b0011: coding_method_name = "CLUT7";
4'b0100: coding_method_name = "CLUT7+7";
4'b0101: coding_method_name = "DYUV";
4'b1011: coding_method_name = "CLUT4";
default: coding_method_name = "N/A";
endcase
end else begin
case (coding)
4'b0000: coding_method_name = "OFF";
4'b0001: coding_method_name = "CLUT8";
4'b0011: coding_method_name = "CLUT7";
4'b0101: coding_method_name = "DYUV";
4'b1011: coding_method_name = "CLUT4";
default: coding_method_name = "N/A";
endcase
end
endfunction
`endif
module mcd212 (
input clk,
input reset,
input [23:1] cpu_address,
input [15:0] cpu_din,
output bit [15:0] cpu_dout,
input cpu_uds,
input cpu_lds,
input cpu_write_strobe,
output bit cpu_bus_ack,
input cs,
input dvc_ram_cs,
input dvc_rom_cs,
output rgb888_s vidout,
output hsync,
output vsync,
output bit hblank,
output vblank,
output vga_f1,
output bit vsd,
output bit [24:0] sdram_addr,
output bit sdram_rd,
output bit sdram_wr,
output bit sdram_word,
output bit [15:0] sdram_din,
input [15:0] sdram_dout,
input sdram_busy,
output bit sdram_burst,
output bit sdram_refresh,
input sdram_burstdata_valid,
output irq,
input [1:0] debug_force_video_plane,
input [1:0] debug_limited_to_full,
input disable_cpu_starve
);
// Memory Swapping according to chapter 3.4
// of MCD212 datasheet.
bit [3:0] early_rom_cnt = 0;
wire cs_early_rom = early_rom_cnt < 4;
always_ff @(posedge clk) begin
// first 4 memory accesses must be mapped to ROM
if (reset) begin
early_rom_cnt <= 0;
end else if (cs && cpu_uds && cs_early_rom && cpu_bus_ack) begin
early_rom_cnt <= early_rom_cnt + 1;
end
end
wire [23:0] cpu_addressb = {cpu_address[23:1], 1'b0};
// implementation of memory map according to MCD212 datasheet
wire cs_ram = cpu_addressb < 24'h400000 && cs && !cs_early_rom; // 4MB
wire cs_rom = cpu_addressb >= 24'h400000 && cpu_addressb < 24'h4ffc00 && cs;
wire cs_system_io = cpu_addressb >= 24'h4ffc00 && cpu_addressb < 24'h4fffe0 && cs;
wire cs_channel2 = cpu_addressb >= 24'h4fffe0 && cpu_addressb < 24'h4ffff0 && cs;
wire cs_channel1 = cpu_addressb >= 24'h4ffff0 && cpu_addressb < 24'h500000 && cs;
wire cs_rom_fused = (cs_rom || cs_early_rom) && cs;
// Bit 18 is the Bank selection for TD=0
// CAS1 if A18=0, CAS2 if A18=1
wire [19:1] ram_address = {cpu_address[21], cpu_address[18], cpu_address[17:1]};
bit sdram_busy_q = 0;
// Is 1 for non interlaced
// and 1 for odd and 0 for even in interlaced mode
// This matches the description in the MCD212 datasheet
wire vt_field_parity /*verilator public_flat_rd*/;
// According to Kitrinx on Discord
// 0 for non interlaced (like in the MiSTer template)
// 0 for odd, 1 for even
// Invert vt_field_parity to achieve this
assign vga_f1 = command_register_dcr1.sm ? !vt_field_parity : 0;
// Expected to be 1 for non interlaced
// and 1 for odd and 0 for even in interlaced mode.
// At least according to the datasheet
// TODO A hack is applied here
// Even and odd fields are swapped to revert a hack in video timing
// to fix the flickering OSD
wire ica_parity /*verilator public_flat_rd*/ = !command_register_dcr1.sm | vt_field_parity;
// Always switches between 1 and 0.
// If interlacing is enabled, it is consistent with the real parity
wire fake_parity;
bit [21:0] ica0_adr;
bit ica0_as;
bit ica0_bus_ack;
bit ica0_burstdata_valid;
bit [21:0] file0_adr;
bit file0_as;
bit file0_bus_ack;
bit file0_burstdata_valid;
bit [21:0] ica1_adr;
bit ica1_as;
bit ica1_bus_ack;
bit ica1_burstdata_valid;
bit [21:0] file1_adr;
bit file1_as;
bit file1_bus_ack;
bit file1_burstdata_valid;
wire cpu_sdram_access = (cs_ram || cs_rom_fused || dvc_ram_cs || dvc_rom_cs) && (cpu_uds || cpu_lds);
wire sdram_refresh_request;
bit cpu_starve;
typedef enum bit [3:0] {
REFRESH, // most important
ICA_DCA0,
ICA_DCA1,
FILE0,
FILE1,
CPU_STARVE,
CPU // least important
} e_arbit_target;
// must only be changed if sdram_busy == false
e_arbit_target sdram_owner;
e_arbit_target sdram_owner_q;
e_arbit_target sdram_owner_next;
`ifdef VERILATOR
// add a check to assert that sdram_owner is never changed during memory access
always_ff @(posedge clk) begin
if (sdram_busy) assert (sdram_owner_q == sdram_owner);
end
`endif
always_comb begin
sdram_owner_next = CPU;
if (cpu_starve && cs_ram) sdram_owner_next = CPU_STARVE;
if (file1_as) sdram_owner_next = FILE1;
if (file0_as) sdram_owner_next = FILE0;
if (ica1_as) sdram_owner_next = ICA_DCA1;
if (ica0_as) sdram_owner_next = ICA_DCA0;
if (sdram_refresh_request) sdram_owner_next = REFRESH;
if (sdram_busy) sdram_owner = sdram_owner_q;
else sdram_owner = sdram_owner_next;
end
bit video_fail = 0;
struct packed {
bit [7:0] reserved3;
bit da; // display active
bit reserved2;
bit pa; // parity, 1 odd, 0 even frame
bit [4:0] reserved1;
} status_register1;
struct packed {
bit [7:0] reserved3;
bit [4:0] reserved;
bit it1; // Channel 0 Interrupt Pending
bit it2; // Channel 1 Interrupt Pending
bit be;
} status_register2;
// according to chapter 3.8 INTERRUPT GENERATION
assign irq = (status_register2.it1 && !control_register_crsr1w.di1) ||
(status_register2.it2 && !control_register_crsr2w.di2);
`ifdef VERILATOR
bit irq_q = 0;
always_ff @(posedge clk) begin
irq_q <= irq;
if (irq && !irq_q) $display("VIDEO IRQ 1 at line %d", video_y);
if (!irq && irq_q) $display("VIDEO IRQ 0");
end
`endif
always_comb begin
status_register1 = 0;
status_register1.da = !vblank;
// TODO This might not be accurate
status_register1.pa = fake_parity;
end
always_comb begin
cpu_dout = 0;
sdram_burst = 0;
sdram_refresh = 0;
// per default everything is acked but not SDRAM
cpu_bus_ack = !cpu_sdram_access;
file0_bus_ack = 0;
file0_burstdata_valid = 0;
ica0_bus_ack = 0;
ica0_burstdata_valid = 0;
file1_bus_ack = 0;
file1_burstdata_valid = 0;
ica1_bus_ack = 0;
ica1_burstdata_valid = 0;
sdram_rd = 0;
sdram_wr = 0;
sdram_word = 0;
sdram_addr = 0;
if (!reset) begin
case (sdram_owner)
REFRESH: begin
// keep reading the same address to force auto refresh
sdram_refresh = 1;
sdram_rd = !sdram_busy;
end
ICA_DCA0: begin
sdram_word = 1;
if (ica0_as) begin
sdram_addr[19:1] = ica0_adr[19:1];
sdram_rd = !sdram_busy && !sdram_busy_q;
sdram_burst = 1;
end
end
FILE0: begin
sdram_word = 1;
if (file0_as) begin
sdram_addr[19:1] = file0_adr[19:1];
sdram_rd = !sdram_busy && !sdram_busy_q;
sdram_burst = 1;
end
end
ICA_DCA1: begin
sdram_word = 1;
if (ica1_as) begin
sdram_addr[18:1] = ica1_adr[18:1];
sdram_addr[19] = 1; // TODO investigate
sdram_rd = !sdram_busy && !sdram_busy_q;
sdram_burst = 1;
end
end
FILE1: begin
sdram_word = 1;
if (file1_as) begin
sdram_addr[18:1] = file1_adr[18:1];
sdram_addr[19] = 1; // TODO investigate
sdram_rd = !sdram_busy && !sdram_busy_q;
sdram_burst = 1;
end
end
CPU: begin
if (cpu_sdram_access) begin
if (cs_rom_fused) begin
sdram_word = 1;
// map 0x400000 to 0x0400000
sdram_addr[24:1] = {3'b001, cpu_address[21:1]};
sdram_addr[0] = 1'b0;
sdram_rd = !sdram_busy && !sdram_busy_q;
sdram_wr = 1'b0;
end else if (cs_ram) begin
// map 0x000000 to 0x000000
// map 0x200000 to 0x080000
sdram_addr[24:1] = {5'b0, ram_address[19:1]};
sdram_addr[0] = cpu_write_strobe ? !cpu_lds && cpu_uds : 1'b0; // only active on odd byte accesses
sdram_word = (cpu_lds && cpu_uds) || !cpu_write_strobe;
sdram_rd = !cpu_write_strobe && !sdram_busy && !sdram_busy_q;
sdram_wr = cpu_write_strobe && !sdram_busy && !sdram_busy_q;
end else if (dvc_ram_cs) begin
// map 0xd00000 to 0x100000
sdram_addr[24:1] = {1'b0, cpu_address[23:20] - 4'hc, cpu_address[19:1]};
sdram_addr[0] = cpu_write_strobe ? !cpu_lds && cpu_uds : 1'b0; // only active on odd byte accesses
sdram_word = (cpu_lds && cpu_uds) || !cpu_write_strobe;
sdram_rd = !cpu_write_strobe && !sdram_busy && !sdram_busy_q;
sdram_wr = cpu_write_strobe && !sdram_busy && !sdram_busy_q;
end else if (dvc_rom_cs) begin
sdram_word = 1;
// map 0xe40000 to 0x0480000
// also map 0xe60000 to 0x0480000 (bit 17 must be ignored)
sdram_addr[24:1] = {8'b00100100, cpu_address[16:1]};
sdram_addr[0] = 1'b0;
sdram_rd = !sdram_busy && !sdram_busy_q;
sdram_wr = 1'b0;
end
end
end
default: begin
end
endcase
case (sdram_owner_q)
CPU: begin
if (cpu_sdram_access) begin
cpu_bus_ack = !sdram_busy && sdram_busy_q;
end
end
FILE0: begin
if (file0_as) begin
file0_bus_ack = !sdram_busy && sdram_busy_q;
file0_burstdata_valid = sdram_burstdata_valid;
end
end
ICA_DCA0: begin
if (ica0_as) begin
ica0_bus_ack = !sdram_busy && sdram_busy_q;
ica0_burstdata_valid = sdram_burstdata_valid;
end
end
FILE1: begin
if (file1_as) begin
file1_bus_ack = !sdram_busy && sdram_busy_q;
file1_burstdata_valid = sdram_burstdata_valid;
end
end
ICA_DCA1: begin
if (ica1_as) begin
ica1_bus_ack = !sdram_busy && sdram_busy_q;
ica1_burstdata_valid = sdram_burstdata_valid;
end
end
default: begin
end
endcase
end
// only the CPU writes to SDRAM
sdram_din = cpu_din;
if (cs_ram || dvc_ram_cs || dvc_rom_cs || cs_rom_fused) begin
cpu_dout = sdram_dout;
end else if (cs_channel1) begin
case (cpu_addressb[7:0])
8'hf0: begin
cpu_dout = status_register1;
end
default: cpu_dout = 16'h0;
endcase
end else if (cs_channel2) begin
case (cpu_addressb[7:0])
8'he0: begin
cpu_dout = status_register2;
end
default: cpu_dout = 16'h0;
endcase
end
end
always_ff @(posedge clk) begin
if (reset) begin
sdram_busy_q <= 0;
sdram_owner_q <= CPU;
video_fail <= 0;
end else begin
sdram_busy_q <= sdram_busy;
sdram_owner_q <= sdram_owner;
if (file0_as && ica0_as) video_fail <= 1;
//if (file1_as && ica1_as) video_fail <= 1;
// Ensure ICA list has ended before FILE access
//assert (!(file0_as && ica0_as));
//assert (!(file1_as && ica1_as));
end
/*
if ((cpu_lds || cpu_uds) && cs_ram && !cpu_write_strobe && cpu_bus_ack)
$display("Read DRAM %x %x", cpu_addressb, cpu_dout);
if (cpu_lds && cpu_uds && cs_ram && cpu_write_strobe) begin
$display("Write DRAM %x %x", cpu_addressb, cpu_din);
assert (!(cpu_addressb==0 && cpu_din ==16'h5aa5));
end else if (cpu_lds && cs_ram && cpu_write_strobe)
$display("Write Lower Byte RAM %x %x", cpu_addressb, cpu_din);
else if (cpu_uds && cs_ram && cpu_write_strobe)
$display("Write Upper Byte RAM %x %x", cpu_addressb, cpu_din);
if ((cpu_lds || cpu_uds) && cs_channel1 && !cpu_write_strobe)
$display("Read Channel 1 %x %x", cpu_addressb, cpu_dout);
if ((cpu_lds || cpu_uds) && cs_channel2 && !cpu_write_strobe)
$display("Read Channel 2 %x %x", cpu_addressb, cpu_dout);
if ((cpu_lds || cpu_uds) && cs_system_io && cpu_write_strobe)
$display("Write Sys %x %x", cpu_addressb, cpu_din);
*/
if ((cpu_lds || cpu_uds) && cs_channel1 && cpu_write_strobe)
$display("Write Channel 1 %x %x", cpu_addressb, cpu_din);
if ((cpu_lds || cpu_uds) && cs_channel2 && cpu_write_strobe)
$display("Write Channel 2 %x %x", cpu_addressb, cpu_din);
end
wire [8:0] video_y;
wire [12:0] video_x;
bit new_frame /*verilator public_flat_rd*/;
wire new_line /*verilator public_flat_rd*/;
wire new_pixel_lores;
wire new_pixel_hires;
bit hblank_vt;
bit hblank_vt_q;
bit hblank_vt_q2;
bit vblank_latched;
bit vblank_q;
always_ff @(posedge clk) begin
new_frame <= 0;
vblank_q <= vblank;
// VBlank has started? Keep track of that
if (vblank && !vblank_q) vblank_latched <= 1;
// When VBlank has started and DCA has finished, begin with ICA immediately
if (vblank_latched && !ica0_as && !ica1_as) begin
vblank_latched <= 0;
new_frame <= 1;
end
end
// we should have 8-9 refresh cycles per horizontal line
// to fulfill 8192 refreshes per 64ms
assign sdram_refresh_request = video_x < 190 && video_x > 130;
always_comb begin
cpu_starve = 0;
if (!disable_cpu_starve) begin
if (vblank) cpu_starve = video_x > 1200;
else cpu_starve = video_x > 1200;
end
end
video_timing vt (
.clk,
.reset,
.fake_parity(fake_parity),
.parity(vt_field_parity),
.sm(command_register_dcr1.sm),
.cf(1), // TODO CF=1 Only accept TV resolutions for now
.st(control_register_crsr1w.st),
.cm(0), // TODO Correct source
.fd(command_register_dcr1.fd),
.video_y(video_y),
.video_x(video_x),
.hsync(hsync),
.vsync(vsync),
.hblank(hblank_vt),
.vblank(vblank),
.new_line(new_line)
);
struct packed {
bit de;
bit cf; // 0=28MHz, 1=30 MHz in PAL and 30.2097 MHz in NTSC
bit fd; // 0=50 Hz, 1=60 Hz
bit sm; // 0=Non-interlace, 1=Interlace
bit cm1; // Probably ignored..
bit reserved1;
bit ic1;
bit dc1;
bit [1:0] reserved2;
bit [5:0] adr;
} command_register_dcr1 /*verilator public_flat_rw*/;
struct packed {
bit [3:0] reserved1;
bit cm2; // Pixel frequency
bit reserved2;
bit ic2;
bit dc2;
bit [1:0] reserved3;
bit [5:0] adr;
} command_register_dcr2;
typedef struct packed {
mosaic_factor_e mf;
file_type_e ft;
bit [5:0] adr;
} s_display_decoder_register_t;
s_display_decoder_register_t display_decoder_register_ddr1;
s_display_decoder_register_t display_decoder_register_ddr2;
struct packed {
bit di1;
bit st;
} control_register_crsr1w /*verilator public_flat_rw*/;
struct packed {bit di2;} control_register_crsr2w;
display_parameters_s ica0_disp_param_out;
display_parameters_s ica1_disp_param_out;
always_ff @(posedge clk) begin
if (reset) begin
command_register_dcr1 <= 0;
command_register_dcr2 <= 0;
control_register_crsr1w <= 0;
control_register_crsr2w <= 0;
display_decoder_register_ddr1 <= 0;
display_decoder_register_ddr2 <= 0;
status_register2 <= 0;
end else begin
if (ica0_disp_param_out.strobe) begin
display_decoder_register_ddr1.mf <= ica0_disp_param_out.mf;
display_decoder_register_ddr1.ft <= ica0_disp_param_out.ft;
command_register_dcr1.cm1 <= ica0_disp_param_out.cm;
end
if (ica1_disp_param_out.strobe) begin
display_decoder_register_ddr2.mf <= ica1_disp_param_out.mf;
display_decoder_register_ddr2.ft <= ica1_disp_param_out.ft;
command_register_dcr2.cm2 <= ica1_disp_param_out.cm;
end
if ((cpu_lds || cpu_uds) && cs_channel1 && cpu_write_strobe) begin
if (cpu_addressb[3:0] == 4'h2) begin
command_register_dcr1 <= cpu_din;
$display("DCR1 set to %x", cpu_din);
assert (cpu_lds && cpu_uds);
end
if (cpu_addressb[3:0] == 4'h0 && cpu_uds) begin
control_register_crsr1w.di1 <= cpu_din[15];
assert (cpu_lds && cpu_uds);
end
if (cpu_addressb[3:0] == 4'h0 && cpu_lds) begin
control_register_crsr1w.st <= cpu_din[1];
assert (cpu_lds && cpu_uds);
end
end
if ((cpu_lds || cpu_uds) && cs_channel2 && cpu_write_strobe) begin
if (cpu_addressb[3:0] == 4'h2) begin
command_register_dcr2 <= cpu_din;
end
if (cpu_addressb[3:0] == 4'h0) begin
control_register_crsr2w.di2 <= cpu_din[15];
end
end
if (cpu_lds && cs_channel2 && !cpu_write_strobe && cpu_addressb[3:0] == 0) begin
status_register2 <= 0;
end
if (ica0_irq) status_register2.it1 <= 1;
if (ica1_irq) status_register2.it2 <= 1;
end
end
typedef struct packed {
bit [3:0] op; // Operation Control Code
bit rf; // Region Flag
bit [5:0] wf; // Next Weight Factor
bit [9:0] x; // Distance from lefthand edge of display, double resolution
} region_entry;
region_entry region_control[8];
clut_entry_s clut_out0;
clut_entry_s clut_out1;
clut_entry_s clut_wr_data0;
clut_entry_s clut_wr_data1;
bit [7:0] clut_wr_addr0;
bit [7:0] clut_wr_addr1;
bit [7:0] clut_addr0;
bit [7:0] clut_addr1;
bit clut_we0;
bit clut_we1;
clut_dual_port_memory clutmem (
.clk(clk),
.data_a(clut_wr_data0),
.addr_a(clut_addr0),
.we_a(clut_we0),
.q_a(clut_out0),
.data_b(clut_wr_data1),
.addr_b(clut_addr1),
.we_b(clut_we1),
.q_b(clut_out1)
);
clut_entry_s trans_color_plane_a;
clut_entry_s trans_color_plane_b;
clut_entry_s mask_color_plane_a;
clut_entry_s mask_color_plane_b;
wire [15:0] ica0_din = sdram_dout;
wire [15:0] ica1_din = sdram_dout;
bit [6:0] ch0_register_adr;
bit [23:0] ch0_register_data;
bit ch0_register_write;
bit ica0_reload_vsr;
bit [21:0] ica0_vsr;
bit ica0_irq;
bit [6:0] ch1_register_adr;
bit [23:0] ch1_register_data;
bit ch1_register_write;
bit ica1_reload_vsr;
bit [21:0] ica1_vsr;
bit ica1_irq;
wire dca0_read = hblank_vt && !hblank_vt_q && !vblank && command_register_dcr1.dc1;
ica_dca_ctrl #(
.unit_index(0)
) ica0 (
.clk,
.reset(new_frame || reset || !command_register_dcr1.ic1),
.address(ica0_adr),
.as(ica0_as),
.din(ica0_din),
.bus_ack(ica0_bus_ack),
.burstdata_valid(ica0_burstdata_valid),
.register_adr(ch0_register_adr),
.register_data(ch0_register_data),
.register_write(ch0_register_write),
.reload_vsr(ica0_reload_vsr),
.vsr(ica0_vsr),
.irq(ica0_irq),
.disp_params(ica0_disp_param_out),
// Fire at end of visible lines
.dca_read(dca0_read),
.hblank(hblank),
.parity(ica_parity)
);
wire dca1_read = hblank_vt && !hblank_vt_q && !vblank && command_register_dcr2.dc2;
ica_dca_ctrl #(
.unit_index(1)
) ica1 (
.clk,
.reset(new_frame || reset || !command_register_dcr2.ic2),
.address(ica1_adr),
.as(ica1_as),
.din(ica1_din),
.bus_ack(ica1_bus_ack),
.burstdata_valid(ica1_burstdata_valid),
.register_adr(ch1_register_adr),
.register_data(ch1_register_data),
.register_write(ch1_register_write),
.reload_vsr(ica1_reload_vsr),
.vsr(ica1_vsr),
.irq(ica1_irq),
.disp_params(ica1_disp_param_out),
// Fire at end of visible lines
.dca_read(dca1_read),
.hblank(hblank),
.parity(ica_parity)
);
wire [15:0] file0_din = sdram_dout;
wire [15:0] file1_din = sdram_dout;
always_ff @(posedge clk) begin
if (reset) begin
hblank_vt_q <= 0;
hblank_vt_q2 <= 0;
hblank <= 0;
end else begin
hblank_vt_q <= hblank_vt;
hblank_vt_q2 <= hblank_vt_q;
hblank <= hblank_vt_q2;
end
end
pixelstream file0_out (.clk);
// Plane A Display File Decoder
display_file_reader #(
.unit_index(0)
) file0 (
.clk,
.reset(new_frame || reset || !command_register_dcr1.ic1),
.st(control_register_crsr1w.st),
.hsync(hsync),
.address(file0_adr),
.as(file0_as),
.din(file0_din),
.bus_ack(file0_bus_ack),
.reload_vsr(ica0_reload_vsr),
.burstdata_valid(file0_burstdata_valid),
.vsr_in(ica0_vsr),
.out(file0_out),
.read_pixels(!vblank)
);
pixelstream file1_out (.clk);
display_file_reader #(
.unit_index(1)
) file1 (
.clk,
.reset(new_frame || reset || !command_register_dcr2.ic2),
.st(control_register_crsr1w.st),
.hsync(hsync),
.address(file1_adr),
.as(file1_as),
.din(file1_din),
.bus_ack(file1_bus_ack),
.reload_vsr(ica1_reload_vsr),
.burstdata_valid(file1_burstdata_valid),
.vsr_in(ica1_vsr),
.out(file1_out),
.read_pixels(!vblank)
);
pixelstream dyuv0_in (.clk);
pixelstream dyuv1_in (.clk);
pixelstream rle0_in (.clk);
pixelstream rle1_in (.clk);
pixelstream_demux demux0 (
.use_b(plane_a_dyuv_active),
.in(file0_out),
.b(dyuv0_in),
.a(rle0_in)
);
pixelstream_demux demux1 (
.use_b(plane_b_dyuv_active),
.in(file1_out),
.b(dyuv1_in),
.a(rle1_in)
);
pixelstream rle0_out (.clk);
pixelstream rle1_out (.clk);
clut_rle rle0 (
.clk,
.reset(vblank || reset || ica0_reload_vsr),
.st(control_register_crsr1w.st),
.src(rle0_in),
.dst(rle0_out),
.ft(display_decoder_register_ddr1.ft),
.mf(display_decoder_register_ddr1.mf)
);
clut_rle rle1 (
.clk,
.reset(vblank || reset || ica1_reload_vsr),
.st(control_register_crsr1w.st),
.src(rle1_in),
.dst(rle1_out),
.ft(display_decoder_register_ddr2.ft),
.mf(display_decoder_register_ddr1.mf)
);
rgb888_s dyuv0_out;
rgb888_s dyuv1_out;
yuv_s dyuv0_abs_start;
yuv_s dyuv1_abs_start;
wire dyuv0_write;
wire dyuv0_strobe;
wire dyuv1_write;
wire dyuv1_strobe;
delta_yuv_decoder dyuv0 (
.clk,
.reset(reset || ica0_reload_vsr || vblank || hsync),
.st(control_register_crsr1w.st),
.absolute_start_yuv(dyuv0_abs_start),
.src(dyuv0_in),
.rgb_out(dyuv0_out),
.write(dyuv0_write),
.strobe(dyuv0_strobe)
);
delta_yuv_decoder dyuv1 (
.clk,
.reset(reset || ica1_reload_vsr || vblank || hsync),
.st(control_register_crsr1w.st),
.absolute_start_yuv(dyuv1_abs_start),
.src(dyuv1_in),
.rgb_out(dyuv1_out),
.write(dyuv1_write),
.strobe(dyuv1_strobe)
);
assign rle0_out.strobe = new_pixel_lores;
assign rle1_out.strobe = new_pixel_lores;
assign dyuv0_strobe = new_pixel_lores;
assign dyuv1_strobe = new_pixel_lores;
bit [7:0] synchronized_pixel0;
bit [7:0] synchronized_pixel1;
bit fail = 0;
always_comb begin
// CLUT7
clut_addr0 = {1'b0, synchronized_pixel0[6:0]};
// CLUT8 (only on Plane A)
if (image_coding_method_register.cm13_10_planea == 4'b0001)
clut_addr0 = synchronized_pixel0;
// CLUT4
if (image_coding_method_register.cm13_10_planea == 4'b1011)
clut_addr0 = {4'b0000, synchronized_pixel0[7:4]};
if (clut_we0) clut_addr0 = clut_wr_addr0;
end
always_comb begin
// Setting highest bit according to Figure 7-2 for CLUT7
clut_addr1 = {1'b1, synchronized_pixel1[6:0]};
// CLUT4
if (image_coding_method_register.cm23_20_planeb == 4'b1011)
clut_addr1 = {4'b1000, synchronized_pixel1[7:4]};
if (clut_we1) clut_addr1 = clut_wr_addr1;
end
typedef struct packed {
bit en; //1 = mosaic on
bit [22:8] reserved;
bit [7:0] z; // 1 normal, 2-255 mosaic effect
} mosaic_pixel_hold_factor_register_t;
mosaic_pixel_hold_factor_register_t mosaic_pixel_hold_factor_register_plane_a;
mosaic_pixel_hold_factor_register_t mosaic_pixel_hold_factor_register_plane_b;
bit [7:0] pixel_hold_cnt_a;
bit [7:0] pixel_hold_cnt_b;
bit pixel_hold_cnt_latch_a;
bit pixel_hold_cnt_latch_b;
always_ff @(posedge clk) begin
rgb555 <= {synchronized_pixel0, synchronized_pixel1};
plane_a_visible_q <= plane_a_visible;
plane_b_visible_q <= plane_b_visible;
pixel_hold_cnt_latch_a <= pixel_hold_cnt_a == 0 || !mosaic_pixel_hold_factor_register_plane_a.en;
pixel_hold_cnt_latch_b <= pixel_hold_cnt_b == 0 || !mosaic_pixel_hold_factor_register_plane_b.en;
if (new_line) begin
synchronized_pixel0 <= 0;
synchronized_pixel1 <= 0;
pixel_hold_cnt_a <= 0;
pixel_hold_cnt_b <= 0;
end else begin
if (pixel_hold_cnt_latch_a) plane_a_q <= plane_a;
if (pixel_hold_cnt_latch_b) plane_b_q <= plane_b;
if (new_pixel_plane_a && !hblank_vt_q2) begin
if (pixel_hold_cnt_a >= mosaic_pixel_hold_factor_register_plane_a.z - 1)
pixel_hold_cnt_a <= 0;
else pixel_hold_cnt_a <= pixel_hold_cnt_a + 1;
end
if (new_pixel_plane_b && !hblank_vt_q2) begin
if (pixel_hold_cnt_b >= mosaic_pixel_hold_factor_register_plane_b.z - 1)
pixel_hold_cnt_b <= 0;
else pixel_hold_cnt_b <= pixel_hold_cnt_b + 1;
end
if (rle0_out.write && rle0_out.strobe) synchronized_pixel0 <= rle0_out.pixel;
if (rle1_out.write && rle1_out.strobe) synchronized_pixel1 <= rle1_out.pixel;
// Implement CLUT4 shifting
if (image_coding_method_register.cm13_10_planea == 4'b1011 && new_pixel_hires && !new_pixel_lores)
synchronized_pixel0[7:4] <= {
// Bit 3 must be forced to 0, when RLE is active
synchronized_pixel0[3] && display_decoder_register_ddr1.ft != kRunLength,
synchronized_pixel0[2:0]
};
if (image_coding_method_register.cm23_20_planeb == 4'b1011 && new_pixel_hires && !new_pixel_lores)
synchronized_pixel1[7:4] <= {
// Bit 3 must be forced to 0, when RLE is active
synchronized_pixel1[3] && display_decoder_register_ddr2.ft != kRunLength,
synchronized_pixel1[2:0]
};
end
end
wire new_pixel_plane_a = image_coding_method_register.cm13_10_planea == 4'b1011 ? new_pixel_hires : new_pixel_lores;
wire new_pixel_plane_b = image_coding_method_register.cm23_20_planeb == 4'b1011 ? new_pixel_hires : new_pixel_lores;
always_ff @(posedge clk) begin
if (ica0_reload_vsr) `dp_vsr(("Reload VSR0 %x", ica0_vsr));
if (ica1_reload_vsr) `dp_vsr(("Reload VSR1 %x", ica1_vsr));
if (dca0_read) `dp_dcaptr(("Start DCA0 on line %d", video_y));
if (dca1_read) `dp_dcaptr(("Start DCA1 on line %d", video_y));
end
bit [15:0] cursor[16];
bit [1:0] clut_bank0;
// Channel 1 can only write to banks 2 and 3 according to chapter 7.2 CLUT DECODER
// According to 5.4.4.5 CLUT Bank Register, A7 is forced 1
bit clut_bank1; // Highest bit is always 1
struct packed {
bit [1:0] reserved1;
bit [9:0] y;
bit [1:0] reserved2;
bit [9:0] x;
} cursor_position_reg;
struct packed {
bit en;
bit blkc; // blink type
bit [2:0] con;
bit [2:0] cof;
bit cuw;
bit [10:0] reserved1;
bit y; // 1 full brightness, 0 half brightness
bit r;
bit g;
bit b;
} cursor_control_register;
struct packed {
bit y; // 1 full brightness, 0 half brightness
bit r;
bit g;
bit b;
} cursor_color;
struct packed {
bit y; // 1 full brightness, 0 half brightness
bit r;
bit g;
bit b;
} backdrop_color_register;
struct packed {
bit cs; // CLUT select for dual 7-bit CLUT
bit nr; // 0 One region flag, 1 Two region flags used
bit ev; // External video enable
bit [3:0] cm23_20_planeb;
bit [3:0] cm13_10_planea;
} image_coding_method_register;
struct packed {
bit mx;
bit [3:0] tb;
bit [3:0] ta;
} transparency_control_register;
bit [5:0] weight_a;
bit [5:0] weight_b;
// Y Position of active pixels. Used for cursor sprite
bit [9:0] active_line = 0;
// Always high resolution pixels, used for Region effects
bit [9:0] active_pixel = 0;
bit [15:0] active_cursor_line;
bit cursor_pixel;
bit inside_cursor_window;
bit plane_b_in_front_of_a;
// Counts 0 to 11 to keep track of 12 frame periods
bit [3:0] cursor_blink_framecnt;
// Counts 12 frame periods the cursor is in the current state
bit [2:0] cursor_blink_periodcnt;
// If 1, then the cursor is either blinked off or complementary
// If 0, the cursor is shown like normal
bit cursor_blink_state;
localparam CURSOR_BLINK_PERIOD = 12;
bit [1:0] subpixelcnt;
assign new_pixel_lores = subpixelcnt[1:0] == 0 && !hblank_vt && !vblank;
assign new_pixel_hires = subpixelcnt[0] == 0 && !hblank_vt && !vblank;
bit inside_cursor_window_q;
bit cursor_pixel_q;
// mouse cursor
always_ff @(posedge clk) begin
if (hblank_vt) subpixelcnt <= 0;
else subpixelcnt <= subpixelcnt + 1;
if (hblank_vt_q) active_pixel <= 0;
else if (new_pixel_hires) active_pixel <= active_pixel + 1;
if (vblank) active_line <= 0;
else if (new_line) active_line <= active_line + 1;
active_cursor_line <= cursor[4'(active_line-cursor_position_reg.y)];
inside_cursor_window_q <= inside_cursor_window;
cursor_pixel_q <= cursor_pixel;
if (cursor_control_register.cuw) begin
// Double Resolution
inside_cursor_window <= (active_line >= cursor_position_reg.y) && (active_line < cursor_position_reg.y + 16) &&
(active_pixel >= cursor_position_reg.x) && (active_pixel < cursor_position_reg.x + 16);
cursor_pixel <= active_cursor_line[4'((cursor_position_reg.x-active_pixel-10'd1))];
end else begin
// Normal Resolution - double the width
inside_cursor_window <= (active_line >= cursor_position_reg.y) && (active_line < cursor_position_reg.y + 16) &&
(active_pixel >= cursor_position_reg.x) && (active_pixel < cursor_position_reg.x + 32);
cursor_pixel <= active_cursor_line[4'((cursor_position_reg.x-active_pixel-10'd1)>>1)];
end
if (new_frame) begin
if (cursor_control_register.cof == 0 || cursor_control_register.con == 0) begin
// Handle COF=0 or CON=0 as reset condition
cursor_blink_framecnt <= 0;
cursor_blink_state <= 0;
cursor_blink_periodcnt <= 0;
end else if (cursor_blink_framecnt == CURSOR_BLINK_PERIOD - 1) begin
cursor_blink_framecnt <= 0;
if (cursor_blink_periodcnt == cursor_control_register.cof - 1 && cursor_blink_state) begin
cursor_blink_state <= 0;
cursor_blink_periodcnt <= 0;
end else if (cursor_blink_periodcnt == cursor_control_register.con - 1 && !cursor_blink_state) begin
cursor_blink_state <= 1;
cursor_blink_periodcnt <= 0;
end else begin
cursor_blink_periodcnt <= cursor_blink_periodcnt + 1;
end
end else begin
cursor_blink_framecnt <= cursor_blink_framecnt + 1;
end
end
// If blink type is on/off, force cursor_pixel to 0
if (!cursor_control_register.blkc && cursor_blink_state) cursor_pixel <= 0;
end
// Cursor complementary colors
always_comb begin
cursor_color.y = cursor_control_register.y;
cursor_color.r = cursor_control_register.r ^ cursor_blink_state;
cursor_color.g = cursor_control_register.g ^ cursor_blink_state;
cursor_color.b = cursor_control_register.b ^ cursor_blink_state;
end
// color mixing
rgb888_s plane_a;
rgb888_s plane_b;
rgb888_s plane_a_q;
rgb888_s plane_b_q;
rgb555_s rgb555;
function clut_entry_s RGB888ToClut(input rgb888_s rgb);
RGB888ToClut.r = rgb.r[7:2];
RGB888ToClut.g = rgb.g[7:2];
RGB888ToClut.b = rgb.b[7:2];
endfunction
bit plane_a_visible;
bit plane_b_visible;
bit plane_a_visible_q;
bit plane_b_visible_q;
bit [1:0] region_flags = 0;
bit plane_a_dyuv_active;
bit plane_b_dyuv_active;
always_ff @(posedge clk) begin
if (reset) begin
plane_a_dyuv_active <= 0;
plane_b_dyuv_active <= 0;
end else begin
plane_a_dyuv_active <= image_coding_method_register.cm13_10_planea == 4'b0101;
plane_b_dyuv_active <= image_coding_method_register.cm23_20_planeb == 4'b0101;
end
end
// Ignore Color Key for DYUV. Use it only for CLUT!
wire plane_a_color_key_match = (clut_out0 == trans_color_plane_a) && !plane_a_dyuv_active && (image_coding_method_register.cm13_10_planea != 0);
wire plane_b_color_key_match = (clut_out1 == trans_color_plane_b) && !plane_b_dyuv_active && (image_coding_method_register.cm23_20_planeb != 0);
function automatic [7:0] WeightCalc(input [7:0] rgb, input [5:0] weight);
if (weight == 0) begin
WeightCalc = 0;
end else begin
WeightCalc = 8'((15'(rgb) * (15'(weight) + 15'd1)) >> 6);
end
endfunction
always_comb begin
bit plane_a_transparent; // Because logic in datasheet is also inverted
bit [7:0] r, g, b;
plane_a_transparent = 1;
r = {clut_out0.r, 2'b00};
g = {clut_out0.g, 2'b00};
b = {clut_out0.b, 2'b00};
if (plane_a_dyuv_active) begin
r = dyuv0_out.r;
g = dyuv0_out.g;
b = dyuv0_out.b;
end
if (image_coding_method_register.cm13_10_planea != 0) begin
plane_a.r = WeightCalc(r, weight_a);
plane_a.g = WeightCalc(g, weight_a);
plane_a.b = WeightCalc(b, weight_a);
end else begin
// According to 8.1 PLANES, OFF is black level of 16
// On a real CD-i it is much blacker than 16. I assume 0
plane_a.r = 0;
plane_a.g = 0;
plane_a.b = 0;
end
if (command_register_dcr1.ic1) begin
// Use only the lower 3 bits first as the highest bit just inverts the result
assert (transparency_control_register.ta[2:0] != 3'b010); // Transparency Bit?
assert (transparency_control_register.ta[2:0] != 3'b111); // N.U.
case (transparency_control_register.ta[2:0])
// Always (Plane Disabled)
3'b000: plane_a_transparent = 1;
// Color Key = True
3'b001: plane_a_transparent = plane_a_color_key_match;
// Region Flag 0
3'b011: plane_a_transparent = region_flags[0];
// Region Flag 1
3'b100: plane_a_transparent = region_flags[1];
// Region Flag 0 or Color Key = True
3'b101: plane_a_transparent = plane_a_color_key_match || region_flags[0];
// Region Flag 1 or Color Key = True
3'b110: plane_a_transparent = plane_a_color_key_match || region_flags[1];
// N.U.
default: plane_a_transparent = 0;
endcase
// Invert original result
if (transparency_control_register.ta[3]) plane_a_transparent = !plane_a_transparent;
// Jeopardy Hack for Plane A just in case it's used elsewhere
// This case is not defined in the datasheet
if (transparency_control_register.ta == 4'b1001 && plane_a_dyuv_active)
plane_a_transparent = 0;
end
plane_a_visible = !plane_a_transparent;
end
always_comb begin
bit plane_b_transparent; // Because logic in datasheet is also inverted
bit [7:0] r, g, b;
plane_b_transparent = 1;
r = {clut_out1.r, 2'b00};
g = {clut_out1.g, 2'b00};
b = {clut_out1.b, 2'b00};
if (plane_b_dyuv_active) begin
r = dyuv1_out.r;
g = dyuv1_out.g;
b = dyuv1_out.b;
end
if (image_coding_method_register.cm23_20_planeb == 4'b0001) begin // RGB555
r = {rgb555.r, 3'b000};
g = {rgb555.g, 3'b000};
b = {rgb555.b, 3'b000};
end
if (image_coding_method_register.cm23_20_planeb != 0) begin
plane_b.r = WeightCalc(r, weight_b);
plane_b.g = WeightCalc(g, weight_b);
plane_b.b = WeightCalc(b, weight_b);
end else begin
// According to 8.1 PLANES, OFF is black level of 16
// On a real CD-i it is much blacker than 16. I assume 0
plane_b.r = 0;
plane_b.g = 0;
plane_b.b = 0;
end
if (command_register_dcr2.ic2) begin
// Use only the lower 3 bits first as the highest bit just inverts the result
assert (transparency_control_register.tb[2:0] != 3'b010); // Transparency Bit?
assert (transparency_control_register.tb[2:0] != 3'b111); // N.U.
case (transparency_control_register.tb[2:0])
// Always (Plane Disabled)
3'b000: plane_b_transparent = 1;
// Color Key = True
3'b001: plane_b_transparent = plane_b_color_key_match;
// Region Flag 0
3'b011: plane_b_transparent = region_flags[0];
// Region Flag 1
3'b100: plane_b_transparent = region_flags[1];
// Region Flag 0 or Color Key = True
3'b101: plane_b_transparent = plane_b_color_key_match || region_flags[0];
// Region Flag 1 or Color Key = True
3'b110: plane_b_transparent = plane_b_color_key_match || region_flags[1];
// N.U.
default: plane_b_transparent = 0;
endcase
// Invert original result
if (transparency_control_register.tb[3]) plane_b_transparent = !plane_b_transparent;
// Jeopardy Hack
// This case is not defined in the datasheet
if (transparency_control_register.tb == 4'b1001 && plane_b_dyuv_active)
plane_b_transparent = 0;
end
plane_b_visible = !plane_b_transparent;
end
function automatic [7:0] clamped_mix(input [7:0] a, input [7:0] b);
bit [8:0] sum = a + b;
if (sum > 255) clamped_mix = 255;
else clamped_mix = sum[7:0];
endfunction
// Scales 16-235 to 0-255
function automatic [7:0] limited_to_full1(input [7:0] value);
if (value >= 235) limited_to_full1 = 255;
else if (value <= 16) limited_to_full1 = 0;
else limited_to_full1 = (value - 16) * 37 / 32;
endfunction
// Scales 16-255 to 0-255
function automatic [7:0] limited_to_full2(input [7:0] value);
if (value <= 16) limited_to_full2 = 0;
else limited_to_full2 = (value - 16) * 34 / 32;
endfunction
always_comb begin
bit backdrop_pixel;
// start with the backdrop color
vidout.r = backdrop_color_register.r ? 8'hff : 0;
vidout.g = backdrop_color_register.g ? 8'hff : 0;
vidout.b = backdrop_color_register.b ? 8'hff : 0;
backdrop_pixel = (!plane_a_visible_q && !plane_b_visible_q);
if (!backdrop_color_register.y) begin
// Half brightness
vidout.r[7] = 0;
vidout.g[7] = 0;
vidout.b[7] = 0;
end
if (transparency_control_register.mx) begin
// No Mix. Only overlay
if (plane_b_in_front_of_a) begin
if (plane_a_visible_q) vidout = plane_a_q;
if (plane_b_visible_q) vidout = plane_b_q;
end else begin
if (plane_b_visible_q) vidout = plane_b_q;
if (plane_a_visible_q) vidout = plane_a_q;
end
end else begin
// Perform mixing
if (plane_a_visible_q && plane_b_visible_q) begin
vidout.r = clamped_mix(plane_a_q.r, plane_b_q.r);
vidout.g = clamped_mix(plane_a_q.g, plane_b_q.g);
vidout.b = clamped_mix(plane_a_q.b, plane_b_q.b);
end else if (plane_a_visible_q) begin
vidout = plane_a_q;
end else if (plane_b_visible_q) begin
vidout = plane_b_q;
end
end
if (debug_force_video_plane == 2'b01) vidout = plane_a_q;
else if (debug_force_video_plane == 2'b10) vidout = plane_b_q;
if (debug_limited_to_full == 1) begin
vidout.r = limited_to_full1(vidout.r);
vidout.g = limited_to_full1(vidout.g);
vidout.b = limited_to_full1(vidout.b);
end else if (debug_limited_to_full == 2) begin
vidout.r = limited_to_full2(vidout.r);
vidout.g = limited_to_full2(vidout.g);
vidout.b = limited_to_full2(vidout.b);
end
// cursor
if (cursor_pixel_q && inside_cursor_window_q && cursor_control_register.en) begin
backdrop_pixel = 0;
vidout.r = cursor_color.r ? 8'hff : 0;
vidout.g = cursor_color.g ? 8'hff : 0;
vidout.b = cursor_color.b ? 8'hff : 0;
if (!cursor_color.y) begin
// Half brightness
vidout.r[7] = 0;
vidout.g[7] = 0;
vidout.b[7] = 0;
end
end
vsd = backdrop_pixel && image_coding_method_register.ev;
`ifdef VERILATOR
//vsd = 1;
`endif
end
// Implementation of Table 5-13 Register Map
always_ff @(posedge clk) begin
if (reset) begin
clut_bank0 <= 0;
cursor_position_reg <= 0;
cursor_control_register <= 0;
backdrop_color_register <= 0;
image_coding_method_register <= 0;
transparency_control_register <= 0;
trans_color_plane_a <= 0;
mask_color_plane_a <= 0;
weight_a <= 0;
plane_b_in_front_of_a <= 0;
end else begin
if (ch0_register_write) begin
case (ch0_register_adr)
7'h40: begin
// Image Coding Method
`dp_raster(
(
"Line %3d Coding A:%b %s B:%b %s NR:%b CS:%b", video_y,
ch0_register_data[3:0], // Coding A
coding_method_name(ch0_register_data[3:0], 0), // Coding A as text
ch0_register_data[11:8], // Coding B
coding_method_name(ch0_register_data[11:8], 1), // Coding B as text
ch0_register_data[19], // Type of region flag handling
ch0_register_data[22])); //CLUT select for dual 7-bit CLUTs
image_coding_method_register.cs <= ch0_register_data[22];
image_coding_method_register.nr <= ch0_register_data[19];
image_coding_method_register.ev <= ch0_register_data[18];
image_coding_method_register.cm23_20_planeb <= ch0_register_data[11:8];
image_coding_method_register.cm13_10_planea <= ch0_register_data[3:0];
end
7'h41: begin
// Transparency Control
`dp_raster(
("Line %3d Transparency Control MX:%b TB:%b TA:%b", video_y,
ch0_register_data[23], ch0_register_data[11:8],
ch0_register_data[3:0]));
transparency_control_register.mx <= ch0_register_data[23];
transparency_control_register.tb <= ch0_register_data[11:8];
transparency_control_register.ta <= ch0_register_data[3:0];
// To find games which use these
assert (ch0_register_data[3:0] != 4'b0010);
assert (ch0_register_data[11:8] != 4'b0010);
end
7'h42: begin
// Plane Order
plane_b_in_front_of_a <= ch0_register_data[0];
if (ch0_register_data[0]) `dp_raster(("Plane B in front of Plane A"));
else `dp_raster(("Plane A in front of Plane B"));
end
7'h43: begin
// CLUT Bank
clut_bank0 <= ch0_register_data[1:0];
end
7'h44: begin
`dp_raster(
("Trans Color Plane A %d %d %d", ch0_register_data[23:18],
ch0_register_data[15:10], ch0_register_data[7:2]));
trans_color_plane_a <= {
ch0_register_data[23:18],
ch0_register_data[15:10],
ch0_register_data[7:2]
};
end
7'h47: begin
`dp_raster(
("Mask Color Plane A %d %d %d", ch0_register_data[23:18],
ch0_register_data[15:10], ch0_register_data[7:2]));
mask_color_plane_a <= {
ch0_register_data[23:18],
ch0_register_data[15:10],
ch0_register_data[7:2]
};
end
7'h4a: begin
// DYUV Abs. Start Value for Plane A
dyuv0_abs_start <= ch0_register_data;
`dp_raster(
("DYUV Abs. Start Plane A %d %d %d", ch0_register_data[23:16],
ch0_register_data[15:8], ch0_register_data[7:0]));
end
7'h4d: begin
// Cursor Position
cursor_position_reg <= ch0_register_data;
`dp_raster(
("Cursor X:%d Y:%d", ch0_register_data[9:0], ch0_register_data[21:12]));
end
7'h4e: begin
// Cursor Control
cursor_control_register <= ch0_register_data;
`dp_raster(("Cursor Control %b", ch0_register_data));
end
7'h4f: begin
// Cursor Pattern
cursor[ch0_register_data[19:16]] <= ch0_register_data[15:0];
`dp_raster(
("Cursor %x %b", ch0_register_data[19:16], ch0_register_data[15:0]));
end
7'h58: begin
// Backdrop Color
`dp_raster(("Backdrop Color %b", ch0_register_data[3:0]));
backdrop_color_register <= ch0_register_data[3:0];
end
7'h59: begin
// Pixel Hold for Plane A
mosaic_pixel_hold_factor_register_plane_a <= ch0_register_data;
`dp_raster(
("Mosaic A %b %b", ch0_register_data[23], ch0_register_data[7:0]));
end
7'h5b: begin
// Weight Factor for Plane A
weight_a <= ch0_register_data[5:0];
`dp_raster(("Line %3d Weight A %d", video_y, ch0_register_data[5:0]));
end
default: begin
// Mask out CLUT and Region writes
if (ch0_register_adr >= 7'h40 && ch0_register_adr[6:3] != 4'b1010) begin
`dp_raster(("Plane A ignored %x", ch0_register_adr));
end
end
endcase
if (ch0_register_adr <= 7'h3f) begin
// CLUT Color 0 to 63
`dp_raster(("CLUT A %d %d %d %d", {clut_bank0, ch0_register_adr[5:0]
}, ch0_register_data[23:18], ch0_register_data[15:10],
ch0_register_data[7:2]));
end
end
if (active_pixel == region_control[rf0_index].x) begin
if (region_control[rf0_index].op[2:1] == 2'b10) begin
// Change Weight of Plane A
`dp_raster(
("Line %3d Weight A changed with Region %d at %d to %d", video_y,
rf0_index, active_pixel, region_control[rf0_index].wf));
weight_a <= region_control[rf0_index].wf;
end
end
if (image_coding_method_register.nr && active_pixel == region_control[{1'b1, rf1_index}].x) begin
if (region_control[{1'b1, rf1_index}].op[2:1] == 2'b10) begin
// Change Weight of Plane A
`dp_raster(
("Line %3d Weight A changed with Region %d at %d to %d", video_y,
rf0_index, active_pixel, region_control[{
1'b1, rf1_index}].wf));
weight_a <= region_control[{1'b1, rf1_index}].wf;
end
end
end
end
assign clut_wr_addr0 = {clut_bank0, ch0_register_adr[5:0]};
// According to 5.4.4.5 CLUT Bank Register, A7 is forced 1
assign clut_wr_addr1 = {1'b1, clut_bank1, ch1_register_adr[5:0]};
assign clut_wr_data0 = {
ch0_register_data[23:18], ch0_register_data[15:10], ch0_register_data[7:2]
};
assign clut_wr_data1 = {
ch1_register_data[23:18], ch1_register_data[15:10], ch1_register_data[7:2]
};
assign clut_we0 = (ch0_register_adr <= 7'h3f) && ch0_register_write;
assign clut_we1 = (ch1_register_adr <= 7'h3f) && ch1_register_write;
always_ff @(posedge clk) begin
if (!reset) begin
if (ch0_register_write && ch0_register_adr[6:3] == 4'b1010) begin
`dp_raster(
("Line %3d Region CH0 %d CMD:%b RF:%b Weight:%d X:%d", video_y,
ch0_register_adr[2:0], ch0_register_data[23:20], ch0_register_data[16],
ch0_register_data[15:10], ch0_register_data[9:0]));
region_control[ch0_register_adr[2:0]] <= {
ch0_register_data[23:20], ch0_register_data[16:0]
};
end
if (ch1_register_write && ch1_register_adr[6:3] == 4'b1010) begin
`dp_raster(
("Line %3d Region CH1 %d CMD:%b RF:%b Weight:%d X:%d", video_y,
ch1_register_adr[2:0], ch1_register_data[23:20], ch1_register_data[16],
ch1_register_data[15:10], ch1_register_data[9:0]));
region_control[ch1_register_adr[2:0]] <= {
ch1_register_data[23:20], ch1_register_data[16:0]
};
end
end
end
always_ff @(posedge clk) begin
if (reset) begin
clut_bank1 <= 0;
trans_color_plane_b <= 0;
mask_color_plane_b <= 0;
weight_b <= 0;
end else begin
if (ch1_register_write) begin
case (ch1_register_adr)
7'h43: begin
// CLUT Bank
clut_bank1 <= ch1_register_data[0];
end
7'h46: begin
`dp_raster(
("Trans Color Plane B %d %d %d", ch1_register_data[23:18],
ch1_register_data[15:10], ch1_register_data[7:2]));
trans_color_plane_b <= {
ch1_register_data[23:18],
ch1_register_data[15:10],
ch1_register_data[7:2]
};
end
7'h49: begin
`dp_raster(
("Mask Color Plane B %d %d %d", ch1_register_data[23:18],
ch1_register_data[15:10], ch1_register_data[7:2]));
mask_color_plane_b <= {
ch1_register_data[23:18],
ch1_register_data[15:10],
ch1_register_data[7:2]
};
end
7'h4b: begin
// DYUV Abs. Start Value for Plane B
dyuv1_abs_start <= ch1_register_data;
`dp_raster(
("DYUV Abs. Start Plane B %d %d %d", ch1_register_data[23:16],
ch1_register_data[15:8], ch1_register_data[7:0]));
end
7'h5c: begin
// Weight Factor for Plane B
weight_b <= ch1_register_data[5:0];
`dp_raster(("Line %3d Weight B %d", video_y, ch1_register_data[5:0]));
end
7'h5A: begin
// Mosaic Pixel Hold for Plane B
mosaic_pixel_hold_factor_register_plane_b <= ch1_register_data;
`dp_raster(
("Mosaic B %b %b", ch1_register_data[23], ch1_register_data[7:0]));
end
default: begin
if (ch1_register_adr >= 7'h40 && ch1_register_adr[6:3] != 4'b1010) begin
`dp_raster(("Plane B ignored %x", ch1_register_adr));
end
end
endcase
if (ch1_register_adr <= 7'h3f) begin
// CLUT Color 0 to 63
`dp_raster(("CLUT B %d %d %d %d", {clut_bank1, ch1_register_adr[5:0]
}, ch1_register_data[23:18], ch1_register_data[15:10],
ch1_register_data[7:2]));
end
end
if (active_pixel == region_control[rf0_index].x) begin
if (region_control[rf0_index].op[2:1] == 2'b11) begin
// Change Weight of Plane B
`dp_raster(("Line %3d Weight B changed with Region %d at %d to %d", video_y, {
1'b1, rf1_index}, active_pixel, region_control[rf0_index].wf));
weight_b <= region_control[rf0_index].wf;
end
end
if (image_coding_method_register.nr && active_pixel == region_control[{1'b1, rf1_index}].x) begin
if (region_control[{1'b1, rf1_index}].op[2:1] == 2'b11) begin
// Change Weight of Plane B
`dp_raster(("Line %3d Weight B changed with Region %d at %d to %d", video_y, {
1'b1, rf1_index}, active_pixel, region_control[{1'b1, rf1_index
}].wf));
weight_b <= region_control[{1'b1, rf1_index}].wf;
end
end
end
end
// --- Regions ---
bit [2:0] rf0_index; // 0 to 7 as it is used for Explicit Control
bit [1:0] rf1_index; // 0 to 3 as only required for Implicit Control
always_ff @(posedge clk) begin
if (new_line) begin
region_flags <= 0;
rf0_index <= 0;
rf1_index <= 0;
// Only execute region effects when active_pixel is incrementing at the same time.
// To avoid repeated execution due to 30 MHz vs 15 MHz, only
// do it before the next hires pixel
end else if (!hblank_vt_q && !new_pixel_hires) begin
if (image_coding_method_register.nr) begin
// Implicit Control of Regions Flags
// RF0 is handles by Region Control Registers 0123 in that order
// X0 < X1 < X2 < X3
// RF1 is handles by Region Control Registers 4567 in that order
// X4 < X5 < X6 < X7
if (active_pixel == region_control[rf0_index].x) begin
if (region_control[rf0_index].op[3]) begin
region_flags[0] <= region_control[rf0_index].op[0];
end
if (region_control[rf0_index].op != 0) begin
rf0_index <= rf0_index + 1;
end
end
if (active_pixel == region_control[{1'b1, rf1_index}].x) begin
if (region_control[{1'b1, rf1_index}].op[3]) begin
region_flags[1] <= region_control[{1'b1, rf1_index}].op[0];
end
if (region_control[{1'b1, rf1_index}].op != 0) begin
rf1_index <= rf1_index + 1;
end
end
end else begin
// Explicit Control of Region Flags
// X0 < X1 < X2 < X3 < X4 < X5 < X6 < X7
if (active_pixel == region_control[rf0_index].x) begin
if (region_control[rf0_index].op[3]) begin
region_flags[region_control[rf0_index].rf]<=region_control[rf0_index].op[0];
end
if (region_control[rf0_index].op != 0) begin
rf0_index <= rf0_index + 1;
end
end
end
end
end
endmodule
module pixelstream_mux (
input use_b,
pixelstream.sink a,
pixelstream.sink b,
pixelstream.source dst
);
assign a.strobe = !use_b ? dst.strobe : 1'b0;
assign b.strobe = use_b ? dst.strobe : 1'b0;
assign dst.write = use_b ? b.write : a.write;
assign dst.pixel = use_b ? b.pixel : a.pixel;
endmodule
module pixelstream_demux (
input use_b,
pixelstream.sink in,
pixelstream.source a,
pixelstream.source b
);
assign b.pixel = in.pixel;
assign b.write = use_b && in.write;
assign a.pixel = in.pixel;
assign a.write = !use_b && in.write;
assign in.strobe = use_b ? b.strobe : a.strobe;
endmodule
// According to
// https://www.intel.com/content/www/us/en/docs/programmable/683082/22-1/true-dual-port-synchronous-ram.html
// to ensure that this is indeed a True Dual-Port RAM with Single Clock
module clut_dual_port_memory (
input clk,
input clut_entry_s data_a,
input clut_entry_s data_b,
input [7:0] addr_a,
input [7:0] addr_b,
input we_a,
input we_b,
output clut_entry_s q_a,
output clut_entry_s q_b
);
// Declare the RAM variable
clut_entry_s ram[256] /*verilator public_flat_rw*/;
// Port A
always @(posedge clk) begin
if (we_a) begin
ram[addr_a] = data_a;
end
q_a <= ram[addr_a];
end
// Port B
always @(posedge clk) begin
if (we_b) begin
ram[addr_b] = data_b;
end
q_b <= ram[addr_b];
end
endmodule