aboutsummaryrefslogtreecommitdiff
path: root/basys3/basys3.srcs/ppu.vhd
blob: efe212576e418343dac475bfe39a6b29b2d98286 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
library ieee;
use ieee.std_logic_1164.all;
--use ieee.numeric_std.all;

entity ppu is port(
	CLK100: in std_logic; -- system clock
	RESET: in std_logic; -- global (async) system reset
	EN: in std_logic; -- PPU VRAM enable (enable ADDR and DATA tri-state drivers)
	WEN: in std_logic; -- PPU VRAM write enable
	ADDR: in std_logic_vector(15 downto 0); -- PPU VRAM ADDR
	DATA: in std_logic_vector(15 downto 0);
	R,G,B: out std_logic_vector(3 downto 0);
	NVSYNC, NHSYNC: out std_logic; -- native VGA out
	TVSYNC, TVBLANK, THSYNC, THBLANK: out std_logic); -- tiny VGA out
end ppu;

architecture Behavioral of ppu is
	constant PPU_FG_SPRITE_COUNT: natural := 128; -- amount of foreground sprites
	constant PPU_COLOR_OUTPUT_DEPTH: natural := 4; -- VGA output channel depth
	constant PPU_PALETTE_IDX_WIDTH: natural := 3; -- palette index width (within sprite)
	constant PPU_PALETTE_WIDTH: natural := 3; -- palette index width (palette table)
	constant PPU_PALETTE_CIDX_WIDTH: natural := PPU_PALETTE_IDX_WIDTH + PPU_PALETTE_WIDTH; -- global palette index width
	constant PPU_TMM_ADDR_WIDTH: natural := 16;
	constant PPU_TMM_DATA_WIDTH: natural := 16;
	constant PPU_BAM_ADDR_WIDTH: natural := 11;
	constant PPU_BAM_DATA_WIDTH: natural := 15;
	constant PPU_FAM_ADDR_WIDTH: natural := 8;
	constant PPU_FAM_DATA_WIDTH: natural := 16;
	constant PPU_PAL_ADDR_WIDTH: natural := 6;
	constant PPU_PAL_DATA_WIDTH: natural := 12;
	constant PPU_AUX_ADDR_WIDTH: natural := 2;
	constant PPU_AUX_DATA_WIDTH: natural := 16;
	constant PPU_POS_H_WIDTH: natural := 9; -- amount of bits for horizontal screen offset
	constant PPU_POS_V_WIDTH: natural := 8; -- amount of bits for vertical screen offset

	component ppu_pceg port( -- pipeline clock edge generator
		CLK: in std_logic; -- system clock
		RESET: in std_logic; -- async reset
		SPRITE: out std_logic; -- sprite info fetch + sprite pixel fetch
		COMP_PAL: out std_logic; -- compositor + palette lookup
		DONE: out std_logic); -- last pipeline stage done
	end component;
	component ppu_addr_dec port( -- pipeline clock edge generator
		WEN: in std_logic; -- EXT write enable
		TMM_WEN,
		BAM_WEN,
		FAM_WEN,
		PAL_WEN,
		AUX_WEN: out std_logic; -- write enable MUX
		EN: in std_logic; -- EXT *ADDR enable (switch *AO to ADDR instead of *AI)
		ADDR: in std_logic_vector(15 downto 0); -- address in
		TMM_AI: in std_logic_vector(PPU_TMM_ADDR_WIDTH-1 downto 0);
		BAM_AI: in std_logic_vector(PPU_BAM_ADDR_WIDTH-1 downto 0);
		FAM_AI: in std_logic_vector(PPU_FAM_ADDR_WIDTH-1 downto 0);
		PAL_AI: in std_logic_vector(PPU_PAL_ADDR_WIDTH-1 downto 0);
		AUX_AI: in std_logic_vector(PPU_AUX_ADDR_WIDTH-1 downto 0);
		TMM_AO: out std_logic_vector(PPU_TMM_ADDR_WIDTH-1 downto 0);
		BAM_AO: out std_logic_vector(PPU_BAM_ADDR_WIDTH-1 downto 0);
		FAM_AO: out std_logic_vector(PPU_FAM_ADDR_WIDTH-1 downto 0);
		PAL_AO: out std_logic_vector(PPU_PAL_ADDR_WIDTH-1 downto 0);
		AUX_AO: out std_logic_vector(PPU_AUX_ADDR_WIDTH-1 downto 0));
	end component;
	component ppu_bam port( -- BAM block memory
		clka: in std_logic;
		rsta: in std_logic;
		wea: in std_logic_vector(0 downto 0);
		addra: in std_logic_vector(PPU_BAM_ADDR_WIDTH-1 downto 0);
		dina: in std_logic_vector(PPU_BAM_DATA_WIDTH-1 downto 0);
		douta: out std_logic_vector(PPU_BAM_DATA_WIDTH-1 downto 0);
		rsta_busy: out std_logic);
	end component;
	component ppu_tmm port( -- TMM block memory
		clka: in std_logic;
		rsta: in std_logic;
		wea: in std_logic_vector(0 downto 0);
		addra: in std_logic_vector(PPU_TMM_ADDR_WIDTH-1 downto 0);
		dina: in std_logic_vector(PPU_TMM_DATA_WIDTH-1 downto 0);
		douta: out std_logic_vector(PPU_TMM_DATA_WIDTH-1 downto 0);
		rsta_busy: out std_logic);
	end component;
	component ppu_aux port(
		CLK: in std_logic; -- system clock
		RESET: in std_logic; -- reset memory

		-- internal memory block (AUX)
		AUX_WEN: in std_logic; -- VRAM AUX write enable
		AUX_ADDR: in std_logic_vector(PPU_AUX_ADDR_WIDTH-1 downto 0); -- VRAM AUX address
		AUX_DATA: in std_logic_vector(PPU_AUX_DATA_WIDTH-1 downto 0); -- VRAM AUX data
		
		-- aux outputs
		BG_SHIFT_X: out std_logic_vector(PPU_POS_H_WIDTH-1 downto 0);
		BG_SHIFT_Y: out std_logic_vector(PPU_POS_V_WIDTH-1 downto 0);
		FG_FETCH: out std_logic);
	end component;
	component ppu_sprite_bg port( -- background sprite
		-- inputs
		CLK: in std_logic; -- system clock
		RESET: in std_logic; -- reset clock counter
		OE: in std_logic; -- output enable (of CIDX)
		X: in std_logic_vector(PPU_POS_H_WIDTH-1 downto 0); -- current screen pixel x
		Y: in std_logic_vector(PPU_POS_V_WIDTH-1 downto 0); -- current screen pixel y
		
		-- aux inputs
		BG_SHIFT_X: in std_logic_vector(PPU_POS_H_WIDTH-1 downto 0);
		BG_SHIFT_Y: in std_logic_vector(PPU_POS_V_WIDTH-1 downto 0);

		-- used memory blocks
		BAM_ADDR: out std_logic_vector(PPU_BAM_ADDR_WIDTH-1 downto 0);
		BAM_DATA: in std_logic_vector(PPU_BAM_DATA_WIDTH-1 downto 0);
		TMM_ADDR: out std_logic_vector(PPU_TMM_ADDR_WIDTH-1 downto 0);
		TMM_DATA: in std_logic_vector(PPU_TMM_DATA_WIDTH-1 downto 0);

		-- outputs
		CIDX: out std_logic_vector(PPU_PALETTE_CIDX_WIDTH-1 downto 0)); -- output color
	end component;
	component ppu_sprite_fg port( -- foreground sprite
		-- inputs
		CLK: in std_logic; -- system clock
		RESET: in std_logic; -- reset internal memory and clock counters
		OE: in std_logic; -- output enable (of CIDX)
		X: in std_logic_vector(PPU_POS_H_WIDTH-1 downto 0); -- current screen pixel x
		Y: in std_logic_vector(PPU_POS_V_WIDTH-1 downto 0); -- current screen pixel y
		FETCH: in std_logic; -- fetch sprite data from TMM (TODO: generic map, set foreground sprite component index)

		-- internal memory block (FAM)
		FAM_WEN: in std_logic; -- VRAM FAM write enable
		FAM_ADDR: in std_logic_vector(PPU_FAM_ADDR_WIDTH-1 downto 0); -- VRAM fam address
		FAM_DATA: in std_logic_vector(PPU_FAM_DATA_WIDTH-1 downto 0); -- VRAM fam data

		-- used memory blocks
		TMM_ADDR: out std_logic_vector(PPU_TMM_ADDR_WIDTH-1 downto 0);
		TMM_DATA: in std_logic_vector(PPU_TMM_DATA_WIDTH-1 downto 0);

		-- outputs
		CIDX: out std_logic_vector(PPU_PALETTE_CIDX_WIDTH-1 downto 0); -- output color
		HIT: out std_logic); -- current pixel is not transparent
	end component;
	component ppu_comp port( -- compositor
		FG_HIT: in std_logic_vector(PPU_FG_SPRITE_COUNT-1 downto 0);
		BG_EN: out std_logic;
		FG_EN: out std_logic_vector(PPU_FG_SPRITE_COUNT-1 downto 0));
	end component;
	component ppu_plut port( -- palette lookup table
		CLK: in std_logic; -- system clock
		CIDX: in std_logic_vector(PPU_PALETTE_CIDX_WIDTH-1 downto 0); -- color in
		RESET: in std_logic;

		-- internal memory block (AUX)
		PAL_WEN: in std_logic; -- VRAM PAL write enable
		PAL_ADDR: in std_logic_vector(PPU_PAL_ADDR_WIDTH-1 downto 0); -- VRAM PAL address
		PAL_DATA: in std_logic_vector(PPU_PAL_DATA_WIDTH-1 downto 0); -- VRAM PAL data
		
		R,G,B: out std_logic_vector(3 downto 0)); -- VGA color out
	end component;
	component ppu_vga_tiny port( -- tiny vga signal generator
		CLK: in std_logic; -- system clock
		RESET: in std_logic;

		X: out std_logic_vector(PPU_POS_H_WIDTH-1 downto 0); -- current screen pixel x
		Y: out std_logic_vector(PPU_POS_V_WIDTH-1 downto 0); -- current screen pixel y
		
		VSYNC, VBLANK,
		HSYNC, HBLANK: out std_logic); -- VGA sync outputs
	end component;
	component ppu_vga_native port( -- native vga signal generator (upscaler)
		CLK: in std_logic; -- system clock
		RESET: in std_logic;

		X: in std_logic_vector(PPU_POS_H_WIDTH-1 downto 0); -- current screen pixel x
		Y: in std_logic_vector(PPU_POS_V_WIDTH-1 downto 0); -- current screen pixel y
		PREADY: in std_logic; -- current pixel ready (pixel color is stable)
		RI,GI,BI: in std_logic_vector(PPU_COLOR_OUTPUT_DEPTH-1 downto 0); -- VGA color in
		
		RO,GO,BO: out std_logic_vector(PPU_COLOR_OUTPUT_DEPTH-1 downto 0); -- VGA color out
		VSYNC, HSYNC: out std_logic); -- VGA sync outputs
	end component;

	-- signals
	signal SYSCLK, SYSRST: std_logic; -- system clock and reset
	signal PL_SPRITE, PL_COMP_PAL, PL_DONE: std_logic; -- pipeline stages
	signal TMM_WEN, BAM_WEN, FAM_WEN, PAL_WEN, AUX_WEN: std_logic;
	signal TMM_AI, TMM_AO: std_logic_vector(PPU_TMM_ADDR_WIDTH-1 downto 0);
	signal BAM_AI, BAM_AO: std_logic_vector(PPU_BAM_ADDR_WIDTH-1 downto 0);
	signal FAM_AI, FAM_AO: std_logic_vector(PPU_FAM_ADDR_WIDTH-1 downto 0);
	signal PAL_AI, PAL_AO: std_logic_vector(PPU_PAL_ADDR_WIDTH-1 downto 0);
	signal AUX_AI, AUX_AO: std_logic_vector(PPU_AUX_ADDR_WIDTH-1 downto 0);
	signal TMM_DO: std_logic_vector(PPU_TMM_DATA_WIDTH-1 downto 0);
	signal BAM_DO: std_logic_vector(PPU_BAM_DATA_WIDTH-1 downto 0);
	signal FAM_DO: std_logic_vector(PPU_FAM_DATA_WIDTH-1 downto 0);
	signal PAL_DO: std_logic_vector(PPU_PAL_DATA_WIDTH-1 downto 0);
	signal AUX_DO: std_logic_vector(PPU_AUX_DATA_WIDTH-1 downto 0);
	signal CIDX: std_logic_vector(PPU_PALETTE_CIDX_WIDTH-1 downto 0);
	signal BG_EN: std_logic;
	signal FG_EN, FG_HIT: std_logic_vector(PPU_FG_SPRITE_COUNT-1 downto 0);
	signal X: std_logic_vector(PPU_POS_H_WIDTH-1 downto 0); -- current screen pixel x
	signal Y: std_logic_vector(PPU_POS_V_WIDTH-1 downto 0); -- current screen pixel y
	signal UR,UG,UB: std_logic_vector(PPU_COLOR_OUTPUT_DEPTH-1 downto 0); -- unstable RGB (to be buffered)
	signal SR,SG,SB: std_logic_vector(PPU_COLOR_OUTPUT_DEPTH-1 downto 0); -- stable RGB (buffered until PL_COMP_PAL)
	signal BG_SHIFT_X: std_logic_vector(PPU_POS_H_WIDTH-1 downto 0);
	signal BG_SHIFT_Y: std_logic_vector(PPU_POS_V_WIDTH-1 downto 0);
	signal FG_FETCH: std_logic;
