24
PDF gerado usando o pacote de ferramentas em código aberto mwlib. Veja http://code.pediapress.com/ para mais informações. PDF generated at: Wed, 10 Sep 2014 18:22:54 UTC Introdução à Arquitetura de Computadores

Introdução à Arquitetura de Computadores

Embed Size (px)

DESCRIPTION

arq comp

Citation preview

Page 1: Introdução à Arquitetura de Computadores

PDF gerado usando o pacote de ferramentas em código aberto mwlib. Veja http://code.pediapress.com/ para mais informações.PDF generated at: Wed, 10 Sep 2014 18:22:54 UTC

Introdução à Arquitetura deComputadores

Page 2: Introdução à Arquitetura de Computadores

ConteúdoPáginas

Capa 1Introdução 2O que é o MIPS? 4Instruções do MIPS 5Representação das Instruções 9As Pseudo-Instruções 10Suporte à Funções 11Representação Numérica 14As Operações da Unidade Lógica Aritmética 17

ReferênciasFontes e Editores da Página 20Fontes, Licenças e Editores da Imagem 21

Licenças das páginasLicença 22

Page 3: Introdução à Arquitetura de Computadores

Capa 1

CapaIntrodução à Arquitetura de Computadores

Clique aqui para ir ao índiceIr para o índice

A imagem de capa deste wikilivro é de uma réplica do Cray-1 - o primeiro supercomputador da história. A réplicaestá em um museu alemão. O Cray-1 foi construído em 1971 e era mais veloz qque qualquer outro computador daépoca. De fato, demorou décadas para que surgissem computadores capazes de superar o Cray-1 em velocidade. Eleera capaz de efetuar 160 milhões operações aritméticas por segundo e custava U$700.000,00.O desenvolvimento de uma máquina tão rápida só foi possível graças à pesquisas em Arquitetura de Computadores.A Arquitetura de Computadores é ciência de selecionar e interconectar componentes de hardware para produzircomputadores que atendam a requisitos funcionais, de desempenho e custo. Ela também pesquisa formas de tornar oscomputadores máquinas mais eficientes, baratas e/ou rápidas.

Clique aqui para ir ao índiceIr para o índice

Page 4: Introdução à Arquitetura de Computadores

Introdução 2

Introdução

O Que é Arquitetura de ComputadoresA Arquitetura de Computadores é o projeto conceitual e fundamental da estrutura operacional de um sistemacomputacional. Ela é o estudo dos requisitos necessários para que um computador funcione e de como organizar osdiversos componentes para obter melhores desempenhos.Como computador entendemos qualquer tipo de dispositivo capaz de receber uma entrada e que retorna uma saídaapós realizar uma série de operações com base nos valores recebidos e armazenados. Existem vários tipos decomputadores. Uma das formas de classificá-los é por meio das seguintes categorias:• Desktop: Computadores de baixo-custo e com desempenho razoável para um usuário "comum".• Servidor: Máquinas projetadas para ter um desempenho considerado bom para uma aplicação muito grande e

complexa ou então para um número muito grande de operações mais simples. Alguns servidores são simplescomputadores de Desktop melhorados. Entretanto, existem também aqueles que possuem arquiteturas muito maissofisticadas que contam com dezenas ou até mesmo centenas de processadores.

• Sistemas Embarcados: Possuem um uso dedicado à uma única tarefa e normalmente vem embutidos em outrosaparelhos como celulares, microondas, elevadores ou veículos. Possuem uma Entrada/Saída muito simples.

Os princípios estudados em Arquitetura de Computadores são fundamentais para se projetar máquinas realmenteeficientes.

Computadores e as Várias Camadas de AbstraçãoComputadores são aparelhos extremamente complexos. Para compreender o seu funcionamento, precisamosentender várias camadas de abstração diferente.A camada mais baixa de todas é aquela formada por transistores, tensão e corrente elétrica. Quem costuma lidar comesta camada são físicos e engenheiros elétricos. Nesta camada estuda-se o funcionamento de transistores e circuitossempre levando em conta as propriedades físicas da corrente elétrica. Abaixo vemos um desenho representando umtransistor.

Uma camada acima, estão as portas lógicas - todas elas compostas por transistores. Neste nível estuda-se como criarestruturas mais complexas combinando-se as diversas portas como AND, OR e NOT para criar estruturas comomultiplexadores, flip-flops e somadores. Neste estágio poode-se usar lingüagens como o Verilog ou VHDL paraprogramar circuitos. Abaixo vemos desenhos que representam várias portas lógicas:

Page 5: Introdução à Arquitetura de Computadores

Introdução 3

Subindo mais um nível de abstração, começamos a lidar com estruturas mais complexas como registradores eunidades lógicas aritméticas - todas compostas por muitos flip-flops, somadores e multiplexadores. Vemos comotodas essas estruturas realmente geram as instruções de cada máquina e como cada instrução funciona. É neste nívelque costuma trabalhar um Arquiteto. Este será o nível que será abordado ao longo deste Wiki-livro. Abaixomostramos a imagem de uma Unidade Lógica Aritmética - estrutura usada por computadores para realizar cálculos:

Um nível além, estuda-se como combinar as instruções da camada anterior para realizar comandos mais sofisticadoscomo as operações da lingüagem C e como coordenar o funcionamento de um sistema operacional por meio deinterrupções e outros recursos. A imagem abaixo é um diagrama que representa o Kernel de um Sistema Operacionalsendo usado como um meio de comunicação entre o Software e o Hardware:

Page 6: Introdução à Arquitetura de Computadores

Introdução 4

Acima desta camada, está o estudo do funcionamento de funções de bibliotecas, APIs e a programação de aplicativose programas de computador simples.E finalmente, na camada de abstração mais superior está o funcionamento deum programa de computador do ponto de vista do usuário. Como utilizar um aplicativo já criado.

O que é o MIPS?

O Processador MIPS

O MIPS é o nome de uma arquitetura de processadores baseados no uso de registradores. As suas instruções tem àdisposição um conjunto de 32 registradores para realizar as operações. Entretanto, alguns destes registradores nãopodem ser usados por programadores, pois são usados pela própria máquina para armazenar informações úteis.

Page 7: Introdução à Arquitetura de Computadores

O que é o MIPS? 5

