Amiya · 5月10日

各大公司IC面试手撕代码总结(超全面)

酒酒爆肝IC面试手撕代码常考题

哈喽,大家好,我是酒酒,酒酒自学互联网和IC,并拿到20个offer及50W+offer,由于关注酒酒公众号的有CS和IC的,所以本篇是IC的~

1. 用任意语言写1:100的质数

  • C语言
// FileName: HelloWorld.java
public class HelloWorld {
  // Java 入口程序,程序从此入口
  public static void main(String[] args) {
    System.out.println("Hello,World!"); // 向控制台打印一条语句
  }
}

支持以下语言种类:

#include<stdio.h>
//输出100之内的所有素数
int main()
{
    int i;
    int j;
    int flag = 1;

    for(i = 2;i <= 100;i++)
    {
        for(j = 2;j <= i / 2;j++)
 {
     if(i % j ==0)
     {
         flag = 0;
  break;
     }
 }
     if(flag == 1)
     {
         printf("%d\n",i);
     }
     flag = 1;

    }

    return 0;
}
  • matlab
for i = 2:100              %外层循环,i的初值是2,终值为100
    for j = 2:100          %内层循环,j的初值为2,终值为100
        if (~mod(i,j))     %i除以j取余后再取反
            break;         %跳出循环
        end
    end
    if(j > (i/j))
       fprintf('%d is prime \n',i); 
    end
    
end

2.奇数分频

奇数分频:分别写两个计数器和分频器(分别是上升沿计数、下降沿计数,上升沿分频、下降沿分频),分频器在(N-1)/2和N-1的时候翻转,最后再将两个分频器

三分频代码

module divtest(
 input clk,
 input rst_n,
 output clk_divider
);

reg [2:0] count_p;
reg [2:0] count_n;

reg clk_p;
reg clk_n;

parameter N = 3;

always@(posedge clk or negedge rst_n)begin
 if(!rst_n)
  count_p <= 3'b0;
 else if(count_p == N-1)
  count_p <= 3'b0;
 else
  count_p <= count_p + 1'b1;
 
end

always@(posedge clk or negedge rst_n)begin
 if(!rst_n)begin
  clk_p <= 1'b1;
 end
 else begin
  if(count_p == (N-1)/2) || (count_p == N-1)begin
  clk_p <= ~clk_p;
  end
 end
end

//下降沿计数
always@(negedge clk or negedge rst_n)begin
 if(!rst_n)
  count_n <= 1'b0;
 else if(count_n == N-1)
  count_n <= 1'b0;
 else
  count_n <= count_n + 1'b1;
end

//下降沿分频
always@(negedge clk or negedge rst_n)begin
 if(!rst_n)begin
  clk_n <= 1'b1;
 end
 else begin
  if(count_n == (N-1)/2 || count_n == N-1)begin
   clk_n = ~clk_n;
  end
 end

end

assign clk_divider = clk_n | clk_p;
endmodule

3.偶数分频

首先定义参数,N分频就是N,偶数分频的时候写一个N位的计数器,再写一个分频器,在N/2-1和N-1的时候翻转

八分频代码

//写一个八分频代码
module divider8(
 input clk,
 input rst_n,
 output dividerclk
);

reg [2:0] count_n;
parameter N = 8;
//写一个计数器
always@(posedge clk or negedge rst_n)begin
 if(!rst_n)
  count_n <= 1'b0;
 else if(count_n == N-1)
  count_n <= 1'b0;
 else
  count_n <= count_n + 1'b1;
end

//写一个分频器
always@(posedge clk or negedge rst_n)begin
 if(!rst_n)begin
  dividerclk <= 1'b1;
 end
 else if(count_n == N/2-1) | (count_n == N-1)begin
  dividerclk <= ~dividerclk;
 end
end

endmodule

4. 序列检测器,周期性的输出一个序列为0010110111

module xlgen(
 input clk,
 input rst_n,
 output Q
);

reg Q;
reg [9:0] Q_r;

always@(posedge clk or negedge rst_n)begin
 if(!rst_n)begin
  Q <= 1'b0;
  Q_r <= 10'b0010110111;
 end
 else begin
  Q <= Q_r[9];
  Q_r <= Q_r1<<1;
  Q_r[0] <= Q;
 
 end
end

endmodule

5.异步复位,同步释放

什么情况下,复位信号需要做"异步复位,同步释放"处理?

一般来说,同步系统,都使用异步复位。这是因为同步复位比异步复位的电路实现要浪费更多电路资源。

异步复位,同步释放原理图
ee71864b81440bf8ff5643ae76ccd4eb.png

