`default_nettype none
module GAL16V8_reg (
	input wire       clk,  // Pin 1
	input wire [7:0] in,   // Pin {9, 8, 7, 6, 5, 4, 3, 2}
	input wire       oe_n, // Pin 11
	inout wire [7:0] io    // Pin {12, 13, 14, 15, 16, 17, 18, 19}
);
	// Read in binary JEDEC file
	reg [7:0] jed_bin_file [0:278];
	initial $readmemh("GAL16V8_reg.hex", jed_bin_file);

	// Linearize to a fuse map
	wire [2193:0] fuses;
	genvar i, j;
	generate
		for (i = 0; i < 274; i = i + 1) begin
			for (j = 0; j < 8; j = j + 1) begin
				assign fuses[8*i + j] = jed_bin_file[i + 4][j];
			end
		end
		// Last couple bits
		for (j = 0; j < 2; j = j + 1) begin
			assign fuses[8*274 + j] = jed_bin_file[274 + 4][j];
		end
	endgenerate

	// Extract useful fuses
	wire syn_fuse, ac0_fuse;
	assign syn_fuse = fuses[2192];
	assign ac0_fuse = fuses[2193];

	wire [7:0] xor_fuses;
	assign xor_fuses = fuses[2055:2048];

	wire [7:0] ac1_fuses;
	assign ac1_fuses = fuses[2127:2120];

	wire [255:0] sop_fuses [0:7];
	wire [7:0] ptd_fuses [0:7];
	generate
		for (i = 0; i < 8; i = i + 1) begin
			assign sop_fuses[i] = fuses[256*i +: 256];
			assign ptd_fuses[i] = fuses[2128 + 8*i +: 8];
		end
	endgenerate

	// Interleave in and feedback for SOP inputs
	wire [7:0] feedback;
	wire [15:0] interleaved;
	generate
		for (i = 0; i < 8; i = i + 1) begin
			assign interleaved[2*i +: 2] = {feedback[i], in[i]};
		end
	endgenerate

	// Generate GAL elements
	generate
		for (i = 0; i < 8; i = i + 1) begin
			wire one_sop_out, sop_out;

			// 1SOP
			sop #(
				.NUM_PRODUCTS(1),
				.NUM_INPUTS(16)
			) one_sop_inst (
				.sop_fuses(sop_fuses[i][31:0]),
				.ptd_fuses(ptd_fuses[i][0]),
				.in(interleaved),
				.out(one_sop_out)
			);

			// SOP
			sop #(
				.NUM_PRODUCTS(7),
				.NUM_INPUTS(16)
			) sop_inst (
				.sop_fuses(sop_fuses[i][255:32]),
				.ptd_fuses(ptd_fuses[i][7:1]),
				.in(interleaved),
				.out(sop_out)
			);

			// OLMC
			olmc olmc_inst (
				.xor_fuse(xor_fuses[i]),
				.ac1_fuse(ac1_fuses[i]),
				.sop(sop_out),
				.one_sop(one_sop_out),
				.clk(clk),
				.oe_n(oe_n),
				.io(io[i]),
				.feedback(feedback[i])
			);
		end
	endgenerate

	// Simulation printing
	initial begin
		#1;
		$display("SYN: %d, AC0: %d", syn_fuse, ac0_fuse);
		$display("XOR: %b, AC1: %b", xor_fuses, ac1_fuses);
		$display("Fuses: %x", fuses);

		$display("sop0 %x", sop_fuses[0]);
		$display("ptd0 %x", ptd_fuses[0]);

		$display("sop1 %x", sop_fuses[1]);
		$display("ptd1 %x", ptd_fuses[1]);
		$display("sop2 %x", sop_fuses[2]);
		$display("ptd2 %x", ptd_fuses[2]);
		$display("sop3 %x", sop_fuses[3]);
		$display("ptd3 %x", ptd_fuses[3]);
		$display("sop4 %x", sop_fuses[4]);
		$display("ptd4 %x", ptd_fuses[4]);
		$display("sop5 %x", sop_fuses[5]);
		$display("ptd5 %x", ptd_fuses[5]);
		$display("sop6 %x", sop_fuses[6]);
		$display("ptd6 %x", ptd_fuses[6]);
		$display("sop7 %x", sop_fuses[7]);
		$display("ptd7 %x", ptd_fuses[7]);
	end
endmodule

module sop #(
	parameter NUM_PRODUCTS = 7,
	parameter NUM_INPUTS = 16
)(
	input  wire [2*NUM_INPUTS*NUM_PRODUCTS-1:0] sop_fuses,
	input  wire [NUM_PRODUCTS-1:0] ptd_fuses,
	input  wire [NUM_INPUTS-1:0] in,
	output reg  out
);
	integer i, j;
	reg match;

	always @ (*) begin
		out = 0;
		for (i = 0; i < NUM_PRODUCTS; i = i + 1) begin
			match = 1;
			for (j = 0; j < NUM_INPUTS; j = j + 1) begin
				if (!sop_fuses[2*NUM_INPUTS*i + 2*j + 0] && !in[j]) match = 0;
				if (!sop_fuses[2*NUM_INPUTS*i + 2*j + 1] &&  in[j]) match = 0;
			end
			if (match && ptd_fuses[i]) out = 1;
		end
	end
endmodule

module olmc (
	input wire xor_fuse,
	input wire ac1_fuse,

	input wire sop,
	input wire one_sop,

	input wire clk,
	input wire oe_n,

	inout  wire io,
	output wire feedback
);
	// Internal combined SOP output with optional inversion
	wire out;
	assign out = (ac1_fuse ? sop : (sop || one_sop)) ^ xor_fuse;

	reg reg_out;
	always @ (posedge clk) begin
		reg_out <= out;
	end

	assign feedback = ac1_fuse ? !out : !reg_out;

	assign io = ac1_fuse ? (one_sop ? !out : 1'bz) : // Combinational
		(oe_n ? 1'bz : !reg_out); // Registered
endmodule