Processadores MIPS são do tipo RISC (Reduced Instruction Set Computer - ou seja, Computadores com Conjunto deInstruções Reduzidas). Isso significa que existe um conjunto bastante pequeno de instruções que o processador sabefazer. Combinando este pequeno número, podemos criar todas as demais operações.Pela sua elegância e simplicidade, processadores MIPS são bastante usados em cursos de arquiteturas de muitasuniversidades. Ele é considerado um processador bastante didático.Projetos MIPS são atualmente bastante usados em muitos sistemas embarcados como dispositivos Windows CE,roteadores Cisco e video-games como o Nintendo 64, Playstation, Playstation 2 e Playstation Portable.

A Filosofia do Projeto do MIPSQuando o MIPS estava sendo desenvolvido, quatro regras foram definidas para guiar o projeto. Elas são a filosofiado MIPS:•• A simplicidade favorece a regularidade.• O menor é (quase sempre) mais rápido. Levando em conta que uma corrente elétrica se propaga cerca de um

palmo por nanosegundo, circuitos simples são menores e portanto, mais rápidos.•• Um bom projeto demanda compromissos.• O caso comum DEVE ser mais rápido. É muito melhor tornar uma instrução que é usada 90% do tempo 10%

mais rápida do que fazer com que uma instrução usada em 10% das vezes torne-se 90% mais rápida. Esta regra ébaseada na "Lei de Amdahl".

Foram estas as regras usadas para decidir vários aspectos do funcionamento do MIPS - incluindo quais seriam suasinstruções e como elas funcionariam.

Instruções do MIPSVamos começar agora a estudar as instruções que existem em um computador MIPS. Para praticar e testar asinstruções caso você não tenha uma máquina MIPS é usar um simulador como o SPIM. O download do simuladorSPIM para Linux, Mac OS, Unix e Windows pode ser feito no link: [1].O objetivo deste capítulo não é fornecer um guia de referência exaustivo sobre todas as instruções dos MIPS. Iremosapenas apresentar um sub-conjunto mínimo de todas as instruções do MIPS que são o mínimo necessário para termosum computador funcionando.

Instruções Aritméticas Simplesadd $r1, $r2, $r3 # Esta instrução soma o conteúdo dos registradores

# $r2 e $r3 colocando o conteúdo no registrador $r1

addi $r4, $r1, 9 # Agora estamos somando o conteúdo do registrador

# $r1 com o valor imediato 9 e armazenando o

# resultado em $r4. O número imediato deve ter

# 16 bits.

addu $r5, $r6, $r4 # Quase igual ao add. Mas agora assumimos que

# todos os valores são não-negativos.

addiu $r7, $r8, 10 # Somamos o conteúdo de $r8 com o valor imediato

# 10 e armazenamos o resultado em $r7. Assume-se

Page 8: Introdução à Arquitetura de Computadores

Instruções do MIPS 6

# que todos os valores são não-negativos.

sub $r1, $r2, $r3 # Subtrai-se o conteúdo de $r3 do conteúdo de $r2

# e coloca-se em $r1. Também existe subi, subu e

# subiu que tem comportamento semelhante a addi,

# addu e addiu, mas para a subtração.

Instruções de Operadores Lógicosand $r1, $r2, $r3 # Realiza uma operação AND bit-a-bit entre $r3 e $r2.

# O resultado é armazenado em $r1.

andi $r1, $r2, 42 # Realiza uma operação AND bit-a-bit entre $r2 e o valor

# imediato 42. O resultado é armazenado em $r1. O número

# imediato deve caber em 16 bits.

or $r1, $r2, $r3 # Realiza uma operação OR bit-a-bit entre $r3 e $r2.

# O resultado é armazenado em $r1.

ori $r1, $r2, 42 # Realiza uma operação OR bit-a-bit entre $r2 e o valor

# imediato 42. O resultado é armazenado em $r1. O número

# imediato deve caber em 16 bits.

Podemos notar que as instruções seguem uma lógica clara. Até agora todas seguem uma mesma lógica. Além destas,não existem instruções mais complexas como potências ou raízes quadradas.Somente as operações matemáticas maissimples são representadas. Podemos ver aquelas 4 regras que formam a filosofia do projeto do MIPS em ação.

Instruções de Uso de memóriaAs instruções do uso da memória seguem uma lógica diferente das instruções de operações aritméticas. Pra começar,existem três tipos de instruções capazes de copiar dados da memória para os registradores.

lw $r1, 4($r2) # Load Word: Esta instrução carrega uma palavra (estrutura de 4 bytes)

# localizada no endereço representado pela soma do valor

# armazenado no registrador $r2 mais 4. O resultado é armazenado em $r1.

lh $r1, 6($r3) # Load Half: Esta instrução carrega uma estrutura de 2 bits localizada

# no endereço representado pela soma do valor armazeado no

# registrador $r3 mais o número 6. O resultado é armazenado em $r1.

lb $r1, 16($r2)# Load Byte: Esta instrução carrega um byte (8 bits) localizado no

# endereço representado pela soma do valor armazenado em $r2 mais o

# número 16. O resultado é armazenado em $r1.

Perceba que desta forma é rápido de carregar o conteúdo de um valor em um vetor. Basta saber o endereço do valorinicial do vetor e o índice de sua posição. Por exemplo, suponha que eu possua um vetor de caracteres char vetor[5].Supondo que o registrador $r1 contenha o endereço de vetor[0], para armazenar o valor de vetor[3] no registrador$r2 e assumindo que cada caractere possui um byte, basta usar a seguinte instrução:

lb $r2 24($r1)

Page 9: Introdução à Arquitetura de Computadores

Instruções do MIPS 7

Colocamos o 24 lá porque cada caractere ocupa 8 bits e queremos pegar o quarto caractere da seqüência. Logo,precisamos nos deslocar 24 bits para acharmos o caractere certo. Afinal, a distância entre o primeiro elemento dovetor armazenado em $r1 e o quarto item do vetor é de 24:

[0][ ][ ][ ][ ][ ][ ][ ].[1][ ][ ][ ][ ][ ][ ][ ].[2][ ][ ][ ][ ][ ][ ][ ].[3][ ][ ][ ][ ][ ][ ][ ]

Para armazenarmos conteúdo de um registrador na memória também existem 3 comandos:

sw $r1, 4($r2) # Store Word: Esta instrução carrega uma palavra (estrutura de 4 bytes)

# localizada no registrador $r1 e armazena no endereço representado

# pela soma do valor armazenado no registrador $r2 mais 4.

