lowRISC / lowrisc-chip

The root repo for lowRISC project and FPGA demos.
http://www.lowrisc.org/
Other
596 stars 148 forks source link

Defining the boot process for bare metal? #86

Open sherrbc1 opened 6 years ago

sherrbc1 commented 6 years ago

Per the 1.91 Privileged Specification, I found that the behavior of the PC on reset is to be set to "an implementation-defined" default value (see section 3.3). What does this mean for the lowRISC chip?

I checked out the latest source code and built the Hello UART example. Closer inspection of the ELF header revealed the entry point to be 0x40000000 (supported by the linker script for release 0.5).

I went back an earlier release (0.2) and the entry point of the same hello baremetal program was 0x100 (supported by the linker script).

jrrk commented 6 years ago

The boot.c/hello.c procedure mentioned earlier is not quite the first code executed. The Rocket hardware description contains an extra piece of code called ROMSlave, the sole purpose of which is to jump from the reset vector to the start of the BRAM. Its source is located in uncore/src/main/scala/rom.scala

The actual reset vector is built into the Chisel code. The code that sets it is in makeBootROM() which is in src/main/scala/LowRISCChip.scala, together with the hardwired instructions that boot either to BRAM (for FPGA) or direct to DDR (for ISA tests)

The hardwired instructions are calculated based on the ResetVector which is configured in src/main/scala/Configs.scala. It would probably be a bad idea to try to change this from the default of 0, unless you are experienced in Chisel.

The purpose of BRAM is to allow the FPGA to boot standalone when DDR memory has undefined contents. Clearly it is not possible to rely on the startup contents of DDR as it is volatile. On the other hand the BRAM is initialised from the FPGA configuration bitstream. It could be used to emulate FLASH but in this case it is convenient to make it writable. There is no direct equivalent in mask programmed systems. It would have to be replaced by an embedded flash or fixed ROM, and the writable data portion of the C program copied to DDR at startup.

If you are downloading code from an external computer then it could go direct to DDR. Otherwise the boot.c in BRAM does the copying from the network or local SD-card memory.

In the ethernet-v0.5 release we have gone away from a fixed memory map, because it is convenient to have addresses automatically generated in Chisel. This means you won't receive a memory map until after customising your peripherals (if wanted) and following the build instructions. This ensures correct alignment of start addresses to prevent crossing address boundaries.

After building, the memory map appears in fpga/board/nexys4_ddr/generated-src/Nexys4Config.cfg and fpga/board/nexys4_ddr/generated-src/dev_map.h which can automatically be used in C-programs. Likewise dev_map.vh is automatically used in necessary Verilog modules.

The Boot ROM serves a secondary purpose of storing the start address of peripherals for use by Linux. This avoids the need for a BIOS to discover these addresses. Linux is not booted directly, it goes via Berkeley boot loader which sets up the machine mode state (including handlers for unaligned fetches/stores and emulated instruction traps) and then launches Linux in supervisor mode. The boot.c mentioned above takes a boot.bin either from the SDcard or downloaded via the Ethernet interface, which consists of BBL with Linux appended as a payload.

On 26/02/18 22:57, sherrbc1 wrote:

Per the 1.91 Privileged Specification https://people.eecs.berkeley.edu/%7Ekrste/papers/riscv-privileged-v1.9.1.pdf, I found that the behavior of the PC on reset is to be set to "an implementation-defined" default value (see section 3.3). What does this mean for the lowRISC chip?

I checked out the latest source code and built the Hello https://github.com/lowRISC/lowrisc-fpga/blob/2691865d1a6692f731bb9969dc1d146385d32964/bare_metal/examples/hello.c UART example. Closer inspection of the ELF header revealed the entry point to be 0x40000000 (supported by the linker script https://github.com/lowRISC/lowrisc-fpga/blob/2691865d1a6692f731bb9969dc1d146385d32964/bare_metal/driver/test.ld#L24 for release 0.5).