begin
	SYSCLK <= CLK100;
	SYSRST <= RESET;

	pipeline_clock_edge_generator: component ppu_pceg port map(
		CLK => SYSCLK,
		RESET => SYSRST,
		SPRITE => PL_SPRITE,
		COMP_PAL => PL_COMP_PAL,
		DONE => PL_DONE);

	address_decoder: component ppu_addr_dec port map(
		EN => EN,
		WEN => WEN,
		ADDR => ADDR,
		TMM_AI => TMM_AI,
		BAM_AI => BAM_AI,
		FAM_AI => FAM_AI,
		PAL_AI => PAL_AI,
		AUX_AI => AUX_AI,
		TMM_AO => TMM_AO,
		BAM_AO => BAM_AO,
		FAM_AO => FAM_AO,
		PAL_AO => PAL_AO,
		AUX_AO => AUX_AO,
		TMM_WEN => TMM_WEN,
		BAM_WEN => BAM_WEN,
		FAM_WEN => FAM_WEN,
		PAL_WEN => PAL_WEN,
		AUX_WEN => AUX_WEN);

	background_attribute_memory: component ppu_bam port map(
		clka => SYSCLK,
		rsta => SYSRST,
		wea => (others => BAM_WEN),
		addra => BAM_AO,
		dina => DATA(PPU_BAM_DATA_WIDTH-1 downto 0),
		douta => BAM_DO,
		rsta_busy => open);
	tilemap_memory: component ppu_tmm port map(
		clka => SYSCLK,
		rsta => SYSRST,
		wea => (others => TMM_WEN),
		addra => TMM_AO,
		dina => DATA(PPU_TMM_DATA_WIDTH-1 downto 0),
		douta => TMM_DO,
		rsta_busy => open);

	aux: component ppu_aux port map(
		CLK => SYSCLK,
		RESET => SYSRST,
		AUX_WEN => AUX_WEN,
		AUX_ADDR => AUX_AO,
		AUX_DATA => DATA(PPU_AUX_DATA_WIDTH-1 downto 0),
		BG_SHIFT_X => BG_SHIFT_X,
		BG_SHIFT_Y => BG_SHIFT_Y,
		FG_FETCH => FG_FETCH);

	background_sprite: component ppu_sprite_bg port map(
		CLK => PL_SPRITE,
		RESET => SYSRST,
		OE => BG_EN,
		X => X,
		Y => Y,
		BG_SHIFT_X => BG_SHIFT_X,
		BG_SHIFT_Y => BG_SHIFT_Y,
		BAM_ADDR => BAM_AI,
		BAM_DATA => BAM_DO,
		TMM_ADDR => TMM_AI,
		TMM_DATA => TMM_DO,
		CIDX => CIDX);

	foreground_sprites: for FG_IDX in 0 to PPU_FG_SPRITE_COUNT-1 generate
		foreground_sprite: component ppu_sprite_fg port map(
			CLK => PL_SPRITE,
			RESET => SYSRST,
			OE => FG_EN(FG_IDX),
			X => X,
			Y => Y,
			FETCH => FG_FETCH,
			FAM_WEN => FAM_WEN,
			FAM_ADDR => FAM_AO,
			FAM_DATA => DATA(PPU_FAM_DATA_WIDTH-1 downto 0),
			TMM_ADDR => TMM_AI,
			TMM_DATA => TMM_DO,
			CIDX => CIDX,
			HIT => FG_HIT(FG_IDX));
	end generate;

	compositor: component ppu_comp port map( -- compositor
		FG_HIT => FG_HIT,
		BG_EN => BG_EN,
		FG_EN => FG_EN);

	palette_lookup: component ppu_plut port map( -- palette lookup table
		CLK => SYSCLK,
		CIDX => CIDX,
		RESET => SYSRST,
		PAL_WEN => PAL_WEN,
		PAL_ADDR => PAL_AO,
		PAL_DATA => DATA(PPU_PAL_DATA_WIDTH-1 downto 0),
		R => UR,
		G => UG,
		B => UB);

	-- palette lookup output buffer (pipeline stage 5)
	process(PL_COMP_PAL, SYSRST)
	begin
		if SYSRST = '1' then
			SR <= x"0";
			SG <= x"0";
			SB <= x"0";
		elsif rising_edge(PL_COMP_PAL) then
			SR <= UR;
			SG <= UG;
			SB <= UB;
		end if;
	end process;

	tiny_vga_signal_generator: component ppu_vga_tiny port map( -- tiny vga signal generator
		CLK => SYSCLK,
		RESET => SYSRST,
		X => X,
		Y => Y,
		VSYNC => TVSYNC,
		VBLANK => TVBLANK,
		HSYNC => THSYNC,
		HBLANK => THBLANK);

	native_vga_signal_generator: component ppu_vga_native port map( -- native vga signal generator (upscaler)
		CLK => SYSCLK,
		RESET => SYSRST,
		X => X,
		Y => Y,
		PREADY => PL_DONE,
		RI => SR,
		GI => SG,
		BI => SB,
		RO => R,
		GO => G,
		BO => B,
		VSYNC => NVSYNC,
		HSYNC => NHSYNC);
end Behavioral;