sh $r1, 4($r2) # Store Half: Esta instrução carrega uma estrutura de 2 bits

# localizada no registrador $r1 e armazena no endereço representado

# pela soma do valor armazenado no registrador $r2 mais 4.

sb $r1, 4($r2) # Store Byte: Esta instrução carrega um byte (8 bits)

# localizado no registrador $r1 e armazena no endereço representado

# pela soma do valor armazenado no registrador $r2 mais 4.

Vamos ver um exemplo de conversão de um código escrito em C para a lingüagem Assembly do MIPS assumindoque um int da lingüagem C tenha 4 bits:

/* CÓDIGO C */

typedef struct A{

int x;

int y;

int z;

int w;

}aType;

aType V[16];

(...)

aPtr = &(V[3]);

m = aPtr -> y;

n = aPtr -> w;

aPtr -> x = m + n;

Vamos assumir que o vetor V esteja no endereço 0x0800.0000. A estrutura aType é formada por 4 valores numéricosconsecutivos, cada um com 4 bits. Logo, cada aType ocupa 16 bits (4*4). Ou, em hexadecimal, 0x010 bits. Veja umarepresentação de um aType como é armazenado na memória:

[x][x][x][x][y][y][y][y][z][z][z][z][w][w][w][w]

Temos um vetor com 16 estruturas como esta chamado V. Logo, o vetor ocupa um espaço de 256 bits. Com estasinformações, concluímos que o código em Assembly necessário para fazer a operação mostrada no código em Cmostrado acima é:

Page 10: Introdução à Arquitetura de Computadores

Instruções do MIPS 8

la $r1, 0x0800.0030 # A instrução Load Address carrega em $r1 o endereço de V[3]. Falaremos sobre o la depois.

lw $r2, 4($r1) # Carregamos para $r2 o quarto bit à partir de V[3]. Ou seja, o valor de y.

lw $r3, 12($r1) # Agora carregamos para $r3 o valor do inteiro w.

add $r4, $r2, $r3 # Somamos os valores de y e w colocando o valor em $r4.

sw $r4, 0($r1) # Colocamos o valor da soma no lugar do x.

Instruções de Controle de FluxoAgora veremos instruções capazes de controlar o fluxo de execução de instruções de um computador. A primeiradelas é a instrução beq, ou Branch if Equal:

beq $r1, $r2, DESTINO

O que esta instrução faz é verificar se o valor de $r1 é igual à $r2. Caso isso seja verdadeiro, ela muda o valor doregistrador PC (Program Counter) que guarda o endereço da próxima instrução a ser executada. Se o valor passadocomo destino for 0, nada acontece. Nenhuma instrução é pulada, o programa executa a próxima instruçãonormalmente, independente de $r1 == $r2 ser verdadeiro ou falso. Se o valor passado for "1", pula-se uma instruçãose $r1 for igual a $r2. Se o valor passado for "2", pulam-se duas instruções e assim por diante. Também pode-sepassar valores negativos. Um valor de "-1" faz com que o programa entre em um loop infinito, nunca mais passandopara a próxima instrução. Uma "-2" faz com que voltemos uma instrução anterior, e assim por diante.Entretanto, o valor de DESTINO só pode ser no máximo um valor com 16 bits. Com um valor destes, só podemospular no máximo cerca de 32 000 instruções para frente ou para trás. Isso não é um problema na maioria das vezes émuito raro existir um programa que exija que você pule uma quantidade tão grande de instruções. Assumindo que 4linhas de código e Assembly correspondem a 1 linha em código C, para que pulemos uma quantidade tão grande deinstruções, teríamos que fazer um if com um bloco com mais de 8.000 linhas de código. Isso é algo extremamentedifícil de ocorrer em um programa real.Mas caso você realmente precise pular mais de 32 000 instruções, isso é perfeitamente possível, desde que vocêaltere antes o valor do registrador PC. Com isso, perde-se um pouco de desempenho, mas torna desvios de códigoextremamente grandes possível. E o importante é que o caso comum (programas em C com blocos menores de 8.000linhas) roda mais rápido (regra 4 da Filosofia de Projeto do MIPS).Além do beq, temos também o bne, ou Branch if Not Equal:

bne $r1, $r2, DESTINO

Ele funciona da mesma forma que o beq. A diferença é que ele pula um determinado número de instruções somentese o valor dos dois registradores for diferente.E finalmente, temos a instrução j, ou Jump:

j ENDEREÇO

Ele faz com que o programa passe a executar a instrução que é encontrada no endereço dado. O endereço passadopara a instrução j é sempre um número de 26 bits. Entretanto, os endereços da máquina sempre são números de 32bits. Então como será possível saber qual o endereço em que está a instrução desejada?É muito simples. Instruções sempre estarão em endereços múltiplos de 4. Então sabemos que os dois últimos bits doendereço são sempre 0. Não precisamos armazená-los. Já os dois primeiros bits, retiramos do PC, pois é sempremuito mais provável que nós usemos o controle de fluxo para saltar para uma posição não muito distante. Casoprecisemos saltar para um posição muito distante, basta alterarmos o valor do PC primeiro. Da mesma forma que foivisto no beq, o caso comum executa mais rápido desta forma. Isso é muito bom, mesmo que tenhamos mais trabalhoe perda de desempenho nas pouquíssimas vezes nas quais precisamos dar saltos muito longos.

Page 11: Introdução à Arquitetura de Computadores

Instruções do MIPS 9

Instruções de ComparaçõesPor fim, precisamos ver ainda instruções de comparação. Um exemplo é o slt, ou Set Less Than:

slt $r1, $r2, $r3

Ela armazena 1 em $r1 se $r2 < $r3 e 0 caso contrário.

Resumo dos Modos de EndereçamentoNas instruções do MIPS podemos representar os endereços de dados das seguintes formas:• A Registrador: Representamos o dado passando o nome do registrador no qual ele está contido. Ex: add $r1,

$r2, $r2.• Base-Deslocamento: Representamos o dado passando o endereço de um vetor no qual ele está e a quantidade de

bits a serem deslocados. Ex: lw $r5, 4($r65).• Imediato: Passamos o dado escrevendo o seu valor imediato. Ex: addi $r1, $r2, 456.• Relativo ao PC: Passamos o dado descrevendo o seu valor relativo ao endereço da instrução atual. Ex: beq $r1,

