目录

设计AI芯片架构的入门-研究生入行数字芯片设计验证的项目-opentitan

设计AI芯片架构的入门 研究生入行数字芯片设计、验证的项目 opentitan

前言

这几年芯片设计行业在国内像坐过山车。时而高亢,时而低潮。最近又因为AI的热潮开始high起来。到底芯片行业的规律是如何?

我谈谈自己观点:芯片设计是“劳动密集型”行业。

“EDA和工具高度标准化和代工厂的工艺标准化之后,芯片设计就变成了“劳动密集型”工作,这也是美国很长一段时间几乎要放弃芯片设计行业的技术背景。当然美国国内也没有这么多EE工程师供应。要跟制造业一样转移给全球各地。”

一家之言,大家看看笑笑就可以了。

我们国内背景是研究生大量扩招,又什么学硕/专硕、全日/非全,品种很齐。

国外研究生、美国、欧洲硕士阶段都是授课型教育,多多益善。国内现在也是。

这给研究生门很大困惑。

为什么呢? 缺项目实践。

1 我一贯的观点是设计芯片和FPGA是一个技术,希望FPGA学习者也关注这方面技术。

2 AI时代入门芯片设计要先学好设计和验证,在学习架构设计

所以,本人尝试在知乎开这个栏目,提供一些稍微实战项目。一方面讲解、一方面跟各位互动。

无论是本专业科班学生还是非科班学习,也无论是你已经就业想转行,都可以参考上面的项目来学习。

网上有opentitan有介绍,让我来做第一人,来详细介绍这个项目如何学习和吃下来。

芯片设计、验证一体化项目opentitan学习

关于芯片设计、验证的入门学习书籍、以及一些小练习网上有很多书籍、视频、也有培训公司在做这方面工作。感谢他们在目前中国芯片行业急需工程师背景下所做的努力。网上做芯片验证培训的路科,我就觉得很好。我们大学这方面教学太缺了。

掌握基础知识如何通过项目提升,可能需要的时间更长,也更困难。

opentitan就是这样一个设计、验证的综合性项目。

虽然他是Google公司联合一些半导体公司以 SOC和安全芯片为样本来打造,但是完全不影响我们把它作为我们入门芯片设计和验证后的实战项目。如果要流片,你也可以采用openLane工具链来生成版图。

1 Opentitan项目介绍

[OpenTitan - OpenTitan Documentation

opentitan.org/book/doc/introduction.html]( )

才看到这个项目肯定会很懵,就让我们一点一点吃透这个项目。

除了在github开源了全部项目代码之外,我们可以通过文档学习到很多。

前几年国内成立太多芯片设计公司,建议补课。

2 芯片设计的流程(前端)

1 设计验证

[https://

opentitan.org/book/doc/

getting_started/setup_dv.html]( )

2 形式验证

[https://

opentitan.org/book/doc/

getting_started/setup_formal.html]( )

3 创建测试软件(SOC需要)

[https://

opentitan.org/book/doc/

getting_started/build_sw.html]( )

4 文档创建

[https://

opentitan.org/book/doc/

getting_started/build_docs.html]( )

5 FPGA 验证

[https://

opentitan.org/book/doc/

getting_started/using_openocd.html]( )

3 Opentitan芯片的设计验证

本篇文章就暂时从这款RISC-V芯片的设计和验证开始,看看我们改如何学习和下手这个项目。

简单来讲,SOC芯片就是 CPU core和 外围电路 加 总线 连接起来的功能模块。

先看看整体模块构成

https://i-blog.csdnimg.cn/img_convert/8422526580c363bb4f12f501217a9500.jpeg

opentitan整体模块构成

可见 这是一个标准的SOC芯片。 还比较复杂,跟商业芯片业差不了太多。

指令集支持RV32IMCB(32位整数指令集 M 整型乘除法扩展 C 压缩指令集扩展 B 位操作扩展 )大概相当于ARM cortex-M3的水平吧。

关于core看这个文档:

[https://ibex-core.readthedocs.io/en/latest/01_overview/index.html

ibex-core.readthedocs.io/en/latest/01_overview/index.html]( )

[Introduction to Ibex

ibex-core.readthedocs.io/en/latest/01_overview/index.html]( )

总线看这个文档:

[https://sifive.cdn.prismic.io/sifive%2F57f93ecf-2c42-46f7-9818-bcdd7d39400a_tilelink-spec-1.7.1.pdf

sifive.cdn.prismic.io/sifive%2F57f93ecf-2c42-46f7-9818-bcdd7d39400a_tilelink-spec-1.7.1.pdf]( )

是SiFive公司的。前几年据说INTEL要收购SiFive,没有成功。芯片行业江湖爱恨情仇每天都在上演,不亚于电视连续剧。

我相信,各位跟关注外围模块的设计验证,这是大家入行最开始干的工作:

https://i-blog.csdnimg.cn/img_convert/e43cdf02a95533c5acec6024beb14e87.jpeg

4 UART 外围电路模块的设计和验证

https://i-blog.csdnimg.cn/img_convert/50f5fff8efa75e2c2d28fc0b763a5651.jpeg

本篇文章就以这个最简单的UART模块讲解一下设计和验证的概述,详细在后面文章讲解。目前你暂时没有system verilo和UVM基础暂时也没关系,我带着你先入门再详细去学习这方面知识。

这方面我上面提的路科验证就可以。

设计:

一般芯片采用的verilog。这个项目采用的是 作为设计语言,但是也有自己的规范:

[style-guides/VerilogCodingStyle.md at master · lowRISC/style-guides

github.com/lowRISC/style-guides/blob/master/VerilogCodingStyle.md

https://i-blog.csdnimg.cn/img_convert/b9e9535de559b82036fd73528cce1aa7.png]( )

各位到自己的公司也会有各个公司的设计语言规范。

A template that demonstrates many of the items is given below.

Template:

// Copyright lowRISC contributors.

// Licensed under the Apache License, Version 2.0, see LICENSE for details.

// SPDX-License-Identifier: Apache-2.0

//

// One line description of the module

module my_module #(

parameter Width = 80,

parameter Height = 24

) (

input clk_i,

input rst_ni,

input req_valid_i,

input [Width-1:0] req_data_i,

output req_ready_o,

);

logic [Width-1:0] req_data_masked;

submodule u_submodule (

.clk_i,

.rst_ni,

.req_valid_i,

.req_data_i (req_data_masked),

.req_ready_o(req_ready),

);

always_comb begin

req_data_masked = req_data_i;

case (fsm_state_q)

ST_IDLE: begin

req_data_masked = req_data_i & MASK_IDLE;

end

endmodule

另外 高度依赖

“Linting is a productivity tool for designers to quickly find typos and bugs at the time when the RTL is written. Running lint is important when using SystemVerilog, a weakly-typed language, unlike other hardware description languages. We consider linting to be critical for conformance to our goals of high quality designs”

因为system verilog写起爽,可能其他工具就要跟上。

5 UART功能框图和代码

https://i-blog.csdnimg.cn/img_convert/1002f7673d4e7ca1355d011bfe35ccfa.jpeg

UART功能框图

5.1 uart模块的TOP

// Copyright lowRISC contributors.  

// Licensed under the Apache License, Version 2.0, see LICENSE for details.  

// SPDX-License-Identifier: Apache-2.0  

//  

// Description: UART top level wrapper file

