本章介绍如何添加一个能被 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 来向mode
和leds_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 文档用浏览器打开后如下:
可以看到,我们为 led 灯设置的两个 CSR 的地址分别为0xf003800
和0xf003804
,都为 32bits。
四、上板测试
如果没有上面的报错的话,应该是可以通过执行自动生成的用户空间的 API 来读写这两个寄存器,下面是 LiteX 自动生成的 API,位于编译目录add_verilog_test
的software/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
- 设置为三个灯亮:
devmem 0xf0003804 32 0x7
至此,我们就完成了通过添加可以由 CPU 读写的 CSR 寄存器,来控制自定义的 Verilog 模块。
没有回复内容