$r2, DESTINO.• Absoluto: passamos o valor informando o seu endereço (pseudo-)absoluto. Ex: j DESTINO.Existem apenas estas 5 formas de endereçamento no MIPS. Através delas, efetuamos todas as operações necessárias.

Referências[1] http:/ / pages. cs. wisc. edu/ ~larus/ spim. html

Representação das InstruçõesSabemos que tudo dentro do computador é representado por meio de bits - que podem ter o valor de 0s e 1s. Atémesmo as instruções de um processador MIPS. Vamos estudar agora como uma instrução é representada a nível debits. O MIPS reconhece 3 famílias diferentes de instruções. São elas:

As Instruções Tipo RComo exemplo de instruções do tipo R vistas, temos: add, addu, sub, subu, or e and.Elas são codificadas da seguinte forma:

[o][o][o][o][o][o] - [u][u][u][u][u] - [t][t][t][t][t] - [d][d][d][d][d] - [s][s][s][s][s] - [f][f][f][f][f][f]

Onde a letra "o", representa o opcode, ou Código de Operação. Ele avisa mais-ou-menos o que a instrução vai fazer.Por exemplo, uma instrução de soma add é muito semelhante à instrução xor que faz um XOR lógico bit-a-bit. Aúnica diferença é que na soma, devemos armazenar um bit a mais para representar o "vai um" quando somamos 1+1.Logo, as duas instruções tem o mesmo opcode.As letras "u" e "t" representam os operandos das instruções. Ou melhor, os registradores nos quais estão osoperandos.A letra "d" é o registrador de destino onde deve ser armazenado o resultado.A letra "s" ou Shampt representa o deslocamento de bits, ou seja um shift que pode se usado após a operação.Finalmente, a letra "f" é o código de função. É por meio dela que diferenciamos instruções semelhantes que tem omesmo opcode, como add e or.

Page 12: Introdução à Arquitetura de Computadores

Representação das Instruções 10

As Instruções Tipo IComo exemplo deste tipo de instrução, podemos citar todas aquelas que contam com um valor imediato, como addi,subi, ori, beq e bnq.Eles são codificados da seguinte forma:

[o][o][o][o][o][o] - [u][u][u][u][u] - [t][t][t][t][t] - [i][i][i][i][i][i][i][i][i][i][i][i][i][i][i][i]

A letra "o" representa o código da instrução. A letra "u" represeenta o número do registrador onde a o resultado daoperação é colocado. Já a letra "t" representa o número do registrador em que está um dos operandos. Já o "i"representa o número imediato. Agora vemos o porquê do valor passado como imediato nunca poder exceder os 16bits.

As Instruções Tipo JA única instrução vista no capítulo anterior do tipo J é a j. Ela é codificada da seguinte forma:

[o][o][o][o][o][o] - [d][d][d][d][d][d][d][d][d][d][d][d][d][d][d][d][d][d][d][d][d][d][d][d][d][d]

O "o" representa o código da operação j e d representa o destino.

As Pseudo-InstruçõesA linguagem Assembly de uma máquina costuma ser um reflexo direto de como são implementadas as instruções deum determinado processador. Entretanto, nem todas as instruções que temos à disposição quando programamos emAssembly são instruções verdadeiras para o processador. Algumas delas são na verdade pseudo-instruções.Pseudo-instruções costumam ser substituídas pelo montador ao gerar instruções para o computador na forma deLingüagem de Máquina. Pseudo-Instruções são na verdade combinações de mais de uma instrução. Vejamos agoraalguns exemplos:

A Pseudo-Instrução movemove $r1, $r2 # Copia o conteúdo do registrador $r2 para $r1

Ela é na verdade implementada da seguinte forma:

addu $r1, $r0, $r2 # Soma $r2 com zero e coloca o resultado em $r1

O registrador $r0 usado acima não é um registrador comum. Ele sempre possui o valor "0" e é um dos poucosregistradores cujo valor nunca pode ser alterado pelo programador.

Page 13: Introdução à Arquitetura de Computadores

As Pseudo-Instruções 11

A Pseudo-Instrução Load Addressla $r1, ENDEREÇO # Coloca o valor numérico de 32 bits "ENDEREÇO" em $r1

Esta instrução é muito útil para fazeer registradores receberem o valor de ponteiros para outros locais de memória.De fato, usamos esta pseudo-instrução no Capítulo anterior quando convertemos um código em C para o Assemblydo MIPS. Ela é na verdade implementada desta forma:

lui $r1, constHI # Carrega-se os 16 bits mais significativos em $r1

ori $r1, $r0, constLO # Executa-se um OR bit-a-bit entre o registrador

# com os 16 bits mais significativos e os 16 bits

# menos significativos

O que a instrução lui, ou Load Upper Immediate faz é uma operação shift de 16 bits para a esquerda e coloca noregistrador indicado. Este passo é necessário porque valores imediatos passados para instruções só podem ter 16 bitspor causa da limitação de espaço das instruções do Tipo I.Também existe a instrução li, ou Load Immediate que faz exatamente a mesma coisa que o la.Note que o montador que remove as pseudo-instruções e as substitui por instruções que realmente existem nohardware sempre podem verificar se o valor que queremos carregar no registrador cabe em 16 bits. Neste caso, ainstrução gerada fica bem mais simples e rápida:

addu $r1, $r0, $r2 # Soma $r2 com 0 e coloca em $r1

Suporte à FunçõesFunções são ferramentas muito importantes, pois tornam código escrito muito mais usável e torna a tarefa deprogramar mais produtiva. Elas permitem que um programador se concentre em apeenas uma tarefa de cada vez.Portanto, é essencial que um processador possua suporte á elas. Veremos como isso ocorre no MIPS.

As Etapas Necessárias para Invocar FunçõesQuando queremos chamar uma função, as seguintes etapas devem ser cumpridas:•• O programa principal deve colocar os parâmetros da função em um local que ela possa acessar.•• O programa principal deve ceeder o controle para a função.•• A função deve coletar todos oos parâmetros deixados pelo programa principal.•• A função deve executar a tarefa desejada.•• A função deve aramazenar seus resultados em um lugar em que o programa principal possa acessar.•• A função deve retornar o fluxo do código para o ponto imediatamente após ser chamada.Para que estes requisitos sejam cumpridos, as seguintes convenções fora criadas:• Os registradores $r4, $r5, $r6 e $r7 seriam usados para armazenar parâmetros de funções. Por causa desta

