Navrhujeme a vyrábíme vlastní CPU: mikroarchitektura procesoru

10. 4. 2025
Doba čtení: 6 minut

Sdílet

Vývojová deska s FPGA
Autor: Martin Beran
Co se děje v procesoru, když vykonává strojový kód? Jak se načítají, dekódují a provádějí jednotlivé instrukce? Jak procesor přistupuje k paměti? Co se stane, když přijde přerušení?

Co je mikroarchitektura

V předchozích dvou dílech jsme probrali návrh ISA, architektury instrukční sady, definující, jak se CPU jeví z pohledu programátora. Nyní se budeme zabývat mikroarchitekturou procesoru, tedy konkrétní implementací ISA v hardwaru. Zdrojové kódy ve VHDL pro celý počítač MB50 jsou v jednom adresáři na GitHubu. Soubory, které jsou součástí implementace CPU, mají jména ve tvaru  mb5016_*.vhd.

Procesor je zapojen do zbytku počítače pomocí signálů. Pokud by existoval jako samostatný integrovaný obvod, tyto signály by byly připojeny na vývody (piny) jeho pouzdra.

 Externí signály procesoru MB5016

Význam jednotlivých signálů:

  • Clk je vstup hodinového signálu o frekvenci 50 MHz
  • DataBusRd, DataBusWr je 8bitová datová sběrnice. FPGA sice podporuje obousměrné fyzické I/O piny, ale interní signály mohou být pouze jednosměrné. Proto je datová sběrnice rozdělená na vstupní část pro čtení z paměti a výstupní část pro zápis do paměti.
  • Rst je signál pro reset procesoru.
  • Run spustí běh procesoru (vykonávání instrukcí) nastavením na logickou 1. Když je hodnota 0, procesor stojí a externí ladicí rozhraní může číst a měnit jeho interní stav, např. hodnoty v registrech.
  • Irq je pět vstupů přerušení. Aktuálně se používají pouze dva, pro systémové hodiny a klávesnici.
  • RegIdx, RegWr, RegCsr, RegDataWr, RegRd, RegDataRd používá ladicí rozhraní pro čtení a zápis hodnot v obecných a řídicích registrech.
  • Busy, Halted, Breakpoint slouží k signalizaci aktuálního stavu procesoru: vykonává instrukci, zastaven výjimkou při zakázaném přerušení, zastaven instrukcí  brk.
  • AddrBus je 16bitová adresová sběrnice.
  • Rd zahajuje čtení z paměti nastavením na logickou 1. Na AddrBus musí zároveň nastavit adresu, za dva takty se na DataBusRd objeví bajt přečtený z paměti.
  • Wr provádí zápis do paměti. Při nastavení na logickou 1 musí zároveň na AddrBus nastavit adresu a na DataBusWr zapisovaný bajt.

Registry

Sada 16 obecných 16bitových registrů r0...r15 je implementována VHDL entitou mb5016_registers v souboru mb5016_registers.vhd. Obsahuje jak paměťové buňky pro zapamatování jednotlivých bitů (celkem 256), tak i multiplexory a demultiplexory, jež podle zadaného čísla registru připojují na čtecí a zápisové signály příslušný registr.

Na implementaci registrů si můžeme demonstrovat typický problém při návrhu digitálních logických obvodů, totiž hledání kompromisu mezi složitostí obvodu a rychlostí zpracování. Při vykonávání instrukce se obvykle musí přečíst jeden (např. v instrukcích MV, INC1) nebo dva ( EXCH, ADD) operandy z registrů, jejich hodnoty poslat na vstupy aritmeticko-logické jednotky (ALU), která spočítá jednu ( MV, ADD) nebo dvě ( EXCH, MULUU) výstupní hodnoty a nové hodnoty příznakových bitů. Tyto hodnoty se musí zapsat zpět do registrů.

Všechny tyto kroky lze provést v jednom taktu hodin, což ale vyžaduje dva multiplexory pro čtení z registrů, dva demultiplexory pro zápis do registrů, obvod pro zápis příznakových bitů v registru f a logiku pro řešení konfliktů, pokud je pro zápis zvolen registr f nebo dva stejné registry.

Alternativně bychom mohli použít pouze jeden multiplexor a jeden demultiplexor. Na vstupy a výstupy ALU bychom museli přidat pomocné registry a výpočet pomocí ALU bychom rozdělili do několika taktů, např. tímto způsobem:

  1. Načteme první operand z registru do pomocného registru.
  2. Připojíme pomocný registr a registr obsahující druhý operand na vstupy ALU. Výstupy z ALU zapíšeme do dvou pomocných registrů. Zároveň připojíme registr f na příznakový výstup ALU a zapíšeme nové hodnoty příznakových bitů.
  3. Obsah prvního pomocného registru zapíšeme do vybraného registru.
  4. Obsah druhého pomocného registru zapíšeme do (jiného) vybraného registru.

Tento algoritmus bychom přidali do stavového automatu řídicího vykonávání instrukcí v řadiči procesoru. Možná bychom tím ušetřili několik logických elementů za cenu prodloužení času na vykonání instrukce o tři takty. Velikost úspory závisí na detailech zapojení registrů a řadiče a na optimalizacích prováděných při syntéze obvodu. Obecně se dá předpokládat, že rozdíl mezi oběma variantami by vzrostl, pokud bychom přidávali další registry nebo zvětšovali šířku (počet bitů) jednotlivých registrů.

