Написал примерчик на верилоге для начинающих - стековый калькулятор

Apr 23, 2012 20:59


Господа! Как вы знаете, я вместе с другими товарищами консультирую Антона Моисеева и Андрея Маклакова, сотрудников кафедры прикладной математики Нижегородского государственного технического университета ( http://www.nntu.ru ), которые создают экспериментальный курс основ проектирования электроники для программистов.

Журнал Антона Моисеева, специально созданный для освещения темы - http://1i7.livejournal.com
Мои посты по теме - http://panchul.livejournal.com/tag/nntu


Студенты Антона Моисеева уже поигрались с микросхемами малой степени интеграции, что в свое время вызвало негодование ЖЖ-юзера uzhas_sovka. Ужас наверное сам бы с удовольствием потыкал проводками в макетную плату, но после учебы в пижонском университете Чикаго uzhas_sovka боится, что его застанут за этим занятием нобелевские лауреаты по экономике и выдавят из своего круга, чисто из-за зависти.

После игрищ с микросхемами серии 4000, студенты Моисеева пересели на Xilinx FPGA, научились описывать простейшие комбинаторные цепи на Верилоге и познакомились с концепциями RS-триггера и D-триггера. Пора задизайнить что-нибудь полезное.

Фото справа - спутник, внутри которого стоит Xilinx FPGA, взято с http://www.eetimes.com/design/military-aerospace-design/4216480/High-performance-FPGAs-take-flight-in-microsatellites

По этому поводу я написал примерчик стекового калькулятора и имплементировал его в плате Digilent Basys 2 c Xilinx Spartan 3E. Примерчик иллюстрирует модульность, конечные автоматы, регистровые файлы в Верилоге, а также содержит всякие полезные штучки для начинающих пользователей FPGA - clock divider, button debouncer, 7-segment display driver.



Калькуратор работает по принципу микрокалькулятора МК-54, который помнят советские школьники физматшкол 1980-х. Имеется стек, в котором хранятся числа. 8-битные числа вводятся с помощью рычажков, которые ненавидит ЖЖ-юзер exler. Текущая верхушка стека светится как 16-битное шестнадцатеричное число. Имеются четыре круглые кнопочки - reset, enter, add (+), multiply (*).

reset все обнуляет
enter вводит 8-битное число с рычажков в верхушку стека
add (+) удаляет два числа из верхушки стека, складывает их и вставляет в стек результат
multiply (*) удаляет два числа из верхушки стека, умножает их и вставляет в стек результат


Видите рычажки на плате справа? Это те самые, которых боится Экслер. А FPGA на этой же плате - той же компании, что и FPGA в британском спутнике.

Имеются две версии калькулятора - одна (calculator) с более-менее правильным стилем, принятым у RTL-дизайнеров, другая (calculator_behavioral) - с стилем, которым хардверные люди как правило не пишут, но который более понятен софтверным людям - код выглядит как последовательная программа, описывающая поведение дизайна во время одного цикла синхросигнала.

Причин, по которым хардверные люди не пишут как в calculator_behavioral, несколько, в частности:

1. В таком коде трудно понять, какой из верилоговских регистров в процессе синтеза превратится в D-триггер, а какой просто становится проводом

2. В таком коде трудно понять, как оптимизировать тайминг

3. Такой стиль может порождать трудно-отлаживаемые ошибки из категории race conditions если человек, который его пишет не очень аккуратен с blocking и non-blocking assignmnets.

Поэтому хардвер-дизайнеры пишут как в calculator - так чтобы в частности в комбинаторном always-блоке не возникало никаких защелок (об этом сигнализирует synthesis tool), а все D-триггеры появлялись явной форме, как переменные, которым присваивается значение в небольшом блоке always @(posedge clock), который не содержит вычисления никаких формул.

Еще у чисто софтверных людей может возникнуть вопрос, почему я для стека сдвигаю весь массив, а не просто указатель на верхушку стека. Это, братцы, вся фишка хардвера - параллельный сдвиг не очень большого массива флип-флопов дешевле, чем гиганский мультиплексор для его индексации.

Мы на эти темы еще поговорим, а пока я выкладываю все файлы своего примерчика, сопровождая их сиськами Mirelle A по наводке kpt_flint для выработки положительных ассоциаций у всех читателей моего журнала мужского пола. А radulova может получать положительные ассоциации от созерцания моего юзерпика (она когда-то писала, но потом стерла, что я доставляю ей удовольствие).

При использовании нетривиального количества моего кода ссылка на меня (Юрий Панчул) обязательна. Понятно, что я не буду карать презрением за какой-то дурацкий дебаунсер на 10 строк (дебаунсеры уже давно общие, я его сам где-то спер), но меня не устроит, если какая-нибудь Дарья Донцова спионерит мой примерчик целиком, а потом ко мне будут прибегать идиоты и настаивать, что это я якобы спионерил у Дарьи Донцовой.

//-------------------------------------------------------------------------//
//
// calculator.v
//
//-------------------------------------------------------------------------//

module calculator
(
input clock,
input reset,
input enter,
input add,
input multiply,
input [ 7:0] data,
output [15:0] result,
output overflow,
output [ 3:0] error
);

assign error = 0;

reg [15:0] alu_a;
reg [15:0] alu_b;
reg alu_multiply;
wire [15:0] alu_result;
wire alu_overflow;

alu alu
(
.a ( alu_a ),
.b ( alu_b ),
.multiply ( alu_multiply ),
.result ( alu_result ),
.overflow ( alu_overflow )
);

reg r_overflow;
assign overflow = r_overflow;

always @(posedge clock)
begin
if (reset)
r_overflow <= 0;
else
r_overflow <= alu_overflow;
end

reg stack_push;
reg stack_pop;
reg [15:0] stack_write_data;
wire [15:0] stack_read_data;

stack stack
(
.clock ( clock ),
.reset ( reset ),
.push ( stack_push ),
.pop ( stack_pop ),
.write_data ( stack_write_data ),
.read_data ( stack_read_data )
);

assign result = stack_read_data;

reg [15:0] r_alu_a;
reg [15:0] r_alu_b;
reg r_alu_multiply;
reg [ 1:0] state;

reg [ 1:0] next_state;

always @(*)
begin
alu_a = r_alu_a;
alu_b = r_alu_b;
alu_multiply = r_alu_multiply;
stack_push = 0;
stack_pop = 0;
stack_write_data = data;
next_state = state;

case (state)
0:
if (enter)
begin
stack_push = 1;
stack_write_data = data;
end
else if (add | multiply)
begin
alu_a = stack_read_data;
alu_multiply = multiply;

stack_pop = 1;
next_state = 1;
end

1:
begin
alu_b = stack_read_data;
stack_pop = 1;
next_state = 2;
end

2:
begin
stack_push = 1;
stack_write_data = alu_result;
next_state = 0;
end

endcase
end

always @(posedge clock)
begin
if (reset)
begin
r_alu_a <= 0;
r_alu_b <= 0;
r_alu_multiply <= 0;
state <= 0;
end
else
begin
r_alu_a <= alu_a;
r_alu_b <= alu_b;
r_alu_multiply <= alu_multiply;
state <= next_state;
end
end

endmodule



//-------------------------------------------------------------------------//
//
// calculator_behavioral.v
//
//-------------------------------------------------------------------------//

`include "defines.vh"

module calculator_behavioral
(
input clock,
input reset,
input enter,
input add,
input multiply,
input [ 7:0] data,
output reg [15:0] result,
output reg overflow,
output reg [ 3:0] error
);

reg [`stack_pointer_size - 1:0] sp;
reg [15:0] stack [0 : `stack_size - 1];
reg empty;

reg [16:0] result_17;
reg [31:0] result_32;

integer i;

// This software-like style is not a good RTL style because:
//
// 1. It mixes blocking and non-blocking assignments
//
// 2. It is difficult to figure out whether a reg
// is going to become a flip-flop or a wire
//
// 3. It makes timing optimization difficult
//
// However this style of coding is easier to read for a software
// or a verification person

always @(posedge clock or posedge reset)
begin
if (reset)
begin
sp = 0;
empty = 1;

result <= 0;
overflow <= 0;
error <= 0;
end
else
begin
if (enter)
begin
if (sp == `stack_size - 1)
begin
error <= 1;
end
else
begin
if (! empty) // Вокруг плохой стиль для RTL - смешивается blocking и non-blocking,
sp = sp + 1; // неочевидно, какой регистр превратится в D-триггер,
// а какой нет; трудно оптимизировать тайминг.
empty = 0;