funcionalidade, tais registradores podem ser chamados pelos seus "apelidos": $a0, $a1, $a2 e $a3.• Os registradores $r2 e $r3 seriam usados para as funções armazenarem seus valores de retorno para o programa

principal. Por isso, eles costumam ser chamados de $v0 e $v1.Tais regras são apenas convenções. Nada impede que um programador as desrespeite e use outros registradores paraestabelecer comunicação entre funções e programas principais. Entretanto, para que um programa funcione bem emconjunto com todos os demais, é importante quee tais convenções sejam seguidas.

Page 14: Introdução à Arquitetura de Computadores

Suporte à Funções 12

Além dos registradores convencionados acima, o registrador $r31 também tem um papel importante. Ele semprearmazena o endereço de retorno para o qual a última função chamada deve retornar. Por ter uma função tãoimportante, este registrador é mais conhecido pelo apelido $ra.

Instruções de Uso de Funções: jal e jrA instrução que usamos para invocar uma função chama-se jal, ou Jump and Link. Ela é usada da seguinte forma:

jal ENDEREÇO_DA_FUNÇÃO

Entretanto, só devemos chamar esta função depois de já termos salvo os argumentos nos registradores apropriados.Depois da função executar todas as operações e salvar os resultados apropriados em $v0 e $v1, podemos usar ainstrução jr, ou Jump Register. Normalmente usamos a instrução passando para ela o valor do registrador $ra, quecontém o endereço certo para voltarmos:

jr $ra

Como exemplo de como isso pode ser feito, vamos converter para Assembly o seguinte código em C:

int example(int a, int b, int c, int d){

int f;

f = (a + b) - (c + d)

return f;

}

O código no Assembly do MIPS ficaria assim:

example: # Label

add $t0, $a0, $a1 # Soma a + b

add $t1, $a2, $a3 # Soma c + d

sub $v0, $t0, $t1 # Subtrai os dois valores e coloca o resultado no registrador de retorno

jr $ra # Retorna o controle para a função principal

Entretanto, observe que para realizarmos os cálculos necessários, precisamos usar registradores. Ao fazer isso, existeo grande risco de apagarmos dados importantes do programa principal. Para evitar isso, existe uma convenção quediz que os registradores $r8 até $r15, $r24 e $r25 são registradores temporários. Por isso, eles costumam serchamados de $t0 até $t9. Após chamar uma função, o programa principal não deve esperar que os valores destesregistradores sejam os mesmos. Somente dados que estejam nos registradores $r16 até $r23 são sempre preservadosentre funções. Por esta razão, tais registradores permanentes (salvos) são chamados de $s0 até $s7.

Funções com Muitas VariáveisExistem funções que podem precisar receber mais do que 4 parâmetros. Entretanto, só temos registradoressuficientes para armazenar 4 parâmetros. Nós também podemos querer retornar mais do que os 2 valores permitidospelos registradores. Ou então, nossa função pode precisar armazenar mais do que 10 valores temporários. O quefazer para resolver isso?Como o espaço dentro de registradores é bastante limitado, só nos resta apelar para a pilha da memória. Porconvenção, o registrador $r29, ou $sp (Stack Pointer) armazena o endereço na pilha de onde novos valores podemser colocados. Acessando seu endereço e alterando-o podemos guardar valores na memória. O acesso à pilha damemória é muito mais lento que o acesso à registradores, mas este é o único modo de podermos armazenar umaquantidade muito maior de informação.

Page 15: Introdução à Arquitetura de Computadores

Suporte à Funções 13

Por motivos históricos, a pilha "cresce" sempre dos valores de endereços altos para valores menores. Ou seja, paraalocar espaço para mais valores, precisamos sempre decrementar o seu valor. Incrementando-o, estamos na verdaderemovendo os últimos dados da pilha.Vamos reescrever agora o código Assembly visto acima, desta vez preeservando os valores dos registradores $t0 e$t1 para vermos como ficaria o resultado:

example: # Label

addi $sp, $sp, -8 # Alocamos espaço para dois valores de 4 bits.

sw $t1, 4($sp) # Preservamos o valor de $t1 à partir do 4o bit após o Stack Pointer

sw $t0, 0($sp) # Preservamos o valor de $t0 sobre o Stack Pointer

add $t0, $a0, $a1 # Soma a + b

add $t1, $a2, $a3 # Soma c + d

sub $v0, $t0, $t1 # Subtrai os dois valores e coloca o resultado no registrador de retorno

lw $t0, 0($sp) # Retiramos o valor antigo salvo de $t0

lw $t1, 4($sp) # Retiramos da pilha o valor antigo de $t1

addi $sp, $sp, 8 # Voltamos o Stack Pointer para a posição original apagando de vez as variáveis locais

jr $ra # Retorna o controle para a função principal

Funções Recursivas e Sub-FunçõesDa mesma forma que o programa principal invocou uma função, uma função também pode invocar outras funções.Se la for recursiva, ela pode até mesmo invocar clones de si mesma para realizar uma tarefa. Como implementar issosendo que temos apenas um registrador $ra? Se chamarmos outra função, o $ra original é sobrescrito e podemosperder a capacidade de voltar ao programa principal. Além disso, valores temporários que representam variáveislocais podem ser perdidos.A única forma de evitar isso é enviar para a pilha tudo aquilo que precisa ser salvo - da mesma forma que fizemoscom alguns valores no exemplo acima. Para mostrar isso na prática vamos implementar em Assembly a seguintefunção em C:

/* Calcula Fatorial */

int fat(int n){

if(n < 1)

return 1;

else

return (n * fat(n - 1));

}

O resultado final é algo como:

fat: # Início da função

addi $sp, $sp, -8 # Aloca espaço na pilha para 2 valores

sw $ra, 4($sp) # Guarda valor de retorno na pilha

sw $a0, 0($sp) # Guarda primeiro argumento na pilha

slti $t0, $a0, 1 # $t0 = ( $a0 < 1)

beq $t0, $zero, L1 # Se $a0 >= 1, vá para L1

addi $v0, $zero, 1 # Senão, coloque 1 como valor de retorno

addi $sp, $sp, 8 # Apague as duas variáveis locais salvas na pilha

jr $ra # Encerra a função

L1: addi $a0, $a0, -1 # Se $a >= 1, decremente $a0

Page 16: Introdução à Arquitetura de Computadores

Suporte à Funções 14

