跳至正文

Verilog功能模块–RAM和ROM(02)–同步写-写冲突与读-写冲突实测

相关文章:

Xilinx IP 解析之 Block Memory Generator v8.4 ——01-手册重点解读(仅Native RAM) – 徐晓康的博客

Xilinx IP 解析之 Block Memory Generator v8.4 ——02-如何配置 IP(仅 Native RAM) – 徐晓康的博客

Verilog功能模块–RAM和ROM(01)–功能说明与关键代码解析 – 徐晓康的博客

Verilog功能模块–RAM和ROM(02)–同步写-写冲突与读-写冲突实测 – 徐晓康的博客

Verilog 功能模块–RAM 和 ROM(03)–自编 RAM 与 Vivado RAM IP 功能对比实测 – 徐晓康的博客

Verilog功能模块--RAM和ROM(01)--功能说明与关键代码解析

前言

要理解Vivado RAM IP,并在自编RAM时保证读写行为和Vivado RAM IP完全一致,一个重点就是理解冲突行为。根据pg058的描述,Vivado RAM并没有任何的冲突抑制逻辑,它只是在仿真时会报一个冲突警告,同样自编RAM也没有冲突处理,那么,当冲突发生时,两个RAM的行为是否一致呢?或者说,如果冲突时写入数据或读取数据可能是随机的,那么要做的就不是去保证自编RAM的冲突行为和Vivado RAM一致了,而应该是在调用时避免冲突发生。
因为仿真有局限性,并不能准确反应真实的硬件行为,这里选择上板实测的方式,通过ILA抓取信号,来研究一下RAM的冲突行为。

异步时钟没必要测,冲突时数据必然是随机的,其它类型的RAM冲突行为应和TDPRAM相同,所以,本文仅实测了同步时钟下的TDPRAM的冲突行为,包括写-写冲突和读-写冲突。

一、上板实测实验设计思路

1.1 需要进行哪些实验?

主要进行 TDPRAM 的实验测试,它的功能是最复杂的,其它类型 RAM 或 ROM 的底层逻辑和 TDPRAM 基本相同。

对于 SPRAM、SDPRAM、SPROM、DPROM 仅进行一次或两次实验验证。

几个需要实验验证的点:

  • 运行尽量高的频率,以验证高频性能
  • 验证 en 信号是否能正常工作
  • 验证 WF、RD、NC 三种操作模式是否能正常工作
  • 验证无输出寄存、一级输出寄存、二级输出寄存是否能正常工作
  • 验证 COE 文件初始化功能是否能正常工作
  • 验证初始化默认值功能(无 COE 或 COE 数据少于存储深度)是否能正常工作

1.2 需要观察哪些实验结果?

1.2.1 写-写冲突时写入数据是什么?

顶层文件设计了两个用于观测的指示信号

//* 检测写冲突后,没有对地址写,而是去读此地址的情况
(* mark_debug *)reg write_collision_clka_locked;
(* mark_debug *)reg [ADDR_WIDTH-1:0] addr_locked;
(* mark_debug *)reg rd_collision_addr_flag;
always @(posedge clka) begin
  if (addra == addrb && (true_ena && wea) && (true_enb && web)) begin
    write_collision_clka_locked <
1'b1;
    addr_locked <= addra;
  end
  if (write_collision_clka_locked) begin
    if (addr_locked == addra)
      if (true_ena && wea)
        write_collision_clka_locked <
1'b0;
      else if (true_ena && ~wea)
        rd_collision_addr_flag <= 1'b1;
      else
        ;
    else if (addr_locked == addrb)
      if (true_enb && web)
        write_collision_clka_locked <= 1'b0;
      else if (true_enb && ~web)
        rd_collision_addr_flag <= 1'b1;
      else
        ;
  end
  else
    rd_collision_addr_flag <= 1'b0;
  if (rd_collision_addr_flag)
    write_collision_clka_locked <= 1'b0;
end

其中,write_collision_clka_locked 为 1 表示写冲突发生了,但没有再一次对相同地址的写入;

rd_collision_addr_flag 为 1,表示写冲突发生后,相同地址没有被写入,而是先被读取了,这样观察读数据就能知道到底写入了什么数据。

在 ILA 中设置 rd_collision_addr_flag = 1 为触发条件即可观测写-写冲突的情况。

