Navrhujeme a vyrábíme vlastní CPU: vývojové nástroje

15. 5. 2025
Doba čtení: 7 minut

Sdílet

Vývojová deska s FPGA
Autor: Martin Beran
Máme dokončený hardware počítače MB5016 a nyní si ukážeme, jak ho můžeme začít programovat. K tomu účelu si vytvoříme dva základní vývojové nástroje – debugger a assembler.

Hardware máme, a co dál?

V předchozích dílech seriálu jsme navrhli a implementovali procesor MB5016, přidali jsme další komponenty a složili z nich počítač MB50. Celý počítač máme popsaný v jazyce VHDL. Když z něj necháme vývojové prostředí vygenerovat binární konfigurační stream a nahrajeme ho do FPGA, získáme kus hardwaru, který zatím nic nedělá. Počítač má prázdnou paměť, černou obrazovku, a CPU stojí na adrese 0×0000. Nastal čas, abychom se pokusili počítač „rozhýbat“.

Počítač MB50 má zabudované řídicí a ladicí rozhraní, připojené na sériový port. Přes něj je možné nahrávat programy do paměti a spustit vykonávání instrukcí v procesoru. Potřebujeme tedy sériový port připojit k nějakému hostitelskému počítači a z něj začít MB50 oživovat. Za tím účelem si naprogramujeme debugger.

Je sice možné psát software přímo v binárním strojovém kódu MB5016, ale to je dost nepohodlné. Na druhou stranu se nechceme pouštět ani do složitého vývoje překladače jazyka C nebo nějakého jiného vyššího programovacího jazyka. Místo toho zvolíme kompromis a naprogramujeme assembler.

Zdrojové kódy pro oba vývojové nástroje, debugger a assembler, jsou v repozitáři v adresáři mb50/mb50dev. Naprogramované jsou v C++23 a přeložit se dají jednoduše příkazem make. Debugger mb50dbg je v souboru mb50dbg.cpp, assembler mb50as je v mb50as.cpp a zdrojové kódy doplňuje společný hlavičkový soubor mb50common.hpp.

Při vývoji jsem používal Clang 19 na Ubuntu s volbami -fexperimental-library -stdlib=libc++, ale kód by měl jít přeložit i jiným dostatečně moderním kompilátorem C++ i na jiných distribucích Linuxu, popř. i na jiných unixových systémech, jako je FreeBSD. Assembler používá pouze standardní knihovnu C++, proto by měl fungovat i na jiných operačních systémech. Debugger volá navíc několik funkcí z POSIXového API: tcgetattr(), tcsetattr() pro konfiguraci sériového portu a select() pro současné čekání na data ze sériového portu a na stisk klávesy.

Debugger

Debugger běží na hostitelském počítači a přes sériový port komunikuje s ladicím rozhraním počítače MB50. To je implementované entitou cdi v souboru cdi.vhd. Ladicí rozhraní je z jedné strany připojené na řadič sériového portu. Na opačné straně je napojené na řídicí signály CPU a řadiče paměti.

Komunikace po sériovém portu používá jednoduchý binární protokol. Komunikaci vždy zahajuje debugger. Pošle jeden bajt obsahující kód požadavku, po němž může následovat jeden nebo několik bajtů parametrů. Ladicí rozhraní vykoná požadovanou operaci a vrátí jednobajtový kód odpovědi, opět volitelně následovaný parametry. Činnost ladicího rozhraní je řízena konečným automatem.

Na sériovém portu se „optimisticky“ používá rychlost 115200 bitů/s bez parity a bez řízení toku dat (flow control). Předpokládá se, a dosavadní zkušenosti to potvrzují, že při přenosu nedochází k chybám a že obě strany stíhají číst příchozí data dostatečně rychle. Na straně MB50 pracuje konečný automat ladicího rozhraní s frekvencí hodinového kmitočtu 50 MHz a musí čekat maximálně než procesor dokončí aktuálně rozpracovanou instrukci, což trvá méně než mikrosekundu. Na straně hostitelského počítače očekáváme, že jeho operační systém (obvykle Linux) stíhá číst ze sériového portu bez ztráty dat.

