Sunteți pe pagina 1din 50

CIC, DM, KP

Diseno de sistemas digitales


20 de enero de 2015

Springer
Capı́tulo 1
Implementación de tareas Software utilizando
procesadores Soft Core

1.1. Introducción

En el capı́tulo anterior se estudió la forma de implementar tareas hardware utili-


zando máquinas de estado algorı́tmicas. La implementación de tareas hardware es
un proceso un poco tedioso ya que involucra la realización de una máquina de es-
tados por cada tarea; la implementación del camino de datos se simplifica de forma
considerable ya que existe un conjunto de bloques constructores que pueden ser to-
mados de una librerı́a creada por el diseñador. El uso de tareas hardware se debe
realizar únicamente cuando las restricciones temporales del diseño lo requieran, ya
que como veremos en este capı́tulo, la implementación de tareas software es más
sencilla y rápida.
La estructura de una máquina de estados algorı́tmica permite entender de forma
fácil la estructura de un procesador ya que tienen los mismos componentes principa-
les (unidad de control y camino de datos), la diferencia entre ellos es la posibilidad
de programación y la configuración fija del camino de datos del procesador.
En este capı́tulo se estudiará la arquitectura del procesador MICO32 creado por
la empresa Lattice semiconductor y gracias a que fué publicado bajo la licencia
GNU, es posible su estudio, uso y modificación. En la primera sección se hace la
presentación de la arquitectura; a continuación se realiza el análisis de la forma en
que el procesador implementa las diferentes instrucciones, iniciando con las ope-
raciones aritméticas y lógicas siguiendo con las de control de flujo de programa
(saltos, llamado a función); después se analizarán la comunicación con la memoria
de datos; y finalmente el manejo de interrupciones.
En la segunda sección se abordará la arquitectura de un SoC (System on a Chip)
basado en el procesador LM32, se analizará la forma de conexión entre los periféri-
cos y la CPU utilizando el bus wishbone; se realizará una descripción detallada de
la programación de esta arquitectura utilizando herramientas GNU.

1
2 1 Implementación de tareas Software utilizando procesadores Soft Core

1.2. Arquitectura del procesador LM32

La figura ?? muestra el diagrama de bloques del soft-core LM32, este procesador


utiliza 32 bits y una arquitectura de 6 etapas del pipeline; tambien cuenta con una
logica de bypass que se encarga de hacer que el caminos de datos entre operacio-
nes sea mas corto y se puedan ejecutar en un ciclo sencillo, para que los datos no
recorran todo el pipeline para completar instrucciones.
Las 6 etapas del pipeline son:

A Address: Se calcula la dirección de la instrucción a ser ejecutada y es enviada al


registro de instrucciones.
F Fetch: La instrucción se lee de la memoria.
D Decode: Se decodifica la instrucción y se toman los operandos del banco de re-
gistros o tomados del bypass.
X Execute: Se realiza la operacion especificada por la instrucción. Para instruccio-
nes simples (sumas y operaciones logicas), la ejecución finaliza en esta etapa, y
el resultado se hace disponible para el bypass.
M Memory: Para instrucciones más complejas como acceso a memoria externa,
multiplicación, corrimiento, división, es necesaria otra etapa.
D Write back: Los resultados producidos por la instrucción son escritas al banco de
registros.

1.2.1. Banco de Registros

El LM32 posee 32 registros de 32 bits; el registro r0 siempre contiene el valor


0, esto es necesario para el correcto funcionamiento de los compiladores de C y
ensamblador; los siguientes 8 registros (r1 a r7) son utilizados para paso de argu-
mentos y retorno de resultados en llamados a funciones; si una función requiere más
de 8 argumentos, se utiliza la pila (stack). Los registros r1 - r28 pueden ser utiliza-
dos como fuente o destino de cualquier instrucción. El registro r29 (ra) es utilizado
por la instrucción call para almacenar la dirección de retorno. El registro r30 (ea) es
utilizado para almacenar el valor del contador de programa cuando se presenta una
excepción. El registro r31 (ba) almacena el valor del contador de programa cuando
se presenta una excepción tipo breakpoint o watchpoint. Los registros r26 (gp) r27
(fp) y r28 (sp) son el puntero global, de frame y de pila respectivamente.
Después del reset los 32 bits de los registros quedan indefinidos, por lo que la
primera acción que debe ejecutar el programa de inicialización es asegurar un cero
en el registro r0, esto lo hace con la siguiente instrucción r0 (xor r0, r0, r0)
1.2 Arquitectura del procesador LM32 3

Contador de
programa Controlador de
interrupciones A
Memoria de programa

F
Registro de Instrucciones

Decodificador
de
Instrucciones Banco de Registros

Registro de
Multiplicac.
Status y Operaciones
Sumador Corrimiento Y
Control lógicas
división
(CSR)

Memoria de
datos Bypass

M
Align

Figura 1.1 Diagrama de bloques del LM32

1.2.2. Registro de estado y control

La tabla ?? muestra los registros de estado y control (CSR), indicando si son de


lectura o escritura y el ı́ndice que se utiliza para acceder al registro.
4 1 Implementación de tareas Software utilizando procesadores Soft Core

Cuadro 1.1 Registro de Estado y Control


Nombre Index Descripción
PC Contador de Programa
IE 0x00 (R/W)Interrupt enable
EID —- (R) Exception ID
IM 0x01 (R/W)Interrupt mask
IP 0x02 (R) Interrupt pending
ICC 0x03 (W) Instruction cache control
DCC 0x04 (W) Data cache control
CC 0x05 (R) Cycle counter
CFG 0x06 (R) Configuration
EBA 0x07 (R/W)Exception base address

1.2.2.1. Contador de Programa (PC)

El contador de programa es un registro de 32 bits que contiene la dirección de la


instrucción que se ejecuta actualmente. Debido a que todas las instrucciones son de
32 bits, los dos bits menos significativos del PC siempre son zero. El valor de este
registro después del reset es h00000000

1.2.2.2. IE Habilitación de interrupción

l registro IE contiene la bandera IE, que determina si se habilitan o no las in-


terrupciones. Si este flag se desactiva, no se presentan interrupciones a pesar de la
activación individual realizada con IM. Existen dos bits BIE y EIE que se utilizan
para almacenar el estado de IE cuando se presenta una excepción tipo breakpoint u
otro tipo de excepción; esto se explicará más adelante cuando se estudien las ins-
trucciones relacionadas con las excepciones.

1.2.2.3. EID Exception ID

El ı́ndice de la excepción es un número de 3 bits que indica la causa de la de-


tención de la ejecución del programa. Las excepciones son eventos que ocurren al
interior o al exterior del procesador y cambian el flujo normal de ejecución del pro-
grama. Los valores y eventos correspondientes son:
0: Reset; se presenta cuando se activa la señal de reset del procesador.
1: Breakpoint; se presenta cuando se ejecuta la instrucción break o cuando se
alcanza un punto de break hardware.
2: Instruction Bus Error; se presenta cuando falla la captura en una instrucción,
regularmente cuando la dirección no es válida.
3: Watchpoint; se presenta cuando se activa un watchpoint.
4: Data Bus Error; se presenta cuando falla el acceso a datos, tı́pica mente porque
la dirección solicitada es inválida o porque el tipo de acceso no es permitido.
1.3 Set de Instrucciones del procesador Mico32 5

5: División por cero; Se presenta cuando se hace una división por cero.
6: Interrupción; se presenta cuando un periférico solicita atención por parte del
procesador. Para que esta excepción se presente se deben habilitar las interrup-
ciones globales (IE) y la interrupción del periférico (IM).
7: System Call; se presenta cuando se ejecuta la instrucción scall.

1.2.2.4. IM Máscara de interrupción

La máscara de interrupción contiene un bit de habilitación para cada una de las


32 interrupciones, el bit 0 corresponde a la interrupción 0. Para que la interrupción
se presente es necesario que el bit correspondiente a la interrupción y el flag IE sean
igual a 1. Después del reset el valor de IM es h00000000

1.2.2.5. IP Interrupción pendiente

El registro IP contine un bit para cada una de las 32 interrupciones, este bit se
activa cuando se presenta la interrupción asociada. Los bits del registro IP deben ser
borrados escribiendo un 1 lógico.

1.3. Set de Instrucciones del procesador Mico32

En esta sección se realizará un análisis del conjunto de instrucciones del pro-


cesador Mico32. Para facilitar el estudio se realizó una división en cuatro grupos
comenzando con las instrucciones aritméticas y lógicas, siguiendo con las relacio-
nadas con saltos, después se analizará la comunicación con la memoria de datos
y finalmente las relacionadas con interrupciones y excepciones. Para cada uno de
estos grupos se mostrará el camino de datos (simplificado) asociado al conjunto de
instrucciones.

1.3.1. Instrucciones aritméticas

1.3.1.1. Entre registros

En la figura ?? se muestra el camino de datos simplificado de las operaciones


aritméticas y lógicas cuyos operandos son registros, y cuyo resultado se almacena
en un registro. En otras palabras son de la forma: gpr[RX] = gpr[RY] OP gpr[RZ],
donde: OP puede ser nor, xor, and, xnor, add, divu, modu, mul, or, sl, sr, sru, sub.
Como puede verse en esta figura la instrucción contiene la información necesaria
para direccionar los registros que almacenan los operandos RY (instruction d 25:21)
6 1 Implementación de tareas Software utilizando procesadores Soft Core

y RZ (instruction d 20:16), estas señales de 5 bits direccionan el banco de registros


y el valor almacenado en ellos puede obtenerse en dos salidas diferentes ( gpr[rz] y
gpr[ry]). En el archivo rtl/lm32/lm32 cpu.v se implementa el banco de registros de
la siguiente forma:
assign reg data 0 = r e g i s t e r s [ read idx 0 d ] ;
assign reg data 1 = r e g i s t e r s [ read idx 1 d ] ;
En este código reg data 0 y reg data 1 son las dos salidas gpr[rz] y gpr[ry];
las señales read idx 0 d y read idx 1 d corresponden a instruction d 25:21 y ins-
truction d 20:16 respectivamente. El contenido de los registros direccionados de
esta forma son llevados al modulo logic op donde se realiza la operacion correspon-
diente a la instrucción y el resultado pasa a través de los estados del pipeline hasta
llegar a la señal w result (parte inferior de la figura). Esta señal entra al banco de
registros para ser almacenada en la dirección dada por la señal write idx w la cual
es fijada por la instrucción, más especı́ficamente por (instruction d 15:11). En el
archivo rtl/lm32/lm32 cpu.v se implementa esta escritura al banco de registros de la
siguiente forma:
a l w a y s @( p o s e d g e c l k i )

begin

i f ( r e g w r i t e e n a b l e q w == ‘TRUE )

r e g i s t e r s [ w r i t e i d x w ] <= w r e s u l t ;

end

1.3.1.2. Inmediatas

Existe otro grupo de operaciones lógicas y aritméticas en las que uno de los ope-
randos es un registro y el otro es un número fijo, esto permite realizar operaciones
con constantes que nos son almacenadas previamente en registros, sino que son al-
macenadas en la memoria de programa. En la figura ?? se muestra como se modifica
el camino de datos para este tipo de instrucciones; en ella, podemos observar que
instruction d 25:21 direcciona uno de los operandos que está almacenado en el ban-
co de registros y de forma similar al caso anterior el dato almacenado es llevado al
bloque logic op. El segundo operando es llevado a este bloque desde un multiplexor
donde se hace una extensión de signo de instruction d 15:0 o se hace un corrimiento
a la derecha de 16 posiciones; esto, para convertir el número de 16 bits a uno de 32
bits, lo que da como resultado 16instruction d[15], instruction d[15:0] y instruc-
tion d[15:0], 16’h0000 respectivamente; el corrimiento de 16 bits a la derecha se
hace para poder realizar las operaciones andhi y orhi, las cuales solo operan sobre
la parte alta de los operandos.
1.3 Set de Instrucciones del procesador Mico32 7
31 26 16 0
Instruction Decoder
IM CODE RY RZ RX 0000000000

instruction_d 25 21 15 11
w_result
RZ RY
gpr[RX] = gpr[RY] {OP} gpr[RZ]
OP = nor, xor, and, xnor, add, divu, modu, mul, or, sl, sr, sru, sub,

read_idx_0_d read_idx_1_d

OP = andhi, orhi
registers write_idx_w RX

x_result x_result
reg_data_0 reg_data_1
m_result m_result
w_result w_result
gpr[rz] gpr[ry]

Bypass for reg port 1 Bypass for reg port 0


001 01x 1xx 000 1'b0 1'b0 000 1xx 01x 001