jal fat # Chame um clone recursivo de fat que retorna fat($a0-1)

lw $a0, 0($sp) # Recupere os valores iniciais do parâmetro da função

lw $ra, 4($sp) # Recupere o endereço de retorno antigo

addi $sp, $sp, 8 # Apague as duas variáveis locais salvas na pilha

mul $v0, $a0, $v0 # Coloque $a0 * fat($a0 - 1) como valor de retorno

jr $ra # Encerra a função

Por fim, uma última coisa útil que é interessante comentar é que o Stack Pointer ($sp) pode ser alterado várias vezesao longo de uma função. Para manter memorizado o valor do endereço da pilha no início da função, costuma-se usaro registrador $r30 como um Frame Pointer ($fp). Nos exemplos acima, isso não foi preciso, pois só mudamos ovalor do Stack Pointer no começo e fim de cada função. Mas em alguns programas utilizar este registrador pode serútil.

Representação NuméricaNúmeros podem ser representados em qualquer tipo de base. Humanos costumam representar números na basedecimal. Esta possui este nome por possuir 10 dígitos diferentes: 0, 1, 2, 3, 4, 5, 6, 7, 8 e 9.Como computadores só são capazes de reconhecer dois tipos diferentes de estados, é natural para eles representarnúmeros na base binária. Ou seja, eles só compreendem os valores "0" e "1". Vamos ver agora coo podemosrepresentar diversos tipos de números usando apenas dois tipos diferentes de sinais.

Números NaturaisRepresentar Números Naturais é simples bastante simples - mesmo quando só podemos usar dois tipos diferentes desinais. O 0 decimal é representado como 0. O 1 decimal também é representado como 1. Já o 2 decimal érepresentado com "10". Enfim, quando contamos os números naturais na base binária, fazemos da seguinte forma:

BASE |

BINÁRIA | 0 1 10 11 100 101 110 111 1000 1001 1010

|

BASE |

DECIMAL | 0 1 2 3 4 5 6 7 8 9 10

Quando representamos números desta forma, não temos números negativos. Logo, nenhum número possui sinalalgum. Por isso, chamamos tais números de unsigned (sem sinal).Atualmente, a maioria das máquinas possui representação numérica 32 bits. Isso significa que seus números sãocompostos por 32 dígitos. Com uma representação destas, o menor número binário sem sinal que podemosrepresentar é 00000000000000000000000000000000 e o maior é 1111111111111111111111111111111.Convertemos tais valores para decimal, chegamos à conclusão que a maioria dos computadores só lida com númerossem sinal cujo valor esteja entre 0 e 4.294.967.295 - que são os equivalentes decimais destes números.Para descobrir qual é o equivalente decimal de um número binário de 32 bits, usa-se a fórmula abaixo:

onde é o dígito mais significativo e é o bit menos significativo.

Page 17: Introdução à Arquitetura de Computadores

Representação Numérica 15

Números InteirosNem sempre os números com os quais queremos lidar são naturais. Pode haver necessidade de realizarmos operaçõescom números negativos. Para tal, devemos encontrar uma forma de representarmos números negativos. Uma dasprimeiras formas tentadas de fazer isso foi reservando o primeiro bit de um número para representar o sinal. Assim,um 0000000 (...) 001 representa +1 e um 10000 (...) 001 representaria um -1. Entretanto, tal forma de representaçãojá foi abandonada há muito tempo. Um dos principai motivos para o abandono desta representação está no fato desempre termos que verificar o primeiro bit para descobrir como efetuar uma soma ou subtração entre dois números.Além disso, tal representação tornaria possível um "+0" e um "-0".A forma de se representar números inteiros em computadores modernos é chamada de Representação emComplemento de 2. Ela é feita da seguinte forma:Os 31 bits mais à esquerda representam sempre números positivos. Calculamos o que eles representam com a mesmafórmula vista acima. Entretanto, o primeiro bit representa sempre "0" se o seu valor for "0" ou " se o seu valorfor "1". Assim, a fórmula para se calcular o que representa um número binário inteiro em decimal é:

onde é o dígito mais significativo e é o bit menos significativo.A representação em complemento de 2 é boa pelos seguintes motivos:•• Para somar dois números inteiros, usa-se o mesmo método. Não precisamos verificar o sinal deles. Com isso,

podemos criar um circuito mais rápido.•• Descobrir o inverso de um número também é simples. Basta invertermos todos os bits e, em seguida, somarmos 1.

Sempre funciona.•• O "0" é sempre "0". Não existe uma versão negativa ou positiva deste número.Por meio desta notação, números tão pequenos como -4.294.967.296 e tão grandes como 4.294.967.295 podem serrepresentados.

Números ReaisPor fim, também é muito útil que computadores possam representar números reais. Vamos estudar agora como elescostumam ser representados em computadores.A forma mais lógica e versátil de representarmos um número real é por meio da notação científica. Um númerodecimal em notação científica sempre toma a seguinte forma:

onde N é sempre um número real maior ou igual à 1 e menor que 10 que pode ser positivo ou negativo enquanto M éum número inteiro qualquer (que pode ser positivo ou negativo). Por exemplo:

representa aproximadamente quantos segundos existe em um século enquantorepresenta quantos segundos existem em um nanossegundo.

Alternativamente, podemos representar também números em notação científica utilizando uma base binária:

. Lembre-se que aquele "10" significa na verdadee "2" em representação decimal. Ou seja, onúmero representado é na verdade 0,5 e não 0,1.Enfim, quando temos um número em notação científica binária, ele sempre tem a seguinte forma:

, onde "S" representa o sinal (que pode ser positivo ou negativo), "XXXXX" sãoa parte fracionária do número e "YYYYYY" representa o expoente.Então, para representarmos números reais, precisamos representar no espaço de 32 bits os valores do sinal, fração eexpoente. No MIPS, isso é feito da seguinte forma:

Page 18: Introdução à Arquitetura de Computadores

Representação Numérica 16

S -- E E E E E E E E -- F F F F F F F F F F F F F F F F F F F F F F F

O Sinal é representado em um único bit (1 para negativo e 0 para positivo), o expoente é representado por 8 bits e afração é representada por 23 bits. Assim, o exemplo (ou 1/2 em decimal) é representado:

0 -- 1 1 1 1 1 1 1 1 -- 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