Podporované akce prováděné prostřednictvím ladicího rozhraní zahrnují:

  • Zjištění stavu procesoru: zda běží a jaká je aktuální hodnota čítače instrukcí (registru  pc)
  • Přečtení hodnoty vybraného registru
  • Změna hodnoty registru
  • Přečtení hodnoty vybraného řídicího registru (CSR)
  • Změna hodnoty řídicího registru
  • Přečtení obsahu paměti. Adresa začátku a délka úseku se zadávají jako parametry požadavku.
  • Zápis úseku paměti. Tato funkce se nejčastěji používá na nahrání programu, ale dá se využít i na modifikaci obsahu paměti při ladění.
  • Vykonání jedné instrukce (krokování programu)
  • Spuštění programu. Běžící program je zastaven když přijde požadavek na zastavení přes sériový port z debuggeru, nebo když se procesor sám zastaví vykonáním instrukce brk nebo v reakci na výjimku při zakázaném přerušení.

Debugger je jednoduchý interaktivní program s řádkovým rozhraním. Pracuje v nekonečném cyklu, kdy vždy přečte vstupní řádek od uživatele, interpretuje zadaný příkaz, vykoná ho buď lokálně nebo komunikací s ladicím rozhraním MB50, zobrazí výsledek a čeká na další vstup uživatele.

Příkazy na čtení a zápis registrů a paměti, krokování, spuštění a přerušení programu generují příslušné požadavky pro ladicí rozhraní. Při nahrávání programu debugger čte binární soubor obsahující strojový kód vygenerovaný assemblerem. Dále debugger podporuje příkazy na logování prováděných operací do souboru a naopak vykonávání příkazů přečtených ze souboru.

Hostitelský počítač s běžícím debuggerem připojeným k MB50 

Assembler

Úkolem assembleru je přeložit zdrojový kód v assembleru (nebo hezky česky v jazyce symbolických adres) do binárního strojového kódu. Na vstupu dostává soubor se zdrojovým kódem s příponou .s. Z něho generuje tři výstupní soubory.

Soubor s příponou .bin obsahuje binární spustitelný strojový kód, který se dá pomocí debuggeru nahrát do paměti počítače MB50. V souboru .mif je stejný strojový kód, ale v textovém formátu Memory Initialization File. Ten je možné přidat do projektu ve vývojovém prostředí Quartus Prime. Během syntézy se jeho obsah zahrne do konfiguračního streamu a při nahrání do FPGA se podle něj inicializuje obsah paměti. Tak lze mít v paměti program hned při zapnutí počítače, bez nutnosti nahrávat ho debuggerem.

Třetí výstupní soubor má příponu .out. Obsahuje kopii zdrojového kódu s přidanými anotacemi obsahujícími expanze maker, symbolický a hexadecimální zápis vygenerovaných instrukcí a adresy, kde jsou instrukce uloženy v paměti. Tento soubor je určen primárně jako pomůcka pro programátora při ladění.

Pro ilustraci si ukážeme úryvek zdrojového kódu

char_loop:
    # Set black on white/yellow checkered pattern
    .set r2, .BG_WHITE | .FG_BLACK
    mv r10, r0
    xor r10, r1

a odpovídající úsek vygenerovaného souboru .out

char_loop:
    # Set black on white/yellow checkered pattern
    .set r2, .BG_WHITE | .FG_BLACK
;   MACRO /home/beran/devel/fpga/mb50/mb50sw/sys/macros.s:16
        ldis REG, pc
;     0f06: ldis r2, r15
;     0f06: $data_b 0x0c, 0x2f # 0x2f0c
        $data_w EXPR
;     0f08: $data_b 0x07, 0x00 # 0x0007
;   END_MACRO /home/beran/devel/fpga/mb50/mb50sw/app/demo1.s:56
    mv r10, r0
; 0f0a: mv r10, r0
; 0f0a: $data_b 0x0e, 0xa0 # 0xa00e
    xor r10, r1
