module zx_ula(
	input rst_n,
	input clk14,
	input clkpal,

	output clkcpu,

	inout [7:0] vd,
	inout [16:0] va,
	output ra14,
	input a0,
	input a1,
	input a14,
	input a15,

	input n_rd,
	input n_wr,
	input n_mreq,
	input n_iorq,
	input n_m1,
	input n_rfsh,
	output reg n_int,

	output n_vrd,
	output n_vwr,
	output n_romcs,

	input [4:0] kd,
	input tape_in,
	output tape_out,
	output reg beeper,

	output ay_clk,
	output reg ay_bdir,
	output reg ay_bc1,

	output reg r,
	output reg g,
	output reg b,
	output reg i,
	output [1:0] chroma,
	output reg csync
);

wire [15:0] xa = {a15, a14, va[13:2], va[1], va[0]}; // a1-va[1] and a0-va[0] may be swapped if fitter is cranky
wire [7:0] xd = vd;

reg screen_read;
wire n_iorq0 = n_iorq | screen_read;


/* SCREEN CONTROLLER */
localparam H_AREA         = 256;
localparam V_AREA         = 192;
localparam SCREEN_DELAY   = 8;
localparam H_TOTAL        = 448;
localparam V_TOTAL        = 320;

reg [8:0] vc;
reg [9:0] hc0;
wire [8:0] hc = hc0[9:1];

wire hc0_reset = hc0 == (H_TOTAL<<1) - 1'b1 ;
wire vc_reset = vc == V_TOTAL - 1'b1 ;

always @(posedge clk14) begin
	if (hc0_reset) begin
		hc0 <= 0;
		if (vc_reset) begin
			vc <= 0;
		end
		else begin
			vc <= vc + 1'b1;
		end
	end 
	else begin
		hc0 <= hc0 + 1'b1;
	end
end

reg [4:0] blink_cnt;
wire blink = blink_cnt[4];
always @(posedge n_int) begin
	blink_cnt <= blink_cnt + 1'b1;
end