注意,这里仅使用了 clka,所以,实验仅做了同步时钟写-写冲突的情况;对于异步时钟的写-写冲突,这里认为没有任何规律,到底写入什么数据是随机的,没有设置信号去判断是否发生了异步的写-写冲突。

1.2.2 读-写冲突时,读出数据是什么?

设计了 a_wr_b_rd_collision 与 a_rd_b_wr_collision 信号用于指示读写冲突

always @(posedge clka) begin
  if (addra == addrb && (true_ena && wea)) a_wr_b_rd_collision <
1'b1;
  else a_wr_b_rd_collision <= 1'b0;
end

always @(posedge clkb) begin
  if (addra == addrb && (true_enb && web)) a_rd_b_wr_collision <
1'b1;
  else a_rd_b_wr_collision <= 1'b0;
end

1.2.3 自编 RAM 读数据是否和 Vivado IP 读数据一直保持一致?

设计了一下两个信号,用于判断读数据是否一致。

(* mark_debug *)wire douta_is_not_equal_vivado_douta = douta != vivado_douta;
(* mark_debug *)wire doutb_is_not_equal_vivado_doutb = doutb != vivado_doutb;

1.3 如何观测实验波形

通过 ILA 观测波形图,并保证用高频时钟作为唯一的采样时钟,这样所有波形才能在一个 ILA 窗口,如 300MHz 和 120MHz 都使用时,选择 300MHz 作为采样时钟。

1.4 如何保证 en、we、din、addr 信号的随机性

使用线性反馈移位寄存器(Linear Feedback Shift Register,简称 lfsr)作为伪随机数发生器,代码如下,此模块可综合。可通过VIO控制复位和seed,以方便在运行阶段实时改变seed

module lfsr (
  output reg  [15:0] random_num,
  input  wire [15:0] seed,
  input wire clk,
  input wire rstn // 复位时设置种子
)
;

// LFSR反馈多项式:x^16 + x^14 + x^13 + x^11 + 1
wire feedback = random_num[15] ^ random_num[13] ^ random_num[12] ^ random_num[10];

always @(posedge clk) begin
  if (~rstn)
    if (seed == 0)
      random_num <
16'hACE1;  // 初始化种子(非零值)
    else
      random_num <= seed;
  else
    random_num <= {random_num[14:0], feedback};
end

endmodule

用随机数的部分位给 en、we、din、addr 赋值,保证输入是随机的。

二、同步时钟下的写-写冲突实验设计

pg058对于同步写-写冲突的描述:

同步写-写冲突:如果两个端口都尝试向内存中的同一位置写入数据,就会发生写-写冲突。此时该内存位置最终的内容是未知的。请注意,写-写冲突会影响内存内容,而与之不同的是,写-读冲突仅会影响数据输出。

本章通过实测自编RAM和Vivado RAM的同步写-写冲突行为,看实际情况是否和pg058描述相符,并加深对同步写-写冲突的理解。

序号 实测实验条件 波形图 结论
1 1.TDPRAM
2.同步时钟–50MHz
3.A 端口和 B 端口均 Write First 模式
4.无输出寄存器
5.无 en 信号
6.不使用 COE
同步写-写冲突波形图-1 同步写-写冲突时:
自编 RAM 写入数据是 A 端口的
Vivado RAM 写入数据是端口 B 的
2 2.同步时钟–100MHz
其余不变
同步写-写冲突波形图-2 结论与 1 一样
3 2.同步时钟–200MHz
其余不变
同步写-写冲突波形图-3 同步写-写冲突时:
自编 RAM 写入数据是 B 端口的
Vivado RAM 写入数据是端口 A 的
结论与 1 相反
4 2.同步时钟–300MHz
其余不变
同步写-写冲突波形图-4
同步写-写冲突波形图-4-1
结论与 1 相反;
小概率情形:自编 RAM 在冲突发生时,写入数据随机,既不是 A 输入也不是 B 输入
5 2.同步时钟–150MHz
其余不变
同步写-写冲突波形图-5 结论与 1 相反;
小概率情形:Vivado RAM 在冲突发生时,写入数据随机,既不是 A 输入也不是 B 输入
6 2.同步时钟–120MHz
其余不变
同步写-写冲突波形图-6 结论与 1 相反;
小概率情形:Vivado RAM 在冲突发生时,写入数据随机,既不是 A 输入也不是 B 输入
7 2.同步时钟–110MHz
其余不变
同步写-写冲突波形图-7 结论与 1 一样
8 2.同步时钟–115MHz
其余不变
同步写-写冲突波形图-8
同步写-写冲突波形图-8-1
某些冲突点两个RAM写入一样,但总是可能在冲突发生时写入错乱值。结论:
同步写-写冲突没有规律,可能写入A端口的值,也可能写入B端口的值,还可能写入错乱值。
应在调用TDPRAM的上层模块来控制避免发生写-写冲突

