概述
Lattice IP Packager 是一个图形工具,用来把 IP 核的所有代码、配置和资源整理成标准 IP 包,让 Radiant 和 Propel 能直接调用使用。本文档的目的是介绍 Lattice IP Packager 2025.2,帮助你快速上手,并创建一个自定义 IP 模块的软件包。
操作教程
打开软件Lattice Propel Builder 2025.2.1里面点击Tools->IP Packager,如下所示

打开文件夹File->Open IP Directory,选择工程文件存放的文件夹,然后界面如下:

- Meta Data
- Design File
- Doc
上面三个为必配置项,
- Test Bench
- Constraint
- Misc
上面三个为可配置项,配置Basic Info信息如下:

新增一个自定义的GPIO输出的例子
新增gpio的位宽Param配置参数如下:

加载自定义的参数,verilog的接口如下:
c
module apb_gpio #(
parameter DATA_WIDTH = 32 // GPIO width: 1~32
)(
// ==========================test===========================
// System signals
// =====================================================
input wire clk_i,
input wire resetn_i,
// =====================================================
// APB interface (FIXED 32-bit standard)
// =====================================================
input wire [31:0] PADDR,
input wire PSEL,
input wire PENABLE,
input wire PWRITE,
input wire [31:0] PWDATA,
output reg [31:0] PRDATA,
output wire PREADY,
output wire PSLVERR,
// =====================================================
// Interrupt
// =====================================================
output reg IRQ,
// =====================================================
// GPIO interface (VARIABLE WIDTH)
// =====================================================
inout wire [DATA_WIDTH-1:0] gpio_io
);
然后配置gpio_io的属性参数,这样新增IP核的时候,自动根据配置的宏参数,生成对应的代码:

然后新增APB的接口,和中断的管脚:

在上传Design Files文件的时候,注意要修改文件名重新上传才能生效,否则生成的rtl文件里面的.v文件不变
化:

代码如下:
c
module apb_gpio #(
parameter DATA_WIDTH = 8
)(
input wire clk_i,
input wire resetn_i,
input wire [31:0] PADDR,
input wire PSEL,
input wire PENABLE,
input wire PWRITE,
input wire [31:0] PWDATA,
output reg [31:0] PRDATA,
output wire PREADY,
output wire PSLVERR,
output reg IRQ,
inout wire [DATA_WIDTH-1:0] gpio_io
);
// =====================================================
// Registers
// =====================================================
reg [DATA_WIDTH-1:0] gpio_out_reg;
reg [DATA_WIDTH-1:0] gpio_dir_reg;
reg [DATA_WIDTH-1:0] reg_int_en;
reg [DATA_WIDTH-1:0] reg_int_type; // 1=edge, 0=level
reg [DATA_WIDTH-1:0] reg_int_pol; // 1=rising/high, 0=falling/low
reg [DATA_WIDTH-1:0] reg_int_sts;
wire [DATA_WIDTH-1:0] gpio_in;
// =====================================================
// GPIO tri-state (FIXED: per-bit control)
// =====================================================
genvar i;
generate
for (i = 0; i < DATA_WIDTH; i = i + 1) begin
assign gpio_io[i] = gpio_dir_reg[i] ? gpio_out_reg[i] : 1'bz;
assign gpio_in[i] = gpio_io[i];
end
endgenerate
// =====================================================
// Synchronizer (2FF)
// =====================================================
reg [DATA_WIDTH-1:0] sync0, sync1, sync2;
always @(posedge clk_i or negedge resetn_i) begin
if (!resetn_i) begin
sync0 <= 0;
sync1 <= 0;
sync2 <= 0;
end else begin
sync0 <= gpio_in;
sync1 <= sync0;
sync2 <= sync1;
end
end
wire [DATA_WIDTH-1:0] gpio_curr = sync1;
wire [DATA_WIDTH-1:0] gpio_prev = sync2;
// =====================================================
// Edge detect
// =====================================================
wire [DATA_WIDTH-1:0] rising_edge = gpio_curr & ~gpio_prev;
wire [DATA_WIDTH-1:0] falling_edge = ~gpio_curr & gpio_prev;
wire [DATA_WIDTH-1:0] edge_event =
(reg_int_pol ? rising_edge : falling_edge);
// =====================================================
// Level detect
// =====================================================
wire [DATA_WIDTH-1:0] level_event =
(reg_int_pol ? gpio_curr : ~gpio_curr);
// =====================================================
// Select mode
// =====================================================
wire [DATA_WIDTH-1:0] int_event =
reg_int_type ? edge_event : level_event;
wire [DATA_WIDTH-1:0] int_active = int_event & reg_int_en;
// =====================================================
// APB ready
// =====================================================
assign PREADY = 1'b1;
assign PSLVERR = 1'b0;
wire apb_write = PSEL && PENABLE && PWRITE;
wire apb_read = PSEL && !PWRITE;
// =====================================================
// Write registers
// =====================================================
always @(posedge clk_i or negedge resetn_i) begin
if (!resetn_i) begin
gpio_out_reg <= 0;
gpio_dir_reg <= 0;
reg_int_en <= 0;
reg_int_type <= 0;
reg_int_pol <= 0;
reg_int_sts <= 0;
end else begin
if (apb_write) begin
case (PADDR[7:0])
8'h00: gpio_out_reg <= PWDATA[DATA_WIDTH-1:0];
8'h04: gpio_dir_reg <= PWDATA[DATA_WIDTH-1:0];
8'h08: reg_int_en <= PWDATA[DATA_WIDTH-1:0];
8'h0C: reg_int_type <= PWDATA[DATA_WIDTH-1:0];
8'h10: reg_int_pol <= PWDATA[DATA_WIDTH-1:0];
// W1C interrupt status
8'h14: reg_int_sts <= reg_int_sts & ~PWDATA[DATA_WIDTH-1:0];
default: ;
endcase
end
// latch interrupt
reg_int_sts <= reg_int_sts | int_active;
end
end
// =====================================================
// Read registers
// =====================================================
always @(*) begin
case (PADDR[7:0])
8'h00: PRDATA = {{(32-DATA_WIDTH){1'b0}}, gpio_out_reg};
8'h04: PRDATA = {{(32-DATA_WIDTH){1'b0}}, gpio_dir_reg};
8'h08: PRDATA = {{(32-DATA_WIDTH){1'b0}}, gpio_in};
8'h0C: PRDATA = {{(32-DATA_WIDTH){1'b0}}, reg_int_type};
8'h10: PRDATA = {{(32-DATA_WIDTH){1'b0}}, reg_int_pol};
8'h14: PRDATA = {{(32-DATA_WIDTH){1'b0}}, reg_int_sts};
default: PRDATA = 32'h0;
endcase
end
// =====================================================
// IRQ output
// =====================================================
always @(posedge clk_i or negedge resetn_i) begin
if (!resetn_i)
IRQ <= 1'b0;
else
IRQ <= |reg_int_sts;
end
endmodule
在下面的界面新增驱动文件:

驱动文件如下:
c
#include "gpio_drv.h"
/* =========================
* init
* ========================= */
void gpio_init(apb_gpio_t *gpio, uint32_t base_addr, uint32_t width)
{
gpio->base = (uint32_t *)base_addr;
gpio->width = width;
gpio->base[GPIO_REG_OUT/4] = 0;
gpio->base[GPIO_REG_DIR/4] = 0;
}
/* =========================
* GPIO output
* ========================= */
void gpio_write(apb_gpio_t *gpio, uint32_t value)
{
gpio->base[GPIO_REG_OUT/4] = value;
}
uint32_t gpio_read_out(apb_gpio_t *gpio)
{
return gpio->base[GPIO_REG_OUT/4];
}
/* =========================
* GPIO input
* ========================= */
uint32_t gpio_read_in(apb_gpio_t *gpio)
{
return gpio->base[GPIO_REG_IN/4];
}
/* =========================
* direction
* 1 = output
* 0 = input
* ========================= */
void gpio_set_dir(apb_gpio_t *gpio, uint32_t dir)
{
gpio->base[GPIO_REG_DIR/4] = dir;
}
/* =========================
* interrupt enable
* ========================= */
void gpio_int_enable(apb_gpio_t *gpio, uint32_t mask)
{
gpio->base[GPIO_REG_INT_EN/4] |= mask;
}
void gpio_int_disable(apb_gpio_t *gpio, uint32_t mask)
{
gpio->base[GPIO_REG_INT_EN/4] &= ~mask;
}
/* =========================
* interrupt mode
* ========================= */
void gpio_int_set_type(apb_gpio_t *gpio, uint32_t mask)
{
gpio->base[GPIO_REG_INT_TYPE/4] |= mask; // edge
}
void gpio_int_set_level(apb_gpio_t *gpio, uint32_t mask)
{
gpio->base[GPIO_REG_INT_TYPE/4] &= ~mask; // level
}
/* rising/falling */
void gpio_int_set_rising(apb_gpio_t *gpio, uint32_t mask)
{
gpio->base[GPIO_REG_INT_POL/4] |= mask;
}
void gpio_int_set_falling(apb_gpio_t *gpio, uint32_t mask)
{
gpio->base[GPIO_REG_INT_POL/4] &= ~mask;
}
/* =========================
* status
* ========================= */
uint32_t gpio_int_get_status(apb_gpio_t *gpio)
{
return gpio->base[GPIO_REG_INT_STS/4];
}
/* =========================
* clear (W1C)
* ========================= */
void gpio_int_clear(apb_gpio_t *gpio, uint32_t mask)
{
gpio->base[GPIO_REG_INT_STS/4] = mask;
}
驱动头文件:
c
#ifndef _APB_GPIO_DRV_H_
#define _APB_GPIO_DRV_H_
#include <stdint.h>
/* =========================
* Register Offsets
* ========================= */
#define GPIO_REG_OUT 0x00
#define GPIO_REG_DIR 0x04
#define GPIO_REG_IN 0x08
#define GPIO_REG_INT_EN 0x0C
#define GPIO_REG_INT_TYPE 0x10
#define GPIO_REG_INT_POL 0x14
#define GPIO_REG_INT_STS 0x18
/* =========================
* GPIO Driver Handle
* ========================= */
typedef struct {
volatile uint32_t *base;
uint32_t width;
} apb_gpio_t;
/* =========================
* Basic IO
* ========================= */
void gpio_init(apb_gpio_t *gpio, uint32_t base_addr, uint32_t width);
/* output */
void gpio_write(apb_gpio_t *gpio, uint32_t value);
uint32_t gpio_read_out(apb_gpio_t *gpio);
/* input */
uint32_t gpio_read_in(apb_gpio_t *gpio);
/* direction */
void gpio_set_dir(apb_gpio_t *gpio, uint32_t dir);
/* =========================
* Interrupt control
* ========================= */
void gpio_int_enable(apb_gpio_t *gpio, uint32_t mask);
void gpio_int_disable(apb_gpio_t *gpio, uint32_t mask);
void gpio_int_set_type(apb_gpio_t *gpio, uint32_t mask); // 1=edge
void gpio_int_set_level(apb_gpio_t *gpio, uint32_t mask); // 0=level
void gpio_int_set_rising(apb_gpio_t *gpio, uint32_t mask);
void gpio_int_set_falling(apb_gpio_t *gpio, uint32_t mask);
/* status */
uint32_t gpio_int_get_status(apb_gpio_t *gpio);
/* clear (W1C) */
void gpio_int_clear(apb_gpio_t *gpio, uint32_t mask);
#endif
配置完成后,点击IP Preview按钮后,出现如下界面,测试修改GPIO width的值,发现左侧的Diagram u_gpio_inst 的预览gpio接口随之变化如下:

随之弹出文件夹,重点关注rtl文件夹里面的u_gpio_inst.v文件是否是自己上传的.v文件。
然后在Lattice Propel Builder软件里New Soc Design里面新建工程,具体的细节参考lattice propel的使用例子前面的这篇文章,主要注意Schematic电路配置如下:

注意锁住Address的参数:

然后从Propel 软件里打开Run Radiant ,执行编译,注意物理管教的限制文件,例如uart管脚核LED的四个输出管脚如下:

具体的下载方法见博文Lattice Radiant第一个跑马灯例子和lattice propel的使用例子,下面开始编写应用代码跑马灯如下:
c
#include "hal.h"
#include "utils.h"
#include <stdio.h>
#include "sys_platform.h"
#include <riscv_errors.h>
#include "platform_init.h"
#include "device_info.h"
#include "terminal.h"
#include "gpio_drv.h"
#if REG_TEST_ENABLE
#include "reg_test.h"
#endif
#ifdef LED_GPIO_INST_BASE_ADDR
#include "gpio.h"
#define LED_COUNT 8
struct gpio_instance led_gpio_inst;
#else
#define LED_COUNT 8
#endif
static int bsp_init(void)
{
int ret = 0;
ret = terminal_init(IRQ_BAREMETAL_DEFAULT);
#if CPU_INST_0_PIC_ENABLE
// initialize PIC. for MC/SM/NANO
pic_init(CPU_INST_0_PICTIMER_START_ADDR);
#endif
#if CPU_INST_0_PLIC_EN
// initialize PLIC. for RX
plic_init();
#endif
#if CPU_INST_0_PLIC_EN || CPU_INST_0_CLINT_EN
trap_init();
#endif
#if REG_TEST_ENABLE
/* Used for register access test, If not sure how to use, disable it. */
reg_test_assert(mem_access_test());
printf("\nmem_access_test success!\n\n");
#endif
#ifdef LED_GPIO_INST_BASE_ADDR
// initialize LED GPIO
led_gpio_inst.instance_name = LED_GPIO_INST_NAME;
gpio_init(&led_gpio_inst, LED_GPIO_INST_BASE_ADDR, LED_GPIO_INST_LINES_NUM, LED_GPIO_INST_GPIO_DIRS);
#endif
#ifdef RISCV_RX_DRV_VER
/*If driver support, set global interrupt-enable bit to 1.*/
#if CPU_INST_0_PLIC_EN || CPU_INST_0_CLINT_EN
plic_enable_global_interrupts(1);
#endif
#endif
return ret;
}
int main(void)
{
static uint8_t idx = 0;
static uint8_t pin_state = 0xFF;
apb_gpio_t usr_gpio;
memset(&usr_gpio, 0, sizeof(usr_gpio));
bsp_init();
printf("Started!\r\nHello RISC-V world!\r\n");
gpio_init(&usr_gpio, USR_INST_BASE_ADDR, 4);
gpio_set_dir(&usr_gpio, 0x0f);
while (true)
{
idx++;
gpio_write(&usr_gpio, idx&0x0f);
if (RTL_SIM)
{
delay(1);
}
else
{
delay(10);
}
gpio_write(&usr_gpio, idx&0x0f);
if (RTL_SIM)
{
delay(1);
}
else
{
delay(10);
}
}
return 0;
}
工程文件如下:

下载到下位机里,就可以观察到下位机的led灯按照不同的频率闪烁了