for (i = `stack_size - 1; i >= 1; i = i - 1)
stack [i] = stack [i - 1];

stack [0] = data;
end
end
else if (add)
begin
if (sp == 0)
begin
error <= 2;
end
else
begin
sp = sp - 1;

result_17 = stack [0] + stack [1];
stack [0] = result_17 [15:0];
overflow <= result_17 [16];

for (i = 1; i <= `stack_size - 2; i = i + 1)
stack [i] = stack [i + 1];
end
end
else if (multiply)
begin
if (sp == 0)
begin
error <= 3;
end
else
begin
sp = sp - 1;

result_32 = stack [0] * stack [1];
stack [0] = result_32 [15:0];
overflow <= | result_32 [31:16];

for (i = 1; i <= `stack_size - 2; i = i + 1)
stack [i] = stack [i + 1];
end
end

result <= empty ? 0 : stack [0];
end
end

endmodule

//-------------------------------------------------------------------------//
//
// defines.vh
//
//-------------------------------------------------------------------------//

`ifdef word_width
`else
`define word_width 16
`endif

`ifdef stack_size
`else
`define stack_size 4
`endif

`ifdef stack_pointer_size
`else
`define stack_pointer_size 2
`endif

//-------------------------------------------------------------------------//
//
// alu.v
//
//-------------------------------------------------------------------------//

module alu
(
input [15:0] a,
input [15:0] b,
input multiply,
output [15:0] result,
output overflow
);

wire [16:0] result_add = a + b;
wire [31:0] result_mul = a * b;

assign result = multiply ? result_mul [15: 0] : result_add [15:0];
assign overflow = multiply ? | result_mul [31:16] : result_add [16];

endmodule

//-------------------------------------------------------------------------//
//
// stack.v
//
//-------------------------------------------------------------------------//

`include "defines.vh"

module stack
(
input clock,
input reset,
input push,
input pop,

input [`word_width - 1:0] write_data,
output [`word_width - 1:0] read_data
);

reg [`word_width - 1:0] stack [0:`stack_size - 1];

assign read_data = stack [0];

integer i;

always @(posedge clock)
begin
if (reset)
begin
for (i = 0; i < `stack_size; i = i + 1)
stack [i] <= 0;
end
else if (push)
begin
for (i = 0; i < `stack_size - 1; i = i + 1)
stack [i + 1] <= stack [i];

stack [0] <= write_data;
end
else if (pop)
begin
for (i = 0; i < `stack_size - 1; i = i + 1)
stack [i] <= stack [i + 1];

stack [`stack_size - 1] <= 0;
end
end

endmodule



//-------------------------------------------------------------------------//
//
// chip.ucf
//
//-------------------------------------------------------------------------//

NET "mclk" LOC = "B8"; # Bank = 0, Signal name = MCLK
NET "mclk" CLOCK_DEDICATED_ROUTE = FALSE;

NET "mclk" PERIOD = 20 ns HIGH 50%;

#TIMESPEC TS01 = FROM : FFS : TO : FFS : 20 ns;
#TIMESPEC TS02 = FROM : RAMS : TO : FFS : 20 ns;
#TIMESPEC TS03 = FROM : FFS : TO : RAMS : 20 ns;
#TIMESPEC TS04 = FROM : RAMS : TO : RAMS : 20 ns;
#TIMESPEC TS05 = FROM : FFS : TO : PADS : 20 ns;
#TIMESPEC TS06 = FROM : PADS : TO : FFS : 20 ns;
#TIMESPEC TS07 = FROM : PADS : TO : RAMS : 20 ns;

# Pin assignment for DispCtl
# Connected to Basys2 onBoard 7seg display
NET "seg<0>" LOC = "L14"; # Bank = 1, Signal name = CA
NET "seg<1>" LOC = "H12"; # Bank = 1, Signal name = CB
NET "seg<2>" LOC = "N14"; # Bank = 1, Signal name = CC
NET "seg<3>" LOC = "N11"; # Bank = 2, Signal name = CD
NET "seg<4>" LOC = "P12"; # Bank = 2, Signal name = CE
NET "seg<5>" LOC = "L13"; # Bank = 1, Signal name = CF
NET "seg<6>" LOC = "M12"; # Bank = 1, Signal name = CG
NET "dp" LOC = "N13"; # Bank = 1, Signal name = DP

NET "an<3>" LOC = "K14"; # Bank = 1, Signal name = AN3
NET "an<2>" LOC = "M13"; # Bank = 1, Signal name = AN2
NET "an<1>" LOC = "J12"; # Bank = 1, Signal name = AN1
NET "an<0>" LOC = "F12"; # Bank = 1, Signal name = AN0

# Pin assignment for LEDs
NET "Led<7>" LOC = "G1" ; # Bank = 3, Signal name = LD7
NET "Led<6>" LOC = "P4" ; # Bank = 2, Signal name = LD6
NET "Led<5>" LOC = "N4" ; # Bank = 2, Signal name = LD5
NET "Led<4>" LOC = "N5" ; # Bank = 2, Signal name = LD4
NET "Led<3>" LOC = "P6" ; # Bank = 2, Signal name = LD3
NET "Led<2>" LOC = "P7" ; # Bank = 3, Signal name = LD2
NET "Led<1>" LOC = "M11" ; # Bank = 2, Signal name = LD1
NET "Led<0>" LOC = "M5" ; # Bank = 2, Signal name = LD0

# Pin assignment for SWs
NET "sw<7>" LOC = "N3"; # Bank = 2, Signal name = SW7
NET "sw<6>" LOC = "E2"; # Bank = 3, Signal name = SW6
NET "sw<5>" LOC = "F3"; # Bank = 3, Signal name = SW5
NET "sw<4>" LOC = "G3"; # Bank = 3, Signal name = SW4
NET "sw<3>" LOC = "B4"; # Bank = 3, Signal name = SW3
NET "sw<2>" LOC = "K3"; # Bank = 3, Signal name = SW2
NET "sw<1>" LOC = "L3"; # Bank = 3, Signal name = SW1
NET "sw<0>" LOC = "P11"; # Bank = 2, Signal name = SW0

NET "btn<3>" LOC = "A7"; # Bank = 1, Signal name = BTN3
NET "btn<2>" LOC = "M4"; # Bank = 0, Signal name = BTN2
NET "btn<1>" LOC = "C11"; # Bank = 2, Signal name = BTN1
NET "btn<0>" LOC = "G12"; # Bank = 0, Signal name = BTN0

//-------------------------------------------------------------------------//
//
// chip.v
//
//-------------------------------------------------------------------------//

module chip
(
input mclk,
output [6:0] seg,
output dp,
output [3:0] an,
output [7:0] Led,
input [7:0] sw,
input [3:0] btn
);

system system
(
.clock ( mclk ),
.switches ( sw ),
.buttons ( btn ),
.leds ( Led ),
.seven_segments ( seg ),
.dot ( dp ),
.anodes ( an )
);

endmodule

//-------------------------------------------------------------------------//
//
// system.v
//
//-------------------------------------------------------------------------//

module system
(
input clock,
input [7:0] switches,
input [3:0] buttons,
output reg [7:0] leds,
output [6:0] seven_segments,
output dot,
output [3:0] anodes
);

wire reset = buttons [3];
wire clock_for_debouncing;
wire clock_for_display;

clock_divider clock_divider
(
clock,
reset,
clock_for_debouncing,
clock_for_display
);

wire enter;
wire add;
wire multiply;

debouncer debouncer2
( clock, clock_for_debouncing, reset, buttons [2], enter );

debouncer debouncer1
( clock, clock_for_debouncing, reset, buttons [1], add );

debouncer debouncer0
( clock, clock_for_debouncing, reset, buttons [0], multiply );

wire [ 7:0] data;
wire [15:0] result;
wire overflow;
wire [ 3:0] error;

assign data = switches;

display display
(
clock_for_display,
reset,
result,
overflow,
error,
seven_segments,
dot,
anodes
);

calculator calculator
(
clock,
reset,
enter,
add,
multiply,
data,
result,
overflow,
error
);

always @(posedge clock_for_display)
leds <= reset ? 0 : switches;

endmodule

//-------------------------------------------------------------------------//
//
// clock_divider.v
//
//-------------------------------------------------------------------------//

module clock_divider
(
input clock,
input reset,
output clock_for_debouncing,
output clock_for_display
);

reg [19:0] counter;

always @(posedge clock)
begin
if (reset)
counter <= 0;
else
counter <= counter + 1;
end

assign clock_for_debouncing = counter [19];
assign clock_for_display = counter [15];

endmodule

//-------------------------------------------------------------------------//
//
// debouncer.v
//
//-------------------------------------------------------------------------//

module debouncer
(
input clock,
input clock_for_debouncing,
input reset,
input button,
output reg push
);

reg [2:0] samples;

always @(posedge clock_for_debouncing)
begin
if (reset)
samples <= 0;
else
samples <= { samples [1:0], button };
end

wire current = & samples;
reg previous;

always @(posedge clock)
begin
if (reset)
begin
previous <= 0;
push <= 0;
end
else
begin
previous <= current;
push <= { previous, current } == 2'b01;
end
end

endmodule

//-------------------------------------------------------------------------//
//
// display.v
//
//-------------------------------------------------------------------------//

module display
(
input clock,
input reset,
input [15:0] number,
input overflow,
input [ 3:0] error,

output reg [ 6:0] seven_segments,
output reg dot,
output reg [ 3:0] anodes
);

parameter [6:0] seg_E = 'b0000110;
parameter [6:0] seg_r = 'b0101111;

function [6:0] bcd_to_seg (input [3:0] bcd);

case (bcd)
'h0: bcd_to_seg = 'b1000000; // a b c d e f g
'h1: bcd_to_seg = 'b1111001;
'h2: bcd_to_seg = 'b0100100; // --a--
'h3: bcd_to_seg = 'b0110000; // | |
'h4: bcd_to_seg = 'b0011001; // f b
'h5: bcd_to_seg = 'b0010010; // | |
'h6: bcd_to_seg = 'b0000010; // --g--
'h7: bcd_to_seg = 'b1111000; // | |
'h8: bcd_to_seg = 'b0000000; // e c
'h9: bcd_to_seg = 'b0011000; // | |
'ha: bcd_to_seg = 'b0001000; // --d--
'hb: bcd_to_seg = 'b0000011;
'hc: bcd_to_seg = 'b1000110;
'hd: bcd_to_seg = 'b0100001;
'he: bcd_to_seg = 'b0000110;
'hf: bcd_to_seg = 'b0001110;
endcase

endfunction

reg [1:0] i;

always @(posedge clock)
begin
if (reset)
begin
i <= 0;

seven_segments <= bcd_to_seg (0);
dot <= ~ 0;
anodes <= ~ 'b1111;
end
else
begin
if (error != 0)
begin
case (i)
0 : seven_segments <= bcd_to_seg (error);
1, 2 : seven_segments <= seg_r;
3 : seven_segments <= seg_E;
endcase

dot <= ~ 0;
anodes <= ~ (1 << i);
end
else
begin
seven_segments <= bcd_to_seg (number [i * 4 +: 4]);
dot <= ~ (i == 0 ? overflow : 0);
anodes <= ~ (1 << i);
end

i <= i + 1;
end
end

endmodule

//-------------------------------------------------------------------------//
//
// testbench.v
//
//-------------------------------------------------------------------------//

module testbench;

reg clock;
reg reset;
reg enter;
reg add;
reg multiply;
reg [ 7:0] data;
wire [15:0] result;
wire overflow;
wire [ 3:0] error;

calculator calculator
(
clock,
reset,
enter,
add,
multiply,
data,
result,
overflow,
error
);

initial
begin
clock = 0;

forever
# 5 clock = ! clock;
end

//----------------------------------------------------------------

task dump;
begin

$write ("data=%h enter=%h add=%h multiply=%h",
data, enter, add, multiply);

$display (" result=%h overflow=%h error=%h",
result, overflow, error);

end
endtask

task t_reset;
begin

reset <= 1; repeat (10) @(posedge clock);

enter <= 0;
add <= 0;
multiply <= 0;
data <= 0;

reset <= 0; repeat (10) @(posedge clock);

$write ("After reset "); dump;

end
endtask

task t_enter (input [7:0] value);
begin

data <= value;
enter <= 1; @(posedge clock);
enter <= 0; repeat (10) @(posedge clock);

$write ("After enter %x ", value); dump;

end
endtask

task t_add;
begin

add <= 1; @(posedge clock);
add <= 0; repeat (10) @(posedge clock);

$write ("After add "); dump;

end
endtask

task t_multiply;
begin

multiply <= 1; @(posedge clock);
multiply <= 0; repeat (10) @(posedge clock);

$write ("After multiply "); dump;

end
endtask

//----------------------------------------------------------------

initial
begin
$display ("******** 2 3 * 4 5 * + ********");

t_reset;

t_enter (2);
t_enter (3);
t_multiply;
t_enter (4);
t_enter (5);
t_multiply;
t_add;

$display ("******** ff ff ff * * overflow ********");

t_reset;

t_enter ('hff);
t_enter ('hff);
t_enter ('hff);
t_multiply;
t_multiply;

$display ("******** ff 1 + ff * ff + 1 + overflow ********");

t_reset;

t_enter ('hff);
t_enter ('h01);
t_add;
t_enter ('hff);
t_multiply;
t_enter ('hff);
t_add;
t_enter ('h01);
t_add;

$display ("******** 1 2 3 4 5 * * * * * ********");

t_reset;

t_enter ('h01);
t_enter ('h02);
t_enter ('h03);
t_enter ('h04);
t_enter ('h05);
repeat (5) t_multiply;

$finish;
end

initial $dumpvars;

endmodule

Poll Примерчик на верилоге для начинающих - стековый калькулятор

UPD: ramlamyammambam подошел и высказал мнение, что в такие серьезные посты нельзя помещать женские груди. Я сказал, что грудями ЖЖ не испортишь. На том и разошлись. Думаю.

ljpromo, приди!

nntu, important

Previous post Next post
Up