Documente Academic
Documente Profesional
Documente Cultură
Springer
Capı́tulo 1
Implementación de tareas Software utilizando
procesadores Soft Core
1.1. Introducción
1
2 1 Implementación de tareas Software utilizando procesadores Soft Core
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
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.
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.
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]
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
x_result
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]
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
x_result
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
1.3.2. Saltos
1.3.2.1. Condicionales
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
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
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
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
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
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
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
branch_taken_m
operand_w
load_data_w
1x 00 01 w_result_sel_load_w
w_result_sel_mul_w
w_result
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
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
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
branch_taken_m
operand_w
load_data_w
1x 00 01 w_result_sel_load_w
w_result_sel_mul_w
w_result
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
*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
*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);
data32++;
*data32 = 0x31313131; *data32 = 0x40403030;
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
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
Time
D_ADR_O[31:0]=00000304 00000204
4200 ns
00000300
4300 ns 4400 ns
00000304
4500 ns
00000404
4600 ns
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
1.3.4. Interrupciones
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)
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
x_result
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
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)
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
x_result
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
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
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
branch_taken_m
operand_w
load_data_w
1x 00 01 w_result_sel_load_w
w_result_sel_mul_w
w_result
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
31 26 20 16
wcsr 25 21 15 0
csr = gpr[RX]
31 26 20 16
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]
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_overflow_x
adder_result_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
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
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
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
branch_taken_m
operand_w
load_data_w
1x 00 01 w_result_sel_load_w
w_result_sel_mul_w
w_result
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 )
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
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
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
m0_cyc_i, m1_cyc_i
s0_stb_0 slave_sel[0]
gnt[1]
s0_dat_i
s0_stb_1 slave_sel[1]
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]
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
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
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
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
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
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
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
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
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 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
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]
D
2 counter0 2
3 trig1 irqen1 ar1 en1 3
wb_dat_o[7:0] 4 compare1 4
ce
5 counter1 5
reset
wb_timer.v
wb_adr_i[5:3]
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]
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
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
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
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),
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
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
tran dentro del directorio firmware y existe un subdirectorio para cada aplicación
hw-test para este caso.
1.5.0.3. Compilación
1.5.0.4. 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.
chivo de salida en varios formatos, en este caso se genera el archivo image.srec con
formato S-record.
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//)
1.5.1.1. Makefile
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
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 . 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