Com esta representação, números quase tão pequenos como ou quase tão grandes como podem serarmazenados.Entretanto, note que a nossa precisão não é perfeita. Existem muitos números que não podem ser representados destaforma. Por isso, sempre é preciso realizar arredondamentos. E quanto mais operações realizamos, mais erros acabamsendo acumulados. Por essa razão, a maioria dos computadores possui também uma forma mais precisa derepresentação de números reais. Essa forma normalmente ocupa o dobro do espaço do que costuma ocupar. NoMIPS uma representação desti tipo ocupa 64 bits. Ela funciona da seguinte forma:S -- E E E E E E E E E E E -- F F F F F F F F F F F F F F F F F F F F F F F F F F F F F F F F F F F F F F F F F F F F F F F F F F F F

Apesar de gastarmos o dobro do espaço de armazenamento desta forma, aumentamos consideravelmente a faixa devalores representados e a precisão com a qual podemos representá-los.

Overflow e UnderflowOverflow é o nome do resultado incorreto ao qual chegamos quando tentamos somar números grandes demais e oresultado não pode ser armazenado e nem representado em 32 bits. Ele pode ocorrer quando efetuamos as seguintesoperações:•• Somamos dois números positivos•• Somamos dois números negativos•• Subtraímos um número negativo de um positivo.É de responsabilidade do hardware sempre verificar se ocorreu um overflow nestes casos. Normalmente, para fazer averificação basta conferir os sinais dos números. Em caso afirmativo, ele deve ativar um sinal de exceção. O que éfeito caso uma excessão seja detectada depende de cada programa.O MIPS, por exemplo, sempre armazena o endeereço da última instrução que gerou excessão em um registradorespecial chamado EPC. A instrução mfc0 (Move from System Control) é usada para copiar este endereço para umregistrador de propósito geral que pode ser conferido por algum programa. Cabe ao programador deste programadecidir o que fazer.Em lingüagens de alto nível, o comportamento depende da lingüagem usada. C, por exemplo, ignora todos osOverflows. Ao contrário de Fortran ou Ada que requerem que o programa sempre seja avisado.Underflow é o que acontecee quando lidamos com um número real tão próximo de 0 que o valor do expoente nãopode ser corretamente representado. Quando um programa requer uma precisão tão grande assim, recomenda-se ouso de pontos flutuantes de dupla precisão (double) para evitar este inconveniente.

Page 19: Introdução à Arquitetura de Computadores

As Operações da Unidade Lógica Aritmética 17

As Operações da Unidade Lógica AritméticaComputadores são máquinas de calcular. Logo, é fundamental que eles sejam capazes de realizar operações básicascomo adição, subtração, divisão e multiplicação. Vamos ver agora como tais operações são implementadas dentro daUnidade Lógica Aritmética de um Processador (ULA).

A Adição e SubtraçãoA Adição é uma das operações mais simples. Um computador realiza ela de uma maneira semelhante à usada pornós humanos. Ele começa somando os bits menos significativos. Caso tenha que somar "1" e "1", o resultado ficasendo "0" e passamos um "vai-um" para o bit à esquerda. Veja abaixo um desenho de um circuito capaz de somardois números de 8 bits:

Embora a imagem mostre apenas um circuito que soma dois números de 8 bits, não é muito difícil perceber que alógica para fazer um somador é a mesma, tanto para números com 8 como para 32 bits. Basta adicionar maissomadores. Perceba que o circuito também é capaz de detectar a presença de Overflow no caso de operações comnúmeros sem sinal.Ele pode ser usado tanto para somar números com ou sem sinal. Graças à representação de números inteiros porcomplemento de dois, não é difícil conseguir isso.O mesmo circuito acima também pode ser reaproveitado para realizar subtrações. Basta inverter antes todos os bitsdo segundo operando e somar 1 à ele. Com isso, estamos na verdade somando o primeiro operando com o negativodo segundo operando.

Page 20: Introdução à Arquitetura de Computadores

As Operações da Unidade Lógica Aritmética 18

A MultiplicaçãoVamos agora estudar como construir um circuito capaz de realizar multiplicações. Primeiro vamos tentar realizaruma multiplicação pelo método convencional que usamos através de papel e caneta. Isso pode nos dar uma pista decomo fazer uma operação de multiplicação. Vamos usar a base binária:

1000

x1001

----

1000

00000

000000

1000000

-------

1001000

Perceba que a multiplicação é um conjunto de somas. Sempre estamos somando o valor 0 ou o valor domultiplicador após este passar por uma operação de SHIFT para a esquerda. O que determina se o que iremos somaré um 0 ou o valor do multiplicador após um SHIFT são os bits do multiplicando. Se o bit da posição n for um 0,somamos 0 e se for 1, somamos com o valor do multiplicando "shiftado" n vezes.Um exemplo de um circuito seqüencial capaz de somar dois números de 4 bits é mostrado na imagem abaixo:

Note que multiplicando números de 4 bits, nós precisamos armazenar os 8 bits possíveis para o resultado. Afinal, oresultado de uma multiplicação é capaz de ter o número de bits igual à soma dos multiplicandos. Máquinas MIPSlidam com o problema dividindo o resultado pela metade. os bits mais significativos terminam em um registradorchamado Hi e os menos significativos acabam em um registrador chamado Lo.Entretanto, o circuito conforme mostrado acima tem muitos problemas. O principal é que ele é muito complexo.Perceba que o circuito para multiplicar número de 4 bits é pelo menos 3 vezes mais complexo e lento que o usadopara realizar a adição de 8 bits (afinal, o circuito de multiplicação precisa realizar a adição de 3 pares de valores de 8bits). Um circuito destes para calcular a multiplicação de números com um valor ainda maior de bits seria ainda maiscomplexo que isso.Com isso, chegamos à conclusão que a multiplicação é uma operação complexa demais para ser efetuada por meiode uma simples lógica combinacional de portas lógicas. Um circuito de multiplicação combinacional seria muitocaro e esquentaria demais. Por isso, sua velocidade teria que ser sacrificada aumentando a distância dos transistores.Por isso, utilizam-se circuitos seqüenciais para realizar esta operação.

Page 21: Introdução à Arquitetura de Computadores

As Operações da Unidade Lógica Aritmética 19

O diagrama abaixo mostra descreve o funcionamento de um circuito seqüencial capaz de realizar a multiplicação:

O que acontece no diagrama acima no caso de multiplicarmos dois números de 32 bits é o seguinte:• 1- O "Produto" é um registrador inicializado em 0.• 2- O produto e o multiplicando são passados para a ULA que soma os dois.• 3- O resultado (o valor do próprio multiplicando) pode ir para o registrador "Produto" ou não. Quem toma a

decisão é o "Controle".• 4- Se o último bit do Multiplicador for "1", o Controle permite a escrita do registrador "Produto". Se for "0", ele

impede.• 5- O Multiplicando sofre um shift para a esquerda e o Multiplicador um Shift para a direita.• 6- Se é a 32a vez que você chega à esta instrução, encerre. A multiplicação terminou. Caso contrário, volte ao

passo 2.O diagrama acima já é uma forma bem melhor de multiplicarmos. Mas ainda existem otimizaçãoes que podem serfeitas para acelerarmos ainda mias a multiplicação:•• Podemos utilizar um único registrador de 64 bits para armazenar tanto o multiplicador como o produto. O

multiplicador vem antes e o produto logo depois. Assim, cada vez que fazemos um shift para a direita nomultiplicador, aproveitamos o espaço liberado mais à esquerda para colocar um novo bit do produto. Desta forma,não é necessário somar números de 32 bits, apenas somar o suficiente para descobrir qual o próximo bit doproduto a ser colocado.

•• Compiladores substituem multiplicação por potências de 2 por operações de shift que são mais rápidas.Para multiplicações envolvendo números negativos, basta invetermos os números transformando-os em positivos.Em seeguida, fazemos a multiplicação normalmente e observamos os sinais do multiplicador e multiplicando. Seforem iguais, masntemos os números como positivos. Caso contrário, convertemos o produto para negativo.

Page 22: Introdução à Arquitetura de Computadores

Fontes e Editores da Página 20

Fontes e Editores da PáginaCapa  Fonte: http://pt.wikibooks.org/w/index.php?oldid=204857  Contribuidores: Master, Raylton P. Sousa, 1 edições anónimas

Introdução  Fonte: http://pt.wikibooks.org/w/index.php?oldid=239231  Contribuidores: Master, Raylton P. Sousa, Thiagoharry, 5 edições anónimas

O que é o MIPS?  Fonte: http://pt.wikibooks.org/w/index.php?oldid=199789  Contribuidores: GrooveDog, Master, Raylton P. Sousa, Thiagoharry, 9 edições anónimas

Instruções do MIPS  Fonte: http://pt.wikibooks.org/w/index.php?oldid=250818  Contribuidores: Kernelsafe, Master, Raylton P. Sousa, Thiagoharry, 5 edições anónimas

Representação das Instruções  Fonte: http://pt.wikibooks.org/w/index.php?oldid=199791  Contribuidores: Master, Raylton P. Sousa, Thiagoharry

As Pseudo-Instruções  Fonte: http://pt.wikibooks.org/w/index.php?oldid=199786  Contribuidores: Master, Mike.lifeguard, Raylton P. Sousa, Thiagoharry, 1 edições anónimas

Suporte à Funções  Fonte: http://pt.wikibooks.org/w/index.php?oldid=239421  Contribuidores: Master, Raylton P. Sousa, Thiagoharry, 3 edições anónimas

Representação Numérica  Fonte: http://pt.wikibooks.org/w/index.php?oldid=199790  Contribuidores: Master, Raylton P. Sousa, Thiagoharry, 3 edições anónimas

As Operações da Unidade Lógica Aritmética  Fonte: http://pt.wikibooks.org/w/index.php?oldid=199785  Contribuidores: Master, Raylton P. Sousa, Thiagoharry

Page 23: Introdução à Arquitetura de Computadores

Fontes, Licenças e Editores da Imagem 21

Fontes, Licenças e Editores da ImagemImagem:Cray-1-deutsches-museum.jpg  Fonte: http://pt.wikibooks.org/w/index.php?title=Ficheiro:Cray-1-deutsches-museum.jpg  Licença: Creative Commons Attribution 2.5  Contribuidores:Clemens PFEIFFERImagem:Transistor_npn.png  Fonte: http://pt.wikibooks.org/w/index.php?title=Ficheiro:Transistor_npn.png  Licença: GNU Free Documentation License  Contribuidores: Original uploader wasPulsar at fr.wikipediaImagem:Logic-gate-index.png  Fonte: http://pt.wikibooks.org/w/index.php?title=Ficheiro:Logic-gate-index.png  Licença: GNU Free Documentation License  Contribuidores: EugeneZelenko,Ilmari Karonen, Jjbeard, Qvarie, Stefan506, The wub, Tothwolf, WikipediaMaster, 2 edições anónimasImagem:ALU_Block_Diagram.png  Fonte: http://pt.wikibooks.org/w/index.php?title=Ficheiro:ALU_Block_Diagram.png  Licença: Creative Commons Attribution-Share Alike  Contribuidores:Tapani TaivalanttiImagem: Kernel_Layout.svg  Fonte: http://pt.wikibooks.org/w/index.php?title=Ficheiro:Kernel_Layout.svg  Licença: Creative Commons Attribution-Sharealike 3.0  Contribuidores: BobboImagem:Toshiba_TC86R4400MC-200_9636YJA_top.jpg  Fonte: http://pt.wikibooks.org/w/index.php?title=Ficheiro:Toshiba_TC86R4400MC-200_9636YJA_top.jpg  Licença: GNU FreeDocumentation License  Contribuidores: EugeneZelenko, Morkork, Qurren, Sdrtirs, 3 edições anónimasImagem:Circuito_soma.png  Fonte: http://pt.wikibooks.org/w/index.php?title=Ficheiro:Circuito_soma.png  Licença: GNU Free Documentation License  Contribuidores: ThiagoharryImagem:Circuito_multiplicacao.png  Fonte: http://pt.wikibooks.org/w/index.php?title=Ficheiro:Circuito_multiplicacao.png  Licença: GNU Free Documentation License  Contribuidores:ThiagoharryImagem:Diagrama_circuito_multiplicacao.png  Fonte: http://pt.wikibooks.org/w/index.php?title=Ficheiro:Diagrama_circuito_multiplicacao.png  Licença: Creative CommonsAttribution-Sharealike 3.0  Contribuidores: Thiagoharry

Page 24: Introdução à Arquitetura de Computadores

Licença 22

LicençaCreative Commons Attribution-Share Alike 3.0//creativecommons.org/licenses/by-sa/3.0/