相关代码:

always @(posedge clka) begin
  if (write_collision_clka_locked)
    wea <
1'b0;
  else
    wea <= random_num[0];
  addra <= random_num[ADDR_WIDTH-1 : 0];
  dina  <= random_num[DATA_WIDTH-1 : 0];
end

always @(posedge clkb) begin
  if (write_collision_clka_locked)
    web <
1'b0;
  else
    web <= random_num[8];
  addrb <= random_num[ADDR_WIDTH-1+8 : 8];
  dinb  <= random_num[DATA_WIDTH-1+2 : 2];
end

当 write_collision_clka_locked 检测到写-写冲突发生时,we 信号始终为 0,直到读此地址的动作发生,这是为了保证,写-写冲突后,不会发生再一次的同地址写,防止写-写冲突被覆盖。

同步写-写冲突波形图-1

箭头 1 处,A 和 B 同时对地址 1 进行写入,dina = 0x161,dinb = 0x058;

箭头 2 处,B 端口读取地址 1,doutb = 0x161,vivado_doutb = 0x058。

这说明,这一次的实验,同时写-写冲突发生时,自编 RAM 写入的是 A 端口数据,而 Vivado RAM 写入的是 B 端口数据。

我们继续看 ILA 抓取到的波形,看下一次发生写-写冲突的情况:

箭头 3 处,A 和 B 同时对地址 d 进行写入,dina = 0x13d,dinb = 0x34f;

箭头 4 处,B 端口读取地址 d,doutb = 0x13d,vivado_doutb = 0x34f。

还是相同的结论:同时写-写冲突发生时,自编 RAM 写入的是 A 端口数据,而 Vivado RAM 写入的是 B 端口数据。

继续观察下一次写-写冲突的情况,如下图所示,结论还是一样。

那是不是能说明,写-写冲突有规律呢?自编 RAM 的 A 端口写优先级高于 B,而 Vivado RAM 则反过来呢?其实不是,这只是当前布局布线是这样的情况,什么条件都不变,重新生成 bit 文件试一下。结论还是一样。

接下来改变同步时钟频率,从 50MHz 改为 100MHz。

同步写-写冲突波形图-2:结论一样。

什么条件都不变,重新生成 bit 文件试一下。结论还是一样。

继续改变同步时钟频率,从 100MHz 改为 200MHz。

同步写-写冲突波形图-3

箭头 1 处,A 和 B 同时对地址 1 进行写入,dina = 0x161,dinb = 0x058;

箭头 2 处,B 端口读取地址 1,doutb = 0x058,vivado_doutb = 0x161。

好吧,结论反过来了,自编 RAM 的 A 端口优先级低于 B,Vivado RAM 的 A 端口优先级高于 B。

箭头 3 和 4 处,结论也是一样反过来。什么条件都不变,重新生成 bit 文件试一下。结论还是一样反过来。

继续改变同步时钟频率,从 200MHz 改为 300MHz。

同步写-写冲突波形图-4

这里可能因为频率过高,组合逻辑的 doutb_is_not_equal_vivado_doutb 信号有问题,从 ILA 看两个输出是一样的。重新将 doutb_is_not_equal_vivado_doutb 和 douta_is_not_equal_vivado_douta 都改为时序逻辑:

(* mark_debug *)reg douta_is_not_equal_vivado_douta;
(* mark_debug *)reg doutb_is_not_equal_vivado_doutb;
always @(posedge clka) begin
  douta_is_not_equal_vivado_douta = douta != vivado_douta;
  doutb_is_not_equal_vivado_doutb = doutb != vivado_doutb;
end

重新生成 bit,再观察 ILA,同步写-写冲突波形图-4-1

结论与 3 一样,与 1 相反。什么条件都不变,重新生成 bit 文件试一下。同步写-写冲突波形图-4-2

