| /* |
| * Copyright (c) 2015, 2017 ARM Limited |
| * 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 |
| BasePixelPump::updateTimings(const DisplayTimings &timings) |
| { |
| panic_if(active(), "Trying to update timings in active PixelPump\n"); |
| |
| _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(); |
| } |
| |
| void |
| BasePixelPump::start() |
| { |
| schedule(evBeginLine, clockEdge()); |
| } |
| |
| |
| 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(); |
| } |
| } |
| |
| 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; |
| } |
| } |
| |
| |
| 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; |
| } |