2015-08-07 10:59:26 +02:00
|
|
|
/*
|
2017-02-24 14:34:22 +01:00
|
|
|
* Copyright (c) 2015, 2017 ARM Limited
|
2015-08-07 10:59:26 +02:00
|
|
|
* All rights reserved
|
|
|
|
*
|
|
|
|
* The license below extends only to copyright in the software and shall
|
|
|
|
* not be construed as granting a license to any other intellectual
|
|
|
|
* property including but not limited to intellectual property relating
|
|
|
|
* to a hardware implementation of the functionality of the software
|
|
|
|
* licensed hereunder. You may use the software subject to the license
|
|
|
|
* terms below provided that you ensure that this notice is replicated
|
|
|
|
* unmodified and in its entirety in all distributions of the software,
|
|
|
|
* modified or unmodified, in source code or in binary form.
|
|
|
|
*
|
|
|
|
* Redistribution and use in source and binary forms, with or without
|
|
|
|
* modification, are permitted provided that the following conditions are
|
|
|
|
* met: redistributions of source code must retain the above copyright
|
|
|
|
* notice, this list of conditions and the following disclaimer;
|
|
|
|
* redistributions in binary form must reproduce the above copyright
|
|
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
|
|
* documentation and/or other materials provided with the distribution;
|
|
|
|
* neither the name of the copyright holders nor the names of its
|
|
|
|
* contributors may be used to endorse or promote products derived from
|
|
|
|
* this software without specific prior written permission.
|
|
|
|
*
|
|
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
|
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
|
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
|
|
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
|
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
|
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
|
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
|
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
|
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
*
|
|
|
|
* Authors: Andreas Sandberg
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "dev/pixelpump.hh"
|
|
|
|
|
|
|
|
const DisplayTimings DisplayTimings::vga(
|
|
|
|
640, 480,
|
|
|
|
48, 96, 16,
|
|
|
|
33, 2, 10);
|
|
|
|
|
|
|
|
|
|
|
|
DisplayTimings::DisplayTimings(unsigned _width, unsigned _height,
|
|
|
|
unsigned hbp, unsigned h_sync, unsigned hfp,
|
|
|
|
unsigned vbp, unsigned v_sync, unsigned vfp)
|
|
|
|
: width(_width), height(_height),
|
|
|
|
hBackPorch(hbp), hFrontPorch(hfp), hSync(h_sync),
|
|
|
|
vBackPorch(vbp), vFrontPorch(vfp), vSync(v_sync)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
DisplayTimings::serialize(CheckpointOut &cp) const
|
|
|
|
{
|
|
|
|
SERIALIZE_SCALAR(width);
|
|
|
|
SERIALIZE_SCALAR(height);
|
|
|
|
|
|
|
|
SERIALIZE_SCALAR(hBackPorch);
|
|
|
|
SERIALIZE_SCALAR(hFrontPorch);
|
|
|
|
SERIALIZE_SCALAR(hSync);
|
|
|
|
|
|
|
|
SERIALIZE_SCALAR(vBackPorch);
|
|
|
|
SERIALIZE_SCALAR(vFrontPorch);
|
|
|
|
SERIALIZE_SCALAR(vSync);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
DisplayTimings::unserialize(CheckpointIn &cp)
|
|
|
|
{
|
|
|
|
UNSERIALIZE_SCALAR(width);
|
|
|
|
UNSERIALIZE_SCALAR(height);
|
|
|
|
|
|
|
|
UNSERIALIZE_SCALAR(hBackPorch);
|
|
|
|
UNSERIALIZE_SCALAR(hFrontPorch);
|
|
|
|
UNSERIALIZE_SCALAR(hSync);
|
|
|
|
|
|
|
|
UNSERIALIZE_SCALAR(vBackPorch);
|
|
|
|
UNSERIALIZE_SCALAR(vFrontPorch);
|
|
|
|
UNSERIALIZE_SCALAR(vSync);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
BasePixelPump::BasePixelPump(EventManager &em, ClockDomain &pxl_clk,
|
|
|
|
unsigned pixel_chunk)
|
|
|
|
: EventManager(em), Clocked(pxl_clk), Serializable(),
|
|
|
|
pixelChunk(pixel_chunk),
|
|
|
|
pixelEvents(),
|
|
|
|
evVSyncBegin("evVSyncBegin", this, &BasePixelPump::onVSyncBegin),
|
|
|
|
evVSyncEnd("evVSyncEnd", this, &BasePixelPump::onVSyncEnd),
|
|
|
|
evHSyncBegin("evHSyncBegin", this, &BasePixelPump::onHSyncBegin),
|
|
|
|
evHSyncEnd("evHSyncEnd", this, &BasePixelPump::onHSyncEnd),
|
|
|
|
evBeginLine("evBeginLine", this, &BasePixelPump::beginLine),
|
|
|
|
evRenderPixels("evRenderPixels", this, &BasePixelPump::renderPixels),
|
|
|
|
_timings(DisplayTimings::vga),
|
|
|
|
line(0), _posX(0), _underrun(false)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
BasePixelPump::~BasePixelPump()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
BasePixelPump::serialize(CheckpointOut &cp) const
|
|
|
|
{
|
|
|
|
SERIALIZE_SCALAR(line);
|
|
|
|
SERIALIZE_SCALAR(_posX);
|
|
|
|
SERIALIZE_SCALAR(_underrun);
|
|
|
|
|
|
|
|
SERIALIZE_OBJ(_timings);
|
|
|
|
SERIALIZE_OBJ(fb);
|
|
|
|
|
|
|
|
for (PixelEvent *event : pixelEvents)
|
|
|
|
event->serializeSection(cp, event->name());
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
BasePixelPump::unserialize(CheckpointIn &cp)
|
|
|
|
{
|
|
|
|
UNSERIALIZE_SCALAR(line);
|
|
|
|
UNSERIALIZE_SCALAR(_posX);
|
|
|
|
UNSERIALIZE_SCALAR(_underrun);
|
|
|
|
|
|
|
|
UNSERIALIZE_OBJ(_timings);
|
|
|
|
UNSERIALIZE_OBJ(fb);
|
|
|
|
|
|
|
|
// We don't need to reschedule the event here since the event was
|
|
|
|
// suspended by PixelEvent::drain() and will be rescheduled by
|
|
|
|
// PixelEvent::drainResume().
|
|
|
|
for (PixelEvent *event : pixelEvents)
|
|
|
|
event->unserializeSection(cp, event->name());
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2017-02-24 14:34:22 +01:00
|
|
|
BasePixelPump::updateTimings(const DisplayTimings &timings)
|
2015-08-07 10:59:26 +02:00
|
|
|
{
|
2017-02-24 14:34:22 +01:00
|
|
|
panic_if(active(), "Trying to update timings in active PixelPump\n");
|
|
|
|
|
2015-08-07 10:59:26 +02:00
|
|
|
_timings = timings;
|
|
|
|
|
|
|
|
// Resize the frame buffer if needed
|
|
|
|
if (_timings.width != fb.width() || _timings.height != fb.height())
|
|
|
|
fb.resize(timings.width, timings.height);
|
|
|
|
|
|
|
|
// Set the current line past the last line in the frame. This
|
|
|
|
// triggers the new frame logic in beginLine().
|
|
|
|
line = _timings.linesPerFrame();
|
2017-02-24 14:34:22 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
BasePixelPump::start()
|
|
|
|
{
|
2015-08-07 10:59:26 +02:00
|
|
|
schedule(evBeginLine, clockEdge());
|
|
|
|
}
|
|
|
|
|
2017-02-24 14:34:22 +01:00
|
|
|
|
2015-08-07 10:59:26 +02:00
|
|
|
void
|
|
|
|
BasePixelPump::stop()
|
|
|
|
{
|
|
|
|
if (evVSyncEnd.scheduled())
|
|
|
|
deschedule(evVSyncEnd);
|
|
|
|
|
|
|
|
if (evHSyncBegin.scheduled())
|
|
|
|
deschedule(evHSyncBegin);
|
|
|
|
|
|
|
|
if (evHSyncEnd.scheduled())
|
|
|
|
deschedule(evHSyncEnd);
|
|
|
|
|
|
|
|
if (evBeginLine.scheduled())
|
|
|
|
deschedule(evBeginLine);
|
|
|
|
|
|
|
|
if (evRenderPixels.scheduled())
|
|
|
|
deschedule(evRenderPixels);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
BasePixelPump::beginLine()
|
|
|
|
{
|
|
|
|
_posX = 0;
|
|
|
|
line++;
|
|
|
|
if (line >= _timings.linesPerFrame()) {
|
|
|
|
_underrun = false;
|
|
|
|
line = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (line == _timings.lineVSyncStart()) {
|
|
|
|
onVSyncBegin();
|
|
|
|
} else if (line == _timings.lineVBackPorchStart()) {
|
|
|
|
onVSyncEnd();
|
|
|
|
}
|
|
|
|
|
|
|
|
const Cycles h_sync_begin(0);
|
|
|
|
schedule(evHSyncBegin, clockEdge(h_sync_begin));
|
|
|
|
|
|
|
|
const Cycles h_sync_end(h_sync_begin + _timings.hSync);
|
|
|
|
schedule(evHSyncEnd, clockEdge(h_sync_end));
|
|
|
|
|
|
|
|
// Visible area
|
|
|
|
if (line >= _timings.lineFirstVisible() &&
|
|
|
|
line < _timings.lineFrontPorchStart()) {
|
|
|
|
|
|
|
|
const Cycles h_first_visible(h_sync_end + _timings.hBackPorch);
|
|
|
|
schedule(evRenderPixels, clockEdge(h_first_visible));
|
|
|
|
}
|
|
|
|
|
|
|
|
schedule(evBeginLine, clockEdge(_timings.cyclesPerLine()));
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
BasePixelPump::renderPixels()
|
|
|
|
{
|
|
|
|
// Try to handle multiple pixels at a time; doing so reduces the
|
|
|
|
// accuracy of the underrun detection but lowers simulation
|
|
|
|
// overhead
|
|
|
|
const unsigned x_end(std::min(_posX + pixelChunk, _timings.width));
|
|
|
|
const unsigned pxl_count(x_end - _posX);
|
|
|
|
const unsigned pos_y(posY());
|
|
|
|
|
|
|
|
Pixel pixel(0, 0, 0);
|
|
|
|
const Pixel underrun_pixel(0, 0, 0);
|
|
|
|
for (; _posX < x_end && !_underrun; ++_posX) {
|
|
|
|
if (!nextPixel(pixel)) {
|
|
|
|
warn("Input buffer underrun in BasePixelPump (%u, %u)\n",
|
|
|
|
_posX, pos_y);
|
|
|
|
_underrun = true;
|
|
|
|
onUnderrun(_posX, pos_y);
|
|
|
|
pixel = underrun_pixel;
|
|
|
|
}
|
|
|
|
fb.pixel(_posX, pos_y) = pixel;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Fill remaining pixels with a dummy pixel value if we ran out of
|
|
|
|
// data
|
|
|
|
for (; _posX < x_end; ++_posX)
|
|
|
|
fb.pixel(_posX, pos_y) = underrun_pixel;
|
|
|
|
|
|
|
|
// Schedule a new event to handle the next block of pixels
|
|
|
|
if (_posX < _timings.width) {
|
|
|
|
schedule(evRenderPixels, clockEdge(Cycles(pxl_count)));
|
|
|
|
} else {
|
|
|
|
if (pos_y == _timings.height - 1)
|
|
|
|
onFrameDone();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-24 14:34:22 +01:00
|
|
|
void
|
|
|
|
BasePixelPump::renderFrame()
|
|
|
|
{
|
|
|
|
_underrun = false;
|
|
|
|
line = 0;
|
|
|
|
|
|
|
|
// Signal vsync end and render the frame
|
|
|
|
line = _timings.lineVBackPorchStart();
|
|
|
|
onVSyncEnd();
|
|
|
|
|
|
|
|
// We only care about the visible screen area when rendering the
|
|
|
|
// frame
|
|
|
|
for (line = _timings.lineFirstVisible();
|
|
|
|
line < _timings.lineFrontPorchStart();
|
|
|
|
++line) {
|
|
|
|
|
|
|
|
_posX = 0;
|
|
|
|
|
|
|
|
onHSyncBegin();
|
|
|
|
onHSyncEnd();
|
|
|
|
|
|
|
|
renderLine();
|
|
|
|
}
|
|
|
|
|
|
|
|
line = _timings.lineFrontPorchStart() - 1;
|
|
|
|
onFrameDone();
|
|
|
|
|
|
|
|
// Signal vsync until the next frame begins
|
|
|
|
line = _timings.lineVSyncStart();
|
|
|
|
onVSyncBegin();
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
BasePixelPump::renderLine()
|
|
|
|
{
|
|
|
|
const unsigned pos_y(posY());
|
|
|
|
|
|
|
|
Pixel pixel(0, 0, 0);
|
|
|
|
for (_posX = 0; _posX < _timings.width; ++_posX) {
|
|
|
|
if (!nextPixel(pixel)) {
|
|
|
|
panic("Unexpected underrun in BasePixelPump (%u, %u)\n",
|
|
|
|
_posX, pos_y);
|
|
|
|
}
|
|
|
|
fb.pixel(_posX, pos_y) = pixel;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2015-08-07 10:59:26 +02:00
|
|
|
BasePixelPump::PixelEvent::PixelEvent(
|
|
|
|
const char *name, BasePixelPump *_parent, CallbackType _func)
|
|
|
|
: Event(), Drainable(),
|
|
|
|
_name(name), parent(*_parent), func(_func),
|
|
|
|
suspended(false),
|
|
|
|
relativeTick(0)
|
|
|
|
{
|
|
|
|
parent.pixelEvents.push_back(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
DrainState
|
|
|
|
BasePixelPump::PixelEvent::drain()
|
|
|
|
{
|
|
|
|
if (scheduled())
|
|
|
|
suspend();
|
|
|
|
return DrainState::Drained;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
BasePixelPump::PixelEvent::drainResume()
|
|
|
|
{
|
|
|
|
if (suspended)
|
|
|
|
resume();
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
BasePixelPump::PixelEvent::serialize(CheckpointOut &cp) const
|
|
|
|
{
|
|
|
|
assert(!scheduled());
|
|
|
|
Event::serialize(cp);
|
|
|
|
SERIALIZE_SCALAR(suspended);
|
|
|
|
SERIALIZE_SCALAR(relativeTick);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
BasePixelPump::PixelEvent::unserialize(CheckpointIn &cp)
|
|
|
|
{
|
|
|
|
Event::unserialize(cp);
|
|
|
|
UNSERIALIZE_SCALAR(suspended);
|
|
|
|
UNSERIALIZE_SCALAR(relativeTick);
|
|
|
|
assert(!scheduled());
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
BasePixelPump::PixelEvent::suspend()
|
|
|
|
{
|
|
|
|
assert(scheduled());
|
|
|
|
assert(!suspended);
|
|
|
|
|
|
|
|
suspended = true;
|
|
|
|
relativeTick = when() - curTick();
|
|
|
|
parent.deschedule(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
BasePixelPump::PixelEvent::resume()
|
|
|
|
{
|
|
|
|
assert(!scheduled());
|
|
|
|
assert(suspended);
|
|
|
|
parent.schedule(this, relativeTick + curTick());
|
|
|
|
suspended = false;
|
|
|
|
relativeTick = 0;
|
|
|
|
}
|