前 2 个箭头处,输出与之前一样,注意第四个箭头,doutb = 0x13f,而第三个箭头处 A 端口的 dina = 13d,显然这个 13f 既不是 A 端口写入数据,也不是 B 端口写入数据 0x34f,显然数据错乱了。

这个数据错乱只发生在了写-写冲突的位置,其它位置数据是正常的,说明,写-写冲突的实际写入是不确定的,可能既不是 dina 也不是 dinb,这和 Xilinx pg058 BRAM 手册上关于同步时钟下的写-写冲突的描述是一样的,即实际写入数据随机。

再重新生成 bit 文件试一下,同步写-写冲突波形图-4-3:又回到了 同步写-写冲突波形图-4-1 的状态。

结论:同步写-写冲突发生时,写入有小概率是随机的。

基于之前的结论,100MHz 的结论与 200MHz 相反,那么 100~200MHz,是否有个区间,自编 RAM 的在写入冲突时的写入值与 Vivado RAM 相同呢?基于这个思路,改变时钟频率为 150MHz,同步写-写冲突波形图-5

注意,第二个箭头处,Vivado_doutb 的输出为 0x169,它既不等于写-写冲突时,A 端口输入 0x161 也不等于 B 端口输入 0x058,可见,之前发现的现象写-写冲突时,自编 RAM 写入错乱并不是自编 RAM 才有,Vivado RAM 也是有的。这里几乎能得到结论,同步写-写冲突,写入数据是随机的,并不一定等于A或B的输入

什么条件都不变,重新生成 bit 文件试一下。结论:Vivado_doutb 还是出现了输出为 0x169 的现象。

继续改变时钟频率为 120MHz,同步写-写冲突波形图-6

注意第二个箭头,Vivado_doutb 的输出为 0x149,发生了错乱。结论与 150MHz 时相同。重新生成 bit 文件,结论还是一样。

继续改变时钟频率为 110MHz,同步写-写冲突波形图-7

结论与100MHz时相同,与120MHz时相反。这时就考虑是不是110~120MHz的某个频率,自编RAM和Vivado RAM的写-写冲突行为会一致呢?虽然说这个一致与否并不是很有意义,因为从理论上来说写-写冲突的写入数据是随机的,一致也可能只是这一次布局布线凑巧而已,但是可以尝试一下,找到一致的频率点。

继续更改时钟频率为为115MHz,同步写-写冲突波形图-8

从波形图上看好像两个RAM的写-写冲突写入值是一样的,但也是某些时刻一样,在其它时刻又会不一样,同步写-写冲突波形图-8-1

显然,第二个箭头处,Vivado_doutb的值为0x03e,这个也是随机的错乱值,所以说,想找到一个频率点,让两个RAM对写-写冲突的写入是一样的,是不可能的,某几次冲突可能一样,但当写-写冲突导致写入随机值时,就会不一样了。

到这里,我们可以得到结论,无论是自编TDPRAM,还是Vivado的TDPRAM IP,只要发生写-写冲突,就可能写入错乱值,也就是说不需要去设定某些逻辑去限制当写-写冲突发生时,应该以A端口为准还是B端口为准,而应该在调用TDPRAM的上层逻辑去避免发生写-写冲突,因为写-写冲突可能造成该地址的存储值错乱。

三、同步时钟下的读-写冲突实验设计

PG058对于同步读写冲突的描述:

同步写-读冲突:如果一个端口尝试向某一内存位置写入数据,而另一个端口读取该相同位置的数据,就可能会发生同步写-读冲突。在写-读冲突中,虽然内存内容不会被破坏,但输出数据的有效性取决于写端口的操作模式。

  • 如果写端口处于读优先(READ_FIRST)模式,另一个端口能够可靠地读取旧的内存内容。

  • 如果写端口处于写优先(WRITE_FIRST)模式或无变化(NO_CHANGE)模式,读端口输出的数据是无效的。

本章通过实测自编RAM和Vivado RAM的同步读-写冲突行为,看实际情况是否和pg058描述相符,并加深对同步读-写冲突的理解。

本章只研究同步读-写冲突,需要避免同步写-写冲突干扰观察,怎么避免呢?代码如下:

always @(posedge clka) begin
  wea   <= random_num[13];
  addra <= random_num[ADDR_WIDTH-1+8 : 8];
  dina  <= random_num[DATA_WIDTH-1+2 : 2];
end

