/
top_demo.sv
303 lines (275 loc) · 9.87 KB
/
top_demo.sv
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
// Project F: 2D Shapes - Demo (Nexys Video)
// (C)2023 Will Green, open source hardware released under the MIT License
// Learn more at https://projectf.io/posts/fpga-shapes/
`default_nettype none
`timescale 1ns / 1ps
module top_demo (
input wire logic clk_100m, // 100 MHz clock
input wire logic btn_rst_n, // reset button
output logic hdmi_tx_ch0_p, // HDMI source channel 0 diff+
output logic hdmi_tx_ch0_n, // HDMI source channel 0 diff-
output logic hdmi_tx_ch1_p, // HDMI source channel 1 diff+
output logic hdmi_tx_ch1_n, // HDMI source channel 1 diff-
output logic hdmi_tx_ch2_p, // HDMI source channel 2 diff+
output logic hdmi_tx_ch2_n, // HDMI source channel 2 diff-
output logic hdmi_tx_clk_p, // HDMI source clock diff+
output logic hdmi_tx_clk_n // HDMI source clock diff-
);
// generate system clock
logic clk_sys;
logic clk_sys_locked;
logic rst_sys;
clock_sys clock_sys_inst (
.clk_100m,
.rst(!btn_rst_n), // reset button is active low
.clk_sys,
.clk_sys_locked
);
always_ff @(posedge clk_sys) rst_sys <= !clk_sys_locked; // wait for clock lock
// generate pixel clock
logic clk_pix;
logic clk_pix_5x;
logic clk_pix_locked;
logic rst_pix;
clock_720p clock_pix_inst (
.clk_100m,
.rst(!btn_rst_n), // reset button is active low
.clk_pix,
.clk_pix_5x,
.clk_pix_locked
);
always_ff @(posedge clk_pix) rst_pix <= !clk_pix_locked; // wait for clock lock
// display sync signals and coordinates
localparam CORDW = 16; // signed coordinate width (bits)
logic signed [CORDW-1:0] sx, sy;
logic hsync, vsync;
logic de, frame, line;
display_720p #(.CORDW(CORDW)) display_inst (
.clk_pix,
.rst_pix,
.sx,
.sy,
.hsync,
.vsync,
.de,
.frame,
.line
);
// colour parameters
localparam CHANW = 4; // colour channel width (bits)
localparam COLRW = 3*CHANW; // colour width: three channels (bits)
localparam CIDXW = 4; // colour index width (bits)
localparam BG_COLR = 'h137; // background colour
localparam PAL_FILE = "sweetie16_4b.mem"; // palette file
// framebuffer (FB)
localparam FB_WIDTH = 320; // framebuffer width in pixels
localparam FB_HEIGHT = 180; // framebuffer height in pixels
localparam FB_SCALE = 4; // framebuffer display scale (1-63)
localparam FB_OFFX = 0; // horizontal offset
localparam FB_OFFY = 0; // vertical offset
localparam FB_PIXELS = FB_WIDTH * FB_HEIGHT; // total pixels in buffer
localparam FB_ADDRW = $clog2(FB_PIXELS); // address width
localparam FB_DATAW = CIDXW; // colour bits per pixel
// pixel read and write addresses and colours
logic [FB_ADDRW-1:0] fb_addr_write, fb_addr_read;
logic [FB_DATAW-1:0] fb_colr_write, fb_colr_read;
logic fb_we; // framebuffer write enable
// framebuffer memory
bram_sdp #(
.WIDTH(FB_DATAW),
.DEPTH(FB_PIXELS),
.INIT_F("")
) bram_inst (
.clk_write(clk_sys),
.clk_read(clk_sys),
.we(fb_we),
.addr_write(fb_addr_write),
.addr_read(fb_addr_read),
.data_in(fb_colr_write),
.data_out(fb_colr_read)
);
// display flags in system clock domain
logic frame_sys, line_sys, line0_sys;
xd xd_frame (.clk_src(clk_pix), .clk_dst(clk_sys),
.flag_src(frame), .flag_dst(frame_sys));
xd xd_line (.clk_src(clk_pix), .clk_dst(clk_sys),
.flag_src(line), .flag_dst(line_sys));
xd xd_line0 (.clk_src(clk_pix), .clk_dst(clk_sys),
.flag_src(line && sy==FB_OFFY), .flag_dst(line0_sys));
//
// draw in framebuffer
//
// reduce drawing speed to make process visible
localparam FRAME_WAIT = 200; // wait this many frames to start drawing
logic [$clog2(FRAME_WAIT)-1:0] cnt_frame_wait;
logic draw_oe; // draw requested
always_ff @(posedge clk_sys) begin
draw_oe <= 0; // comment out to draw at full speed
if (cnt_frame_wait != FRAME_WAIT-1) begin // wait for initial frames
if (frame_sys) cnt_frame_wait <= cnt_frame_wait + 1;
end else if (line_sys) draw_oe <= 1; // every screen line
end
// render shapes
parameter DRAW_SCALE = 1; // relative to framebuffer dimensions
logic drawing; // actively drawing
logic clip; // location is clipped
logic signed [CORDW-1:0] drx, dry; // draw coordinates
render_rects #( // switch module name to change demo
.CORDW(CORDW),
.CIDXW(CIDXW),
.SCALE(DRAW_SCALE)
) render_instance (
.clk(clk_sys),
.rst(rst_sys),
.oe(draw_oe),
.start(frame_sys),
.x(drx),
.y(dry),
.cidx(fb_colr_write),
.drawing,
/* verilator lint_off PINCONNECTEMPTY */
.done()
/* verilator lint_on PINCONNECTEMPTY */
);
// calculate pixel address in framebuffer (three-cycle latency)
bitmap_addr #(
.CORDW(CORDW),
.ADDRW(FB_ADDRW)
) bitmap_addr_instance (
.clk(clk_sys),
.bmpw(FB_WIDTH),
.bmph(FB_HEIGHT),
.x(drx),
.y(dry),
.offx(0),
.offy(0),
.addr(fb_addr_write),
.clip
);
// delay write enable to match address calculation
localparam LAT_ADDR = 3; // latency (cycles)
logic [LAT_ADDR-1:0] fb_we_sr;
always_ff @(posedge clk_sys) begin
fb_we_sr <= {drawing, fb_we_sr[LAT_ADDR-1:1]};
if (rst_sys) fb_we_sr <= 0;
end
always_comb fb_we = fb_we_sr[0] && !clip; // check for clipping
//
// read framebuffer for display output via linebuffer
//
// count lines for scaling via linebuffer
logic [$clog2(FB_SCALE):0] cnt_lb_line;
always_ff @(posedge clk_sys) begin
if (line0_sys) cnt_lb_line <= 0;
else if (line_sys) begin
cnt_lb_line <= (cnt_lb_line == FB_SCALE-1) ? 0 : cnt_lb_line + 1;
end
end
// which screen lines need linebuffer?
logic lb_line;
always_ff @(posedge clk_sys) begin
if (line0_sys) lb_line <= 1; // enable from sy==0
if (frame_sys) lb_line <= 0; // disable at frame start
end
// enable linebuffer input
logic lb_en_in;
logic [$clog2(FB_WIDTH)-1:0] cnt_lbx; // horizontal pixel counter
always_comb lb_en_in = (lb_line && cnt_lb_line == 0 && cnt_lbx < FB_WIDTH);
// calculate framebuffer read address for linebuffer
always_ff @(posedge clk_sys) begin
if (line_sys) begin // reset horizontal counter at start of line
cnt_lbx <= 0;
end else if (lb_en_in) begin // increment address when LB enabled
fb_addr_read <= fb_addr_read + 1;
cnt_lbx <= cnt_lbx + 1;
end
if (frame_sys) fb_addr_read <= 0; // reset address at frame start
end
// enable linebuffer output
logic lb_en_out;
localparam LAT_LB = 3; // output latency compensation: lb_en_out+1, LB+1, CLUT+1
always_ff @(posedge clk_pix) begin
lb_en_out <= (sy >= FB_OFFY && sy < (FB_HEIGHT * FB_SCALE) + FB_OFFY
&& sx >= FB_OFFX - LAT_LB && sx < (FB_WIDTH * FB_SCALE) + FB_OFFX - LAT_LB);
end
// display linebuffer
logic [FB_DATAW-1:0] lb_colr_out;
linebuffer_simple #(
.DATAW(FB_DATAW),
.LEN(FB_WIDTH)
) linebuffer_instance (
.clk_sys,
.clk_pix,
.line,
.line_sys,
.en_in(lb_en_in),
.en_out(lb_en_out),
.scale(FB_SCALE),
.data_in(fb_colr_read),
.data_out(lb_colr_out)
);
// colour lookup table (CLUT)
logic [COLRW-1:0] fb_pix_colr;
clut_simple #(
.COLRW(COLRW),
.CIDXW(CIDXW),
.F_PAL(PAL_FILE)
) clut_instance (
.clk_write(clk_pix),
.clk_read(clk_pix),
.we(0),
.cidx_write(0),
.cidx_read(lb_colr_out),
.colr_in(0),
.colr_out(fb_pix_colr)
);
// paint screen
logic paint_area; // area of screen to paint
logic [CHANW-1:0] paint_r, paint_g, paint_b; // colour channels
always_comb begin
paint_area = (sy >= FB_OFFY && sy < (FB_HEIGHT * FB_SCALE) + FB_OFFY
&& sx >= FB_OFFX && sx < (FB_WIDTH * FB_SCALE) + FB_OFFX);
{paint_r, paint_g, paint_b} = paint_area ? fb_pix_colr : BG_COLR;
end
// display colour: paint colour but black in blanking interval
logic [CHANW-1:0] display_r, display_g, display_b;
always_comb {display_r, display_g, display_b} = (de) ? {paint_r, paint_g, paint_b} : 0;
// DVI signals (8 bits per colour channel)
logic [2*CHANW-1:0] dvi_r, dvi_g, dvi_b;
logic dvi_hsync, dvi_vsync, dvi_de;
always_ff @(posedge clk_pix) begin
dvi_hsync <= hsync;
dvi_vsync <= vsync;
dvi_de <= de;
dvi_r <= {2{display_r}};
dvi_g <= {2{display_g}};
dvi_b <= {2{display_b}};
end
// TMDS encoding and serialization
logic tmds_ch0_serial, tmds_ch1_serial, tmds_ch2_serial, tmds_clk_serial;
dvi_generator dvi_out (
.clk_pix,
.clk_pix_5x,
.rst_pix,
.de(dvi_de),
.data_in_ch0(dvi_b),
.data_in_ch1(dvi_g),
.data_in_ch2(dvi_r),
.ctrl_in_ch0({dvi_vsync, dvi_hsync}),
.ctrl_in_ch1(2'b00),
.ctrl_in_ch2(2'b00),
.tmds_ch0_serial,
.tmds_ch1_serial,
.tmds_ch2_serial,
.tmds_clk_serial
);
// TMDS output pins
tmds_out tmds_ch0 (.tmds(tmds_ch0_serial),
.pin_p(hdmi_tx_ch0_p), .pin_n(hdmi_tx_ch0_n));
tmds_out tmds_ch1 (.tmds(tmds_ch1_serial),
.pin_p(hdmi_tx_ch1_p), .pin_n(hdmi_tx_ch1_n));
tmds_out tmds_ch2 (.tmds(tmds_ch2_serial),
.pin_p(hdmi_tx_ch2_p), .pin_n(hdmi_tx_ch2_n));
tmds_out tmds_clk (.tmds(tmds_clk_serial),
.pin_p(hdmi_tx_clk_p), .pin_n(hdmi_tx_clk_n));
endmodule