1'b0 1'b0
1'b0 1'b0
{32{1'bx}}
sign/zero_extend(imm16) {pc_f,2'b00}
imm16<<16{32{1'b0}} bypass_data_1 bypass_data_0

00 10 11 01 0 1
2'b01 1'b0

d_result_1
d_result_0

~stall_x
D/X stage registers

operand_1_x operand_0_x

divide_q_d
modulus_q_d

stall_m stall_d
direction_x kill_x
4'b1000 sign_extend_x

fixed-point
adder logic_op shifter mc_arithmetic
multiplier

adder_carry_n_x logic_result_x
adder_result_x multiplier_result_w
adder_overflow_x shifter_result_m

1xxx 0000 0001 001x 01xx


1'b0
1'b0
1'b0
1'b0

x_result

X/M stage registers ~stall_m

operand_m
{31{1'b0},condition_met_m}

1x 00 01
1'b0

1'b0

m_result

1'b0
M/W stage registers

load_data_w operand_w

1x 00 01 1'b0

1'b0
w_result

Figura 1.2 Camino de datos de las operaciones aritméticas y lógicas entre registros
8 1 Implementación de tareas Software utilizando procesadores Soft Core
31 26 20 16
Instruction Decoder IM CODE RY RX imm16

instruction_d 25 21 15 0
w_result
RY
gpr[RX] = gpr[RY] {OP} sign/zero_extend(imm16)
OP = nori, xori, andi, xnori, addi, muli, ori, sli, sri, srui

read_idx_0_d read_idx_1_d
gpr[RX] = gpr[RY] {OP} (imm16 << 16)
OP = andhi, orhi
registers write_idx_w RX

x_result x_result
reg_data_0 reg_data_1
m_result m_result
w_result w_result

gpr[ry]

Bypass for reg port 1 Bypass for reg port 0


001 01x 1xx 000 1'b0 1'b0 000 1xx 01x 001

1'b0 1'b0
1'b0 1'b0
{32{1'bx}}
sign/zero_extend(imm16) {pc_f,2'b00}
imm16<<16{32{1'b0}} bypass_data_1 bypass_data_0

00 10 11 01 0 1
2'b01 1'b0

d_result_1
d_result_0

~stall_x
D/X stage registers

operand_1_x operand_0_x

divide_q_d
modulus_q_d

stall_m stall_d
direction_x kill_x
4'b1000 sign_extend_x

fixed-point
adder logic_op shifter mc_arithmetic
multiplier

adder_carry_n_x logic_result_x
adder_result_x multiplier_result_w
adder_overflow_x shifter_result_m

1xxx 0000 0001 001x 01xx


1'b0
1'b0
1'b0
1'b0

x_result

X/M stage registers ~stall_m

operand_m
{31{1'b0},condition_met_m}

1x 00 01
1'b0

1'b0

m_result

1'b0
M/W stage registers

load_data_w operand_w

1x 00 01 1'b0

1'b0
w_result

Figura 1.3 Camino de datos de las operaciones aritméticas y lógicas inmediatas


1.3 Set de Instrucciones del procesador Mico32 9

1.3.2. Saltos

Los saltos permiten controlar el flujo de ejecución del programa posibilitando la


implementación de ciclos, llamado a funciones, y toma de decisiones. En esta sub-
sección estudiaremos el camino de datos resultante para este tipo de instrucciones.
A diferencia de las instrucciones aritméticas y lógicas, en este tipo de instrucciones
se modifica el valor del contador de programa.

1.3.2.1. Condicionales

En los saltos condicionales la instrucción se almacena la dirección de los regis-


tros que deben ser comparados, especı́ficamente en instruction d 25:21 y instruc-
tion d 20:16; los valores almacenados en estos registros son llevados al sumador y
a un bloque especial que determina si se cumple o nó la condición (señales rojas en
la gráfica); la señal condition met x se activa si la condición se cumple. En la figura
?? se muestra el camino de datos para las instrucciones condicionales.
Para que el valor del contador de programa se modifique, es necesario que las
señales condition met x, branch m y valid m se encuentren activas (señales anaran-
jadas en la gráfica); la señal branch m se activa cuando la instrucción es de tipo
branch o call; la señal valid m se activa cuando se presenta una instrucción válida.
Adicionalmente, es necesario que el procesador no se encuentre en un estado de
stall. Si se cumplen las condiciones anteriores, se activará la señal branch taken m,
la que le indicará a la unidad de instrucciones que cargue el valor de la señal
branch target m en el contador de programa.
El valor de branch target m (señal azul en la gráfica) es fijado por dos diferentes
métodos: cuando se produce una excepción o cuando se produce un salto, la señal
exception x selecciona el valor adecuado para cada caso. La señal branch target x
es el resultado de la suma de pc d y de branch offset d (para esta suma no se uti-
liza el bloque sumador). El valor de branch offset es seleccionado por la señal se-
lect call immediate entre las señales call immediate (para instrucciones de llamado
a función) y branch immediate; está última tiene como valor 16inst[15], inst[15:0],
lo que es una extensión de signo de la constante de 16 bits almacenado en la memo-
ria de programa.
En la figura ?? se ilustran 3 ciclos que utilizan condicionales; en color azul se
muestra el código en C y en negro se muestra el código implementado por el com-
pilador.

1.3.2.2. Llamado a función y salto incondicional

Existen dos tipos de llamado a función y de salto incondicional; su diferencia


radica en la forma de almacenar la dirección a la que deben saltar. En la figura ??
se muestra el camino de datos correspondiente a las instrucciones calli y bi, estas
almacenan en la instrucción la dirección y en la figura ?? se muestra el camino de
10 1 Implementación de tareas Software utilizando procesadores Soft Core

Instruction Decoder call_immediate= {{6{inst[25]}}, inst[25:0]} bra call


branch_immediate={{16{inst[15]}}, inst[15:0]}
31 26 20 16
select_call_immediate
IM CODE RY RX imm16

instruction_d 25 21 15 0
w_result branch
RX RY
condition_d
if (gpr[RX] condition gpr[RY] branch_offset

PC = PC + sign_extended(imm16 << 2)
condition: equal (be), not equal (bne), greater(bg),
greater or equal(bge), greater or equel unsigned(bgeu),
greater unsigned(bgu)
registers

x_result x_result
gpr[rx] gpr[ry]
m_result m_result
w_result w_result

Bypass for reg port 1


001 01x 1xx 000 1'b0 Bypass for reg port 0
1'b0 000 1xx 01x 001
1'b0
1'b0
1'b0
1'b0
{32{1'bx}}
bypass_data_0
immediate_d {pc_f,2'b00}
{32{1'b0}} bypass_data_1

1 0
00 10 11 01 d_result_sel_0_d
2'b10
pc_d + branch_offset_d

d_result_1 d_result_0

~stall_x branch_reg_d
D/X stage registers

condition_x
branch_target_x
operand_1_x
operand_0_x

Condition Evaluation
1'b0
1'b1

condition_met_x
adder

adder_carry_n_x logic_result_x
valid_m
branch_m adder_overflow_x

condition_met_m adder_result_x

1xxx 0000 0001 001x 01xx


1'b1
1'bx
1'bx
1'bx {eba, eid_x, {3{1'b0}}}

x_result

stall_m
~stall_m
X/M stage registers exception_x

branch_target_m

operand_m
{31{1'b0},condition_met_m}
Instruction Unit

pc_a=pc_d+sign_extend(imm16[15:2]) 1x 00 01
1'b0
exception_m
1'b0

m_result

1'b0
M/W stage registers

branch_taken_m
operand_w
load_data_w

1x 00 01 1'b0

1'b0

w_result

Figura 1.4 Camino de datos de los saltos condicionales


1.3 Set de Instrucciones del procesador Mico32 11

for(loop = 0; loop == 10; loop++){ for(loop = 0; loop < 10; loop++){ for(loop = 0; loop > 10; loop++){
278: mvi r1,0 29c: mvi r1,0 2c0: mvi r1,0
27c: sb (sp+43),r1 2a0: sb (sp+43),r1 2c4: sb (sp+43),r1
280: bi 290 2a4: bi 2b4 2c8: bi 2d8
284: lbu r1,(sp+43) 2a8: lbu r1,(sp+43) 2cc: lbu r1,(sp+43) Salta a evaluar
Salta si la condi- 288: addi r1,r1,1 2ac: addi r1,r1,1 2d0: addi r1,r1,1 La condición
ción se cumple 28c: sb (sp+43),r1 2b0: sb (sp+43),r1 2d4: sb (sp+43),r1
290: lbu r1,(sp+43) 2b4: lbu r2,(sp+43) 2d8: lbu r1,(sp+43)
294: cmpei r1,r1,10 2b8: mvi r1,9 2dc: cmpgui r1,r1,0xa
298: bne r1,r0,284 2bc: bgeu r1,r2,2a8 2e0: bne r1,r0,2cc
} } }
Salta si no es igual a 10 Salta si es mayor o igual a 9 Salta si no es igual a 10

Figura 1.5 Ejemplo de código: saltos condicionales

dato correspondiente a las instrucciones call y b las que almacenan la dirección en


un registro.
Para ambos casos el contador de programa es modificado si se activan las señales
condition met x, branch m y valid m; la señal valid m se activa cuando se presenta
una instrucción válida; branch m (color amarillo en los graficos) se activa cuan-
do la instrucción que se está ejecutando es un salto o un llamado a función; y
condition met x se activa cuando se cumple con la condición para el salto, debi-
do a que estos saltos y llamados son incondiconales, el MICO32 contempla dos
casos en los que activa esta señal, tal como se muestra a continuación (tomado de
rtl/lm32/lm32 cpu.v):
a l w a y s @∗

begin

case ( i n s t r u c t i o n [ 2 8 : 2 6 ] )

3 ’ b000 : c o n d i t i o n m e t x = ‘TRUE ;

3 ’ b110 c o n d i t i o n m e t x = ‘TRUE ;

....
....
....
default : c o n d i t i o n m e t x = 1 ’ bx ;

endcase

end
Los bits instruction[28:26] hacen parte del código de la instrucción; el valor para
las instrucciones bi y b es 000 y para call y calli es 110, lo que activa condition met x
cada vez que se presentan estas instrucciones.
De forma similar a las instrucciones relacionadas con saltos condicionales el
valor del contador de programa es igual al valor de las señal branch target x (señal
12 1 Implementación de tareas Software utilizando procesadores Soft Core

de color verde en las figuras); el valor de esta señal para las instrucciones call y b
proviene del valor almacenado en el registro seleccionado por instruction d [25:21].
Para las instrucciones calli y bi el valor está dado por la señal branch offset la que
toma como valor 6ins[25],ins[25:0] o 16ins[15],ins[15:0] para una instrucción call
o b respectivamente.
Adicionalmente, para las instrucciones de llamado a función call y calli se debe
almacenar en el registro R29 la dirección de memoria siguiente a la que se realizó el
llamado a la función, esto con el fı́n de retornar al flujo de programa principal, esto
se logra haciendo uso del pipeline y se utiliza el valor del contador de programa
pc m cuyo valor contiene el valor adecuado para el retorno del llamado a función;
el valor de pc m (señal color morado en las figuras) es asignado a la señal w result
del banco de registros para ser almacenado en el registro indicado por write idx
(señal marrón en los gráficos); la que toma el valor de 29 cuando se presenta una
instrucción calli o call.
En la figura ?? se muestra un ejemplo de uso de la función call. El código en C
utilizado para este ejemplo se muestra en color azul. La lı́nea de código result1 =
function(0x30) hace el llamado a la función function pasándole el parámetro 0x30
(decimal 48); el código implementado por el compilador se muestra junto al código
en C; como se mencionó anteriormente, los primeros registros del banco de registros
se usan para pasar parámetros entre funciones, en el paso 1, se almacena el valor
0x30 (48 decimal) en r1; en el paso dos se hace un llamado inmediato a función a
la dirección de memoria 0x8C, lo que hace que el valor del contador de programa
tome el valor 0x8C y se almacene el valor 0x310 en el registro ra.
La función function está declarada como int function(int x) y reside en la posi-
ción de memoria 0x8C. En el paso 3, se almacena el valor de los registros que se
utilizan en la función con el fı́n de restaurarlos antes de retornar al programa donde
fué llamada, esto se hace debido a que solo existe un banco de registros en el pro-
cesador y si no se hace esto el valor de los registros antes y después del llamado
será diferente lo que ocasionará errores en los algoritmos implementados. EL regis-
tro ra almacena el valor de la dirección de retorno, y se almacena para asegurar que
cuando se hagan llamados a función anidados se retorne a la dirección adecuada.
En el paso 4 se restaura el valor de los registros, garantizando la continuidad del
programa principal; finalmente, en el paso 6 la función ret carga el valor del ra en
el contador de programa
En la figura ?? se ilustra la importancia del almacenamiento de los registros en
los llamados a funciones, para este ejemplo se consideró el registro ra; cuando se
almacena el registro ra en la función F2 para ser restaurado al finalizar la función
el flujo de programa retorna a la función F1, lo que se ejecuta correctamente en los
dos casos. Cuando finaliza F1 el valor de ra varı́a; cuando no se almacena el valor
la dirección de retorno de F1 es modificada por lo que cuando se retorna el contador
de programa se hace igual a la dirección de retorno de F2.
1.3 Set de Instrucciones del procesador Mico32 13

inst[20:16] inst[15:11]
call_immediate= {{6{inst[25]}}, inst[25:0]}
31 26 branch_immediate={{16{inst[15]}}, inst[15:0]} op_call op_calli
Instruction Decoder inst[31]
IM bra call 5'd29
CODE imm26
select_call_immediate
instruction_d calli 0
31 26
IM CODE imm26 write_idx
w_result
branch_offset branch
instruction_d bi 0
read_idx_0_d read_idx_1_d

registers write_idx_w

x_result x_result
reg_data_0 reg_data_1
m_result m_result
w_result w_result

001 01x 1xx 000 raw_x_1


Bypass for reg port 1 000 1xx 01x 001
raw_m_1 raw_x_0 Bypass for reg port 0
raw_w_1 raw_m_0
raw_w_0
{32{1'bx}}
immediate_d bypass_data_0
{32{1'b0}} {pc_f,2'b00}
bypass_data_1

00 10 11 01 1 0
d_result_sel_1_d d_result_sel_0_d
pc_d + branch_offset_d
d_result_1
d_result_0

~stall_x
D/X stage registers branch_reg_d

branch_target_x
operand_1_x
operand_0_x

adder_op_x
adder_op_x_n

adder

adder_carry_n_x
valid_m
branch_m adder_overflow_x

condition_met_m adder_result_x

1xxx 0000 0001 001x 01xx


x_result_sel_add_x
x_result_sel_csr_x
x_result_sel_mc_arith_x
x_result_sel_sext_x {eba, eid_x, {3{1'b0}}}

x_result

stall_m ~stall_m
exception_x
X/M stage registers

branch_target_m

{31{1'b0},condition_met_m} operand_m
Instruction Unit
pc_a = branch_target_m
m_result_sel_compare_m 1x 00 01

exception_m m_result_sel_shift_m

{pc_m, 2'b00} m_result

exception_m M/W stage registers


1'b0

branch_taken_m
operand_w

load_data_w

1x 00 01 w_result_sel_load_w

w_result_sel_mul_w

w_result

Figura 1.6 Camino de datos de los saltos y llamado a funciones inmediatos


14 1 Implementación de tareas Software utilizando procesadores Soft Core

inst[20:16] inst[15:11]
31 26 op_call op_calli
Instruction Decoder inst[31] bra call
5'd29
IM CODE RX 00000000000000000000

instruction_d call 25 21 0
31 26
IM CODE RX 00000000000000000000 write_idx
w_result
RX branch
instruction_d b 25 21 0
read_idx_0_d read_idx_1_d

registers write_idx_w

x_result x_result
pc_a pc_f pc_d pc_x pc_m pc_w reg_data_0 reg_data_1
m_result m_result
w_result w_result

001 01x 1xx 000 raw_x_1


Bypass for reg port 1 000 1xx 01x 001
raw_m_1 raw_x_0 Bypass for reg port 0
raw_w_1 raw_m_0
raw_w_0
{32{1'bx}}
immediate_d bypass_data_0
{32{1'b0}} {pc_f,2'b00}
bypass_data_1

00 10 11 01 1 0
d_result_sel_1_d d_result_sel_0_d
pc_d + branch_offset_d
d_result_1
d_result_0

~stall_x
D/X stage registers branch_reg_d

branch_target_x
operand_1_x
operand_0_x

adder_op_x
adder_op_x_n

adder

adder_carry_n_x
valid_m
branch_m adder_overflow_x

condition_met_m adder_result_x

1xxx 0000 0001 001x 01xx


x_result_sel_add_x
x_result_sel_csr_x
x_result_sel_mc_arith_x
x_result_sel_sext_x {eba, eid_x, {3{1'b0}}}

x_result

stall_m ~stall_m
exception_x
X/M stage registers

branch_target_m

{31{1'b0},condition_met_m} operand_m
Instruction Unit
pc_a = branch_target_m
m_result_sel_compare_m 1x 00 01

exception_m m_result_sel_shift_m

{pc_m, 2'b00} m_result

exception_m M/W stage registers


1'b0

branch_taken_m
operand_w

load_data_w

1x 00 01 w_result_sel_load_w

w_result_sel_mul_w

w_result

Figura 1.7 Camino de datos de los saltos y llamado a funciones


1.3 Set de Instrucciones del procesador Mico32 15

0000008c <function>: 3. Almacena los registros


int function(int x){ que va a utilizar y ra
1. Paso de parámetros 8c: 37 9c ff f8 addi sp,sp,-8
90: 5b 9d 00 04 sw (sp+4),ra
94: 5b 81 00 08 sw (sp+8),r1
result1 = function(0x30); return x;
308: 34 01 00 30 mvi r1,48 98: 2b 81 00 08 lw r1,(sp+8) 4. Restaura el valor de los
9c: 2b 9d 00 04 lw ra,(sp+4) registros utilizados
30c: fb ff ff 60 calli 8c <function>
a0: 37 9c 00 08 addi sp,sp,8
310: 5b 81 00 1c sw (sp+28),r1 2. Llamado a función a4: c3 a0 00 00 ret

5. Retorno a la siguiente posición


del llamado
6. R1 almacena el valor retornado por la función

Figura 1.8 Ejemplo de código: llamado a función

dir1: Call F1 dir1: Call F1


ra = dir1 + 4 ra = dir1 + 4
ret: ra = dir1 + 4 ret: ra = dir2 + 4
PC= dir1 + 4 PC= dir2 + 4
F1 F1
ret: ra = dir1 + 4 ret: ra = dir2 + 4
PC = dir2 + 4 PC = dir2 + 4
dir2: Call F2 dir2: Call F2
ra = dir2 + 4 ra = dir2 + 4
F2 F2

Almacenando ra Sin almacenar ra

Figura 1.9 Llamado a función anidado

1.3.3. Comunicación con la memoria de datos

Antes de estudiar el camino de datos correspondiente a este grupo de instruc-


ciones, hablaremos de los tipos de datos que soporta el procesador MICO32. En la
figura ?? se muestran ejemplos de manipulación de diferentes tipos de datos y como
estos son tratados en la memoria del procesador.

1.3.3.1. Tipos de datos

El primer tipo de datos que se muestra en esta figura es el char, la variable da-
ta8 es declarada como un volatile unsigned char *, es decir un puntero a un char
sin signo tipo volatile; los tipos de datos volatile le indican al compilador que no
realice optimizaciones sobre esta variable, lo que es importante cuando se direccio-
nan periféricos. Al puntero data8 se le asigna la dirección 0x400 y el valor 0x44. Si
se aumenta el valor de la dirección del puntero en una posición data8++ la nueva
dirección será 0x401 y si se aumenta de nuevi pasará a ser 0x402; lo que indica
que el procesador a pesar de ser de 32 bits puede realizar direccionamiento con
16 1 Implementación de tareas Software utilizando procesadores Soft Core

granularidad byte; esto es muy conveniente para un almacenamiento eficiente de


información, de no ser ası́ se utilizarı́a una palabra de 32 bits para almacenar 8 bits.
La segunda parte de la figura ?? ilustra el manejo del tipo de dato short el cual
es de 16 bits; para esto se utiliza en puntero data16 con una dirección inicial de
0x200 y un valor de 0x2020; al aumentar la dirección del puntero en 1 (data16++)
la dirección resultante es 0x202, lo que permite el almacenamiento eficiente de este
tipo de dato.
Finalmente se ilustra el tipo de datos int y se observa como las direcciones de
memoria inical y final después de aumentar el valor del puntero son 0x300 0x304;
lo que muestra que el direccionamiento interno de la memoria depende del tipo de
datos.

data8 = (volatile unsigned char *)(0x400);

*data8 = 0x44;
0x44
8c: 34 02 00 44 m
0x11 0x404 0x22 0x403 0x55 0x402 0x66 0x401 0x400 90: 34 01 04 00 m
(68) (1024) 94: 30 22 00 00 s

data8++; data8++; data8++; data8++;


*data8 = 0x11; *data8 = 0x22; *data8 = 0x55; *data8 = 0x66; *data8 = 0x44;

data16 = (volatile unsigned short *)(0x200);

*data16 = 0x2020;
c8: 34 02 20 20 m
0x22 0x205 0x22 0x204 0x201 0x20 0x200 cc: 34 01 02 00 m
0x21 0x203 0x21 0x202 0x20
(512)
d0: 0c 22 00 00 s

data16++; data16++;
*data16 = 0x2222; *data16 = 0x2121; *data16 = 0x2020 (8224);

data8 = (volatile unsigned int *)(0x300);


*data32 = 0x40403030
e8: 78 02 40 40
f0: 38 42 30 30
0x300
0x31 0x307 0x31 0x306 0x31 0x305 0x31 0x304 0x40 0x303 0x40 0x302 0x30 0x301 0x30 (768)
f8: 34 03 03 00
fc: 58 62 00 00

data32++;
*data32 = 0x31313131; *data32 = 0x40403030;

Figura 1.10 Tipos de datos soportados por el procesador Mico32

El procesador MICO32 posee 4 señales D SEL O[3:0] que son utilizadas para
indicarle a los periféricos el tipo de operación de lectura/escritura que se está efec-
tuando; en la figura ?? se observa que estas señales se activan de forma individual
indicando el byte que se está direccionando; en la figura ?? las señales se activan
por parejas indicando el grupo de 2 bytes que se está direccionando; finalmente en
la figura ?? las 4 señales se activan al tiempo lo que indica un acceso a los 4 bytes
al mismo tiempo.
1.3 Set de Instrucciones del procesador Mico32 17

Time 2300 ns
D_ADR_O[31:0]=00000000 00000400
2400 ns
00000401
2500 ns 2600 ns 2700 ns
00000402
2800 ns 2900 ns
00000403
3 us 3100 ns
00000404
3200 ns 3300 ns 3400 ns

D_DAT_O[31:0]=00000000 44444444 66666666 55555555 22222222 11111111


D_SEL_O[3:0]=0 8 4 2 1 8
D_SEL_O[3]=0
D_SEL_O[2]=0
D_SEL_O[1]=0
D_SEL_O[0]=0

Figura 1.11 Acceso a un data tipo char

Time 3400 ns
D_ADR_O[31:0]=00000000 00000200
3500 ns 3600 ns 3700 ns
00000202
3800 ns 3900 ns 4 us
00000204
4100 ns 4200 ns
00000300
D_DAT_O[31:0]=00000000 20202020 21212121 22222222 30303030
D_SEL_O[3:0]=0 C 3 C F
D_SEL_O[3]=0
D_SEL_O[2]=0
D_SEL_O[1]=0
D_SEL_O[0]=0

Figura 1.12 Acceso a un data tipo short

Time
D_ADR_O[31:0]=00000304 00000204
4200 ns
00000300
4300 ns 4400 ns
00000304
4500 ns
00000404
4600 ns

D_DAT_O[31:0]=31313131 22222222 30303030 31313131


D_SEL_O[3:0]=F C F 8
D_SEL_O[3]=1
D_SEL_O[2]=1
D_SEL_O[1]=1
D_SEL_O[0]=1

Figura 1.13 Acceso a un data tipo int

1.3.3.2. Escritura a la memoria de datos

El acceso a memoria de datos permite extender las capacidades del procesador


posibilitando la conexión de periféricos; los que a su vez, realizan la comunicación
con el exterior utilizando diferentes protocolos de comunicación y medios fı́sicos.
En esta subsección se describirá la forma en la que el MICO32 implementa las
operaciones de lectura y escritura a la memoria de datos.
En la figura ?? se ilustra el camino de datos asociado a las instrucciones sb,
sh y sw. En las tres, el valor contenido en el registro direccionado por instruc-
tion d[25:21] (RX señales color rojo en la figura) más el valor de 16 bits (con signo
extendido a 32 bits) forman la dirección a la que se desea escribir. El valor conte-
nido en el registro direccionado por instruction d[20:16] (RY señales color azul en
la figura) corresponde al dato que será escrito en esa posición de memoria; de esta
forma se construyen los buses de datos y direcciones del procesador. Cómo se dijo
anteriormente el MICO32 direcciona con granularidad de byte, por esta razón en
las instrucciones sh y sw se indica el valor escrito en las direcciones +1 y +1, +2
y +3 respectivamente; indicando el tamaño en bytes del tipo de dato escrito. Estas
18 1 Implementación de tareas Software utilizando procesadores Soft Core

señales ingresan a un módulo llamado load store unit que se encarga de generar las
señales correspondientes al bus wishbone, más adelante estudiaremos en detalle el
funcionamiento de este bus.
En la figura ??, y ?? se muestran las formas de onda cuando se escribe un dato
tipo char (0x44) a la dirección 0x400, el dato escrito en el bus es 0x44444444 pa-
ra que el periférico pueda utilizar cualquiera de las cuatro partes del bus de datos
D DAT O[7:0], D DAT O[15:8], D DAT O[23:16], D DAT O[31:24], algo simi-
lar ocurre en la escritura del tipo de dato short (con valor 0x2020) mostrado en
la figura ??, aquı́ se repite el dato para poder utilizar dos partes del bus del datos
D DAT O[15:0] y D DAT O[31:16].

1.3.3.3. Lectura

En la figura ?? se muestra el camino de datos asociado a las instrucciones lb/lbu,


lh/lhu y lw. La dirección de la cual se leera se calcula de forma similar al caso de
la escritura; el valor contenido en el registro direccionado por instruction d[25:21]
(RY señales color rojo en la figura). El dato leı́do por el módulo load store unit
(señal morada en la figura) es almacenado en el registro cuya dirección está dada
por instruction d[20:16].

1.3.4. Interrupciones

Existen dos formas de conocer si un periférico conectado al procesador requiere


atención por parte del procesador; examinando de forma constante los registros de
estado del periférico o utilizando interrupciones. La consulta constante de los regis-
tros de estado del periférico requiere incluir en el código una rutina que realice esta
operación, la cual debe ser llamada de forma regular en el programa principal, la
velocidad con que se realice esta consulta debe ser la adecuada para que no se pier-
dan eventos; debido a esto; uno de los problemas de esta técnica es que al aumentar
el número de periféricos aumenta el tiempo entre consultas para un periférico, lo
que aumenta la posibilidad de pérdida de eventos; adicionalmente, aumenta el tiem-
po dedicado a la consulta, lo que disminuye el tiempo disponible para ejecutar las
tareas software en el procesador.
Las interrupciones modifican el flujo normal de ejecución del sistema y son ori-
ginadas por señales dedicadas, lo que hace que su atención ocurra de forma inme-
diata. Cuando se presenta una interrupción, el valor del contador de programa toma
un valor fijo que recibe el nombre de vector de interrupción, el valor del vector de
interrupción está formado por (ver figura ?? señales de color rojo) una dirección ba-
se EBA (Exception Base Address) que por defecto es 0x00, un ı́ndice que indica la
exepción que se presentó eid x (6 para la interrupción) y tres ceros; estos tres ceros
hacen que el espacio entre vectores de excepción sea de 8 palabras de 32 bits, por lo
1.3 Set de Instrucciones del procesador Mico32 19

31 26 20 16
Instruction Decoder
IM CODE RX RY imm16

instruction_d 25 21 15 0
w_result
RY RX
address = gpr[RX] + sign_extended(imm16)
memory[address] = gpr[ry] & 0xFF (SB)
read_idx_0_d read_idx_1_d
memory[address] = gpr[ry] & 0xFF
memory[address +1] = (gpr[rY] >> 8) & 0xff (SH)

memory[address] = gpr[ry] & 0xFF registers


write_idx_w
memory[address +1] = (gpr[rY] >> 8) & 0xff
memory[address +2] = (gpr[rY] >> 16) & 0xff x_result x_result
memory[address +3] = (gpr[rY] >> 32) & 0xff (SW) m_result m_result
w_result w_result
gpr[ry] gpr[rx]

Bypass for reg port 1


001 01x 1xx 000 1'b0 Bypass for reg port 0
1'b0 000 1xx 01x 001
1'b0
1'b0
1'b0
store_operand_x 1'b0
{32{1'bx}}
load_store_address_x sign_extend(imm16) {pc_f,2'b00}
{32{1'b0}} bypass_data_1 bypass_data_0
D_DAT_0 load_store_address_m
D_ADR_0
D_CYC_0 load_store_address_w 00 10 11 01 0 1
2'b10 1'b0
D_SEL_0
Load Store Unit load_q_x
D_STB_0
D_WE_0 d_result_1
load_q_m d_result_0
D_CTI_0
D_LOCK_0
store_q_m = 1'b1
D_BTE_0
sign_extend = 1'b1 ~stall_x
D/X stage registers

size_x = 2'b00

operand_1_x

sign_extend(memory[address])

1'b0
1'b1

adder

adder_carry_n_x logic_result_x

adder_overflow_x

adder_result_x

1xxx 0000 0001 001x 01xx


1'b1
1'bx
1'bx
1'bx

x_result

X/M stage registers ~stall_m

operand_m
{31{1'b0},condition_met_m}

1x 00 01
1'b0

1'b0

m_result

1'b0
M/W stage registers

operand_w
load_data_w

1x 00 01 1'b0

1'b0

w_result

Figura 1.14 Camino de datos de las instrucciones de escritura a memoria


20 1 Implementación de tareas Software utilizando procesadores Soft Core

31 26 20 16
Instruction Decoder
IM CODE RY RX imm16

instruction_d 25 21 15 0
w_result
RY
address = gpr[RY] + sign_extended(imm16)
gpr[rx] = sign/zero_extend(memory[address]) (LB/LBU)

gpr[rX] = sign/zero_extend((memory[address] << 8)


| (memory[address+1])) (LH/LHU)

gpr[rx] = (memory[address] << 24) registers RX


| (memory[address+1] << 16)
| (memory[address+2] << 8) x_result x_result
| (memory[address+3]) (LW) m_result m_result
w_result w_result
gpr[ry]

Bypass for reg port 1


001 01x 1xx 000 1'b0 Bypass for reg port 0
1'b0 000 1xx 01x 001
1'b0
1'b0
1'b0
store_operand_x 1'b0
{32{1'bx}}
load_store_address_x sign_extend(imm16) {pc_f,2'b00}
{32{1'b0}} bypass_data_1 bypass_data_0
D_DAT_0
load_store_address_m
D_ADR_0
D_CYC_0 00 10 11 01 0 1
load_store_address_w
D_SEL_0 2'b10 1'b0
D_STB_0 Load Store Unit load_q_x
D_WE_0 d_result_1
D_CTI_0 load_q_m d_result_0
D_LOCK_0
D_BTE_0 store_q_m = 1'b1

sign_extend = 1'b1 ~stall_x


D/X stage registers

size_x = 2'b00

operand_1_x

sign_extend(memory[address])

1'b0
1'b1

adder

adder_carry_n_x logic_result_x

adder_overflow_x

adder_result_x

1xxx 0000 0001 001x 01xx


1'b1
1'bx
1'bx
1'bx

x_result

X/M stage registers ~stall_m

operand_m
{31{1'b0},condition_met_m}

1x 00 01
1'b0

1'b0

m_result

1'b0
M/W stage registers

operand_w
load_data_w

1x 00 01 1'b0

1'b0

w_result

Figura 1.15 Camino de datos de las instrucciones de escritura a memoria


1.3 Set de Instrucciones del procesador Mico32 21

que la rutina de atención a la interrupción debe tener máximo 8 instrucciones (esta


rutina se explicará más adelante); para la interrupción el valor del vector es de 0x30.
Como se puede observar en la figura ?? para que se genere una excepción
(señales de color azul), se debe activar cualquiera de las señales instruction bus error exception,
sysrem call exception exception, data bus error exception, divide by zero exception
o interrupt exception; lo que activará la señal branch taken m quien a su vez realiza
el cambio en el contador de programa pc a. Para que la señal interrupt exception
se active es necesario: 1- habilitar la generación de interrupciones, es decir, que el
flag ie (interrupt enable) está activo; 2- habilitar la generación de la interrupción
deseada, para esto el bit correspondiente a la interrupción debe ser igual a 1 en la
señal im (interrupt mask), lo que recibe el nombre de enmascaramiento y 3 - Que el
periférico asociado a la interrupción realice una solicitud de atención activando su
señal de interrupción, lo que origina una activación de la señal correspondiente en
ip (interrupt pending).
Al activarsre la señal exception x la variable que direcciona el registro a ser es-
crito en el banco de registros write idx toma el valor 30 decimal (ea - exception
address) y el valor a ser escrito (w result) será pc m, 2’b00 (los saltos en el con-
tador de programa es de a 4 bytes, debido a que las instrucciones son de 32 bits,
por esta razón los dos bits menos significativos no son tomados en cuenta); lo que
garantiza que al salir de la interrupción, el programa principal continuará donde se
interrumpió.

1.3.4.1. Rutina de atención a la interrupción

A continuación se lista la rutina que se ejecuta cada vez que se presenta una
interrupción; como se dijo anteriormente, la dirección del vector de interrupción
debe ser 0x48, por lo que este código debe residir en la memoria de programa en
dicha dirección.
48 sw ( sp +0) , r a
49 calli save all
50 rcsr r1 , I P
51 calli irq handler
52 mvhi r1 , 0 x f f f f
53 ori r1 , r1 , 0 x f f f f
54 wcsr IP , r 1
55 bi restore all and eret
En la lı́nea 48 se almacena el valor del registro ra en la pila (la pila es una región
de la memoria RAM que se utiliza para diferentes propósitos en la ejecución de un
programa), esto se hace para que al salir de la rutina de atención a la interrupción el
programa continúe de forma adecuada, de no hacer esto, si la interrupción se produjo
cuando se estaba ejecutando una función el valor de retorno de la interrupción se
modificarı́a.
En la lı́nea 49 se hace un llamado a la función save all:
22 1 Implementación de tareas Software utilizando procesadores Soft Core

interrupt_n
valid_m
branch_m
csr_write_data
asserted condition_met_m
LM32_EID_INST_BUS_ERROR

LM32_EID_INTERRUPT LM32_EID_DATA_BUS_ERROR

LM32_EID_SCALL LM32_EID_DIVIDE_BY_ZERO

instruction_bus_error_exception
D csr, exception stall_m data_bus_error_exception
Q divide_by_zero_exception
interrupt_exception

im ip system_call_exception
eid_x

ie

{eba, eid_x, {3{1'b0}}} (48)


5'd30
x_result
write_idx_m
interrupt_exception
~stall_m X/M stage registers
system_call_exception data_bus_error_exception exception_x
instruction_bus_error_exception
divide_by_zero_exception
branch_target_m
write_idx_m

operand_m

Instruction Unit
1x 00 01
exception_x pc_a = branch_target_m m_result_sel_compare_m
m_result_sel_shift_m
exception_m
{pc_m, 2'b00} m_result

exception_m M/W stage registers


1'b0

branch_taken_m
operand_w

load_data_w

1x 00 01 w_result_sel_load_w

w_result_sel_mul_w

w_result

Figura 1.16 Camino de datos correspondiente a las generación de excepciones

addi sp , sp , −128
sw ( sp +4) , r1
...
sw ( sp +108) , r27
# endif
sw ( sp +120) , ea
sw ( s p + 1 2 4 ) , ba
/ ∗ r a and s p n e e d s p e c i a l h a n d l i n g , a s t h e y h a v e b e e n m o d i f i e d ∗ /
lw r1 , ( s p + 1 2 8 )
sw ( sp +116) , r1
mv r1 , s p
addi r1 , r1 , 128
sw ( sp +112) , r1
ret
En esta función, toma una “fotografı́a” del estado del procesador en el instante
en que se presenta la interrupción, almacenando el valor de todos los registros en la
1.3 Set de Instrucciones del procesador Mico32 23

pila, esto se hace para garantizar que el estado del procesador antes y después de la
interrupción sea el mismo.
En la lı́nea 49 se almacena el valor de la señal ip (interrupt pending) en el re-
gistro r1, esto se hace para pasar parámetros a la función que será llamada en la
lı́nea 50. irq handler es la función que realizará las acciones correspondientes a una
determinada interrupción, esta función debe ser declarada en C en cualquier archivo
que haga parte del código fuente de la aplicación (en los ejemplos del repositorio se
declara en el archivo soc-hw.c) como: void irq handler(uint32 t pending).
En las lı́neas 51 - 53 se llena con unos la señal IP, lo que equivale a una restau-
ración de esta señal, y puede verse como una forma de informarle al procesador que
las interrupciones ya fueron atendidas. Finalmente en la lı́nea 54 se hace un llamado
a la función restore all and eret:
lw r1 , ( s p + 4 )
...
lw r27 , ( s p + 1 0 8 )
lw ra , ( sp +116)
lw ea , ( s p + 1 2 0 )
lw ba , ( s p + 1 2 4 )
/ ∗ S t a c k p o i n t e r m u s t be r e s t o r e d l a s t , i n c a s e i t h a s b e e n u p d a t e d ∗ /
lw sp , ( s p + 1 1 2 )
eret
Esta función: restaura el valor de todos los registros del procesador, incluyendo
los registros ra, ea y ba, el registro ea se almacena para asegurar el correcto funcio-
namiento ante el caso de excepciones anidades; y ejecuta la instrucción eret la que
hace que el contador de programa tome el valor almacenado en el registro ea con
lo que el programa retorna a la siguiente instrucción del punto donde se generó la
interrupción.
Como se mencionó anteriormente, para que la interrupción se presente es nece-
sario habilitar las interrupciones globales y la máscara asociada al periférico. Para
esto, el archivo crt0ram.S suministra las siguientes funciones:
irq enable :
mvi r1 , 1
wcsr IE , r 1
ret

irq disable :
mvi r1 , 0
wcsr IE , r 1
ret

irq set mask :


wcsr IM , r 1
ret
24 1 Implementación de tareas Software utilizando procesadores Soft Core

En este código se utiliza la instrucción wcsr y en la función de atención a la


interrupción ya se habı́a utilizado la instrucción rcsr (rcsr r1, IP); estas instrucciones
realizan operaciones de escritura y lectura sobre los registros de estatus y control
del procesador. En la figura ?? se muestra el camino de datos relacionado con estas
intrucciones.
El camino de color rojo muestra la escritura utilizando la instrucción wcsr; ins-
truction d[20:16] contiene la dirección del registro a ser escrito en csr; y instruc-
tion d[25:21] el registro de estatus y control a escribir.
La lectura de los registros de estado y control se muestra en color morado en la
figura; de forma similar a la escritura instruction d[25:21] direcciona el registro a
leer y instruction d[15:11] la dirección del registro que almacenará el valor leı́do.

1.3.5. Retorno de función y de excepción

La figura ?? muestra el camino de datos asociado a las instrucciones de retorno


de excepción y de función eret y ret; en estas instrucciones, el valor de la dirección
del registro que va a ser almacenado en el contador de programa es fijo (instruc-
tion d[25:21]), siendo 30 para la instrucción eret y 29 para la instrucción ret. El
valor contenido en estos registros pasa a la señal branch target y su valor será al-
macenado en el contador de programa retornando a la dirección siguiente a la que
se produjo la excepción o el llamado a función.
En la Figura ?? se resume el proceso de atención a la interrupción. La solicitud de
atención por parte de un periférico recibe el nombre de IRQ (interrupt request) y la
rutina que atiende esta solicitud recibe el nombre de ISR (interrupt service routine)

1.4. Arquitectura del SoC LM32

En la sección anterior se explicó el funcionamiento detallado de cada grupo de


instrucciones del procesador MICO32; en esta sección se realizará una descripción
de un SoC (sistema sobre silicio) basado en el procesador MICO32; esta arquitec-
tura permitirá entender los SoC modernos desde el punto de vista estructural y de
programación
En la figura ?? se muestra el diagrama de bloques del SoC LM32, el cual tiene
como unidad de procesamiento central el procesador MICO32; esta CPU se conecta
a una serie de periféricos a través de el bus wishbone. La funcionalidad del SoC
está determinada por los periféricos implementados, en esta sección se realizará una
descripción de cuatro periféricos básicos para el desarrollo de operaciones básicas
de entrada/salida:
Boot-RAM: Esta memoria almacena la aplicación que se ejecutará al inicializar
el SoC.
1.4 Arquitectura del SoC LM32 25

31 26 20 16

IM CODE CSR RX 0000000000000000

wcsr 25 21 15 0

csr = gpr[RX]

31 26 20 16

IM CODE CSR 00000 RX 00000000000

25 21 15 0
Instruction Decoder rcsr
gpr[RX] = csr

w_result
RX

read_idx_0_d read_idx_1_d

RX = instruction[15:11]
registers write_idx_w

x_result x_result
reg_data_0 reg_data_1
m_result m_result
w_result w_result
gpr[RX]

001 01x 1xx 000 raw_x_1


Bypass for reg port 1 000 1xx 01x 001
raw_m_1 raw_x_0 Bypass for reg port 0
raw_w_1 raw_m_0
raw_w_0
{32{1'bx}}
immediate_d
{32{1'b0}} {pc_f,2'b00}
bypass_data_1

00 10 11 01 1 0
d_result_sel_1_d d_result_sel_0_d
interrupt_n csr_write_data
d_result_1
d_result_0
asserted

~stall_x
D/X stage registers

operand_0_x
operand_1_x

D D D
Q Q Q
csr_x = instruction[25:21]
exception
ip im {29'b0.bie, eie,ie}
adder_op_x
adder_op_x_n csr_x

csr_read_data_x

{eba, 8'h00}

adder cfg {32{1'bx}}


interrupt_csr_read_data_x

3'h0 3'h6 3'h7 default


3'h1
csr_x = instruction[15:11]
adder_carry_n_x 3'h2

adder_overflow_x
adder_result_x

1xxx 0000 0001 001x 01xx


x_result_sel_add_x
x_result_sel_csr_x
x_result_sel_mc_arith_x
x_result_sel_sext_x

x_result

~stall_m
X/M stage registers

{31{1'b0},condition_met_m}

operand_m

1x 00 01
m_result_sel_compare_m
m_result_sel_shift_m

{pc_m, 2'b00} m_result

exception_m M/W stage registers


1'b0

operand_w

load_data_w

1x 00 01 w_result_sel_load_w

w_result_sel_mul_w

w_result

Figura 1.17 Camino de datos correspondiente al acceso de los registros asociados a las excepcio-
nes
26 1 Implementación de tareas Software utilizando procesadores Soft Core
31 26
IM CODE 11110 00000000000000000000
Instruction Decoder
instruction_d eret 25 21 0
31 26
IM CODE 11101 00000000000000000000
w_result
RX
instruction_d ret 25 21 0
read_idx_0_d read_idx_1_d
pc_a pc_f pc_d pc_x pc_m pc_w

registers write_idx_w

x_result x_result
reg_data_0 reg_data_1
m_result m_result
w_result w_result

001 01x 1xx 000 raw_x_1


Bypass for reg port 1 000 1xx 01x 001
raw_m_1 raw_x_0 Bypass for reg port 0
raw_w_1 raw_m_0
raw_w_0
{32{1'bx}}
immediate_d bypass_data_0
{32{1'b0}} {pc_f,2'b00}
bypass_data_1

00 10 11 01 1 0
d_result_sel_1_d d_result_sel_0_d
pc_d + branch_offset_d
d_result_1
d_result_0

~stall_x
D/X stage registers branch_reg_d

branch_target_x
operand_1_x
operand_0_x

adder_op_x
adder_op_x_n

adder

adder_carry_n_x
valid_m
branch_m adder_overflow_x

condition_met_m adder_result_x

1xxx 0000 0001 001x 01xx


x_result_sel_add_x
x_result_sel_csr_x
x_result_sel_mc_arith_x
x_result_sel_sext_x {eba, eid_x, {3{1'b0}}}

x_result

stall_m ~stall_m
exception_x
X/M stage registers

branch_target_m

{31{1'b0},condition_met_m} operand_m
Instruction Unit
pc_a = branch_target_m
m_result_sel_compare_m 1x 00 01

exception_m m_result_sel_shift_m

{pc_m, 2'b00} m_result

exception_m M/W stage registers


1'b0

branch_taken_m
operand_w

load_data_w

1x 00 01 w_result_sel_load_w

w_result_sel_mul_w

w_result

Figura 1.18 Camino de datos asociado al retorno de función y de excepción


1.4 Arquitectura del SoC LM32 27

(IRQ1) (IRQ1) Solicitud de atención a interrupción


(IRQ1)
irq_enable: irq_set_mask:
mvi r1, 1 wcsr IM, r1 addr - 4 addr addr+4
wcsr IE, r1 ret
ret Programa principal
main.c

Solicitud no atendida: Solicitud no atendida:


Interrupciones Interrupción 1 no
globales no habilitadas habilitada
ea = addr + 4
PC = 0x48
Guarda todos los
registros en la pila
(BRAM) Vector de interrupción (0x48)
sw (sp+0), ra
calli _save_all
Asigna IP como parámetro rcsr r1, IP
de la función irq_handler calli irq_handler
mvhi r1, 0xffff
void irq_handler( uint32_t pending )
ori r1, r1, 0xffff
Inicializa el valor {
wcsr IP, r1
de IP switch ( pending ) {
bi _restore_all_and_eret
case per1:
crt0ram.S isr_per1(); void isr_per1( void)
Restaura el valor de los break; {
registros almacenados case per2: //código
en la pila y retorna al isr_per2(); }
programa principal break;
default:
// Code void isr_per2( void)
break; {
} //código
} }
soc-hw.c

Figura 1.19 Flujo asociado a la atención de una interrupción

Maestro 0
Datos
BOOT RAM
Maestro 1
Instrucciones Esclavo1 .text
lm32_cpu
.rodata
rtl/lm32
Interrupciones .data
(rtl/wb_bram)
Parameter bootram_file = "image.ram"
Interconexión 0x00000000
del bus
Wishbone Esclavo2 UART
rtl/wb_conbus (rtl/wb_uart)
0x20000000
conbus #(
.s_addr_w(2),
.s0_addr(2'b00), Esclavo3
.s1_addr(2'b01), TIMER
.s2_addr(2'b11), (rtl/wb_timer)
.s3_addr(2'b10) 0x40000000
SoC )

system.v Esclavo4 GPIO


(rtl/wb_timer)

Figura 1.20 Diagrama de bloques del SoC LM32


28 1 Implementación de tareas Software utilizando procesadores Soft Core

UART (Universal Asynchronous Receiver-Transmitter): Puerto serie que permite


comunicarse con el exterior y es utilizado como medio de depuración.
TIMER: Encargado de generar bases de tiempo precisas, de vital importancia en
el funcionamiento de la mayorı́a de las aplicaciones.
GPIO: Pines de entrada/salida de propósito general.
Adicionalmente, existe un módulo llamado conbus que realiza la interconexión
entre los periféricos y el procesador, su arquitectura y funcionamiento se explicarán
más adelante.

1.4.1. Bus wishbone

El bus wishbone es un bus diseñado para comunicar los diferentes comonentes


de un SoC, este bus es abierto y puede ser utilizado libremente. A continueación se
listan las señales que componen este bus:
ack o: La activación de esta señal indica la terminación normal de un ciclo del
bus.
addr i: Bus de direcciones.
cyc i: Esta señal se activa cuando un ciclo de bus válido se encuentra en progreso.
sel i: Estas señales indican cuando se coloca un dato válido en el bus dat i duran-
te un ciclo de escritura, y cuando deberı́an estar presentes en el bus dat o durante
un ciclo de lectura. El número de señales depende de la granularidad del puerto.
El LM32 maneja una granularidad de 8 bits sobre un bus de 32 bits, por lo tanto
existen 4 señales para seleccionar el byte deseado (sel i(3:0)).
stb i: Esta señal se activa cuando se selecciona un esclavo; el cual debe responder
a las otras señales únicamente cuando se activa esta señal. El esclavo debe activar
la señal ack o como respuesta a la activación de stb i.
we i: Esta señal indica la dirección del flujo de datos; en un ciclo de lectura tiene
un nivel lógico bajo y en escritura tiene un nivel lógico alto.
dat i: Bus de datos de entrada.
dat o: Bus de datos de salida.

En la figura ?? se muestra un ciclo de lectura tı́pico a un periférico con dirección


de memoria 0xF0000000, en ella podemos observar la activación de las señales
wb cyc i y wb stb i indicando un ciclo de bus válido y la selección del esclavo,
el valor de wb we i indica que el acceso es de lectura, a lo que el esclavo debe
responder colocando el dato requerido por el procesador en el bus de salida wb dat o
y con la activación de la señal wb ack o
En la figura ?? se muestra la escritura del valor 0x2A a la dirección de memoria
0xF0000004, las formas de onda son similares a las del ciclo de lectura, salvo que
el valor de la señal wb we i es uno indicando la escritura.
1.4 Arquitectura del SoC LM32 29

Time
wb_stb_i=1
5200 ns 5210 ns 5220 ns 5230 ns 5240 ns 5250 ns 5260 ns 5270 ns 5280 ns 5290 ns 5300 ns 5310 ns 5320 ns 5330 ns

wb_cyc_i=1
wb_we_i=0
wb_ack_o=1
wb_adr_i[31:0]=F0000000 00000258 F0000000
wb_sel_i[3:0]=F F
wb_dat_i[31:0]=00000060 00000000 00000060
wb_dat_o[31:0]=00000000 000000xx 00000000
uart_rxd=z
uart_txd=1

Figura 1.21 Ciclo de lectura del bus wishbone

Time
wb_stb_i=1
5200 ns 5210 ns 5220 ns 5230 ns 5240 ns 5250 ns 5260 ns 5270 ns 5280 ns 5290 ns 5300 ns 5310 ns 5320 ns 5330 ns

wb_cyc_i=1
wb_we_i=0
wb_ack_o=1
wb_adr_i[31:0]=F0000000 00000258 F0000000
wb_sel_i[3:0]=F F
wb_dat_i[31:0]=00000060 00000000 00000060
wb_dat_o[31:0]=00000000 000000xx 00000000
uart_rxd=z
uart_txd=1

Figura 1.22 Ciclo de escritura del bus wishbone

1.4.1.1. Interface del bus wishbone (conmax)

El bus wishbone tiene una arquitectura maestro/esclavo en la que solo los maes-
tros pueden iniciar las operaciones de lectura y escritura y únicamente el esclavo
al que se le hace el requerimiento debe responder. Para coordinar la comunicación
entre múltiples maestros se debe incluir un árbitro, que en el LM32 recibe el nombre
de conmax. La figura ?? muestra el diagrama de bloques de este árbitro.
Una de las funciones del árbitro conmax es fijarle un rango de direcciones único
a cada periférico, por esta razón todo árbitro debe tener un decodificador de direc-
ciones (módulo ADDRESS DECODER en la figura ??) que tiene como entradas los
bits más significativos del bus de direcciones, en este caso solo se usan dos bits
(mx adr i[29:28]) ya que solo se cuenta con cuatro periféricos; este decodificador
activa las señales slave sel[3:0] de acuerdo a la definición en el archivo system.v:
conbus #(
. s addr w (2) ,
. s 0 a d d r ( 2 ’ b00 ) , // bram 0 x00000000
. s 1 a d d r ( 2 ’ b01 ) , // uart 0 x20000000
. s 2 a d d r ( 2 ’ b11 ) , // timer 0 x60000000
. s 3 a d d r ( 2 ’ b10 ) // gpio 0 x40000000
) conbus0 (
30 1 Implementación de tareas Software utilizando procesadores Soft Core

m0_adr_i, m0_cti_i, m0_sel_i, m0_dat_i, s0_adr_o, s0_cti_o, s0_sel_i, s0_dat_o, s0_we_o, s0_cyc_o s0_dat_i
m0_we_i, m0_cyc_i, m0_stb_i
s1_adr_o, s1_cti_o, s1_sel_i, s1_dat_o, s1_we_o, s1_cyc_o s1_dat_i

s2_adr_o, s2_cti_o, s2_sel_i, s2_dat_o, s2_we_o, s2_cyc_o s2_dat_i

s3_adr_o, s3_cti_o, s3_sel_i, s3_dat_o, s3_we_o, s3_cyc_o s3_dat_i


gnt[0]
i_bus_m m0_stb_i m1_stb_i

m0_cyc_i, m1_cyc_i
s0_stb_0 slave_sel[0]
gnt[1]
s0_dat_i

s0_stb_1 slave_sel[1]

m1_adr_i, m1_cti_i, m1_sel_i, m1_dat_i, s1_dat_i m0_dat_o


m1_we_i, m1_cyc_i, m1_stb_i
slave_sel[2] m1_dat_o
s0_stb_2 s2_dat_i
gnt[0]
m1_cyc_i, m0_cyc_i conbus_arb slave_sel[3]
gnt[1] s0_stb_3
s3_dat_i
slave_sel[0]
slave_sel[1] gnt[0]

Address slave_sel[2]
s0_dat_i m0_ack_o
i_bus_m[72:71] decoder slave_sel[3] s1_dat_i
mx_adr_i[29:28]
s2_dat_i
s3_dat_i m1_ack_o

gnt[1]

Figura 1.23 Circuito de interconexión del bus wishbone

Asignado las direcciones de memoria 0x00000000 - 0x1FFFFFFF, 0x20000000


- 0x3FFFFFFF, 0x60000000 - 0x7FFFFFFF y 0x40000000 - 0x5FFFFFFF a la
BRAM, UART, TIMER y GPIO respectivamente, la activación de slave sel[3:0]
hace que se active su correspondiente señal s0 stb [3:0] ( y se presenta un ciclo
válido de bus ) indicándole al periférico que ha sido seleccionado para una operación
de lectura o escritura.
En la figura ?? se muestra el circuito simplificado del árbitro conmax para una
operación de escritura; en ella se puede observar que todos los esclavos comparten
las señales s0 adr o, s0 cti o, s0 sel i, s0 dat o, s0 we o, s0 cyc o y sx dat i, las
cuales son la salida de un multiplexor que selecciona entre las señales correspon-
dientes a los diferentes maestros del SoC (m0 y m1 en este caso); las señales gnt[0]
y gnt[1] seleccionan al maestro que se conectará con todos los esclavos, por esta
razón nunca se activarán las dos al tiempo. Las únicas señales que no comparten los
esclavos wishbone son las que indican a los periféricos que han sido seleccionados
para una transferencia de información s0 stb [3:0]. slave sel [3:0]

m0_adr_i, m0_cti_i, m0_sel_i, m0_dat_i,


m0_we_i, m0_cyc_i, m0_stb_i

gnt[0] s0_adr_o, s0_cti_o, s0_sel_i, s0_dat_o, s0_we_o, s0_cyc_o s0_dat_i

s1_adr_o, s1_cti_o, s1_sel_i, s1_dat_o, s1_we_o, s1_cyc_o s1_dat_i

s2_adr_o, s2_cti_o, s2_sel_i, s2_dat_o, s2_we_o, s2_cyc_o s2_dat_i


gnt[1]
s3_adr_o, s3_cti_o, s3_sel_i, s3_dat_o, s3_we_o, s3_cyc_o s3_dat_i

s0_stb_0
s0_stb_1
m1_adr_i, m1_cti_i, m1_sel_i, m1_dat_i,
m1_we_i, m1_cyc_i, m1_stb_i s0_stb_2
s0_stb_3
gnt[0]
m1_cyc_i, m0_cyc_i conbus_arb
gnt[1]

Figura 1.24 Circuito equivalente a una operación de escritura para el árbitro del bus wishbone
1.4 Arquitectura del SoC LM32 31

En la figura ?? se muestra el circuito simplificado del árbitro conmax para una


operación de lectura; en ella podemos observar que los buses de datos de los pe-
riféricos s[3:0] dat i se conectan a los buses de datos de los maestros m[1:0] dat o;
las señales slave sel [3:0] se activan una a la vez y seleccionan el esclavo que se
conectará con el maestro.

slave_sel[0]
s0_dat_i

slave_sel[1]
s1_dat_i m0_dat_o

slave_sel[2] m1_dat_o
s2_dat_i

slave_sel[3]
s3_dat_i

Figura 1.25 Circuito equivalente a una operación de lectura para el árbitro del bus wishbone

Para ilustrar de forma gráfica la operación del árbitro se implementó un programa


que escribe los siguientes valores a las direcciones de los periféricos UART, TIMER
y GPIO:
1. 0xAA a la dirección del esclavo 1 0x20000004
2. 0x55 a la dirección del esclavo 3 0x40000000
3. 0xFF a la dirección del esclavo 2 0x60000000
Como podemos ver en la figura ??, las señales gnt[0] y gnt[1] se activan de
forma alterna y solo esta activa una de ellas, cuando se escribe el valor 0xAA a la
dirección 0x20000004 se activan las señales slave sel [1] y s1 stb o indicando la
activacı́ón del primer periférico; similarmente, cuando se escribe el valor 0x55 a la
dirección 0x40000000 se activan las señales slave sel [3] y s3 stb o indicando la
activacı́ón del tercer periférico y finalmente, cuando se escribe el valor 0xFF a la
dirección 0x60000000 se activan las señales slave sel [2] y s2 stb o indicando la
activacı́ón del segundo periférico.

Time 3100 ns 3200 ns 3300 ns


wb_adr_i[31:0] 0+ 20000004 000000E4 000000E8 000000EC 00000FE4
3400 ns 3500 ns 3600 ns 3700 ns 3800 ns 3900 ns
000000F0 000000F4 000000F8 40000000 000000FC 00000100 00000104 00000FE4
4 us 4100 ns 4200 ns 4300 ns
00000108 0000010C 00000110 60000000 00000104
wb_dat_i[31:0] 0+ 000000AA 00000000 40000000 00000000 00000055 00000000 60000000 00000000 000000FF 00000000
gnt[6:0] 01 02 01 02 01 02 01 02 01 02 01
gnt[1]
gnt[0]
slave_sel[4:0] 01 02 01 08 01 04 01
slave_sel[3]
s3_stb_o
slave_sel[2]
s2_stb_o
slave_sel[1]
s1_stb_o

Figura 1.26 Formas de onda del proceso de comunicación entre la CPU y los periféricos usando
el bus wishbone
32 1 Implementación de tareas Software utilizando procesadores Soft Core

1.4.2. Arquitectura de los periféricos

En esta subsección se realizará un estudio de la arquitectura de los esclavos wish-


bone, se analizarán tres periféricos: GPIO, UART y TIMER

1.4.2.1. Periférico GPIO

En todo SoC es necesario contar con pines de entrada/salida de propósito general,


este sencillo periférico permite controlar la dirección de un pin, controlar el valor
de un pin de salida y leer el valor de un pin de entrada; en la figura ?? se muestra el
diagrama de bloques de este periférico.

wb_dat_i[7:0]

D wb_stb_i
wb_stb_i wb_cyc_i wb_ack_o
wb_cyc_i wb_wr
ack
wb_we_i
ce Q
~ack

gpio_inout[7:0]
1 gpio_o
>
2 gpio_dir

wb_adr_i[3:2]

D gpio_input
wb_rd
wb_dat_o[7:0]
ce wb_stb_i
wb_rd wb_cyc_i
wb_wr
~wb_we_i set
ack
< ~ack ~wb_adr_i[3:2]
wb_gpio.v reset

Figura 1.27 Ejemplo de periférico wishbone: GPIO

La dirección del pin es fijada con un buffer tri-estado que a su vez es controlado
por el valor almacenado en el registro gpio dir los registros gpio o y gpio input
almacenan los valores escritos y leidos de los pines respectivamente.
Para entender el comportamiento de este periférico analizaremos los circuitos de
lectura y escritura de forma separada. En la figura ?? se muestra el circuito de lectu-
ra; el valor del registro gpio input es almacenado en un registro que está conectado
al bus de datos de salida del periférico wb dat o cuando la señal wb rd sea igual a
1 y la señal ack sea igual a cero. wb rd es igual a 1 cuando se presente un ciclo de
bus válido, se seleccione el periférico y se realice una operación de lectura.
1.4 Arquitectura del SoC LM32 33

wb_rd 1
1

1 wb_stb_i 0
wb_wr 0
1 wb_cyc_i wb_ack_o set
ack ack

reset
0

D gpio_input gpio_inout[7:0]

wb_dat_o[7:0]
ce wb_stb_i
1
1 wb_rd 1
wb_cyc_i
1 ~wb_we_i 1
< ~ack
wb_gpio.v

Figura 1.28 Circuito equivalente de lectura del periférico GPIO

El circuito simplificado de escritura se muestra en la figura ??; en este periférico


el bus de datos proveniente del maestro puede almacenarse en los registros gpio dir
y gpio o; el multiplexor controlado por wb adr i[3:2] selecciona donde será al-
macenado el dato. La transferencia al registro seleccionado se realiza únicamente
cuando la señal wb wr sea igual a 1 y la señal ack sea igual a cero. wb wr es igual a
1 cuando se presente un ciclo de bus válido, se seleccione el periférico y se realice
una operación de escritura.

wb_rd 0
0
wb_dat_i[7:0] 1
wb_wr 1 1
set
1 wb_stb_i ack
D
1 wb_cyc_i 1 wb_wr
reset
1 wb_we_i 0
~ack ce Q

1 wb_stb_i >
wb_cyc_i wb_ack_o gpio_inout[7:0]
1
ack
1 gpio_o
2 gpio_dir

wb_adr_i[3:2]
wb_gpio.v

Figura 1.29 Circuito equivalente de escritura del periférico GPIO


34 1 Implementación de tareas Software utilizando procesadores Soft Core

Tanto en la operación de lectura como en la de escritura se debe generar la señal


wb ack o para indicarle al maestro que la solicitud de comunicación ha sido recibida
y atendida; para esto se implementó el circuito compuesto de las 2 compuertas AND,
una compuerta OR y un FLIP FLOP, este circuito hace que la señal ack sea igual a 1
cuando cualquiera de las señales wb rd o wb wr sea igual a 1 y el estado de la señal
ack sea igual a 0; es decir, cuando el dispositivo pasa del estado no seleccionado a
ser seleccionado para una operación de lectura o escritura.

1.4.2.2. Periférico UART

En la figura ?? se muestra el diagrama de bloques de un periférico un poco más


complejo una UART, su arquitectura se basa en un módulo que implementa las ta-
reas de comunicación que se encuentra descrito en el archivo uart.v; en el archivo
wb uart.v se hace la adaptación de esta unidad funcional al bus wishbone, esta ar-
quitectura permite que el módulo funcional pueda ser conectado a diferentes buses
sin tener que re-escribir todo el código.

wb_stb_i
wb_cyc_i wb_ack_o
wb_stb_i ack
wb_cyc_i wb_wr 1 D
wb_we_i ~ack
wb_adr_i[2] Q
ce
~tx_busy

>
wb_adr_i[2]

wb_dat_i[7:0]
tx_data tx_wr
1
rx_data
rx_ack uart_rxd
D
tx_busy uart_txd
wb_dat_o[7:0] rx_error
0
ce UCR rx_avail

000 00 uart.v
<
wb_stb_i wb_rd
wb_rd wb_cyc_i
~wb_we_i
~ack wb_wr
set
ack

reset
wb_uart.v

Figura 1.30 Ejemplo de periférico wishbone: UART

Del diagrama de bloques de la UART podemos observar que su arquitectura es


similar a la del GPIO, existen los mismos bloques de interconexión con los buses de
datos de entrada y de salida y se utiliza el mismo circuito para generar la señal ack.
1.4 Arquitectura del SoC LM32 35

El circuito simplificado de salida se muestra en la figura ??, se observa que exis-


ten dos valores que pueden ser leı́dos desde el procesador: la señal rx data y los
bits de estado tx , tx error y tx avail; en este caso la lı́nea de dirección wb adr i[2]
selecciona la información que será transmitida al procesador.

wb_rd 1
1

1 wb_stb_i 0
wb_wr 0
1 wb_cyc_i wb_ack_o set
ack ack

reset
0
wb_adr_i[2]

1
rx_data
rx_ack uart_rxd
D
tx_busy uart_txd
wb_dat_o[7:0] rx_error
0
ce UCR rx_avail

000 00 uart.v
<
wb_stb_i
wb_rd wb_cyc_i
~wb_we_i
~ack
wb_uart.v

Figura 1.31 Circuito equivalente de lectura de la UART

wb adr i[3:2]
El circuito de escritura se muestra en la figura ??, en este ejemplo el bus de
datos proveniente del maestro se conecta directamente a la señal tx data ya que
este periférico no permite modificar otros parámetros. Por esta razón el circuito solo
transmite un 1 a la señal uart txd lo que hace que la uart transmita el valor fijado
por la señal tx data.

1.4.2.3. Periférico TIMER

En la figura ?? se muestra el diagrama de bloques resumido del periférico TI-


MER; el cual posee 6 registros que pueden ser modificados y leı́dos por el procesa-
dor. De nuevo la arquitectura de este periférico es similar a los anteriores ası́ como
el circuito de generación de la señal ack
En la figura ?? se muestra el circuito de lectura del periférico timer. La diferecia
frente a los anteriores es la posibilidad de leer 6 diferentes variables; por esta razón
se utilizan tres señales del bus de direcciones wb adr i [5:3].
36 1 Implementación de tareas Software utilizando procesadores Soft Core

1 wb_stb_i
1 wb_cyc_i wb_wr 1 D
1 wb_we_i ~ack
1 1
1 wb_adr_i[2] ce Q
~tx_busy

>
1 wb_stb_i
1 wb_cyc_i wb_ack_o wb_dat_i[7:0]
tx_data tx_wr
ack
wb_rd 0
0
1 uart_txd
wb_wr 1 1
set
ack

0
reset uart.v
wb_uart.v

Figura 1.32 Circuito equivalente de escritura de la UART

wb_dat_i[7:0]

wb_stb_i D wb_stb_i
wb_cyc_i wb_wr wb_cyc_i wb_ack_o
wb_we_i ack
ce Q
~ack

>

wb_adr_i[5:3]

0 trig0 irqen0 ar0 en0 0


1 compare0 1

D
2 counter0 2
3 trig1 irqen1 ar1 en1 3
wb_dat_o[7:0] 4 compare1 4
ce
5 counter1 5

< wb_stb_i wb_rd


wb_rd wb_cyc_i
~wb_we_i
~ack wb_wr
set
ack

reset
wb_timer.v

Figura 1.33 Ejemplo de periférico wishbone: TIMER


1.4 Arquitectura del SoC LM32 37

wb_adr_i[5:3]

0 trig0 irqen0 ar0 en0


wb_stb_i
1 compare0 wb_cyc_i wb_ack_o
2 counter0 ack
D
3 trig1 irqen1 ar1 en1
wb_dat_o[7:0] 4 compare1
ce
5 counter1 wb_rd

< wb_stb_i wb_wr


set
wb_rd wb_cyc_i ack
~wb_we_i
wb_timer.v ~ack reset

Figura 1.34 Circuito equivalente de lectura del periférico TIMER

En la figura ?? se muestra el diagrama de escritura del timer, de forma similar al


circuito de lectura la señal wb adr i [5:3] selecciona el registro que almacenará el
valor proveniente del procesador.

wb_dat_i[7:0]

wb_stb_i D wb_stb_i
wb_cyc_i wb_wr wb_cyc_i wb_ack_o
wb_we_i ack
ce Q
~ack

>

wb_adr_i[5:3]

trig0 irqen0 ar0 en0 0


wb_rd
compare0 1
wb_wr counter0 2
set trig1 irqen1 ar1 en1 3
ack
compare1 4
reset
counter1 5
wb_timer.v

Figura 1.35 Circuito equivalente de escritura del periférico TIMER


38 1 Implementación de tareas Software utilizando procesadores Soft Core

1.4.3. Interfaz Software

En la subsección anterior se hizo una descripción de los diferentes componentes


de la configiración básica del SoC LM32; aquı́, se explicará como controlar desde
un programa en C la comunicación con los periféricos.

1.4.3.1. Estructura de datos del periférico

Para facilitar el acceso a los diferentes registros de un periférico es conveniente


declarar un nuevo tipo de dato que haga una representación de su mapa de memoria.
En la figura ?? se muestra el diagrama de bloques del GPIO y la declaración
del tipo de dato gpio t; el multiplexor que selecciona el sitio donde se almace-
nará el dato proveniente del procesador está controlado por las lı́neas de dirección
wb adr i[3:2] cuando estas señales tengan el valor de 01 o lo que es lo mismo la
dirección termine en 0x04 se seleccionará el registro gpio o; si estas señales tengan
el valor de 10 o lo que es lo mismo la dirección termine en 0x08 se seleccionará el
registro gpio dir. De aquı́ la posición de los elementos write y w dir de la estruc-
tura gpio t; al ser declarada la variable read como uint32 t se reservan cuatro bytes
(0x00, 0x01, 0x02 y 0x03) para almacenar esta variable, la siguiente posición de
memoria (0x04) corresponde a la variable write, la cual es declarada como un tipo
de dato uint32 t por lo que se reservan cuatro bytes para su almacenamiento (0x04,
0x05, 0x06 y 0x07), en la siguiente posición de memoria (0x08) se almacenará la
variable w dir y se reservarán cuatro bytes (0x08, 0x09, 0x0A y 0x0B) para su al-
macenamiento.
Como puede observarse en la figura ??, el contenido del registro gpio input siem-
pre está disponible sin importar el valor de la dirección, lo que indica que el dato
estará disponible siempre que se seleccione el periférico para una operación de lec-
tura, en este caso se colocó en la primera posición de memoria por conveniencia.
Todos los tipos de datos declarados en la estructura gpio t son del tipo volatile.
este tipo de dato le indica al compilador que no realice optimizaciones sobre esta
variable.
En la figura ?? se observa la declaración del tipo de dato uart t y su relación con
el circuito interno de la UART. Aquı́, wb adr i[2] controla el valor que será pasado
al bus de datos del maestro; si wb adr i[2] es 0, se transmite el valor del registro
UCR, si el valor de wb adr i[2] es 1, se transmitirá el valor de la señak rx data. Al
definir la variable ucr al comienzo de la estructura y al asignarle el tipo uint32 t se
reservan los bytes 0x00, 0x01, 0x02 y 0x03 para su almacenamiento; al declarar a
continuación la variable uint32 t rxtx se reservan los siguientes cuatro bytes (0x04,
0x05, 0x06 y 0x07) para su almacenamiento.
Finalmente, en la figura ?? se muestra la declaración del tipo de dato timer t y su
relación con los registros internos del periférico.
1.4 Arquitectura del SoC LM32 39

typedef struct {
volatile uint32_t read; BASE = X X 0 0 0 0
volatile uint32_t write; BASE+4 = X X 0 1 0 0
volatile uint32_t w_dir; BASE+8 = X X 1 0 0 0
} gpio_t;
wb_dat_i[7:0]
D wb_stb_i
wb_stb_i wb_cyc_i wb_ack_o
wb_cyc_i wb_wr
ack
wb_we_i
ce Q
~ack

gpio_inout[7:0]
01 gpio_o
>
10 gpio_dir

wb_adr_i[3:2]

D gpio_input
wb_rd
wb_dat_o[7:0]
ce wb_stb_i
wb_rd wb_cyc_i
wb_wr
~wb_we_i set
ack
< ~ack ~wb_adr_i[3:2]
wb_gpio.v reset

Figura 1.36 Definición de la dirección de los registros internos del GPIO

typedef struct {
volatile uint32_t ucr; BASE =XX000
volatile uint32_t rxtx; BASE + 4 = X X 1 0 0
} uart_t;

wb_adr_i[2]

wb_dat_i[7:0]
tx_data tx_wr
1
rx_data
rx_ack uart_rxd
D
tx_busy uart_txd
wb_dat_o[7:0] rx_error
0
ce UCR rx_avail

000 00 uart.v
<
wb_stb_i
wb_rd wb_cyc_i
~wb_we_i
~ack

Figura 1.37 Definición de la dirección de los registros internos de la UART


40 1 Implementación de tareas Software utilizando procesadores Soft Core

typedef struct {
volatile uint32_t tcr0; BASE =XXX0 00000
volatile uint32_t compare0; BASE + 4 = X X X 0 00100
volatile uint32_t counter0; BASE + 8 = X X X 0 01000
volatile uint32_t tcr1; BASE + 12 = X X X 0 01100
volatile uint32_t compare1; BASE + 16 = X X X 0 10000
volatile uint32_t counter1; BASE + 20 = X X X 0 10100
} timer_t;

wb_adr_i[4:2]

wb_dat_i[7:0]
0 trig0 irqen0 ar0 en0 0 D
1 compare0 1

D
2 counter0 2
Q
ce
3 trig1 irqen1 ar1 en1 3
wb_dat_o[7:0] 4 compare1 4
ce <
5 counter1 5

< wb_timer.v

Figura 1.38 Definición de la dirección de los registros internos del TIMER

1.4.3.2. Dirección de memoria de los periféricos

Una vez creados los tipos de datos que representan los registros internos de los
periféricos se debe asignar un valor a la dirección base de cada uno de ellos, esta
dirección debe ser la misma que le asigna el decodificador de direcciones del árbitro
wishbone. En la figura ?? se muestra el valor que deben tomar estas direcciones.

conbus #(
.s_addr_w(2),

.s0_addr(2'b00), // bram 000 0 0 0 0 0 0 0


.s1_addr(2'b01), // uart 010 0 0 0 0 0 0 0 uart_t *uart0 = (uart_t *) 0x20000000;
.s2_addr(2'b10), // timer 100 0 0 0 0 0 0 0 timer_t *timer0 = (timer_t *) 0x40000000;
.s3_addr(2'b11) // gpio 110 0 0 0 0 0 0 0 gpio_t *gpio0 = (gpio_t *) 0x60000000;
system.v soc-hw.c

Figura 1.39 Asignación de la dirección de memoria a los periféricos

1.5. Programación del SoC LM32

En esta sección se realizará una explicación detallada del flujo de diseño software
que seguirse para implementar aplicaciones en el SoC LM32 (ver figura ??). El
1.5 Programación del SoC LM32 41

desarrollo de aplicaciones en el SoC posee dos componentes: hardware y software,


el componente hardware implementa tareas en forma de periféricos, mientras que
el componente software las implementa como una serie de instrucciones que son
ejecutadas de forma secuencial en el procesador MICO32.

soc-hw.h
Encabezado ELF
.text
.text
.rodata
.rodata
GCC LD .data
crt0ram.S ld -Tlinker.ld -o image \ .data
gcc $(CFLAGS) -c
main.c, soc-hw.c crt0ram.o main.o soc-hw.o .bss
image.srec
crt0ram.o
main.o, soc-hw.o .debug objcopy -j .text -j .rodata \
-j .data -O srec image image.srec
image

(firmware/hw-test/) linker.ld

Maestro 0 Srec2vram
Datos srec2vram image.srec 0 0x1000 > image.ram
BOOT RAM
Maestro 1
.text
(tools/srec2vram//)
Instrucciones Esclavo2
lm32_cpu
.rodata
rtl/lm32
Interrupciones .data
(rtl/wb_bram)
Interconexión Parameter bootram_file = "image.ram"
conbus #(
del bus 0x00000000
.s_addr_w(2),
Wishbone .s0_addr(2'b00), // 0x00000000
Esclavo3 UART .s1_addr(2'b01), // 0x20000000
rtl/wb_conbus .s2_addr(2'b11), // 0x40000000
(rtl/wb_uart) .s3_addr(2'b10) // 0x60000000
0x20000000 )

Esclavo4 TIMER
(rtl/wb_timer)
0x40000000
SoC
system.v

Figura 1.40 Flujo de diseño para el procesador LM32

El SoC LM32 ejecuta las instrucciones que están almacenadas en la memoria


BOOT RAM en la posición de memoria 0x00; para generar las instrucciones que
serán ejecutadas es necesario generar un archivo de inicialización (bootram file =
image.ram) para la BOOT RAM, la herramienta de sı́ntesis lee este archivo de ini-
cialización y al momento de crear el bloque de memoria inicializa el contenido con
dicho archivo.
En la figura ?? se muestra como se genera el archivo image.srec que contine las
instrucciones que forman la aplicación. Todas las aplicaciones software se encuen-
42 1 Implementación de tareas Software utilizando procesadores Soft Core

tran dentro del directorio firmware y existe un subdirectorio para cada aplicación
hw-test para este caso.

1.5.0.3. Compilación

La aplicación hw-test tiene tres archivos fuente: crt0ram.S, soc-hw.c y main.c,


estos archivos deben ser compilados por la herramienta GCC para generar archivos
que contengan la funcionalidad de cada archivo fuente implementado en el lenguaje
del MICO32, estos archivos reciben el nombre de objetos y sus nombre son los
mismos que los archivos fuentes pero con la extensión .o (crt0ram.o main.o soc-
hw.o). Adicionalmente, los objetos suministran información sobre las funciones y
señales globales que se declaran en ellos, esta información recibe el nombre de
sı́mbolo.

1.5.0.4. Enlazado

A continuación se debe generar un archivo ejecutable que integre el código de


los diferentes objetos, esta tarea la realiza el enlazador LD. El enlazador analiza
todos los objetos y crea una lista de sı́mbolos clasificándolos en sı́mbolos resueltos
o no resueltos; un sı́mbolo se considera resuelto cuando se encuentra el código que
lo implementa en cualquiera de los objetos que se van a enlazar, si al finalizar de
enlazar todos los objetos no se resuelve un sı́mbolo el enlazador no podrá generar el
ejecutable y emitirá un mensaje de error.
El archivo ejecutable que se genera utiliza el formato ELF (Executable and Lin-
kable Format) el cuál es un estándar para objetos, librerı́as y ejecutables. Un ejecu-
table ELF está compuesto por diferentes secciones (link view) o segmentos (execu-
tion view). Si un programador está interesado en obtener información de secciones
sobre tablas de sı́mbolos, código ejecutable especı́fico o información de enlazado
dinámico debe utilizar link view. Pero si busca información sobre segmentos, como
por ejemplo, la localización de los segmentos text o data debe utilizar execution
view. Este archivo posee un encabezado que describe el layout del archivo, propor-
cionando información de la forma de acceder a las secciones. Las secciones pueden
almacenar código ejecutable, datos, información de enlazado dinámico, datos de de-
puración, tablas de sı́mbolos,comentarios, tablas de cadenas, y notas. Las secciones
más importantes son:
.bss Datos no inicializados. (RAM)
.comment Información de la versión.
.data y .data1 Datos inicializados. (RAM)
.debug Información para depuración simbólica.
.dynamic Información sobre enlace dinámico
.dynstr Strings necesarios para el enlace dinámico
.dynsym Tabla de sı́mbolos utilizada para enlace dinámico.
1.5 Programación del SoC LM32 43

.fini Código de terminación de proceso.


.init Código de inicialización de proceso.
.line Información de número de lı́nea para depuración simbólica.
.rodata y .rodta1 Datos de solo-lectura (ROM)
.shstrtab Nombres de secciones.
.symtab Tabla de sı́mbolos.
.text Instrucciones ejecutables (ROM)

1.5.0.5. Script de enlazado

El enlazador permite definir donde serán ubicados los diferentes segmentos del
archivo ELF por medio de un archivo de enlace que recibe el nombre de script de
enlazado; de esta forma podemos ajustar el ejecutable a plataformas con diferentes
configuraciones de memoria, lo que brinda un grado mayor de flexibilidad de la ca-
dena de herramientas GNU. El archivo de enlazado en este ejemplo tiene el nombre
de linker.ld y se muestra a continuación.
OUTPUT FORMAT( ” e l f 3 2 −lm32 ” )
ENTRY( s t a r t )
DYNAMIC = 0 ;
RAM START = 0 x00000000 ;
RAM SIZE = 0 x1000 ;
RAM END = RAM START + RAM SIZE ;

MEMORY {
ram : ORIGIN = 0 x00000000 , LENGTH = 0 x1000
/∗ 4k ∗/
}
SECTIONS
{
. text :
{
ftext = .;
∗ ( . t e x t . s t u b . t e x t . ∗ . gnu . l i n k o n c e . t . ∗ )
etext = .;
} > ram
. rodata :
{
. = ALIGN ( 4 ) ;
frodata = .;
∗ ( . r o d a t a . r o d a t a . ∗ . gnu . l i n k o n c e . r . ∗ )
∗(. rodata1 )
erodata = . ;
} > ram
. data :
44 1 Implementación de tareas Software utilizando procesadores Soft Core

{
. = ALIGN ( 4 ) ;
fdata = .;
∗ ( . d a t a . d a t a . ∗ . gnu . l i n k o n c e . d . ∗ )
∗(. data1 )
g p = ALIGN ( 1 6 ) ;
∗ ( . s d a t a . s d a t a . ∗ . gnu . l i n k o n c e . s . ∗ )
edata = . ;
} > ram
. bss :
{
. = ALIGN ( 4 ) ;
fbss = . ;
∗ ( . dynsbss )
∗ ( . s b s s . s b s s . ∗ . gnu . l i n k o n c e . s b . ∗ )
∗ ( . scommon )
∗ ( . dynbss )
∗ ( . b s s . b s s . ∗ . gnu . l i n k o n c e . b . ∗ )
∗ (COMMON)
ebss = . ;
end = . ;
} > ram
}
PROVIDE ( f s t a c k = ORIGIN ( ram ) + LENGTH( ram ) − 4 ) ;
En este archivo se declara la posición inicial de la memoria ( RAM START), su
longitud ( RAM SIZE) y se declaran las diferentes memorias que posee el sistema,
para este caso solo existe la memoria BOOT RAM que recibe el nombre de ram. Las
secciones son declaradas en el orden que serán almacenadas, en la ram iniciando
con la sección text en la posición de memoria 0x00000000; el enlazador coloca todo
el lenguaje de máquina extraido de los objetos generados a partir del código fuente
a partir de esta dirección; a continuación coloca las constantes utilizadas en el códi-
go (como los #define, las cadenas de caracteres a ser impresas) que hacen parte de
la sección rodata; después las variables inicializadas que hacen parte de la sección
data y finalmente las variables sin inicializar de la sección bss. Adicionalmente, este
archivo declara un espacio de memoria llamado fstack, el cual realizará las funcio-
nes de la pila. Al finalizar el proceso de enlazado se genera un archivo ejecutable
llamado image.

1.5.0.6. Utilitarios binarios

El formato ELF no puede ser utilizado directamente para generar la memoria de


programa del procesador; para ello, se debe extraer de este la información corres-
pondiente a las secciones text, rodata y data. Para realizar esta tarea la cadena de
herramientas GNU proporcciona la utilidad objcopy, la cual puede generar un ar-
1.5 Programación del SoC LM32 45

chivo de salida en varios formatos, en este caso se genera el archivo image.srec con
formato S-record.

1.5.0.7. Inicialización de la memoria BRAM

El bloque de memoria bram se utiliza como memoria de programa, memoria


RAM y pila; por este motivo es necesario inicializarla con los datos de la aplicación.
La inicialización de esta memoria se realiza en el archivo rtl/wb bram/wb bram.v:
initial
begin
i f ( m e m f i l e n a m e ! = ” none ” )
begin
$readmemh ( m e m f i l e n a m e , ram ) ;
end
end
Donde el parámetro mem file name es pasado desde el archivo system.v
parameter bootram file = ” . . / f i r m w a r e / hw− t e s t / image . ram ” ,
...
...
wb bram # (
. a d r w i d t h ( 12 ) ,
. mem file name ( b o o t r a m f i l e )
)
El archivo image.ram es un archivo de texto plano donde se inicializan todas las
0x1000 posiciones de memoria, este archivo es generado por la utilidad srec2vram.

1.5.1. Ejemplo de programación

En esta sección se explicará de forma detallada un ejemplo que ayudará a enten-


der como se deben escribir las aplicaciones para este tipo de arquitectura. El código
fuente de este ejemplo se encuentra en el directorio firmware/hw-test y está com-
puesto por los archivos que se muestran en la figura ??.
El proceso de compilación requiere ejecutar una serie de comandos que pueden
llegar a ser un poco engorrosos en la fase de diseño; por esta razón, la comunidad
del software libre creó una aplicación que permite automatizar este proceso, esta
utilidad recibe el nombre de make; make lee los pasos que debe ejecutar para com-
pilar y generar los archivos necesarios para la aplicación desde un script que recibe
el nombre de Makefile.
46 1 Implementación de tareas Software utilizando procesadores Soft Core

soc-hw.h
Encabezado ELF
.text
.text
.rodata
.rodata
GCC LD .data
crt0ram.S ld -Tlinker.ld -o image \ .data
gcc $(CFLAGS) -c
main.c, soc-hw.c crt0ram.o main.o soc-hw.o .bss
image.srec
crt0ram.o
main.o, soc-hw.o .debug objcopy -j .text -j .rodata \
-j .data -O srec image image.srec
image
Srec2vram
srec2vram image.srec 0 0x1000 > image.ram
(firmware/hw-test/) linker.ld
(tools/srec2vram//)

Figura 1.41 Flujo de diseño para el procesador LM32

1.5.1.1. Makefile

En esta subsección se explicará el contenido del archivo Makefile para el ejemplo


bajo estudio; en el siguiente listado se muestra la primera parte del archivo:
LM32 CC=lm32−l i n u x −g c c
LM32 LD=lm32−l i n u x −l d
LM32 OBJCOPY=lm32−l i n u x −o b j c o p y
LM32 OBJDUMP=lm32−l i n u x −objdump

SREC2VRAM ?= . . / . . / t o o l s / s r e c 2 v r a m / s r e c 2 v r a m
VRAMFILE= image . ram

CFLAGS=−MMD −O2 −Wall −g −s −f o m i t −frame −p o i n t e r −m b a r r e l −s h i f t −e n a b l e d


−m m u l t i p l y −e n a b l e d −m d i v i d e −e n a b l e d −msign−e x t e n d −e n a b l e d
LDFLAGS=− n o s t d l i b − n o d e f a u l t l i b s −T l i n k e r . l d
SEGMENTS = − j . t e x t − j . r o d a t a − j . d a t a
En este segmento del archivo Makefile se declaran variables locales que serán
utilizadas posteriormente. Es costumbre crear una variable global con el nombre de
los ejecutables de: el compilador (LM32 CC), el enlazador (LM32 LD), manipu-
lador de binarios (LM32 OBJCOPY) y utilidad para generar el listado del código
en assembler del ejecutable (LM32 OBJDUMP); esto con el fı́n de que el mismo
Makefile pueda ser usado con diferentes cadena de herramientas.
Adicionalmente, se define el sitio del ejecutable srec2vram que como se men-
cionó anteriormente genera el archivo de inicialización de la memoria BRAM; se
define el nombre de la imagen que contiene la memoria de programa (srec2vram).
Las variables CFLAGS y LDFLAGS son parámetros que se pasan al compilador y
al enlazador respectivamente; -Tlinker.ld, le indica al enlazador que utilice el archivo
linker.ld para definir la distribución de memoria del ejecutable.
1.5 Programación del SoC LM32 47

En la siguiente sección del archivo Makefile se realizan las tareas necesarias para
generar el archivo image.ram
a l l : image . s r e c $ (VRAMFILE)

crt0ram . o : crt0ram . S
$ ( LM32 CC ) $ ( CFLAGS ) −c c r t 0 r a m . S

main . o : main . c
$ ( LM32 CC ) $ ( CFLAGS ) −c main . c

soc−hw . o : soc−hw . c
$ ( LM32 CC ) $ ( CFLAGS ) −c soc−hw . c

image : c r t 0 r a m . o main . o soc−hw . o


$ ( LM32 LD ) $ (LDFLAGS) −Map image . map −N −o image c r t 0 r a m . o main . o soc−hw

image . l s t : image
$ ( LM32 OBJDUMP ) −h −S $< > $@

image . s r e c : image . l s t
$ ( LM32 OBJCOPY ) $ (SEGMENTS) −O s r e c image image . s r e c

$ (VRAMFILE ) : image . s r e c
$ (SREC2VRAM) image . s r e c 0 x00000000 0 x1000 > $ (VRAMFILE)

clean :
rm −f image image . l s t image . b i n image . s r e c image . map
∗. o ∗. d
Cada cadena de caracteres que termina con dos puntos : es un posible parámetro
que puede ser pasado a la herramienta make, la que se encarga de leer el Make-
file y ejecutar las operaciones en él asignadas. Al ejecutar el comando make sin
parámetros este busca en el directorio donde fué ejecutado un archivo con el nom-
bre Makefile o makefile y ejecutará lo que encuentre en la etiqueta all; los nombres
que aparecen después de los dos puntos, son dependencias que se deben ejecutar
antes de realizar las acciones propias; en este caso se deben buscar las etiquetas
image.srec y $(VRAMFILE) y ejecutarlas.
Para ejecutar image.srec es necesario ejecutar antes image.lst, la cual a su vez re-
quiere que se ejecute image, este tipo de encadenamientos son tı́picos en estos archi-
vos y son necesarios para seguir el flujo de compilación. Para ejecutar las acciones
de image es necesario ejecutar crt0ram.o, main.o y soc-hw.o las cuales compilan el
código fuente crt0ram.S, main.c y soc-hw.c para generar los objetos correspondien-
tes; una vez creados los objetos se pueden ejecutar la acción de image: $(LM32 LD)
$(LDFLAGS) -Map image.map -N -o image crt0ram.o main.o soc-hw.o, este co-
mando llama al enlazador para que cree el ejecutable image a partir de los objetos
48 1 Implementación de tareas Software utilizando procesadores Soft Core

crt0ram.o, main.o y soc-hw.o. A continuación se ejecutarán las operaciones de ima-


ge.lst, seguidas por las de image.srec y $(VRAMFILE.
El siguiente es el resultado de ejecutar el comando make:
lm32−l i n u x −g c c −MMD −O2 −Wall −g −s −f o m i t −frame −p o i n t e r −m b a r r e l −s h i f t −e n a b l e d −
lm32−l i n u x −g c c −MMD −O2 −Wall −g −s −f o m i t −frame −p o i n t e r −m b a r r e l −s h i f t −e n a b l e d −
lm32−l i n u x −g c c −MMD −O2 −Wall −g −s −f o m i t −frame −p o i n t e r −m b a r r e l −s h i f t −e n a b l e d −
lm32−l i n u x −l d − n o s t d l i b − n o d e f a u l t l i b s −T l i n k e r . l d −Map image . map −N −o image c
lm32−l i n u x −objdump −h −S image > image . l s t
lm32−l i n u x −o b j c o p y − j . t e x t − j . r o d a t a − j . d a t a −O s r e c image image . s r e c
. . / . . / t o o l s / s r e c 2 v r a m / s r e c 2 v r a m image . s r e c 0 x00000000 0 x1000 > image . ram
E x t r a c t i n g [ 0 0 0 0 0 0 0 0 , 0 0 0 0 1 0 0 0 ) ( s i z e =0 x1000 )

S-ar putea să vă placă și