//异步复位同步释放
module rstsync(
 input clk,
 input rst_async_n,
 output rst_sync_n
);
reg rst_s1;
reg rst_s2;

always@(posedge clk or negedge rst_async_n)begin
 if(!rst_async_n)begin
  rst_s1 <= 1'b0;
  rst_s2 <= 1'b0;
 end
 else begin
  rst_s1 <= 1'b1;
  rst_s2 <= reg_s1;
 end
  
end

assign rst_sync_n = rst_s2;

endmodule

6.用verilog实现1bit信号边沿检测功能,输出一个周期宽度的脉冲信号

重点在上升沿,下降沿,上升沿或下降沿

module EdgeDetect(
 input clk,
 input rst_n,
 input data,
 output pos_edge,
 output neg_edge,
 output data_edge
);

reg [1:0] data_r;
always@(posedge clk or negedge rst_n)begin
 if(rst_n == 1'b0)begin
  data_r <= 2'b00;
 end
 else begin
  data_r <= {data_r[0],data};
 end
end

assign pos_edge = ~data_r[1] & data_r[0];          //01
assign neg_edge = data_r[1] & ~data_r[0];          //10
assign data_edge = pos_edge | neg_edge;
endmodule

7. 描述带进位输入、输出的两位全加器

先写一位全加器,然后再写个顶层模块,调用两个一位全加器,其中第一个加法器的cout作为第二个加法器的cin

S = a^b^cin;
Cout = a&b|a&cin|b&cin;
//一位全加器
module fulladder(
 input a,
 input b,
 input cin,
 output s,
 output cout
);
 assign s = a^b^cin;
 assign cout = a&b|a&cin|b&cin;
endmodule
//2位全加器
module top(a,b,cin,s,cout);

 input [1:0] a;
 input [1:0] b;
 input cin;
 output [1:0] s;
 output cout;
 wire carry;
 //采用结构化描述的方式实现一个2位加法器
 fulladder m0(a[0],b[0],cin,s[0],carry);
 fulladder m1(a[1],b[1],carry,s[1],cout);
endmodule

8. verilog实现8位串行数据转并行数据

module deserialize(
 input rst,clk,din,
 output reg [7:0] dout
);

reg [7:0] data;
reg [2:0] cnt;

always@(posedge clk or negedge rst)begin
 if(rst == 1'b0)begin
  data <= 8'b0;
  cnt <= 3'b0;
  dout <= 8'b0;
 end
 else 
 begin
  if(cnt == 3'b111)
  begin
   dout[7-cnt] <= din;
   dout[7:1] <= data[7:1];
   cnt <= 3'b0;
  end
  else
  begin
  data[7-cnt] <= din;
  cnt <= cnt+1;
  end
 end
end
endmodule

9.跨时钟域(面试经常问!)

单bit(慢时钟域到快时钟域):用快时钟打两拍,直接采一拍大概率也是没问题的,两拍的主要目的是消除亚稳态;单bit(快时钟域到慢时钟域):握手、异步FIFO、异步双口RAM;快时钟域的信号脉宽较窄,慢时钟域不一定能采到,可以通过握手机制让窄脉冲展宽,慢时钟域采集到信号后再“告诉”快时钟域已经采集到信号,确保能采集到;多bit:异步FIFO、异步双口RAM、握手、格雷码;

9.1 慢时钟域信号到快时钟域信号,一般情况下都是打两拍(单比特传输)也叫两级寄存器同步

module clockdomain(
 input clk,
 input rst_n,
 input signal_in,
 output signal_out
);

reg [1:0] signal_r;

always@(posedge clk or negedge rst_n)begin
 if(!rst_n)begin
  signal_r <= 2'b00;
 end
 else begin
  signal_r <= {signal_r[0],signal_in};
 end
end
assign signal_out = signal_r[1];
endmodule 

9.2快时钟域到慢时钟域(clka慢时钟域,clkb快时钟域)

  • 将快的时钟域中的信号进行展宽
  • 对展宽的信号在慢的时钟域2级同步
  • 在慢时钟域中产生反馈信号来对快的展宽信号进行拉低处理

跨时钟域处理从快时钟域到慢时钟域,如果是下面第一个图,cklb则可以采样到signal_a_in,但是如果 只有单脉冲,如第二个图,则不能确保采样掉signal_a_in。这个时候用两级触发器同步是没有用的。

898b75be08b7364338df181c5d252a2f.png

module Sync_Pulse(
  input      clka,
  input      clkb,
  input      rst_n,
  input      pulse_ina,
  output      pulse_outb,
  output      signal_outb
);
//-------------------------------------------------------
reg       signal_a;
reg       signal_b;
reg   [1:0]  signal_b_r;
reg   [1:0]  signal_a_r;
//-------------------------------------------------------
//在clka下,生成展宽信号signal_a
always @(posedge clka or negedge rst_n)begin
  if(rst_n == 1'b0)begin
    signal_a <= 1'b0;
  end
  else if(pulse_ina == 1'b1)begin
    signal_a <= 1'b1;
  end
  else if(signal_a_r[1] == 1'b1)
    signal_a <= 1'b0;
  else
   signal_a <= signal_a;
end
//-------------------------------------------------------
//在clkb下同步signal_a
always @(posedge clkb or negedge rst_n)begin
  if(rst_n == 1'b0)begin
    signal_b <= 1'b0;
  end
  else begin
    signal_b <= signal_a;
  end
end
//-------------------------------------------------------
//在clkb下生成脉冲信号和输出信号
always @(posedge clkb or negedge rst_n)begin
  if(rst_n == 1'b0)begin
   signal_b_r <= 2'b00;
 end
 else begin
   signal_b_r <= {signal_b_r[0], signal_b};
  end
end
assign   pulse_outb = ~signal_b_r[1] & signal_b_r[0];
assign   signal_outb = signal_b_r[1];
//-------------------------------------------------------
//在clka下采集signal_b[1],生成signal_a_r[1]用于反馈拉低signal_a
always @(posedge clka or negedge rst_n)begin
  if(rst_n == 1'b0)begin
   signal_a_r <= 2'b00;
  end
 else begin
   signal_a_r <= {signal_a_r[0], signal_b_r[1]};
  end
end
endmodule

10.写一个同步FIFO,深度为16,数据位宽为8bit

module Syn_fifo
#(
  parameter  DATA_WIDTH  =  8,
  parameter  ADDR_WIDTH  =  4,
  parameter  RAM_DEPTH  =  (1 << ADDR_WIDTH)
)
(
  input      clk,
  input      rst_n,
  input  [DATA_WIDTH-1:0]   data_in,
  input      wr_en,
  input      rd_en,
  output reg  [DATA_WIDTH-1:0]   data_out,
  output      empty,      //fifo empty
  output      full       //fifo full
);
reg   [ADDR_WIDTH-1:0]   wr_cnt;
reg   [ADDR_WIDTH-1:0]   rd_cnt;
reg   [ADDR_WIDTH-1:0]   status_cnt;
reg   [DATA_WIDTH-1:0]   data_ram;
//-------------------------------------------------------
assign  full = (status_cnt == (RAM_DEPTH-1))? 1'b1: 1'b0;
assign  empty = (status_cnt == 0)? 1'b1: 1'b0;
//Syn
reg   rd_en_r;
always @(posedge clk or negedge rst_n)begin
  if(rst_n == 1'b0)begin
    rd_en_r <= 0;
  end
  else begin
    rd_en_r <= rd_en;
  end
end
//-------------------------------------------------------
always @(posedge clk or negedge rst_n)begin
  if(rst_n == 1'b0)begin
    wr_cnt <= 0;
  end
  else if(wr_cnt == RAM_DEPTH-1)
    wr_cnt <= 0;
  else if(wr_en)begin
    wr_cnt <= wr_cnt + 1'b1;
  end
  else
    wr_cnt <= wr_cnt;
end
always @(posedge clk or negedge rst_n)begin
  if(rst_n == 1'b0)begin
    rd_cnt <= 0;
  end
  else if(rd_cnt == RAM_DEPTH-1)
    rd_cnt <= 0;
  else if(rd_en)begin
    rd_cnt <= rd_cnt + 1'b1;
  end
  else
    rd_cnt <= rd_cnt;
end
always @(posedge clk or negedge rst_n)begin
  if(rst_n == 1'b0)begin
    data_out <= 0;
  end
  else if(rd_en_r)begin
    data_out <= data_ram;
  end
end
always @(posedge clk or negedge rst_n)begin
  if(rst_n == 1'b0)begin
    status_cnt <= 0;
  end
  else if(rd_en && !wr_en && (status_cnt != 0))begin
    status_cnt <= status_cnt - 1;
  end
  else if(wr_en && !rd_en && (status_cnt != RAM_DEPTH-1))
    status_cnt <= status_cnt + 1;
  else
    status_cnt <= status_cnt;
end
//-------------------------------------------------------
//Syn_Dual_Port_RAM
integer   i;
reg   [DATA_WIDTH-1:0]  register[RAM_DEPTH-1:0];
always @(posedge clk or negedge rst_n)begin
  if(rst_n == 1'b0)begin
    for(i = 0; i < RAM_DEPTH; i = i + 1)
      register[i] <= 0;
  end
  else if(wr_en == 1'b1)
    register[wr_cnt] <= data_in; 
end
always @(posedge clk or negedge rst_n)begin
  if(rst_n == 1'b0)begin
    data_ram <= 0;
  end
  else if(rd_en == 1'b1)
    data_ram <= register[rd_cnt];
  else
    data_ram <= data_ram;
end
endmodule

11.异步FIFO测试点和设计

  1. 同时读写,读写数据正确检查
  2. FIFO满标志位检查
  3. FIFO空标志位检查
  4. 写过程中发生写复位,写数据和FIFO满标志位被清空
  5. 读过程中发生读复位,读数据和读空标志位被清空
  6. 读写时钟相位相同,异步FIFO能正常工作
  7. 读写时钟相位不同,异步FIFO能正常工作
  8. 写时钟等与读时钟,异步FIFO能正常工作
  9. 写时钟快于读时钟,异步FIFO能正常工作
  10. 写时钟慢于读时种异步FIFO能正常工作
  11. 写过程中发生写复位以后,异步FIFO能继续正常工作
  12. 读过程中发生读复位以后,异步FIFO能继续正常工作
  13. FIFO满以后,继续往FIFO写数据,异步FIFO不会被卡死,数据被读走以后,异步步FIFO能继续正常工作。异步FIFO设计:
`timescale 1ns / 1ps
//
// Company: 
// Engineer: zhangsy
// 
// Create Date: 2019/08/05 11:58:12
// Design Name: 
// Module Name: asynfifo
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//


module asynfifo#(
         parameter data_depth = 4,
         parameter data_width = 4,
         parameter ptr_width  = 2
        )
        (
        
         input w_clk,r_clk,
         input write,read,
         input rst_n,
         input [data_width-1:0] data_in,
         output [data_width-1:0] data_out,
         output  valid,
         output full,
         output emty
        );
///
//name              enable
//write               H
//read                H
//rst_n               L
//valid               H
//full                H
//emty                H
/
reg [data_width-1:0]fifo [data_depth-1:0];
reg [ptr_width:0] r_ptr;
wire [ptr_width:0]r_ptr_g;
reg [ptr_width:0] w_ptr;
wire [ptr_width:0] w_ptr_g;
//****************update r_ptr************
always @ (posedge r_clk)
 if(!rst_n)
  r_ptr <= 2'd0;
 else if(read && !emty )
  r_ptr <= r_ptr + 1'b1;
 assign r_ptr_g = (r_ptr >>1) ^r_ptr;
//****************update w_ptr************
always @ (posedge w_clk)
 if(!rst_n)
  w_ptr <= 2'd0;
 else if(write && !full )
  w_ptr <= w_ptr + 1'b1; 
  
assign w_ptr_g = (w_ptr >>1) ^w_ptr;

//---------------------------------read-------------
//******************synchronization w_ptr***********
reg[ptr_width:0] w_reg1,w_reg2;
wire [ptr_width:0] w_gray;
reg [data_width-1:0] dout;
reg valid_r;
always @ (posedge r_clk)
 if(!rst_n)
  {w_reg1,w_reg2} <= 2'b00;
 else
  {w_reg1,w_reg2} <= {w_ptr,w_reg1};
  
always @(posedge r_clk)
 if(!rst_n)
  begin
   dout <= 'h0;
   valid_r <= 1'b0;
  end
 else if(read && !emty)
  begin
   dout <= fifo[r_ptr[ptr_width-1-:ptr_width]];
   valid_r <= 1'b1;
  end 
  else
  begin
   dout <= 'h0;
   valid_r <= 1'b0;
  end
//---------------------------------write-------------
//******************synchronization r_ptr***********
reg[ptr_width:0] r_reg1,r_reg2;
wire [ptr_width:0] r_gray;

always @ (posedge w_clk)
 if(!rst_n)
  {r_reg1,r_reg2} <= 2'b00;
 else
  {r_reg1,r_reg2} <= {r_ptr,r_reg1};

always @ (posedge w_clk)
  if(write && !full)
  begin
   fifo[w_ptr[ptr_width-1-:ptr_width]] <= data_in;
  end 

//---------------gray----------
assign r_gray = (r_reg2 >> 1) ^r_reg2;
assign w_gray = (w_reg2 >> 1) ^w_reg2; 

assign full =  ({~w_ptr_g[ptr_width:ptr_width-1],w_ptr_g[ptr_width-2:0]}== r_gray); 
assign emty =  (w_gray == r_ptr_g);
assign data_out = dout;
assign valid = valid_r;
endmodule

12.格雷码和二进制的转换

fcfa376b3c52fb07b053f247fdd454f9.png

97c869eb6d0e952e0a5d68273b7b61c6.png

12.1 二进制转格雷码

原数右移一位再与原数异或

module bin_to_gray(
 bin_in,
 gray_out
);

parameter data_width = 4;
input  [data_width-1:0] bin_in;
output [data_width-1:0] gray_out;

assign gray_out = (bin_in >> 1)^bin_in;

endmodule

12.2 格雷码转二进制

二进制最高位取格雷码最高位,然后循环做二进制高位与低一位的格雷码异或

module gray_to_bin(
 gray_in,
 bin_out
);

parameter data_width = 4;
input  [data_width-1:0] gray_in;
output [data_width-1:0] bin_out;
reg    [data_width-1:0] bin_out;

always@(gray_in)begin
 bin_out[3] = gray_in[3];
 bin_out[2] = gray_in[2]^gray_in[3];
 bin_out[1] = gray_in[1]^gray_in[2];
 bin_out[0] = gray_in[0]^gray_in[1];
end

endmodule

上边是酒酒自己敲得啦,如果有错误的地方请指正哈~

另外最近有很多小伙伴问:酒酒,我研究生,本科是专业,研究生是**,现在研二/研三,想备战明天春招、秋招可以嘛?例如:

c6c73513374b6b086c7bf9c12a8ac057.jpg

a39c0d61b1d0dbc3152300fbcec6ec87.jpg

主要分为三种情况:参加明年春招,秋招,以及现在才研一,甚至即将步入研究生生活的小伙伴~

  1. 参加明年春招的小伙伴
  • 离明年三四月份春招还剩5个月,如果可以的话,能签个保底的工作可以先签一下,我们你们春招万一没找着合适的,就啥也没了
  • 然后需要考虑一下毕业论文的准备情况,毕业要紧,如果毕业论文准备的差不多了,有充足的时间学习,那还是挺不错的
  • 由于春招的名额可能较少,可能竞争会激烈一些,所以你可以考虑清楚自己的优势在哪里,学校,专业,学习能力以及面试表现等,对自己有清晰的认知和定位,比如说以你的水平,能不能进一些大厂,万一进不了大厂,小公司是否能够接收,考虑清楚之后再决定,还有就是你为啥要转IC?当然这也是面试中经常会问的问题~
  1. 参加明年秋招的小伙伴
  • 你们的时间还比较充裕,还有一年,这一年中,只要你好好学,有充足的时间的话,问题应该都不太大,只不过明年的形势不好说,是否薪资还这么高我可不敢保证~
  • 很多人都问参加明年秋招转IC还来得及嘛,可能你是想听到一个肯定吧,我说不行,你也不会相信,但我感觉重点是你自己内心想不想转?有没有决心,行动力强不强,当然这跟个人因素有很大相关性的
  • 还有就是不要以为时间很长,不着急,有时候你会发现早学那么几个月真的不一样的,举个例子,我有个小伙伴去年12月就开始学验证了,他寒假也会学点儿,我记得我过年那会儿刚有想学验证的念头的时候,他都学到UVM的phase机制了,那会儿在寒假我也没啥动力,就看了看入门的几节课,好像连SV的前两章都没看完,开学后,人家做了做虚拟项目就去投实习了,拿了好几家offer,最后去了平头哥实习,而我当时学的很少都没敢投实习,就投了海康,结果性格测评还给挂了
  1. 目前研一甚至即将步入研究生的小伙伴
  • 你们还早,还有两三年呢,先做好当下的事情,提升自己的能力,丰富自己的简历
  • 不晓得三年后IC发展会怎么样,这很难说,感觉变化挺大的,三年前我们微电子专业的学生保研很少有人选本专业,好多都去做信号处理了,因此,可以先学点儿IC相关知识,也可以学学JAVA,你的选择有很多,不要太局限

原文链接: https://mp.weixin.qq.com/s/ynL4tFLdek8d6puEHTI2Qw
作者:酒酒

推荐阅读

更多IC设计技术干货请关注IC设计技术专栏。欢迎添加极术小姐姐微信(id:aijishu20)加入技术交流群,请备注研究方向。
4 阅读 780
推荐阅读
关注数
8914
内容数
839
主要交流IC以及SoC设计流程相关的技术和知识
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息