I went back an earlier release (0.2) and the entry point of the same hello baremetal program was 0x100 (supported by the linker script https://github.com/lowRISC/lowrisc-nexys4/blob/4e4b0d3d049737654b599edd10200778c4c9e111/driver/test.ld#L24).

  • What is the PC set to on reset?
  • Is there a fixed ROM loader that jumps to these payload entry points? If so, where is the source?
  • Where can I find the latest system memory map for release 0.5? Is this http://www.lowrisc.org/docs/untether-v0.2/bootload/ still accurate?
  • What is the purpose of BRAM when we have DDR DRAM? Could we not just load code there? In a production system, would BRAM actually be something like Flash?

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/lowRISC/lowrisc-chip/issues/86, or mute the thread https://github.com/notifications/unsubscribe-auth/AAgF1-lRRI1Hdhytmj9HTjPVvOmJVe6Xks5tYzbygaJpZM4SUEdr.

noureddine-as commented 4 years ago

Dear Jonathan, I would like to ask relatively the same question, but for the refresh-v0.6 version.

From the top-level module chip_top.sv, I saw that the io_reset_vector is exposed, and is determined by the input dip switches (on Nexys 4 DDR) as follows:

   always @*
     begin
        casez (i_dip[1:0])
          2'b?0: io_reset_vector = 32'h40000000;
          2'b01: io_reset_vector = 32'h80000000;
          2'b11: io_reset_vector = 32'h80200000;
        endcase // casez ()
     end

So my guess is that, in version refresh-v0.6, the execution will actually start directly at one of those addresses, and that the BootROM included in Rocket-Chip (in subsystem/Configs.scala) is never executed, right?

Moreover, the bootrom.S included in Rocket-Chip by default is supposed to jump to DRAM_BASE, while in the beginning there is nothing valid there, so this keeps my assumption valid.

So in summary, in a typical scenario, where boot starts at 0x40000000 and a bare-metal application is loaded to DDR using GDB: the reset vector is picked at the very beginning (typically 0x40000000), this will set the PC towards the BRAM, where the bootloader (boot.c) resides. Starting the execution by initializing the processor and all the necessary peripherals, print the logo, then choose again where to go next depending on the same dip switches (for example SW0=1 --> GDB loading). And to load the application using GDB, the dip switch SW0 should be set to '1' (which also sets io_reset_vector to 0x80000000), so once OpenOCD and GDB are launched and the application is loaded, it resumes execution from the DDR memory.

The second scenario when we want to load the application from the SD card to the DDR memory. In this case, the bootloader should be executed first, so SW0 should be set to 0 i.e. io_reset_vector is set to 0x40000000. But then, in the second step, for the application to be loaded from the SD card, SW1 should be set to 1, the reset vector will still be pointing at 0x40000000, but it is not needed since there will be no restart, and the execution will resume in the DDR memory by a simple mret instruction call.

Is my interpretation correct? Thank you very much in advance.

Best regards,

jrrk commented 4 years ago

The bottom inside the rocket is still used to provide memory map information. There is no need to adjust the reset vector for gdb use, it automatically sets the PC to the entry address. The ability to adjust the address is primarily for simulation use, for example ISA tests. For real FPGA use, both switches should be low constantly.

Sent from my iPhone

On 27 Dec 2019, at 16:24, Noureddine Ait Said notifications@github.com wrote:

 Dear Jonathan, I would like to ask relatively the same question, but for the refresh-v0.6 version.

From the top-level module chip_top.sv, I saw that the io_reset_vector is exposed, and is determined by the input dip switches (on Nexys 4 DDR) as follows:

always @* begin casez (i_dip[1:0]) 2'b?0: io_reset_vector = 32'h40000000; 2'b01: io_reset_vector = 32'h80000000; 2'b11: io_reset_vector = 32'h80200000; endcase // casez () end So my guess is that, in version refresh-v0.6, the execution will actually start directly at one of those addresses, and that the BootROM included in Rocket-Chip (in subsystem/Configs.scala) is never executed, right?

Moreover, the bootrom.S included in Rocket-Chip by default is supposed to jump to DRAM_BASE, while in the beginning there is nothing valid there, so this keeps my assumption valid.

So in summary, in a typical scenario, where boot starts at 0x40000000 and a bare-metal application is loaded to DDR using GDB: the reset vector is picked at the very beginning (typically 0x40000000), this will set the PC towards the BRAM, where the bootloader (boot.c) resides. Starting the execution by initializing the processor and all the necessary peripherals, print the logo, then choose again where to go next depending on the same dip switches (for example SW0=1 --> GDB loading). And to load the application using GDB, the dip switch SW0 should be set to '1' (which also sets io_reset_vector to 0x80000000), so once OpenOCD and GDB are launched and the application is loaded, it resumes execution from the DDR memory.

The second scenario when we want to load the application from the SD card to the DDR memory. In this case, the bootloader should be executed first, so SW0 should be set to 0 i.e. io_reset_vector is set to 0x40000000. But then, in the second step, for the application to be loaded from the SD card, SW1 should be set to 1, the reset vector will still be pointing at 0x40000000, but it is not needed since there will be no restart, and the execution will resume in the DDR memory by a simple mret instruction call.

Is my interpretation correct? Thank you very much in advance.

Best regards,

— You are receiving this because you commented. Reply to this email directly, view it on GitHub, or unsubscribe.

noureddine-as commented 4 years ago

Okay, thank you for the clarification

jrrk commented 4 years ago

See below

Sent from my iPhone

On 26 Feb 2018, at 22:58, sherrbc1 notifications@github.com wrote:

 Per the 1.91 Privileged Specification, I found that the behavior of the PC on reset is to be set to "an implementation-defined" default value (see section 3.3). What does this mean for the lowRISC chip?

By default Rocket hangs waiting for the ISA tests on startup. Your choices are for sane FPGA behaviour to modify this ROM (at 0x10000) or change the boot vector. The latter option maintains a degree of compatibility with ISA tests. I checked out the latest source code and built the Hello UART example. Closer inspection of the ELF header revealed the entry point to be 0x40000000 (supported by the linker script for release 0.5).

0x40000000 is in peripheral memory, allowing us to substitute our own boot code. For this release it is in fpga/baremetal/example I went back an earlier release (0.2) and the entry point of the same hello baremetal program was 0x100 (supported by the linker script).

We had to keep adapting our style as the Rocket code evolved. I wasn’t involved in these earlier releases, and others who were are no longer active. What is the PC set to on reset? Is there a fixed ROM loader that jumps to these payload entry points? If so, where is the source? The ROM loader is in the rocket-chip/bootrom directory for v0.5, just bootrom for earlier release. Where can I find the latest system memory map for release 0.5? Is this still accurate? I’m sorry we are reorganising our website just now, you can check out old versions of the docs from the LowRISC-site repo. What is the purpose of BRAM when we have DDR DRAM? Could we not just load code there? In a production system, would BRAM actually be something like Flash? The whole issue of DDR is latency and it’s contents is undefined at power up. So you cannot boot from it, unless you use gdb. The boot ram would be mask Rom or flash in a production system (flash more or less doubles the number of mask layers).Rocket has its own boot Rom, by default it just hangs waiting for the debugger connection. It is inconvenient to rebuild the whole of Rocket from Chisel to FPGA to upgrade the boot software. — You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub, or mute the thread.