目录
1. 前言
2. RTL and testbench generation
2.1 RTL generation
Explanation:
Assumptions:
2.2 Testbench generation
Explanation:
2.3 Add fsdb dump to testbench
3. 仿真与调试
3.1 RTL中的语法问题
3.2 testbench的问题
3.3 RTL中empty/full状态标志逻辑问题
3.3.1 full generation logic
3.3.2 read empty and write full protection
4. 小结
1. 前言
本实验用一个国内某大厂的AI大模型试着生成了一个同步FIFO的RTL模型,然后相应生成了一个简易的test-bench。目的是想看看现在大模型在数字逻辑设计领域能够对工程师有多少帮助。
2. RTL and testbench generation
2.1 RTL generation
首先,请求大模型生成一个同步FIFO的RTL设计:
Prompt:
Can you generate one synchronous FIFO RTL implementation?
Response:
Certainly! Below is a basic synchronous FIFO (First-In-First-Out) implementation in Verilog RTL (Register-Transfer Level). This FIFO uses a fixed-size array to store data and simple counters to manage the read and write pointers. It assumes a synchronous clock domain and includes basic functionality for writing, reading, checking if the FIFO is empty, and checking if it is full.
module SFIFO #(
parameter DATA_WIDTH = 8, // Width of each data element
parameter FIFO_DEPTH = 16 // Depth of the FIFO
)(
input wire clk, // Clock signal
input wire rst_n, // Active-low reset signal
input wire wr_en, // Write enable
input wire rd_en, // Read enable
input wire [DATA_WIDTH-1:0] din, // Data input
output reg [DATA_WIDTH-1:0] dout, // Data output
output wire full, // FIFO full flag
output wire empty // FIFO empty flag
);
// Internal memory array to store FIFO data
reg [DATA_WIDTH-1:0] fifo_mem [0:FIFO_DEPTH-1];
// Pointers for write and read operations
reg [3:0] wr_ptr = 0; // Write pointer (assuming FIFO_DEPTH <= 16)
reg [3:0] rd_ptr = 0; // Read pointer (assuming FIFO_DEPTH <= 16)
// Count of valid entries in the FIFO
reg [3:0] count = 0;
// Write operation
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
wr_ptr <= 0;
end else if (wr_en && !full) begin
fifo_mem[wr_ptr] <= din;
wr_ptr <= wr_ptr + 1;
end
end
// Read operation
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
rd_ptr <= 0;
dout <= 0;
end else if (rd_en && !empty) begin
dout <= fifo_mem[rd_ptr];
rd_ptr <= rd_ptr + 1;
end
end
// Count of valid entries
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
count <= 0;
end else begin
The originally generated code segment.
case ({wr_en, rd_en, full, empty})
// Write without read when not full
{1'b1, 1'b0, 1'b0, _}: count <= count + 1; // change "_" to "1'bx"
// Write without read when already full (should not happen due to wr_en condition)
{1'b1, 1'b0, 1'b1, _}: count <= count;
// Read without write when not empty
{1'b0, 1'b1, _, 1'b0}: count <= count - 1;
// Read without write when already empty (should not happen due to rd_en condition)
{1'b0, 1'b1, _, 1'b1}: count <= count;
// Both read and write (balance)
{1'b1, 1'b1, _, _}: count <= count;
// No operation
default: count <= count;
end
end
// FIFO full flag
assign full = (count == FIFO_DEPTH);
// FIFO empty flag
assign empty = (count == 0);
endmodule
除了代码外,大模型还给出了简要的设计描述,如下所示:
Explanation:
- Parameters:
DATA_WIDTH
: Width of each data element in the FIFO.FIFO_DEPTH
: Depth (number of elements) of the FIFO.- Inputs and Outputs:
clk
: Clock signal.rst_n
: Active-low reset signal.wr_en
: Write enable signal.rd_en
: Read enable signal.din
: Data input.dout
: Data output.full
: FIFO full flag.empty
: FIFO empty flag.- Internal Registers:
fifo_mem
: Array to store FIFO data.wr_ptr
andrd_ptr
: Write and read pointers.count
: Number of valid entries in the FIFO.- Logic:
- The write pointer increments on a write enable and the read pointer increments on a read enable.
- The
count
register tracks the number of valid entries.- The
full
andempty
flags are derived from thecount
register.Assumptions:
FIFO_DEPTH
is less than or equal to 16, so a 4-bit pointer is sufficient.rst_n
is an active-low reset.- This FIFO does not handle overflow or underflow conditions; it assumes that
wr_en
will not be asserted when the FIFO is full, andrd_en
will not be asserted when the FIFO is empty.You can adjust the
DATA_WIDTH
andFIFO_DEPTH
parameters as needed for your application.
2.2 Testbench generation
接着,让大模型生成了一个简易的testbench:
Prompt:
Please create one testbench for the above synchronous FIFO design
Response:
Certainly! Below is a testbench for the synchronous FIFO design provided earlier. This testbench will simulate the FIFO's behavior by applying various write and read operations and observing the FIFO's state, output, and flags.
module tb_top;
// Parameters for the FIFO
parameter DATA_WIDTH = 8;
parameter FIFO_DEPTH = 16;
// Signals for the FIFO instance
reg clk;
reg rst_n;
reg wr_en;
reg rd_en;
reg [DATA_WIDTH-1:0] din;
wire [DATA_WIDTH-1:0] dout;
wire full;
wire empty;
// Instantiate the FIFO
fifo #(
.DATA_WIDTH(DATA_WIDTH),
.FIFO_DEPTH(FIFO_DEPTH)
) fifo_inst (
.clk(clk),
.rst_n(rst_n),
.wr_en(wr_en),
.rd_en(rd_en),
.din(din),
.dout(dout),
.full(full),
.empty(empty)
);
// Clock generation
initial begin
clk = 0;
forever #5 clk = ~clk; // 10 time units clock period (5 up, 5 down)
end
// Test sequence
initial begin
// Initialize signals
rst_n = 0;
wr_en = 0;
rd_en = 0;
din = 0;
// Apply reset
#10 rst_n = 1;
// Write data to FIFO
#10 wr_en = 1; din = 8'hAA;
#10 wr_en = 1; din = 8'hBB;
#10 wr_en = 1; din = 8'hCC;
#10 wr_en = 0;
// Check FIFO state
#10 if (full) $display("Error: FIFO should not be full yet.");
#10 if (!empty) $display("FIFO is not empty (expected).");
#10 if (dout != 0) $display("Error: dout should be 0.");
// Read data from FIFO
#10 rd_en = 1;
#10 if (dout != 8'hAA) $display("Error: dout should be 8'hAA.");
#10 rd_en = 0;
#10 rd_en = 1;
#10 if (dout != 8'hBB) $display("Error: dout should be 8'hBB.");
#10 rd_en = 0;
// Write and read simultaneously
#10 wr_en = 1; din = 8'hDD;
#10 rd_en = 1;
#10 if (dout != 8'hCC) $display("Error: dout should be 8'hCC.");
#10 wr_en = 0; rd_en = 0;
#10 rd_en = 1;
#10 if (dout != 8'hDD) $display("Error: dout should be 8'hDD.");
#10 rd_en = 0;
// Fill FIFO to capacity
#10 wr_en = 1; din = 8'hEE;
#10 wr_en = 1; din = 8'hFF;
#10 wr_en = 1; din = 8'h00;
#10 wr_en = 1; din = 8'h01;
#10 wr_en = 0;
#10 if (!full) $display("Error: FIFO should be full.");
// Read until FIFO is empty
#10 rd_en = 1;
#10 if (dout != 8'hEE) $display("Error: dout should be 8'hEE.");
#10 rd_en = 0;
#10 rd_en = 1;
#10 if (dout != 8'hFF) $display("Error: dout should be 8'hFF.");
#10 rd_en = 0;
#10 rd_en = 1;
#10 if (dout != 8'h00) $display("Error: dout should be 8'h00.");
#10 rd_en = 0;
#10 rd_en = 1;
#10 if (dout != 8'h01) $display("Error: dout should be 8'h01.");
#10 rd_en = 0;
#10 if (!empty) $display("Error: FIFO should be empty.");
// End simulation
#10 $finish;
end
endmodule
同样,也给出了概要性的说明:
Explanation:
- Parameters:
- The
DATA_WIDTH
andFIFO_DEPTH
parameters are set to match the FIFO instance.- Signals:
- The testbench includes all the signals required by the FIFO instance:
clk
,rst_n
,wr_en
,rd_en
,din
,dout
,full
, andempty
.- Clock Generation:
- A simple clock generator is created using an
initial
block that toggles theclk
signal every 10 time units.- Test Sequence:
- The test sequence is defined in another
initial
block.- The FIFO is reset by setting
rst_n
to 0 and then to 1 after a delay.- Various write and read operations are performed, and the FIFO's state, output, and flags are checked using
if
statements.- If any check fails, an error message is displayed using
$display
.- The simulation ends after all the test operations are completed using the
$finish
system task.To run this testbench, you would need to compile and simulate it using a Verilog simulator like ModelSim, Quartus, Vivado, or any other Verilog-compatible tool. The testbench will apply the specified sequences of write and read operations and verify that the FIFO behaves as expected.
2.3 Add fsdb dump to testbench
在以上生成的testbench中,没有波形dump语句,这个后续仿真调试肯定是需要的,所以:
Prompt:
I think you'd better add fsdb waveform dump statements to the testbench
Response:
Understanding your suggestion, it seems you are proposing to incorporate fsdb waveform dump statements into a testbench for better signal tracing and debugging in a hardware design or verification environment. Fsdb (Fast Signal Database) is commonly used in EDA (Electronic Design Automation) tools for capturing and visualizing signal behavior during simulation.
Here’s how you might approach adding fsdb waveform dump statements to a testbench:
Identify Key Signals: First, determine which signals in your design are critical for debugging. This could include input signals, internal states, and output signals.
Incorporate Dump Statements: In your testbench code, you will need to add specific commands or function calls to dump these signals to an fsdb file. The exact syntax depends on the simulation tool you are using (e.g., ModelSim, Questasim, VCS, etc.).
For example, if you are using a tool that supports SystemVerilog and fsdb dumping through a specific library or API, your code might look something like the code segment shown below.
Alternatively, some tools may require you to configure the simulation environment or use specific directives in your testbench to enable fsdb dumping.Configure Simulation Tool: Ensure that your simulation tool is configured to generate fsdb files. This may involve setting environment variables, command-line options, or modifying simulation scripts.
Run Simulation: Execute your testbench with the added fsdb dump statements. The simulation tool should now capture the specified signals and store them in an fsdb file.
Analyze Fsdb File: Use a compatible waveform viewer or debugger to load and analyze the fsdb file. This will allow you to visualize the signal behavior and debug your design.
Keep in mind that the specific implementation details will vary depending on the simulation tool and language you are using. Therefore, it is essential to consult the documentation for your specific EDA tool to understand how to properly incorporate fsdb waveform dump statements into your testbench.
// Assuming 'fsdb_dump' is a predefined function or task for dumping signals
initial begin
fsdb_dump("signal_dump.fsdb"); // Start dumping signals to fsdb file
// Add dump commands for specific signals here
// For instance: fsdb_dump_signal(signal_name);
// (Note: The exact function name and usage may differ based on the tool)
// Wait for the simulation to end
wait(forever);
end
大模型balabala说了一大堆,看上去对fsdb波形及其dump是有一定了解的,但是似乎并不知道verilog中用于fsdb waveform dump的语句,所以比较保守地只给出了以上几行示意性的伪代码。。。这个就没啥意义了,好在也比较简单,自己手动添加了以下代码:
initial
begin
if($test$plusargs("fsdb"))
begin
$fsdbDumpfile("./tb_top.fsdb");
$fsdbDumpSVA(0,tb_top);
$fsdbDumpvars(0,tb_top);
end
end
3. 仿真与调试
写了个脚本对以上设计和testbench进行仿真和调试。
3.1 RTL中的语法问题
一上来编译报告rtl中有错误:
说的是case语句用的“_”,这个的本意是应该是用于表示通配符,即对应输入数据不管是什么都可以。其实我也不知道(System)verilog中这种意图应该怎么描述,也懒得去查了,所以就请大模型来回答这个问题:
但是,这个问题似乎并不容易表述(或许只是我的表述方式),反正,来回交互了好几轮,大模型始终没有理解我的意思。最后,我问:
Not what I expected. I mean codes segement like below, is it correct?
always @(*) begin
case (sel)
4'b110x: result = 1'b1; // Matches 1100 or 1101
4'b1110: result = 1'b1; // Matches 1110
4'b1111: result = 1'b1; // Matches 1111
default: result = 1'b0; // Matches any other value endcase end
这回大模型终于搞明白了,给了我一个比较像样的回复:
In Verilog, the
case
statement does not support the use of wildcards likex
in the way you might be used to in regular expressions or some other programming languages. Thecase
statement in Verilog requires exact binary, octal, decimal, or hexadecimal matches for its branches.However, you can achieve a similar effect using a combination of logical operations within the
case
statement or by using thecasez
(case with don't cares) orcasex
(case with don't cares and hexadecimal) statements, which do support the use ofz
(high-impedance state, treated as a don't care) andx
(unknown value, also treated as a don't care) respectively.Here's how you could rewrite your code using the
casez
statement to allow for the wildcard behavior you're looking for:
always @(*) begin
casez (sel)
4'b110z: result = 1'b1; // Matches 1100 or 1101 (z is a don't care)
4'b1110: result = 1'b1; // Matches 1110
4'b1111: result = 1'b1; // Matches 1111
default: result = 1'b0; // Matches any other value
endcase
end
In this code:
- The
casez
statement allows the use ofz
to represent a don't care bit. This means that any value (0 or 1) in the position ofz
will match the pattern.- The pattern
4'b110z
will match both4'b1100
and4'b1101
because the least significant bit is a don't care.- The other patterns (
4'b1110
and4'b1111
) match exactly as specified.- The
default
case handles any other value ofsel
.This should achieve the behavior you're looking for in your code.
看上去好像说得通,就用这个代码段替换掉了原始代码段,这次编译通过了。
3.2 testbench的问题
以上rtl中的语法问题解决了后,仿真倒是能够跑起来了(当然仿真脚本还是折腾了一会儿),但是报告错误如下:
FIFO is not empty (expected).
Error: dout should be 8'hBB.
Error: dout should be 8'hCC.
Error: FIFO should be full.
Error: dout should be 8'hFF.
Error: dout should be 8'h00.
$finish called from file "tb_top.sv", line 108.
$finish at simulation time 410
把波形打开看了看:
好像看起来也挺正常啊。。。至少没有出现什么X态之类的东西。
琢磨了一会儿,好像看出点名堂来了,testbench中所有的驱动信号至少有两个问题:
(1) 都是异步产生的。这个问题不致命,但是没有必要。同步设计的验证时用同步信号驱动更简单明了。
(2) wr_en和rd_en的脉冲宽度控制不对。第一次连续写三个数据的操作只是恰好正确,然后接下来两次读操作,
// Read data from FIFO
#10 rd_en = 1;
#10 if (dout != 8'hAA) $display("Error: dout should be 8'hAA.");
#10 rd_en = 0;
#10 rd_en = 1;
#10 if (dout != 8'hBB) $display("Error: dout should be 8'hBB.");
#10 rd_en = 0;
以上写法所得到的第一个rd_en的宽度其实是20ns,而工作时钟周期是10ns,因为该脉冲覆盖了两个时钟上升沿,所以实际上发生了两次读操作。这样的话,后面的动作序列就完全错位了。。。
搞清楚出错原因后,解决就比较简单了。最好是全部激励信号改为时钟同步驱动,但是如上所述这个不是致命的,为了节约时间暂时就不改了。以上语句的根本问题在于rd_en deassert与rd_en assert之间还插入了一条判断语句,而该判断语句前面也有一个延时操作。直接的解决方案是将判断语句前面的延时操作删除,或者将rd_en de-assert语句放到前面去,or both。比如说如下修改后,这个问题就解决了。
// Read data from FIFO
#10 rd_en = 1;
#10 rd_en = 0;
if (dout != 8'hAA) $display("Error: The 1st read: dout should be 8'hAA.");
#10 rd_en = 1;
#10 rd_en = 0;
if (dout != 8'hBB) $display("Error: The 2nd read: dout should be 8'hBB.");
后面的wr_en和rd_en的脉冲宽度按同样方式进行修改后,验证就通过了。
当然这个testbench只执行了简单的验证操作,要进行同步FIFO的完备的验证,估计不能指望大模型,还得自己花点功夫进行testcase设计。
3.3 RTL中empty/full状态标志逻辑问题
进一步测试发现,原始RTL代码中empty和full标志的逻辑有问题。
比如说,当FIFO_DEPTH=16时,连续写入16个数据,一方面full信号没有拉起来,另一方面empty信号反而(在写入第一个数据后拉低之后)在写入第16个数据后被拉低了。。。
经分析后错误的原因如下所述。
3.3.1 full generation logic
首先,full信号的生成逻辑有问题:
// FIFO full flag
assign full = (count == FIFO_DEPTH);
在该实现中count其实是用于计数FIFO中所剩下的有效数据。但是verilog中的计数是从0开始,所以以上条件应该是(count == (FIFO_DEPTH-1))。
3.3.2 read empty and write full protection
其次,更为严重的问题在于在full时没有防止进一步写入的机制,以及在empty时没有防止读取的机制。
当前代码中,在full(count == 15)时继续写入的话,count又变回0。同理,empty时继续读的话,count也会从0变为15。这样的动作显然不是所想看到的。
当然,这只是个简单的设计。可以要求读写控制方管理读写保护控制,即在full时不写入,在empty不读取。而更为robust的设计则是比如说full时写入的话不发生实际写操作同时反馈出错信号;在empty时读取的话不发生实际读操作,同时反馈出错信号。这个修改起来稍微麻烦了一点,超出了本文所有讨论的范围,这里就不再赘述。
4. 小结
大模型针对简单的通用的功能模块能生成一些verilog代码,但是不能完全采信,需要仔细验证,文责自负。当然,有些问题有可能通过给更详细的生成需求来解决。另外,该实验用的是免费模型,版本稍低,如果用付费(VIP)版本是不是应该有更高质量一点的生成呢?