reg web_tmp;
always @(posedge clkb) begin
  web_tmp <= random_num[5];
  addrb   <= random_num[ADDR_WIDTH-1 : 0];
  dinb    <= random_num[DATA_WIDTH-1 : 0];
end

always @(*) begin
  if (addra == addrb && wea)
    web 
1'b0;
  else
    web = web_tmp;
end

A端口信号保持随机,B端口的写使能web在检测到A端口正在写时赋值0,避免写-写冲突发生。

这里必须使用组合逻辑进行判断,否则判断延迟一个时钟周期,写-写冲突还是可能发生。

同步读-写冲突实验设计:

序号 实测实验条件 波形图 结论
1 1.TDPRAM
2.同步时钟–50MHz
3.A 端口和 B 端口均 Write First 模式
4.无输出寄存器
5.无 en 信号
6.不使用 COE
同步读-写冲突波形图-1 同步读-写冲突时:
Vivado RAM的A端口读数据读到了B端口的最新输入,而自编RAM没有;
但自编RAM的B端口读到了A端口的最新输入,而Vivado RAM没有
2 2.同步时钟–100MHz
其余不变
同步读-写冲突波形图-2
同步读-写冲突波形图-2-1
同步读-写冲突时:
小概率情形:
Vivado RAM可能读数据错乱
自编RAM也可能读数据错乱
3 2.同步时钟–100MHz
3.A端口 Read First模式,B端口No Change模式
其余不变
同步读-写冲突波形图-3
同步读-写冲突波形图-3-1
B端口读正常,
A端口读可能出错,
与PG058对读-写冲突的描述吻合
4 2.同步时钟–100MHz
3.A端口 Read First模式,B端口Read First模式
其余不变
同步读-写冲突波形图-4 A,B端口读均正常
与PG058对读-写冲突的描述吻合

同步读-写冲突波形图-1

箭头1:A读B写,地址c,douta=0x338,vivado_douta=0x03c=dinb,说明,Vivado RAM的A端口读数据读到了B端口的最新输入,而自编RAM没有;

箭头2:A读B写,地址d,douta=0x245,vivado_douta=0x13d=dinb,说明,与箭头1相同;

箭头3:A写B读,地址a,doutb=0x29e=dina,vivado_doutb=0x03a,说明,自编RAM的B端口读到了A端口的最新输入,而Vivado RAM没有,与箭头1相反;

箭头4,A写B读,地址4,doutb=0x13d=dina,vivado_doutb=0x074,说明,与箭头3相反

箭头5,A写B读,地址9,doutb=0x27a=dina,vivado_doutb=0x0e9,说明,与箭头3相反

结论,Vivado RAM的A端口读数据读到了B端口的最新输入,而自编RAM没有;但自编RAM的B端口读到了A端口的最新输入,而Vivado RAM没有。

这应该就是pg058的Vivado RAM IP手册中对同步读-写冲突的描述:如果写端口处于写优先(WRITE_FIRST)模式或无变化(NO_CHANGE)模式,读端口输出的数据是无效的

最终结论可能是,同步读-写冲突发生时,读数据是无效的,可能是存储器旧数据,也可能是新写入数据,或者是随机数据?这个还需要再观察。

改变时钟频率为100MHz,ILA波形图,同步读-写冲突波形图-2

箭头1,A读B写,地址d,douta=0x205,vivado_douta=0x13d=dinb,说明,Vivado RAM的A端口能读数据能读出B端口最新输入,而自编RAM不行;

箭头2,A写B读,地址a,doutb=0x29e=vivado_doutb=dina,说明Vivado RAM和自编RAM的B端口都能读出A端口的最新输入;

箭头3,A写B读,地址4,doutb=0x13d=dina,vivado_doutb=0x034,说明自编RAM的B端口能读出A端口的最新输入,而Vivado RAM不行;

箭头4,结论和箭头3相同。

再继续观察ILA,同步读-写冲突波形图-2-1

箭头1:端口A写入地址2,数据0x0af;

箭头2,端口B写入地址1,数据0x1e1;

箭头3,A写B读,地址1,doutb=0x054=dina,而vivado_doutb=0x044,这既不等于dina也不等于旧存储器值0x1e1,这就是所谓的读取数据错乱,说明,vivado RAM的B端口在读-写冲突时发生了读数据错乱;

箭头4,A读B写,地址2,vivado_dina=0x2a2=dinb,而dina=0x0a2,这既不等于dinb也不等于旧存储器值0x0af,说明,自编RAM的A端口在读-写冲突时发生了读数据错乱。