`include “prim_assert.sv”

module uart

import uart_reg_pkg:😗;

#(

parameter logic [NumAlerts-1:0] AlertAsyncOn = {NumAlerts{1’b1}}

) (

input clk_i,

input rst_ni,

// Bus Interface

input tlul_pkg::tl_h2d_t tl_i,

output tlul_pkg::tl_d2h_t tl_o,

// Alerts

input prim_alert_pkg::alert_rx_t [NumAlerts-1:0] alert_rx_i,

output prim_alert_pkg::alert_tx_t [NumAlerts-1:0] alert_tx_o,

// Generic IO

input cio_rx_i,

output logic cio_tx_o,

output logic cio_tx_en_o,

// Interrupts

output logic intr_tx_watermark_o ,

output logic intr_rx_watermark_o ,

output logic intr_tx_empty_o ,

output logic intr_rx_overflow_o ,

output logic intr_rx_frame_err_o ,

output logic intr_rx_break_err_o ,

output logic intr_rx_timeout_o ,

output logic intr_rx_parity_err_o

);

logic [NumAlerts-1:0] alert_test, alerts;

uart_reg2hw_t reg2hw;

uart_hw2reg_t hw2reg;

uart_reg_top u_reg (

.clk_i,

.rst_ni,

.tl_i,

.tl_o,

.reg2hw,

.hw2reg,

// SEC_CM: BUS.INTEGRITY

.intg_err_o (alerts[0]),

.devmode_i (1’b1)

);

uart_core uart_core (

.clk_i,

.rst_ni,

.reg2hw,

.hw2reg,

.rx    (cio_rx_i   ),
.tx    (cio_tx_o   ),

.intr_tx_watermark_o,
.intr_rx_watermark_o,
.intr_tx_empty_o,
.intr_rx_overflow_o,
.intr_rx_frame_err_o,
.intr_rx_break_err_o,
.intr_rx_timeout_o,
.intr_rx_parity_err_o

);

// Alerts

assign alert_test = {

reg2hw.alert_test.q &

reg2hw.alert_test.qe

};

for (genvar i = 0; i < NumAlerts; i++) begin : gen_alert_tx

prim_alert_sender #(

.AsyncOn(AlertAsyncOn[i]),

.IsFatal(1’b1)

) u_prim_alert_sender (

.clk_i,

.rst_ni,

.alert_test_i ( alert_test[i] ),

.alert_req_i ( alerts[0] ),

.alert_ack_o ( ),

.alert_state_o ( ),

.alert_rx_i ( alert_rx_i[i] ),

.alert_tx_o ( alert_tx_o[i] )

);

end

// always enable the driving out of TX

assign cio_tx_en_o = 1’b1;

// Assert Known for outputs

ASSERT(TxEnIsOne_A, cio_tx_en_o === 1'b1) ASSERT_KNOWN(TxKnown_A, cio_tx_o, clk_i, !rst_ni || !cio_tx_en_o)

// Assert Known for alerts

`ASSERT_KNOWN(AlertsKnown_A, alert_tx_o)

// Assert Known for interrupts

ASSERT_KNOWN(TxWatermarkKnown_A, intr_tx_watermark_o) ASSERT_KNOWN(RxWatermarkKnown_A, intr_rx_watermark_o)

ASSERT_KNOWN(TxEmptyKnown_A, intr_tx_empty_o) ASSERT_KNOWN(RxOverflowKnown_A, intr_rx_overflow_o)

ASSERT_KNOWN(RxFrameErrKnown_A, intr_rx_frame_err_o) ASSERT_KNOWN(RxBreakErrKnown_A, intr_rx_break_err_o)

ASSERT_KNOWN(RxTimeoutKnown_A, intr_rx_timeout_o) ASSERT_KNOWN(RxParityErrKnown_A, intr_rx_parity_err_o)

// Alert assertions for reg_we onehot check