; 0f0c: xor r10, r1
; 0f0c: $data_b 0x1a, 0xa1 # 0xa11a

Assembler generuje přímo spustitelný program, neexistuje zde žádný linker, jenž by skládal samostatně přeložené moduly dohromady. Není ale potřeba psát vše do jednoho souboru. Pomocí direktivy $use se dá výsledný program skládat z více zdrojových souborů.

Celý překlad probíhá v jednom průchodu zdrojovým kódem. Pokud se na nějakém místě odkazujeme na pojmenovanou konstantu nebo makro, musí být příslušné jméno definované před místem použití. Výjimkou jsou návěští (labels), která mohou být v kódu definovaná i za místem použití, protože jinak by nešly zapsat dopředné skoky.

Při překladu ze zdrojového textu do strojového kódu provádí assembler několik hlavních operací. Především podle tabulky instrukčních kódů převádí symbolický zápis instrukcí do binární podoby. Do výsledného binárního kódu vkládá data definovaná direktivami $data_b a $data_w, jejichž hodnoty se definují jako numerické nebo řetězcové literály, jména konstant definovaných v direktivách $const, jména návěští (nahradí se příslušnou adresou), nebo kombinace těchto hodnot pomocí aritmetických operátorů. Assembler postupně buduje tabulku pro převod návěští na absolutní adresy. Naopak odkazy na návěští nahrazuje adresami, buď již při zpracování zdrojového kódu, nebo při druhém průchodu tabulkou návěští po přečtení celého vstupního souboru.

Důležitou funkcí je také definice a expanze maker. Direktivou $macro se definuje makro. Následně je možné toto makro, volitelně s parametry, použít jinde v kódu. Použití makra je nahrazeno tělem makra, ohraničeným řádky $macro a $end_macro, v němž se jména parametrů makra nahradí hodnotami příslušných argumentů zadaných při použití makra. V těle mohou být použita další makra, expanze probíhá rekurzivně.

Makra se používají pro dva hlavní účely. Definují se pomocí nich pseudoinstrukce, tedy operace tvářící se jako instrukce, ale ve skutečnosti implementované pomocí jedné nebo více skutečných instrukcí. Příklady takových maker jsou nop (žádná operace), definované jako instrukce mv r0, r0, nebo set, REG, EXPR (uložení hodnoty do registru), definované

$macro set, REG, EXPR
    ldis REG, pc
    $data_w EXPR
$end_macro

Další oblastí využití maker je definice opakovaně použitelných úseků kódu, které z nějakého důvodu nechceme volat jako podprogramy, např. makra save_all a restore_all pro uložení všech registrů na zásobník na začátku podprogramu a pro obnovení registrů ze zásobníku před návratem z podprogramu.

Ukázky zdrojových souborů pro assembler je možné najít v adresáři mb50/mb50sw.

linux_sprava_tip

Obsah následujícího dílu

Příště si představíme systémovou knihovnu počítače MB50 a použijeme ji v několika ukázkových programech.

(Autorem obrázků je Martin Beran.)

Neutrální ikona do widgetu na odběr článků ze seriálů

Zajímá vás toto téma? Chcete se o něm dozvědět víc?

Objednejte si upozornění na nově vydané články do vašeho mailu. Žádný článek vám tak neuteče.


Autor článku

Vystudoval informatiku na MFF UK v Praze, kde následně několik let učil programování v Unixu. Poté se dlouhá léta věnoval síťové bezpečnosti a programování firewallů. V současnosti se zabývá vývojem interních backendových systémů ve společnosti Gen (dříve Avast).