根据PG058对于同步读-写冲突的描述,当写端口设置为Read First时,读端口应能稳定读取到存储器旧数据;当写端口设置为No Change时,读端口读数据无效。为此将A端口模式改为Read First,B端口改为No Change,观察ILA,同步读-写冲突波形图-3

这是一个整理视图,可以看到douta_is_not_equal_vivado_douta有置高的,说明A端口读数据时,自编RAM和Vivado RAM有不同的;而doutb_is_not_equal_vivado_doutb没有置高,说明B端口读数据时,自编RAM和Vivado RAM始终相同,上述将A端口设置为了Read First,这个设置保证了B端口读数据始终能读到存储器旧数据,实测证明了PG058的这一说法。

继续看其他读-写冲突时刻,同步读-写冲突波形图-3-1

第一个箭头,B端口写入地址7,dinb=0x177;

第二个箭头,B写A读,B端口因为是No Change所以读数据不变,没问题;A端口读数据,doutb=0x377,vivado_doutb=0x177,自编RAM读出的是B正在写入的数据,而Vivado RAM读出的是旧存储器数据,这里就不一样了。这说明了,B端口设置为No_Change时功能正常,当读-写冲突发生时,写端口为No Change时,读端口数据是无效的,进一步证明了PG058描述是符合实际的。

那么继续进行实现,将A和B端口都设置为Read First,那么根据PG058描述,即使冲突发生,RAM读数据应该是稳定的旧存储器数据,那么两个RAM应该不会出现读数据不一样的情况,观察ILA,同步读-写冲突波形图-4

可以看到,再改了Read First之后,两个RAM的读取数据始终一致了。可见,PG058关于同步读-写冲突的描述是准确的,我们实测也是一样的结果,还说明了自编RAM的同步读-写冲突行为和Vivado RAM也是一致的。

到这里我们得到结论

  • 如果写端口处于读优先(READ_FIRST)模式,另一个端口能够可靠地读取旧的内存内容。

  • 如果写端口处于写优先(WRITE_FIRST)模式或无变化(NO_CHANGE)模式,读端口输出的数据是无效的。

  • 自编RAM和Vivado RAM对于同步读-写冲突的行为完全一致。

四、总结与分享

通过对同步时钟下的自编TDPRAM和Vivado TDPRAM IP的上板实测观察,我们可以得到以下结论:

  1. 同步写-写冲突发生时,两个TDPRAM写入值都可能是错乱的,它可能是A端口输入,也可能是B端口输入,也可能是随机值;

  2. 同步读-写冲突发生时,写入没有问题,但读数据可能是错乱的,读端口数据可能等于写端口正在写入的数据,可能等于旧存储器数据,也可能是随机值。

所以,在使用TDPRAM时,应在调用模块保证不会发生同步冲突,同步写-写冲突可能破坏存储器数据,应避免;读-写冲突可能导致读数据出错,也应该避免。

这里没有去实测异步时钟,显然,无论是根据Vivado RAM IP的手册pg058,还是自编RAM的代码逻辑,异步时钟下,也一样会发生写-写或读-写冲突,也需要在调用时避免冲突的发生,但异步时钟的冲突控制会更困难一些。

本模块开源,两处仓库同步。

Gitee: Verilog 功能模块–RAM 和 ROM https://gitee.com/xuxiaokang/verilog-function-module–RAM_ROM

Github: zhengzhideakang/verilog-function-module–RAM_ROM https://github.com/zhengzhideakang/verilog-function-module–RAM_ROM

仿真和实测的Vivado 2024.2工程通过网盘分享(本系列文章所有分享均相同,均需获取一次即可):

欢迎大家关注我的微信公众号:徐晓康的博客,回复以下6位数字获取网盘链接。

402368

建议复制过去不会码错字!


如果本文对你有所帮助,欢迎点赞、转发、收藏、评论让更多人看到,赞赏支持就更好了。

如果对文章内容有疑问,请务必清楚描述问题,留言评论或私信告知我,我看到会回复。


徐晓康的博客持续分享高质量硬件、FPGA与嵌入式知识,软件,工具等内容,欢迎大家关注。

0 0 投票数
文章评分
订阅评论
提醒
0 评论
内联反馈
查看所有评论
0
希望看到您的想法,请您发表评论x