reg [2:0] border;
reg [7:0] bitmap, attr, bitmap_next, attr_next;
wire pixel = bitmap[7];
wire attr_read = screen_read & ~hc0[0];
wire bitmap_read = screen_read & hc0[0];
wire [14:0] bitmap_addr = { 2'b10, vc[7:6], vc[2:0], vc[5:3], hc[7:3] };
wire [14:0] attr_addr = { 5'b10110, vc[7:3], hc[7:3] };
wire [14:0] screen_addr = bitmap_read? bitmap_addr : attr_addr;
wire screen_show = (vc < V_AREA) && (hc >= SCREEN_DELAY) && (hc < H_AREA + SCREEN_DELAY);
wire screen_update = (vc < V_AREA) && (hc < H_AREA) && hc0[3:0] == 4'b1111;
wire border_update = (hc0[3:0] == 4'b1111) || (screen_show == 0);

always @(posedge clk14) begin
	screen_read <= n_mreq == 1'b1 && n_iorq == 1'b1;

	if (attr_read)
		attr_next <= vd;
	if (bitmap_read)
		bitmap_next <= vd;

	if (screen_update)
		attr <= attr_next;
	else if (border_update)
		attr[7:3] <= {2'b00, border};

	if (screen_update)
		bitmap <= {bitmap_next[7] ^ (attr_next[7] & blink), bitmap_next[6:0]};
	else if (hc0[0])
		bitmap <= {bitmap[6] ^ (attr[7] & blink), bitmap[5:0], 1'b0};
end


/* VIDEO OUTPUT */
// blank range: [320-400)
wire blank = (vc[7:4] == 4'b1111) || (hc[8:6] == 3'b101) || (hc[8:4] == 5'b11000);
always @(posedge clk14) if (hc0[0]) begin
	if (blank)
		{g, r, b, i} = 4'b0000;
	else begin
		{g, r, b} = pixel? attr[2:0] : attr[5:3];
		i = (g | r | b) & attr[6];
	end
end

// hsync range: [328-360)
wire hsync0 = hc[8:5] == 4'b1010;
wire vsync0 = vc[7:3] == 5'b11111;
reg hsync1;
always @(posedge clk14) if (hc[3]) begin
	csync <= ~(vsync0 ^ hsync0);
	hsync1 <= ~hsync0;
end


/* INT GENERATOR */
always @(posedge clk14)
	n_int <= ~(vc == 239 && hc[8:6] == 3'b101);


/* CLOCK */
assign clkcpu = hc[0];


/* PORT #FE */
wire port_fe_cs = n_m1 == 1 && n_iorq0 == 0 && xa[0] == 0;

wire [7:0] port_fe_data = {1'b1, tape_in, 1'b1, kd[4:0]};
reg port_fe_rd;
always @(posedge clk14)
	port_fe_rd <= port_fe_cs && n_rd == 0;

reg tape_out0;
assign tape_out = tape_in ^ tape_out0;
always @(posedge clk14 or negedge rst_n) begin
	if (!rst_n) begin
		beeper <= 0;
		tape_out0 <= 0;
		border <= 0;
	end
	else if (port_fe_cs && n_wr == 0) begin
		beeper <= xd[4];
		tape_out0 <= xd[3];
		border <= xd[2:0];
	end
end


/* PORT #7FFD */
wire port_7ffd_cs = n_m1 == 1 && n_iorq0 == 0 && xa[1] == 0 && xa[15] == 0;
reg [2:0] rambank;
reg rombank, vbank, lock_7ffd;
always @(posedge clk14 or negedge rst_n) begin
	if (!rst_n) begin
		rambank <= 0;
		vbank <= 0;
		rombank <= 0;
		lock_7ffd <= 0;
	end
	else if (port_7ffd_cs && n_wr == 0 && lock_7ffd == 0) begin
		rambank <= xd[2:0];
		vbank <= xd[3];
		rombank <= xd[4];
		lock_7ffd <= xd[5];
	end
end


/* AY */
always @(posedge clk14 or negedge rst_n) begin
	if (!rst_n) begin
		ay_bc1 <= 0;
		ay_bdir <= 0;
	end
	else begin
		ay_bc1  <= xa[15] == 1 && xa[14] == 1 && xa[1] == 0 && n_m1 == 1 && n_iorq0 == 0;
		ay_bdir <= xa[15] == 1 && xa[1] == 0 && n_m1 == 1 && n_iorq0 == 0 && n_wr == 0;
	end
end
assign ay_clk = hc[1];


/* VIDEO */
wire chroma_carrier;
wire [1:0] chroma0;
chroma_gen #(.CLK_FREQ(28_375_000)) chroma_gen1(
	.clk(clkpal),
	.hsync(hsync1),
	.en(1'b1),
	.ntsc(1'b0),
	.r(r),
	.g(g),
	.b(b),
    .i(i),
	.out_carrier(chroma_carrier),
	.out(chroma0)
);
assign chroma[0] = chroma0[0]? chroma_carrier : 1'bz;
assign chroma[1] = chroma0[1]? chroma_carrier : 1'bz;


/* MEMORY CONTROLLER */
// 7ffe a15-14 va16-14
//  xxx   01     010   bank 2
//  xxx   10     101   bank 5
//  000   11     000   bank 0
//  001   11     001   bank 1 | contended
//  010   11     010   bank 2
//  011   11     011   bank 3 | contended
//  100   11     100   bank 4
//  101   11     101   bank 5 | contended (video)
//  110   11     110   bank 6
//  111   11     111   bank 7 | contended (video alt)
// 7ffe a15-14  ra14
//    0   00       0   rom0
//    1   00       1   rom1

wire n_vcs_cpu = n_mreq | ~(a15 | a14);
assign n_vrd = ((n_vcs_cpu == 0 && n_rd == 0) || screen_read == 1)? 1'b0 : 1'b1;
assign n_vwr = ((n_vcs_cpu == 0 && n_wr == 0) && screen_read == 0)? 1'b0 : 1'b1;
assign n_romcs = n_mreq | a15 | a14;

assign ra14 = rombank;

assign va[16:0] = 
	screen_read? {1'b1, vbank, screen_addr} :
	a15 & a14? {rambank, {14{1'bz}}} :
	{a14, a15, a14, {14{1'bz}}};

assign vd[7:0] = 
	port_fe_rd? port_fe_data :
	n_iorq0 == 0 && (n_rd == 0 | n_m1 == 0)? {8{1'b1}} :
	{8{1'bz}};


endmodule
