使用LiteX快速创建FPGA SoC工程(9)-LiteX社区-FPGA CPLD-ChipDebug

使用LiteX快速创建FPGA SoC工程(9)

本章介绍如何添加一个能被 CPU 通过寄存器来控制的自定义组件。

一、项目介绍

假设我们有一个流水灯的 Verilog 代码,如下:

module my_leds(
    input clock,
    input reset,
    input mode, // mode == 0: leds blink, mode == 1: leds cotrol by CPU
    input [3:0] leds_in, // if mode == 1, leds_in is the value of leds
    output reg [3:0] leds//there is 4 leds on my boards
);

localparam COUNT=100_000_000;//the freq of clock is 100MHz
reg led_mode;
//set the mode of leds
always @(posedge clock)begin
    if(reset)
        led_mode <= 1'b0;
    else
        led_mode <= mode;
end

//leds control
reg [31:0] cnt;
always @(posedge clock)begin
    if(reset)
        leds <= 4'b1111;
    else if(led_mode)begin
        leds <= leds_in;
    end
    else begin
        if(cnt==COUNT)begin
            leds <= ~leds;
        end
    end
end

// count will not be control by the mode
always @(posedge clock)begin
    if(reset)
        cnt <= 4'b1111;
    else if(cnt==COUNT)begin
        cnt <= 0;
    end
    else begin
        cnt <= cnt + 1;
    end
end
endmodule

 

该流水灯新增了mode端口和leds_in端口,这两个端口用于接受 CPU 的控制信号,当:

  • mode==1时,输出的leds的值由 CPU 的leds_in决定;
  • mode==0时,输出的leds的值由计数器来翻转;
  • 计数器本身不会受到mode的控制;

我们的目的是,通过 CPU 来向modeleds_in传输值,以控制该 Verilog 模块。这基本上可以对应于实际应用中的一些非常简单的 IP 在 SoC 中的应用。

二、修改封装模块

我们需要创建两个能直接被 CPU 访问的 CSR,然后将这两个 CSR 连接到 Verilog 模块的端口。这需要使用一个由LiteX提供的类CSRStorage,其定义如下:

class CSRStorage(_CompoundCSR):
    """Control Register.

    The ``CSRStorage`` class provides a memory location that can be read and written by the CPU, and read and optionally written by the design.

    It can span several CSR addresses.

    Parameters
    ----------
    size : int
        CSR寄存器的位宽(以bit计),可以比总线的数据位宽大;默认为1bit;

    reset : string
        寄存器的复位值,默认为`0`;

    reset_less : bool
        如果为`True`,则不会为该寄存器生成复位逻辑;默认值为`False`;

    atomic_write : bool
        提供CPU的原子写操作;当启用时,当启用时,如果寄存器位宽需要多次操作才能填充满,那么当最后一部分地址的数据没有写入之前,其他的数据会被暂时保存在一个back-buffer中,直到所有数据写入完成后才会向该CSR一次性写入;默认值为`False`;

    write_from_dev : bool
         允许除了CPU之外的逻辑写改寄存器;注意,这种情况下,不保证CPU的读操作是原子的;默认值为`False`;

    name : string
        该控制状态寄存器的名字;默认为None;

    Attributes
    ----------
    storage : Signal(size), out
        该信号是CSR的值的输出信号

    re : Signal(), in
        The strobe signal indicating a write to the ``CSRStorage`` register from the CPU. It is active
        for one cycle, after or during a write from the bus.
        该信号表示CPU向该CSR写入了值,在总线的写操作期间或者之后,有效一个时钟周期;即如果CPU写了该CSR,则该输入信号会有效一个时钟周期,我们可以通过该信号判断CPU的写操作是否发生;

    we : Signal(), out
        当`write_from_dev==True`时,该信号表示CPU之外的其他逻辑的写信号;不启用则该信号无效;

    dat_w : Signal(), out
        当`write_from_dev==True`时,CPU之外的其他逻辑的写数据;不启用则该信号无效;
    """

    def __init__(self, size=1, reset=0, reset_less=False, fields=[], atomic_write=False, write_from_dev=False, name=None, description=None, n=None):

因此,我们直接修改 led verilog 代码的 Python 封装类,直接贴代码如下:

import os
from migen import *
from migen.fhdl import verilog
from random import randrange
from litex.gen import *
from litex.gen.genlib.misc import WaitTimer
from litex.soc.interconnect.csr import *

class my_leds(LiteXModule):
    def __init__(self, pads, clk=None, rst=None):
        #self.pads = pads
        self._leds_cpu_mode = CSRStorage(1, description="Led Mode Control.") #复位值默认为0
        self._leds_cpu_value = CSRStorage(len(pads), description="Led CPU Value Control.") #复位值默认为0
        self.leds = Signal(len(pads))

        self.specials += [
            Instance("my_leds",
                        i_clock = clk,
                        i_reset = rst,
                        i_mode = self._leds_cpu_mode.storage,
                        i_leds_in = self._leds_cpu_value.storage,
                        o_leds = self.leds,
                    )
        ] #Instance.of = "myleds",;

        self.comb += pads.eq(self.leds)

然后执行编译,发生报错,如下:

/usr/lib/riscv64-unknown-elf/bin/ld: cmd_bios.o: in function `leds_handler':
/home/xlg/wrk/litex/litex/soc/software/bios/cmds/cmd_bios.c:177: undefined reference to `leds_out_write'
collect2: error: ld returned 1 exit status
make[1]: *** [/home/xlg/wrk/litex/litex/soc/software/bios/Makefile:72:bios.elf] 错误 1
rm crt0.o
make[1]: 离开目录“/home/xlg/wrk/litex-boards/litex_boards/targets/add_verilog_test/software/bios”
Traceback (most recent call last):
  File "/home/xlg/wrk/litex-boards/litex_boards/targets/./alinx_ax7z100_target_linux.py", line 165, in <module>
    main()
  File "/home/xlg/wrk/litex-boards/litex_boards/targets/./alinx_ax7z100_target_linux.py", line 158, in main
    builder.build(**vivado_build_argdict(args))
  File "/home/xlg/wrk/litex/litex/soc/integration/builder.py", line 357, in build
    self._generate_rom_software(compile_bios=use_bios)
  File "/home/xlg/wrk/litex/litex/soc/integration/builder.py", line 291, in _generate_rom_software
    subprocess.check_call(["make", "-C", dst_dir, "-f", makefile])
  File "/home/xlg/miniforge3/lib/python3.10/subprocess.py", line 369, in check_call
    raise CalledProcessError(retcode, cmd)
subprocess.CalledProcessError: Command '['make', '-C', '/home/xlg/wrk/litex-boards/litex_boards/targets/add_verilog_test/software/bios', '-f', '/home/xlg/wrk/litex/litex/soc/software/bios/Makefile']' returned non-zero exit status 2.
make: *** [Makefile:5:vexriscv_smp_test] 错误 1

 

貌似是软件编译报错,我们打开报错的文件,我的报错文件为litex/soc/software/bios/cmds/cmd_bios.c的第 182 行,将报错的函数 leds_out_wire 进行屏蔽,然后再进行编译即可,目前不太清楚为什么会报错

#ifdef CSR_LEDS_BASE
static void leds_handler(int nb_params, char **params)
{
	char *c;
	unsigned int value;

	if (nb_params < 1) {
		printf("leds <value>");
		return;
	}

	value = strtoul(params[0], &c, 0);
	if (*c != 0) {
		printf("Incorrect value");
		return;
	}

	printf("Settings Leds to 0x%x", value);
	//leds_out_write(value);
}

define_command(leds, leds_handler, "Set Leds value", SYSTEM_CMDS);
#endif

 

三、确定 CSR 地址

如果想要在 Linux 系统中读写某个寄存器,需要知道其地址。因此,我们在编译命令中还使用了--doc选项来生成对应的文档,该选项需要安装 Python 包:

pip3 install sphinxcontrib-wavedrom sphinx

然后就能在编译根目录下找到doc/_build/html文件夹,打开后其中就包含了各个组件的地址定义和 html 文件,以我们定义的 my_leds 为例,其生成的 html 文档用浏览器打开后如下:

图片[1]-使用LiteX快速创建FPGA SoC工程(9)-LiteX社区-FPGA CPLD-ChipDebug图片[2]-使用LiteX快速创建FPGA SoC工程(9)-LiteX社区-FPGA CPLD-ChipDebug

可以看到,我们为 led 灯设置的两个 CSR 的地址分别为0xf0038000xf003804,都为 32bits。

四、上板测试

如果没有上面的报错的话,应该是可以通过执行自动生成的用户空间的 API 来读写这两个寄存器,下面是 LiteX 自动生成的 API,位于编译目录add_verilog_testsoftware/include/generated/csr.h中:

/* leds */
#define CSR_LEDS_BASE (CSR_BASE + 0x3800L)
#define CSR_LEDS_LEDS_CPU_MODE_ADDR (CSR_BASE + 0x3800L)
#define CSR_LEDS_LEDS_CPU_MODE_SIZE 1
static inline uint32_t leds_leds_cpu_mode_read(void) {
	return csr_read_simple((CSR_BASE + 0x3800L));
}
static inline void leds_leds_cpu_mode_write(uint32_t v) {
	csr_write_simple(v, (CSR_BASE + 0x3800L));
}
#define CSR_LEDS_LEDS_CPU_VALUE_ADDR (CSR_BASE + 0x3804L)
#define CSR_LEDS_LEDS_CPU_VALUE_SIZE 1
static inline uint32_t leds_leds_cpu_value_read(void) {
	return csr_read_simple((CSR_BASE + 0x3804L));
}
static inline void leds_leds_cpu_value_write(uint32_t v) {
	csr_write_simple(v, (CSR_BASE + 0x3804L));
}

 

可以看出,其为mode寄存器和value寄存器都生成了读写 API。不过由于上面的报错,这些 API 不清楚是否能正常使用。

为了简单起见,我们使用 Linux 系统中的devmem工具直接对内存的指定地址读写,使用方法如下:

devmem 0x40200000 32  //读取该地址的值

devmem 0x40200000 32 0x12345678  //写入该地址0x12345678

 

通过串口下载 image 并启动 LInux 系统后,可以观察到 led 灯默认是由计数器控制的闪烁模式,首先我们将模式改为由 CPU 控制,执行:

devmem 0xf0003800 32  0x1 //向寄存器mode写1,将模式设置为由CPU控制

设置为 2 个灯亮:

devmem 0xf0003804 32  0x3

图片[3]-使用LiteX快速创建FPGA SoC工程(9)-LiteX社区-FPGA CPLD-ChipDebug

  • 设置为三个灯亮:
    devmem 0xf0003804 32  0x7

图片[4]-使用LiteX快速创建FPGA SoC工程(9)-LiteX社区-FPGA CPLD-ChipDebug

至此,我们就完成了通过添加可以由 CPU 读写的 CSR 寄存器,来控制自定义的 Verilog 模块。

 

请登录后发表评论

    没有回复内容