数字ic笔试
编写一个单端口 RAM 模块,深度 256、宽度 16bit。
// File: spram_256x16.v
// 单端口 RAM:深度 256、宽度 16bit
// 接口:同步写、同步读(posedge clk)
module spram_256x16 (input wire clk, // 时钟input wire we, // 写使能:1=写入input wire [7:0] addr, // 地址(0~255)input wire [15:0] din, // 写数据output reg [15:0] dout // 读数据
);// 声明存储体:256 x 16reg [15:0] mem [0:255];// 同步读 & 写(read-first:读优先/旧数据)always @(posedge clk) begin// 先读dout <= mem[addr];// 后写if (we) beginmem[addr] <= din;endendendmodule
上面写法是“读优先(read-first)”:同一拍对同一地址又读又写时,dout
输出的是写入前的旧数据。很多 FPGA/综合器都能据此推断出单端口 Block RAM。
TB
// File: tb_spram_256x16.v
`timescale 1ns/1psmodule tb_spram_256x16;reg clk;reg we;reg [7:0] addr;reg [15:0] din;wire [15:0] dout;// DUTspram_256x16 dut (.clk (clk),.we (we),.addr(addr),.din (din),.dout(dout));// 10ns 时钟initial beginclk = 0;forever #5 clk = ~clk;end// 激励initial beginwe = 0; addr = 0; din = 16'h0000;@(negedge clk);// 写:addr=8'h12 <= 16'hABCDaddr = 8'h12; din = 16'hABCD; we = 1;@(negedge clk);// 停止写we = 0;@(negedge clk);// 读同一地址(同步读,数据在下一个 posedge 后出现在 dout)addr = 8'h12;@(negedge clk);@(negedge clk);$display("Read @0x12 = 0x%h (expect 0xABCD)", dout);// 冲突场景:同拍读写同址,观察 read-first 行为addr = 8'h34; din = 16'h1234; we = 1; // 写 0x34@(negedge clk);we = 0;@(negedge clk);// 此时已有 0x1234,演示同拍读写addr = 8'h34; din = 16'h5678; we = 1; // 同拍对同址读写@(negedge clk);we = 0;@(negedge clk);$display("Conflict read-first: dout(old)=0x%h (上一拍), mem(new)=0x5678", dout);$finish;end
endmodule
可参数化
如果想展示工程化能力,可把宽度/深度写成参数:
module spram #(parameter AW = 8, // Address width -> depth = 2^AWparameter DW = 16
)(input wire clk,input wire we,input wire [AW-1:0] addr,input wire [DW-1:0] din,output reg [DW-1:0] dout
);reg [DW-1:0] mem [0:(1<<AW)-1];always @(posedge clk) begindout <= mem[addr];if (we) mem[addr] <= din;end
endmodule
实现一个 3 级流水线的 16 位加法器
把 16 位分成 5/5/6 位 三段,段与段之间插入寄存器;把进位在段间传递,同时把较低位的部分和用寄存器“对齐”到最后一级再拼接输出。(3 级流水、吞吐 1/cycle,时延 3 cycles)
// File: pipelined_adder16_3stage.v
// 3 级流水 16 位加法器:吞吐=1/cycle,时延=3 cycles
module pipelined_adder16_3stage (input wire clk,input wire rst_n, // 同步低有效复位input wire valid_in, // 输入有效input wire [15:0] a,input wire [15:0] b,output reg valid_out, // 输出有效(=valid_in 延后 3 拍)output reg [15:0] sum,output reg cout // 最高位进位(可选)
);// -------- Stage 0: 低 5 位相加 --------wire [4:0] a0 = a[4:0];wire [4:0] b0 = b[4:0];wire [5:0] add0 = a0 + b0; // {c0, s0}wire c0 = add0[5];wire [4:0] s0 = add0[4:0];// 把中/高位在 S0 注册,供后续级使用reg [4:0] a1_r, b1_r; // 位[9:5]reg [5:0] a2_r1, b2_r1; // 位[15:10]// 把 S0 的部分和/进位沿流水线对齐reg [4:0] s0_r1, s0_r2; // s0 需要延两拍到 S2reg c0_r1; // 传给下一段reg vld_r1, vld_r2, vld_r3;// -------- Stage 1: 中 5 位 + c0 --------wire [5:0] add1 = a1_r + b1_r + c0_r1; // {c1, s1}wire c1 = add1[5];wire [4:0] s1 = add1[4:0];// 对齐 s1 到 S2reg [4:0] s1_r1;// 把高位在 S1 再注册一次,供 S2 使用reg [5:0] a2_r2, b2_r2;reg c1_r1;// -------- Stage 2: 高 6 位 + c1 --------wire [6:0] add2 = a2_r2 + b2_r2 + c1_r1; // {c2, s2}wire c2 = add2[6];wire [5:0] s2 = add2[5:0];// --------- 时序寄存器 ---------always @(posedge clk) beginif (!rst_n) begin// 复位流水线有效位vld_r1 <= 1'b0; vld_r2 <= 1'b0; vld_r3 <= 1'b0;sum <= 16'd0; cout <= 1'b0; valid_out <= 1'b0;// 其它寄存器清零(可省略,视项目复位策略而定)a1_r<=0; b1_r<=0; a2_r1<=0; b2_r1<=0;s0_r1<=0; s0_r2<=0; c0_r1<=0; s1_r1<=0; a2_r2<=0; b2_r2<=0; c1_r1<=0;end else begin// ---------- Stage0 寄存 ----------a1_r <= a[9:5];b1_r <= b[9:5];a2_r1 <= a[15:10];b2_r1 <= b[15:10];s0_r1 <= s0;c0_r1 <= c0;vld_r1 <= valid_in;// ---------- Stage1 寄存 ----------s0_r2 <= s0_r1; // s0 再对齐一拍s1_r1 <= s1; // s1 对齐到下一级a2_r2 <= a2_r1; // 把高位继续传递b2_r2 <= b2_r1;c1_r1 <= c1;vld_r2 <= vld_r1;// ---------- Stage2 输出 ----------sum <= {s2, s1_r1, s0_r2}; // 6 + 5 + 5 = 16cout <= c2;valid_out<= vld_r2; // 输出有效(延后 3 拍)vld_r3 <= vld_r2; // 供需要更深对齐时使用(可删)endend
endmodule
行为说明
段宽划分:5/5/6(也可 4/6/6 或 8/4/4,根据时序压力选择)。
吞吐量:每拍接受一组
(a,b)
;时延:
valid_out
相比valid_in
延后 3 个时钟;同周期多组数据互不干扰,因为每级都有寄存器。
cout 是 17 位和的最高位,是否需要由题目决定(这里给出,便于完整性)。
TB
// File: tb_pipelined_adder16_3stage.v
`timescale 1ns/1ps
module tb_pipelined_adder16_3stage;reg clk, rst_n;reg valid_in;reg [15:0] a, b;wire valid_out;wire [15:0] sum;wire cout;pipelined_adder16_3stage dut (.clk(clk), .rst_n(rst_n),.valid_in(valid_in), .a(a), .b(b),.valid_out(valid_out), .sum(sum), .cout(cout));// 时钟initial begin clk=0; forever #5 clk=~clk; end// 激励integer i;initial beginrst_n = 0; valid_in = 0; a=0; b=0;repeat(3) @(posedge clk);rst_n = 1;// 连续喂入 10 组随机数据,验证吞吐=1/cyclefor (i=0; i<10; i=i+1) begin@(posedge clk);valid_in <= 1;a <= $random; b <= $random;end@(posedge clk) valid_in <= 0;// 观察 valid_out 与 sum(延后 3 拍)repeat(10) @(posedge clk);$finish;end// 参考结果对比(可选)reg [16:0] golden [0:31];// 也可以在 testbench 里排队比对 sum==a+b(注意延 3 拍)
endmodule