'; document.getElementById('preroll-iframe').onload = function () { setupIframe(); } prerollContainer = document.getElementsByClassName('preroll-container-iframe')[0]; } function setupIframe() { prerollDocument = document.getElementById('preroll-iframe').contentWindow.document; let el = prerollDocument.createElement('style'); prerollDocument.head.appendChild(el); el.innerText = "#adContainer>div:nth-of-type(1),#adContainer>div:nth-of-type(1) > iframe { width: 99% !important;height: 99% !important;max-width: 100%;}#videoContent,body{ width:100vw;height:100vh}body{ font-family:'Helvetica Neue',Arial,sans-serif}#videoContent{ overflow:hidden;background:#000}#adMuteBtn{ width:35px;height:35px;border:0;background:0 0;display:none;position:absolute;fill:rgba(230,230,230,1);bottom:20px;right:25px}"; videoContent = prerollDocument.getElementById('contentElement'); videoContent.style.display = 'none'; videoContent.volume = 1; videoContent.muted = false; const playPromise = videoContent.play(); if (playPromise !== undefined) { playPromise.then(function () { console.log('PREROLL sound allowed'); // setUpIMA(true); videoContent.volume = 1; videoContent.muted = false; setUpIMA(); }).catch(function () { console.log('PREROLL sound forbidden'); videoContent.volume = 0; videoContent.muted = true; setUpIMA(); }); } } function setupDimensions() { prerollWidth = Math.min(iinfoPrerollPosition.offsetWidth, 480); prerollHeight = Math.min(iinfoPrerollPosition.offsetHeight, 320); } function setUpIMA() { google.ima.settings.setDisableCustomPlaybackForIOS10Plus(true); google.ima.settings.setLocale('cs'); google.ima.settings.setNumRedirects(10); // Create the ad display container. createAdDisplayContainer(); // Create ads loader. adsLoader = new google.ima.AdsLoader(adDisplayContainer); // Listen and respond to ads loaded and error events. adsLoader.addEventListener( google.ima.AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED, onAdsManagerLoaded, false); adsLoader.addEventListener( google.ima.AdErrorEvent.Type.AD_ERROR, onAdError, false); // An event listener to tell the SDK that our content video // is completed so the SDK can play any post-roll ads. const contentEndedListener = function () { adsLoader.contentComplete(); }; videoContent.onended = contentEndedListener; // Request video ads. const adsRequest = new google.ima.AdsRequest(); adsRequest.adTagUrl = iinfoVastUrls[iinfoVastUrlIndex]; console.log('Preroll advert: ' + iinfoVastUrls[iinfoVastUrlIndex]); videoContent.muted = false; videoContent.volume = 1; // Specify the linear and nonlinear slot sizes. This helps the SDK to // select the correct creative if multiple are returned. // adsRequest.linearAdSlotWidth = prerollWidth; // adsRequest.linearAdSlotHeight = prerollHeight; adsRequest.nonLinearAdSlotWidth = 0; adsRequest.nonLinearAdSlotHeight = 0; adsLoader.requestAds(adsRequest); } function createAdDisplayContainer() { // We assume the adContainer is the DOM id of the element that will house // the ads. prerollDocument.getElementById('videoContent').style.display = 'none'; adDisplayContainer = new google.ima.AdDisplayContainer( prerollDocument.getElementById('adContainer'), videoContent); } function unmutePrerollAdvert() { adVolume = !adVolume; if (adVolume) { adsManager.setVolume(0.3); prerollDocument.getElementById('adMuteBtn').innerHTML = ''; } else { adsManager.setVolume(0); prerollDocument.getElementById('adMuteBtn').innerHTML = ''; } } function onAdsManagerLoaded(adsManagerLoadedEvent) { // Get the ads manager. const adsRenderingSettings = new google.ima.AdsRenderingSettings(); adsRenderingSettings.restoreCustomPlaybackStateOnAdBreakComplete = true; adsRenderingSettings.loadVideoTimeout = 12000; // videoContent should be set to the content video element. adsManager = adsManagerLoadedEvent.getAdsManager(videoContent, adsRenderingSettings); // Add listeners to the required events. adsManager.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR, onAdError); adsManager.addEventListener( google.ima.AdEvent.Type.CONTENT_PAUSE_REQUESTED, onContentPauseRequested); adsManager.addEventListener( google.ima.AdEvent.Type.CONTENT_RESUME_REQUESTED, onContentResumeRequested); adsManager.addEventListener( google.ima.AdEvent.Type.ALL_ADS_COMPLETED, onAdEvent); // Listen to any additional events, if necessary. adsManager.addEventListener(google.ima.AdEvent.Type.LOADED, onAdEvent); adsManager.addEventListener(google.ima.AdEvent.Type.STARTED, onAdEvent); adsManager.addEventListener(google.ima.AdEvent.Type.COMPLETE, onAdEvent); playAds(); } function playAds() { // Initialize the container. Must be done through a user action on mobile // devices. videoContent.load(); adDisplayContainer.initialize(); // setupDimensions(); try { // Initialize the ads manager. Ad rules playlist will start at this time. adsManager.init(1920, 1080, google.ima.ViewMode.NORMAL); // Call play to start showing the ad. Single video and overlay ads will // start at this time; the call will be ignored for ad rules. adsManager.start(); // window.addEventListener('resize', function (event) { // if (adsManager) { // setupDimensions(); // adsManager.resize(prerollWidth, prerollHeight, google.ima.ViewMode.NORMAL); // } // }); } catch (adError) { // An error may be thrown if there was a problem with the VAST response. // videoContent.play(); } } function onAdEvent(adEvent) { const ad = adEvent.getAd(); console.log('Preroll event: ' + adEvent.type); switch (adEvent.type) { case google.ima.AdEvent.Type.LOADED: if (!ad.isLinear()) { videoContent.play(); } prerollDocument.getElementById('adContainer').style.width = '100%'; prerollDocument.getElementById('adContainer').style.maxWidth = '640px'; prerollDocument.getElementById('adContainer').style.height = '360px'; break; case google.ima.AdEvent.Type.STARTED: window.addEventListener('scroll', onActiveView); if (ad.isLinear()) { intervalTimer = setInterval( function () { // Example: const remainingTime = adsManager.getRemainingTime(); // adsManager.pause(); }, 300); // every 300ms } prerollDocument.getElementById('adMuteBtn').style.display = 'block'; break; case google.ima.AdEvent.Type.ALL_ADS_COMPLETED: if (ad.isLinear()) { clearInterval(intervalTimer); } if (prerollLastError === 303) { playYtVideo(); } break; case google.ima.AdEvent.Type.COMPLETE: if (ad.isLinear()) { clearInterval(intervalTimer); } playYtVideo(); break; } } function onAdError(adErrorEvent) { console.log(adErrorEvent.getError()); prerollLastError = adErrorEvent.getError().getErrorCode(); if (!loadNext()) { playYtVideo(); } } function loadNext() { iinfoVastUrlIndex++; if (iinfoVastUrlIndex < iinfoVastUrls.length) { iinfoPrerollPosition.remove(); playPrerollAd(); } else { return false; } adVolume = 1; return true; } function onContentPauseRequested() { videoContent.pause(); } function onContentResumeRequested() { videoContent.play(); } function onActiveView() { if (prerollContainer) { const containerOffset = prerollContainer.getBoundingClientRect(); const windowHeight = window.innerHeight; if (containerOffset.top < windowHeight/1 && containerOffset.bottom > 0.0) { if (prerollPaused) { adsManager.resume(); prerollPaused = false; } return true; } else { if (!prerollPaused) { adsManager.pause(); prerollPaused = true; } } } return false; } function playYtVideo() { iinfoPrerollPosition.remove(); youtubeIframe.style.display = 'block'; youtubeIframe.src += '&autoplay=1&mute=1'; } }
'; document.getElementById('outstream-iframe').onload = function () { setupIframe(); } replayScreen = document.getElementById('iinfoOutstreamReplay'); iinfoOutstreamPosition = document.getElementById('iinfoOutstreamPosition'); outstreamContainer = document.getElementsByClassName('outstream-container')[0]; setupReplayScreen(); } function setupIframe() { outstreamDocument = document.getElementById('outstream-iframe').contentWindow.document; let el = outstreamDocument.createElement('style'); outstreamDocument.head.appendChild(el); el.innerText = "#adContainer>div:nth-of-type(1),#adContainer>div:nth-of-type(1) > iframe { width: 99% !important;height: 99% !important;max-width: 100%;}#videoContent,body{ width:100vw;height:100vh}body{ font-family:'Helvetica Neue',Arial,sans-serif}#videoContent{ overflow:hidden;background:#000}#adMuteBtn{ width:35px;height:35px;border:0;background:0 0;display:none;position:absolute;fill:rgba(230,230,230,1);bottom:-5px;right:25px}"; videoContent = outstreamDocument.getElementById('contentElement'); videoContent.style.display = 'none'; videoContent.volume = 1; videoContent.muted = false; if ( location.href.indexOf('rejstriky.finance.cz') !== -1 || location.href.indexOf('finance-rejstrik') !== -1 || location.href.indexOf('firmy.euro.cz') !== -1 || location.href.indexOf('euro-rejstrik') !== -1 || location.href.indexOf('/rejstrik/') !== -1 || location.href.indexOf('/rejstrik-firem/') !== -1) { outstreamDirectPlayed = true; soundAllowed = true; iinfoVastUrlIndex = 0; } if (!outstreamDirectPlayed) { console.log('OUTSTREAM direct'); setUpIMA(true); } else { if (soundAllowed) { const playPromise = videoContent.play(); if (playPromise !== undefined) { playPromise.then(function () { console.log('OUTSTREAM sound allowed'); setUpIMA(false); }).catch(function () { console.log('OUTSTREAM sound forbidden'); renderBanner(); }); } } else { renderBanner(); } } } function getWrapper() { let articleWrapper = document.querySelector('.rs-outstream-placeholder'); // Outstream Placeholder from RedSys manipulation if (articleWrapper && articleWrapper.style.display !== 'block') { articleWrapper.innerHTML = ""; articleWrapper.style.display = 'block'; } // Don't render OutStream on homepages if (articleWrapper === null) { if (document.querySelector('body.p-index')) { return null; } } if (articleWrapper === null) { articleWrapper = document.getElementById('iinfo-outstream'); } if (articleWrapper === null) { articleWrapper = document.querySelector('.layout-main__content .detail__article p:nth-of-type(6)'); } if (articleWrapper === null) { // Euro, Autobible, Zdravi articleWrapper = document.querySelector('.o-article .o-article__text p:nth-of-type(6)'); } if (articleWrapper === null) { articleWrapper = document.getElementById('sidebar'); } if (!articleWrapper) { console.error("Outstream wrapper of article was not found."); } return articleWrapper; } function setupDimensions() { outstreamWidth = Math.min(iinfoOutstreamPosition.offsetWidth, 480); outstreamHeight = Math.min(iinfoOutstreamPosition.offsetHeight, 320); } /** * Sets up IMA ad display container, ads loader, and makes an ad request. */ function setUpIMA(direct) { google.ima.settings.setDisableCustomPlaybackForIOS10Plus(true); google.ima.settings.setLocale('cs'); google.ima.settings.setNumRedirects(10); // Create the ad display container. createAdDisplayContainer(); // Create ads loader. adsLoader = new google.ima.AdsLoader(adDisplayContainer); // Listen and respond to ads loaded and error events. adsLoader.addEventListener( google.ima.AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED, onAdsManagerLoaded, false); adsLoader.addEventListener( google.ima.AdErrorEvent.Type.AD_ERROR, onAdError, false); // An event listener to tell the SDK that our content video // is completed so the SDK can play any post-roll ads. const contentEndedListener = function () { adsLoader.contentComplete(); }; videoContent.onended = contentEndedListener; // Request video ads. const adsRequest = new google.ima.AdsRequest(); if (direct) { adsRequest.adTagUrl = directVast; console.log('Outstream DIRECT CAMPAING advert: ' + directVast); videoContent.muted = true; videoContent.volume = 0; outstreamDirectPlayed = true; } else { adsRequest.adTagUrl = iinfoVastUrls[iinfoVastUrlIndex]; console.log('Outstream advert: ' + iinfoVastUrls[iinfoVastUrlIndex]); videoContent.muted = false; videoContent.volume = 1; } // Specify the linear and nonlinear slot sizes. This helps the SDK to // select the correct creative if multiple are returned. // adsRequest.linearAdSlotWidth = outstreamWidth; // adsRequest.linearAdSlotHeight = outstreamHeight; adsRequest.nonLinearAdSlotWidth = 0; adsRequest.nonLinearAdSlotHeight = 0; adsLoader.requestAds(adsRequest); } function setupReplayScreen() { replayScreen.addEventListener('click', function () { iinfoOutstreamPosition.remove(); iinfoVastUrlIndex = 0; outstreamInit(); }); } /** * Sets the 'adContainer' div as the IMA ad display container. */ function createAdDisplayContainer() { // We assume the adContainer is the DOM id of the element that will house // the ads. outstreamDocument.getElementById('videoContent').style.display = 'none'; adDisplayContainer = new google.ima.AdDisplayContainer( outstreamDocument.getElementById('adContainer'), videoContent); } function unmuteAdvert() { adVolume = !adVolume; if (adVolume) { adsManager.setVolume(0.3); outstreamDocument.getElementById('adMuteBtn').innerHTML = ''; } else { adsManager.setVolume(0); outstreamDocument.getElementById('adMuteBtn').innerHTML = ''; } } /** * Loads the video content and initializes IMA ad playback. */ function playAds() { // Initialize the container. Must be done through a user action on mobile // devices. videoContent.load(); adDisplayContainer.initialize(); // setupDimensions(); try { // Initialize the ads manager. Ad rules playlist will start at this time. adsManager.init(1920, 1080, google.ima.ViewMode.NORMAL); // Call play to start showing the ad. Single video and overlay ads will // start at this time; the call will be ignored for ad rules. adsManager.start(); // window.addEventListener('resize', function (event) { // if (adsManager) { // setupDimensions(); // adsManager.resize(outstreamWidth, outstreamHeight, google.ima.ViewMode.NORMAL); // } // }); } catch (adError) { // An error may be thrown if there was a problem with the VAST response. // videoContent.play(); } } /** * Handles the ad manager loading and sets ad event listeners. * @param { !google.ima.AdsManagerLoadedEvent } adsManagerLoadedEvent */ function onAdsManagerLoaded(adsManagerLoadedEvent) { // Get the ads manager. const adsRenderingSettings = new google.ima.AdsRenderingSettings(); adsRenderingSettings.restoreCustomPlaybackStateOnAdBreakComplete = true; adsRenderingSettings.loadVideoTimeout = 12000; // videoContent should be set to the content video element. adsManager = adsManagerLoadedEvent.getAdsManager(videoContent, adsRenderingSettings); // Add listeners to the required events. adsManager.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR, onAdError); adsManager.addEventListener( google.ima.AdEvent.Type.CONTENT_PAUSE_REQUESTED, onContentPauseRequested); adsManager.addEventListener( google.ima.AdEvent.Type.CONTENT_RESUME_REQUESTED, onContentResumeRequested); adsManager.addEventListener( google.ima.AdEvent.Type.ALL_ADS_COMPLETED, onAdEvent); // Listen to any additional events, if necessary. adsManager.addEventListener(google.ima.AdEvent.Type.LOADED, onAdEvent); adsManager.addEventListener(google.ima.AdEvent.Type.STARTED, onAdEvent); adsManager.addEventListener(google.ima.AdEvent.Type.COMPLETE, onAdEvent); playAds(); } /** * Handles actions taken in response to ad events. * @param { !google.ima.AdEvent } adEvent */ function onAdEvent(adEvent) { // Retrieve the ad from the event. Some events (for example, // ALL_ADS_COMPLETED) don't have ad object associated. const ad = adEvent.getAd(); console.log('Outstream event: ' + adEvent.type); switch (adEvent.type) { case google.ima.AdEvent.Type.LOADED: // This is the first event sent for an ad - it is possible to // determine whether the ad is a video ad or an overlay. if (!ad.isLinear()) { // Position AdDisplayContainer correctly for overlay. // Use ad.width and ad.height. videoContent.play(); } outstreamDocument.getElementById('adContainer').style.width = '100%'; outstreamDocument.getElementById('adContainer').style.maxWidth = '640px'; outstreamDocument.getElementById('adContainer').style.height = '360px'; break; case google.ima.AdEvent.Type.STARTED: window.addEventListener('scroll', onActiveView); // This event indicates the ad has started - the video player // can adjust the UI, for example display a pause button and // remaining time. if (ad.isLinear()) { // For a linear ad, a timer can be started to poll for // the remaining time. intervalTimer = setInterval( function () { // Example: const remainingTime = adsManager.getRemainingTime(); // adsManager.pause(); }, 300); // every 300ms } outstreamDocument.getElementById('adMuteBtn').style.display = 'block'; break; case google.ima.AdEvent.Type.ALL_ADS_COMPLETED: if (ad.isLinear()) { clearInterval(intervalTimer); } if (outstreamLastError === 303) { if (isBanner) { renderBanner(); } else { replayScreen.style.display = 'flex'; } } break; case google.ima.AdEvent.Type.COMPLETE: // This event indicates the ad has finished - the video player // can perform appropriate UI actions, such as removing the timer for // remaining time detection. if (ad.isLinear()) { clearInterval(intervalTimer); } if (isBanner) { renderBanner(); } else { replayScreen.style.display = 'flex'; } break; } } /** * Handles ad errors. * @param { !google.ima.AdErrorEvent } adErrorEvent */ function onAdError(adErrorEvent) { // Handle the error logging. console.log(adErrorEvent.getError()); outstreamLastError = adErrorEvent.getError().getErrorCode(); if (!loadNext()) { renderBanner(); } } function renderBanner() { if (isBanner) { console.log('Outstream: Render Banner'); iinfoOutstreamPosition.innerHTML = ""; iinfoOutstreamPosition.style.height = "330px"; iinfoOutstreamPosition.appendChild(bannerDiv); } else { console.log('Outstream: Banner is not set'); } } function loadNext() { iinfoVastUrlIndex++; if (iinfoVastUrlIndex < iinfoVastUrls.length) { iinfoOutstreamPosition.remove(); outstreamInit(); } else { return false; } adVolume = 1; return true; } /** * Pauses video content and sets up ad UI. */ function onContentPauseRequested() { videoContent.pause(); // This function is where you should setup UI for showing ads (for example, // display ad timer countdown, disable seeking and more.) // setupUIForAds(); } /** * Resumes video content and removes ad UI. */ function onContentResumeRequested() { videoContent.play(); // This function is where you should ensure that your UI is ready // to play content. It is the responsibility of the Publisher to // implement this function when necessary. // setupUIForContent(); } function onActiveView() { if (outstreamContainer) { const containerOffset = outstreamContainer.getBoundingClientRect(); const windowHeight = window.innerHeight; if (containerOffset.top < windowHeight/1 && containerOffset.bottom > 0.0) { if (outstreamPaused) { adsManager.resume(); outstreamPaused = false; } return true; } else { if (!outstreamPaused) { adsManager.pause(); outstreamPaused = true; } } } return false; } let outstreamInitInterval; if (typeof cpexPackage !== "undefined") { outstreamInitInterval = setInterval(tryToInitializeOutstream, 100); } else { const wrapper = getWrapper(); if (wrapper) { let outstreamInitialized = false; window.addEventListener('scroll', () => { if (!outstreamInitialized) { const containerOffset = wrapper.getBoundingClientRect(); const windowHeight = window.innerHeight; if (containerOffset.top < windowHeight / 1 && containerOffset.bottom > 0.0) { outstreamInit(); outstreamInitialized = true; } } }); } } function tryToInitializeOutstream() { const wrapper = getWrapper(); if (wrapper) { const containerOffset = wrapper.getBoundingClientRect(); const windowHeight = window.innerHeight; if (containerOffset.top < windowHeight / 1 && containerOffset.bottom > 0.0) { if (cpexPackage.adserver.displayed) { clearInterval(outstreamInitInterval); outstreamInit(); } } } else { clearInterval(outstreamInitInterval); } } }
OSZAR »