Posted on Leave a comment

Notes on RISC-V Assembly Language Programming – Part 2

25 January 2025

Reviewing yesterday’s notes, I should add that the use of the supplied value ‘_eusrstack’ from the MRS2 linker script is only necessary until such time as I replace it with my own linker script. This will happen in due time.

Examining the processor’s registers, not the peripheral registers, can be enabled while debugging by enabling ‘Disassembly view’. Right-click in the editor area and select ‘Open Disassembly View’. On the right-hand side of the Disassembly view is the ‘Variables’ section, with Local, Global, Static and Registers as collapsed lists. Expand the ‘Registers’ list to see the processor register contents, which also includes the program counter (pc) and CSRs. Double-click on a register in the listing and you can edit the value in real time. Handy!

Now I never claimed to be a CSR expert but there are some CSRs in this list that I have yet to meet, e.g., ‘vcsr’ and ‘seed’. A check for the latest QingKeV4 Microprocessor Manual on the WCH web site gives me V1.3, which is later than the V1.2 I already had.

A quick reminder to myself: The CH32X035 is based on the QingKe V4C core, with RV32IMAC instruction set with some vendor-proprietary extensions. It supports ‘machine’ and ‘user’ mode privilege levels, but not ‘supervisor’. Changing modes is briefly described in Section 1.3, p. 2. I have yet to have a compelling reason to enter ‘user’ mode. One day.

Note: I happened to notice this when I first started debugging my test program and saw that the sp register was 0x20002800. Once it had actually executed the instruction I had specified, the sp was 0x20005000, as would be expected. I don’t know where this value came from.

There are several taxonomic schemes involved with the naming and addressing of the CSRs. It’s all very fascinating and something I will need to understand more fully in the near future. I don’t actually need them for my immediate goals today, which are:

1.  Configure system clock for 48 MHz
2.  Output system clock on MCO, PB9
3.  Configure GPIO ports
4.  Blink LED(s)
5.  Configure USART
6.  Fake reset signal using PB4 or 'Download' push button.

Beyond that I will need to extract the register definitions from the SVD, as the novelty of looking up all the addresses and bit positions will have worn thin.

I also see that the RST signal is an alternate function of PB7.

In other news, the i8 battery is still running! The i8 is a small, wireless mouse and keyboard with a rechargeable battery that I am evaluating for this project.

So step 1 involves writing to a couple of registers in the RCC peripheral, so I will need to look up the base address in the RM, Section 1.2 Memory Map, Figure 1-2 Storage image, which is an excellent reference for all the base addresses of peripherals on the chip.

RCC starts at address 0x40021000, so I will load that indirectly into one of the pointer registers, even though I can use any of the registers for this. But which one to use? I had been using the ‘thread pointer’, or tp register, also known as x4. I have also used the global pointer, gp/x3 and frame pointer fp/s0/x8. But which one is the right one? Does it even matter? It turns out that there are documents that cover these questions. A proposal for an embedded application binary interface (EABI) is published here:

https://github.com/riscvarchive/riscv-eabi-spec/blob/master/EABI.adoc

In the meantime, since it absolutely does not matter, I will use t0/x5 as the base address for accessing peripherals.

Interestingly, the compiler has no problem using a single lui instruction to load the t0 register with a constant value (the base address of the RCC block) when I used the ‘la’ load address pseudo-instruction, where it wanted to use the auipc/mv combination for the stack pointer initialization. Also, the sp showed up initially as 0x20002800 again. Weird.

Oddly, the Register list shows some values in hexadecimal and some in decimal. I’m not finding a control to allow me to tell it which one I want. The sp is showing in hex but t0 is in decimal, 1073876992, which is valid but unintuitive.

And it seems the ‘Disassembly view’ is not required to be up to see the register values.

So to know which bits to flip in the RCC registers to get the results I want, I spent some quality time reading the RM. The CH32X035 has a vastly streamlined clock system when compared to any of the other devices on offer. It has one clock source, in internally generated RC oscillator, HSI, that operates at 48 MHz. There are no other options. There is no support for external oscillators (HSE) of any sort. There are no PLLs. There is also no support for any low speed oscillators, internal or external.

The HSI is characterized at the factory and a fixed adjustment value is burned in somewhere. The device loads this value and programs the HSICALC field of the RCC_CTLR automatically. The frequency can be further trimmed using the HSITRIM field of the RCC_CTLR.

There is a clock prescaler called the HB clock source prescaler, but it applies to the entire system and not just the HB bus. The default setting is /6 so the system is running at 8 MHz on boot. The only other option in the RCC_CFGR0 register is the selection of MCO output signal. The two choices are SYSCLK (4) or HSI (5).

So to select 48 MHz system clock, I need to write a zero into the HPRE field. To select system clock as the MCO output, I need to write a 4 into the MCO field.

To do that, I need to define the needed bits in their proper positions and just write that to the RCC_CFGR0.

RCC_MCO_SYS = (4 << 24)
RCC_MCO_HSI = (5 << 24)

The ’24’ is the bit position of the beginning of the bit field within the RCC_CTLR.

Writing just that value to RCC_CFGR0 will effectively also set the HPRE to zero, which is what I want.

I will also have to define the offset of the RCC_CFGR0 register from the base address.

It seems to run, but I will have to swap items 2 & 3 if I really want to see the MCO on PB9, as it is, by default, an input.

So I define the base addresses of both GPIOA and GPIOB, then define the offsets to the CFGLR and CFGHR configuration registers.

And then I got stuck for several hours. Odd things were happening, and none of them involved a square wave signal of any kind on the MCO.

First, the Register view may or may not update in real time. I created another control project just to enable the MCO output and see if that works. Yes, it works.

Along the way I see that there is only one speed setting available for the GPIO pins and that is 50 MHz. I don’t know what that means.

Next, I found that things worked better when I did not try to set the system clock to 48 MHz. Leaving it at the boot default of 8 MHz makes everybody happy.

So now I have to read the flash memory section and see how to turn on the prefetch buffer, which is mentioned in the HPRE field settings as a footnote, but that phrase ‘prefetch buffer’ is not to be found elsewhere in the RM. I think it might be referring to the wait-state setting for flash access.

Yes, the FLASH_ACTLR register contains a single field, LATENCY. It suggests 0 wait states for HCLK <= 12 MHz, 1 wait state for 12 MHz < HCLK <= 24 MHz and 2 wait states for 24 MHz < HCLK <= 48 MHz. So to run successfully at 48 MHz, we need to set the LATENCY field to 0x02 in the FLASH_ACTLR.

So now we have a very rounded wave of ~47.76 MHz coming out of the MCO. The LED is blinking pretty quickly, as well, but stepping it in debug mode shows it going on and off as expected. So that’s the first 4 steps accomplished.

To set up the USART, we need to enable its peripheral clock first, then configure it in the normal manner. This particular peripheral is not hard to set up for very basic communication.

So that’s 5 of six goals so far today. Since the sixth and final goal as previously outlined involves setting up interrupts and who knows what else, I will postpone it until tomorrow. However, at that point, I might decide that it would be more productive to proceed with the distillation of the SVD and not have to do the tedious transcription of addresses and bit maps by hand.

Leave a Reply