1/*
2 * Copyright (c) 2015, 2017 ARM Limited
3 * All rights reserved
4 *
5 * The license below extends only to copyright in the software and shall
6 * not be construed as granting a license to any other intellectual
7 * property including but not limited to intellectual property relating
8 * to a hardware implementation of the functionality of the software
9 * licensed hereunder.  You may use the software subject to the license
10 * terms below provided that you ensure that this notice is replicated
11 * unmodified and in its entirety in all distributions of the software,
12 * modified or unmodified, in source code or in binary form.
13 *
14 * Redistribution and use in source and binary forms, with or without
15 * modification, are permitted provided that the following conditions are
16 * met: redistributions of source code must retain the above copyright
17 * notice, this list of conditions and the following disclaimer;
18 * redistributions in binary form must reproduce the above copyright
19 * notice, this list of conditions and the following disclaimer in the
20 * documentation and/or other materials provided with the distribution;
21 * neither the name of the copyright holders nor the names of its
22 * contributors may be used to endorse or promote products derived from
23 * this software without specific prior written permission.
24 *
25 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
26 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
27 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
28 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
29 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
30 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
31 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
32 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
33 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
34 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
35 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
36 *
37 * Authors: Andreas Sandberg
38 */
39
40#include "dev/pixelpump.hh"
41
42const DisplayTimings DisplayTimings::vga(
43    640, 480,
44    48, 96, 16,
45    33, 2, 10);
46
47
48DisplayTimings::DisplayTimings(unsigned _width, unsigned _height,
49                               unsigned hbp, unsigned h_sync, unsigned hfp,
50                               unsigned vbp, unsigned v_sync, unsigned vfp)
51    : width(_width), height(_height),
52      hBackPorch(hbp), hFrontPorch(hfp), hSync(h_sync),
53      vBackPorch(vbp), vFrontPorch(vfp), vSync(v_sync)
54{
55}
56
57void
58DisplayTimings::serialize(CheckpointOut &cp) const
59{
60    SERIALIZE_SCALAR(width);
61    SERIALIZE_SCALAR(height);
62
63    SERIALIZE_SCALAR(hBackPorch);
64    SERIALIZE_SCALAR(hFrontPorch);
65    SERIALIZE_SCALAR(hSync);
66
67    SERIALIZE_SCALAR(vBackPorch);
68    SERIALIZE_SCALAR(vFrontPorch);
69    SERIALIZE_SCALAR(vSync);
70}
71
72void
73DisplayTimings::unserialize(CheckpointIn &cp)
74{
75    UNSERIALIZE_SCALAR(width);
76    UNSERIALIZE_SCALAR(height);
77
78    UNSERIALIZE_SCALAR(hBackPorch);
79    UNSERIALIZE_SCALAR(hFrontPorch);
80    UNSERIALIZE_SCALAR(hSync);
81
82    UNSERIALIZE_SCALAR(vBackPorch);
83    UNSERIALIZE_SCALAR(vFrontPorch);
84    UNSERIALIZE_SCALAR(vSync);
85}
86
87
88BasePixelPump::BasePixelPump(EventManager &em, ClockDomain &pxl_clk,
89                             unsigned pixel_chunk)
90    : EventManager(em), Clocked(pxl_clk), Serializable(),
91      pixelChunk(pixel_chunk),
92      pixelEvents(),
93      evVSyncBegin("evVSyncBegin", this, &BasePixelPump::onVSyncBegin),
94      evVSyncEnd("evVSyncEnd", this, &BasePixelPump::onVSyncEnd),
95      evHSyncBegin("evHSyncBegin", this, &BasePixelPump::onHSyncBegin),
96      evHSyncEnd("evHSyncEnd", this, &BasePixelPump::onHSyncEnd),
97      evBeginLine("evBeginLine", this, &BasePixelPump::beginLine),
98      evRenderPixels("evRenderPixels", this, &BasePixelPump::renderPixels),
99      _timings(DisplayTimings::vga),
100      line(0), _posX(0), _underrun(false)
101{
102}
103
104BasePixelPump::~BasePixelPump()
105{
106}
107
108void
109BasePixelPump::serialize(CheckpointOut &cp) const
110{
111    SERIALIZE_SCALAR(line);
112    SERIALIZE_SCALAR(_posX);
113    SERIALIZE_SCALAR(_underrun);
114
115    SERIALIZE_OBJ(_timings);
116    SERIALIZE_OBJ(fb);
117
118    for (PixelEvent *event : pixelEvents)
119        event->serializeSection(cp, event->name());
120}
121
122void
123BasePixelPump::unserialize(CheckpointIn &cp)
124{
125    UNSERIALIZE_SCALAR(line);
126    UNSERIALIZE_SCALAR(_posX);
127    UNSERIALIZE_SCALAR(_underrun);
128
129    UNSERIALIZE_OBJ(_timings);
130    UNSERIALIZE_OBJ(fb);
131
132    // We don't need to reschedule the event here since the event was
133    // suspended by PixelEvent::drain() and will be rescheduled by
134    // PixelEvent::drainResume().
135    for (PixelEvent *event : pixelEvents)
136        event->unserializeSection(cp, event->name());
137}
138
139void
140BasePixelPump::updateTimings(const DisplayTimings &timings)
141{
142    panic_if(active(), "Trying to update timings in active PixelPump\n");
143
144    _timings = timings;
145
146    // Resize the frame buffer if needed
147    if (_timings.width != fb.width() || _timings.height != fb.height())
148        fb.resize(timings.width, timings.height);
149
150    // Set the current line past the last line in the frame. This
151    // triggers the new frame logic in beginLine().
152    line = _timings.linesPerFrame();
153}
154
155void
156BasePixelPump::start()
157{
158    schedule(evBeginLine, clockEdge());
159}
160
161
162void
163BasePixelPump::stop()
164{
165    if (evVSyncEnd.scheduled())
166        deschedule(evVSyncEnd);
167
168    if (evHSyncBegin.scheduled())
169        deschedule(evHSyncBegin);
170
171    if (evHSyncEnd.scheduled())
172        deschedule(evHSyncEnd);
173
174    if (evBeginLine.scheduled())
175        deschedule(evBeginLine);
176
177    if (evRenderPixels.scheduled())
178        deschedule(evRenderPixels);
179}
180
181void
182BasePixelPump::beginLine()
183{
184    _posX = 0;
185    line++;
186    if (line >= _timings.linesPerFrame()) {
187        _underrun = false;
188        line = 0;
189    }
190
191    if (line == _timings.lineVSyncStart()) {
192        onVSyncBegin();
193    } else if (line == _timings.lineVBackPorchStart()) {
194        onVSyncEnd();
195    }
196
197    const Cycles h_sync_begin(0);
198    schedule(evHSyncBegin, clockEdge(h_sync_begin));
199
200    const Cycles h_sync_end(h_sync_begin + _timings.hSync);
201    schedule(evHSyncEnd, clockEdge(h_sync_end));
202
203    // Visible area
204    if (line >= _timings.lineFirstVisible() &&
205        line < _timings.lineFrontPorchStart()) {
206
207        const Cycles h_first_visible(h_sync_end + _timings.hBackPorch);
208        schedule(evRenderPixels, clockEdge(h_first_visible));
209    }
210
211    schedule(evBeginLine, clockEdge(_timings.cyclesPerLine()));
212}
213
214void
215BasePixelPump::renderPixels()
216{
217    // Try to handle multiple pixels at a time; doing so reduces the
218    // accuracy of the underrun detection but lowers simulation
219    // overhead
220    const unsigned x_end(std::min(_posX + pixelChunk, _timings.width));
221    const unsigned pxl_count(x_end - _posX);
222    const unsigned pos_y(posY());
223
224    Pixel pixel(0, 0, 0);
225    const Pixel underrun_pixel(0, 0, 0);
226    for (; _posX < x_end && !_underrun; ++_posX) {
227        if (!nextPixel(pixel)) {
228            warn("Input buffer underrun in BasePixelPump (%u, %u)\n",
229                 _posX, pos_y);
230            _underrun = true;
231            onUnderrun(_posX, pos_y);
232            pixel = underrun_pixel;
233        }
234        fb.pixel(_posX, pos_y) = pixel;
235    }
236
237    // Fill remaining pixels with a dummy pixel value if we ran out of
238    // data
239    for (; _posX < x_end; ++_posX)
240        fb.pixel(_posX, pos_y) = underrun_pixel;
241
242    // Schedule a new event to handle the next block of pixels
243    if (_posX < _timings.width) {
244        schedule(evRenderPixels, clockEdge(Cycles(pxl_count)));
245    } else {
246        if (pos_y == _timings.height - 1)
247            onFrameDone();
248    }
249}
250
251void
252BasePixelPump::renderFrame()
253{
254    _underrun = false;
255    line = 0;
256
257    // Signal vsync end and render the frame
258    line = _timings.lineVBackPorchStart();
259    onVSyncEnd();
260
261    // We only care about the visible screen area when rendering the
262    // frame
263    for (line = _timings.lineFirstVisible();
264        line < _timings.lineFrontPorchStart();
265        ++line) {
266
267        _posX = 0;
268
269        onHSyncBegin();
270        onHSyncEnd();
271
272        renderLine();
273    }
274
275    line = _timings.lineFrontPorchStart() - 1;
276    onFrameDone();
277
278    // Signal vsync until the next frame begins
279    line = _timings.lineVSyncStart();
280    onVSyncBegin();
281}
282
283void
284BasePixelPump::renderLine()
285{
286    const unsigned pos_y(posY());
287
288    Pixel pixel(0, 0, 0);
289    for (_posX = 0; _posX < _timings.width; ++_posX) {
290        if (!nextPixel(pixel)) {
291            panic("Unexpected underrun in BasePixelPump (%u, %u)\n",
292                 _posX, pos_y);
293        }
294        fb.pixel(_posX, pos_y) = pixel;
295    }
296}
297
298
299BasePixelPump::PixelEvent::PixelEvent(
300    const char *name, BasePixelPump *_parent, CallbackType _func)
301    : Event(), Drainable(),
302      _name(name), parent(*_parent), func(_func),
303      suspended(false),
304      relativeTick(0)
305{
306    parent.pixelEvents.push_back(this);
307}
308
309DrainState
310BasePixelPump::PixelEvent::drain()
311{
312    if (scheduled())
313        suspend();
314    return DrainState::Drained;
315}
316
317void
318BasePixelPump::PixelEvent::drainResume()
319{
320    if (suspended)
321        resume();
322}
323
324void
325BasePixelPump::PixelEvent::serialize(CheckpointOut &cp) const
326{
327    assert(!scheduled());
328    Event::serialize(cp);
329    SERIALIZE_SCALAR(suspended);
330    SERIALIZE_SCALAR(relativeTick);
331}
332
333void
334BasePixelPump::PixelEvent::unserialize(CheckpointIn &cp)
335{
336    Event::unserialize(cp);
337    UNSERIALIZE_SCALAR(suspended);
338    UNSERIALIZE_SCALAR(relativeTick);
339    assert(!scheduled());
340}
341
342void
343BasePixelPump::PixelEvent::suspend()
344{
345    assert(scheduled());
346    assert(!suspended);
347
348    suspended = true;
349    relativeTick = when() - curTick();
350    parent.deschedule(this);
351}
352
353void
354BasePixelPump::PixelEvent::resume()
355{
356    assert(!scheduled());
357    assert(suspended);
358    parent.schedule(this, relativeTick + curTick());
359    suspended = false;
360    relativeTick = 0;
361}
362