Protože jsme nenarazili na omezení dané celkovým dostupným počtem logických elementů, je v procesoru MB5016 použita rychlejší jednotaktová implementace registrů. Jestliže instrukce chce zapsat dvě hodnoty do stejného registru, konflikt se vyřeší zápisem pouze první hodnoty a zahozením druhé. Jestliže je použit příznakový registr jako cílový registr instrukce, nezapisují se změny příznaků, ale pouze výsledek. To je důležité pro možnost manipulace s jednotlivými příznaky pomocí logických operací, které normálně mění příznaky podle výsledku operace.

Podobně jako obecné registry jsou implementované řídicí registry csr0...csr15 entitou mb5016_csr v souboru mb5016_csr.vhd. Jejich implementace je jednodušší, protože jsou využívány pouze první čtyři registry, vždy se čte nebo zapisuje pouze jeden registr a nemusíme řešit ukládání příznaků. I tak se jedná o netriviální obvod, jak je vidět na obrázku.

 Výřez schématu implementace řídicích registrů, každý obdélník v prostředním sloupci představuje jednobitový registr obsažený v logickém elementu FPGA

Aritmeticko-logická jednotka

Procesor MB5016 obsahuje 16bitovou aritmeticko-logickou jednotku (arithmetic-logic unit, ALU). Příslušná entita mb5016_alu je v souboru mb5016_alu.vhd. Jedná se o čistě kombinační obvod. To znamená, že nemá žádné interní registry a požadovanou operaci počítá v jednom taktu. ALU má dva 16bitové vstupy pro operandy, vstup pro výběr operace, dva 16bitové výstupy pro výsledky a čtyři jednobitové výstupy s hodnotami příznaků Zero, Carry, Sign a Overflow.

Kromě aritmetických ( ADD, SUB, SHL, atd.) a logických instrukcí ( NOT, AND, OR, XOR) se ALU používá i při přesunech dat mezi registry ( MV, EXCH, CSRR, CSRW) a z/do paměti ( LD, LDB, STO, STOB). Pro tyto instrukce se využívají pouze multiplexory a demultiplexory zapojené mezi registry a ALU. Operace prováděná v těchto případech je prosté kopírování vstupů na výstupy. ALU se také používá na generování vybraných konstant, jako je nula nebo kódy příčin výjimek.

Při implementaci ALU hodně pomáhá, že nemusíme skládat obvody počítající aritmetické a logické operace z jednotlivých logických elementů. Syntetizér umí vygenerovat zapojení přímo z použitých operátorů v jazyce VHDL. Například implementace sčítačky včetně přidání bitu pro přenos se tak redukuje na jednoduchý zápis  OutA := ('0' & InA) + ('0' & InB);

 Zapojení ALU a výřez zahrnující implementace několika operací

Řadič

Řadič (Control Unit, CU) je implementovaný entitou mb5016_cu v souboru mb5016_cu.vhd. Úkolem řadiče je koordinovat všechny ostatní části CPU a řídit vykonávání instrukcí. Jádrem řadiče je konečný automat, viz obrázek, který na začátku vykonávání každé instrukce začíná ve stavu Init a po dokončení instrukce se do tohoto stavu opět vrací.

 Konečný automat řadiče

Zpracování instrukce začíná testem, zda není nastavený příznak přerušení. Pokud ano, místo původní instrukce se provede volání obslužné rutiny přerušení. V opačném případě se z paměti přečtou dva bajty tvořící instrukci. Následně se zavolá funkce decode_opcode , která dekóduje operační kód instrukce z prvního bajtu, a proceduroudecode_regs se extrahují čísla zdrojového a cílového registru z druhého bajtu instrukce.

Dekodér obsahuje tabulku popisující vlastnosti jednotlivých instrukcí:

  • instrukce je/není implementovaná,
  • instrukce je/není podmíněná, tedy prováděné akce závisí na výsledku testu bitu z příznakového registru,
  • volba operace v ALU,
  • uložení nebo zahození příznakových bitů,
  • přečtení 0, 1, nebo 2 bajtů z paměti před provedením operace v ALU,
  • zápis 0, 1, nebo 2 bajtů do paměti po provedení operace v ALU.

Následně automat prochází dalšími stavy podle výsledku dekódování a provádí jednotlivé dílčí akce: test podmínky, čtení z paměti, použití ALU, zápis do paměti. Některé instrukce vyžadují další akce, jež nezapadají do obecného schématu. Pro ty jsou pak v kódu konečného automatu definované speciální větve.

Propojení všech částí CPU

Jednotlivé části procesoru jsou propojeny entitou mb5016_cpu v souboru mb5016_cpu.vhd. Zde jsou definované instance entit pro registry, řídicí registry, ALU a řadič. Jejich signály jsou navzájem propojené s minimem přidané logiky. Jedná se o kombinaci strukturního (skládání z částí) a data flow (přiřazování signálů a jejich kombinace pomocí operátorů) přístupu k popisu obvodu ve VHDL. Hlavní entita CPU také řídí externí signály procesoru, vyjmenované na začátku tohoto článku.

hacking_tip

 Schéma interního zapojení procesoru MB5016

Obsah následujícího dílu

V následujícím dílu si ukážeme další obvody potřebné pro postavení celého počítače MB50. Nakonec CPU MB5016 a všechny další části poskládáme dohromady.

(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 »