找回密码
 注册
关于网站域名变更的通知
查看: 596|回复: 2
打印 上一主题 下一主题

linux学习之路_or1200第一个裸机程序(上)

[复制链接]
  • TA的每日心情

    2019-11-20 15:22
  • 签到天数: 2 天

    [LV.1]初来乍到

    跳转到指定楼层
    1#
    发表于 2021-4-22 17:38 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式

    EDA365欢迎您登录!

    您需要 登录 才可以下载或查看,没有帐号?注册

    x
    5 r7 U! Y) a0 v- z+ ?
    经过前面千辛万苦、爬山涉水、纠结了好久才弄好的环境,现在我们终于可以把FPGA当成个单片机使了,稍微比单片机猛一点,但是绝对比单片机贵一个数量级,FPGA现在能完成由CPU做的事,我们就把它当成一个大价钱买回来的单片机,不过相比于S3C24XX的片子来说,那么贵的一个FPGA板卡跑40M的一个CPU绝对的奢侈,不过仅限于理解和学习嵌入式的过程,管他的。( ~- @' G9 e$ g: ^! F0 c
            但是如果是这样呢,把or1200资源优化好,例化两个或者多个CPU,是不是可以做成SMP架构?又或者说现有的产品上有FPGA芯片,但是里面的资源还足以容得下一个or1200,是不是可以做个协处理器在里面?我想应该可以做的事情还很多的······吧。% z) j2 R1 Y3 N

    + r( w* K* B' Q        按照惯例,硬件代码第一个无非是点亮LED灯;软件代码无非是写个hello world,有同感木有。, K. L1 w. T7 U' u
    7 F4 q! X4 B9 D2 M4 x$ n
            但是想想,我们现在的SOC只有什么?CPU、RAM、Debug、UART、BUS,我们没有把GPIO外设加进去,点个灯貌似行不通了,但是能操作的毕竟外设蛮多的,但是又想看到有现象输出的,那我们只好选择去写个UART的代码咯。5 j+ ]( k# u$ ?5 N% d) D* D- n
    5 B9 U" M3 k# F
            我们在后面完善or1200_basic_soc时把其他外设添加进去后,上了linux系统后,在编写GPIO字符驱动时再去把灯点亮吧,现在我们就当FPGA是个单片机去编写所谓的“裸机”程序。" a, D7 J7 u4 V: X

      U# l) o+ K, J/ q3 Z% ?        我们现在手头上有两本书对吧,《开源软核处理器OPENRISC的SOPC设计》、《CPU源代码分析与芯片设计及Linux移植》,那我们就先看看接下来要写的UART16550外设的说明文档先咯,uart16550_latest.tar.gz源码包里的doc文件夹有该ipcore的说明文档,其实这就是相当于看芯片手册一样了,是不是很熟悉,那就不继续吐槽了,或者《开源软核处理器OPENRISC的SOPC设计》里面第7章也有UART16550的中文翻译版本,感觉这本书就是opencores上面大堆文档的翻译大杂烩,算了,自己写不出的书还是不吐槽人家的了。
    4 d0 ?# `! f. P% m" `6 O& \  [6 a. e/ X+ f6 `& m
            大概浏览一下UART16550的结构后我们就来咯,敲代码。1 p- h# [: {5 x

    . o+ t3 |1 l5 _/ ~& J        在敲代码之前先搞清楚一些流程,当内嵌在FPGA的or1200上电复位时,它在做些什么,在硬件环境搭建时,我们把CPU的复位地址设置为0x100,这个地址可以在or1200_defines.v中任意指定的,什么是0x100呢,我们在CPU体系手册《openrisc-arch-1.0-rev0.pdf》中16章操作系统接口有所描述,0x100是复位后取指令的第一个地址,虽然是这样说,但是复位地址可以是任意指定的,以后我们固化代码在Flash时,会将这个复位地址设置为Flash的起始地址,目前我们先指定在0x100,所以,CPU复位时就会从0x100取出第一条执行的指令,OK。
    + ~, y+ G. s" C/ K% Q1 p7 \3 P  G4 ~2 s- f  ?+ w' ^# t# q
    & y' \- o6 T% B" N* |& t& `

    4 _: N3 X' b& T+ F- `        对于其他向量地址来说,是CPU设计时规定好的异常向量地址,好比0x100是复位异常向量,我们在写C51时熟悉的对比一下看还有什么?0x500是否熟悉?只是时钟中断异常向量。还有什么咧?0x800,外中断异常向量,所以,当我们在CPU将计数器中断enable后,当时钟计满中断时,就会跳转到0x500执行指令。对于外中断而言,前面我们在修改or1200_defines.v把PIC的中断个数设置为20个,所以,当这20个外中断enable后,中断源触发后,CPU就跳转到0x800地址来执行指令啦,至于其他的异常向量都是类似的,可能以前我们接触得比较少,但在对于上了操作系统后,这每个异常向量都会有对应的处理过程。现在对于or1200_basic_soc来说,只使用了复位、时钟、中断异常向量。
    # Y& T; y, i1 B, i
    ' J2 b- K, {* w; ]& G        所以我们敲代码的第一步就是编写这些异常向量的处理程序,即异常向量表,因为刚上电时处理器的C环境还没有建立好,所以这些代码都是要用汇编去写的,我们现在至少要用到reset复位异常向量,硬着头皮码吧。
    0 y  a7 L: |+ M2 N4 _* c; R6 N. @5 Z
    6 K) Z" x  e& Q# A- r        我理解的reset复位异常向量是通常说的启动代码,在用Keil MDK创建S3C2440的工程时也有对应的启动代码文件s3c2440.S,看起来蛮多的,1k多行,但是往下看,800多行的宏定义,那启动代码就200多行了。再来吐槽下or1200在linux系统下的关于启动代码的文件entry.S、head.S,这两个文件加起来2k5多行,那我们手动去敲那么多代码么,开玩笑吧。( N& y2 n# x% w3 K
    9 L2 g4 r6 b( ]+ W1 S
            好在我们现在用到的外设不多,就一个UART,而且我们现在还不需要用到时钟计数,还没需要到中断,所以复位向量用到的代码大概只有50行,50的汇编对于我这种级别        来说简直小菜一碟,好,码字。* \1 U8 d* D6 @- N/ V% ?

    7 p+ Z/ j/ x2 ^* H        至于这个简单的UART代码我都打包好在附件中了,那我们简单分析一下这50多行reset向量需要的代码。9 u, a2 q; n1 t- F. n7 w
    & z% W2 x4 j9 r; P+ y& |; _
            把工程都扔进sourceinsight,方便查看嘛,打开reset.S,对于前面的宏定义先跳过,用到的时候再说吧。
    + x- E  R: H& Y7 i$ L# k6 }  J8 W
    , U, L" y2 i, x9 ^, j* w7 a+ s) R) x% j2 M6 f) Z
            先看0x100的异常向量,3行代码,是不是很有成就感。
    * I6 {# H/ N. q+ @6 P2 F  h& `1 p# _9 u: K+ S) D* s9 L3 F

    $ f- q& q# J7 w4 Y
    : P+ k) r/ m% g; {! m# v8 g3 s- ]
    , `9 N! ~1 I/ e6 m6 j* p0 w) u        LOAD_SYMBOL_2_GPR看名字就知道意思,作用是将标号的地址存入CPU的寄存器。! x# i$ k- s- c
    ; g* k5 v  _7 z, [0 \0 N7 y1 T

    - L: c) J" x: O+ d) d5 d8 y) r& F: V+ y6 m  G
    : V$ m% `3 ]. M( \; f
            18行l.movhi是or1200处理器的逻辑指令,作用是把立即数零拓展左移16bit
    ( W, k" Z- I/ K) p* S/ ^* p) m  C6 j8 h# c
    l.movhi rD,K2 p: Y# p, e4 @. j9 [- Y. U
    ! V/ I8 p( J2 Y( a; {. N# U$ ]% s
    rD[31:0] < -extz(Immediate) < < 161 H5 `. {# s+ w
    $ {9 D1 G! a: w! J8 A( l! e6 Y
            所以18行将symbol的高16位存入gpr中。
    - u6 t6 ^- B. [  U& v
    2 e0 n4 b+ Z+ B- K# G1 ^0 _        l.or是or1200处理器的逻辑指令按位或,作用不用讲了吧,' g; I9 @2 z# U& s% I, ~- X9 k7 x0 T

    6 B: b3 M% E* A1 S( J  Xl.or rD,rA,rB
    ' ~" _. j3 x9 j; l# S2 I% `8 }" I( C( `$ r$ p0 L% A$ `# L
    rD[31:0] < -rA[31:0] OR rB[31:0]
    1 e$ ]* v" Y  |% D' Y3 I- {& P6 d- P. N# f2 b
            所以19行将symbol的低16位存入gpr中。
    0 w. z8 x6 H" H+ G2 M! {. E' U" J8 i& K4 p( A
            继续reset向量,l.jr r3,即跳转到r3寄存器的地址上,所以reset向量前3行就是跳转到_reset标号地址上。  O7 Q1 v. o8 u$ ^8 q+ z# O

    ! M. u) \! b% L  f( [+ b        貌似55行还有个l.nop指令,只是or1200的空操作指令。跳转指令之后紧接着的指令称为延时槽,在or1200体系中,在跳转指令之后的紧接着的一条指令是规定、必须、肯定会执行的,所以在现在or1200的gcc编译器中,在跳转指令之后都会自动补上一个nop指令,貌似在新版本的编译器会在跳转指令后执行别的有效指令代替nop操作,提高效率。
    - Q; M" V1 Y' d* ]3 R5 k3 J7 x% |" g0 q. j, M; w, E" t; H' _
            好,continue······  k4 W  Q% x2 v1 v% u
    : y: Q4 b$ p1 w4 ^& B1 U
    % t; i  D1 H0 Z/ A* G3 Q
            Sourceinsight中一跳,转到_reset标号上
    7 G; q0 O2 h% N
    0 k5 z# E& u8 C  s( p. e
    6 T" G8 B: O* k  b, k) R. Z: S
    ! L* C7 v4 r) Y9 u* n8 p: b
    9 U2 @! \  p0 |: l2 v& J* A5 @        l.movhi,l.ori很熟悉啊,目的将r0寄存器清零。6 j/ _/ e- [. V* A' U$ W. j2 Q
    4 K0 y% {9 \! H( ^2 R# e
            l.addi作用是将r0和SPR_SR_SM相加后存入r3中,至于SPR_SR_SM又是什么?跟踪下就可以发现在spr-defs.h有其定义。% W  V% }2 A! M9 G5 V4 u; V

      o5 k! T" u. E! V
    + i  v% E# R) \( t
    * b5 r$ I  x( L4 K/ c1 V$ W7 ?+ {4 s5 C7 h
            这是supervisor模式,这个linux的内核态和用户态概念很像,or1200存在两种模式,supervisor和user,区别是操作CPU内部寄存器组时权限不同。6 `1 L$ n+ M) [. T* K8 s
    , u: K7 A& l" F" l' [$ _6 e) {
    继续,l.mtspr就是操作or1200内特殊寄存器组的专用指令,专用的,将r0和r3的寄存器的值相加后送入SPR_SR寄存器内
    + }* j1 c  o8 R! g5 ~
    / o, k/ ]* g9 I/ D- j
    : C% d8 W6 q* z! z' p8 ^% m6 M+ J+ h+ j/ [! n6 r3 T5 K% @/ {
    & |  G  p0 W: H7 n5 Q
            在《openrisc-arch-1.0-rev0.pdf》中可以看到,SPR_SR的first bit就是SM位# A' K  o& T, ~
    8 n7 Y# d8 p" K7 q
            为1时就是Supervisor Mode
    * j* Z/ X! H# \! H1 _. d1 t, h8 |/ U2 W6 M% Z- @6 N. N
            继续往下看,有个CLEAR_GPR的宏,目的是将gpr设置成r0的值
    2 P2 V$ ^2 s7 m: |3 d" w% Y0 J0 }# D6 e5 E
    6 Q5 ]- r7 w, S
    % g6 Q) C# W' s; f3 C$ ?5 R3 }

    : I" V) n  z+ e4 D$ H0 W        在or1200中,r0被设置成常数0,做与这个宏的作用就是清零
    & s" K2 t' U  Y2 v. ~9 F. _4 R3 a8 G6 P# _: `0 m4 _$ |) r. J( R
    好,继续
    7 t3 G; P1 K0 X) e9 Y6 _/ {$ }- s' n) o. d- z; |8 w

    ; \: O' b0 o* c- a5 |" e5 v
    2 s7 N+ |6 e/ m' `
    ) I4 t7 w+ f0 B: \* Q& O7 \* c: D% F4 M1 c
            SPR_TTMR是关于or1200内Timer的寄存器,将r0的值赋入SPR_TTMR中,至于SPR_TTMR的解释如下表。
    : S" ^7 b& F/ V3 H8 V9 N9 C) H% x6 t1 ^# u$ t

      d* w. F' F, [9 d# d: M+ H& t5 L
    ! K. J$ B( Q2 C3 a/ F/ L: \+ k5 j7 b  C/ Z
    " E. C9 `) K( K6 T" F2 L5 p        目的就是把执行计数单元(简称定时器吧)的中断和使能都禁用了。$ ~. f1 |. k$ y" [- ~  t

    2 I2 B6 Y- O* j# i- [8 E/ |2 R        好,继续······貌似完了,200行吧_stack的值赋给r1,_stack是CPU堆栈的值,_stack这个值在lds文件内定义,这个文件稍后再解释。
    1 n$ A. k( t) U& m/ N- i  c0 P' q# s5 k+ O
            第203,204行是不是很熟悉,跳转到main函数执行,堆栈建立好之后下面main函数就可以用C来码了。
    - s% e  k: c3 Q2 F9 E
    7 \' W+ j9 d: T$ y        Reset向量就需要这些代码了,至于reset.S剩下的代码是关于中断处理的,比如在
    & ^+ M$ Z% w' Q1 y/ ?! H( T3 m- N! m) L& R$ e4 D- v
    $ ?" j$ H% a! J
    5 z( h' B, f* w1 c  l4 y

    * d+ d1 O7 d3 a4 H        在定时器中断和外中断会先执行EXCEPTION_HANDLER宏,目的是执行中断处理之前的现场保护,跳转至中断服务函数,执行完中断服务函数之后的现场恢复,中断的概念不用多解释了,应该是很熟悉的了。7 ?1 }  z! R+ a% z* ^
    2 _) T, A; s# E( Q; c
            关于or1200中断的流程可以参考《CPU源代码分析与芯片设计及Linux移植》,里面讲得蛮详细的,虽然大部分也是翻译手册的,至于附件的代码关于reset.S剩余部分不是特别难,就不解释了,不解释咯······
    - ?! _5 Q2 s! h" ?5 ~0 R. ]% C8 q1 @" |
    5 W: Y9 \1 g1 N1 Z$ g2 q+ w        突然发现,reset的异常向量我们就敲完了,在reset向量里会跳转至main函数,那就开始了,main函数熟悉了吧,9 K9 t- o" s" x! ^/ G: d: W

    : l1 t* q( C, T* p, D2 E0 I) \1 o        附件中的uart.c和uart.h关于UART16550的串口驱动和头文件,程序不难,对比着uart16550_latest.tar.gz的说明文档、《开源软核处理器OPENRISC的SOPC设计》和源码的注释5分钟就可以看懂了,不详细说了,因为大家都有C语言功底嘛。
      z5 }1 B* E9 ?2 y3 q# W2 r: j* x2 ]0 u  O) G
             至于附件driver和include的其他文件可以先不管,因为现在还用不上,有兴趣可以自行翻阅下,是关于中断二级向量表的实现和timer的驱动,大部分都是参考u-boot下openrisc架构修修改改过来的,所以最好还是稍微浏览下,然后在下次移植u-boot时能快速上手。! H2 |5 l8 E" h4 n

    * w, x- X. s1 ?7 v" x        Main函数更不用多说了,手痒自己随意打的,随便修改吧,至于里面的函数用sourceinsight追一下就可以了,都是比较简单的东西了。* d7 i. I2 e8 o& Q( T0 f
    7 p: K# y4 Z- D* r: T
            UART的代码到这里我们就敲完了,最后就是把程序下到我们的SDRAM中运行了,好,在下到SDRAM之前呢,还需要编写一个连接脚本文件,告诉编译器编译出来的可执行程序各个段的存储位置,入口地址,在学校学汇编和计算机原理的时候都应该讲过这些的了,就是编译源码后得到的可执行bin文件的在RAM的存储位置,例如在link.lds文件中: }& ?% f2 I7 s0 Y; N) H( i

    ; e( ~2 @2 x+ t- `9 m8 {7 A0 y ) b; I; |) V. K' {

    * ^  r* m- x1 _, [, [* X
    1 g! ^: f- x1 W        vectors段,即我们之前编写的异常向量表的存放位置,从0x0开始到0x2000,除or1200结构本身定义的异常向量表之外,还保留空的异常向量允许自定义操作。+ d6 a. Y/ }- \" `# ]. t! m

    ! ?3 f2 U, K2 Z' _" `, e1 F
    2 m9 r6 Y1 Y: T1 I5 O        ram段就是开始存放我们uart的程序代码啦,继续看······; @# B9 b6 f# Z+ _9 q7 g
    ) G: ~; C: M) L" M
    0 \  W5 J0 i' f
    1 ^8 Q; h- W2 u; a
    . J" g. e& k' I- ^
            这里定义了一个_min_stack的变量,大小为8K,记得我们在写reset向量时设置CPU堆栈的值吗?# t" `9 F/ g6 ^9 |' @: |
    6 O& r9 A2 G: @9 I- q
    / ^3 L& R5 H+ N
    ' C. K0 @, R! a/ o* r: W7 J

    % d7 m3 a5 [4 `. J9 y! q! |, E- u        在这里_stack的值在这里取出,这里我说的不是很明白,关于lds连接文件附件中的ARM79出品-u-boot移植手册.pdf的附录一有详细说明,参考参考很容易就明白啦,或者可以google一下,再或者看看别人的技术博客就行了,这里不再详细解释了,因为码字是在是很累人的一件事。
    * D! l5 Q% b, U" v
    3 P: s( d7 r2 R* ]# v, `$ n2 e% j5 P1 }, ?" m
            一切OK准备就绪,但是在linux环境下编译我们还要写一个makefile,哇天啊,好吧,认栽吧,写吧。, ~; I: ~+ @5 U; r
    9 ~, V0 @% d0 o( C! Z: J4 D" d( z
            ······
    ( z8 L3 @% H: Q; c8 H
    & I- X3 U' I# h! `         现在可以打开virtualbox,先浏览一下放在桌面的那些文件,比如: W- [4 m- Y/ G% l0 I1 m

    4 D* Y8 Q& L, m , |/ X0 P( j6 m+ @

    9 J' t% W( s0 P
    * \/ Z- {- i3 T$ ?: ?        可大概看看这个环境可以做些什么东西。
    : B, O8 M4 x6 e! y/ u6 y
    8 o$ N8 f" ^5 r3 u
    ) A3 A2 u, G5 C" L" K& l        然后打开home folder,点啊点,点啊点
    - N. _6 W- J1 V' O% r+ |5 h+ a+ O. j  t- d$ b$ o: D

    8 h& _, S8 h$ a( l5 G+ k" o* C1 _* m* w4 t* O6 Z0 h) Y( f4 k

    7 t3 j% z' k" L( k+ @: g        在orpmon文件夹中包含一个opencores社区针对or1200做的bootloader,而且,我在写makefile的时候都是参考这个bootloader的makefile写的,至于怎么去写一个makefile,参考《跟我一起写 Makefile.pdf》和《GNU+makefile中文手册.pdf》这两个手册咯。, {7 U' c! Z6 _5 L0 w

    该用户从未签到

    2#
    发表于 2021-4-22 18:31 | 只看该作者
    or1200第一个裸机程序(上)

    该用户从未签到

    3#
    发表于 2021-4-23 18:06 | 只看该作者
    现在我们终于可以把FPGA当成个单片机使了,稍微比单片机猛一点,但是绝对比单片机贵一个数量级,FPGA现在能完成由CPU做的事$ V+ G4 \: J+ l5 E7 {
    您需要登录后才可以回帖 登录 | 注册

    本版积分规则

    关闭

    推荐内容上一条 /1 下一条

    EDA365公众号

    关于我们|手机版|EDA365电子论坛网 ( 粤ICP备18020198号-1 )

    GMT+8, 2025-11-24 13:22 , Processed in 0.187500 second(s), 26 queries , Gzip On.

    深圳市墨知创新科技有限公司

    地址:深圳市南山区科技生态园2栋A座805 电话:19926409050

    快速回复 返回顶部 返回列表