`ASSERT_PRIM_REG_WE_ONEHOT_ERROR_TRIGGER_ALERT(RegWeOnehotCheck_A, u_reg, alert_tx_o[0])

endmodule

代码下面是用于形式验证,暂时不管,可见核心是uart_core:

其中.reg2hw .hw2reg

  // Register -> HW type  

typedef struct packed {  

uart_reg2hw_intr_state_reg_t intr_state; // [126:119]  

uart_reg2hw_intr_enable_reg_t intr_enable; // [118:111]  

uart_reg2hw_intr_test_reg_t intr_test; // [110:95]  

uart_reg2hw_alert_test_reg_t alert_test; // [94:93]  

uart_reg2hw_ctrl_reg_t ctrl; // [92:68]  

uart_reg2hw_status_reg_t status; // [67:56]  

uart_reg2hw_rdata_reg_t rdata; // [55:47]  

uart_reg2hw_wdata_reg_t wdata; // [46:38]  

uart_reg2hw_fifo_ctrl_reg_t fifo_ctrl; // [37:27]  

uart_reg2hw_ovrd_reg_t ovrd; // [26:25]  

uart_reg2hw_timeout_ctrl_reg_t timeout_ctrl; // [24:0]  

} uart_reg2hw_t;  
  // HW -> register type  

typedef struct packed {  

uart_hw2reg_intr_state_reg_t intr_state; // [64:49]  

uart_hw2reg_status_reg_t status; // [48:43]  

uart_hw2reg_rdata_reg_t rdata; // [42:35]  

uart_hw2reg_fifo_ctrl_reg_t fifo_ctrl; // [34:28]  

uart_hw2reg_fifo_status_reg_t fifo_status; // [27:16]  

uart_hw2reg_val_reg_t val; // [15:0]  

} uart_hw2reg_t;  

hw就是我们UART的硬件实现事宜叫hw。

及硬件电路与寄存器之间的关系

5.2 core代码

module uart_core (  

input                  clk_i,  

input                  rst_ni,

input uart_reg_pkg::uart_reg2hw_t reg2hw,

output uart_reg_pkg::uart_hw2reg_t hw2reg,

input rx,

output logic tx,

output logic intr_tx_watermark_o,

output logic intr_rx_watermark_o,

output logic intr_tx_empty_o,

output logic intr_rx_overflow_o,

output logic intr_rx_frame_err_o,

output logic intr_rx_break_err_o,

output logic intr_rx_timeout_o,

output logic intr_rx_parity_err_o

);

import uart_reg_pkg:😗;

localparam int NcoWidth = $bits(reg2hw.ctrl.nco.q);

logic [15:0] rx_val_q;

logic [7:0] uart_rdata;

logic tick_baud_x16, rx_tick_baud;

logic [5:0] tx_fifo_depth, rx_fifo_depth;

logic [5:0] rx_fifo_depth_prev_q;

logic [23:0] rx_timeout_count_d, rx_timeout_count_q, uart_rxto_val;

logic rx_fifo_depth_changed, uart_rxto_en;

logic tx_enable, rx_enable;

logic sys_loopback, line_loopback, rxnf_enable;

logic uart_fifo_rxrst, uart_fifo_txrst;

logic [2:0] uart_fifo_rxilvl;

logic [1:0] uart_fifo_txilvl;

logic ovrd_tx_en, ovrd_tx_val;

logic [7:0] tx_fifo_data;

logic tx_fifo_rready, tx_fifo_rvalid;

logic tx_fifo_wready, tx_uart_idle;

logic tx_out;

logic tx_out_q;

logic [7:0] rx_fifo_data;

logic rx_valid, rx_fifo_wvalid, rx_fifo_rvalid;

logic rx_fifo_wready, rx_uart_idle;

logic rx_sync;

logic rx_in;

logic break_err;

logic [4:0] allzero_cnt_d, allzero_cnt_q;

logic allzero_err, not_allzero_char;

logic event_tx_watermark, event_rx_watermark, event_tx_empty, event_rx_overflow;

logic event_rx_frame_err, event_rx_break_err, event_rx_timeout, event_rx_parity_err;

logic tx_watermark_d, tx_watermark_prev_q;

logic rx_watermark_d, rx_watermark_prev_q;

logic tx_uart_idle_q;

assign tx_enable = reg2hw.ctrl.tx.q;

assign rx_enable = reg2hw.ctrl.rx.q;

assign rxnf_enable = reg2hw.ctrl.nf.q;

assign sys_loopback = reg2hw.ctrl.slpbk.q;

assign line_loopback = reg2hw.ctrl.llpbk.q;

assign uart_fifo_rxrst = reg2hw.fifo_ctrl.rxrst.q & reg2hw.fifo_ctrl.rxrst.qe;

assign uart_fifo_txrst = reg2hw.fifo_ctrl.txrst.q & reg2hw.fifo_ctrl.txrst.qe;

assign uart_fifo_rxilvl = reg2hw.fifo_ctrl.rxilvl.q;

assign uart_fifo_txilvl = reg2hw.fifo_ctrl.txilvl.q;

assign ovrd_tx_en = reg2hw.ovrd.txen.q;

assign ovrd_tx_val = reg2hw.ovrd.txval.q;

typedef enum logic {

BRK_CHK,

BRK_WAIT

} break_st_e ;

break_st_e break_st_q;

assign not_allzero_char = rx_valid & (~event_rx_frame_err | (rx_fifo_data != 8’h0));

assign allzero_err = event_rx_frame_err & (rx_fifo_data == 8’h0);

assign allzero_cnt_d = (break_st_q == BRK_WAIT || not_allzero_char) ? 5’h0 :

//allzero_cnt_q[4] never be 1b without break_st_q as BRK_WAIT

//allzero_cnt_q[4] ? allzero_cnt_q :

allzero_err ? allzero_cnt_q + 5’d1 :

allzero_cnt_q;

always_ff @(posedge clk_i or negedge rst_ni) begin

if (!rst_ni) allzero_cnt_q <= ‘0;

else if (rx_enable) allzero_cnt_q <= allzero_cnt_d;

end

// break_err edges in same cycle as event_rx_frame_err edges ; that way the

// reset-on-read works the same way for break and frame error interrupts.

always_comb begin

unique case (reg2hw.ctrl.rxblvl.q)

2’h0: break_err = allzero_cnt_d >= 5’d2;

2’h1: break_err = allzero_cnt_d >= 5’d4;

2’h2: break_err = allzero_cnt_d >= 5’d8;

default: break_err = allzero_cnt_d >= 5’d16;

endcase

end

always_ff @(posedge clk_i or negedge rst_ni) begin

if (!rst_ni) break_st_q <= BRK_CHK;

else begin

unique case (break_st_q)

BRK_CHK: begin

if (event_rx_break_err) break_st_q <= BRK_WAIT;

end

    BRK_WAIT: begin
      if (rx_in) break_st_q &lt;= BRK_CHK;
    end

    default: begin
      break_st_q &lt;= BRK_CHK;
    end
  endcase
end

end

assign hw2reg.val.d = rx_val_q;

assign hw2reg.rdata.d = uart_rdata;

assign hw2reg.status.rxempty.d = ~rx_fifo_rvalid;

assign hw2reg.status.rxidle.d = rx_uart_idle;

assign hw2reg.status.txidle.d = tx_uart_idle & ~tx_fifo_rvalid;

assign hw2reg.status.txempty.d = ~tx_fifo_rvalid;

assign hw2reg.status.rxfull.d = ~rx_fifo_wready;

assign hw2reg.status.txfull.d = ~tx_fifo_wready;

assign hw2reg.fifo_status.txlvl.d = tx_fifo_depth;

assign hw2reg.fifo_status.rxlvl.d = rx_fifo_depth;

// resets are self-clearing, so need to update FIFO_CTRL

assign hw2reg.fifo_ctrl.rxilvl.de = 1’b0;

assign hw2reg.fifo_ctrl.rxilvl.d = 3’h0;

assign hw2reg.fifo_ctrl.txilvl.de = 1’b0;

assign hw2reg.fifo_ctrl.txilvl.d = 2’h0;

// NCO 16x Baud Generator

// output clock rate is:

// Fin * (NCO/2**NcoWidth)

logic [NcoWidth:0] nco_sum_q; // extra bit to get the carry

always_ff @(posedge clk_i or negedge rst_ni) begin

if (!rst_ni) begin

nco_sum_q <= 17’h0;

end else if (tx_enable || rx_enable) begin

nco_sum_q <= {1’b0,nco_sum_q[NcoWidth-1:0]} + {1’b0,reg2hw.ctrl.nco.q[NcoWidth-1:0]};

end

end

assign tick_baud_x16 = nco_sum_q[16];

//

// TX Logic //

//

assign tx_fifo_rready = tx_uart_idle & tx_fifo_rvalid & tx_enable;

prim_fifo_sync #(

.Width (8),

.Pass (1’b0),

.Depth (32)

) u_uart_txfifo (

.clk_i,

.rst_ni,

.clr_i (uart_fifo_txrst),

.wvalid_i(reg2hw.wdata.qe),

.wready_o(tx_fifo_wready),

.wdata_i (reg2hw.wdata.q),

.depth_o (tx_fifo_depth),

.full_o (),

.rvalid_o(tx_fifo_rvalid),

.rready_i(tx_fifo_rready),

.rdata_o (tx_fifo_data),

.err_o ()

);

uart_tx uart_tx (

.clk_i,

.rst_ni,

.tx_enable,

.tick_baud_x16,

.parity_enable (reg2hw.ctrl.parity_en.q),

.wr (tx_fifo_rready),

.wr_parity ((^tx_fifo_data) ^ reg2hw.ctrl.parity_odd.q),

.wr_data (tx_fifo_data),

.idle (tx_uart_idle),

.tx (tx_out)

);

assign tx = line_loopback ? rx : tx_out_q ;

always_ff @(posedge clk_i or negedge rst_ni) begin

if (!rst_ni) begin

tx_out_q <= 1’b1;

end else if (ovrd_tx_en) begin

tx_out_q <= ovrd_tx_val ;

end else if (sys_loopback) begin

tx_out_q <= 1’b1;

end else begin

tx_out_q <= tx_out;

end

end

//

// RX Logic //

//

// sync the incoming data

prim_flop_2sync #(

.Width(1),

.ResetValue(1’b1)

) sync_rx (

.clk_i,

.rst_ni,

.d_i(rx),

.q_o(rx_sync)

);

// Based on: en.wikipedia.org/wiki/Repetition_code mentions the use of a majority filter

// in UART to ignore brief noise spikes

logic rx_sync_q1, rx_sync_q2, rx_in_mx, rx_in_maj;

always_ff @(posedge clk_i or negedge rst_ni) begin

if (!rst_ni) begin

rx_sync_q1 <= 1’b1;

rx_sync_q2 <= 1’b1;

end else begin

rx_sync_q1 <= rx_sync;

rx_sync_q2 <= rx_sync_q1;

end

end

assign rx_in_maj = (rx_sync & rx_sync_q1) |

(rx_sync & rx_sync_q2) |

(rx_sync_q1 & rx_sync_q2);

assign rx_in_mx = rxnf_enable ? rx_in_maj : rx_sync;

assign rx_in = sys_loopback ? tx_out :

line_loopback ? 1’b1 :

rx_in_mx;

uart_rx uart_rx (

.clk_i,

.rst_ni,

.rx_enable,

.tick_baud_x16,

.parity_enable (reg2hw.ctrl.parity_en.q),

.parity_odd (reg2hw.ctrl.parity_odd.q),

.tick_baud (rx_tick_baud),

.rx_valid,

.rx_data (rx_fifo_data),

.idle (rx_uart_idle),

.frame_err (event_rx_frame_err),

.rx (rx_in),

.rx_parity_err (event_rx_parity_err)

);

assign rx_fifo_wvalid = rx_valid & ~event_rx_frame_err & ~event_rx_parity_err;

prim_fifo_sync #(

.Width (8),

.Pass (1’b0),

.Depth (32)

) u_uart_rxfifo (

.clk_i,

.rst_ni,

.clr_i (uart_fifo_rxrst),

.wvalid_i(rx_fifo_wvalid),

.wready_o(rx_fifo_wready),

.wdata_i (rx_fifo_data),

.depth_o (rx_fifo_depth),

.full_o (),

.rvalid_o(rx_fifo_rvalid),

.rready_i(reg2hw.rdata.re),

.rdata_o (uart_rdata),

.err_o ()

);

always_ff @(posedge clk_i or negedge rst_ni) begin

if (!rst_ni) rx_val_q <= 16’h0;

else if (tick_baud_x16) rx_val_q <= {rx_val_q[14:0], rx_in};

end

// Interrupt & Status //

always_comb begin

unique case(uart_fifo_txilvl)

2’h0: tx_watermark_d = (tx_fifo_depth < 6’d2);

2’h1: tx_watermark_d = (tx_fifo_depth < 6’d4);

2’h2: tx_watermark_d = (tx_fifo_depth < 6’d8);

default: tx_watermark_d = (tx_fifo_depth < 6’d16);

endcase

end

assign event_tx_watermark = tx_watermark_d & ~tx_watermark_prev_q;

// The empty condition handling is a bit different.

// If empty rising conditions were detected directly, then every first write of a burst

// would trigger an empty. This is due to the fact that the uart_tx fsm immediately

// withdraws the content and asserts “empty”.

// To guard against this false trigger, empty is qualified with idle to extend the window

// in which software has an opportunity to deposit new data.

// However, if software deposit speed is TOO slow, this would still be an issue.

//

// The alternative software fix is to disable tx_enable until it has a chance to

// burst in the desired amount of data.

assign event_tx_empty = ~tx_fifo_rvalid & ~tx_uart_idle_q & tx_uart_idle;

always_ff @(posedge clk_i or negedge rst_ni) begin

if (!rst_ni) begin

tx_watermark_prev_q <= 1’b1; // by default watermark condition is true

rx_watermark_prev_q <= 1’b0; // by default watermark condition is false

tx_uart_idle_q <= 1’b1;

end else begin

tx_watermark_prev_q <= tx_watermark_d;

rx_watermark_prev_q <= rx_watermark_d;

tx_uart_idle_q <= tx_uart_idle;

end

end

always_comb begin

unique case(uart_fifo_rxilvl)

3’h0: rx_watermark_d = (rx_fifo_depth >= 6’d1);

3’h1: rx_watermark_d = (rx_fifo_depth >= 6’d4);

3’h2: rx_watermark_d = (rx_fifo_depth >= 6’d8);

3’h3: rx_watermark_d = (rx_fifo_depth >= 6’d16);

3’h4: rx_watermark_d = (rx_fifo_depth >= 6’d30);

default: rx_watermark_d = 1’b0;

endcase

end

assign event_rx_watermark = rx_watermark_d & ~rx_watermark_prev_q;

// rx timeout interrupt

assign uart_rxto_en = reg2hw.timeout_ctrl.en.q;

assign uart_rxto_val = reg2hw.timeout_ctrl.val.q;

assign rx_fifo_depth_changed = (rx_fifo_depth != rx_fifo_depth_prev_q);

assign rx_timeout_count_d =

// don’t count if timeout feature not enabled ;

// will never reach timeout val + lower power

(uart_rxto_en == 1’b0) ? 24’d0 :

// reset count if timeout interrupt is set

event_rx_timeout ? 24’d0 :

// reset count upon change in fifo level: covers both read and receiving a new byte

rx_fifo_depth_changed ? 24’d0 :

// reset count if no bytes are pending

(rx_fifo_depth == ‘0) ? 24’d0 :

// stop the count at timeout value (this will set the interrupt)

// Removed below line as when the timeout reaches the value,

// event occured, and timeout value reset to 0h.

//(rx_timeout_count_q == uart_rxto_val) ? rx_timeout_count_q :

// increment if at rx baud tick

rx_tick_baud ? (rx_timeout_count_q + 24’d1) :

rx_timeout_count_q;

assign event_rx_timeout = (rx_timeout_count_q == uart_rxto_val) & uart_rxto_en;

always_ff @(posedge clk_i or negedge rst_ni) begin

if (!rst_ni) begin

rx_timeout_count_q <= 24’d0;

rx_fifo_depth_prev_q <= 6’d0;

end else begin

rx_timeout_count_q <= rx_timeout_count_d;

rx_fifo_depth_prev_q <= rx_fifo_depth;

end

end

assign event_rx_overflow = rx_fifo_wvalid & ~rx_fifo_wready;

assign event_rx_break_err = break_err & (break_st_q == BRK_CHK);

// instantiate interrupt hardware primitives

prim_intr_hw #(.Width(1)) intr_hw_tx_watermark (

.clk_i,

.rst_ni,

.event_intr_i (event_tx_watermark),

.reg2hw_intr_enable_q_i (reg2hw.intr_enable.tx_watermark.q),

.reg2hw_intr_test_q_i (reg2hw.intr_test.tx_watermark.q),

.reg2hw_intr_test_qe_i (reg2hw.intr_test.tx_watermark.qe),

.reg2hw_intr_state_q_i (reg2hw.intr_state.tx_watermark.q),

.hw2reg_intr_state_de_o (hw2reg.intr_state.tx_watermark.de),

.hw2reg_intr_state_d_o (hw2reg.intr_state.tx_watermark.d),

.intr_o (intr_tx_watermark_o)

);

prim_intr_hw #(.Width(1)) intr_hw_rx_watermark (

.clk_i,

.rst_ni,

.event_intr_i (event_rx_watermark),

.reg2hw_intr_enable_q_i (reg2hw.intr_enable.rx_watermark.q),

.reg2hw_intr_test_q_i (reg2hw.intr_test.rx_watermark.q),

.reg2hw_intr_test_qe_i (reg2hw.intr_test.rx_watermark.qe),

.reg2hw_intr_state_q_i (reg2hw.intr_state.rx_watermark.q),

.hw2reg_intr_state_de_o (hw2reg.intr_state.rx_watermark.de),

.hw2reg_intr_state_d_o (hw2reg.intr_state.rx_watermark.d),

.intr_o (intr_rx_watermark_o)

);

prim_intr_hw #(.Width(1)) intr_hw_tx_empty (

.clk_i,

.rst_ni,

.event_intr_i (event_tx_empty),

.reg2hw_intr_enable_q_i (reg2hw.intr_enable.tx_empty.q),

.reg2hw_intr_test_q_i (reg2hw.intr_test.tx_empty.q),

.reg2hw_intr_test_qe_i (reg2hw.intr_test.tx_empty.qe),

.reg2hw_intr_state_q_i (reg2hw.intr_state.tx_empty.q),

.hw2reg_intr_state_de_o (hw2reg.intr_state.tx_empty.de),

.hw2reg_intr_state_d_o (hw2reg.intr_state.tx_empty.d),

.intr_o (intr_tx_empty_o)

);

prim_intr_hw #(.Width(1)) intr_hw_rx_overflow (

.clk_i,

.rst_ni,

.event_intr_i (event_rx_overflow),

.reg2hw_intr_enable_q_i (reg2hw.intr_enable.rx_overflow.q),

.reg2hw_intr_test_q_i (reg2hw.intr_test.rx_overflow.q),

.reg2hw_intr_test_qe_i (reg2hw.intr_test.rx_overflow.qe),

.reg2hw_intr_state_q_i (reg2hw.intr_state.rx_overflow.q),

.hw2reg_intr_state_de_o (hw2reg.intr_state.rx_overflow.de),

.hw2reg_intr_state_d_o (hw2reg.intr_state.rx_overflow.d),

.intr_o (intr_rx_overflow_o)

);

prim_intr_hw #(.Width(1)) intr_hw_rx_frame_err (

.clk_i,

.rst_ni,

.event_intr_i (event_rx_frame_err),

.reg2hw_intr_enable_q_i (reg2hw.intr_enable.rx_frame_err.q),

.reg2hw_intr_test_q_i (reg2hw.intr_test.rx_frame_err.q),

.reg2hw_intr_test_qe_i (reg2hw.intr_test.rx_frame_err.qe),

.reg2hw_intr_state_q_i (reg2hw.intr_state.rx_frame_err.q),

.hw2reg_intr_state_de_o (hw2reg.intr_state.rx_frame_err.de),

.hw2reg_intr_state_d_o (hw2reg.intr_state.rx_frame_err.d),

.intr_o (intr_rx_frame_err_o)

);

prim_intr_hw #(.Width(1)) intr_hw_rx_break_err (

.clk_i,

.rst_ni,

.event_intr_i (event_rx_break_err),

.reg2hw_intr_enable_q_i (reg2hw.intr_enable.rx_break_err.q),

.reg2hw_intr_test_q_i (reg2hw.intr_test.rx_break_err.q),

.reg2hw_intr_test_qe_i (reg2hw.intr_test.rx_break_err.qe),

.reg2hw_intr_state_q_i (reg2hw.intr_state.rx_break_err.q),

.hw2reg_intr_state_de_o (hw2reg.intr_state.rx_break_err.de),

.hw2reg_intr_state_d_o (hw2reg.intr_state.rx_break_err.d),

.intr_o (intr_rx_break_err_o)

);

prim_intr_hw #(.Width(1)) intr_hw_rx_timeout (

.clk_i,

.rst_ni,

.event_intr_i (event_rx_timeout),

.reg2hw_intr_enable_q_i (reg2hw.intr_enable.rx_timeout.q),

.reg2hw_intr_test_q_i (reg2hw.intr_test.rx_timeout.q),

.reg2hw_intr_test_qe_i (reg2hw.intr_test.rx_timeout.qe),

.reg2hw_intr_state_q_i (reg2hw.intr_state.rx_timeout.q),

.hw2reg_intr_state_de_o (hw2reg.intr_state.rx_timeout.de),

.hw2reg_intr_state_d_o (hw2reg.intr_state.rx_timeout.d),

.intr_o (intr_rx_timeout_o)

);

prim_intr_hw #(.Width(1)) intr_hw_rx_parity_err (

.clk_i,

.rst_ni,

.event_intr_i (event_rx_parity_err),

.reg2hw_intr_enable_q_i (reg2hw.intr_enable.rx_parity_err.q),

.reg2hw_intr_test_q_i (reg2hw.intr_test.rx_parity_err.q),

.reg2hw_intr_test_qe_i (reg2hw.intr_test.rx_parity_err.qe),

.reg2hw_intr_state_q_i (reg2hw.intr_state.rx_parity_err.q),

.hw2reg_intr_state_de_o (hw2reg.intr_state.rx_parity_err.de),

.hw2reg_intr_state_d_o (hw2reg.intr_state.rx_parity_err.d),

.intr_o (intr_rx_parity_err_o)

);

// unused registers

logic unused_reg;

assign unused_reg = ^reg2hw.alert_test;

endmodule

core代码有点多,其核心就是一个带FIFO的uart实现。其他不分是所有模块都有的部分,是中断和错误的管理。

到现在,你会发现这个项目真是一个宝库,完全可以实践你学的设计和验证技巧,并且实现得非常规范,是一个很好的范例。

后续

罗马不是一天建成的。要在AI时代成为时代的弄潮儿,一定要打下扎实的基础,才能发挥你的创造力

后面我会介绍使用什么工具链和使用自己的工具链如何来设计和验证这个项目的每个模块。

一天一天的进步。

欢迎关注我这个新项目。

为自己加油!